Merge branch 'master' of vc.linet-services.de:public/lx-office-erp
authorG. Richardson <information@lx-office-hosting.de>
Thu, 8 Nov 2012 10:34:18 +0000 (11:34 +0100)
committerG. Richardson <information@lx-office-hosting.de>
Thu, 8 Nov 2012 10:34:18 +0000 (11:34 +0100)
23 files changed:
.gitignore
SL/CT.pm
SL/Controller/CsvImport/Base.pm
SL/DBUpgrade2.pm
SL/Helper/Csv.pm
SL/Helper/Csv/Dispatcher.pm
SL/IS.pm
SL/Menu.pm
SL/Template/OpenDocument.pm
bin/mozilla/am.pl
bin/mozilla/arap.pl
bin/mozilla/ic.pl
bin/mozilla/io.pl
bin/mozilla/wh.pl
locale/de/all
locale/de_DE/all
menu.ini
scripts/dbupgrade2_tool.pl
t/helper/csv.t
templates/webpages/admin/create_dataset.html
templates/webpages/am/edit_accounts.html
templates/webpages/ic/form_header.html
templates/webpages/menu/menunew.html

index 6c00133..bc497ae 100644 (file)
@@ -9,3 +9,4 @@ crm
 /doc/online/*/*.html
 pod2html*
 /doc/build/dobudish*
+/spool/*
index de10623..93c3fce 100644 (file)
--- a/SL/CT.pm
+++ b/SL/CT.pm
@@ -598,6 +598,7 @@ sub search {
   my $dbh = $form->dbconnect($myconfig);
 
   my $cv = $form->{db} eq "customer" ? "customer" : "vendor";
+  my $join_records = $form->{l_invnumber} || $form->{l_ordnumber} || $form->{l_quonumber};
 
   my $where = "1 = 1";
   my @values;
@@ -611,7 +612,7 @@ sub search {
   $form->{sort} = $sortorder;
   my $sortdir   = !defined $form->{sortdir} ? 'ASC' : $form->{sortdir} ? 'ASC' : 'DESC';
 
-  if ($sortorder !~ /(business|id)/ && 1 >= scalar grep { $form->{$_} } qw(l_ordnumber l_quonumber l_invnumber )) {
+  if ($sortorder !~ /(business|id)/ && !$join_records) {
     $sortorder  = "lower($sortorder) ${sortdir}";
   } else {
     $sortorder .= " ${sortdir}";
@@ -705,21 +706,22 @@ sub search {
 
   my $query =
     qq|SELECT ct.*, b.description AS business | .
+    (qq|, NULL AS invnumber, NULL AS ordnumber, NULL AS quonumber, NULL AS invid, NULL AS module, NULL AS formtype, NULL AS closed | x!! $join_records) .
     qq|FROM $cv ct | .
     qq|LEFT JOIN business b ON (ct.business_id = b.id) | .
     qq|WHERE $where|;
 
   my @saved_values = @values;
   # redo for invoices, orders and quotations
-  if ($form->{l_invnumber} || $form->{l_ordnumber} || $form->{l_quonumber}) {
-    my ($ar, $union, $module);
-    $query = "";
+  if ($join_records) {
+    my $union = "UNION";
 
     if ($form->{l_invnumber}) {
       my $ar = $cv eq 'customer' ? 'ar' : 'ap';
       my $module = $ar eq 'ar' ? 'is' : 'ir';
-
-      $query =
+      push(@values, @saved_values);
+      $query .=
+        qq| UNION | .
         qq|SELECT ct.*, b.description AS business, | .
         qq|  a.invnumber, a.ordnumber, a.quonumber, a.id AS invid, | .
         qq|  '$module' AS module, 'invoice' AS formtype, | .
@@ -728,16 +730,12 @@ sub search {
         qq|JOIN $ar a ON (a.${cv}_id = ct.id) | .
         qq|LEFT JOIN business b ON (ct.business_id = b.id) | .
         qq|WHERE $where AND (a.invoice = '1')|;
-
-      $union = qq|UNION|;
     }
 
     if ( $form->{l_ordnumber} ) {
-      if ($union eq "UNION") {
-        push(@values, @saved_values);
-      }
+      push(@values, @saved_values);
       $query .=
-        qq| $union | .
+        qq| UNION | .
         qq|SELECT ct.*, b.description AS business,| .
         qq|  ' ' AS invnumber, o.ordnumber, o.quonumber, o.id AS invid, | .
         qq|  'oe' AS module, 'order' AS formtype, o.closed | .
@@ -745,16 +743,12 @@ sub search {
         qq|JOIN oe o ON (o.${cv}_id = ct.id) | .
         qq|LEFT JOIN business b ON (ct.business_id = b.id) | .
         qq|WHERE $where AND (o.quotation = '0')|;
-
-      $union = qq|UNION|;
     }
 
     if ( $form->{l_quonumber} ) {
-      if ($union eq "UNION") {
-        push(@values, @saved_values);
-      }
+      push(@values, @saved_values);
       $query .=
-        qq| $union | .
+        qq| UNION | .
         qq|SELECT ct.*, b.description AS business, | .
         qq|  ' ' AS invnumber, o.ordnumber, o.quonumber, o.id AS invid, | .
         qq|  'oe' AS module, 'quotation' AS formtype, o.closed | .
index f84d54b..656495e 100644 (file)
@@ -29,6 +29,7 @@ sub run {
                                   profile                => $profile,
                                   ignore_unknown_columns => 1,
                                   strict_profile         => 1,
+                                  case_insensitive_header => 1,
                                   map { ( $_ => $self->controller->profile->get($_) ) } qw(sep_char escape_char quote_char),
                                  ));
 
@@ -212,6 +213,12 @@ sub init_profile {
     $profile{$col} = $name;
   }
 
+  if ($self->can('all_cvar_configs')) {
+    for (@{ $self->all_cvar_configs }) {
+      $profile{ 'cvar_' . $_->name } = '';
+    }
+  }
+
   $self->profile(\%profile);
 }
 
index a39ef93..a84f6b1 100644 (file)
@@ -25,12 +25,17 @@ sub init {
 
   $params{path_suffix} ||= '';
   $params{schema}      ||= '';
+  $params{path}          = "sql/" . $params{dbdriver} . "-upgrade2" . $params{path_suffix};
 
   map { $self->{$_} = $params{$_} } keys %params;
 
   return $self;
 }
 
+sub path {
+  $_[0]{path};
+}
+
 sub parse_dbupdate_controls {
   $::lxdebug->enter_sub();
 
@@ -42,7 +47,7 @@ sub parse_dbupdate_controls {
   local *IN;
   my %all_controls;
 
-  my $path = "sql/" . $self->{dbdriver} . "-upgrade2" . $self->{path_suffix};
+  my $path = $self->path;
 
   foreach my $file_name (<$path/*.sql>, <$path/*.pl>) {
     next unless (open(IN, $file_name));
index 5b629ee..e481614 100644 (file)
@@ -10,7 +10,7 @@ use Text::CSV_XS;
 use Rose::Object::MakeMethods::Generic scalar => [ qw(
   file encoding sep_char quote_char escape_char header profile class
   numberformat dateformat ignore_unknown_columns strict_profile _io _csv
-  _objects _parsed _data _errors
+  _objects _parsed _data _errors all_cvar_configs case_insensitive_header
 ) ];
 
 use SL::Helper::Csv::Dispatcher;
@@ -33,6 +33,7 @@ sub new {
     dateformat             => 0,
     ignore_unknown_columns => 0,
     strict_profile         => 0,
+    case_insensitive_header => 0,
   });
   my $self = bless {}, $class;
 
@@ -120,7 +121,25 @@ sub _check_header {
   }
 
   return unless $header;
-  return $self->header([ map { lc } @$header ]);
+
+  # Special case: human stupidity
+  # people insist that case sensitivity doesn't exist and try to enter all
+  # sorts of stuff. at this point we've got a profile (with keys that represent
+  # valid methods), and a header full of strings. if two of them match, the user
+  # mopst likely meant that field, so rewrite the header
+  if ($self->case_insensitive_header) {
+    die 'case_insensitive_header is only possible with profile' unless $self->profile;
+    my @names = (
+      keys %{ $self->profile || {} },
+    );
+    for my $name (@names) {
+      for my $i (0..$#$header) {
+        $header->[$i] = $name if lc $header->[$i] eq lc $name;
+      }
+    }
+  }
+
+  return $self->header($header);
 }
 
 sub _parse_data {
@@ -345,6 +364,12 @@ these information are unique, and should be connected to preexisting data, you
 will have to do that for yourself. Since you provided the profile, it is
 assumed you know what to do in this case.
 
+If no profile is given, any header field found will be taken as is.
+
+If the path in a profile entry is empty, the field will be subjected to
+C<strict_profile> and C<case_insensitive_header> checking, will be parsed into
+C<get_data>, but will not be attempted to be dispatched into objects.
+
 =item C<class>
 
 If present, the line will be handed to the new sub of this class,
@@ -355,11 +380,24 @@ and the return value used instead of the line itself.
 If set, the import will ignore unkown header columns. Useful for lazy imports,
 but deactivated by default.
 
+=item C<case_insensitive_header>
+
+If set, header columns will be matched against profile entries case
+insensitive, and on match the profile name will be taken.
+
+Only works if a profile is given, will die otherwise.
+
+If both C<case_insensitive_header> and C<strict_profile> is set, matched header
+columns will be accepted.
+
 =item C<strict_profile>
 
 If set, all columns to be parsed must be specified in C<profile>. Every header
 field not listed there will be treated like an unknown column.
 
+If both C<case_insensitive_header> and C<strict_profile> is set, matched header
+columns will be accepted.
+
 =back
 
 =head1 ERROR HANDLING
index be3fadd..add444b 100644 (file)
@@ -78,7 +78,11 @@ sub parse_profile {
         $self->unknown_column($col, undef);
       }
     } else {
-      push @specs, $self->make_spec($col, $profile->{$col} || $col);
+      if (exists $profile->{$col}) {
+        push @specs, $self->make_spec($col, $profile->{$col});
+      } else {
+        push @specs, $self->make_spec($col, $col);
+      }
     }
   }
 
@@ -91,6 +95,9 @@ sub make_spec {
   my ($self, $col, $path) = @_;
 
   my $spec = { key => $col, steps => [] };
+
+  return unless $path;
+
   my $cur_class = $self->_csv->class;
 
   return unless $cur_class;
index db18d5a..616e2bf 100644 (file)
--- a/SL/IS.pm
+++ b/SL/IS.pm
@@ -2028,7 +2028,7 @@ sub get_pricegroups_for_parts {
 
     my $pricegroup_old = $form->{"pricegroup_old_$i"};
 
-    # sellprice has format 13,0000 or 0,00000, can't check for 0 numerically
+    # sellprice has format 13,0000 or 0,00000,  can't check for 0 numerically
     my $sellprice = $form->{"sellprice_$i"};
     my $pricegroup_id = $form->{"pricegroup_id_$i"};
     $form->{"new_pricegroup_$i"} = $selectedpricegroup_id;
@@ -2130,14 +2130,12 @@ sub get_pricegroups_for_parts {
 
           $pkr->{selected}  = ' selected'; # unless $form->{selected};
           # no customer pricesgroup set
-          if ($pkr->{price_unfmt} == $pkr->{default_sellprice}) {
+          if ($pkr->{price_unfmt} == $pkr->{default_sellprice} || $form->{'sellprice_'.$i} * 1 > 1) {
 
             $pkr->{price} = $form->{"sellprice_$i"};
 
           } else {
 
-# this sub should not set anything and only return. --sschoeling, 20090506
-# is this correct? put in again... -- grichardson 20110119
             $form->{"sellprice_$i"} = $pkr->{price};
           }
 
index 48ac622..57d8b4e 100644 (file)
@@ -59,7 +59,7 @@ sub new {
 }
 
 sub menuitem_new {
-  $main::lxdebug->enter_sub();
+  $main::lxdebug->enter_sub(LXDebug::DEBUG2());
 
   my ($self, $name, $item) = @_;
 
@@ -84,7 +84,7 @@ sub menuitem_new {
     $item->{href}      .= "&" . $form->escape($key) . "=" . $form->escape($value);
   }
 
-  $main::lxdebug->leave_sub();
+  $main::lxdebug->leave_sub(LXDebug::DEBUG2());
 }
 
 sub access_control {
index 12a7bc8..b4ea1f2 100644 (file)
@@ -270,14 +270,14 @@ sub parse {
 
   $zip->contents("content.xml", Encode::encode('utf-8-strict', $new_contents));
 
-  my $styles = $zip->contents("styles.xml");
+  my $styles = Encode::decode('utf-8-strict', $zip->contents("styles.xml"));
   if ($contents) {
     my $new_styles = $self->parse_block($styles);
     if (!defined($new_contents)) {
       $main::lxdebug->leave_sub();
       return 0;
     }
-    $zip->contents("styles.xml", $new_styles);
+    $zip->contents("styles.xml", Encode::encode('utf-8-strict', $new_styles));
   }
 
   $zip->writeToFileNamed($form->{"tmpfile"}, 1);
index c114c7e..5005475 100644 (file)
@@ -426,10 +426,6 @@ sub save_as_new_account {
   }
 
   $form->{id} = 0;
-  if ($form->{"original_accno"} &&
-      ($form->{"accno"} eq $form->{"original_accno"})) {
-    $form->error($locale->text('Account Number already used!'));
-  }
   $form->redirect($locale->text('Account saved!'))
     if (AM->save_account(\%myconfig, \%$form));
   $form->error($locale->text('Cannot save account!'));
index 27b0e3b..fe5fa2d 100644 (file)
@@ -78,6 +78,7 @@ sub check_name {
 
       $form->{"${name}_id"} = $new_id;
 
+      _reset_salesman_id();
       IS->get_customer(\%myconfig, \%$form) if ($name eq 'customer');
       IR->get_vendor(\%myconfig, \%$form) if ($name eq 'vendor');
 
@@ -119,6 +120,7 @@ sub check_name {
         $form->{$name}        = $form->{name_list}[0]->{name};
         $form->{"old$name"}   = qq|$form->{$name}--$form->{"${name}_id"}|;
 
+        _reset_salesman_id();
         IS->get_customer(\%myconfig, \%$form) if ($name eq 'customer');
         IR->get_vendor(\%myconfig, \%$form) if ($name eq 'vendor');
 
@@ -266,6 +268,8 @@ sub name_selected {
   # index for new item
   my $i = $form->{ndx};
 
+  _reset_salesman_id();
+
   $form->{ $form->{vc} }    = $form->{"new_name_$i"};
   $form->{"$form->{vc}_id"} = $form->{"new_id_$i"};
   $form->{"old$form->{vc}"} =
@@ -286,6 +290,14 @@ sub name_selected {
   $main::lxdebug->leave_sub();
 }
 
+# Reset the $::form field 'salesman_id' to the ID of the currently
+# logged in user. Useful when changing to a customer/vendor that has
+# no salesman listed in their master data.
+sub _reset_salesman_id {
+  my $current_employee   = SL::DB::Manager::Employee->current;
+  $::form->{salesman_id} = $current_employee->id if $current_employee && exists $::form->{salesman_id};
+}
+
 sub check_project {
   $main::lxdebug->enter_sub();
 
index 20c670c..b86623b 100644 (file)
@@ -38,6 +38,7 @@ use List::MoreUtils qw(any);
 use SL::AM;
 use SL::CVar;
 use SL::IC;
+use SL::Helper::Flash;
 use SL::ReportGenerator;
 
 #use SL::PE;
@@ -1585,6 +1586,10 @@ sub form_header {
   IC->retrieve_buchungsgruppen(\%myconfig, $form);
   @{ $form->{BUCHUNGSGRUPPEN} } = grep { $_->{id} eq $form->{buchungsgruppen_id} || ($form->{id} && $form->{orphaned}) || !$form->{id} } @{ $form->{BUCHUNGSGRUPPEN} };
 
+  if (!SL::TransNumber->new(number => $form->{partnumber}, type => $form->{item}, id => $form->{id})->is_unique) {
+    flash('info', $::locale->text('This partnumber is not unique. You should change it.'));
+  }
+
   # use JavaScript Calendar or not (yes!)
   $form->{jsscript} = 1;
 
index b518db0..37df554 100644 (file)
@@ -49,6 +49,7 @@ use SL::IO;
 
 use SL::DB::Language;
 use SL::DB::Printer;
+use SL::Helper::Flash;
 
 require "bin/mozilla/common.pl";
 
@@ -802,6 +803,7 @@ sub validate_items {
 
   # check if items are valid
   if ($form->{rowcount} == 1) {
+    flash('warning', $::locale->text('The action you\'ve chosen has not been executed because the document does not contain any item yet.'));
     &update;
     ::end_of_request();
   }
@@ -1536,23 +1538,27 @@ sub print_form {
   my $emailed = $form->{emailed};
 
   if ($form->{media} eq 'queue') {
-    my %queued = map { s|.*/|| } split / /, $form->{queued};
+    my %queued = map { s|.*[/\\]||; $_ } split / /, $form->{queued};
 
     my $filename;
     my $suffix = ($form->{postscript}) ? '.ps' : '.pdf';
     if ($filename = $queued{ $form->{formname} }) {
-      $form->{queued} =~ s/\Q$form->{formname} $filename\E//;
       unlink $::lx_office_conf{paths}->{spool} . "/$filename";
-      $filename =~ s/\..*$//g;
-      $filename .= $suffix;
-      $form->{OUT} = $::lx_office_conf{paths}->{spool} . "/$filename";
-      $form->{OUT_MODE} = '>';
+      delete $queued{ $form->{formname} };
+
+      $form->{queued}    =  join ' ', %queued;
+      $filename          =~ s/\..*$//g;
+      $filename         .=  $suffix;
+      $form->{OUT}       =  $::lx_office_conf{paths}->{spool} . "/$filename";
+      $form->{OUT_MODE}  =  '>';
+
     } else {
       my $temp_fh;
       ($temp_fh, $filename) = File::Temp::tempfile(
         'kivitendo-spoolXXXXXX',
         SUFFIX => "$suffix",
-        DIR => $::lx_office_conf{paths}->{spool},
+        DIR    => $::lx_office_conf{paths}->{spool},
+        UNLINK => 0,
       );
       close $temp_fh;
       $form->{OUT} = "$filename";
index 0315bb9..92cfcb6 100644 (file)
@@ -44,6 +44,8 @@ use SL::WH;
 use SL::OE;
 use SL::ReportGenerator;
 
+use SL::DB::Part;
+
 use Data::Dumper;
 
 require "bin/mozilla/common.pl";
@@ -85,8 +87,16 @@ sub transfer_warehouse_selection {
   show_no_warehouses_error() if (!scalar @{ $form->{WAREHOUSES} });
 
   my $units      = AM->retrieve_units(\%myconfig, $form);
+
+  my $part = 0;
+  if ( $form->{parts_id} ) {
+    $part = SL::DB::Part->new();
+    $part->id($form->{parts_id});
+    $part->load();
+  }
+
   # der zweite Parameter von unit_select_data gibt den default-Namen (selected) vor
-  $form->{UNITS} = AM->unit_select_data($units, $form->{unit}, 0, $form->{unit});
+  $form->{UNITS} = AM->unit_select_data($units, $form->{unit}, 0, $part ? $part->unit : 0);
 
   if (scalar @{ $form->{WAREHOUSES} }) {
     $form->{warehouse_id} ||= $form->{WAREHOUSES}->[0]->{id};
index f118a53..b378b34 100644 (file)
@@ -87,7 +87,6 @@ $self->{texts} = {
   'Account Link IC_taxpart'     => 'Warenliste Steuer',
   'Account Link IC_taxservice'  => 'Dienstleistungen Steuer',
   'Account Number'              => 'Kontonummer',
-  'Account Number already used!' => 'Kontonummer ist bereits in Benutzung!',
   'Account Number missing!'     => 'Kontonummer fehlt!',
   'Account Nummer'              => 'Kontonummer',
   'Account Type'                => 'Kontoart',
@@ -1041,6 +1040,7 @@ $self->{texts} = {
   'Invoice total less discount' => 'Rechnungssumme abzüglich Skonto',
   'Invoice with Storno (abbreviation)' => 'R(S)',
   'Invoices'                    => 'Rechnungen',
+  'Invoices, Credit Notes & AR Transactions' => 'Rechnungen, Gutschriften & Debitorenbuchungen',
   'Is Searchable'               => 'Durchsuchbar',
   'Is this a summary account to record' => 'Sammelkonto für',
   'It is possible that even after such a correction there is something wrong with this transaction (e.g. taxes that don\'t match the selected taxkey). Therefore you should re-run the general ledger analysis.' => 'Auch nach einer Korrektur kann es mit dieser Buchung noch weitere Probleme geben (z.B. nicht zum Steuerschlüssel passende Steuern), weshalb ein erneutes Ausführen der Hauptbuchanalyse empfohlen wird.',
@@ -1874,6 +1874,7 @@ $self->{texts} = {
   'The access rights have been saved.' => 'Die Zugriffsrechte wurden gespeichert.',
   'The account 3804 already exists, the update will be skipped.' => 'Das Konto 3804 existiert schon, das Update wird übersprungen.',
   'The account 3804 will not be added automatically.' => 'Das Konto 3804 wird nicht automatisch hinzugefügt.',
+  'The action you\'ve chosen has not been executed because the document does not contain any item yet.' => 'Die von Ihnen ausgewählte Aktion wurde nicht ausgeführt, weil der Beleg noch keine Positionen enthält.',
   'The application "#1" was not found on the system.' => 'Die Anwendung "#1" wurde auf dem System nicht gefunden.',
   'The assembly has been created.' => 'Das Erzeugnis wurde hergestellt.',
   'The assistant could not find anything wrong with #1. Maybe the problem has been solved in the meantime.' => 'Der Korrekturassistent konnte kein Problem bei #1 feststellen. Eventuell wurde das Problem in der Zwischenzeit bereits behoben.',
@@ -2057,6 +2058,7 @@ $self->{texts} = {
   'This option controls the inventory system.' => 'Dieser Parameter legt die Warenbuchungsmethode fest.',
   'This option controls the method used for profit determination.' => 'Dieser Parameter legt die Berechnungsmethode für die Gewinnermittlung fest.',
   'This option controls the posting and calculation behavior for the accounting method.' => 'Dieser Parameter steuert die Buchungs- und Berechnungsmethoden für die Versteuerungsart.',
+  'This partnumber is not unique. You should change it.' => 'Diese Artikelnummer ist nicht eindeutig. Bitte wählen Sie eine andere.',
   'This transaction has to be split into several transactions manually.' => 'Diese Buchung muss manuell in mehrere Buchungen aufgeteilt werden.',
   'This update will change the nature the onhand of goods is tracked.' => 'Dieses update &auml;ndert die Art und Weise wie Lagermengen gez&auml;lt werden.',
   'This upgrade script tries to map all existing parts in the database to the newly created Buchungsgruppen.' => 'Dieses Upgradescript versucht, bei allen bestehenden Artikeln neu erstellte Buchungsgruppen zuzuordnen.',
@@ -2174,7 +2176,7 @@ $self->{texts} = {
   'Vendor'                      => 'Lieferant',
   'Vendor (name)'               => 'Lieferant (Name)',
   'Vendor Invoice'              => 'Einkaufsrechnung',
-  'Vendor Invoices'             => 'Einkaufsrechnungen',
+  'Vendor Invoices & AP Transactions' => 'Einkaufsrechnungen & Kreditorenbuchungen',
   'Vendor Name'                 => 'Lieferantenname',
   'Vendor Number'               => 'Lieferantennummer',
   'Vendor Order Number'         => 'Bestellnummer beim Lieferanten',
index 7eb8d8d..52d3ec5 100644 (file)
@@ -2139,7 +2139,7 @@ $self->{texts} = {
   'Weight unit'                 => 'Gewichtseinheit',
   'What <b>term</b> you are looking for?' => 'Nach welchem <b>Begriff</b> wollen Sie suchen?',
   'What type of item is this?'  => 'Was ist dieser Artikel?',
-  'Which is located at doc/Lx-Office-Dokumentation.pdf. Click here: ' => 'Zu finden in doc/Lx-Office-Dokumentation.pdf. Oder hier klicken: ',
+  'Which is located at doc/kivitendo-Dokumentation.pdf. Click here: ' => 'Zu finden in doc/kivitendo-Dokumentation.pdf. Oder hier klicken: ',
   'With Extension Of Time'      => 'mit Dauerfristverlängerung',
   'Workflow Delivery Order'     => 'Workflow Lieferschein',
   'Workflow purchase_order'     => 'Workflow Lieferantenauftrag',
index cb07003..fa99ded 100644 (file)
--- a/menu.ini
+++ b/menu.ini
@@ -149,7 +149,7 @@ module=do.pl
 action=search
 type=sales_delivery_order
 
-[AR--Reports--Invoices]
+[AR--Reports--Invoices, Credit Notes & AR Transactions]
 ACCESS=invoice_edit
 module=ar.pl
 action=search
@@ -222,7 +222,7 @@ module=do.pl
 action=search
 type=purchase_delivery_order
 
-[AP--Reports--Vendor Invoices]
+[AP--Reports--Vendor Invoices & AP Transactions]
 ACCESS=vendor_invoice_edit
 module=ap.pl
 action=search
index 253706c..f4a7bb7 100755 (executable)
@@ -43,7 +43,8 @@ use SL::Dispatcher;
 
 my ($opt_list, $opt_tree, $opt_rtree, $opt_nodeps, $opt_graphviz, $opt_help);
 my ($opt_user, $opt_apply, $opt_applied, $opt_unapplied, $opt_format, $opt_test_utf8);
-my ($opt_dbhost, $opt_dbport, $opt_dbname, $opt_dbuser, $opt_dbpassword);
+my ($opt_dbhost, $opt_dbport, $opt_dbname, $opt_dbuser, $opt_dbpassword, $opt_create, $opt_type);
+my ($opt_description, $opt_encoding, @opt_depends);
 
 our (%myconfig, $form, $user, $auth, $locale, $controls, $dbupgrader);
 
@@ -231,6 +232,48 @@ sub dump_nodeps {
     "\n\n";
 }
 
+sub create_upgrade {
+  my (%params) = @_;
+
+  my $filename    = $params{filename};
+  my $dbupgrader  = $params{dbupgrader};
+  my $type        = $params{type}        || '';
+  my $description = $params{description} || '';
+  my $encoding    = $params{encoding}    || 'utf-8';
+  my @depends     = @{ $params{depends} };
+
+  if (!@depends) {
+    my @releases = grep { /^release_/ } keys %$controls;
+    @depends = ((sort @releases)[-1]);
+  }
+
+  my $comment;
+  if ($type eq 'sql') {
+    $comment = '--';
+  } elsif ($type eq 'pl') {
+    $comment = '#';
+  } elsif (!$type) {
+    die 'Error: No --type was given but is required for --create.';
+  } else {
+    die 'Error: Unknown --type. Try "sql" or "pl".';
+  }
+
+  my $full_filename = $dbupgrader->path . '/' . $filename . '.' . $type;
+
+  die "file '$full_filename' already exists, aborting" if -f $full_filename;
+
+
+  open my $fh, ">:utf8", $full_filename or die "can't open $full_filename";
+  print $fh "$comment \@tag: $filename\n";
+  print $fh "$comment \@description: $description\n";
+  print $fh "$comment \@depends: @depends\n";
+  print $fh "$comment \@encoding: $encoding\n";
+  close $fh;
+
+  system("\$EDITOR $full_filename");
+  exit 0;
+}
+
 sub apply_upgrade {
   my $name = shift;
 
@@ -399,6 +442,11 @@ GetOptions("list"         => \$opt_list,
            "user=s"       => \$opt_user,
            "apply=s"      => \$opt_apply,
            "applied"      => \$opt_applied,
+           "create=s"     => \$opt_create,
+           "type=s"       => \$opt_type,
+           "encoding=s"   => \$opt_encoding,
+           "description=s" => \$opt_description,
+           "depends=s"    => \@opt_depends,
            "unapplied"    => \$opt_unapplied,
            "test-utf8"    => \$opt_test_utf8,
            "dbhost:s"     => \$opt_dbhost,
@@ -420,6 +468,12 @@ dump_tree_reverse()                         if ($opt_rtree);
 dump_graphviz('file_name' => $opt_graphviz,
               'format'    => $opt_format)   if (defined $opt_graphviz);
 dump_nodeps()                               if ($opt_nodeps);
+create_upgrade(filename   => $opt_create,
+               dbupgrader  => $dbupgrader,
+               type        => $opt_type,
+               description => $opt_description,
+               encoding    => $opt_encoding,
+               depends     => \@opt_depends) if ($opt_create);
 
 if ($opt_user) {
   $auth = SL::Auth->new();
@@ -456,6 +510,7 @@ if ($opt_unapplied) {
   dump_unapplied();
 }
 
+
 if ($opt_test_utf8) {
   $form->error("--test-utf8 used but no database name given with --dbname.") if (!$opt_dbname);
 
index 63fc858..3023833 100644 (file)
@@ -1,11 +1,11 @@
-use Test::More tests => 41;
+use Test::More tests => 47;
 
 use lib 't';
+use utf8;
 
 use Data::Dumper;
-use utf8;
+use Support::TestSetup;
 
-use_ok 'Support::TestSetup';
 use_ok 'SL::Helper::Csv';
 
 Support::TestSetup::login();
@@ -278,6 +278,8 @@ is_deeply $csv->get_data, [ { description => 'Kaffee' } ], 'eol bug at the end o
 $csv = SL::Helper::Csv->new(
   file   => \"Description\nKaffee",
   class  => 'SL::DB::Part',
+  case_insensitive_header => 1,
+  profile => { description => 'description' },
 );
 $csv->parse;
 is_deeply $csv->get_data, [ { description => 'Kaffee' } ], 'case insensitive header from csv works';
@@ -285,9 +287,11 @@ is_deeply $csv->get_data, [ { description => 'Kaffee' } ], 'case insensitive hea
 #####
 
 $csv = SL::Helper::Csv->new(
-file   => \"Kaffee",
-header => [ 'Description' ],
-class  => 'SL::DB::Part',
+  file   => \"Kaffee",
+  header => [ 'Description' ],
+  class  => 'SL::DB::Part',
+  case_insensitive_header => 1,
+  profile => { description => 'description' },
 );
 $csv->parse;
 is_deeply $csv->get_data, [ { description => 'Kaffee' } ], 'case insensitive header as param works';
@@ -302,4 +306,54 @@ $csv = SL::Helper::Csv->new(
 $csv->parse;
 is_deeply $csv->get_data, [ { description => 'Kaffee' } ], 'utf8 BOM works (bug 1872)';
 
+#####
+
+$csv = SL::Helper::Csv->new(
+  file   => \"Kaffee",
+  header => [ 'Description' ],
+  class  => 'SL::DB::Part',
+);
+$csv->parse;
+is_deeply $csv->get_data, undef, 'case insensitive header without flag ignores';
+
+#####
+
+$csv = SL::Helper::Csv->new(
+  file   => \"Kaffee",
+  header => [ 'foo' ],
+  class  => 'SL::DB::Part',
+  profile => { foo => '' },
+);
+$csv->parse;
+
+is_deeply $csv->get_data, [ { foo => 'Kaffee' } ], 'empty path still gets parsed into data';
+ok $csv->get_objects->[0], 'empty path gets ignored in object creation';
+
+#####
+
+$csv = SL::Helper::Csv->new(
+  file   => \"Kaffee",
+  header => [ 'foo' ],
+  class  => 'SL::DB::Part',
+  strict_profile => 1,
+  profile => { foo => '' },
+);
+$csv->parse;
+
+is_deeply $csv->get_data, [ { foo => 'Kaffee' } ], 'empty path still gets parsed into data (strict profile)';
+ok $csv->get_objects->[0], 'empty path gets ignored in object creation (strict profile)';
+
+$csv = SL::Helper::Csv->new(
+  file   => \"Phil",
+  header => [ 'CVAR_grOUnDHog' ],
+  class  => 'SL::DB::Part',
+  strict_profile => 1,
+  case_insensitive_header => 1,
+  profile => { cvar_Groundhog => '' },
+);
+$csv->parse;
+
+is_deeply $csv->get_data, [ { cvar_Groundhog => 'Phil' } ], 'using empty path to get cvars working';
+ok $csv->get_objects->[0], '...and not destorying the objects';
+
 # vim: ft=perl
index 191bbec..94ddf82 100644 (file)
@@ -57,7 +57,7 @@
       <select name="inventory_system">
        [% FOREACH row = INVENTORY_SYSTEMS %]<option value=[% HTML.escape(row.name) %] [% IF row.selected %]selected[% END %]>[% HTML.escape(row.name) | $T8 %]</option>[% END %]
       </select>
-     [% '* there are restrictions for the perpetual method, look at chapter "Bemerkungen zu Bestandsmethode"  in' | $T8 %] <a href="doc/Lx-Office-Dokumentation.pdf">Lx-Office-Dokumentation.pdf</a>.
+     [% '* there are restrictions for the perpetual method, look at chapter "Bemerkungen zu Bestandsmethode"  in' | $T8 %] <a href="doc/kivitendo-Dokumentation.pdf">kivitendo-Dokumentation.pdf</a>.
      </td>
 
     </tr>
index dcf0be0..1c5e7a9 100644 (file)
@@ -19,7 +19,6 @@ $(function() {
 <input type="hidden" name="type"               value="account">
 <input type="hidden" name="orphaned"           value="[% HTML.escape(orphaned) %]">
 <input type="hidden" name="new_chart_valid"    value="[% HTML.escape(new_chart_valid) %]">
-<input type="hidden" name="original_accno"    value="[% HTML.escape(accno) %]">
 <input type="hidden" name="inventory_accno_id" value="[% HTML.escape(inventory_accno_id) %]">
 <input type="hidden" name="income_accno_id"    value="[% HTML.escape(income_accno_id) %]">
 <input type="hidden" name="expense_accno_id"   value="[% HTML.escape(expense_accno_id) %]">
index 4beaf5e..01185cc 100644 (file)
@@ -6,6 +6,8 @@
 
  <p><div class="listtop">[% title %]  [% HTML.escape(partnumber) %]  [% HTML.escape(description) %]</div></p>
 
+[% PROCESS 'common/flash.html' %]
+
  <form method="post" name="ic" action="[% script %]">
 
   <input name="id" type="hidden" value="[% HTML.escape(id) %]">
index 7285d86..4c51b66 100644 (file)
@@ -42,7 +42,7 @@ $(clockon);
     [%- HTML.escape(mainitem.title) %]
    </a>
    [%- IF mainitem.subitems %]
-    <ul[%- IF force_ul_width %] width="[% mainitem.max_width * 12 %]"[% END %]>
+    <ul[%- IF force_ul_width %] width="[% mainitem.max_width * 10 %]"[% END %]>
      [%- SET sub1_id = main_id * 100 %]
      [%- FOREACH sub1item = mainitem.subitems %]
       [%- SET sub1_id = sub1_id + 1 %]
@@ -51,7 +51,7 @@ $(clockon);
         [%- HTML.escape(sub1item.title) %]
        </a>
        [%- IF sub1item.subitems %]
-        <ul[%- IF force_ul_width %] width="[% sub1item.max_width * 12 %]"[% END %]>
+        <ul[%- IF force_ul_width %] width="[% sub1item.max_width * 10 %]"[% END %]>
          [%- SET sub2_id = sub1_id * 100 %]
          [%- FOREACH sub2item = sub1item.subitems %]
           [%- SET sub2_id = sub2_id + 1 %]