From f87763cd84d89aafc4c4110ba42d041b02ce4371 Mon Sep 17 00:00:00 2001 From: Moritz Bunkus Date: Tue, 1 Mar 2011 11:18:58 +0100 Subject: [PATCH] Kunden-/Lieferantenstammdatenimport: zumindest das Testen funktioniert --- SL/Controller/CsvImport.pm | 17 ++- SL/Controller/CsvImport/Base.pm | 102 ++++++++++++++++++ SL/Controller/CsvImport/CustomerVendor.pm | 84 +++++++++++++++ SL/DB/Customer.pm | 1 + SL/DB/Vendor.pm | 1 + locale/de/all | 43 ++++---- templates/webpages/csv_import/_data.html | 12 +++ templates/webpages/csv_import/_errors.html | 21 ++++ .../csv_import/_form_customers_vendors.html | 3 + .../webpages/csv_import/_form_parts.html | 2 + templates/webpages/csv_import/_preview.html | 35 ++++++ templates/webpages/csv_import/_result.html | 3 + templates/webpages/csv_import/form.html | 13 ++- 13 files changed, 308 insertions(+), 29 deletions(-) create mode 100644 SL/Controller/CsvImport/Base.pm create mode 100644 SL/Controller/CsvImport/CustomerVendor.pm create mode 100644 templates/webpages/csv_import/_data.html create mode 100644 templates/webpages/csv_import/_errors.html create mode 100644 templates/webpages/csv_import/_preview.html create mode 100644 templates/webpages/csv_import/_result.html diff --git a/SL/Controller/CsvImport.pm b/SL/Controller/CsvImport.pm index 03b9b4006..37d801a7e 100644 --- a/SL/Controller/CsvImport.pm +++ b/SL/Controller/CsvImport.pm @@ -6,6 +6,7 @@ use SL::DB::Buchungsgruppe; use SL::DB::CsvImportProfile; use SL::Helper::Flash; use SL::SessionFile; +use SL::Controller::CsvImport::CustomerVendor; use List::MoreUtils qw(none); @@ -13,7 +14,8 @@ use parent qw(SL::Controller::Base); use Rose::Object::MakeMethods::Generic ( - scalar => [ qw(type profile file all_profiles all_charsets sep_char all_sep_chars quote_char all_quote_chars escape_char all_escape_chars all_buchungsgruppen) ], + scalar => [ qw(type profile file all_profiles all_charsets sep_char all_sep_chars quote_char all_quote_chars escape_char all_escape_chars all_buchungsgruppen + import_status errors headers data num_imported) ], ); __PACKAGE__->run_before('check_auth'); @@ -128,18 +130,25 @@ sub test_and_import { $self->profile_from_form; if ($::form->{file}) { - my $file = SL::SessionFile->new($self->csv_file_name, "w"); + my $file = SL::SessionFile->new($self->csv_file_name, mode => '>'); $file->fh->print($::form->{file}); $file->fh->close; } - my $file = SL::SessionFile->new($self->csv_file_name, "w"); + my $file = SL::SessionFile->new($self->csv_file_name, mode => '<', encoding => $self->profile->get('charset')); if (!$file->fh) { flash('error', $::locale->text('No file has been uploaded yet.')); return $self->action_new; } - # do the import thingy... + my $worker = $self->{type} eq 'customers_vendors' ? SL::Controller::CsvImport::CustomerVendor->new(controller => $self, file => $file) + : die "Program logic error"; + + $worker->run; + $worker->save_objects if !$params{test}; + + $self->import_status($params{test} ? 'tested' : 'imported'); + $self->action_new; } diff --git a/SL/Controller/CsvImport/Base.pm b/SL/Controller/CsvImport/Base.pm new file mode 100644 index 000000000..13ae97c23 --- /dev/null +++ b/SL/Controller/CsvImport/Base.pm @@ -0,0 +1,102 @@ +package SL::Controller::CsvImport::Base; + +use strict; + +use SL::Helper::Csv; + +use parent qw(Rose::Object); + +use Rose::Object::MakeMethods::Generic +( + scalar => [ qw(controller file csv) ], + 'scalar --get_set_init' => [ qw(profile existing_objects class manager_class) ], +); + +sub run { + my ($self) = @_; + + $::lxdebug->dump(0, "file", $self->file); + $::lxdebug->dump(0, "profile", $self->controller->profile); + my $profile = $self->profile; + $self->csv(SL::Helper::Csv->new(file => $self->file->file_name, + encoding => $self->controller->profile->get('charset'), + class => $self->class, + profile => $profile, + ignore_unknown_columns => 1, + map { ( $_ => $self->controller->profile->get($_) ) } qw(sep_char escape_char quote_char), + )); + $self->csv->parse; + + $self->controller->errors([ $self->csv->errors ]) if $self->csv->errors; + + $::lxdebug->dump(0, "err", $self->csv->errors); + + return unless $self->csv->header; + + my $headers = { headers => [ grep { $profile->{$_} } @{ $self->csv->header } ] }; + $headers->{methods} = [ map { $profile->{$_} } @{ $headers->{headers} } ]; + $self->controller->headers($headers); + + $self->controller->data([ map { { object => $_, errors => [] } } $self->csv->get_objects ]); + + $self->check_objects; + $self->check_duplicates if $self->controller->profile->get('duplicates', 'no_check') ne 'no_check'; +} + +sub init_profile { + my ($self) = @_; + + eval "require " . $self->class; + + my %profile; + for my $col ($self->class->meta->columns) { + my $name = $col->isa('Rose::DB::Object::Metadata::Column::Numeric') ? "$col\_as_number" + : $col->isa('Rose::DB::Object::Metadata::Column::Date') ? "$col\_as_date" + : $col->isa('Rose::DB::Object::Metadata::Column::Timestamp') ? "$col\_as_date" + : $col->name; + + $profile{$col} = $name; + } + + $self->profile(\%profile); +} + +sub init_existing_objects { + my ($self) = @_; + + eval "require " . $self->class; + $self->existing_objects($self->manager_class->get_all); +} + +sub init_class { + die "class not set"; +} + +sub init_manager_class { + my ($self) = @_; + + $self->class =~ m/^SL::DB::(.+)/; + $self->manager_class("SL::DB::Manager::" . $1); +} + +sub check_objects { +} + +sub check_duplicates { +} + +sub save_objects { + my ($self, %params) = @_; + + my $data = $params{data} || $self->controller->data; + + foreach my $entry (@{ $data }) { + if (!$entry->{object}->save) { + push @{ $entry->{errors} }, $::locale->text('Error when saving: #1', $entry->{object}->db->error); + } else { + $self->controller->num_imported($self->controller->num_imported + 1); + } + } +} + +1; diff --git a/SL/Controller/CsvImport/CustomerVendor.pm b/SL/Controller/CsvImport/CustomerVendor.pm new file mode 100644 index 000000000..5fe15af6e --- /dev/null +++ b/SL/Controller/CsvImport/CustomerVendor.pm @@ -0,0 +1,84 @@ +package SL::Controller::CsvImport::CustomerVendor; + +use strict; + +use SL::Helper::Csv; + +use parent qw(SL::Controller::CsvImport::Base); + +use Rose::Object::MakeMethods::Generic +( + scalar => [ qw(table) ], +); + +sub run { + my ($self) = @_; + + $self->table($self->controller->profile->get('table') eq 'customer' ? 'customer' : 'vendor'); + $self->class('SL::DB::' . ucfirst($self->table)); + + $self->SUPER::run; +} + +sub check_objects { + my ($self) = @_; + + my $numbercolumn = $self->controller->profile->get('table') . "number"; + my %vcs_by_number = map { ( $_->$numbercolumn => 1 ) } @{ $self->existing_objects }; + + foreach my $entry (@{ $self->controller->data }) { + my $object = $entry->{object}; + + my $name = $object->name; + $name =~ s/^\s+//; + $name =~ s/\s+$//; + if (!$name) { + push @{ $entry->{errors} }, $::locale->text('Error: Name missing'); + next; + } + + if ($vcs_by_number{ $object->$numbercolumn }) { + $entry->{object}->$numbercolumn('####'); + } else { + $vcs_by_number{ $object->$numbercolumn } = $object; + } + } +} + +sub check_duplicates { + my ($self, %params) = @_; + + my $normalizer = sub { my $name = $_[0]; $name =~ s/[\s,\.\-]//g; return $name; }; + + my %by_name; + if ('check_db' eq $self->controller->profile->get('duplicates')) { + %by_name = map { ( $normalizer->($_->name) => 1 ) } @{ $self->existing_objects }; + } + + foreach my $entry (@{ $self->controller->data }) { + next if @{ $entry->{errors} }; + + my $name = $normalizer->($entry->{object}->name); + if (!$by_name{$name}) { + $by_name{$name} = 1; + + } else { + push @{ $entry->{errors} }, $::locale->text('Duplicate'); + } + } +} + +sub save_objects { + my ($self, %params) = @_; + + my $numbercolumn = $self->table . 'number'; + my $with_number = [ grep { $_->{object}->$numbercolumn ne '####' } @{ $self->controller->data } ]; + my $without_number = [ grep { $_->{object}->$numbercolumn eq '####' } @{ $self->controller->data } ]; + + map { $_->{object}->$numbercolumn('') } @{ $without_number }; + + $self->SUPER::save_objects(data => $with_number); + $self->SUPER::save_objects(data => $without_number); +} + +1; diff --git a/SL/DB/Customer.pm b/SL/DB/Customer.pm index 55a1ee434..85d699933 100644 --- a/SL/DB/Customer.pm +++ b/SL/DB/Customer.pm @@ -32,6 +32,7 @@ sub _before_save_set_customernumber { my ($self) = @_; $self->create_trans_number if $self->customernumber eq ''; + return 1; } sub short_address { diff --git a/SL/DB/Vendor.pm b/SL/DB/Vendor.pm index e8ec8bca0..d1d972a15 100644 --- a/SL/DB/Vendor.pm +++ b/SL/DB/Vendor.pm @@ -31,6 +31,7 @@ sub _before_save_set_vendornumber { my ($self) = @_; $self->create_trans_number if $self->vendornumber eq ''; + return 1; } 1; diff --git a/locale/de/all b/locale/de/all index a542bacc2..9d54cbb33 100644 --- a/locale/de/all +++ b/locale/de/all @@ -182,11 +182,9 @@ $self->{texts} = { 'Ap aging on %s' => 'Offene Verbindlichkeiten zum %s', 'Application Error. No Format given' => 'Fehler in der Anwendung. Das Ausgabeformat fehlt.', 'Application Error. Wrong Format' => 'Fehler in der Anwendung. Falsches Format: ', - 'Applying #1:' => 'Führe #1 aus:', - 'Approximately #1 prices will be updated.' => 'Ungefähr #1 Preise werden aktualisiert.', - 'Apply Filter' => 'Filtern', 'Apply to all parts' => 'Bei allen Artikeln setzen', 'Apply to parts without buchungsgruppe' => 'Bei allen Artikeln ohne gültige Buchungsgruppe setzen', + 'Applying #1:' => 'Führe #1 aus:', 'Approximately #1 prices will be updated.' => 'Ungefähr #1 Preise werden aktualisiert.', 'Apr' => 'Apr', 'April' => 'April', @@ -281,6 +279,7 @@ $self->{texts} = { 'Birthday' => 'Geburtstag', 'Bis' => 'bis', 'Bis Konto: ' => 'bis Konto: ', + 'Block' => 'Block', 'Body' => 'Text', 'Body:' => 'Text:', 'Books are open' => 'Die Bücher sind geöffnet.', @@ -371,7 +370,6 @@ $self->{texts} = { 'Chartaccounts connected to this Tax:' => 'Konten, die mit dieser Steuer verknüpft sind:', 'Check' => 'Scheck', 'Check Details' => 'Bitte Angaben überprüfen', - 'Check the number format.' => 'Prüfen Sie das Format der Zahlen.', 'Check for duplicates' => 'Dublettencheck', 'Checks' => 'Schecks', 'Choose Customer' => 'Endkunde wählen:', @@ -388,8 +386,6 @@ $self->{texts} = { 'Close Window' => 'Fenster Schließen', 'Closed' => 'Geschlossen', 'Collective Orders only work for orders from one customer!' => 'Sammelaufträge funktionieren nur für Aufträge von einem Kunden!', - 'Column' => 'Spalte', - 'Column definitions can be found here:' => 'Die einzelnen Spaltendefinitionen sind hier verzeichnet:', 'Comma' => 'Komma', 'Comment' => 'Kommentar', 'Company' => 'Firma', @@ -548,8 +544,8 @@ $self->{texts} = { 'Delete delivery order' => 'Lieferschein löschen', 'Delete drafts' => 'Entwürfe löschen', 'Delete group' => 'Gruppe löschen', - 'Delete transaction' => 'Buchung löschen', 'Delete profile' => 'Profil löschen', + 'Delete transaction' => 'Buchung löschen', 'Delivered' => 'Geliefert', 'Delivery Date' => 'Lieferdatum', 'Delivery Order' => 'Lieferschein', @@ -579,11 +575,13 @@ $self->{texts} = { 'Dimension unit' => 'Maßeinheit', 'Directory' => 'Verzeichnis', 'Discard duplicate entries in CSV file' => 'Doppelte Einträge in CSV-Datei verwerfen', - 'Discard entries with duplicates in database and CSV file' => 'Einträge aus CSV-Datei verwerfen, die es bereits in der Datenbank gibt', + 'Discard entries with duplicates in database or CSV file' => 'Einträge aus CSV-Datei verwerfen, die es bereits in der Datenbank oder der CSV-Datei gibt', 'Discount' => 'Rabatt', 'Display' => 'Anzeigen', 'Display file' => 'Datei anzeigen', 'Display options' => 'Anzeigeoptionen', + 'Do not check for duplicates' => 'Nicht nach Dubletten suchen', + 'Do not set default buchungsgruppe' => 'Nie Standardbuchungsgruppe setzen', 'Do you really want to close the following SEPA exports? No payment will be recorded for bank collections that haven\'t been marked as executed yet.' => 'Wollen Sie wirklich die folgenden SEPA-Exporte abschließen? Für Überweisungen, die noch nicht gebucht wurden, werden dann keine Zahlungen verbucht.', 'Do you really want to close the following SEPA exports? No payment will be recorded for bank transfers that haven\'t been marked as executed yet.' => 'Wollen Sie wirklich die folgenden SEPA-Exporte abschließen? Für Überweisungen, die noch nicht gebucht wurden, werden dann keine Zahlungen verbucht.', 'Do you really want to delete AP transaction #1?' => 'Wollen Sie wirklich die Kreditorenbuchung #1 löschen?', @@ -591,9 +589,6 @@ $self->{texts} = { 'Do you really want to delete GL transaction #1?' => 'Wollen Sie wirklich die Dialogbuchung #1 löschen?', 'Do you really want to delete this group?' => 'Gruppe wirklich löschen?', 'Do you really want to delete this object?' => 'Wollen Sie dieses Objekt wirklich löschen?', - 'Do not check for duplicates' => 'Nicht nach Dubletten suchen', - 'Do not set default buchungsgruppe' => 'Nie Standardbuchungsgruppe setzen', - 'Do you really want to delete this record?' => 'Wollen Sie diesen Beleg wirklich löschen?', 'Do you really want to delete this warehouse?' => 'Wollen Sie dieses Lager wirklich löschen?', 'Do you want Lx-Office to create a group for access to all functions?' => 'Wollen Sie, dass Lx-Office eine Gruppe mit Zugriff auf alle Funktionen anlegt?', 'Do you want to limit your search?' => 'Wollen Sie Ihre Suche spezialisieren?', @@ -627,6 +622,7 @@ $self->{texts} = { 'Dunning number' => 'Mahnungsnummer', 'Dunning overview' => 'Mahnungsübersicht', 'Dunnings' => 'Mahnungen', + 'Duplicate' => 'Duplikat', 'During this user migration Lx-Office can create such a group for you and grant all users access to all of Lx-Office\'s functions.' => 'Im Rahmen dieser Benutzerdatenmigration kann Lx-Office eine solche Gruppe für Sie anlegen und allen Benutzern Zugriff auf alle Lx-Office-Funktionen gewähren.', 'E-mail' => 'eMail', 'E-mail Statement to' => 'Fälligkeitsabrechnung als eMail an', @@ -728,7 +724,10 @@ $self->{texts} = { 'Error in position #1: You must either assign no transfer at all or the full quantity of #2 #3.' => 'Fehler in Position #1: Sie müssen einer Position entweder gar keinen Lagerausgang oder die vollständige im Lieferschein vermerkte Menge von #2 #3 zuweisen.', 'Error in row #1: The quantity you entered is bigger than the stocked quantity.' => 'Fehler in Zeile #1: Die angegebene Menge ist größer als die vorhandene Menge.', 'Error message from the database driver:' => 'Fehlermeldung des Datenbanktreibers:', + 'Error when saving: #1' => 'Fehler beim Speichern: #2', 'Error!' => 'Fehler!', + 'Error: Name missing' => 'Fehler: Name fehlt', + 'Errors' => 'Fehler', 'Ertrag' => 'Ertrag', 'Ertrag prozentual' => 'Ertrag prozentual', 'Escape character' => 'Escape-Zeichen', @@ -806,6 +805,8 @@ $self->{texts} = { 'Foreign Revenues' => 'Erlöse Ausland', 'Form details (second row)' => 'Formulardetails (zweite Positionszeile)', 'Formula' => 'Formel', + 'Found #1 errors.' => '#1 Fehler gefunden.', + 'Found #1 objects to import.' => '#1 zu importierende Objekte gefunden.', 'Free report period' => 'Freier Zeitraum', 'Free-form text' => 'Textzeile', 'Fristsetzung' => 'Fristsetzung', @@ -826,7 +827,6 @@ $self->{texts} = { 'Given Name' => 'Vorname', 'Go one step back' => 'Einen Schritt zurück', 'Go one step forward' => 'Einen Schritt vorwärts', - 'Gogogo' => '', 'Greeting' => 'Anrede', 'Greetings' => 'Anreden', 'Group' => 'Warengruppe', @@ -871,10 +871,12 @@ $self->{texts} = { 'If you want to set up the authentication database yourself then log in to the administration panel. Lx-Office will then create the database and tables for you.' => 'Wenn Sie die Authentifizierungsdatenbank selber einrichten wollen, so melden Sie sich an der Administrationsoberfläche an. Lx-Office wird dann die Datenbank und die Tabellen für Sie anlegen.', 'If you yourself want to upgrade the installation then please read the file "doc/UPGRADE" and follow the steps outlined in this file.' => 'Wenn Sie selber die Aktualisierung bzw. Einrichtung übernehmen wollen, so lesen Sie bitte die Datei "doc/UPGRADE" und folgen Sie den dort beschriebenen Schritten.', 'Image' => 'Grafik', + 'Import' => 'Import', 'Import CSV' => 'CSV-Import', - 'Import Help' => 'Importhilfe', 'Import file' => 'Import-Datei', + 'Import preview' => 'Import-Vorschau', 'Import profiles' => 'Import-Profil', + 'Import result' => 'Import-Ergebnis', 'In Lx-Office 2.4.0 the administrator has to enter a list of units in the administrative section.' => 'In Lx-Office 2.4.0 muss der Administrator in den Systemeinstellungen eine Liste von verwendbaren Einheiten angeben.', 'In order to do that hit the button "Delete transaction".' => 'Drücken Sie dafür auf den Button "Buchung löschen".', 'In the latter case the tables needed by Lx-Office will be created in that database.' => 'In letzterem Fall werden die von Lx-Office benötigten Tabellen in dieser existierenden Datenbank angelegt.', @@ -990,6 +992,7 @@ $self->{texts} = { 'Licenses' => 'Lizenzen', 'Limit part selection' => 'Artikelauswahl eingrenzen', 'Line Total' => 'Zeilensumme', + 'Line and column' => 'Zeile und Spalte', 'Line endings' => 'Zeilenumbrüche', 'List' => 'Anzeigen', 'List Accounting Groups' => 'Buchungsgruppen anzeigen', @@ -1233,7 +1236,6 @@ $self->{texts} = { 'Part Notes' => 'Bemerkungen', 'Part Number' => 'Artikelnummer', 'Part Number missing!' => 'Artikelnummer fehlt!', - 'Partnumber' => '', 'Partnumber must not be set to empty!' => 'Die Artikelnummer darf nicht auf leer geändert werden.', 'Partnumber not unique!' => 'Artikelnummer bereits vorhanden!', 'Parts' => 'Waren', @@ -1272,8 +1274,8 @@ $self->{texts} = { 'Please ask your administrator to create warehouses and bins.' => 'Bitten Sie Ihren Administrator, dass er Lager und Lagerplätze anlegt.', 'Please enter a license key.' => 'Bitte geben Sie einen Lizenzschlüssel an.', 'Please enter a number of licenses.' => 'Bitte geben Sie die Anzahl Lizenzschlüssel an.', - 'Please enter the login for the new user.' => 'Bitte geben Sie das Login für den neuen Benutzer ein.', '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 enter the name of the dataset you want to restore the backup in.' => 'Bitte geben Sie den Namen der Datenbank ein, in der Sie die Sicherung wiederherstellen wollen.', 'Please enter the sales tax identification number.' => 'Bitte geben Sie die Umsatzsteueridentifikationsnummer an.', @@ -1520,12 +1522,9 @@ $self->{texts} = { 'Selection' => 'Auswahlbox', 'Selection fields: The option field must contain the available options for the selection. Options are separated by \'##\', for example \'Early##Normal##Late\'.' => 'Auswahlboxen: Das Optionenfeld muss die für die Auswahl verfügbaren Einträge enthalten. Die Einträge werden mit \'##\' voneinander getrennt. Beispiel: \'Früh##Normal##Spät\'.', 'Sell Price' => 'Verkaufspreis', - 'Sell prices have fallen below the minimum sell price. PDF export and email deactivated until the quotation has been approved.' => 'Verkaufspreise liegen teilweise unter dem Mindestverkaufspreis. PDF-Export und E-Mail deaktiviert, bis das Angebot freigegeben wurde.', - 'Sellprice' => 'Verkaufspreis', 'Sellprice adjustment' => 'Verkaufspreis: Preisanpassung', 'Sellprice significant places' => 'Verkaufspreis: Nachkommastellen', 'Semicolon' => 'Semikolon', - 'Send documents' => 'Dokumente verschicken', 'Send the backup via Email' => 'Die Sicherungsdatei per Email verschicken', 'Sep' => 'Sep', 'Separator' => 'Trennzeichen', @@ -1541,9 +1540,9 @@ $self->{texts} = { 'Services' => 'Dienstleistungen', 'Set Language Values' => 'Spracheinstellungen', 'Set eMail text' => 'eMail Text eingeben', + 'Settings' => 'Einstellungen', 'Setup Menu' => 'Menü-Variante', 'Setup Templates' => 'Vorlagen auswählen', - 'Settings' => 'Einstellungen', 'Ship to' => 'Lieferadresse', 'Ship via' => 'Transportmittel', 'Shipping Address' => 'Lieferadresse', @@ -1563,8 +1562,8 @@ $self->{texts} = { 'Show your TODO list after loggin in' => 'Aufgabenliste nach dem Anmelden anzeigen', 'Signature' => 'Unterschrift', 'Since bin is not enforced in the parts data, please specify a bin where goods without a specified bin will be put.' => 'Da Lagerplätze kein Pflichtfeld sind, geben Sie bitte einen Lagerplatz an, in dem Waren ohne spezifizierten Lagerplatz eingelagert werden sollen.', - 'Skip' => 'Überspringen', 'Single quotes' => 'Einfache Anführungszeichen', + 'Skip' => 'Überspringen', 'Skonto' => 'Skonto', 'Skonto Terms' => 'Zahlungsziel Skonto', 'Sold' => 'Verkauft', @@ -1661,6 +1660,7 @@ $self->{texts} = { 'Template database' => 'Datenbankvorlage', 'Templates' => 'Vorlagen', 'Terms missing in row ' => '+Tage fehlen in Zeile ', + 'Test and preview' => 'Test und Vorschau', 'Test connection' => 'Verbindung testen', 'Text field' => 'Textfeld', 'Text field variables: \'WIDTH=w HEIGHT=h\' sets the width and height of the text field. They default to 30 and 5 respectively.' => 'Textfelder: \'WIDTH=w HEIGHT=h\' setzen die Breite und die Höhe des Textfeldes. Wenn nicht anders angegeben, so werden sie 30 Zeichen breit und fünf Zeichen hoch dargestellt.', @@ -1923,7 +1923,6 @@ $self->{texts} = { 'Update prices of existing entries' => 'Preise von vorhandenen Artikeln aktualisieren', 'Update?' => 'Aktualisieren?', 'Updated' => 'Erneuert am', - 'Use' => 'Verwendung', 'Uploaded on #1, size #2 kB' => 'Am #1 hochgeladen, Größe #2 kB', 'Use As Template' => 'Als Vorlage verwenden', 'Use Templates' => 'Benutze Vorlagen', @@ -2144,8 +2143,8 @@ $self->{texts} = { 'order' => 'Reihenfolge', 'our vendor number at customer' => 'Unsere Lieferanten-Nr. beim Kunden', 'part_list' => 'warenliste', - 'pick_list' => 'Sammelliste', 'percental' => 'prozentual', + 'pick_list' => 'Sammelliste', 'plural first char' => 'P', 'pos_bilanz' => 'Bilanz', 'pos_bwa' => 'BWA', diff --git a/templates/webpages/csv_import/_data.html b/templates/webpages/csv_import/_data.html new file mode 100644 index 000000000..36633824b --- /dev/null +++ b/templates/webpages/csv_import/_data.html @@ -0,0 +1,12 @@ +[% USE LxERP %] + +[%- IF SELF.errors %] + [%- PROCESS 'csv_import/_errors.html' %] +[%- END %] + +[%- IF SELF.import_status == 'imported' %] + [%- PROCESS 'csv_import/_result.html' %] +[%- END %] + +[%- PROCESS 'csv_import/_preview.html' %] + diff --git a/templates/webpages/csv_import/_errors.html b/templates/webpages/csv_import/_errors.html new file mode 100644 index 000000000..3fda491f1 --- /dev/null +++ b/templates/webpages/csv_import/_errors.html @@ -0,0 +1,21 @@ +[% USE LxERP %] + +

[%- LxERP.t8('Errors') %]

+ +

[%- LxERP.t8('Found #1 errors.', SELF.errors.size) %]

+ + + + + + + + [% FOREACH err = SELF.errors %] + + + + + + [% END %] +
[%- LxERP.t8('Line and column') %][%- LxERP.t8('Block') %][%- LxERP.t8('Error') %]
[% err.4 %]:[% err.3 %][% err.0 %][% err.2 %]
+[% END %] diff --git a/templates/webpages/csv_import/_form_customers_vendors.html b/templates/webpages/csv_import/_form_customers_vendors.html index f53dcf3c9..c3bf9a51d 100644 --- a/templates/webpages/csv_import/_form_customers_vendors.html +++ b/templates/webpages/csv_import/_form_customers_vendors.html @@ -1,3 +1,6 @@ +[% USE LxERP %] +[% USE L %] + [%- LxERP.t8('Target table') %]: diff --git a/templates/webpages/csv_import/_form_parts.html b/templates/webpages/csv_import/_form_parts.html index 26465aa9e..21dd42316 100644 --- a/templates/webpages/csv_import/_form_parts.html +++ b/templates/webpages/csv_import/_form_parts.html @@ -1,3 +1,5 @@ +[% USE LxERP %] +[% USE L %] [%- LxERP.t8('Parts with existing part numbers') %]: diff --git a/templates/webpages/csv_import/_preview.html b/templates/webpages/csv_import/_preview.html new file mode 100644 index 000000000..323fdb483 --- /dev/null +++ b/templates/webpages/csv_import/_preview.html @@ -0,0 +1,35 @@ +[% USE HTML %] +[% USE LxERP %] + +

+ [%- IF SELF.import_status == 'tested' %] + [%- LxERP.t8('Import preview') %] + [%- ELSE %] + [%- LxERP.t8('Import result') %] + [%- END %] +

+ +

[%- LxERP.t8('Found #1 objects to import.', SELF.data.size || 0) %]

+ +[% IF SELF.data.size %] + + + [%- FOREACH column = SELF.headers.headers %] + + [%- END %] + + + + [%- FOREACH row = SELF.data %] + + [%- FOREACH method = SELF.headers.methods %] + + [%- END %] + + + [%- END %] + +
[%- HTML.escape(column) %][%- LxERP.t8('Notes') %]
[%- HTML.escape(row.object.$method) %] + [%- FOREACH error = row.errors %][%- HTML.escape(error) %][% UNLESS loop.last %]
[%- END %][%- END %] +
+[%- END %] diff --git a/templates/webpages/csv_import/_result.html b/templates/webpages/csv_import/_result.html new file mode 100644 index 000000000..089651837 --- /dev/null +++ b/templates/webpages/csv_import/_result.html @@ -0,0 +1,3 @@ +[% USE LxERP %] + +

[%- LxERP.t8('Found #1 objects to import.', SELF.data.size || 0) %]

diff --git a/templates/webpages/csv_import/form.html b/templates/webpages/csv_import/form.html index 4ae1cf6d1..bc23d54c2 100644 --- a/templates/webpages/csv_import/form.html +++ b/templates/webpages/csv_import/form.html @@ -112,9 +112,9 @@ [%- LxERP.t8('Check for duplicates') %]: - [% opts = [ [ 'no_check', LxERP.t8('Do not check for duplicates') ], + [% opts = [ [ 'no_check', LxERP.t8('Do not check for duplicates') ], [ 'check_csv', LxERP.t8('Discard duplicate entries in CSV file') ], - [ 'check_db', LxERP.t8('Discard entries with duplicates in database and CSV file') ] ] %] + [ 'check_db', LxERP.t8('Discard entries with duplicates in database or CSV file') ] ] %] [% L.select_tag('settings.duplicates', L.options_for_select(opts, default => SELF.profile.get('duplicates')), style => 'width: 300px') %] @@ -139,10 +139,17 @@ - [% L.submit_tag('action_test', LxERP.t8('Gogogo')) %] + [% L.submit_tag('action_test', LxERP.t8('Test and preview')) %] + [% IF SELF.import_status && SELF.data.size %] + [% L.submit_tag('action_import', LxERP.t8('Import')) %] + [%- END %] + [%- IF SELF.import_status %] + [%- PROCESS 'csv_import/_data.html' %] + [%- END %] +