Skip to content

프로퍼티 유닛 CSV 가져오기 — Backend 계약

소유권과 경계

공간 identity는 플랫폼 코어 property_spaces가 소유한다. 물리 면적 프로필은 property_space_unit_profiles가 공용 코어 projection으로 소유하며 관리비·시설 등 선택 상품이 같은 공간 UUID를 참조한다. 가져오기 command·revision·불변 row ledger는 관리비 상품이 소유한다.

  • Core migration: 20260717213716_property_space_unit_profiles.sql
  • Product migration: 20260717213728_service_charge_unit_catalog_import.sql (product-owner: service-charge)
  • offices.property_data는 외부/수동 프로퍼티 설정 snapshot이므로 이 command가 수정하지 않는다.
  • 관리비 외 상품 entitlement를 요구하지 않는다. 반대로 관리비 미신청 상태에서는 이 product command를 실행할 수 없다.

공개 RPC

sql
public.import_service_charge_units(
  p_company_id uuid,
  p_office_id uuid,
  p_expected_revision bigint,
  p_rows jsonb,
  p_request_key text
)

조회는 public.get_service_charge_unit_catalog(p_company_id, p_office_id) 한 번으로 catalog revision과 active core profile rows를 같은 DB snapshot에서 반환한다. 브라우저가 core rows와 product revision을 병렬 조회해 서로 다른 시점을 섞지 않는다.

브라우저는 로그인 세션으로 RPC만 호출한다. actor/provider/service-role 값은 payload로 받지 않는다. 서버는 auth.uid(), Company·Office membership, can_manage_service_charge_office, 관리비 entitlement를 다시 검증한다.

행 계약

각 행은 registrationCode,dongName,floorName,unitName,usage,exclusiveArea,residentialCommonArea,otherCommonArea,supplyArea,contractArea를 가진다. 1~5,000행을 허용하며 다음 규칙을 한 transaction에서 검사한다.

  • 관리코드는 서버가 자동 생성하고 기존 값은 불변이다.
  • 등록코드는 optional·중복 허용·최초 설정 후 불변이다.
  • 숫자 면적은 음수가 아니며 공급면적·계약면적은 구성 면적에서 결정적으로 계산된다.
  • present row만 merge/upsert한다. 파일에 없는 공간은 archive/delete하지 않는다.
  • legacy Office snapshot 공간은 core profile로 backfill하지만 snapshot JSON은 바꾸지 않는다.

멱등·동시성·거부

Office scope advisory lock과 catalog revision으로 동시 수정을 직렬화한다. 동일 request key와 동일 payload는 exact replay하며 revision 검사는 그 뒤에 적용한다. stale expected revision과 행 검증 실패는 rejected 원장을 남기고 core 공간·profile·revision을 하나도 변경하지 않는다. 같은 key의 다른 payload는 idempotency conflict 예외로 새 원장을 만들지 않으며, 권한·상품 계약 실패도 원장 기록 전에 차단한다.

성공·거부 header와 입력 rows는 append-only ledger다. 직접 table DML은 authenticated에서 revoke하고, public wrapper와 private SECURITY DEFINER helper는 빈 search_path와 최소 function grant를 사용한다. public read table은 RLS와 명시 SELECT grant를 모두 통과해야 한다.

검증

  • migration/parser/repo/composable unit tests
  • supabase/tests/service_charge_unit_catalog_import_smoke.sql
  • fresh DB reset, smoke, DB lint/advisor
  • replay/conflict/stale/atomic rejection/등록코드 불변/권한/RLS·grant/property_data 불변을 고정한다.