# Collection Link Front-End Usage Guide

This document covers how to use the collection link API to model many-to-many relationships and cross-entity references from the front end.

Use collection links when a record needs more than one related record.

Good examples:
- A job assigned to multiple technicians
- A job linked to multiple invoices
- A machine linked to multiple parts outside the parent-child tree

Do not use collection links when a single promoted relational column already models the relationship well.

Examples:
- Use `user_ref_id` for the primary assigned technician
- Use `status_id` for workflow status
- Use `entity_ref_id` for the main machine attached to a job

For your job and technicians case, the clean pattern is:
- Store the main assigned technician in `user_ref_id` on the job when you need fast filtering or a single owner.
- Store additional technician assignments in `collection_link` using `relation_type = job_technician` and `target_collection = users`.

---

## TypeScript Interfaces

```ts
export interface ICollectionLink<TMeta = any> {
  id: number;
  source_id: number;
  source_collection: string;
  target_id: number;
  target_collection: string;
  relation_type: string;
  data?: TMeta | string | null;
  created_at?: string;
  updated_at?: string;
}

export interface IJobTechnicianLinkMeta {
  assigned_at?: string;
  assigned_by?: number;
  role?: 'lead' | 'assistant';
  notes?: string;
}

export enum CollectionLinkRelationTypes {
  JobTechnician = 'job_technician',
}
```

---

## API Surface Available Today

Base path:

```ts
const apiUrl = `${Constants.ApiBase}/collection-link`;
```

### Add a link

```http
POST /collection-link/add.php
```

Request body:

```json
{
  "source_id": 54,
  "source_collection": "jobs",
  "target_id": 2,
  "target_collection": "users",
  "relation_type": "job_technician",
  "data": {
    "assigned_at": "2026-03-14T08:00:00Z",
    "assigned_by": 1,
    "role": "assistant"
  }
}
```

Notes:
- The backend avoids exact duplicates by checking the full composite of source, source collection, target, target collection, and relation type.
- The response is the created row or the existing matching row.

### Get links for one source and relation type

```http
GET /collection-link/get-links.php?sourceId=54&relationType=job_technician
```

Notes:
- `sourceId` is required.
- `relationType` is required.
- The response contains raw link rows only.
- The current endpoint does not hydrate the user or collection data target row.

### Delete one link

```http
DELETE /collection-link/delete.php?id=14
```

---

## Angular Service Example

```ts
@Injectable({ providedIn: 'root' })
export class CollectionLinkService {
  private apiUrl = `${Constants.ApiBase}/collection-link`;

  constructor(private http: HttpClient) {}

  add<TMeta = any>(payload: Omit<ICollectionLink<TMeta>, 'id' | 'created_at' | 'updated_at'>) {
    return this.http.post<ICollectionLink<TMeta>>(`${this.apiUrl}/add.php`, payload);
  }

  getBySource<TMeta = any>(sourceId: number, relationType: string) {
    const params = new URLSearchParams({
      sourceId: String(sourceId),
      relationType,
    });

    return this.http.get<ICollectionLink<TMeta>[]>(`${this.apiUrl}/get-links.php?${params}`);
  }

  delete(id: number) {
    return this.http.delete(`${this.apiUrl}/delete.php?id=${id}`);
  }
}
```

---

## Usage Examples

### Assign multiple technicians to a job

```ts
const technicianIds = [2, 5, 9];

forkJoin(
  technicianIds.map(userId =>
    this.collectionLinkService.add<IJobTechnicianLinkMeta>({
      source_id: jobId,
      source_collection: CollectionNames.Jobs,
      target_id: userId,
      target_collection: 'users',
      relation_type: CollectionLinkRelationTypes.JobTechnician,
      data: {
        assigned_at: new Date().toISOString(),
        assigned_by: currentUser.id,
        role: userId === primaryTechnicianId ? 'lead' : 'assistant',
      },
    })
  )
).subscribe();
```

### Load technicians linked to a job

```ts
this.collectionLinkService
  .getBySource<IJobTechnicianLinkMeta>(jobId, CollectionLinkRelationTypes.JobTechnician)
  .subscribe(links => {
    const technicianIds = links.map(link => link.target_id);
    console.log(technicianIds);
  });
```

### Remove one technician from a job

```ts
this.collectionLinkService.delete(linkId).subscribe();
```

---

## Recommended Pattern For Jobs

Use both the promoted column and the link table together.

Recommended split:
- `user_ref_id`: the primary technician for dashboards, default assignment, and indexed filtering
- `collection_link`: all technician memberships for collaboration and many-to-many assignment

This gives you both:
- fast single-tech queries
- flexible multi-tech membership

---

## Important Current Limitations

The current collection link API is usable, but it is not fully stable for broad front-end use without a few more endpoints or filters.

Current gaps:
- `get-links.php` only filters by `sourceId` and `relationType`
- It does not filter by `source_collection`
- It does not filter by `target_collection`
- It returns raw links, not hydrated user records or collection data records
- There is no reverse lookup such as all jobs linked to technician 2
- There is no delete-by-composite-key endpoint
- There is no bulk add or bulk delete endpoint

Because of that, the safest current front-end usage is:
- keep relation types specific and stable, such as `job_technician`
- keep source ids scoped by the business meaning in the UI
- fetch linked target records separately after reading the links

---

## Recommended Next Endpoints

If collection links will become a first-class feature in the UI, these are the next endpoints worth exposing:

1. `GET /collection-link/get-links.php?sourceId={id}&sourceCollection={collection}&relationType={type}`
2. `GET /collection-link/get-by-target.php?targetId={id}&targetCollection={collection?}&relationType={type?}`
3. `GET /collection-link/get-targets.php?sourceId={id}&sourceCollection={collection}&relationType={type}`
4. `DELETE /collection-link/delete.php?sourceId={id}&targetId={id}&relationType={type}` or a dedicated `delete-by-composite.php`
5. `POST /collection-link/save-range.php` for bulk insert and sync operations

These would make the link system much easier to consume from the front end and would remove a lot of extra request orchestration.