From 6eab116bba9a778985364665fe06a62eb91cfa8f Mon Sep 17 00:00:00 2001
From: Stefan Beck <stefan.beck@hrz.uni-giessen.de>
Date: Mon, 18 Nov 2024 10:36:03 +0100
Subject: [PATCH] Bestellschein Retrokarte
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Erzeugt einen Bestellschein für eine Retrokarte.
---
 src/app.py            |   3 +
 src/pdf.py            | 267 +++++++++++++++++++++++++++++++++---------
 tests/data/retro.json |  28 +++++
 3 files changed, 245 insertions(+), 53 deletions(-)
 create mode 100644 tests/data/retro.json

diff --git a/src/app.py b/src/app.py
index 8dc1775..7c56c11 100644
--- a/src/app.py
+++ b/src/app.py
@@ -7,6 +7,7 @@ from flask import request
 
 from .pdf import reservation_order_a4
 from .pdf import reservation_bon
+from .pdf import retro_a4
 
 # Wir arbeiten hier mit Handlern und filtern um ein einigermaßen gutes Logging für den Container zu bekommen.
 # Die ist einfach: Für das Loglevel INFO gehen wir nach stdout, höheres nach stderr. Daher filtern wir beim Handler für Level INFO.
@@ -43,6 +44,8 @@ def make_pdf():
         logger.info(print_request)
     if print_group_name == "Phil II Vormerkungen":
         func = reservation_bon
+    elif print_group_name == "retro":
+        func = retro_a4
     # Die restlichen Zettel sehen alle gleich aus, daher diese "unsaubere" Lösung.
     else:
         func = reservation_order_a4
diff --git a/src/pdf.py b/src/pdf.py
index f9413e1..573ff00 100644
--- a/src/pdf.py
+++ b/src/pdf.py
@@ -1,8 +1,10 @@
+from base64 import b64decode
+
 import barcode
 import os
 import unicodedata
 
-from datetime import timedelta, datetime, date
+from datetime import timedelta, datetime, date, timezone
 from io import BytesIO
 
 from reportlab.lib.enums import TA_CENTER
@@ -47,15 +49,16 @@ def bestelldatum(data: dict) -> str:
     """
     Formatiert das Bestelldatum mit Uhrzeit sinnvoll menschenlesbar.
 
+    Bei einem Datum ohne Zeitzone nehmen wir UTC an.
     Als Bonus wird auch die Zeitzone korrekt berechnet.
     :param data:
     :return: bestelldatum (str)
     """
-    value = (
-        datetime.fromisoformat(f"{data['requestDate']}+00:00")
-        .astimezone()
-        .strftime("%Y-%m-%d %H:%M:%S %Z")
-    )
+    request_date = datetime.fromisoformat(data["requestDate"])
+    # Datum ohne Zeitzone ist wahrscheinlich UTC.
+    if not request_date.tzinfo:
+        request_date = request_date.replace(tzinfo=timezone.utc)
+    value = request_date.astimezone().strftime("%Y-%m-%d %H:%M %Z")
     return value
 
 
@@ -70,6 +73,47 @@ def print_ppa_group(data: dict) -> str:
     return ""
 
 
+def pre_story_to_story(pre_story: list, style: ParagraphStyle) -> list:
+    """
+    Erstellt aus Sting-Elementen einen Paragraph mit Stil.
+
+    Nicht-Strings werden übernommen, wie sie sind.
+    Möchte man einen anderen ParagraphStyle für den Paragraphen, muss man diesen so abliefern.
+    :param pre_story:
+    :param style:
+    :return:
+    """
+    story = [
+        (
+            Paragraph(unicodedata.normalize("NFC", line), style=style)
+            if isinstance(line, str)
+            else line
+        )
+        for line in pre_story
+    ]
+    return story
+
+
+def watermark(canvas, page_width, page_height, font_size):
+    """
+    Fügt einem canvas ein Wasserzeichen in der vorgegebenen Schriftgröße hinzu.
+
+    Für die Platzierung wird Seitenbreite- und höhe genutzt. Das Wasserzeichen erscheint in der oberen Hälfte.
+    :param canvas:
+    :param page_width:
+    :param page_height:
+    :param font_size:
+    :return:
+    """
+    canvas.setFillColor(colors.grey, alpha=0.4)
+    canvas.setFont("dejavu-mono", font_size)
+    # Startkoordinaten verschieben (oberes Viertel), anschließend dort drehen
+    canvas.translate(page_width / 2, 3 * page_height / 4)
+    canvas.rotate(45)
+    canvas.drawCentredString(0 * mm, 0 * mm, "Testsystem")
+    canvas.rotate(-45)
+
+
 # Definition der Schrift
 registerFont(TTFont("dejavu-mono", "DejaVuSansMono.ttf"))
 registerFont(TTFont("dejavu-mono-bold", "DejaVuSansMono-Bold.ttf"))
@@ -80,6 +124,15 @@ registerFontFamily(
     italic="",
     boldItalic="",
 )
+registerFont(TTFont("dejavu-sans", "DejaVuSans.ttf"))
+registerFont(TTFont("dejavu-sans-bold", "DejaVuSans-Bold.ttf"))
+registerFontFamily(
+    "dejavu-sans",
+    normal="dejavu-sans",
+    bold="dejavu-sans-bold",
+    italic="",
+    boldItalic="",
+)
 
 
 def reservation_order_a4(data: dict) -> BytesIO:
@@ -189,25 +242,11 @@ def reservation_order_a4(data: dict) -> BytesIO:
         ),
         f"Auslage bis: <b>{auslage_bis(data)}</b>",
     ]
-    story = [
-        (
-            Paragraph(unicodedata.normalize("NFC", line), style=default_style)
-            if isinstance(line, str)
-            else line
-        )
-        for line in pre_story
-    ]
-
-    frame.addFromList(story, canvas)
+    frame.addFromList(pre_story_to_story(pre_story, default_style), canvas)
 
     # Handelt es sich um das Testsystem, fügen wir ein Wasserzeichen ein
     if os.environ.get("TESTSYSTEM"):
-        canvas.setFillColor(colors.grey, alpha=0.4)
-        canvas.setFont("dejavu-mono", 70)
-        # Startkoordinaten verschieben (oberes Viertel), anschließend dort drehen
-        canvas.translate(A4[0] / 2, 3 * A4[1] / 4)
-        canvas.rotate(45)
-        canvas.drawCentredString(0 * mm, 0 * mm, "Testsystem")
+        watermark(canvas, *A4, 70)
 
     canvas.save()
 
@@ -257,53 +296,175 @@ def reservation_bon(data: dict) -> BytesIO:
         data["itemBarcode"], writer=barcode.writer.ImageWriter(), add_checksum=False
     ).write(volume_barcode, text="")
 
-    story = [
+    pre_story = [
         Paragraph("Bibliothekssystem", style=center_style),
         Paragraph("JLU Gießen", style=center_style),
         Spacer(0, 3 * spacer_size),
-        Paragraph("Zweigbibliothek Philosophikum II", style=default_style),
-        Paragraph("Karl-Glöckner-Straße 21 F", style=default_style),
-        Paragraph("35394 Gießen", style=default_style),
+        "Zweigbibliothek Philosophikum II",
+        "Karl-Glöckner-Straße 21 F" "35394 Gießen",
         Spacer(0, 3 * spacer_size),
-        # Paragraph("Signatur", style=default_style),
-        Paragraph(
-            f'<b><font size="12">{data["itemCallNumber"]}</font></b>',
-            style=default_style,
-        ),
+        f'<b><font size="12">{data["itemCallNumber"]}</font></b>',
         Spacer(0, 4 * spacer_size),
-        Paragraph(
-            unicodedata.normalize("NFC", escape(data.get("instanceContributorName", "-")))[:32],
-            style=default_style,
-        ),
-        Paragraph(
-            unicodedata.normalize("NFC", escape(data.get("instanceTitle", "-")))[: 2 * 32],
-            style=default_style,
-        ),
+        escape(data.get("instanceContributorName", "-")[:32]),
+        escape(data.get("instanceTitle", "-")[: 2 * 32]),
         Spacer(0, 3 * spacer_size),
         Image(volume_barcode, width=72 * mm, height=25 * mm, kind="proportional"),
         Paragraph(data["itemBarcode"], style=center_style),
         Spacer(0, 3 * spacer_size),
-        Paragraph(
-            f'Auslage bis: <b><font size="12">{auslage_bis(data)}</font></b>',
-            style=default_style,
-        ),
+        f'Auslage bis: <b><font size="12">{auslage_bis(data)}</font></b>',
         Spacer(0, 3 * spacer_size),
-        Paragraph(
-            f'Benutzernummer: xxxx<b><font size="12">{data.get("requesterBarcode", "")[-4:]}</font></b> {print_ppa_group(data)}',
-            style=default_style,
+        f'Benutzernummer: xxxx<b><font size="12">{data.get("requesterBarcode", "")[-4:]}</font></b> {print_ppa_group(data)}',
+    ]
+
+    frame.addFromList(pre_story_to_story(pre_story, default_style), canvas)
+
+    # Handelt es sich um das Testsystem, fügen wir ein Wasserzeichen ein
+    if os.environ.get("TESTSYSTEM"):
+        watermark(canvas, *pagesize, 30)
+
+    canvas.save()
+    return buffer
+
+
+def retro_a4(data: dict) -> BytesIO:
+    """
+    Erzeugt einen Retro-Bestellzettel.
+
+    :param data:
+    :return:
+    """
+    buffer = BytesIO()
+    canvas = Canvas(buffer)
+    canvas.line(0, A4[1] / 2, A4[0], A4[1] / 2)
+
+    font_name = "dejavu-sans"
+    font_size = 10
+    spacer_size = 0.75 * font_size
+
+    default_style = ParagraphStyle(
+        "default",
+        fontName=font_name,
+        fontSize=font_size,
+        bulletFontName=font_name,
+        spaceBefore=1,
+        justifyLastLine=1,
+        justifyBreak=1,
+    )
+    center_style = ParagraphStyle(
+        "default",
+        fontName=font_name,
+        fontSize=font_size,
+        spaceBefore=1,
+        justifyLastLine=1,
+        justifyBreak=1,
+        alignment=TA_CENTER,
+    )
+
+    frame_left = Frame(
+        0,
+        A4[1] / 2,
+        50 * mm,
+        A4[1] / 2 - 20 * mm,
+        leftPadding=5 * mm,
+        topPadding=5 * mm,
+        bottomPadding=5 * mm,
+        rightPadding=0 * mm,
+    )
+    frame_middle = Frame(
+        50 * mm,
+        A4[1] / 2,
+        A4[0] - 100 * mm,
+        A4[1] / 2 - 5 * mm,
+        leftPadding=5 * mm,
+        topPadding=5 * mm,
+        bottomPadding=5 * mm,
+        rightPadding=5 * mm,
+    )
+
+    frame_right = Frame(
+        A4[0] - 50 * mm,
+        A4[1] / 2,
+        50 * mm,
+        A4[1] / 2 - 20 * mm,
+        leftPadding=0 * mm,
+        topPadding=5 * mm,
+        bottomPadding=5 * mm,
+        rightPadding=5 * mm,
+    )
+
+    story_left = [
+        "Besteller/in",
+        f"<b>{data['requesterLastName']},</b>",
+        f"<b>{data.get('requesterFirstName', '-')}</b>",
+        f"<font size=\"8\">{escape(data.get('requesterAddressLine1', ''))}</font>",
+        f"<font size=\"8\">{escape(data.get('requesterAddressLine2', '-'))}</font>",
+        Spacer(0, spacer_size),
+        "Lieferung an",
+        f"{data.get('customFieldLieferungAn', '-')}",
+        Spacer(0, spacer_size),
+        "Nutzergruppe",
+        f"{data.get('requesterPatronGroupName')}",
+        Spacer(0, spacer_size),
+        "Nutzernummer",
+        f"<b>{data.get('requesterBarcode', '-')}</b>",
+        Spacer(0, spacer_size),
+        "Theke",
+        f"<b>{data['pickupServicePointName']}</b>",
+        Spacer(0, spacer_size),
+        "Bestelldatum",
+        f"{bestelldatum(data)}",
+        Spacer(0, spacer_size),
+        "Auslage bis",
+        f"<b>{auslage_bis(data)}</b>",
+    ]
+
+    story_middle = [
+        Paragraph('<font size="16"><b>Bestellschein</b></font>', style=center_style),
+        Spacer(0, 2 * spacer_size),
+        Image(
+            BytesIO(b64decode(data.get("itemRetroCard"))),
+            width=A4[0] - 100 * mm,
+            height=500 * mm,
+            kind="proportional",
         ),
+        Spacer(0, 2 * spacer_size),
+        Paragraph(f"hebis Retro ID: <b>{data['itemRetroId']}</b>", style=center_style),
     ]
 
-    frame.addFromList(story, canvas)
+    standorte = [
+        "Freihandbereich / Lesesaal",
+        "Zweigbibliothek ZNL",
+        "Zweigbibliothek Re/Wi",
+        "Zweigbibliothek Zeughaus",
+        "Zweigbibliothek Phil II",
+        "dezentrale Fachbibliothek",
+    ]
+
+    def vorhanden_in():
+        for item in standorte:
+            yield Spacer(0, spacer_size)
+            yield Paragraph(
+                f'<font size="8"><bullet>\u25cb</bullet> {item}</font>',
+                style=default_style,
+            )
+
+    story_right = [
+        "<b>Universitätsbibliothek</b>",
+        "<b>Otto-Behagel-Str. 8</b>",
+        "<b>34394 Gießen</b>",
+        Spacer(0, spacer_size),
+        "Vorhanden",
+        *vorhanden_in(),
+    ]
+
+    frame_middle.addFromList(pre_story_to_story(story_middle, default_style), canvas)
+    frame_left.addFromList(pre_story_to_story(story_left, default_style), canvas)
+    frame_right.addFromList(pre_story_to_story(story_right, default_style), canvas)
 
     # Handelt es sich um das Testsystem, fügen wir ein Wasserzeichen ein
     if os.environ.get("TESTSYSTEM"):
-        canvas.setFillColor(colors.grey, alpha=0.4)
-        canvas.setFont("dejavu-mono", 30)
-        # Startkoordinaten verschieben (oberes Viertel), anschließend dort drehen
-        canvas.translate(72 * mm / 2, 3 * 150 * mm / 4)
-        canvas.rotate(45)
-        canvas.drawCentredString(0 * mm, 0 * mm, "Testsystem")
+        watermark(canvas, *A4, 70)
 
     canvas.save()
+
     return buffer
diff --git a/tests/data/retro.json b/tests/data/retro.json
new file mode 100644
index 0000000..d729684
--- /dev/null
+++ b/tests/data/retro.json
@@ -0,0 +1,28 @@
+[
+  {
+    "id": "16061ec0-7bb0-4dd9-a8d8-477e907e1e5b",
+    "successfully_printed": false,
+    "external_id": "e19a6738-3740-4f23-af30-e417772708d7",
+    "origin": "retro",
+    "print_group_name": "retro",
+    "data": {
+      "requestId": "b84a1e01-7e5b-4de5-95ef-d13288e42aa8",
+      "requestDate": "2024-11-12 11:55:10.457197+00:00",
+      "itemIssue": "",
+      "itemRetroId": "g6580146",
+      "itemRetroCard": "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",
+      "itemMaterialTypeName": "Retro",
+      "pickupServicePointName": "Ausleihe/UB",
+      "requesterBarcode": "00099997",
+      "requesterPatronGroupId": "9e7a5f7d-bf8a-408f-a02e-f0f5576a8214",
+      "requesterPatronGroupName": "UB-Mitarbeiter",
+      "requesterFirstName": "Testnutzer",
+      "requesterLastName": "HRZ-Administrator",
+      "requesterAddressLine1": "zH Stefan",
+      "requesterAddressLine2": "Heinrich-Buff-Ring 44",
+      "requesterAddressPostalCode": "35392",
+      "requesterAddressCity": "Gießen",
+      "customFieldsLieferungAn": "ZSK000"
+    }
+  }
+]
\ No newline at end of file
-- 
GitLab