Neuer Datenbankupgrademechanismus: Die Upgradedateien im neuen Verzeichnis sql/Pg...
authorMoritz Bunkus <m.bunkus@linet-services.de>
Fri, 22 Dec 2006 15:13:00 +0000 (15:13 +0000)
committerMoritz Bunkus <m.bunkus@linet-services.de>
Fri, 22 Dec 2006 15:13:00 +0000 (15:13 +0000)
19 files changed:
SL/DBUpgrade2.pm [new file with mode: 0644]
SL/User.pm
doc/sql-upgrade-dateien.txt [new file with mode: 0644]
locale/de/admin
locale/de/all
locale/de/am
locale/de/locales.pl
locale/de/login
locale/de/ustva
scripts/dbupgrade2_tool.pl [new file with mode: 0755]
sql/Pg-upgrade/Pg-upgrade-2.4.0.0-2.4.0.1.sql [deleted file]
sql/Pg-upgrade/Pg-upgrade-2.4.0.1-2.4.0.2.sql [deleted file]
sql/Pg-upgrade/Pg-upgrade-2.4.0.2-2.4.0.3.sql [deleted file]
sql/Pg-upgrade2/README [new file with mode: 0644]
sql/Pg-upgrade2/language_output_formatting.sql [new file with mode: 0644]
sql/Pg-upgrade2/tax_id_if_taxkey_is_0.sql [new file with mode: 0644]
sql/Pg-upgrade2/units_translations_and_singular_plural_distinction.sql [new file with mode: 0644]
templates/webpages/dbupgrade/upgrade_message2_de.html [new file with mode: 0644]
templates/webpages/dbupgrade/upgrade_message2_master.html [new file with mode: 0644]

diff --git a/SL/DBUpgrade2.pm b/SL/DBUpgrade2.pm
new file mode 100644 (file)
index 0000000..514448f
--- /dev/null
@@ -0,0 +1,157 @@
+package SL::DBUpgrade2;
+
+require Exporter;
+@ISA = qw(Exporter);
+
+@EXPORT = qw(parse_dbupdate_controls sort_dbupdate_controls);
+
+sub parse_dbupdate_controls {
+  $main::lxdebug->enter_sub();
+
+  my ($form, $dbdriver) = @_;
+
+  my $locale = $main::locale;
+
+  local *IN;
+  my %all_controls;
+
+  my $path = "sql/${dbdriver}-upgrade2";
+
+  foreach my $file_name (<$path/*.sql>, <$path/*.pl>) {
+    next unless (open(IN, $file_name));
+
+    my $file = $file_name;
+    $file =~ s|.*/||;
+
+    my $control = {
+      "priority" => 1000,
+      "depends" => [],
+    };
+
+    while (<IN>) {
+      chomp();
+      next unless (/^(--|\#)\s*\@/);
+      s/^(--|\#)\s*\@//;
+      s/\s*$//;
+      next if ($_ eq "");
+
+      my @fields = split(/\s*:\s*/, $_, 2);
+      next unless (scalar(@fields) == 2);
+
+      if ($fields[0] eq "depends") {
+        push(@{$control->{"depends"}}, split(/\s+/, $fields[1]));
+      } else {
+        $control->{$fields[0]} = $fields[1];
+      }
+    }
+
+    _control_error($form, $file_name,
+                   $locale->text("Missing 'tag' field."))
+      unless ($control->{"tag"});
+
+    _control_error($form, $file_name,
+                   $locale->text("The 'tag' field must only consist of " .
+                                 "alphanumeric characters or the carachters " .
+                                 "- _ ( )"))
+      if ($control->{"tag"} =~ /[^a-zA-Z0-9_\(\)\-]/);
+
+    _control_error($form, $file_name,
+                   sprintf($locale->text("More than one control file " .
+                                         "with the tag '%s' exist."),
+                           $control->{"tag"}))
+      if (defined($all_controls{$control->{"tag"}}));
+
+    _control_error($form, $file_name,
+                   sprintf($locale->text("Missing 'description' field.")))
+      unless ($control->{"description"});
+
+    $control->{"priority"} *= 1;
+    $control->{"priority"} = 1000 unless ($control->{"priority"});
+
+    $control->{"file"} = $file;
+
+    map({ delete($control->{$_}); } qw(depth applied));
+
+    $all_controls{$control->{"tag"}} = $control;
+
+    close(IN);
+  }
+
+  foreach my $control (values(%all_controls)) {
+    foreach my $dependency (@{$control->{"depends"}}) {
+      _control_error($form, $control->{"file"},
+                     sprintf($locale->text("Unknown dependency '%s'."),
+                             $dependency))
+        if (!defined($all_controls{$dependency}));
+    }
+
+    map({ $_->{"loop"} = 0; } values(%all_controls));
+    _check_for_loops($form, $control->{"file"}, \%all_controls,
+                     $control->{"tag"});
+  }
+
+  map({ _dbupdate2_calculate_depth(\%all_controls, $_->{"tag"}) }
+      values(%all_controls));
+
+  $main::lxdebug->leave_sub();
+
+  return \%all_controls;
+}
+
+sub _check_for_loops {
+  my ($form, $file_name, $controls, $tag, @path) = @_;
+
+  push(@path, $tag);
+
+  _control_error($form, $file_name,
+                 $main::locale->text("Dependency loop detected:") .
+                 " " . join(" -> ", @path))
+    if ($controls->{$tag}->{"loop"});
+
+  $controls->{$tag}->{"loop"} = 1;
+  map({ _check_for_loops($form, $file_name, $controls, $_, @path); }
+      @{$controls->{$tag}->{"depends"}});
+}
+
+sub _control_error {
+  my ($form, $file_name, $message) = @_;
+
+  my $form = $main::form;
+  my $locale = $main::locale;
+
+  $form->error(sprintf($locale->text("Error in database control file '%s': %s"),
+                       $file_name, $message));
+}
+
+sub _dbupdate2_calculate_depth {
+  $main::lxdebug->enter_sub();
+
+  my ($tree, $tag) = @_;
+
+  my $node = $tree->{$tag};
+
+  return $main::lxdebug->leave_sub() if (defined($node->{"depth"}));
+
+  my $max_depth = 0;
+
+  foreach $tag (@{$node->{"depends"}}) {
+    _dbupdate2_calculate_depth($tree, $tag);
+    my $value = $tree->{$tag}->{"depth"};
+    $max_depth = $value if ($value > $max_depth);
+  }
+
+  $node->{"depth"} = $max_depth + 1;
+
+  $main::lxdebug->leave_sub();
+}
+
+sub sort_dbupdate_controls {
+  return
+    sort({ $a->{"depth"} != $b->{"depth"} ? $a->{"depth"} <=> $b->{"depth"} :
+             $a->{"priority"} != $b->{"priority"} ?
+             $a->{"priority"} <=> $b->{"priority"} :
+             $a->{"tag"} cmp $b->{"tag"} } values(%{$_[0]}));
+}
+
+
+1;
index f331ca4..88deb0b 100644 (file)
@@ -34,6 +34,8 @@
 
 package User;
 
+use SL::DBUpgrade2;
+
 sub new {
   $main::lxdebug->enter_sub();
 
@@ -162,14 +164,21 @@ sub login {
                  '$myconfig{tel}', 'user')|;
       $dbh->do($query);
     }
+
+    $self->create_schema_info_table($form, $dbh);
+
     $dbh->disconnect;
 
     $rc = 0;
 
-    if (&update_available($myconfig{"dbdriver"}, $dbversion)) {
+    my $controls =
+      parse_dbupdate_controls($form, $myconfig{"dbdriver"});
 
-      map { $form->{$_} = $myconfig{$_} }
-        qw(dbname dbhost dbport dbdriver dbuser dbpasswd dbconnect);
+    map({ $form->{$_} = $myconfig{$_} }
+        qw(dbname dbhost dbport dbdriver dbuser dbpasswd dbconnect));
+
+    if (update_available($myconfig{"dbdriver"}, $dbversion) ||
+        update2_available($form, $controls)) {
 
       $form->{"stylesheet"} = "lx-office-erp.css";
       $form->{"title"} = $main::locale->text("Dataset upgrade");
@@ -185,8 +194,7 @@ sub login {
       }
 
       # update the tables
-      open FH, ">$userspath/nologin" or die "
-$!";
+      open(FH, ">$userspath/nologin") or die("$!");
 
       # required for Oracle
       $form->{dbdefault} = $sid;
@@ -196,9 +204,10 @@ $!";
       $SIG{QUIT} = 'IGNORE';
 
       $self->dbupdate($form);
+      $self->dbupdate2($form, $controls);
 
       # remove lock file
-      unlink "$userspath/nologin";
+      unlink("$userspath/nologin");
 
       print($form->parse_html_template("dbupgrade/footer"));
 
@@ -453,7 +462,7 @@ sub process_perl_script {
 sub process_query {
   $main::lxdebug->enter_sub();
 
-  my ($self, $form, $dbh, $filename, $version) = @_;
+  my ($self, $form, $dbh, $filename, $version_or_control) = @_;
 
   #  return unless (-f $filename);
 
@@ -510,7 +519,11 @@ sub process_query {
     }
   }
 
-  if ($version) {
+  if (ref($version_or_control) eq "HASH") {
+    $dbh->do("INSERT INTO schema_info (tag, login) VALUES (" .
+             $dbh->quote($version_or_control->{"tag"}) . ", " .
+             $dbh->quote($form->{"login"}) . ")");
+  } elsif ($version_or_control) {
     $dbh->do("UPDATE defaults SET version = " . $dbh->quote($version));
   }
   $dbh->commit();
@@ -725,6 +738,25 @@ sub update_available {
   return ($#upgradescripts > -1);
 }
 
+sub create_schema_info_table {
+  $main::lxdebug->enter_sub();
+
+  my ($self, $form, $dbh) = @_;
+
+  my $query = "SELECT tag FROM schema_info LIMIT 1";
+  if (!$dbh->do($query)) {
+    $query =
+      "CREATE TABLE schema_info (" .
+      "  tag text, " .
+      "  login text, " .
+      "  itime timestamp DEFAULT now(), " .
+      "  PRIMARY KEY (tag))";
+    $dbh->do($query) || $form->dberror($query);
+  }
+
+  $main::lxdebug->leave_sub();
+}
+
 sub dbupdate {
   $main::lxdebug->enter_sub();
 
@@ -794,7 +826,7 @@ sub dbupdate {
       last if ($version < $mindb);
 
       # apply upgrade
-      $main::lxdebug->message(DEBUG2, "Appliying Update $upgradescript");
+      $main::lxdebug->message(DEBUG2, "Applying Update $upgradescript");
       if ($file_type eq "sql") {
         $self->process_query($form, $dbh, "sql/" . $form->{"dbdriver"} . "-upgrade/$upgradescript", $str_maxdb);
       } else {
@@ -815,6 +847,112 @@ sub dbupdate {
   return $rc;
 }
 
+sub dbupdate2 {
+  $main::lxdebug->enter_sub();
+
+  my ($self, $form, $controls) = @_;
+
+  $form->{sid} = $form->{dbdefault};
+
+  my @upgradescripts = ();
+  my ($query, $sth, $tag);
+  my $rc = -2;
+
+  @upgradescripts = sort_dbupdate_controls($controls);
+
+  foreach my $db (split / /, $form->{dbupdate}) {
+
+    next unless $form->{$db};
+
+    # strip db from dataset
+    $db =~ s/^db//;
+    &dbconnect_vars($form, $db);
+
+    my $dbh =
+      DBI->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd})
+      or $form->dberror;
+
+    map({ $_->{"applied"} = 0; } @upgradescripts);
+
+    $query = "SELECT tag FROM schema_info";
+    $sth = $dbh->prepare($query);
+    $sth->execute() || $form->dberror($query);
+    while (($tag) = $sth->fetchrow_array()) {
+      $controls->{$tag}->{"applied"} = 1 if (defined($controls->{$tag}));
+    }
+    $sth->finish();
+
+    my $all_applied = 1;
+    foreach (@upgradescripts) {
+      if (!$_->{"applied"}) {
+        $all_applied = 0;
+        last;
+      }
+    }
+
+    next if ($all_applied);
+
+    foreach my $control (@upgradescripts) {
+      next if ($control->{"applied"});
+
+      $control->{"file"} =~ /\.(sql|pl)$/;
+      my $file_type = $1;
+
+      # apply upgrade
+      $main::lxdebug->message(DEBUG2, "Applying Update $control->{file}");
+      print($form->parse_html_template("dbupgrade/upgrade_message2",
+                                       $control));
+
+      if ($file_type eq "sql") {
+        $self->process_query($form, $dbh, "sql/" . $form->{"dbdriver"} .
+                             "-upgrade2/$control->{file}", $control);
+      } else {
+        $self->process_perl_script($form, $dbh, "sql/" . $form->{"dbdriver"} .
+                                   "-upgrade2/$control->{file}", $control);
+      }
+    }
+
+    $rc = 0;
+    $dbh->disconnect;
+
+  }
+
+  $main::lxdebug->leave_sub();
+
+  return $rc;
+}
+
+sub update2_available {
+  $main::lxdebug->enter_sub();
+
+  my ($form, $controls) = @_;
+
+  map({ $_->{"applied"} = 0; } values(%{$controls}));
+
+  dbconnect_vars($form, $form->{"dbname"});
+
+  my $dbh =
+    DBI->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}) ||
+    $form->dberror;
+
+  my ($query, $tag, $sth);
+
+  $query = "SELECT tag FROM schema_info";
+  $sth = $dbh->prepare($query);
+  $sth->execute() || $form->dberror($query);
+  while (($tag) = $sth->fetchrow_array()) {
+    $controls->{$tag}->{"applied"} = 1 if (defined($controls->{$tag}));
+  }
+  $sth->finish();
+  $dbh->disconnect();
+
+  map({ $main::lxdebug->leave_sub() and return 1 if (!$_->{"applied"}) }
+      values(%{$controls}));
+
+  $main::lxdebug->leave_sub();
+  return 0;
+}
+
 sub create_config {
   $main::lxdebug->enter_sub();
 
diff --git a/doc/sql-upgrade-dateien.txt b/doc/sql-upgrade-dateien.txt
new file mode 100644 (file)
index 0000000..5b9ae18
--- /dev/null
@@ -0,0 +1,142 @@
+Neuer Mechanismus für SQL-Upgradedateien
+----------------------------------------
+
+Der alte Mechanismus für SQL-Upgradescripte, der auf einer
+Versionsnummer beruht und dann in sql/Pg-upgrade nach einem Script für
+diese Versionsnummer sucht, schränkt sehr ein, z.B. was die parallele
+Entwicklung im stable- und unstable-Baum betrifft.
+
+Dieser Mechanismus wurde für Lx-Office 2.4.1 deutlich erweitert. Es
+werden weiterhin alle Scripte aus sql/Pg-upgrade
+ausgeführt. Zusätzlich gibt es aber ein zweites Verzeichnis,
+sql/Pg-upgrade2. In diesem Verzeichnis muss pro Datenbankupgrade eine
+Datei existieren, die neben den eigentlich auszuführenden SQL- oder
+Perl-Befehlen einige Kontrollinformationen enthält.
+
+Neu sind die Kontrollinformationen, die Abhängigkeiten und Prioritäten
+definieren können werden, sodass Datenbankscripte zwar in einer
+sicheren Reihenfolge ausgeführt werden (z.B. darf ein "ALTER TABLE"
+erst ausgeführt werden, wenn die Tabelle mit "CREATE TABLE" angelegt
+wurde), diese Reihenfolge aber so flexibel ist, dass man keine
+Versionsnummern mehr braucht.
+
+Lx-Office merkt sich dabei, welches der Upgradescripte in
+sql/Pg-upgrade2 bereits durchgeführt wurde und führt diese nicht
+erneut aus. Dazu dient die Tabelle "schema_info", die bei der
+Anmeldung automatisch angelegt wird.
+
+Format der Kontrollinformationen
+--------------------------------
+
+Die Kontrollinformationen sollten sich am Anfang der jeweiligen
+Upgradedatei befinden. Jede Zeile, die Kontrollinformationen enthält,
+hat dabei das folgende Format:
+
+Für SQL-Upgradedateien:
+
+-- @key: value
+
+
+Für Perl-Upgradedateien:
+
+# @key: value
+
+
+Leerzeichen vor "value" werden entfern.
+
+Die folgenden Schlüsselworte werden verarbeitet:
+
+* tag: Wird zwingend benötigt. Dies ist der "Name" des
+  Upgrades. Dieser "tag" kann von anderen Kontrolldateien in ihren
+  Abhängigkeiten verwendet werden (Schlüsselwort "depends"). Der "tag"
+  ist auch der Name, der in der Datenbank eingetragen wird.
+
+  Normalerweise sollte die Kontrolldatei genau so heißen wie der
+  "tag", nur mit der Endung ".sql" bzw. "pl".
+
+  Ein Tag darf nur aus alphanumerischen Zeichen sowie den Zeichen _ -
+  ( ) bestehen. Insbesondere sind Leerzeichen nicht erlaubt und
+  sollten stattdessen mit Unterstrichen ersetzt werden.
+
+* description: Benötigt. Eine Beschreibung, was in diesem Update
+  passiert. Diese wird dem Benutzer beim eigentlichen Datenbankupdate
+  angezeigt. Während der Tag in englisch gehalten sein sollte, sollte
+  die Beschreibung auf Deutsch erfolgen.
+
+* depends: Optional. Eine mit Leerzeichen getrennte Liste von "tags",
+  von denen dieses Upgradescript abhängt. Lx-Office stellt sicher,
+  dass die in dieser Liste aufgeführten Scripte bereits durchgeführt
+  wurden, bevor dieses Script ausgeführt wird.
+
+  Abhängigkeiten werden rekursiv betrachtet. Wenn also ein Script "b"
+  existiert, das von Änderungen in "a" abhängt, und eine neue
+  Kontrolldatei für "c" erstellt wird, die von Änderungen in "a" und
+  "b" abhängt, so genügt es, in "c" nur den Tag "b" als Abhängigkeit
+  zu definieren.
+
+  Es ist nicht erlaubt, sich selbst referenzierende Abhängigkeiten zu
+  definieren (z.B. "a" -> "b", "b" -> "c" und "c" -> "a").
+
+* priority: Optional. Ein Zahlenwert, der die Reihenfolge bestimmt, in
+  der Scripte ausgeführt werden, die die gleichen Abhängigkeitstiefen
+  besitzen. Fehlt dieser Parameter, so wird der Wert 1000 benutzt.
+
+  Dies ist reine Kosmetik. Für echte Reihenfolgen muss "depends"
+  benutzt werden. Lx-Office sortiert die auszuführenden Scripte zuerst
+  nach der Abhängigkeitstiefe (wenn "z" von "y" abhängt und "y" von
+  "x", so hat "z" eine Abhängigkeitstiefe von 2, "y" von 1 und "x" von
+  0. "x" würde hier zuerst ausgeführt, dann "y", dann "z"), dann nach
+  der Priorität und bei gleicher Priorität alphabetisch nach dem
+  "tag".
+
+Hilfsscript dbupgrade2_tool.pl
+------------------------------
+
+Um die Arbeit mit den Abhängigkeiten etwas zu erleichtern, existiert
+ein Hilfsscript namens "scripts/dbupgrade2_tool.pl". Es muss aus dem
+Lx-Office-ERP-Basisverzeichnis heraus aufgerufen werden. Dieses Tool
+liest alle Datenbankupgradescripte aus dem Verzeichnis sql/Pg-upgrade2
+aus. Es benutzt dafür die gleichen Methoden wie Lx-Office selber,
+sodass alle Fehlersituationen von der Kommandozeile überprüft werden
+können.
+
+Wird dem Script kein weiterer Parameter übergeben, so wird nur eine
+Überprüfung der Felder und Abhängigkeiten vorgenommen. Man kann sich
+aber auch Informationen auf verschiedene Art ausgeben lassen:
+
+1. Listenform: "./scripts/dbupgrade2_tool.pl --list"
+
+   Gibt eine Liste aller Scripte aus. Die Liste ist in der Reihenfolge
+   sortiert, in der Lx-Office die Scripte ausführen würde. Es werden
+   neben der Listenposition der Tag, die Abhängigkeitstiefe und die
+   Priorität ausgegeben.
+
+2. Baumform: "./scripts/dbupgrade2_tool.pl --tree"
+
+   Listet alle Tags in Baumform basierend auf den Abhängigkeiten
+   auf. Die "Wurzelknoten" sind dabei die Scripte, von denen keine
+   anderen abhängen. Die Unterknoten sind Scripte, die beim
+   übergeordneten Script als Abhängigkeit eingetragen sind.
+
+3. Umgekehrte Baumform: "./scripts/dbupgrade2_tool.pl --rtree"
+
+   Listet alle Tags in Baumform basierend auf den Abhängigkeiten auf.
+   Die "Wurzelknoten" sind dabei die Scripte mit der geringsten
+   Abhängigkeitstiefe. Die Unterknoten sind Scripte, die das
+   übergeordnete Script als Abhängigkeit eingetragen haben.
+
+4. Baumform mit Postscriptausgabe: "./scripts/dbupgrade2_tool.pl --graphviz"
+
+   Benötigt das Tool "graphviz", um mit seiner Hilfe die Baumform aus
+   3. in eine Postscriptdatei namens "db_dependencies.ps"
+   auszugeben. Dies ist vermutlich die übersichtlichste Form, weil
+   hierbei jeder Knoten nur einmal ausgegeben wird. Bei den
+   Textmodusbaumformen hingegen können Knoten und all ihre
+   Abhängigkeiten mehrfach ausgegeben werden.
+
+5. Scripte, von denen kein anderes Script abhängt:
+   "./scripts/dbupgrade2_tool.pl --nodeps"
+
+   Listet die Tags aller Scripte auf, von denen keine anderen Scripte
+   abhängen.
+
index 0ebbd17..99ba154 100644 (file)
@@ -29,11 +29,13 @@ $self->{texts} = {
   'Date Format'                 => 'Datumsformat',
   'Delete'                      => 'Löschen',
   'Delete Dataset'              => 'Datenbank löschen',
+  'Dependency loop detected:'   => 'Schleife in den Abh&auml;ngigkeiten entdeckt:',
   'Directory'                   => 'Verzeichnis',
   'Driver'                      => 'Treiber',
   'Dropdown Limit'              => 'Auswahllistenbegrenzung',
   'E-mail'                      => 'eMail',
   'Edit User'                   => 'Benutzerdaten bearbeiten',
+  'Error in database control file \'%s\': %s' => 'Fehler in Datenbankupgradekontrolldatei \'%s\': %s',
   'Existing Datasets'           => 'existierende Datenbanken',
   'Fax'                         => 'Fax',
   'File locked!'                => 'Datei gesperrt!',
@@ -48,6 +50,9 @@ $self->{texts} = {
   'Login'                       => 'Anmeldung',
   'Login name missing!'         => 'Loginname fehlt.',
   'Manager'                     => 'Manager',
+  'Missing \'description\' field.' => 'Fehlendes Feld \'description\'.',
+  'Missing \'tag\' field.'      => 'Fehlendes Feld \'tag\'.',
+  'More than one control file with the tag \'%s\' exist.' => 'Es gibt mehr als eine Kontrolldatei mit dem Tag \'%s\'.',
   'Multibyte Encoding'          => 'Schriftsatz',
   'Name'                        => 'Name',
   'New Templates'               => 'neue Vorlagen',
@@ -72,11 +77,13 @@ $self->{texts} = {
   'Stylesheet'                  => 'Stilvorlage',
   'Supervisor'                  => 'Supervisor',
   'Templates'                   => 'Vorlagen',
+  'The \'tag\' field must only consist of alphanumeric characters or the carachters - _ ( )' => 'Das Feld \'tag\' darf nur aus alphanumerischen Zeichen und den Zeichen - _ ( ) bestehen.',
   'The following Datasets are not in use and can be deleted' => 'Die folgenden Datenbanken sind nicht in Verwendung und können gelöscht werden',
   'The following Datasets need to be updated' => 'Folgende Datenbanken müssen aktualisiert werden',
   'The passwords do not match.' => 'Die Passw&ouml;rter stimmen nicht &uuml;berein.',
   'This is a preliminary check for existing sources. Nothing will be created or deleted at this stage!' => 'In diesem Schritt werden bestehende Datenbanken gesucht. Es werden noch keine Änderungen vorgenommen!',
   'To add a user to a group edit a name, change the login name and save.  A new user with the same variables will then be saved under the new login name.' => 'Um einer Gruppe einen neuen Benutzer hinzuzufügen, ändern und speichern Sie am einfachsten einen bestehen den Zugriffsnamen. Unter dem neuen Namen wird dann ein Benutzer mit denselben Einstellungen angelegt.',
+  'Unknown dependency \'%s\'.'  => 'Unbekannte Abh&auml;ngigkeit \'%s\'.',
   'Unlock System'               => 'System entsperren',
   'Update Dataset'              => 'Datenbank aktualisieren',
   'Use Templates'               => 'benutze Vorlagen',
index 7597d93..bd9fd60 100644 (file)
@@ -96,6 +96,7 @@ $self->{texts} = {
   'Ansprechpartner'             => '',
   'Application Error. No Format given!' => 'Fehler in der Anwendung. Das Format fehlt.',
   'Application Error. Wrong Format: ' => 'Fehler in der Anwendung. Falsches Format: ',
+  'Applying <TMPL_VAR file ESCAPE=HTML>:' => 'F&uuml;hre <TMPL_VAR file ESCAPE=HTML> aus:',
   'Apr'                         => 'Apr',
   'April'                       => 'April',
   'Are you sure you want to delete Invoice Number' => 'Soll die Rechnung mit folgender Nummer wirklich gelöscht werden:',
@@ -322,6 +323,7 @@ aktualisieren wollen?',
   'Department deleted!'         => 'Abteilung gelöscht.',
   'Department saved!'           => 'Abteilung gespeichert.',
   'Departments'                 => 'Abteilungen',
+  'Dependency loop detected:'   => 'Schleife in den Abh&auml;ngigkeiten entdeckt:',
   'Deposit'                     => 'Gutschrift',
   'Description'                 => 'Beschreibung',
   'Description missing!'        => 'Beschreibung fehlt.',
@@ -416,6 +418,7 @@ gestartet',
   'Erlöse EU o. UStId'          => 'Erl&ouml;se EU o. UStId',
   'Erlöse Inland'               => 'Erl&ouml;se Inland',
   'Error'                       => 'Fehler',
+  'Error in database control file \'%s\': %s' => 'Fehler in Datenbankupgradekontrolldatei \'%s\': %s',
   'Error!'                      => 'Fehler!',
   'Exch'                        => 'Wechselkurs.',
   'Exchangerate'                => 'Wechselkurs',
@@ -621,6 +624,8 @@ gestartet',
   'Method'                      => 'Verfahren',
   'Microfiche'                  => 'Mikrofilm',
   'Minimum Amount'              => 'Mindestbetrag',
+  'Missing \'description\' field.' => 'Fehlendes Feld \'description\'.',
+  'Missing \'tag\' field.'      => 'Fehlendes Feld \'tag\'.',
   'Missing Method!'             => 'Fehlender Voranmeldungszeitraum',
   'Missing Preferences: Outputroutine disabled' => 'Die Ausgabefunktionen sind wegen unzureichender Voreinstellungen deaktiviert!',
   'Missing Tax Authoritys Preferences' => 'Fehlende Angaben zum Finanzamt!',
@@ -630,6 +635,7 @@ gestartet',
   'Model'                       => 'Modell',
   'Monat'                       => 'Monat',
   'Monthly'                     => 'monatlich',
+  'More than one control file with the tag \'%s\' exist.' => 'Es gibt mehr als eine Kontrolldatei mit dem Tag \'%s\'.',
   'Multibyte Encoding'          => 'Schriftsatz',
   'MwSt. inkl.'                 => 'MwSt. inkl.',
   'N/A'                         => 'N.Z.',
@@ -945,6 +951,7 @@ gestartet',
   'Templates'                   => 'Vorlagen',
   'Terms missing in row '       => '+Tage fehlen in Zeile ',
   'Terms: Net'                  => 'Zahlungsziel',
+  'The \'tag\' field must only consist of alphanumeric characters or the carachters - _ ( )' => 'Das Feld \'tag\' darf nur aus alphanumerischen Zeichen und den Zeichen - _ ( ) bestehen.',
   'The base unit does not exist or it is about to be deleted in row %d.' => 'Die Basiseinheit in Zeile %d existiert nicht oder soll gel&ouml;scht werden.',
   'The base unit does not exist.' => 'Die Basiseinheit existiert nicht.',
   'The base unit relations must not contain loops (e.g. by saying that unit A\'s base unit is B, B\'s base unit is C and C\'s base unit is A) in row %d.' => 'Die Beziehungen der Einheiten d&uuml;rfen keine Schleifen beinhalten (z.B. wenn gesagt wird, dass Einheit As Basiseinheit B, Bs Basiseinheit C und Cs Basiseinheit A ist) in Zeile %d.',
@@ -1054,6 +1061,7 @@ gestartet',
   'Unit'                        => 'Einheit',
   'Unit of measure'             => 'Maßeinheit',
   'Units'                       => 'Einheiten',
+  'Unknown dependency \'%s\'.'  => 'Unbekannte Abh&auml;ngigkeit \'%s\'.',
   'Unlock System'               => 'System entsperren',
   'Until'                       => 'Bis',
   'Update'                      => 'Erneuern',
index 6c5829e..d643edd 100644 (file)
@@ -71,6 +71,7 @@ $self->{texts} = {
   'Department deleted!'         => 'Abteilung gelöscht.',
   'Department saved!'           => 'Abteilung gespeichert.',
   'Departments'                 => 'Abteilungen',
+  'Dependency loop detected:'   => 'Schleife in den Abh&auml;ngigkeiten entdeckt:',
   'Description'                 => 'Beschreibung',
   'Description missing!'        => 'Beschreibung fehlt.',
   'Discount'                    => 'Rabatt',
@@ -99,6 +100,7 @@ $self->{texts} = {
   'Erlöse EU m. UStId'          => 'Erl&ouml;se EU m. UStId',
   'Erlöse EU o. UStId'          => 'Erl&ouml;se EU o. UStId',
   'Erlöse Inland'               => 'Erl&ouml;se Inland',
+  'Error in database control file \'%s\': %s' => 'Fehler in Datenbankupgradekontrolldatei \'%s\': %s',
   'Expense'                     => 'Aufwandskonto',
   'Expense Account'             => 'Aufwandskonto',
   'Expense/Asset'               => 'Aufwand/Anlagen',
@@ -138,6 +140,9 @@ $self->{texts} = {
   'Link'                        => 'Verknüpfungen',
   'Long Dates'                  => 'Lange Monatsnamen',
   'Long Description'            => 'Langtext',
+  'Missing \'description\' field.' => 'Fehlendes Feld \'description\'.',
+  'Missing \'tag\' field.'      => 'Fehlendes Feld \'tag\'.',
+  'More than one control file with the tag \'%s\' exist.' => 'Es gibt mehr als eine Kontrolldatei mit dem Tag \'%s\'.',
   'Name'                        => 'Name',
   'Netto Terms'                 => 'Zahlungsziel netto',
   'No'                          => 'Nein',
@@ -197,6 +202,7 @@ $self->{texts} = {
   'Template Code'               => 'Vorlagenkürzel',
   'Template Code missing!'      => 'Vorlagenkürzel fehlt!',
   'Template saved!'             => 'Schablone gespeichert!',
+  'The \'tag\' field must only consist of alphanumeric characters or the carachters - _ ( )' => 'Das Feld \'tag\' darf nur aus alphanumerischen Zeichen und den Zeichen - _ ( ) bestehen.',
   'The base unit does not exist or it is about to be deleted in row %d.' => 'Die Basiseinheit in Zeile %d existiert nicht oder soll gel&ouml;scht werden.',
   'The base unit does not exist.' => 'Die Basiseinheit existiert nicht.',
   'The base unit relations must not contain loops (e.g. by saying that unit A\'s base unit is B, B\'s base unit is C and C\'s base unit is A) in row %d.' => 'Die Beziehungen der Einheiten d&uuml;rfen keine Schleifen beinhalten (z.B. wenn gesagt wird, dass Einheit As Basiseinheit B, Bs Basiseinheit C und Cs Basiseinheit A ist) in Zeile %d.',
@@ -256,6 +262,7 @@ $self->{texts} = {
   'UStVA-Nr. 98'                => 'Kz. 98',
   'Umsatzsteuervoranmeldung'    => 'Umsatzsteuervoranmeldung',
   'Unit'                        => 'Einheit',
+  'Unknown dependency \'%s\'.'  => 'Unbekannte Abh&auml;ngigkeit \'%s\'.',
   'Value'                       => 'Wert',
   'Variable'                    => 'Variable',
   'Warehouse deleted!'          => 'Das Lager wurde gelöscht.',
index 7a73120..6415370 100755 (executable)
@@ -15,6 +15,7 @@ $| = 1;
 $basedir  = "../..";
 $bindir   = "$basedir/bin/mozilla";
 $dbupdir  = "$basedir/sql/Pg-upgrade";
+$dbupdir2 = "$basedir/sql/Pg-upgrade2";
 $menufile = "menu.ini";
 $submitsearch = qr/type\s*=\s*[\"\']?submit/i;
 
@@ -45,6 +46,10 @@ opendir DIR, $dbupdir or die "$!";
 @dbplfiles = grep { /\.pl$/ } readdir DIR;
 closedir DIR;
 
+opendir DIR, $dbupdir2 or die "$!";
+@dbplfiles2 = grep { /\.pl$/ } readdir DIR;
+closedir DIR;
+
 # slurp the translations in
 if (-f 'all') {
   require "all";
@@ -59,6 +64,7 @@ if (-f 'all') {
 
 map({ handle_file($_, $bindir); } @progfiles);
 map({ handle_file($_, $dbupdir); } @dbplfiles);
+map({ handle_file($_, $dbupdir2); } @dbplfiles2);
 
 sub handle_file {
   my ($file, $dir) = @_;
index 52d2f65..d9b9894 100644 (file)
@@ -3,11 +3,18 @@ $self->{texts} = {
   'Database Host'               => 'Datenbankcomputer',
   'Dataset'                     => 'Datenbank',
   'Dataset upgrade'             => 'Datenbankaktualisierung',
+  'Dependency loop detected:'   => 'Schleife in den Abh&auml;ngigkeiten entdeckt:',
+  'Error in database control file \'%s\': %s' => 'Fehler in Datenbankupgradekontrolldatei \'%s\': %s',
   'Incorrect username or password!' => 'Ungültiger Benutzername oder falsches Passwort!',
   'Licensed to'                 => 'Lizensiert für',
   'Login'                       => 'Anmeldung',
   'Login Name'                  => 'Benutzername',
+  'Missing \'description\' field.' => 'Fehlendes Feld \'description\'.',
+  'Missing \'tag\' field.'      => 'Fehlendes Feld \'tag\'.',
+  'More than one control file with the tag \'%s\' exist.' => 'Es gibt mehr als eine Kontrolldatei mit dem Tag \'%s\'.',
   'Password'                    => 'Passwort',
+  'The \'tag\' field must only consist of alphanumeric characters or the carachters - _ ( )' => 'Das Feld \'tag\' darf nur aus alphanumerischen Zeichen und den Zeichen - _ ( ) bestehen.',
+  'Unknown dependency \'%s\'.'  => 'Unbekannte Abh&auml;ngigkeit \'%s\'.',
   'User'                        => 'Benutzer',
   'Version'                     => 'Version',
   'You are logged out!'         => 'Auf Wiedersehen!',
index f4a7f8e..4252558 100644 (file)
@@ -32,10 +32,12 @@ $self->{texts} = {
   'Dauerfristverlängerung'      => 'Dauerfristverlängerung',
   'Dec'                         => 'Dez',
   'December'                    => 'Dezember',
+  'Dependency loop detected:'   => 'Schleife in den Abh&auml;ngigkeiten entdeckt:',
   'Description'                 => 'Beschreibung',
   'ELSTER Export nach Taxbird'  => 'ELSTER-Export nach Taxbird',
   'ELSTER Export nach Winston'  => 'ELSTER Export nach Winston',
   'ELSTER-Steuernummer: '       => 'ELSTER-Steuernummer: ',
+  'Error in database control file \'%s\': %s' => 'Fehler in Datenbankupgradekontrolldatei \'%s\': %s',
   'Fax'                         => 'Fax',
   'Fax. : '                     => 'Fax. : ',
   'Fax.: '                      => 'Fax.: ',
@@ -69,9 +71,12 @@ $self->{texts} = {
   'May'                         => 'Mai',
   'May '                        => 'Mai',
   'Method'                      => 'Verfahren',
+  'Missing \'description\' field.' => 'Fehlendes Feld \'description\'.',
+  'Missing \'tag\' field.'      => 'Fehlendes Feld \'tag\'.',
   'Missing Method!'             => 'Fehlender Voranmeldungszeitraum',
   'Missing Preferences: Outputroutine disabled' => 'Die Ausgabefunktionen sind wegen unzureichender Voreinstellungen deaktiviert!',
   'Missing Tax Authoritys Preferences' => 'Fehlende Angaben zum Finanzamt!',
+  'More than one control file with the tag \'%s\' exist.' => 'Es gibt mehr als eine Kontrolldatei mit dem Tag \'%s\'.',
   'Name'                        => 'Name',
   'Nov'                         => 'Nov',
   'November'                    => 'November',
@@ -93,10 +98,12 @@ $self->{texts} = {
   'Tel. : '                     => 'Tel. : ',
   'Tel.: '                      => 'Tel.: ',
   'Telefon'                     => 'Telefon',
+  'The \'tag\' field must only consist of alphanumeric characters or the carachters - _ ( )' => 'Das Feld \'tag\' darf nur aus alphanumerischen Zeichen und den Zeichen - _ ( ) bestehen.',
   'USTVA-Hint: Method'          => 'Wenn Sie Ist-Versteuert sind, wählen Sie die Einnahmen-/Überschuß-Rechnung aus. Sind Sie Soll-Versteuert und bilanzverpflichtet, dann wählen Sie Bilanz aus.',
   'USTVA-Hint: Tax Authoritys'  => 'Bitte das Bundesland UND die Stadt bzw. den Einzugsbereich Ihres zuständigen Finanzamts auswählen.',
   'UStVA'                       => 'UStVA',
   'UStVA als PDF-Dokument'      => 'UStVa als PDF-Dokument',
+  'Unknown dependency \'%s\'.'  => 'Unbekannte Abh&auml;ngigkeit \'%s\'.',
   'Vendor not on file!'         => 'Lieferant ist nicht in der Datenbank!',
   'Verfahren'                   => 'Verfahren',
   'Verrechnung des Erstattungsbetrages erwünscht (Zeile 71)' => 'Verrechnung des Erstattungsbetrages erwünscht (Zeile 71)',
diff --git a/scripts/dbupgrade2_tool.pl b/scripts/dbupgrade2_tool.pl
new file mode 100755 (executable)
index 0000000..850ef8d
--- /dev/null
@@ -0,0 +1,192 @@
+#!/usr/bin/perl
+
+BEGIN {
+  if (! -d "bin" || ! -d "SL") {
+    print("This tool must be run from the Lx-Office ERP base directory.\n");
+    exit(1);
+  }
+}
+
+use DBI;
+use Data::Dumper;
+use Getopt::Long;
+
+use SL::LXDebug;
+
+$lxdebug = LXDebug->new();
+
+use SL::Form;
+use SL::DBUpgrade2;
+
+#######
+#######
+#######
+
+sub show_help {
+  print("dbupgrade2_tool.pl [--list] [--tree] [--rtree] [--graphviz]\n" .
+        "                   [--nodepds] [--help]\n");
+}
+
+sub calc_rev_depends {
+  map({ $_->{"rev_depends"} = []; } values(%{$controls}));
+  foreach my $control (values(%{$controls})) {
+    map({ push(@{$controls->{$_}{"rev_depends"}}, $control->{"tag"}) }
+        @{$control->{"depends"}});
+  }
+}
+
+sub dump_list {
+  my @sorted_controls = sort_dbupdate_controls($controls);
+
+  print("LIST VIEW\n\n");
+  print("number tag depth priority\n");
+  $i = 0;
+  foreach (@sorted_controls) {
+    print("$i $_->{tag} $_->{depth} $_->{priority}\n");
+    $i++;
+  }
+
+  print("\n");
+}
+
+sub dump_node {
+  my ($tag, $depth) = @_;
+
+  print(" " x $depth . $tag . "\n");
+
+  my $c = $controls->{$tag};
+  my $num = scalar(@{$c->{"depends"}});
+  for (my $i = 0; $i < $num; $i++) {
+    dump_node($c->{"depends"}[$i], $depth + 1);
+  }
+}
+
+sub dump_tree {
+  print("TREE VIEW\n\n");
+
+  calc_rev_depends();
+
+  my @sorted_controls = sort_dbupdate_controls($controls);
+
+  foreach my $control (@sorted_controls) {
+    dump_node($control->{"tag"}, "") unless (@{$control->{"rev_depends"}});
+  }
+
+  print("\n");
+}
+
+sub dump_node_reverse {
+  my ($tag, $depth) = @_;
+
+  print(" " x $depth . $tag . "\n");
+
+  my $c = $controls->{$tag};
+  my $num = scalar(@{$c->{"rev_depends"}});
+  for (my $i = 0; $i < $num; $i++) {
+    dump_node_reverse($c->{"rev_depends"}[$i], $depth + 1);
+  }
+}
+
+sub dump_tree_reverse {
+  print("REVERSE TREE VIEW\n\n");
+
+  calc_rev_depends();
+
+  my @sorted_controls = sort_dbupdate_controls($controls);
+
+  foreach my $control (@sorted_controls) {
+    last if ($control->{"depth"} > 1);
+    dump_node_reverse($control->{"tag"}, "");
+  }
+
+  print("\n");
+}
+
+sub dump_graphviz {
+  print("GRAPHVIZ POSTCRIPT\n\n");
+  print("Output will be written to db_dependencies.ps\n");
+  $dot = "|dot -Tps ";
+  open(OUT, "${dot}> db_dependencies.ps");
+  print(OUT
+        "digraph db_dependencies {\n" .
+        "node [shape=box];\n");
+  my %ranks;
+  foreach my $c (values(%{$controls})) {
+    $ranks{$c->{"depth"}} = [] unless ($ranks{$c->{"depth"}});
+    push(@{$ranks{$c->{"depth"}}}, $c->{"tag"});
+  }
+  foreach (sort(keys(%ranks))) {
+    print(OUT "{ rank = same; " .
+          join("", map({ '"' . $_ . '"; ' } @{$ranks{$_}})) .
+          " }\n");
+  }
+  foreach my $c (values(%{$controls})) {
+    print(OUT "$c->{tag};\n");
+    foreach my $d (@{$c->{"depends"}}) {
+      print(OUT "$c->{tag} -> $d;\n");
+    }
+  }
+  print(OUT "}\n");
+  close(OUT);
+}
+
+sub dump_nodeps {
+  calc_rev_depends();
+
+  print("SCRIPTS NO OTHER SCRIPTS DEPEND ON\n\n" .
+        join("\n",
+             map({ $_->{"tag"} }
+                 grep({ !@{$_->{"rev_depends"}} }
+                      values(%{$controls})))) .
+        "\n\n");
+}
+
+#######
+#######
+#######
+
+eval { require "lx-erp.conf"; };
+
+$form = Form->new();
+$locale = Locale->new("de", "login");
+
+#######
+#######
+#######
+
+my ($opt_list, $opt_tree, $opt_rtree, $opt_nodeps, $opt_graphviz, $opt_help);
+
+GetOptions("list" => \$opt_list,
+           "tree" => \$opt_tree,
+           "rtree" => \$opt_rtree,
+           "nodeps" => \$opt_nodeps,
+           "graphviz" => \$opt_graphviz,
+           "help" => \$opt_help,
+  );
+
+if ($opt_help) {
+  show_help();
+  exit(0);
+}
+
+$controls = parse_dbupdate_controls($form, "Pg");
+
+if ($opt_list) {
+  dump_list();
+}
+
+if ($opt_tree) {
+  dump_tree();
+}
+
+if ($opt_rtree) {
+  dump_tree_reverse();
+}
+
+if ($opt_graphviz) {
+  dump_graphviz();
+}
+
+if ($opt_nodeps) {
+  dump_nodeps();
+}
diff --git a/sql/Pg-upgrade/Pg-upgrade-2.4.0.0-2.4.0.1.sql b/sql/Pg-upgrade/Pg-upgrade-2.4.0.0-2.4.0.1.sql
deleted file mode 100644 (file)
index b55199e..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-ALTER TABLE language ADD COLUMN output_numberformat text;
-ALTER TABLE language ADD COLUMN output_dateformat text;
-ALTER TABLE language ADD COLUMN output_longdates boolean;
diff --git a/sql/Pg-upgrade/Pg-upgrade-2.4.0.1-2.4.0.2.sql b/sql/Pg-upgrade/Pg-upgrade-2.4.0.1-2.4.0.2.sql
deleted file mode 100644 (file)
index ed2c2cd..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-CREATE TABLE units_language (
-       unit varchar (20) NOT NULL,
-       language_id integer NOT NULL,
-       localized varchar (20),
-       localized_plural varchar (20),
-
-       FOREIGN KEY (unit) REFERENCES units (name),
-       FOREIGN KEY (language_id) REFERENCES language (id)
-);
-CREATE INDEX units_name_idx ON units (name);
-CREATE INDEX units_language_unit_idx ON units_language (unit);
diff --git a/sql/Pg-upgrade/Pg-upgrade-2.4.0.2-2.4.0.3.sql b/sql/Pg-upgrade/Pg-upgrade-2.4.0.2-2.4.0.3.sql
deleted file mode 100644 (file)
index 36d291a..0000000
+++ /dev/null
@@ -1 +0,0 @@
-update tax set id=0 WHERE taxkey=0;
diff --git a/sql/Pg-upgrade2/README b/sql/Pg-upgrade2/README
new file mode 100644 (file)
index 0000000..3a53151
--- /dev/null
@@ -0,0 +1,3 @@
+Bitte lesen Sie die Datei doc/sql-upgrade-dateien.txt, bevor
+Sie hier Dateien anlegen.
+
diff --git a/sql/Pg-upgrade2/language_output_formatting.sql b/sql/Pg-upgrade2/language_output_formatting.sql
new file mode 100644 (file)
index 0000000..4183c49
--- /dev/null
@@ -0,0 +1,6 @@
+-- @tag: language_output_formatting
+-- @description: Speichern des Ausgabeformates f&uuml;r Zahlen und Datumsangaben bei jeder Sprache.
+-- @depends:
+ALTER TABLE language ADD COLUMN output_numberformat text;
+ALTER TABLE language ADD COLUMN output_dateformat text;
+ALTER TABLE language ADD COLUMN output_longdates boolean;
diff --git a/sql/Pg-upgrade2/tax_id_if_taxkey_is_0.sql b/sql/Pg-upgrade2/tax_id_if_taxkey_is_0.sql
new file mode 100644 (file)
index 0000000..3df3414
--- /dev/null
@@ -0,0 +1,4 @@
+-- @tag: tax_id_if_taxkey_is_0
+-- @description: Aktualisierung der Spalte tax.id, wenn tax.taxkey = 0 ist.
+-- @depends:
+UPDATE tax SET id = 0 WHERE taxkey = 0;
diff --git a/sql/Pg-upgrade2/units_translations_and_singular_plural_distinction.sql b/sql/Pg-upgrade2/units_translations_and_singular_plural_distinction.sql
new file mode 100644 (file)
index 0000000..c1603e6
--- /dev/null
@@ -0,0 +1,14 @@
+-- @tag: units_translations_and_singular_plural_distinction
+-- @description: F&uuml;r jede Einheit kann f&uuml;r jede Sprache eine &Uuml;bersetzung sowie eine Unterscheidung zwischen Singular und Plural gespeichert werden.
+-- @depends:
+CREATE TABLE units_language (
+       unit varchar (20) NOT NULL,
+       language_id integer NOT NULL,
+       localized varchar (20),
+       localized_plural varchar (20),
+
+       FOREIGN KEY (unit) REFERENCES units (name),
+       FOREIGN KEY (language_id) REFERENCES language (id)
+);
+CREATE INDEX units_name_idx ON units (name);
+CREATE INDEX units_language_unit_idx ON units_language (unit);
diff --git a/templates/webpages/dbupgrade/upgrade_message2_de.html b/templates/webpages/dbupgrade/upgrade_message2_de.html
new file mode 100644 (file)
index 0000000..685a391
--- /dev/null
@@ -0,0 +1 @@
+F&uuml;hre <TMPL_VAR file ESCAPE=HTML> aus: <TMPL_VAR description ESCAPE=HTML><br>
diff --git a/templates/webpages/dbupgrade/upgrade_message2_master.html b/templates/webpages/dbupgrade/upgrade_message2_master.html
new file mode 100644 (file)
index 0000000..f0cc573
--- /dev/null
@@ -0,0 +1 @@
+<translate>Applying <TMPL_VAR file ESCAPE=HTML>:</translate> <TMPL_VAR description ESCAPE=HTML><br>