크라우니솔루션 공사관리 — 접수·진행·결과리포트 (고객/스태프/관리자)
개요
solution.crowny.org(:9735)에 공사 접수 → 진행과정 → 결과 리포트 도구를 구축했다. 마스터템플릿 = 크라우니클린(clean.crowny.org:9624)의 견적→예약→시공→비포애프터 리포트 구조. 클린이 "청소 시공"이라면 솔루션은 "B2B 설비 공사"(화재경보·LED·PA·관제 설치)로 도메인 이식.
- 라이브: https://solution.crowny.org/work.html · /work-staff.html · /work-admin.html (전부 200 실측)
- 서비스 재기동:
launchctl kickstart -k gui/$(id -u)/org.crowny.solution
무엇을 했는지
공정 7단계 (SSOT)
접수(received) → 현장실사(survey) → 견적·계약(contract) → 자재준비(prep) → 시공(construction) → 검수(inspection) → 완료(done)역행·건너뛰기는 단계전이가능() 게이트가 400으로 차단. 관리자만 임의 단계 이동 허용.
3역할 도구
| 역할 | 페이지 | 기능 |
|---|---|---|
| 고객 | /work.html | 공사 접수(솔루션 카탈로그 연동) · 공사번호+연락처로 진행조회(7단계 스테퍼·진행률바·4상 상태배지·타임라인·공개사진) · 결과리포트 열람 |
| 스태프 | /work-staff.html | 스태프ID+PIN 로그인 · 배정목록 · 다음단계 보고 · 시공전/중/후 사진 업로드(base64) · 현장로그(진행/특이사항/자재/안전). 모바일 우선(터치타깃 44px) |
| 관리자 | /work-admin.html | 기존 crew 세션 재사용 · 통계카드+단계별 카운트 · 접수함(필터·검색) · 상세(배정·예정일·상태변경) · 사진 공개토글 · 리포트 발행 · 스태프 관리 |
4상 상태판정
- 티=정상 / 옴=임박(예정일 2일 이내) / 타=지연(예정일 경과) / 음=미배정
리포트 발행 게이트 (클린의 "비포애프터 신뢰구조" 이식)
검수통과판정(before사진수, after사진수, 체크통과수, 체크총수)
- 1 = 발행 가능 (before·after 각 1장 이상 + 체크리스트 전항목 통과)
- 0 = 체크리스트 미달 → 400
- -1 = 사진 부족 → 400
관련 파일
/Users/ef/crowny-solution/
├── 공사관리.한선 198줄 SSOT 엔진(단계전이·진행률·4상판정·검수통과·역할권한) + 자체테스트 34
├── lib/공사관리.js 103줄 JS 1:1 미러 (파리티 20케이스 PASS)
├── 공사서버.한선 110줄 서버 핵심로직 한선씨 동반
├── server.js /api/works* 라우트 25종 배선 (기존 라우트 무훼손)
├── data/공사/ append-only PSV (work·workstage·workphoto·worklog·workreport·workstaff·seq)
└── web/
├── work.html/한선 고객 (420줄)
├── work-staff.html/한선 스태프 (384줄)
└── work-admin.html/한선 관리자 (573줄)
저장 방식
클린과 동일한 append-only PSV, id별 last-wins. 상태 변경 이력이 통째로 남아 감사추적이 된다. 사진 실체는data/공사/uploads/<workId>/.검증 실측
임시포트 9739 종단(에이전트) + 라이브 9735 종단(메인):
| 항목 | 결과 |
|---|---|
| 한선씨 자체테스트 | 34/34 PASS |
| JS 미러 파리티 | 20/20 PASS |
| 접수 → lookup | 201 / 200 (진행률 14, 상태=음) |
| 잘못된 연락처 조회 | 404 (대조 게이트 작동) |
| 단계 역행 / 건너뛰기 | 400 ×2 |
| 정상 진행 3단계 | 200 ×3 |
| 사진업로드·배정·공개 | 200 전량 |
| 리포트 체크 미달 | 400 + 사유 |
| 리포트 체크 통과 | 200, stage→done |
| 기존 라우트 회귀 | health/solutions/dashboard 200 |
| 공개 HTTPS 3페이지 | 200 ×3 |
| 골격 게이트 | DOCTYPE·charset·viewport 3/3 |
| 디자인 대조 | crew.html 외 신규 hex 0건 |
수리한 결함
- solutionName 미해석 (라이브 실측 발견) — 접수 시 solutionId만 저장되고 솔루션명이 빈 문자열이라 고객 조회·리포트에 이름이 안 보였다.
workSolutionName()헬퍼로 카탈로그 해석 배선 →Inter-M IPA 디지털화재경보 시스템정상 표시 확인. workDaysUntil헬퍼 누락 ReferenceError (에이전트 자체 발견·수리).lib/공사관리.js동시작성 충돌 — 엔진판을 정본으로 확정하고 서버 호출부를 맞춤(ID 무하이픈WK000001, 역할권한 한글 어휘, 검수판정 -1/0 매핑).
VM 함정 실측 재현
통과 = 통과 + 검증(...) 누적식이 최종 합계를 "1건"으로 손상 — 가드레일 기록된 x = x + 무거운함수() 함정.
처방: 변수 rN = 검증(...); 통과 = 통과 + rN 임시변수 경유.
잔여 이슈
- 크라우니디자인 중앙CSS 미적용(의도) — 솔루션은 premium 다크 팔레트가 확정 아이덴티티라 형제 페이지(crew/partner/rental)와의 일관성을 우선했다. 중앙CSS 적용 시 대비 붕괴 우려. 적용 여부는 사용자 판단 필요.
- 스태프 계정은 시드 3명(WS1 김현수/WS2 이도현/WS3 박서연, PIN 1111/2222/3333) — 실운영 전 교체 필요.
- 사진 업로드 용량 상한·이미지 리사이즈 미배선.
- 고객 조회는 공사번호+연락처 대조만(무인증) — 클린과 동일 수준. 민감 현장정보가 늘면 인증 승격 검토.
- 크라우니AI 앱카드 등록 미실시.
추가 작업 (2026-07-31, 세션2) — P0 사진서빙 수리 + 태그 + 관리소 열람
무엇을 했는지
- P0 결함 수리:
GET /api/works/:workId/files/:filename신설. 경로탈출 차단(path.basename+resolve 검증), 확장자별 Content-Type, 스태프/crew/본인 관리소=전량 열람·그 외=published만.workPhotos()가 이제 각 사진에url필드를 항상 붙임(과거 실사고: p.url 참조하는데 서버가 안 만들어서 사진 전부 깨짐 → 수리 완료, curl 바이트 동일 검증). - 사진 태그: WORKPHOTO_COLS에
tags추가(append-only, 구행은 빈문자열로 안전 파싱). 업로드 API·GET 응답에 반영. work-staff.html에 태그 입력+퀵칩(균열·누수·마감·안전·자재), work-admin.html 사진 그리드에 태그뱃지. - 관리소(발주처) 열람 계정:
data/공사/workowner.psv(id|password|workId|name|createdAt),POST /api/works/owner/login·GET /api/works/owner/me·GET /api/works/owner/photos·GET /api/works/owner/logs신설(WORK_OWNER_SESSIONS, X-Owner-Token). - web/work-owner.html + web/work-owner.한선 신규: 로그인→대시보드(현장명·주소·단계·진행률·4상 상태배지, phase탭+태그필터 사진그리드+라이트박스, 타임라인, 로그). 기존 work.html/work-admin.html 토큰만 재사용(신규 hex 없음, grep 대조 완료).
- work-staff.html 로그인 비밀번호 입력
inputmode="numeric"→"text", 라벨 "PIN"→"비밀번호"(한글 비밀번호 입력 불가 버그 수리). - 실케이스 1건 시딩: WK000001(천안 용곡 우림아파트, SOL-104, stage=construction), 스태프
아파트스태프, 관리소천안용곡우림/12341234, workstage 접수~시공 5행, 샘플사진 2장(before/progress, 태그 포함, 캡션 "샘플 - 실제 현장사진으로 교체 예정").
관련 파일
/Users/ef/crowny-solution/server.js(파일서빙 라우트, tags 컬럼, owner 세션/라우트 4종)/Users/ef/crowny-solution/web/work-owner.html,/Users/ef/crowny-solution/web/work-owner.한선(신규)/Users/ef/crowny-solution/web/work-staff.html(태그 입력+퀵칩, 로그인 인풋모드)/Users/ef/crowny-solution/web/work-admin.html(태그뱃지)/Users/ef/crowny-solution/data/공사/{work,workstage,workstaff,workowner}.psv(시드 WK000001)
검증 (curl 실측)
- 관리소 로그인 성공/실패(401) OK,
/api/works/owner/me진행률71%·상태 "정상" 확인 - 스태프 로그인 OK, 사진 2장 업로드(캡션·태그 포함)
/api/works/owner/photos2장 반환(url·tags 포함), 태그필터?tag=균열1장만- 파일 GET: Content-Type image/png, 68바이트 원본과 byte-identical
- 무인증 파일 GET → 403(비공개 사진 차단)
- 회귀: /api/health /api/solutions /api/dashboard /work.html /work-staff.html /work-admin.html /work-owner.html 전부 200, https://solution.crowny.org/work-owner.html 200
- work-owner.한선 hanseonc_rpn 컴파일 exit=0(기존 work.한선과 동일 경고 프로파일)
잔여 이슈
- 없음. 단, work-owner.한선/work.한선류는 실제 페이지와 완전 1:1은 아니고 "구조 요약본"(레포 기존 컨벤션 그대로 답습).
추가 수리 (2026-07-31, 세션3) — 한글 비밀번호 입력 불가
증상: 스태프 계정 아파트스태프/아파트1234 로 폰에서 로그인하려는데 비밀번호 칸에 한글이 안 쳐짐.
원인: <input type="password">는 모바일 브라우저에서 IME를 ASCII로 강제한다. 앞선 세션에서 inputmode="numeric"→"text" 로 고쳤지만 그것으로는 안 풀리는 브라우저 레벨 제약. 서버는 문자열 비교라 정상이었고 API 검증(200)만 해서 못 잡았다 — 렌더/실기기 검증이 아니면 안 드러나는 유형.
수리: type="text" + CSS 마스킹(-webkit-text-security:disc) + 표시/숨김 토글. secure-entry가 아니라 시각적 마스킹만 하므로 IME가 살아 있다.
web/work-staff.html— 비밀번호 필드 교체,.pw-wrap/.pw-mask/.pw-toggleCSS, 토글 JS, 에러문구 "PIN"→"비밀번호"web/work-owner.html— 동일 적용(아이디가 한글이라 비번도 한글로 바뀔 여지 대비)- 신규 hex 색상 0건(기존 토큰만 사용)
type="password" 잔존 0건, 로컬·공개 HTTPS 200, 서빙본에 수정 반영 확인, 크라우니AI검증.sh text 로 "비밀번호 / 표시 / 로그인" 렌더 확인.메모리 기록: feedback_password필드_모바일IME_한글입력차단.md (재발 방지)
추가 수리 (2026-07-31, 세션4) — 모바일 4건
사용자 실기기 피드백 4건 전량 수리.
1. 한글 입력기가 아예 안 뜸 (복사·붙여넣기로만 로그인됨)
세션3의 CSS 마스킹만으로는 부족 — 기기/키보드에 따라 한글 IME가 안 올라온다. → ASCII 대체계정 도입.workstaff.psv/workowner.psv 끝에 altId|altPw 컬럼 추가, workPwMatch()로 한글·ASCII 어느 조합이든 인증.
- 스태프:
아파트스태프/아파트1234=aptstaff/apt1234(교차조합 포함 4조합 전부 200, 오답 401) - 관리소:
천안용곡우림=yonggok(pw12341234) - 입력필드는
lang="ko"추가, autocorrect/autocapitalize/spellcheck 제거
2. 사진이 현장 촬영만 되고 앨범 선택 불가
원인:<input type="file" capture="environment"> — capture 속성이 있으면 카메라만 열린다.
→ 입력을 분리: #photoCam(촬영, capture 유지) / #photoFile(앨범, multiple 다중선택). 한쪽 선택 시 다른 쪽 초기화, 선택 파일명 표시, 다중 업로드 루프(성공/실패 건수 토스트).3. 태그가 도메인과 안 맞음
아파트 수리가 아니라 방송시스템 구축/수리 도메인. → 퀵칩 교체: 균열·누수·마감·안전·자재 → 현황·고장·진단·탈거·양중·설치·결선·수리·청소·시운전 → 케이스 솔루션도 SOL-104(화재경보) → SOL-201 Inter-M 전관방송·PA 시스템, desc "공동주택 전관방송 시스템 구축"4. 샘플 이미지가 물음표(?)로 표시
원인: 세션2 검증에 쓴 파일이 1×1 픽셀 회색 PNG(68바이트)라 렌더 불가. → 샘플 사진 2장·PSV 행 전량 제거(실제 현장사진만 올라가게). 64×64 유효 PNG로 업로드→서빙 왕복 검증 후 그것도 제거.검증
- 로그인 4조합 200 / 오답 401, 관리소 2조합 200
owner/me솔루션명·desc 방송시스템 전환 확인, 사진 0장(샘플 제거)- 유효 PNG 업로드 200 ×2 → 파일 GET 200
image/png64×64 정상 - 서빙본에 촬영·앨범·신규태그 10종 전부 반영,
#photoFile에 capture 없음 확인 - 공개 HTTPS 6경로 200, 렌더 확인
잔여
- 한글 IME는 기기 설정 문제라 웹에서 완전 보장 불가 → ASCII 대체계정이 실질 해법
- 사진 용량 상한·리사이즈 여전히 미배선(고화질 다중 업로드 시 base64 팽창)
브라우저 실행 검증 (메인 세션 독립 실측 — 2026-08-01)
에이전트가 "canvas 축소 경로는 curl 로 재현 불가"라며 Node 바이트검증으로 대체했던 구간을 실제 WKWebView 에서 실행해 메꿨다.
방법: work-staff.html 의 EXIF 모듈+processImageFile 을 그대로 추출한 하네스 HTML 을 web/ 에 만들고(WKWebView loadFileURL 부모디렉토리 제한 회피), crowny-ai-browser MCP 로 열어 결과를 읽음. 입력=exiftool 로 GPS·DateTimeOriginal·Make/Model·Artist(한글)·Orientation=6 을 심은 3600×2400 JPEG(619,547B).
| 항목 | 실측 |
|---|---|
| EXIF 파싱 | orientation=6 · gps=36.815100,127.113900 · shotAt=2026:07:31 14:22:31 · Apple iPhone 15 Pro · artist=아파트현장팀(한글 정상) |
| 축소 | 619,547B → 214,813B (65% 감소) |
| 서버 전달 메타 | shotAt·gps·device·shooter 전부 채워짐 |
| APP1 바이트 대조 | 원본 556B vs 축소본 556B — 1바이트만 상이(byte 66: 6→1 = Orientation 정규화, 의도된 동작). 나머지 555B 완전 동일 |
dd 로 브라우저와 독립 추출해 대조 — 같은 파서로 재파싱하는 순환검증을 피했다.
하네스는 검증 후 삭제(공개 경로 조회 시 SPA 폴백만 반환, __B64__ 유출 0건 확인).중간 발견: 하네스 1차 시도가 window.onerror: Script error. 로 죽음 → 추출 구간 끝줄이 함수 중간이라 문법 파손. node --check 로 경계를 이분탐색해 확정. 추출식 검증은 경계 문법검사가 선행돼야 한다.