Skip to content

Latest commit

 

History

History
1352 lines (1124 loc) · 64.6 KB

File metadata and controls

1352 lines (1124 loc) · 64.6 KB

Bereinigung von mehrfach-Holdings

Einrückungen beim Export

Wenn org-src-preserve-indentation gesetzt ist, wird bei org-babel-tangle mit den Einrückungen exportiert, die in der org-Datei sind. Das macht die Python-Datei natürlich unbrauchbar. Daher:

(setq org-src-preserve-indentation nil)

Ausgangslage und Ziel

Bei der Datenvorbereitung vor der Migration konnten nicht alle Exemplare einem Holding zugeordnet werden, weil in Aleph kein HOL vorhanden war. Infolgedessen gibt es Titeldatensätze mit bis zu 678 Holdings mit je einem Item.

Auswertung (Aleph) der DHB-Datensätze ohne Holding

Es handelt sich um insgesamt 41639 Exemplare an 291 AC-Sätzen und 2394 LB-Sätzen.

Ziel ist es, die Exemplare an diesen mehrfach-HOLs an ein einzelnes Holding zu hängen und die überschüssigen HOLs zu löschen. Dies sollte in einem halbautomatischen Prozess passieren (wenn es vollautomatisch ginge, hätten wir das wohl schon vorher in Aleph gemacht), weil eine manuelle Bereinigung nicht leistbar ist.

Angestrebter Workflow

Der angestrebte Workflow ist implementiert: Siehe die Anleitung für BearbeiterInnen.

Eine Bearbeiterin stößt auf einen der betroffenen Datensätze und will ihn bereinigen. Dazu sollte sie die folgenden Schritte ausführen müssen:

  1. Das Holding anlegen, an dem die Items im Endeffekt hängen sollen. Wichtig ist, dass zumindest Bibliothek, Standort und Grundsignatur eingetragen werden, weil über diese ein Abgleich stattfindet. Wenn notwendig mehrere HOLs (für Indexe, neue folgen, etc.).
  2. Das Bereinigungsprogramm aufrufen und folgende Daten eingeben:
    • MMS-ID des Titeldatensatzes (*3339)
    • MMS-ID des Zielholdings
  3. Nach Ausführung kontrollieren, ob Fehler aufgetreten sind und diese manuell bereinigen.

Technischer Ablauf

Im Endeffekt sollen alle Items an ihren richtigen Holdings hängen. Dazu wird aus dem Zielholding die Grundsignatur ausgelesen. Nur Exemplare, bei denen Bibliothek, Standort und Grundsignatur mit dem Holding übereinstimmen, sollten an das jeweilige Holding gehängt werden.

Dabei wird folgendermaßen vorgegangen:

  1. Die Benutzerin/der Benutzer übergibt dem Programm die (lokale) MMS-ID des bibliografischen Datensätzes und die MMS-ID des Zielholdings (Feststellen, welche Datensätze bearbeitet werden sollen und ein paar Daten auslesen).
  2. Es werden alle Exemplare an einem Titeldatensatz via API von Alma geholt (Items holen).
  3. Über einen Abgleich mit dem Zielholding werden die Exemplare herausgefiltert, bei denen Bibliothek und Standort übereinstimmen und deren Signatur mit der Signatur des Zielholdings beginnt. (Items holen)
  4. Alle sich qualifizierenden Items werden vor der weiteren Bearbeitung lokal gesichert. (Sicherungen machen)
  5. Nun werden an jedem einzelnen Item folgende Änderungen vorgenommen (Änderungen an den Items machen):
    • Die Signatur aus dem Holding wird ausgelesen und im Zuge der Verarbeitung insofern vereinheitlicht, dass I 12345, 1 in I 12345/1 umgewandelt wird.
    • Wenn keine alternative Signatur vorhanden ist, wird die Signatur aus dem Holding in die Alternative Signatur geschrieben. Das ist notwendig, weil das Holding ja dann gelöscht wird.
    • Wenn eine alternative Signatur vorhanden ist, wird die Signatur aus dem Holding nach “ ; ” (Leerzeichen, Semikolon, Leerzeichen) an die alternative Signatur angefügt.
    • Die Exemplarrichtlinie wird gelöscht.
  6. Danach werden die Exemplare an das Zielholding verschoben (Items umhängen und Holdings löschen):
    • Das Exemplar wird am Quellholding gelöscht. Durch den Parameter holdings: delete wird das Holding gelöscht. Sollte das nicht funktionieren, wird eine Fehlermeldung ausgegeben und ins Logfile geschrieben.
    • Das Exemplar wird am Zielholding angehängt.
  7. Verarbeitung abgeschlossen.

Alle Vorgänge werden in einem Logfile (Logging) dokumentiert. Nachdem ein Programmlauf sehr lange dauern kann (~45 min für ca. 250 Exemplare in der Sandbox), werden auch Informationen auf stdout geschrieben, sodass die Benutzerinnen und Benutzer sehen, dass das Programm noch läuft/arbeitet.

Mögliche Probleme
  • Für die Bearbeiterin
    • Bei großen Anzahlen an Holdings ist es nicht leicht feststellbar, ob mehrere Zielholdings (für neue Folgen, Indizes etc.) angelegt werden müssen. Das lässt sich nicht verhindern, die Praxis wird zeigen, ob es da gute Workflows gibt.
  • Für das Programm
    • Sind Bestellungen mit einem der vorhandenen HOLs verknüpft oder ist ein Exemplar entlehnt? ⇒ Es wird eine Fehlermeldung ausgegeben und ins Log geschrieben. Das betroffene HOL/Exemplar wird übersprungen und muss manuell bearbeitet werden. Die Verarbeitung wird mit dem nächsten HOL/Exemplar fortgesetzt.
    • Verschiedene Grundsignaturen können mit demselben String anfangen: I 123/1 und I 123/N.F.,1. ⇒ Wenn die Signaturen in der richtigen Reihenfolge (beginnend mit dem kürzesten gemeinsamen Substring) abgearbeitet werden, funktioniert alles. Siehe Mehrere Grundsignaturen an einem bibliografischen Datensatz.

Zur Lesbarkeit des Codes

Dieses Programm wird im Literate Programming-Verfahren geschrieben. D. h. ich schreibe alles in der Datei multi-hol.org. Alles heißt tatsächlich alles: Die Dokumentation, die Sie gerade lesen, das Benutzerhandbuch und der tatsächliche Programmcode. Alle Dateien deren Namen mit .py enden (also die tatsächlichen Scripts), werden automatisch aus diesem Text extrahiert. Wie gut diese Programmdateien lesbar sind weiß ich nicht, weil ich sie eigentlich nie direkt ansehe. Ich bitte daher, stilistische Schwächen in diesen zu verzeihen.

Skript

Allgemeine Vorbereitungen

Dieses Script benötigt Python 3.6 oder höher.

Python Virtual environment

Damit immer die richtigen Versionen des Interpreters und der Module verwendet werden, erstellen wir eine Virtual Environment. Dazu führen wir in der Shell folgendes aus:

# Die virtuelle Umgebung erstellen
python -m venv ~/.venvs/multi-hol

# Die virtuelle Umgebung aktivieren
source ~/.venvs/multi-hol/bin/activate

Unter Windows (mit PowerShell) schaut das so aus:

# Die virtuelle Umgebung erstellen
python -m venv ~/.venvs/multi-hol

# Die virtuelle Umgebung aktivieren
~/.venvs/multi-hol/Scripts/Activate.ps1
Imports etc.

Als erstes importieren wir verschiedenen Module, die wir brauchen:

import sys
import re
import os
import datetime
from requests import Session
import urllib.parse
import xml.etree.ElementTree as ET
import json
from time import sleep
from easygui import multenterbox
import logging
import logging.config
import getpass
from .conf import config
from multi_hol import __version__ as version
sys
um Kommandozeilenargumente entgegenzunehmen (sys.argv) oder die Ausführung abzubrechen (sys.exit)
os
Verzeichnisse anlegen, Dateien löschen, etc.
requests.Session
vereinfacht die API-Calls, indem man die Header nicht immer eingeben muss, etc. Achtung: Dieses Modul gehört nicht zur Standardbibliothek und muss erst via pip installiert werden.
urllib.parse
wird verwendet, um Strings, die als Teil des URL verwendet werden, richtig zu codieren
xml.etree.ElementTree
Nachdem wir nicht viel brauchen, ist es einfacher XML zu parsen als die Holdings in pymarc zu lesen, oder in JSON umzuwandeln.
json
Wir bekommen von der Alma Item-Objekte als JSON. Mit dieser Bibliothek lassen sich JSON-Daten gut manipulieren.
time.sleep
um zwischen Löschen am Quell- und Posten am Zielholding zu warten
easygui.multenterbox
um von der Benutzerin die MMS-ID von Bibsatz und Zielholding zu bekommen
logging
um zu loggen
getpass
damit wir fürs loggen den Usernamen abfragen können
.conf
lokale Konfiguration in der Datei conf.py. Aus dieser Datei stehen Konstanten im dictionary config. Von dort holen wir uns den API_KEY und das Arbeitsverzeichnis. Nachdem man dort den API_KEY hineinschreibt, sollte diese Datei in .gitignore stehen. Wie sie

aussehen sollte können Sie im Abschnitt Beispiel für conf.py sehen.

Logging
  • State “DONE” from “TODO” [2019-01-04 Fr 13:58]
Falls etwas danebengeht, wollen wir genau wissen, was passiert ist. Daher loggen wir alles mit, was passiert. Fast alles – nachdem wir für den Dateinamen die MMS-IDs brauchen holen wir uns selbige schon, bevor wir den logger konfigurieren (MMS-IDs).

Im Log-File werden so gut wie möglich alle Vorgänge aufgezeichnet. Um die Ausgabe für die BenutzerInnen übersichtlich zu halten, gehen nur INFO-Level-Ereignisse an stdout.

def logging_setup(bib_mms, target_hol_id):
    log_file = os.path.join(config["WORKING_DIR"], "log", f"{bib_mms}_{target_hol_id}.log")
    # logging.basicConfig(level=logging.DEBUG,
    #                     format='%(asctime)s - %(levelname)s - %(message)s',
    #                     handlers=[logging.FileHandler(log_file),
    #                               logging.StreamHandler()])

    logging.config.dictConfig({
        'version': 1,
        'disable_existing_loggers': False,
        'formatters': {
            'logfile_formatter': {
                'format': '%(asctime)s %(levelname)s %(message)s',
            },
            'stderr_formatter': {
                'format' : '%(levelname)s %(message)s',
            },
        },
        'handlers': {
            'stderr': {
                'class': 'logging.StreamHandler',
                'formatter': 'stderr_formatter',
                'level': 'INFO',
            },
            'log_file': {
                'class': 'logging.FileHandler',
                'filename': log_file,
                'mode': 'a',
                'formatter': 'logfile_formatter',
                'level': 'DEBUG',
            },
        },
        'loggers': {
            '': {
                'level': 'DEBUG',
                'handlers': ['stderr', 'log_file'],
            },
        },
    })
Voreinstellungen für die APIs

Nachdem wir viele Calls machen werden, ist es wohl gut, die APIs in Variablen mit benannten Platzhaltern zu schreiben, sodass wir dann nur noch die jeweiligen IDs einfüllen müssen:

# api-url-templates
base_url = 'https://api-eu.hosted.exlibrisgroup.com/almaws/v1'
barcode_api = base_url + "/items?item_barcode={barcode}"
holdings_api = base_url + "/bibs/{mms_id}/holdings"
bib_api = base_url + "/bibs/{mms_id}"
item_api = base_url + "/bibs/{mms_id}/holdings/{holding_id}/items"
Session, Authentifizierung

Damit wir nicht bei jedem Aufruf die Header übergeben müssen, ist es praktisch, dass die requests-Bibliothek ein Session-Objekt hat.

# session um immer gleiche header zu schicken etc.
session = Session()
session.headers.update({
    "accept": "application/json",
    "authorization": f"apikey {API_KEY}",
    "User-Agent": f"multi-hol/{version}",
})

Der API-Key wird in conf.py abgelegt. Nachd dem Import dieser Datei können wir ihn hier zuweisen:

API_KEY = config["API_KEY"]

Verarbeitung

Feststellen, welche Datensätze bearbeitet werden sollen und ein paar Daten auslesen
  • State “DONE” from “TODO” [2018-12-10 Mo 16:44]
Um zu wissen, an welchen Datensätzen gearbeitet werden soll, muss die Bearbeiterin die MMS-IDs vom Bibsatz und dem Zielolding eingeben.

Nachdem Whitespace vorne und hinten entfern wurde, sollte folgendes überprüft werden:

  • [X] Beginnt die bib-mms mit 99?
  • [X] Beginnt die hol-mms mit 22?
  • [X] Endet die bib-mms auf 3339?
def get_mmsids(msg=""):
    """Return the MMS-IDs of the bibrecord and the target-holding."""

    if msg == "":
        msg =  "Bitte folgende Daten eingeben."
    else:
        msg = msg

    bib_mms, target_hol_id = multenterbox(msg=msg,
                                           title="Multi-HOL-Bereinigung",
                                           fields=["MMS-ID des Bibsatzes", "MMS-ID des Zielholdings"])
    # check the input
    if (not bib_mms.startswith("99")
            or not bib_mms.endswith("3339")
            or not target_hol_id.startswith("22")):
        msg = """*** Formaler Fehler in der Eingabe ***

    1. Die MMS-ID des Bibsatzes muss mit "99" beginnen
    2. Die MMS-ID des Bibsatzes muss mit "3339" enden
    3. Die MMS-ID des HOL-Satzes muss mit "22" beginnen
"""
        get_mmsids(msg)
    else:
        return bib_mms, target_hol_id
Items holen
  • State “DONE” from “TODO” [2018-07-30 Mon 13:54]
Nachdem die Bearbeiterin uns mit den Identifiern versorgt hat, holen wir uns die Item-Liste. Nachdem die API per default nur zehn Items liefert, setzen wir das Limit auf die höchstzahl (100). Sollten mehr als 100 Exemplare vorhanden sein, machen wir mehrere API-Aufrufe mit entsprechendem Offset.

Dazu verwenden wir eine Funktion, die die MMS-IDs des Bibsatzes und eine Liste von Item-Objekten zurückgibt.

def get_items(mms_id, target_hol_id):
    mms_id = mms_id
    outlist = []
    hol_bch = get_bch(mms_id, target_hol_id)

    # get the item-list from Alma
    item_list = session.get(item_api.format(mms_id=mms_id, holding_id="ALL"),
                            params={"limit": "100"})

    # DONE check response
    if item_list.status_code == 200:
        item_list = item_list.json()
    else:
        logging.error(f"Fehler beim Holen der Daten: {item_list.text}")
        input("Drücken Sie ENTER um das Programm zu beenden.")
        sys.exit(1)

    # append the items to the list to be returned, if they pass the tests
    logging.debug("get_items(): Items zur outlist hinzufügen")
    for item in item_list["item"]:
        if check_bch(item, hol_bch):
            outlist.append(item)

    # check if there are more than 100 items
    total_record_count = int(item_list["total_record_count"])
    if total_record_count > 100:
        # calculate number of needed additional calls
        add_calls = total_record_count // 100
        logging.debug(f"get_items(): {total_record_count} items vorhanden, {add_calls} weitere API-calls notwendig.")
        # make the additional calls and add answer to the outlist
        for i in range(add_calls):
            offset = (i + 1) * 100
            logging.debug(f"get_items(): additional call {offset}")

            next_list = session.get(item_api.format(mms_id=mms_id, holding_id="ALL"),
                                    params={"limit": "100", "offset": offset}).json()
            logging.debug(f"get_items(): weitere items zu outlist hinzufügen (call {offset}/{add_calls})")
            for item in next_list["item"]:
                if check_bch(item, hol_bch):
                    outlist.append(item)

    # DONE save the item list to disk
    logging.info("Schreibe Backup.")
    backup_file = os.path.join(backup_dir, f"{mms_id}_{hol_bch[0]}_{hol_bch[1]}_{hol_bch[2].replace('.', '').replace(',', '').replace('/', '').replace(' ', '-')}")
    save_json(outlist, backup_file)
    return outlist
Inhaltliche Checks
  • State “DONE” from “TODO” [2019-01-04 Fr 09:43]
  • State “TODO” from “DONE” [2022-02-02 Mi 14:19]
  • Note: Fix missing param in holdings_api
  • State “DONE” from “TODO” [2022-02-02 Mi 15:02]
Überprüfung, ob die richtigen Signaturen vorhanden sind, etc. Dazu holen wir uns zuerst das Zielholding und lesen dort 856 b, c und h aus.
def get_bch(mms_id, holding_id):
    hol = session.get(holdings_api.format(mms_id=mms_id)+ "/" + holding_id, headers = {"accept": "application/xml"})
    try:
        holxml = ET.fromstring(hol.text)
        b = holxml.find('.//*[@tag="852"]/*[@code="b"]').text
        c = holxml.find('.//*[@tag="852"]/*[@code="c"]').text
        h = holxml.find('.//*[@tag="852"]/*[@code="h"]').text
    except:
        logging.exception("Fehler beim Lesen des Zielholdings (XML).")
        logging.error(hol.text)
        print("Ein Fehler ist aufgetreten. Kontrollieren Sie die Log-Datei.")
        input("Drücken Sie ENTER um das Programm zu beenden.")
        sys.exit(1)

    return b, c, h

Dann schauen wir, ob das Item zum HOL passt, damit nicht falschlicherweise Items von anderen Standorten oder mit anderen Grundsignaturen umgehängt werden. Subfelder b und c müssen übereinstimmen; die Signatur des Items (genaugenommen von dessen HOL) muss mit demselben String anfangen, der in Subfeld h steht. Nachdem in den Daten oft Leerzeichen vorhanden sind (z. B. N. S. anstatt N.S.), werden Leerzeichen bei diesem Vergleich ignoriert.

# check if the item fits the target holding's 852 b, c and h
def check_bch(item, hol_bch):
    """Check if the item fits the target holdings library, location and call number.

    Take an item object (dict) and return True or False."""

    hol_b, hol_c, hol_h = hol_bch

    item_b = item["item_data"]["library"]["value"]
    item_c = item["item_data"]["location"]["value"]
    item_h = item["holding_data"]["call_number"].replace(" ", "")
    item_alt = item["item_data"]["alternative_call_number"]
    item_h_from_alt = re.sub(r"^.* ; ", "", item_alt).replace(" ", "")

    bch_check = [False, False, False]

    if hol_b == item_b:
        bch_check[0] = True

    if hol_c == item_c:
        bch_check[1] = True

    if item_h.startswith(hol_h.replace(" ", "")):
        bch_check[2] = True
    elif item_h_from_alt.startswith(hol_h.replace(" ", "")):
        # if the item has already been moved to a false holding because the false
        # call number is a substring of the right one
        bch_check[2] = True

    if False in bch_check:
        return False
    else:
        return True
Code für Inhaltliche checks zusammensetzen
<<get-bch>>
<<check-bch>>
  
Sicherungen machen
  • State “DONE” from “TODO” [2019-01-04 Fr 10:58]
Das Sicherungsverzeichnis festlegen Hier legen wir das Verzeichnis fest, in das die Sicherungen und Logs kommen. Falls es nicht vorhanden ist, erstellen wir es.
backup_dir = os.path.join(config["WORKING_DIR"], "backup")
# make the directory if it does not exist
if not os.path.exists(backup_dir):
    os.makedirs(backup_dir)
  
Items
  • State “DONE” from “TODO” [2019-01-04 Fr 10:58]
Nachdem wir ja von get_items() eine Liste mit Item-Objekten zurückbekommen, schreiben wir diese einfach in eine Datei.
def save_json(json_list, filename, count=1):
    """Save JSON-file with a list of items to disk.

    Takes a list of JSON-objects."""

    fname = f"{filename}_{count}.json"
    try:
        with open(fname, "x") as backup:
            backup.write(json.dumps(json_list))
    except FileExistsError:
        save_json(json_list, filename, count + 1)
  
Änderungen an den Items machen
An den Exemplaren sind unter Umständen noch Änderungen vorzunehmen. Diese beziehen sich in erste Linie auf die Signaturen.Bearbeitung der SignaturenNachdem im Zielholding ja nur die Grundsignatur steht, würde diese Information verloren gehen. Daher schreiben wir sie in die Alternative Signatur des Exemplars.

Damit eine etwaig schon vorhandene alternative Signatur nicht überschrieben wird, prüfen wir vorher, ob dort schon eine HB-Signatur vorhanden ist. Wenn ja, wird die Signatur aus dem Holding nach " ; " eingefügt.

alt_call_nr = clean_cn(item["item_data"]["alternative_call_number"])
hol_call_nr = clean_cn(item["holding_data"]["call_number"])

# check if the alternative call number is empty
if alt_call_nr == "":
    item["item_data"]["alternative_call_number"] = hol_call_nr
    item["item_data"]["alternative_call_number_type"]["value"] = 8
    item["item_data"]["alternative_call_number_type"]["desc"] = "Other scheme"
elif " ; " in alt_call_nr or hol_call_nr in alt_call_nr:
    pass
else:
    item["item_data"]["alternative_call_number"] = f"{alt_call_nr} ; {hol_call_nr}"
  
Signaturen putzen
  • State “DONE” from “TODO” [2019-01-23 Mi 09:25]
Nachdem die Daten eine Geschichte haben, kommen Signaturen manchmal mit “/” als Trennzeichen oder auch mit “,” daher. Wo wir schon dabei sind, versuchen wir, das zu vereinheitlichen:
def clean_cn(cn):
         """Return call numbers with '/' as delimiter after base call number"""
         # matches correct prefixes only
         match = re.match(r'(^I{1,3}V?,?(?:I{1,3}V?)? [0-9]+)(, ?)(.*$)', cn)
         # matches all prefixes
         # match = re.match(r'(^[IV,]+? [0-9]+)(, ?)(.*$)', cn)
         if match:
             print(match.groups())
             cn = match[1] + "/" + match[3]
         return cn
  
Exemplarstatus leeren Wir nutzen diese Gelegenheit auch gleich, um den Exemplarstatus zu löschen, der bei diesen Items in Alma nicht mehr notwendig ist.
item["item_data"]["policy"]["desc"] = None
item["item_data"]["policy"]["value"] = ''
  
Materialtyp auf “ISSBD” setzen
item["item_data"]["physical_material_type"]["value"] = "ISSBD"
item["item_data"]["physical_material_type"]["desc"] = "Bound Issue"
  
Zusammensetzen der einzelnen Änderungen zu einer FunktionDamit die einzelnen Änderungen im Script ein bisschen übersichtlicher zusammengefasst sind, ziehen wir sie in eine Funktion change_item_information() zusammen, die wir dann während der Bearbeitung aufrufen.
def change_item_information(item):
    """Make all necessary changes to the item object"""
    # Set the alternative call number
    <<set-alt-call-nr>>

    # clear the item policy
    <<clear-item-policy>>

    # set the physical material type to ISSBD
    <<set-material-type>>
    return item
  
Items umhängen und Holdings löschen
  • State “DONE” from “TODO” [2019-01-04 Fr 09:39]
Das Umhängen des Exemplars sollte der letzte Schritt sein. Vorher sollten alle Checks laufen und das Item entsprechend angepasst werden (z. B. die HOL-Signatur in die alternative_call_number schreiben).

Um ein Exemplar umzuhängen, muss man es erst löschen und dann am Zielholding anhängen. Zuerst löschen deswegen, weil sonst der Barcode schon vorhanden ist und einen Error verursacht.

Um ein Exemplar also umzuhängen, sind folgende Schritte notwendig:

  1. Das Exemplar sichern. Das sollten wir ohnehin beim Abrufen der Exemplare schon gemacht haben. Die nötigen Funktionen finden sich im entsprechenden Kapitel.
  2. Das Exemplar via DELETE-request löschen. Wir übergeben den Parameter “holdings=delete”, um das Holding gleich mit zu löschen.
  3. Das Exemplar mit einem POST-request ans Zielholding hängen.

Der erste Schritt, wird oben abgearbeitet, die beiden weiteren werden in der Funktion move_item() abgehandelt.

Wenn Bestellposten vorhanden sind, führt das je nach Sachverhalt zu Fehlern beim DELETE oder beim POST. Die möglichen Sacherverhalte sind:

Bestellposten vorhanden, der sich auf das Item bezieht
Hier gibt es einen Fehler beim DELETE, weil Items mit verknüpfter POL nicht gelöscht werden können. Damit gehen wir um, indem wir die POL löschen, dann das Item löschen und beim POST die POL wieder hineinschreiben.
Mit dem Holding verknüpfter Bestellposten wird geerbt
Hier gibt es einen Fehler beim POST, weil ein Bestellposten dieses Typs nicht im Item stehen darf. Nachdem man nur einen Bestellposten mit einem Holding verknüpfen kann, wird der Bestllposten in diesem Fall verworfen. Die Bestellung selbst bleibt davon unberührt.
def move_item(item, bib_mms, target_hol_id):
    """Move items to other holding and delete source-holding"""
    # delete the items, but prevent the target-hol from being deleted
    barcode = item["item_data"]["barcode"]
    target = item_api.format(mms_id=bib_mms, holding_id=target_hol_id)
    def delete_item(item):
        if not target_hol_id in item["link"]:
            logging.debug(f"move_item(): lösche {barcode}")
            delete_item_response = session.delete(item["link"], params={"holdings": "delete"})
        else:
            logging.debug(f"move_item(): lösche {barcode}")
            delete_item_response = session.delete(item["link"], params={"holdings": "retain"})
        return delete_item_response

    delete_item_response = delete_item(item)
    # check for errors in the deletion process
    while delete_item_response.status_code != 204:
        delete_res_json = delete_item_response.json()
        error_code = delete_res_json["errorList"]["error"][0]["errorCode"]
        error_msg = delete_res_json["errorList"]["error"][0]["errorMessage"]
        if error_code == "401849" and "PO line" in error_msg:
            # can't delete item because of POL
            error = delete_res_json["errorList"]["error"][0]["errorMessage"].strip()
            logging.warning(f"move_item(): Fehler bei DELETE: {error} Versuche ohne POL zu löschen.")

            # delete POL and put it
            pol = item["item_data"]["po_line"]
            item["item_data"]["po_line"] = ""
            put_item_response = session.put(item["link"],json=item).json()
            if "errorsExist" in put_item_response:
                error = put_item_response["errorList"]["error"][0]["errorMessage"]
                error_code = put_item_response["errorList"]["error"][0]["errorCode"]
                logging.error(f"move_item(): unerwarteter Fehler bei PUT: {error}; code: {error_code}")
                return
            else:
                delete_item_response = delete_item(item)
                item["item_data"]["po_line"] = pol
        else:
            logging.error(f"move_item(): löschen fehlgeschlagen bei {barcode}. {delete_item_response.text}")
            return

    # post the item. Wait for 1 second before that, so that Alma can update the
    # barcode index. Try again, if barcode index is not updated.
    sleep(1)
    tries = 0
    logging.debug(f"move_item(): POST von {barcode}")
    post_item_response = session.post(target, json=item).json()
    while "errorsExist" in post_item_response:
        if tries > 5:
            error = post_item_response["errorList"]["error"][0]["errorMessage"]
            logging.error(f"move_item(): {barcode} Fünfter POST-Versuch fehlgeschlagen, Abbruch.")
            break
        elif post_item_response["errorList"]["error"][0]["errorCode"] == "401873":
            # if the error is an existing barcode, try again
            logging.info(f"move_item(): {barcode}: weiterer POST-Versuch ({tries + 1}x)")
            sleep(1)
            post_item_response = session.post(target, json=item).json()
            tries += 1
        elif post_item_response["errorList"]["error"][0]["errorCode"] == "401871":
            # po_line (most likely inherited from holding) not found
            error = post_item_response["errorList"]["error"][0]["errorMessage"]
            error_code = post_item_response["errorList"]["error"][0]["errorCode"]
            logging.warning(f"move_item(): Fehler bei POST: {error} Item wird ohne Bestellnummer verarbeitet.")
            item["item_data"]["po_line"] = ""
            post_item_response = session.post(target, json=item).json()
        else:
            error = post_item_response["errorList"]["error"][0]["errorMessage"]
            error_code = post_item_response["errorList"]["error"][0]["errorCode"]
            logging.error(f"move_item(): unerwarteter Fehler bei POST: {error}; code: {error_code}")
            break
Problem mit API # 00577276: Es können nur Datensätze mit <500 Items bearbetet werden

Derzeit (Mai 2019) können nur Datensätze mit maximal 500 Items bearbeitet werden. Dieses Problem soll im Quartal 3/2019 behoben werden.

Problem mit der API # 00580797

Dear Support Team,

we need to move items from one holding to another via API. As I understand it, the way to go is to delete the item in one place and create it again by POSTing it at the target holding – if there is a better/more efficient way, I’m glad to hear it.

When doing so, I get an HTTP 200 for evey item I post and the API returns the item object for every item. So I’m thinking everything went right.

But it gets funky:

When looking in Alma, there’s only one item on this holding (the first one I have POSTed), but there should be several. So I try to get the item list for all items on that bib:

GET https://api-eu.hosted.exlibrisgroup.com/almaws/v1/bibs/990011168120203339/holdings/ALL/items

Response: {“item”:[{“bib_data”:{“mms_id”:”990011168120203339”,”title”:”Kaerntner Gemeindeblatt”,”author”:null,”issn”:null,”isbn”:null,”complete_edition”:”“,”network_number”:[“(Aleph)001116812UBG01”,”(AT-UBG)LB00780006”,”LB00780006”],”link”:”https://api-eu.hosted.exlibrisgroup.com/almaws/v1/bibs/990011168120203339”},”holding_data”:{“holding_id”:”22326791880003339”,”call_number_type”:{“value”:”8”,”desc”:”Other scheme”},”call_number”:”Testsig”,”accession_number”:”“,”copy_id”:”“,”in_temp_location”:false,”temp_library”:{“value”:null,”desc”:null},”temp_location”:{“value”:null,”desc”:null},”temp_call_number_type”:{“value”:”“,”desc”:null},”temp_call_number”:”“,”temp_policy”:{“value”:”“,”desc”:null},”link”:”https://api-eu.hosted.exlibrisgroup.com/almaws/v1/bibs/990011168120203339/holdings/22326791880003339”},”item_data”:{“pid”:”23326791890003339”,”barcode”:”DC-25388”,”creation_date”:”2018-08-01Z”,”modification_date”:”2018-08-01Z”,”base_status”:{“value”:”1”,”desc”:”Item in place”},”physical_material_type”:{“value”:”ISSBD”,”desc”:”Bound Issue”},”policy”:{“value”:”60”,”desc”:”Kopiebestellung”},”provenance”:{“value”:”“,”desc”:null},”po_line”:”“,”is_magnetic”:false,”arrival_date”:”1999-04-01Z”,”year_of_issue”:”“,”enumeration_a”:”1971”,”enumeration_b”:”2”,”enumeration_c”:”“,”enumeration_d”:”“,”enumeration_e”:”“,”enumeration_f”:”“,”enumeration_g”:”“,”enumeration_h”:”“,”chronology_i”:”1971”,”chronology_j”:”“,”chronology_k”:”“,”chronology_l”:”“,”chronology_m”:”“,”description”:”1971,2”,”receiving_operator”:”import”,”process_type”:{“value”:”“,”desc”:null},”library”:{“value”:”BDEPO”,”desc”:”Depotbibliothek”},”location”:{“value”:”DHB20”,”desc”:”Depot HB20”},”alternative_call_number”:”HB20-918”,”alternative_call_number_type”:{“value”:”8”,”desc”:”Other scheme”},”storage_location_id”:”“,”pages”:”“,”pieces”:”“,”public_note”:”“,”fulfillment_note”:”“,”internal_note_1”:”FH03 - I 380584, 1971,2. 1971 :: KKD”,”internal_note_2”:”“,”internal_note_3”:”“,”statistics_note_1”:”O#RAK#2014”,”statistics_note_2”:”“,”statistics_note_3”:”“,”requested”:null,”edition”:null,”imprint”:null,”language”:null,”physical_condition”:{“value”:null,”desc”:null}},”link”:”https://api-eu.hosted.exlibrisgroup.com/almaws/v1/bibs/990011168120203339/holdings/22326791880003339/items/23326791890003339”}],”total_record_count”:1}

Hmm. Why is there only one item, when I got confirmation that everything went good – the API returned HTTP 200 and the item object for every item.

It gets even more interesting: When retrieving the items for the specific holding (the only one, I might add), this happens:

GET https://api-eu.hosted.exlibrisgroup.com/almaws/v1/bibs/990011168120203339/holdings/22326792100003339/items

Response: {“total_record_count”:14}

That’s all of the response – no omissions. Total record count of 14, but no item list?

Best of it all: I can retrieve the individual items via API though (I know where to look for, as I got the item object as response for the POST request).

For example: GET https://api-eu.hosted.exlibrisgroup.com/almaws/v1/bibs/990011168120203339/holdings/22326791910003339/items/23326791770003339

This returns the corresponding item.

What am I doing wrong?

If there’s an easyer way to move items from one holding to another, I’m happy to be educated about that too.

Best regards Stefan

Alles Zusammensetzen

Das Modul
<<imports>>

# Get the users input
<<MMS-IDs>>
# set up the backup
<<configure-backup>>
#configure logging
<<logging-setup>>

# get everything ready for making the API-Calls
<<api-strings>>
<<API-key>>
<<session>>

# function for backing up JSON to disk
<<save-items>>

# functions for checking the api-responses
<<content-checks>>

# Get the items
<<API-get-items>>

# Change item information like call numbers etc.
<<clean-call-number>>
<<change-item-information>>

# Move the item to the target holding
<<move-item>>

def main():
    # assign values to bib_mms and target_hol_id
    if len(sys.argv) == 3:
        bib_mms = sys.argv[1]
        target_hol_id = sys.argv[2]
    else:
        bib_mms, target_hol_id = get_mmsids()

    global logger
    logger = logging_setup(bib_mms, target_hol_id)

    # log who started the program
    logging.debug(f"Programm gestartet von {getpass.getuser()}.")
    logging.debug(f"bib_mms: {bib_mms}, target_hol_id: {target_hol_id}")

    # do your work
    logging.info("Hole Daten von Alma ...")
    item_list = get_items(bib_mms, target_hol_id)
    item_count = len(item_list)
    logging.info(f"Zu bearbeitende Exemplare: {item_count}")

    for idx, item in enumerate(item_list):
        logging.info(f"Exemplar {idx + 1} von {item_count}: {item['item_data']['barcode']}")
        logging.info("Bearbeite Exemplardaten ...")
        change_item_information(item)

        logging.info("Verschieben an Zielholding ...")
        move_item(item, bib_mms, target_hol_id)

    input("Verarbeitung abgeschlossen!\nDrücken Sie ENTER um das Programm zu verlassen.")

if __name__ == "__main__":
    main()

Erstellen einer ausführbaren Datei für die BenutzerInnen

Die Anwenderinnen dieses Programms werden kein ProgrammiererInnen mit einem Linux- oder Mac-System sein, sondern Bibliothekarinnen mit Windows. Unter Windows ist, im Gegensatz zu den landläufigen Linux-Distros oder OSX, Python nicht standardmäßig installiert. Es reicht also nicht das Script mit chmod +x app_multi-hol.py ausführbar zu machen.

Es muss eine exe-Datei erstellt werden. Das geht am einfachsten mit dem Modul pyinstaller, das via pip verfügbar ist. pynistaller ist nicht in requirements.txt aufgeführt, daher muss es nachinstalliert werden. Die nachfolgende Anleitung geht davon aus, dass Sie das alles unter Windows mit PowerShell machen.

Aktivieren Sie das Virtual Environment (siehe <a href=”Python Virtual environment”>Python Virtual environment) und installieren sie pyinstaller

# virtual environment aktivieren
~/.venvs/multi-hol/Scripts/Activate.ps1

# das Modul installieren
pip install pyinstaller

Danach sollten Sie in multi-hol/conf.py in das dict config Ihren API-Key eintragen. Wie oben erwähnt verwende ich während der Entwicklung den Keyring des Systems, für die Erstellung einer ausführbaren Datei muss der Key tatsächlich im Klartext hier stehen. Daher steht diese Datei bei mir in .gitignore. Vergessen Sie nicht, ihn danach wieder zu löschen (vor allem, falls Sie Ihre Version dieses Repos auf GitHub hosten …). Wenn das Skript auf einem Server laufen soll, bietet es sich an, den API-Key in einer Umgebungsvariable zu speichern und von da zu holen. Damit steht er auch nirgends im Klartext.

Auch in config kommt das Arbeitsverzeichnis, in das die Backups und die logs geschrieben werden sollen. Nachdem multi-hol/conf.py bei mir in .gitignore steht, sehen sie im Abschnitt Beispiel für conf.py, wie diese Datei aussehen sollte.

Danach führen Sie folgende Zeile aus, um die ausführbare Datei zu erstellen. Das Flag -F macht, dass alles in eine einzelne Datei gepackt wird, anstatt eines Ordners mit einzelnen Files.

# run pyinstaller
pyinstaller -F app_multi-hol.py

Das war es auch schon. Sie sollten im Ordner dist die Datei app_multi-hol.exe finden.

Tests

Natürlich will das alles gut getestet sein.

Beispieldatensätze in der Sandbox:

  • 990011505800203339: 10 Hols, keine alternative Signatur
  • 990011608060203339: 10 Hols, alternative Signatur
  • 990006489880203339: 106 Hols, alternative Signatur

Zuerst holen wir mal alle Exemplare und speichern sie, sodass wir mir schnell den Ausgangszustand wiederherstellen können.

import pytest
import logging
from multi_hol.multi_hol import *

# setup logging
global logger
logger = logging_setup("TEST", "LOG")

# with alternative call number
with open("tests/testdata/10items_alt.json") as fh:
    items_alt = json.load(fh)["item"]
# without alternative call number
with open("tests/testdata/10items_no_alt.json") as fh:
    items_no_alt = json.load(fh)["item"]

item_alt = items_alt.pop(0)
item_no_alt = items_no_alt.pop(0)

def test_get_item():
    items = get_items("990006489880203339", "22332262300003339")
    assert len(items) == 106
    barcodes = []
    for item in items:
        barcodes.append(item["item_data"]["barcode"])
    assert len(items) == len(barcodes)
    assert len(set(barcodes)) == len(barcodes)

def test_get_bch():
    assert get_bch("22312549980003339") == ("BDEPO", "DHB40", "II 140137, 219,Ind. 1879")

def test_change_item_info():
    # load items
    # with alternative call number
    with open("tests/testdata/10items_alt.json") as fh:
        items_alt = json.load(fh)["item"]
    # without alternative call number
    with open("tests/testdata/10items_no_alt.json") as fh:
        items_no_alt = json.load(fh)["item"]

    item_alt = items_alt.pop(0)
    item_no_alt = items_no_alt.pop(0)

    assert change_item_information(item_alt)["item_data"]["alternative_call_number"] == "HB20-918 ; I 380584/1971,2"
    assert change_item_information(item_no_alt)["item_data"]["alternative_call_number"] == "I 380010/48"
    assert change_item_information(item_no_alt)["item_data"]["alternative_call_number_type"]["value"] == 8
    assert change_item_information(item_no_alt)["item_data"]["alternative_call_number_type"]["desc"] == "Other scheme"

API-Dokumentation

Beispiel für conf.py

Die Datei multi_hol/conf.py sollte ungefähr so aussehen:

import os
import keyring

config = {
    "WORKING_DIR": os.path.join( "Y:", os.sep, "MULTI-HOL", "TEST"),

    # get api key from system keyring
    "API_KEY": keyring.get_password("ALMA-API", "BIB-Sandbox").rstrip()
    #
    # for compiling an executable, the key has to be here
    # "API_KEY": "super-secret-api-key"
    #
    # or get it from the environment
    # "API_KEY": os.environ.get("API_KEY_ENVAR")
    }

WORKING_DIR ist das Verzeichnis, in dem die Sicherungen gemacht werden etc. API_KEY hier kommt ihr API-Key hin. Ich hole ihn aus dem Keyring des Systems. Wenn Sie mit pyinstaller eine ausführbare Datei erstellen, müssen sie den String hier auf jeden fall direkt eintragen.

Versions-Historie

1.1

  • physical_material_type wird immer auf “ISSBD” gesetzt.

Dokumentation für BearbeiterInnen

DIESEN ABSCHNITT IM LaTeX-EXPORT ENTFERNEN

Die Bilder im Handbuch funktionieren im HTML-Export nicht. Es ist derzeit ohnehin geplant, die BenutzerInnenanleitung nur als PDF zur Verfügung zu stellen.

Allgemeines

In Alma gibt es Datensätze (größenteils Zeitschriften und Reihen), an denen für jedes Exemplar ein Holding vorhanden ist, obwohl eigentlich die ganzen Exemplare an einem oder wenigen Holdings hängen sollten. Meistens ist das der Fall, weil in Aleph kein Holding an diesem Titel vorhanden war. Nachdem jedes Exemplar eine andere Signatur hatte (I 12345/1, I 12345/2, usw.), wurde bei der Migration für jedes einzelne ein eigenes Holding gebildet. Das wollen wir nun bereinigen.

Nachdem das bei mehr als 40.000 Exemplaren intellektuell nicht zu leisten ist, gibt es zu diesem Zweck ein kleines Programm, das Sie dabei unterstützt.

Der Ablauf für Sie schaut folgendermaßen aus:

  • Zielholding identifizieren/erstellen
  • Programm aufrufen
  • Falls mehrere Grundsignaturen vorhanden (z. B. “N.F.”), mit nächster Grundsignatur wiederholen, bis alles Exemplare richtig hängen
  • Falls noch nicht geschehen, die Informationen in den Holdings ergänzen, die noch fehlen
Zuordnung von Exemplaren an das Zielholding

Im Zuge der Verarbeitung werden alle Holdings auf Übereinstimmungen mit dem Zielholding geprüft. Wenn die richtigen Werte übereinstimmen, werden die Exemplare von diesen Holdings ans Zielholding gehängt und das dann überflüssige Holding gelöscht.

Die Überprüfung, ob ein Exemplar sich zum Umhängen qualifiziert, läuft über das Feld 852 im Holding:

  • $$b muss übereinstimmen
  • $$c muss übereinstimmen
  • $$h genauso beginnen wie $$h im Zielholding
Ein paar Beispiele Zielholding: 852 81 $$b BDEPO $$c DHB $$h II 47550
Informationen im AusgangsholMatchKommentar
$$b BHB $$c MAG $$h II 47550/1Nein$$b und $$c stimmen nicht überein
$$b BDEPO $$c DHB $$h II 47550/N.F.2Ja$$b und $$c stimmen überein
$$h beginnt wie $$h im Zielholding
$$b BDEPO $$c DHB $$h II 47550/3Ja
$$b BDEPO $$c DHBMA $$h 47550/1Nein$$c stimmt nicht überein

Wir sehen, dass sowohl II 47550/3 als auch II 47550/N.F.2 der Grundsignatur zugeordnet werden, obwohl hier eigentlich zwei Holdings angelegt werden müssten. Das ist technisch nicht anders möglich. Daher ist die Reihenfolge, in der diese Exemplare bearbeitet werden entscheidend. Mehr dazu im Abschnitt Mehrere Grundsignaturen an einem bibliografischen Datensatz.

Die Grundsignatur Um ein Zielholding zu identifizieren bzw. zu erstellen, müssen wir klären, was wir in diesem Zusammenhang unter dem Begriff Grundsignatur verstehen:

Unter Grundsignatur verstehen wir den Teil einer Signatur, der mehreren Exemplaren einer Zählfolge gemeinsam ist. Z. B. I 156715, aber auch I 156715/N.F. oder I 156715/3.Ser.. Diese Unterscheidung ist wichtig, weil die Zuordnung der Exemplare an ein Zielholding unter anderem dadurch passiert, dass die Signatur im zu bereinigenden Holding gleich anfängt, wie die im Zielholding.

Technische Beschränkungen

Wegen technischer Beschränkungen in der Programmierschnittstelle von Alma können nur Datensätze bearbeitet werden, die höchstens 500 Exemplare haben! Hat ein Datensatz mehr als 500 Items, bricht die Verarbeitung mit einer Fehlermeldung ab. Bitte führen Sie das Programm nicht aus, wenn mehr als 500 Exemplare an einem Datensatz vorhanden sind.

Arbeitsablauf

Allgemeine Überlegungen

Dieses Programm unterstützt Sie /halb/automatisch beim der Datenpflege. Bevor Sie es einsetzen, stellen Sie sicher, dass Sie es mit den richtigen Parametern ausführen.

Es werden sämtliche Exemplardaten gesichert und können somit wiederhergestellt werden. Die Holdings werden NICHT gesichert. Dies ist so, weil davon ausgegangen wird, dass die zu löschenden Holdings automatisch generiert wurden und keine Informationen enthalten, die nicht auch im Exemplar stehen. Sollten Sie das Programm auf Fälle anwenden, die nicht durch die Migration von Aleph nach Alma entstanden sind, kontrollieren Sie, ob eines der nicht-Zielholding Informationen enthält, die nicht gelöscht werden sollen.

Anhand der Logik des Programmes, die unter erklärt wird, wissen Sie, welche Holdings gelöscht werden. Sollten in diesen Informationen sein, die Sie behalten wollen (Buchbinderinformationen, Anmerkungen), übertragen Sie diese vorher ins Zielholding. Wenn nur ein solches Holding vorhanden ist, nutzen Sie dieses als Zielholding.

Vergleichen Sie zur Sicherheit immer die Gesamtzahl der Exemplare vor und nach der Ausführung des Programms. Sie sollte gleich bleiben.

Vor dem Ausführen prüfen ob es Bestellposten an Quellholdings gibt

Wenn mit einem Nicht-Zielholding eine Bestellnummer verknüpft ist, geht diese Bestellnummer im Laufe der Verarbeitung verloren. Falls Sie ihr Ziel-Holding mit der Bestellung verknüpfen wollen, so notieren Sie die Bestellnummer und machen Sie die Verknüpfung nachdem die Verarbeitung abgeschlossen ist. Die mit den einzelnen Holdings verknüpften Bestellummern (die verworfen werden) werden in der Ausgabe des Programms in je einer Zeile angezeigt, die ungefähr so aussieht:

WARNING move_item(): Fehler bei POST: PoLine 0000003139 not found.
Item wird ohne Bestellnummer verarbeitet.

Die Bestellung bleibt dabei unangetastet.

Bestellposten, die direkt mit dem Exemplar verknüpft sind Wenn eine Bestellung mit einem Exemplar verknüpft ist (kann bei Fortsetzungsbestellungen vorkommen), erscheint zwar eine Fehlermeldung, die Verarbeitung sollte aber funktionieren. Die Fehlermeldung schaut so aus:
WARNING move_item(): Fehler bei DELETE: Item delete errors: There is a
PO line POL-17562 linked to this item +BM79458700. Please handle the
order (using the PO line pages) before withdrawing this item / these
items. Versuche ohne POL zu löschen.
  

Wenn darauf keine weitere Fehlermeldung folgt, passt alles, das Exemplar wird umgehängt und die Bestellnummer bleibt erhalten. Sollte direkt darauf eine weitere Fehlermeldung folgen, melden Sie das bitte an die Person, die bei Ihnen dieses Programm betreut.

MMS-ID des bibliografischen Datensatzes ermitteln

Damit das Programm arbeiten kann brauchen wir die lokale MMS-ID des Titeldatensatzes und die MMS-ID des Zielholdings. Am einfachsten ist es, wenn man sich diese Nummern irgendwo zwischenspeichert (im Editor z. B.), um sie dann in die Eingabefelder zu kopieren.

Wie kommt man zur lokalen MMS-ID? Die lokale MMS-ID ist die, die mit 3339 endet (im Gegensatz zu 3331 in der NZ). Am einfachsten kommt man zu dieser in der Datensatz-Ansicht (d. h. wenn man beim Suchergebnis auf den Titel klickt):

~/projects/multi-hol/doc/pic/mmsid_bib.png

Diese Nummer muss mit 99 anfangen und mit 3339 aufhören. Öffnen Sie den Texteditor – einfach Windows-Taste drücken und “Editor” eingeben:

~/projects/multi-hol/doc/pic/start_edit.png

Kopieren Sie die Nummer hinein.

Das Ziel-Holding identifizieren/anlegen
Es ist bereits ein passendes Zielholding vorhanden Wenn bereits ein Holding vorhanden ist, das als Zielholding für überzählige Hols dienen kann, kopieren Sie die MMS-ID dieses Holdings in den Editor.

ACHTUNG: Wenn an diesem Holding bereits Exemplare vorhanden sind, muss die Alternative Signatur aller dieser Exemplare unbedingt bereits VOR der Bearbeitung durch das Programm korrekt sein. Es kann sonst dazu kommen, dass Teile der alternativen Signatur verloren gehen.

Es ist kein passendes Zielholding vorhanden In den allermeisten Fällen müssen Sie das Zielholding neu anlegen. Das geht aber recht schnell:
  1. Suchen Sie ein beliebiges Holding am gleichen Standort, mit der Grundsignatur, die Sie brauchen und öffnen Sie dieses im Metadateneditor zum bearbeiten
  2. Klicken Sie auf [Datei -> Duplizieren]
  3. Im duplizierten Holding (erkennbar am ausgegrauten Symbol) löschen Sie den hinteren Teil der Signatur, sodass nur die Grundsignatur übrig bleibt:

    ~/projects/multi-hol/doc/pic/dupliziertes_hol.png

  4. Klicken sie auf Speichern
  5. Kopieren Sie die MMS-ID des Holdings (siehe den grünen Pfeil im Bild bei Punkt 3) auch in den Editor. Die MMS-ID eines Holdings beginnt immer mit 22 und endet mit 3339. Im Bild sehen Sie das Editorfenster mit der MMS-ID des Bibsatzes in der ersten und der des Holdings in der zweiten Zeile.

    ~/projects/multi-hol/doc/pic/mmsid_editor.png

Das Programm ausführen

Jetzt, wo Sie das Zielholding angelegt haben und die MMS-IDs vom bibliografischen Datensatz und vom Holding in den Editor kopiert haben, können Sie das Programm ausführen. Wo es genau liegt, haben Sie normalerweise bei der Einschulung erfahren, wahrscheinlich haben Sie auch eine Verknüpfung auf Ihrem Desktop. Machen Sie einen Doppelklick auf das Programm und nach ein paar Sekunden kommt ein Eingabefenster:

~/projects/multi-hol/doc/pic/eingabefenster.png

Fügen Sie die jeweiligen Nummern in die entsprechenden Felder ein und klicken Sie auf “OK”.

Im schwarzen Fenster, das sich auch mit dem Programm geöffnet hat, sehen Sie den Fortschritt des Programms. Wenn es fertig ist, sehen Sie die Zeilen

~/projects/multi-hol/doc/pic/verarbeitung_abgeschlossen.png

Wenn Sie ENTER drücken, schließt sich das Fenster und das Progamm ist beendet.

Die Zusammenfassende Bestandsangabe, etc. in Zielholding eintragen

Nach Ausführung des Programms sollte es am Datensatz für Ihre Signatur nur noch ein Holding geben, an dem alle Exemplare hängen. Überprüfen Sie, was da ist und machen Sie eine entsprechende zusammenfassende Bestandsangabe im Holding.

Es ist empfehlenswert, diesen Schritt am Schluss zu machen, weil es sein kann, dass die Bearbeitung mit weiteren Grundsignaturen (“N.F.”, etc.; siehe Mehrere Grundsignaturen an einem bibliografischen Datensatz) wiederholt werden muss. Erst wenn alle Exemplare richtig hängen, lassen sich die Angaben in den Holdings korrekt machen.

Spezialfälle

Mehrere Grundsignaturen an einem bibliografischen Datensatz

Manchmal ist es notwendig, die Exemplare an einem bibliografischen Datensatz auf mehrere Holdings aufzuteilen. Das kommt dann vor, wenn es mehrere Zählfolgen gibt. Jede dieser Zählfolgen hat eine eigene <a href=”Die Grundsignatur”>Grundsignatur, für die jeweils ein eigenes Holding angelegt werden muss.

Wenn wir uns das Beispiel von Ein paar Beispiele noch einmal ansehen, bemerken wir, dass die Signaturen II 47550/3 und II 47550/N.F.2 beide dem gleichen Zielholding zugeordnet werden. Nachdem der Anfang der Signatur übereinstimmt, lässt sich das nicht verhindern. Im Endeffekt funktioniert das Ganze aber trotzdem, wenn wir die Signaturen in der richtigen Reihenfolge, nämlich beginnend mit der kürzesten Signatur, abarbeiten.

Würden wir diese Reihenfolge nicht einhalten, d. h. z. B. zuerst II 47550/N.F. und erst dann II 47550 bearbeiten, würden beim zweiten Lauf die Exemplare alle von II 47550/N.F. wegwandern und sich an II 47550 hängen (weil ihre Signatur ja auch mit II 47550 beginnt). Umgekehrt passiert das nicht, weil z. B. II 47550/23 ja nicht mit II 47550/N.F. anfängt.

Das kling komplizierter, als es in der Praxis ist:

  1. Zuerst das Zielholding für die kürzeste Signatur anlegen und das Programm ausführen. Damit hängen sich alle Exemplare an dieses Holding.
  2. Danach das Zielholding für die nächste Signatur (z. B. II 47550/N.F.) anlegen und das Programm ausführen. Damit wandern die Exemplare der Neuen Folge vom ersten Zielholding an das richtige. An diesem Punkt ist die Reihenfolge nicht mehr wichtig, d. h. es ist egal ob man jetzt mit der neuen Folge oder der 3. Serie weitermacht.
  3. Diesen Vorgang mit allen notwendigen Grundsignaturen wiederholen, bis alle Exemplare beim richtigen Holding sind.
Ein Beispiel für mehrere Signaturen Hier ein Screenshot der Holdings-Liste vor der Bearbeitung, an jedem HOL gibt es genau ein Exemplar:

~/projects/multi-hol/doc/pic/holdings_vorher.png

Nachdem wir ein Holding für II 209630 angelegt und unser Programm haben laufen lassen, gibt es nur noch ein Holding (dafür mit 5 Exemplaren):

~/projects/multi-hol/doc/pic/holding_nachher.png

Wenn wir die Exemplare dieses Holdings anzeigen lassen, sehen wir in der alternativen Signatur die einzelnen Signaturen für die Exmplare. Auch die neue Folge und die 3. Serie sind hier vertreten:

~/projects/multi-hol/doc/pic/exemplare_nachher1.png

Also legen wir ein weiteres Holding mit der Signatur II 209630/N.F. an und führen das Programm noch einmal aus. Wieder die gleiche MMS-ID für den Bibsatz, aber die MMS-ID für das gerade angelegte neue Holding. Danach gibt es zwei Holdings:

~/projects/multi-hol/doc/pic/holdings_nachher2.png

Wir sehen, dass bei II 209630 nur noch drei Exemplare sind, die anderen beiden sind zu II 209630/N.F. gewandert. Nun fehlt uns noch das eine Exemplar für die 3. Serie. Also legen wir noch ein Holding mit II 209630/3.Ser. an und lassen das Programm noch einmal laufen. Dann gibt es drei Holdings:

~/projects/multi-hol/doc/pic/holdings_nachher3.png

Wenn wir alle Exemplare anzeigen und dort die Signatur und die alternative Signatur ansehen, sehen wir, dass jetzt alles richtig hängt:

~/projects/multi-hol/doc/pic/exemplare_nachher2.png

Nun können wir die restlichen Daten in den Holdings nachtragen:

Bei II 209630:

~/projects/multi-hol/doc/pic/bestand1.png

Bei II 209630/N.F.:

~/projects/multi-hol/doc/pic/bestand2.png

Bei II 209630/3.Ser.:

~/projects/multi-hol/doc/pic/bestand3.png

Fehlermeldungen

Wenn alles reibungslos funktioniert sehen sie in dem Terminalfenster, das sich mit dem Programm öffnet, diverse Informationen vorbeiziehen. Was diese genau bedeuten, muss Sie nicht weiter interessieren. Sie sehen das nur, damit Sie wissen, dass das Programm etwas tut – es kann nämlich recht lange dauern, wenn viele Exemplare umgehängt werden. Führen Sie das Programm also lieber nicht aus, kurz bevor Sie nachhause gehen wollen. ;-)

Normalerweise beginnt jede einzelne Zeile mit INFO:. Wenn das der Fall ist, ist alles ok. Es kann aber auch sein, dass einmal eine Zeile mit ERROR: beginnt. Dann hat etwas nicht funktioniert. Üblicherweise passiert das, wenn ein Exmplar entlehnt ist, oder eine Bestellung mit dem Exemplar oder dem Holding verbunden ist.

Das ist in den meisten Fällen kein Grund zur Besorgnis: Das Programm läuft weiter und lässt das betroffene Holding samt Item in Ruhe. Allerdings müssen Sie dieses dann manuell bereinigen. Das heißt, wenn eine Bestellung damit verbunden ist, diese entsprechend bearbeiten, nämlich mit dem Zielholding verbinden. Wenn das Exemplar entlehnt ist, müssen Sie eh warten, bis es zurück ist und können es dann umhängen.

Hier zwei Beispiele für typische Fehlermeldungen:

Bestellung vorhanden:

ERROR: move_item(): löschen fehlgeschlagen bei @B1103200. {"errorsExist":true,
"errorList":{"error":[{"errorCode":"401849","errorMessage":"Item delete errors:
There is a PO line POL-13073 linked to this item @B1103200. Please handle the 
order (using the PO line pages) before withdrawing this item / these items. \n",
"trackingId":"E01-2101110719-OYNS8-AWAE1622782160"}]},"result":null}

Exemplar entlehnt:

ERROR: move_item(): löschen fehlgeschlagen bei @B1303276. {"errorsExist":true,
"errorList":{"error":[{"errorCode":"401849","errorMessage":"Item delete errors:
There are loans registered for item @B1303276. Please handle the loans before deleting
this item / these items. \n","trackingId":"E01-2201074327-ZSZQY-AWAE1622782160"}]},
"result":null}

Bei solchen Fehlermeldungen wissen Sie damit, was zu tun ist.

Sollten allerdings andere Fehlermeldungen ausgegeben werden, ist das auch kein Grund zur Panik. Das was Sie am Bildschirm vorbeiziehen sehen (und noch einiges mehr) wird automatisch in eine Log-Datei geschrieben. Wenden Sie sich in so einem Fall bitte an die Person, die dieses Programm betreut (an der UBG stefan.schuh@uni-graz.at). Diese kann dann in der Log-Datei nachsehen, was passiert ist und wie sich der Fehler beheben lässt. Es werden keine Daten verloren gehen – das Programm schreibt immer eine Sicherungskopie bevor es irgendetwas im System ändert.