From a693fe43c163416a4b6b3134df57ba8aa78f6a3d 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 | 268 +++++++++++++++++++++++++++++++++--------- tests/data/retro.json | 28 +++++ 3 files changed, 245 insertions(+), 54 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..a893182 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,174 @@ 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(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", 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, *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, + showBoundary=1, + ) + 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, + showBoundary=1, + ) + + 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, + showBoundary=1, + ) + + 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), + ] + + 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) + + 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