다크모드
프로퍼티 유닛 관리 — Frontend 핸드오프
진입점과 컴포넌트
정본 화면은 /administration/organization/workspace/property/console/unit이다. /master-data/resource/unit/general과 /master-data/property/console/unit도 같은 정본 View를 재사용하는 호환 라우트다. 별도 화면·목록·상태를 만들지 않는다.
text
MainOrg.vue
├─ blocks/DataToolBarOrg.vue
│ ├─ #sheet-create-workspace-unit-art
│ ├─ #sheet-import-property-units-art
│ └─ #dialog-archive-workspace-units-art
├─ blocks/DynamicTableOrg.vue
├─ overlays/SheetCreateUnitArt.vue
├─ overlays/SheetCreateUploadUnitArt.vue
└─ overlays/DialogArchiveUnitsArt.vue관리비 부과산정의 유닛 관리 버튼은 이 라우트로 이동할 뿐 프로퍼티 UI를 import하지 않는다. 관리비 일반·중간정산·공지 화면에는 업로드 sheet mount가 없다. 범용 스캐폴딩 ID인 sheet-create-upload-unit-art를 재사용하지 않는다.
단건 추가·선택 삭제는 상품과 무관한 Workspace 공간 command다. DataToolBarOrg의 CSV 가져오기만 useModuleSubscription().isModuleEnabled('service-charge')를 읽어 관리비 미신청 Office에서 disabled 처리한다. 상품 미신청 상태에서도 공용 유닛 목록·Seat 현황·단건 관리는 사용할 수 있다.
MainOrg.vue는 플랫폼 코어 화면과 선택 상품 adapter를 조립하는 명시적 composition root다(config/product-boundaries.json). 관리비 parser·command composable은 이 파일에서만 주입하고, core overlay는 parseCsv·importRows·importing props만 소비한다. 따라서 core 하위 컴포넌트가 관리비 구현을 직접 import하지 않으며 다른 상품은 같은 방식으로 선택 연결할 수 있다.
페이지는 active Office ID와 관리비 entitlement를 watch해 catalog와 Unit Seat capacity를 함께 hydrate한다. 관리비 신청 상태면 atomic service-charge catalog RPC로 profile과 command revision을 함께 읽고, 미신청 상태면 상품 table에 의존하지 않는 core profile read를 사용한다. useWorkspaceUnitSeatManagement()는 별도로 get_workspace_unit_seat_capacity를 읽고 단건 생성·archive 후 catalog, Office unitCount projection, capacity를 다시 hydrate한다.
hydrate 중·실패 상태에서는 쓰기 command를 잠그고, 오류 안내의 다시 불러오기로 복구한다. Office가 바뀌면 선택된 Unit ID를 초기화한다.
Unit Seat와 Unit UI 계약
- 상단 요약은
purchasedQuantity,usedQuantity,remainingQuantity를 각각 표시한다. remainingQuantity === 0이면 단건 추가를 비활성화한다.- Office 화면은
unitPrice, 통화, 과금주기를 요청하거나 표시하지 않는다. - 목록 선택 키는 legacy
unitCode가 아니라propertySpaceId다. - 삭제 확인 후
archive_workspace_units를 호출한다. 물리 delete UI나 API는 없다. - archive 성공 후 선택을 비우고 목록·capacity를 다시 읽는다.
- 단건 추가는
create_workspace_unit을 호출한다. 동·층·호, 등록코드, 용도, 3개 원천 면적만 입력하고 공급·계약면적은 파생 표시한다.
데이터 흐름
text
File.text()
→ parseServiceChargeUnitCsv(text)
→ 오류 0건일 때 preview + requestKey 고정
→ useServiceChargeUnitImport().importRows({ rows, requestKey, expectedRevision })
→ RPC committed
→ core catalog 재hydrate
→ propertyUnitMaster reactive catalog 교체
→ 유닛 목록·배분·첫 부과 readiness 갱신useServiceChargeUnitImport()는 active Company·Office를 내부에서 가져온다. hydrate()와 importRows()는 caller가 actor/provider/service-role을 넘기지 못하게 한다. import 성공 시 latest catalog와 revision을 재조회하고 activeOffice.unitCount projection을 갱신한다. 실패나 rejected 결과에서는 기존 cache를 유지한다.
useWorkspaceUnitSeatManagement()도 scope와 actor를 UI 입력으로 받지 않는다. active Company·Office에서 scope를 유도하고 다음 repo command만 노출한다.
text
getCapacity() → get_workspace_unit_seat_capacity
createUnit() → create_workspace_unit
archiveUnits() → archive_workspace_unitsCSV·상태 계약
- exact header:
등록코드,동명,층명,호명,용도,전용면적,주거공용면적,기타공용면적,공급면적,계약면적 - UTF-8/BOM, quoted comma, escaped quote 지원
.csv, 5MB, 5,000행 한도- formula prefix, 잘못된 숫자·필수값·헤더·면적 합계는 all-or-nothing 오류
- preview는 최초 10행만 표시하고 전체 건수를 별도로 보여 준다.
- submit 중 close·file change·재submit을 막는다.
- validation/RPC 실패 시 sheet를 유지한다. committed 뒤에만 close/reset한다.
requestKey는 유효한 파일을 선택했을 때 한 번 만들고 응답을 알 수 없는 통신 실패의 같은 payload 재시도 동안 유지한다. 서버가 명시적으로 rejected를 반환하면 그 key의 결과가 확정됐으므로 폐기한다. 특히 revision conflict는 최신 catalog를 먼저 hydrate한 뒤 새 key로만 다시 제출한다. 파일을 다시 골라도 새 key를 만든다.
UI 규율
시트는 SSOT의 sheet-width-3xl, sheet-lg, field/input, alert, card/table, sheet-footer 패턴만 사용한다. 로컬 style/override는 없다. 목록의 관리코드는 노출하지 않고 등록코드·이름·속성 순서를 유지한다.
단건 Create 시트는 세로 폼 집중 폭 3xl, 선택 삭제는 dialog-md를 사용한다. Seat 현황은 기존 card·타이포그래피·grid만 사용하며 새 CSS나 제품 로컬 override를 만들지 않는다.
테스트
- parser edge cases와 row normalization
- import repo RPC payload/response
- hydration과
propertyUnitMaster/useAllocations/readiness 반영 - overlay: 잘못된 파일·preview·double-submit·실패 유지·성공 close
scripts/service-charge-product-boundary.spec.js: 관리비 upload wrapper/mount/trigger 0workspaceUnitSeatManagement.spec.js: 구매/사용/잔여, capacity 초과, 생성, archiveworkspaceUnitSeatMigration.spec.js: RLS·grant·capacity trigger·RPC 계약UnitPage.spec.js: 두 호환 route가 쓰는 정본 페이지의 추가/삭제/CSV command 배선