'*) Since version 2.7 these parameters ares set in the client database and not in the configuration file, details in chapter:' => '*) Seit 2.7 werden Gewinnermittlungsart, Versteuerungsart und Warenbuchungsmethode in der Mandanten-DB gesteuert und nicht mehr in der Konfigurationsdatei, Umstellungs-Details:',
'*/' => '*/',
'---please select---' => '---bitte auswählen---',
+ '. Automatically generated.' => '. Automatisch erzeugt.',
'...after loggin in' => '...nach dem Anmelden',
'...done' => '...fertig',
'...on the TODO list' => '...auf der Aufgabenliste',
+ '0% tax with taxkey' => '0% Steuer mit Steuerschlüssel ',
'1. Quarter' => '1. Quartal',
'2. Quarter' => '2. Quartal',
'3. Quarter' => '3. Quartal',
'Income accno' => 'Erlöskonto',
'Incoming Payments' => 'Zahlungseingänge',
'Incoming invoice number' => 'Eingangsrechnungsnummer',
+ 'Inconsistency in database' => 'Unstimmigkeiten in der Datenbank',
'Incorrect Password!' => 'Ungültiges Passwort!',
'Incorrect password!' => 'Ungültiges Passwort!',
'Incorrect username or password!' => 'Ungültiger Benutzername oder falsches Passwort!',
'Please Check the bank information for each vendor:' => 'Bitte überprüfen Sie die Kontoinformationen der Lieferanten:',
'Please ask your administrator to create warehouses and bins.' => 'Bitten Sie Ihren Administrator, dass er Lager und Lagerplätze anlegt.',
'Please contact your administrator.' => 'Bitte wenden Sie sich an Ihren Administrator.',
+ 'Please define a taxkey for the following taxes and run the update again:' => 'Bitte definieren Sie einen Steuerschlüssel für die folgenden Steuern und starten Sie dann das Update erneut:',
'Please enter a profile name.' => 'Bitte geben Sie einen Profilnamen an.',
'Please enter the login for the new user.' => 'Bitte geben Sie das Login für den neuen Benutzer ein.',
'Please enter the name of the database that will be used as the template for the new database:' => 'Bitte geben Sie den Namen der Datenbank an, die als Vorlage für die neue Datenbank benutzt wird:',
'Please select the destination bank account for the collections:' => 'Bitte wählen Sie das Bankkonto als Ziel für die Einzüge aus:',
'Please select the source bank account for the transfers:' => 'Bitte wählen Sie das Bankkonto als Quelle für die Überweisungen aus:',
'Please seletct the dataset you want to delete:' => 'Bitte wählen Sie die zu löschende Datenbank aus:',
+ 'Please set another taxnumber for the following taxes and run the update again:' => 'Bitte wählen Sie ein anderes Steuerautomatik-Konto für die folgenden Steuern aus uns starten Sie dann das Update erneut.',
'Please specify a description for the warehouse designated for these goods.' => 'Bitte geben Sie den Namen des Ziellagers für die übernommenen Daten ein.',
'Plural' => 'Plural',
'Port' => 'Port',
'There are #1 more open invoices from this vendor with other currencies.' => 'Es gibt #1 weitere offene Rechnungen von diesem Lieferanten, die in anderen Währungen ausgestellt wurden.',
'There are #1 unfinished follow-ups of which #2 are due.' => 'Es gibt #1 Wiedervorlage(n), von denen #2 fällig ist/sind.',
'There are bookings to the account 3803 after 01.01.2007. If you didn\'t change this account manually to 19% the bookings are probably incorrect.' => 'Das Konto 3803 wurde nach dem 01.01.2007 bebucht. Falls Sie dieses Konto nicht manuell auf 19% gestellt haben sind die Buchungen wahrscheinlich mit falscher Umsatzsteuer gebucht worden.',
+ 'There are entries in tax where taxkey is NULL.' => 'In der Datenbank sind Steuern ohne Steuerschlüssel vorhanden (in der Tabelle tax Spalte taxkey).',
'There are four tax zones.' => 'Es gibt vier Steuerzonen.',
+ 'There are invalid taxnumbers in use.' => 'Es werden ungültige Steuerautomatik-Konten benutzt.',
'There are no entries in the background job history.' => 'Es gibt keine Einträge im Hintergrund-Job-Verlauf.',
'There are no items in stock.' => 'Dieser Artikel ist nicht eingelagert.',
'There are no items on your TODO list at the moment.' => 'Ihre Aufgabenliste enthält momentan keine Einträge.',
'There are still transfers not matching the qty of the delivery order. Stock operations can not be changed later. Do you really want to proceed?' => 'Einige der Lagerbewegungen sind nicht vollständig und Lagerbewegungen können nachträglich nicht mehr verändert werden. Wollen Sie wirklich fortfahren?',
'There are usually three ways to install Perl modules.' => 'Es gibt normalerweise drei Arten, ein Perlmodul zu installieren.',
'There is already a taxkey 0 with tax rate not 0.' => 'Es existiert bereits ein Steuerschlüssel mit Steuersatz ungleich 0%.',
+ 'There is an inconsistancy in your database.' => 'In Ihrer Datenbank sind Unstimmigkeiten vorhanden.',
'There is at least one sales or purchase invoice for which kivitendo recorded an inventory transaction with taxkeys even though no tax was recorded.' => 'Es gibt mindestens eine Verkaufs- oder Einkaufsrechnung, für die kivitendo eine Warenbestandsbuchung ohne dazugehörige Steuerbuchung durchgeführt hat.',
'There is at least one transaction for which the user has chosen a logically wrong taxkey.' => 'Es gibt mindestens eine Buchung, bei der ein logisch nicht passender Steuerschlüssel ausgewählt wurde.',
'There is not enough available of \'#1\' at warehouse \'#2\', bin \'#3\', #4, #5, for the transfer of #6.' => 'Von \'#1\' ist in Lager \'#2\', Lagerplatz \'#3\', #4, #5, nicht genügend eingelagert, um insgesamt #6 auszulagern.',
'*) Since version 2.7 these parameters ares set in the client database and not in the lx-erp.conf / lx_office.conf file, details in chapter:' => '',
'*/' => '',
'---please select---' => '',
+ '. Automatically generated.' => '',
'...after loggin in' => '',
'...done' => '',
'...on the TODO list' => '',
+ '0% tax with taxkey' => '',
'1. Quarter' => '',
'2. Quarter' => '',
'3. Quarter' => '',
'There are #1 more open invoices from this vendor with other currencies.' => '',
'There are #1 unfinished follow-ups of which #2 are due.' => '',
'There are bookings to the account 3803 after 01.01.2007. If you didn\'t change this account manually to 19% the bookings are probably incorrect.' => '',
+ 'There are entries in tax where taxkey is NULL.' => '',
'There are four tax zones.' => '',
+ 'There are invalid taxnumbers in use.' => '',
+ 'There are no entries in the background job history.' => '',
'There are no items in stock.' => '',
'There are no items on your TODO list at the moment.' => '',
'There are still entries in the database for which no unit has been assigned.' => '',
'There are still transfers not matching the qty of the delivery order. Stock operations can not be changed later. Do you really want to proceed?' => '',
'There are usually three ways to install Perl modules.' => '',
'There is already a taxkey 0 with tax rate not 0.' => '',
+ 'There is an inconsistancy in your database.' => '',
'There is at least one sales or purchase invoice for which kivitendo recorded an inventory transaction with taxkeys even though no tax was recorded.' => '',
'There is at least one transaction for which the user has chosen a logically wrong taxkey.' => '',
'There is not enough available of \'#1\' at warehouse \'#2\', bin \'#3\', #4, #5, for the transfer of #6.' => '',
--- /dev/null
+# @tag: tax_constraints
+# @description: Setzt Fremdschlüssel und andere constraints auf die Tabellen tax und taxkeys
+# @depends: release_3_0_0
+# @charset: utf-8
+
+use utf8;
+use strict;
+use SL::Locale;
+
+die("This script cannot be run from the command line.") unless ($main::form);
+
+sub mydberror {
+ my ($msg) = @_;
+ die($dbup_locale->text("Database update error:") . "<br>$msg<br>" . $DBI::errstr);
+}
+
+sub do_query {
+ my ($query, $may_fail) = @_;
+
+ if (!$dbh->do($query)) {
+ mydberror($query) unless ($may_fail);
+ $dbh->rollback();
+ $dbh->begin_work();
+ }
+}
+
+sub do_update {
+#CHECK CONSISTANCY OF tax
+ #update tax.rate and tax.taxdescription in order to set later NOT NULL constraints
+my $query= <<SQL;
+UPDATE tax SET rate=0 WHERE rate IS NULL;
+UPDATE tax SET taxdescription='-' WHERE taxdescription IS NULL;
+SQL
+
+ do_query($query);
+
+ #check automatic tax accounts
+ $query= <<SQL;
+SELECT count(*) FROM tax WHERE chart_id NOT IN (SELECT id FROM chart);
+SQL
+
+ my ($invalid_tax_account) = $dbh->selectrow_array($query);
+
+ if ($invalid_tax_account > 0){
+ #list all invalid tax accounts
+ $query = <<SQL;
+SELECT id,
+ taxkey,
+ taxdescription,
+ round(rate * 100, 2) AS rate
+FROM tax WHERE chart_id NOT IN (SELECT id FROM chart);
+SQL
+
+ my $sth = $dbh->prepare($query);
+ $sth->execute || $main::form->dberror($query);
+
+ $main::form->{TAX} = [];
+ while (my $ref = $sth->fetchrow_hashref("NAME_lc")) {
+ push @{ $main::form->{TAX} }, $ref;
+ }
+ $sth->finish;
+
+ $main::form->{invalid_tax_account} = 1;
+ print_error_message();
+ return 0;
+ }
+
+ #check entry tax.taxkey of NOT NULL
+ $query= <<SQL;
+SELECT count(*) FROM tax WHERE taxkey IS NULL;
+SQL
+
+ my ($taxkey_is_null) = $dbh->selectrow_array($query);
+
+ if ($taxkey_is_null > 0){
+ #list all invalid tax accounts
+ $query = <<SQL;
+SELECT id,
+ taxdescription,
+ round(rate * 100, 2) AS rate,
+ (SELECT accno FROM chart WHERE id = chart_id) AS taxnumber,
+ (SELECT description FROM chart WHERE id = chart_id) AS account_description
+FROM tax
+WHERE taxkey IS NULL;
+SQL
+
+ my $sth = $dbh->prepare($query);
+ $sth->execute || $main::form->dberror($query);
+
+ $main::form->{TAX} = [];
+ while (my $ref = $sth->fetchrow_hashref("NAME_lc")) {
+ push @{ $main::form->{TAX} }, $ref;
+ }
+ $sth->finish;
+
+ $main::form->{taxkey_is_null} = 1;
+ print_error_message();
+ return 0;
+ }
+#END CHECK OF tax
+
+#CHECK CONSISTANCY OF taxkeys
+ #delete invalide entries in taxkeys
+ $query= <<SQL;
+DELETE FROM taxkeys
+WHERE chart_id IS NULL
+OR chart_id NOT IN (SELECT id FROM chart)
+OR startdate IS NULL;
+SQL
+
+ do_query($query);
+
+#There are 3 cases for taxkeys.tax_id and taxkeys.taxkey_id
+#taxkeys.taxkey_id is NULL and taxkeys.tax_id is not NULL:
+
+ #Update taxkeys.taxkey_id with tax.taxkey
+ $query= <<SQL;
+UPDATE taxkeys
+SET taxkey_id = (SELECT t.taxkey
+ FROM tax t
+ WHERE t.id=tax_id)
+WHERE taxkey_id IS NULL
+AND tax_id IS NOT NULL;
+SQL
+
+ do_query($query);
+
+#taxkeys.taxkey_id and taxkeys.tax_id are NULL:
+
+ #Set taxkey 0 in this case:
+ $query= <<SQL;
+UPDATE taxkeys
+SET taxkey_id = 0, tax_id = (SELECT id FROM tax WHERE taxkey=0)
+WHERE taxkey_id IS NULL
+AND tax_id IS NULL;
+SQL
+
+ do_query($query);
+
+#Last case where taxkeys.taxkey_id is not null and taxkeys.tax_id is null
+
+ #If such entries exist we update with an entry in tax where tax.rate=0
+ #and tax.taxkey corresponds to taxkeys.taxkey_id.
+ #If no entry in tax with rate 0 and taxkey taxkeys.taxkey_id exists
+ #we create one.
+ $query= <<SQL;
+SELECT DISTINCT taxkey_id
+FROM taxkeys
+WHERE taxkey_id IS NOT NULL
+AND tax_id IS NULL;
+SQL
+
+ my $sth = $dbh->prepare($query);
+ $sth->execute || $main::form->dberror($query);
+
+ $main::form->{TAXID} = [];
+ my $rowcount = 0;
+ while (my $ref = $sth->fetchrow_hashref("NAME_lc")) {
+ push @{ $main::form->{TAXID} }, $ref;
+ $rowcount++;
+ }
+ $sth->finish;
+
+ my $insertquery;
+ my $updatequery;
+ my $tax_id;
+ for my $i (0 .. $rowcount-1){
+ $query= qq|
+SELECT id FROM tax WHERE rate = 0 and taxkey=| . $main::form->{TAXID}[$i]->{taxkey_id} . qq| LIMIT 1
+|;
+ ($tax_id) = $dbh->selectrow_array($query);
+ if ( not $tax_id ){
+ $insertquery=qq|
+INSERT INTO tax (rate, taxdescription, taxkey) VALUES (0, '| . $::locale->text('0% tax with taxkey') . $main::form->{TAXID}[$i]->{taxkey_id} . $::locale->text('. Automatically generated.') .
+qq|', | . $main::form->{TAXID}[$i]->{taxkey_id} . qq|);
+|;
+ do_query($insertquery);
+ ($tax_id) = $dbh->selectrow_array($query);
+ $tax_id || $main::form->dberror($query);
+ }
+ $updatequery = qq|
+UPDATE taxkeys SET tax_id= | . $tax_id . qq| WHERE taxkey_id = | . $main::form->{TAXID}[$i]->{taxkey_id} . qq| AND tax_id IS NULL
+|;
+ do_query($updatequery);
+ }
+
+#The triple taxkey_id, chart_id, startdate in taxkeys has to be unique
+ #Select these entries:
+ $query= <<SQL;
+SELECT DISTINCT tk1.chart_id AS chart_id, tk1.startdate AS startdate
+FROM taxkeys tk1
+WHERE (SELECT count(*)
+ FROM taxkeys tk2
+ WHERE tk2.chart_id = tk1.chart_id
+ AND tk2.startdate = tk1.startdate) > 1;
+SQL
+
+ $sth = $dbh->prepare($query);
+ $sth->execute || $main::form->dberror($query);
+
+ $main::form->{TAXKEYS} = [];
+ $rowcount = 0;
+ while (my $ref = $sth->fetchrow_hashref("NAME_lc")) {
+ push @{ $main::form->{TAXKEYS} }, $ref;
+ $rowcount++;
+ }
+ $sth->finish;
+
+ for my $i (0 .. $rowcount-1){
+ $query= qq|
+DELETE FROM taxkeys tk1
+WHERE (SELECT count(*)
+ FROM taxkeys tk2
+ WHERE tk2.chart_id = tk1.chart_id
+ AND tk2.startdate = tk1.startdate) > 1
+AND NOT tk1.id = (SELECT id
+ FROM taxkeys
+ WHERE chart_id = | . $main::form->{TAXKEYS}[$i]->{chart_id} . qq|
+ AND startdate = '| . $main::form->{TAXKEYS}[$i]->{startdate} . qq|'
+ LIMIT 1)
+|;
+
+ do_query($query);
+ }
+
+#END CHECK OF taxkeys
+
+#ADD CONSTRAINTS:
+#Now the database is consistent, so we can add constraints:
+ #Crate NOT NULL constraint for tax.rate with default value 0
+ $query= <<SQL;
+ALTER TABLE tax ALTER COLUMN rate SET NOT NULL;
+ALTER TABLE tax ALTER COLUMN rate SET DEFAULT 0;
+SQL
+
+ do_query($query);
+
+ #Create NOT NULL constraint for tax.description
+ $query= <<SQL;
+ALTER TABLE tax ALTER COLUMN taxdescription SET NOT NULL;
+SQL
+
+ do_query($query);
+
+ #Create foreign key for tax.chart_id to chart.id
+ $query= <<SQL;
+ALTER TABLE tax ADD FOREIGN KEY (chart_id) REFERENCES chart(id);
+SQL
+
+ do_query($query);
+
+ #Create NOT NULL constraint for tax.taxkey
+ $query= <<SQL;
+ALTER TABLE tax ALTER COLUMN taxkey SET NOT NULL;
+SQL
+
+ do_query($query);
+
+ #Create NOT NULL constraint for taxkey.chart_id and foreign key for taxkey.chart_id
+ $query= <<SQL;
+ALTER TABLE taxkeys ALTER COLUMN chart_id SET NOT NULL;
+ALTER TABLE taxkeys ADD FOREIGN KEY (chart_id) REFERENCES chart(id);
+SQL
+
+ do_query($query);
+
+ #Create NOT NULL constraint for taxkey.startdate
+ $query= <<SQL;
+ALTER TABLE taxkeys ALTER COLUMN startdate SET NOT NULL;
+SQL
+
+ do_query($query);
+
+ #Create NOT NULL constraint for taxkey.taxkey_id
+ $query= <<SQL;
+ALTER TABLE taxkeys ALTER COLUMN taxkey_id SET NOT NULL;
+SQL
+
+ do_query($query);
+
+ #Create NOT NULL constraint for taxkey.tax_id
+ $query= <<SQL;
+ALTER TABLE taxkeys ALTER COLUMN tax_id SET NOT NULL;
+SQL
+
+ do_query($query);
+
+ #The triple chart_id, taxkey_id, startdate should be unique:
+ $query= <<SQL;
+CREATE UNIQUE INDEX taxkeys_chartid_startdate ON taxkeys(chart_id, startdate);
+SQL
+
+ do_query($query);
+#ALL CONSTRAINTS WERE ADDED
+
+ return 1;
+}; # end do_update
+
+
+sub print_error_message {
+ print $main::form->parse_html_template("dbupgrade/tax_constraints");
+}
+
+return do_update();
--- /dev/null
+[%- USE T8 %]
+[% USE HTML %]<div class="listtop">[% 'Inconsistency in database' | $T8 %]</div>
+
+<form name="Form" method="post" action="login.pl">
+<input type="hidden" name="action" value="login">
+
+<p>[% 'There is an inconsistancy in your database.' | $T8 %]</p>
+<p>[% 'Please contact your administrator.' | $T8 %]</p>
+
+[% IF invalid_tax_account %]
+ <p>[% 'There are invalid taxnumbers in use.' | $T8 %]</p>
+ <p>[% 'Please set another taxnumber for the following taxes and run the update again:' | $T8 %]</p>
+ <table>
+ <tr>
+ <th class="listheading">[% 'tax_taxkey' | $T8 %]</th>
+ <th class="listheading">[% 'tax_taxdescription' | $T8 %]</th>
+ <th class="listheading">[% 'tax_rate' | $T8 %]</th>
+ </tr>
+
+ [% SET row_odd = '1' %][% FOREACH row = TAX %]
+ <tr class="listrow[% IF row_odd %]1[% SET row_odd = '0' %][% ELSE %]0[% SET row_odd = '1' %][% END %]">
+ <td align="right">[% HTML.escape(row.taxkey) %]</td>
+ <td align="left"> [% HTML.escape(row.taxdescription) %]</a></td>
+ <td align="right">[% HTML.escape(row.rate) %] %</td>
+ </tr>
+ [% END %]
+ </table>
+
+[% END %]
+
+[% IF taxkey_is_null %]
+ <p>[% 'There are entries in tax where taxkey is NULL.' | $T8 %]</p>
+ <p>[% 'Please define a taxkey for the following taxes and run the update again:' | $T8 %]</p>
+ <table>
+ <tr>
+ <th class="listheading">[% 'tax_taxdescription' | $T8 %]</th>
+ <th class="listheading">[% 'tax_rate' | $T8 %]</th>
+ <th class="listheading">[% 'taxnumber' | $T8 %]</th>
+ <th class="listheading">[% 'account_description' | $T8 %]</th>
+ </tr>
+
+ [% SET row_odd = '1' %][% FOREACH row = TAX %]
+ <tr class="listrow[% IF row_odd %]1[% SET row_odd = '0' %][% ELSE %]0[% SET row_odd = '1' %][% END %]">
+ <td align="left"> [% HTML.escape(row.taxdescription) %]</a></td>
+ <td align="right">[% HTML.escape(row.rate) %] %</td>
+ <td align="right">[% HTML.escape(row.taxnumber) %]</td>
+ <td>[% HTML.escape(row.account_description) %]</td>
+ </tr>
+ [% END %]
+ </table>
+
+[% END %]
+</form>