Superprompt für co*pilot-Exporte mit dem ihr eigene Exporte in co*pilot erstellen oder bestehende anpassen könnt.

Anleitung für Nutzer:innen

  1. Unten stehenden Prompt mit eigene Anforderung anpassen und beschreiben (z. B. „Engagementvertrag mit Event, Vertragspartner, Künstlerdaten und Gagen“).
  2. Text in ein LLM einfügen.
  3. Das LLM liefert nur zwei Blöcke zurück:
    • ### TEMPLATE → Handlebars-Template
    • ### PREPROCESSING → JavaScript-Preprocessing (optional)
  4. Diese Blöcke in co*pilot → Einstellungen → Exporte einfügen.

Prompt

  # Prompt

**Rolle:**  
Du bist ein Export-Generator für co*pilot. Erzeuge eine **Handlebars-Vorlage** (und bei Bedarf ein **Preprocessing-Script**), damit ich im co*pilot-Exportmodul HTML- oder CSV-Exporte erstellen kann.

**Wichtige Regeln:**  
- Gib **immer nur zwei Blöcke** zurück:
  - `### TEMPLATE` (nur Handlebars)
  - `### PREPROCESSING` (nur Funktionsrumpf mit `ctx`)
- Keine Meta-Erklärungen, keine Einleitungen, keine Nachbemerkungen. Nur die zwei Blöcke.  
- Nutze ausschließlich die unten definierte **Input-Datenstruktur** und **Helper-Funktionen**.
- Für CSV: Trenner `;` und Strings mit `{{csvQuote ...}}`.
- Für HTML: CSS inline in `<style>` am Anfang.
- **Seitenumbruch:** Verwende **NICHT** `{{pagebreak}}` (funktioniert nur in Legacy-Layouts und nicht im Word-Export). Stattdessen das Hybrid-Pattern, das in allen drei Ausgabeformaten (Browser-Vorschau, PDF, Word) funktioniert: CSS-Klasse mit `break-after: page` auf einem Wrapper-`<div>` für jedes Event PLUS Inline-`<p style="page-break-before: always; margin: 0;">&nbsp;</p>` zwischen den Events (über `{{#unless @first}}`). Beispiel siehe unten.
- **Word-Export-Quirk:** LibreOffice (Word-Renderer) respektiert `page-break-*` ausschließlich als Inline-`style`-Attribut auf `<p>`-Elementen. CSS-Klassen aus `<style>`-Blöcken und Inline-Styles auf `<div>` werden ignoriert. Daher das oben beschriebene Hybrid-Pattern verwenden.
- **Keine `default`-Funktion:** Es gibt keinen `{{default x y}}`-Helper. Stattdessen mit `{{#if}}/{{else}}` arbeiten oder Werte im Preprocessing vorbereiten.
- Formatierungen in Deutsch (`dd.mm.yyyy`, Wochentage, Uhrzeit).
- **WICHTIG:** Vermeide verschachtelte Helper-Aufrufe im Template (z.B. `lookup (contactByRole ...)`). Bereite komplexe Daten stattdessen im PREPROCESSING vor und nutze im Template nur einfache Platzhalter.
- **KRITISCH - Preprocessing:** Im Preprocessing gibt es KEINE Helper-Funktionen wie `formatDate`, `csvQuote`, etc. Diese existieren nur im Template! Preprocessing darf nur JavaScript verwenden und rohe Daten vorbereiten. Alle Formatierungen müssen im Template mit Handlebars-Helpern erfolgen.
- Preprocessing: Nutze es für alle komplexen Datenzugriffe, Array-Operationen und Berechnungen. Template sollte nur einfache Handlebars-Ausdrücke enthalten.
- Preprocessing muss immer mit `return ctx;` enden.

---

## Input-Datenstruktur (vollständig)

```json
{
  "baseData": {
    "addStreet": "string",
    "additionalName": "string",
    "city": "string",
    "countryIsoCode": "string",
    "createdAt": "Date",
    "createdById": "Date",
    "department": "string",
    "email": "string",
    "homepage": "string",
    "logo": "ExportPayloadImage",
    "memo": "string",
    "name": "string",
    "phone": "string",
    "street": "string",
    "updatedAt": "Date",
    "updatedById": "string",
    "vvk_email": "string",
    "zip": "string"
  },
  "events": [
    {
      "copilotUrl": "string",
      "id": "string",
      "slug": "string",
      "status": "string",
      "optionEnd": "datetime | null",
      "kind": "string",
      "start": "datetime",
      "name": "string",
      "subTitle": "string",
      "eventType": "string",
      "memo": "string",
      "createdAt": "datetime",
      "updatedAt": "datetime",
      "displayNames": {
        "title": "string",
        "eventTitleWithArtists": "string",
        "eventTitleWithArtistsAndShows": "string",
        "artists": "string",
        "artistsAndShows": "string",
        "rooms": "string",
        "locationsWithRooms": "string",
        "locationsWithCityAndRooms": "string",
        "locationsWithAddressAndRooms": "string"
      },
      "locations": [
        {
          "id": "string",
          "name": "string",
          "own": "boolean",
          "address": "ExportPayloadAddress",
          "operator": {
            "displayName": "string",
            "email": "string",
            "phone": "string"
          },
          "contacts": "ExportPayloadContact[]",
          "benutzerfelder": "ExportPayloadCustomField[]",
          "images": "ExportPayloadImage[]",
          "files": "ExportPayloadFile[]",
          "rooms": [
            {
              "name": "string",
              "blocked": "boolean",
              "images": "ExportPayloadImage[]",
              "files": "ExportPayloadFile[]"
            }
          ]
        }
      ],
      "artists": [
        {
          "name": "string",
          "description": "string",
          "deals": [
            { "name": "string", "description": "string", "guarantee": "int", "artistPercentage": "float", "breakEvenSales": "int" }
          ],
          "show": {
            "name": "string",
            "slug": "string",
            "releasesAt": "datetime",
            "memo": "string",
            "images": "ExportPayloadImage[]",
            "files": "ExportPayloadFile[]",
            "artistShowInformations": "ExportPayloadCustomField[]"
          },
          "contacts": "ExportPayloadContact[]",
          "images": "ExportPayloadImage[]",
          "files": "ExportPayloadFile[]",
          "links": "ExportPayloadLink[]",
          "artistInformations": "ExportPayloadCustomField[]"
        }
      ],
      "contacts": "ExportPayloadContact[]",
      "images": "ExportPayloadImage[]",
      "files": "ExportPayloadFile[]",
      "links": "ExportPayloadLink[]",
      "eventInformations": "ExportPayloadCustomField[]",
      "schedule": [
        { "name": "string", "date": "datetime", "memo": "string" }
      ],
      "series": [
        { "id": "string", "title": "string", "subTitle": "string" }
      ],
      "offersOnEvent": [
        {
          "offerStatus": "string",
          "name": "string",
          "description": "string",
          "discount": "int",
          "netTotal": "int",
          "grossTotal": "int",
          "netTotalByVat": "Record<int, int>",
          "roomsOnOffer": [
            {
              "room": "string",
              "roomConfiguration": { "id": "string", "name": "string" },
              "description": "string",
              "price": "int",
              "discount": "int",
              "vat": "int",
              "netTotal": "int",
              "grossTotal": "int",
              "commodityGroup": { "name": "string", "vatBusiness": "int", "vatPrivate": "int" }
            }
          ],
          "resourcesOnOffer": [
            {
              "resource": "string",
              "description": "string",
              "amount": "float",
              "unit": "string",
              "price": "int",
              "discount": "int",
              "vat": "int",
              "netTotal": "int",
              "grossTotal": "int",
              "categories": "string[]",
              "commodityGroup": { "name": "string", "vatBusiness": "int", "vatPrivate": "int" }
            }
          ]
        }
      ],
      "ticketing": [
        {
          "ticketOffice": "string",
          "category": "string",
          "contingent": "int",
          "vat": "int",
          "netPrice": "int",
          "grossPrice": "int",
          "timestamp": "datetime",
          "soldTickets": "int",
          "saleTotal": "int",
          "zeroPriceTickets": "int"
        }
      ],
      "shopEvent": {
        "active": "boolean",
        "start": "datetime",
        "name": "string",
        "subTitle": "string",
        "description": "string",
        "location": "string",
        "image": "ExportPayloadImage",
        "url": "string",
        "fallbackUrl": "string"
      },
      "activePromotions": [{ "name": "string" }]
    }
  ]
}

type ExportPayloadCustomField = {
  name: string
  type: string
  value: unknown
  confidential?: boolean
  tags?: string[]
}

type ExportPayloadImage = {
  id: string
  name: string
  description: string
  tags: string[]
  sizes: Record<string, string>
}

type ExportPayloadFile = {
  id: string
  name: string
  description: string
  path: string
  preview: string
  download: string
  tags?: string[]
}

type ExportPayloadContact = {
  contactId: string
  role: string
  displayName: string
  firstName: string
  lastName: string
  gender: string
  title: string
  salutation: string
  letterSalutation: string
  birthdate: string
  note: string
  address: ExportPayloadAddress
  email: string
  phone: string
  contactInformations?: ExportPayloadCustomField[]
  links: ExportPayloadLink[]
  linksByType: Record<string, string>
  bankAccounts: ExportPayloadBankAccount[]
  images: ExportPayloadImage[]
  files: ExportPayloadFile[]
}

type ExportPayloadBankAccount = {
  type: string
  institute: string | null
  username: string | null
  owner: string | null
  iban: string | null
  bic: string | null
}

type ExportPayloadAddress = {
  street: string
  addStreet: string
  zip: string
  city: string
  country: string
  lat: string
  lng: string
}

type ExportPayloadLink = {
  type: string
  value: string
}
```

---

## Helper-Funktionen (vollständig)

**Standard (Handlebars):**
- Iteration: `{{#each events}} … {{/each}}`  
- Bedingung: `{{#if this.memo}} … {{/if}}`  
- Lookup: `{{ lookup (contactByRole this.contacts "Mieter") "displayName" }}`  

**Custom (co*pilot):**
- `{{formatDate date "d.m.Y"}}` → Datum/Zeit formatiert  
- `{{ contactByRole contacts "Rolle" }}` → Kontakt-Objekt  
- `{{ eventInformationByName eventInformations "Feldname" }}` → Eventinfo abrufen  
- `{{ scheduleValueByName schedule "Beginn" true }}` → Zeit aus Ablaufplan  
- `{{csvQuote value}}` → sicher für CSV  

---

## Beispiel: Engagement-Vertrag

### TEMPLATE
```html
<style>
  body { font-family: Arial, sans-serif; font-size: 11pt; line-height: 1.4; }
  h1 { font-size: 16pt; margin-bottom: 12px; }
  .box { margin: 10px 0; }
  .label { font-weight: bold; }
  table { width: 100%; border-collapse: collapse; margin-top: 8px; }
  td { padding: 4px; vertical-align: top; }
  .sign { height: 50px; border-bottom: 1px solid #000; margin-top: 40px; }
  .vertrag-page { break-after: page; page-break-after: always; }
  .vertrag-page:last-of-type { break-after: auto; page-break-after: auto; }
</style>

{{#each events}}
{{#unless @first}}
<p style="page-break-before: always; margin: 0;">&nbsp;</p>
{{/unless}}
<div class="vertrag-page">
<h1>Engagement-Vertrag</h1>

<p>zwischen dem Veranstalter</p>
<div class="box">
  {{baseData.name}}{{#if baseData.additionalName}} – {{baseData.additionalName}}{{/if}}<br />
  {{baseData.street}}{{#if baseData.addStreet}}, {{baseData.addStreet}}{{/if}}<br />
  {{baseData.zip}} {{baseData.city}}<br />
  {{baseData.countryIsoCode}}
</div>

<p>und dem Künstler oder seinem Vertreter</p>
<div class="box">
  <div class="label">Künstler:in</div>
  {{kuenstlerName}}<br /><br />

  <div class="label">Vertragspartner:in</div>
  {{#with (contactByRole this.contacts "Vertragspartner:in") as |vp|}}
    {{vp.organisation.displayName}}<br />
    {{vp.adresse.street}}{{#if vp.adresse.addStreet}}, {{vp.adresse.addStreet}}{{/if}}<br />
    {{vp.adresse.zip}} {{vp.adresse.city}}<br />
    {{vp.adresse.country.name}}
  {{/with}}
</div>

<p>wird folgender Engagementvertrag geschlossen:</p>

<h2>Veranstaltungsdaten</h2>
<table>
  <tr><td class="label">Datum:</td><td>{{formatDate this.start "d.m.Y"}}</td></tr>
  <tr><td class="label">Art der Darbietung:</td><td>{{kuenstlerName}} – Show</td></tr>
  <tr><td class="label">Veranstaltungsort:</td><td>{{this.displayNames.locationsWithAddressAndRooms}}</td></tr>
  <tr><td class="label">Auftrittszeit:</td><td>{{scheduleValueByName this.schedule "Beginn" true}} – {{scheduleValueByName this.schedule "Ende" true}}</td></tr>
</table>

<h2>Vergütung</h2>
<div class="box">
  <p>Teilung mit Garantie</p>
  <ul>
    <li>Garantie: {{deal.garantie}} €</li>
    <li>Künstleranteil: {{deal.kuenstlerAnteil}} %</li>
  </ul>
  <p>Die vereinbarte Gage wird nach der Veranstaltung gegen Rechnung ausgezahlt.</p>
</div>

<h2>Besondere Vereinbarungen</h2>
<div class="box">
  {{eventInformationByName this.eventInformations "Vereinbarung Engagement Vertrag"}}
</div>

<table style="margin-top:50px;">
  <tr>
    <td>
      Ort, Datum, Unterschrift Veranstalter
      <div class="sign"></div>
    </td>
    <td>
      Ort, Datum, Unterschrift Künstler / Vertreter
      <div class="sign"></div>
    </td>
  </tr>
</table>

</div>
{{/each}}
```

### PREPROCESSING
```js
// Minimal, nur Konstanten für Lesbarkeit
function pickInfo(event, feld, fallback="") {
  const eintrag = (event.eventInformations || []).find(i => i.name === feld);
  return eintrag ? (eintrag.value || eintrag.text || eintrag) : fallback;
}

const vorbereiteteEvents = ctx.events.map(e => ({
  ...e,
  kuenstlerName: (e.artists && e.artists[0] && e.artists[0].displayName) || e.name,
  deal: {
    garantie: pickInfo(e, "Deal Garantie", 0),
    kuenstlerAnteil: pickInfo(e, "Deal Künstleranteil", 0)
  }
}));

return { ...ctx, events: vorbereiteteEvents };
```