/** * Tabular-part template — Suppa v2 declarative migration. * A tabular part is a child entity whose rows only exist inside one parent * record (checklist items of a task, lines of an order, attendees of an * event) — the platform renders it as a table on the parent's form instead * of giving it its own navigation entry. * * The triad, ALL THREE required — the platform does not infer any of them: * 1. type: 'tabular-part' on @Entity() * 2. relationEntityName — the PARENT entity's @Entity() name * 3. a @ManyToOne field named EXACTLY `owner`, pointing at the parent class * * Put `owner` FIRST in importKeyFields — a row is unique inside its parent, * not across the workspace; owner missing/misplaced ⇒ duplicate rows across * different parents (E110/W607). Replace __markers__. Rules: * references/tabular-parts.md. * * File name: __kebab-case-plural__.entity.ts (e.g. task-checklist-items.entity.ts) */ import { Column, Entity, FieldTypeEnum, Index, ManyToOne, SystemBaseEntity, } from '@suppa/sdk'; import { __ParentEntity__ } from './__parent-entity__.entity'; @Index('__EntityName__OwnerKeyUindex', ['owner', '__businessKeyField__'], { unique: true, where: '"deletedAt" IS NULL', }) @Entity({ name: '__EntityName__', // = class name; table + createSeed() key title: { en: '__EntityName__', uk: '__Українська назва__' }, key: '__entity-stable-key__', // survives renames — a rename stays a rename type: 'tabular-part', // 1/3 — makes this a tabular part of __ParentEntity__ relationEntityName: '__ParentEntity__', // 2/3 — the PARENT's @Entity() name, not the class name if they differ importKeyFields: ['owner', '__businessKeyField__'], // owner FIRST — see references/tabular-parts.md representativeFieldName: '__businessKeyField__', }) export class __EntityName__ extends SystemBaseEntity { // NEVER declare: id, createdAt, updatedAt, deletedAt, createdBy, removedBy // (SystemBaseEntity provides them). // 3/3 — the property MUST be named `owner`, pointing at the parent via a thunk. // No inverse passed — it's optional on @ManyToOne and omitted by default. // nullable: false, because a tabular-part row always belongs to a parent — // and `owner` is the first importKeyField, which a null would break. The // property is typed as the parent class, with no '?' to match that. @ManyToOne(() => __ParentEntity__, { name: 'owner', key: '__EntityName__.owner', nullable: false, title: { en: '__Parent title__', uk: '__Батьківська назва__' }, }) owner: __ParentEntity__; @Column({ name: '__businessKeyField__', title: { en: '__Field__', uk: '__Назва поля__' }, type: FieldTypeEnum.Text, nullable: false, }) __businessKeyField__: string; @Column({ name: 'order', title: { en: 'Order', uk: 'Порядок' }, type: FieldTypeEnum.Integer, }) order: number; } /* Declare the back reference on the PARENT entity (__parent-entity__.entity.ts) so the * rows read as a nested array and the tab gets a localized name. A back reference is * readable, not writable — NEVER include it in a seed, not even as []: * * @OneToMany(() => __EntityName__, (item) => item.owner, { * title: { en: '__Tab title__', uk: '__Назва вкладки__' }, * }) * __childrenField__: __EntityName__[]; * * Seed the tabular part with its own seed, referencing the parent through owner — * exactly like any other @ManyToOne (via the PARENT's importKeyFields): * * import { createSeed } from '@suppa/sdk'; * * export const __camelCasePlural__Seed = createSeed('__EntityName__', [ * { owner: { __parentBusinessKeyField__: '__parent-key-value__' }, __businessKeyField__: 'First', order: 1000 }, * ]); * * Register both classes in forFeature() — the parent does not need to come first — * but the PARENT's seed must run before this one, or owner resolves to nothing: * * EntityModule.forFeature([__ParentEntity__, __EntityName__], { * seeds: [__parentEntity__Seed, __camelCasePlural__Seed], * }); */