9. 개발 반영 이력
확인 · 수정된 사항. 최근 작업이 위에 온다.
v1.158 ~ v1.177 · UXM-010~015 · 실적 기준 확정 (2026-09-17)
| 버전 | 내용 |
| v1.177 | 원장 반영 전 R+ 전환을 실적에 넣는다 (kevin 지정) — 고객Data 는 주 1회, Flow 요금변경은 실시간이라 며칠 어긋난다.
⚠ 실측 10건 중 9건은 이미 R+ 를 썼다가 해지 · 다운그레이드한 곳이었다. 「신청했다」 와 갈라야 한다 — 원장에 인사이트 흔적이 하나도 없는 곳만 본다 (도울 1건).
⚠ 09-17 작업 디렉터리가 통째로 사라졌다. v1.114~v1.176 의 빌드 패치 35개를 잃었다.
배포본(v1.174) 하나로 되살렸고, 이제부터는 조각 파일을 쌓지 않고 배포본을 바탕으로 직접 고친다 —
배포본 하나만 있으면 이어 갈 수 있는 구조가 안전하다 |
| v1.172 ~ v1.176 | 실적 기준을 확정했다 (kevin 확정). 다섯 판에 걸쳐 잣대를 하나씩 맞췄다.
v1.172 요금제 · 상태를 지금 것으로 — 요금제는 이용요금제, 상태는 원장 상태.
인사이트의 정지 · 해지는 접속을 막는 용도지 요금제 상태가 아니다 (실측 플러스 102 vs 인사이트정상 97).
⚠ 판정을 쓰는 세 곳을 함께 옮겼다 — 신규고객 표 · 목록 배지 · 이용 중 서비스.
v1.173 전환은 한 달을 넘긴 것만 — 월말 가입 뒤 며칠 만에 R+ 를 붙이는 일이 흔하다 (7 · 9 · 17 · 28일).
v1.174 세모R · 세모R+ · 전환을 각각 제 사건으로 —
⚠ v1.173 에서 「한 달 안이면 동시 가입」 으로 보고 세모R 실적에서 뺐다.
그러면 서온세무회계가 4월 세모R 에서 사라진다. 세모R 로 가입한 것은 사실이다.
한 달 규칙은 「전환이라 부를 것인가」 에만 쓴다.
joinAll 을 join + conv 에서 join + plus 로 바꿨다 —
전환은 plus 안에 이미 있어 더하면 두 번 센다.
v1.175 세모R 가입일을 재가입 우선으로 — 해지했다 다시 들어온 곳은 그때 계약이 새로 시작된 것이다.
재가입 이력 38곳. 화면의 가입일 칸(_joinedAt) 과 같은 잣대가 됐다.
v1.176 도움말에 실적 기준을 한 절로 정리.
원장 기준 실측 — 2026 세모R 92 · R+ 83 · 전환 53 · 합계 175 |
| v1.171 | 도움말에 반영 — 고객Data 날짜 칸 뜻, 요금제 · 가입일 규칙, v1.158~v1.170 이력 |
| v1.170 | 상품별 탭에 전환 열 추가 (kevin 지정). 열 수를 cols.length 로 가변화했다 — 종전에는 세 칸이 박혀 있어 열을 하나 더하면 분기 · 합계 · 상품소계 · 여백 · 전년을 전부 따로 고쳐야 했다.
⚠ 네 열을 더하면 안 된다. 전환은 R+ 안에 이미 들어 있고(joinAll = 가입 + 전환), 성장도 겹치는 열이다 |
| v1.169 | 신규고객 표를 헤더 회사 필터에 맞춘다 (kevin 지정) — v1.150 에서 「전체여도 내 회사만」 으로 좁혔던 것을 되돌린다.
그래서 성장 14건 중 영업채널1 소속 1건(리유세무회계) 이 로움 화면에서 빠져 「엑셀은 14인데 화면은 13」 이 되었다.
⚠ 볼 수 있는 범위는 RLS 가 정한다 — 화면이 권한을 넓히지는 않는다.
⚠ 목표 저장용 회사를 따로 둔다(_ncSaveCompany). company_id 가 NOT NULL 이라 「전체」 로 두고 목표를 고치면 저장이 통째로 실패한다 |
v1.168 SQL 014-A |
성장패키지 판정을 원장으로 (kevin 지정). 고객Data 엑셀이 99컬럼으로 늘었다 (프로모션 구분명 · 시작 연월 · 종료 연월).
종전에는 crm_inquiries.inquiry_promo 를 봤다 — Flow 제목에 「개업성장패키지」 가 든 건만 잡혀 나중에 전환한 고객을 놓쳤다 (실측 13 vs 14).
성장 가입일은 프로모션 시작연월이다. 가입일시는 최초 세모R 가입일이고 인사이트 가입일도 아니다 — R+ 로 쓰다 성장으로 옮겨 온 고객이 있다 (서울택스 : R+ 26-05 → 성장 26-08).
⚠ 판정을 쓰는 세 곳을 함께 옮겼다 — 신규고객 표 · 고객목록 배지(_promoBiz) · 정지해지 카드(_stageStats). 하나만 고치면 화면마다 「성장패키지」 가 다른 것을 가리킨다.
⚠ 성장만 플래그로 만들지 않는다. 프로모션이 열 가지다 (Special 180 · 무제한_10 64 …) — 구분명을 담아 두면 다른 프로모션을 볼 때 컬럼을 또 늘리지 않아도 된다.
⚠ 연월만 온다. _cvYM 을 새로 만들었다 — 기존 _cvD 는 일까지 있어야 값을 준다 |
| v1.167 | 문의 상세의 빈 칸을 다른 곳에서 채운다 (kevin 신고) — 세무회계 열린의 주소가 안 보였다. 원장에도 있고 옛 문의에도 있는데.
[세모R 플러스 신청] 같은 요금변경 · 추가신청 글에는 주소를 안 적는다. 가입 때 이미 받았으니까. 그런데 화면은 그 문의만 보므로 빈 칸이 된다.
차례 — ① 이 문의 → ② 원장 → ③ 같은 사업자의 다른 문의 중 최신. 원장을 앞에 두는 이유는 이전하면 엑셀로 갱신되지만 옛 문의 주소는 그대로 남기 때문이다.
⚠ 화면에서만 채운다. DB 에 적어 넣으면 「이 문의에 적혀 있던 것」 과 「다른 데서 가져온 것」 이 섞여 구분할 수 없다. 빌려 온 값에는 원장 · 이전 문의 표시를 붙인다 |
| v1.164 ~ v1.166 | 내 정보 (v1.164) — 이름 옆 메뉴에서 이름 · 닉네임 · 핸드폰을 고친다. 종전에는 관리자만 설정 화면에서 할 수 있었다.
⚠ 이메일은 잠근다. 로그인이 이메일로 사람을 찾는다 (ilike('email', 구글계정)) — 바꾸면 본인이 못 들어온다.
⚠ 닉네임은 담당자 저장값이라 참조까지 갱신해야 한다. _stSetNickname 을 그대로 쓴다 — 다시 짜면 참조 테이블이 늘 때 한쪽만 고치게 된다.
금주실적 (v1.166) — 전주 목 ~ 이번주 수. 월요일 시작 주의 수요일을 끝으로 잡는다. 실적 마감이 수요일이라 월~금 내내 같은 창을 본다.
누적에서 유입 · 가입 · 실패 · 해지 · 정지를 뺐다 — 위 표와 나란히 놓여 두 숫자를 견주게 만드는데, 위는 계약 · 여기는 고객 수라 견줄 수 없는 값이었다 |
| v1.159 ~ v1.165 | 활동은 적은 사람만 고친다 (kevin 지정). 남의 활동에는 수정 버튼이 안 보인다.
⚠ 주인이 없는 활동(created_by 가 비었거나 'flow') 은 누구나 고친다 — 잠그면 291건이 영영 굳는다. 「본인만」 은 남의 기록을 지키자는 뜻이지 아무도 못 고치게 하자는 뜻이 아니다.
⚠ created_by 에 무엇이 들어가는지가 자리마다 다르다 (v1.165) — 예약은 _myNickname() || 이메일 이다. 이메일하고만 견주었더니 닉네임이 있는 사람은 자기가 만든 예약도 못 고쳤다. 읽는 쪽에서 둘 다 받는다.
⚠ 화면에서 감추는 것이지 서버가 막는 것이 아니다. RLS 는 회사 단위다 |
| v1.158 ~ v1.163 | 신규고객 표 — 한 잣대를 여러 뜻에 돌려쓰던 것을 갈랐다.
v1.158 상품 딱지 둘 — prod(가입 시점) · pnow(현재). 현재 R+ 라는 이유로 옛 가입을 R+ 로 세어 24년에 R+ 37건이 있었다. R+ 는 25년 하반기에 시작했으니 있을 수 없는 숫자다.
v1.161 성장은 전환을 세지 않는다 — 프로모션이라 전환이라는 사건이 없다. joinAll 로 세어 올해 13 · 전 기간 12 가 되었다. 부분이 전체보다 컸다.
v1.162~163 세모R+ 는 자기 상태로 — 플러스만 내린 고객은 status='정상' 인데 insight_status='해지' 다. 원장 상태로 세어 「지금 안 쓰는 곳」 이 R+ 정상에 남았다 (117 vs 100여).
⚠ pnow 를 「가입한 적 있음」 으로 두었더니 정상 398 인데 세모R 280 + R+ 100 = 380 이었다 — 플러스를 내린 18건이 두 줄 어디에도 없었다 |
이 표에서 네 번 걸렸다 — 뿌리는 하나다
상품 · 가입 · 상태 · 날짜가 각각 두 가지 뜻을 갖는데 한 값으로 처리하려 했다.
① 상품 — 가입 시점 상품 vs 지금 쓰는 요금제 ② 가입 — 계약 수 vs 고객 수
③ 상태 — 세모R 상태 vs 인사이트 상태 ④ 날짜 — 최초 가입일 vs 그 상품의 시작일
열을 하나 더할 때 「이 칸의 뜻이 기존 칸과 같은 층인가」 를 먼저 묻는다. 다르면 값을 따로 담는다 —
나중에 맞추려 하면 이미 여러 화면이 그 값을 쓰고 있다 |
v1.141 ~ v1.157 · UXM-005~009 · 신규고객 표 (2026-09-07)
| 버전 | 내용 |
| v1.157 | 도움말에 오늘 작업을 반영 — 신규고객 표 절 신설, Flow 8절 다리 설명 보강 |
| v1.156 | D+ 를 「마지막 활동 후」 로 (kevin 지정) — 세무법인 아성 서초지점이 26.09.02 에 통화했는데 D+487 로 떴다. 리드 접수가 25.05.08 이고, 접촉을 다시 시작해도 새 리드를 만들지 않으니 접수일은 영영 그대로다.
⚠ 이 배지의 쓸모는 「오래 묵은 건 골라내기」 다. 묵었다는 것은 접수한 지 오래됐다가 아니라 손을 안 댄 지 오래됐다는 뜻이다.
_lastAnyAt 을 쓴다 — 활동이 없으면 유입(=접수) 이 나오므로 갈래를 따로 두지 않는다. 목록 · 칸반을 함께 고쳤다 (v1.136 과 같은 이유).
⚠ 온보딩은 그대로다. 거기 D+ 는 「가입 후 며칠」 이라 셋업 지연을 재는 값이다 |
v1.147 ~ v1.155 SQL 009-A |
STEP 신규고객 카드 → 월별 표 (kevin 지정). 셀렉트 하나로 가입실적 · 상품별 · 해지정지 · 목표대비 를 갈아 낀다.
월 · 분기 · 합계 · 상품 소계 · 전년 · 누적. 목표는 crm_targets(회사 · 연 · 월 · 상품) 에 담고 관리자가 표에서 바로 고친다.
⚠ 표가 전부 0 이던 일 (v1.149) — 첫 렌더가 데이터 적재보다 먼저 돌아 빈 결과가 캐시에 박혔다. 머리 숫자(_stageStats) 는 매번 다시 세므로 895 가 나와 「표만 0」 이었다. 「비어 있음」 과 「아직 모름」 을 안 가른 것이 원인이다.
⚠ 세모R+ 를 세모R 가입일로 세던 일 (v1.154 · kevin 지적) — 24년에 R+ 37건이 있었다. R+ 는 25년 하반기에 시작했으니 있을 수 없는 숫자다. 원장에 insight_joined_at 이 따로 있는데 안 쓰고 있었다.
⚠ 가입은 계약 기준이다 (v1.155 · kevin 지정) — 신규 가입 + 옛 고객의 R+ 전환. 그래야 상품 소계(48 + 82) 와 합계가 맞는다. 같은 달에 가입하며 R+ 를 산 곳은 전환이 아니다 — 두 번 세게 된다.
⚠ 기준 회사는 내 소속 회사다 (v1.150). 헤더가 「전체」 여도 표는 내 회사만 센다 — 목표가 회사별인데 실적만 전사면 달성율이 뜻을 잃는다. 표에 회사 배지를 적어 둔다 |
| v1.152 | 활성화 — 기본 정상만 · 정지는 별도 칸 (kevin 지정).
실측 : 전체 587 = 정상 397 + 정지 190. 위험 374 중 정지 190 이 통째로 들어 있었다. 쓰지 않는 고객이라 거래처 · 발송 · 인증서가 전부 미달인 것이 당연한데, 그래서 위험 목록의 절반이 손쓸 수 없는 건이었다.
위험 · 정상 판정을 status='정상' 로 한정하고(_riskable), 정지를 켜면 「정지」 칩이 따로 선다. 세부 칩(거래처 · 발송수 …) 도 같은 잣대다 — 칩 숫자는 정상만인데 목록에 정지가 섞이면 둘이 어긋난다 |
| v1.145 ~ v1.146 | 캘린더 동의 없이 예약이 저장되던 일 (kevin 확인).
김선민이 잡은 예약 4건이 전부 캘린더에 없었다. 등록률 08-24 주 100% → 08-31 주 20% → 09-07 주 0%. 화면에 아무 흔적이 없어 열흘 동안 몰랐다.
⚠ 관문(_gcalRequireScope) 첫 줄이 if (!_gcalOn()) return true 였다. _gcalOn 에 _gcalAllowed() 가 들어 있어, gcal_allow 목록에 없는 사람은 「나와 상관없는 사람」 으로 보고 그냥 통과시켰다 — 못 올리는 사람이 오히려 자유롭게 저장하는 뒤집힌 결과였다.
「올릴 것인가(_gcalOn)」 와 「막을 것인가(_gcalGateReason)」 를 갈랐다. 권한 확인이 실패했을 때(null) 는 막지 않는다 — 통신이 흔들린다고 현장에서 예약을 못 잡으면 안 된다.
gcal_allow 를 * 로 열었다. 예약 시각도 셀렉트로 통일했다 (v1.146) — 추가 폼은 v0.622 에 이미 바꿨는데 수정 폼만 남아 있었다 |
| v1.144 | 고객 검색이 일부를 못 찾던 일 (kevin 신고) — 「세무법인하누리」 가 안 나왔다. 구멍 둘.
① 원장은 이용기관명, 문의는 Flow 제목이라 표기가 다르다. 원장에 있는 곳은 보강 경로를 안 타므로 원장 이름과 다르게 치면 어디서도 못 찾았다 → 원장 행을 검색할 때 문의 쪽 이름도 함께 본다.
② if (!i.biz_no) return 이 사업자번호 없는 문의를 통째로 걸렀다 → 번호가 없으면 문의 id 로 묶는다 |
v1.141 ~ v1.143 SQL 005-A·B |
UXM 키워드에 발송 실적 반영 — 엑셀 95건(2024-07 ~ 2026-07 · 발송 365,001 · 클릭 12,895). 제목 일치 82 · 신규 13. send_count · click_count · sent_at 추가, 화면에 발송 · 클릭 · 클릭률 칸.
⚠ 클릭률순에서 발송 0건은 맨 뒤로 보낸다. 0 으로 두면 「최악의 성과」 로 줄 서는데, 성과가 나쁜 것이 아니라 잰 적이 없는 것이다.
⚠ 열 순서가 어긋났던 일 (v1.142) — 머리글은 「상세/링크」 뒤에, 본문은 앞에 넣어 통째로 한 칸씩 밀렸다. table-layout:fixed 라 칸이 스스로 늘지도 않는다. 머리글 · 본문 · colgroup 셋을 함께 세야 한다.
STEP 카드를 일정 빠른 순으로 (v1.143) — 마친 예약은 올리지 않고, 지난 예약은 올린다 (놓친 약속이 먼저 보여야 한다) |
오늘 배운 것 — 같은 함정을 다시 밟지 않으려고 적는다
① btrim(x) 는 공백만 지운다. 줄바꿈 · 탭은 남는다. 사람이 붙여넣은 값, 엑셀 · Flow 에서 온 값에는 끝에
이 흔히 붙는다 — 눈으로는 같은데 코드에서만 다르다. 하루에 두 번 걸렸다 (키워드 매칭 · 본문 판정). → btrim(x, E'
')
② LIKE 패턴의 백슬래시는 이스케이프다. '%"CONTENTS":"-
"%' 의
이 줄바꿈이 아니라 글자 n 으로 읽혀 늘 0건이었다. → JSON 안을 LIKE 로 뒤지지 말고 값을 꺼내 비교한다
③ 화면 렌더가 데이터 적재보다 먼저 돌 수 있다. 결과를 캐시하려면 「비어 있음」 과 「아직 안 실림」 을 반드시 갈라야 한다 — 안 그러면 첫 렌더의 빈 값이 굳어 영영 0 으로 남는다
④ 버전 문자열을 전역 치환하지 않는다. 주석 · 도움말이 함께 끌려 올라가 기록이 조용히 거짓이 된다 (실측 53곳). 빌드 스크립트의 NEWV 한 줄만 고친다
⑤ create or replace view 는 컬럼을 끼워 넣거나 순서를 바꾸지 못한다. 정의가 바뀌면 drop 부터 한다 |
v1.114 ~ v1.140 · UXM-002~004 · 활동이력 정비 (2026-09-04)
| 버전 | 내용 |
| v1.140 | 도움말에 v1.136 ~ v1.139 반영 |
| v1.139 | 고객 뷰 정렬도 대체값을 쓴다 — 같은 「활동이력」 칸인데 고객 뷰만 대체 없이 _lastAnyAt 을 써서, 활동 · 상담 · 문의가 하나도 없는 원장 전용 고객(옛 엑셀 유입분) 이 통째로 맨 아래로 밀렸다.
이제 다섯 뷰가 같다 : 활동 · 상담 · 유입 중 가장 나중 것 → 없으면 가입일 → 없으면 접수일.
⚠ 같은 이름의 칸이 화면마다 다르게 정렬되면 나중에 「왜 여기만 순서가 이상한가」 를 처음부터 다시 찾게 된다 |
| v1.138 | 리드 · 영업 컬럼 폭 — 활동이력 168 → 240, 유입코드 96 → 84 (kevin 지정).
활동이력은 날짜 + 종류 + 내용이 한 칸에 들어가 168px 에서는 늘 잘렸다. 유입코드는 가장 긴 값이 TAX_SEMO_HOME(13자) 이고 대부분 비어 있었다.
⚠ 늘린 만큼 줄여 표 전체 폭을 지킨다 — 한쪽만 늘리면 오른쪽 칸이 화면 밖으로 밀린다 |
| v1.137 | 유입(문의) 을 전 뷰의 활동으로 본다 (kevin 지정) — v1.133 에서 「리드 · 영업은 행 자체가 문의라 제 얘기를 두 번 한다」 며 뺐던 것을 되돌린다.
실제 화면(세무법인 명품) 에서 문의 1건이 있는데도 「활동 없음」 만 떴다 — 그 칸이 「아무 일도 없었다」 로 읽힌다. 무엇이 들어왔는지라도 적히는 편이 낫다.
리드 컬럼 — 문의내용을 빼고 활동이력을 유입코드 앞에 둔다. 문의내용은 한 줄로 잘려 ■ 신… 밖에 안 보였다. 자리만 쓰고 읽히지 않는다. ⚠ 지운 것이 아니라 기본에서 뺀 것이다.
리드 정렬 — 활동 최신순. 유입을 활동으로 보므로 새 리드는 제 접수일이 그 자리에 서서 맨 위로 온다 — 「신규가 위에」 가 저절로 된다.
⚠ 정렬 대체값에 접수일을 더했다. 리드에는 가입일이 없어 joined_at 만 보면 새 리드가 빈 값으로 맨 아래로 밀린다 — 하려던 것과 정반대가 된다.
⚠ 상세는 그대로 둔다 (v1.048 규칙) — 거기서는 문의가 「리드」 탭에 따로 서 있다 |
| v1.136 | 온보딩 D+ 가 옛 가입일을 보던 문제 (kevin 신고) — 세무법인울림 북부지점이 26.08.25 에 세모R+ 로 요금변경했는데 D+939 로 떴다. 939일 전은 2024-02-07, 그 사무소의 세모R 가입일이다.
_ddayBase · _kbBadge 가 cus.joined_at 을 그대로 읽었다. 세모R 만 쓰던 시절에는 그것이 유일한 가입일이라 맞았다. 지금은 세모R+ 로 올라온 건의 온보딩이 따로 돌고, 그 셋업의 기준은 insight_joined_at 이다.
⚠ 새 규칙을 만들지 않았다 — _joinedAt() 이 이미 그 갈래를 안다. 가입일 칸 · 상세 가입일자가 그것을 쓰는데 D+ 만 다른 값을 봐서 같은 화면 안에서 「가입일 26.08.25 인데 D+939」 였다.
⚠ 목록과 칸반 두 군데를 함께 고쳤다. 한쪽만 고치면 같은 건이 칸반 D+939 · 목록 D+10 이 되어 어느 쪽이 맞는지 알 수 없다.
세모R 로 오래 쓰다 세모R+ 로 전환한 곳이 전부 같은 문제였다 — 지연 경고(14일) 가 신호 역할을 못 하고 있었다 |
| v1.135 | 도움말에 오늘 작업을 반영 — UXM 절 신설 (화면 구성 · Flow 연동), 원장대기 규칙 갱신 |
v1.134 SQL 004-B·C·D |
Flow [신규가입] 을 고객원장에 진짜 행으로 넣는다 (kevin 지정) — v1.106 의 화면 합성 방식을 되돌린다.
왜 — 원장에 없으니 가입일자가 없어 정렬에서 통째로 밀렸다. 온보딩을 활동 최신순(v1.133) 으로 바꾸면 그 건들이 화면 밖으로 나간다. 그런데 막 접수된 건이야말로 먼저 손댈 건이다.
어떻게 — utlz_id = 'PEND-{사업자번호}' 로 넣는다. NULL 로 두면 onConflict 가 안 걸려 새로고침할 때마다 행이 늘고, 진짜 utlz_id 는 UTLZ_2409191075380 꼴이라 겹치지 않는다.
가입일 = 접수일. 세모R+ 신청은 insight_joined_at 도 채운다 — _joinedAt() 이 「세모R+ 문의면 insight_joined_at 을 본다」 로 갈라 읽어, 여기를 비우면 목록 가입일이 그대로 빈다.
⚠ 반대로 _joinedAt 을 insight_joined_at || joined_at 으로 느슨하게 고치면 안 된다 — 세모R 만 쓰는 진짜 고객 수백 건이 「세모R+ 가입일이 있다」 로 잡혀 실패 판정이 뒤집힌다 (v0.328 · 실측 125건 전례).
⚠ 화면 합성(_pendJoinRows · _pendRow) 을 뗐다. 안 떼면 같은 곳이 두 줄로 서고 상태바도 두 번 세어 「396 을 눌렀는데 397줄」 이 된다. 흐린 점 표시는 남기되 판정을 utlz_id 로 바꿨다 |
| v1.133 | 온보딩 정렬을 활동 최신순으로 (kevin 지정) — v1.064 의 「셋업은 가입 순서대로 밟는 일이라 가입일순이 곧 처리 순서」 를 되돌린다. 가입일순은 한번 정해지면 안 바뀌어 매일 같은 줄만 위에 선다.
⚠ 활동이 없으면 가입일을 그 자리에 놓고 정렬한다 — 종전에는 빈 값이라 통째로 맨 아래로 밀렸다. 화면에는 그대로 「활동 없음」 이라 적는다. 없는 일을 있는 것처럼 쓰지 않는다.
목록 활동이력에 문의(리드) 도 함께 본다 — 고객이 새로 신청 · 요금변경을 넣은 것도 그 사이에 생긴 변동이다. 온보딩 · 활성화 · 고객만. 리드 · 영업은 행 자체가 문의라 넣으면 그 줄이 제 얘기를 한 번 더 한다.
⚠ 상세는 건드리지 않는다 (v1.048 규칙) — 거기서는 문의가 「리드」 탭에 따로 서 있다.
⚠ 칸과 정렬이 같은 뷰 목록을 보게 함께 고쳤다. 한쪽만 고치면 「위에 있는데 날짜가 더 옛것」 이 된다 |
| v1.132 | 리드 상세의 등록일 → 등록일시 (kevin 지정) — 값은 v0.880 부터 분까지 있었는데 slice(0,10) 으로 날짜만 보였다. 같은 날 두 번 들어온 건을 화면에서 구분할 수 없었다.
⚠ 옛 이관분은 시각이 없다 — 00:00:00 이면 날짜만 적는다. 실제 자정 등록은 놓치지만 없는 시각을 지어내는 것보다 낫다.
⚠ 값은 한국시간을 +00:00 으로 담은 것이다 (v0.622 관례). Date 로 파싱해 다시 포맷하면 9시간이 밀린다 — 문자열에서 자른다 |
| v1.131 | 소스 URL 점검을 서버에서 (Edge Function url-check) — 브라우저 fetch 는 상대 사이트가 Access-Control-Allow-Origin 을 줄 때만 응답을 읽는다. 네이버 블로그 · 국세청은 주지 않아 살아 있는 사이트도 전부 「CORS 차단으로 확인 불가」 로 적혔다.
서버끼리는 CORS 가 없다 — 실제 상태 코드를 그대로 본다. ✅ 정상 (HTTP 200 · 412ms) · ⚠️ 오류 응답 (HTTP 404) · ⚠️ 응답 없음 (8초 초과).
⚠ User-Agent 를 브라우저처럼 보낸다 — 없으면 봇으로 보고 403 을 주는 곳이 많아 살아 있는데 죽었다고 적힌다.
⚠ 전체 확인은 한 번에 보낸다 — 한 건씩 돌면 느린 소스 하나가 8초를 다 써서 그 뒤가 전부 밀린다 |
v1.128 SQL 003-B |
소재작성 — 저장됨을 다시 누르면 취소, 👍 선호 표현 추가 (kevin 지정).
⚠ 취소는 상태를 추천 으로 되돌린다. 보류 같은 새 값을 만들지 않는다 — 신규 추천을 지울 때 status 로 걸러 내는 곳이 있어, 여기서만 아는 값을 쓰면 그 판단에서 조용히 빠진다.
⚠ saveMaterialDB 가 저장 직후 버튼을 글씨(span) 로 바꾸고 있었다 — 방금 저장한 것만 취소를 못 눌렀다. 버튼으로 바꿨다.
👍 는 AI 글작성 프롬프트에 같은 타겟 최신 8건을 예시로 싣는다. 많이 넣으면 그것을 베낀다 — 결만 옮기면 된다. 타겟이 다르면 말투가 다르므로 섞지 않는다 |
v1.125 SQL 002-B |
UXM DB 를 roumitBP 로 통합 — SEMO_MKT 프로젝트(mdkbcz…) 의 8개 테이블을 uxm_ 접두어로 옮기고 uxm_material_contents 를 더했다 (총 9개 · 325건).
접두어를 붙인 이유 — BP 에 테이블이 90개다. sources · keywords · schedules 를 그냥 두면 반년 뒤에 어느 기능 것인지 이름만으로 모른다.
RLS 는 crm_* 와 같다 : is_loum() or company_id = my_company_id(). anon 권한 없음 — 화면에서 가리는 게 아니라 DB 가 막는다.
⚠ 이식 코드에 있던 SEMO_MKT 설정 화면 함수 24개를 들어냈다. employees · improvement_* · audit_log · activity_log 를 찌르는데 그 이름이 roumitBP 에 실제로 있다. anon 으로 남의 DB 를 보던 동안엔 닿지 않는 죽은 코드였지만, sb 로 바꾸면 BP 직원 명부를 건드리는 코드가 된다.
⚠ uxmInit() 이 window._currentUserEmail 을 덮어쓰고 있었다 — CRM 은 이 값을 소문자로 넣어 두고 _isMaster() 가 소문자 비교로 읽는다. 대문자가 섞인 값으로 덮이면 마스터 판정이 조용히 깨진다 |
| v1.114 ~ v1.130 | UXM 메뉴 신설 — SEMO_MKT v1.139 의 「소재 관리」 를 이식했다. 상단 바 현황 · 소재작성 · 컨텐츠 + 오른쪽 끝 자동수집.
로움 직원에게만 보인다 (_isLoumCompany()) — 소재 · 소스 · 키워드는 로움 내부 자산이다. 나중에 열려면 uxmAllowed() 한 군데만 고치면 된다.
현황 — 한 줄 = 소재 하나, 채널 9개를 가로 컬럼으로 펼치고 칸마다 클릭 (가입). 종전에는 소재 × 채널마다 한 줄이라 같은 소재가 흩어져 「어느 채널에서 잘 들어왔나」 를 볼 수 없었다.
⚠ 미배포는 –, 배포했는데 결과 없음은 0 (0). 같은 모양으로 두면 「안 보낸 것」 과 「보냈는데 반응이 없는 것」 이 섞인다.
컨텐츠 — 소재 하나에 채널별로 글을 쓴다. 저장 단위가 (소재 × 채널) 인 이유는 문자 900byte 와 블로그 2,000자를 한 본문으로 돌려쓸 수 없어서다. 현황 표의 칸 하나와 같은 단위다.
⚠ 「배포」 는 발송을 실행하지 않는다 — 사람이 올린 뒤 그 사실과 발행 URL 을 남기는 기록이다. 실제 발송과 클릭 · 가입 집계는 추적 링크가 붙어야 한다 (미결) |
v1.085 ~ v1.097 · SQL 427~435 (2026-08-27 저녁)
| 버전 | 내용 |
| v1.113 | 로움 · 마스터는 「회사」(전체) 로 시작 (kevin 지정) — v0.620 「자기 소속으로 시작」 을 되돌린다. 왜 — 로움 직원은 로움 필터가 걸려도 전 회사 고객이 보였다. 「로움 (1708)」 이라 적힌 채 다른 회사 고객이 섞여 나와 숫자와 내용이 어긋났다. 「회사」 를 기본으로 두면 그 어긋남이 사라지고, 특정 채널만 보려 할 때 필터가 제 역할을 한다. 다른 회사 직원은 종전대로 자기 회사에 고정된다. ⚠ 회사 이니셜 배지(v1.107) 가 여기서 함께 산다 — 「전체」 일 때만 W · 1 · 2 가 붙어 남의 회사 고객이 한눈에 갈린다 |
| v1.112 | 개선의견 창에 다른 설정 화면이 겹쳐 그려지던 것 수정 (kevin 지적) — 제목은 「개선 의견」, 탭줄은 감춰진 채 내용만 유입코드 로 바뀌었다. 원인 — openFeedbackOnly 가 제목 · 탭줄 · fb-only 를 바꾸는데 되돌리는 곳이 closeSettings 뿐이었다. 창을 닫지 않고 알림의 「미등록 유입코드」 를 누르면 그 흔적이 그대로 남는다. ⚠ 켜는 쪽과 끄는 쪽이 다른 함수면 순서가 어긋날 때 흔적이 남는다. _stExitFeedbackOnly() 로 모으고 여는 자리에서 스스로 제 상태를 세우게 했다 |
| v1.111 | 알림 아이콘의 단계 넷을 글자 원으로 (kevin 지정) — 유 · 영 · 온 · 활 · 기. 색만으로는 「파랑이 영업이었나 온보딩이었나」 를 매번 떠올려야 했다. ⚠ 색은 GRP_COLOR 를 그대로 쓴다. 목록 · 칸반 · 배지가 이미 그 색이라 여기만 다른 색을 쓰면 같은 단계가 화면마다 다른 색이 된다. ⚠ 나머지(⚠️ 판정보류 · 🏷️ 미등록코드 · ⛔ 연동오류) 는 그대로 둔다 — 뜻이 이미 분명하고, 단계가 아니라 사건이다 |
| v1.110 | 「중복제외」 를 기본값으로 (kevin 지정) — 같은 사무소가 리드 유형마다 여러 줄로 서서 직원들이 혼동했다. 고객 · 유입 · 영업 · 온보딩 · 활성화 모두 사업자당 최신 1건만 선다. 세그먼트([전체 | 중복제외]) 는 그대로 둔다 — 여러 건을 봐야 할 때가 있다. 기본만 바꾼다. ⚠ 이미 쓰던 사람은 localStorage 에 '0' 이 남아 기본값을 바꿔도 안 바뀐다. crm.dedup.seed2 로 한 번만 밀어 준다 — 그 뒤로는 각자 고른 값이 이긴다 |
| v1.109 | 로그인에서 캘린더 범위를 뗀다 (kevin 지정) — v0.820 을 되돌린다. 왜 — 로그인하는 세 길에 모두 calendar.events 가 붙어 있어 로그인만 하려는 사람도 캘린더 권한을 요구받았다. Google 테스트 명단 밖 계정은 403 access_denied 로 문 앞에서 막혀 승인대기에 오지도 못했다 (실측 wondef678@gmail.com). 이제 로그인은 email · profile 만 쓴다 — 누구나 들어와 승인대기에 선다. 캘린더는 로움만 — _gcalEnsureScope 가 접속 때 묻되 _isLoumCompany() 일 때만. 외부 사람에게 물으면 동의 화면에서 막혀 「로그인은 됐는데 갑자기 차단」 이 된다. ⚠ 예약 저장 관문(_gcalRequireScope · v0.822) 은 그대로다. 로움이 「나중에」 를 골라도 예약만은 동의를 받는다 |
| v1.108 | 원장대기를 「정상」 에 넣는다 (kevin 지정) — 가입한 곳이니 정상이 맞다. 원장이 늦다는 것은 시스템 쪽 사정이지 쓰는 사람이 가릴 일이 아니다. 「정상 396」 도 397 로 함께 센다. 목록에 서는데 숫자에 안 들어가면 「396 을 눌렀는데 397줄」 이 된다 — v0.556 에서 「전체 890 인데 목록은 0건」 으로 겪은 그 문제다. ⚠ 대신 이름 앞에 흐린 점을 남긴다. 사용현황 · 요금 칸이 빈 이유는 보여야 「데이터가 깨졌다」 는 오해가 없다. ⚠ 원장대기는 사무소도 없을 수 있어 회사 이니셜을 문의 로 판정한다 — 안 그러면 배지가 안 붙는다 |
| v1.107 | 사무소명 앞에 회사 이니셜 (kevin 지정) — W · 1 · 2 를 파란 배지로. 대부분이 로움이라 로움은 안 그린다 — 눈에 띄는 것만 남긴다. 회사 필터가 「전체」 일 때만 붙는다. 자기 회사만 보는 중에는 줄마다 같은 글자가 서서 읽기만 방해한다. 고객 · 유입 · 영업 · 온보딩 · 활성화에 함께 붙는다 — 한 곳만 하면 「여기는 왜 안 보이지」 가 난다. ⚠ 이니셜을 코드에 박지 않는다. crm_companies.initial 값이다 (SQL 444) — 회사가 늘어도 설정에서 값만 넣는다. ⚠ 로움도 R 을 담아 둔다. 감추는 것은 화면의 판단이라 나중에 보이려면 조건만 푼다. ⚠ 이름 앞에 둔다. 뒤에 붙이면 이름 길이에 따라 자리가 달라 줄이 안 맞는다 (v0.789 와 같은 판단) |
| v1.106 | 고객 목록에 「원장대기」 를 함께 보여준다 (kevin 지정) — Flow 에 [신규가입] 이 들어와도 원장은 고객 Data 엑셀로만 채워져 그 사이 며칠 동안 고객 화면에서 아예 안 보였다. ⚠ 원장에 임시 행을 넣지 않는다. 903건이 모두 utlz_id 를 갖는 규칙이 깨지고, 한 사업자가 utlz_id 를 여럿 갖는 곳(실측 13건 · 지점 · 재가입) 이 한 줄로 뭉개진다. 화면에서만 합친다 — 엑셀이 오면 저절로 사라진다. ⚠ 실패 · 테스트는 뺀다. 실측 14건 중 9건이 가입 실패였다. ⚠ 없는 값은 비운다. 0 을 넣으면 「거래처 0곳」 이 되어 평균을 끌어내린다. 상태 집계(정상 · 해지 · 정지) 에도 안 넣는다 — 상태가 없는 곳이다. 「원장대기 N」 칩으로 따로 센다 |
| v1.105 | 유입코드를 등록하면 회사도 함께 옮긴다 (kevin 지정) — 종전에는 inflow_code_id 만 이어서 등록해도 실적에 안 잡혔다 (실측 리유세무회계 · A001 → 영업채널1 인데 문의는 로움에 남음). 미등록 코드로 들어온 문의는 회사가 정해진 적이 없다 — flow_apply_new() 가 모르는 코드라 기본회사로 떨어뜨린 것이라 사람이 고른 값이 아니다. ⚠ 사람이 손으로 회사를 바꾼 건은 건드리지 않는다 (crm_field_edits). 토스트에 「N건 연결 · M건 회사 이동」 으로 무엇이 움직였는지 적는다 |
| v1.104 | 좌측 메뉴를 누르면 설정이 닫힌다 (kevin 지정). 설정은 띄우는 창이 아니라 콘텐츠 영역에 꽉 차게 붙는다 (v0.707) — 바깥 여백이 없어 「바깥을 눌러 닫기」 가 걸릴 자리가 없었다. stBack 에 event.target===this 가 붙어 있지만 닿을 곳이 없다. 캘린더와 같은 성격이라 setView 의 같은 자리에서 닫는다. ⚠ moved 로 감싸지 않는다 — 같은 메뉴를 다시 누르는 것은 「설정을 치우려고」 누르는 경우다 |
| v1.103 | 연동 오류를 한 번 읽으면 다시 알리지 않는다 (kevin 지정). v1.099 는 id 에 시 단위 시각을 넣어 한 시간마다 되살렸다 — 「안 고쳐진 오류가 읽음으로 사라지면 안 된다」 는 판단이었는데, 써 보니 성가신 쪽이 컸다. 이제 sys:{code} 로 고정한다. 배지 · 미확인에서는 빠지되 「⛔ 오류」 탭에는 남는다 — 사라지는 것은 알림이지 오류가 아니다. 탭 숫자는 안 읽은 것만 센다 |
| v1.102 | 상담 메뉴에서 카드를 누르면 늘 펼쳐서 열린다 (kevin 지적) — 첫 건만 펼쳐지고 그 뒤로는 닫힌 채 열려 한 번 더 눌러야 했다. 원인 — idpSetTab 이 사업자가 바뀔 때 _vocOpen 을 비우는데, 그 비움이 방금 정한 「이것을 펼쳐라」 까지 지웠다. 첫 건은 캐시가 비어 있어 무효화가 안 돌아 우연히 됐고, 두 번째부터 드러났다. 뜻(_vocWant) 과 상태(_vocOpen) 를 갈랐다. 사람이 직접 접으면 뜻을 버린다 — 안 그러면 탭을 옮겼다 올 때마다 다시 펼쳐진다 |
| v1.101 | 말을 「요금변경」 으로 통일 (kevin 지적) — request_kind 의 기존 값이 이미 「요금변경」 인데 갈아타기만 「요금제변경」 으로 적고 있었다. 같은 것이 두 이름으로 불리면 배지 · 필터 · 알림이 서로 다른 말을 한다. 배지 · 알림 제목 · 리드 카드 요약 · 상세 제목 네 곳을 맞췄다 |
| v1.100 | 갈아타기 표시를 화면 전체로 넓혔다 (kevin 지적) — 목록 활동이력 칸이 「세모R · 해지」 였다. ⚠ v1.098 에서 리드 카드 · 알림만 고쳤다. 목록 접수 배지 · 활동이력 칸 · 상세 · 휴지통 네 곳이 그대로였다 — 「한 경로만 고치고 나머지를 놓치는」 그 패턴이 또 났다. 판정(_isSwap) 과 라벨(_reqKindLabel) 을 한 곳에 두고 전부 그것을 부른다. SHORT 지역 표와 알림의 재계산도 없었다. ⚠ 목록 컬럼의 html 과 get 이 같은 값을 봐야 한다 — 갈리면 「요금제변경」 으로 보이는 줄이 「해지」 로 정렬 · 검색된다. ⚠ 판정은 사업자 전체를 묶어 계산하므로 캐시에 둔다. 문의를 다시 실을 때 · 저장할 때 버린다 |
| v1.099 | 알림에 「⛔ 오류」 탭 (kevin 지정) — 연동 이상을 로움 관리자에게만 보인다. 오류가 없으면 탭 자체가 없어 평소 화면은 그대로다. 왜 만들었나 — cron succeeded · flow_sync_log ok=true · HTTP 200 · 함수 ok:true, 네 겹이 다 초록인데 flow-push 가 하루 1,440번 아무것도 안 했다. picked: 0 이라는 숫자 하나만 아무도 안 봤다. 「멈췄나」 가 아니라 「일을 하고 있나」 를 본다 (flow_health() · SQL 438). ⚠ sys:true 로 다른 탭에서 걸러낸다 — 담당자 알림에 섞이면 못 고칠 것을 보게 되고 할 일이 묻힌다. ⚠ id 에 시각(시 단위) 을 넣는다. 읽음 처리해도 한 시간 뒤 다시 선다 — 안 고쳐진 오류가 읽음으로 사라지면 안 된다. ⚠ _notiBuild() 는 동기 함수다. RPC 를 끼우면 updateBellBadge · renderNotiList 까지 async 로 번진다 — 따로 받아 캐시에 두고 읽는다 |
| v1.098 | 알림 제목을 사실대로 쓴다 (kevin 지정) — 요금제 변경 (세모R+ → 세모R) · 신규 유입 (세모R) · 해지 (세모R+). ⚠ 「신규」 를 함부로 붙이지 않는다. 갈아타기 · 해지를 「신규 접수」 로 부르면 새 영업 건이 들어온 것으로 읽힌다 — 알림에서 가장 흔한 오독이었다. ⚠ 갈아타기 판정을 _svcSwitchMap() 하나로 모았다. 리드 카드와 알림이 두 벌로 판정하면 같은 건이 두 화면에서 다르게 불린다 |
| v1.097 | 서비스가 바뀌는 해지는 「요금제변경」 으로 보인다 (kevin 지정). 「세모R+ → 세모R」 처럼 갈아타는 건이 해지 태스크로 들어온다 — 실측 이원희 세무회계는 해지사유가 홈페이지 이용 중단 이라 「해지사유에 요금제 변경이 있으면」 규칙에 안 걸렸다. ↗ 전환 줄이 있는 해지를 갈아타기로 본다. ⚠ 보이는 것만 바꾼다. request_kind 는 Flow 원본이다 — 이탈률 집계를 고치려면 DB 쪽을 따로 정해야 한다. 알림 색을 단계에 맞춘다 — 해지 · 요금변경은 활성화/정상 인데 알림만 유입 노랑이라 「새로 들어온 영업 건」 으로 읽혔다. 이제 단계 색을 따르고 제목도 신규 접수 · 해지 가 된다 |
| v1.096 | 홈페이지 · 블로그의 「열기」 가 늘 보인다 (kevin 지정). 값이 있을 때만 그렸더니 비어 있는 블로그에는 아예 없었고, 방금 적어 넣은 주소도 저장하기 전에는 열어 볼 수 없었다. ⚠ 렌더 시점 값을 굳혀 넘기지 않는다 — 누를 때 입력칸의 지금 값을 읽는다. Enter 로도 열린다 |
| v1.095 | 고객 상세의 홈페이지 · 블로그를 고칠 수 있다 (kevin 지정). 종전에는 링크로 보기만 해서 neotax.kr 이 주소 칸에 들어간 것 같은 잘못을 화면에서 바로잡을 수 없었다. 저장 자리는 crm_offices — 홈페이지는 hp_insight 다. homepage 는 지도 크롤링 값이라 사람이 고친 값과 섞지 않는다. ⚠ 고치면 hp_insight_at 에 그날 날짜를 찍는다. 6번 카드는 나중 것이 이기므로 날짜가 없으면 옛 신청서가 되덮는다. ⚠ 저장 뒤 crmOffices 캐시도 함께 고친다 — 빠뜨리면 저장해도 칸이 옛 값으로 되돌아온다 |
| v1.094 | 「주소 아닌 것」 을 두 부류로 가른다 (실측 24건). ⓐ 부스러기 — neotax.kr · kiu1130@n · - 메인화면선택 : 3안 · 1303~1308호1303~1308. 무엇으로 바꿔도 낫다 → 덮는다. ⓑ 시도만 빠진 멀쩡한 주소 — 「송파구 양재대로 62길 47 가락빌딩 401호」. 파일 값이 더 길 때만 덮는다. 「지역 : 송파구」 로 덮으면 되레 나빠진다 |
| v1.093 | 본문 보완이 주소 아닌 것도 고친다 — 실측으로 neotax.kr · watax.kr · - 사업자등록번호 : · 분당 이 주소 칸에 들어 있었다. 잘린 본문에서 뽑던 시절의 잔재다. 덮는 조건 셋 — ① 비었다 ② 시도로 시작하지 않는다 ③ 파일 값의 앞부분이라 끊긴 것이다. 그 외에 값이 다르면 두고 목록으로 보여준다 |
| v1.092 | 본문 보완의 주소 후보에 「지역」 추가 (실측 226건이 그것뿐이다) — 온전한 주소가 있으면 그쪽이 이긴다. ⚠ 첫 값을 집고 나서 시도 검사를 하면 안 된다 — 「주소 : 분당」 에서 멈춰 뒤의 「지역 : 경기도 성남시」 를 못 본다. 후보를 돌면서 통과하는 첫 값을 쓴다 |
| v1.091 | Data 등록에 7) 플로우 본문 보완 추가 — Flow 태스크 목록 파일로 잘린 본문을 채우고 문의 주소를 다시 넣는다. ⚠ 원본은 화면에서 직접 안 고친다. flow_text_fill()(SQL 432-B) 에 맡긴다 — 「잘린 것만」 조건이 함수 안에 있다. ⚠ 주소가 바뀌면 좌표를 비운다 — 안 비우면 「좌표 보완」 이 다시 잡지 않아 엉뚱한 자리에 마커가 그대로 선다 |
| v1.090 | 기획문서는 로움만 본다 · 카드 묶음 머리로 「어디까지가 남에게 보이는 것인가」 를 가른다 |
| v1.089 | 상담 홈 버튼 · 알림 탭 이동 마무리 |
| v1.088 | 알림을 누르면 그 알림에 맞는 탭이 열린다 — 유입 · 활동 · 예약은 리드, 판정보류는 고객. 종전에는 앞서 보던 탭이 남아 「무엇을 보라는 것인지」 알 수 없었다 |
| v1.087 | 목록 활동이력 칸에 상담을 함께 본다 — 활동 · 유입 · 상담 중 가장 나중 것. ⚠ 데이터는 안 옮긴다. 활동으로 넣으면 523행이 섞여 우리가 한 일이 묻힌다. 정렬도 셋을 함께 잰다 |
| v1.086 | 상담 화면의 「전체」 가 홈 버튼 — 상품 · 고객 · 쪽수를 한 번에 푼다. 카드를 누르면 그 상담만 펼쳐서 상세 탭이 열린다 |
| SQL 427~431 | Flow 문의에 주소 담기 · 담당자 자동 생성(crm_contacts) |
⚠ 주소 정규식 — 두 겹으로 거른다 (SQL 431)
「주\s*소」 만으로는 「홈페이지주소」 · 「도메인 주소」 의 값이 함께 걸린다 —
실측에서 neotax.kr · kiu1130@n · 「- 메인화면선택 : 3안」 이 주소로 들어왔다.
① 줄 첫머리의 「주 소」 만 잡는다 ((?n)^[\s\-•■]*) —
앞에 「홈페이지」 가 붙으면 안 걸린다.
② 시도 이름으로 시작하는 것만 남긴다 — ①만으로는 값이 비어 다음 줄이 딸려 오는 것을 못 막는다.
담당자가 Flow 문의에는 안 만들어지고 있었다
crm_contacts 는 「+등록」 폼으로 넣을 때만 만들어졌다.
Flow 로 들어온 문의는 그 경로를 안 타서 연락처가 비어 있었다 —
문의에는 전화 · 이메일이 있는데 담당자 칸은 「등록된 담당자가 없습니다」 였다.
이제 문의를 만들 때 함께 만든다. 기존 19곳도 문의에 담긴 값으로 채웠다.
⚠ 그 사업자에 담당자가 하나도 없을 때만 만든다 — 사람이 넣은 것은 건드리지 않는다.
이름이 없으면 만들지 않는다 (번호만 있으면 목록에서 「(이름 없음)」 이 된다).
⚠ 남은 일 — 옛 게시글 본문이 잘려 있다
Flow 목록 API 는 content 를 100자쯤에서 자른다.
flow-sync v3 배포(2026-08-27) 뒤로는 상세 API 로 전문을 받지만,
그 전에 들어온 492건은 잘린 채다 — 주소가 뒷부분에 있어 못 받았다.
실측 「충청남도 아산시 시민」 처럼 중간에서 끊긴 주소가 들어갔다.
⚠ 그 상태로 좌표를 받으면 엉뚱한 자리에 마커가 선다 — 「📍 좌표 보완」 을 돌리지 않는다.
①로 정했다 (v1.091) — Flow 화면에서 태스크 목록을 내려받아
「Data 등록 › 7) 플로우 본문 보완」 에 올린다. 파일에는 본문이 온전히 들어 있다.
순서 — 본문을 채운 뒤에 「📍 좌표 보완」 을 돌린다. 주소가 바뀐 건은 카드가 좌표를 비운다.
v1.021 ~ v1.084 (2026-08-27)
하루에 64판이 올라갔습니다. 큰 줄기는 넷입니다 —
고객상담(채널톡) 도입 · Flow 세 프로젝트 연동 · 보안 점검 ·
원장을 마스터로. 나머지는 그 과정에서 드러난 화면 손질입니다.
| 버전 | 내용 |
| v1.084 | 상담 파일 시트 이름이 바뀌어도 읽는다 — 2026-06 은 「UserChat」, 07 은 「UserChat data」 였다. 이름 후보 + 특징 컬럼(summarizedMessage · chatId · profile.BIZ_NO) 두 겹으로 찾는다. mediumType → mediumName 도 둘 다 받는다 |
| v1.083 | 접수구분에 「해지」 추가 — Flow 해지관리가 그 값으로 들어온다. 색은 상태의 진자주와 같게. 단계 재판정은 활성화/정상 — 원장이 이미 해지라 CRM 까지 해지로 두면 두 번 적는 셈이다 |
| v1.073~82 | 고객상담(채널톡) — 업로드 · 상담 메뉴 · 고객 상세 「상담」 탭 · 목록 배지. 홈페이지 신청 Data 카드(6번) |
| v1.059~72 | 컬럼 폭을 사람이 정한다(모든 목록) · 화면 이동 시 열린 창 닫기 · 주소 고치면 좌표 자동 갱신 · 붙여넣기에서 층 · 호 살리기 · 영업 · 활성화 · 고객 활동 최신순 정렬 |
| v1.049~58 | 회사 판정이 원장을 먼저 본다(사무소 뒤 행이 앞을 덮던 문제) · 사람 이름으로 검색 · 낱말 단위 검색 · 고객 목록에 방문일시 · 활동이력 · 담당자 |
| v1.044~48 | 「기타」 단계 신설 — 활동 구분과 문의 단계 양쪽. 어느 흐름에도 안 속하는 건을 담는다. GRP_RANK 는 활성화와 같은 3 으로 둔다 |
| v1.037~43 | 지도 색을 통일 팔레트로 · 활성화 위험을 W 거래처 대비 비율과 함께 판정 · 인증서 만료를 따로 표시 · 칸 전체에 색 |
| v1.021~36 | 알림을 활동 이력으로(담당자 미배정 제거) · 활성화 상태 칩 · 고객 탭과 숫자 맞춤 · 온보딩 설정 |
⚠ 오늘 같은 자리를 세 번 고쳤다 — crmCustomers
loadData() 가 crmCustomers = [] 로 비우고 다시 채우지 않는다 (데모 잔재).
그런데 이름이 「고객원장」 처럼 읽혀 세 곳이 그것을 보고 있었다 —
v1.039 사람 이름 · SNS 집계, v1.061 주소, v1.081 홈페이지 · 블로그.
모두 「값이 늘 비어 있다」 는 같은 증상이었다.
지우는 것이 맞다 — 지우려면 남은 참조를 전부 훑어야 한다.
고객상담 — 채널톡 (v1.073~84)
고객이 채널톡으로 건 문의를 담는다.
우리가 진행하는 컨설팅(crm_consults) 과는 다른 것이라 표를 따로 둔다 —
섞으면 「컨설팅 3회차」 와 「채널톡 문의」 가 한 목록에 들어간다.
| 표 · 값 | 내용 |
crm_consult_chats | 상담 한 건. chat_id 가 PK 라 재업로드하면 덮인다. 회사는 트리거가 사무소 → 원장 순으로 채운다 |
crm_consult_msgs | 대화 한 줄. chat_id + seq. bot 응답까지 담는다 — 빼면 맥락이 끊긴다 |
| 연결률 | 523 / 734 (2026-01~06). 나머지는 익명(「엘리 488」) 이라 어느 고객인지 알 수 없다 |
| 태그 | 콤마로 여럿 붙고 각각 「상품/유형」 이다. 상품 목록을 기준으로 갈라야 순서가 뒤바뀐 것도 읽힌다 |
| 유형 묶기 | 54가지를 첫 낱말로 묶는다. 같은 뜻이 두 이름이다 — 오류↔오류문의 · 불편사항↔불편개선문의. [I] 는 세모인사이트 표시라 뗀다 |
| 화면 | 상담 메뉴(좌 고객 · 우 일자별) · 고객 상세 「상담」 탭 · 목록 배지. 카드를 누르면 상세 탭이 열린다 — 전문을 두 곳에서 그리지 않는다 |
태그를 다는 습관이 이 화면의 값을 정한다
실측 523건 중 미분류 91 · 단순문의 300 으로 75%가 뭉쳐 있다.
지금 태그로는 「무슨 문의가 많은가」 를 가릴 수 없다. 클레임 분류도 없어
가장 가까운 「불편」 계열이 7건뿐이다 — 실제 클레임이 그만큼일 리 없다.
Flow 세 프로젝트 연동 (2026-08-27)
1834962 하나만 보던 연동을 셋으로 넓혔습니다.
Edge Function 은 손대지 않았습니다 — flow-sync · flow-tasks 가
이미 body.projectId 를 받고 있어 cron 만 늘리면 됐습니다.
잘 도는 함수를 안 건드리는 것이 가장 안전합니다.
| 프로젝트 | 무엇 | 문의 단계 |
1834962 | 신규 문의 · 가입 관리 | 유입 / 신규접수 |
2667221 | 세모.홈페이지 신청 관리 | 온보딩 / 대기 — 세모R 쓰던 곳이 세모R+ 로 바꾼 것 |
2782272 | 세무사 해지관리 | 활성화 / 정상 — 해지도 요금변경도 같은 단계. 구분은 접수구분으로 |
실측으로 알아낸 것 — 기록이 없으면 다시 찾아야 한다
| 사실 | 왜 중요한가 |
| 목록 API 는 본문을 200자에서 자른다 | 해지사유 · 홈페이지 주소가 뒤쪽에 있어 안 온다. /user/posts/{postId} 로 상세를 한 번 더 불러야 한다 (flow-sync v3) |
| 제목의 PLUS · BASIC 은 요금제다 | 상품이 아니다. 실측 [BASIC] 4건이 본문 「세모리포트 (Basic)」 즉 세모R 해지 였다. 상품은 본문의 「이용서비스 · 신청서비스」 로 판정한다 |
| 세모R+ 를 부르는 이름이 넷 | 인사이트 · 세모리포트 플러스 · 플러스 · 홈페이지. 「홈페이지」 는 우리가 만들어 주는 경우이고, 인사이트만 신청하면 자체 홈페이지에 연결한다 — 둘 다 상품은 세모R+ |
| 하위업무는 자기 게시글이 없다 | 실측 177/177. 목록 API 가 상위 게시글만 준다. up_task_id 로 상위를 찾아 그쪽 본문에서 사업자번호를 얻는다 |
하위업무의 이름은 task_name | content 는 비어 있다. 처음에 게시글 제목으로 떨어뜨렸더니 「[세모.홈페이지 신청] 영앤택스」 가 6건씩 중복됐다 |
| 본문에 「\n」 이 글자로 들어 있다 | 진짜 줄바꿈이 아니라 두 글자다. [^\n\r] 로는 안 끊겨 JSON 조각("}}]}) 까지 딸려 왔다 |
| 업무번호는 조직 전체에서 이어진다 | 프로젝트가 달라도 TASK_NUM 이 겹치지 않는다. f_task_no 하나로 중복을 막을 수 있다 |
세 프로젝트 모두 templateType "4" | 뷰의 조건을 넓힐 필요가 없었다. flow-probe 로 확인했다 |
⚠ 문의 생성 규칙 — cutoff 예외
기본은 2026-08-01 이후 등록분 만 문의로 만든다. 옛 데이터가 쏟아지면
「2025년 신청이 오늘 리드로」 뜬다.
다만 그 사업자에 문의가 하나도 없으면 과거분도 만든다 (kevin 확정) —
원장에만 있고 CRM 에 흔적이 없던 곳이 이 규칙으로 채워진다. 실측 21건.
문의로 만들지 않는 것
채널톡 AI 자동접수 — 수정 요청이지 문의가 아니다 (본문 형식도 다르다).
홈페이지 상세신청 — 이미 신청한 곳의 후속이라 문의로 만들면 세모R+ 건이 두 개가 된다 (45건).
[테스트] — 5건.
셋 다 flow_tasks 에는 남아 활동 으로 붙는다.
활동 생성 (flow_act_new · SQL 423~425)
| 항목 | 규칙 |
| 대상 | 문의가 되지 않은 모든 업무 — 본건(리드가 이미 있는 것) · 하위업무 · 제외된 제목 |
| 붙일 곳 | 그 사업자의 최신 문의. 단계를 가리지 않는다 — 「어느 리드에 붙나」 보다 「그 고객 이력에 남나」 가 중요하다 |
| 중복 방지 | f_task_no 유니크 인덱스. ⚠ 부분 인덱스(where) 로 만들면 on conflict 가 추론하지 못한다 — 조건 없이 만든다 |
| 내용 | 해지 · 요금변경은 본건에만 사유를 적는다. 하위업무가 물려받으면 같은 사유가 여러 번 적힌다 (10 → 25건으로 부풀었다) |
| 실측 | 활동 354건 · 사업자 123곳. 한 곳당 두세 줄이라 활동이력이 덮이지 않는다 |
보안 점검 (SQL 403~404)
| 확인 | 결과 |
| RLS 꺼진 테이블 | 0 ✓ |
| anon 쓰기 권한 | 0 ✓ |
flow_apply_new() | anon 이 실행할 수 있었다 — 문의를 외부에서 찍어낼 수 있는 상태. 회수했다. ⚠ is_loum · is_master · my_company_id 는 그대로 둔다 — RLS 정책이 그 함수를 부르므로 회수하면 로그인부터 막힌다 |
| BP 기획노트 | plan_notes 정책이 using true 라 CRM 에 로그인한 위멤버스 직원이 읽고 · 고치고 · 지울 수 있었다. 로움 계정(@roumit.com) 전용으로 좁혔다 |
| 뷰의 실행 권한 | 뷰는 소유자 권한으로 돈다 — security_invoker=true 를 켜야 밑 테이블의 RLS 가 산다. 안 켜면 테이블을 잠가도 뷰로 샌다 |
원장을 마스터로 (SQL 408 · 414)
| 한 것 | 왜 |
crm_customers.company_id | 902건 채움 + 트리거. 종전에는 사무소로 이어 판정하는 화면 필터 였다 — anon 키로 직접 조회하면 남의 회사 원장이 읽혔다 |
| 사업장 → 원장 전파 | 상세의 「사업장」 을 고치면 원장이 따라온다. 주소 · 대표자 · 전화는 사업자 단위라 전부에, 사무소명은 원장이 한 행인 곳에만 — 「세무법인송촌 / 4팀 / 5팀」 처럼 갈린 곳(12곳) 이 하나로 뭉개진다 |
| 업로드 보호 | 주 1회 업로드가 파일에 없는 값을 빈 문자열로 덮는다. 트리거로 막지 않으면 방금 넣은 값이 다음 주에 사라진다 — 「보관한다」 는 말에는 「지워지지 않는다」 가 포함된다 |
| 무번호 사무소 | 386건. 온라인 문의는 사업자번호 없이 들어온다. merged_into_id 로 가리키기만 한다 — 지우면 되돌릴 수 없다. 잠재(linked_office_id) 가 이미 쓰는 방식을 그대로 베꼈다 |
⚠ 오늘 되풀이된 실수 넷
① 컬럼 이름을 확인 없이 씀 (5회) — crm_prospects.biz_no(없음 · biz_name) ·
plan_r(없음 · plan) 등. information_schema 조회를 먼저 붙인다.
② 제약 이름을 짐작하고 drop — 옛것이 안 지워져 제약이 두 개가 됐고,
CHECK 는 AND 라 새 값이 여전히 막혔다. 이름을 조회한 뒤 지운다.
③ S-05 재발 (3회) — stage_group · request_kind 에 새 값을 넣기 전에
CHECK 를 넓히지 않았다. 순서: 확장 → 배포.
④ 설명용 조각을 실행 코드처럼 전달 (4회) — 코드는 파일로만 준다.
v1.001 ~ v1.020 (2026-08-26)
| 버전 | 내용 |
| v1.020 | 색 체계 정리 — STATUS_COLOR 를 새로 두고 네 곳(좌측 메뉴 · 칸반 · 검색 줄 · 상태 배지) 이 그것만 본다. 칸반 「리드」 가 보라였던 것을 노랑으로, 정지 · 해지가 같은 회색이던 것을 슬레이트 · 진자주로 갈랐다 |
| v1.019 | 활성화 숫자를 고객 탭과 맞췄다 — 상태 칩이 문의를 세어 「정상 400」 인데 고객 탭은 원장을 세어 358 이었다. 원장 대표행(사업자당 1건) 으로 통일. 좌측 「전체」 도 문의 유무로 거르지 않는다 |
| v1.018 | 정지만 골랐을 때 목록 0건 이던 것 수정 — 목록을 세우는 자리에 step_group !== '활성화' 가 남아 있었다. 상태 범위를 고친 세 곳 중 네 번째를 놓친 것. 「활성화 설정」 을 버튼 모양으로 |
| v1.017 | 알림을 활동 이력으로 — 담당자 미배정을 걷어내고 신규 유입 · 영업 · 온보딩 · 활성화 활동을 최근 14일치 담는다. 미확인은 「내 알림 → 기타」 로 나누고 일자별로 묶는다 |
| v1.016 | 활성화에 상태 칩(정상 · 정지 · 해지) — 기본은 정상 · 정지. 마지막 하나는 끌 수 없다 |
| v1.015 | 위험 판정을 「미만」 → 「이하」 — 기준 0 이면 「0 미만」 이라 아무도 안 걸렸다. 등록 0 인 곳이 있는데 0건으로 나왔다. 정지 고객도 활성화 화면에 포함 |
| v1.013~14 | 위험 항목을 다섯으로 (거래처 · 사용수 · 발송수 · 인증서 · 발송감소) · 활성화 설정 패널 신설 · 지표 칸을 기준값과 대조해 형광 표시(위험 붉은색 · 잠재 노란색) · 저장 표시 |
| v1.010~12 | 활성화 카드 「전체」 가 헤더 숫자(위험) 를 쓰던 것 수정 · 활성화 진입 시 「위험 있음」 자동 선택 |
| v1.009 | 리포트 바깥을 누르면 닫힌다 — 첫 클릭은 삼킨다(뒤 줄이 함께 열리지 않게) · ESC |
| v1.001~08 | 좌측 메뉴 단계 색 막대 — 항상 · 같은 크기 · 100%. 흰 띠를 깔았다 걷어낸 과정이 있다 |
색 체계 (v1.020 확정)
축이 셋이다. 섞어 쓰면 같은 것이 화면마다 다른 색으로 보인다 —
실제로 리드가 좌측은 노랑인데 칸반은 보라였고, 고객과 활성화가 같은 보라였다.
| 축 | 항목 | 색 | 쓰이는 곳 · 뜻 |
진행 단계
GRP_COLOR |
리드 · 유입 | #FACC15 노랑 | 좌측 메뉴 · 칸반 · 단계 배지 · 활동 구분 · 검색 결과 줄 |
| 영업 | #1D4ED8 파랑 |
| 온보딩 | #10B981 초록 |
| 활성화 | #7C3AED 보라 |
고객 상태
STATUS_COLOR |
정상 | #7C3AED 보라 | 활성화와 같은 색 — 같은 것을 가리키는 말이다 |
| 정지 | #64748B 슬레이트 | 멈춤 — 되살릴 수 있다 |
| 해지 | #9F1239 진자주 | 가입 후 이탈 |
| 실패 | #DC2626 빨강 | 가입하지 못함 |
| 그 밖 |
고객 | #94A3B8 | 모든 상태를 담는 자리라 단계 색을 주지 않는다 |
| STEP · 상담 · MAP | #475569 | 단계가 아니라 보기 · 도구다 |
| 예약 | #F97316 주황 | 어디서 보이든 예약 (v0.770) — 다른 데 쓰지 않는다 |
⚠ 색을 새로 적지 않는다
GRP_COLOR · STATUS_COLOR · APPT_COLOR ·
NEUTRAL_COLOR 네 곳에서만 온다. 화면에 직접 #7C3AED 를 적으면
다음에 바꿀 때 그 한 곳이 남는다 — v0.770 에서 세 곳에 흩어져 있던 것을 모았는데
v1.020 에서 또 네 곳이 갈려 있었다.
어두운 배경에서는 값이 다르게 보인다
사이드바(#1E2235) 에서 진한 파랑 · 보라는 묻힌다. 한때 밝은 변형(#60A5FA · #A78BFA) 을
쓰다가, 흰 띠를 깔았다가, 결국 막대를 100% 불투명 으로 칠하는 것으로 정리했다.
불투명하면 바탕이 필요 없다.
v1.000 — 1.0 완성 (2026-08-25 · kevin 선언)
v0.100 부터 v0.889 까지가 0 번대다. 여기까지를 1.0 으로 본다 —
CRM 이 혼자 돌던 도구에서 Flow 와 양방향으로 이어진 업무 시스템이 된 지점이다.
다음 수정부터 v1.000 으로 센다. 버전 3종 세트(<title> ·
APP_VERSION · 파일명) 규칙은 그대로다.
1.0 에 담긴 것
| 영역 | 내용 |
| 단계 체계 | 유입 · 영업 · 온보딩 · 활성화 4단계와 세부단계. 활동이 단계를 옮기고, 예약은 시간축으로 따로 둔다 |
| 고객 3계층 | 잠재 → 문의(리드) → 고객원장. 사업자번호가 세 층을 잇는다 |
| 회사별 격리 | 로움 · 위멤버스 · 외부. RLS 한 줄(is_loum() or company_id = my_company_id()) 로 막는다 |
| 구글 캘린더 | 예약이 회사 공유 캘린더에 함께 오른다. 새로고침 토큰을 서버에 두어 매일 묻지 않는다 |
| Flow 양방향 | 신청 → 문의 자동 생성(1분) · 활동 → 하위업무 · 댓글(1분). 수동 엑셀 업로드 폐지 |
| 리포트 · 지도 | 일일 브리핑, 기간 비교, 잠재 발굴 지도 |
1.0 이 뜻하는 것
「기능이 다 됐다」 가 아니라 「사람이 손으로 옮기던 일이 없어졌다」 는 뜻이다.
Flow 엑셀을 받아 올리던 일, 예약을 캘린더에 따로 넣던 일,
활동을 Flow 에 다시 적던 일이 각각 자동으로 돈다.
남은 것은 10번 · 11번에 적혀 있다.
v0.402 ~ v0.457 — 배포 · 마스터 정리 · 회사별 격리
| 구분 | 내용 |
| 배포 | crm.semo.im 연결 (Netlify · Netlify DNS 하위도메인). 로그인을 기본값으로 전환하고 anon 권한을 전면 회수했다. Redirect URLs 는 /** 까지 등록해야 한다 — 정확한 주소만 넣으면 쿼리스트링이 붙는 순간 Site URL(BP)로 튕긴다. 로컬(localhost)에서만 로그인을 건너뛰고, 배포 주소에서는 우회 수단이 없다 |
| 사무소 마스터 | crm_offices 를 실질적 마스터로 정리했다. 같은 사업자가 369개 · 745행으로 흩어져 있어 376행을 병합(1,457 → 1,191)하고, 문의에서 좌표 1,357건 · 사업자번호 695건을 채웠다. 원장에만 있던 110건을 새로 넣어 계약 고객이 지도에서 빠지는 문제를 없앴다. step 은 구 라벨을 옮기지 않고 원장 상태와 최근 문의에서 다시 계산했다 — 8월 6일 생성 후 갱신이 없어 해지 256건이 가입으로 남아 있었다 |
| 단계 라벨 통일 | 세 테이블에 구 라벨(가입 · 완료 · 접촉 · 셋업 · 문의/타겟 · 컨설팅/접촉)이 섞여 있었다. 현 체계 7종으로 통일하고 지도의 되돌림 매핑(_STAGE_BY_LABEL)에서 구 라벨을 걷어냈다. 컨설팅예약 → 방문예약, 예정 폐지(가능성은 속성으로 상시 입력), 문의 → 리드 |
| 회사별 격리 | 사무소 · 잠재 · 문의 · 활동 · 원장에 company_id 를 넣고 RLS 로 막았다. 판정은 my_company_id() · is_loum() 두 함수가 하고, 정책은 is_loum() or company_id = my_company_id() 한 줄이다. 기존 전체 허용 정책을 지우지 않으면 아무 효과가 없다 — 정책은 OR 로 합쳐지므로 using(true) 가 하나라도 남으면 전부 통과한다 |
| 타채널고객 | RLS 는 행 단위라 「이 행은 이름 · 주소만」 이 안 된다. 그래서 other_channel_offices() 함수가 여섯 컬럼만 돌려준다 — 담당자 · 연락처 · 문의 내역은 함수가 내보내지 않으니 콘솔로도 못 꺼낸다. 지도에서 세모 마커로 그린다 |
| 리드 등록 | 데모 배열(crmDeals)에 담던 옛 등록창을 걷어내고 crm_inquiries 에 직접 저장한다. 지도의 「리드 등록하기」 와 같은 창이다. 주소는 후보를 보여주고 고르게 하며 좌표를 함께 저장한다 — 조용히 첫 결과를 쓰면 엉뚱한 건물이 잡혀도 알 수 없다 |
| 권한 체계 | employees.is_admin 신설 · 마스터 · 로움 관리자 · 회사 관리자 3단. 설정 탭별 권한, 개선의견 읽기전용, 휴지통 회사별 + 복원 권한, 사용자 목록 회사별 · 추가 · 이름 · 핸드폰 수정, 관리자 탭에서 관리 회사 지정. 관리자 전용 탭에는 붉은 점을 붙여 무엇이 나에게만 보이는지 알린다 |
| 가입 승인 | 도메인 제한을 없애고 회사 선택 → 관리자 승인으로 바꿨다. crm_access_log 에 가입 · 승인 · 거절을 남긴다 — 거절하면 행이 지워지니 그쪽이 유일한 기록이다 |
| 컬럼을 추가하면 읽는 곳도 | is_admin 을 만들고 정책까지 걸었는데 화면은 계속 「관리자 아님」 이었다 — employees 조회 컬럼 목록에 안 넣어 undefined 였다. 오늘 company_id · lat/lng 에서도 같은 일이 있었다. 컬럼을 늘리면 select 목록을 함께 고친다 |
| 함수는 마지막이 이긴다 | create or replace 를 여러 블록에서 하다 마스터 조건이 든 is_loum() 이 덮여 사라졌다. 같은 함수를 여러 번 만들면 마지막 정의만 남는다 |
| 설정 창 분리 | 같은 주소를 ?settings=탭 으로 새 창에 열어 코드를 그대로 쓴다. 목록 · 지도를 보면서 사용자 · 개선의견을 함께 다룬다 |
| 활성화 정렬 | 이 뷰만 컬럼이 코드에 박혀 있어 헤더가 정렬 링크가 아니었고 정렬 자체도 건너뛰었다(if (!conf.cols)). 뷰 전용 정렬 정의를 두고 다른 뷰로 나가면 반드시 비운다 |
| 잠재 ↔ 고객 매칭 | 이름만 보면 22건, 주소 코어(도로명+건물번호)를 함께 보면 140건이 잡혔다. 세무사무소 이름은 표기가 다양한데 건물은 하나면 하나다 — 영신로 220 knk디지털타워 216호 와 영신로 220 (영등포동8가) 는 호수 · 괄호만 다르다. 주소 코어만으로는 2,645쌍이 걸린다(한 빌딩에 세무사무소가 여럿 모여 있다). 주소 코어 + 정규화 이름이면서 양쪽 1:1 인 것만 자동 전환한다. 지점→점 통일까지 넣으면 1건이 더 붙는다. 정리 결과 — 순수 잠재 12,637 → 11,980. 전환된 657건 중 84건이 정지 · 해지 · 실패 였다. 이미 접촉한 곳에 신규 영업을 하고 있었다는 뜻이다 |
| 같은 코드 두 곳 | stageOrder 가 검색 경로와 페이지 렌더 경로에 따로 있어, 한 곳만 고치고 「타채널고객이 안 나온다」 를 한 번 더 겪었다. 건수는 맞는데 화면에만 안 나오면 렌더 경로가 여러 개인지 본다 — 그때 전국 12 = 잠재 5 + 고객 7 이 이미 답을 말하고 있었다 |
| 지도 데이터 로드 시점 | 사무소 · 타채널 목록이 「지도를 열어야 채워지는」 구조라 지도 진입 · 등록창 중복확인 · 검색 세 자리에서 같은 증상이 났다. 세 곳 모두 필요할 때 스스로 채우게 고쳤다 |
| 화면이 가려질 때 | 캡처하거나 다른 창으로 가면 지도 범위가 순간 무효해져 「화면 안」 을 세는 칩과 목록이 전부 0 으로 떨어졌다. document.hidden 이거나 범위 높이가 0 이면 그리지 않고 미뤄 두었다가 돌아올 때 한 번 처리한다 |
| 네이버 지도 로더 | 콘솔의 parser-blocking 경고를 없애려 비동기(appendChild)로 바꿨더니 geocoder 서브모듈이 죽었다 — maps.js 가 서브모듈을 document.write 로 붙이는데 비동기 스크립트에서는 그것이 금지된다. 주소 검색 · 좌표 보완이 함께 멈춘다. 경고를 남기고 기능을 지킨다 |
| 말 가르기 | 같은 「담당자」 가 두 뜻으로 쓰여 혼동됐다. 영업담당(우리 회사 · crm_inquiries.assignee) 과 사무소 담당자(crm_contacts · 세무사 · 담당자 · 신청자) 로 갈랐다. 사용중 → 타채널고객 도 같은 이유다 — 우리 채널이 아닌 곳이 확보한 고객임이 읽혀야 한다 |
| 기획서 검색 | 상단 검색으로 전 절을 훑어 좌측 메뉴에 일치 건수를 붙이고 본문을 형광 표시한다. 닫힌 절도 함께 세는 이유는 「다른 데 있나」 를 알 수 없기 때문이다. 9만 자를 넘어 타이핑마다 훑으면 느리므로 180ms 를 쉰다 |
| 사업자번호 형식 | 사무소는 숫자 10자리, 문의는 하이픈으로 저장돼 있어 대조가 매번 어긋났다. 세 테이블 모두 하이픈으로 통일했다. 입력 칸은 숫자만 받아 화면에서 하이픈을 넣는다 |
CHECK 제약을 하루에 다섯 번 만났다 — activity_type · step_detail · step_group · step · request_kind. 코드에 코드값을 추가하기 전에 제약을 먼저 조회한다. 확장 SQL 은 허용값을 손으로 적지 말고 실측 distinct ∪ 신규 목록으로 재생성한다 — 손으로 적으면 외부 연동으로 들어온 값이 빠져 기존 행이 막힌다. (GD010 · S-05)
컬럼명을 확인 없이 쓰다 세 번 틀렸다 — ai_resources.content(실제 body) · crm_prospects.biz_no(없음) · linked_office_id(FK 대상이 crm_offices 였다). 스키마를 모르면 조회 SQL 을 먼저 낸다.
v0.191 ~ v0.206
Supabase 권한 3중 관문
데이터가 화면에 안 보일 때 원인이 세 곳으로 갈리며, 실패 방식이 서로 달라 구분이 가능하다.
| 관문 | 실패 증상 | 대응 |
| ① 테이블 GRANT | 401 + 42501 | 명시적 GRANT 부여 |
| ② RLS 정책 | 200 + 빈 배열 | 정책의 롤과 실제 접속 롤 일치 |
| ③ 앱 에러 처리 | 화면 무반응 | 에러 코드를 UI 에 노출 |
테이블 생성 경로에 따라 권한이 정반대다
Table Editor UI 로 만들면 anon · authenticated 에 GRANT ALL 이 자동 부여된다. SQL Editor 로 CREATE TABLE 하면 아무 권한도 안 붙는다.
이번 건에서 crm_prospects(SQL Editor)는 401, crm_inquiries(Table Editor)는 200 빈 배열로 증상이 갈렸다. 같은 “데이터가 안 보임”인데 원인이 달랐다.
새 테이블을 만들 때마다 role_table_grants 로 확인할 것.
anon 이 TRUNCATE · DELETE 를 가지고 있었다
Table Editor 로 만든 crm_inquiries 등은 GRANT ALL 상태였다. anon 키는 HTML 에 평문 노출되므로 키를 아는 누구나 문의 1,588건을 지울 수 있는 상태였다. v0.026 으로 전체 회수 후 SELECT 만 재부여했다.
현재 anon 권한
전 crm_* 테이블 SELECT 만. 예외로 배치 지오코딩을 위해 컴럼 단위 권한만 보유한다.
crm_prospects (lat, lng, geocoded_at) · crm_inquiries (lat, lng)
컴럼 단위 GRANT 는 테이블 단위를 덮어쓰지 않는다
둘은 합집합이다. 범위를 좁히려면 REVOKE 가 반드시 선행해야 한다.
SQL 운영 원칙
- 파일 하나에 실행 단위 하나 — 통째로 실행해도 안전해야 한다. 조회 쿼리가 여럿이면
024a, 024b 처럼 파일로 분리
rollback · begin 절대 포함 금지 — Supabase SQL Editor 는 스크립트 전체를 한 트랜잭션으로 실행한다. 검증용 rollback 이 앞의 DDL 까지 되돌렸다
- 마지막 문장 결과만 표시된다 — 검증 쿼리는 파일 맨 끝에 하나만
TRUNCATE ... CASCADE 금지 — FK 연쇄로 crm_prospects 가 전삭제된 사고 발생. 개별 DELETE FROM 사용
- 변경 전 프리뷰 — 건수 · 샘플을 먼저 확인하고 실행. 롤백 파일을 함께 준비하되 설명에 “지금 실행하지 말것”을 명시
- 예외 — 짝이 맞아야 의미가 있는 문장은 한 파일에 둔다 (v0.239 확정)
DROP TRIGGER IF EXISTS; CREATE TRIGGER; 처럼 교체가 목적인 쌍은 분리하면 순서가 어긋나 42710 trigger already exists 가 난다. 실제로 발생했다.
같은 이유로 DROP CONSTRAINT IF EXISTS; ADD CONSTRAINT; 도 한 문장(ALTER TABLE 다중 절) 으로 묶는다.
기준 — 따로 실행하면 중간 상태가 잘못되는 쌍은 한 파일. 독립적으로 의미가 있는 문장은 분리한다.
SQL 작성 함정 (v0.233 실측)
| 함정 | 증상 · 대응 |
VALUES 첫 행 NULL 타입 추론 | column "customer_count" is of type integer but expression is of type text. 첫 행이 NULL 이면 Postgres 가 text 로 추론한다. SELECT v.* 대신 v.col::integer 처럼 명시적 캐스트를 쓴다 |
| 정규식 공백류가 개행 통과 | - 유입코드 : 처럼 값이 빈 줄에서 공백 패턴이 개행을 넘어 다음 줄 값을 캡처한다. 같은 줄로 한정하려면 탭 · 스페이스만 허용하고 캡처는 개행 제외 문자류로 쓴다 |
| 자동 명명 CHECK 제약 중복 | 테이블 생성 시 붙은 {table}_{col}_check 가 별도로 남아 있다. 새 제약을 추가해도 둘이 AND 로 걸려 교집합만 통과한다. pg_constraint 로 전수 확인 후 구 제약을 DROP 한다 |
Failed to fetch (api.supabase.com) | SQL 오류가 아니다. Studio 통신 실패이므로 쿼리는 서버에 도달하지 않았다. 재실행 · 새로고침으로 해결 |
트리거 42710 | trigger "..." for relation "..." already exists. DROP 과 CREATE 를 파일로 쪼개면 순서가 어긋날 때 발생한다. 교체는 한 파일에 둔다 (SQL 운영 원칙 예외 참조). 이미 있으면 그 자체가 생성 성공 상태이므로 건너뛰면 된다 |
| 화면 이상 ≠ DB 이상 | 화면에 이상값이 보이면 DB 를 먼저 실측한다. join_* 오염으로 판단해 정리 SQL 을 만들었으나 실제 DB 는 정상이었고 브라우저의 오래된 데이터였다. 하드 리프레시가 첫 확인 절차다 |
Postgres \s 는 nbsp 를 공백으로 보지 않는다 | 값이 nbsp(U+00A0) 로 시작하면 ^\s* 가 매칭하지 않아 오염 탐지 쿼리가 0 을 반환한다. 문자열 실측은 encode(convert_to(v,'UTF8'),'hex') 로 바이트를 직접 본다 |
엑셀 _x000D_ 오염 | 셀 내 줄바꿈이 문자열로 적재된다. 로더마다 처리하면 반드시 새는 곳이 생기므로 DB 트리거(crm_strip_x000d()) 로 일괄 차단한다 (6번 참조) |
| 제약 걸기 전 실측 필수 | 기존 데이터가 허용값 밖이면 23514 로 실패한다. 반드시 값 분포를 먼저 조회하고 필요하면 NOT VALID 로 추가한 뒤 VALIDATE 로 승격한다 |
코드 수정 내역
| 버전 | 내용 |
| v0.192 | 잠재고객 로드 시 error 와 빈 데이터 분리. 오류 코드별 메시지 UI 노출. 빈 결과는 IndexedDB 캐싱 안 함 (실패 상태 고착 방지) |
| v0.192 | EXTRA 칩(잠재 · 휴폐업 · 경쟁사)에 카운트 span 추가. 색상 문자열 비교를 dataset.stageId 조회로 교체 — 브라우저가 rgb() 로 정규화해 절대 일치하지 않았다 |
| v0.192 | _custStage 우선순위 배열 보강. indexOf 가 -1 을 반환해 미정의 단계가 최우선 정렬되던 버그 |
| v0.193 | 권한 · 인증 실패는 재시도 무의미 → _prospectLoadBlocked 차단. _checkProspectUpdate 가 에러 시 count=null 을 “변경됨”으로 오인해 전체 재로드를 유발 |
| v0.194 | _loadAllProspects in-flight 가드. 세 곳에서 동시 호출되어 같은 요청이 3번 나가던 문제 |
| v0.195 | 리드 탭에 동일 에러 처리 · in-flight 가드 적용 |
| v0.196 | 본 기획서 화면 추가 (kevin 드롭다운) |
| v0.201 | 배치 지오코딩 2단계 추가 — 문의 유입분. 잠재에 동일 상호가 있으면 API 호출 없이 좌표 복사 |
| v0.202 | 전환 건을 단계별 색상으로 표시. 잠재 칩은 crm_stage is null 만 집계 |
| v0.203 | 검색 범위를 토글 버튼 → 지도 N · 전국 N 칩 2개로. 응답 순서 역전 방지 처리 포함 |
| v0.204 | 잠재 0건이면 조기 종료되어 2단계까지 가지 못하던 버그 수정. 페이징 오류도 함께 — 성공 건은 lat is null 조건에서 빠지므로 offset 을 페이지 크기만큼 올리면 중간을 건너뛴다 |
| v0.205 | 주소 정제 + 3단계 폴백. 실패 건을 콘솔에 표로 출력 |
| v0.206 | 기획서 갱신 — 매칭 규칙 · 전환 결과 · 권한 원칙 반영 |
| v0.209 | 기획서를 레이어 팝업에서 새 창으로 전환. document.write 로 HTML 문자열을 조립했다가 스크립트 블록 안의 닫는 태그 리터럴이 HTML 파서와 충돌 |
| v0.210 | DOM API 로만 구성하도록 재작성. 문법 검증만으로는 이 유형을 못 잡는다 — 스크립트 블록 내 닫는 태그 리터럴 검사를 배포 전 점검에 추가 |
| v0.211~213 | 칸반 컬럼명 · id 재정리. lead→target, contact→consulting, won→onboarding, setup→active. 저장된 사용자 설정 키를 _v2 로 올려 구 값 무효화 |
| v0.214 | 메뉴탭 재편 — 가입 탭 제거, 컨설팅 · 온보딩 · 활성화 탭 추가 |
| v0.215 | 칸반을 그룹 구조로 시도 후 원래 형태로 되돌림(v0.216). DB 작업은 유지 |
| v0.217 | 칸반 컬럼 기본을 리스트형으로 변경 |
| v0.218~223 | 활성화 위험 필터 · 기준 설정 인라인화. 완료 · 정지 · 해지를 통계 패널로 교체 |
| v0.224 | 기획서 갱신 — 단계 체계 재편 · 화면 구성 섹션 추가 |
| v0.225~226 | 칸반을 목업에서 실데이터로 전환. 카드 이동 시 DB 반영 + 이력 기록. 교진 중 상수 선언 부가 삭제되어 RISK_FILTERS is not defined 발생 |
| v0.227 | Supabase 1,000행 제한 해소. 컬럼당 50장 + 더보기 버튼 |
| v0.228~231 | 통계형 컬럼 폭 50%로 줄이기. 스톤러 버그 수정(calc(100vh) 제거). 활성화 · 완료 패널 서식 통일 |
| v0.232 | 기획서 갱신 — 요건 목록 섹션 추가 |
| v0.233 | Flow 유형 매핑 확정 — 제목 대신 본문 필드 판정으로 전환. 행종류 7단계 · 접수구분 3종 · 문의서비스 7단계 규칙 수록 (9-C). 정확도 99.94% |
| v0.233 | 사업자번호 추출 규칙 보강 — 하이픈 없는 10자리 파싱 추가로 1,159 → 1,652건 (99.0%) |
| v0.233 | crm_inquiries 에 inquiry_service · inquiry_promo · request_kind · plan_prev 추가. crm_inquiry_activities 에 f_parent_title · activity_type 추가 |
| v0.234 | DB 동기화 실행 완료 — 문의 1,588 → 1,652 · 활동 58 → 59 · 링크패스 16 → 72 · biz_no 공백 757 → 0 |
| v0.234 | 동기화 원칙 확정 (6번) — export = 정답, is_deleted soft delete, 물리 삭제 · CASCADE 금지, 문의 → 활동 순서 |
| v0.234 | SQL 작성 함정 5종 수록 (7번) — VALUES NULL 타입 추론 · 정규식 개행 통과 · 자동 명명 CHECK 중복 등 |
| v0.234 | 활동유형 10종 확정 (7번) — 방문 우선. inquiry_type 레거시 판정 수록 (9-D) |
| v0.234 | plan · plan_promo 컬럼 제거 — 실측 0건 미사용 |
| v0.235 | 리스트 필터 교체 — listTypeFilter(레거시 inquiry_type 기반) 를 서비스 + 접수 2단(listServiceFilter · listKindFilter) 으로 대체. 각 옵션에 건수 표시 |
| v0.235 | loadInquiries COLS 에 inquiry_service · inquiry_promo · request_kind · plan_prev 추가 — 없으면 필터가 전부 빈 값으로 걸린다 |
| v0.235 | 리스트 컬럼 유형 → 서비스 + 접수 분리. _serviceBadge(성장패키지 부배지 포함) · _kindBadge 추가 |
| v0.235 | INQUIRY_TYPES · INQUIRY_TYPE_COLOR · _inquiryTypeBadge 제거. 상세 패널에 Flow 유형 (레거시) 로 참고 표시만 남김 |
| v0.236 | 필터를 헤더로 통합 — 상품(문의서비스) · 단계 · 담당자 · 검색을 상단 필터바에서 처리. 로컬 필터바에는 접수구분만 남김 |
| v0.236 | stageSel 신설 — 상품 우측. 문의 데이터를 쓰는 뷰(deal · consulting · onboarding · active)에서만 노출 |
| v0.236 | assigneeSel 에 onchange 누락 수정. 담당자 목록에 문의 담당자 합집합 반영 — 문의 뷰에서 비어 있던 문제 |
| v0.236 | applyFilters() 도입 — 필터 변경 시 _inquiryPage = 1. 5페이지에서 결과가 1페이지가 되면 빈 화면이 뜨던 문제 |
| v0.236 | _dealProduct() 가드 — productSel 값이 문의서비스로 바뀌어 지도 · 칸반의 crmDeals.product 와 매칭되지 않으면 필터를 걸지 않는다 |
| v0.236 | 리스트 상단에 적용 중인 헤더 필터 표시(_renderFilterHint) — 필터가 헤더로 옮겨가 어디서 걸렸는지 보이지 않는 문제 보완 |
| v0.237 | 접수구분도 헤더로 이동(kindSel) — 로컬 필터바 완전 제거. 필터는 헤더 한 곳에서만 관리한다 |
| v0.237 | 하단 푸터 3분할 신설 — 좌측 페이지당 건수(20 · 50 · 100 · 200 · 전체) · 중앙 페이저 · 우측 건수 |
| v0.237 | 건수 표시 개선 — 필터 적용 시 1–20 · 72건 / 1,652 형태로 현재 구간 · 필터 결과 · 전체를 함께 보여준다 |
| v0.237 | 페이지당 건수를 localStorage 에 저장해 재접속 시 복원. 변경 시 1페이지로 되돌린다 |
| v0.237 | _renderFilterHint 제거 — 필터가 모두 헤더에 보이므로 중복 표시가 불필요해졌다 |
| v0.238 | _x000D_ 제거를 연동 규칙으로 고정 — crm_strip_x000d() 트리거 함수를 crm_inquiries · crm_inquiry_activities 에 BEFORE INSERT OR UPDATE 등록. 로더 무관하게 저장 전 정리된다 |
| v0.238 | 기존 오염분 일괄 정리 (46 · 47) — 주소 · 유입경로 · 유입코드 · 가입내역 끝에 붙어 화면에 노출되던 문제 |
| v0.239 | SQL 운영 원칙에 예외 조항 추가 — DROP+CREATE 처럼 교체가 목적인 쌍은 한 파일에 둔다. 분리하면 42710 이 난다 (실제 발생) |
| v0.240 | 문의 상세를 우측 도킹 패널 600px 로 재작성 — 리스트 위에 덮이며(position:absolute) 바깥 클릭 · ESC 로 닫힌다. 리스트 컬럼 7개 유지 |
| v0.240 | 탭 2개 — 문의/신청(좌 200 타임라인 + 우 400 상세) · 활동이력(평면 시간순 + 단계그룹 · 유형 칩) |
| v0.240 | 좌측 타임라인 최신 → 과거 정렬. 서비스 전환 배너는 시간순으로 계산해 해당 카드에 부착. 접수 1건이면 타임라인을 숨기고 상세만 전체 폭 |
| v0.240 | 활동 조회를 eq('inquiry_id', id) → in('inquiry_id', ids) 1회로 변경. 같은 사업자 안에서 카드를 옮기면 재조회하지 않는다 |
| v0.240 | 활동 추가는 문의/신청 탭에서만 — 선택된 문의가 곧 부모라 선택 단계가 없다. 수정 · 삭제는 두 탭 모두. 유형 10종 · stage_group 3종 입력값 검증 추가 |
| v0.240 | 리스트 사무소명 옆 다건 배지(_bizCount) — 클릭 전에 다건 사업자를 알 수 있다. 클라이언트 계산이라 추가 쿼리 없음 |
| v0.240 | updateInquiryStep 이 존재하지 않는 step 컬럼에 저장하던 버그 수정 → step_detail |
| v0.241 | 타임라인 카드 — 날짜를 배지 줄 우측에 진하게 이동. 시간 흐름이 먼저 읽히도록 |
| v0.241 | 카드 요약에서 서비스명 중복 제거 — inquiry_type 접두어를 떼면 배지와 같은 값이 남았다. 요금변경은 변경 전 → 플러스, 그 외는 거래처수만 표시 |
| v0.241 | _inqNarrative() 신설 — 문의내용에서 상단 표에 이미 나온 필드와 가입내역 블록을 제거하고 실제 문의 · 요금제변경 신청 내용만 남긴다. 남는 게 없으면 섹션을 숨긴다 |
| v0.242 | _parseJoin() 신설 — 정상 (231113, 스페셜요금제, 113개) 를 상태 · 가입일 · 요금제 · 거래처수로 분해. 8번 미확정 #10 의 부분 해결(표시 전용, 컬럼 분해는 미실시) |
| v0.242 | 거래처 표시를 서비스별로 분리 — 세모 113개(스페셜요금제) · 위멤 164개(프리미엄). 가입내역에 수치가 없으면 customer_count 로 폴백 |
| v0.243 | 거래처 표시에서 세모R 요금제 제거 — 서비스 배지(세모R · 세모R+)가 이미 등급을 나타내 중복이었다. 위멤버스 요금제는 별개 축이라 유지 |
| v0.244 | _cleanOffice() 신설 — 사무소명에 붙어 있던 유입코드 · 등록일 · 거래처수 접미어를 제거한다. 칠도세무회계_TAX_SEMO_UXM → 칠도세무회계, 선율회계법인(972개,5명)_250616_HP_LP → 선율회계법인. 앞쪽 접두어(로직지점_) 는 사무소명의 일부이므로 보존 |
| v0.244 | _officeLabel() — 사무소명 (인물명) 형태로 표시. 문의는 신청자명, 신청(가입) 은 대표자명을 쓴다. 리스트 · 패널 헤더 · 상세 표에 적용 |
| v0.245 | 접수가 1건일 때도 좌측 타임라인을 표시 — 이전에는 숨기고 상세만 전체 폭으로 썼다. 레이아웃이 건수에 따라 바뀌지 않는 편이 읽기 쉽다 |
| v0.246 | 상품 필터에서 성장패키지를 세모R+ 바로 아래로 이동 — 세모R+ 의 하위 프로모션이므로 목록 끝이 아니라 부모 옆에 둔다 |
| v0.247 | 기획서 10번 확장 — 헤더 단일 소스 필터 · 하단 푸터 · 문의 상세 패널 · 활동이력 탭 설계를 수록. 문의 건수 1,588 → 1,652 정정 |
| v0.247 | 11-A 전제 갱신 — 상세 패널이 생겨 남은 다섯 개는 입력 UI 추가로 범위가 좁아졌다. 9-D 활동 기록 출처(D10) 확정 반영 |
| v0.248 | 타임라인 카드에 활동 요약 노출 — 활동 2 대신 25.09.22 방문 컨설팅 형태로. 실측 문의 1건당 최대 3건(1건 25 · 2건 14 · 3건 2)이라 길어지지 않는다. 3건 초과는 +N 더 로 접는다 |
| v0.248 | join_* 오염 발견 — 이전 이관이 다음 줄 라벨을 값으로 잡았다 → 오진이었다. v0.251 정정 참조 |
| v0.249 | 활동 입력 · 수정을 패널 안 인라인 폼으로 교체 — prompt 연속 호출 방식을 없앴다. 활동 종류(선택) + 내용(여러 줄) 두 필드 |
| v0.249 | 기존 활동도 수정 을 누르면 그 자리에서 폼으로 바뀐다. 삭제 버튼 포함. 저장 후 활동만 다시 읽고(_actReload) 패널을 재렌더한다 |
| v0.249 | 수정 시 일자 · 단계그룹은 기존 값을 유지한다. 신규는 오늘 · 문의 로 들어간다 |
| v0.250 | 활동 일자 수정 권한을 출처로 가른다 — Flow 유입(f_task_no 有) 은 날짜 고정 + Flow 표시, CRM 직접 입력(f_task_no 없음) 은 날짜 입력란 제공 |
| v0.250 | Flow 활동의 날짜를 고치면 동기화 때 되돌아가고 원본과 어긋나 추적이 깨진다. 그래서 UPDATE 에서 activity_date 를 아예 제외한다 |
| v0.250 | 활동 행 메타에 출처(Flow · CRM 입력) 표시 — 수정 가능 범위를 열기 전에 알 수 있다 |
| v0.251 | v0.248 의 join_* 오염 진단을 철회한다. 화면에 위멤버스 = "- 링크패스 :" 로 보였으나 실측 결과 DB 는 정상이었다 — 브라우저의 오래된 데이터였다 |
| v0.251 | 실제 이상값은 자유입력 변형 8건 — () 3 · 미가입 1 · 가입 2 · 장기미사용정지 1 · 위멤버스 스탠다드 1. SQL 69 로 () · 미가입 · 해지 () 만 정리하고 의미 있는 값은 보존 |
| v0.252 | 상세 패널을 15% 확대 — 600 → 690px (좌 200 → 230 · 우 400 → 460). 문의내용 본문과 가입내역이 한 줄에 더 들어간다 |
| v0.253 | 리스트 배지를 문의 N · 활동 N 두 개로 분리 — 라벨은 작게, 숫자는 굵게. 1건인 사업자도 표시한다 (이전에는 2건 이상만 숫자 하나) |
| v0.253 | 활동 전체(crmAllActs) 를 문의와 함께 로드 — 리스트에 활동 수를 쓰려면 미리 있어야 한다. 59건 규모라 부담이 없다 |
| v0.253 | _bizStats() 집계 캐시 — 행마다 전체를 훑으면 전체보기(1,652행) 에서 2.7M 회 순회가 된다. 렌더 1회당 Map 을 한 번만 만든다 |
| v0.254 | 활동 배지를 활동 1/2 형태로 — 분자는 이번 항목의 활동, 분모는 이 고객과의 모든 활동. 같은 사업자의 다른 접수에 활동이 있는지 리스트에서 바로 보인다 |
| v0.255 | 메뉴탭을 좌측 세로 사이드바로 이동 — 헤더가 필터 전용이 된다. 기본 접힘 56px(아이콘 + 라벨), 펼침 152px. 접기 상태는 localStorage 에 저장 |
| v0.255 | 펼친 상태에서만 탭별 건수 표시 — 문의 1,652 · 컨설팅 63 처럼. crmInquiries 클라이언트 집계라 추가 쿼리가 없다. 접힌 상태에는 표시할 자리가 없다 |
| v0.255 | 본문을 #mainWrap(사이드바 + #contentWrap) 으로 감쌌다. 사이드바 토글 시 지도는 resize 를 트리거해 타일이 깨지지 않게 한다 |
| v0.256 | 헤더에 [전체 | 중복제외] 세그먼트 추가 (팀 앞) — 다건 사업자가 425개라 전체 목록에서 같은 사무소가 여러 줄 반복돼 혼란스럽다 |
| v0.256 | 중복제외는 필터를 먼저 걸고 적용한다 — 걸린 조건 안에서 최신 접수 1건만 남는다. 등록일 내림차순 배열의 첫 등장만 취해 최신 건이 남는다 |
| v0.256 | 건수 분모도 모드에 따라 바뀐다 — 전체는 1,652, 중복제외는 고유 사업자 1,094. 상태는 localStorage 에 저장 |
| v0.257 | 사용자 노출 문구에서 접수 → 문의 통일 — 패널 헤더 배지(접수 2건 → 문의 2건) · 리스트 배지 툴팁 · 중복제외 툴팁 |
| v0.257 | 접수구분 필터 라벨과 리스트 컬럼명은 그대로 둔다 — 이 카테고리의 값이 문의 · 신청(가입) · 요금변경 이라, 라벨을 문의 로 바꾸면 값과 같은 이름이 되어 모호해진다 |
| v0.258 | 타임라인 카드 우측에 단계 · 담당자 셀렉트 추가 — 상세로 들어가지 않고 목록을 보며 완료 처리하거나 담당자를 부여할 수 있다. 패널 690 → 840px (좌 230 → 380) |
| v0.258 | 담당자 배정은 기존 assignInquiry() 를 재사용 — 배정 시 컨설팅 대기로 자동 이동하는 규칙이 그대로 적용된다. 빈 값 선택은 배정 해제 |
| v0.258 | 셀렉트 영역에 stopPropagation — 카드 클릭(문의 전환) 과 겹치지 않게 한다 |
| v0.259 | 중복제외 키를 biz_no → biz_no + 문의서비스 로 변경. 다건 사업자 425개 중 109개가 서비스 2종 이상이라 기존 키는 살아있는 기회를 가렸다. 기준 조합 1,094 → 1,207 |
| v0.259 | 칸반에도 중복제외 적용 — 전체 집합에 먼저 적용한다. 컬럼별로 적용하면 같은 고객이 여러 컬럼에 남는다. 활성화 위험 패널도 같은 소스를 쓴다 |
| v0.259 | 자동 실패 규칙 보강 (D2) — 기산 단위는 사업자번호 + 문의서비스. 재문의 시 그 일자부터 단계가 문의로 재시작하며 이전 건을 완료로 닫지 않는다 |
| v0.259 | _dedupRows 가 입력 정렬에 의존하지 않도록 내부 정렬 추가 — 칸반은 등록일 순서가 보장되지 않는다 |
| v0.260 | 타임라인 단계 · 담당자 셀렉트를 글씨 폭만큼만 축소 — 고정 132px → width:auto (최대 96px). 남는 공간은 카드가 가져가 활동 요약이 더 잘 보인다 |
| v0.260 | 기본 화살표를 appearance:none + 인라인 SVG 캐럿으로 교체 — 브라우저 기본 화살표가 차지하는 여백을 없애기 위해서다. 담당자명이 잘릴 수 있어 툴팁에 전체 값을 노출 |
| v0.261 | 중복제외 키를 모드별로 분리 — 전체 = biz_no + 문의서비스(1,207) · 중복제외 = biz_no(1,094). 두 모드 모두 그룹 최신 1건만 표시하며 전체 이력은 상세 패널 타임라인에서 본다 |
| v0.261 | 리스트 · 칸반 · 활성화 위험 패널이 모두 같은 기준을 쓴다 |
| v0.262 | 담당자 표시 규칙 확정 — Flow 값이 한글이름/영어닉 형태면 로움 직원이므로 닉네임만(박승현/kevin → kevin), 슬래시가 없으면 외부이므로 한글 이름을 그대로 쓴다 |
| v0.262 | 복수 담당자는 각각 변환해 이어붙인다 — 정상균/Tony, 김성기, 박원덕 → Tony, 김성기, 박원덕. 실측 44종 전부 정상 변환 |
| v0.262 | 저장값은 원본을 유지한다. 표시만 바꾼다 — Flow 동기화 값과 어긋나면 담당자 매칭이 깨진다. 셀렉트 option value 도 원본이며 라벨만 표시명이다. 리스트 담당자 칸에 마우스를 올리면 원본이 툴팁으로 나온다 |
| v0.263 | 담당자가 여러 명이면 김성기+2 형태로 축약 — 첫 사람 + 나머지 수. 마우스를 올리면 전체가 툴팁으로 나온다. 리스트 · 상세 · 활동 메타에 적용 |
| v0.263 | 가입 판정 기준 확정 — 고객 데이터 업로드의 서비스명 + 가입일자가 소스다. 자동 실패 구현은 업로드 기능 이후로 미룬다 (2번 표 반영) |
| v0.264 | Data 등록 신설 (kevin 메뉴) — 파일 4종을 올려 CRM 에 반영한다. 화면에 표시하는 일자는 업로드 일자가 아니라 파일 내용의 최신 일자(data_max_date) 다. 그래야 “이 일자부터 다시 받으면 된다” 가 성립한다 |
| v0.264 | 1단계 — 플로우 Data 파싱 · 반영 연결. 9-C 판정 규칙을 JS 로 포팅했고 실측 1,745행에서 SQL 결과와 완전히 일치(문의 1,652 · 활동 59 · 제외 28) |
| v0.264 | 고객 · 정산(자동이체 · 카드) · 상담 Data 는 파일 선택 시 행수 · 컬럼 · 기간만 미리보기. 파싱 규칙 확정 후 연결한다 (2단계) |
| v0.264 | 엑셀 파싱에 SheetJS CDN 추가. 반영은 f_task_no 기준 upsert 이며 200행씩 나눠 보낸다 — Supabase 요청당 행 수 제한 때문이다 |
| v0.265 | 미리보기가 사라지던 버그 수정 — _duPick 이 DOM 에 직접 쓴 뒤 _duRender 가 전체를 다시 그려 지워졌다. 미리보기 HTML 을 상태에 담고 렌더 함수가 그리도록 변경 |
| v0.265 | 반영 결과를 카드에 남긴다 — 토스트는 사라져서 실패 원인을 놓친다. code · message · details · hint 를 모두 표시하고, 이력 저장 실패도 따로 보여준다 |
| v0.265 | 미연결 3종 미리보기에 컬럼 전체를 표시 — 8개까지만 보여 매핑 확정에 필요한 정보가 잘렸다 |
| v0.266 | Data 등록 카드 높이 축소 — 제목과 설명을 한 줄로 합치고 여백 · 버튼 크기를 줄였다. 카드당 약 40% 낮아져 4종이 스크롤 없이 들어온다 |
| v0.267 | Data 등록에 쓰기 권한 진단 배너 추가 — DEV_MODE = true 라 로그인을 건너뛰고 anon 롤로 접속하는데, anon 은 SELECT 만 가능해 반영 · 단계 변경 · 활동 저장이 모두 조용히 실패한다 |
| v0.267 | 반영 전 세션을 확인해 미리 차단한다 — 1,652건을 전송하고 전부 실패하는 낭비를 막는다 |
| v0.267 | 실측 확인 — 업로드 이력 0건 · 최근 2시간 갱신 0건 · last_registered 가 파일(08-11) 이 아닌 08-03 에 머물러 반영이 되지 않았음이 확정됐다 |
| v0.268 | DEV_MODE = false 적용 — 구글 OAuth 로그인 후 authenticated 롤이 되어 쓰기 권한이 열린다. 8번 미확정 #8 해소 |
| v0.268 | ?dev=1 비상 우회 유지 — OAuth 설정 전 잠기는 것을 막는다. 우회 상태에서는 상단에 읽기 전용 경고 띠가 뜬다 |
| v0.268 | 로그인 도메인 제한 — @roumit.com 이 아니면 즉시 로그아웃. Supabase 는 누구나 구글로 가입할 수 있어 제한이 없으면 외부인이 고객 데이터에 접근한다 |
| v0.268 | 서버측 방어 — RLS 정책을 authenticated full access 에서 roumit members full access 로 교체 (SQL 93~95). 화면단 검사는 우회 가능하므로 이것이 실제 방어선이다 |
| v0.269 | 임시 조치 — anon 쓰기로 전환. 로그인은 나중에 적용한다. DEV_MODE 기본값을 true 로 되돌리고, 전환 시험용으로 ?login=1 스위치를 남겼다 |
| v0.269 | SQL 86 · 88 · 89 · 90 · 91 · 92 로 anon 에 문의 · 활동 · 업로드이력 쓰기 권한과 정책 부여. 되돌리기는 87 · 96 · 97 |
| v0.269 | 8번 #8 과 보안 경고를 현재 상태로 갱신 — anon 키로 고객 데이터 조회 · 수정이 가능한 상태임을 명시. 공개 호스팅 전 필수 조치 3단계를 적었다 |
| v0.270 | 활동 내용이 두 번 나오던 버그 수정 — f_title 이 없으면 제목 자리에 content 를 쓰는데 아래에서 content 를 또 출력했다. CRM 직접 입력 활동은 f_title 이 없어 항상 중복됐다 |
| v0.270 | 담당자가 이메일이면 @ 앞부분만 표시 — kevin.park@roumit.com → kevin.park. CRM 직접 입력은 로그인 이메일이 담당자로 들어간다 |
| v0.271 | 리드 탭 진입 시 단계를 리드 로 설정 — 컨설팅 · 온보딩 · 활성화는 뷰가 step_group 을 자동으로 거는데 리드 탭만 전체가 나와 일관성이 없었다. 지울 수 있는 소프트 필터라 전체 조회도 그대로 가능하다 |
| v0.271 | 탭을 옮길 때만 값을 손댄다 — 들어온 뒤 사용자가 바꾼 단계는 재렌더에도 유지된다. 다른 문의 뷰로 가면 단계 필터를 비워 뷰의 그룹 조건과 충돌하지 않게 한다 |
| v0.272 | 담당자 이원화 원칙 기록 (3번 · 8번 #21) — Flow 담당자는 임시담당자이고 CRM 담당자와 성격이 다르다. flow_assignee 와 assignee 를 매핑하지 않는다. 코드는 이미 분리돼 있어 원칙만 명문화 |
| v0.272 | 상세 패널에 Flow 임시담당자 행 추가 — 값이 있을 때만 표시한다. CRM 담당자와 나란히 보여 혼동을 막는다 |
| v0.272 | 미확인 건 처리 정정 — 3개월 경과분을 완료로 닫으려던 것을 취소. 단계가 비어 있던 건은 리드 / 신규접수 로 두고, 고객 데이터 업로드 후 실패 · 가입 중 하나로 확정한다 (2번 표 반영) |
| v0.273 | 페이지당 건수에 자동 추가 — 화면 높이에서 헤더 · 푸터를 뺀 뒤 실제 행 높이로 나눠 한 화면에 딱 맞는 수를 쓴다. 행 높이는 렌더 후 측정해 갱신하고, 창 크기가 바뀌면 다시 계산한다. 기본값을 자동으로 했다 |
| v0.273 | 드롭다운 건수와 하단 건수가 어긋나던 문제 수정 — 드롭다운은 중복제외 전을 세고 하단은 중복제외 후를 세서 문의 (150) vs 141건 이 됐다 |
| v0.273 | _facetCount() 신설 — 드롭다운 건수를 패싯 방식으로 계산한다. 다른 필터는 적용하고 세려는 필터 자신만 뺀 뒤 _dedupRows 를 통과시킨다. 상단 [전체 | 중복제외] 선택값이 그대로 반영되며, 이제 드롭다운 숫자가 곧 선택 시 보일 건수다 |
| v0.274 | 단계 드롭다운을 step_group 기준으로 교체 — 이전 목록(문의 · 접촉 · 가입 · 셋업…)은 DB 실제값(신규접수 · 담당자배정 · 대기 · 정상)과 겹치는 것이 완료 하나뿐이라, 값이 있어도 “단계 미설정”으로 보였다 |
| v0.274 | updateInquiryStep 이 step_group 과 step_detail 을 함께 저장하도록 변경. 같은 그룹 안에서는 기존 세부단계를 유지해 담당자배정이 신규접수로 되돌아가지 않는다. 실패 시 이전 값으로 되돌린다 |
| v0.274 | STEP_COLOR 에 세부단계 색 추가 — 신규접수 · 담당자배정 · 대기 · 정상 이 회색으로만 보이던 문제 |
| v0.275 | Flow 유형 (레거시) 행에 불일치 경고 추가 — 현행 inquiry_service 와 어긋나면 ⚠ 서비스와 불일치 · 참고용 을 붙인다. 라벨도 흐리게 처리해 배지와 구분한다 |
| v0.275 | 18962 오인 사례 — 배지는 세모R 로 정상인데 레거시 행의 신규 세모R+ 를 서비스로 오해했다. 이 값은 신규가입 · 재가입 구간에서 780건이 틀리다 (9-D) |
| v0.276 | 단계 전면 재계산 완료 — 문의/신규접수 1,599 · 문의/담당자배정 47 · 활성화/정상 6. 판정 규칙을 2번 · 8번 #22 에 명문화 |
| v0.276 | 담당자 배정 시 컨설팅 자동이동 제거 — 문의 / 담당자배정 까지만 간다. 배정만으로 컨설팅에 넣으면 아직 접촉도 안 한 건이 쌓인다. 재계산 규칙과도 어긋났다 |
| v0.277 | v0.276 을 되돌린다 — 담당자 배정 시 컨설팅 / 대기 로 자동 이동한다. 배정은 담당자가 맡아 컨설팅에서 확인하고 방문을 진행한다는 뜻이므로, 문의 컬럼에는 아직 아무도 잡지 않은 건만 남는 편이 대기열로서 분명하다 |
| v0.277 | 판정 규칙에 담당자 축 추가 — 요금변경 → 활성화/정상 · 담당자 있음 → 컨설팅/대기 · 그 외 → 문의/신규접수. 담당자배정 세부단계는 쓰지 않는다 |
| v0.278 | 전체 는 묶지 않는다 — 이전에는 전체 모드도 biz_no + 문의서비스 로 묶어 1,652 중 445건이 어디에서도 보이지 않았다. 전체 는 그 메뉴 안의 모든 건을 뜻한다 |
| v0.278 | 중복제외 키를 biz_no 로 단순화. 하단 분모도 뷰 조건만 적용한 수로 바꿨다 — 사용자 필터를 뺀 “해당 메뉴에서의 전체” |
| v0.279 | 좌측 탭을 옮기면 헤더 단계 필터가 따라간다 — 문의→문의 · 컨설팅→컨설팅 · 온보딩→온보딩 · 활성화→활성화 · 상담/고객→전체. VIEW_STAGE 로 한 곳에서 관리한다 |
| v0.279 | 고객 탭 재작성 — 데모 데이터를 버리고 crm_inquiries 기반으로 바꿨다. 사업자당 한 줄(최신 접수가 대표) 이며 사무소명 가나다순. 중복제외 세그먼트와 무관하게 항상 묶는다 — 고객 목록이라는 성격 자체가 그렇다 |
| v0.279 | 고객 탭에서 접수구분 필터를 숨긴다 — 고객당 한 줄이라 접수 단위 필터가 맞지 않는다. 분모도 고유 사업자 수(1,094) 를 쓴다 |
| v0.280 | inquiry_type 화면 표시 전부 제거 — 상세 패널 Flow 유형 행 · 칸반 카드 부제 · 활동 메타. 신규가입 · 재가입 구간이 실제 서비스와 무관하게 세모R+ 로 찍혀 780건이 틀렸고, 실제로 두 번 오인을 일으켰다 |
| v0.280 | 값을 재생성하지 않은 이유 — {접수구분} {서비스} 로 다시 만들면 request_kind + inquiry_service 의 복사본이 된다. 배지가 이미 정확하므로 표시를 없애는 편이 맞다. 컬럼은 DB 에 남긴다 |
| v0.280 | COLS 에서 inquiry_type 제외 — 읽지 않는 컬럼을 매번 실어 나를 이유가 없다 |
| v0.281 | Data 등록 반영이 핵심 필드만 보내던 누락 수정 — 사무소명 · 대표자 · 신청자 · 연락처 · 이메일 · 주소 · 거래처수 · 담당자수 · 유입경로 · 유입코드 · 가입내역 4종을 모두 채운다. 이전 반영 건은 사무소명이 비어 있었다 |
| v0.281 | 주소 라벨이 주 소 처럼 공백 여러 칸이라 주 ?소 로는 못 잡았다. 주[ \t]*소 로 고쳐 누락 1,633 → 83건 |
| v0.281 | step_group · step_detail · assignee 는 일부러 보내지 않는다 — 기존 행의 진행 상태와 CRM 담당자를 덮어쓰지 않기 위해서다. 신규 행은 DB 기본값이 채운다 |
| v0.282 | 새로고침 시 단계 필터가 안 걸리던 문제 수정 — setView 는 탭을 옮길 때만 돌아서 첫 로드에는 적용되지 않았다. _initInquiryFilters 에서 뷰가 바뀔 때 한 번만 적용하도록 보완. 사용자가 바꾼 값은 그대로 유지된다 |
| v0.283 | 상세 패널 하단에 삭제 추가 — is_deleted 플래그만 세우는 휴지통 방식이다. 물리 삭제하지 않는다. 활동이 inquiry_id 로 물려 있어 고아가 생긴다 (6번 원칙) |
| v0.283 | 설정 화면 신설 — BP 환경설정과 같은 5탭 구성(히스토리 · 휴지통 · 사용자 · 관리자 · 감사로그). 휴지통부터 동작하며 되돌리기를 지원한다. 나머지는 사용자 · 팀 · 권한 테이블 설계 확정 후 붙인다 |
| v0.283 | 삭제 후 같은 사업자에 남은 건이 있으면 패널을 유지하고 없으면 닫는다 — 여러 건을 연속으로 정리할 때 매번 다시 열지 않아도 된다 |
| v0.284 | 설정 › 사용자 탭 구현 — BP 와 같은 테이블(employees · teams · admins)을 공유한다. 사람을 두 곳에서 관리하지 않는다. 승인 대기자는 상단에 별도 섹션으로 분리 |
| v0.284 | 회사 · 부서 2단 구조 — 회사(crm_companies) 아래에 부서(teams, BP 기존 목록). BP 의 teams 에 회사를 넣지 않는다 — 넣으면 BP 메뉴 분류에 섞인다 |
| v0.284 | 설정 › 회사 탭 — 추가 · 이름변경 · 삭제. 삭제 처리는 DB 가 한다 — ON DELETE SET NULL 이라 소속자가 자동으로 미지정이 된다. 앱이 일일이 옮기지 않는다 |
| v0.284 | 사용자 목록에서 회사 · 부서를 인라인 셀렉트로 바로 바꾼다. 상단 회사 필터에 각 회사별 인원수를 함께 표시 |
| v0.285 | 헤더 검색을 돋보기 아이콘으로 접었다 — 누르면 입력창이 펼쳐지고 자동으로 포커스가 간다. 필터가 늘어 헤더가 빽빽해진 것을 덜어낸다 |
| v0.285 | 검색어가 있으면 접지 않는다 — 걸린 조건이 숨으면 왜 결과가 적은지 알 수 없다. 바깥을 눌러도 검색 중이면 열린 채 유지되고, 지우기를 누르면 접힌다 |
| v0.285 | 접힌 상태에서도 검색 중이면 아이콘을 파랗게 표시 — 조건이 걸려 있다는 신호를 남긴다 |
| v0.286 | 설정 › 히스토리 탭 구현 — 누가 · 언제 · 무엇을 시간순으로 본다. crm_stage_history(단계 변경) 와 crm_inquiry_activities(활동) 를 각각 읽어 클라이언트에서 병합한다. 조인하면 한쪽이 없을 때 행이 사라진다 |
| v0.286 | 드롭다운 단계 변경도 이력에 남긴다 — 지금까지 칸반 드래그(_applyStageMove) 만 기록해 절반이 비어 있었다. _logStage() 로 한 곳을 지나게 했다 |
| v0.286 | 삭제 · 복구도 기록한다. 이력 기록 실패가 본 작업을 막지 않도록 예외를 삼킨다 — 부수 기록이 주 작업을 되돌리면 안 된다 |
| v0.286 | 같은 값으로 단계를 다시 지정하면 이력을 남기지 않는다 — 실수로 같은 항목을 눌러도 기록이 더럽혀지지 않는다 |
| v0.287 | 설정에서 회사 추가 · 삭제가 즉시 반영되지 않던 버그 수정 — _stUsersLoad 가 사용자 탭일 때만 다시 그려서, 회사 탭에서는 아무 일도 없는 것처럼 보였다 |
| v0.287 | 부서를 회사별로 나눈다 — teams.company_id 로 소속을 정하고, 회사를 바꾸면 그 회사의 부서만 목록에 나온다. company_id 가 없는 부서는 공통으로 보고 어디서나 표시 |
| v0.287 | 회사를 옮길 때 새 회사에 없는 부서면 자동 해제 — 목록에 없는 값이 저장된 채로 남지 않게 한다 |
| v0.287 | 헤더 팀 → 회사. crm_companies 에서 목록을 만들고 회사별 문의 건수를 함께 표시한다. 필터는 담당자의 소속 회사로 건다 — 문의 자체에는 회사가 없기 때문이다 |
| v0.288 | 설정에서 사용자가 안 보이던 문제 — Promise.all 로 네 테이블을 묶어 읽어서 하나라도 실패하면 전부 비었다. 테이블별로 성공 · 실패를 따로 잡도록 바꿨다 |
| v0.288 | 조회 오류를 화면에 표시한다 — 어느 테이블이 어떤 코드로 막혔는지 보여준다. 이전에는 토스트로 한 번 스치고 사라져 원인을 알 수 없었다 |
| v0.288 | teams.company_id 가 아직 없어도 동작하도록 보완 — 컬럼이 없으면 부서를 전부 공통으로 본다. SQL 127 실행 전에도 화면이 비지 않는다 |
| v0.289 | 담당자 목록을 직원 명부(employees) 기준으로 교체 — 이전에는 문의에 적힌 문자열을 모아 써서 헤나 처럼 명부에 없는 표기가 굳어졌다. 그 결과 회사 필터가 모두 0 으로 나왔다 |
| v0.289 | 저장값은 nickname 으로 통일. 목록에는 이혜미 henna 처럼 이름을 함께 보여주되 저장은 닉네임 그대로다 |
| v0.289 | 명부를 못 읽으면 기존 값으로 대체한다 — 권한 문제로 employees 가 비어도 배정 기능이 멈추지 않게 |
| v0.290 | 담당자 필터에 건수 표시 — 상품 · 단계와 같은 패싯 방식이다. 다른 필터를 적용한 뒤 자기 자신만 빼고 세므로, 드롭다운 숫자가 곧 그것을 골랐을 때 보일 건수다 |
| v0.290 | 담당자 목록을 명부 + 실제 배정값 합집합으로 — 명부에 없는 표기가 남아 있으면 ⚠ 를 붙여 함께 보여준다. 목록에서 빠지면 그 건들을 걸러낼 방법이 사라진다 |
| v0.291 | 담당자 목록에서 배정할 수 없는 사람을 뺀다 — 승인 대기(approved=false) · 거절(rejected_at) · 외부 기간 만료(access_until) · 시작 전(access_from). 승인되지 않은 사람에게 일을 맡길 수 없다 |
| v0.291 | 이미 배정된 값은 계속 보여준다 — 나중에 퇴사 · 만료되어도 그 건들을 걸러낼 방법이 남아야 한다 (⚠ 표시) |
| v0.292 | 담당자 셀렉트에 추천 구획 추가 — 같은 서비스 · 같은 단계에서 실제로 많이 배정된 사람을 위로 올린다. 배정 횟수를 함께 표시한다 |
| v0.292 | 조합이 맞는 이력이 없으면 단계만으로 다시 센다. 그것도 없으면 추천을 내지 않는다 — 근거 없는 추천은 방해가 된다. 추천 구획 이름에 근거를 적는다(추천 · 세모R+ 컨설팅) |
| v0.292 | 추천에는 배정 가능한 사람만 넣는다 (v0.291 기준). 배정할 때마다 색인을 비워 순위가 곧바로 반영된다 |
| v0.293 | 필터를 바꿔도 드롭다운 건수가 갱신되지 않던 버그 수정 — _initInquiryFilters() 가 데이터 로드 직후에만 돌았다. 중복제외를 켜면 하단은 1,076 인데 드롭다운은 1,606 그대로였다 |
| v0.293 | applyFilters() 가 건수 재계산까지 맡는다. 회사 · 채널 필터도 같은 경로를 타게 해 모든 필터가 한 곳을 지나간다 |
| v0.293 | 탭을 옮기면 _stageInitView 를 비운다 — 단계 기본값이 다시 적용되도록 |
| v0.294 | 고객원장 신설 — crm_customers (49컬럼). 세무사무소목록 엑셀 93컬럼 중 화면에 쓰는 33개 항목을 담는다. 키는 biz_no 이며 재업로드 시 upsert 로 갱신된다 |
| v0.294 | Data 등록 › 고객 Data 파싱 · 반영 연결. 실측 900행 전부 파싱 성공 (제외 0). 엑셀의 - 를 NULL 로 바꾸고 날짜 · 숫자를 형변환한다 — 문자열로 두면 정렬 · 비교 · 판정이 성립하지 않는다 |
| v0.294 | 날짜 형식 3종을 모두 받는다 — 2023-10-24 · 20231024 · 25-04-01(정지일시). 한 파일 안에서도 컬럼마다 표기가 다르다 |
| v0.294 | 고객 반영과 단계 판정을 분리한다 — 원장을 먼저 정확히 채우고 판정은 별도로 실행한다. 한 번에 하면 잘못됐을 때 되돌리기 어렵다 |
| v0.295 | 고객원장 키를 biz_no → 이용기관아이디 로 변경. 실측 900행 중 사업자번호는 886개(중복 12) 이고 이용기관아이디는 900개 전부 고유하다 |
| v0.295 | 한 사업자에 여러 이용기관이 붙는다 — 지점 · 팀 분리(세무법인송촌 본점/4팀/5팀) 와 재가입(위너세무회계 24-08 가입 → 24-10 해지 → 26-03 재가입). biz_no 를 키로 쓰면 이 이력이 사라지고 upsert 도 21000 으로 실패한다 |
| v0.295 | 고객 색인을 사업자당 배열로 바꿨다. 정상 > 정지 > 해지, 같으면 가입일 최신 순으로 정렬해 첫 번째를 대표로 삼는다. 지점이 여럿이어도 현재 상태를 정확히 고른다 |
| v0.296 | 상세 패널에 고객 탭 신설 — 사업자 · 서비스 이용 · 보고서 · 거래처 · 셋업 · 카카오채널 · 판정 근거 7구획. 탭 순서는 고객 → 문의/신청 → 활동이력 |
| v0.296 | 지점 · 재가입이 여러 건이면 상단에서 골라 본다. 기본은 대표 1건(정상 > 정지 > 해지 · 가입일 최신). 사업자가 바뀌면 선택을 처음으로 되돌린다 |
| v0.296 | 좌측 고객 탭에서 들어오면 상세도 고객 탭부터 연다 — 고객 목록을 보다 한 줄을 눌렀는데 문의 이력이 먼저 뜨면 맥락이 끊긴다 |
| v0.296 | 고객원장에 없는 사업자는 탭을 흐리게 표시. 여러 건이면 고객 3 처럼 수를 붙인다. 인사이트 요금제 칸에는 홈피신청 · 자체홈피 를 쓴다 — 인사이트 가입자만 값이 생긴다 |
| v0.297 | 컨설팅 세부단계 7종 — 대기 · 접촉 · 접촉실패 · 컨설팅예약 · 예정 · 완료 · 실패. 저장값에 공백을 넣지 않고 화면에서만 넣는다 — 공백이 들어가면 비교 · 필터에서 실수가 생긴다 |
| v0.297 | 가능성은 단계와 별개 축(possibility) — 높음 · 보통 · 낮음. step_detail 에 예정 높음 처럼 합치지 않는다. 접촉 중에도 매길 수 있어야 파이프라인 예측이 성립한다. 예정에서 벗어나면 자동으로 비운다 |
| v0.297 | 컨설팅 화면에 세부단계 필터 줄 — 세부단계 7종 · 예정 가능성 3종 · 잠재 4종. 예정 · 잠재는 라벨로 묶고 색을 달리해 성격을 구분한다 |
| v0.297 | 잠재 목록 · 상세 · 배정 — 잠재를 누르면 목록 구성 자체가 바뀐다(문의가 아닌 다른 테이블). 잠재-DB 12,135건은 누를 때 한 번만 읽는다 |
| v0.297 | 배정 = 문의 생성 + 잠재 전환. match_method = manual 로 남겨 자동 전환분(addr_core_v4) 과 구분한다. 출처를 활동으로 기록해 전환 후에도 어디서 왔는지 알 수 있다 |
| v0.298 | 잠재 DB 건수가 0 으로 보이던 버그 수정 — 목록을 누를 때 읽는 배열의 길이를 쓰고 있었다. head 조회로 건수만 미리 받는다. 12,135행을 내려받지 않으므로 즉시 끝난다 |
| v0.298 | 잠재 위멤 · 해지 는 사업자당 대표 1건만 센다 — SQL 로 900행을 세면 318 이지만 지점 · 재가입이 섞인다. 접촉 대상으로 같은 사무소를 두 번 넣으면 안 되므로 대표 기준 305 가 맞다 |
| v0.299 | 줄 수를 줄이는 배치로 전환 — 구획 제목을 없애고 라벨을 왼쪽으로 뺐다. 값은 가로로 늘어놓는다. 고객 탭이 약 32줄 → 11줄 로 줄어 스크롤 없이 한 화면에 들어온다 |
| v0.299 | 현재 단계를 패널 헤더 우측에 상시 표시 — 탭을 옮겨도 남는다. 판정 근거도 함께 담아 아래쪽 단계 판정 근거 구획을 없앴다. 단계 · 세부단계 · 가능성을 바꾸면 즉시 갱신된다 |
| v0.299 | 보고서 · 부가세 · 거래처를 라벨 + 가로 카드로 — 보고서 3줄 + 부가세 4줄이 2줄, 거래처 6줄이 2줄이 됐다. 거래처는 위멤버스 · 세모 로 분리해 라벨에 표기 |
| v0.300 | 주소 옆에 복사 버튼 — 지도에 붙여넣어 쓴다. clipboard 가 막히면 옛 방식으로 넘어가고, 그것도 실패하면 안내한다. 누르면 복사됨 으로 1.4초간 바뀐다 |
| v0.300 | 모바일에서는 공유 버튼도 함께 — navigator.share 가 있을 때만 표시한다. 없는 곳에 두면 눌러도 아무 일이 없어 고장으로 보인다. 제목에 사무소명을 넣어 어디 주소인지 알 수 있게 한다 |
| v0.300 | viewport 를 width=1600 으로 고정 — 모바일에서도 축소된 PC 화면을 보여준다. 칸반 · 리스트 · 상세 패널이 넓은 폭을 전제로 설계돼 있어 반응형으로 다시 짜는 대신 확대해 보는 방식을 택했다 |
| v0.300 | onclick 에 주소를 넣을 때 JS 문자열 + HTML 속성 두 단계로 이스케이프한다. 따옴표 · 개행 · & 가 섞이면 속성이 잘려 버튼이 죽는다 |
| v0.301 | 문의 좌표 보완 추가 (kevin 메뉴) — 주소가 있고 좌표가 없는 건을 지오코딩한다. 기존 _geocodeSmart 의 3단 폴백(원본 → 괄호 · 호수 제거 → 도로명만) 을 그대로 쓴다 |
| v0.301 | 실측 — 좌표 없는 문의 198건 중 주소가 있는 것은 53건. 나머지 145건은 주소가 없어 어떤 방법으로도 좌표를 구할 수 없다 (8번 미확정 #12 주소 필수화 필요) |
| v0.301 | 잠재 좌표를 옮기는 방법은 쓸 수 없다 — linked_inquiry_id 로 연결된 495건은 이미 좌표가 채워져 남은 건과 겹치지 않는다. 그래서 주소를 직접 변환한다 |
| v0.301 | 실패분은 콘솔에 console.table 로 남긴다 — 주소 표기를 손으로 고쳐야 하는 건들이라 목록이 필요하다 |
| v0.302 | 칸반 실패 컬럼이 비어 있던 버그 수정 — 판정 규칙은 세부단계를 실패 로 저장하는데 KB_LOST_DETAILS 에는 접촉실패 · 가입실패 만 있었다. 511건이 어디에도 보이지 않았다 |
| v0.302 | 칸반 활성화 전수를 항상 표시 — 이전에는 위험 항목이 모두 - 일 때 헤더 숫자까지 숨겼다. 400건이 있는데 컬럼이 비어 보여 반영이 안 된 것으로 읽혔다 |
| v0.302 | 활성화 컬럼에 전체 목록 줄 추가 — 위험 집계가 비어 있어도 400건을 목록으로 볼 수 있다. 위험 기준은 고객원장 연동 후에 채워진다 |
| v0.303 | 위험 집계 3종 구현 — 고객원장이 들어와 계산이 가능해졌다. 거래처 위험(semo_joined) · 발송수 위험(report_done) · 인증서 위험(cert_realtime · cert_auto 미등록 · 만료) |
| v0.303 | 발송 감소는 null 로 둔다 — 전월 대비 비교가 필요한데 고객원장에 월별 데이터가 없다. 값을 억지로 만들지 않는다. 계산 못 하는 것과 0 은 다르다 |
| v0.303 | 위험 줄을 눌렀을 때 목록이 실제로 걸러지지 않던 문제 수정 — _riskFilter 를 설정하기만 하고 필터링에 쓰지 않았다. 활성화를 벗어나면 자동 해제된다 |
| v0.303 | 인증서 위험에서 자동수집 인증서를 제외한다 — 실측 정상 고객 390건 중 자동수집이 만료 226 · 미등록 152 로 애초에 쓰지 않는 고객이 대부분이다. 포함하면 378/390 이 위험이 되어 지표가 무의미해진다. 실시간만 보면 25건이고 그게 실제 조치 대상이다 |
| v0.304 | 위험 판정 대상을 현재 이용 중인 고객으로 바꿨다 — 문의 단계(활성화 400건) 가 아니라 고객원장 상태(정상 390건) 를 쓴다. 활성화는 “문의가 전환된 건” 이고 위험 관리는 “지금 쓰고 있는 고객” 을 봐야 한다 |
| v0.304 | 위험 있음 · 정상 두 줄 추가 — 항목별 건수는 서로 겹쳐 합계가 전체와 맞지 않는다. 한 고객이 셋에 동시에 걸릴 수 있다. 실제 조치 대상은 합집합이고 안심할 수 있는 수는 여집합이다 |
| v0.304 | 실측 (기준값 20) — 전체 390 · 위험 있음 123 · 정상 267. 위험 1개 56 · 2개 56 · 3개 11건으로 겹침이 상당하다 |
| v0.304 | 활성화 목록도 같은 기준으로 걸러 헤더 · 항목 숫자와 어긋나지 않게 했다. 문의 단계가 활성화여도 원장에서 해지 · 정지면 대상이 아니다 |
| v0.305 | 고객 탭을 고객원장 기준으로 바꿨다 — 가입 이력이 있는 고객만 나온다. 이전에는 문의 목록을 사업자별로 묶어 보여주어 가입한 적 없는 사업자도 섞여 있었다 |
| v0.305 | 상단에 상태 필터 추가 — 전체 · 정상 · 해지 · 정지. 기본은 정상이다. 지금 쓰고 있는 고객이 먼저 보여야 한다 |
| v0.305 | 검색 중에는 상태 필터를 무시한다 — 필터를 잊고 검색해 “없다” 고 오해하는 일을 막는다. 검색어가 있으면 필터 줄에 검색 중 — 상태 무관 전체에서 찾습니다 를 표시한다 |
| v0.305 | 컬럼을 사무소명 · 상태 · 서비스 · 거래처 · 최근 접수 · 가입일 로 바꿨다. 이용 서비스는 원장에서 계산해 여러 개를 함께 표시한다. 문의가 없는 고객은 문의 없음 으로 표시하고 클릭을 막는다 |
| v0.306 | 고객 탭을 편집 가능하게 — 사무소명 · 대표자 · 신청자 · 연락처 · 이메일 · 거래처 · 주소 · 방문메모 8개 항목. 가입 이력이 없어도 열리며 문의 값을 채워 보여준다 |
| v0.306 | 저장 위치는 crm_inquiries 다 — crm_customers 는 엑셀이 주인이라 손으로 고쳐도 다음 고객 파일 업로드가 덮어쓴다. 원장 값(상태 · 요금제 · 보고서 · 인증서) 은 읽기 전용으로 둔다 |
| v0.306 | 고친 칸은 노랗게 표시하고 저장 버튼은 수정이 있을 때만 나온다. 무엇을 고쳤는지 사무소명 · 연락처 수정됨 처럼 함께 보여준다 |
| v0.306 | 저장 없이 닫기 · 탭 이동 · 다른 고객 선택 시 확인을 띄운다 — 입력 계속 · 저장 안 함 · 저장 세 갈래. 무엇을 잃는지 항목명을 적는다 — “저장하지 않은 내용이 있습니다” 만으로는 판단할 수 없다 |
| v0.307 | 활성화 위험 패널 발송 감소 아래에 중복 안내 한 줄 — 위험 대상 합계는 중복 대상이 있어 다를 수 있습니다. 한 고객이 세 항목에 동시에 걸릴 수 있어 합이 전체와 맞지 않는데, 적어두지 않으면 숫자가 틀린 것으로 읽힌다 |
| v0.308 | 목록으로 보기 버튼 제거 — 완료 · 정지 · 해지 통계와 활성화 위험 패널 양쪽에서 없앴다. 건수를 누르면 그 조건으로 목록이 열린다. 숫자를 보고 바로 누르는 것이 자연스럽고 줄도 하나 줄어든다 |
| v0.308 | 통계 행을 누르면 단계 + 기간으로 걸러 본다 — 이번 달 · 최근 3개월 · 올해 · 누적. 건수가 0 인 행은 누를 수 없고, 선택된 행은 파랗게 표시된다 |
| v0.308 | 기간 판정에 updated_at 을 쓴다 — SQL 로 일괄 판정한 건들은 crm_stage_history 에 기록이 없어 이력만으로는 기간을 가릴 수 없다 |
| v0.308 | 이전 _listStageJump 는 선언만 있고 실제 필터에 쓰이지 않았다 — 버튼을 눌러도 활성화 전체가 나왔다. _stageJump 로 바꾸며 위험 필터와 서로 배타적으로 동작하게 했다 |
| v0.309 | 완료 → 신규고객 으로 바꾸고 가입 실적을 보여준다. 기준 날짜는 고객원장의 joined_at 이다 |
| v0.309 | 기간 집계를 고객원장 날짜로 바꿨다 — 신규고객 joined_at · 정지 paused_at · 해지 canceled_at. crm_stage_history 는 SQL 일괄 판정분이 없어 기간 집계에 쓸 수 없고, 원장 날짜는 외부 시스템이 확정한 사실이라 더 정확하다 |
| v0.309 | 정지 · 해지는 현재 그 상태인 고객만 센다 — 과거에 정지했다 정상으로 돌아온 건 제외한다. 사업자당 대표 1건만 보아 지점 · 재가입을 두 번 세지 않는다 |
| v0.309 | 건수를 눌렀을 때 목록도 같은 기준으로 걸러 숫자와 어긋나지 않게 했다. 같은 사업자의 문의가 여럿이면 최신 1건만 남긴다 — 집계가 고객 수이므로 맞춰야 한다 |
| v0.310 | 신규고객 섹션에 인사이트 · 성장패키지 두 줄 추가 (최근 3개월 아래 · 26년 위). 실측 인사이트 106 · 성장패키지 10건 |
| v0.310 | 기준이 서로 다르다 — 인사이트는 원장 insight_joined_at, 성장패키지는 문의 inquiry_promo 다. 프로모션은 원장에 컬럼이 없다 |
| v0.310 | 성장패키지는 사업자 단위로 센다 — 같은 사업자의 문의가 여럿이면 한 명으로 봐야 실적이 부풀지 않는다. 가입일이 있는 고객만 포함한다 |
| v0.311 | 신규고객 최근 3개월 · 26년 아래에 서비스별 분해 3줄씩 — 세모R · 세모R+ · 성장패키지. 들여쓰고 흐리게 표시해 상위 항목과 구분한다 |
| v0.311 | 세모R + 세모R+ = 그 기간 합계다 (26년 42+27=69 · 3개월 10+2=12). 인사이트 가입일이 있으면 세모R+, 없으면 세모R (v0.297 규칙). 성장패키지는 그중 일부라 합계에 더해지지 않는다 — 프로모션이라 겹친다 |
| v0.311 | 줄이 6 → 12 로 늘어 통계 행 상하 여백을 줄였다. 하위 줄은 더 작은 글씨를 쓴다 |
| v0.311 | 하위 줄도 눌러 목록으로 볼 수 있다 — period 를 기간:서비스 형태(year:plus)로 두어 한 값으로 처리한다 |
| v0.312 | 첫 렌더에서 Cannot read properties of undefined (reading 'base') 로 칸반이 죽던 버그 수정 — 고객원장 로드보다 첫 렌더가 먼저 돌아 하위 행의 분해 값이 없었다. 값이 없으면 null 을 넘겨 - 로 표시한다 |
| v0.312 | 고객원장이 도착하면 칸반을 다시 그린다 — 신규고객 · 정지 · 해지 통계와 위험 집계가 모두 원장에 의존한다. 안 그리면 새로고침할 때까지 - 로 남는다 |
| v0.313 | 신규고객 · 정지 · 해지 세 섹션을 내용 높이만큼 쓰게 했다 — 이전에는 flex:1 로 균등 분할해 12줄인 신규고객은 잘리고 4줄인 정지 · 해지는 빈 공간이 남았다. 넘치면 컬럼 전체가 스크롤된다 |
| v0.313 | 통계 본문 여백을 줄였다 (gap 4 → 2 · padding 10 → 7/9) — 줄이 늘어난 만큼 밀도를 높여 세 섹션이 한 화면에 들어오게 한다 |
| v0.314 | 이번 달 아래에도 서비스별 3줄을 넣어 세 기간이 같은 구조가 됐다 — 이번 달 · 최근 3개월 · 26년 모두 세모R · 세모R+ · 성장패키지로 나뉜다 |
| v0.315 | 고객 탭 상태 필터가 걸리지 않던 버그 수정 — 정상 390 을 눌러도 정지 · 해지 고객이 함께 나왔다. v0.305 에서 고객 뷰를 원장 기준으로 바꾼 패치 중 data 부분만 저장되지 않아, 여전히 문의 목록을 읽고 있었다 |
| v0.315 | 같은 이유로 컬럼도 이전 것(서비스 · 단계 · 담당자) 이 남아 row 가 그리는 값과 어긋났다. 사무소명 · 상태 · 서비스 · 거래처 · 최근 접수 · 가입일 로 맞췄다 |
| v0.316 | 고객 탭 위멤버스 상태를 정상 → 이용 으로 — 고객원장의 status 는 세모R 기준이라 위멤버스 행에 쓰면 안 된다. 상품명이 있다는 사실만 표시하고, 색도 상태 계열이 아닌 정보 계열로 구분한다 |
| v0.316 | 잠재 위멤 정의는 그대로 유지 — 위멤버스를 쓰는데 세모R 은 안 쓰는 곳. 이름이 오해를 부르므로 버튼 툴팁에 뜻을 적었다. 위멤버스 DB 를 받으면 그 자료로 다시 정의한다 |
| v0.317 | 기획서에 오늘 작업 반영 — 확정 항목 23~28 추가(위멤버스 상태 미확정 · 위험 판정 대상 · 잠재 4종 · 컨설팅 세부단계 · 고객원장 기준 판정 · 고객원장 신설) |
| v0.317 | 4번에 고객원장 절 신설 — 33개 항목 · 형변환 필수 근거 · 키가 사업자번호가 아닌 이유(지점 · 재가입) · 원장은 외부가 주인 |
| v0.317 | 2번에 컨설팅 세부단계 7종과 고객원장 기준 재판정 절 신설. 같은 날 가입은 전환 성공이라는 근거를 남겼다 — 시각까지 비교해 280여 건이 뒤집힌 것을 기록 |
| v0.317 | 5번에 BP 사용자 공유 · 회사 · 부서 2단 구조 · 자동 배정 트리거 · 담당자 저장값 규칙 반영. 6번에 잠재 4종과 배정 흐름 추가 |
| v0.317 | 10번에 고객 탭 · 컨설팅 필터 줄 · 고객 상태 필터 · 실적 패널 4개 화면 반영. 줄 수를 줄이는 배치 원칙도 함께 적었다 |
| v0.317 | 단계 관련 절이 6번에 흩어져 있던 것을 2번으로 모았다 — 단계 체계 섹션에 있어야 찾을 수 있다 |
| v0.318 | STEP 에서 카드를 눌러도 상세가 보이지 않던 문제 수정 — 패널이 listArea 안에 있어 STEP 화면에서는 부모가 display:none 이었다. 클릭은 되고 있었지만 화면에 나타날 수 없었다 |
| v0.318 | 패널을 contentWrap 으로 옮겨 STEP · 리스트 양쪽에서 덮이게 했다. contentWrap 에 position:relative 를 주어 위치 기준이 되게 한다 |
| v0.318 | 상세를 연 카드를 파랗게 강조한다 — 리스트에서는 행을 강조하고 있었는데 카드에는 표시가 없어 무엇을 보고 있는지 알 수 없었다. 닫으면 해제된다 |
| v0.319 | 위험 칩을 눌러도 색이 바뀌지 않던 문제 수정 — _riskFilter 만 바꾸고 renderRiskBar() 를 다시 부르지 않아 선택 표시가 갱신되지 않았다 |
| v0.319 | 상단 숫자와 하단 건수가 어긋나던 문제 수정 — 위험 건수는 고객 수인데 목록은 문의 건수였다. 한 고객에 문의가 여럿이면 목록이 더 많아진다. 목록도 사업자당 1건으로 줄였다 |
| v0.319 | 상단에 위험 있음 · 정상 칩을 추가 — 칸반에서 그것으로 들어왔을 때 표시할 자리가 없어 무엇을 보고 있는지 알 수 없었다 |
| v0.320 | 칸반 카드 제목을 회사명 (대표자명) 으로 — office_name 원본을 그대로 써서 유입코드가 붙어 나왔다 (태인_TAX_SEMO_HOME). 리스트는 _officeLabel 로 정리하고 있었는데 카드만 빠져 있었다 |
| v0.320 | 카드 우측에 등록일 추가 (26-08-10). 카드 · 목록형 양쪽에 넣었다 |
| v0.320 | 목록형 담당자도 _dispAssigneeShort 를 쓴다 — 박승현/kevin 이 아니라 kevin 으로 나온다 |
| v0.321 | 상세 패널에 유입코드 행을 등록일 아래에 분리했다 — 이전에는 유입 한 줄에 경로와 코드를 · 로 붙여 놓아 어느 쪽이 코드인지 구분되지 않았다. 카드 제목에서 유입코드를 떼어낸(v0.320) 뒤로는 볼 곳이 아예 없었다 |
| v0.322 | 고객 탭 방문메모 를 메모 한 줄로 — 라벨이 두 줄을 차지해 그만큼 높이가 늘었다. 칸 안의 중복 라벨도 없앴다 (왼쪽에 이미 있다). 라벨이 빈 칸은 span 을 아예 그리지 않아 여백이 남지 않는다 |
| v0.323 | 개선의견 기능 — kevin › 개선 의견 메뉴는 있었으나 준비 중입니다 만 뜨고 있었다. 등록 · 수정 · 삭제 · 상태 변경 · 댓글 · 공감 · 첨부를 붙였다 |
| v0.323 | BP 와 분리한다 (crm_feedback) — 같은 프로젝트라 공유할 수도 있지만 쌓이면 어느 제품 얘기인지 헷갈린다 |
| v0.323 | 첨부는 menu-files 버킷(public) 에 crm/feedback/ 경로로 올린다 — BP 파일과 섞이지 않게 나눈다. public 이라 서명 URL 이 필요 없다.
파일명을 키로 쓰지 않는다 — 한글 · 공백이 있으면 Storage 키로 못 쓴다. 확장자만 살리고 시각_난수 로 만든다. 원래 이름은 DB 에 남겨 화면에 보여준다 |
| v0.323 | 파일은 건당 5개 · 10MB 까지. 넘는 파일은 이름을 알려주고 제외한다 — 조용히 빠지면 올라간 줄 안다 |
| v0.323 | 삭제는 is_deleted 를 세운다 — 잘못 지웠을 때 되살릴 수 있다. 수정은 그 자리에서 한다 — 창을 하나 더 띄우면 원문이 안 보여 무엇을 고치는지 알기 어렵다 |
| v0.324 | 컨설팅 세부단계 완료 → 가입완료. 종료계 step_group = 완료 의 세부단계도 완료 라서 이름이 겹쳤다 |
| v0.324 | 상세 패널에 가입일자 추가 (등록일 아래) — crm_inquiries.joined_at. crm_customers.joined_at 에 넣지 않는다 — 원장은 외부 시스템이 주인이라 다음 고객 파일 업로드가 덮어쓴다 |
| v0.324 | joined_at_manual 로 수기 입력을 표시한다 — 원장 동기화가 손으로 넣은 값을 덮어써도 되는지 판단할 근거가 필요하다. 없으면 매번 어느 쪽이 최신인지 알 수 없다.
화면에 원장 · 수기 출처를 적고, 두 값이 다르면 원장과 다름 을 표시한다 (툴팁에 원장 값) |
| v0.324 | 가입일자를 비우면 manual 이 해제돼 다음 동기화가 원장 값을 넣는다 — 잘못 넣었을 때 원장으로 되돌릴 방법이 있어야 한다 |
| v0.325 | 완료 대단계 폐지 (7 → 6종) — 칸반 신규고객 섹션은 원장 가입일을 세고 완료/완료 40건은 어디에도 쓰이지 않아 이름만 남아 있었다. 한 문의의 결말은 컨설팅 안에서 정한다 |
| v0.325 | 컨설팅 세부단계에 문의종료 추가 — 결말이 네 갈래가 됐다. 실패 와 다르다 — 문의종료는 가입 실적이 있는 고객의 문의를 닫는 것이고, 실패는 가입하지 않은 상태로 끝나는 것이다 |
| v0.325 | 미가입 고객은 문의종료 를 고를 수 없게 막는다 — 드롭다운에서 (가입 후 가능) 으로 표시하고 비활성화한다. 목록에서 아예 빼지 않는다 — 왜 없는지 알 수 없다.
저장 직전에 한 번 더 검증한다 — 그 사이 가입 정보가 바뀔 수 있다 |
| v0.325 | 가입 판정은 원장과 CRM 양쪽을 본다 (_hasJoined) — 원장에 없어도 손으로 가입일자를 넣은 건은 실제로 가입한 것이다 |
| v0.325 | 칸반 컨설팅 컬럼에서 결말 4종을 제외한다 — 끝난 건이 쌓이면 대기열이 비워지지 않아 칸반의 역할을 잃는다. 문의종료 는 종료 컬럼에도 넣지 않는다 — 이미 가입한 고객이라 영업 대기열에 있을 이유가 없다 |
| v0.326 | 컨설팅 필터 줄을 그룹 알약으로 — 버튼 15개에 각각 테두리를 두면 화면이 시끄럽다. 그룹 하나를 알약으로 묶고 안쪽은 글과 숫자만 둔다. 라벨은 알약 밖에 둬 그룹 이름이 분명해진다 |
| v0.326 | 5개 그룹 — 진행(대기 · 접촉 · 예약) · 예정(높음 · 보통 · 낮음) · 가입(완료 · 종료) · 실패(접촉실패 · 가입실패) · 잠재(위멤 · 해지 · 주소 · 기타) |
| v0.326 | 클릭 가능함은 밑줄로 알린다 — 평소 투명이고 올렸을 때만 보인다. 항상 밑줄이 있으면 화면이 시끄러워진다. 색은 단색으로 통일했다 |
| v0.326 | 0건인 항목은 흐리게 하고 클릭을 막는다 — 눌러도 빈 목록이라 헛걸음이 된다 |
| v0.326 | 잠재 DB 를 화면에서 주소 로 표기 — 출처가 지도 수집이라 주소가 알기 쉽다. 저장값은 DB 그대로 두어 기존 코드가 깨지지 않게 한다 |
| v0.326 | 고객 탭 상태 필터도 같은 형태로 맞췄다 — 화면마다 다르면 같은 자리인데 다르게 읽힌다 |
| v0.327 | 칸반 카드에서 컬럼 전체가 같은 값인 항목을 지웠다 — 문의 컬럼은 전부 신규접수 · 미배정 이라 적어도 아무것도 구분해 주지 않는다 |
| v0.327 | 세부단계는 컬럼 기본값과 다를 때만 보여준다 (KB_COL_DROP 비교). 담당자도 배정된 건만 표시한다 — 배정되면 그것은 실제 정보다 |
| v0.327 | 카드형에서 등록일이 두 번 나오던 것을 정리했다 (제목 우측 · 하단). 하단이 비면 그 줄을 아예 그리지 않아 카드가 짧아진다 |
| v0.328 | 기획서 2번에 업그레이드 고객을 실패로 잡던 문제를 기록 — 세모R = 인사이트 미가입 판정 때문에 세모R 로 시작해 세모R+ 로 올라간 고객의 과거 성공이 실패로 뒤집혔다 (125건) |
| v0.328 | 그 125건을 문의종료 로 둔다 — 실패로 두면 방문 대상이 되는데 이미 상위 상품 가입이라 필요 없고, 가입완료로 두면 이 문의의 성과가 아니라 실적이 왜곡된다 |
| v0.328 | 서비스별로 가입일을 채워야 한다는 원칙을 기록 — 구분 없이 채우면 가입한 적 없는 서비스에 날짜가 붙는다. 링크패스 실패 39건이 그랬다. 근거 없는 날짜는 비운다 — 남겨두면 문의종료 로 잘못 바꿀 수 있다 |
| v0.329 | 기획서 2번에 실패는 방문 대기열 절 신설 — 실패 는 결과 기록이 아니라 나중에 방문할 대상이다. 그래서 미가입만 남아야 한다 |
| v0.329 | 고객원장이 갱신되면 실패 → 문의종료 자동 전환이 함께 일어나야 한다는 원칙을 기록. 담당자가 매번 원장을 대조할 수는 없고, 이미 가입한 곳이 섞이면 헛걸음이 된다. 반대 방향은 없다 — 가입 이력은 사라지지 않는다 |
| v0.329 | 서비스별 가입 판정 기준을 표로 정리. 세모R 은 인사이트 가입 여부를 보지 않는다 — 업그레이드 고객도 세모R 가입자다 |
| v0.329 | 8번 확정 항목 29번 추가. 고객 Data 반영 후 안내에도 다음에 할 일을 적었다 — 업로드만 하고 전환을 잊으면 방문 목록이 어긋난다 |
| v0.330 | 실패 → 문의종료 자동 전환 구현 — 고객 Data 반영이 끝나면 이어서 돈다. 실패는 방문 대기열이라 이미 가입한 곳이 섞이면 헛걸음이 된다 (기획서 2번) |
| v0.330 | 서비스별로 판정 컬럼이 다르다 (_svcJoined) — 구분 없이 보면 가입한 적 없는 서비스가 가입으로 잡힌다.
세모R 은 status 만 본다 — 인사이트 가입 여부는 보지 않는다. 업그레이드 고객도 세모R 가입자다.
링크패스는 plan = 플러스 일 때만 — 별도 가입 경로가 없다 |
| v0.330 | 원장을 다시 읽은 뒤에 전환한다 — 새 원장 값을 봐야 가입 여부를 판정할 수 있다. joined_at_manual 인 건은 건드리지 않는다 |
| v0.330 | 가입일이 서로 달라 한 번에 못 묶는다. 같은 날짜끼리 모아 요청 수를 줄인다 — 100건 단위로 나누고 그 안에서 날짜별로 묶는다 |
| v0.331 | 고객 탭 글자가 깨지던 문제 수정 — v0.326 에서 필터 줄 CSS 를 교체할 때 범위를 잘못 잡아 고객 탭 CSS 15개 클래스를 함께 지웠다 (cu-row · cu-k · cu-g · cu-c · cu-svc 등). 표와 배치가 모두 무너져 값이 줄바꿈 없이 이어졌다 |
| v0.331 | CSS 블록을 시작과 끝 주석으로 잡아 통째로 교체하면 그 사이의 다른 블록이 함께 사라진다. 앞으로는 교체 후 클래스 정의 유무를 대조한다 — 문법 검증만으로는 이 유형을 못 잡는다 (v0.210 과 같은 교훈) |
| v0.332 | 신규고객 · 정지 · 해지 섹션이 커 보이던 문제 수정 — 행마다 테두리가 있어 15줄에서 그만큼 높이가 늘었다. 테두리를 없애고 여백을 줄여 목록처럼 읽히게 했다. 구분은 배경으로 충분하다 |
| v0.332 | v0.313 의 압축 규칙이 먹지 않고 있었다 — 원본 padding:7px 10px 정의가 뒤에 있어 앞의 규칙을 덮어썼다. !important 로 상하 여백만 이겼고 좌우 여백과 border 는 그대로였다.
원본 정의를 직접 고쳤다 — 뒤에 규칙을 덧붙여 이기려 하면 무엇이 실제로 적용되는지 알기 어려워진다 |
| v0.333 | 실패 를 정식 대단계로 넣었다 (6 → 7종) — DB 에 step_group = 실패 384건이 있는데 목록에 없어 드롭다운에서 고를 수도, 헤더 필터로 걸러낼 수도 없었다. 칸반에도 이미 실패 컬럼이 있으니 대단계로 두는 것이 실제 구조와 맞는다 |
| v0.333 | 헤더 단계 드롭다운에 세부단계를 함께 넣었다 — 컨설팅 (167) 아래에 대기 · 접촉 · 예정 · 가입완료 · 문의종료 가 들여쓰기로 붙는다. 「컨설팅 중 예정만」 같은 조회가 실제로 많다.
세부단계가 하나뿐인 대단계는 하위를 만들지 않는다 — 같은 값이 두 번 나온다 |
| v0.333 | 컨설팅 화면이 실패 대단계까지 함께 본다 — 결말이 두 대단계에 걸쳐 있어 한쪽만 세면 실패 그룹이 항상 0 이 된다. 필터 줄과 목록이 같은 범위를 써야 숫자를 눌렀을 때 그 수가 그대로 나온다 |
| v0.333 | 필터 없이 컨설팅에 들어오면 진행 중인 건만 보여준다 — 결말이 섞이면 대기열이 아니다. 전체 숫자도 같은 기준으로 센다 |
| v0.334 | 활성화 목록을 고객원장 기준으로 바꿨다 — 문의를 나열하고 있어 위험 있음 123명 중 71명만 나왔다. 문의가 없는 고객 16명, 문의가 활성화 가 아닌 고객 36명이 빠졌다 |
| v0.334 | 빠진 36명은 가입일이 문의일보다 앞서 그 문의가 문의종료 로 판정된 건들이다. 이미 고객이었으니 활성화가 아니지만, 지금 위험한 고객임은 변하지 않는다 |
| v0.334 | 컬럼을 위험 관리에 맞게 바꿨다 — 사무소명 · 서비스 · 거래처 · 보고서 · 인증서 · 담당자. 기준 미달 값은 붉게 표시해 어느 항목이 위험한지 바로 보인다.
접수 · 단계 · 등록일은 뺐다 — 고객 관점에서 부수적이다. 문의가 있으면 행을 눌러 상세로 가고, 없으면 문의 없음 으로 표시한다 |
| v0.335 | STEP 화면에도 단계 · 접수 필터를 노출했다 — 카드가 문의 데이터라 같은 필터가 성립한다 |
| v0.335 | 칸반이 헤더 필터를 반영하지 않던 문제 수정 — 담당자만 걸고 있어 상품 · 단계 · 접수 · 회사 · 검색을 바꿔도 카드가 그대로였다. 필터가 있는데 듣지 않으면 고장으로 보인다 |
| v0.335 | 단계 필터는 대단계 · 세부단계 둘 다 받는다 — 드롭다운에 함께 들어 있다 (v0.333) |
| v0.335 | 빈 컬럼에 필터 적용 중 을 표시한다 — 단계를 걸면 다른 컬럼이 모두 비는데 그 이유를 모르면 고장으로 보인다. 칸반에서도 드롭다운 건수를 채운다 |
| v0.336 | 단계 드롭다운의 세부단계 건수가 틀렸던 문제 수정 — 대기 가 컨설팅과 온보딩에 모두 있는데 구분 없이 세어 「컨설팅 > 대기 36」 으로 보였지만 그 36건은 온보딩/대기 였다. 건수를 그 대단계 안에서 센다 |
| v0.336 | 옵션 값에 대단계를 담는다 (컨설팅|대기) — 세부단계 이름만으로는 어느 대단계인지 알 수 없어 눌렀을 때 어디로 갈지 정할 수 없었다 |
| v0.336 | 단계를 고르면 그 대단계의 탭으로 옮긴다 — 다른 탭에서 골라도 목록이 보이지 않았다. 뷰가 자기 step_group 을 먼저 걸어 다른 대단계는 애초에 들어오지 않는다. 전용 탭이 없는 대단계(실패 · 정지 · 해지)는 리드 탭에서 본다 |
| v0.336 | 단계 판정을 _stageHit 한 곳으로 모았다 — 네 곳에 흩어져 있어 값 형식이 바뀌면 일부만 고쳐질 위험이 있었다.
컨설팅 뷰에서 단계 필터가 걸리면 그것이 우선이다 — 사용자가 명시적으로 고른 값이 기본 동작보다 앞선다 |
| v0.337 | 문의 건을 컨설팅 대기 에서 함께 본다 — 담당자 배정 전이라 성격이 같다. 컨설팅 화면에서 바로 배정하고 처리할 수 있어야 한다. 배정하면 컨설팅 / 대기 로 자동 이동한다 (기존 동작) |
| v0.337 | 헤더 컨설팅 숫자를 진행 건수로 바꿨다 — 결말(가입완료 · 문의종료) 까지 세면 처리할 일이 몇 건인지 알 수 없다. 167건이 보이는데 실제 처리 대상은 0건이었다 |
| v0.337 | 진행 판정을 _isConsultingOpen 한 곳으로 모았다 — 집계 · 목록 · 헤더 드롭다운 · 단계 필터 네 곳이 같은 기준을 쓴다. 흩어져 있으면 한 곳만 고쳐질 위험이 있다 |
| v0.337 | 컨설팅|대기 필터는 문의도 잡고, 컨설팅 대단계 필터는 진행 건만 남긴다 — 결말은 각자 하위 항목으로 고른다 |
| v0.338 | STEP 에 단계 · 접수 필터가 여전히 안 보이던 문제 수정 — v0.335 에서 한 곳만 고쳤는데 setView 에 같은 요소를 숨기는 코드가 또 있었다. 뷰를 옮길 때마다 다시 숨기고 값까지 지웠다 |
| v0.338 | 같은 요소를 두 곳에서 조작하면 나중에 도는 쪽이 이긴다 — 한 곳을 고쳐 화면에서 확인했는데 뷰를 옮기는 순간 되돌아갔다. 다음에 display 를 다룰 때는 그 요소를 건드리는 코드가 몇 곳인지 먼저 센다 |
| v0.339 | 여전히 안 보이던 진짜 원인 — init 은 switchTab 도 setView 도 부르지 않는다. renderAll 만 불러서 마크업의 인라인 display:none 이 그대로 남았다. 탭을 옮기면 보이고 새로고침하면 사라졌다 |
| v0.339 | 판단을 syncHeaderFilters 한 곳으로 모았다 — 세 곳(switchTab · setView · 초기 로드) 에서 각자 판단하고 있어 한 곳이 빠지면 안 보였다. 이제 렌더할 때마다 함께 맞춘다 |
| v0.339 | 같은 판단을 여러 곳에서 반복하지 않는다 — 두 번 고쳐도 세 번째가 남아 있었다. 화면 상태를 정하는 규칙은 한 함수에 두고 필요한 곳에서 부른다 |
| v0.340 | 단계 필터를 마지막에 누른 것이 이기게 했다 — 두 필터를 동시에 걸면 교집합이 비어 데이터가 없습니다 가 됐다. 대기 14 를 고른 상태에서 문의종료 167 을 누르면 0건이 나왔다 |
| v0.340 | 상단 단계를 고르면 필터 줄 선택을, 필터 줄을 누르면 상단 단계를 자동으로 푼다 — 어느 쪽을 눌러도 누른 것이 항상 보인다. 위험 · 실적 필터도 함께 푼다 |
| v0.340 | 상단 단계로 들어오면 필터 줄에서 그 항목이 눌린 것처럼 표시된다 — 어느 조건으로 걸러졌는지 화면에 드러나야 한다 |
| v0.341 | 실패 를 고르면 컨설팅 탭으로 간다 — 리드 탭으로 보내고 있었는데 거기에는 그 목록이 없다. 실패는 결말이라 컨설팅의 일부이고, 컨설팅 뷰가 이미 실패 대단계를 범위에 넣고 있다 |
| v0.341 | 실패 저장값을 화면에서 문의실패 로 부른다 — 접촉실패 와 나란히 놓으면 무엇이 실패했는지 구분된다. 접촉실패는 연결 자체가 안 된 것, 문의실패는 만났으나 가입하지 않은 것이다 |
| v0.341 | 필터 줄 라벨을 _stepLabel 로 통일했다 — 하드코딩(가입실패) 이라 드롭다운과 이름이 어긋났다 |
| v0.341 | 대단계만 골랐어도 건수가 한 세부단계에 몰려 있으면 그것을 선택 표시한다 — 실패 384 를 고르면 문의실패 384 가 눌린 것으로 보여야 어느 조건인지 알 수 있다 |
| v0.342 | 활성화 헤더 숫자를 위험 있음 123 으로 바꿨다 — 다른 컬럼의 숫자는 “여기 쌓인 일” 을 뜻하는데 활성화만 전체 고객 수를 보여 400 이 처리 대상처럼 읽혔다. 실제 손댈 곳은 123 이다 |
| v0.342 | 분모를 작게 함께 적는다 (123 / 390) — 위험 수만 있으면 전체가 몇인지 알 수 없다 |
| v0.342 | RISK_SUMMARY 가 이 함수보다 뒤에 선언돼 있어 방어를 넣었다 — 실행 시점에는 있지만 첫 렌더가 앞서면 ReferenceError 로 칸반이 죽는다 (v0.311 과 같은 유형) |
| v0.343 | 문의실패 를 가입실패 로 되돌렸다 — v0.341 에서 확인 없이 이름을 바꿨다. 저장값은 실패 그대로이고 화면 표기만이라 데이터에는 영향이 없다 |
| v0.343 | 상단 드롭다운에서 실패 를 컨설팅 하위로 넣었다 — 대단계로 나란히 두면 별개 흐름처럼 보이는데, 실제로는 컨설팅에서 끝난 건이고 컨설팅 화면에서 함께 본다 |
| v0.343 | 값에는 실제 대단계를 유지한다 (실패|접촉실패) — 화면에서만 컨설팅 아래로 보이고 필터는 step_group = 실패 로 걸린다. 상세 패널 드롭다운에는 실패 가 대단계로 남아 카드를 그쪽으로 옮길 수 있다 |
| v0.344 | 문의 하위 담당자배정 을 제거했다 — 배정하면 컨설팅/대기 로 자동 이동하므로 이 상태에 머무는 건이 없다. 도달할 수 없는 값을 목록에 두면 항상 0 으로 보인다 |
| v0.344 | 부여하던 곳 두 군데도 함께 정리했다 — 칸반에서 문의로 되돌릴 때와 단계 드롭다운으로 문의를 고를 때 담당자가 있으면 담당자배정 을 붙이고 있었다. 이제 항상 신규접수 다 |
| v0.345 | 상단 드롭다운 활성화 건수를 위험 있음 123 으로 — 이름은 그대로 두고 숫자만 바꿨다. 400 은 전체 고객 수라 손댈 곳이 몇 건인지 알 수 없었다 |
| v0.345 | 칸반 헤더(v0.342) 와 같은 기준을 쓴다 — 같은 항목이 자리마다 다른 수를 보이면 어느 쪽을 믿을지 알 수 없다. 컨설팅도 이미 진행 건만 세고 있다 |
| v0.346 | 상단에서 활성화 를 고르면 위험 있음 칩이 함께 선택된다 — 건수가 위험 있음을 세므로(v0.345) 필터도 같이 걸어야 목록이 맞는다. 걸지 않으면 드롭다운은 123 인데 목록은 390 이 나와 어긋난다 |
| v0.346 | 위험 칩을 누르면 상단 단계를 푼다 — 활성화로 들어오면 위험 필터가 자동으로 걸리는데, 그 상태에서 다른 위험 항목을 누르면 두 값이 겹쳐 결과가 예측하기 어려워진다 (v0.340 「마지막 누른 것이 이긴다」) |
| v0.347 | 문의 목록 컬럼을 요청 순서로 — 등록일 · 사무소명 · 사업자번호 · 신청자 · 핸드폰 · 문의서비스 · 담당자 · 주소 · 유입코드 · 문의내용 |
| v0.347 | 컬럼 제목을 눌러 정렬한다. 같은 컬럼을 다시 누르면 방향이 바뀌고 ▲▼ 로 표시된다.
숫자 컬럼은 숫자로, 나머지는 한국어 사전순으로 비교한다 — 문자열로 비교하면 10 이 9 보다 앞에 오고, 한국어를 코드값으로 비교하면 순서가 어긋난다.
빈 값은 방향과 무관하게 뒤로 보낸다 — 위에 몰리면 목록이 비어 보인다 |
| v0.347 | 컬럼을 추가 · 제거 · 순서 변경할 수 있다 (헤더 우측 ⚙). 18개 항목 중 골라 쓴다 — 대표자 · 이메일 · 접수 · 단계 · 거래처 · 유입 · 가입일자 · 가능성이 추가 가능하다 |
| v0.347 | 구성을 사용자별 · 뷰별로 저장한다 (crm_list_prefs) — 영업 담당자는 연락처와 주소를 보고 관리자는 담당자와 단계를 본다. 한 벌로 맞추면 누군가는 늘 필요 없는 컬럼을 본다.
값은 jsonb 한 덩어리다 — 컬럼 목록 · 순서 · 정렬이 함께 바뀌므로 나눠 두면 일부만 저장돼 어긋난 상태가 남는다 |
| v0.347 | 정렬은 페이지를 자르기 전에 적용한다 — 나중에 하면 1페이지 안에서만 정렬돼 진짜 첫 건이 오지 않는다.
정렬 컬럼을 껐으면 첫 컬럼으로 옮긴다 — 없는 컬럼으로 정렬하면 순서가 뒤죽박죽이 된다 |
| v0.348 | 목록 담당자 셀에서 바로 배정한다 — 상세를 열지 않아도 되게 한다. 상세 패널과 같은 추천 규칙을 쓴다 (서비스 · 단계 조합별 배정 이력 상위 3명) |
| v0.348 | 셀렉트에서 이벤트를 멈춘다 — 행 클릭이 상세를 열므로, 안 멈추면 고르는 순간 상세가 함께 열려 목록에서 처리하는 의미가 없다 |
| v0.348 | 배정 이력이 없으면 나 자신을 추천한다 — 셀 것이 없으면 추천이 비는데, 목록에서 바로 배정하는 경우 대부분 자기가 맡는다 |
| v0.348 | 배정 건수가 0 이면 괄호를 숨긴다 — (0) 은 정보가 아니다 |
| v0.349 | 잘려 보이는 칸(문의내용 · 주소 · 이메일) 에 마우스를 올리면 전체 내용을 보여준다. 브라우저 기본 title 은 뜨는 데 1초 넘게 걸리고 줄바꿈이 안 되며 길면 잘린다 |
| v0.349 | 값을 DOM 속성에 담지 않는다 — 문의 id 로 그때 읽는다. 1,600건 × 긴 본문을 속성에 넣으면 HTML 이 그만큼 무거워진다 |
| v0.349 | 화면 밖으로 나가면 반대쪽에 붙인다 — 잘리면 읽을 수 없다. 우측 · 하단 · 모서리를 모두 확인했다. 스크롤하면 숨긴다 — 남아 떠 있으면 화면을 가린다 |
| v0.350 | 추가할 수 있는 컬럼을 18 → 31개로 늘렸다 — 상세 패널에만 있던 값을 목록에서도 볼 수 있게 한다.
가입내역 4종(세모R · 위멤버스 · 링크패스 · 인사이트) · 프로모션 · 변경 전 요금제 · Flow 임시담당자 · 직원수 · 다음 액션 · 액션 기한 · 메모 · 대단계 · Flow 번호 |
| v0.350 | 설정 창에서 묶음별로 보여준다 (기본 · 문의 · 단계 · 담당 · 규모 · 가입내역) — 31개를 한 줄로 늘어놓으면 찾기 어렵다 |
| v0.350 | 가입내역은 Flow 본문에서 파싱한 문자열이라 접수 당시 기록으로만 본다 — 요금제 · 거래처수의 정확한 값은 고객원장이 주인이다 |
| v0.350 | 마우스 미리보기가 컬럼 정의의 get 을 쓴다 — 원본 필드를 그대로 읽으면 가공한 값(담당자 표시 등) 과 어긋난다 |
| v0.351 | 사무소명 옆 문의 · 활동 건수 배지를 되돌렸다 — v0.347 에서 컬럼을 교체하며 빠졌다. 같은 사업자에 문의가 여럿인지, 활동이 남아 있는지가 목록에서 바로 보여야 한다 |
| v0.351 | 정렬 · 미리보기는 배지 없는 이름을 쓴다 (get 과 html 분리) — 배지가 섞이면 가나다순이 어긋난다 |
| v0.352 | 고객 목록 컬럼을 요청 순서로 — 가입일 · 사무소명(대표 · 문의 · 활동) · 상태 · 서비스 · 고객(전체) · 세모(전체) · 세모(사용) · 요금제 · 최근문의 · 주소 · 핸드폰 |
| v0.352 | 고객 탭에도 정렬 · 컬럼 설정을 넣었다 (29개 중 선택). 컬럼 정의는 문의와 따로 둔다 — 원장을 나열하므로 필드가 아예 다르다. 저장 틀(crm_list_prefs) 과 정렬 로직은 공유한다 |
| v0.352 | 문의가 없는 고객도 상세가 열린다 — 원장 정보와 방문메모는 문의 없이도 볼 일이 있다 (전화가 와서 찾아보거나 방문 예정을 적어두는 경우). 문의 · 활동 탭은 보여줄 게 없어 숨기고 문의 없음 을 표시한다 |
| v0.352 | 컬럼 로직을 _lcDefs() 로 일반화했다 — 뷰에 따라 정의를 골라 쓴다. 정렬 · 헤더 · 행 · 설정 창 · 미리보기가 모두 한 경로를 쓴다 |
| v0.353 | 목록 날짜를 26.08.01 로 통일했다 (_lcDate) — 2026-08-01 은 10자를 차지해 좁은 컬럼에서 줄이 바뀌거나 다른 값을 밀어낸다. 목록은 훑어보는 곳이라 연도 네 자리가 필요 없다 |
| v0.353 | 등록일 · 가입일자 · 액션 기한 · 가입일 · 최근문의 · 인사이트 가입 · 해지일 · 정지일 8개 컬럼에 적용. 폭도 78 → 66px 로 줄여 그만큼 사무소명에 돌아간다 |
| v0.353 | 사무소명을 한 줄로 고정하고 최소 폭 260px 을 준다 — 두 줄이 되면 행 높이가 들쭉날쭉해져 훑기 어렵다. 문의내용 200 → 130px · 담당자 96 → 72px |
| v0.354 | 같은 행을 다시 누르면 상세가 닫힌다 — 열려 있는 것을 또 열려는 동작은 닫으려는 뜻이다. 리드 · 컨설팅 · 온보딩 · 활성화 · 고객 · 잠재 · 칸반 카드 모두 적용 |
| v0.354 | ESC 로도 닫는다. 여러 겹이 열려 있으면 위에서부터 — 확인창 → 컬럼 설정 → 개선의견 → 상세. 컬럼 설정이 열려 있는데 상세가 닫히면 순서가 뒤집힌다 |
| v0.354 | 바깥 클릭으로도 닫는다. 목록 · 칸반 · 헤더 · 좌측 탭은 예외다 —
목록을 예외로 두지 않으면 다른 행을 누를 때 닫힌 뒤 다시 열려 화면이 깜빡인다. 헤더를 예외로 두지 않으면 필터를 고치는 중에 패널이 사라진다 |
| v0.354 | 편집 중이면 closeInquiryDetail 이 확인을 띄운다 — 토글 · ESC · 바깥 클릭에서 따로 막지 않는다. 판단이 여러 곳에 있으면 한쪽만 고쳐질 위험이 있다 (v0.339 교훈) |
| v0.355 | 사무소명 표시를 모든 목록에서 통일했다 — 사무소명 (대표자) 문의 1 활동 0/0. 활성화 위험 목록 · 잠재 목록에는 배지가 없었고, 잠재-DB 에는 대표자가 없었다 |
| v0.355 | 목록 첫 칸을 항상 한 줄로 (CSS td:first-child) — 뷰마다 따로 지정하면 새 목록을 만들 때 또 빠진다. 최소 폭 240px 을 주어 첫 칸이 폭을 먼저 쓰고 나머지가 줄어든다 — 뒤 컬럼은 잘려도 마우스로 볼 수 있다 |
| v0.355 | 칸반 카드 제목도 한 줄로 — .kb-card-name 에 CSS 가 없어 두 줄이 되면 카드 높이가 서로 달라져 컬럼이 지저분해졌다 |
| v0.355 | 잠재 해지 · 위멤 목록의 행을 누르면 고객 상세가 열린다 — 원장에 있는 고객이라 볼 내용이 있는데 클릭이 막혀 있었다 |
| v0.356 | 고객 탭에 리드 탭 컬럼이 나오던 버그 수정 — _lcApply() 를 currentView = view 앞에서 부르고 있었다. _lcViewKey() 가 이전 뷰를 반환해 남의 설정이 적용되고, 그 상태로 저장되기까지 했다 |
| v0.356 | 저장된 값이 그 뷰의 컬럼 정의와 절반도 안 맞으면 버린다 — 이전 버그로 잘못 저장된 설정이 남아 있어도 기본값으로 되돌아온다. 사용자가 지우지 않아도 스스로 복구된다 |
| v0.357 | 서비스 배지가 두 줄로 넘치던 문제 수정 — 3개면 줄이 바뀌어 그 행만 높아지고 훑을 때 눈이 걸린다. 한 줄로 끊고 폭을 150 → 172px 로 늘렸다 |
| v0.357 | 핸드폰 · 대표전화 폭을 106 → 112px — 010-0000-0000 이 한 줄에 들어가야 한다. 끊기면 번호를 잘못 읽을 수 있다 |
| v0.357 | 고객 목록에서 최근문의를 기본에서 뺐다 — 실측 정상 고객 390명 중 273명(76%) 이 가입일과 같다. 가입 신청 문의가 곧 마지막 문의인 경우가 대부분이라 같은 값이 두 칸을 차지했다. 필요하면 컬럼 설정에서 켠다 |
| v0.358 | 가입일과 사무소명 사이가 벌어지던 문제 수정 — td:first-child 에 min-width:240px 을 걸었는데 고객 탭은 첫 칸이 가입일이라 그 칸이 240px 을 차지했다 |
| v0.358 | 위치로 칸을 찾을 수 없다 — 컬럼 순서를 바꿀 수 있게 만든 뒤로 첫 칸이 사무소명이 아니다. nm-cell 클래스를 붙여 그 칸만 지정한다. 활성화 · 잠재 목록에도 함께 붙였다 |
| v0.358 | 셀 좌우 여백을 14 → 9px — 14px 은 컬럼이 적을 때 기준이었다. 컬럼이 11개면 여백만 300px 이 넘어 값이 들어갈 자리가 없다 |
| v0.359 | 서비스 배지를 3종으로 줄였다 — 세모R · 세모R+ · 성장패키지. 위멤버스는 900건 전부라 구분이 안 되고, 링크패스는 요금제 컬럼과 같은 말을 두 번 한다 |
| v0.359 | 성장패키지 사업자를 미리 모아 둔다 (_promoBiz) — 프로모션은 원장에 컬럼이 없어 문의에서 읽어야 하는데, 행마다 1,659건을 훑으면 목록이 느려진다 |
| v0.359 | 기획서에 v0.347~v0.359 반영 — 확정 항목 30~33 추가(문의는 컨설팅 대기 · 실패는 대단계 · 서비스 배지 3종 · 목록 컬럼은 사용자별) |
| v0.359 | 2번에 단계 체계 최종 절 신설 — 흐름도와 네 가지 원칙(완료 폐지 · 담당자배정 제거 · 공백 없음 · 문의도 진행). 처리할 일의 수를 보여준다와 마지막에 누른 것이 이긴다도 함께 적었다 |
| v0.359 | 10번에 목록 컬럼 · 정렬과 상세 패널 열기 · 닫기 절 신설 — 정렬 세 규칙, 위치로 칸을 찾지 않는 이유, 목록 표기 규칙 5가지, 바깥 클릭 예외 근거 |
| v0.359 | 4번에 보조 테이블 행 추가 — crm_feedback · crm_list_prefs 와 첨부 저장 규칙 |
| v0.360 | 사무소명 옆에 경과일을 붙였다 (D+10) — 화면마다 기한이 다르다. 문의 90일(그 뒤 실패로 자동 판정) · 컨설팅 30일(그 안에 처리해야 한다) |
| v0.360 | 기한이 없는 화면(고객 · 온보딩 · 활성화) 에는 보여주지 않는다 — 재촉할 근거가 없는데 붉은 숫자가 있으면 신호가 무뎌진다 |
| v0.360 | 기한의 2/3 를 넘기면 노랗게, 넘기면 붉게. 툴팁에 기한까지 20일 처럼 남은 날을 적는다 — 숫자만 있으면 며칠이 문제인지 매번 따져야 한다 |
| v0.360 | 셀 여백을 9 → 6px 로 한 번 더 줄이고, 표의 양 끝만 12px 을 준다 — 벽에 붙으면 답답하다 |
| v0.361 | 기획서를 좌측 메뉴 도움말 로 옮겼다 (펼치기 위) — kevin 메뉴 안에 있어 찾으려면 두 번 눌러야 했다 |
| v0.361 | 아이콘은 펼친 책을 쓴다 — 물음표는 “모르는 걸 물어보는 곳” 을 뜻하는데, 이 문서는 설계 근거와 판단 기록이다. 「왜 이렇게 만들었나」 를 담고 있어 책이 맞는다 |
| v0.361 | 도움말은 강조 대상에서 뺐다 — 새 창을 열 뿐 화면을 바꾸지 않는다. 눌렀다고 켜져 있으면 지금 그 화면에 있는 것처럼 보인다 |
| v0.363 | 개선의견을 설정 맨 앞 탭으로 옮겼다. kevin › 개선 의견 도 그 탭을 연다 — 두 경로가 같은 화면을 쓴다 |
| v0.363 | 모달을 없앴다 — 같은 기능이 두 곳에 있으면 한쪽만 고쳐진다 |
| v0.363 | 이전 openFeedback 에 async 가 빠져 있었다 — await _fbLoad() 를 쓰는데 선언이 없어 목록을 읽기 전에 그려 항상 “읽는 중…” 에서 멈췄다 |
| v0.363 | 마크업을 지울 때 앞 요소의 닫는 태그까지 함께 지워 헤더 구조가 깨졌다 — 사용자 메뉴(kevin) 가 사라졌다.
지울 구간의 <div> 짝이 맞는지 먼저 세고 지운다. 문법 검증으로는 이 유형을 못 잡는다 (v0.331 과 같은 교훈) |
| v0.364 | 설정 창을 화면 전체로 넓혔다 — max-width:1000px 로 좁혀 개선의견 한 줄이 접혔다. 설정은 목록을 담는 화면이라 넓어야 한다 |
| v0.364 | 개선의견을 한 줄 목록으로 바꿨다 (BP 와 같은 형태) — 카드로 쌓으면 화면이 넓어도 몇 건 못 본다. 본문이 길면 말줄임으로 끊고 눌러 펼친다 |
| v0.364 | 한 줄에 내용 · 첨부 · 댓글 수 · 작성자 · 날짜 · 공감 · 상태 · 수정/삭제를 담는다. 상태는 그 자리에서 바꾸고, 버튼을 눌러도 펼쳐지지 않게 이벤트를 멈춘다 |
| v0.365 | async is not defined 로 화면이 죽던 버그 수정 — v0.363 에서 async function openFeedback 을 function openFeedback 으로 매칭해 async 만 남았다 (v0.296 과 같은 실수) |
| v0.365 | node --check 가 이 유형을 못 잡는다 — async 뒤에 주석이 오면 다음 function 과 이어져 문법은 통과하지만 실행 시 죽는다. 검증을 통과했는데 화면이 안 뜬 이유다 |
| v0.365 | 검증 도구를 만들었다 (checkjs.py) — 문법 · 고아 async · 태그 짝 · 버전 일치를 함께 본다. 오늘 겪은 세 유형(v0.331 CSS 삭제 · v0.363 태그 삭제 · v0.365 고아 async) 을 모두 잡는다 |
| v0.366 | 좌측 메뉴를 두 그룹으로 나눴다 — 위는 화면 이동(STEP · 고객 · 문의 …), 아래는 도움말 · 접기. 성격이 달라 붙어 있으면 같은 메뉴로 읽힌다 |
| v0.366 | margin-top:auto 를 아래 그룹의 첫 요소로 옮겼다 — 이전에는 접기 버튼에만 있어 도움말이 위 메뉴에 붙어 있었다. 메뉴가 몇 개든 아래 그룹은 항상 바닥에 붙는다 |
| v0.367 | 페이지 번호를 눌러도 아무 일이 없던 버그 수정 — setInquiryPage() 를 호출하는데 정의가 없었다. 오류도 안 나 알아채기 어려웠다 |
| v0.367 | checkbp.py 「죽은 함수 참조」 검사로 찾았다 — node --check 는 통과하고 클릭했을 때 죽는 유형이다 |
| v0.367 | BP 3종 세트 규칙에 맞춰 APP_VERSION 상수를 도입했다 (CRM_VERSION 은 이를 참조). 상수 · 파일명 · 배지가 항상 같은지 검사로 확인된다 |
| v0.367 | 페이지를 옮기면 목록 맨 위로 올린다 — 이전 페이지의 스크롤 위치가 남으면 새 목록의 중간부터 보게 된다 |
| v0.368 | 개선의견 등록을 모달로 바꿨다 (BP 와 같은 형태) — 목록 위에 폼을 항상 두면 그만큼 볼 수 있는 건수가 줄고, 쓰지 않을 때도 자리를 차지한다 |
| v0.368 | 목록 상단에 검색 · 상태 · 등록 버튼을 놓았다. 검색은 본문과 작성자를 함께 본다 — “누가 올린 건지” 로 찾는 일이 잦다 |
| v0.368 | 등록 모달의 z-index 를 설정 창(400) 보다 위(460) 에 둔다. ESC 스택에도 넣어 등록 모달이 먼저 닫힌다 |
| v0.368 | 창이 열려 있으면 바깥 클릭으로 상세 패널이 닫히지 않게 했다 — 모달 안을 누를 때마다 뒤에서 패널이 사라졌다 |
| v0.369 | 좌측 메뉴 펼치기 아래에 버전을 작게 표시한다 — 이전에는 kevin 메뉴를 열어야 보였다. 어느 버전을 보고 있는지 확인할 곳이 필요하다 |
| v0.369 | APP_VERSION 상수를 그대로 쓴다 — 손으로 적으면 3종 세트가 어긋난다. 접었을 때는 9px, 펼치면 10px 로 보인다 |
| v0.370 | 사무소명 칸에서 이름은 왼쪽, 배지(문의 · 활동 · D+n) 는 오른쪽 한 자리에 모았다 — 배지가 이름 길이를 따라다니면 행마다 위치가 달라 세로로 어긋나 보인다 |
| v0.370 | 배지를 칸 끝에 딱 붙이지 않는다 (우측 14px) — 옆 컬럼과 붙으면 어디까지가 이름 칸인지 흐려진다 |
| v0.370 | 사무소명 최소 폭 220 → 200px, 문의내용 130 → 200px, 주소 180 → 230px(고객 190 → 250px) — 마우스를 올려야만 읽히면 목록의 뜻이 없다 |
| v0.371 | 배지 표기를 성장패키지 → 성장 으로 줄였다. 저장값은 그대로 둔다 — 판정 규칙 · 필터 · 집계가 모두 그 값을 쓴다 |
| v0.371 | 마우스를 올리면 전체 이름이 나온다. 고객 목록은 성장패키지를 서비스 자리에 넘기므로 그쪽도 함께 처리했다 |
| v0.372 | 사무소명 칸이 남는 폭을 다 가져가 이름과 배지 사이가 멀어졌다 — 한 줄로 안 읽힌다. 폭을 정해 뒀다 (문의 330px · 고객 290px) |
| v0.372 | 고객 목록은 D+n 이 없어 배지가 짧다 — 문의보다 좁게 잡았다 |
| v0.372 | 남는 폭은 주소가 가져간다 (w:0) — 목록에서 가장 길고 잘리면 아쉬운 값이다 |
| v0.373 | 사무소명 폭을 실측으로 정했다 — 원장 886건 「사무소명 (대표자)」 폭 분포는
50% 137px · 90% 188px · 95% 209px · 최대 248px 이다 |
| v0.373 | 최대에 맞추지 않는다 — 248px 로 잡으면 절반의 행에서 100px 넘게 빈다. 95% 를 담고 나머지는 말줄임한다 |
| v0.373 | 문의 364px (이름 209 + 배지 143 + 여백 12) · 고객 328px (배지 107, D+n 없음). 실측 결과 잘리는 건 43건(4.9%) 이고 마우스를 올리면 전체 이름이 나온다 |
| v0.374 | 고객 상세에서 라벨과 값이 멀어지던 문제 수정 — margin-left:auto 로 값을 칸 끝까지 밀고 있었다. 어느 값이 어느 이름의 것인지 눈이 한 번 더 움직인다 |
| v0.374 | 두 줄 라벨을 한 줄로 — 거래처/위멤버스 → 위멤버스, 거래처/세모 → 세모, 카카오/채널 → 카카오. 라벨 폭을 52 → 58px 로 늘려 접히지 않게 했다 |
| v0.374 | 보고서 · 거래처 두 줄을 4열로 통일했다 — 줄마다 열 수가 다르면 세로로 눈이 흔들린다 |
| v0.374 | 서비스 표에 행 구분선을 넣었다 — 값이 많아 구분이 없으면 어느 줄인지 놓친다 |
| v0.375 | 문자 값을 라벨 바로 옆으로 옮겼다 — 칸 끝으로 밀면 상태 와 정상 사이가 비어 짝이 한눈에 안 읽힌다.
숫자는 오른쪽에 그대로 둔다 — 자릿수가 세로로 맞아야 1,447 과 0 을 바로 비교한다 |
| v0.375 | 서비스 표에 현황 라벨을 붙였다 — 표만 왼쪽 끝에서 시작해 다른 줄과 시작선이 어긋나 있었다 |
| v0.375 | 표를 회색 상자로 감쌌다 — 다른 줄은 회색 칸인데 표만 맨바닥이면 같은 영역으로 안 읽힌다 |
| v0.375 | 입력란도 왼쪽 정렬로. 거래처만 숫자라 오른쪽에 둔다 |
| v0.376 | 고객 상세를 C안으로 — 회색 상자를 없애고 밑줄만 남겼다. 한 화면에 상자가 20개 가까이 놓이면 그것만으로 답답해 보인다 |
| v0.376 | 라벨과 값 사이를 5 → 10px — 붙으면 한 낱말처럼 읽히고, 멀면 짝이 안 보인다 |
| v0.376 | 상태 강조를 배경 대신 밑줄 색과 글자색으로 — 상자가 없으니 배경만 남으면 그 칸이 떠 보인다 |
| v0.376 | 서비스 표 규칙이 두 벌로 겹쳐 있었다 (v0.374 추가분 · 원래 것). 뒤엣것이 이겨 v0.374 에서 넣은 구분선이 무효였다 — 원래 규칙에 합쳤다 |
| v0.377 | 사업장 사무소명에 유입코드가 붙어 나오던 문제 수정 (일공삼택스(5개,1명)_260806) — 목록은 _cleanOffice 로 정리하는데 편집 칸만 원본을 보여주고 있었다 |
| v0.377 | 거래처 입력칸을 없애고 신청자 · 연락처 · 이메일을 한 줄로 — 거래처 수는 아래 현황 표에서 서비스별로 보여준다 |
| v0.377 | 현황 표에서 사유를 빼고 전체 · 정상 · 휴폐업 · 정지를 넣었다 — 거래처 수를 아래에 따로 두면 어느 서비스의 수인지 한 번 더 짚어야 한다. 사유는 줄 전체 툴팁으로 옮겼다 |
| v0.377 | 아래 위멤버스 · 세모 두 줄을 삭제했다 (현황 표로 이동). 셋업도 두 줄 → 한 줄 (4열) — 라벨을 실시간 · 자동수집 · 부서사용자 · 신고 로 줄였다 |
| v0.378 | 입력란이 든 칸의 밑줄을 없앴다 (사업장 · 메모) — 입력란에 이미 테두리가 있어 선이 두 겹으로 겹쳐 보였다 |
| v0.378 | :has() 대신 클래스를 직접 붙였다 — 브라우저에 따라 무시되면 밑줄이 그대로 남는다 |
| v0.379 | 보고서 · 부가세 숫자도 라벨 옆으로 — 원장의 상태 정상 과 같은 규칙으로 통일했다. 자릿수를 세로로 비교하는 일은 위쪽 현황 표가 맡는다 — 여기는 낱개 값이라 「어느 이름의 수인가」 가 먼저다 |
| v0.379 | 카카오를 4칸 기준 앞 3칸으로 — 3열이라 다른 줄과 칸 폭이 어긋났다. 빈 칸에는 밑줄을 두지 않는다 (허공에 선이 그어져 보인다) |
| v0.380 | 셋업과 카카오 순서를 바꿨다 — 카카오가 위, 셋업이 아래다 |
| v0.381 | 인사이트 상태 기타 를 뜻이 드러나는 말로 바꿨다 — 원장 실측 797건 중 794건이 가입일자조차 없다(쓴 적 없음). 나머지 3건은 해지일이 있다 |
| v0.381 | 해지일 있음 → 해지 · 없음 → 미이용. 날짜를 오늘과 비교하지 않는다 — 해지 예약 2건이 시간이 지나며 다르게 보인다 |
| v0.381 | 원장 값은 고치지 않는다 — 다음 파일 업로드가 덮어쓴다. 표시만 바꾼다. 미이용 은 링크패스와 같은 회색으로 나온다 |
| v0.382 | 한 사업자에 계정이 여럿이면 사무소명 옆에 이용기관코드를 붙인다 — 실측 886개 사업자 중 12곳이 복수다 (2개 10곳 · 3개 2곳) |
| v0.382 | 하나뿐인 곳에는 붙이지 않는다 — 900건 중 874건이 그러하고, 모두에 붙이면 정작 봐야 할 12곳이 묻힌다 |
| v0.382 | 세무법인진명은 이름이 셋 다 같다 — 코드 없이는 구분할 방법이 없다. 코드는 줄이지 않는다 — 자르면 다른 자료와 대조할 때 다시 찾아야 한다 |
| v0.382 | 고객 목록 사무소명 폭 328 → 400px — 코드 18자가 약 120px 을 쓴다 |
| v0.383 | v0.382 의 목록 변경을 되돌렸다 — 이용기관코드는 고객 상세의 사무소명과 대표자 사이에 둔다. 목록에서는 자리를 많이 쓰고, 구분이 필요한 순간은 상세를 열었을 때다 |
| v0.383 | 복수 계정일 때만 칸이 생긴다 — 1fr auto 1fr. 하나뿐이면 기존과 똑같이 두 칸이다 |
| v0.384 | 배지를 이름 바로 뒤에 붙이되 최소 188px 자리를 지키게 했다 — 실측 886건 중 797건(90%) 이 그 안에 들어 배지가 세로로 맞고, 89건만 뒤로 밀린다 |
| v0.384 | 칸 끝으로 밀면 이름과 멀어지고, 아예 붙이면 행마다 위치가 달라 어지럽다. 둘 사이를 잡았다 |
| v0.384 | 이름 자리가 209 → 188px 로 줄어 컬럼도 좁아진다 — 문의 364 → 343px · 고객 328 → 307px. 남는 폭은 주소가 가져간다 |
| v0.385 | 메모를 이력으로 바꿨다 (crm_notes) — 지금까지는 stage_memo 한 칸에 덮어써서 누가 언제 무엇을 적었는지 남지 않고, 나중에 적은 사람이 앞의 내용을 지웠다 |
| v0.385 | 사업자 단위로 쌓는다 — 한 사업자에 계정이 여럿이어도(지점 · 재가입) 방문 기록은 그 사무소 전체의 것이다. 계정마다 나뉘면 흩어져 못 찾는다 |
| v0.385 | 누구나 남의 메모를 고칠 수 있다 — 오탈자나 잘못된 정보를 발견한 사람이 바로 고치는 편이 낫다.
다만 고친 사실은 crm_note_edits 에 남고 화면에 수정됨 배지가 붙는다 — 확인할 방법이 없으면 아무도 메모를 믿지 않게 된다 |
| v0.385 | 입력칸이 내용에 따라 늘어난다 (최대 260px) — 스크롤 대신 늘리는 편이 전체를 보기 쉽다. Ctrl+Enter 로 등록한다 — 여러 줄을 적는 칸이라 Enter 는 줄바꿈이어야 한다 |
| v0.385 | 삭제는 is_deleted 를 세운다 — 잘못 지웠을 때 되살릴 방법이 있어야 한다. 메모는 맨 아래로 옮겼다 |
| v0.386 | 메모 입력칸을 한 줄로. 오른쪽에 등록 버튼을 두고 Ctrl+Enter 안내 문구는 없앴다 — 메모는 대개 한 문장이라 여러 줄 칸을 늘 펼쳐두면 자리만 차지한다. 길게 적으면 그때 늘어난다 |
| v0.386 | 입력칸을 상자 없이 밑줄만으로 — 아래 목록이 밑줄이라 성격을 맞춘다. 상자를 두면 입력칸만 도드라져 목록과 따로 노는 것처럼 보인다 |
| v0.386 | 메모 내용도 한 줄. 길면 말줄임하고 마우스로 전체를 본다 |
| v0.386 | 수정 · 삭제 는 그 줄에 마우스를 올렸을 때만 나온다 — 늘 보이면 메모마다 버튼이 둘씩 붙어 내용보다 눈에 먼저 든다 |
| v0.387 | 메모 한 건을 한 줄에 담았다 — 두 줄로 나누면 다섯 개만 있어도 열 줄이 되어 훑기 어렵다 |
| v0.387 | 내용이 먼저다 — 목록을 훑을 때 찾는 것은 내용이지 날짜가 아니다. 일시 · 작성자는 뒤에 둔다 |
| v0.387 | 마우스를 올리면 일시 · 작성자 자리를 버튼이 덮는다 — 버튼을 위해 자리를 따로 비워 두면 그만큼 내용이 좁아진다 |
| v0.387 | 메모 라벨과 입력줄 높이를 맞췄다 (padding-top 8 → 5px · 위 여백 제거) |
| v0.388 | 일시를 한국 시각으로 바꿨다 — 저장은 UTC(timestamptz) 인데 문자열을 잘라 그대로 보여주고 있었다. 11:09 에 적은 메모가 02:09 로 보였다 |
| v0.388 | 여섯 곳이 모두 같은 방식이었다 — 메모 · 메모 수정 · 개선의견 · 업로드 · 최근 접속 · 히스토리. _kst() 한 곳으로 모았다 |
| v0.388 | 날짜만 쓰는 곳도 고쳤다 (_kstD()) — 시각이 없어도 UTC 자정 근처에서는 하루가 밀린다. 한국 09시 이전에 저장된 건이 전날로 보인다 |
| v0.389 | 확인창을 화면 중앙에 띄운다 — 브라우저 confirm 은 창 맨 위에 떠서 보던 자리에서 눈이 멀리 움직이고, 브라우저마다 생김새가 달라 우리 화면과 따로 논다 |
| v0.389 | confirm 을 쓰던 11곳을 모두 바꿨다 — 한 곳만 바꾸면 화면마다 다른 창이 뜬다. deleteNote · deleteTeam · removeMember 세 곳은 async 가 아니라 함께 붙였다 |
| v0.389 | Promise 로 돌려줘 if (!await ask(…)) return; 한 줄로 쓴다. 지우는 동작은 붉은 버튼으로 구분하고 확인 대신 삭제 · 반영 처럼 할 일을 적는다 |
| v0.390 | 고객 상세 연락처가 비어 보이던 문제 수정 — main_phone(대표전화) 만 보고 있었다. 실측 900건 중 674건(75%) 이 휴대폰만 있어 빈칸으로 보였다 |
| v0.390 | 대표자휴대폰을 먼저 본다 — 대표전화만 있는 경우는 0건이라 휴대폰이 우선이다 |
| v0.391 | 담당자를 여러 명 관리한다 (crm_contacts) — 원장에 대표자 한 명, 문의에 신청자 한 명뿐이라 한 사무소에 세무사가 여럿이고 실무 담당자도 따로 있는데 담을 곳이 없었다 |
| v0.391 | 구분 3종 — 신청자 · 세무사 · 담당자. 이름 · 핸드폰 · 이메일 · 메모를 적고 별표로 먼저 연락할 사람을 표시한다. 별표는 여럿 가능하고, 목록에는 세무사를 먼저 쓴다 — 연락은 대개 그쪽부터 한다 |
| v0.391 | 사업장에 대표전화 칸을 넣었다. 신청자 · 연락처 · 이메일은 담당자 표로 옮겼다 — 한 사무소에 사람이 여럿이라 한 칸으로는 담기지 않는다 |
| v0.391 | 기존 값을 옮긴다 (SQL 208 · 209) — 원장 대표자 → 세무사 · 별표, 문의 신청자 → 신청자. 처음부터 비어 있으면 아무도 채우지 않는다.
대표자와 같은 이름은 넣지 않는다 — 한 사람이 두 줄로 보인다 |
| v0.391 | 칸을 벗어날 때 저장한다 — 한 글자마다 보내면 요청이 쏟아진다. 삭제는 is_deleted 를 세운다 |
| v0.392 | 한 사업자에 계정이 여럿이면 몇 번째인지 적는다 — 세무법인송촌 (김명선) 2. 세무법인진명처럼 이름과 대표자가 같으면 목록에서 구분할 방법이 없다 |
| v0.392 | 번호는 가입일 순이다 — 처음 가입한 곳이 1번. 배열 순서(정상 > 정지 > 해지) 와 다르다.
그 순서는 「어느 것을 대표로 볼까」 이고 번호는 「몇 번째로 만든 계정인가」 라 기준이 다르다 |
| v0.392 | 지점 선택 버튼에도 번호와 대표자를 붙였다 — 이름이 같으면 상태만으로는 구분이 어렵다. 마우스를 올리면 이용기관코드와 가입일이 나온다 |
| v0.393 | 목록에서는 번호를 빼고 상세에서만 보여준다 — 목록은 대표 1건만 나오는 자리라 「3번째 계정」 이 「계정 3개」 로 읽힌다 |
| v0.394 | 목록에 이용기관 개수를 적는다 — 세무법인송촌 (김명선) 3. 몇 번째가 아니라 몇 개인가다.
대표 1건만 보이는 자리이므로 알아야 할 것은 「다른 계정이 더 있나」 이고, 몇 번째인지는 상세의 지점 버튼에 붙는다 |
| v0.395 | + 담당자 버튼을 표 머리 오른쪽으로 옮겼다 — 표 아래에 두면 그만큼 줄이 늘고, 담당자가 없을 때는 버튼이 홀로 떠 보인다 |
| v0.396 | 가입 이력이 없는 고객도 같은 화면을 쓴다 — 담당자 표와 메모를 붙였다. 원장에서 오는 현황만 빠진다 |
| v0.396 | 담당자와 메모는 가입 전에 더 필요하다 — 누구를 만났고 무슨 얘기를 했는지가 영업의 내용이다. 그런데 그 자리가 비어 있었다 |
| v0.396 | SQL 211 — 미가입 사업자의 신청자를 담당자로 넣는다. 원장이 없으면 그 사람이 유일한 연락처라 별표를 붙인다 |
| v0.397 | 기획서에 v0.384~v0.396 반영 — 확정 항목 34~37 추가(담당자 여러 명 · 메모는 쌓는다 · 일시는 한국 시각 · 확인창은 화면 중앙) |
| v0.397 | 10번에 담당자와 메모 절 신설 — 구분 3종과 실측(세무사 886 · 신청자 393), 별표 규칙, 가입 전에 더 필요한 이유. 메모는 왜 덮어쓰면 안 되는가를 다섯 규칙으로 |
| v0.397 | 4번에 사람 · 기록 계층 추가 — crm_contacts · crm_notes · crm_note_edits. 모두 사업자 단위인 이유를 적었다 |
| v0.397 | 8번 미확정 표를 다시 썼다 — 완료된 항목이 섞여 있었다. 남은 12건을 우선순위로 정리했다 (Must 3 · Should 4 · 2차 5) |
| v0.398 | 담당자 블록 위아래 여백을 같게 하고, 현황 · 담당자 표 머리 위에도 선을 넣었다 — 위에 선이 없으면 앞 줄과 붙어 어디서 표가 시작하는지 흐려진다 |
| v0.398 | 문의 상세 주소에 복사 · 공유를 붙였다 — 고객 탭에는 있었는데 리드 탭에만 없었다. 방문 전에 지도 앱으로 옮기는 일이 잦다 |
| v0.398 | 휴지통 되돌리기 가 두 줄로 접히던 문제 — 열 폭이 60px 이라 4자가 들어가지 않았다. 74px 로 넓혔다 |
| v0.398 | 메모에 첨부를 붙였다 (SQL 217) — 방문 사진이나 받아온 자료를 남긴다. 개선의견과 같은 버킷 · 같은 경로 규칙을 쓴다 (menu-files · crm/) |
| v0.398 | SQL 215 · 216 — Storage 업로드 권한. 버킷이 public 이라도 쓰기는 별개다.
crm/ 경로만 열어 CRM 에서 BP 첨부를 지울 수 없게 한다 |
| v0.399 | 현황 · 담당자 표 아래에도 선을 넣었다 — 위만 있으면 어디서 끝나는지 흐려지고 다음 줄(보고서) 과 붙어 표의 일부처럼 읽힌다 |
| v0.400 | 미리보기를 잘렸을 때만 보여준다 — 다 보이는데 뜨면 읽는 것을 가려 방해만 된다. scrollWidth > clientWidth 로 판정한다 |
| v0.400 | 사무소명도 같게 고쳤다 — 브라우저 title 은 늘 뜬다. 마우스가 들어올 때 판정해 잘렸을 때만 붙인다 |
| v0.401 | 컨설팅 필터 줄 첫 버튼을 전체 → 컨설팅 으로 — 전체 라고 하면 잠재까지 포함하는 것처럼 읽힌다. 실제로는 컨설팅에서 진행 중인 건만 센다 |
Supabase 요국당 1,000행 제한
조용하게 잔렸다
Supabase 는 요국당 기본 1,000행만 반환하며 .limit(3000) 으로는 넘지 못한다. 서버 max-rows 설정에 마혀 잔리는데 에러가 안 나기 때문에 드러나지 않는다.
칸반 건수가 49 + 54 + 0 + 11 + 886 = 1,000 이라는 우연한 합계를 보고서야 알아처렸다. 588건이 버려지고 있었다.
| 방법 | 통계 |
| 대시보드에서 제한 상향 | 권하지 않음. 문제를 미룰 뿐이고 데이터가 늘면 다시 잔린다. 그때는 원인 찾기가 더 어렵다 |
.range() 페이지네이션 | 채택. 1,000행씩 나눠 받아 합치다. 서버 설정 의존이 없고 1,588건이면 요국 2번이라 부담도 없다 |
| 새로 다 받지 않기 | 데이터가 5,000건을 넘으면 필요. 컬럼별 limit 50 + 건수는 count head 로 분리 |
같은 제한이 걸리는 곳
잠재고객 로드와 배치 지오코딩은 이밌 페이징이 들어가 있어 문제가 없다. 새 조회를 만들 때마다 전체 건수와 실제 로드 건수를 대조하는 것이 안전하다.
DB 작업 이력
| 파일 | 내용 |
| v0.061 | step_group · step_detail · 가능성 · 위험도 · 다음액션 컴럼 추가. crm_stage_history 생성 |
| v0.062 | crm_inquiries_step_backup — 이관 전 1,588건 스냅샷 |
| v0.063 | 신규 단계 체계로 이관 |
| v0.067 | 담당자 분리 — Flow 담당자를 flow_assignee 로 이동, assignee 는 CRM 배정 전용 |
| v0.068~069 | crm_settings 생성. 위험 기준 2개로 단순화 |
| v0.065 | 기존 step 컴럼 제거 — 실행 완료. 값은 백업 테이블에 보존 |
이관 결과
| 그룹 | 세부 단계 | 건수 | 비고 |
| 문의 | 담당자배정 | 48 | 헤나 일괄 배정 |
| 신규접수 | 2 | 2026-07 이후 등록 |
| 컨설팅 | 대기 | 63 | 중 57건 판정보류 |
| 온보딩 | 대기 | 11 | 최근 3개월 가입 |
| 활성화 | 정상 | 1,464 | 전량 이관분 |
이관분 표시가 있어야 통계가 산다
1,464건은 CRM 안에서 온보딩을 거친 적이 없다. migrated_at 을 찍어 온보딩 전환율 계산에서 제외한다. 이걸 안 하면 평균 소요일 같은 지표가 의미를 잃는다.
v0.233 동기화 결과 (실행 완료)
| 항목 | 이전 | 이후 | 내용 |
crm_inquiries | 1,588 | 1,652 | +101 INSERT · −37 is_deleted |
crm_inquiry_activities | 58 | 59 | +7 INSERT · −6 DELETE · 26건 유형 재분류 |
| 링크패스 문의 | 16 | 72 | Link Path 도입문의(구 표기) 56건 복구 |
| 성장패키지 | 0 | 10 | 문의 4 · 요금변경 6 |
biz_no 공백 | 757 | 0 | 하이픈 없는 10자리 파싱 추가 |
| 미사용 컬럼 | plan · plan_promo | 제거 | 실측 0건 · inquiry_promo 와 개념 중복 |
가장 큰 누락 — 제목 표기 변경을 추적하지 않으면 못 찾는다
Link Path 도입문의(2024-09~2025-01) 가 링크패스 도입문의(2025-02~) 로 바뀐 것을 이전 이관이 반영하지 못해 56건이 통째로 빠져 있었다. 링크패스 문의가 16건으로 보였던 이유다.
같은 유형의 표기 변경이 세모리포트 신청 → 세모R 신청, 인사이트 신청 → 세모R 플러스 신청 에도 있다. 제목을 판정 키로 쓰면 안 되는 이유이며, 9-C 의 본문 필드 기반 규칙이 이를 해결한다.
신규 101건은 지도에 뜨지 않는다
구 템플릿이 주소를 받지 않아 address 43/101 · email 61/101 · 거래처수 28/101 만 채워졌다. 좌표가 없어 MAP 탭에서 빠진다. biz_no 로 crm_prospects 와 매칭해 주소 · 좌표를 보완해야 한다. 8번 미확정 #12(문의 양식 주소 필수화) 와 같은 원인이다.
활동유형 10종 (확정)
기존 앱 입력값 6종에 Flow 파생 4종을 더한 합집합. 제목 키워드로 자동 분류한다. 위에서 아래로 먼저 매칭한다.
| 유형 | 건수 | 키워드 |
| 촬영 | 1 | 촬영 |
| 문자 | 6 | 문자 |
| 이메일 | 0 | 메일 |
| 방문 | 19 | 방문 — 목적어보다 우선 (kevin 확정) |
| 컨설팅 | 11 | 컨설팅 · 셋업 · 교육 · 설명 · 시연 |
| 상담 | 1 | 상담 · 협의 · 문의 |
| 요금/정산 | 2 | 요금 · 면제 · 선결제 · 프로모션 · 세금계산서 · 견적 |
| 가입/해지 | 1 | 가입 · 해지 · 정지 · 활성화 |
| 통화 | 18 | 통화 · 전화 · 접촉 |
| 기타 | 0 | 위 어느 것도 아님 |
제목에 방문이 있으면 방문으로 확정한다
방문 컨설팅 · 컨설팅 방문 진행 처럼 목적과 방문이 함께 있는 건이 8건 있었다. 방문 우선으로 확정했다. 목적은 activity_note 에 원문이 남는다.
촬영 만 방문보다 위에 둔다 — 1건이고 성격이 명확히 다르다.
코딩 원칙
- 비동기 DB 호출에 in-flight 가드 필수 — 지도 idle 이벤트가 요청 폭주를 유발한다
- 에러를 삼키지 말 것 —
console.warn 으로 둥치면 권한 문제와 “데이터 없음”을 구분할 수 없다
- 재시도해도 소용없는 실패는 차단 — 권한 · 인증 오류는 매 이벤트마다 재요청해도 결과가 같다
- 조건부 페이징 주의 — 처리 결과가 조회 조건을 바꾸면 offset 이 어긋난다. 실패분만큼만 전진시키고 무한루프 가드를 둔다
- 배포 전
node --check — 인라인 script 블록을 추출해 문법 검증
- 문법 검증만으로는 부족하다 — 상수 선언이 통째로 삭제되어도 통과한다. 함수 부하를 이동 · 교진할 때는 중복 선언 탐지와 필수 식별자 존재 검사를 함께 돌린다
- 범위 지정 교진 주의 — “다음 함수 진전까지” 를 재단하는 방식은 그 사이의 상수 선언도 함께 지운다. 이 사고로 v0.215 · v0.225 두 번 같은 버그가 발생했다
<button> 은 font-size 를 명시한다 — 및로우젌 기본값(Chrome 13.33px)이 상속을 가로말아 나란한 div 와 크기가 어긋난다
- 높이는
calc(100vh - N) 대신 flex 로 — 헤더 높이가 바뀌면 어긋나며, 넘친 부분이 부모의 overflow:hidden 에 잔려 스톤론도 안 된다
- 버전 4단계 —
<title> · CRM_VERSION · 파일명 · 문법검증
v0.500 → v0.531 (2026-08-14)
사용자 명부 분리 — employees → crm_users
CRM 이 BP 소유 테이블을 공유하던 구조를 끊었다. BP 명부가 외부 회사 직원으로 오염되지 않고, CRM 은 필요한 필드를 자유롭게 가진다.
| 대상 | 등록 경로 |
| 로움 직원 | BP 에서 등록 → 트리거가 crm_users 에 동기화. BP 화면 · RPC 6개를 손대지 않았다 |
| 외부 회사 | CRM 가입 신청 → crm_users 직접 (source='crm'). employees 를 건드리지 않는다 |
필드 소유권을 갈랐다
BP 가 주인 — email · name · dept · approved · access_until. 트리거가 덮어쓴다.
CRM 이 주인 — company_id · team_id · is_admin · is_active. 트리거가 손대지 않는다.
nickname 은 비어 있을 때만 채운다 — 담당자 저장값이라 바뀌면 기존 문의 연결이 끊긴다.
| 항목 | 내용 |
| 관리자 독립 | crm_users.is_admin 으로 CRM 만의 관리자를 둔다. admins(BP) 를 공유하면 위멤버스 관리자에게 BP 권한까지 열린다 |
| 판정 함수 | crm_is_admin() · crm_is_loum_admin() · crm_my_company() — 모두 SECURITY DEFINER. RLS 정책 안에서 같은 테이블을 조회하면 무한 재귀가 되므로 소유자 권한으로 우회한다 |
| 거절 처리 | 물리 삭제 → rejected_at 표시로 바꿨다. 지우면 담당자 · 유입코드 참조가 끊긴다 |
user_id 채우기 | 로그인 시 직접 채운다. 비면 crm_my_company() 가 false 를 돌려 RLS 가 아무것도 통과시키지 않는다 |
record_user_login 의 회귀를 찾았다
함수가 두 개였고 ON CONFLICT 대상이 달랐다 — 1인자는 email, BP 가 쓰는 2인자는 user_id.
pre_add_employee_v2 는 user_id 없이 행을 만들므로 첫 로그인에서 email UNIQUE 를 위반하고,
호출부가 try/catch 로 삼켜(console.warn) user_id 가 영구히 비어 있었다.
그래서 is_crm_admin() 이 항상 false 였다. 충돌 기준을 email 로 고쳤다.
유입코드 — 실적 귀속
| 항목 | 내용 |
| 축 | 유입 = 실적이 누구 것인가. 담당 배정(assignee) 과 다른 축이다 — 섞지 않는다 |
| 관계 | 코드 → 회사 1:1(귀속 판정) · 사용자 → 코드 1:N(블로그 · 홈페이지 · 뉴스레터) · 코드 → 실적자 1:1 |
| 집계 | 이 구조라 코드별 건수를 실적자로 묶으면 합계가 정확히 맞는다. 한 코드에 여러 명을 묶었다면 배분 문제가 생겼다 |
| Flow 원본 | crm_inquiries.inflow_code(텍스트) 는 그대로 보존하고 inflow_code_id 를 따로 붙였다 — 덮어쓰면 매핑이 틀렸을 때 되돌릴 수 없다 |
| 코드값 잠금 | 연결 문의가 1건 이상이면 code 수정 차단. 고치면 연결이 깨진다 |
잠재 ↔ 사무소 중복 자동 정리
같은 업체가 crm_offices 와 crm_prospects 에 각각 있어 지도 목록에 두 줄로 보였다. 유입 시점에 걸러지지 않아 누적된 것이다.
판정을 DB 함수 한 곳에 뒀다
crm_link_prospect() 를 트리거 · 1회성 정리 · 앱이 모두 부른다.
경로마다 규칙을 따로 넣으면 갈리고, 갈리는 것이 애초에 중복을 만든 원인이었다.
트리거가 crm_inquiries 의 INSERT 와 좌표 갱신 두 시점에 걸려 있어
Flow Data 일괄등록 · 수기 등록 · SQL 직접 삽입을 모두 지난다.
| 구분 | 건수 | 처리 |
| 완전일치 | 226 | 자동 연결 · auto_exact |
| 앞부분 포함 | 16 | 자동 연결 · auto_prefix — 샤인택스 / 샤인택스(SHINETAX) |
| 부분 포함 | 4 | 자동 연결 · auto_contain |
| 좌표만 일치 | 2,484 | 손대지 않는다 — 좌표는 건물 단위다. 현대타워 한 곳에 다른 사무소가 11곳 있었다 |
좌표만 일치는 중복 신호가 아니다
지오코딩 결과는 건물 단위라 같은 빌딩의 다른 업체가 전부 같은 좌표를 갖는다.
이름 정규화(crm_norm_name) 를 반드시 함께 본다.
그 함수는 이미 있었고 실제 데이터로 다듬어져 있었다 — 세무사사무소 · 세무회계사무소 → 세무회계 동의어 통일 등.
새로 만들려다 CREATE OR REPLACE 의 인자명 제약에 막혀 덮어쓰지 않았다.
| 항목 | 내용 |
| 제거 방식 | is_deleted=true. 물리 삭제하지 않는다 — kakaoid 로 관리되는 외부 수집 명단이라 지우면 다음 수집 때 다시 들어와 매번 지우게 된다 |
crm_stage | 건드리지 않는다. 사무소가 진실이다 — 복사하면 사무소 단계가 바뀔 때마다 어긋난다 |
| 되돌리기 | match_method LIKE 'auto_%' 로 근거별 선택 원복이 된다 |
지도 — 좌표 그룹 통합
| 항목 | 내용 |
| 겹침 원인 | 고객 레이어에 좌표 그룹핑이 없었다. 잠재 레이어에는 있었는데 두 레이어가 서로를 몰랐다 |
| 통합 | 좌표 하나당 마커 하나. 목록에 고객 + 잠재를 함께 보여준다. 대표는 고객이 맡고, 고객끼리는 진행이 앞선 쪽 |
| 필터 | 필터가 마커를 결정한다 — 실패 를 끄면 그 고객이 빠지고 잠재가 대표를 맡는다. 「필터와 겹침 판정을 섞은 것」 이 원래 버그였다 |
| 표기 | 이름 뒤 +N(좌표의 전체 건수). 클릭은 이름 라벨만 — 네이버 마커의 click 은 아이콘 전체에 걸리므로 마커 이벤트를 쓰지 않고 라벨 onclick 으로 처리한다 |
| 검색 | 검색 전용 마커를 만들지 않는다. 검색은 강조할 좌표만 정하고 그리는 일은 본 레이어가 전담한다 — 겹침 · 배지 소실 · 목록 누락이 한 번에 해소됐다 |
팝업은 마커가 아니라 좌표에 붙인다
클릭 핸들러가 _renderNaverMarkers() 로 마커를 지운 뒤 그 마커에 InfoWindow 를 열면 표시되지 않는다.
같은 클릭이 document 까지 버블링될 때 e.target 이 떨어진 노드가 되어
「지도 밖 클릭」 으로 오인해 방금 연 팝업을 닫는 문제도 함께 있었다.
활동 유형 2단 구조
| 대분류 | 소분류 (저장값) | 일자 · 단계 |
| 상담 | 채팅 · 통화 | activity_date = 등록일 · 단계 → 접촉 |
| 방문 | 방문 · 촬영 | activity_date = 등록일 · 단계 → 접촉 |
| 예약 | 방문예약 · 온라인상담 | scheduled_at 입력 · 단계 → 방문예약 / 온라인상담 |
| 발송 | 문자 · 이메일 · 카톡 | activity_date = 등록일 · 단계 → 접촉 |
| 기타 | 기타 | 단계를 건드리지 않는다 |
일자를 두 컬럼으로 나눴다
activity_date 는 실제 일어난 날, scheduled_at 은 예약 일시다.
한 컬럼에 섞으면 「예약일이 지났는지」 를 판정할 수 없어 일정관리가 성립하지 않는다.
실측으로 방문 20건의 최신 일자가 미래(2026-08-20) 였다 — 사람들이 방문에 미래 날짜를 적어 예약처럼 쓰고 있었다.
단계 자동 변경 서열
대기(0) < 접촉(1) < 방문예약 · 온라인상담(2) < 가입완료 · 문의종료(3)
올라갈 때만 바꾼다. 없으면 방문예약 건에 확인 전화 한 번으로 단계가 접촉으로 밀려 예약이 사라진다.
scheduled_at > now() 인 유효 예약은 고정되고, 예약일이 지나도 활동이 없으면 그대로 둔다(자동 강등 없음).
실패 그룹은 자동 변경 제외 — 재접촉 전화 한 통에 실패 목록이 조용히 비워진다.
안정성
| 항목 | 내용 |
range() 정렬 누락 | 7곳 전수 수정. ORDER BY 없는 페이지네이션은 반환 순서가 보장되지 않아 같은 행이 두 번 오고 다른 행은 빠진다. 총 건수는 맞는데 집합이 달라져 실행마다 지도에 없는 사무소가 생겼다. id 로 정렬한다 — 고유하고 불변이다 |
| 네트워크 재시도 | ERR_QUIC_PROTOCOL_ERROR · Failed to fetch 는 서버에 닿지도 못한 경우라 3회 재시도한다. DB 오류(권한 · 테이블 없음) 는 다시 보내도 같으므로 재시도하지 않는다 |
| 오류 안내 | 토스트는 2.2초 뒤 사라져 조치가 필요한 안내에 맞지 않는다. 사라지지 않는 안내창 + 새로 고침 버튼 |
| CRM 접속 통계 | employees.login_count 는 BP 누적값이라 CRM 이 갱신하지 않는다. crm_access_log 에 action='login' 을 남기고 crm_login_stat 뷰가 집계한다 — 요청 1회 · 결과 인원수만큼 |
rel="noopener" | 외부 링크 6곳 누락. _safeUrl() 로 스킴 검증도 함께 — javascript: 차단 |
SQL 전달 · 검증에서 배운 것
① Supabase SQL Editor 는 스크립트 전체를 한 트랜잭션으로 실행한다.
마지막 검증 쿼리의 사소한 오류가 앞의 DDL 까지 롤백시킨다 — 검증은 별도 파일로 분리한다.
② 결과를 봐야 하는 조회는 파일 하나에 쿼리 하나. 여러 개를 묶으면 뒤쪽 하나가 실패해 앞쪽 결과까지 못 본다.
③ 데이터를 바꾸는 SQL 앞에는 대상 건수 확인을 같은 흐름에 둔다.
측정에는 crm_stage IS NULL 을 넣고 함수에서 빼먹어, 246건이라고 본 것이 697건이 되었다.
검증을 「만들어졌는지」 가 아니라 「무엇에 영향을 주는지」 로 한다.
v0.532 → v0.562 (2026-08-14)
회사 격리 — 여덟 화면을 하나씩
외부 회사 계정에 로움 데이터가 그대로 보였다. 집계 진입점이 흩어져 있어 화면마다 따로 고쳐야 했다.
같은 필터를 여덟 곳에 넣었다
원인이 하나가 아니라 집계 함수가 흩어져 있는 구조다.
화면이 늘 때마다 같은 일이 반복된다 — 집계 진입점 일원화를 미결(E절) 에 남겼다.
| 버전 | 화면 | 고친 것 |
| v0.550 | 지도 | 회사를 고르면 그 회사 시점이 된다. 이전에는 고른 회사 것만 남기고 나머지를 버려 로움 고객 1,144건이 사라지고 타채널이 0 이 됐다 |
| v0.553 | 고객원장 로드 | 외부 회사 계정은 사무소가 자기 회사인 원장만 읽는다. crm_customers 에 company_id 가 없어 사무소를 사업자번호로 이어 판정한다 |
| v0.554 | 활성화 · 컨설팅 · 칸반 · 검색 · 드롭다운 건수 | 회사 판정을 담당자 이름으로만 하고 있었다. 담당자가 비면 null 이라 걸러지지 않는다. crm_inquiries.company_id 를 먼저 보게 했다(_inqCompany) |
| v0.555 | 고객 목록 행 | 필터에 회사 조건이 아예 없었다 |
| v0.556 | 고객 상단 카드 · 분모 | 세 곳이 각자 세어 「전체 890 인데 목록 0건」 이 됐다 |
| v0.557 | 리드 · 컨설팅 · 온보딩 분모 활성화 칩 5개 | _riskBase() 한 곳을 고치니 칩 다섯 개와 분모가 함께 맞았다 |
| v0.558 | STEP 신규고객 · 정지 · 해지 | _stageStats() 에 의존하는 세 카드가 함께 맞는다. 성장패키지만 crmInquiries 를 직접 순회해 따로 넣었다 |
판정 기준을 셋으로 나눴다
_inqCompany(inq) — 문의. company_id 우선, 없으면 담당자 추적
_companyOfBiz(biz) — 사업자. 사무소의 company_id 가 기준
_companyOfAssignee() — 담당자 추적. 이제 보조로만 쓴다
원장에 company_id 가 없는 것이 근본 제약이다 — RLS 로 막으려면 그 컬럼과 업로드 시점 판정 규칙이 먼저 필요하다.
보안 — 자격증명이 열려 있었다
anon 에 전권이 열린 테이블이 51개였다
그 안에 work_account_creds · acc_hp_pw_history · acc_homepages 가 있었다.
anon 키는 HTML 에 평문으로 박히므로 키를 가진 누구나 저장된 비밀번호를 읽고 · 고치고 · TRUNCATE 로 비울 수 있었다.
SQL 295 로 네 테이블의 anon 권한을 전부 회수했다 — 자격증명은 읽히는 것 자체가 사고다.
| 항목 | 내용 |
| 기본 권한의 정체 | 역할별로 따로 있다. postgres(SQL Editor) 는 anon 이 이미 빠져 있고, supabase_admin(Table Editor) 은 전권 arwdDxtm 이 그대로다. 51개가 그렇게 생겼다 |
| 바꿀 수 없다 | supabase_admin 의 기본값은 SQL Editor 로 변경 불가(42501) — 자기 역할 것만 바꿀 수 있다 |
| 유일한 예방 | 테이블은 SQL 로만 만든다. 권한이 없어 401 이 나면 필요한 만큼만 GRANT 한다. 조용히 열려 있는 쪽보다 낫다 |
TRUNCATE 와 RLS | 정책은 행 단위 통제라 TRUNCATE 를 막지 못한다. 권한 자체를 회수해야 한다 |
버전 확인 — 옛 파일이 계속 돌았다
가입 신청이 employees 로 들어간 사건
신청자가 브라우저에 캐시된 v0.516 이전 파일을 쓰고 있었다. 그 버전은 employees 에 넣었고
employees_signup 정책에 source 조건이 없어 통과했다.
CRM 승인 화면은 crm_users 를 보므로 신청이 어디에도 보이지 않았다.
| 대응 | 내용 |
| 이관 | SQL 288 — crm_users 로 옮기고 BP 명부에서 제거 |
| 저장 확인 | .select() 로 삽입된 행을 돌려받아 검사한다. RLS 가 막으면 오류가 아니라 0행 삽입으로 조용히 끝난다 — 신청자는 성공한 줄 알고 관리자는 아무것도 못 봤다 |
| 버전 알림 | 로고를 누르면 배포된 파일을 직접 받아 APP_VERSION 을 읽어 비교한다. 한때 crm_settings 와 대조했으나 버전이 두 곳에 있어 배포마다 SQL 이 필요했다 — 파일이 유일한 진실이면 배포만으로 반영된다 |
| 캐시 우회 | cache:'no-store' 와 쿼리 문자열 두 겹. Netlify 가 HTML 을 캐시하면 한 겹으로는 옛 파일이 온다 |
웰컴 화면 · 명언
| 항목 | 내용 |
| 이식 | BP v4.280 의 구조를 옮겼다 — 명언 205개 · 상황별 표시 시간 · 요일 · 시간대 · 계절 문구 |
| 명언 공유 | 인덱스를 ai_resources 의 같은 code 로 공유한다(KB_DAILYQUOTE_{uid}). 아침에 BP 에서 본 명언과 CRM 의 명언이 같다 — localStorage 만 쓰면 기기마다 달라져 「하루 하나」 가 깨진다 |
| 표시 시간 | BP 값(3.8 · 1.5 · 0.7초) 을 늘렸다(4.2 · 2.6 · 1.8). CRM 은 IndexedDB 캐시가 있어 로딩이 빨라 인사말을 읽을 틈이 없었다 |
다른 파일에서 코드를 옮길 때 — 행 번호로 자르지 않는다
BP 의 CSS 를 행 범위로 잘라 오면서 끝의 </style> 까지 가져왔다.
CRM 의 style 블록이 조기 종료되어 그 뒤 CSS 수백 줄이 전부 무효가 됐다.
checkbp.py 의 CSS 미정의 경고가 10 → 72 로 뛴 것이 신호였다 — 그 수치를 넘겼다면 찾기 어려웠다.
지도 · 목록
| 항목 | 내용 |
| 타채널 표기 | 타채널고객 → 타채널. 대표자 · 전화를 작게, 담당자는 감춘다 — 남의 회사 담당자라 우리 화면에서 의미가 없다 |
| 삼각형 통일 | 지도 마커 · 상단 칩 · 목록 카드 세 곳. 갈리면 「지도의 그 삼각형이 어느 줄인가」 를 매번 짚어야 한다 |
| 전화번호 구조 | phone(유입 원본) · mobile_phone · office_phone 으로 나눴다. inflow_code 와 inflow_code_id 를 나눈 것과 같은 구조 — 원본을 덮어쓰면 분류가 틀렸을 때 되돌릴 수 없다 |
| 대표전화 저장 | crm_inquiries 에 main_phone 컬럼이 없어 지금까지 계속 실패해 왔다(42703). crm_offices.office_phone 으로 보낸다. 읽기도 같은 자리를 봐야 저장한 값이 화면에 돌아온다 |
STEP 화면 재배치
| 항목 | 내용 |
| 배치 | 리드 · 컨설팅 · 실패 · 온보딩 │ 신규고객 │ 활성화 · 정지 · 해지 왼쪽은 카드(진행 중), 오른쪽은 지표(결과) 로 갈린다 |
| 신규고객 2단 | 유입고객 위, 가입고객(전환) 아래. 서비스 분해도 같은 형태다 — 위에서 아래로 전환 흐름이 읽힌다 |
| 문의 집계 기준 | 사업자 단위로 센다. 같은 사업자의 문의가 여럿이면 한 곳으로 봐야 유입 수가 부풀지 않는다. 가장 이른 문의를 유입 시점으로 본다 |
| 분류 차이 | 원장은 insight_joined_at, 문의는 inquiry_service 로 세모R+ 를 가른다. 같은 뜻이지만 출처가 달라 숫자가 정확히 대응하지 않을 수 있다 |
안정성 · 데이터
| 항목 | 내용 |
| 수정 기록 | crm_field_edits(SQL 279) 신설. 사업장 편집이 저장될 때 실제로 바뀐 필드만 남긴다. Flow 업로드가 그 필드를 덮지 않게 하는 근거다 |
| 고객원장 삭제 | 상세 하단에 버튼. utlz_id 로 한 건만 지운다 — biz_no 로 지우면 같은 사업자의 다른 이용기관까지 사라진다 |
| 사업자번호 형식 | 테이블마다 달랐다 — 사무소 · 원장은 000-00-00000, 문의 5건만 하이픈 없음. 전부 테스트 데이터였다. 형식이 어긋나면 고객 상세에서 문의가 연결되지 않는다 |
about:blank | 기획서 창은 origin 이 없어 history.replaceState 가 SecurityError 를 던진다. 존재만 검사하고 호출 실패를 대비하지 않아 클릭 핸들러가 중단됐다 |
권한 정리 — anon 쓰기 51개 → 0
감사에서 시작해 다섯 묶음으로 나눠 회수했다. 자격증명이 열려 있던 것이 가장 급했다.
| SQL | 대상 | 내용 |
| 293 | 감사 | anon 전권 51개 발견. 그 안에 work_account_creds · acc_hp_pw_history 가 있었다 |
| 295 | 자격증명 4개 | 가장 급했다. anon 은 읽기까지 회수 — 비밀번호는 읽히는 것 자체가 사고다 |
| 305~309 | BP 47개 | 기획 노트 8 · 메뉴 11 · 개선의견/세모 8 · 업무계정/서류 9 · 로그 12 |
| 311 · 310 | CRM 28개 | TRUNCATE 회수 · 백업 6개 읽기 전용 |
| 312 | BP 공유 4개 | employees · teams · admins · employees_active 의 anon 읽기 |
TRUNCATE 는 RLS 를 지나지 않는다
정책은 행 단위 통제라 테이블을 비우는 것을 막지 못한다.
crm_prospects 에 그것이 열려 있어 로그인한 누구나 잠재 11,758건을 한 번에 지울 수 있었다.
되돌릴 방법은 백업뿐이고 kakaoid 수집 명단이라 재수집도 오래 걸린다.
권한과 정책은 층이 다르다 — 정책만 보고 안심하면 안 된다.
DO 블록은 실행 여부를 알 수 없다
결과창이 비어 있어 305 가 두 번 미실행 상태로 넘어갔다.
이후 확인 쿼리를 같은 파일에 붙였다 —
「검증은 별도 파일로」 원칙과 반대지만, 실행됐는지 모르는 상태가 더 위험하다.
태그 짝 검사의 한계 — 두 오류가 서로를 상쇄했다
s6 에 </div> 하나가 부족하고 s7 에 하나가 남아 전체 개수는 맞았다.
그래서 checkbp.py 가 통과했고, s6 가 s7 을 삼켜 개발 반영 이력이 통째로 안 보였다.
div 균형 문제로 화면이 사라진 것이 v0.363 · v0.532 두 번이다 —
검사기에 섹션별 독립 검증을 넣어야 한다(E절).
v0.771 → v0.805 (2026-08-21 ~ 22)
예약을 처리하는 자리를 세 화면에 똑같이 깔고,
고객원장에 회사 격리(RLS) 를 걸었다.
화면마다 갈려 있던 것 — 색 · 컬럼 · 판정 · 저장 경로 — 을 한 곳으로 모으는 판이었다.
이번 판에서 되풀이된 것
① 한 곳에서 판정한다 — GRP_COLOR · _apptAlive ·
_isEndedInq · _apptSet · _riskBase.
화면마다 조건을 적으면 반드시 하나가 어긋난다.
② 그리는 쪽만 막고 상태를 안 끄면 되살아난다 — 사용현황상세(_avOpen).
③ 새 필드를 쓰기 시작하면 읽어오는지부터 본다 —
biz_no · done_result 가 select 에 없어
색인이 비고 취소가 안 먹었다. undefined 는 오류를 내지 않는다.
④ 거르는 곳은 세는 곳이기도 해야 한다 — 조용한 return 은 원인을 지운다.
⑤ 어긋나 보인다 ≠ 어긋났다 — 좌표를 재서 확인한 뒤에 고친다.
버튼이 상태를 말한다
결과 는 누르라는 뜻일 뿐 지금 상태를 말해주지 않았다
그리고 「완료」 라고 적혀 있으면 끝났다는 뜻으로 읽힌다 — 결과를 남기는 자리인데.
상태를 그대로 적으면 누르기 전에도 읽힌다.
예정 ──▶ 지연 날짜가 정한다 (저장값 아님)
│
└──▶ 취소 · 완료 · 가입 · 실패 고른 값이 그대로 남는다
「지연」 은 미처리인데 예약일이 지난 것이다.
목록 · 캘린더에서도 빨강으로 나와 바로 걸린다 —
v0.769 까지는 지난 미처리가 가장 흐리게 보였다.
2단계를 없앴다
v0.770 은 완료 · 취소를 고르고, 완료면 다시 세 갈래였다
① 가입까지 갔다는 것을 남기려고 두 번 눌러야 했다
② 저장값이 전부 '완료' 라 나중에 봐도 가입이었는지 실패였는지 알 수 없었다
③ 잘못 처리한 것을 되돌릴 방법이 아예 없었다 — SQL 을 직접 쳐야 했다
한 줄에 다섯을 놓고 done_result 에 고른 값을 그대로 담는다 (SQL 369).
「예정」 이 되돌리기다.
⚠ 「예정」 으로 되돌려도 단계는 되돌리지 않는다 —
활동이 옮긴 단계인지 사람이 옮긴 것인지 알 수 없어서다.
저장 경로가 둘이었다
온보딩 목록만 _onbSetupDone 으로 따로 저장했다
예약 완료와 단계 이동을 그 함수 안에서 각각 했다 —
카드 · 캘린더는 _apptDone 을 쓰는데 목록만 달랐다.
경로가 둘이면 한쪽만 고쳐 규칙이 갈린다.
지금은 세 화면이 _apptPickHtml 하나로 그리고 _apptSet 하나로 저장한다.
온보딩의 「완료」 는 온보딩|__done 으로 가고
updateInquiryStage 가 활성화|정상 + onboarding_done_at 으로 옮긴다.
영업 목록은 확인창으로 안 된다 — 다섯 갈래다.
셀 안에서 선택줄로 펼친다(날짜 편집 _lcVisitEdit 와 같은 방식).
팝업을 띄우면 표 밖으로 넘쳐 잘린다.
목록에서도 처리할 것이 먼저 보인다
영업 · 온보딩 목록에서 미처리 예약이 있는 건을 위로 올린다 (_lcHoistAppt).
활동이력에 넣은 것과 같은 규칙이고, 블록 안은 예약일시 오름차순이다.
헤더 정렬을 덮어쓰지 않는다
_lcCompare 로 정렬한 뒤에 예약 있는 것만 앞으로 빼낸다 —
나머지 순서는 고른 정렬 그대로다. 가입일이 비어 뒤로 밀린 건도 예약이 있으면 올라온다.
완료 · 취소한 예약은 올라오지 않는다. 고객 · 활성화는 예약을 보는 화면이 아니라 적용하지 않는다.
구분이 안 붙던 자리 둘
| 자리 | 고친 것 |
| 유입 화면의 방문예약 | stage_group 을 '유입' 로 남겨
활동이력에서 「미지정」 으로 보였다 — 방문일정을 넣는 순간 영업이 시작된다(kevin 확정) 로 보고 '영업' 으로 바꿨다 |
| 캘린더 | 색으로만 갈라 두어 「무슨 색이 무슨 뜻인가」 를 외워야 했다 —
일정 줄에 구분 이름을 적는다. ⚠ 활동의 구분이지 문의의 현재 단계가 아니다. 옆의 단계 칩이 그것을 말한다 |
_calIsOnb 를 걷어냈다 — 온보딩만 초록으로 특별대우하던 함수인데(v0.766),
세 구분을 모두 색으로 가르면서(v0.770) 필요 없어졌다.
구글 캘린더 연동 (v0.816)
예약을 만들면 구글 일정을 만들고, 고치면 고치고, 취소 · 삭제하면 지운다.
완료는 지우지 않는다 — 다녀온 기록이라 지우면 이력이 사라진다.
⚠ 「[N] 세모-일정공유」 는 @resource 다
회의실 · 장비 같은 Workspace 리소스 캘린더이지 @group 공유 캘린더가 아니다.
거기에 직접 쓰지 않는다 — 내 캘린더(primary) 에 만들고 그 리소스를 참석자로 붙인다
(resource:true). info공통 이 지금 쓰는 방식과 같고, 리소스를 구독한 사람 모두가 본다.
⚠ 일정은 만든 사람 개인 캘린더에도 남는다.
⚠ 서버를 두지 않는다
로그인한 사람의 provider_token 으로 브라우저에서 바로 쓴다.
서비스 계정 키를 단일 HTML 에 두면 파일을 받는 누구나 그 캘린더를 조작할 수 있다.
⚠ Supabase 는 그 토큰을 자동 갱신하지 않는다 — 한 시간쯤 뒤 만료된다.
실패해도 저장은 그대로 두고 화면에만 알린다. 연동이 저장을 막으면 안 된다.
⚠ 범위를 늘렸으므로 이미 로그인한 사람은 한 번 로그아웃했다 들어와야 캘린더가 붙는다.
| 설정 | 값 |
gcal_enabled | off 로 시작한다. 시험한 뒤 on —
문제가 생기면 배포 없이 여기서 끈다 |
gcal_resource_id | 붙일 리소스 캘린더 |
gcal_event_id | 활동마다 남기는 일정 id.
⚠ 없으면 고칠 수도 지울 수도 없어 같은 방문이 서너 개씩 쌓인다 |
일정 본문의 「CRM 열기」 가 실제로 연다 (v0.817) —
?inq= 로 들어오면 그 건의 대단계에 맞는 화면으로 옮기고 상세를 편다.
화면을 안 옮기면 목록에 없는 건의 상세만 떠서 「이게 어디 있는 건지」 알 수 없다.
⚠ 로그인 전에 들어오면 구글로 갔다 오면서 쿼리가 날아간다 —
loginWithGoogle 이 pathname 만 넘기기 때문이다(?dev=1 이 붙은 채 돌아오면 anon 으로 돈다).
부팅하자마자 sessionStorage 로 옮겨 두고 돌아온 뒤 꺼내 쓴다.
⚠ 예약을 만드는 길이 셋인데 하나만 손댔다 (v0.816 → v0.817)
상세 「+ 추가」 에만 연동을 붙여, 온보딩 목록의 「+ 예약」 칸에서 잡으면 올라가지 않았다.
같은 일을 하는 경로가 여럿이면 전부 세어보고 손댄다 —
이번 판에서 _apptOf · _avMount 에 이어 세 번째다.
⚠ 10:30 예약이 구글에 19:30 으로 들어갔다 (v0.816 → v0.818)
scheduled_at 은 2026-08-25T10:30:00+00:00 으로 저장되지만
그 10:30 은 실제로 한국시간이다 — v0.622 이래의 관례다
(화면이 문자열을 잘라 쓰므로 변환이 끼면 값이 어긋난다).
new Date(iso).toISOString() 으로 넘기면 진짜 UTC 로 해석되어 +9시간 밀린다.
변환하지 않고 앞 16자만 떼어 2026-08-25T10:30:00 으로 보내고
timeZone: 'Asia/Seoul' 을 붙인다 — 구글이 그 지역시간으로 읽는다.
⚠ 저장 형식이 사실과 다르면 그 값을 밖으로 내보낼 때 반드시 걸린다.
화면 안에서만 쓰는 동안에는 드러나지 않는다.
⚠ 지울 것을 못 찾으면 조용히 넘어갔다 (v0.819)
삭제 경로가 _idpActs 한 곳만 봤다. 그 배열은 상세를 연 사업자의 것만 담아
다른 자리에서 부르면 비어 있다 — 그러면 아무 말 없이 건너뛰고 캘린더에 일정이 남는다.
crmAllActs 를 거쳐 DB 까지 한 번 더 확인한다.
그래도 gcal_event_id 가 없으면 「직접 삭제해 주세요」 라고 알린다 —
연동을 켜기 전에 만든 예약은 CRM 이 지울 수 없다.
⚠ 연동이 무엇을 했는지 콘솔에 남긴다 (_gcalLog) —
「됐는지 안 됐는지」 를 화면만 보고는 알 수 없어 원인 찾기에 시간이 걸렸다.
[캘린더] delete {id, type, when, event, note} 로 찍힌다.
⚠ 로그아웃하지 않으면 동의 화면이 안 뜬다 (v0.820)
Supabase 세션이 살아 있으면 signInWithOAuth 자체가 불리지 않아,
캘린더 권한이 빠진 옛 토큰을 그대로 쓴다 — 「나만 되고 남은 안 된다」 가 된다.
직원 대부분이 여기 해당한다.
접속할 때 실제로 API 를 한 번 불러 보고(events 목록) 401 · 403 이면
동의로 보낸다 (kevin 확정 「다」). 토큰이 있어도 범위가 없을 수 있어 값만 봐서는 모른다.
동의해야 예약을 저장할 수 있다 (v0.822 · kevin 확정)
접속 때 「나중에」 를 고른 사람이 그대로 예약을 잡으면 캘린더에 안 올라간 채 남아
「내 건만 달력에 없다」 가 된다. 예약만은 관문을 지나야 저장된다
(_gcalRequireScope) — 예약을 만드는 네 입구 모두에 걸었다.
⚠ 통화 · 문자는 막지 않는다. 캘린더와 무관한 기록까지 막으면
연동이 안 되는 날 CRM 을 아예 못 쓴다.
⚠ 권한 확인이 실패하면(네트워크 등) 막지 않는다 —
확인이 안 된 것이지 없는 것이 아니다. 연동이 꺼져 있을 때도 마찬가지다.
캘린더 상단에 연동 상태를 적는다 (v0.828)
종전 「전 담당자 · 예약 · 방문 · 촬영 (숨김 …)」 을 걷어냈다 —
늘 같은 말이라 읽지 않게 되고, 정작 알아야 할 「구글에 올라가는 중인가」 를 볼 데가 없었다.
| 상태 | 표시 |
| 연동됨 | 구글캘린더 연동 ([N] 세모-일정공유 연동) — 초록 |
| 미연결 | 구글캘린더 연동 (미연결 : 지금 연결하기) — 빨강, 누르면 동의 화면 |
| 확인 불가 | 구글캘린더 연동 (확인 불가) — 회색.
네트워크 문제일 수 있어 「미연결」 이라 단정하지 않는다 |
| 꺼짐 | 구글캘린더 연동 (꺼짐) — gcal_enabled 가 off |
STEP 활성화 칸에서 기준값 입력을 뺐다 (v0.832)
활성화 화면 서브메뉴에 같은 것이 있다 (v0.790 · 거래처 · 비율).
⚠ 두 곳에 두면 어디서 고쳐야 하는지 매번 고르게 되고,
한쪽만 본 사람은 값이 바뀐 줄 모른다.
STEP 은 「지금 어떤가」 를 보는 자리이지 설정을 만지는 자리가 아니다.
쓰이지 않게 된 .kb-risk-set CSS 도 함께 걷어냈다.
연동은 사람 단위로 연다 (v0.831)
승인이 끝난 사람에게만 켠다 (kevin 확정). 세 관문을 모두 지나야 동작한다.
① 전체 스위치 gcal_enabled = on
② 허용 명단 gcal_allow 에 내 메일이 있거나 '*'
③ 우리 회사 캘린더 gcal_cal_<회사명> 이 있다
⚠ 회사 단위로는 부족하다 — 영업채널1 안에서도 지금은 한 사람만 시험 중이다.
gcal_allow 는 쉼표로 잇고, '*' 하나면 전사 공개다.
⚠ 열지 않은 사람에게는 동의를 묻지도 않는다
아직 쓰지 않는 사람에게 구글 권한을 물으면 「이게 뭔가」 가 된다.
_gcalOn() 이 false 면 예약 관문(_gcalRequireScope) 도 그냥 통과시킨다 —
저장이 막히지 않는다.
⚠ 상단 표시도 왜 꺼졌는지 갈라 적는다 — 「꺼짐」 · 「미사용」 · 「캘린더 미설정」.
그냥 「꺼짐」 이면 어디를 손봐야 할지 알 수 없다.
전사 공개로 바꿀 때는 gcal_allow 를 '*' 로 둔다 —
그래도 회사 캘린더가 없는 회사는 여전히 안 켜진다. 두 관문이 따로 남는다.
회사마다 다른 캘린더를 쓴다 (v0.830)
설정 키는 gcal_cal_<회사명> 이다. 값의 종류에 따라 쓰는 법이 갈린다.
| 값 | 쓰는 법 |
@resource | 내 캘린더에 만들고 참석자(회의실) 로 붙인다 — 로움 |
@group | 그 캘린더에 직접 만든다.
도메인 밖 사람과도 공유되므로 외부 회사는 이것을 쓴다 — 영업채널1 |
| 없음 | 개인 캘린더에만 — 위멤버스(아직 캘린더 없음) |
⚠ 만들 때 쓴 캘린더를 함께 남긴다
gcal_event_id 에 이벤트id@@캘린더id 로 담는다.
일정 id 만으로는 어느 캘린더 것인지 알 수 없어,
그 사이 회사 설정이 바뀌면 엉뚱한 캘린더에서 지우려 든다.
⚠ 옛 형식(@@ 없는 값) 은 primary 로 읽는다 — 그때는 거기에만 만들었다.
⚠ 설정 키가 회사명이라 crm_companies.name 과 글자까지 같아야 한다.
다르면 조용히 「없음」 이 되어 개인 캘린더에만 만들어진다 —
그래서 회사명을 바꿀 때 이 키도 함께 바꾼다 (SQL 376).
「외부1팀」 을 「영업채널1」 로 바꿨다 (kevin 확정) —
회사명은 화면 라벨이 아니라 저장값이라 표시명만 바꾸는 관례를 쓸 수 없다.
⚠ 다른 표는 회사를 uuid 로 물고 있어 이름만 바꾸면 깨지지 않는다 — SQL 376-A 에서 확인한다.
⚠ 외부 회사는 리소스를 붙일 수 없다 (v0.829 · kevin 확정 「가」)
「[N] 세모-일정공유」 는 roumit.com Workspace 의 리소스다.
회의실을 남의 회사 사람이 잡을 수 없듯, 외부 계정은 이 리소스를 예약할 수 없다 —
붙이면 400 · 403 이 나 예약이 통째로 안 올라간다.
로움 사람에게만 붙이고, 외부 회사는 자기 캘린더에만 만든다.
상단 표시도 「내 캘린더에만」 으로 갈라 적는다 —
공유되는 줄 알고 있다가 남이 못 보면 그것이 더 나쁘다.
⚠ 나중에 위멤버스가 자기 공유 캘린더를 만들면 회사별 id 를 두면 된다.
그때까지는 이 절충으로 둔다.
⚠ 권한 확인은 네트워크를 탄다 — 그릴 때마다 부르지 않는다.
달을 넘길 때마다 구글에 묻게 된다. 먼저 「확인 중」 으로 그리고 한 번만 확인한다.
⚠ 숨김 건수(_calDrop) 는 뺐다 — 원인을 찾을 때만 쓰던 것이라 콘솔로 충분하다.
매일 아침 다시 묻지 않게 — 서버에서 토큰을 갱신한다 (v0.826)
⚠ provider_token 은 한 시간짜리이고 자동 갱신되지 않는다
하룻밤 지나면 반드시 죽는다. 구글 계정과 캘린더는 연결돼 있어도
이 토큰이 살아 있는가는 다른 문제다 — 그래서 매일 아침 「연결하기」 를 눌러야 했다.
지금 브라우저 → 구글 1시간 뒤 죽음, 갱신 불가
바꾸면 브라우저 → Edge Function → 구글 서버가 갱신, 계속 씀
새로고침 토큰을 crm_google_tokens(SQL 375) 에 두고
Edge Function gcal-token 이 갱신을 맡는다.
⚠ 갱신 요청에는 client_secret 이 든다 —
단일 HTML 에 두면 파일을 받는 누구나 우리 계정으로 토큰을 찍어낼 수 있다.
⚠ 새로고침 토큰은 비밀번호에 준한다
그 값이 있으면 그 사람 캘린더를 계속 조작할 수 있다.
이 표만은 마스터 우회를 넣지 않는다 — 본인만 읽고 쓴다.
Edge Function 은 service_role 이라 정책과 무관하게 읽는다.
⚠ 함수는 부른 사람을 JWT 에서 꺼낸다. 본문에서 user_id 를 받으면
남의 토큰을 받아갈 수 있다.
⚠ 새로고침 토큰은 첫 동의 때만 온다 — 이미 동의한 사람은 한 번
prompt:'consent' 로 다시 받아야 한다. 그 한 번은 피할 수 없다.
⚠ 함수가 없어도 CRM 은 그대로 돈다. 호출이 실패하면 종전 방식으로 떨어진다 —
배포 전에도 지금과 똑같이 쓸 수 있어야 한다.
⚠ 401 을 만나면 새 토큰으로 한 번만 다시 보낸다. 되풀이하면 실패할 때 무한히 돈다.
⚠ 권한 확인도 2단이다 — 세션 토큰이 죽었으면 서버 토큰으로 한 번 더 본다.
그것만 보고 「권한 없음」 이라 하면 매일 아침 동의를 다시 묻게 된다.
⚠ 확인은 「실제로 쓸 API」 로 해야 한다 (v0.825)
v0.820~824 는 권한 확인에 calendarList 를 불렀다. 그런데 그 API 는
calendar.readonly 범위를 요구한다 — 우리가 받은 것은 calendar.events 다.
권한이 멀쩡한 사람에게도 403 이 나서 「연결하라」 고 계속 물었다.
일정은 정상으로 만들어지고 있었는데 확인만 틀렸다.
events 목록으로 바꿨다 — 쓰기와 같은 범위라 이것이 통과하면 저장도 된다.
⚠ 「있는지 없는지」 를 볼 때는 그 권한을 쓰는 바로 그 호출로 본다.
비슷해 보이는 다른 API 는 다른 범위를 요구한다.
⚠ prompt:'consent' 는 넣지 않는다 (v0.824) —
붙이면 로그인할 때마다 동의 화면이 다시 뜬다. 처음 한 번이면 되는 일이다.
빼면 구글이 알아서 판단한다 — 처음 동의하는 사람에게만 띄우고 나머지는 통과시킨다.
권한이 없는 사람은 _gcalEnsureScope 가 접속 때 따로 잡는다.
⚠ 되돌아왔는데도 권한이 없으면 또 보내게 된다 — 무한 왕복이다.
sessionStorage 에 「이 탭에서 이미 시도했다」 를 남겨 한 번으로 끝낸다.
「나중에」 를 고른 사람은 그 탭에서 다시 묻지 않는다.
⚠ 네트워크 오류는 재인증으로 안 풀린다 — 그때는 건드리지 않는다.
연동이 꺼져 있으면 아예 묻지 않는다.
⚠ 범위는 「로그인하는 모든 길」 에 붙인다
loginWithGoogle 에만 붙여 두었더니, 화면 잠금 해제로 돌아오는 순간
캘린더 권한이 빠진 토큰으로 바뀐다 — unlockScreen 에도 넣었다.
⚠ GCAL_SCOPE 선언을 파일 앞으로 옮겼다. 쓰는 곳이 셋으로 늘었는데
const 는 TDZ 라 선언보다 먼저 실행되면 그 자리에서 죽는다.
⚠ 시각을 고칠 때마다 일정이 하나씩 늘었다 (v0.823)
crmAllActs 의 select 에 gcal_event_id 가 없었다.
_apptOf 가 이 배열에서 꺼내는데 값이 undefined 라
PATCH 가 아니라 POST 로 가서 10:00 · 10:30 이 함께 남았다.
⚠ v0.799 에서 biz_no · done_result 를 넣으면서 이것만 놓쳤다 —
새 컬럼을 쓰기 시작하면 「읽어오는 곳」 을 전부 세어본다.
이번 판에서 같은 유형이 두 번째다.
일정 길이를 1시간 30분 으로 잡았다 (GCAL_MIN · kevin 확정) —
방문은 오가는 시간까지 잡아야 그날 다른 일정을 겹쳐 넣지 않는다. 30분이면 달력이 비어 보인다.
⚠ 자정을 넘으면 그날 23:59 로 자른다 — 날짜가 밀리면 다른 날 일정이 된다.
⚠ 예약을 만드는 곳이 다섯이었다 (v0.821)
from('crm_inquiry_activities') 를 전수로 훑어 찾았다 —
상세 「+ 추가」 · 목록 「+ 예약」(만들기 · 고치기) ·
방문계획 일괄 등록 · 잠재에서 활동 남기기.
v0.816 은 첫 하나만 붙였다.
⚠ 특히 같은 함수 안의 두 갈래(_lcSetVisit 의 만들기 / 고치기) 를 놓치기 쉽다 —
고치는 쪽을 빠뜨리면 CRM 만 바뀌고 캘린더는 옛 시각으로 남는다.
⚠ 고칠 때는 방금 저장한 값으로 넘긴다. _apptOf 는 색인을 다시 만드는데
그 사이 다른 예약이 「가장 가까운 것」 이 되면 엉뚱한 일정을 고친다.
방문계획은 여러 건을 한 번에 만든다 — _gcalNew 에 모았다가
문의 · 활동을 다 읽은 뒤 _gcalFlushNew() 로 한 번에 보낸다.
하나가 실패해도 나머지는 올라간다.
⚠ assignLead 는 '기타' 활동이라 연동 대상이 아니다.
⚠ loadCrmSettings 가 category='risk' 로 좁혀 읽고 있었다 —
다른 갈래의 키를 넣어도 실리지 않는다. 전부 읽게 고쳤다.
⚠ 구글에서 사람이 일정을 지웠으면 PATCH 가 404 로 온다 — 그때는 새로 만든다.
삭제의 404 · 410 은 「이미 없다」 이므로 오류로 보지 않는다.
「유입」 을 「리드」 로 · 활동으로는 함부로 만들지 않는다
표시명만 바꿨다 (v0.812 · kevin 확정) — 저장값은 step_group='유입' 그대로다.
비교하는 곳이 49군데라 값을 바꾸면 S-05 3단계에 그 전부를 손봐야 한다.
'실패'→가입실패 · '셋업 사전준비'→셋업준비 와 같은 관례다.
⚠ 유입경로 · 유입코드는 그대로 둔다 (kevin 동의) —
그것은 「어디서 왔나(채널)」 라 뜻이 다르다. 「리드코드」 는 말이 안 된다.
활동만으로 리드가 생기는 범위를 좁혔다
v0.783 은 끝난 건에 통화 한 통만 남겨도 새 건을 만들었다 —
해지 명단에 전화를 돌린 날 목록이 통째로 불어난다.
새로 만드는 것은 두 경우뿐이다.
① 붙일 리드가 아예 없다 (잠재 · 원장만) — 다른 방법이 없다
② 그 사업자의 리드가 전부 끝났고, 예약 3종 · 방문 · 촬영이다
⚠ 「전부 끝났나」 는 그 사업자의 리드 전체를 본다 (_allEndedForBiz) —
종전에는 열려 있는 그 건 하나만 봐서, 진행 중인 다른 리드가 있어도 새 건이 생겼다.
「+ 리드」 를 리드 탭 첫 카드 위에 두었다.
통화로 시작하거나 활동 없이 리드만 세우고 싶을 때 쓴다 — 구분(영업 · 온보딩 · 활성화) 을 고른다.
⚠ 세 갈래를 묻는 창이 없어 _pick() 을 만들었다. 확인창에 버튼 줄만 얹는다 —
새로 만들면 잠금 · 겹침 · ESC 처리를 또 짜야 한다.
⚠ 확인 버튼을 감췄다가 반드시 되돌린다. 안 하면 다음 확인창에 확인 버튼이 없다.
예약 카드는 네 줄로 나눈다 (v0.813 → v0.815 · kevin 확정) —
한 줄에 몰아 넣으면 kei+1 처럼 줄어 누가 같이 가는지 알 수 없다.
[영업] kevin 26.08.21 수정 ← 등록자
[방문예약] 26.08.24 14:30 D-2 예정 ← 예약 내용만
[방문자] kei, kevin ← 자동
방문 컨설팅 w. kevin ← 사람이 쓴 메모
⚠ 등록자를 담을 자리가 없었다 (SQL 373)
assignee 는 방문자(여럿) 라 「kei, kevin 이 간다」 와 「kevin 이 잡았다」 는 다른 값이다.
created_by 를 새로 두되 예약에만 채운다 —
통화 · 문자까지 넣으면 카드가 한 줄 더 길어지는데 그 자리에서 얻는 것이 없다.
⚠ 기존 활동은 비어 있다. 누가 만들었는지 남긴 적이 없어 소급할 수 없다 —
비면 담당자 첫 사람으로 대신 보여준다. 「-」 로 두면 절반이 「모르는 건」 이 되어 오히려 못 읽는다.
캘린더 툴팁도 kei+1 → 전원으로 바꿨다.
격자 칸은 좁아 줄일 수밖에 없지만, 마우스를 올리면 다 보여야 한다.
목록의 예약일시 칸도 같다 (v0.822) — 툴팁에 방문자 : kei, kevin 과 등록자를 적는다.
새 리드를 만들 때는 문의내용에 방문자 kei · kevin 줄이 들어간다.
⚠ 좁은 칸(목록 담당자 64px) 은 _dispAssigneeShort 그대로다 —
한 함수를 고치면 그 칸이 깨진다.
request_kind 를 갈랐다 (SQL 372) —
자동등록(활동으로 생긴 건) · 직접등록(「+ 리드」 · 「+ 등록」).
종전에는 자동 생성분이 '문의' 로 박혀 「고객이 문의했다」 로 남았다.
회사 · 달 단위로 본다 (v0.834)
⚠ 일정은 회사를 따지는데 활동은 전사가 섞였다
_calEvents 는 _notiCompOk 로 거르는데 crmAllActs 는 그대로였다 —
로움 직원에게 위멤버스 활동이 함께 세어져 「내가 안 한 일」 이 실적에 들었다.
같은 리포트 안에서 한쪽만 회사를 따지면 숫자끼리 앞뒤가 안 맞는다.
머리에 회사 셀렉트를 두었다 (kevin 확정) —
로움은 전체 · 각 회사를 고를 수 있고, 나머지는 자기 회사만 본다 (v0.438 과 같은 원칙).
⚠ 헤더의 회사 필터를 쓰지 않는다. 리포트는 아침에 한 번 보는 것이라
거기서 고른 값이 목록 화면까지 따라가면 「왜 목록이 줄었지」 가 된다 — 리포트 안에서만 쓴다.
⚠ 일정도 리포트 회사로 한 번 더 거른다. 안 그러면 위(일정) 와 아래(활동) 가 다른 회사를 센다.
유형별 합계는 표 맨 아래 줄에 둔다 (v0.841 · kevin 확정) —
제목 줄에 두면 같은 숫자가 위아래 두 곳에 나와 어느 것을 봐야 할지 고르게 된다.
표의 열과 같은 자리에 세워야 세로로 견줄 수 있다.
⚠ 합계는 사람별 세로합이 아니라 전체 건수다.
방문자가 여럿인 활동은 사람마다 세므로 두 값이 다르다 —
합계 줄은 「실제로 몇 건이었나」 를 말한다.
⚠ byType 은 활동만 담는다. 「가입」 은 원장 날짜라 거기 없어
그 칸만 cus 에서 가져온다 — 안 그러면 합계의 가입이 늘 비어 보인다.
⚠ 기간은 적지 않는다 (v0.842 · kevin 확정) —
「이달 · 전월」 이라 적혀 있으면 언제부터 언제까지인지는 누구나 안다.
쓰이지 않게 된 range · md 도 함께 걷어냈다.
고객 변화 맨 앞에 유입을 넣었다 (v0.844 · kevin 확정) —
흐름이 유입 → 신규 → 재가입 → 정지 → 해지 로 읽힌다.
⚠ 유입은 request_kind = '문의' 만 센다
「신청(가입)」 은 아래 「신규」 와 같은 것을 센다 (kevin) —
유입은 「물어보러 온 것」, 신규는 「실제로 가입한 것」 이라야 둘을 견줄 수 있다.
요금변경도 뺀다 — 이미 우리 고객이 요금을 바꾸는 것이라 새 유입이 아니다.
⚠ 자동등록 · 직접등록도 뺀다 (SQL 372). 우리가 만든 리드까지 세면
「이달에 고객이 몇이나 찾아왔나」 가 아니라 「리드를 몇 개 만들었나」 가 된다 —
맨 앞이 우리 손으로 부풀려지면 그 뒤 숫자와 견줄 수 없다.
고객 변화도 표로 바꿨다 (v0.843 · kevin 확정) —
활동 표와 같은 모양이라야 나란히 놓고 읽는다. 항목을 열로 세우고 맨 뒤에 계.
⚠ 줄이 둘뿐이라(이달 · 전월) 세로로 견주기 쉽다 —
활동 표는 사람이 여럿이라 두 달을 한 칸에 넣었지만 여기는 줄로 나눈다.
두 구획이 모두 표가 되면서 blk 의 tail 인자와
.dr-line · .dr-1l CSS 가 쓰이지 않게 되어 걷어냈다 —
한 판에서 만든 것을 다음 판에서 지우는 일은 흔하다. 남겨 두면 다음 사람이 「이건 뭐지」 로 멈춘다.
요약을 제목 줄 안에 넣었던 것(v0.839) 은 표로 바뀌며 사라졌다.
⚠ 표는 제목 줄 안에 넣지 않는다. flex 안의 table 은 폭이 무너진다 —
한 줄로 흐를 것(tail) 과 아래로 이어질 것(inner) 을 인자로 갈라 받는다.
「알아서 접히겠지」 로 두면 넓은 화면에서만 맞고 좁아지면 깨진다.
⚠ 제목에 HTML 을 넣어 태그가 글자로 찍혔다 (v0.838)
blk(label, …) 의 label 은 h() 로 이스케이프된다 —
당연하다. 제목은 사람이 쓴 글이 들어올 수 있는 자리다.
거기에 two() 가 만든 <span> 을 넣으니
「이달의 활동 (21 <span style=…>/ 2</span>)」 로 나왔다.
숫자 자리(n) 는 이스케이프하지 않으므로 그쪽으로 넘긴다.
⚠ 이스케이프하는 자리와 안 하는 자리를 섞어 쓰면 반드시 이런 일이 난다.
헬퍼를 쓸 때는 「이 인자가 h() 를 타는가」 를 먼저 본다.
활동을 일곱 칸으로 모은다 (v0.837 · kevin 확정) —
유형이 11종이라 그대로 세우면 표가 옆으로 길어진다. 뜻이 같은 것끼리 묶는다.
통화 · 방문 · 메일 · 기타 영업 활동을 무엇으로 했나
온보딩 · 활성화 구분으로 센다 — 담당자가 다르다
가입 활동이 아니라 원장 날짜다
⚠ 구분이 온보딩 · 활성화면 유형과 상관없이 그 칸으로 간다.
셋업예약을 「예약」 으로 세면 영업 예약과 섞여 「누가 셋업을 맡았나」 가 안 보인다.
⚠ 열은 늘 같은 자리에 있다. 달마다 순서가 바뀌면 견줄 수 없다.
아무도 안 한 칸도 남긴다 — 빠지면 「그 일을 아무도 안 했다」 를 볼 수 없다.
빈 칸은 0 / 0 이 아니라 점 하나다. 0 이 늘어서면 표가 숫자로 뒤덮인다.
⚠ 한 줄 요약과 표가 같은 묶음을 쓴다. 두 곳이 다르면 합이 안 맞는다.
지난달 숫자를 #9CA3AF → #6B7280 로 진하게 했다 (kevin) —
견주려고 보면 잘 안 보였다. 다만 이번달보다는 물러나야 해서 굵기는 그대로 둔다.
일정 줄의 방문자는 전원 적는다 (v0.836 · kevin 확정) —
kei+1 로 줄이면 누가 같이 가는지 알 수 없다. 이 자리는 폭이 넉넉하다.
⚠ 좁은 칸(목록 담당자 · 캘린더 격자) 은 그대로 줄인다.
요약은 한 줄로 흘린다 (v0.835 · kevin 확정) —
칩 상자로 쌓으면 줄이 늘어 화면을 먹는다. 제목에 총계를 넣고 아래 한 줄에 내역을 편다.
사람별 표에 가입 열을 넣었다.
⚠ 가입은 활동이 아니라 원장 날짜라 담당자가 없다 —
그 사업자의 문의에서 담당자를 찾아 붙이고, 못 찾으면 세지 않는다.
「누구 것인지 모르는 가입」 을 아무에게나 얹으면 표가 거짓말을 한다.
⚠ 「기타 N명」 은 이번달 상위 세 사람을 뺀 나머지로 묶는다 —
지난달도 같은 이름 기준으로 모아야 두 숫자를 견줄 수 있다.
기간을 주에서 달로 바꿨다 (kevin 확정) —
「지난 7일」 은 월말에 실적을 볼 때 쓸 수 없다.
모든 숫자를 이번달 / 지난달로 나란히 적어 「지금 얼마나 하고 있나」 를 견주게 한다.
활동 유형은 한 줄로 모으고 가입 수를 함께 넣었다.
⚠ 빈 날은 감춘다 (v0.833 → v0.834) —
여드레 중 엿새가 「일정 없음」 이면 그 줄들이 화면을 다 먹는다.
오늘 · 내일만 남겨 「오늘 무엇을 하나」 를 늘 답하게 하고, 나머지는 있을 때만 보인다.
일정을 날짜로 죽 늘어놓는다 (v0.833)
「처리할 예약」 과 「다음 7일」 을 따로 두니 오늘 것이 어디 있는지 헷갈렸다
오늘 예약은 위(미처리) 에도 아래(다음 7일) 에도 없을 수 있었다.
오늘부터 8일을 날짜로 늘어놓으면 「오늘 · 내일 · 모레」 가 한눈에 읽힌다.
지나고 안 처리한 것만 따로 앞에 세운다 (kevin 확정).
⚠ 일정이 없는 날도 줄을 만든다. 감추면 그 날이 정말 빈 건지, 빠진 건지 알 수 없다.
오늘만 「오늘은 일정이 없습니다」 로 또렷하게 적는다.
「내일」 만으로는 언제인지 모른다 —
남은 날짜 옆에 날짜 · 요일 · 시각을 함께 적는다. 토 · 일은 색으로 가른다.
사람별 활동 표를 넣었다 (kevin 확정) —
많이 움직인 세 사람과 나머지를 「기타 N명」 으로 묶는다.
열은 실제로 나온 유형만 많이 쓰인 순서로 세운다.
⚠ 방문자가 여럿인 활동은 각자에게 1건씩 센다 —
「누가 얼마나 움직였나」 를 보는 표라 함께 간 사람도 움직인 것이다.
그래서 사람별 합이 전체 건수보다 클 수 있다.
리포트 숫자를 누르면 그 목록이 열린다 (v0.847)
리포트가 곧 필터다 (kevin 확정) — 목록에 기간 · 담당자 · 유형 필터를 새로 만들지 않는다.
리포트가 이미 그 일을 했으니 고른 묶음만 넘기면 된다.
_pickIds 리포트가 정한 id 묶음. 비어 있으면 목록은 종전대로 돈다
목록 let data = conf.data 뒤에 한 줄 — 그 묶음만 남긴다
서브메뉴 「리포트 · kei · 통화 12건만 보고 있습니다 [전체 보기]」
| 누른 것 | 가는 곳 · 세는 단위 |
| 통화 · 방문 · 메일 · 기타 | 영업 — 문의 id |
| 온보딩 · 활성화 | 그 메뉴 — 문의 id |
| 가입 · 신규 · 재가입 · 정지 · 해지 | 고객 — 사업자번호 |
| 유입 | 리드 — 문의 id.
⚠ 다섯 중 이것만 원장이 아니라 문의다. 한 묶음으로 다루면 엉뚱한 곳이 열린다 |
⚠ 「통화 2」 를 눌렀는데 1건만 보였다 (v0.849)
리포트는 활동의 stage_group 으로 세는데
목록은 문의의 step_group 으로 화면을 가린다.
영업 구분으로 통화했어도 그 사이 문의가 온보딩으로 넘어갔으면 영업 화면에서 사라진다.
필터가 걸린 동안은 화면의 기본 대상을 무시한다 (kevin 확정 「가」) —
고른 것은 다 보여야 한다. 화면은 「어디서 보는가」 일 뿐이고
무엇을 볼지는 이미 리포트가 정했다.
⚠ 한 문의에 활동이 여럿이면 목록은 한 줄이다 —
「통화 3건」 인데 2줄일 수 있다. 이것은 그대로다.
⚠ 「기타 N명」 은 누가 들었는지 알 수 없어 누르지 않는다.
사람별 가입 칸도 누르지 않는다 — 원장이라 문의 목록과 단위가 다르다.
⚠ 목록은 let data = conf.data 뒤 한 곳에서 거른다.
뷰마다 data 를 따로 만들지만 거기서 모이므로 한 줄이면 전부 걸린다.
해제 표시는 유입코드 안내(v0.719) 와 같은 자리 · 같은 방식이다 —
모르면 「왜 몇 건만 보이나」 로 읽힌다.
리포트를 헤더 아래 패널로 (v0.849)
가운데 뜨는 창(v0.806) 을 「리포트」 버튼 아래로 옮겼다 (kevin 확정) —
뒤 화면이 바뀌는 것을 보면서 숫자를 누를 수 있다.
⚠ 배경 막을 두지 않는다
뒤 화면을 그대로 눌러야 목록이 바뀐 것을 바로 확인한다.
그런데 막이 없으면 감싼 div 가 보이지 않는 채로 화면 전체를 덮어 클릭을 먹는다 —
pointer-events:none 을 감싼 쪽에 주고 카드에만 auto 로 되돌린다.
① 리포트 열기 리포트 열림 · 필터 없음
② 숫자 클릭 리포트 열림 · 필터 걸림 · 화면 이동
③ 고객 클릭 → 상세 리포트만 닫힘 · 필터 유지
④ 다른 메뉴 모두 해제 · 그 메뉴로
⚠ ③에서 필터는 그대로 둔다 (v0.850 · kevin 확정) —
리포트가 고른 묶음을 훑어보는 중이고 상세를 닫으면 그 목록으로 돌아와야 한다.
여기서 필터까지 풀면 한 건 보고 나올 때마다 처음부터 다시 찾아야 한다.
⚠ 상세를 여는 길이 둘이다 — openInquiryDetail ·
openCustomerDetail. 하나만 손대면 고객 탭에서는 리포트가 열린 채 남는다.
다른 메뉴를 누르면 리포트를 닫고 필터도 푼다 —
리포트가 보낸 목록을 보다가 옆 메뉴를 누르면 그 화면도 좁혀져 있어 「왜 몇 건만 보이나」 가 된다.
스스로 옮긴 것은 새로 보겠다는 뜻이다.
⚠ _pickSet 이 setView 를 부르고 setView 가 필터를 지우면
서로 부르며 무한히 돈다 — _pickGoing 으로 「스스로 옮긴 것인가」 를 가린다.
헤더에 리포트 · 캘린더 두 버튼 (v0.845)
| 버튼 | 표시 |
| 리포트 | 오늘 안 봤으면 「리포트 N」 에 파란 바탕.
열면 본 것으로 치고 N 이 사라진다 |
| 캘린더 | 7일 이내 예정 + 미처리 건수 (v0.848 · kevin 확정).
내가 가는 오늘 일정이나 미처리가 있으면 빨강 |
⚠ 다음 일정을 버튼에 늘어놓던 것을 걷어냈다 (v0.653 → v0.845)
「08.24 14:30 세빛세무회계 D-2」 처럼 담으니 폭이 들쭉날쭉하고
옆의 「리포트」 와 나란히 서지 않았다.
일정 내용은 리포트가 맡는다 — 여기는 「있나 없나」 만 말하고 색으로 급한 정도를 알린다.
캘린더 숫자는 리포트의 「최근 7일 일정」 과 같은 범위다 (v0.848) —
⚠ 두 곳이 다른 기준을 쓰면 「리포트는 3건인데 버튼은 1」 이 된다.
오늘 것만 세던 것을 오늘 ~ +7일로 넓혔다. 미처리는 그대로 더한다 —
버튼 숫자는 「앞으로 챙길 것」 이다.
⚠ 빨강은 「내가 가는 일정」 이다 (v0.846 · kevin 확정)
종전에는 「한 시간 이내」 로 갈랐다 (v0.845) — 그런데
남이 가는 일정이 코앞이어도 나는 할 일이 없다.
급한 것은 「내가 오늘 움직여야 하나」 다 — 방문자에 내가 들어 있는 건만 본다.
⚠ 방문자는 여럿일 수 있다. _dispAssigneeList 로 풀어 하나라도 나면 내 것이다.
⚠ 미처리는 누구 것이든 빨강이다 — 「빠뜨린 일」 이라 내 것이 아니어도 누군가 손을 대야 한다.
툴팁에 오늘 3건 (내 일정 1) 처럼 갈라 적는다.
⚠ 「안 읽음」 은 그날 열었는지로 본다 (DR_KEY) —
내용이 바뀌었는지는 알 수 없고, 매일 아침 한 번 보는 것이라 그것으로 충분하다.
⚠ 「오늘은 그만 보기」 버튼은 없앴다 (v0.851 · kevin 확정) — 닫기 하나면 된다.
여는 순간 이미 「봤다」 를 남기므로, 따로 눌러 두지 않아도 다음에 자동으로 뜨지 않고
헤더의 「리포트 N」 도 사라진다 — 두 일을 한 동작이 함께 한다.
알림의 「일일리포트 다시 보기」 는 걷어냈다 — 헤더에 버튼이 생겨 자리가 겹친다.
쓰이지 않게 된 .dr-open · #drDate 도 함께 지웠다.
리포트 머리에 「리포트」 배지를 붙였다 (kevin) — 어디서 볼 수 있는지 알게 한다.
일일리포트 — 로그인하면 위에서 내려온다
로그인해서 「오늘 무엇을 할까」 를 찾아다니지 않게, 하루 첫 진입에 브리핑이 내려온다 (v0.804 · kevin 확정).
한 번 열면 그날은 다시 자동으로 뜨지 않는다 — 다음 날 아침에는 다시 나온다.
알림 센터에는 「다시 보기」 한 줄만 두어 언제든 열 수 있다. 알림 폭도 360 → 520px 로 넓혔다.
화면 한가운데에 선다 (v0.806) — 위에서 내려오되 상단에 붙여 두면 아래가 비어 화면이 기운다.
구획마다 선을 긋는다. 다섯 덩어리가 붙어 있으면 어디까지가 한 묶음인지 안 읽힌다.
⚠ 「지난 7일」 은 실제 날짜로도 적는다 (08.15 ~ 08.22) —
말로만 적으면 「7일이 지났다는 뜻인가」 로 읽힌다. 기간을 말할 때는 그 기간을 보여준다.
⚠ v0.806 이 통째로 안 떴다 — blk is not defined
구획을 sec → blk 로 바꾸는 편집이 저장 전에 멈췄는데
그 뒤 호출부만 바꿔 저장했다. 정의는 sec 인 채로 남고 호출은 blk —
node --check 는 통과하고 실행해야 나는 오류다.
⚠ 「쓰는 곳이 0 개」 를 확인하는 것으로는 모자란다. 정의가 남아 있는지도 함께 센다 —
이름을 바꿀 때는 정의 · 호출을 한 번에 하고, 나눠 했으면 양쪽을 다시 대조한다.
같은 편집에서 r.range 도 함께 날아가 기간이 undefined 로 찍혔다.
⚠ 알림 드롭다운 안에 두었다가 옮겼다 (v0.803 → v0.804)
폭 · 높이가 드롭다운에 묶이고 스크롤이 이중이 된다 (리포트 + 알림 목록).
그리고 알림은 「처리하는 곳」, 리포트는 「읽는 것」 이라 성격이 다르다 —
한 상자에 넣으면 둘 다 좁아진다.
⚠ 데이터가 다 모인 뒤에 띄운다. 문의 · 활동 · 원장이 없으면 전부 0 으로 나오는데,
비어 있는 리포트는 「고장났다」 로 읽힌다.
| 구획 | 재료 |
| 처리할 예약 | 지나고 아직 처리 안 한 예약. 있을 때만 나온다 |
| 최근 7일 일정 | 오늘부터 날짜별. 오늘 · 내일은 늘 보이고 그 뒤는 일정이 있는 날만 |
| 이달의 활동 | 제목에 총계, 아래 한 줄에 유형별 + 가입, 그 아래 사람별 표.
모든 칸이 이번달 / 지난달 |
| 고객 변화 | 원장 날짜 — joined_at · rejoined_at · paused_at · canceled_at.
이것도 이번달 / 지난달 |
| 지금 쌓여 있는 것 | 영업 진행 중 · 예약 없음 / 온보딩 진행 중 / 활성화 위험 있음.
누르면 그 화면으로 간다 — 리포트에서 끝내지 않고 일로 이어져야 한다 |
날짜별로 쌓지 않는다 (kevin 확정)
① 리포트는 그 시점의 파생값이다. 저장하지 않으면 어제 것을 그대로 되살릴 수 없고,
저장하려면 표가 하나 더 생긴다.
② 알림 센터는 「처리할 것」 이 쌓이는 자리다. 리포트가 매일 하나씩 들어오면
한 달이면 서른 개가 실제 알림을 밀어낸다.
③ 목적이 「오늘 무엇을 할까」 라 지난 리포트를 다시 볼 일이 드물다.
접힘은 그날 하루만 기억한다 — 접어 두고 다음 날 잊는 편이 낫다.
⚠ 「위험 진입」 은 아직 못 만든다
위험은 매번 계산하는 파생값이고 어제 위험이었는지 기록이 없다.
「지난 7일에 새로 위험해진 N건」 을 만들려면 매일 판정을 남기는 표가 필요하다.
지금은 현재 수(활성화 서브메뉴) 로만 본다.
⚠ 고객 변화에 crm_stage_history 를 쓰지 않았다 —
SQL 일괄 판정분이 기록에 없어 기간 집계에 쓸 수 없다 (v0.309 와 같은 판단).
원장 날짜는 외부 시스템이 확정한 사실이라 더 정확하다.
같은 말이 두 번 나왔다
활동 카드에 「취소 ✕ 취소」 · 「완료 ✓ 완료」
예약줄 오른쪽에 상태 라벨(ap.label) 과 처리 꼬리가 둘 다 붙었다.
미처리일 때는 「D-11」 과 「예정」 이라 뜻이 갈렸는데,
처리하고 나면 둘이 같은 말이 된다 (v0.802 에서 라벨을 감췄다).
⚠ 상태를 두 곳에서 말하면 어느 한쪽 값이 바뀔 때만 어긋남이 드러난다 —
v0.775 에서 저장값을 그대로 라벨로 쓰기 시작하며 겹쳤다.
목록 칸에는 남은 날짜를 넣었다 (kevin 확정) —
버튼은 상태(예정 · 지연) 만 말하고 「며칠 남았나」 는 안 알려줬다.
168px 칸이라 「16일 지남」 은 길어 지난 것은 +16 으로 줄인다.
전체 문구는 툴팁에 남는다.
달력에서 말없이 빠지던 일정
_calEvents 의 두 관문이 아무 말 없이 버린다
① 그 활동의 문의를 crmInquiries 에서 못 찾으면 버린다
② 회사 판정(_notiCompOk) 에 걸리면 버린다
둘 다 이유가 화면에 남지 않아 「예약을 잡았는데 달력에 없다」 를 짚을 수가 없었다.
힌트 줄에 (숨김 — 문의 없음 N · 다른 회사 N) 으로 적는다 (v0.801).
⚠ 거르는 곳은 세는 곳이기도 해야 한다.
조건이 여럿인 수집 함수에서 조용히 return 하면
결과가 비었을 때 어느 줄에서 빠졌는지 알 방법이 없다.
달력 목록의 날짜를 연도까지 적고 크기를 9.5px → 11.5px 로 올렸다 (kevin 확정).
⚠ 월·일만 적으면 「04.20 → 12.18 → 08.20」 처럼 뒤죽박죽으로 보인다 —
실제로는 연도가 내려가는 내림차순인데 해가 안 보여 순서가 틀린 것처럼 읽혔다.
지난 기록의 정렬도 그 자리에서 다시 밝혔다 — 앞의 정렬이 바뀌면 조용히 어긋나기 때문이다.
⚠ v0.801 에서 날짜를 11.5px 굵게로 올렸더니 시간(cev-hm) 도 같은 크기라
둘이 함께 커져 이름을 눌렀다 — 한쪽만 보고 키우면 옆이 밀린다.
v0.808 에서 날짜 10.5 · 시간 11 · 이름 11.5 로 층을 만들었다.
먼저 읽어야 하는 것은 「어느 사무소인가」 다.
색인을 만들었는데 재료가 없었다
_loadAllActs 가 biz_no 를 안 읽어왔다
v0.798 에서 사업자 색인(_apptBizIdx) 을 만들었는데
select 목록에 biz_no 가 없어 색인이 통째로 비어 있었다 —
고쳤는데도 셋업예약 칸이 계속 비었던 이유다.
⚠ 새 필드를 쓰기 시작하면 읽어오는지부터 본다.
undefined 는 오류를 내지 않고 조용히 아무 일도 안 한다.
같은 자리에서 done_result 도 빠져 있었다
SQL 368 · 369 로 컬럼을 만들고 v0.770 부터 취소 판정(_apptAlive) 이 그것을 보는데,
crmAllActs 에는 실려 오지 않았다 —
목록 · 캘린더 · 칸반에서 취소한 예약이 계속 살아 있었다.
상세 활동이력만 제대로 보였다 (그쪽은 따로 읽는다).
⚠ .range() 도 없었다. Supabase 는 요청당 1,000행이 한계라
넘으면 조용히 잘리고 그만큼의 예약이 화면에서 사라진다 — 끝까지 읽게 했다.
주석에는 「59건 규모」 라 적혀 있는데 그것은 초기 값이다.
예약을 잡았는데 목록 칸이 비어 있었다
묶어 보는 화면과 예약이 붙는 자리가 어긋난다
온보딩 · 활성화는 사업자당 한 줄로 묶는다(_onbDedup).
그런데 예약은 문의에 붙으므로, 대표로 남은 문의가 아닌 다른 문의에 예약이 있으면
_apptOf(i.id) 로는 못 찾는다 —
활동이력에는 「셋업예약 D-11」 이 보이는데 목록 칸은 「+ 예약」 이었다.
활동은 biz_no 도 함께 갖는다 (SQL 320). 사업자 색인(_apptBizIdx) 을
같이 만들어 묶어 보는 화면에서만 그것으로 찾는다 (_apptOfRow).
검색이 활동이력까지 본다 (v0.858)
종전에는 고객 정보만 봤다 — 「지난달 통화에서 요금 얘기했던 곳」 처럼
활동에 적어둔 말로는 찾을 수가 없었다.
검색창을 펼치면 전체 · 고객 · 활동 세 칩이 나오고, 기본은 전체다 (kevin 확정).
| 범위 | 보는 것 |
| 고객 | 사무소 · 사업자 · 대표자 · 신청자 · 연락처 · 주소 · 담당자 · 문의내용 |
| 활동 | 활동 내용 · 유형 · 담당자 · 등록자 |
| 전체 | 둘 다 — 하나라도 걸리면 나온다 |
⚠ 검색 필드가 열 군데에 흩어져 있었다
_onbCounts · _ssCounts · renderKanban ·
_facetCount · _activeViewConf …
화면마다 보는 필드가 조금씩 달랐다 (assignee 가 있는 곳 · 없는 곳).
_inqHit 하나로 모았다 — 흩어져 있으면 한 곳만 넓히고 나머지를 잊는다.
정규식으로 같은 꼴을 전부 찾아 바꾸고 남은 것을 세어 확인했다.
⚠ 활동 글은 색인을 만들어 둔다 (_actTextIdx) —
문의마다 매번 훑으면 900건 × 활동 수가 되어 검색이 늦다.
_loadAllActs 가 다시 읽으면 색인을 비운다.
⚠ _searchScope 는 이미 지도 검색이 쓰고 있었다 —
같은 이름을 두 뜻으로 쓰면 한쪽을 고칠 때 다른 쪽이 조용히 바뀐다.
이것은 「무엇을 볼까」 라 _searchIn 으로 갈랐다.
⚠ 잠재 · 고객원장 검색은 문의가 아니라 그대로 두었다.
활동을 남기면 담당자가 채워진다 (v0.857)
「담당자 미배정」 알림이 쌓이는데, 정작 그 고객에 전화를 걸고 방문을 해도
배정은 따로 눌러야 했다 — 실제로 맡은 사람이 있는데 화면은 비어 있었다.
활동을 남기면 그 사람이 담당자가 된다 (kevin 확정).
⚠ 이미 담당자가 있으면 건드리지 않는다.
남의 건을 대신 처리하는 일이 있고, 그때 담당자가 바뀌면 「내 건이 사라졌다」 가 된다.
⚠ 방문자가 여럿이면 첫 사람이 담당자다. 활동의 담당자와 같은 규칙이다.
⚠ 실패해도 활동 저장은 되돌리지 않는다 — 배정은 곁다리다.
⚠ 활동을 남기는 네 곳 모두에 걸었다 —
상세 「+ 추가」 · 활동 수정 · 목록 「+ 예약」 · 방문계획 일괄.
「담당자 미배정」 알림이 배정하면 사라지는 것은 정상이다 —
읽음 처리가 아니라 조건이 풀린 것이다.
이 알림은 assignee 가 빈 동안에만 만들어진다.
이 판에서 한 일 — v0.812 ~ v0.856
하루에 44판이 나갔다. 낱개로는 앞의 절들에 적었고, 여기서는 무엇이 어떻게 이어졌는지만 남긴다.
| 묶음 | 한 일 |
| 이름 · 구분 | 「유입」 → 리드 (표시명만).
request_kind 를 자동등록 · 직접등록 으로 갈랐다 —
우리가 만든 건이 「고객이 문의했다」 로 남고 있었다 |
| 리드 생성 | 끝난 건에 예약 · 방문일 때만 새 리드.
통화로는 안 만든다 — 해지 명단에 전화를 돌린 날 목록이 통째로 불어난다.
「+ 리드」 로 사람이 만드는 길을 따로 두었다 |
| 구글 캘린더 | 예약을 만들면 일정이 생기고 고치면 따라가고 지우면 사라진다.
회사마다 다른 캘린더 — 로움은 @resource, 나머지는 @group.
허용 명단으로 사람 단위로 연다 |
| 일일리포트 | 날짜별 일정 · 이달/전월 비교 · 사람별 표.
숫자를 누르면 그 목록이 열린다 — 리포트가 곧 필터다 |
| 회사 | 리드 카드 · 상세에 어느 회사 것인지 적는다.
고객 원장에 제품별 소유권(세모R · 세모R+) 을 둔다 — 로움만 고친다 |
이 판의 SQL — 370 ~ 382
낱개 설명은 각 절에 있다. 여기는 무엇을 왜 바꿨는지 한 줄씩만 남긴다 —
나중에 「이 컬럼은 언제 왜 생겼나」 를 찾을 때 여기부터 본다.
| 번호 | 대상 | 왜 |
| 370 | crm_customers.company_id |
원장에 회사를 채운다 — 유입코드 → 사무소 → 로움 3단계 판정 |
| 371 | 고객원장 RLS |
회사별로 가린다. anon 권한 회수 |
| 372 | request_kind |
자동등록 · 직접등록 신설. 우리가 만든 건이 「문의」 로 남고 있었다.
신규등록 → 직접등록 이관 |
| 373 | created_by |
예약을 누가 잡았나. assignee 는 방문자라 뜻이 다르다 |
| 374 | gcal_event_id · 설정 |
구글 일정 id 를 남긴다 — 없으면 고칠 수도 지울 수도 없어
같은 방문이 서너 개씩 쌓인다 |
| 375 | crm_google_tokens |
새로고침 토큰 보관. ⚠ 비밀번호에 준해 마스터 우회를 넣지 않았다 |
| 376 | 회사명 · 캘린더 |
외부1팀 → 영업채널1. 회사별 캘린더 키 신설 |
| 377 | 영업채널1 캘린더 교체 |
Workspace 캘린더는 외부인에게 「일정 변경」 권한을 줄 수 없다 —
개인 계정 소유 @group 으로 바꿨다 |
| 378 | 위멤버스 캘린더 |
세 회사가 모두 자기 캘린더를 갖는다 |
| 379 | gcal_allow |
연동을 사람 단위로 연다. 회사 단위로는 부족했다 —
영업채널1 안에서도 한 사람만 시험 중이었다 |
| 380 | 테스트 문의 이관 |
「제니 테스트」 가 위멤버스라 로움 캘린더에서 빠졌다.
⚠ 활동의 company_id 도 함께 옮긴다 — 문의만 바꾸면 리포트가 어긋난다 |
| 381 | 고객 소유권 |
제품별 담당 회사(owner_semor · owner_semorp).
로움만 고치게 트리거로도 막는다 |
| 382 | 업로드 이력 CHECK |
'activation' 이 빠져 활성화 이력이 안 남았다 — S-05 |
Edge Function gcal-token 도 함께 올렸다 —
client_secret 을 단일 HTML 에 둘 수 없어 서버가 토큰 갱신을 맡는다.
⚠ 남은 것 — 그 함수의 CORS 에 apikey 헤더를 허용해야 한다.
v0.827 에서 헤더를 추가했는데 함수 쪽 허용 목록에 넣지 않았다.
⚠ 이 판에서 되풀이된 실수 넷
① 정의를 만들고 연결을 빠뜨린다 — blk · _actEditWhoAll ·
who · _gcalNew. node --check 는 통과한다.
이제 「정의만 있고 호출 없는 함수」 를 센다.
② 같은 일을 하는 경로가 여럿인데 하나만 손댄다 — 예약을 만드는 곳이 다섯,
상세를 여는 곳이 둘, 로그인하는 길이 셋이었다. 전수로 세어보고 손댄다.
③ 새 컬럼을 쓰기 시작하면 「읽어오는 곳」 을 전부 본다 —
gcal_event_id 를 select 에 빠뜨려 일정이 매번 새로 생겼다.
④ S-05 · CHECK 제약 — request_kind · data_kind 두 번.
새 코드 값을 쓰기 전에 CHECK 를 먼저 넓힌다.
⚠ 남은 것 — 구글 검수(홈페이지 정비 넷),
Edge Function CORS(apikey 헤더 허용),
로움 화면에서 빠지는 comp 건이 전부 타사 것인지 확인.
활성화 업로드 이력이 안 남았다 (v0.855 · SQL 382)
⚠ 데이터는 들어갔는데 이력만 빠졌다
crm_activation 에 394건이 정상으로 들어갔는데
crm_upload_log 의 data_kind CHECK 에 'activation' 이 없었다.
그래서 「올렸는데 내역에 없다」 가 됐다.
⚠ S-05 — 또 만났다. 새 코드 값을 쓰기 전에 CHECK 를 먼저 넓힌다.
도움말에 「하루에 다섯 번 만났다」 로 남은 그 유형이고 이번이 또 한 번이다.
⚠ 실패를 console.warn 으로만 넘겼다
이력이 안 남아도 화면은 「반영 394건」 으로 성공을 알렸다 —
콘솔을 열어 보지 않으면 알 길이 없다.
이제 카드에 「반영은 됐지만 업로드 이력에는 남지 않았습니다」 라고 적는다.
⚠ 둘을 갈라 적는다. 뭉뚱그려 「실패」 라 하면 다시 올려 중복을 만든다 —
데이터는 이미 들어가 있다.
업로드 이력에 쓰는 세 곳(활성화 · 고객 · 플로우) 을 전수로 훑어 모두 표시하게 했다.
고객 소유권 — 제품별 담당 회사 (v0.854 · SQL 381)
리드는 유입 경로대로 들어온다. 한 고객에 여러 회사의 문의가 붙을 수 있고,
로움은 전부 보고 각 회사는 자기 리드만 본다 — 그 필터는 그대로다 (kevin 확정).
그와 별개로 「이 고객의 세모R 은 결국 누구 것인가」 를 적어 둔다.
고객 탭 원장 위에 세모R · 세모R+ 두 칸을 두고 회사를 고른다.
활동 내용을 보고 사람이 판단해야 하는 일이라 자동으로 정하지 않는다.
⚠ 회사 필터에 물리지 않는다
값이 다 차기 전에 필터에 반영하면 화면이 통째로 흔들린다.
지금은 표시만 하고 값을 쌓는다 — 배분에 쓰는 것은 그다음이다.
⚠ 로움만 고친다. 화면에서 셀렉트를 감추고 DB 트리거로도 막는다 —
화면만 막으면 콘솔로 우회할 수 있다.
⚠ crm_customers 의 UPDATE 정책(SQL 371) 은 넓히지 않는다.
소유권만 따로 트리거로 막는다 — 정책을 손대면 다른 수정까지 영향을 받는다.
⚠ 저장에 실패하면 화면을 다시 그린다 — 셀렉트가 바뀐 채로 남으면
저장된 줄 알게 된다.
어느 회사 고객인지 화면에 적는다 (v0.853)
같은 사업자번호를 둘 이상의 회사가 쓰는 일이 있어(v0.852),
「이 건이 누구 것인가」 가 화면에 없으면 목록과 캘린더가 어긋난 이유를 알 수 없다.
| 자리 | 표시 |
| 리드 카드 | 일자 왼쪽에 「위멤버스」 · 「영업채널1」.
⚠ 로움은 적지 않는다 — 대부분이 로움이라 다 적으면 눈에 안 들어온다.
다른 회사일 때만 눈에 띄어야 뜻이 있다 |
| 상세 탭 줄 우측 | 「회사 · 로움」 처럼 늘 적는다 — 한 건을 들여다보는 자리다 |
⚠ 회사를 못 정한 건은 노랑으로 적는다
company_id 가 비고 담당자로도 못 찾는 건이 있다.
감추면 「로움이겠지」 로 읽히는데, 실제로는 회사 필터에 걸려
목록 · 캘린더에서 사라지는 건이다 — 그 사실이 보여야 한다.
⚠ 문의마다 회사가 다를 수 있어 그 건의 회사를 적는다.
사업자 전체로 뭉뚱그리면 「이 건」 이 아니라 「이 사무소」 가 되어
여러 회사가 섞인 곳에서 틀린 말이 된다.
⚠ 사업자로 찾은 예약은 회사를 다시 본다 (v0.852 · kevin 확정)
같은 사업자번호를 쓰는 문의가 둘 있고 회사가 다르면
로움 화면의 줄이 위멤버스 예약을 끌어온다.
캘린더(_calEvents) 는 회사로 거르는데 여기만 안 걸러
「목록에는 있는데 캘린더에는 없다」 가 됐다.
실제로 「배찌 세무회계」 줄이 「제니 테스트」 의 셋업예약을 끌어왔고,
로움 계정에서는 캘린더에만 안 보였다 — 같은 기준(_notiCompOk) 을 쓴다.
⚠ 화면마다 다른 기준으로 거르면 어느 쪽이 맞는지 알 수 없다.
한쪽을 고칠 때 다른 쪽도 같이 본다.
⚠ 영업 · 유입은 문의마다 줄이 서므로 문의 단위여야 한다 —
사업자로 찾으면 같은 사무소의 다른 문의 예약이 여러 줄에 겹쳐 나온다.
⚠ 목록 칸 · 정렬값 · 상단 고정이 모두 같은 판정을 써야 한다.
하나만 바꾸면 「칸에는 보이는데 위로 안 올라온다」 가 된다.
⚠ 색인을 비울 때 _apptBizIdx 도 함께 비운다. 하나만 비우면 옛 값이 남는다.
온보딩 컬럼에서 셋업예약을 단계 바로 뒤로 옮겼다 (kevin 확정) —
「어느 단계인가」 와 「언제 만나나」 가 이어 읽혀야 한다.
LIST_COLS_VER.onboarding 을 1 로 올려 저장된 설정이 있어도 새 순서가 선다.
가능성 — 크기 · 색은 같고 채운 정도만 다르게
높음이 빨강이라 「문제 있음」 으로 읽혔다
빨강은 이 화면에서 지연 · 실패 에 쓰는 색이다.
같은 축의 세 값을 색으로 가르면 다른 뜻의 표시로 보인다 —
가망이 높다는 좋은 소식인데 경고처럼 읽혔다.
그리고 글리프(● ◐ ○) 는 글꼴마다 원 크기가 달라
나란히 두면 들쭉날쭉했다.
SVG 로 그려 반지름을 4.6 으로 고정하고 색은 #F59E0B 한 가지만 쓴다 (v0.797 · kevin 확정).
높음은 가득, 보통은 왼쪽 반원, 낮음은 테두리만이다 — 채운 정도가 곧 값이다.
⚠ 지도 배지(_possBadge) · 유입 카드 셀렉트도 POSS_COLOR 를 쓴다.
아이콘만 고치면 그 둘에서 높음이 빨강으로 남아 뜻이 갈린다 — 함께 한 색으로 맞췄다.
⚠ 칸반 카드가 두 줄로 밀렸다 (v0.800 에서 고침)
.kb-poss 는 CSS 정의가 아예 없어 그냥 <span> 이었다.
글리프일 때는 글자라 줄 안에 흘렀는데, SVG(display:block) 로 바꾸자
블록이 되어 제목이 아래로 내려갔다.
아이콘을 글자에서 그림으로 바꾸면 그 자리를 감싸는 쪽도 함께 본다 —
글자는 어디에 두어도 흐르지만 그림은 그렇지 않다.
마우스가 올라간 줄을 또렷하게
종전 #F9FAFB 는 흰 바탕과 거의 같아 어느 줄에 있는지 알 수 없었다.
칸이 많아 눈이 가로로 길게 움직이는 화면이라 줄이 또렷해야 한다 —
옅은 노랑 #FEF9C3 으로 바꿨다 (v0.796 · kevin 확정).
⚠ .list-table tr:hover 가 두 곳에 있었다. 뒤에 쓴 것이 실제로 이기고 있어
위만 고치면 색이 안 바뀐다 — 같은 선택자를 고칠 때는 전수로 세어보고 손댄다.
⚠ 고른 줄(파랑) 이 마우스보다 세야 한다. CSS 는 같은 자세기면 나중에 쓴 것이 이기므로
.selected 를 :hover 뒤에 둔다 — 순서가 뜻을 갖는다.
기본 컬럼을 고치면 저장된 설정을 버린다
저장된 컬럼 설정이 기본값보다 세다
crm_list_prefs 에 값이 있으면 _lcDefs().def 가 먹지 않는다.
그래서 기본 컬럼을 고쳐도 한 번이라도 컬럼 설정을 만진 사람에게는 반영되지 않았다 —
활성화의 첫 칸이 등록일로 남아 있던 것이 그 예다 (v0.788).
LIST_COLS_VER 에 뷰마다 판을 두었다 (v0.795 · kevin 확정).
저장분의 판이 낮으면 그 설정을 무시하고 새 기본값을 세운다.
| 상황 | 결과 |
| 판이 낮은 저장분 | 무시하고 기본값. 지우지는 않는다 —
사람이 다시 고치면 그때 새 판으로 저장된다. 여기서 지우면 「맞춰 둔 것이 말없이 사라졌다」 가 된다 |
| 판이 같은 저장분 | 그대로 쓴다 |
| 판을 안 올린 뷰 | 손대지 않는다 |
⚠ 손대지 않은 뷰는 0 으로 둔다
기존 저장분에는 ver 이 없어 0 으로 읽힌다.
전부 1 로 시작하면 지금 이 순간 모든 사람의 모든 뷰 설정이 한 번에 날아간다.
기본 컬럼을 고친 뷰만 1 씩 올린다 — 지금은 active: 1 하나뿐이다.
⚠ 키는 _lcViewKey() 가 돌려주는 값과 같아야 한다.
다르면 조용히 0 이 되어 아무 일도 일어나지 않는다.
⚠ 기본 컬럼을 고칠 때마다 그 뷰의 숫자를 올린다. 안 올리면 이번에도 안 보인다.
메뉴를 옮기면 사용현황상세도 닫는다
목록을 덮는 패널이라 캘린더와 성격이 같다 (v0.794 · kevin 확정) —
캘린더도 좌측 메뉴를 누르면 닫는다 (v0.654).
⚠ _avOpen 은 전역이라 뷰를 옮겨도 켜진 채 남았다.
_avMount 가 패널만 지우고 값은 그대로여서
온보딩 ↔ 활성화로 옮기거나 되돌아오면 다시 펼쳐졌다 —
「그리는 쪽만 막고 상태를 안 끄면 되살아난다」.
_avYMPick 은 남긴다 — 어느 달을 보던 중이었는지는 기억하는 편이 낫다.
「줄이 안 맞는다」 의 절반은 착시였다
행 위치는 맞았다 — 색칠된 칸이 이어져 보였을 뿐이다
구분선이 #F3F4F6 인데 분홍(#FEE2E2) · 노랑(#FEF3C7) 배경 위에서는 보이지 않는다.
위아래로 같은 색이 이어지면 두 줄이 한 덩어리로 읽히고,
옆의 목록은 또박또박 나뉘어 있어 어긋난 것처럼 보인다.
⚠ 처음에는 색칠된 칸의 border-bottom-color 만 진하게 했는데 여전히 안 보였다 —
border-collapse:collapse 에서 테두리는 이웃 칸과 합쳐지고
배경색이 그 위를 덮는다. box-shadow:inset 0 -1px 0 로 바꿨다 —
칸 안쪽에 배경 위로 그려지므로 색과 무관하게 남는다 (v0.793).
⚠ 「어긋나 보인다」 와 「어긋났다」 는 다르다. 좌표를 재서 확인한 뒤에 고쳐야
엉뚱한 곳을 손대지 않는다 — v0.791 의 행별 보정은 그것대로 필요했지만
눈에 보이던 문제는 이쪽이었다.
사용현황상세 — 활성화에서 안 열리고, 온보딩에서 줄이 밀렸다
활성화에는 버튼만 있고 패널이 없었다
v0.778 에서 서브메뉴에 「사용현황상세」 를 넣었는데
_avMount 의 조건이 currentView === 'onboarding' 뿐이었다.
버튼은 켜지는데 패널은 안 그려졌다.
그 탓에 .av-ctl(position:absolute) 이 기준을 잃고
left:0 으로 「26-08」 이 첫 칩 위에 겹쳐 보였다 —
「창이 안 보인다」 와 「칩이 겹친다」 가 같은 원인이었다.
tr 의 height 는 표에서 「최솟값」 이다
테두리 · 반올림 · sticky 처리가 조금씩 달라 실제로 그려진 높이가 1px 씩 커지면
아래로 갈수록 쌓인다. 한 줄만 보면 눈에 안 띄는데 스무 줄이면 20px 이다 —
위쪽은 맞는데 아래쪽만 어긋나 보이는 이유다.
계산으로 맞추려 들지 말고 그려진 이웃 간격을 좌우로 견줘 그 차이만큼 더한다.
두 번이면 수렴한다 — 더 돌아도 반올림 때문에 끝나지 않는다.
⚠ v0.751~758 의 보정(머리 높이 · 첫 행 잔차) 은 전체를 한 번에 미는 것이라
행마다 쌓이는 차이는 못 잡았다.
알림이 캘린더 뒤로 숨었다
.header{position:relative; z-index:10} 가 스태킹 컨텍스트를 만든다
안쪽의 .noti-drop 은 z-index:999 인데 그 값은 헤더 안에서만 쓴다 —
바깥에서 겨루는 것은 헤더 전체의 10 이다.
그래서 캘린더(#calDrawer 40) 나 상세 패널(.idp 60) 을 열면
알림 · 계정 드롭다운이 그 뒤로 숨었다.
헤더를 100 으로 올렸다. 확인창(500) · 툴팁(600) · 잠금(9999) 보다는 아래다 —
그것들은 헤더까지 덮어야 하는 것이다.
⚠ 안쪽 z-index 를 아무리 키워도 소용없다. 조상이 컨텍스트를 만들면 거기서 잘린다 —
겹침 문제는 그 요소가 아니라 조상의 z-index 부터 본다.
방문자를 여럿 넣는다
assignee 는 처음부터 쉼표로 여럿을 담았다 —
목록의 「kevin+1」 이 그 표시다(_dispAssigneeList).
Flow 업로드는 그렇게 넣는데 화면에는 칸이 하나뿐이라 사람이 추가할 방법이 없었다 (v0.790 해소).
등록 · 수정 폼의 줄 수와 순서를 같게 맞췄다 (v0.809 · kevin 확정) —
같은 일을 하는 두 화면이 다르게 생기면 매번 다시 익혀야 한다.
⚠ 수정 폼은 한 줄에 다섯(구분 · 유형 · 담당자 · 날짜 · 시각) 을 넣어
272px 에서 제멋대로 접혔다.
1줄 구분 · 유형
2줄 담당자 · + 방문자
3줄 동행 N (있을 때만)
4줄 일시 날짜 · 시각 (일시가 있는 유형만)
5줄 내용
6줄 저장 · 취소 · 삭제
수정 폼에도 방문자 추가를 넣었다.
⚠ 열 때 저장된 담당자가 여럿이면 둘째부터 동행 줄로 채운다 —
안 채우면 내용만 고쳐도 동행이 조용히 지워진다.
⚠ 다시 그리기 전에 입력 중인 값을 챙긴다 (v0.810)
+ 방문자 · 구분 변경은 _idpRerender() 로 폼을 통째로 다시 만든다.
그때 내용 · 날짜 · 시각을 저장값에서 다시 읽으므로 적던 내용이 말없이 사라졌다.
등록 폼(_actAddRender) 은 이미 챙기고 있었다 —
같은 자리에서 두 폼이 다르게 동작하면 한쪽만 고쳐진다.
⚠ 빈 문자열도 사람이 지운 것이라 ?? 로 받는다. || 를 쓰면
내용을 비우고 + 를 누른 순간 옛 내용이 되살아난다.
⚠ v0.809 는 동행이 저장되지 않았다
_actEditWhoAll() 을 만들어 놓고 저장부에서 부르지 않았다 —
afWho 값만 읽고 있었다. 정의는 있는데 호출이 없으니
node --check 도 checkbp 도 잡지 못한다.
「정의만 있고 호출이 없는 함수」 를 세는 검사를 붙였다 —
이름을 만들어 놓고 연결을 빠뜨리는 것이 이번 판에서만 두 번이다 (blk · 이것).
방문 · 예약처럼 일시가 있는 활동에만 + 방문자 가 붙고, 누르면 「동행 2 · 3 …」 줄이 늘어난다.
⚠ select multiple 은 272px 폭에서 다루기 어렵고 모바일에서 특히 나쁘다 — 한 줄씩 늘리는 쪽이 맞다.
⚠ 빈 칸과 중복은 버린다. 「kevin, kevin」 이 저장되면 목록에 「kevin+1」 로 나와 두 명처럼 보인다.
활성화의 첫 칸은 가입일 — 재가입했으면 재가입일
가입한 고객만 활성화로 오므로 「언제 문의했나」 보다 「언제 가입했나」 가 기준이다 (v0.788 · kevin 확정).
온보딩과 같은 규칙이다.
_joinedAt(inq)
① 원장 세모R+ 이면 insight_joined_at
그 외는 rejoined_at → joined_at ← 재가입일이 먼저다
② 문의 joined_at (수기 입력) — 원장이 없을 때만
상세의 가입일자 칸(_joinedRow) 이 이미 이 규칙을 쓴다.
목록과 상세가 다른 날짜를 보이면 어느 것이 맞는지 알 수 없다.
⚠ 원장이 먼저다 — 문의의 joined_at 은 수기 입력이라 원장이 없을 때만 의미가 있다 (기획서 4번).
⚠ 저장된 컬럼 설정이 기본값을 이긴다
crm_list_prefs 에 값이 있으면 ACT_COLS_DEFAULT 가 먹지 않는다.
활성화는 v0.778 전까지 컬럼 설정을 안 썼는데 뷰를 옮기며 남은 값이 있어
등록일이 그대로 첫 칸에 섰다. _lcMigrateOnb 가 갈아 끼운다 —
온보딩에 쓰던 이관을 활성화에도 태웠다. 정렬 키도 함께 옮긴다.
활성화는 「셋업까지 끝난 고객」 만 센다
한 고객이 온보딩과 활성화 두 화면에 동시에 서 있었다
가입하면 원장에는 바로 정상 으로 올라오는데, 셋업이 끝나기 전까지 CRM 단계는 온보딩이다.
_riskBase 가 원장만 보고 세다 보니 그 고객이 양쪽에 나왔다 —
기획서 2절의 「카드는 항상 한 곳에만 있다」 와 어긋난다.
v0.778 에서 활성화 목록을 문의 기반으로 바꾸면서 단계 컬럼이 붙어 드러났다 —
목록에 「온보딩 / 대기」 라고 적힌 행이 활성화 화면에 있었다.
대상을 원장 정상 ∩ 문의가 활성화 대단계 로 좁혔다 (v0.786 · kevin 확정).
셋업 중인 고객의 사용현황은 온보딩 목록의 활성화 지표(av_*) 로 본다 —
활성화 화면은 「셋업까지 끝난 고객을 관리하는 자리」 다.
⚠ 칩 다섯 개와 분모가 모두 _riskBase 하나에 매달려 있다 (v0.557)
여기만 좁히면 전체 · 정상 · 위험 있음 · 거래처 · 발송수 · 인증서가 함께 맞는다.
목록만 좁히고 칩을 두면 「위험 있음 110 인데 목록엔 일부만」 이 된다.
⚠ 900건 × 1,655건을 매번 훑으면 칩 다섯 개마다 150만 회가 된다 —
_activeBizSet() 로 한 번 만들어 돌려 쓴다.
빠진 건은 서브메뉴에 「온보딩 진행 N」 으로 적는다 —
안 보여주면 「가입했는데 활성화에 없다」 가 원인 불명으로 남는다.
문의가 없는 원장도 함께 빠진다. CRM 단계가 없으니 어느 목록에도 속하지 않는다 — 고객 탭에서는 그대로 보인다.
되살리기를 걷어내고 새 건을 만든다
v0.631~779 의 되살리기(reopen) 를 통째로 뺐다.
_applyActStage 는 끝난 건을 만나면 그냥 돌아가고,
_ensureLiveInquiry 가 새 문의를 만든다.
⚠ 판정은 _isEndedInq 한 곳에서만 한다 —
화면마다 조건을 적으면 반드시 하나가 어긋난다.
⚠ 가입완료를 「끝난 건」 에 넣을 뻔했다
STEP_END_DETAILS(가입완료 · 문의종료) 를 그대로 쓰면
셋업예약을 잡을 때마다 새 문의가 생겨 가입완료 → 온보딩 진행이 끊긴다.
ENDED_DETAILS 를 따로 두어 문의종료만 담았다.
같은 배열이라도 부르는 자리에 따라 뜻이 다르다 —
「영업 목록의 결말」 과 「고객이 끝났다」 는 다른 말이다.
정지 칩
잠재 그룹의 해지 옆에 정지를 넣었다 (v0.782).
칩 · 목록(_leadRows) · 툴팁 셋을 함께 넣는다 —
칩만 넣으면 숫자는 보이는데 눌러도 빈 화면이 나온다.
고객원장 귀속 회사 (A-5d 준비)
원장 업로드가 company_id 를 채운다 (v0.784). SQL 370 의 백필과 같은 규칙이라야 한다 —
다르면 백필한 값과 업로드한 값이 갈려 회사가 매달 흔들린다.
① 유입코드 사업자번호로 문의를 이어 가장 최근 문의의 코드 → 회사
② 사무소 crm_offices.company_id (지금 화면 필터가 쓰는 판정 · v0.553)
③ 로움 둘 다 없을 때
⚠ 첫 판(v0.781) 은 「유입코드 → 없으면 로움」 이었다
실측에서 사무소 기준과 902건 중 806건이 달랐다 — 코드가 없어 로움으로 떨어지는 건이
그만큼 많았고, 그대로 걸면 외부 회사가 자기 원장을 대거 잃는다.
②를 한 단계 넣어 「지금 화면이 이미 쓰고 있는 판정」 을 잇는다 (kevin 확정 「가」).
로움으로 가는 것은 코드도 사무소도 없는 진짜 미지정만 남는다.
⚠ 여기서 안 채우면 새 원장이 아무에게도 안 보인다
정책(SQL 371) 아래에서 company_id 가 NULL 인 행은
로움 계정에서도 사라진다. 업로드가 늘 채워야 한다.
⚠ 로움 회사를 이름으로 찾는다(_loumCompanyId). 회사 이름을 바꾸면
여기와 SQL 370 을 함께 고쳐야 한다.
Flow 가 사람이 고친 값을 덮지 않는다 (E-1)
사업장 편집이 다음 업로드에서 사라졌다
편집은 crm_inquiries 에 저장되는데 Flow 업로드가 f_task_no 기준
upsert 로 같은 필드를 덮었다. v0.537 부터 crm_field_edits 에
「무엇을 고쳤나」 를 쌓아 두었고, 이제 그것을 보고 payload 에서 뺀다 —
upsert 는 보낸 필드만 갱신하므로 빼면 DB 값이 그대로 남는다.
| 결정 | 내용 |
| 범위 | 전부 보호 (kevin 확정). Flow 가 정정한 값도 안 들어온다는 뜻이다 —
전화번호를 손으로 고친 뒤 Flow 에서 진짜 번호가 바뀌어도 반영되지 않는다 |
| 기간 | 영원히. CRM 값이 기준이다 |
| 되돌리기 | 화면에 두지 않는다 — 범위와 기준을 정하기 어렵다. 다시 고치려면 직접 고친다 |
| 알림 | 업로드 후 「N건의 M칸을 덮지 않았습니다」 와 필드별 내역을 카드에 남긴다.
안 보여주면 「Flow 에는 새 값이 있는데 화면은 그대로」 가 원인 불명으로 남는다 |
⚠ 조심한 것 셋
① f_task_no 는 절대 빼지 않는다 — upsert 의 충돌 키다.
빠지면 매칭이 안 돼 전부 새 행으로 INSERT 된다. company_id ·
inflow_code_id · updated_at 도 사람이 고치는 값이 아니라 함께 막았다
(FLOW_NEVER_SKIP).
② 기록 조회에 실패하면 반영을 중단한다. 보호 목록을 모르는 채로 올리면
조용히 덮는데, 그것이 가장 나쁘다.
③ crm_field_edits 를 끝까지 읽는다 — Supabase 는 요청당 1,000행이 한계다.
잘리면 그만큼 보호가 샌다.
⚠ 편집 경로가 늘면 _logFieldEdits 호출을 그곳에도 넣어야 한다.
빠뜨리면 그 경로의 수정은 보호받지 못한다 — 지금은 사업장 편집(_cuSaveEdit) 한 곳이다.
정지 · 해지도 되살린다
기존 정지 · 해지 고객에게도 영업 · 활성화를 다시 붙이기 위해(kevin 확정)
_applyActStage 의 조기 반환을 걷어냈다.
끝난 건 판정을 ended 하나로 모았다 —
실패 · 문의종료 · 가입완료 · 정지 · 해지가 모두 같은 규칙을 쓴다.
예약을 잡으면 그 예약의 구분으로 되살아나고, 통화 · 발송으로는 움직이지 않는다.
활성화 화면을 온보딩과 같은 구조로
활성화만 다른 세계였다
목록은 원장(crm_customers) 기준 전용 6컬럼(sortDefs) 이라
컬럼 설정도 · 사무소명 배지도 · 예약 칸도 없었다. 같은 목록인데 조작이 달랐다.
필터는 riskBar 라는 별도 줄에 있어, 같은 자리에 성격이 다른 두 줄이 겹쳤다.
| 항목 | 바꾼 것 |
| 목록 행 | 원장 → 문의. 대상은 「원장이 정상인 사업자」 이고
위험 판정은 종전대로 원장이 한다(_riskBase) — 화면에 세우는 것만 그 사업자의 문의로 바꿨다.
사업자당 한 줄로 묶는다(_onbDedup) |
| 컬럼 | ACT_COLS_DEFAULT — 온보딩과 같은 구성.
⚠ onb_step 대신 step 을 쓴다. 온보딩 단계는 활성화에 없는 값이다 |
| 서브메뉴 | 온보딩과 같은 줄로 옮겼다.
위험 항목 넷은 「위험 있음」 의 내역이라 괄호로 묶어 한 단 작게 넣는다 —
나란히 늘어놓으면 여섯 개가 같은 무게로 보여 「정상 / 위험 있음」 두 갈래가 안 읽힌다 |
| 기준값 입력 | 같은 줄로 합쳤다. riskBar 는 감추기만 한다 —
⚠ 호출부가 여러 곳이라 함수는 남긴다. 지우면 그 경로에서 ReferenceError 가 난다 |
⚠ 문의가 없는 원장은 목록에 서지 못한다
행이 문의가 되었으므로 문의 없는 원장은 세울 수가 없다.
조용히 빠지면 「전체 359 인데 목록은 340」 이 되어 어디로 샜는지 알 수 없다 —
서브메뉴에 「문의 없음 N」 으로 적는다.
⚠ 항목별 건수는 서로 겹친다 — 한 고객이 셋에 동시에 걸릴 수 있어
괄호 안의 합이 「위험 있음」 과 맞지 않는다. 그래서 합집합을 괄호 밖에 둔다.
renderRiskBar 를 부르던 곳(설정 로드 · 기준값 변경 · 칸반 진입) 은
이제 renderSubStepBar 를 부른다 — 안 바꾸면 칩 숫자가 갱신되지 않는다.
끝난 건의 되살리기 — 활성화만 빠져 있었다
ACT_STEP_MAP_BY_GRP['활성화'] 가 비어 있었다
「활성화의 세부단계는 정상 하나라 활동으로 정할 값이 없다」 고 두었는데,
그 탓에 to 가 나오지 않아 활성화 예약은 끝난 건을 되살리지 못했다.
통화 · 발송은 그대로 두고 예약 두 종만 채웠다.
되살아나는 자리는 그 예약의 구분이 정한다 — 「예약을 잡았다」 는 그 여정을 다시 시작한다는 뜻이다.
E-21 을 목록 포함 조건이 아니라 이 방식으로 풀었다 (kevin 확정) —
예약을 넣는 순간 단계가 진행 중으로 돌아와 목록에 저절로 나타난다.
실패 385건이 통째로 섞이지 않는다.
이번에 돌린 SQL
| 번호 | 내용 | 순서 주의 |
| 366 | activity_type CHECK 확장 — 셋업예약 추가 (11종) |
⚠ 코드 배포 전. 먼저 배포하면 저장이 23514 로 실패한다 |
| 367 | 온보딩 구분의 예약 → 셋업예약 이관 |
⚠ 코드 배포 후. 먼저 옮기면 옛 코드가 ACT_RESV_TYPES 에서 못 찾아
그 예약이 목록 · 캘린더 · 알림에서 통째로 사라진다 |
| 368 | done_result 컬럼 신설 (완료 · 취소) |
⚠ 코드 배포 전. 새 컬럼은 GRANT UPDATE 를 따로 준다 —
컬럼 단위 권한이 걸려 있으면 테이블 GRANT 로 덮이지 않는다 |
| 369 | done_result 값 확장 — 가입 · 실패 추가 |
⚠ 코드 배포 전. 넓히기만 하고 옛 값은 그대로 둔다 (S-05) |
| 370 | 고객원장 company_id 추가 + 3단계 백필
(유입코드 → 사무소 → 로움) |
⚠ 이 파일만으로는 아무것도 막지 않는다. V 의 「회사 미지정 0」 을 확인한 뒤 371 로 간다 |
| 371 | crm_customers RLS + anon 권한 회수 |
⚠ 백필 전에 걸면 원장이 통째로 사라진다. 실측 — anon 은 이미 없었다 (A-5 감사 때 정리됨) |
⚠ 370 은 첫 판을 버리고 다시 썼다
처음 규칙은 「유입코드 → 없으면 로움」 이었는데, 실측에서 사무소 기준과
902건 중 806건이 달랐다 — 코드가 없어 로움으로 떨어지는 건이 그만큼 많았다.
그대로 걸었으면 외부 회사가 자기 원장을 대거 잃는다.
사무소를 한 단계 넣으니 901 / 1 로 지금 화면과 사실상 같아졌다.
규칙을 정했으면 걸기 전에 지금 결과와 비교한다 — 숫자를 보기 전에는 알 수 없었다.
그 밖에
| 항목 | 내용 |
| 가능성 | 칸반 카드에만 있던 ● ◐ ○ 를
목록의 사무소명 바로 뒤에 붙였다. 같은 값을 두 화면에서 다르게 보면 「목록에는 왜 없나」 가 된다 |
| 셋업완료 | 셋업 완료 → 셋업완료 로 붙여 쓴다 —
셋업예약 · 셋업준비와 나란히 서는 자리다. ONB_DONE 한 곳만 고치면 되도록
손으로 적던 네 곳을 상수 참조로 바꿨다 |
| 0 건수 | 온보딩 중메뉴의 0 을 회색에서 파랑으로 —
0 은 「아직 아무도 없다」 는 뜻이지 「볼 것이 없다」 는 뜻이 아니다.
⚠ CSS 만 지우면 JS 가 붙이는 zero 클래스가 정의 없이 남는다. 함께 걷어냈다 |
ask() | 아니오 라벨이 취소 로 고정이라
「창을 닫는다」 와 「예약을 취소한다」 가 같은 말이 됐다. o.no 를 받게 넓혔다 —
⚠ 준 값을 남기면 다음 확인창까지 따라가므로 없으면 되돌린다 |
v0.770 (2026-08-21)
색을 한 곳으로 모으고, 활동이력 카드에서 구분 · 유형 · 예약 상태 세 가지가
서로 다른 자리에서 말하게 갈랐다.
같은 것이 화면마다 다른 색이었다
구분 색이 세 곳에 따로 적혀 있었다
CRM_COLS(좌측 메뉴 · 칸반 · 지도) · STEP_COLOR(단계 배지) ·
ACT_GRP_COLOR(활동 구분).
영업이 주황과 파랑, 온보딩이 초록과 보라, 활성화가 파랑과 보라로 갈려 있었다.
특히 활성화의 파랑(#3B82F6) 과 활동 영업의 파랑(#1D4ED8) 이 거의 같아
활동이력의 「영업」 배지가 다른 화면의 「활성화」 색으로 읽혔다.
| 구분 | 색 | 비고 |
| 영업 | #1D4ED8 파랑 | 주황에서 옮겼다 — 주황을 예약에 넘기기 위해서다 |
| 온보딩 | #10B981 초록 | |
| 활성화 | #7C3AED 보라 | |
| 예약 | #F97316 주황 | 구분이 아니라 시간축이다. 한 자리를 통째로 비워 뒀다 |
GRP_COLOR 한 곳에서 세 곳이 모두 온다.
세부단계도 따라간다 — 온라인상담 이 #7C3AED 였는데 그것이 새 활성화 색이라
그대로 두면 겹쳤다. 대기 는 주황을 예약에 넘겨 회색으로 내렸다.
캘린더도 구분 색을 따른다 (kevin 확정)
달력에는 예약 · 방문뿐이라 「무슨 일인가」 보다 「어느 여정의 일인가」 가 하루를 짜는 값이다.
시간 상태는 오른쪽 배지(오늘 · D-12 · 미처리) 가 말한다.
onbplan · onbsoon 를 지웠다 — 두 축을 한 cls 에 담으면 조합이 계속 불어난다.
카드 — 유형을 예약줄로 내렸다 (안2)
시안을 만들 때는 온보딩 예약의 유형이 온라인상담 이라
예약줄만 봐서는 방문인지 온라인인지 알 수 없었다. 그런데 v0.769 에서 셋업예약 유형이 생기면서
첫 줄에 이미 적히게 됐다 — 예약줄에 또 넣으면 「셋업예약」 과 「셋업」 이 한 카드에 두 번 나온다.
예약이면 첫 줄은 「누가 · 언제 남겼나」, 예약줄은 「무슨 예약 · 언제 · 어떻게 됐나」 로 갈랐다.
유형칩(ACT_COLOR) 은 걷어냈다 — 구분 색과 무관한 두 벌이 돌고 있었다.
결과 = 완료 / 취소
「완료」 버튼이 종료로 읽혔다 — 결과를 남기는 자리인데 끝내는 것처럼 보였다.
1단계는 어디서 누르든 완료 · 취소 둘이다. 2단계는 구분에 따라 갈린다 —
영업은 접촉 · 가입완료 · 실패, 온보딩은 갈 곳이 하나뿐이라 바로 셋업 완료,
활성화는 옮길 곳이 없어 완료 표시만 남는다.
취소는 단계를 옮기지 않는다 — 못 만난 것이지 영업이 끝난 것이 아니다.
단계는 그대로 남고 예약만 사라져 「다시 잡아야 하는 건」 이 된다.
done_result 를 새로 두었다 (SQL 368).
종전에는 done_at 만 남고 무엇을 골랐는지는 단계 이동으로만 흡수되어,
나중에 카드를 봐도 완료였는지 실패였는지 알 수 없었다.
취소 판정이 새기 쉬운 자리였다
「이 건에 예약이 있나」 를 묻는 곳이 여럿이다
_apptOf · _hasLiveResv · 캘린더 수집이 각자 ACT_RESV_TYPES 를 봤다.
각자 취소를 걸러내게 하면 반드시 한 곳을 빠뜨린다 (E-12 유형).
_apptAlive(a) 하나를 만들어 세 곳이 그것만 쓰게 했다.
_hasOpenAppt · 알림센터는 done_at 을 보므로 자동으로 걸린다.
⚠ _hasLiveResv 는 done_at 을 아예 안 보고 있었다 —
완료한 미래 예약도 「살아있는 예약」 으로 세고 있었다. 함께 고쳤다.
미처리 예약을 위로 묶는다
정렬 키를 바꾸는 것이 아니다. v0.571 에서 activity_date 정렬을 물린 이유가
「예약이 미래 날짜라 맨 위에 몰린다」 였는데, 그때는 완료한 예약까지 올라왔다.
여기서 올리는 것은 done_at 이 없는 것뿐이라 처리한 예약은 시간순에 그대로 남는다.
블록 안은 예약일시 오름차순 — 이력이 아니라 할 일 목록이라 가까운 것이 먼저다.
지남 → 오늘 → 내일 → D-12 순으로 선다. 미처리가 없으면 구분선을 감춘다.
지남 · 미처리가 가장 흐리게 보이고 있었다
past 가 회색(#9CA3AF · 배경 없음) 이었다. 미래 예약은 형광 노랑인데
정작 조치가 필요한 것이 제일 눈에 안 띄었다 — 순서가 뒤집혀 있었다.
빨강으로 바꾸고 「지남」 대신 「16일 지남」 으로 경과일을 적는다.
얼마나 방치됐는지가 조치 여부를 가르는 값이다.
ask() 의 아니오 라벨
온보딩 목록에서 「완료 / 예약 취소」 두 갈래를 물어야 하는데
아니오 버튼이 취소 로 고정이라 「창을 닫는다」 와 「예약을 취소한다」 가 같은 말이 됐다.
o.no 를 받게 넓혔다. ⚠ 준 값을 남기면 다음 확인창까지 따라가므로 없으면 되돌린다.
창을 닫거나 ESC 를 눌러도 아니오와 같은 값이 오므로, 취소 쪽은 한 번 더 묻는다.
v0.767 (2026-08-21)
단계와 활동이 한 컬럼에 두 축으로 눌려 있던 것을 갈랐다.
「온보딩 셋업예약을 잡았는데 영업으로 간다」 에서 시작했다.
왜 영업으로 갔나
_applyActStage 가 대단계를 '영업' 으로 고정하고 있었다
ACT_STEP_MAP 은 세부단계만 정하고, 대단계는 한 줄로 박혀 있었다.
활동에 이미 구분(stage_group) 이 붙어 있는데 그것을 보지 않았다 —
온보딩 구분으로 남긴 온라인상담이 영업|온라인상담 이 되어
그 건이 온보딩 목록에서 사라지고 영업 목록으로 내려갔다.
v0.725 의 ACT_STAGE_KEEP 가드는 문의의 현재 단계를 본다.
아직 영업 단계인 건은 가드에 걸리지 않아 그대로 끌려갔다.
| 축 | 담는 곳 | 정하는 것 |
| 구분 (대단계) | stage_group | 이 활동이 영업인가 온보딩인가 활성화인가 |
| 세부단계 | step_detail | 그 여정 안에서 어디까지 갔나 |
| 활동 유형 | activity_type | 무엇을 했나 — 수단 |
대단계는 활동의 구분을 그대로 쓴다 — 앞에서 고른 것이 곧 그 건의 단계가 된다.
매핑과 서열을 구분별로 갈랐다 (ACT_STEP_MAP_BY_GRP · STEP_RANK_BY_GRP).
앞선 구분으로 남기면 단계를 올린다 (kevin 확정 「가」)
문의는 영업|접촉 인데 온보딩 셋업예약을 잡으면 온보딩|셋업예약 이 된다 —
영업|가입완료 를 건너뛴다. 셋업예약을 잡았다는 것은 가입이 확정됐다는 뜻이다.
⚠ 실적이 새지 않는지 확인했다 — 유입코드의 가입 판정(_ifJoined) 은
['온보딩','활성화','정지','해지'] 를 포함하고, 영업 칩의 「가입 › 완료」 는
지금 영업에 걸린 건을 세는 자리라 떠난 건은 애초에 대상이 아니다.
가입 실적은 원장의 가입일자가 기준이다.
뒤처진 구분으로 남겨도 단계는 내리지 않는다 (GRP_RANK) — v0.633 원칙 그대로다.
서열은 같은 대단계 안에서만 비교한다
영업의 방문예약 2 와 온보딩의 셋업준비 2 는 다른 축이다.
한 표로 견주면 「온보딩에서 영업으로 내려오는데 서열이 같아 통과」 같은 일이 생긴다.
대단계가 다르면 서열 비교를 건너뛰고 GRP_RANK 만 본다.
들여쓰기는 CSS 로 안 된다 — optgroup 이 유일하다
option.so-sub{padding-left:18px} 는 실제로 먹지 않았다 (v0.575)
Chrome 은 펼친 목록의 option 을 OS 로 그린다 — CSS 가 닿지 않는다.
공백 문자는 닫힌 박스에도 남아 값이 밀려 보여서 그것도 피했는데, 결과적으로 둘 다 안 된 상태로 남아 있었다.
브라우저가 하위를 들여쓰는 방법은 optgroup 뿐이다 — 활동 유형 셀렉트가 이미 그렇게 생겼다.
라벨은 고를 수 없으므로 대단계 전체를 첫 항목으로 넣는다.
⚠ 「안 되는 방식으로 넣어두면 된 줄 안다」 — 넣은 뒤 실제 화면에서 확인해야 한다.
셋업 완료는 STEP_DETAILS 에 넣지 않는다
저장값이 활성화|정상 + onboarding_done_at 인 파생 상태다.
STEP_DETAILS['온보딩'] 에 넣으면 온보딩|셋업 완료 로 저장되어
「셋업완료인데 온보딩」 과 「셋업완료인데 활성화」 두 가지가 생긴다.
셀렉트에는 온보딩|__done 표식으로 두고 updateInquiryStage 에서 갈라 처리한다.
셋업 사전준비 → 셋업준비 는 STEP_DETAIL_LABEL 로 표시만 바꿨다 —
'실패'→'가입실패' 와 같은 관례다. 저장값을 바꾸면 S-05 3단계가 필요한데 얻는 것이 표시명뿐이다.
수정 폼에 구분이 없었다
등록 폼에는 actAddS 가 있는데 수정 폼(_actFormHtml) 에는 없었다.
_actSave 의 patch 에 stage_group 이 없어 값이 지워지지는 않았지만,
화면에서 사라져 「없어졌다」 로 보였고 잘못 들어간 구분을 되돌릴 방법이 없었다.
기본 구분도 모든 대단계에서 문의를 따르게 넓혔다 (유입 · 실패는 영업으로 읽는다).
상수를 쪼갤 때 참조 한 곳이 남았다
ACT_STEP_MAP → ACT_STEP_MAP_BY_GRP 로 바꾸며 21966행을 빠뜨렸다
잠재에서 영업 대상으로 올리는 경로다. node --check 는 통과한다 —
없는 상수를 읽는 것은 실행해야 나는 오류다.
정의된 이름 목록과 쓰인 대문자 상수를 대조해서 잡았다.
상수를 쪼개거나 이름을 바꿀 때는 전수 대조를 붙인다 (GD010 신규 유형).
v0.759 → v0.762 (2026-08-20)
화면 잠금. 「아침에 오면 로그인되어 있다」 가 편한 만큼 위험하기도 하다는 데서 시작했다.
왜 늘 로그인되어 있었나
createClient(URL, KEY, { auth:{ persistSession:true } })
① 페이지 열림 → localStorage 에서 세션을 읽는다
② access_token 이 만료됐으면 refresh_token 으로 새로 받는다 (SDK 자동)
③ SIGNED_IN → onLogin()
| 토큰 | 수명 | 하는 일 |
access_token | 1시간 | 실제 요청에 쓰인다 |
refresh_token | 30일 | 만료된 access_token 을 새로 받아온다 |
refresh_token 이 살아 있는 한 다시 로그인할 필요가 없다 — 30일 넘게 안 들어와야 로그인 화면이 뜬다.
편하지만 그동안 화면은 계속 열려 있는 셈이다.
BP 와 견줘 빠져 있던 것
BP 기본형에는 10분 무활동 잠금이 있다 (MD001)
CRM 은 그것이 없어 세모리포트형에 가까웠다.
그런데 CRM 은 사업자번호 · 대표자 연락처 · 매출 규모를 다룬다 —
BP 의 업무계정만큼은 아니어도 가볍지 않다.
보안 전문가 체크리스트(GA005) 에도 「로그인 세션 만료 정책이 정의되어 있는가」 가 있다.
| 항목 | BP 기본형 | roumitCRM (v0.762) |
| 무활동 잠금 | 10분 | 30분 |
| 잠금 화면 재인증 | 있음 | 있음 — 구글로 한 번 더 |
| 활동 감지 | mousemove · keydown · click · touchstart | + wheel · scroll |
| 세션 복구 진입 | getSession() + 리스너 | 리스너만 (v0.510) |
getSession() 을 뺀 것은 v0.510 이다 —
리스너와 겹쳐 onLogin 이 두 번 돌아 지도의 고객 레이어가 사라졌다.
BP 는 currentUser?.id === user.id 로 중복을 막고 둘 다 쓴다.
지금 방식도 도는데, 리스너가 늦게 붙으면 첫 이벤트를 놓칠 여지가 남아 있다 — 아직 손대지 않았다.
잠금은 로그아웃이 아니다
세션은 그대로 두고 화면만 가린다
로그아웃까지 하면 다시 들어올 때 데이터를 처음부터 다시 읽어야 한다 —
문의 1,600건 · 원장 900건 · 활동 전체다. 잠금을 풀면 바로 이어서 쓸 수 있어야 한다.
배경은 반투명 + 블러다. 「가려져 있다」 가 읽히면서 뒤 내용은 알아볼 수 없다.
잠글 때 열려 있던 드롭다운 · 알림을 닫는다 — 잠금 위로 떠 있으면 내용이 보인다.
「브라우저를 닫으면 잠금」 은 넣었다가 뺐다
sessionStorage 는 탭마다 따로다
탭을 닫으면 지워지고 localStorage(=세션) 는 남는다 — 그 차이로 「창을 다시 열었다」 를 알 수 있다.
v0.760 에 그렇게 넣었다.
그런데 새 탭으로 열 때마다 잠긴다. 하루에도 여러 번 겪는 일이라
얻는 것보다 성가심이 컸다 (kevin 확정). 창만 닫고 나가는 경우는 30분 잠금이 대신 막는다.
기능을 뺄 때는 왜 뺐는지를 남긴다 — 안 남기면 반년 뒤에 같은 것을 다시 만든다.
구현에서 조심한 것
| 항목 | 내용 |
| 활동 감지 빈도 | mousemove 는 초당 수십 번 온다 —
1초에 한 번만 타이머를 다시 건다 |
| 재인증 | 구글 OAuth 로 돌린다. 구글 세션이 살아 있어 대개 한 번 누르면 돌아온다.
prompt:'select_account' 로 다른 계정으로 잘못 들어가는 것도 막는다 |
| 돌아온 뒤 | onLogin 에서 잠금을 내리고 버튼을 되돌린다 —
안 하면 재인증하고 왔는데 잠금이 그대로 떠 있다 |
| 감시 시작 | 로그인 뒤 한 번만 (window._lockOn) —
SIGNED_IN 이 다시 와도 리스너가 겹쳐 붙지 않는다 |
남은 것
· getSession() 병행 — BP 처럼 중복 방지를 두고 둘 다 쓰는 형태
· 잠금 시간을 설정으로 뺄지 — 지금은 LOCK_MIN 상수다
v0.695 → v0.726 (2026-08-20)
유입코드 관리 · 설정 위치 · 주소줄 토큰. 이 구간의 축은 유입코드를 화면에서 관리하게 만든 것이다.
유입코드 — 표는 있었는데 화면이 없었다
문의 160건에 코드가 적혀 있는데 마스터와 이어진 것은 0건이었다
crm_inflow_codes 는 v0.506 에 만들었지만 6개 컬럼짜리 인라인 편집이 전부였다.
운영은 엑셀(로움_유입코드관리)로 하고 있었고, 그 사이 어느 코드로 몇 건이 들어왔는지 아무도 몰랐다.
실측 — HP_LP 65건이 마스터에 없었다. 가장 많이 들어오는 경로인데 관리표에도 CRM 에도 없었다.
| 단계 | 내용 |
| 컬럼 확장 | SQL 345 — 채널 · 카테고리 · 유입경로 · 담당 · 단축URL · 비고 등 11개 |
| 이관 | SQL 347(29) → 352(38) → 353(40). 엑셀을 세 번 고쳐 받았다 |
| 문의 연결 | SQL 350 — 160건 중 65건이 이어졌고, 나머지는 마스터에 없는 코드였다 |
| 빠진 코드 채움 | 엑셀에 11개를 더해 40건. 이제 미등록 0 |
| 임시 사용자 | SQL 349 — 위멤버스 담당자 4명. 아직 CRM 계정이 없어 실적이 이어지지 않았다 |
화면 (v0.702 ~ 0.724)
| 항목 | 결정 |
| 목록 12컬럼 | 17개 항목을 다 펼치면 가로로 넘쳐 훑는 기능을 잃는다.
나머지는 우측 패널에서 본다 |
| 패널 720px | 목록 위로 겹친다 — 나란히 두면 목록이 줄어든다.
두 겹으로 만든다 (절대배치 + sticky) : 하나만 쓰면 스크롤할 때 사라지거나 목록을 밀어낸다 |
| 열면 바로 편집 | 나갈 때 묻는다. 푸터에 무엇을 고쳤는지 적는다 |
| 미등록 코드 탭 | 표로 두지 않고 그때그때 집계한다 — 등록하면 저절로 사라진다 |
| 「등록하고 65건 연결」 | 등록만 하고 연결을 미루면 그 사이 통계가 틀린다.
한 번에 한다 (200건씩 나눠서) |
| 문의 · 가입 건수 | 누르면 그 코드로 들어온 건만 목록에 남는다.
숫자만 보고 「그게 누구인가」 를 알 수 없으면 그 숫자는 판단에 쓰이지 않는다 |
드릴다운이 1건만 나왔다 (v0.722)
원인이 둘 겹쳐 있었다 —
① 유입 뷰는 들어올 때 단계를 「유입」 으로 맞춘다. 33건 대부분은 이미 영업 · 온보딩으로 올라가 걸러졌다
② 같은 사업자의 문의를 한 줄로 묶고 있었다
「33」 이라고 세어 놓고 다른 기준으로 목록을 만들면 어긋난다.
검색 중과 같은 규칙(v0.593) 으로 단계를 무시하고, 좁혔을 때는 묶지 않는다.
푼 뒤에는 그 화면의 기본 단계로 되돌린다 (v0.723) — 「전체 보기」 는 「이 메뉴의 전체」 다.
최종 URL 은 저장하지 않는다
40행 모두 …?PATH_CD={코드} 다. 손으로 적게 두면 코드를 고쳤을 때 URL 이 옛 값으로 남고,
그 링크로 들어온 문의는 분류되지 않는다. 코드에서 만들고 읽기 전용으로 둔다.
사용 상태는 두 갈래다 — 사용중 · 미사용. 「목록에서 감추는 코드」 는 두지 않는다 (kevin 확정) —
감추면 그 유입을 아무도 세지 않게 된다. 이미 나간 링크로 문의는 계속 들어온다.
설정을 앱 안으로 · 개선의견만 새 창 (v0.707 · 0.711)
설정 메뉴가 인자 없이 불려 새 창으로 뜨고 있었다
if (tab && tab !== 'feedback') — tab 이 undefined 라 조건이 거짓이 되어
새 창 경로로 빠졌고, 거기서 개선의견 탭이 열렸다. 기본값이 없는 인자는 언젠가 빈 채로 불린다.
| 화면 | 처리 |
| 설정 | 콘텐츠 영역에 붙는다. 헤더 · 좌측 메뉴가 남아 어느 화면에 있었는지 안다.
둥근 모서리 · 그림자를 걷었다 — 띄우는 창처럼 보이면 바깥을 눌러 닫으려 한다 |
| 개선의견 | 탭 없이 새 창. 쓰면서 적는 곳이라 본 화면과 나란히 둔다.
탭줄을 보여주면 다른 탭을 눌러 설정이 또 열린다 |
| 유입코드 탭 | 맨 앞으로 (v0.717) — 첫 탭이 곧 기본 탭이다 |
주소줄에 토큰이 남아 있었다 (v0.726)
refresh_token 이 화면에 그대로 보였다
https://crm.semo.im/#access_token=…&refresh_token=…
해시는 서버로 가지 않지만 화면 · 히스토리 · 캡처에는 남는다.
access_token 은 한 시간이면 만료되는데 refresh_token 은 그것만으로 새 토큰을 계속 받아낼 수 있다.
supabase-js 가 보통 스스로 지우지만 실패하면 그대로 남는다 —
세션을 읽은 뒤 직접 지운다. 읽기 전에 지우면 로그인이 풀리므로 순서가 중요하다.
그 밖
| 항목 | 내용 |
| 캘린더 회사 필터 | v0.700 — 빠져 있어 남의 회사 일정이 보였다.
활동에는 회사가 없어 그 문의의 회사를 본다 |
| 사라진 두 함수 | v0.712 — v0.702 에서 모듈을 통째로 갈 때
_stInflowEmps · _stInflowCell 을 옮기지 않아
사용자 탭과 상세 패널이 열리지 않았다.
호출부가 템플릿 문자열 안이라 checkbp 가 못 잡는다 —
구간을 갈 때는 그 안의 선언을 목록으로 뽑아 대조한다 |
| 단계 자동변경 | v0.725 — 대단계를 늘 「영업」 으로 고정하고 있었다.
온보딩 고객에게 온라인상담을 남기면 영업으로 되돌아갔다.
가입 이후(온보딩 · 활성화 · 정지 · 해지) 는 손대지 않는다 — 그 단계의 통화는 운영이다 |
| 캘린더 단계 표시 | v0.699 — 「어디까지 왔나」 와 「무엇을 하나」 를 나란히 읽는다 |
v0.727 → v0.758 (2026-08-20)
온보딩. 가입한 뒤 셋업이 어디까지 갔는지 담을 자리를 만들었다.
세부단계가 「대기」 하나뿐이었다
48건이 한 덩어리였다
온보딩 대단계는 있었지만 세부단계가 ['대기'] 뿐이라
셋업 예약인지 첫발송을 기다리는지 CRM 은 몰랐다.
운영은 세모리포트 화면에서 따로 보고 있었다.
대기 › 셋업예약 › 셋업 사전준비 › 셋업 진행 → 활성화|정상
모니터링 3단계(수임처 · 첫발송 · 정기발송) 는 넣었다가 뺐다 (v0.746) —
실제로 머무는 건이 없었다. CHECK 제약에는 그 값을 남겨 둔다 —
되살릴 때 제약을 다시 고치지 않아도 되고, 옛 데이터가 있어도 막히지 않는다.
CHECK 제약이 없다고 알고 시작했는데 있었다
crm_inq_step_detail_chk 가 13개 값만 허용하고 있었다.
그대로 뒀으면 새 단계가 조용히 저장 실패했을 것이다 — v0.638 「컨설팅」 사고와 같은 경로다.
355V 의 마지막 항목이 그것을 잡았다. 검증 SQL 에 「제약 확인」 을 넣어 둔 것이 값을 했다.
저장이 실제로 되는지도 begin … rollback 으로 눈으로 확인했다 (357V).
「활성화 완료」 는 온보딩 단계가 아니다
같은 사실을 두 곳에 적지 않는다
CRM 에는 이미 활성화|정상 이 있고 원장이 그것을 정한다.
온보딩 세부단계에 「활성화 완료」 를 또 두면 한쪽만 바뀌었을 때 어느 것이 맞는지 알 수 없다.
단계 줄에는 8칸이 보이지만 저장되는 값은 4개다.
「활성화 완료」 로 옮기면 대단계가 활성화가 되고 onboarding_done_at 이 찍힌다 —
그 값이 있는 건만 그 탭에 뜬다. 활성화 387건을 다 보여주면 온보딩이 전체 고객 목록이 된다.
목록은 사업자당 한 줄 · 데이터는 문의마다
올해 가입한 47건을 온보딩으로 되돌렸다 (SQL 359). 사업자 39곳에 문의 47건 —
한 사무소에 문의가 여럿이다.
| 방법 | 판단 |
| A · 사업자당 한 건만 옮긴다 | 「어느 문의를 옮겼나」 가 데이터에 박힌다.
새 문의가 들어오면 옛 문의는 온보딩, 새 문의는 활성화로 갈린다 |
| B · 전부 옮기고 화면에서 묶는다 | 사실이 일관되고 목록은 한 줄이다.
이쪽으로 했다 (kevin 확정) |
묶을 때 단계가 가장 앞선 문의를 남긴다 — 손으로 「셋업 진행」 까지 옮겨 둔 건이 있는데
최신이라는 이유로 「대기」 를 보여주면 그 작업이 사라진 것처럼 보인다.
단계 줄의 건수도 같은 기준으로 센다 — 숫자와 목록이 다르면 어느 것이 맞는지 알 수 없다.
활성화 지표 — 월별 스냅샷
| 항목 | 내용 |
| 표 | crm_activation · (ym, biz_no) 유일 (SQL 356).
같은 달을 다시 올리면 그 달만 덮어쓴다 |
| 기준월 | 파일명 앞의 일자 — 20260820_… → 2026-08.
파일 안에 기준월이 없어 그것이 유일한 근거다. 반영 전에 확인받는다 |
| 매칭 키 | 사업자번호. 이름은 쓰지 않는다 —
첫 파일에는 번호가 없었고 사무소명이 겹치는 것이 9쌍이었다.
「더리치 세무회계 김민아 / 김훈」 처럼 대표자가 다른 별개 사무소다.
이름으로 이으면 수임처 17 인 곳에 359 가 붙는다 |
| 사용성 지수 | 파일 값을 그대로 담는다. 계산에 쓰이는 값 일부가 파일에 없어
다시 세면 원본과 다른 값이 나온다 |
활성화현황 패널 — 두 표의 줄을 맞추는 일
왼쪽에서 사무소를 보며 오른쪽에서 그 지표를 읽는다. 줄이 어긋나면 남의 숫자를 읽게 된다.
| 버전 | 시도 | 결과 |
| v0.749 | 스크롤만 동기화 | 행 높이가 달라 아래로 갈수록 벌어졌다 |
| v0.751 | 행 높이를 재서 맞추고, 머리 차이는 음수 여백으로 | 머리가 잘렸다 |
| v0.753 | 왼쪽 머리를 오른쪽만큼 키운다 | 빈 자리가 생길 뿐 잘리지 않는다. 몇 px 이 남았다 |
| v0.755 | 패널 제목 · 월 버튼을 서브메뉴 줄로 올린다 | 제목줄 37px 이 밀던 것이 사라졌다 |
| v0.756 | 남은 차이를 실측해서 민다 | 맞았다 |
| v0.758 | 표 전체가 아니라 본문만 민다 | 위쪽 흰 여백이 사라졌다 |
계산으로 맞추려 들지 않는다
테두리 · sticky · 반올림이 표마다 조금씩 다르다. 머리 높이를 맞춰도 몇 px 이 남는다.
getBoundingClientRect() 로 그려진 결과를 재서 그만큼 민다.
스크롤이 멈출 때 · 목록이 다시 그려질 때(MutationObserver) · 창 크기가 바뀔 때 다시 잰다.
스크롤은 양쪽에서 묶는다. 둘 다 서로를 밀면 무한히 되튀므로
「지금 누가 굴렸는가」 를 표시하고 반대쪽만 따라가게 한다 (v0.757).
목록에서 겪은 것
| 증상 | 원인 |
| 목록이 갑자기 7건만 | v0.732 — 빈 목록의 「데이터가 없습니다」 행(padding:40px) 을
데이터 행으로 알고 높이를 쟀다. _rowH 가 100px 으로 굳어 자동 행수가 무너졌다.
0건 단계를 한 번만 열어도 그 뒤 모든 목록이 줄었다 |
| 목록이 통째로 안 그려짐 | v0.738 — _lcCols 가 아직 null 인데 읽었다.
설정을 못 읽었어도 기본 컬럼으로는 그릴 수 있다 |
| 컬럼 설명이 안 뜸 | v0.745 — 머리를 만드는 곳이 두 군데였고 한쪽만 고쳤다.
같은 일을 하는 코드가 둘이면 한쪽만 고치게 된다 |
| 핸드폰이 비어 보임 | v0.740 — 목록은 문의의 번호를, 상세는 담당자 표의 번호를 읽는다.
문의에 없으면 담당자 표의 ★ 번호를 쓴다. 사업자 단위로 한 번에 읽는다 |
| 사무소 409 오류 | v0.742 — 지운 사무소도 유니크 인덱스에 걸린다.
중복은 오류가 아니라 「이미 있다」 는 사실이다 — 그 사무소를 찾아 쓰고, 지워져 있었으면 되살린다 |
| 관리팀이 지워질 뻔 | v0.741 — 새 고객 Data 파일에 그 헤더가 없었다.
그대로 올리면 upsert 가 902건을 null 로 덮는다. 헤더가 있을 때만 넣는다 |
이 구간에서 배운 것
· 세어 놓은 숫자와 목록은 같은 기준이라야 한다 — 「33」 을 눌렀는데 1건이 나오면 둘 중 하나가 틀린 것이다
· 화면 정렬은 계산이 아니라 실측이다 — 재서 미는 편이 짧고 정확하다
· 같은 일을 하는 코드가 둘이면 한쪽만 고치게 된다 — 머리 생성 · 사무소 생성 모두 그랬다
· 검증 SQL 에 「제약 확인」 을 넣어 두면 조용한 실패를 미리 막는다
· 기본값 없는 인자는 언젠가 빈 채로 불린다 — 설정이 새 창으로 뜬 것이 그것이다
v0.678 → v0.694 (2026-08-19)
알림 · 사무소 마스터 · 태블릿. 이날 가장 큰 소득은 사무소가 왜 없는가를 찾은 것이다.
사무소 마스터 — 앱이 만든 적이 없었다
3개월간 드러나지 않은 구멍
지도 고객층은 crm_offices 가 기준인데(v0.430), 이 테이블은
v0.428 의 일괄 SQL 로 한 번 만든 뒤 앱이 INSERT 한 적이 없다 — select · update 뿐이었다.
그래서 그 뒤에 만든 문의는 사무소가 없어 목록에는 있는데 지도에서 통째로 빠졌다.
「영업 5건인데 지도는 0건」 이 그 증상이다.
담당자 · 상담 · 메모의 키(biz_no) 도 걸리지 않아 아무것도 남길 수 없었다.
| 경로 | 조치 |
| 잠재 → 영업 배정 | v0.687 — _ensureOffice() 하나를 지나게 했다.
중복 방지 · 실패 처리 · 좌표 · 홈피 · 업종 이관이 한 곳에 모인다 |
| 잠재에서 활동 등록 |
| +등록 모달 |
| 방문계획 → 캘린더 등록 |
| 고객 Data 반영 | v0.692 — 원장에만 있는 고객의 사무소를 만든다.
좌표는 원장에 없으므로 「좌표 보완」 을 돌려야 자리를 잡는다 (결과 카드에 적는다) |
| 기존 데이터 | SQL 342 (사무소 8건) · 343 (office_id 26건) · 344 (원장분 0건) |
실패해도 흐름을 멈추지 않는다
문의는 이미 만들어졌다. 여기서 throw 하면 「등록 실패」 로 보이지만 실제로는 등록된 상태가 된다.
콘솔에 [사무소] 생성 실패 로 남기고 넘어간다.
마지막 안전망은 DB 다 — crm_offices_biz_uk 부분 유니크 인덱스.
is_deleted = false 조건을 넣어야 지운 사무소를 되살릴 수 있다.
사업자번호 없이도 쓴다 — 키 폴백
사업자번호 있음 → 123-45-67891
사업자번호 없음 → off:<사무소 uuid>
둘 다 없음 → inq:<문의 uuid> (화면 판정용)
담당자 · 상담 · 메모는 biz_no 를 키로 쓴다. 그런데 지도에서 수집한 잠재는
사업자번호를 모른다 — 방문해서 명함을 받아야 아는 값이라 접촉 단계에서 요구할 수 없다.
그동안 그런 곳에서는 아무 기록도 남길 수 없었다.
| 항목 | 내용 |
| 폴백 | _scopeKey() — 사업자번호가 없으면 off:<사무소id>.
「-」 도 숫자도 아니라 진짜 번호와 헷갈리지 않는다. 스키마 변경이 없다 |
| 승격 | 나중에 번호를 넣으면 세 테이블의 키를 그 번호로 옮긴다 (_cuPromoteKey).
안 옮기면 그때까지 쌓은 기록이 끊긴다 |
| 입력 자리 | 고객 탭 사업장 줄에 사업자번호 칸을 만들었다 — 알아 온 값을 넣을 곳이 없었다.
비어 있으면 밑줄이 붉다 (가입 자동확인에 필요) |
「사업자가 바뀌었나」 를 사업자번호로 판정하고 있었다
bizChanged = (_idpBiz !== inq.biz_no) || !_idpGroup.length
사업자번호 없는 고객끼리 옮겨 다니면 null !== null 이 거짓이고 그룹도 비어 있지 않아
「바뀌지 않았다」 로 판정되어 활동 · 타임라인을 다시 읽지 않았다 —
다른 고객을 눌렀는데 앞 고객의 활동이력이 그대로 남았다.
v0.693 에서 _scopeKey() 로 바꿨다. 키가 하나면 이런 어긋남이 생기지 않는다.
지도 — 두 레이어를 겹치지 않게
| 버전 | 내용 |
| v0.681 | 문의도 원장도 없는 사무소를 지도에서 가린다 — 눌러도 열리지 않는 점이라 잡음이다.
화면에서 가릴 뿐 지우지 않는다 (실제 정리는 SQL 339) |
| v0.686 | 사무소가 아직 없는 문의를 보완 레이어로 그린다 |
| v0.688 | 전환된 잠재를 마커에서 뺀다 — v0.686 이후 같은 곳이 두 번 찍혔다 |
| v0.690 | 중복 판정에 사무소 id 를 더한다. 사업자번호로만 막으면
지도 수집 잠재가 두 번 그려진다 (영업 5 → 10) |
알림 — 저장하지 않고 계산한다
데모 배열 4건(Alex · Sam · (주)나라기업) 이 하드코딩돼 있었다. 데이터만 비어 있는 상태였다.
| 유형 | 판정 |
| 지난 예약 미처리 | scheduled_at < 오늘 + done_at is null |
| 오늘 예약 | 오늘 + 미완료 |
| 가입 승인 대기 | approved != true |
| 담당자 미배정 | 유입 · 영업 + assignee 빈 값 |
| 판정보류 | hold_reason 있음 |
왜 테이블을 두지 않았나
조건이 풀리면 알림도 사라진다 — 승인하면 그 줄이 없어진다.
저장형은 트리거를 빠뜨리면 알림이 영영 생기지 않고, 처리해도 남는다.
id 는 늘 같은 값이라야 한다 (ap:<uuid>) — 읽음을 id 로 기억하므로
시각이나 순번을 섞으면 다시 그릴 때마다 읽음이 풀린다.
| 색 | 뜻 |
| 붉은 띠 | 내가 맡은 것 |
| 초록 띠 | 다른 담당자거나 주인이 없는 것 |
왼쪽 세로 띠로 준다 — 아이콘 색은 이미 「알림 종류」 를 뜻해서 자리가 겹치면 둘 다 안 읽힌다.
탭은 미확인 · 내 알림 · 전체 셋. 「좌표 없음」 은 뺐다 (v0.683) —
「지금 처리할 일」 이 아니라 「언젠가 채워야 할 데이터」 다.
좌표 보완 — 주소가 있는 것만 센다
수가 줄지 않아 고장으로 보였다
주소가 없으면 어떤 방법으로도 좌표를 구할 수 없다. 그런 건을 숫자에 넣으면
「좌표 보완」 을 아무리 눌러도 수가 그대로다.
v0.684 에서 7곳의 조건을 주소 있음 + 좌표 없음 으로 통일했다.
이제 그 수만큼은 실제로 줄어든다.
태블릿 · 모바일 — 조작하는 부분만 키운다
| 기기 | 처리 |
| 태블릿 | 콘텐츠 영역만 핀치로 확대 · 축소 (60~160%).
헤더 · 좌측 메뉴는 contentWrap 바깥이라 그대로 남는다 |
| 폰 | 헤더 · 좌측 메뉴를 2배로 (body.small-ui).
viewport 가 width=1600 이라 폰에서는 전체가 40% 로 줄어 12px 글자가 5px 가 된다 |
지도는 배율에서 뺀다
네이버 지도는 컨테이너를 픽셀로 재어 마커 위치를 계산한다. CSS zoom 이 걸리면
그 값이 어긋나 라벨이 엉뚱한 자리에 뭉친다. 한 번 겪고 나서
#contentWrap 전체가 아니라 #kanbanArea · #listArea · #calDrawer · .idp 에만 준다.
transform 이 아니라 zoom 이다 — transform 은 겉모습만 늘려 스크롤 · 클릭 좌표가 어긋난다.
미디어 쿼리로는 판정할 수 없다 — width=1600 이라 CSS 폭이 늘 1600px 이다.
실제 기기 폭(screen.width) 을 JS 가 본다.
그 밖
| 항목 | 내용 |
| 지도 목록 접기 | v0.679 — 손잡이는 접혀도 남는다. display:none 이 아니라 밀어 둔다 —
감추면 스크롤 위치와 페이지 번호가 초기화된다. 손가락 기기는 접어서 연다 |
| 상담 회차 충돌 | v0.682 — _csBlank 가 그때의 캐시로 번호를 정해
목록을 읽기 전에 누르면 crm_consults_round_uk 에 막혔다.
저장 직전에 DB 에서 다시 세고, 23505 면 한 번 더 민다 |
| 새 상담 버튼 | v0.682 상자 밖으로 옮기며 안쪽을 지우지 않아 둘이 됐다.
v0.694 에서 하나로. 첫 회차면 「상담 시작」, 있으면 「새 상담」 |
| 위험 지표 라벨 | v0.677 — 항목에서 「위험」 을 뗐다. 패널 제목이 이미 「위험 있음」 이라
발송수 위 험 처럼 줄이 갈라졌다. 세 글자로 맞추면 숫자가 같은 자리에 선다 |
| 폼 정리 | v0.691 — 고객을 바꾸면 열린 폼을 닫는다.
그대로 두면 저장 시 앞 고객의 활동이 고쳐진다 |
이날 배운 것
· 화면 증상은 한 단계 안쪽에 원인이 있다 — 「활동이력이 고정」 은 폼 문제가 아니라 판정 조건 문제였다
· 키가 둘이면 언젠가 어긋난다 — 사업자번호 · 사무소 id 를 _scopeKey() 하나로 모았다
· 선언만 지우고 사용처를 남기면 실행 시 터진다 — checkbp 로도 안 잡힌다
· 실패를 조용히 넘기면 원인을 영영 모른다 — 502 대신 {ok:false, error}, 빈 배열 대신 콘솔 경고
v0.621 → v0.677 (2026-08-19)
상담 · 예약 완료 · 캘린더 · 방문계획 네 갈래. 예약 일정 관리가 축이 됐다.
상담 — 고객 탭에 회차로 쌓는다 (SQL 334)
고객 탭 : 원장 → 사업장 → 담당자 → 상담 → 메모
| 항목 | 내용 |
| 테이블 | crm_consults — (biz_no, round_no) UNIQUE. 사업자 단위로 회차를 쌓는다 |
| 담는 값 | 거래처 수 · 직원 수 · 개업일 · 신청 이유(다중) · 좋은 점 · 불편한 점 · 개선 희망 · 사용 의향 · 기초셋업지원 · 방문자 의견 |
| 조건부 입력 | 신청 이유에 기타 를 켤 때만 주관식이 열린다. 사용 의향이 없음 일 때만 이유 칩이 열린다. 끄면 저장 시 값도 비운다 — 남겨두면 「기타 아님인데 기타 사유가 있는」 행이 된다 |
| 기본 접힘 | 펼치면 메모가 화면 밖으로 밀린다. 접힌 줄에 거래처 113 · 직원 4 · 7년차 · 사용의향 있음 요약을 남긴다 |
덮어쓰지 않고 회차로 쌓는 이유
만족도는 시점의 값이다. 「1년 전 사용의향 있음 → 오늘 없음」 의 차이가 곧 해지 신호인데
한 행에 덮어쓰면 그 신호가 사라진다. 메모 v0.385 와 같은 판단이다.
고객의 말과 담당자의 판단을 섞지 않는다
좋은 점 · 불편한 점은 고객이 한 말이고, 방문자 의견은 담당자의 판단이다 —
「말은 만족한다는데 실제로는 안 쓰는 것 같다」 같은 관찰이 거기 들어간다.
안내문은 명사가 아니라 질문 문장으로 쓴다 (세모 사용시 좋은 점은 무엇인가요?) —
채우는 사람은 세무사가 아니라 방문한 담당자다. 라벨이 명사면 담당자마다 다르게 묻는다.
예약 — 완료 처리와 단계 규칙 (SQL 335)
| 변경 | 내용 |
| 완료 컬럼 | done_at · done_by 신설. scheduled_at 만으로는 「지났다」 와 「마쳤다」 를 구분할 수 없다 |
| 완료 = 결과 선택 | 완료 버튼을 누르면 접촉 · 가입완료 · 실패 중 하나를 고른다. 완료만 표시하고 단계를 두면 방문예약 목록이 계속 불어난다 |
| 단계는 내려오지 않는다 | 예약 상태에서 통화 · 방문을 남겨도 예약으로 유지된다. 예약일이 지나도 마찬가지다 — 예약 당일·이후의 통화 한 통이 가장 흔한 기록인데 그때마다 접촉으로 밀려 예약이 사라졌다 |
| 끝난 건도 되살린다 | 가입완료 · 실패 · 문의종료여도 예약을 잡으면 방문예약으로 올라간다. 종료된 고객도 방문한다 — 해지 재유치 · 정기 방문 · 실패 재접촉. 예약이 아닌 활동은 종전대로 건드리지 않는다 |
| 예약 칩 | 미완료만 센다. 목록 필터도 같은 기준이라 숫자와 목록이 어긋나지 않는다 |
대단계 이름을 한 곳만 고치지 않아 3개월을 놓쳤다
v0.428 에서 컨설팅 → 영업 으로 바꿨는데 _applyActStage() 한 줄에 옛 이름이 남아
step_group='컨설팅' 을 저장하고 있었다. 그 값은 CHECK 에 막혀 쓰기가 조용히 실패했고,
결과적으로 방문예약을 등록해도 단계가 한 번도 바뀌지 않았다.
화면에는 「컨설팅 / 방문예약」 으로 보였는데 DB 에는 그 값이 0건이었다 — 메모리 값과 저장 값이 다르면 저장에 실패한 것이다.
방문 · 촬영에도 일시와 방문자
| 유형 | 담는 자리 | 기본값 |
| 방문예약 · 온라인상담 | scheduled_at | 날짜 비움 · 시간 필수 |
| 방문 · 촬영 | activity_date | 오늘 · 시간은 비울 수 있다 |
「언제 갈 것인가」 와 「언제 갔는가」 는 담는 자리가 다르다 — 방문을 scheduled_at 에 넣으면 예약 칩과 D-day 계산에 섞인다.
시각이 00:00 이면 「적지 않았다」 로 보고 화면에서 감춘다 (활동이력 _actHM 과 같은 규칙).
방문자는 문의 담당자를 따른다 — 예약 · 방문 · 촬영은 「누가 가는가」 이고, 통화 · 문자는 「누가 했는가」 라 등록자를 쓴다.
캘린더 — 헤더 버튼 + 콘텐츠 영역 전체
[방문 3] [● 09.02 10:30 제니 테스트 D-14 미처리 1] 🔍 🔔 +등록
| 항목 | 내용 |
| 헤더 버튼 | 누르지 않아도 다음 일정이 보인다. 1시간 이내면 빨강 · 오늘 없으면 다음 일정 · 미처리 N 병기 |
| 대상 | 예약 · 방문 · 촬영 (kevin 확정). 통화 · 문자는 뺀다 — 달력은 「어디에 가는가」 를 보는 곳이다 |
| 담당자 | 헤더 필터를 따른다. 고르면 그 사람, 비면 전 담당자. 캘린더에만 따로 두면 헤더와 어긋난다 |
| 기본 화면 | 날짜를 고르지 않은 전체 요약 — 미처리 → 오늘 → 예정 → 지난 기록. 오늘 일정이 없으면 빈 화면만 나오던 것을 고쳤다 |
| 배치 | 목록이 좌 · 달력이 우. 먼저 읽는 것은 「오늘 어디를 가는가」 이고 달력은 그 날을 고르는 도구다 |
탭이 되돌아가던 원인
좌측 목록의 다른 고객을 누르면 ① 바깥 클릭으로 패널이 먼저 닫히고 ② onclick 이 다시 연다.
닫을 때 _idpTab='inq' 로 되돌리고 있어 「열려 있으면 유지」 규칙이 한 번도 걸리지 않았다.
닫을 때 탭을 건드리지 않는 것으로 해결했다.
방문계획 — 담고 · 정렬하고 · 예약으로 만든다
| 항목 | 내용 |
| 저장 | localStorage. 예약은 상대와 약속한 사실이고 계획은 내 머릿속 순서다. 키만 담는다 (c:off:123) — 이름 · 좌표를 함께 저장하면 원본이 바뀌어도 옛 값이 남는다 |
| 동선 정렬 | 기준점에서 가장 가까운 곳 → 거기서 또 가장 가까운 곳. 기준점과의 거리로 줄세우면 동선이 되지 않는다 — 2 · 3 · 2.5km 이면 2 → 3 → 2.5 가 짧을 수 있다 |
| 기준점 | 내 위치 · 지도 중심 · 주소로 지정 셋. GPS 는 실내에서 수백 미터씩 어긋나 순서가 통째로 뒤바뀐다. 지정한 값은 저장한다 |
| 캘린더 등록 | 담은 곳을 한 번에 방문예약으로 만든다. 시작 · 간격으로 채우고 곳마다 고칠 수 있다. 그날 기존 일정을 시각순으로 섞어 보여주고 겹치면 알린다 (막지는 않는다) |
| 문의 생성 | 잠재 · 문의 없는 사무소는 문의를 새로 만든다 — 활동은 문의에 붙는 구조다. 잠재는 전환 처리한다. 되돌릴 수 없어 무엇이 몇 건 생기는지 미리 알린다 |
실제 경로는 그릴 수 없다
네이버 Directions API 는 별도 신청 · 유료다. 직선 연결 + 순서 번호로 간다 —
「어느 쪽부터 도는가」 는 이것으로 판단된다. 거리도 직선거리 합계라 약 N km 로 적는다.
주소 검색 — 좌표를 함께 담는다
① 주소로 먼저 (naver geocode) → ② 0건이면 상호 · 건물명 (Edge Function) → ③ 좌표 확보
| 항목 | 내용 |
| 왜 프록시인가 | openapi.naver.com · dapi.kakao.com 은 브라우저에 CORS 를 열지 않고, Secret 을 HTML 에 넣으면 노출된다. Supabase Edge Function 뒤에 둔다 |
| 공급자 | 카카오 로컬을 쓴다 — 좌표를 WGS84 로 그대로 준다 (네이버 지역검색의 mapx/mapy 는 좌표계가 달라 재지오코딩이 필요하다). 한도도 4배다 |
| 슬러그 | clever-worker. Supabase 는 만들 때 정한 슬러그를 바꿔 주지 않는다 — Settings 의 Name 을 고쳐도 URL 은 그대로다. 코드에는 FN_PLACE_SEARCH 상수로 둔다 |
| 좌표 저장 | 「찾기」 로 고른 값만 좌표가 함께 바뀐다. 손으로 친 주소는 좌표를 건드리지 않고 좌표 없음 배지로 드러낸다. 저장은 crm_inquiries 와 crm_offices 양쪽에 — 지도는 사무소를 본다 |
실패해도 200 으로 돌려준다
502 로 내보내면 supabase-js 가 본문을 감춰 「non-2xx status code」 만 남는다 —
키가 없는 것인지 한도가 넘은 것인지 화면에서 구분할 수 없다.
{ ok:false, error } 를 담아 이유를 그대로 보여준다.
원장 삭제 — 사무소도 함께 정리 (SQL 337)
지도 고객층은 crm_offices 가 기준이라(v0.430) 원장만 지우면 「활성화」 상태 그대로 지도에 남았다.
| 조건 | 처리 |
| 원장이 더 남아 있음 | 사무소 그대로 — 아직 고객이다 |
| 마지막 원장 + 문의 있음 | 단계를 최신 문의 기준으로 되돌린다 |
| 마지막 원장 + 문의 없음 | is_deleted — 지도에서 사라진다 |
태블릿 — 콘텐츠만 확대 · 축소
지도는 배율에서 뺀다
네이버 지도는 컨테이너를 픽셀로 재어 마커 위치를 계산한다. CSS zoom 이 걸리면
그 값이 어긋나 라벨이 엉뚱한 자리에 뭉친다. 그래서 #contentWrap 전체가 아니라
#kanbanArea · #listArea · #calDrawer · .idp 에만 배율을 준다.
transform 이 아니라 zoom 을 쓴다 — transform 은 겉모습만 늘려 스크롤 · 클릭 좌표가 어긋난다.
html,body{touch-action:pan-x pan-y} 로 브라우저 전체 확대를 막아 헤더 · 좌측 메뉴가 밀려 나가지 않게 한다.
죽은 코드 정리 — 1,046줄 · 51KB
| 덩어리 | 내용 |
| 기획노트 · 할일 | 화면 진입점이 없는데 함수만 남아 있었다 (933줄) |
| 지도 주소검색 · 직접등록 | 헤더 검색과 +등록 모달이 대신한다 (108줄) |
| 잔재 | verTxt · duBar-* · toggleMapPanel · mapGeocodeBtn |
선언만 지우고 사용처를 남기면 실행 시 터진다
const btn = getElementById('mapGeocodeBtn') 만 지웠더니
if (btn) { btn.disabled = ... } 4곳이 남아 ReferenceError 가 될 뻔했다.
checkbp 의 「함수 참조」 로도 안 잡히는 종류다 — id 하나를 지우면 그 이름이 쓰인 모든 줄을 훑는다.
남은 정리 대상
· 사업자번호 없는 문의 — 담당자 · 상담 · 메모를 남길 수 없다 (키가 biz_no 다)
· v0.646 이전 방문 활동의 activity_date — 등록 시각이 UTC 로 들어가 엉뚱한 시각으로 보인다
· 원장 없는 사무소 (SQL 337 ①) — 유입 · 영업 · 실패는 정상, 온보딩 이후 단계면 유령이다
v0.563 → v0.620 (2026-08-16)
단계 용어 변경 — 리드 · 컨설팅 → 유입 · 영업
DB 값과 코드를 함께 바꿨다. 화면 표시만 바꾸면 SQL 로 데이터를 볼 때 다른 말이 나온다.
| 대상 | 내용 |
| DB 값 (SQL 317~318) | crm_offices.step · crm_inquiries.step_group · crm_prospects.crm_stage · crm_stage_history 네 곳. 「문의」 4건도 유입으로 합쳤다 — v0.428 에서 남은 것이다 |
| 활동 (SQL 321) | crm_inquiry_activities.stage_group 65건. 317 에서 이 테이블을 빠뜨려 23514 로 막혔다 — 조사 범위를 좁게 잡은 것이 원인이다 |
| 코드 | 문자열 77곳 · 상수 4개(CRM_COLS · STEP_TO_MAP · P_STAGE_BY_LABEL · INQUIRY_VIEW_NAME) · 메뉴 · 화면 문구 8곳 |
| 내부 id | 그대로 둔다 — target · consulting. CSS 클래스와 뷰 키가 엮여 있어 바꾸면 범위가 커진다 |
CHECK 확장 → 값 교체 → CHECK 축소 세 단계로 나눈다
한 번에 바꾸면 어느 쪽도 통과하지 못하는 순간이 생긴다.
317 로 옛 값과 새 값을 모두 허용하고, 318 로 교체하고, 코드 배포 후에 옛 값을 뺀다.
상세 패널 — 3탭 + 우측 활동이력 고정
[헤더 — 공통] 사무소명 · 문의N건 · 연락처 추가 · 현재 단계
[고객 · 유입 · 이용현황] | [활동이력 272px]
| 변경 | 내용 |
| 활동이력 위치 | 탭 → 우측 고정. 어느 탭에서 보든 함께 필요하다 — 탭이면 「고객을 보다가 활동을 보려면 화면을 떠나야」 한다 |
| 활동 귀속 (SQL 320) | inquiry_id → biz_no. 사업자 단위로 모으고, 앱이 빠뜨려도 트리거가 부모 문의에서 채운다 |
| 정렬 | activity_date → created_at. 예약은 미래 날짜라 목록 맨 위에 몰렸다 |
| 이용현황 탭 | 현황 · 보고서 · 부가세 · 카카오 · 셋업을 고객 탭에서 갈랐다. 「누구인가」 와 「어떻게 쓰고 있나」 는 다른 질문이다 |
| 담당자 표 | 메모 열 제거 · 핸드폰에 복사 버튼. 폭 계산에 버튼 자리를 넣지 않아 번호 뒷자리가 잘렸다(v0.595) |
| 폭 | 840 → 1040 → 940(유입 268 + 상세 400 + 활동 272). 활동이 빠지며 상세가 245px 만 남아 주소가 세로로 잘렸다 |
블록만 옮기면 그 블록이 쓰는 선언이 남는다
이용현황을 가를 때 HTML 만 옮기고 hp · svc · lp 를 두고 와
svc is not defined 로 탭이 아예 안 열렸다.
checkbp.py 의 JS 문법 검사는 이것을 못 잡는다 —
템플릿 문자열 안의 식별자는 실행해야 드러난다.
잠재 — 고객과 같은 상세로
요약 팝업을 없앴다
팝업 안 좁은 칸에 이름 · 주소만 보여주던 화면이라 「여기 전화했다」 를 남길 곳이 없었다.
이제 고객 · 유입과 같은 3탭 상세로 열리고 우측에서 활동을 등록할 수 있다.
| 항목 | 내용 |
| 활동 → 문의 자동 생성 | 잠재에서 활동을 남기면 그 순간 문의가 만들어진다. 단계는 활동 유형이 정한다 — ACT_STEP_MAP 그대로다. 통화 · 방문 · 문자 → 영업/접촉 · 방문예약 → 영업/방문예약 · 온라인상담 → 영업/온라인상담 ⚠ 되돌릴 수 없어 확인 창을 둔다 — 전화 한 통이 실적 · 통계에 잡힌다 |
| 진입 경로 3개 | 마커 1개 · 그룹 팝업 · 좌측 목록. 셋이 따로 있어 하나씩 고쳐야 했다 — mapFocusProspect · _mixedGroupPick · showGroupDetail |
| 조회 컬럼 통일 | 잠재를 읽는 세 곳의 컬럼이 달라 대표자 · 출처가 빈칸으로 보였다. 데이터가 없는 게 아니라 조회에 안 넣은 것이다 |
| 다른 탭 | 「아직 잠재고객입니다 · 활동을 남기면 문의가 만들어집니다」. 빈 화면만 두면 고장으로 읽힌다 |
이전 고객의 상태와 DOM 이 남았다
_activeInquiryId 를 비우지 않아 「장래원세무회계사무소」 를 열었는데 유입 탭에 「택스앤톡」 이 나왔다.
상태 일곱 개와 DOM 세 곳을 함께 비워야 한다 —
상태만 지우면 이미 그려진 화면이 남는다.
지도
| 항목 | 내용 |
| 지도 / 전체 | 기본은 지도 — 화면에 보이는 곳만. 전체 1,138건은 44페이지라 지도를 보는 의미가 없다 |
| 고객 / 잠재 토글 | 목록에 무엇을 보일지 밑줄로 표시한다. 상단 칩과 별개다 — 칩은 마커를, 이것은 목록을 결정한다 |
| 잠재 목록 | 고객 아래에 갈라서 붙인다. 전환된 건은 뺀다 — crm_stage 가 있으면 이미 고객이라 활성화 · 온보딩 색이 잠재로 읽혔다 |
| 2단계 닫기 | 빈 지도 1번째 상세만 · 2번째 그룹 팝업도. 한 번에 둘 다 닫으면 「같은 위치 2개 업체」 를 비교할 수 없다 |
| 타채널 | 타채널고객 → 타채널 · 담당자 감춤 · 삼각형 통일(마커 · 칩 · 카드). 셋이 갈리면 「지도의 그 삼각형이 어느 줄인가」 를 매번 짚어야 한다 |
| 회사 전환 | 로움이 회사를 고르면 그 회사 시점이 된다 — 남의 회사 사무소는 타채널로 보인다 |
STEP 화면
유입 · 영업 · 온보딩 │ 신규고객 · 실패 │ 활성화 · 정지 · 해지
| 항목 | 내용 |
| 실패 컬럼 제거 | 385건이 쌓여 스크롤만 길고 카드를 옮길 일도 없었다. 지표 카드로 옮겼다 |
| 신규고객 2단 | 유입고객 위, 가입고객(전환) 아래. 사업자 단위로 센다 — 같은 사업자의 문의가 여럿이면 한 곳으로 봐야 유입 수가 부풀지 않는다 |
| 카드 배지 | 유입 · 영업 D+n(접수일) · 온보딩 D+n(가입일) · 실패 활동 건수. 서비스명 · 담당자는 컬럼 안에서 거의 같아 카드를 구분해 주지 못했다 |
| 온보딩 기준일 | 원장의 가입일. crm_inquiries.joined_at 은 전 단계 0건이다(SQL 324) — 수기 입력용인데 쓰인 적이 없다 |
| 「26년」 → 「올해」 | 이번 달 · 최근 3개월과 같은 상대 표현이라 읽는 기준이 일정하다 |
저장값과 화면 표시가 다르다 — 같은 함정을 두 번 밟았다
STEP_DETAIL_LABEL 이 실패 를 「가입실패」로 표시한다.
화면 문구로 조건을 걸어 실패 카드가 0 이 됐다.
v0.302 에 똑같은 버그가 적혀 있다 — 「KB_LOST_DETAILS 에 접촉실패 · 가입실패만 있어 511건이 어디에도 안 보였다」.
정답이 코드에 있었는데 확인하지 않았다.
검색 · 필터
| 항목 | 내용 |
| 단계 무관 검색 | 어느 메뉴에서 찾든 전 단계에서 찾는다(실패 · 종료 · 정지 · 해지 포함). 세 겹으로 좁히고 있어 하나만 풀면 다른 곳에서 다시 걸렸다 |
| 번호 검색 | 8455300656 로 845-53-00656 을 찾는다. 양쪽에서 구분기호를 지워 비교한다 — 숫자 4자 이상 · 기호만 인 검색어에 한정한다(이름까지 지우면 A-B 와 AB 가 같아진다) |
| 단계 셀렉트 | 「전체」 가 두 번 눌러야 됐다 — _initInquiryFilters 가 VIEW_STAGE 기본값을 다시 넣었다 |
| 들여쓰기 | 공백 문자 → option 의 padding-left. 공백은 닫힌 박스에도 남아 선택된 값이 밀려 보였다 |
| 담당자 목록 | 회사를 고르면 그 회사 직원만. 마스터도 _isLoumCompany() 를 통과해 전 직원이 나왔다 |
| 회사 기본값 | 자기 소속 회사로 시작한다. 마스터도 마찬가지다 — 이전에는 소속과 무관하게 로움으로 열려 시험이 어려웠다 |
그 밖에
| 항목 | 내용 |
| 연락처 추가 | vCard 3.0 을 만들어 공유 시트로 넘긴다. 브라우저가 연락처에 직접 쓸 방법은 없다 — Contact Picker API 는 읽기만 된다. 이름 = 대표자(없으면 사무소명) · 회사 = 사무소명. canShare({files}) 로 따로 확인한다 — navigator.share 가 있어도 파일을 못 받는 브라우저가 있다 |
| 판정보류 해제 (B-5) | 유입 탭에 노란 블록 + 해제 버튼. 원장이 있으면 보류만 지운다 — 재판정하면 활성화 건이 영업/대기 로 강등된다. 실측 55건 중 원장 있음 21 · 없음 34(전부 실패). 어느 쪽이든 「가입 여부 확정 필요」 라는 목적이 이미 이뤄졌다(SQL 333) |
| 온보딩 정리 (SQL 326) | 가입 30일 경과 30건을 활성화 / 정상으로. 가입일이 2023년인 건도 있었다 |
| 테스트 데이터 (SQL 330 · 331) | 테스트 3건 + 사업자번호 없는 유입 15건 삭제. crm_inquiries.office_id FK 때문에 이름으로만 지우면 23503 으로 막힌다 — 사무소 id 를 먼저 잡아야 한다 |
main_phone → office_phone | 대표번호 이름을 통일했다(SQL 327). 이름이 갈려 있어 v0.538 에서 실제 사고가 났다 — 없는 컬럼에 저장하려다 42703 으로 계속 실패 |
SQL 은 실행 단위로 나눈다
NNN-A 조회(대상 확인) · NNN-B 변경 · NNN-V 검증.
한 파일에 여러 단위를 넣으면 뒤가 실패했을 때 앞 결과를 못 보고,
어디까지 실행됐는지도 알 수 없다 — 오늘 그 일이 몇 번 있었다.
확인하지 않고 구조를 짐작한 것이 오늘 여섯 번이다
crm_stage_history.to_stage(컬럼명) ·
crm_inquiries.main_phone(없는 컬럼) ·
ON CONFLICT(유니크 제약 없음) ·
UPDATE ... FROM LATERAL(대상 참조 불가) ·
crm_inquiries.office_id(FK) ·
실패 세부단계(저장값 ≠ 표시).
써본 적 없는 형태와 남의 테이블 구조는 조회로 확인한 뒤 쓴다.