> For the complete documentation index, see [llms.txt](https://helpcenter.etron.info/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://helpcenter.etron.info/schnelles-wissen/funktionen-und-vorgange/gutscheinauswertung-bericht.md).

# Gutscheinauswertung (Bericht)

Gutscheine, Gutscheinverkauf, Gutscheineinlösung, Produktübersicht, Jahresauswertung, automatischer Report, Reportplan

{% hint style="success" %}
*Modul: ETRON onRetail V2 Gutscheinverwaltung und Kundenpflege*
{% endhint %}

Für die Auswertung von Gutscheinen und eWallets (Verkäufen, Aufladung, Einlösung, Stornierung) kann eine Gutscheinauswertung erstellt werden.

* Option 1: [Gutscheinauswertung über den Produktübersicht-Bericht](#gutscheinauswertung-uber-den-produktubersicht-bericht)
* Option 2: [Gutscheinauswertung über Belegzeilen-Bericht](#gutscheinauswertung-uber-die-kassenbelegzeilen) (nur für Kasse)
* Option 3: [Gutscheinauswertung über automatischen Report](#gutscheinauswertung-uber-automatischen-report) im Excel- oder CSV-Format erstellen

## Gutscheinauswertung über den Produktübersicht-Bericht

> *Berichte* -> *Verkaufsaufträge* -> *Produktübersicht*\
> *Berichte* -> *Kasse* -> *Produktübersicht*

Über den Produktübersicht-Bericht werden Gutscheinverkäufe und die zugehörigen Beträge je Jahr ausgewertet. Kassenverkäufe und Kundenrechnungen werden zusammengeführt.

Für die Jahresauswertung wird die Produktübersicht auf Gutscheinprodukte eingeschränkt. Anschließend wird nach *Jahr* gruppiert.

### Voraussetzungen

Für aussagekräftige Ergebnisse wird ein eindeutiges Merkmal am Gutscheinprodukt benötigt. Es wird eines der folgenden Merkmale empfohlen:

* Eine eigene **Produktkategorie** *Gutschein*, der alle Gutscheinprodukte zugeordnet sind.
* Ein einheitlich benanntes **Produkt**, beispielsweise *Gutschein*, wenn nur ein Gutscheinprodukt verwendet wird.
* Ein **Produkt-Stichwort** *Gutschein* an allen betroffenen Produkten.

{% hint style="info" %}
*Für die **Auswertung von Einlösungen** wird die **Kategorie oder Produkt-Stichwort zusätzlich am Belohnungsprodukt des Gutscheinprogramms hinterlegt**. Es wird beim Anlegen des Gutschein/e-Wallet-Programms automatisch erstellt, isund heißt **"Gutscheine"** oder **"eWallet".*** \
***Das Belohnungsprodukt ist in den Produkt-Stammdaten sichtbar, wenn der Filter "Kann verkauft werden" entfernt wird.***
{% endhint %}

### Auswertung erstellen

{% stepper %}
{% step %}

### Bericht öffnen

Die Produktübersicht wird über einen der beiden Menüpfade geöffnet. Beim Öffnen sind der Filter **Dieses Monat** und die Gruppierung nach **Kategorie** voreingestellt.

<div data-with-frame="true"><figure><img src="https://2281246901-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FlM26Mg6nljOOOe95HyIq%2Fuploads%2FeFUAFCj4VYhhI6kHGn6c%2Fimage.png?alt=media&amp;token=ec158fea-09f8-422c-b31d-76133e6de396" alt="Berichte -> Auftragswesen -> Produktübersicht"><figcaption><p>Berichte -> Auftragswesen -> Produktübersicht</p></figcaption></figure></div>
{% endstep %}

{% step %}

### Zeitraumfilter entfernen

Der voreingestellte Filter **Dieses Monat** wird in der Suchleiste über das *x* am Filtereintrag entfernt. Andernfalls bleibt die Auswertung auf den laufenden Monat beschränkt.
{% endstep %}

{% step %}

### Auf Gutscheine einschränken

Abhängig vom vorhandenen Merkmal wird eine der folgenden Einschränkungen gesetzt:

**Über die Produktkategorie**

In der Suchleiste wird `Gutschein` eingegeben. Anschließend wird in der Vorschlagsliste *Produktkategorie* gewählt. Untergeordnete Kategorien werden automatisch berücksichtigt.

<div data-with-frame="true"><figure><img src="https://2281246901-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FlM26Mg6nljOOOe95HyIq%2Fuploads%2FgbKPr0sD3J8SHOwRLL1Z%2Fimage.png?alt=media&amp;token=4017c3cb-dff0-4845-8a1d-0fedd3094786" alt="Produktübersicht auf Produktkategorie einschränken"><figcaption><p>Produktübersicht auf Produktkategorie einschränken</p></figcaption></figure></div>

**Über das Produkt**

In der Suchleiste wird `Gutschein` eingegeben. Anschließend wird *Produkt* gewählt. Diese Variante wirkt auf die Produktbezeichnung. Es werden nur Produkte erfasst, deren Name den Suchbegriff enthält.

<div data-with-frame="true"><figure><img src="https://2281246901-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FlM26Mg6nljOOOe95HyIq%2Fuploads%2F69XjQnRKnNAiOOemVE71%2Fimage.png?alt=media&amp;token=e0c94384-9013-4b44-bd8a-ffe643df3af7" alt="Produktübersicht auf Produkt einschränken"><figcaption><p>Produktübersicht auf Produkt einschränken</p></figcaption></figure></div>

**Über das Produkt-Stichwort**

In der Suchleiste wird `Gutschein` eingegeben. Anschließend wird *Produkt-Stichwörter* gewählt.

<div data-with-frame="true"><figure><img src="https://2281246901-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FlM26Mg6nljOOOe95HyIq%2Fuploads%2FacENwMqY4SyGT2U6iPDC%2Fimage.png?alt=media&amp;token=72b31a8e-8c6d-4890-b4ba-f157858f694d" alt="Produktübersicht auf Produkt-Stichwörter einschränken"><figcaption><p>Produktübersicht auf Produkt-Stichwörter einschränken</p></figcaption></figure></div>

{% hint style="warning" %}
*Bei der Einschränkung über das Produkt entscheidet allein die Bezeichnung. Produkte wie „Gutschein 50“ und „Geschenkgutschein“ werden erfasst. Ein Produkt namens „Wertkarte“ wird nicht erfasst.*

***

*Das Belohnungsprodukt für die Einlösung wird beim Anlegen des Gutscheinprogramms automatisch erstellt. Es enthält zunächst keine Produktkategorie und kein Stichwort. Daher müssen Produktkategorie und Stichwort auch bei diesem hinterlegt werden. Ansonsten zeigt die Einschränkung über die Kategorie/Stichwort nur Verkäufe.*
{% endhint %}
{% endstep %}

{% step %}

### Nach Jahr gruppieren

Bestehende Gruppierungen werden entfernt. Unter *Gruppieren nach* wird **Datum** geöffnet und die Ebene **Jahr** gewählt.

<div data-with-frame="true"><figure><img src="https://2281246901-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FlM26Mg6nljOOOe95HyIq%2Fuploads%2FTY0UC1ZtRve2aJP3I7aO%2Fimage.png?alt=media&amp;token=2179b135-19af-4990-8a69-538d50c699c3" alt="Produktübersicht nach Jahr gruppieren" width="563"><figcaption><p>Produktübersicht nach Jahr gruppieren</p></figcaption></figure></div>

Die Liste zeigt je Jahr die Summen für *Verkaufte Menge*, *Zwischensumme* und *Marge*. Als zweite Ebene kann **Produkt** gewählt werden. Damit werden die einzelnen Gutscheinprodukte innerhalb eines Jahres aufgeschlüsselt.

<div data-with-frame="true"><figure><img src="https://2281246901-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FlM26Mg6nljOOOe95HyIq%2Fuploads%2FigQpVeguk8yzF0wiaFP8%2Fimage.png?alt=media&amp;token=4c9f6f26-5a1e-4d46-809f-42217b840adb" alt="Gutscheinauswertung im Produktübersicht-Bericht"><figcaption><p>Gutscheinauswertung im Produktübersicht-Bericht</p></figcaption></figure></div>

| Wert              | Bedeutung                                                                                          |
| ----------------- | -------------------------------------------------------------------------------------------------- |
| *Verkaufte Menge* | Anzahl der Gutscheinzeilen, einschließlich Verkäufen und Einlösungen                               |
| *Zwischensumme*   | Saldo ohne Steuern: Verkäufe positiv, Einlösungen negativ                                          |
| *Gesamtpreis*     | Dieselbe Betrachtung einschließlich Steuern. Die Spalte wird über die Spaltenauswahl eingeblendet. |
| {% endstep %}     |                                                                                                    |
| {% endstepper %}  |                                                                                                    |

### Verkauf und Einlösung unterscheiden

Der Bericht enthält beide Vorgänge mit unterschiedlichem Vorzeichen:

* **Verkauf eines Gutscheins:** positive Zwischensumme auf dem verkauften Gutscheinprodukt.
* **Einlösung eines Gutscheins:** negative Zwischensumme auf dem Belohnungsprodukt des Gutscheinprogramms.

{% hint style="warning" %}
*Die Summe der Zwischensumme je Jahr ist ein Saldo aus Verkäufen und Einlösungen. Sie entspricht nicht dem Gutscheinumsatz. Für den reinen Verkaufsumsatz werden Einlösungen ausgeschlossen.*

***

*Die Spalte „Verkaufte Menge“ zählt auch Einlösungen mit der Menge 1. Die Stückzahl je Jahr liegt deshalb über der Anzahl tatsächlich verkaufter Gutscheine.*
{% endhint %}

### Nur Verkäufe auswerten

Über *Filter -> Filter hinzufügen* wird eine Bedingung auf *Zwischensumme* **größer als** `0` gesetzt. Damit bleiben nur Verkaufszeilen in der Auswertung.

<div data-with-frame="true"><figure><img src="https://2281246901-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FlM26Mg6nljOOOe95HyIq%2Fuploads%2FoZW5zdX1F0ft2EZiZtPs%2Fimage.png?alt=media&amp;token=b2eca340-a838-4453-aec0-a9a925d868a5" alt="Nur Gutscheinverkäufe über den Filter &#x22;Zwischensumme ist größer als 0&#x22; auswerten"><figcaption><p>Nur Gutscheinverkäufe über den Filter "Zwischensumme ist größer als 0" auswerten</p></figcaption></figure></div>

### Nur Einlösungen auswerten

Mit der Bedingung *Zwischensumme* **weniger als** `0` werden nur Einlösungen angezeigt. Der Betrag erscheint negativ und entspricht dem eingelösten Wert.

<div data-with-frame="true"><figure><img src="https://2281246901-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FlM26Mg6nljOOOe95HyIq%2Fuploads%2FC9ez4rC3V9vzOQhep7oX%2Fimage.png?alt=media&amp;token=6f74fbb2-51ff-423a-9715-10db1130f297" alt="Nur Gutscheineinlösungen über den Filter &#x22;Zwischensumme ist weniger als 0&#x22; auswerten"><figcaption><p>Nur Gutscheineinlösungen über den Filter "Zwischensumme ist weniger als 0" auswerten</p></figcaption></figure></div>

### Auswertung als Favorit speichern

Über das Menü **Favoriten -> Aktuelle Suche speichern** kann die Berichtansicht mit den gesetzten Einstellungen (Filtern, Gruppierungen) im Favoriten-Menü gespeichert werden.

<div data-with-frame="true"><figure><img src="https://2281246901-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FlM26Mg6nljOOOe95HyIq%2Fuploads%2FtARWCLlofXgIAsKt0gXm%2Fimage.png?alt=media&amp;token=9bfb180d-0faf-4049-a779-8d45f74739f5" alt="Gutscheinauswertung als Favorit speichern"><figcaption><p>Gutscheinauswertung als Favorit speichern</p></figcaption></figure></div>

Als Name kann beispielsweise **Gutscheinauswertung** verwendet werden. Mit **Standardmäßig verwenden** wird die Auswertung beim Öffnen des Berichts automatisch angezeigt. Mit **Mit allen Benutzern teilen** wird sie weiteren Benutzern bereitgestellt.

Ist Standardmäßig verwenden nicht aktiviert, kann die Auswertung beim Aufrufen des Berichts über das Favoriten-Menü erneut aufgerufen werden.

<div data-with-frame="true"><figure><img src="https://2281246901-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FlM26Mg6nljOOOe95HyIq%2Fuploads%2F1zUdsJ1AfWmybrCfuKie%2Fimage.png?alt=media&amp;token=537f99ca-d25e-4dc7-8403-31772384af83" alt="Gutscheinauswertung über das Favoriten-Menü aufrufen"><figcaption><p>Gutscheinauswertung über das Favoriten-Menü aufrufen</p></figcaption></figure></div>

### Gutscheinauswertung exportieren

Die Auswertung kann über **Aktion -> Exportieren** heruntergeladen werden.

### FAQ Gutscheinauswertung über den Produktübersicht-Bericht

<details>

<summary>Warum erscheinen Zeilen ohne Datum und ohne Menge?</summary>

Ausgewertet werden alle aktiven Produkte. Gutscheinprodukte ohne Verkauf erscheinen ohne Datum und Kennzahlen. Bei der Gruppierung nach Jahr werden diese Zeilen in einer Gruppe ohne Datumsangabe zusammengefasst.

</details>

<details>

<summary>Warum ist die Zwischensumme in einer Zeile negativ?</summary>

Es handelt sich um eine Einlösung. Der eingelöste Betrag wird als negative Zeile auf das Belohnungsprodukt des Gutscheinprogramms gebucht.

</details>

<details>

<summary>Kann aus dem Bericht das offene Gutscheinguthaben abgelesen werden?</summary>

Dies ist nur eingeschränkt möglich. Gutscheine werden häufig in einem anderen Jahr eingelöst. Der Jahressaldo wird dadurch verzerrt.

Das aktuelle Gutscheinguthaben kann [im Gutscheinprogramm, durch Öffnen eines Gutscheins](/kasse/verkaufsoberflache/gutscheine-and-ewallets-rabatt-and-treue/gutscheine-and-ewallets.md) eingesehen werden.

</details>

<details>

<summary>Warum ist die Marge bei Gutscheinen null?</summary>

Die Marge wird aus Verkaufserlös und Einstandswert gebildet. Bei Gutscheinprodukten ist üblicherweise kein Einstandswert hinterlegt. Die Marge entspricht daher dem Erlös oder bleibt ohne Aussagekraft.

</details>

<details>

<summary>Warum sehen zwei Benutzer unterschiedliche Summen?</summary>

Zeilen aus Kassenverkäufen werden nur angezeigt, wenn die zugehörige [Filiale](/schnelles-wissen/funktionen-und-vorgange/filialbetrieb.md) den zugewiesenen Filialen des angemeldeten Benutzers entspricht. Rechnungszeilen sind für alle Benutzer sichtbar.

</details>

## Gutscheinauswertung über die Kassenbelegzeilen

> *Berichte* -> *Kasse* -> *Belegzeilen*

Der Belegzeilen-Bericht listet die einzelnen Positionen aller Kassenbelege. Über einen benutzerdefinierten Filter auf **Gutschein ist gesetzt** werden daraus jene Positionen herausgefiltert, die mit einem Gutschein verknüpft sind — sowohl der Verkauf eines Gutscheins als auch dessen Einlösung.

{% hint style="warning" %}
*Die Gutscheinauswertung über den Belegzeilen-Bericht gibt nur Aufschluss über Gutscheine die in der Kasse erstellt und eingelöst wurden. Um auch Gutscheine aus der Verwaltungsoberfläche und dem Onlineshop auszuwerten, sollte der Produktübersicht-Bericht oder der Automatische Report gewählt werden.*
{% endhint %}

#### Auswertung erstellen

{% stepper %}
{% step %}
**Bericht öffnen**

Der Bericht wird über *Berichte -> Kasse -> Belegzeilen* geöffnet.
{% endstep %}

{% step %}
**Benutzerdefinierten Filter hinzufügen**

Über *Filter -> Benutzerdefinierten Filter hinzufügen* wird im oberen Feld **Gutschein** und im unteren Feld **ist gesetzt** gewählt. Mit **Anwenden** wird der Filter übernommen.

<div data-with-frame="true"><figure><img src="https://2281246901-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FlM26Mg6nljOOOe95HyIq%2Fuploads%2F3wATSQWon5StXIEeuDqI%2Fimage.png?alt=media&amp;token=3a5963c3-e638-4b68-b408-4d795cedf2df" alt="Gutscheinauswertung unter Berichte -> Kassen -> Belegzeilen mit Filter &#x22;Gutschein ist gesetzt&#x22;"><figcaption><p>Gutscheinauswertung unter Berichte -> Kassen -> Belegzeilen mit Filter "Gutschein ist gesetzt"</p></figcaption></figure></div>

Angezeigt werden daraufhin ausschließlich Positionen, die mit einem Gutschein verknüpft sind.
{% endstep %}

{% step %}
**Verkauf und Einlösung trennen**

Verkauf und Einlösung stehen gemeinsam in der Liste und unterscheiden sich am Vorzeichen: Der Verkauf eines Gutscheins erscheint mit positiver **Zwischensumme**, die Einlösung mit negativer.

Zur Trennung wird über **Eine Bedingung hinzufügen** eine zweite Bedingung ergänzt — *Zwischensumme* **größer als** `0` für Verkäufe, *Zwischensumme* **weniger als** `0` für Einlösungen.
{% endstep %}

{% step %}
**Zeitraum einschränken**

Die Belegzeile führt kein eigenes Belegdatum. Eine zeitliche Einschränkung erfolgt daher über eine weitere Bedingung auf **Erstellungsdatum** oder über den Filter **Beleg** auf einen bestimmten Kassenbeleg.
{% endstep %}
{% endstepper %}

## Gutscheinauswertung über automatischen Report

{% hint style="success" %}
*Module: ETRON onRetail v2 Automatische Reports + Gutscheinverwaltung und Kundenpflege*
{% endhint %}

> *Berichte* -> *Dashboards* -> *Eigene Reporte* -> *Reportpläne*\
> *Berichte* -> *Dashboards* -> *Eigene Reporte* -> *Erstellte Reporte*

Der automatische Report **Gutscheinauswertung** stellt die vollständige Saldohistorie aller Gutscheine und Geldbörsen als Excel- oder CSV-Datei bereit. Je Saldoänderung werden Datum, Aktionsart, Betrag sowie Saldo davor und danach ausgegeben. Zusätzlich werden Gutscheincode, Erstelldatum, Saldo bei Erstellung und, sofern eindeutig ermittelbar, die Kassenauftragsreferenz ausgegeben.

Der automatische Report wird über einen [Reportplan ](/verwaltungsoberflache/berichte/dashboards/eigene-reporte.md#report-plane-eigene-reporte-erstellen)eingerichtet. Dieser wird zeitgesteuert im Hintergrund ausgeführt. Die erzeugten Dateien werden unter [Erstellte Reporte](/verwaltungsoberflache/berichte/dashboards/eigene-reporte.md#erstellte-reporte) zum Download abgelegt.

{% hint style="info" %}
*Für das Erstellen von Automatischen Reports wird ein Benutzer mit der **Systemrolle Administrator** und dem **Zugriffsrecht für Berichte** benötigt.*
{% endhint %}

***

### Report für Gutscheinauswertung einrichten

{% stepper %}
{% step %}

#### Reportplan anlegen

Unter [Berichte -> Dashboards -> Eigene Reporte -> Reportpläne](/verwaltungsoberflache/berichte/dashboards/eigene-reporte.md#report-plane-eigene-reporte-erstellen) wird über den Button **Neu** ein Reportplan erstellt.

<div data-with-frame="true"><figure><img src="https://2281246901-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FlM26Mg6nljOOOe95HyIq%2Fuploads%2FtmvKDu9GyAQDdxUUgdVt%2Fimage.png?alt=media&amp;token=7a42cf8b-77b3-4b9b-9cb5-67e104c5cd19" alt="Reportplan unter Berichte -> Dashboards -> Eigene Reporte -> Reportläne anlegen"><figcaption><p>Reportplan unter <strong>Berichte -> Dashboards -> Eigene Reporte -> Reportläne</strong> anlegen</p></figcaption></figure></div>

&#x20;Im Feld **Name der Aktion** wird eine Bezeichnung eingetragen, beispielsweise *Gutscheinauswertung.*

<div data-with-frame="true"><figure><img src="https://2281246901-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FlM26Mg6nljOOOe95HyIq%2Fuploads%2F4vRZ5zGRr05JZ7FDK8V1%2Fimage.png?alt=media&amp;token=5824ded4-4b27-4ecb-83df-b4f0b3b12e45" alt="Reportplan für Gutscheinauswertung"><figcaption><p>Reportplan für Gutscheinauswertung</p></figcaption></figure></div>
{% endstep %}

{% step %}

#### Modell festlegen

Im Feld **Modell** wird *Treuegutschein* ausgewählt. Der Report greift auf die Gutscheine dieses Modells zu.
{% endstep %}

{% step %}

#### Reporttyp wählen

Im Feld **Reporttyp** wird **Excel** oder **CSV** gewählt.

{% hint style="info" %}
*Excel wird empfohlen. Datums- und Betragsspalten werden als Datums- und Zahlenzellen ausgegeben. Sie können unmittelbar sortiert und berechnet werden.*
{% endhint %}
{% endstep %}

{% step %}

#### Report-Code hinterlegen

Im Reiter **Report Code** wird der folgende Code eingefügt:

<details>

<summary>Report-Code für Reportplan Gutscheinauswertung</summary>

Der folgende Code wird im **Reiter Report Code** des Reportplans vollständig eingefügt.

```python
# Voucher Balance Report
# Report code for a report plan. Model: Loyalty Coupon (loyalty.card).
# Fills "headers" and "rows"; the report plan turns them into Excel or CSV.
# One row per balance change of every gift card and eWallet: voucher code,
# creation date, balance at creation, every movement with date, amount,
# resulting balance and the PoS order reference if available.
# Vouchers are ordered by program name, then creation date, then code; the
# movements of one voucher follow chronologically and stay together.

# ----------------------------------------------------------
# Configuration
# ----------------------------------------------------------
filename = 'voucher_report_%s.xlsx' % datetime.datetime.now().strftime('%Y%m%d')

PROGRAM_TYPES = ['gift_card', 'ewallet']
FULL_HISTORY = True
DAYS_BACK = 31
REPORT_TIMEZONE = 'Europe/Vienna'
ORIGIN_TOLERANCE_MINUTES = 120
EXPIRY_TOLERANCE_MINUTES = 5
ISSUE_WINDOW_MINUTES = 5
MATCH_MAX_DAYS = 2
ISSUE_MATCH_WINDOW_MINUTES = 60
AMOUNT_TOLERANCE = 0.005

MOVEMENT_LABELS = {
    'issue': 'Ausgabe',
    'topup': 'Aufladung',
    'redeem': 'Einlösung',
    'expiration': 'Verfall',
}

# ----------------------------------------------------------
# Helper
# ----------------------------------------------------------

utc_zone = timezone('UTC')
local_zone = timezone(REPORT_TIMEZONE)
origin_tolerance = datetime.timedelta(minutes=ORIGIN_TOLERANCE_MINUTES)
expiry_tolerance = datetime.timedelta(minutes=EXPIRY_TOLERANCE_MINUTES)
issue_window = datetime.timedelta(minutes=ISSUE_WINDOW_MINUTES)
match_window = datetime.timedelta(days=MATCH_MAX_DAYS)
issue_match_window = datetime.timedelta(minutes=ISSUE_MATCH_WINDOW_MINUTES)
report_now = datetime.datetime.now()
date_from = report_now - datetime.timedelta(days=DAYS_BACK)


def to_local(value):
    if not value:
        return ''
    local_value = utc_zone.localize(value).astimezone(local_zone)
    return local_value.replace(tzinfo=None)

def to_amount(value):
    return round(value or 0.0, 2)

def to_text(value):
    return '' if value in (False, None) else '%s' % value

def add_origin(origins, card_id, reference, order_date, amount):
    if not reference:
        return
    candidates = origins.setdefault(card_id, {})
    candidate = candidates.get(reference)
    if not candidate:
        candidate = {'date': order_date, 'amounts': []}
        candidates[reference] = candidate
    if not candidate['date']:
        candidate['date'] = order_date
    if amount:
        value = round(abs(amount), 2)
        known = False
        for existing in candidate['amounts']:
            if abs(existing - value) <= AMOUNT_TOLERANCE:
                known = True
                break
        if not known:
            candidate['amounts'].append(value)
            
def assign_references(candidates, movements, skip_index):
    best = {}
    for index in range(len(movements)):
        if index == skip_index:
            continue
        movement = movements[index]
        if movement['type'] == 'expiration':
            continue
        window = match_window
        if movement['type'] == 'issue':
            window = issue_match_window
        target = abs(movement['points_change'] or 0.0)
        moment = movement['date']
        for reference in candidates:
            candidate = candidates[reference]
            candidate_date = candidate['date']
            gap = window
            if candidate_date and moment:
                gap = abs(candidate_date - moment)
                if gap > window:
                    continue
            matched = False
            for value in candidate['amounts']:
                if abs(value - target) <= AMOUNT_TOLERANCE:
                    matched = True
                    break
            if not matched:
                continue
            current = best.get(index)
            if not current or gap < current[0]:
                best[index] = (gap, reference, value)
    claims = []
    for index in best:
        gap, reference, value = best[index]
        claims.append((gap, index, reference, value))
    claims.sort()
    assigned = {}
    taken = {}
    for gap, index, reference, value in claims:
        key = (reference, value)
        if key in taken:
            continue
        taken[key] = index
        assigned[index] = reference
    return assigned


def card_sort_key(card):
    """Order vouchers by program name, then creation date, then code."""
    return (
        (card.program_id.name or '').lower(),
        card.create_date or datetime.datetime.min,
        card.code or '',
    )

# ----------------------------------------------------------
# Source data
# ----------------------------------------------------------

cards = env['loyalty.card'].sudo().search(
    [('program_id.program_type', 'in', PROGRAM_TYPES)]
)
points_field = env['ir.model.fields'].sudo()._get('loyalty.card', 'points')
changes_per_card = {}
if cards and points_field:
    messages = env['mail.message'].sudo().search(
        [
            ('model', '=', 'loyalty.card'),
            ('res_id', 'in', cards.ids),
            ('tracking_value_ids.field', '=', points_field.id),
        ],
        order='date, id',
    )
    for message in messages:
        for tracking_value in message.tracking_value_ids:
            if tracking_value.field.id != points_field.id:
                continue
            changes_per_card.setdefault(message.res_id, []).append(
                {
                    'date': message.date,
                    'points_before': tracking_value.old_value_float,
                    'points_after': tracking_value.new_value_float,
                    'author_id': message.author_id.id,
                }
            )
author_ids = set()
for card_changes in changes_per_card.values():
    for change in card_changes:
        if change['author_id']:
            author_ids.add(change['author_id'])
user_name_per_partner = {}
if author_ids:
    report_users = env['res.users'].sudo().with_context(active_test=False).search(
        [('partner_id', 'in', list(author_ids))]
    )
    for report_user in report_users:
        user_name_per_partner.setdefault(report_user.partner_id.id, report_user.name)
origins_per_card = {}
if cards:
    pos_lines = env['pos.order.line'].sudo().search([('coupon_id', 'in', cards.ids)])
    for pos_line in pos_lines:
        pos_order = pos_line.order_id
        pos_ref = pos_order.pos_reference or pos_order.name
        add_origin(
            origins_per_card,
            pos_line.coupon_id.id,
            pos_ref,
            pos_order.date_order,
            pos_line.points_cost,
        )
        add_origin(
            origins_per_card,
            pos_line.coupon_id.id,
            pos_ref,
            pos_order.date_order,
            pos_line.price_subtotal_incl,
        )
    sale_lines = env['sale.order.line'].sudo().search([('coupon_id', 'in', cards.ids)])
    for sale_line in sale_lines:
        sale_order = sale_line.order_id
        add_origin(
            origins_per_card,
            sale_line.coupon_id.id,
            sale_order.name,
            sale_order.date_order,
            sale_line.points_cost,
        )
        add_origin(
            origins_per_card,
            sale_line.coupon_id.id,
            sale_order.name,
            sale_order.date_order,
            sale_line.price_total,
        )
    if 'sale.order.coupon.points' in env:
        coupon_points = env['sale.order.coupon.points'].sudo().search(
            [('coupon_id', 'in', cards.ids)]
        )
        for coupon_point in coupon_points:
            add_origin(
                origins_per_card,
                coupon_point.coupon_id.id,
                coupon_point.order_id.name,
                coupon_point.order_id.date_order,
                coupon_point.points,
            )
expiries_per_card = {}
if cards and 'onr_loyalty.expiration_line' in env:
    expiration_lines = env['onr_loyalty.expiration_line'].sudo().search(
        [
            ('loyalty_card_id', 'in', cards.ids),
            ('expired_date', '!=', False),
        ]
    )
    for expiration_line in expiration_lines:
        expiries_per_card.setdefault(expiration_line.loyalty_card_id.id, []).append(
            expiration_line.expired_date
        )

# ----------------------------------------------------------
# Rows
# ----------------------------------------------------------

headers = [
    'Voucher Code',
    'Voucher Created on',
    'Balance at Creation',
    'Program',
    'Program Type',
    'Customer',
    'Company',
    'Currency',
    'Movement Date',
    'Movement Type',
    'Balance Before',
    'Change',
    'Balance After',
    'Order Reference',
    'Responsible',
]
rows = []
for card in sorted(cards, key=card_sort_key):
    card_changes = changes_per_card.get(card.id, [])
    card_origins = origins_per_card.get(card.id, {})
    card_expiries = expiries_per_card.get(card.id, [])

    opening_balance = card.points
    opening_author_id = False
    remaining_changes = card_changes
    if card_changes:
        opening_balance = card_changes[0]['points_before']
        first_change = card_changes[0]
        loaded_on_creation = (
            card.create_date
            and first_change['date']
            and first_change['points_before'] == 0.0
            and abs(first_change['date'] - card.create_date) <= issue_window
        )
        if loaded_on_creation:
            opening_balance = first_change['points_after']
            opening_author_id = first_change['author_id']
            remaining_changes = card_changes[1:]
    opening_user = user_name_per_partner.get(opening_author_id, '')
    if not opening_user:
        opening_user = card.create_uid.name
    movements = [
        {
            'date': card.create_date,
            'type': 'issue',
            'points_before': 0.0,
            'points_change': opening_balance,
            'points_after': opening_balance,
            'reference': '',
            'user': opening_user,
        }
    ]
    for change in remaining_changes:
        points_change = change['points_after'] - change['points_before']
        movement_type = 'topup'
        if points_change < 0:
            movement_type = 'redeem'
            for expired_date in card_expiries:
                if abs(expired_date - change['date']) <= expiry_tolerance:
                    movement_type = 'expiration'
                    break
        movements.append(
            {
                'date': change['date'],
                'type': movement_type,
                'points_before': change['points_before'],
                'points_change': points_change,
                'points_after': change['points_after'],
                'reference': '',
                'user': user_name_per_partner.get(change['author_id'], ''),
            }
        )
    creation_reference = ''
    skip_index = -1
    if card.order_id:
        creation_reference = card.order_id.name
        skip_index = 0
    assigned = assign_references(card_origins, movements, skip_index)
    if not creation_reference:
        creation_reference = assigned.get(0, '')
    source_pos_order = card.source_pos_order_id
    if not creation_reference and source_pos_order and source_pos_order.date_order:
        if card.create_date:
            source_reference = (
                source_pos_order.pos_reference or source_pos_order.name
            )
            if source_reference not in assigned.values():
                if abs(source_pos_order.date_order - card.create_date) <= issue_match_window:
                    creation_reference = source_reference
    movements[0]['reference'] = creation_reference
    for index in range(1, len(movements)):
        movements[index]['reference'] = assigned.get(index, '')
    card_values = [
        to_text(card.code),
        to_local(card.create_date),
        to_amount(opening_balance),
        to_text(card.program_id.name),
        to_text(card.program_id.program_type),
        to_text(card.partner_id.display_name),
        to_text(card.company_id.name),
        to_text(card.currency_id.name),
    ]
    for movement in movements:
        if not FULL_HISTORY:
            if not movement['date'] or movement['date'] < date_from:
                continue
        rows.append(
            card_values
            + [
                to_local(movement['date']),
                MOVEMENT_LABELS.get(movement['type'], movement['type']),
                to_amount(movement['points_before']),
                to_amount(movement['points_change']),
                to_amount(movement['points_after']),
                to_text(movement['reference']),
                to_text(movement['user']),
            ]
        )

```

</details>

{% hint style="info" %}
*Der Dateiname (filename) ist im Code auf die Endung **.xlsx** eingestellt. Bei Reporttyp CSV ist die Endung auf **.csv** zu ändern.*
{% endhint %}
{% endstep %}

{% step %}

#### Ausführungsintervall festlegen

Im Feld **Ausführen alle** wird das Intervall eingetragen, beispielsweise `1` und *Monate*. Im Feld **Nächstes Ausführungsdatum** wird der erste Lauf festgelegt.
{% endstep %}

{% step %}

#### Aufbewahrungsdauer festlegen

Das Feld **Lösche nach Tagen** steuert die Verfügbarkeit unter *Erstellte Reporte*. Voreingestellt sind 30 Tage. Mit dem Wert `0` bleiben die Dateien dauerhaft erhalten.
{% endstep %}

{% step %}

#### Reportplan aktivieren

Der Toggle **Aktiv** wird eingeschaltet und der Reportplan gespeichert. Der Report wird anschließend im hinterlegten Intervall automatisch ausgeführt.

{% hint style="info" %}
*Ohne aktivierten Schalter wird der Reportplan gespeichert, aber nicht automatisch ausgeführt. Der Report kann manuell über den Button* ![Manuell starten](https://2281246901-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FlM26Mg6nljOOOe95HyIq%2Fuploads%2FKIvh2HBUwUAgM0gsYca8%2Fimage.png?alt=media\&token=c67021fb-304e-4849-9501-5e6492478964) *erstellt werden.*
{% endhint %}
{% endstep %}
{% endstepper %}

### Report Gutscheinauswertung öffnen und herunterladen

{% stepper %}
{% step %}

#### Erstellte Reporte öffnen

Unter [Berichte -> Dashboards -> Eigene Reporte -> Erstellte Reporte](/verwaltungsoberflache/berichte/dashboards/eigene-reporte.md#erstellte-reporte) werden alle erzeugten Reporte aufgelistet. Durch Auswählen eines Reports wird dieser geöffnet.

<div data-with-frame="true"><figure><img src="https://2281246901-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FlM26Mg6nljOOOe95HyIq%2Fuploads%2FhYcmGh7ea10INZcPE4Sc%2Fimage.png?alt=media&amp;token=8dbf243b-f89b-4d55-951d-8976826b296a" alt=""><figcaption><p>Erstellte Reporte unter <strong>Berichte -> Dashboards -> Eigene Reporte -> Erstellte Reporte</strong></p></figcaption></figure></div>
{% endstep %}

{% step %}

#### Datei herunterladen

Über das Datei-Symbol im Feld **Report** wird die **Datei heruntergeladen**. Der Dateiname enthält das Erstelldatum im Format `voucher_report_JJJJMMTT.xlsx`.

<div data-with-frame="true"><figure><img src="https://2281246901-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FlM26Mg6nljOOOe95HyIq%2Fuploads%2FJWJjoNWcy3H9vTgmZsF8%2Fimage.png?alt=media&amp;token=98bd0cc9-8165-4a4c-b1d4-f9354528dfde" alt=""><figcaption></figcaption></figure></div>
{% endstep %}

{% step %}

#### Auswertung in Excel

Die Datei enthält eine Tabelle mit Kopfzeile.&#x20;

Die Zeilen werden nach Programmname, danach nach Erstelldatum und Gutscheincode sortiert. Die Bewegungen eines Gutscheins folgen chronologisch.

Programm, Erstelldatum und Gutscheincode werden in jeder Zeile wiederholt. Dadurch bleiben Gutscheindaten bei Sortierung und Filterung zusammen auswertbar.

<div data-with-frame="true"><figure><img src="https://2281246901-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FlM26Mg6nljOOOe95HyIq%2Fuploads%2Fsy2d9pIiLiVRSRVo8IxI%2Fimage.png?alt=media&amp;token=b60bc415-fc3d-47a3-938c-af173a204f6d" alt=""><figcaption><p>Erstellter Report für Gutscheinauswertung</p></figcaption></figure></div>

<details>

<summary>Aufbau der Auswertung</summary>

**Spalten im Bericht:**

| Spalte                | Bedeutung                                                                   |
| --------------------- | --------------------------------------------------------------------------- |
| `Voucher Code`        | Gutscheincode                                                               |
| `Voucher Created on`  | Zeitpunkt der Gutscheinerstellung                                           |
| `Balance at Creation` | Saldo zum Erstellzeitpunkt                                                  |
| `Program`             | Name des Gutschein- oder eWallet-Programms                                  |
| `Program Type`        | `gift_card` für Gutscheinkarten, `ewallet` für eWallets                     |
| `Customer`            | Zugeordneter Kontakt, sofern der Gutschein auf einen Kunden ausgestellt ist |
| `Company`             | Unternehmen, dem das Programm zugeordnet ist                                |
| `Currency`            | Währung des Programms                                                       |
| `Movement Date`       | Zeitpunkt der Saldoänderung                                                 |
| `Movement Type`       | Ausgabe, Aufladung, Einlösung oder Verfall                                  |
| `Balance Before`      | Saldo vor der Bewegung                                                      |
| `Change`              | Veränderung, negativ bei Einlösung und Verfall                              |
| `Balance After`       | Saldo nach der Bewegung                                                     |
| `Order Reference`     | Kassenauftragsreferenz oder Verkaufsauftrag, sofern eindeutig zuordenbar    |
| `Responsible`         | Benutzer, auf den die Buchung zurückgeht                                    |

</details>
{% endstep %}
{% endstepper %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://helpcenter.etron.info/schnelles-wissen/funktionen-und-vorgange/gutscheinauswertung-bericht.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
