From: Moritz Bunkus Date: Fri, 22 Dec 2006 15:13:00 +0000 (+0000) Subject: Neuer Datenbankupgrademechanismus: Die Upgradedateien im neuen Verzeichnis sql/Pg... X-Git-Tag: release-2.4.1~1^2~117 X-Git-Url: http://wagnertech.de/git?a=commitdiff_plain;h=4fd8bdbfb46b8b63a7d05457959b98c4956d1dbd;p=kivitendo-erp.git Neuer Datenbankupgrademechanismus: Die Upgradedateien im neuen Verzeichnis sql/Pg-upgrade2 enthalten Informationen über Abhängigkeiten, die von Lx-Office ausgewertet werden, um die Reihenfolge zu bestimmen, in der Updates angewandt werden. Wird nicht mehr über Versionsnummern geregelt. --- diff --git a/SL/DBUpgrade2.pm b/SL/DBUpgrade2.pm new file mode 100644 index 000000000..514448f6d --- /dev/null +++ b/SL/DBUpgrade2.pm @@ -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 () { + 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; diff --git a/SL/User.pm b/SL/User.pm index f331ca4af..88deb0b1f 100644 --- a/SL/User.pm +++ b/SL/User.pm @@ -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 index 000000000..5b9ae1808 --- /dev/null +++ b/doc/sql-upgrade-dateien.txt @@ -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. + diff --git a/locale/de/admin b/locale/de/admin index 0ebbd1755..99ba1546a 100644 --- a/locale/de/admin +++ b/locale/de/admin @@ -29,11 +29,13 @@ $self->{texts} = { 'Date Format' => 'Datumsformat', 'Delete' => 'Löschen', 'Delete Dataset' => 'Datenbank löschen', + 'Dependency loop detected:' => 'Schleife in den Abhä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örter stimmen nicht ü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ängigkeit \'%s\'.', 'Unlock System' => 'System entsperren', 'Update Dataset' => 'Datenbank aktualisieren', 'Use Templates' => 'benutze Vorlagen', diff --git a/locale/de/all b/locale/de/all index 7597d93b6..bd9fd60f3 100644 --- a/locale/de/all +++ b/locale/de/all @@ -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 :' => 'Führe 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ängigkeiten entdeckt:', 'Deposit' => 'Gutschrift', 'Description' => 'Beschreibung', 'Description missing!' => 'Beschreibung fehlt.', @@ -416,6 +418,7 @@ gestartet', 'Erlöse EU o. UStId' => 'Erlöse EU o. UStId', 'Erlöse Inland' => 'Erlö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ö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ü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ängigkeit \'%s\'.', 'Unlock System' => 'System entsperren', 'Until' => 'Bis', 'Update' => 'Erneuern', diff --git a/locale/de/am b/locale/de/am index 6c5829e07..d643edd7a 100644 --- a/locale/de/am +++ b/locale/de/am @@ -71,6 +71,7 @@ $self->{texts} = { 'Department deleted!' => 'Abteilung gelöscht.', 'Department saved!' => 'Abteilung gespeichert.', 'Departments' => 'Abteilungen', + 'Dependency loop detected:' => 'Schleife in den Abhängigkeiten entdeckt:', 'Description' => 'Beschreibung', 'Description missing!' => 'Beschreibung fehlt.', 'Discount' => 'Rabatt', @@ -99,6 +100,7 @@ $self->{texts} = { 'Erlöse EU m. UStId' => 'Erlöse EU m. UStId', 'Erlöse EU o. UStId' => 'Erlöse EU o. UStId', 'Erlöse Inland' => 'Erlö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ö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ü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ängigkeit \'%s\'.', 'Value' => 'Wert', 'Variable' => 'Variable', 'Warehouse deleted!' => 'Das Lager wurde gelöscht.', diff --git a/locale/de/locales.pl b/locale/de/locales.pl index 7a73120e8..641537044 100755 --- a/locale/de/locales.pl +++ b/locale/de/locales.pl @@ -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) = @_; diff --git a/locale/de/login b/locale/de/login index 52d2f65ae..d9b989487 100644 --- a/locale/de/login +++ b/locale/de/login @@ -3,11 +3,18 @@ $self->{texts} = { 'Database Host' => 'Datenbankcomputer', 'Dataset' => 'Datenbank', 'Dataset upgrade' => 'Datenbankaktualisierung', + 'Dependency loop detected:' => 'Schleife in den Abhä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ängigkeit \'%s\'.', 'User' => 'Benutzer', 'Version' => 'Version', 'You are logged out!' => 'Auf Wiedersehen!', diff --git a/locale/de/ustva b/locale/de/ustva index f4a7f8ee3..4252558fb 100644 --- a/locale/de/ustva +++ b/locale/de/ustva @@ -32,10 +32,12 @@ $self->{texts} = { 'Dauerfristverlängerung' => 'Dauerfristverlängerung', 'Dec' => 'Dez', 'December' => 'Dezember', + 'Dependency loop detected:' => 'Schleife in den Abhä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ä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 index 000000000..850ef8d71 --- /dev/null +++ b/scripts/dbupgrade2_tool.pl @@ -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 index b55199e29..000000000 --- a/sql/Pg-upgrade/Pg-upgrade-2.4.0.0-2.4.0.1.sql +++ /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 index ed2c2cd6e..000000000 --- a/sql/Pg-upgrade/Pg-upgrade-2.4.0.1-2.4.0.2.sql +++ /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 index 36d291a16..000000000 --- a/sql/Pg-upgrade/Pg-upgrade-2.4.0.2-2.4.0.3.sql +++ /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 index 000000000..3a531519f --- /dev/null +++ b/sql/Pg-upgrade2/README @@ -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 index 000000000..4183c4970 --- /dev/null +++ b/sql/Pg-upgrade2/language_output_formatting.sql @@ -0,0 +1,6 @@ +-- @tag: language_output_formatting +-- @description: Speichern des Ausgabeformates fü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 index 000000000..3df341430 --- /dev/null +++ b/sql/Pg-upgrade2/tax_id_if_taxkey_is_0.sql @@ -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 index 000000000..c1603e625 --- /dev/null +++ b/sql/Pg-upgrade2/units_translations_and_singular_plural_distinction.sql @@ -0,0 +1,14 @@ +-- @tag: units_translations_and_singular_plural_distinction +-- @description: Für jede Einheit kann für jede Sprache eine Ü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 index 000000000..685a39191 --- /dev/null +++ b/templates/webpages/dbupgrade/upgrade_message2_de.html @@ -0,0 +1 @@ +Führe aus:
diff --git a/templates/webpages/dbupgrade/upgrade_message2_master.html b/templates/webpages/dbupgrade/upgrade_message2_master.html new file mode 100644 index 000000000..f0cc5738a --- /dev/null +++ b/templates/webpages/dbupgrade/upgrade_message2_master.html @@ -0,0 +1 @@ +Applying :