Nummernvergabe per FLEX

Es ist keine leichte Sache, fortlaufende Nummern automatisch zu erzeugen, z.B. Zugangsnummern, Bestellnummern, Signaturen oder andere Identnummern. Das ging bis V25.8 nur auf zwei Wegen: 

Datensatz-Identnummern: Befehle  ci / cg  in der CFG (Handbuch Anh.A.1.3): Damit erzeugt man genau eine Nummer pro Datensatz, und zwar beim Abspeichern, wenn das in cg angegebene Feld (z.B. #00, wenn cg00 gesetzt ist) zu dem Zeitpunkt nichts enthält oder eine Angabe, in der ein Fragezeichen vorkommt - an dessen Stelle tritt dann die Nummer, und ihre Gestalt wird bestimmt durch das, was im Befehl ci steht. Recht einfach eigentlich, aber eben begrenzt auf genau eine Identnummer pro Satz, also nicht geeignet für zusätzliche Nummern wie Bestellnummern, Zugangsnummern, Signaturen etc. 

Andere laufende Nummern: PV-Routine in den Indexparametern (Handbuch 10.2.8, aber nur im Abschnitt für den Modus N). Dieses nutzereigene Unterprogramm wird ebenfalls beim Speichern eines Satzes ausgeführt und kann auf beliebige, im Datensatz vorliegende Bedingungen reagieren. Eine schwierige Methode, denn man muß die Exportsprache recht gut beherrschen. 

Beides funktioniert zwar in gleicher Weise auch im DOS-Programm PRESTO, es kam aber die Idee auf, für das Windows-System eine komfortablere Methode der Nummernvergabe zu entwickeln, die man bei Bedarf in FLEX-Routinen einsetzen kann.  
Daraus entstand ein universelles FLEX-Unterprogramm nextnum, das weiter unten vorgestellt wird. Es liefert die nächste Nummer, egal für welchen Zweck. 

Hinweis: Im `Anhang 2` wird angedeutet, wie man laufende Nummern auch nachträglich vergeben kann, und zwar für alle Sätze einer Datenbank oder für eine Ergebnismenge. 

Ein paar Dinge muß man freilich wissen, um es anzuwenden, und darum geht es jetzt: 

Eindeutigkeit!
Eine grundsätzliche Schwierigkeit gilt es zu bedenken. Jede automatische Nummernvergabe hat dafür zu sorgen, daß keine Nummer doppelt vergeben wird. Wenn zwei verschiedene Datensätze (von zwei Arbeitsplätzen aus) gleichzeitig gespeichert werden, entsteht dieses Problem, wie man leicht einsieht: irgendwo muß ja die zuletzt vergebene Nummer stehen, damit das Programm sich daraus die nächste errechnen kann. Und wenn zwei PCs gleichzeitig feststellen: "Aha, die letzte Nummer war 999!", dann würden beide die Nummer 1000 vergeben. Dies gilt es zu verhindern. 
[Anmerkung für Kundige: a99 ist kein Client-Programm. Das bedeutet: die Speichervorgänge werden nicht von einem Serverprogramm für alle angeschlossenen Clients zentral erledigt, sondern jedes laufende a99 macht alles selber. Deshalb kann die letzte Nummer nicht als Variable im Programm stehen, sie muß in einer Datei stehen.] 

Wo findet man die letzte vergebene Nummer? Sie könnte grundsätzlich an drei Stellen stehen: 
a) in der Indexdatei: Ganz klar, denn man wird ja mit den Nummern zugreifen wollen, also müssen sie indexiert werden, also kann man im Index nachschauen, welche die letzte war. Das Eintragen der neuen Nummer in den Index muß dann aber unmittelbar beim Speichern eines Satzes passieren, nicht irgendwann später. Diese Bedingung ist erfüllt. 
b) in einer anderen Datei: Das einfachste wäre eine ganz kleine Datei nur für diesen einen Zweck, in der nichts anderes stünde als die letzte Nummer. Diese Datei würde man einfach jedesmal mit der um 1 erhöhten Nummer überschreiben. Das wäre recht simpel, würde aber Schreibrecht aller Beteiligten an der betreffenden Datei voraussetzen. Dieses müßte man separat erteilen, während es für die Indexdatei sowieso schon gegeben sein muß. Und: verschwindet die Datei oder wird sie verfälscht, versagt die Nummernvergabe. 
c) in einem eigenen Datensatz: So war es bei der alten Methode des ORDER-Systems. Ein Datensatz vom Typ "Z: Zugangsnummern-Generator", "B: Bestellnummerngenerator" oder "S: Signaturgenerator" enthielt die zuletzt vergebene Nummer des betreffenden Typs. Diese Methode ist anfällig gegen willkürliche Änderungen, denn der Generatorsatz könnte manipuliert oder gar gelöscht werden; außerdem ist dann jede Speicherung eines neuen Satzes verbunden mit dem Laden und erneuten Speichern des Generatorsatzes - ein nicht ganz geringer Aufwand. 

Wegen der genannten Probleme von b) und c) wurde für das FLEX-Unterprogramm die Methode a) gewählt. Aber: ändert man die Indexierung, muß man auch an die Nummerngenerierung denken, was bei b) und c) nicht nötig wäre. 

Geht man über den Index, um die letzte Nummer zu finden, müssen zwei Dinge bekannt sein: 
1. In welchem Register stehen die Nummern? Grundsätzlich sollte dafür jedes der 11 Register wählbar sein, und es sollte nicht für jeden Nummerntyp dasselbe Register sein müssen. 
2. Welche Zeichen stehen im Register vor der Nummer? Jedes beliebige "Präfix" sollte möglich sein. D.h. im Register soll  xyz-123  stehen können oder AB00123 oder auch ganz andere denkbare Formen, statt einfach nur 123. 

Nicht möglichst kurz, aber bündig!
Zu bedenken ist ein weiteres Problem: Im Index müssen alle Nummern desselben Typs, also z.B. alle Zugangs- oder Bestellnummern, dieselbe Länge haben. Die Länge kann man frei wählen, aber die Nummern müssen dann rechtsbündig auf diese Stellenzahl ausgerückt sein. Führende Nullen sind nicht zwingend, auch Leerzeichen sind möglich, dann aber einheitlich. Wenn diese Bedingung nicht eingehalten wird, klappt die Ordnung im Register nicht:  Nummer 10 käme z.B. vor Nummer 2, und so könnte man die höchste vergebene Nummer nicht per Programm feststellen. 

Jetzt wissen wir, was man alles beachten muß. Daraus ergibt sich, wie man vorgeht, wenn eine neue Art von Nummer eingeführt werden soll. Mehrere Dinge sind dafür zu überlegen und zu entscheiden: 

1. Im Datensatz
In welchem Feld oder Unterfeld soll die Nummer im Datensatz stehen und bei welchen Satztypen soll sie vergeben werden? Soll die Nummer schon im Datensatz eine bestimmte Stellenzahl haben (rechtsbündig mit führenden Nullen), und soll schon im Datensatz ein bestimmtes Präfix davorstehen (xyz- oder AB oder 2007/ oder 07- oder was auch immer)? 
In dieser Frage besteht völlige Freiheit. Bestellnummern gehören nur in Bestellsätze, und dort werden sie ein ganz anderes Feld belegen als eine Signatur in einem Exemplar- oder Titelsatz. Zugangsnummern werden oft im Datensatz z.B. eine Jahrszahl als Präfix haben sollen, Bestellnummern vielleicht nicht, aber eine feste Stellenzahl mit führenden Nullen. Jeder Nummerntyp kann seine eigenen Eigenschaften im Satz und andere Eigenschaften im Index haben. Damit zusammen hängt die Anforderung 2: 

2. Im Index
Wie soll die Nummer indexiert sein? Auch dies ist weitgehend freigestellt: Welches Register man wählt, welches Präfix vor die Nummer soll (das kann ein anderes sein als im Datensatz, oder in Klein- statt Großbuchstaben), mit welcher Stellenzahl die eigentliche Zahl (rechtsbündig!) darzustellen ist, das alles unterliegt keiner Einschränkung. Man muß es aber parametrieren, also in die Indexparameter einbauen, das ist das Hauptproblem. Wir geben deshalb unten im Anhang ein `Rezept` an, wie man dies auf einfachste Weise bewerkstelligt. 
Wichtig, deshalb nochmal: Nur im Index kommt es auf Stellenzahl und Präfix an. Im Datensatz darf die nackte Nummer ohne sonstige Zutaten stehen, oder mit anderen Zutaten, denn die Indexparameter können und müssen alles richten, was im Index gebraucht wird. 

3. Definition der Nummerntypen
Wenn man mehrere unterschiedliche Nummerntypen zu vergeben gedenkt, muß irgendwo stehen, wie sie aussehen. Das Unterprogramm braucht ja die Information, wo es nachschauen soll nach der zuletzt vergebenen Nummer. Die dazu nötigen Angaben sind Register und Präfix. Zum Speichern dieser Angaben dient ein Nummern-Definitionssatz, das ist ein Systemsatz des Typs B, S oder Z, wie schon beim alten ORDER-System. 
Ein solcher Satz sieht so aus (er besteht nur aus einem Feld #9A ): 
#9A Ztyp²Nname²Rregister²Ppräfix
also z.B. 
#9A ZZGN²NZugangsnummer²RNUM²P2007-
oder 
#9A ZZGN²NZugangsnummer²R|9²PX2007-
(wenn NUM die Abteilung X im Register 9 ist, sind diese beiden Formen gleichwertig) 
Das Präfix im Unterfeld ²P kann somit jederzeit leicht verändert werden. 
Optional kann hinzukommen: ²F als Präfix für das Feld im Datensatz 
Existiert noch kein Eintrag mit dem angegebenen Präfix, ist das Ergebnis der Wert 1. 
Solche Nummern-Definitionssätze werden in den Standard-Indexparametern cat.api im Register 11 indexiert unter  SYSZ, dieses Beispiel würde also unter SYSZZGN erscheinen. 

Einfachere Alternative: Im eigenen FLEX vor dem Aufruf des Unterprogramms die Nutzervariablen  #uoR und #uoP besetzen, und zwar so: 
#uoR register  	z.B.  #uoR|9  oder  #uoR|:  oder  #uoRPPN (d.h. Reg.Nummer oder symbolischer Name möglich)
#uoP präfix  	z.B.  #uoPZ  oder  #uoPe  oder  #uoPab-   (Wenn symbol. Reg.Name, dann hier nur ein nicht dazu gehöriges Präfix) 
#uoF feldpräfix  	z.B.  #uoF2007/  oder  #uoFZn-  Präfix im Datensatzfeld [optional] 
Dann wird das Unterprogramm diese Angaben nehmen, statt nach einem Definitionssatz zu suchen. 
Zwischen Präfix und Nummer darf im Index auch ein Leerzeichen stehen. Das macht nichts: in #uoP setzt man aber ein # hinter das Präfix. 
Achtung: Soll am Ende des Feldpräfixes ein Leerzeichen stehen, muß man es mit einem # andeuten, sonst wird im Datensatz die Nummer unmittelbar hinter das Feldpräfix gesetzt. 

4. Durchführung der Vergabe 
Das eigentliche Vergeben einer neuen Nummer soll nun also in einem FLEX passieren. Dieser FLEX muß dann so eingerichtet werden, daß er nicht nur die Nummer vergibt, sondern unmittelbar danach auch den Satz speichert. Dazwischen darf nicht im Netzwerk eine andere Speicheraktion eines anderen Nutzers möglich sein. 
Lösung: Einbau des FLEX-Unterprogramms in den eigenen FLEX  onput.flx. Dieser wird automatisch gestartet, wenn man [Speichern] drückt. Bedingung ist, daß in der INI-Datei steht: 
SaveAsk=2    [Beschreibung dazu in der Datei  a99.ini ] 
Soll die Vergabe nur in besonderen Fällen passieren, die man mit eigenen FLEXen kontrolliert: Einbau des Unterprogramms in die eigenen FLEXe, evtl. mit eigenen Modifikationen. In so einem FLEX muß dann ebenfalls gleich nach der Nummernvergabe der Befehl  put  stehen. 

Das Unterprogramm sieht man unten zwischen den mit ****** markierten Zeilen. Man kopiert es in einen eigenen FLEX oder bindet es ein mit 
  include nextnum.flx

So nutzt man das Unterprogramm 
Zuerst muß man zwei Fälle unterscheiden: 

  A) MIT Generator-Datensatz   (siehe 3.) 
In  #uoZ  setzt man den Code  ABC  des Nummerngenerator-Satzes, 
und davor B, S oder Z, je nach Typ (Bestellnr., Signatur, Zugangsnr.) 
dieser Satz muß im Reg. 11 unter  SYSBABC, SYSSABC bzw. SYSZABC  stehen und im 
Systemsatz in der Kategorie #9A hinter dem B, S bzw. Z! (s.u.) 

  B) OHNE Generator-Datensatz
In #uoR den Registernamen (oder -nummer) und in #uoP das Präfix angeben, also die Zeichenkombination, hinter der im Register die Nummern stehen. 

Es entsteht in beiden Fällen:  in  #uoY  die nächste Nummer, vor der Nummer steht 
	der Inhalt von #9A$F bzw. #uoF, sonst nur die Nummer, und zwar ohne führende Nullen. 

Muster für den Aufruf des Unterprogramms  
(Dieses Muster in eigene FLEXe kopieren und modifizieren) 

#uoZ <code>    // Fall A), z.B.  #uoZ ZSTD : Systemsatz #9A ZSTD... 
     alternativ z.B.  (Fall B)
  #uoR |9           // Register 9 
  #uoP X2005-       // Praefix X2005-, d.h. Nr. steht dahinter
  #uoF 05-          // Praefix 05-  fuer das Feld im Datensatz [optional] 
set tbl loc           // .TBL blockieren, damit keiner speichern kann 
perf nextnum          // ermittelt die Nummer, liefert sie in #uoY 
if ="-1" jump fehler  // Nummernermittlung nicht gelungen 
var "praefix" #uoY    // <praefix = eigenes, gewünschtes Präfix im Datensatz 
ins #nnn              // nicht im Index! #nnn = Feld im Datensatz 
put free        // Satz speichern, zugl. TBL freigeben (erst ab V25.9) 
    Vor V25.9:
  set tbl fre   // TBL freigeben 
  put           // und Satz speichern 

Die Indexparameter stellen aus #nnn die Form im Register her. Das passiert dann beim Speichern, d.h. es ist Teil des Vorgangs, der mit "put" ausgelöst wird, FLEX hat nichts damit zu tun. 

Und hier folgt das eigentliche Unterprogramm. Wie gesagt, man muß es in den eigenen FLEX einkopieren (hinten anhängen) oder aber mit dem Befehl 
include nextnum.flx
einbinden. Wenn man nach dem Muster vorgeht, braucht man den Inhalt von nextnum.flx nicht zu kennen oder gar zu verstehen! 

  ********************** Beginn des Unterprogramms   [ nextnum.flx ]
:nextnum 

var i 
ins $n:oi 

  Steht eine Vorgabe in  #uoR ? Dann keinen Generatorsatz suchen 
if #uoR jump nUMVER 

var #uoz 
  derselbe Code wie beim letzten Mal? Dann muss er nicht geladen werden 
if =#uoX jump nUMVER 
  Finde den NummerngeneratorSatz im Reg. 11 
var "|; SYS" #uoz 
f1nd 
if no jump gEXIT 

if not #9A jump gEXIT 
var #9A 
if not "Z" if not "B" if not "S" jump gEXIT 
var #9A 

if #9A$R if #9A$P jump iNX 
jump gERR 

:iNX 
  Inhalt von #9A nach #uoX fuers naechste Mal 
ins #uoX 

  Eigentliche Nummernvergabe 
:nUMVER 
if not #uoR var "-2 " #uoX$R " " #uoX$P "999999999999A" 
if #uoR var "-2 " #uoR " _" #uoP "999999999999A" 

  nur zur evtl. Kontrolle 
ins #uAA 

  die 2 Zeilen holen, wo die hoechste Nummer stehen muesste 

qrix 

  gefundene Zeile bearbeiten 
var (f" " b" " e"") 
  und die Zeile mit der letzten Nr in #uzn sichern 

ins #uzn 
  jetzt hat man die Registereintragung ermittelt 
  nun noch die Zahl herausloesen, 
  sie steht hinter dem Praefix #uoP bzw. #uoY: 
if #uoR var #uzn(b"#uoP");jump aDD 
  Praefix fuers Datenfeld, falls gesetzt 
var #uoX$F 
ins #uoF 

var #uoX$P 
ins #uoY 
if not #uoR var #uzn(b"#uoY") 

:aDD 
  in iV steht jetzt die Nr., also 1 draufaddieren: 
z= 
  (wenn im Index noch keine Zeile mit dem Praefix existiert, ergibt sich z=1) 
z+1 
var #uoF z 
  # ist Platzhalter fuer Blank, ersetzen: 
ins _#_ _ 
ins #uoY 
jump resto2 
  #uoY  enthaelt die naechste Nummer, davor #uoX$F 

:gERR 
var "Es fehlt ein Teilfeld in " #9A n 
var +"$R und $P  muessen vorkommen" 
ansi 
mes 
jump resto1 
end 

:gEXIT 
var "Generator " #uoz " nicht gefunden" 
mes 

:resto1 
var "-1" 
ins #uoY 

:resto2 
  Den anfangs geladenen Satz zurueckholen, weil evtl. nicht mehr da 
      oder er wird sonst nicht gelb angezeigt 
var "#" $n:oi 
f1nd 
return 
end 
  ******************  ENDE UP 

  Verwendete Variablen im Unterprogramm: 
  Vorgaben beim Start: 
  #uoZ   Code des zu verwendenden Generators   ODER: 
  #uoR   enthalten die Vorgaben für Register und Präfix, wenn 
  #uoP      kein Num.Generator verwendet werden soll 

  #uoX   Speicherung der Nummerngenerator-Daten (Feld #9A) 
  #uoY   Ergebnis: naechste Nummer 

  Inhalt von #9A in einem Nummerngenerator-Satz (s. alf.frm) 
  #9A ZABC              Code ABC, dieser Code steht dann 
                             in Reg. 11 hinter SYSZ, dort steht also  SYSZABC 
  $N  Name              Klartext-Name des Generators (hat keine Funktion) 
  $R  Registername    symbolisch oder direkt,  z.B.  NUM oder |9 oder |: (für Reg. 10) 
  $P  Praefix            was im Register vor der eigentlichen Nummer steht! 
  $Z  letzte Nr.        Zuletzt vergebene Nummer (nur bei DOS-Prog. ORDER) 

  Die Teilfelder R  und P  gab es im DOS-System noch nicht! 

Anhang 1 
Indexierung einer Nummer in die Indexparameter einbauen

Sagen wir, die Nummer steht im Datenfeld #072, und dort steht immer "AB " davor, z.B.  #072 AB 99. 
Die Nummer soll 6-stellig im Register 10 auftauchen mit dem Präfix cd-, also z.B.  cd-000099
Gebraucht wird eine in den Indexparametern noch nicht vorkommende Sprungmarke. 
Ein Beispiel in  CAT.API  ist der Buchstabe z. 

1. Neue ak-Zeile einbauen (unter den schon vorhandenen ak-Zeilen) 

ak=072+z

2. Abschnitt  #-z  einbauen (irgendwo genau unter einer Zeile  #+# . Es gibt viele solche Zeilen, und es ist egal, welche man wählt.) 

#-z 
#t{ "|9cd-" }    wenn es Register 9 sein soll und Praefix cd- 
    (In #u1 steht in diesem Moment der Inhalt von #072, das 
     bewirkt der ak-Befehl) 
#u1 f"AB " r6,0 
    Die Nummer entnehmen (AB vorne wegnehmen) und 6-stellig 
    mit fuehrenden Nullen ausgeben 
#+# 

Hinweise
  Für die Standardparameter $A.CFG / CAT.API gilt: Zugangsnummern stehen in 
  #91     im Titelsatz (falls man das Bestellwesen nicht mit allegro macht) 
  #9DG$z  im Exemplarsatz (wichtig für die Ausleihe) 
  #9DB$z  im inventarisierten Bestellsatz (nur für Bestellwesen wichtig) 
          (entsteht bei Inventarisierung in CAT.API) 
  Im Bestellsatz sollte die ZgNr allerdings nicht indexiert werden, wohl aber 
  im Exemplarsatz. Dies wurde für V25.9 korrigiert. Ob sie überhaupt im 
  Bestellsatz stehen sollte, ist fraglich, aber sie wird beim Vorgang 
  "Inventarisierung/Abschluß der Bestellung" gebildet, weil dies der 
  logisch dafür sinnvollste Zeitpunkt ist. Wenn man die abgeschlossenen 
  Bestellsätze irgendwann archiviert, kann es nicht schaden, wenn sie 
  neben der Titel-IdNr auch die Zugangsnummer enthält, die dem 
  erworbenen Exemplar zugeteilt wurde. 

Anhang 2

Achtung: Das Programm  index  kann nur indexieren, aber keine Nummern vergeben. Das geht nur mit a99 oder acon. 

Sonderfall
Wenn man nachträglich Nummern vergeben will, dann in das Nummernregister z.B. x000000 einstellen, wenn etwa x?6  das IdNummernschema der CFG wäre (d.h.  ci9x?6  in der CFG). Dann in einer FLEX-Schleife mit  first # / next #  alle Sätze nacheinander laden und sofort mit  put  (oder mit  if not #00 put falls es schon einige Sätze eine #00 haben) wieder speichern.  Denn dabei merkt a99 oder acon, daß der Satz noch keine IdNummer hat und vergibt eine. 

?Rezept=h =Indexierung einer Nummer 
?Anhang 2=h =Sonderfall