import { CreateDateColumn, Entity, Index, Column, RelationId, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm' import { ObjectType, Field, ID, Int } from 'type-graphql' import { Domain, ScalarObject } from '@things-factory/shell' import { TwinSpace } from './twin-space.js' /* * TwinSpaceRepresentation — 공간을 보는 한 방식(map|image|model), 복수·1개 primary(공간구성 아키텍처, design/ui/space-composition.md). * config(simple-json): model={boardId}(board-service 참조) / image={attachmentId|boardId,width,height} / map={center,zoom,provider}. * 폴리곤 영역은 TwinSpaceArea(map/image), model 표현은 씬 컴포넌트가 대신. */ @Entity() @Index('ix_twin_space_rep_0', (e: TwinSpaceRepresentation) => [e.domain, e.space]) @ObjectType({ description: 'A way to view a Space (map | image | model). Multiple per space, one primary.' }) export class TwinSpaceRepresentation { @PrimaryGeneratedColumn('uuid') @Field(type => ID) readonly id: string @ManyToOne(type => Domain) @Field(type => Domain, { nullable: true, description: 'Owning tenant domain.' }) domain?: Domain @RelationId((e: TwinSpaceRepresentation) => e.domain) domainId?: string @ManyToOne(type => TwinSpace) @Field(type => TwinSpace, { nullable: true, description: 'Owning space.' }) space?: TwinSpace @RelationId((e: TwinSpaceRepresentation) => e.space) spaceId?: string @Column() @Field({ description: 'Representation type: map | image | model.' }) type: string @Column({ nullable: true }) @Field({ nullable: true, description: 'Representation name.' }) name?: string @Column({ type: 'int', nullable: true }) @Field(type => Int, { nullable: true, description: 'Display order.' }) seq?: number @Column({ nullable: true, default: false }) @Field({ nullable: true, description: 'Whether this is the primary representation.' }) isPrimary?: boolean /* * ── 묶는 이름 · 목록에 나오는지 (2026-08-27 · 사용자 지시) ────────────────── * * 표현이 많아지면 목록만으로는 관리가 안 된다는 요구다. * * `group` 을 **자유 문자열**로 둔다. 묶는 축이 현장마다 다르다(건물별·용도별·층별). 고정 목록으로 * 두면 현장마다 맞지 않는다. 별도 표로 올리지 않는 이유는 그룹에 붙을 속성이 아직 없다는 것이고, * 생기면 그때 올린다. * * **그룹 자체의 순서는 컬럼으로 두지 않는다.** 그 그룹에 속한 `seq` 의 최솟값이 그 순서다 — * 화면이 계산한다. 컬럼을 두면 그룹 이름을 바꿀 때 두 곳을 맞춰야 한다. */ @Column({ nullable: true }) @Field({ nullable: true, description: 'Free-form group name for arranging a long representation list. Sites group differently (by building, by purpose, by floor), so this is not a fixed vocabulary. The order of the groups themselves is not stored: it is the smallest seq within each group, computed where the list is drawn.' }) group?: string /* * 목록에 나올 것과 다른 표현에서 열리는 것을 가른다. * * `isPrimary` 에 얹지 않는다 — 그것은 「현장 현황에 나올 하나」를 정하는 다른 질문이다. 하나의 칸이 * 두 질문에 답하면 한쪽을 고칠 때 다른 쪽이 오류 없이 바뀐다. * * **없으면 `index` 다.** 그 값이 흔한 쪽이라 비어 있는 것을 기본으로 읽는다. 대신 값이 있을 때는 * 아는 값이어야 한다 — 모르는 값을 그냥 두면 화면이 그것을 어느 쪽으로도 그리지 못한다. */ @Column({ nullable: true }) @Field({ nullable: true, description: 'What this representation is for: "index" (default when absent — it appears in the representation list) or "derived" (it is opened from another representation, such as a popup or drill target, and is folded out of the list rather than hidden). Kept separate from isPrimary, which answers a different question: which single representation stands for the site.' }) role?: string @Column({ type: 'simple-json', nullable: true }) @Field(type => ScalarObject, { nullable: true, description: 'Type config: model {boardId} / image {attachmentId|boardId,width,height} / map {center,zoom,provider}.' }) config?: any @CreateDateColumn() @Field({ nullable: true }) createdAt?: Date @UpdateDateColumn() @Field({ nullable: true }) updatedAt?: Date }