Merge branch 'gewicht'
authorNiclas Zimmermann <niclas@kivitendo-premium.de>
Mon, 3 Jun 2013 11:49:40 +0000 (13:49 +0200)
committerNiclas Zimmermann <niclas@kivitendo-premium.de>
Mon, 3 Jun 2013 11:49:40 +0000 (13:49 +0200)
Conflicts:
SL/Controller/ClientConfig.pm
SL/DB/MetaSetup/Default.pm

89 files changed:
SL/AM.pm
SL/AP.pm
SL/AR.pm
SL/CP.pm
SL/CT.pm
SL/CVar.pm
SL/Common.pm
SL/Controller/ClientConfig.pm
SL/Controller/CsvImport/Base.pm
SL/Controller/CsvImport/CustomerVendor.pm
SL/Controller/DeliveryPlan.pm
SL/Controller/Helper/Filtered.pm [new file with mode: 0644]
SL/Controller/Helper/GetModels.pm
SL/Controller/Helper/Paginated.pm
SL/Controller/Helper/ParseFilter.pm
SL/DB/Currency.pm [new file with mode: 0644]
SL/DB/Default.pm
SL/DB/Helper/ALL.pm
SL/DB/Helper/Filtered.pm [new file with mode: 0644]
SL/DB/Helper/FlattenToForm.pm
SL/DB/Helper/Mappings.pm
SL/DB/Helper/PriceTaxCalculator.pm
SL/DB/Invoice.pm
SL/DB/Manager/OrderItem.pm
SL/DB/Manager/Part.pm
SL/DB/MetaSetup/Currency.pm [new file with mode: 0644]
SL/DB/MetaSetup/Customer.pm
SL/DB/MetaSetup/Default.pm
SL/DB/MetaSetup/DeliveryOrder.pm
SL/DB/MetaSetup/Exchangerate.pm
SL/DB/MetaSetup/Invoice.pm
SL/DB/MetaSetup/Order.pm
SL/DB/MetaSetup/Part.pm
SL/DB/MetaSetup/PurchaseInvoice.pm
SL/DB/MetaSetup/Vendor.pm
SL/DB/VC.pm
SL/DBConnect.pm
SL/DN.pm
SL/DO.pm
SL/Form.pm
SL/Helper/Csv.pm
SL/IC.pm
SL/IR.pm
SL/IS.pm
SL/InstanceConfiguration.pm
SL/Mailer.pm
SL/MoreCommon.pm
SL/OE.pm
SL/Presenter/Tag.pm
SL/RP.pm
SL/Template/Plugin/L.pm
SL/User.pm
SL/WH.pm
bin/mozilla/admin.pl
bin/mozilla/cp.pl
bin/mozilla/ic.pl
bin/mozilla/ir.pl
bin/mozilla/rp.pl
bin/mozilla/wh.pl
js/part_selection.js
locale/de/all
sql/Pg-upgrade2/add_warehouse_defaults.sql [new file with mode: 0644]
sql/Pg-upgrade2/contacts_convert_cp_birthday_to_date.pl
sql/Pg-upgrade2/currencies.pl [new file with mode: 0644]
sql/Pg-upgrade2/default_bin_parts.pl [new file with mode: 0644]
sql/Pg-upgrade2/erzeugnisnummern.pl
sql/Pg-upgrade2/rm_whitespaces.pl [new file with mode: 0644]
sql/Pg-upgrade2/steuerfilterung.pl
sql/Pg-upgrade2/tax_constraints.pl
t/controllers/helpers/parse_filter.t
t/helper/csv.t
templates/print/Standard/ustva.html
templates/webpages/admin/create_dataset.html
templates/webpages/am/edit_accounts.html
templates/webpages/am/edit_defaults.html
templates/webpages/client_config/form.html
templates/webpages/common/select_warehouse_bin.html [new file with mode: 0644]
templates/webpages/ct/form_header.html
templates/webpages/dbupgrade/default_bin_parts.html [new file with mode: 0644]
templates/webpages/dbupgrade/default_bin_parts_no_warehouse.html [new file with mode: 0644]
templates/webpages/dbupgrade/erzeugnisnummern.html
templates/webpages/dbupgrade/no_default_currency.html [new file with mode: 0644]
templates/webpages/dbupgrade/orphaned_currencies.html [new file with mode: 0644]
templates/webpages/delivery_plan/_filter.html
templates/webpages/do/stock_in_form.html
templates/webpages/generic/select_part.html
templates/webpages/gl/form_header_chart_balances_js.html
templates/webpages/ic/form_header.html
templates/webpages/wh/transfer_parts_selection.html

index c85c479..4934d86 100644 (file)
--- a/SL/AM.pm
+++ b/SL/AM.pm
@@ -40,6 +40,7 @@ package AM;
 use Carp;
 use Data::Dumper;
 use Encode;
+use List::MoreUtils qw(any);
 use SL::DBUtils;
 
 use strict;
@@ -207,14 +208,15 @@ sub save_account {
   # connect to database, turn off AutoCommit
   my $dbh = $form->dbconnect_noauto($myconfig);
 
-  # sanity check, can't have AR with AR_...
-  if ($form->{AR} || $form->{AP} || $form->{IC}) {
-    map { delete $form->{$_} }
-      qw(AR_amount AR_tax AR_paid AP_amount AP_tax AP_paid IC_sale IC_cogs IC_taxpart IC_income IC_expense IC_taxservice);
+  for (qw(AR_include_in_dropdown AP_include_in_dropdown summary_account)) {
+    $form->{$form->{$_}} = $form->{$_} if $form->{$_};
   }
 
-  for (qw(AR_include_in_dropdown AP_include_in_dropdown)) {
-    $form->{$form->{$_}} = $form->{$_} if $form->{$_};
+  # sanity check, can't have AR with AR_...
+  if ($form->{AR} || $form->{AP} || $form->{IC}) {
+    if (any { $form->{$_} } qw(AR_amount AR_tax AR_paid AP_amount AP_tax AP_paid IC_sale IC_cogs IC_taxpart IC_income IC_expense IC_taxservice)) {
+      $form->error($::locale->text('It is not allowed that a summary account occurs in a drop-down menu!'));
+    }
   }
 
   $form->{link} = "";
@@ -1060,10 +1062,6 @@ sub save_defaults {
   my %accnos;
   map { ($accnos{$_}) = split(m/--/, $form->{$_}) } qw(inventory_accno income_accno expense_accno fxgain_accno fxloss_accno ar_paid_accno);
 
-  $form->{curr}  =~ s/ //g;
-  my @currencies =  grep { $_ ne '' } split m/:/, $form->{curr};
-  my $currency   =  join ':', @currencies;
-
   # these defaults are database wide
 
   my $query =
@@ -1087,7 +1085,6 @@ sub save_defaults {
         assemblynumber     = ?,
         sdonumber          = ?,
         pdonumber          = ?,
-        curr               = ?,
         businessnumber     = ?,
         weightunit         = ?,
         language_id        = ?|;
@@ -1100,11 +1097,24 @@ sub save_defaults {
                 $form->{articlenumber},   $form->{servicenumber},
                 $form->{assemblynumber},
                 $form->{sdonumber},       $form->{pdonumber},
-                $currency,
                 $form->{businessnumber},  $form->{weightunit},
                 conv_i($form->{language_id}));
   do_query($form, $dbh, $query, @values);
 
+  $main::lxdebug->message(0, "es gibt rowcount: " . $form->{rowcount});
+
+  for my $i (1..$form->{rowcount}) {
+    if ($form->{"curr_$i"} ne $form->{"old_curr_$i"}) {
+      $query = qq|UPDATE currencies SET name = ? WHERE name = ?|;
+      do_query($form, $dbh, $query, $form->{"curr_$i"}, $form->{"old_curr_$i"});
+    }
+  }
+
+  if (length($form->{new_curr}) > 0) {
+    $query = qq|INSERT INTO currencies (name) VALUES (?)|;
+    do_query($form, $dbh, $query, $form->{new_curr});
+  }
+
   $dbh->commit();
 
   $main::lxdebug->leave_sub();
@@ -1118,7 +1128,7 @@ sub save_preferences {
 
   my $dbh = $form->get_standard_dbh($myconfig);
 
-  my ($currency, $businessnumber) = selectrow_query($form, $dbh, qq|SELECT curr, businessnumber FROM defaults|);
+  my ($businessnumber) = selectrow_query($form, $dbh, qq|SELECT businessnumber FROM defaults|);
 
   # update name
   my $query = qq|UPDATE employee SET name = ? WHERE login = ?|;
@@ -1126,10 +1136,6 @@ sub save_preferences {
 
   my $rc = $dbh->commit();
 
-  # save first currency in myconfig
-  $currency               =~ s/:.*//;
-  $form->{currency}       =  $currency;
-
   $form->{businessnumber} =  $businessnumber;
 
   $myconfig = User->new(login => $form->{login});
@@ -1286,6 +1292,15 @@ sub defaultaccounts {
   }
 
   $sth->finish;
+
+  #Get currencies:
+  $query              = qq|SELECT name AS curr FROM currencies ORDER BY id|;
+  $form->{CURRENCIES} = selectall_hashref_query($form, $dbh, $query);
+
+  #Which of them is the default currency?
+  $query = qq|SELECT name AS defaultcurrency FROM currencies WHERE id = (SELECT currency_id FROM defaults LIMIT 1);|;
+  ($form->{defaultcurrency}) = selectrow_query($form, $dbh, $query);
+
   $dbh->disconnect;
 
   $main::lxdebug->leave_sub();
@@ -2101,9 +2116,10 @@ sub get_warehouse {
   map { $form->{$_} = $ref->{$_} } keys %{ $ref };
 
   $query = qq|SELECT b.*, EXISTS
-                (SELECT i.warehouse_id
-                 FROM inventory i
+                (SELECT i.warehouse_id, p.warehouse_id
+                 FROM inventory i, parts p
                  WHERE i.bin_id = b.id
+                 OR    p.bin_id = b.id
                  LIMIT 1)
                 AS in_use
               FROM bin b
index 53c1d2f..bc5a362 100644 (file)
--- a/SL/AP.pm
+++ b/SL/AP.pm
@@ -55,7 +55,6 @@ sub post_transaction {
   my $exchangerate = 0;
 
   $form->{defaultcurrency} = $form->get_default_currency($myconfig);
-  delete $form->{currency} unless $form->{defaultcurrency};
 
   ($null, $form->{department_id}) = split(/--/, $form->{department});
 
@@ -185,7 +184,7 @@ sub post_transaction {
     $query = qq|UPDATE ap SET
                 invnumber = ?, transdate = ?, ordnumber = ?, vendor_id = ?, taxincluded = ?,
                 amount = ?, duedate = ?, paid = ?, netamount = ?,
-                curr = ?, notes = ?, department_id = ?, storno = ?, storno_id = ?,
+                currency_id = (SELECT id FROM currencies WHERE name = ?), notes = ?, department_id = ?, storno = ?, storno_id = ?,
                 globalproject_id = ?, direct_debit = ?
                WHERE id = ?|;
     @values = ($form->{invnumber}, conv_date($form->{transdate}),
@@ -666,7 +665,6 @@ sub post_payment {
 
   $form->{exchangerate}    = $form->format_amount($myconfig, $form->{exchangerate});
   $form->{defaultcurrency} = $form->get_default_currency($myconfig);
-  delete $form->{currency} unless $form->{defaultcurrency};
 
   # Get the AP accno.
   $query =
index 5ec2b18..b6a7376 100644 (file)
--- a/SL/AR.pm
+++ b/SL/AR.pm
@@ -56,7 +56,6 @@ sub post_transaction {
 
   my $dbh = $provided_dbh ? $provided_dbh : $form->dbconnect_noauto($myconfig);
   $form->{defaultcurrency} = $form->get_default_currency($myconfig);
-  delete $form->{currency} unless $form->{defaultcurrency};
 
   # set exchangerate
   $form->{exchangerate} = ($form->{currency} eq $form->{defaultcurrency}) ? 1 :
@@ -135,8 +134,8 @@ sub post_transaction {
     } else {
       $query = qq|SELECT nextval('glid')|;
       ($form->{id}) = selectrow_query($form, $dbh, $query);
-      $query = qq|INSERT INTO ar (id, invnumber, employee_id) VALUES (?, 'dummy', ?)|;
-      do_query($form, $dbh, $query, $form->{id}, $form->{employee_id});
+      $query = qq|INSERT INTO ar (id, invnumber, employee_id, currency_id) VALUES (?, 'dummy', ?, (SELECT id FROM currencies WHERE name=?))|;
+      do_query($form, $dbh, $query, $form->{id}, $form->{employee_id}, $form->{currency});
       $form->{invnumber} = $form->update_defaults($myconfig, "invnumber", $dbh) unless $form->{invnumber};
     }
   }
@@ -156,12 +155,12 @@ sub post_transaction {
       qq|UPDATE ar set
            invnumber = ?, ordnumber = ?, transdate = ?, customer_id = ?,
            taxincluded = ?, amount = ?, duedate = ?, paid = ?,
-           netamount = ?, curr = ?, notes = ?, department_id = ?,
+           netamount = ?, notes = ?, department_id = ?,
            employee_id = ?, storno = ?, storno_id = ?, globalproject_id = ?,
            direct_debit = ?
          WHERE id = ?|;
     my @values = ($form->{invnumber}, $form->{ordnumber}, conv_date($form->{transdate}), conv_i($form->{customer_id}), $form->{taxincluded} ? 't' : 'f', $form->{amount},
-                  conv_date($form->{duedate}), $form->{paid}, $form->{netamount}, $form->{currency}, $form->{notes}, conv_i($form->{department_id}),
+                  conv_date($form->{duedate}), $form->{paid}, $form->{netamount}, $form->{notes}, conv_i($form->{department_id}),
                   conv_i($form->{employee_id}), $form->{storno} ? 't' : 'f', $form->{storno_id},
                   conv_i($form->{globalproject_id}), $form->{direct_debit} ? 't' : 'f', conv_i($form->{id}));
     do_query($form, $dbh, $query, @values);
@@ -427,7 +426,6 @@ sub post_payment {
 
   $form->{exchangerate}    = $form->format_amount($myconfig, $form->{exchangerate});
   $form->{defaultcurrency} = $form->get_default_currency($myconfig);
-  delete $form->{currency} unless $form->{defaultcurrency};
 
   # Get the AR accno (which is normally done by Form::create_links()).
   $query =
index 18673c2..29ac35a 100644 (file)
--- a/SL/CP.pm
+++ b/SL/CP.pm
@@ -93,10 +93,9 @@ sub paymentaccounts {
   }
   $sth->finish;
 
-  # get currencies and closedto
-  $query = qq|SELECT curr, closedto FROM defaults|;
-  ($form->{currencies}, $form->{closedto}) =
-    selectrow_query($form, $dbh, $query);
+  # get closedto
+  $query = qq|SELECT closedto FROM defaults|;
+  ($form->{closedto}) = selectrow_query($form, $dbh, $query);
 
   $dbh->disconnect;
 
@@ -150,9 +149,10 @@ sub get_openinvoices {
   my $arap = $form->{arap} eq "ar" ? "ar" : "ap";
 
   my $query =
-     qq|SELECT a.id, a.invnumber, a.transdate, a.amount, a.paid, a.curr | .
+     qq|SELECT a.id, a.invnumber, a.transdate, a.amount, a.paid, cu.name AS curr | .
      qq|FROM $arap a | .
-     qq|WHERE (a.${vc}_id = ?) AND (COALESCE(a.curr, '') = ?) AND NOT (a.amount = a.paid)| .
+     qq|LEFT JOIN currencies cu ON (cu.id=a.currency_id)| .
+     qq|WHERE (a.${vc}_id = ?) AND cu.name = ? AND NOT (a.amount = a.paid)| .
      qq|ORDER BY a.id|;
   my $sth = prepare_execute_query($form, $dbh, $query,
                                   conv_i($form->{"${vc}_id"}),
@@ -174,7 +174,7 @@ sub get_openinvoices {
     SELECT COUNT(*)
     FROM $arap
     WHERE (${vc}_id = ?)
-      AND (COALESCE(curr, '') <> ?)
+      AND ((SELECT cu.name FROM currencies cu WHERE cu.id=${arap}.currency_id) <> ?)
       AND (amount <> paid)
 SQL
   ($form->{openinvoices_other_currencies}) = selectfirst_array_query($form, $dbh, $query, conv_i($form->{"${vc}_id"}), "$form->{currency}");
@@ -250,7 +250,7 @@ sub process_payment {
         qq|SELECT $buysell | .
         qq|FROM exchangerate e | .
         qq|JOIN ${arap} a ON (a.transdate = e.transdate) | .
-        qq|WHERE (e.curr = ?) AND (a.id = ?)|;
+        qq|WHERE (e.currency_id = (SELECT id FROM currencies WHERE name = ?)) AND (a.id = ?)|;
       my ($exchangerate) =
         selectrow_query($form, $dbh, $query,
                         $form->{currency}, $form->{"id_$i"});
index bf4aad4..66b3e25 100644 (file)
--- a/SL/CT.pm
+++ b/SL/CT.pm
@@ -68,11 +68,12 @@ sub get_tuple {
   my $ref = $sth->fetchrow_hashref("NAME_lc");
 
   map { $form->{$_} = $ref->{$_} } keys %$ref;
+  $sth->finish;
 
-  # remove any trailing whitespace
-  $form->{curr} =~ s/\s*$//;
+  #get name of currency instead of id:
+  $query = qq|SELECT name AS curr FROM currencies WHERE id=?|;
+  ($form->{curr}) = selectrow_query($form, $dbh, $query, conv_i($form->{currency_id}));
 
-  $sth->finish;
   if ( $form->{salesman_id} ) {
     my $query =
       qq|SELECT ct.name AS salesman | .
@@ -275,7 +276,7 @@ sub save_customer {
     $query = qq|SELECT nextval('id')|;
     ($form->{id}) = selectrow_query($form, $dbh, $query);
 
-    $query = qq|INSERT INTO customer (id, name) VALUES (?, '')|;
+    $query = qq|INSERT INTO customer (id, name, currency_id) VALUES (?, '', (SELECT currency_id FROM defaults))|;
     do_query($form, $dbh, $query, $form->{id});
   }
 
@@ -319,7 +320,7 @@ sub save_customer {
     qq|user_password = ?, | .
     qq|c_vendor_id = ?, | .
     qq|klass = ?, | .
-    qq|curr = ?, | .
+    qq|currency_id = (SELECT id FROM currencies WHERE name = ?), | .
     qq|taxincluded_checked = ? | .
     qq|WHERE id = ?|;
   my @values = (
@@ -362,7 +363,7 @@ sub save_customer {
     $form->{user_password},
     $form->{c_vendor_id},
     conv_i($form->{klass}),
-    substr($form->{currency}, 0, 3),
+    $form->{currency},
     $form->{taxincluded_checked} ne '' ? $form->{taxincluded_checked} : undef,
     $form->{id}
     );
@@ -422,7 +423,7 @@ sub save_vendor {
     $query = qq|SELECT nextval('id')|;
     ($form->{id}) = selectrow_query($form, $dbh, $query);
 
-    $query = qq|INSERT INTO vendor (id, name) VALUES (?, '')|;
+    $query = qq|INSERT INTO vendor (id, name, currency_id) VALUES (?, '', (SELECT currency_id FROM defaults))|;
     do_query($form, $dbh, $query, $form->{id});
 
     my $vendornumber      = SL::TransNumber->new(type   => 'vendor',
@@ -471,7 +472,7 @@ sub save_vendor {
     qq|  username = ?, | .
     qq|  user_password = ?, | .
     qq|  v_customer_id = ?, | .
-    qq|  curr = ? | .
+    qq|  currency_id = (SELECT id FROM currencies WHERE name = ?) | .
     qq|WHERE id = ?|;
   my @values = (
     $form->{vendornumber},
@@ -511,7 +512,7 @@ sub save_vendor {
     $form->{username},
     $form->{user_password},
     $form->{v_customer_id},
-    substr($form->{currency}, 0, 3),
+    $form->{currency},
     $form->{id}
     );
   do_query($form, $dbh, $query, @values);
@@ -615,24 +616,24 @@ sub search {
   my @values;
 
   my %allowed_sort_columns = (
-      "id" => "id",
-      "customernumber" => "customernumber",
-      "vendornumber" => "vendornumber",
-      "name" => "ct.name",
-      "contact" => "contact",
-      "phone" => "phone",
-      "fax" => "fax",
-      "email" => "email",
-      "street" => "street",
-      "taxnumber" => "taxnumber",
-      "business" => "business",
-      "invnumber" => "invnumber",
-      "ordnumber" => "ordnumber",
-      "quonumber" => "quonumber",
-      "zipcode" => "zipcode",
-      "city" => "city",
-      "country" => "country",
-      "salesman" => "e.name"
+      "id"                 => "ct.id",
+      "customernumber"     => "ct.customernumber",
+      "vendornumber"       => "ct.vendornumber",
+      "name"               => "ct.name",
+      "contact"            => "ct.contact",
+      "phone"              => "ct.phone",
+      "fax"                => "ct.fax",
+      "email"              => "ct.email",
+      "street"             => "ct.street",
+      "taxnumber"          => "ct.taxnumber",
+      "business"           => "ct.business",
+      "invnumber"          => "ct.invnumber",
+      "ordnumber"          => "ct.ordnumber",
+      "quonumber"          => "ct.quonumber",
+      "zipcode"            => "ct.zipcode",
+      "city"               => "ct.city",
+      "country"            => "ct.country",
+      "salesman"           => "e.name"
     );
 
   $form->{sort} ||= "name";
@@ -674,10 +675,10 @@ sub search {
     $where .= " AND ((lower(ct.city) LIKE lower(?))
                      OR
                      (ct.id IN (
-                        SELECT trans_id
-                        FROM shipto
-                        WHERE (module = 'CT')
-                          AND (lower(shiptocity) LIKE lower(?))
+                        SELECT sc.trans_id
+                        FROM shipto sc
+                        WHERE (sc.module = 'CT')
+                          AND (lower(sc.shiptocity) LIKE lower(?))
                       ))
                      )";
     push @values, ('%' . $form->{addr_city} . '%') x 2;
@@ -687,10 +688,10 @@ sub search {
     $where .= " AND ((lower(ct.country) LIKE lower(?))
                      OR
                      (ct.id IN (
-                        SELECT trans_id
-                        FROM shipto
-                        WHERE (module = 'CT')
-                          AND (lower(shiptocountry) LIKE lower(?))
+                        SELECT so.trans_id
+                        FROM shipto so
+                        WHERE (so.module = 'CT')
+                          AND (lower(so.shiptocountry) LIKE lower(?))
                       ))
                      )";
     push @values, ('%' . $form->{addr_country} . '%') x 2;
@@ -716,20 +717,20 @@ sub search {
   }
 
   if ($form->{obsolete} eq "Y") {
-    $where .= qq| AND obsolete|;
+    $where .= qq| AND ct.obsolete|;
   } elsif ($form->{obsolete} eq "N") {
-    $where .= qq| AND NOT obsolete|;
+    $where .= qq| AND NOT ct.obsolete|;
   }
 
   if ($form->{business_id}) {
-    $where .= qq| AND (business_id = ?)|;
+    $where .= qq| AND (ct.business_id = ?)|;
     push(@values, conv_i($form->{business_id}));
   }
 
   # Nur Kunden finden, bei denen ich selber der Verkäufer bin
   # Gilt nicht für Lieferanten
   if ($cv eq 'customer' &&   !$main::auth->assert('customer_vendor_all_edit', 1)) {
-    $where .= qq| AND ct.salesman_id = (select id from employee where login= ?)|;
+    $where .= qq| AND ct.salesman_id = (select em.id from employee em where em.login = ?)|;
     push(@values, $form->{login});
   }
 
@@ -743,12 +744,12 @@ sub search {
   }
 
   if ($form->{addr_street}) {
-    $where .= qq| AND (street ILIKE ?)|;
+    $where .= qq| AND (ct.street ILIKE ?)|;
     push @values, '%' . $form->{addr_street} . '%';
   }
 
   if ($form->{addr_zipcode}) {
-    $where .= qq| AND (zipcode ILIKE ?)|;
+    $where .= qq| AND (ct.zipcode ILIKE ?)|;
     push @values, $form->{addr_zipcode} . '%';
   }
 
index 0a91c59..02456cb 100644 (file)
@@ -2,12 +2,13 @@ package CVar;
 
 use strict;
 
+use List::MoreUtils qw(any);
 use List::Util qw(first);
 use Scalar::Util qw(blessed);
 use Data::Dumper;
 
 use SL::DBUtils;
-use SL::MoreCommon qw(any listify);
+use SL::MoreCommon qw(listify);
 
 sub get_configs {
   $main::lxdebug->enter_sub();
index ff6c721..dc1cc04 100644 (file)
@@ -85,7 +85,7 @@ sub retrieve_parts {
   }
 
   if ($form->{no_services}) {
-    $filter .= qq| AND (inventory_accno_id is not NULL or assembly=TRUE)|; # @mb hier nochmal optimieren ... nach kurzer ruecksprache alles i.o.
+    $filter .= qq| AND (inventory_accno_id is not NULL or assembly=TRUE)|;
   }
 
   substr($filter, 1, 3) = "WHERE" if ($filter);
@@ -94,7 +94,8 @@ sub retrieve_parts {
   $order_dir = $order_dir ? "ASC" : "DESC";
 
   my $query =
-    qq|SELECT id, partnumber, description, ean | .
+    qq|SELECT id, partnumber, description, ean, | .
+    qq|       warehouse_id, bin_id | .
     qq|FROM parts $filter | .
     qq|ORDER BY $order_by $order_dir|;
   my $sth = $dbh->prepare($query);
@@ -501,6 +502,8 @@ sub save_email_status {
   } elsif ($form->{script} eq 'ir.pl') {
     $table = 'ap';
 
+  } elsif ($form->{script} eq 'do.pl') {
+    $table = 'delivery_orders';
   }
 
   return $main::lxdebug->leave_sub() if (!$form->{id} || !$table || !$form->{formname});
index 74bc8b5..148054e 100644 (file)
@@ -43,6 +43,19 @@ sub action_edit {
 
   map { $self->{$_} = SL::DB::Default->get->$_ } qw(sales_order_show_delete purchase_order_show_delete sales_delivery_order_show_delete purchase_delivery_order_show_delete);
 
+  map { $self->{$_} = SL::DB::Default->get->$_ } qw(warehouse_id bin_id);
+  $::form->get_lists('warehouses' => { 'key'    => 'WAREHOUSES',
+                                       'bins'   => 'BINS', });
+  $self->{WAREHOUSES} = $::form->{WAREHOUSES};
+  # leerer lagerplatz mit id 0
+  my $no_default_bin_entry = { 'id' => '0', description => '--', 'BINS' => [ { id => '0', description => ''} ] };
+  push @ { $self->{WAREHOUSES} }, $no_default_bin_entry;
+
+  if (my $max = scalar @{ $self->{WAREHOUSES} }) {
+    $self->{warehouse_id} ||= $self->{WAREHOUSES}->[$max -1]->{id};
+    $self->{bin_id}       ||= $self->{WAREHOUSES}->[$max -1]->{BINS}->[0]->{id};
+  }
+
   $self->{show_weight} = SL::DB::Default->get->show_weight;
 
   $self->render('client_config/form', title => $::locale->text('Client Configuration'));
@@ -66,7 +79,16 @@ sub action_save {
 
   map { SL::DB::Default->get->update_attributes($_ => $::form->{$_}); } qw(sales_order_show_delete purchase_order_show_delete sales_delivery_order_show_delete purchase_delivery_order_show_delete);
 
+<<<<<<< HEAD
+  # undef warehouse_id if the empty value is selected
+  if ( ($::form->{warehouse_id} == 0) && ($::form->{bin_id} == 0) ) {
+    undef $::form->{warehouse_id};
+    undef $::form->{bin_id};
+  }
+  map { SL::DB::Default->get->update_attributes($_ => $::form->{$_}); } qw(warehouse_id bin_id);
+=======
   SL::DB::Default->get->update_attributes('show_weight'     => $::form->{show_weight});
+>>>>>>> gewicht
 
   flash_later('info', $::locale->text('Client Configuration saved!'));
 
index 7f4326a..db6dc4d 100644 (file)
@@ -5,6 +5,7 @@ use strict;
 use List::MoreUtils qw(pairwise);
 
 use SL::Helper::Csv;
+use SL::DB::Currency;
 use SL::DB::Customer;
 use SL::DB::Language;
 use SL::DB::PaymentTerm;
@@ -16,7 +17,7 @@ use parent qw(Rose::Object);
 use Rose::Object::MakeMethods::Generic
 (
  scalar                  => [ qw(controller file csv test_run save_with_cascade) ],
- 'scalar --get_set_init' => [ qw(profile displayable_columns existing_objects class manager_class cvar_columns all_cvar_configs all_languages payment_terms_by all_vc vc_by) ],
+ 'scalar --get_set_init' => [ qw(profile displayable_columns existing_objects class manager_class cvar_columns all_cvar_configs all_languages payment_terms_by all_currencies default_currency_id all_vc vc_by) ],
 );
 
 sub run {
@@ -139,6 +140,18 @@ sub init_all_languages {
   return SL::DB::Manager::Language->get_all;
 }
 
+sub init_all_currencies {
+  my ($self) = @_;
+
+  return SL::DB::Manager::Currency->get_all;
+}
+
+sub init_default_currency_id {
+  my ($self) = @_;
+
+  return SL::DB::Default->get->currency_id;
+}
+
 sub init_payment_terms_by {
   my ($self) = @_;
 
index 96eb696..4796493 100644 (file)
@@ -13,7 +13,7 @@ use parent qw(SL::Controller::CsvImport::Base);
 
 use Rose::Object::MakeMethods::Generic
 (
- 'scalar --get_set_init' => [ qw(table languages_by businesses_by) ],
+ 'scalar --get_set_init' => [ qw(table languages_by businesses_by currencies_by) ],
 );
 
 sub init_table {
@@ -44,6 +44,12 @@ sub init_languages_by {
   return { map { my $col = $_; ( $col => { map { ( $_->$col => $_ ) } @{ $self->all_languages } } ) } qw(id description article_code) };
 }
 
+sub init_currencies_by {
+  my ($self) = @_;
+
+  return { map { my $col = $_; ( $col => { map { ( $_->$col => $_ ) } @{ $self->all_currencies } } ) } qw(id name) };
+}
+
 sub check_objects {
   my ($self) = @_;
 
@@ -65,6 +71,7 @@ sub check_objects {
     $self->check_language($entry);
     $self->check_business($entry);
     $self->check_payment($entry);
+    $self->check_currency($entry);
     $self->handle_cvars($entry);
 
     next if @{ $entry->{errors} };
@@ -155,6 +162,36 @@ sub check_language {
   return 1;
 }
 
+sub check_currency {
+  my ($self, $entry) = @_;
+
+  my $object = $entry->{object};
+
+  # Check whether or not currency ID is valid.
+  if ($object->currency_id && !$self->currencies_by->{id}->{ $object->currency_id }) {
+    push @{ $entry->{errors} }, $::locale->text('Error: Invalid currency');
+    return 0;
+  }
+
+  # Map name to ID if given.
+  if (!$object->currency_id && $entry->{raw_data}->{currency}) {
+    my $currency = $self->currencies_by->{name}->{  $entry->{raw_data}->{currency} };
+    if (!$currency) {
+      push @{ $entry->{errors} }, $::locale->text('Error: Invalid currency');
+      return 0;
+    }
+
+    $object->currency_id($currency->id);
+  }
+
+  # Set default currency if none was given.
+  $object->currency_id($self->default_currency_id) if !$object->currency_id;
+
+  $entry->{raw_data}->{currency_id} = $object->currency_id;
+
+  return 1;
+}
+
 sub check_business {
   my ($self, $entry) = @_;
 
@@ -253,6 +290,8 @@ sub setup_displayable_columns {
                                  { name => 'contact',           description => $::locale->text('Contact')                         },
                                  { name => 'country',           description => $::locale->text('Country')                         },
                                  { name => 'creditlimit',       description => $::locale->text('Credit Limit')                    },
+                                 { name => 'currency',          description => $::locale->text('Currency')                        },
+                                 { name => 'currency_id',       description => $::locale->text('Currency (database ID)')          },
                                  { name => 'customernumber',    description => $::locale->text('Customer Number')                 },
                                  { name => 'department_1',      description => $::locale->text('Department 1')                    },
                                  { name => 'department_2',      description => $::locale->text('Department 2')                    },
index 33bede6..00adfc9 100644 (file)
@@ -8,7 +8,7 @@ use SL::DB::OrderItem;
 use SL::Controller::Helper::GetModels;
 use SL::Controller::Helper::Paginated;
 use SL::Controller::Helper::Sorted;
-use SL::Controller::Helper::ParseFilter;
+use SL::Controller::Helper::Filtered;
 use SL::Controller::Helper::ReportGenerator;
 use SL::Locale::String;
 
@@ -18,10 +18,12 @@ use Rose::Object::MakeMethods::Generic (
 
 __PACKAGE__->run_before(sub { $::auth->assert('sales_order_edit'); });
 
-__PACKAGE__->get_models_url_params('flat_filter');
+__PACKAGE__->make_filtered(
+  MODEL             => 'OrderItem',
+  LAUNDER_TO        => 'filter'
+);
 __PACKAGE__->make_paginated(
   MODEL         => 'OrderItem',
-  PAGINATE_ARGS => 'db_args',
   ONLY          => [ qw(list) ],
 );
 
@@ -42,110 +44,91 @@ __PACKAGE__->make_sorted(
   customer          => t8('Customer'),
 );
 
-sub action_list {
-  my ($self) = @_;
-
-  $self->db_args($self->setup_for_list(filter => $::form->{filter}));
-  $self->flat_filter({ map { $_->{key} => $_->{value} } $::form->flatten_variables('filter') });
-  $self->make_filter_summary;
-
-  $self->prepare_report;
-
-  my $orderitems = $self->get_models(%{ $self->db_args });
-
-  $self->report_generator_list_objects(report => $self->{report}, objects => $orderitems);
-}
-
-# private functions
-
-sub setup_for_list {
-  my ($self, %params) = @_;
-  $self->{filter} = {};
-  my %args = (
-    parse_filter(
-      $self->_pre_parse_filter($::form->{filter}, $self->{filter}),
-      with_objects => [ 'order', 'order.customer', 'part' ],
-      launder_to => $self->{filter},
-    ),
-  );
-
-  $args{query} = [ @{ $args{query} || [] },
-    (
-      'order.customer_id' => { gt => 0 },
-      'order.closed' => 0,
-      or => [ 'order.quotation' => 0, 'order.quotation' => undef ],
-
-      # filter by shipped_qty < qty, read from innermost to outermost
-      'id' => [ \"
-        -- 3. resolve the desired information about those
-        SELECT oi.id FROM (
-          -- 2. slice only part, orderitem and both quantities from it
-          SELECT parts_id, trans_id, qty, SUM(doi_qty) AS doi_qty FROM (
-            -- 1. join orderitems and deliverorder items via record_links.
-            --    also add customer data to filter for sales_orders
-            SELECT oi.parts_id, oi.trans_id, oi.id, oi.qty, doi.qty AS doi_qty
-            FROM orderitems oi, oe, record_links rl, delivery_order_items doi
-            WHERE
-              oe.id = oi.trans_id AND
-              oe.customer_id IS NOT NULL AND
-              (oe.quotation = 'f' OR oe.quotation IS NULL) AND
-              NOT oe.closed AND
-              rl.from_id = oe.id AND
-              rl.from_id = oi.trans_id AND
-              oe.id = oi.trans_id AND
-              rl.from_table = 'oe' AND
-              rl.to_table = 'delivery_orders' AND
-              rl.to_id = doi.delivery_order_id AND
-              oi.parts_id = doi.parts_id
-          ) tuples GROUP BY parts_id, trans_id, qty
-        ) partials
-        LEFT JOIN orderitems oi ON partials.parts_id = oi.parts_id AND partials.trans_id = oi.trans_id
-        WHERE oi.qty > doi_qty
-
-        UNION ALL
-
-        -- 4. since the join over record_links fails for sales_orders wihtout any delivery order
-        --    retrieve those without record_links at all
-        SELECT oi.id FROM orderitems oi, oe
+my $delivery_plan_query = [
+  'order.customer_id' => { gt => 0 },
+  'order.closed' => 0,
+  or => [ 'order.quotation' => 0, 'order.quotation' => undef ],
+
+  # filter by shipped_qty < qty, read from innermost to outermost
+  'id' => [ \"
+    -- 3. resolve the desired information about those
+    SELECT oi.id FROM (
+      -- 2. slice only part, orderitem and both quantities from it
+      SELECT parts_id, trans_id, qty, SUM(doi_qty) AS doi_qty FROM (
+        -- 1. join orderitems and deliverorder items via record_links.
+        --    also add customer data to filter for sales_orders
+        SELECT oi.parts_id, oi.trans_id, oi.id, oi.qty, doi.qty AS doi_qty
+        FROM orderitems oi, oe, record_links rl, delivery_order_items doi
         WHERE
           oe.id = oi.trans_id AND
           oe.customer_id IS NOT NULL AND
           (oe.quotation = 'f' OR oe.quotation IS NULL) AND
           NOT oe.closed AND
-          oi.trans_id NOT IN (
-            SELECT from_id
-            FROM record_links rl
-            WHERE
-              rl.from_table ='oe' AND
-              rl.to_table = 'delivery_orders'
-          )
-
-        UNION ALL
-
-        -- 5. In case someone deleted a line of the delivery_order there will be a record_link (4 fails)
-        --    but there won't be a delivery_order_items to find (3 fails too). Search for orphaned orderitems this way
-        SELECT oi.id FROM orderitems AS oi, oe, record_links AS rl
-        WHERE
+          rl.from_id = oe.id AND
+          rl.from_id = oi.trans_id AND
+          oe.id = oi.trans_id AND
           rl.from_table = 'oe' AND
           rl.to_table = 'delivery_orders' AND
+          rl.to_id = doi.delivery_order_id AND
+          oi.parts_id = doi.parts_id
+      ) tuples GROUP BY parts_id, trans_id, qty
+    ) partials
+    LEFT JOIN orderitems oi ON partials.parts_id = oi.parts_id AND partials.trans_id = oi.trans_id
+    WHERE oi.qty > doi_qty
+
+    UNION ALL
+
+    -- 4. since the join over record_links fails for sales_orders wihtout any delivery order
+    --    retrieve those without record_links at all
+    SELECT oi.id FROM orderitems oi, oe
+    WHERE
+      oe.id = oi.trans_id AND
+      oe.customer_id IS NOT NULL AND
+      (oe.quotation = 'f' OR oe.quotation IS NULL) AND
+      NOT oe.closed AND
+      oi.trans_id NOT IN (
+        SELECT from_id
+        FROM record_links rl
+        WHERE
+          rl.from_table ='oe' AND
+          rl.to_table = 'delivery_orders'
+      )
 
-          oi.trans_id = rl.from_id AND
-          oi.parts_id NOT IN (
-            SELECT doi.parts_id FROM delivery_order_items AS doi WHERE doi.delivery_order_id = rl.to_id
-          ) AND
+    UNION ALL
 
-          oe.id = oi.trans_id AND
+    -- 5. In case someone deleted a line of the delivery_order there will be a record_link (4 fails)
+    --    but there won't be a delivery_order_items to find (3 fails too). Search for orphaned orderitems this way
+    SELECT oi.id FROM orderitems AS oi, oe, record_links AS rl
+    WHERE
+      rl.from_table = 'oe' AND
+      rl.to_table = 'delivery_orders' AND
 
-          oe.customer_id IS NOT NULL AND
-          (oe.quotation = 'f' OR oe.quotation IS NULL) AND
-          NOT oe.closed
-      " ],
-    )
-  ];
+      oi.trans_id = rl.from_id AND
+      oi.parts_id NOT IN (
+        SELECT doi.parts_id FROM delivery_order_items AS doi WHERE doi.delivery_order_id = rl.to_id
+      ) AND
 
-  return \%args;
+      oe.id = oi.trans_id AND
+
+      oe.customer_id IS NOT NULL AND
+      (oe.quotation = 'f' OR oe.quotation IS NULL) AND
+      NOT oe.closed
+  " ],
+];
+
+sub action_list {
+  my ($self) = @_;
+
+  $self->make_filter_summary;
+
+  my $orderitems = $self->get_models(query => $delivery_plan_query, with_objects => [ 'order', 'order.customer', 'part' ]);
+
+  $self->prepare_report;
+  $self->report_generator_list_objects(report => $self->{report}, objects => $orderitems);
 }
 
+# private functions
+#
 sub prepare_report {
   my ($self)      = @_;
 
@@ -209,14 +192,15 @@ sub make_filter_summary {
     [ $filter->{order}{customer}{"customernumber:substr::ilike"}, $::locale->text('Customer Number')                                    ],
   );
 
-  my @flags = (
-    [ $filter->{part}{type}{part},     $::locale->text('Parts')      ],
-    [ $filter->{part}{type}{service},  $::locale->text('Services')   ],
-    [ $filter->{part}{type}{assembly}, $::locale->text('Assemblies') ],
+  my %flags = (
+    part     => $::locale->text('Parts'),
+    service  => $::locale->text('Services'),
+    assembly => $::locale->text('Assemblies'),
   );
+  my @flags = map { $flags{$_} } @{ $filter->{part}{type} || [] };
 
   for (@flags) {
-    push @filter_strings, "$_->[1]" if $_->[0];
+    push @filter_strings, $_ if $_;
   }
   for (@filters) {
     push @filter_strings, "$_->[1]: $_->[0]" if $_->[0];
@@ -248,45 +232,4 @@ sub link_to {
   }
 }
 
-# unfortunately ParseFilter can't handle compount filters.
-# so we clone the original filter (still need that for serializing)
-# rip out the options we know an replace them with the compound options.
-# ParseFilter will take care of the prefixing then.
-sub _pre_parse_filter {
-  my ($self, $orig_filter, $launder_to) = @_;
-
-  return undef unless $orig_filter;
-
-  my $filter = clone($orig_filter);
-  if ($filter->{part} && $filter->{part}{type}) {
-    $launder_to->{part}{type} = delete $filter->{part}{type};
-    my @part_filters = grep $_, map {
-      $launder_to->{part}{type}{$_} ? SL::DB::Manager::Part->type_filter($_) : ()
-    } qw(part service assembly);
-
-    push @{ $filter->{and} }, or => [ @part_filters ] if @part_filters;
-  }
-
-  for my $op (qw(le ge)) {
-    if ($filter->{"reqdate:date::$op"}) {
-      $launder_to->{"reqdate_date__$op"} = delete $filter->{"reqdate:date::$op"};
-      my $parsed_date = DateTime->from_lxoffice($launder_to->{"reqdate_date__$op"});
-      push @{ $filter->{and} }, or => [
-        'reqdate' => { $op => $parsed_date },
-        and => [
-          'reqdate' => undef,
-          'order.reqdate' => { $op => $parsed_date },
-        ]
-      ] if $parsed_date;
-    }
-  }
-
-  if (my $style = delete $filter->{searchstyle}) {
-    $self->{searchstyle}       = $style;
-    $launder_to->{searchstyle} = $style;
-  }
-
-  return $filter;
-}
-
 1;
diff --git a/SL/Controller/Helper/Filtered.pm b/SL/Controller/Helper/Filtered.pm
new file mode 100644 (file)
index 0000000..d248bf6
--- /dev/null
@@ -0,0 +1,310 @@
+package SL::Controller::Helper::Filtered;
+
+use strict;
+
+use Exporter qw(import);
+use SL::Controller::Helper::ParseFilter ();
+use List::MoreUtils qw(uniq);
+our @EXPORT = qw(make_filtered get_filter_spec get_current_filter_params disable_filtering _save_current_filter_params _callback_handler_for_filtered _get_models_handler_for_filtered);
+
+use constant PRIV => '__filteredhelper_priv';
+
+my %controller_filter_spec;
+
+sub make_filtered {
+  my ($class, %specs)       = @_;
+
+  $specs{MODEL}           //=  $class->controller_name;
+  $specs{MODEL}             =~ s{ ^ SL::DB:: (?: .* :: )? }{}x;
+  $specs{FORM_PARAMS}     //= 'filter';
+  $specs{LAUNDER_TO}        = '__INPLACE__' unless exists $specs{LAUNDER_TO};
+  $specs{ONLY}            //= [];
+  $specs{ONLY}              = [ $specs{ONLY} ] if !ref $specs{ONLY};
+  $specs{ONLY_MAP}          = @{ $specs{ONLY} } ? { map { ($_ => 1) } @{ $specs{ONLY} } } : { '__ALL__' => 1 };
+
+  $controller_filter_spec{$class} = \%specs;
+
+  my %hook_params           = @{ $specs{ONLY} } ? ( only => $specs{ONLY} ) : ();
+  $class->run_before('_save_current_filter_params', %hook_params);
+
+  SL::Controller::Helper::GetModels::register_get_models_handlers(
+    $class,
+    callback   => '_callback_handler_for_filtered',
+    get_models => '_get_models_handler_for_filtered',
+    ONLY       => $specs{ONLY},
+  );
+
+  # $::lxdebug->dump(0, "CONSPEC", \%specs);
+}
+
+sub get_filter_spec {
+  my ($class_or_self) = @_;
+
+  return $controller_filter_spec{ref($class_or_self) || $class_or_self};
+}
+
+sub get_current_filter_params {
+  my ($self)   = @_;
+
+  return %{ _priv($self)->{filter_params} } if _priv($self)->{filter_params};
+
+  require Carp;
+  Carp::confess('It seems a GetModels plugin tries to access filter params before they got calculated. Make sure your make_filtered call comes first.');
+}
+
+sub _make_current_filter_params {
+  my ($self, %params)   = @_;
+
+  my $spec              = $self->get_filter_spec;
+  my $filter            = $params{filter} // _priv($self)->{filter} // {},
+  my %filter_args       = _get_filter_args($self, $spec);
+  my %parse_filter_args = (
+    class        => "SL::DB::Manager::$spec->{MODEL}",
+    with_objects => $params{with_objects},
+  );
+  my $laundered;
+  if ($spec->{LAUNDER_TO} eq '__INPLACE__') {
+
+  } elsif ($spec->{LAUNDER_TO}) {
+    $laundered = {};
+    $parse_filter_args{launder_to} = $laundered;
+  } else {
+    $parse_filter_args{no_launder} = 1;
+  }
+
+  my %calculated_params = SL::Controller::Helper::ParseFilter::parse_filter($filter, %parse_filter_args);
+
+  $calculated_params{query} = [
+    @{ $calculated_params{query} || [] },
+    @{ $filter_args{query} || [] },
+    @{ $params{query} || [] },
+  ];
+
+  $calculated_params{with_objects} = [
+    uniq
+    @{ $calculated_params{with_objects} || [] },
+    @{ $filter_args{with_objects} || [] },
+    @{ $params{with_objects} || [] },
+  ];
+
+  if ($laundered) {
+    if ($self->can($spec->{LAUNDER_TO})) {
+      $self->${\ $spec->{LAUNDER_TO} }($laundered);
+    } else {
+      $self->{$spec->{LAUNDER_TO}} = $laundered;
+    }
+  }
+
+  # $::lxdebug->dump(0, "get_current_filter_params: ", \%calculated_params);
+
+  _priv($self)->{filter_params} = \%calculated_params;
+
+  return %calculated_params;
+}
+
+sub disable_filtering {
+  my ($self)               = @_;
+  _priv($self)->{disabled} = 1;
+}
+
+#
+# private functions
+#
+
+sub _get_filter_args {
+  my ($self, $spec) = @_;
+
+  $spec ||= $self->get_filter_spec;
+
+  my %filter_args       = ref($spec->{FILTER_ARGS}) eq 'CODE' ? %{ $spec->{FILTER_ARGS}->($self) }
+                        :     $spec->{FILTER_ARGS}            ? do { my $sub = $spec->{FILTER_ARGS}; %{ $self->$sub() } }
+                        :                                       ();
+}
+
+sub _save_current_filter_params {
+  my ($self)        = @_;
+
+  return if !_is_enabled($self);
+
+  my $filter_spec = $self->get_filter_spec;
+  $self->{PRIV()}{filter} = $::form->{ $filter_spec->{FORM_PARAMS} };
+
+  # $::lxdebug->message(0, "saving current filter params to " . $self->{PRIV()}->{page} . ' / ' . $self->{PRIV()}->{per_page});
+}
+
+sub _callback_handler_for_filtered {
+  my ($self, %params) = @_;
+  my $priv            = _priv($self);
+
+  if (_is_enabled($self) && $priv->{filter}) {
+    my $filter_spec                             = $self->get_filter_spec;
+    my ($flattened) = SL::Controller::Helper::ParseFilter::flatten($priv->{filter}, undef, $filter_spec->{FORM_PARAMS});
+    %params = (%params, @$flattened);
+  }
+
+  # $::lxdebug->dump(0, "CB handler for filtered; params after flatten:", \%params);
+
+  return %params;
+}
+
+sub _get_models_handler_for_filtered {
+  my ($self, %params)    = @_;
+  my $spec               = $self->get_filter_spec;
+
+  # $::lxdebug->dump(0,  "params in get_models_for_filtered", \%params);
+
+  my %filter_params;
+  %filter_params = _make_current_filter_params($self, %params)  if _is_enabled($self);
+
+  # $::lxdebug->dump(0, "GM handler for filtered; params nach modif (is_enabled? " . _is_enabled($self) . ")", \%params);
+
+  return (%params, %filter_params);
+}
+
+sub _priv {
+  my ($self)        = @_;
+  $self->{PRIV()} ||= {};
+  return $self->{PRIV()};
+}
+
+sub _is_enabled {
+  my ($self) = @_;
+  return !_priv($self)->{disabled} && ($self->get_filter_spec->{ONLY_MAP}->{$self->action_name} || $self->get_filter_spec->{ONLY_MAP}->{'__ALL__'});
+}
+
+
+1;
+
+__END__
+
+=pod
+
+=encoding utf8
+
+=head1 NAME
+
+SL::Controller::Helper::Filtered - A helper for semi-automatic handling
+of filtered lists of database models in a controller
+
+=head1 SYNOPSIS
+
+In a controller:
+
+  use SL::Controller::Helper::GetModels;
+  use SL::Controller::Helper::Filtered;
+
+  __PACKAGE__->make_filter(
+    MODEL       => 'Part',
+    ONLY        => [ qw(list) ],
+    FORM_PARAMS => [ qw(filter) ],
+  );
+
+  sub action_list {
+    my ($self) = @_;
+
+    my $filtered_models = $self->get_models(%addition_filters);
+    $self->render('controller/list', ENTRIES => $filtered_models);
+  }
+
+
+=head1 OVERVIEW
+
+This helper module enables use of the L<SL::Controller::Helper::ParseFilter>
+methods in conjunction with the L<SL::Controller::Helper::GetModels> style of
+plugins. Additional filters can be defined in the database models and filtering
+can be reduced to a minimum of work.
+
+This plugin can be combined with L<SL::Controller::Sorted> and
+L<SL::Controller::Paginated> for filtered, sorted and paginated lists.
+
+The controller has to provive information where to look for filter information
+at compile time. This call is L<make_filtered>.
+
+The underlying functionality that enables the use of more than just
+the paginate helper is provided by the controller helper
+C<GetModels>. See the documentation for L<SL::Controller::Sorted> for
+more information on it.
+
+=head1 PACKAGE FUNCTIONS
+
+=over 4
+
+=item C<make_filtered %filter_spec>
+
+This function must be called by a controller at compile time. It is
+uesd to set the various parameters required for this helper to do its
+magic.
+
+Careful: If you want to use this in conjunction with
+L<SL:Controller::Helper::Paginated>, you need to call C<make_filtered> first,
+or the paginating will not get all the relevant information to estimate the
+number of pages correctly. To ensure this does not happen, this module will
+croak when it detects such a scenario.
+
+The hash C<%filter_spec> can include the following parameters:
+
+=over 4
+
+=item * C<MODEL>
+
+Optional. A string: the name of the Rose database model that is used
+as a default in certain cases. If this parameter is missing then it is
+derived from the controller's package (e.g. for the controller
+C<SL::Controller::BackgroundJobHistory> the C<MODEL> would default to
+C<BackgroundJobHistory>).
+
+=item * C<FORM_PARAMS>
+
+Optional. Indicates a key in C<$::form> to be used as filter.
+
+Defaults to the values C<filter> if missing.
+
+=item * C<LAUNDER_TO>
+
+Option. Indicates a target for laundered filter arguments in the controller.
+Can be set to C<undef> to disable laundering, and can be set to method named or
+hash keys of the controller. In the latter case the laundered structure will be
+put there.
+
+Defaults to inplace laundering which is not normally settable.
+
+=item * C<ONLY>
+
+Optional. An array reference containing a list of action names for
+which the paginate parameters should be saved. If missing or empty then
+all actions invoked on the controller are monitored.
+
+=back
+
+=back
+
+=head1 INSTANCE FUNCTIONS
+
+These functions are called on a controller instance.
+
+=over 4
+
+=item C<get_current_filter_params>
+
+Returns a hash to be used in manager C<get_all> calls or to be passed on to
+GetModels. Will only work if the get_models chain has been called at least
+once, because only then the full parameters can get parsed and stored. Will
+croak otherwise.
+
+=item C<disable_filtering>
+
+Disable filtering for the duration of the current action. Can be used
+when using the attribute C<ONLY> to L<make_filtered> does not
+cover all cases.
+
+=back
+
+=head1 BUGS
+
+Nothing here yet.
+
+=head1 AUTHOR
+
+Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>
+
+=cut
index e353657..8e2ede1 100644 (file)
@@ -7,7 +7,7 @@ our @EXPORT = qw(get_models_url_params get_callback get_models);
 
 use constant PRIV => '__getmodelshelperpriv';
 
-my %registered_handlers = ( callback => [], get_models => [] );
+my $registered_handlers = {};
 
 sub register_get_models_handlers {
   my ($class, %additional_handlers) = @_;
@@ -18,7 +18,8 @@ sub register_get_models_handlers {
 
   $class->run_before(sub { $_[0]->{PRIV()} = { current_action => $_[1] }; }, %hook_params);
 
-  map { push @{ $registered_handlers{$_} }, $additional_handlers{$_} if $additional_handlers{$_} } keys %registered_handlers;
+  my $handlers    = _registered_handlers($class);
+  map { push @{ $handlers->{$_} }, $additional_handlers{$_} if $additional_handlers{$_} } keys %$handlers;
 }
 
 sub get_models_url_params {
@@ -34,7 +35,7 @@ sub get_models_url_params {
     );
   };
 
-  push @{ $registered_handlers{callback} }, $callback;
+  push @{ _registered_handlers($class)->{callback} }, $callback;
 }
 
 sub get_callback {
@@ -48,9 +49,8 @@ sub get_callback {
 sub get_models {
   my ($self, %override_params) = @_;
 
-  my %default_params           = _run_handlers($self, 'get_models');
+  my %params                   = _run_handlers($self, 'get_models', %override_params);
 
-  my %params                   = (%default_params, %override_params);
   my $model                    = delete($params{model}) || die "No 'model' to work on";
 
   return "SL::DB::Manager::${model}"->get_all(%params);
@@ -63,7 +63,7 @@ sub get_models {
 sub _run_handlers {
   my ($self, $handler_type, %params) = @_;
 
-  foreach my $sub (@{ $registered_handlers{$handler_type} }) {
+  foreach my $sub (@{ _registered_handlers(ref $self)->{$handler_type} }) {
     if (ref $sub eq 'CODE') {
       %params = $sub->($self, %params);
     } elsif ($self->can($sub)) {
@@ -76,6 +76,10 @@ sub _run_handlers {
   return %params;
 }
 
+sub _registered_handlers {
+  $registered_handlers->{$_[0]} //= { callback => [], get_models => [] }
+}
+
 1;
 __END__
 
index 15997b8..25c4efa 100644 (file)
@@ -18,6 +18,7 @@ sub make_paginated {
   $specs{MODEL}             =~ s{ ^ SL::DB:: (?: .* :: )? }{}x;
   $specs{PER_PAGE}        ||= "SL::DB::Manager::$specs{MODEL}"->default_objects_per_page;
   $specs{FORM_PARAMS}     ||= [ qw(page per_page) ];
+  $specs{PAGINATE_ARGS}   ||= '__FILTER__';
   $specs{ONLY}            ||= [];
   $specs{ONLY}              = [ $specs{ONLY} ] if !ref $specs{ONLY};
   $specs{ONLY_MAP}          = @{ $specs{ONLY} } ? { map { ($_ => 1) } @{ $specs{ONLY} } } : { '__ALL__' => 1 };
@@ -57,9 +58,13 @@ sub get_current_paginate_params {
     per_page            => ($params{per_page} * 1) || $spec->{PER_PAGE},
   );
 
-  my %paginate_args     = ref($spec->{PAGINATE_ARGS}) eq 'CODE' ? %{ $spec->{PAGINATE_ARGS}->($self) }
-                        :     $spec->{PAGINATE_ARGS}            ? do { my $sub = $spec->{PAGINATE_ARGS}; %{ $self->$sub() } }
-                        :                                         ();
+  # try to use Filtered if available and nothing else is configured, but don't
+  # blow up if the controller does not use Filtered
+  my %paginate_args     = ref($spec->{PAGINATE_ARGS}) eq 'CODE'       ? %{ $spec->{PAGINATE_ARGS}->($self) }
+                        :     $spec->{PAGINATE_ARGS}  eq '__FILTER__'
+                           && $self->can('get_current_filter_params') ? $self->get_current_filter_params
+                        :     $spec->{PAGINATE_ARGS}  ne '__FILTER__' ? do { my $sub = $spec->{PAGINATE_ARGS}; %{ $self->$sub() } }
+                        :                                               ();
   my $calculated_params = "SL::DB::Manager::$spec->{MODEL}"->paginate(%paginate_params, args => \%paginate_args);
 
   # $::lxdebug->dump(0, "get_current_paginate_params: ", $calculated_params);
index 3bf1b65..ee5a740 100644 (file)
@@ -8,6 +8,7 @@ our @EXPORT = qw(parse_filter);
 use DateTime;
 use SL::Helper::DateTime;
 use List::MoreUtils qw(uniq);
+use SL::MoreCommon qw(listify);
 use Data::Dumper;
 
 my %filters = (
@@ -33,10 +34,15 @@ sub parse_filter {
   my ($filter, %params) = @_;
 
   my $hint_objects = $params{with_objects} || [];
+  my $auto_objects = [];
 
-  my ($flattened, $objects) = _pre_parse($filter, $hint_objects, '', %params);
+  my ($flattened, $objects) = flatten($filter, $auto_objects, '', %params);
 
-  my $query = _parse_filter($flattened, %params);
+  if ($params{class}) {
+    $objects = $hint_objects;
+  }
+
+  my $query = _parse_filter($flattened, $objects, %params);
 
   _launder_keys($filter, $params{launder_to}) unless $params{no_launder};
 
@@ -55,7 +61,7 @@ sub _launder_keys {
     if ('' eq ref $filter->{$orig}) {
       $launder_to->{$key} = $filter->{$orig};
     } elsif ('ARRAY' eq ref $filter->{$orig}) {
-      $launder_to->{$key} = [ @{ $filter->{$orig} } ];
+      $launder_to->{"${key}_"} = { map { $_ => 1 } @{ $filter->{$orig} } };
     } else {
       $launder_to->{$key} ||= { };
       _launder_keys($filter->{$key}, $launder_to->{$key});
@@ -63,7 +69,7 @@ sub _launder_keys {
   };
 }
 
-sub _pre_parse {
+sub flatten {
   my ($filter, $with_objects, $prefix, %params) = @_;
 
   return (undef, $with_objects) unless 'HASH'  eq ref $filter;
@@ -74,7 +80,7 @@ sub _pre_parse {
   while (my ($key, $value) = each %$filter) {
     next if !defined $value || $value eq ''; # 0 is fine
     if ('HASH' eq ref $value) {
-      my ($query, $more_objects) = _pre_parse($value, $with_objects, _prefix($prefix, $key));
+      my ($query, $more_objects) = flatten($value, $with_objects, _prefix($prefix, $key));
       push @result,        @$query if $query;
       push @$with_objects, _prefix($prefix, $key), ($more_objects ? @$more_objects : ());
     } else {
@@ -86,25 +92,97 @@ sub _pre_parse {
 }
 
 sub _parse_filter {
-  my ($flattened, %params) = @_;
+  my ($flattened, $with_objects, %params) = @_;
 
   return () unless 'ARRAY' eq ref $flattened;
 
-  my %sorted = ( @$flattened );
+  $flattened = _collapse_indirect_filters($flattened);
 
-  my @keys = sort { length($b) <=> length($a) } keys %sorted;
-  for my $key (@keys) {
-    next unless $key =~ /^(.*\b)::$/;
-    $sorted{$1 . '::' . delete $sorted{$key} } = delete $sorted{$1} if $sorted{$1} && $sorted{$key};
-  }
+  my @result;
+  for (my $i = 0; $i < scalar @$flattened; $i += 2) {
+    my ($key, $value) = ($flattened->[$i], $flattened->[$i+1]);
 
-  my %result;
-  while (my ($key, $value) = each %sorted) {
     ($key, $value) = _apply_all($key, $value, qr/\b:(\w+)/,  { %filters, %{ $params{filters} || {} } });
     ($key, $value) = _apply_all($key, $value, qr/\b::(\w+)/, { %methods, %{ $params{methods} || {} } });
-    $result{$key} = $value;
+    ($key, $value) = _dispatch_custom_filters($params{class}, $with_objects, $key, $value) if $params{class};
+
+    push @result, $key, $value;
+  }
+  return \@result;
+}
+
+sub _dispatch_custom_filters {
+  my ($class, $with_objects, $key, $value) = @_;
+
+  # the key should by now have no filters left
+  # if it has, catch it here:
+  die 'unrecognized filters' if $key =~ /:/;
+
+  my @tokens     = split /\./, $key;
+  my $last_token = pop @tokens;
+  my $curr_class = $class->object_class;
+
+  for my $token (@tokens) {
+    eval {
+      $curr_class = $curr_class->meta->relationship($token)->class;
+      1;
+    } or do {
+      require Carp;
+      Carp::croak("Could not resolve the relationship '$token' in '$key' while building the filter request");
+    }
+  }
+
+  my $manager    = $curr_class->meta->convention_manager->auto_manager_class_name;
+  my $obj_path   = join '.', @tokens;
+  my $obj_prefix = join '.', @tokens, '';
+
+  if ($manager->can('filter')) {
+    ($key, $value, my $obj) = $manager->filter($last_token, $value, $obj_prefix);
+    _add_uniq($with_objects, $obj);
+  } else {
+    _add_uniq($with_objects, $obj_path);
+  }
+
+  return ($key, $value);
+}
+
+sub _add_uniq {
+   my ($array, $what) = @_;
+
+   $array //= [];
+   $array = [ uniq @$array, listify($what) ];
+}
+
+sub _collapse_indirect_filters {
+  my ($flattened) = @_;
+
+  die 'flattened filter array length is uneven, should be possible to use as hash' if @$flattened % 2;
+
+  my (%keys_to_delete, %keys_to_move, @collapsed);
+
+  # search keys matching /::$/;
+  for (my $i = 0; $i < scalar @$flattened; $i += 2) {
+    my ($key, $value) = ($flattened->[$i], $flattened->[$i+1]);
+
+    next unless $key =~ /^(.*\b)::$/;
+
+    $keys_to_delete{$key}++;
+    $keys_to_move{$1} = $1 . '::' . $value;
   }
-  return [ %result ];
+
+  for (my $i = 0; $i < scalar @$flattened; $i += 2) {
+    my ($key, $value) = ($flattened->[$i], $flattened->[$i+1]);
+
+    if ($keys_to_move{$key}) {
+      push @collapsed, $keys_to_move{$key}, $value;
+      next;
+    }
+    if (!$keys_to_delete{$key}) {
+      push @collapsed, $key, $value;
+    }
+  }
+
+  return \@collapsed;
 }
 
 sub _prefix {
@@ -200,19 +278,6 @@ As a rule all value filters require a single colon and must be placed before
 match method suffixes, which are appended with 2 colons. See below for a full
 list of modifiers.
 
-The reason for the method being last is that it is possible to specify the
-method in another input. Suppose you want a date input and a separate
-before/after/equal select, you can use the following:
-
-  [% L.date_tag('filter.appointed_date:date', ... ) %]
-
-and later
-
-  [% L.select_tag('filter.appointed_date::', ... ) %]
-
-The special empty method will be used to set the method for the previous
-method-less input.
-
 =back
 
 =head1 LAUNDERING
@@ -223,15 +288,21 @@ default laundered into underscores, so you can use them like this:
 
   [% L.input_tag('filter.price:number::lt', filter.price_number__lt) %]
 
-All of your original entries will stay intactg. If you don't want this to
+Also Template has trouble when looking up the contents of arrays, so
+these will get copied into a _ suffixed version as hashes:
+
+  [% L.checkbox_tag('filter.ids[]', value=15, checked=filter.ids_.15) %]
+
+All of your original entries will stay intact. If you don't want this to
 happen pass C<< no_launder => 1 >> as a parameter.  Additionally you can pass a
 different target for the laundered values with the C<launder_to>  parameter. It
 takes an hashref and will deep copy all values in your filter to the target. So
-if you have a filter that looks liek this:
+if you have a filter that looks like this:
 
   $filter = {
     'price:number::lt' => '2,30',
-    'closed            => '1',
+    closed             => '1',
+    type               => [ 'part', 'assembly' ],
   }
 
 and parse it with
@@ -243,9 +314,46 @@ like this:
 
   $filter = {
     'price_number__lt' => '2,30',
-    'closed            => '1',
+     closed            => '1',
+    'type_'            => { part => 1, assembly => 1 },
+  }
+
+=head1 INDIRECT FILTER METHODS
+
+The reason for the method being last is that it is possible to specify the
+method in another input. Suppose you want a date input and a separate
+before/after/equal select, you can use the following:
+
+  [% L.date_tag('filter.appointed_date:date', ... ) %]
+
+and later
+
+  [% L.select_tag('filter.appointed_date:date::', ... ) %]
+
+The special empty method will be used to set the method for the previous
+method-less input.
+
+=head1 CUSTOM FILTERS FROM OBJECTS
+
+If the L<parse_filter> call contains a parameter C<class>, custom filters will
+be honored. Suppose you have added a custom filter 'all' for parts which
+expands to search both description and partnumber, the following
+
+  $filter = {
+    'part.all:substr::ilike' => 'A1',
   }
 
+will expand to:
+
+  query => [
+    or => [
+      part.description => { ilike => '%A1%' },
+      part.partnumber  => { ilike => '%A1%' },
+    ]
+  ]
+
+For more abuot custom filters, see L<SL::DB::Helper::Filtered>.
+
 =head1 FILTERS (leading with :)
 
 The following filters are built in, and can be used.
@@ -307,7 +415,7 @@ following will not work as you expect:
   L.input_tag('customer.name:substr::ilike', ...)
   L.input_tag('invoice.customer.name:substr::ilike', ...)
 
-This will sarch for orders whoe invoice has the _same_ customer, which matches
+This will sarch for orders whose invoice has the _same_ customer, which matches
 both inputs. This is because tables are aliased by their name and not by their
 position in with_objects.
 
diff --git a/SL/DB/Currency.pm b/SL/DB/Currency.pm
new file mode 100644 (file)
index 0000000..14ba066
--- /dev/null
@@ -0,0 +1,13 @@
+# This file has been auto-generated only because it didn't exist.
+# Feel free to modify it at will; it will not be overwritten automatically.
+
+package SL::DB::Currency;
+
+use strict;
+
+use SL::DB::MetaSetup::Currency;
+
+# Creates get_all, get_all_count, get_all_iterator, delete_all and update_all.
+__PACKAGE__->meta->make_manager_class;
+
+1;
index b71b0f4..4ea7be6 100644 (file)
@@ -9,8 +9,8 @@ __PACKAGE__->meta->make_manager_class;
 
 sub get_default_currency {
   my $self = shift->get;
-  my @currencies = grep { $_ } split(/:/, $self->curr || '');
-  return $currencies[0] || '';
+  return $self->currency->name || '' if $self->currency_id;
+  return '';
 }
 
 sub get {
index bff5089..f951bf9 100644 (file)
@@ -20,6 +20,7 @@ use SL::DB::Chart;
 use SL::DB::Contact;
 use SL::DB::CsvImportProfile;
 use SL::DB::CsvImportProfileSetting;
+use SL::DB::Currency;
 use SL::DB::CustomVariable;
 use SL::DB::CustomVariableConfig;
 use SL::DB::CustomVariableValidity;
diff --git a/SL/DB/Helper/Filtered.pm b/SL/DB/Helper/Filtered.pm
new file mode 100644 (file)
index 0000000..15e0985
--- /dev/null
@@ -0,0 +1,186 @@
+package SL::DB::Helper::Filtered;
+
+use strict;
+use SL::Controller::Helper::ParseFilter ();
+
+require Exporter;
+our @ISA    = qw(Exporter);
+our @EXPORT = qw (filter add_filter_specs);
+
+my %filter_spec;
+
+sub filter {
+  my ($class, $key, $value, $prefix) = @_;
+
+  my $filters = _get_filters($class);
+
+  return ($key, $value) unless $filters->{$key};
+
+  return $filters->{$key}->($key, $value, $prefix);
+}
+
+sub _get_filters {
+  my ($class) = @_;
+  return $filter_spec{$class} ||= {};
+}
+
+sub add_filter_specs {
+  my $class = shift;
+
+  my $filters = _get_filters($class);
+
+  while (@_ > 1) {
+    my $key          = shift;
+    $filters->{$key} = shift;
+  }
+}
+
+1;
+
+__END__
+
+=encoding utf-8
+
+=head1 NAME
+
+SL::Helper::Sorted - Manager mixin for filtered results.
+
+=head1 SYNOPSIS
+
+In the manager:
+
+  use SL::Helper::Filtered;
+
+  __PACKAGE__->add_filter_specs(
+    custom_filter_name => sub {
+      my ($key, $value, $prefix) = @_;
+      # code to handle this
+      return ($key, $value, $with_objects);
+    },
+    another_filter_name => \&_sub_to_handle_this,
+  );
+
+In consuming code:
+
+  ($key, $value, $with_objects) = $manager_class->filter($key, $value, $prefix);
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item C<add_filter_specs %PARAMS>
+
+Adds new filters to this package as key value pairs. The key will be the new
+filters name, the value is expected to be a coderef to an implementation of
+this filter. See L<INTERFACE OF A CUSTOM FILTER> for details on this.
+
+You can add multiple filters in one call, but only one filter per key.
+
+=item C<filter $key, $value, $prefix>
+
+Tells the manager to pply custom filters. If none is registered for C<$key>,
+returns C<$key, $value>.
+
+Otherwise the filter code is called.
+
+=back
+
+=head1 INTERFACE OF A CUSTOM FILTER
+
+Lets look at an example of a working filter. Suppose your model has a lot of
+notes fields, and you need to search in all of them. A working filter would be:
+
+  __PACKAGE__->add_filter_specs(
+    all_notes => sub {
+      my ($key, $value, $prefix) = @_;
+
+      return or => [
+        $prefix . notes1 => $value,
+        $prefix . notes2 => $value,
+      ];
+    }
+  );
+
+If someone filters for C<filter.model.all_notes:substr::ilike=telephone>, your
+filter will get called with:
+
+  ->filter('all_notes', { ilike => '%telephone%' }, '')
+
+and the result will be:
+
+  or => [
+    notes1 => { notes1 => '%telephone%' },
+    notes2 => { notes1 => '%telephone%' },
+  ]
+
+The prefix is to make sure this also works when called on submodels:
+
+  C<filter.customer.model.all_notes:substr::ilike=telephone>
+
+will pass C<customer.> as prefix so that the resulting query will be:
+
+  or => [
+    customer.notes1 => { notes1 => '%telephone%' },
+    customer.notes2 => { notes1 => '%telephone%' },
+  ]
+
+which is pretty much what you would expect.
+
+As a final touch consider a filter that needs to search somewhere else to work,
+like this one:
+
+  __PACKAGE__->add_filter_specs(
+    name => sub {
+      my ($key, $value, $prefix) = @_;
+
+      return $prefix . person.name => $value,
+             $prefix . 'person';
+    },
+  };
+
+Now you can search for C<name> in your model without ever knowing that the real
+name lies in the table C<person>. Unfortunately Rose has to know about it to
+get the joins right, and so you need to tell it to include C<person> into its
+C<with_objects>. That's the reason for the third return value.
+
+
+To summarize:
+
+=over 4
+
+=item *
+
+You will get passed the name of your filter as C<$key> stripped of all filters
+and escapes.
+
+=item *
+
+You will get passed the C<$value> processed with all filters and escapes.
+
+=item *
+
+You will get passed a C<$prefix> that can be prepended to all database columns
+to make sense to Rose.
+
+=item *
+
+You are expeceted to return exactly one key and one value. That can mean you
+have to encapsulate your arguments into C<< or => [] >> or C<< and => [] >> blocks.
+
+=item *
+
+If your filter needs relationships that are not always loaded, you need to
+return them in C<with_objects> style. If you need to return more than one, use
+an arrayref.
+
+=back
+
+=head1 BUGS
+
+None yet.
+
+=head1 AUTHOR
+
+Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>
+
+=cut
index c21df97..9754d2b 100644 (file)
@@ -14,11 +14,11 @@ sub flatten_to_form {
 
   my $vc = $self->can('customer_id') && $self->customer_id ? 'customer' : 'vendor';
 
-  _copy($self, $form, '', '', 0, qw(id type taxzone_id ordnumber quonumber invnumber donumber cusordnumber taxincluded shippingpoint shipvia notes intnotes curr cp_id
+  _copy($self, $form, '', '', 0, qw(id type taxzone_id ordnumber quonumber invnumber donumber cusordnumber taxincluded shippingpoint shipvia notes intnotes cp_id
                                     employee_id salesman_id closed department_id language_id payment_id delivery_customer_id delivery_vendor_id shipto_id proforma
                                     globalproject_id delivered transaction_description container_type accepted_by_customer invoice terms storno storno_id dunning_config_id
                                     orddate quodate reqdate gldate duedate deliverydate datepaid transdate));
-  $form->{currency} = $form->{curr}; # curr is called currency in almost all forms
+  $form->{currency} = $form->{curr} = $self->currency_id ? $self->currency->name || '' : '';
 
   if (_has($self, 'transdate')) {
     my $transdate_idx = ref($self) eq 'SL::DB::Order'   ? ($self->quotation ? 'quodate' : 'orddate')
index cc4d2ab..f0b8be9 100644 (file)
@@ -55,6 +55,7 @@ my %lxoffice_package_names = (
   csv_import_reports             => 'csv_import_report',
   csv_import_report_rows         => 'csv_import_report_row',
   csv_import_report_status       => 'csv_import_report_status',
+  currencies                     => 'currency',
   custom_variable_configs        => 'custom_variable_config',
   custom_variables               => 'custom_variable',
   custom_variables_validity      => 'custom_variable_validity',
index 0801299..73613f8 100644 (file)
@@ -53,8 +53,9 @@ sub calculate_prices_and_taxes {
 sub _get_exchangerate {
   my ($self, $data, %params) = @_;
 
-  if (($self->curr || '') ne SL::DB::Default->get_default_currency) {
-    $data->{exchangerate}   = $::form->check_exchangerate(\%::myconfig, $self->curr, $self->transdate, $data->{is_sales} ? 'buy' : 'sell');
+  my $currency = $self->currency_id ? $self->currency->name || '' : '';
+  if ($currency ne SL::DB::Default->get_default_currency) {
+    $data->{exchangerate}   = $::form->check_exchangerate(\%::myconfig, $currency, $self->transdate, $data->{is_sales} ? 'buy' : 'sell');
     $data->{exchangerate} ||= $params{exchangerate};
   }
   $data->{exchangerate} ||= 1;
index 09c7f86..d93de71 100644 (file)
@@ -98,9 +98,9 @@ sub new_from {
 
   my $terms = $source->can('payment_id') && $source->payment_id ? $source->payment_terms->terms_netto : 0;
 
-  my %args = ( map({ ( $_ => $source->$_ ) } qw(customer_id taxincluded shippingpoint shipvia notes intnotes curr salesman_id cusordnumber ordnumber quonumber
+  my %args = ( map({ ( $_ => $source->$_ ) } qw(customer_id taxincluded shippingpoint shipvia notes intnotes salesman_id cusordnumber ordnumber quonumber
                                                 department_id cp_id language_id payment_id delivery_customer_id delivery_vendor_id taxzone_id shipto_id
-                                                globalproject_id transaction_description)),
+                                                globalproject_id transaction_description currency_id)),
                transdate   => DateTime->today_local,
                gldate      => DateTime->today_local,
                duedate     => DateTime->today_local->add(days => $terms * 1),
index e908b6b..e964f4f 100644 (file)
@@ -5,12 +5,27 @@ use strict;
 use SL::DB::Helper::Manager;
 use base qw(SL::DB::Helper::Manager);
 
+use SL::DB::Helper::Filtered;
 use SL::DB::Helper::Paginated;
 use SL::DB::Helper::Sorted;
 
 sub object_class { 'SL::DB::OrderItem' }
 
 __PACKAGE__->make_manager_methods;
+__PACKAGE__->add_filter_specs(
+  reqdate => sub {
+    my ($key, $value, $prefix) = @_;
+
+    return or => [
+      $prefix . reqdate => $value,
+      and => [
+        $prefix . reqdate => undef,
+        $prefix . 'order.reqdate' => $value,
+      ]
+    ], $prefix . 'order';
+  },
+);
+
 
 sub _sort_spec {
   return ( columns => { delivery_date => [ 'deliverydate',        ],
index 6c781a9..e238346 100644 (file)
@@ -3,6 +3,9 @@ package SL::DB::Manager::Part;
 use strict;
 
 use SL::DB::Helper::Manager;
+use SL::DB::Helper::Sorted;
+use SL::DB::Helper::Paginated;
+use SL::DB::Helper::Filtered;
 use base qw(SL::DB::Helper::Manager);
 
 use Carp;
@@ -12,31 +15,49 @@ use SL::MoreCommon qw(listify);
 sub object_class { 'SL::DB::Part' }
 
 __PACKAGE__->make_manager_methods;
+__PACKAGE__->add_filter_specs(
+  type => sub {
+    my ($key, $value, $prefix) = @_;
+    return __PACKAGE__->type_filter($value, $prefix);
+  },
+  all => sub {
+    my ($key, $value, $prefix) = @_;
+    return or => [ map { $prefix . $_ => $value } qw(partnumber description) ]
+  }
+);
 
 sub type_filter {
-  my ($class, $type) = @_;
+  my ($class, $type, $prefix) = @_;
 
   return () unless $type;
 
+  $prefix //= '';
+
+  # this is to make selection like type => { part => 1, service => 1 } work
+  if ('HASH' eq ref $type) {
+    $type = grep { $type->{$_} } keys %$type;
+  }
+
   my @types = listify($type);
   my @filter;
 
   for my $type (@types) {
     if ($type =~ m/^part/) {
-      push @filter, (and => [ or                    => [ assembly => 0, assembly => undef ],
-                       '!inventory_accno_id' => 0,
-                       '!inventory_accno_id' => undef,
+      push @filter, (and => [ or                    => [ $prefix . assembly => 0, $prefix . assembly => undef ],
+                       "!${prefix}inventory_accno_id" => 0,
+                       "!${prefix}inventory_accno_id" => undef,
                      ]);
     } elsif ($type =~ m/^service/) {
-      push @filter, (and => [ or => [ assembly           => 0, assembly           => undef ],
-                       or => [ inventory_accno_id => 0, inventory_accno_id => undef ],
+      push @filter, (and => [ or => [ $prefix . assembly           => 0, $prefix . assembly           => undef ],
+                       or => [ $prefix . inventory_accno_id => 0, $prefix . inventory_accno_id => undef ],
                      ]);
     } elsif ($type =~ m/^assembl/) {
-      push @filter, (assembly => 1);
+      push @filter, ($prefix . assembly => 1);
     }
   }
 
-  return @filter ? (or => \@filter) : ();
+  return @filter > 2 ? (or => \@filter) :
+         @filter     ? @filter          : ();
 }
 
 sub get_ordered_qty {
diff --git a/SL/DB/MetaSetup/Currency.pm b/SL/DB/MetaSetup/Currency.pm
new file mode 100644 (file)
index 0000000..705467f
--- /dev/null
@@ -0,0 +1,23 @@
+# This file has been auto-generated. Do not modify it; it will be overwritten
+# by rose_auto_create_model.pl automatically.
+package SL::DB::Currency;
+
+use strict;
+
+use base qw(SL::DB::Object);
+
+__PACKAGE__->meta->setup(
+  table   => 'currencies',
+
+  columns => [
+    id   => { type => 'serial', not_null => 1 },
+    name => { type => 'text', not_null => 1 },
+  ],
+
+  primary_key_columns => [ 'id' ],
+
+  unique_key => [ 'name' ],
+);
+
+1;
+;
index 82d808c..0bb7cd9 100644 (file)
@@ -53,8 +53,8 @@ __PACKAGE__->meta->setup(
     iban                => { type => 'varchar', length => 100 },
     bic                 => { type => 'varchar', length => 100 },
     direct_debit        => { type => 'boolean', default => 'false' },
-    curr                => { type => 'text' },
     taxincluded_checked => { type => 'boolean' },
+    currency_id         => { type => 'integer', not_null => 1 },
   ],
 
   primary_key_columns => [ 'id' ],
@@ -67,6 +67,11 @@ __PACKAGE__->meta->setup(
       key_columns => { business_id => 'id' },
     },
 
+    currency => {
+      class       => 'SL::DB::Currency',
+      key_columns => { currency_id => 'id' },
+    },
+
     language_obj => {
       class       => 'SL::DB::Language',
       key_columns => { language_id => 'id' },
index d45a7cf..6c1e5ec 100644 (file)
@@ -20,7 +20,6 @@ __PACKAGE__->meta->setup(
     weightunit                          => { type => 'varchar', length => 5 },
     businessnumber                      => { type => 'text' },
     version                             => { type => 'varchar', length => 8 },
-    curr                                => { type => 'text' },
     closedto                            => { type => 'date' },
     revtrans                            => { type => 'boolean', default => 'false' },
     ponumber                            => { type => 'text' },
@@ -68,10 +67,31 @@ __PACKAGE__->meta->setup(
     ar_show_mark_as_paid                => { type => 'boolean', default => 'true' },
     ap_show_mark_as_paid                => { type => 'boolean', default => 'true' },
     assemblynumber                      => { type => 'text' },
+    warehouse_id                        => { type => 'integer' },
+    bin_id                              => { type => 'integer' },
+    currency_id                         => { type => 'integer', not_null => 1 },
     show_weight                         => { type => 'boolean', default => 'false', not_null => 1 },
   ],
 
   primary_key_columns => [ 'id' ],
+
+  allow_inline_column_values => 1,
+
+  foreign_keys => [
+    bin => {
+      class       => 'SL::DB::Bin',
+      key_columns => { bin_id => 'id' },
+    },
+
+    warehouse => {
+      class       => 'SL::DB::Warehouse',
+      key_columns => { warehouse_id => 'id' },
+    },
+    currency => {
+      class       => 'SL::DB::Currency',
+      key_columns => { currency_id => 'id' },
+    },
+  ],
 );
 
 1;
index 5233121..86cdc64 100644 (file)
@@ -39,7 +39,7 @@ __PACKAGE__->meta->setup(
     taxzone_id              => { type => 'integer' },
     taxincluded             => { type => 'boolean' },
     terms                   => { type => 'integer' },
-    curr                    => { type => 'text' },
+    currency_id             => { type => 'integer', not_null => 1 },
   ],
 
   primary_key_columns => [ 'id' ],
@@ -52,6 +52,11 @@ __PACKAGE__->meta->setup(
       key_columns => { cp_id => 'cp_id' },
     },
 
+    currency => {
+      class       => 'SL::DB::Currency',
+      key_columns => { currency_id => 'id' },
+    },
+
     customer => {
       class       => 'SL::DB::Customer',
       key_columns => { customer_id => 'id' },
index 44bce84..ddd0943 100644 (file)
@@ -10,18 +10,25 @@ __PACKAGE__->meta->setup(
   table   => 'exchangerate',
 
   columns => [
-    curr      => { type => 'text' },
-    transdate => { type => 'date' },
-    buy       => { type => 'numeric', precision => 5, scale => 15 },
-    sell      => { type => 'numeric', precision => 5, scale => 15 },
-    itime     => { type => 'timestamp', default => 'now()' },
-    mtime     => { type => 'timestamp' },
-    id        => { type => 'serial', not_null => 1 },
+    transdate   => { type => 'date' },
+    buy         => { type => 'numeric', precision => 5, scale => 15 },
+    sell        => { type => 'numeric', precision => 5, scale => 15 },
+    itime       => { type => 'timestamp', default => 'now()' },
+    mtime       => { type => 'timestamp' },
+    id          => { type => 'serial', not_null => 1 },
+    currency_id => { type => 'integer', not_null => 1 },
   ],
 
   primary_key_columns => [ 'id' ],
 
   allow_inline_column_values => 1,
+
+  foreign_keys => [
+    currency => {
+      class       => 'SL::DB::Currency',
+      key_columns => { currency_id => 'id' },
+    },
+  ],
 );
 
 1;
index b01b141..2a0e50e 100644 (file)
@@ -26,7 +26,6 @@ __PACKAGE__->meta->setup(
     shippingpoint             => { type => 'text' },
     terms                     => { type => 'integer', default => '0' },
     notes                     => { type => 'text' },
-    curr                      => { type => 'text' },
     ordnumber                 => { type => 'text' },
     employee_id               => { type => 'integer' },
     quonumber                 => { type => 'text' },
@@ -57,6 +56,7 @@ __PACKAGE__->meta->setup(
     donumber                  => { type => 'text' },
     invnumber_for_credit_note => { type => 'text' },
     direct_debit              => { type => 'boolean', default => 'false' },
+    currency_id               => { type => 'integer', not_null => 1 },
   ],
 
   primary_key_columns => [ 'id' ],
@@ -69,6 +69,11 @@ __PACKAGE__->meta->setup(
       key_columns => { cp_id => 'cp_id' },
     },
 
+    currency => {
+      class       => 'SL::DB::Currency',
+      key_columns => { currency_id => 'id' },
+    },
+
     customer => {
       class       => 'SL::DB::Customer',
       key_columns => { customer_id => 'id' },
index f68c276..063ca0b 100644 (file)
@@ -21,7 +21,6 @@ __PACKAGE__->meta->setup(
     taxincluded             => { type => 'boolean' },
     shippingpoint           => { type => 'text' },
     notes                   => { type => 'text' },
-    curr                    => { type => 'character', length => 3 },
     employee_id             => { type => 'integer' },
     closed                  => { type => 'boolean', default => 'false' },
     quotation               => { type => 'boolean', default => 'false' },
@@ -43,9 +42,10 @@ __PACKAGE__->meta->setup(
     delivered               => { type => 'boolean', default => 'false' },
     globalproject_id        => { type => 'integer' },
     salesman_id             => { type => 'integer' },
-    transaction_description => { type => 'text' },
     marge_total             => { type => 'numeric', precision => 5, scale => 15 },
     marge_percent           => { type => 'numeric', precision => 5, scale => 15 },
+    transaction_description => { type => 'text' },
+    currency_id             => { type => 'integer', not_null => 1 },
   ],
 
   primary_key_columns => [ 'id' ],
@@ -58,6 +58,11 @@ __PACKAGE__->meta->setup(
       key_columns => { cp_id => 'cp_id' },
     },
 
+    currency => {
+      class       => 'SL::DB::Currency',
+      key_columns => { currency_id => 'id' },
+    },
+
     customer => {
       class       => 'SL::DB::Customer',
       key_columns => { customer_id => 'id' },
index c94c66c..bdc726e 100644 (file)
@@ -26,7 +26,6 @@ __PACKAGE__->meta->setup(
     inventory_accno_id => { type => 'integer' },
     income_accno_id    => { type => 'integer' },
     expense_accno_id   => { type => 'integer' },
-    bin                => { type => 'text' },
     shop               => { type => 'boolean', default => 'false' },
     obsolete           => { type => 'boolean', default => 'false' },
     bom                => { type => 'boolean', default => 'false' },
@@ -48,13 +47,21 @@ __PACKAGE__->meta->setup(
     onhand             => { type => 'numeric', default => '0', precision => 5, scale => 25 },
     stockable          => { type => 'boolean', default => 'false' },
     has_sernumber      => { type => 'boolean', default => 'false' },
+    warehouse_id       => { type => 'integer' },
+    bin_id             => { type => 'integer' },
   ],
 
   primary_key_columns => [ 'id' ],
 
   allow_inline_column_values => 1,
+  unique_key => [ 'partnumber' ],
 
   foreign_keys => [
+    bin => {
+      class       => 'SL::DB::Bin',
+      key_columns => { bin_id => 'id' },
+    },
+
     buchungsgruppen => {
       class       => 'SL::DB::Buchungsgruppe',
       key_columns => { buchungsgruppen_id => 'id' },
@@ -79,6 +86,11 @@ __PACKAGE__->meta->setup(
       class       => 'SL::DB::Unit',
       key_columns => { unit => 'name' },
     },
+
+    warehouse => {
+      class       => 'SL::DB::Warehouse',
+      key_columns => { warehouse_id => 'id' },
+    },
   ],
 );
 
index bee7eb2..e484fb9 100644 (file)
@@ -23,7 +23,6 @@ __PACKAGE__->meta->setup(
     duedate                 => { type => 'date' },
     invoice                 => { type => 'boolean', default => 'false' },
     ordnumber               => { type => 'text' },
-    curr                    => { type => 'text' },
     notes                   => { type => 'text' },
     employee_id             => { type => 'integer' },
     quonumber               => { type => 'text' },
@@ -44,6 +43,8 @@ __PACKAGE__->meta->setup(
     transaction_description => { type => 'text' },
     storno_id               => { type => 'integer' },
     direct_debit            => { type => 'boolean', default => 'false' },
+    deliverydate            => { type => 'date' },
+    currency_id             => { type => 'integer', not_null => 1 },
   ],
 
   primary_key_columns => [ 'id' ],
@@ -56,6 +57,11 @@ __PACKAGE__->meta->setup(
       key_columns => { cp_id => 'cp_id' },
     },
 
+    currency => {
+      class       => 'SL::DB::Currency',
+      key_columns => { currency_id => 'id' },
+    },
+
     department => {
       class       => 'SL::DB::Department',
       key_columns => { department_id => 'id' },
index d8854fa..14fb4a9 100644 (file)
@@ -52,7 +52,7 @@ __PACKAGE__->meta->setup(
     iban           => { type => 'varchar', length => 100 },
     bic            => { type => 'varchar', length => 100 },
     direct_debit   => { type => 'boolean', default => 'false' },
-    curr           => { type => 'text' },
+    currency_id    => { type => 'integer', not_null => 1 },
   ],
 
   primary_key_columns => [ 'id' ],
@@ -65,6 +65,11 @@ __PACKAGE__->meta->setup(
       key_columns => { business_id => 'id' },
     },
 
+    currency => {
+      class       => 'SL::DB::Currency',
+      key_columns => { currency_id => 'id' },
+    },
+
     language_obj => {
       class       => 'SL::DB::Language',
       key_columns => { language_id => 'id' },
index 44b2dbb..c41f287 100644 (file)
@@ -26,7 +26,7 @@ SQL
   $query = <<SQL;
     SELECT o.amount,
       (SELECT e.buy FROM exchangerate e
-       WHERE e.curr = o.curr
+       WHERE e.currency_id = o.currency_id
          AND e.transdate = o.transdate)
     FROM oe o
     WHERE (o.${type}_id = ?)
index 4e08450..8dc9787 100644 (file)
@@ -20,4 +20,14 @@ sub connect {
   return DBIx::Log4perl->connect(@_);
 }
 
+sub get_options {
+  my $self    = shift;
+  my $options = {
+    pg_enable_utf8 => $::locale->is_utf8,
+    @_
+  };
+
+  return $options;
+}
+
 1;
index 46749ab..8eb407b 100644 (file)
--- a/SL/DN.pm
+++ b/SL/DN.pm
@@ -208,7 +208,7 @@ sub create_invoice_for_fees {
   $query =
     qq|INSERT INTO ar (id,          invnumber, transdate, gldate, customer_id,
                        taxincluded, amount,    netamount, paid,   duedate,
-                       invoice,     curr,      notes,
+                       invoice,     currency_id,      notes,
                        employee_id)
        VALUES (
          ?,                     -- id
@@ -228,7 +228,7 @@ sub create_invoice_for_fees {
          -- duedate:
          (SELECT duedate FROM dunning WHERE dunning_id = ? LIMIT 1),
          'f',                   -- invoice
-         ?,                     -- curr
+         (SELECT id FROM currencies WHERE name = ?), -- curr
          ?,                     -- notes
          -- employee_id:
          (SELECT id FROM employee WHERE login = ?)
@@ -761,7 +761,7 @@ sub print_dunning {
          ar.transdate,       ar.duedate,      ar.customer_id,
          ar.invnumber,       ar.ordnumber,    ar.cp_id,
          ar.amount,          ar.netamount,    ar.paid,
-         ar.curr,
+         (SELECT cu.name FROM currencies cu WHERE cu.id=ar.currency_id) AS curr,
          ar.amount - ar.paid AS open_amount,
          ar.amount - ar.paid + da.fee + da.interest AS linetotal
 
index e47b5d1..a4677bb 100644 (file)
--- a/SL/DO.pm
+++ b/SL/DO.pm
@@ -229,7 +229,7 @@ sub save {
     $query = qq|SELECT nextval('id')|;
     ($form->{id}) = selectrow_query($form, $dbh, $query);
 
-    $query = qq|INSERT INTO delivery_orders (id, donumber, employee_id) VALUES (?, '', ?)|;
+    $query = qq|INSERT INTO delivery_orders (id, donumber, employee_id, currency_id) VALUES (?, '', ?, (SELECT currency_id FROM defaults LIMIT 1))|;
     do_query($form, $dbh, $query, $form->{id}, conv_i($form->{employee_id}));
   }
 
@@ -350,7 +350,7 @@ sub save {
          shippingpoint = ?, shipvia = ?, notes = ?, intnotes = ?, closed = ?,
          delivered = ?, department_id = ?, language_id = ?, shipto_id = ?,
          globalproject_id = ?, employee_id = ?, salesman_id = ?, cp_id = ?, transaction_description = ?,
-         is_sales = ?, taxzone_id = ?, taxincluded = ?, terms = ?, curr = ?
+         is_sales = ?, taxzone_id = ?, taxincluded = ?, terms = ?, currency_id = (SELECT id FROM currencies WHERE name = ?)
        WHERE id = ?|;
 
   @values = ($form->{donumber}, $form->{ordnumber},
@@ -364,7 +364,7 @@ sub save {
              conv_i($form->{salesman_id}), conv_i($form->{cp_id}),
              $form->{transaction_description},
              $form->{type} =~ /^sales/ ? 't' : 'f',
-             conv_i($form->{taxzone_id}), $form->{taxincluded} ? 't' : 'f', conv_i($form->{terms}), substr($form->{currency}, 0, 3),
+             conv_i($form->{taxzone_id}), $form->{taxincluded} ? 't' : 'f', conv_i($form->{terms}), $form->{currency},
              conv_i($form->{id}));
   do_query($form, $dbh, $query, @values);
 
@@ -618,7 +618,7 @@ sub retrieve {
          d.description AS department, dord.language_id,
          dord.shipto_id,
          dord.globalproject_id, dord.delivered, dord.transaction_description,
-         dord.taxzone_id, dord.taxincluded, dord.terms, dord.curr AS currency
+         dord.taxzone_id, dord.taxincluded, dord.terms, (SELECT cu.name FROM currencies cu WHERE cu.id=dord.currency_id) AS currency
        FROM delivery_orders dord
        JOIN ${vc} cv ON (dord.${vc}_id = cv.id)
        LEFT JOIN employee e ON (dord.employee_id = e.id)
@@ -640,9 +640,6 @@ sub retrieve {
   }
   $sth->finish();
 
-  # remove any trailing whitespace
-  $form->{currency} =~ s/\s*$//;
-
   $form->{donumber_array} =~ s/\s*$//g;
 
   $form->{saved_donumber} = $form->{donumber};
@@ -684,7 +681,7 @@ sub retrieve {
   $query =
     qq|SELECT doi.id AS delivery_order_items_id,
          p.partnumber, p.assembly, p.listprice, doi.description, doi.qty,
-         doi.sellprice, doi.parts_id AS id, doi.unit, doi.discount, p.bin, p.notes AS partnotes,
+         doi.sellprice, doi.parts_id AS id, doi.unit, doi.discount, p.notes AS partnotes,
          doi.reqdate, doi.project_id, doi.serialnumber, doi.lastcost,
          doi.ordnumber, doi.transdate, doi.cusordnumber, doi.longdescription,
          doi.price_factor_id, doi.price_factor, doi.marge_price_factor, doi.pricegroup_id,
index 7b5d257..5a2b38d 100644 (file)
@@ -138,9 +138,15 @@ sub _flatten_variables_rec {
     foreach my $idx (0 .. scalar @{ $curr->{$key} } - 1) {
       my $first_array_entry = 1;
 
-      foreach my $hash_key (sort keys %{ $curr->{$key}->[$idx] }) {
-        push @result, $self->_flatten_variables_rec($curr->{$key}->[$idx], $prefix . $key . ($first_array_entry ? '[+].' : '[].'), $hash_key);
-        $first_array_entry = 0;
+      my $element = $curr->{$key}[$idx];
+
+      if ('HASH' eq ref $element) {
+        foreach my $hash_key (sort keys %{ $element }) {
+          push @result, $self->_flatten_variables_rec($element, $prefix . $key . ($first_array_entry ? '[+].' : '[].'), $hash_key);
+          $first_array_entry = 0;
+        }
+      } else {
+        @result = ({ 'key' => $prefix . $key . ($first_array_entry ? '[+]' : '[]'), 'value' => $element });
       }
     }
   }
@@ -1361,21 +1367,13 @@ sub datetonum {
 
 # Database routines used throughout
 
-sub _dbconnect_options {
-  my $self    = shift;
-  my $options = { pg_enable_utf8 => $::locale->is_utf8,
-                  @_ };
-
-  return $options;
-}
-
 sub dbconnect {
   $main::lxdebug->enter_sub(2);
 
   my ($self, $myconfig) = @_;
 
   # connect to database
-  my $dbh = SL::DBConnect->connect($myconfig->{dbconnect}, $myconfig->{dbuser}, $myconfig->{dbpasswd}, $self->_dbconnect_options)
+  my $dbh = SL::DBConnect->connect($myconfig->{dbconnect}, $myconfig->{dbuser}, $myconfig->{dbpasswd}, SL::DBConnect->get_options)
     or $self->dberror;
 
   # set db options
@@ -1394,7 +1392,7 @@ sub dbconnect_noauto {
   my ($self, $myconfig) = @_;
 
   # connect to database
-  my $dbh = SL::DBConnect->connect($myconfig->{dbconnect}, $myconfig->{dbuser}, $myconfig->{dbpasswd}, $self->_dbconnect_options(AutoCommit => 0))
+  my $dbh = SL::DBConnect->connect($myconfig->{dbconnect}, $myconfig->{dbuser}, $myconfig->{dbpasswd}, SL::DBConnect->get_options(AutoCommit => 0))
     or $self->dberror;
 
   # set db options
@@ -1490,19 +1488,17 @@ sub update_exchangerate {
     $main::lxdebug->leave_sub();
     return;
   }
-  $query = qq|SELECT curr FROM defaults|;
-
-  my ($currency) = selectrow_query($self, $dbh, $query);
-  my ($defaultcurrency) = split m/:/, $currency;
+  $query = qq|SELECT name AS curr FROM currencies WHERE id=(SELECT currency_id FROM defaults)|;
 
+  my ($defaultcurrency) = selectrow_query($self, $dbh, $query);
 
   if ($curr eq $defaultcurrency) {
     $main::lxdebug->leave_sub();
     return;
   }
 
-  $query = qq|SELECT e.curr FROM exchangerate e
-                 WHERE e.curr = ? AND e.transdate = ?
+  $query = qq|SELECT e.currency_id FROM exchangerate e
+                 WHERE e.currency_id = (SELECT cu.id FROM currencies cu WHERE cu.name=?) AND e.transdate = ?
                  FOR UPDATE|;
   my $sth = prepare_execute_query($self, $dbh, $query, $curr, $transdate);
 
@@ -1528,12 +1524,12 @@ sub update_exchangerate {
   if ($sth->fetchrow_array) {
     $query = qq|UPDATE exchangerate
                 SET $set
-                WHERE curr = ?
+                WHERE currency_id = (SELECT id FROM currencies WHERE name = ?)
                 AND transdate = ?|;
 
   } else {
-    $query = qq|INSERT INTO exchangerate (curr, buy, sell, transdate)
-                VALUES (?, $buy, $sell, ?)|;
+    $query = qq|INSERT INTO exchangerate (currency_id, buy, sell, transdate)
+                VALUES ((SELECT id FROM currencies WHERE name = ?), $buy, $sell, ?)|;
   }
   $sth->finish;
   do_query($self, $dbh, $query, $curr, $transdate);
@@ -1573,18 +1569,17 @@ sub get_exchangerate {
     return 1;
   }
 
-  $query = qq|SELECT curr FROM defaults|;
+  $query = qq|SELECT name AS curr FROM currencies WHERE id = (SELECT currency_id FROM defaults)|;
 
-  my ($currency) = selectrow_query($self, $dbh, $query);
-  my ($defaultcurrency) = split m/:/, $currency;
+  my ($defaultcurrency) = selectrow_query($self, $dbh, $query);
 
-  if ($currency eq $defaultcurrency) {
+  if ($curr eq $defaultcurrency) {
     $main::lxdebug->leave_sub();
     return 1;
   }
 
   $query = qq|SELECT e.$fld FROM exchangerate e
-                 WHERE e.curr = ? AND e.transdate = ?|;
+                 WHERE e.currency_id = (SELECT id FROM currencies WHERE name = ?) AND e.transdate = ?|;
   my ($exchangerate) = selectrow_query($self, $dbh, $query, $curr, $transdate);
 
 
@@ -1617,7 +1612,7 @@ sub check_exchangerate {
 
   my $dbh   = $self->get_standard_dbh($myconfig);
   my $query = qq|SELECT e.$fld FROM exchangerate e
-                 WHERE e.curr = ? AND e.transdate = ?|;
+                 WHERE e.currency_id = (SELECT id FROM currencies WHERE name = ?) AND e.transdate = ?|;
 
   my ($exchangerate) = selectrow_query($self, $dbh, $query, $currency, $transdate);
 
@@ -1632,11 +1627,10 @@ sub get_all_currencies {
   my $self     = shift;
   my $myconfig = shift || \%::myconfig;
   my $dbh      = $self->get_standard_dbh($myconfig);
+  my @currencies =();
 
-  my $query = qq|SELECT curr FROM defaults|;
-
-  my ($curr)     = selectrow_query($self, $dbh, $query);
-  my @currencies = grep { $_ } map { s/\s//g; $_ } split m/:/, $curr;
+  my $query = qq|SELECT name FROM currencies|;
+  my @currencies = map { $_->{name} } selectall_hashref_query($self, $dbh, $query);
 
   $main::lxdebug->leave_sub();
 
@@ -1647,11 +1641,14 @@ sub get_default_currency {
   $main::lxdebug->enter_sub();
 
   my ($self, $myconfig) = @_;
-  my @currencies        = $self->get_all_currencies($myconfig);
+  my $dbh      = $self->get_standard_dbh($myconfig);
+  my $query = qq|SELECT name AS curr FROM currencies WHERE id = (SELECT currency_id FROM defaults)|;
+
+  my ($defaultcurrency) = selectrow_query($self, $dbh, $query);
 
   $main::lxdebug->leave_sub();
 
-  return $currencies[0];
+  return $defaultcurrency;
 }
 
 sub set_payment_options {
@@ -2191,9 +2188,7 @@ $main::lxdebug->enter_sub();
 
   $key = "all_currencies" unless ($key);
 
-  my $query = qq|SELECT curr AS currency FROM defaults|;
-
-  $self->{$key} = [split(/\:/ , selectfirst_hashref_query($self, $dbh, $query)->{currency})];
+  $self->{$key} = [$self->get_all_currencies()];
 
   $main::lxdebug->leave_sub();
 }
@@ -2501,7 +2496,7 @@ sub all_vc {
   my $query = qq|SELECT count(*) FROM $table $obsolete|;
   my ($count) = selectrow_query($self, $dbh, $query);
 
-  if ($count < $myconfig->{vclimit}) {
+  if ($count <= $myconfig->{vclimit}) {
     $query = qq|SELECT id, name, salesman_id
                 FROM $table $obsolete
                 ORDER BY name|;
@@ -2704,7 +2699,7 @@ sub create_links {
     $query =
       qq|SELECT
            a.cp_id, a.invnumber, a.transdate, a.${table}_id, a.datepaid,
-           a.duedate, a.ordnumber, a.taxincluded, a.curr AS currency, a.notes,
+           a.duedate, a.ordnumber, a.taxincluded, (SELECT cu.name FROM currencies cu WHERE cu.id=a.currency_id) AS currency, a.notes,
            a.intnotes, a.department_id, a.amount AS oldinvtotal,
            a.paid AS oldtotalpaid, a.employee_id, a.gldate, a.type,
            a.globalproject_id, ${extra_columns}
@@ -2722,9 +2717,6 @@ sub create_links {
       $self->{$key} = $ref->{$key};
     }
 
-    # remove any trailing whitespace
-    $self->{currency} =~ s/\s*$//;
-
     my $transdate = "current_date";
     if ($self->{transdate}) {
       $transdate = $dbh->quote($self->{transdate});
@@ -2808,9 +2800,11 @@ sub create_links {
     }
 
     $sth->finish;
+    #check das:
     $query =
       qq|SELECT
-           d.curr AS currencies, d.closedto, d.revtrans,
+           d.closedto, d.revtrans,
+           (SELECT cu.name FROM currencies cu WHERE cu.id=d.currency_id) AS defaultcurrency,
            (SELECT c.accno FROM chart c WHERE d.fxgain_accno_id = c.id) AS fxgain_accno,
            (SELECT c.accno FROM chart c WHERE d.fxloss_accno_id = c.id) AS fxloss_accno
          FROM defaults d|;
@@ -2822,7 +2816,8 @@ sub create_links {
     # get date
     $query =
        qq|SELECT
-            current_date AS transdate, d.curr AS currencies, d.closedto, d.revtrans,
+            current_date AS transdate, d.closedto, d.revtrans,
+            (SELECT cu.name FROM currencies cu WHERE cu.id=d.currency_id) AS defaultcurrency,
             (SELECT c.accno FROM chart c WHERE d.fxgain_accno_id = c.id) AS fxgain_accno,
             (SELECT c.accno FROM chart c WHERE d.fxloss_accno_id = c.id) AS fxloss_accno
           FROM defaults d|;
@@ -2832,7 +2827,7 @@ sub create_links {
     if ($self->{"$self->{vc}_id"}) {
 
       # only setup currency
-      ($self->{currency}) = split(/:/, $self->{currencies}) if !$self->{currency};
+      ($self->{currency}) = $self->{defaultcurrency} if !$self->{currency};
 
     } else {
 
@@ -2857,19 +2852,17 @@ sub lastname_used {
   my ($arap, $where);
 
   $table         = $table eq "customer" ? "customer" : "vendor";
-  my %column_map = ("a.curr"                  => "currency",
-                    "a.${table}_id"           => "${table}_id",
+  my %column_map = ("a.${table}_id"           => "${table}_id",
                     "a.department_id"         => "department_id",
                     "d.description"           => "department",
                     "ct.name"                 => $table,
-                    "ct.curr"                 => "cv_curr",
+                    "cu.name"                 => "currency",
                     "current_date + ct.terms" => "duedate",
     );
 
   if ($self->{type} =~ /delivery_order/) {
     $arap  = 'delivery_orders';
-    delete $column_map{"a.curr"};
-    delete $column_map{"ct.curr"};
+    delete $column_map{"cu.currency"};
 
   } elsif ($self->{type} =~ /_order/) {
     $arap  = 'oe';
@@ -2898,18 +2891,12 @@ sub lastname_used {
                         FROM $arap a
                         LEFT JOIN $table     ct ON (a.${table}_id = ct.id)
                         LEFT JOIN department d  ON (a.department_id = d.id)
+                        LEFT JOIN currencies cu ON (cu.id=ct.currency_id)
                         WHERE a.id = ?|;
   my $ref          = selectfirst_hashref_query($self, $dbh, $query, $trans_id);
 
   map { $self->{$_} = $ref->{$_} } values %column_map;
 
-  # remove any trailing whitespace
-  $self->{currency} =~ s/\s*$// if $self->{currency};
-  $self->{cv_curr} =~ s/\s*$// if $self->{cv_curr};
-
-  # if customer/vendor currency is set use this
-  $self->{currency} = $self->{cv_curr} if $self->{cv_curr};
-
   $main::lxdebug->leave_sub();
 }
 
index e481614..2d454be 100644 (file)
@@ -3,6 +3,7 @@ package SL::Helper::Csv;
 use strict;
 use warnings;
 
+use version 0.77;
 use Carp;
 use IO::File;
 use Params::Validate qw(:all);
@@ -156,11 +157,19 @@ sub _parse_data {
       push @data, \%hr;
     } else {
       last if $self->_csv->eof;
-      push @errors, [
-        $self->_csv->error_input,
-        $self->_csv->error_diag,
-        $self->_io->input_line_number,
-      ];
+      # Text::CSV_XS 0.89 added record number to error_diag
+      if (qv(Text::CSV_XS->VERSION) >= qv('0.89')) {
+        push @errors, [
+          $self->_csv->error_input,
+          $self->_csv->error_diag,
+        ];
+      } else {
+        push @errors, [
+          $self->_csv->error_input,
+          $self->_csv->error_diag,
+          $self->_io->input_line_number,
+        ];
+      }
     }
     last if $self->_csv->eof;
   }
index 74cfe17..0b8924b 100644 (file)
--- a/SL/IC.pm
+++ b/SL/IC.pm
@@ -346,7 +346,8 @@ sub save {
          notes = ?,
          formel = ?,
          rop = ?,
-         bin = ?,
+         warehouse_id = ?,
+         bin_id = ?,
          buchungsgruppen_id = ?,
          payment_id = ?,
          inventory_accno_id = $subq_inventory,
@@ -378,7 +379,8 @@ sub save {
              $form->{notes},
              $form->{formel},
              $form->{rop},
-             $form->{bin},
+             conv_i($form->{warehouse_id}),
+             conv_i($form->{bin_id}),
              conv_i($form->{buchungsgruppen_id}),
              conv_i($form->{payment_id}),
              conv_i($form->{buchungsgruppen_id}),
@@ -567,7 +569,7 @@ sub retrieve_assemblies {
   # retrieve assembly items
   my $query =
     qq|SELECT p.id, p.partnumber, p.description,
-         p.bin, p.onhand, p.rop,
+              p.onhand, p.rop,
          (SELECT sum(p2.inventory_accno_id)
           FROM parts p2, assembly a
           WHERE (p2.id = a.parts_id) AND (a.id = p.id)) AS inventory
@@ -671,7 +673,7 @@ sub assembly_item {
 #   partnumber ean description partsgroup microfiche drawing
 #
 # column flags:
-#   l_partnumber l_description l_listprice l_sellprice l_lastcost l_priceupdate l_weight l_unit l_bin l_rop l_image l_drawing l_microfiche l_partsgroup
+#   l_partnumber l_description l_listprice l_sellprice l_lastcost l_priceupdate l_weight l_unit l_rop l_image l_drawing l_microfiche l_partsgroup
 #
 # exclusives:
 #   itemstatus  = active | onhand | short | obsolete | orphaned
@@ -721,7 +723,7 @@ sub all_parts {
   my @apoe_filters         = qw(transdate);
   my @like_filters         = (@simple_filters, @invoice_oi_filters);
   my @all_columns          = (@simple_filters, @makemodel_filters, @apoe_filters, @project_filters, qw(serialnumber));
-  my @simple_l_switches    = (@all_columns, qw(notes listprice sellprice lastcost priceupdate weight unit bin rop image));
+  my @simple_l_switches    = (@all_columns, qw(notes listprice sellprice lastcost priceupdate weight unit rop image));
   my @oe_flags             = qw(bought sold onorder ordered rfq quoted);
   my @qsooqr_flags         = qw(invnumber ordnumber quonumber trans_id name module qty);
   my @deliverydate_flags   = qw(deliverydate);
@@ -964,7 +966,7 @@ sub all_parts {
 
   my $token_builder = $make_token_builder->(\%joins_needed);
 
-  my @sort_cols    = (@simple_filters, qw(id bin priceupdate onhand invnumber ordnumber quonumber name serialnumber soldtotal deliverydate));
+  my @sort_cols    = (@simple_filters, qw(id priceupdate onhand invnumber ordnumber quonumber name serialnumber soldtotal deliverydate));
      $form->{sort} = 'id' unless grep { $form->{"l_$_"} } grep { $form->{sort} eq $_ } @sort_cols; # sort by id if unknown or invisible column
   my $sort_order   = ($form->{revers} ? ' DESC' : ' ASC');
   my $order_clause = " ORDER BY " . $token_builder->($form->{sort}) . ($form->{revers} ? ' DESC' : ' ASC');
@@ -1022,7 +1024,7 @@ sub all_parts {
   if ($form->{searchitems} eq 'assembly' && $form->{bom}) {
     $query =
       qq|SELECT p.id, p.partnumber, p.description, a.qty AS onhand,
-           p.unit, p.bin, p.notes,
+           p.unit, p.notes,
            p.sellprice, p.listprice, p.lastcost,
            p.rop, p.weight, p.priceupdate,
            p.image, p.drawing, p.microfiche,
@@ -1496,7 +1498,7 @@ sub retrieve_accounts {
       $transdate = $form->{deliverydate};
     }
   } elsif ($form->{script} eq 'ir.pl') {
-    # when a purchase invoice is opened from the report of purchase invoices 
+    # when a purchase invoice is opened from the report of purchase invoices
     # $form->{type} isn't set, but $form->{script} is, not sure why this is or
     # whether this distinction matters in some other scenario. Otherwise one
     # could probably take out this elsif and add a
index 06f84ab..36f105e 100644 (file)
--- a/SL/IR.pm
+++ b/SL/IR.pm
@@ -57,6 +57,7 @@ sub post_invoice {
   # connect to database, turn off autocommit
   my $dbh = $provided_dbh ? $provided_dbh : $form->dbconnect_noauto($myconfig);
   $form->{defaultcurrency} = $form->get_default_currency($myconfig);
+  my $defaultcurrency = $form->{defaultcurrency};
 
   my $ic_cvar_configs = CVar->get_configs(module => 'IC',
                                           dbh    => $dbh);
@@ -70,18 +71,16 @@ sub post_invoice {
 
   my $all_units = AM->retrieve_units($myconfig, $form);
 
+#markierung
   if (!$payments_only) {
     if ($form->{id}) {
       &reverse_invoice($dbh, $form);
     } else {
       ($form->{id}) = selectrow_query($form, $dbh, qq|SELECT nextval('glid')|);
-      do_query($form, $dbh, qq|INSERT INTO ap (id, invnumber) VALUES (?, '')|, $form->{id});
+      do_query($form, $dbh, qq|INSERT INTO ap (id, invnumber, currency_id) VALUES (?, '', (SELECT id FROM currencies WHERE name=?))|, $form->{id}, $form->{currency});
     }
   }
 
-  my ($currencies)    = selectfirst_array_query($form, $dbh, qq|SELECT curr FROM defaults|);
-  my $defaultcurrency = (split m/:/, $currencies)[0];
-
   if ($form->{currency} eq $defaultcurrency) {
     $form->{exchangerate} = 1;
   } else {
@@ -686,7 +685,7 @@ sub post_invoice {
                 orddate      = ?, quodate     = ?, vendor_id     = ?, amount      = ?,
                 netamount    = ?, paid        = ?, duedate       = ?,
                 invoice      = ?, taxzone_id  = ?, notes         = ?, taxincluded = ?,
-                intnotes     = ?, curr        = ?, storno_id     = ?, storno      = ?,
+                intnotes     = ?, storno_id   = ?, storno        = ?,
                 cp_id        = ?, employee_id = ?, department_id = ?,
                 globalproject_id = ?, direct_debit = ?
               WHERE id = ?|;
@@ -695,7 +694,7 @@ sub post_invoice {
       conv_date($form->{orddate}), conv_date($form->{quodate}),     conv_i($form->{vendor_id}),               $amount,
                 $netamount,                  $form->{paid},      conv_date($form->{duedate}),
             '1',                             $taxzone_id,                  $form->{notes},          $form->{taxincluded} ? 't' : 'f',
-                $form->{intnotes},           $form->{currency},     conv_i($form->{storno_id}),     $form->{storno}      ? 't' : 'f',
+                $form->{intnotes},           conv_i($form->{storno_id}),     $form->{storno}      ? 't' : 'f',
          conv_i($form->{cp_id}),      conv_i($form->{employee_id}), conv_i($form->{department_id}),
          conv_i($form->{globalproject_id}),
                 $form->{direct_debit} ? 't' : 'f',
@@ -925,8 +924,7 @@ sub retrieve_invoice {
                (SELECT c.accno FROM chart c WHERE d.income_accno_id = c.id)    AS income_accno,
                (SELECT c.accno FROM chart c WHERE d.expense_accno_id = c.id)   AS expense_accno,
                (SELECT c.accno FROM chart c WHERE d.fxgain_accno_id = c.id)    AS fxgain_accno,
-               (SELECT c.accno FROM chart c WHERE d.fxloss_accno_id = c.id)    AS fxloss_accno,
-               d.curr AS currencies
+               (SELECT c.accno FROM chart c WHERE d.fxloss_accno_id = c.id)    AS fxloss_accno
                $q_invdate
                FROM defaults d|;
   $ref = selectfirst_hashref_query($form, $dbh, $query);
@@ -943,15 +941,12 @@ sub retrieve_invoice {
   $query = qq|SELECT cp_id, invnumber, transdate AS invdate, duedate,
                 orddate, quodate, globalproject_id,
                 ordnumber, quonumber, paid, taxincluded, notes, taxzone_id, storno, gldate,
-                intnotes, curr AS currency, direct_debit
+                intnotes, (SELECT cu.name FROM currencies cu WHERE cu.id=ap.currency_id) AS currency, direct_debit
               FROM ap
               WHERE id = ?|;
   $ref = selectfirst_hashref_query($form, $dbh, $query, conv_i($form->{id}));
   map { $form->{$_} = $ref->{$_} } keys %$ref;
 
-  # remove any trailing whitespace
-  $form->{currency} =~ s/\s*$//;
-
   $form->{exchangerate}  = $form->get_exchangerate($dbh, $form->{currency}, $form->{invdate}, "sell");
 
   # get shipto
@@ -975,7 +970,7 @@ sub retrieve_invoice {
         i.id AS invoice_id,
         i.description, i.longdescription, i.qty, i.fxsellprice AS sellprice, i.parts_id AS id, i.unit, i.deliverydate, i.project_id, i.serialnumber,
         i.price_factor_id, i.price_factor, i.marge_price_factor, i.discount,
-        p.partnumber, p.inventory_accno_id AS part_inventory_accno_id, p.bin, pr.projectnumber, pg.partsgroup
+        p.partnumber, p.inventory_accno_id AS part_inventory_accno_id,  pr.projectnumber, pg.partsgroup
 
         FROM invoice i
         JOIN parts p ON (i.parts_id = p.id)
@@ -1093,21 +1088,19 @@ sub get_vendor {
          v.id AS vendor_id, v.name AS vendor, v.discount as vendor_discount,
          v.creditlimit, v.terms, v.notes AS intnotes,
          v.email, v.cc, v.bcc, v.language_id, v.payment_id,
-         v.street, v.zipcode, v.city, v.country, v.taxzone_id, v.curr, v.direct_debit,
+         v.street, v.zipcode, v.city, v.country, v.taxzone_id, cu.name AS curr, v.direct_debit,
          $duedate + COALESCE(pt.terms_netto, 0) AS duedate,
          b.description AS business
        FROM vendor v
        LEFT JOIN business b       ON (b.id = v.business_id)
        LEFT JOIN payment_terms pt ON (v.payment_id = pt.id)
+       LEFT JOIN currencies cu    ON (v.currency_id = cu.id)
        WHERE 1=1 $where|;
   my $ref = selectfirst_hashref_query($form, $dbh, $query, @values);
   map { $params->{$_} = $ref->{$_} } keys %$ref;
 
-  # remove any trailing whitespace
-  $form->{curr} =~ s/\s*$//;
-
-  # use vendor currency if not empty
-  $form->{currency} = $form->{curr} if $form->{curr};
+  # use vendor currency
+  $form->{currency} = $form->{curr};
 
   $params->{creditremaining} = $params->{creditlimit};
 
@@ -1118,7 +1111,7 @@ sub get_vendor {
   $query = qq|SELECT o.amount,
                 (SELECT e.sell
                  FROM exchangerate e
-                 WHERE (e.curr = o.curr)
+                 WHERE (e.currency_id = o.currency_id)
                    AND (e.transdate = o.transdate)) AS exch
               FROM oe o
               WHERE (o.vendor_id = ?) AND (o.quotation = '0') AND (o.closed = '0')|;
@@ -1234,7 +1227,7 @@ sub retrieve_item {
   my $query =
     qq|SELECT
          p.id, p.partnumber, p.description, p.lastcost AS sellprice, p.listprice,
-         p.unit, p.assembly, p.bin, p.onhand, p.formel,
+         p.unit, p.assembly, p.onhand, p.formel,
          p.notes AS partnotes, p.notes AS longdescription, p.not_discountable,
          p.inventory_accno_id, p.price_factor_id,
 
@@ -1393,9 +1386,10 @@ sub vendor_details {
   # fax and phone and email as vendor*
   my $query =
     qq|SELECT ct.*, cp.*, ct.notes as vendornotes, phone as vendorphone, fax as vendorfax, email as vendoremail,
-         ct.curr AS currency
+         cu.name AS currency
        FROM vendor ct
        LEFT JOIN contacts cp ON (ct.id = cp.cp_cv_id)
+       LEFT JOIN currencies cu ON (ct.currency_id = cu.id)
        WHERE (ct.id = ?) $contact
        ORDER BY cp.cp_id
        LIMIT 1|;
@@ -1412,8 +1406,6 @@ sub vendor_details {
   }
 
   map { $form->{$_} = $ref->{$_} } keys %$ref;
-  # remove any trailing whitespace
-  $form->{currency} =~ s/\s*$// if ($form->{currency});
 
   my $custom_variables = CVar->get_custom_variables('dbh'      => $dbh,
                                                     'module'   => 'CT',
index 2db51a5..1dc1eff 100644 (file)
--- a/SL/IS.pm
+++ b/SL/IS.pm
@@ -471,9 +471,10 @@ sub customer_details {
   my $query =
     qq|SELECT ct.*, cp.*, ct.notes as customernotes,
          ct.phone AS customerphone, ct.fax AS customerfax, ct.email AS customeremail,
-         ct.curr AS currency
+         cu.name AS currency
        FROM customer ct
        LEFT JOIN contacts cp on ct.id = cp.cp_cv_id
+       LEFT JOIN currencies cu ON (ct.currency_id = cu.id)
        WHERE (ct.id = ?) $where
        ORDER BY cp.cp_id
        LIMIT 1|;
@@ -491,9 +492,6 @@ sub customer_details {
 
   map { $form->{$_} = $ref->{$_} } keys %$ref;
 
-  # remove any trailing whitespace
-  $form->{currency} =~ s/\s*$// if ($form->{currency});
-
   if ($form->{delivery_customer_id}) {
     $query =
       qq|SELECT *, notes as customernotes
@@ -549,6 +547,8 @@ sub post_invoice {
   }
 
   $form->{defaultcurrency} = $form->get_default_currency($myconfig);
+  my $defaultcurrency = $form->{defaultcurrency};
+
   # Seit neuestem wird die department_id schon Ã¼bergeben UND $form->department nicht mehr
   # korrekt zusammengebaut. Sehr wahrscheinlich beim Umstieg auf T8 kaputt gegangen
   # Ich lass den Code von 2005 erstmal noch stehen ;-) jb 03-2011
@@ -569,8 +569,8 @@ sub post_invoice {
       $query = qq|SELECT nextval('glid')|;
       ($form->{"id"}) = selectrow_query($form, $dbh, $query);
 
-      $query = qq|INSERT INTO ar (id, invnumber) VALUES (?, ?)|;
-      do_query($form, $dbh, $query, $form->{"id"}, $form->{"id"});
+      $query = qq|INSERT INTO ar (id, invnumber, currency_id) VALUES (?, ?, (SELECT id FROM currencies WHERE name=?))|;
+      do_query($form, $dbh, $query, $form->{"id"}, $form->{"id"}, $form->{currency});
 
       if (!$form->{invnumber}) {
         $form->{invnumber} =
@@ -583,9 +583,6 @@ sub post_invoice {
   my ($netamount, $invoicediff) = (0, 0);
   my ($amount, $linetotal, $lastincomeaccno);
 
-  my ($currencies)    = selectfirst_array_query($form, $dbh, qq|SELECT curr FROM defaults|);
-  my $defaultcurrency = (split m/:/, $currencies)[0];
-
   if ($form->{currency} eq $defaultcurrency) {
     $form->{exchangerate} = 1;
   } else {
@@ -1098,7 +1095,8 @@ sub post_invoice {
                 amount      = ?, netamount     = ?, paid          = ?,
                 duedate     = ?, deliverydate  = ?, invoice       = ?, shippingpoint = ?,
                 shipvia     = ?, terms         = ?, notes         = ?, intnotes      = ?,
-                curr        = ?, department_id = ?, payment_id    = ?, taxincluded   = ?,
+                currency_id = (SELECT id FROM currencies WHERE name = ?),
+                department_id = ?, payment_id    = ?, taxincluded   = ?,
                 type        = ?, language_id   = ?, taxzone_id    = ?, shipto_id     = ?,
                 employee_id = ?, salesman_id   = ?, storno_id     = ?, storno        = ?,
                 cp_id       = ?, marge_total   = ?, marge_percent = ?,
@@ -1572,8 +1570,7 @@ sub retrieve_invoice {
          (SELECT c.accno FROM chart c WHERE d.income_accno_id = c.id)    AS income_accno,
          (SELECT c.accno FROM chart c WHERE d.expense_accno_id = c.id)   AS expense_accno,
          (SELECT c.accno FROM chart c WHERE d.fxgain_accno_id = c.id)    AS fxgain_accno,
-         (SELECT c.accno FROM chart c WHERE d.fxloss_accno_id = c.id)    AS fxloss_accno,
-         d.curr AS currencies
+         (SELECT c.accno FROM chart c WHERE d.fxloss_accno_id = c.id)    AS fxloss_accno
          ${query_transdate}
        FROM defaults d|;
 
@@ -1592,7 +1589,7 @@ sub retrieve_invoice {
            a.orddate, a.quodate, a.globalproject_id,
            a.transdate AS invdate, a.deliverydate, a.paid, a.storno, a.gldate,
            a.shippingpoint, a.shipvia, a.terms, a.notes, a.intnotes, a.taxzone_id,
-           a.duedate, a.taxincluded, a.curr AS currency, a.shipto_id, a.cp_id,
+           a.duedate, a.taxincluded, (SELECT cu.name FROM currencies cu WHERE cu.id=a.currency_id) AS currency, a.shipto_id, a.cp_id,
            a.employee_id, a.salesman_id, a.payment_id,
            a.language_id, a.delivery_customer_id, a.delivery_vendor_id, a.type,
            a.transaction_description, a.donumber, a.invnumber_for_credit_note,
@@ -1604,9 +1601,6 @@ sub retrieve_invoice {
     $ref = selectfirst_hashref_query($form, $dbh, $query, $id);
     map { $form->{$_} = $ref->{$_} } keys %{ $ref };
 
-    # remove any trailing whitespace
-    $form->{currency} =~ s/\s*$//;
-
     $form->{exchangerate} = $form->get_exchangerate($dbh, $form->{currency}, $form->{invdate}, "buy");
 
     # get shipto
@@ -1651,7 +1645,7 @@ sub retrieve_invoice {
            i.description, i.longdescription, i.qty, i.fxsellprice AS sellprice, i.discount, i.parts_id AS id, i.unit, i.deliverydate AS reqdate,
            i.project_id, i.serialnumber, i.id AS invoice_pos, i.pricegroup_id, i.ordnumber, i.transdate, i.cusordnumber, i.subtotal, i.lastcost,
            i.price_factor_id, i.price_factor, i.marge_price_factor,
-           p.partnumber, p.assembly, p.bin, p.notes AS partnotes, p.inventory_accno_id AS part_inventory_accno_id, p.formel, p.listprice,
+           p.partnumber, p.assembly, p.notes AS partnotes, p.inventory_accno_id AS part_inventory_accno_id, p.formel, p.listprice,
            pr.projectnumber, pg.partsgroup, prg.pricegroup
 
          FROM invoice i
@@ -1771,13 +1765,14 @@ sub get_customer {
          c.id AS customer_id, c.name AS customer, c.discount as customer_discount, c.creditlimit, c.terms,
          c.email, c.cc, c.bcc, c.language_id, c.payment_id,
          c.street, c.zipcode, c.city, c.country,
-         c.notes AS intnotes, c.klass as customer_klass, c.taxzone_id, c.salesman_id, c.curr,
+         c.notes AS intnotes, c.klass as customer_klass, c.taxzone_id, c.salesman_id, cu.name AS curr,
          c.taxincluded_checked, c.direct_debit,
          $duedate + COALESCE(pt.terms_netto, 0) AS duedate,
          b.discount AS tradediscount, b.description AS business
        FROM customer c
        LEFT JOIN business b ON (b.id = c.business_id)
        LEFT JOIN payment_terms pt ON ($payment_id (c.payment_id = pt.id))
+       LEFT JOIN currencies cu ON (c.currency_id=cu.id)
        WHERE c.id = ?|;
   push @values, $cid;
   $ref = selectfirst_hashref_query($form, $dbh, $query, @values);
@@ -1786,11 +1781,8 @@ sub get_customer {
 
   map { $form->{$_} = $ref->{$_} } keys %$ref;
 
-  # remove any trailing whitespace
-  $form->{curr} =~ s/\s*$//;
-
-  # use customer currency if not empty
-  $form->{currency} = $form->{curr} if $form->{curr};
+  # use customer currency
+  $form->{currency} = $form->{curr};
 
   $query =
     qq|SELECT sum(amount - paid) AS dunning_amount
@@ -1819,7 +1811,7 @@ sub get_customer {
   $query =
     qq|SELECT o.amount,
          (SELECT e.buy FROM exchangerate e
-          WHERE e.curr = o.curr
+          WHERE e.currency_id = o.currency_id
             AND e.transdate = o.transdate)
        FROM oe o
        WHERE o.customer_id = ?
@@ -1959,7 +1951,7 @@ sub retrieve_item {
          c3.new_chart_id AS expense_new_chart,
          date($transdate) - c3.valid_from AS expense_valid,
 
-         p.unit, p.assembly, p.bin, p.onhand,
+         p.unit, p.assembly, p.onhand,
          p.notes AS partnotes, p.notes AS longdescription,
          p.not_discountable, p.formel, p.payment_id AS part_payment_id,
          p.price_factor_id, p.weight,
index 1e9c6a1..d9b7129 100644 (file)
@@ -15,9 +15,9 @@ sub init {
 
   $self->{data} = selectfirst_hashref_query($::form, $::form->get_standard_dbh, qq|SELECT * FROM defaults|);
 
-  my $curr            =  $self->{data}->{curr} || '';
-  $curr               =~ s/\s+//g;
-  $self->{currencies} =  [ split m/:/, $curr ];
+  #To get all currencies and the default currency:
+  ($self->{data}->{curr}) = selectrow_query($::form, $::form->get_standard_dbh, qq|SELECT name AS curr FROM currencies WHERE id = (SELECT currency_id FROM defaults)|);
+  $self->{currencies}     = [ map { $_->{name} } selectall_hashref_query($::form, $::form->get_standard_dbh, qq|SELECT name FROM currencies ORDER BY id|) ];
 
   return $self;
 }
@@ -25,13 +25,13 @@ sub init {
 sub get_default_currency {
   my ($self) = @_;
 
-  return ($self->get_currencies)[0];
+  return $self->{data}->{curr};
 }
 
 sub get_currencies {
   my ($self) = @_;
 
-  return $self->{currencies} ? @{ $self->{currencies} } : ();
+  return @{ $self->{currencies} };
 }
 
 sub get_accounting_method {
@@ -144,6 +144,16 @@ sub get_purchase_delivery_order_show_delete {
   return $self->{data}->{purchase_delivery_order_show_delete};
 }
 
+sub get_default_warehouse_id {
+  my ($self) = @_;
+  return ($self->{data}->{warehouse_id});
+}
+
+sub get_default_bin_id {
+  my ($self) = @_;
+  return ($self->{data}->{bin_id});
+}
+
 1;
 
 __END__
@@ -256,6 +266,14 @@ corresponding record type (true or false).
 Returns the default behavior for showing the delete button for the
 corresponding record type (true or false).
 
+=item C<get_default_warehouse_id>
+
+Returns the default warehouse_id
+
+=item C<get_default_bin_id>
+
+Returns the default bin_id
+
 =back
 
 =head1 BUGS
index 2857e1c..f5b3b77 100644 (file)
@@ -83,16 +83,23 @@ sub _create_message_id {
 sub _create_address_headers {
   my ($self) = @_;
 
+  # $self->{addresses} collects the recipients for use in e.g. the
+  # SMTP 'RCPT TO:' envelope command. $self->{headers} collects the
+  # headers that make up the actual email. 'BCC' should not be
+  # included there for certain transportation methods (SMTP).
+
   $self->{addresses} = {};
 
   foreach my $item (qw(from to cc bcc)) {
     $self->{addresses}->{$item} = [];
-    next if !$self->{$item} || $self->{driver}->keep_from_header($item);
+    next if !$self->{$item};
 
     my @header_addresses;
 
     foreach my $addr_obj (Email::Address->parse($self->{$item})) {
       push @{ $self->{addresses}->{$item} }, $addr_obj->address;
+      next if $self->{driver}->keep_from_header($item);
+
       my $phrase = $addr_obj->phrase();
       if ($phrase) {
         $phrase =~ s/^\"//;
@@ -187,7 +194,7 @@ sub send {
   $self->{contenttype} ||=  "text/plain";
   $self->{headers}       =  [
     Subject              => $self->{subject},
-    'Message-ID'         => $self->_create_message_id,
+    'Message-ID'         => '<' . $self->_create_message_id . '>',
     'X-Mailer'           => "kivitendo $self->{version}",
   ];
 
index dfc5a7d..41cac3c 100644 (file)
@@ -3,7 +3,7 @@ package SL::MoreCommon;
 require Exporter;
 our @ISA = qw(Exporter);
 
-our @EXPORT    = qw(save_form restore_form compare_numbers any cross);
+our @EXPORT    = qw(save_form restore_form compare_numbers cross);
 our @EXPORT_OK = qw(ary_union ary_intersect ary_diff listify ary_to_hash uri_encode uri_decode);
 
 use List::MoreUtils qw(zip);
@@ -76,15 +76,6 @@ sub compare_numbers {
   return $a <=> $b;
 }
 
-sub any (&@) {
-  my $f = shift;
-  return if ! @_;
-  for (@_) {
-    return 1 if $f->();
-  }
-  return 0;
-}
-
 sub cross(&\@\@) {
   my $op = shift;
   use vars qw/@A @B/;
index 3d6b328..0c0b767 100644 (file)
--- a/SL/OE.pm
+++ b/SL/OE.pm
@@ -91,7 +91,7 @@ sub transactions {
     qq|JOIN $vc ct ON (o.${vc}_id = ct.id) | .
     qq|LEFT JOIN employee e ON (o.employee_id = e.id) | .
     qq|LEFT JOIN employee s ON (o.salesman_id = s.id) | .
-    qq|LEFT JOIN exchangerate ex ON (ex.curr = o.curr | .
+    qq|LEFT JOIN exchangerate ex ON (ex.currency_id = o.currency_id | .
     qq|  AND ex.transdate = o.transdate) | .
     qq|LEFT JOIN project pr ON (o.globalproject_id = pr.id) | .
     qq|$periodic_invoices_joins | .
@@ -311,7 +311,7 @@ sub save {
     $query = qq|SELECT nextval('id')|;
     ($form->{id}) = selectrow_query($form, $dbh, $query);
 
-    $query = qq|INSERT INTO oe (id, ordnumber, employee_id) VALUES (?, '', ?)|;
+    $query = qq|INSERT INTO oe (id, ordnumber, employee_id, currency_id) VALUES (?, '', ?, (SELECT currency_id FROM defaults))|;
     do_query($form, $dbh, $query, $form->{id}, $form->{employee_id});
   }
 
@@ -494,7 +494,7 @@ sub save {
     qq|UPDATE oe SET
          ordnumber = ?, quonumber = ?, cusordnumber = ?, transdate = ?, vendor_id = ?,
          customer_id = ?, amount = ?, netamount = ?, reqdate = ?, taxincluded = ?,
-         shippingpoint = ?, shipvia = ?, notes = ?, intnotes = ?, curr = ?, closed = ?,
+         shippingpoint = ?, shipvia = ?, notes = ?, intnotes = ?, currency_id = (SELECT id FROM currencies WHERE name=?), closed = ?,
          delivered = ?, proforma = ?, quotation = ?, department_id = ?, language_id = ?,
          taxzone_id = ?, shipto_id = ?, payment_id = ?, delivery_vendor_id = ?, delivery_customer_id = ?,
          globalproject_id = ?, employee_id = ?, salesman_id = ?, cp_id = ?, transaction_description = ?, marge_total = ?, marge_percent = ?
@@ -506,7 +506,7 @@ sub save {
              $amount, $netamount, conv_date($reqdate),
              $form->{taxincluded} ? 't' : 'f', $form->{shippingpoint},
              $form->{shipvia}, $form->{notes}, $form->{intnotes},
-             substr($form->{currency}, 0, 3), $form->{closed} ? 't' : 'f',
+             $form->{currency}, $form->{closed} ? 't' : 'f',
              $form->{delivered} ? "t" : "f", $form->{proforma} ? 't' : 'f',
              $quotation, conv_i($form->{department_id}),
              conv_i($form->{language_id}), conv_i($form->{taxzone_id}),
@@ -759,14 +759,13 @@ sub retrieve {
                      (SELECT c.accno FROM chart c WHERE d.income_accno_id    = c.id) AS income_accno,
                      (SELECT c.accno FROM chart c WHERE d.expense_accno_id   = c.id) AS expense_accno,
                      (SELECT c.accno FROM chart c WHERE d.fxgain_accno_id    = c.id) AS fxgain_accno,
-                     (SELECT c.accno FROM chart c WHERE d.fxloss_accno_id    = c.id) AS fxloss_accno,
-              d.curr AS currencies
+                     (SELECT c.accno FROM chart c WHERE d.fxloss_accno_id    = c.id) AS fxloss_accno
               $query_add
               FROM defaults d|;
   my $ref = selectfirst_hashref_query($form, $dbh, $query);
   map { $form->{$_} = $ref->{$_} } keys %$ref;
 
-  ($form->{currency}) = split(/:/, $form->{currencies}) unless ($form->{currency});
+  $form->{currency} = $form->get_default_currency($myconfig);
 
   # set reqdate if this is an invoice->order conversion. If someone knows a better check to ensure
   # we come from invoices, feel free.
@@ -785,7 +784,7 @@ sub retrieve {
     $query =
       qq|SELECT o.cp_id, o.ordnumber, o.transdate, o.reqdate,
            o.taxincluded, o.shippingpoint, o.shipvia, o.notes, o.intnotes,
-           o.curr AS currency, e.name AS employee, o.employee_id, o.salesman_id,
+           (SELECT cu.name FROM currencies cu WHERE cu.id=o.currency_id) AS currency, e.name AS employee, o.employee_id, o.salesman_id,
            o.${vc}_id, cv.name AS ${vc}, o.amount AS invtotal,
            o.closed, o.reqdate, o.quonumber, o.department_id, o.cusordnumber,
            d.description AS department, o.payment_id, o.language_id, o.taxzone_id,
@@ -807,9 +806,6 @@ sub retrieve {
     if ($ref) {
       map { $form->{$_} = $ref->{$_} } keys %$ref;
 
-      # remove any trailing whitespace
-      $form->{currency} =~ s/\s*$//;
-
       $form->{saved_xyznumber} = $form->{$form->{type} =~ /_quotation$/ ? "quonumber" : "ordnumber"};
 
       # set all entries for multiple ids blank that yield different information
@@ -874,7 +870,7 @@ sub retrieve {
            c3.accno AS expense_accno,   c3.new_chart_id AS expense_new_chart,   date($transdate) - c3.valid_from as expense_valid,
            oe.ordnumber AS ordnumber_oe, oe.transdate AS transdate_oe, oe.cusordnumber AS cusordnumber_oe,
            p.partnumber, p.assembly, p.listprice, o.description, o.qty,
-           o.sellprice, o.parts_id AS id, o.unit, o.discount, p.bin, p.notes AS partnotes, p.inventory_accno_id AS part_inventory_accno_id,
+           o.sellprice, o.parts_id AS id, o.unit, o.discount, p.notes AS partnotes, p.inventory_accno_id AS part_inventory_accno_id,
            o.reqdate, o.project_id, o.serialnumber, o.ship, o.lastcost,
            o.ordnumber, o.transdate, o.cusordnumber, o.subtotal, o.longdescription,
            o.price_factor_id, o.price_factor, o.marge_price_factor,
index 9ead2f2..e5690e7 100644 (file)
@@ -19,6 +19,15 @@ sub _call_on {
   return $object->$method(@params);
 }
 
+{ # This will give you an id for identifying html tags and such.
+  # It's guaranteed to be unique unless you exceed 10 mio calls per request.
+  # Do not use these id's to store information across requests.
+my $_id_sequence = int rand 1e7;
+sub _id {
+  return ( $_id_sequence = ($_id_sequence + 1) % 1e7 );
+}
+}
+
 
 sub stringify_attributes {
   my ($self, %params) = @_;
@@ -68,6 +77,7 @@ sub man_days_tag {
 sub name_to_id {
   my ($self, $name) = @_;
 
+  $name =~ s/\[\+?\]/ _id() /ge; # give constructs with [] or [+] unique ids
   $name =~ s/[^\w_]/_/g;
   $name =~ s/_+/_/g;
 
index 65cb302..095f758 100644 (file)
--- a/SL/RP.pm
+++ b/SL/RP.pm
@@ -1238,7 +1238,7 @@ sub aging {
       "duedate", invoice, ${arap}.id, date_part('days', now() - duedate) as overduedays,
       (SELECT $buysell
        FROM exchangerate
-       WHERE (${arap}.curr = exchangerate.curr)
+       WHERE (${arap}.currency_id = exchangerate.currency_id)
          AND (exchangerate.transdate = ${arap}.transdate)) AS exchangerate
     FROM ${arap}, ${ct}
     WHERE ((paid != amount) OR (datepaid > (date $todate) AND datepaid is not null))
index 5ef07b7..3e157a3 100644 (file)
@@ -390,8 +390,10 @@ JAVASCRIPT
     my $filter  = ".filter(function(idx) { return this.substr(0, " . length($params{with}) . ") == '$params{with}'; })";
     $filter    .= ".map(function(idx, str) { return str.replace('$params{with}_', ''); })";
 
+    my $params_js = $params{params} ? qq| + ($params{params})| : '';
+
     $stop_event = <<JAVASCRIPT;
-        \$.post('$params{url}', { '${as}[]': \$(\$('${selector}').sortable('toArray'))${filter}.toArray() });
+        \$.post('$params{url}'${params_js}, { '${as}[]': \$(\$('${selector}').sortable('toArray'))${filter}.toArray() });
 JAVASCRIPT
   }
 
@@ -733,6 +735,11 @@ If trueish then the children will not be recolored. The default is to
 recolor the children by setting the class C<listrow0> on odd and
 C<listrow1> on even entries.
 
+=item C<params>
+
+An optional JavaScript string that is evaluated before sending the
+POST request. The result must be a string that is appended to the URL.
+
 =back
 
 Example:
index 368a0a2..e11fda4 100644 (file)
@@ -105,7 +105,7 @@ sub login {
     my %myconfig = $main::auth->read_user(login => $self->{login});
 
     # check if database is down
-    my $dbh = SL::DBConnect->connect($myconfig{dbconnect}, $myconfig{dbuser}, $myconfig{dbpasswd})
+    my $dbh = SL::DBConnect->connect($myconfig{dbconnect}, $myconfig{dbuser}, $myconfig{dbpasswd}, SL::DBConnect->get_options)
       or $self->error($DBI::errstr);
 
     # we got a connection, check the version
@@ -245,7 +245,7 @@ sub dbsources {
   $form->{sid} = $form->{dbdefault};
   &dbconnect_vars($form, $form->{dbdefault});
 
-  my $dbh = SL::DBConnect->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd})
+  my $dbh = SL::DBConnect->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}, SL::DBConnect->get_options)
     or $form->dberror;
 
   if ($form->{dbdriver} eq 'Pg') {
@@ -262,7 +262,7 @@ sub dbsources {
         next if ($db =~ /^template/);
 
         &dbconnect_vars($form, $db);
-        my $dbh = SL::DBConnect->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd})
+        my $dbh = SL::DBConnect->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}, SL::DBConnect->get_options)
           or $form->dberror;
 
         $query =
@@ -317,7 +317,7 @@ sub dbclusterencoding {
 
   dbconnect_vars($form, $form->{dbdefault});
 
-  my $dbh                = SL::DBConnect->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}) || $form->dberror();
+  my $dbh                = SL::DBConnect->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}, SL::DBConnect->get_options) || $form->dberror();
   my $query              = qq|SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname = 'template0'|;
   my ($cluster_encoding) = $dbh->selectrow_array($query);
   $dbh->disconnect();
@@ -335,7 +335,7 @@ sub dbcreate {
   $form->{sid} = $form->{dbdefault};
   &dbconnect_vars($form, $form->{dbdefault});
   my $dbh =
-    SL::DBConnect->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd})
+    SL::DBConnect->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}, SL::DBConnect->get_options)
     or $form->dberror;
   $form->{db} =~ s/\"//g;
   my %dbcreate = (
@@ -377,7 +377,7 @@ sub dbcreate {
 
   &dbconnect_vars($form, $form->{db});
 
-  $dbh = SL::DBConnect->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd})
+  $dbh = SL::DBConnect->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}, SL::DBConnect->get_options)
     or $form->dberror;
 
   my $db_charset = $Common::db_encoding_to_charset{$form->{encoding}};
@@ -398,6 +398,8 @@ sub dbcreate {
   do_query($form, $dbh, $query, $form->{profit_determination});
   $query = "UPDATE defaults SET inventory_system = ?";
   do_query($form, $dbh, $query, $form->{inventory_system});
+  $query = "UPDATE defaults SET curr = ?";
+  do_query($form, $dbh, $query, $form->{defaultcurrency});
 
   $dbh->disconnect;
 
@@ -414,7 +416,7 @@ sub dbdelete {
 
   $form->{sid} = $form->{dbdefault};
   &dbconnect_vars($form, $form->{dbdefault});
-  my $dbh = SL::DBConnect->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd})
+  my $dbh = SL::DBConnect->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}, SL::DBConnect->get_options)
     or $form->dberror;
   my $query = $dbdelete{$form->{dbdriver}};
   do_query($form, $dbh, $query);
@@ -460,7 +462,7 @@ sub dbneedsupdate {
     map { $form->{$_} = $member->{$_} } qw(dbname dbuser dbpasswd dbhost dbport);
     dbconnect_vars($form, $form->{dbname});
 
-    my $dbh = SL::DBConnect->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd});
+    my $dbh = SL::DBConnect->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}, SL::DBConnect->get_options);
 
     next unless $dbh;
 
@@ -590,7 +592,7 @@ sub dbupdate {
     $db =~ s/^db//;
     &dbconnect_vars($form, $db);
 
-    my $dbh = SL::DBConnect->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd})
+    my $dbh = SL::DBConnect->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}, SL::DBConnect->get_options)
       or $form->dberror;
 
     $dbh->do($form->{dboptions}) if ($form->{dboptions});
@@ -654,7 +656,7 @@ sub dbupdate2 {
     $db =~ s/^db//;
     &dbconnect_vars($form, $db);
 
-    my $dbh = SL::DBConnect->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}) or $form->dberror;
+    my $dbh = SL::DBConnect->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd}, SL::DBConnect->get_options) or $form->dberror;
 
     $dbh->do($form->{dboptions}) if ($form->{dboptions});
 
@@ -694,7 +696,7 @@ sub save_member {
 
   $main::auth->save_user($self->{login}, map { $_, $self->{$_} } config_vars());
 
-  my $dbh = SL::DBConnect->connect($self->{dbconnect}, $self->{dbuser}, $self->{dbpasswd});
+  my $dbh = SL::DBConnect->connect($self->{dbconnect}, $self->{dbuser}, $self->{dbpasswd}, SL::DBConnect->get_options);
   if ($dbh) {
     $self->create_employee_entry($::form, $dbh, $self, 1);
     $dbh->disconnect();
index 28e836e..f7ca693 100644 (file)
--- a/SL/WH.pm
+++ b/SL/WH.pm
@@ -132,7 +132,13 @@ sub transfer {
           bin       => $dst_bin->id,
           qty       => $qty,
         )->save;
-      }
+        # Standardlagerplatz in Stammdaten gleich mitverschieben
+        if (defined($transfer->{change_default_bin})){
+          my $part = SL::DB::Part->new(id        => conv_i($transfer->{parts_id}))->load;
+          $part->update_attributes(warehouse_id  => conv_i($transfer->{dst_warehouse_id}));
+          $part->update_attributes(bin_id        => conv_i($transfer->{dst_bin_id}));
+        }
+     }
 
       push @trans_ids, $trans_id;
     }
index 7f0b80d..84c8b06 100755 (executable)
@@ -780,6 +780,7 @@ sub dbcreate {
   my $locale = $main::locale;
 
   $form->isblank("db", $locale->text('Dataset missing!'));
+  $form->isblank("defaultcurrency", $locale->text('Default currency missing!'));
 
   User->dbcreate(\%$form);
 
index a70c32e..b9aed48 100644 (file)
@@ -109,10 +109,9 @@ sub payment {
   # geben und hier reinparsen, oder besser multibox oder html auslagern?
   # Antwort: form->currency wird mit oldcurrency oder curr[0] Ã¼berschrieben
   # Wofür macht das Sinn?
-  @curr = split(/:/, $form->{currencies});
-  chomp $curr[0];
+  @curr = $form->get_all_currencies();
   $form->{defaultcurrency} = $form->{currency} = $form->{oldcurrency} =
-    $curr[0];
+    $form->get_default_currency(\%myconfig);
 
   # Entsprechend präventiv die Auswahlliste für Währungen
   # auch mit value= zusammenbauen (s.a. oben bugfix 1771)
index 222a4d1..04257ce 100644 (file)
@@ -1489,8 +1489,7 @@ sub link_part {
   IC->create_links("IC", \%myconfig, \%$form);
 
   # currencies
-  map({ $form->{selectcurrency} .= "<option>$_\n" }
-      split(/:/, $form->{currencies}));
+  map({ $form->{selectcurrency} .= "<option>$_\n" } $::form->get_all_currencies());
 
   # parts and assemblies have the same links
   my $item = $form->{item};
@@ -1586,7 +1585,20 @@ sub form_header {
 
   $form->get_lists('price_factors' => 'ALL_PRICE_FACTORS',
                    'partsgroup'    => 'all_partsgroup',
-                   'vendors'       => 'ALL_VENDORS',);
+                   'vendors'       => 'ALL_VENDORS',
+                   'warehouses'    => { 'key'    => 'WAREHOUSES',
+                                        'bins'   => 'BINS', });
+  # leerer wert für Lager und Lagerplatz korrekt einstellt
+  # ID 0 sollte in Ordnung sein, da der Zähler sowieso höher ist
+  my $no_default_bin_entry = { 'id' => '0', description => '--', 'BINS' => [ { id => '0', description => ''} ] };
+  push @ { $form->{WAREHOUSES} }, $no_default_bin_entry;
+  if (my $max = scalar @{ $form->{WAREHOUSES} }) {
+
+    my $default_warehouse_id = $::instance_conf->get_default_warehouse_id;
+    my $default_bin_id       = $::instance_conf->get_default_bin_id;
+    $form->{warehouse_id} ||= $default_warehouse_id || $form->{WAREHOUSES}->[$max -1]->{id};
+    $form->{bin_id}       ||= $default_bin_id       ||  $form->{WAREHOUSES}->[$max -1]->{BINS}->[0]->{id};
+  }
 
 
   IC->retrieve_buchungsgruppen(\%myconfig, $form);
@@ -1847,6 +1859,11 @@ sub save {
   $form->error($locale->text('Description must not be empty!')) unless $form->{description};
   $form->error($locale->text('Partnumber must not be set to empty!')) if $form->{id} && !$form->{partnumber};
 
+  # undef warehouse_id if the empty value is selected
+  if ( ($form->{warehouse_id} == 0) && ($form->{bin_id} == 0) ) {
+    undef $form->{warehouse_id};
+    undef $form->{bin_id};
+  }
   # save part
   if (IC->save(\%myconfig, \%$form) == 3) {
     $form->error($locale->text('Partnumber not unique!'));
index 1436963..4209fdf 100644 (file)
@@ -149,7 +149,7 @@ sub invoice_links {
     $form->{currency} = $currency;
   }
 
-  my @curr = split(/:/, $form->{currencies}); #seems to be missing
+  my @curr = $form->get_all_currencies();
   map { $form->{selectcurrency} .= "<option>$_\n" } @curr;
 
   $form->{oldvendor} = "$form->{vendor}--$form->{vendor_id}";
index c3a13da..d455d5a 100644 (file)
@@ -969,7 +969,7 @@ sub aging {
   $report->set_columns(%column_defs);
   $report->set_column_order(@columns);
 
-  my @hidden_variables = qw(todate customer vendor arap title ct);
+  my @hidden_variables = qw(todate customer vendor arap title ct fordate reporttype);
   $report->set_export_options('generate_' . ($form->{arap} eq 'ar' ? 'ar' : 'ap') . '_aging', @hidden_variables);
 
   my @options;
index eb12fcb..f76b4e9 100644 (file)
@@ -282,6 +282,7 @@ sub transfer_parts {
     }
 
     $transfer->{comment} = $form->{comment};
+    $transfer->{change_default_bin} = $form->{change_default_bin};
 
     push @transfers, $transfer;
 
@@ -331,7 +332,7 @@ sub transfer_stock_update_part {
     if (!scalar @{ $parts }) {
       new_item(action => "transfer_stock_update_part");
     } elsif (scalar @{ $parts } == 1) {
-      @{$form}{qw(parts_id partnumber description ean)} = @{$parts->[0]}{qw(id partnumber description ean)};
+      @{$form}{qw(parts_id partnumber description ean warehouse_id bin_id)} = @{$parts->[0]}{qw(id partnumber description ean warehouse_id bin_id)};
       transfer_stock_get_partunit();
       transfer_warehouse_selection();
 
@@ -398,7 +399,7 @@ sub transfer_stock_part_selected {
 
   my $form     = $main::form;
 
-  @{$form}{qw(parts_id partnumber description ean)} = @{$part}{qw(id partnumber description ean)};
+  @{$form}{qw(parts_id partnumber description ean warehouse_id bin_id)} = @{$part}{qw(id partnumber description ean warehouse_id bin_id)};
 
   transfer_stock_get_partunit();
   transfer_warehouse_selection();
index 9ce022c..9fd5657 100644 (file)
@@ -12,7 +12,7 @@ function part_selection_window(input_partnumber, input_description, input_partsi
     input_partnotes = input_partnumber;
     input_partnotes = input_partnotes.replace(/partnumber/, "partnotes");
     if (input_partnotes == input_partnumber)
-      input_partnoes = "";
+      input_partnotes = "";
   }
 
   if (filter)
index 716a690..5136a93 100755 (executable)
@@ -18,6 +18,7 @@ $self->{texts} = {
   '#1 h'                        => '#1 h',
   '#1 of #2 importable objects were imported.' => '#1 von #2 importierbaren Objekten wurden importiert.',
   '#1 prices were updated.'     => '#1 Preise wurden aktualisiert.',
+  '(recommended) Insert the used currencies in the system. You can simply change the name of the currencies by editing the textfields above. Do not use a name of a currency that is already in use.' => '(empfohlen) Fügen Sie die verwaisten Währungen in Ihr System ein. Sie können den Namen der Währung einfach Ã¤ndern, indem Sie die Felder oben bearbeiten. Benutzen Sie keine Namen von Währungen, die Sie bereits benutzen.',
   '* there are restrictions for the perpetual method, look at chapter "Bemerkungen zu Bestandsmethode"  in' => ' für die Bestandsmethode gibt es Einschränkungen, siehe Kapitel "Bemerkungen zu Bestandsmethode"  in',
   '*) 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:',
   '*/'                          => '*/',
@@ -32,6 +33,12 @@ $self->{texts} = {
   '2. Quarter'                  => '2. Quartal',
   '3. Quarter'                  => '3. Quartal',
   '4. Quarter'                  => '4. Quartal',
+  '<b> I DO CARE!</b> Please check create warehouse and bins and define a name for the warehouse (Bins will be created automatically) and then continue' => '<b>ICH KÃœMMER MICH</b> Bitte haken Sie Lager und Lagerplätze erzeugen an (Automatisches Zuweisen der Lagerplätze) und vergeben einen Namen für dieses Lager (Lagerplätze werden automatisch Ã¼bernommen). Danach auf weiter.',
+  '<b> I DO CARE!</b> Please click back and cancel the update and come back after there has been at least one warehouse defined with bin(s).:' => '<b>ICH KÃœMMER MICH</b> Brechen Sie das Update ab und legen selber mindestens ein Lager mit Lagerplätzen unter dem Menü System / Lager an.',
+  '<b> I DO NOT CARE</b> Please click continue and the following data (see list) will be deleted:' => '<b>IST MIR EGAL</b> Mit einem Klick auf Weiter (rot) werden keine Daten Ã¼bernommen, bzw. migriert und die folgende Information in der untenstehenden Liste wird gelöscht.',
+  '<b>Automatically create new bins</b> in the following new warehouse ' => '<b>Automatisches Zuweisen der Lagerplätze</b> im folgenden neuem Lager:',
+  '<b>Automatically create new bins</b> in the following warehouse if not selected in the list above' => '<b>Automatisches Zuweisen der Lagerplätze</b> im folgenden Lager, falls keine andere Zuweisung oben ausgewählt ist. ',
+  '<b>Default Bins Migration !READ CAREFULLY!</b>' => 'Standardlagerplatz Migraition !AUFMERKSAM LESEN!',
   '<b>What</b> do you want to look for?' => '<b>Wonach</b> wollen Sie suchen?',
   'A Buchungsgruppe consists of a descriptive name and the account numbers for the income and expense accounts for those four tax zones as well as the inventory account number.' => 'Eine Buchungsgruppe besteht aus einem deskriptiven Namen, den Erl&ouml;s- und Aufwandskonten f&uuml;r diese vier Steuerzonen sowie aus einem Inventarkonto.',
   'A digit is required.'        => 'Eine Ziffer ist vorgeschrieben.',
@@ -65,6 +72,7 @@ $self->{texts} = {
   'ASSETS'                      => 'AKTIVA',
   'ATTENTION! If you enabled this feature you can not simply turn it off again without taking care that best_before fields are emptied in the database.' => 'ACHTUNG! Wenn Sie diese Einstellung aktivieren, dann können Sie sie später nicht ohne Weiteres deaktivieren, ohne dafür zu sorgen, dass die Felder der Mindeshaltbarkeitsdaten in der Datenbank leer gemacht werden.',
   'ATTENTION! You can not simply change it from periodic to perpetual once you started posting.' => 'ACHTUNG! Es kann nicht ohne Weiteres im laufenden Betrieb von der Aufwandsmethode zur Bestandsmethode gewechselt werden.',
+  'AUTOMATICALLY MATCH BINS'    => 'LAGERPLÄTZE AUTOMATISCH ZUWEISEN',
   'Abort'                       => 'Abbrechen',
   'Abrechnungsnummer'           => 'Abrechnungsnummer',
   'Abteilung'                   => 'Abteilung',
@@ -284,7 +292,7 @@ $self->{texts} = {
   'Batch Printing'              => 'Druck',
   'Bcc'                         => 'Bcc',
   'Bcc E-mail'                  => 'BCC (E-Mail)',
-  'Because the useability gets worth if one partnumber is used for several parts (for example if you are searching a position for an invoice), partnumbers should be unique.' => 'Da die Benutzerfreundlichkeit durch doppelte Artikelnummern erheblich verschlechtert wird (zum Beispiel, wenn man einen Artikel für eine Rechnung sucht), sollten Artikelnummern eindeutig vergeben sein.',
+  'Because the useability gets worse if one partnumber is used for several parts (for example if you are searching a position for an invoice), partnumbers should be unique.' => 'Da die Benutzerfreundlichkeit durch doppelte Artikelnummern erheblich verschlechtert wird (zum Beispiel, wenn man einen Artikel für eine Rechnung sucht), sollten Artikelnummern eindeutig vergeben sein.',
   'Belegnummer'                 => 'Buchungsnummer',
   'Beratername'                 => 'Beratername',
   'Beraternummer'               => 'Beraternummer',
@@ -318,6 +326,7 @@ $self->{texts} = {
   'Both'                        => 'Beide',
   'Bottom'                      => 'Unten',
   'Bought'                      => 'Gekauft',
+  'Break up the update and contact a service provider.' => 'Diese Option bricht das Update ab. Bitte kontaktieren Sie Ihren Administrator oder beauftragen einen Dienstleister.',
   'Buchungsdatum'               => 'Buchungsdatum',
   'Buchungsgruppe'              => 'Buchungsgruppe',
   'Buchungsgruppe (database ID)' => 'Buchungsgruppe (Datenbank-ID)',
@@ -395,6 +404,7 @@ $self->{texts} = {
   'Cash'                        => 'Zahlungsverkehr',
   'Cc'                          => 'Cc',
   'Cc E-mail'                   => 'CC (E-Mail)',
+  'Change default bin for this parts' => 'Standardlagerplatz für diese Waren Ã¤ndern',
   'Change kivitendo installation settings (all menu entries beneath \'System\')' => 'Ver&auml;ndern der kivitendo-Installationseinstellungen (Men&uuml;punkte unterhalb von \'System\')',
   'Change representative to'    => 'Vertreter Ã¤ndern in',
   'Changes in this block are only sensible if the account is NOT a summary account AND there exists one valid taxkey. To select both Receivables and Payables only make sense for Payment / Receipt (i.e. account cash).' => 'Es ist nur sinnvoll Ã„nderungen vorzunehmen, wenn das Konto KEIN Sammelkonto ist und wenn ein gültiger Steuerschlüssel für das Konto existiert. Gleichzeitig Haken bei Forderungen und Verbindlichkeiten zu setzen, macht auch NUR für den Zahlungsein- und Ausgang (bspw. Bank oder Kasse) Sinn.',
@@ -525,6 +535,7 @@ $self->{texts} = {
   'Curr'                        => 'Währung',
   'Currencies'                  => 'W&auml;hrungen',
   'Currency'                    => 'Währung',
+  'Currency (database ID)'      => 'Währung (Datenbank-ID)',
   'Current / Next Level'        => 'Aktuelles / Nächstes Mahnlevel',
   'Current Earnings'            => 'Gewinn',
   'Current assets account'      => 'Konto für Umlaufvermögen',
@@ -605,8 +616,13 @@ $self->{texts} = {
   'Decrease'                    => 'Verringern',
   'Default (no language selected)' => 'Standard (keine Sprache ausgewählt)',
   'Default Accounts'            => 'Standardkonten',
+  'Default Bin'                 => 'Standard-Lagerplatz',
+  'Default Customer/Vendor Language' => 'Standard-Kunden-/Lieferantensprache',
+  'Default Warehouse'           => 'Standard-Lager',
   'Default Customer/Vendor Language' => 'Standard-Kunden-/Lieferantensprache',
   'Default buchungsgruppe'      => 'Standardbuchungsgruppe',
+  'Default currency'            => 'Standardwährung',
+  'Default currency missing!'   => 'Standardwährung fehlt!',
   'Default output medium'       => 'Standardausgabekanal',
   'Default printer'             => 'Standarddrucker',
   'Default template format'     => 'Standardvorlagenformat',
@@ -812,7 +828,6 @@ $self->{texts} = {
   'End date'                    => 'Enddatum',
   'Enter a description for this new draft.' => 'Geben Sie eine Beschreibung f&uuml;r diesen Entwurf ein.',
   'Enter longdescription'       => 'Langtext eingeben',
-  'Enter the abbreviations separated by a colon (i.e CAD:USD:EUR) for your native and foreign currencies' => 'Geben Sie Ihre und weitere Währungen als Abkürzungen durch Doppelpunkte getrennt ein (z.B. EUR:USD:CAD)',
   'Enter the requested execution date or leave empty for the quickest possible execution:' => 'Geben Sie das jeweils gewünschte Ausführungsdatum an, oder lassen Sie das Feld leer für die schnellstmögliche Ausführung:',
   'Entries for which automatic conversion failed:' => 'Einträge, für die die automatische Umstellung fehlschlug:',
   'Entries for which automatic conversion succeeded:' => 'Einträge, für die die automatische Umstellung erfolgreich war:',
@@ -829,6 +844,7 @@ $self->{texts} = {
   'Error: Customer/vendor not found' => 'Fehler: Kunde/Lieferant nicht gefunden',
   'Error: Gender (cp_gender) missing or invalid' => 'Fehler: Geschlecht (cp_gender) fehlt oder ungültig',
   'Error: Invalid business'     => 'Fehler: Kunden-/Lieferantentyp ungültig',
+  'Error: Invalid currency'     => 'Fehler: ungültige Währung',
   'Error: Invalid language'     => 'Fehler: Sprache ungültig',
   'Error: Invalid part type'    => 'Fehler: Artikeltyp ungültig',
   'Error: Invalid parts group'  => 'Fehler: Warengruppe ungültig',
@@ -932,6 +948,7 @@ $self->{texts} = {
   'From'                        => 'Von',
   'From Date'                   => 'Von',
   'From this version on a new feature is available.' => 'Ab dieser Version ist ein neues Feature verfügbar.',
+  'From this version on it is necessary to name a default value.' => 'Ab dieser Version benötigt kivitendo eine Standardwährung.',
   'From this version on the partnumber of services, articles and assemblies have to be unique.' => 'Ab dieser Version müssen Artikelnummern eindeutig vergeben werden.',
   'From this version on the taxkey 0 must have a tax rate of 0 (for DATEV compatibility).' => 'Ab dieser Version muss der Steuerschlüssel 0 einen Steuersatz von 0% haben (auf Grund der DATEV-Kompatibilität).',
   'Full Access'                 => 'Vollzugriff',
@@ -989,7 +1006,6 @@ $self->{texts} = {
   'ID-Nummer'                   => 'ID-Nummer (intern)',
   'II'                          => 'II',
   'III'                         => 'III',
-  'IMPORTANT NOTE: You cannot safely change currencies, IF you have already booking entries!' => 'WICHTIGER HINWEIS: Falls schon Buchungen im Mandanten vorhanden sind, kann man nicht mehr UNKRITISCH neue oder andere Währungen konfigurieren!',
   'IV'                          => 'IV',
   'If checked the taxkey will not be exported in the DATEV Export, but only IF chart taxkeys differ from general ledger taxkeys' => 'Falls angehakt wird der DATEV-Steuerschlüssel bei Buchungen auf dieses Konto nicht beim DATEV-Export mitexportiert, allerdings nur wenn zusätzlich der Konto-Steuerschlüssel vom Buchungs (Hauptbuch) Steuerschlüssel abweicht',
   'If the article type is set to \'mixed\' then a column called \'type\' must be present.' => 'Falls der Artikeltyp auf \'gemischt\' gestellt wird, muss eine Spalte namens \'type\' vorhanden sein.',
@@ -1003,6 +1019,7 @@ $self->{texts} = {
   'If you want to change any of these parameters then press the &quot;Back&quot; button, edit the file &quot;config/kivitendo.conf&quot; and login into the admin module again.' => 'Wenn Sie einen der Parameter &auml;ndern wollen, so dr&uuml;cken Sie auf den &quot;Zur&uuml;ck&quot;-Button, bearbeiten Sie die Datei &quot;config/kivitendo.conf&quot;, und melden Sie sich erneut im Administrationsbereich an.',
   'If you want to delete such a dataset you have to edit the user(s) that are using the dataset in question and have them use another dataset.' => 'Wenn Sie eine solche Datenbank l&ouml;schen wollen, so m&uuml;ssen Sie zuerst die Benutzer bearbeiten, die die fragliche Datenbank benutzen, und sie so &auml;ndern, dass sie eine andere Datenbank benutzen.',
   'If you want to set up the authentication database yourself then log in to the administration panel. kivitendo will then create the database and tables for you.' => 'Wenn Sie die Authentifizierungs-Datenbank selber einrichten wollen, so melden Sie sich im Administrationsbereich an. kivitendo wird dann die Datenbank und die erforderlichen Tabellen für Sie anlegen.',
+  'If your old bins match exactly Bins in the Warehouse CLICK on <b>AUTOMATICALLY MATCH BINS</b>.' => 'Falls die alte Lagerplatz-Beschreibung in Stammdaten genau mit einem Lagerplatz in einem vorhandenem Lager Ã¼bereinstimmt, KLICK auf <b>LAGERPLÄTZE AUTOMATISCH ZUORDNEN</b>',
   'Illegal characters have been removed from the following fields: #1' => 'Ungültige Zeichen wurden aus den folgenden Feldern entfernt: #1',
   'Image'                       => 'Grafik',
   'Import'                      => 'Import',
@@ -1082,6 +1099,7 @@ $self->{texts} = {
   'Invoices, Credit Notes & AR Transactions' => 'Rechnungen, Gutschriften & Debitorenbuchungen',
   'Is Searchable'               => 'Durchsuchbar',
   'Is this a summary account to record' => 'Sammelkonto für',
+  'It is not allowed that a summary account occurs in a drop-down menu!' => 'Ein Sammelkonto darf nicht in Aufklappmenüs aufgenommen werden!',
   '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.',
   'It is possible to do this automatically for some Buchungsgruppen, but not for all.' => 'Es ist m&ouml;glich, dies f&uuml;r einige, aber nicht f&uuml;r alle Buchungsgruppen automatisch zu erledigen.',
   'It is possible to do this automatically for some units, but for others the user has to chose the new unit.' => 'Das ist f&uuml;r einige Einheiten automatisch m&ouml;glich, aber bei anderen muss der Benutzer die neue Einheit ausw&auml;hlen.',
@@ -1201,6 +1219,7 @@ $self->{texts} = {
   'Marked as paid'              => 'Als bezahlt markiert',
   'Marked entries printed!'     => 'Markierte Einträge wurden gedruckt!',
   'Master Data'                 => 'Stammdaten',
+  'Master Data Bin Text Deleted' => 'Gelöschte Stammdaten Freitext-Lagerplätze',
   'Max. Dunning Level'          => 'höchste Mahnstufe',
   'May'                         => 'Mai',
   'May '                        => 'Mai',
@@ -1286,6 +1305,7 @@ $self->{texts} = {
   'No data was found.'          => 'Es wurden keine Daten gefunden.',
   'No databases have been found on this server.' => 'Auf diesem Server wurden keine Datenbanken gefunden.',
   'No datasets have been selected.' => 'Es wurden keine Datenbanken ausgew&auml;hlt.',
+  'No default currency'         => 'Keine Standardwährung',
   'No department has been created yet.' => 'Es wurde noch keine Abteilung erfasst.',
   'No dunnings have been selected for printing.' => 'Es wurden keine Mahnungen zum Drucken ausgew&auml;hlt.',
   'No entries were found which had no unit assigned to them.' => 'Es wurden keine Eintr&auml;ge gefunden, denen keine Einheit zugeordnet war.',
@@ -1299,6 +1319,7 @@ $self->{texts} = {
   'No problems were recognized.' => 'Es wurden keine Probleme gefunden.',
   'No report with id #1'        => 'Es gibt keinen Report mit der Id #1',
   'No shipto selected to delete' => 'Keine Lieferadresse zum Löschen ausgewählt',
+  'No summary account'          => 'Kein Sammelkonto',
   'No transaction selected!'    => 'Keine Transaktion ausgewählt',
   'No transfers were executed in this export.' => 'In diesem SEPA-Export wurden keine Ãœberweisungen ausgeführt.',
   'No unknown units where found.' => 'Es wurden keine unbekannten Einheiten gefunden.',
@@ -1371,11 +1392,13 @@ $self->{texts} = {
   'Orders / Delivery Orders deleteable' => 'Aufträge / Lieferscheine löschbar',
   'Orientation'                 => 'Seitenformat',
   'Orphaned'                    => 'Nie benutzt',
+  'Orphaned currencies'         => 'Verwaiste Währungen',
   'Other users\' follow-ups'    => 'Wiedervorlagen anderer Benutzer',
   'Other values are ignored.'   => 'Andere Eingaben werden ignoriert.',
   'Others'                      => 'Andere',
   'Otherwise all users will only have access to their own settings.' => 'Andernfalls haben alle Benutzer nur Zugriff auf ihre Einstellungen.',
   'Otherwise the variable is only available for printing.' => 'Andernfalls steht die Variable nur beim Ausdruck zur Verfügung.',
+  'Otherwise you can simply check create warehouse and bins and define a name for the warehouse (Bins will be created automatically) and then continue' => 'Andernfalls einfach Lager und Lagerplätze erschaffen anhaken und einen Namen für das Lager vergeben (Lagerplätze werden dann automatisch hinzugefügt) danach auf weiter',
   'Out of balance transaction!' => 'Buchung ist nicht ausgeglichen!',
   'Out of balance!'             => 'Summen stimmen nicht berein!',
   'Output Number Format'        => 'Zahlenformat (Ausgabe)',
@@ -1454,6 +1477,7 @@ $self->{texts} = {
   '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 currency you are working with.' => 'Bitte geben Sie die Währung an, mit der Sie arbeiten.',
   '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&uuml;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.',
@@ -1615,6 +1639,7 @@ $self->{texts} = {
   'Removed spoolfiles!'         => 'Druckdateien entfernt!',
   'Removing marked entries from queue ...' => 'Markierte Einträge werden von der Warteschlange entfernt ...',
   'Rename the group'            => 'Gruppe umbenennen',
+  'Replace the orphaned currencies by other not orphaned currencies. To do so, please delete the currency in the textfields above and replace it by another currency. You could loose or change unintentionally exchangerates. Go on very carefully since you could destroy transactions.' => 'Ersetze die Währungen durch andere gültige Währungen. Wenn Sie sich hierfür entscheiden, ersetzen Sie bitte alle Währungen, die oben angegeben sind, durch Währungen, die in Ihrem System ordnungsgemäß eingetragen sind. Alle eingetragenen Wechselkurse für die verwaiste Währung werden dabei gelöscht. Bitte gehen Sie sehr vorsichtig vor, denn die betroffenen Buchungen können unter Umständen kaputt gehen.',
   'Report Positions'            => 'Berichte',
   'Report about warehouse contents' => 'Lagerbestand anzeigen',
   'Report about warehouse transactions' => 'Lagerbuchungen anzeigen',
@@ -1925,9 +1950,12 @@ $self->{texts} = {
   'Text variables: \'MAXLENGTH=n\' sets the maximum entry length to \'n\'.' => 'Textzeilen: \'MAXLENGTH=n\' setzt eine Maximall&auml;nge von n Zeichen.',
   'Text, text field and number variables: The default value will be used as-is.' => 'Textzeilen, Textfelder und Zahlenvariablen: Der Standardwert wird so wie er ist &uuml;bernommen.',
   'That export does not exist.' => 'Dieser Export existiert nicht.',
+  'That is why kivitendo could not find a default currency.' => 'Daher konnte kivitendo keine Standardwährung finden.',
   '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 AP transaction #1 has been deleted.' => 'Die Kreditorenbuchung #1 wurde gelöscht.',
   'The AR transaction #1 has been deleted.' => 'Die Debitorenbuchung #1 wurde gelöscht.',
+  'The Bins in Inventory were only a information text field.' => 'Die Lagerplätze unter Stammdaten/Waren, sind nur ein informatives Textfeld.',
+  'The Bins in master data were only a information text field.' => 'Die Lagerplätze unter Stammdaten/Waren, sind nur ein informatives Textfeld.',
   'The GL transaction #1 has been deleted.' => 'Die Dialogbuchung #1 wurde gelöscht.',
   'The LDAP server "#1:#2" is unreachable. Please check config/kivitendo.conf.' => 'Der LDAP-Server "#1:#2" ist nicht erreichbar. Bitte &uuml;berpr&uuml;fen Sie die Angaben in config/kivitendo.conf.',
   'The SEPA export has been created.' => 'Der SEPA-Export wurde erstellt',
@@ -2004,6 +2032,7 @@ $self->{texts} = {
   'The follow-up date is missing.' => 'Das Wiedervorlagedatum fehlt.',
   'The following Buchungsgruppen have already been created:' => 'Die folgenden Buchungsgruppen wurden bereits angelegt:',
   'The following Datasets need to be updated' => 'Folgende Datenbanken müssen aktualisiert werden',
+  'The following currencies have been used, but they are not defined:' => 'Die folgenden Währungen wurden benutzt, sind aber nicht ordnungsgemäß in der Datenbank eingetragen:',
   'The following drafts have been saved and can be loaded.' => 'Die folgenden Entw&uuml;rfe wurden gespeichert und k&ouml;nnen geladen werden.',
   'The following old files whose settings have to be merged manually into the new configuration file "config/kivitendo.conf" still exist:' => 'Es existieren noch die folgenden alten Dateien, deren Einstellungen manuell in die neue Konfiguratsdatei "config/kivitendo.conf" migriert werden müssen:',
   'The following transaction contains wrong taxes:' => 'Die folgende Buchung enthält falsche Steuern:',
@@ -2100,6 +2129,8 @@ $self->{texts} = {
   'There are #1 more open invoices for this customer with other currencies.' => 'Es gibt #1 weitere offene Rechnungen für diesen Kunden, die in anderen Währungen ausgestellt wurden.',
   '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 Bins defined in your Inventory.' => 'Unter Stammdaten/Waren sind Lagerplätze defininert',
+  'There are Bins defined in your master data.' => 'Unter Stammdaten/Waren sind Lagerplätze defininert',
   '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 double partnumbers in your database.' => 'In ihrer Datenbank befinden sich mehrfach vergebene Artikelnummern.',
   'There are entries in tax where taxkey is NULL.' => 'In der Datenbank sind Steuern ohne Steuerschlüssel vorhanden (in der Tabelle tax Spalte taxkey).',
@@ -2109,8 +2140,10 @@ $self->{texts} = {
   '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&auml;lt momentan keine Eintr&auml;ge.',
+  'There are several options you can handle this problem, please select one:' => 'Bitte wählen Sie eine der folgenden Optionen, um mit dem Problem umzugehen:',
   'There are still entries in the database for which no unit has been assigned.' => 'Es gibt noch Eintr&auml;ge in der Datenbank, f&uuml;r die keine Einheit zugeordnet ist.',
   '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 undefined currencies in your system.' => 'In Ihrer Datenbank wurden Währungen benutzt, die nicht ordnungsgemäß in den Währungen eingetragen wurden.',
   '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.',
@@ -2132,9 +2165,12 @@ $self->{texts} = {
   'This customer number is already in use.' => 'Diese Kundennummer wird bereits verwendet.',
   'This feature especially prevents mistakes by mixing up prior tax and sales tax.' => 'Dieses Feature vermeidet insbesondere Verwechslungen von Umsatz- und Vorsteuer.',
   'This group will be called &quot;Full Access&quot;.' => 'Diese Gruppe wird &quot;Vollzugriff&quot; genannt.',
+  'This has been changed in this version, therefore please change the "old" bins to some real warehouse bins.' => 'Das wurde in dieser Version umgestellt, bitte Ã¤ndern Sie die Freitext-Lagerplätze auf wirkliche Lagerplätze.',
+  'This has been changed in this version.' => 'Ab dieser Version ist dies nicht mehr so.',
   'This installation uses an unknown chart of accounts (&quot;#1&quot;). This database upgrade cannot create standard buchungsgruppen automatically.' => 'Diese Installation benutzt einen unbekannten Kontenrahmen (&quot;#1&quot;). Dieses Datenbankupgrade kann die Standardbuchungsgruppen nicht automatisch anlegen.',
   '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 &Auml;nderungen vorgenommen!',
   'This is a very critical problem.' => 'Dieses Problem ist sehr schwerwiegend.',
+  'This is the default bin for parts' => 'Standard-Lagerplatz für Stammdaten/Waren',
   'This list is capped at 15 items to keep it fast. If you need a full list, please use reports.' => 'Diese Liste ist auf 15 Zeilen begrenzt. Wenn Sie eine vollständige Liste benötigen, erstellen Sie bitte einen Bericht.',
   'This means that the user has created an AP transaction and chosen a taxkey for sales taxes, or that he has created an AR transaction and chosen a taxkey for input taxes.' => 'Das bedeutet, dass ein Benutzer eine Kreditorenbuchung angelegt und in ihr einen Umsatzsteuer-Steuerschlüssel verwendet oder eine Debitorenbuchung mit Vorsteuer-Steuerschlüssel angelegt hat.',
   'This module can help you identify and correct such entries by analyzing the general ledger and presenting you likely solutions but also allowing you to fix problems yourself.' => 'Dieses Modul kann Ihnen helfen, problematische Einträge im Hauptbuch zu identifizieren und teilweise zu beheben. Dabei werden je nach Problem mögliche Lösungen aufgezeigt, wobei Sie die entscheiden können, welche Probleme automatisch gelöst werden sollen.',
@@ -2148,6 +2184,7 @@ $self->{texts} = {
   '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.',
   'This upgrade script tries to map all existing units in the database to the newly created units.' => 'Dieses Update-Script versucht, alle bestehenden Einheiten automatisch in die neuen Einheiten umzuwandeln.',
   'This vendor number is already in use.' => 'Diese Lieferantennummer wird bereits verwendet.',
+  'Three Options:'              => 'Drei Optionen:',
   'Time period for the analysis:' => 'Analysezeitraum:',
   'Timestamp'                   => 'Uhrzeit',
   'Title'                       => 'Titel',
@@ -2218,6 +2255,7 @@ $self->{texts} = {
   'Unbalanced Ledger'           => 'Bilanzfehler',
   'Unchecked custom variables will not appear in orders and invoices.' => 'Unmarkierte Variablen werden für diesen Artikel nicht in Aufträgen und Rechnungen angezeigt.',
   'Unfinished follow-ups'       => 'Nicht erledigte Wiedervorlagen',
+  'Unfortunately you have no warehouse defined.' => 'Leider, gibt es kein Lager in diesem Mandanten.',
   'Unit'                        => 'Einheit',
   'Unit (if missing or empty default unit will be used)' => 'Einheit (falls nicht vorhanden oder leer wird die Standardeinheit benutzt)',
   'Unit missing.'               => 'Die Einheit fehlt.',
@@ -2346,6 +2384,7 @@ $self->{texts} = {
   'You cannot create an invoice for delivery orders from different vendors.' => 'Sie können keine Rechnung aus Lieferscheinen von verschiedenen Lieferanten erstellen.',
   'You do not have the permissions to access this function.' => 'Sie verf&uuml;gen nicht &uuml;ber die notwendigen Rechte, um auf diese Funktion zuzugreifen.',
   'You have entered or selected the following shipping address for this customer:' => 'Sie haben die folgende Lieferadresse eingegeben oder ausgew&auml;hlt:',
+  'You have never worked with currencies.' => 'Sie haben noch nie  mit Währungen gearbeitet.',
   'You have not added bank accounts yet.' => 'Sie haben noch keine Bankkonten angelegt.',
   'You have not selected any delivery order.' => 'Sie haben keinen Lieferschein ausgewählt.',
   'You have not selected any export.' => 'Sie haben keinen Export ausgewählt.',
diff --git a/sql/Pg-upgrade2/add_warehouse_defaults.sql b/sql/Pg-upgrade2/add_warehouse_defaults.sql
new file mode 100644 (file)
index 0000000..0cc00c8
--- /dev/null
@@ -0,0 +1,9 @@
+-- @tag: add_warehouse_defaults
+-- @description: Standardlager und Lagerplatz in der Tabelle defaults. Sowie als ID-Verknüpfung in parts
+-- @depends: release_3_0_0
+-- @charset: utf-8
+ALTER TABLE defaults ADD COLUMN warehouse_id integer references warehouse(id);
+ALTER TABLE defaults add column bin_id integer references bin(id);
+ALTER TABLE parts ADD COLUMN warehouse_id integer references warehouse(id);
+ALTER TABLE parts add column bin_id integer references bin(id);
+
index bb45209..ab4a017 100644 (file)
@@ -11,10 +11,10 @@ use parent qw(SL::DBUpgrade2::Base);
 sub convert_to_date {
   my ($self, $str) = @_;
 
-  return '' if !$str;
+  return '' if !$str || ($str =~ m/00.*00.*00.*00/); # 0000-00-00 may be present in old databases.
 
   my $sth = $self->dbh->prepare('SELECT ?::date AS date') or return undef;
-  $sth->execute($str)                               or return undef;
+  $sth->execute($str)                                     or return undef;
 
   return $sth->fetchrow_hashref->{date};
 }
diff --git a/sql/Pg-upgrade2/currencies.pl b/sql/Pg-upgrade2/currencies.pl
new file mode 100644 (file)
index 0000000..9a4a8fd
--- /dev/null
@@ -0,0 +1,217 @@
+# @tag: currencies
+# @description: Erstellt neue Tabelle currencies. Währungen können dann einfacher eingegeben und unkritisch geändert werden.
+# @depends: release_3_0_0 rm_whitespaces
+
+package SL::DBUpgrade2::currencies;
+
+use utf8;
+use strict;
+
+use parent qw(SL::DBUpgrade2::Base);
+
+
+sub run {
+  my ($self) = @_;
+  #Check wheather default currency exists
+  my $query = qq|SELECT curr FROM defaults|;
+  my ($currencies) = $self->dbh->selectrow_array($query);
+
+  if (length($currencies) == 0 and length($main::form->{defaultcurrency}) == 0){
+    print_no_default_currency();
+    return 2;
+  } else {
+    if (length($main::form->{defaultcurrency}) == 0){
+      $main::form->{defaultcurrency} = (split m/:/, $currencies)[0];
+    }
+  }
+  my @currency_array = grep {$_ ne '' } split m/:/, $currencies;
+
+  $query = qq|SELECT DISTINCT curr FROM ar
+              UNION
+              SELECT DISTINCT curr FROM ap
+              UNION
+              SELECT DISTINCT curr FROM oe
+              UNION
+              SELECT DISTINCT curr FROM customer
+              UNION
+              SELECT DISTINCT curr FROM delivery_orders
+              UNION
+              SELECT DISTINCT curr FROM exchangerate
+              UNION
+              SELECT DISTINCT curr FROM vendor|;
+
+  my $sth = $self->dbh->prepare($query);
+  $sth->execute || $self->dberror($query);
+
+  $main::form->{ORPHANED_CURRENCIES} = [];
+  my $is_orphaned;
+  my $rowcount = 0;
+  while (my $ref = $sth->fetchrow_hashref("NAME_lc")) {
+    next unless length($ref->{curr}) > 0;
+    $is_orphaned = 1;
+    foreach my $key (split(/:/, $currencies)) {
+      if ($ref->{curr} eq $key) {
+        $is_orphaned = 0;
+        last;
+      }
+    }
+    if ($is_orphaned) {
+     push @{ $main::form->{ORPHANED_CURRENCIES} }, $ref;
+     $main::form->{ORPHANED_CURRENCIES}[$rowcount]->{name} = "curr_$rowcount";
+     $rowcount++;
+    }
+  }
+
+  $sth->finish;
+
+  if (scalar @{ $main::form->{ORPHANED_CURRENCIES} } > 0 and not ($main::form->{continue_options})) {
+    print_orphaned_currencies();
+    return 2;
+  }
+
+  if ($main::form->{continue_options} eq 'break_up') {
+    return 0;
+  }
+
+  if ($main::form->{continue_options} eq 'insert') {
+    for my $i (0..($rowcount-1)){
+      push @currency_array, $main::form->{"curr_$i"};
+    }
+    create_and_fill_table($self, @currency_array);
+    return 1;
+  }
+
+  my $still_orphaned;
+  if ($main::form->{continue_options} eq 'replace') {
+    for my $i (0..($rowcount - 1)){
+      $still_orphaned = 1;
+      for my $item (@currency_array){
+        if ($main::form->{"curr_$i"} eq $item){
+          $still_orphaned = 0;
+          $query = qq|DELETE FROM exchangerate WHERE curr = '| . $main::form->{"old_curr_$i"} . qq|'|;
+          $self->db_query($query);
+          $query = qq|UPDATE ap SET curr = '| . $main::form->{"curr_$i"} . qq|' WHERE curr = '| . $main::form->{"old_curr_$i"} . qq|'|;
+          $self->db_query($query);
+          $query = qq|UPDATE ar SET curr = '| . $main::form->{"curr_$i"} . qq|' WHERE curr = '| . $main::form->{"old_curr_$i"} . qq|'|;
+          $self->db_query($query);
+          $query = qq|UPDATE oe SET curr = '| . $main::form->{"curr_$i"} . qq|' WHERE curr = '| . $main::form->{"old_curr_$i"} . qq|'|;
+          $self->db_query($query);
+          $query = qq|UPDATE customer SET curr = '| . $main::form->{"curr_$i"} . qq|' WHERE curr = '| . $main::form->{"old_curr_$i"} . qq|'|;
+          $self->db_query($query);
+          $query = qq|UPDATE delivery_orders SET curr = '| . $main::form->{"curr_$i"} . qq|' WHERE curr = '| . $main::form->{"old_curr_$i"} . qq|'|;
+          $self->db_query($query);
+          $query = qq|UPDATE vendor SET curr = '| . $main::form->{"curr_$i"} . qq|' WHERE curr = '| . $main::form->{"old_curr_$i"} . qq|'|;
+          $self->db_query($query);
+          last;
+        }
+      }
+      if ($still_orphaned){
+        $main::form->{continue_options} = '';
+        return do_update();
+      }
+    }
+    create_and_fill_table($self, @currency_array);
+    return 1;
+  }
+
+  #No orphaned currencies, so create table:
+  create_and_fill_table($self, @currency_array);
+  return 1;
+}; # end do_update
+
+sub create_and_fill_table {
+  my $self = shift;
+  #Create an fill table currencies:
+  my $query = qq|CREATE TABLE currencies (id   SERIAL        PRIMARY KEY,
+                                          name TEXT NOT NULL UNIQUE)|;
+  $self->db_query($query);
+  foreach my $item ( @_ ) {
+    $query = qq|INSERT INTO currencies (name) VALUES ('| . $item . qq|')|;
+    $self->db_query($query);
+  }
+
+  #Set default currency if no currency was chosen:
+  $query = qq|UPDATE ap SET curr = '| . $main::form->{"defaultcurrency"} . qq|' WHERE curr IS NULL or curr='';|;
+  $query .= qq|UPDATE ar SET curr = '| . $main::form->{"defaultcurrency"} . qq|' WHERE curr IS NULL or curr='';|;
+  $query .= qq|UPDATE oe SET curr = '| . $main::form->{"defaultcurrency"} . qq|' WHERE curr IS NULL or curr='';|;
+  $query .= qq|UPDATE customer SET curr = '| . $main::form->{"defaultcurrency"} . qq|' WHERE curr IS NULL or curr='';|;
+  $query .= qq|UPDATE delivery_orders SET curr = '| . $main::form->{"defaultcurrency"} . qq|' WHERE curr IS NULL or curr='';|;
+  $query .= qq|UPDATE vendor SET curr = '| . $main::form->{"defaultcurrency"} . qq|' WHERE curr IS NULL or curr='';|;
+  $query .= qq|DELETE FROM exchangerate WHERE curr IS NULL or curr='';|;
+  $self->db_query($query);
+
+  #Check wheather defaultcurrency is already in table currencies:
+  $query = qq|SELECT name FROM currencies WHERE name = '| . $main::form->{defaultcurrency} . qq|'|;
+  my ($insert_default) = $self->dbh->selectrow_array($query);
+
+  if (!$insert_default) {
+    $query = qq|INSERT INTO currencies (name) VALUES ('| . $main::form->{defaultcurrency} . qq|')|;
+    $self->db_query($query);
+  }
+
+  #Create a new columns currency_id and update with curr.id:
+  $query = qq|ALTER TABLE ap ADD currency_id INTEGER;
+              ALTER TABLE ar ADD currency_id INTEGER;
+              ALTER TABLE oe ADD currency_id INTEGER;
+              ALTER TABLE customer ADD currency_id INTEGER;
+              ALTER TABLE delivery_orders ADD currency_id INTEGER;
+              ALTER TABLE exchangerate ADD currency_id INTEGER;
+              ALTER TABLE vendor ADD currency_id INTEGER;
+              ALTER TABLE defaults ADD currency_id INTEGER;|;
+  $self->db_query($query);
+  #Set defaultcurrency:
+  $query = qq|UPDATE defaults SET currency_id= (SELECT id FROM currencies WHERE name = '| . $main::form->{defaultcurrency} . qq|')|;
+  $self->db_query($query);
+  $query = qq|UPDATE ap SET currency_id = (SELECT id FROM currencies c WHERE c.name = ap.curr);
+              UPDATE ar SET currency_id = (SELECT id FROM currencies c WHERE c.name = ar.curr);
+              UPDATE oe SET currency_id = (SELECT id FROM currencies c WHERE c.name = oe.curr);
+              UPDATE customer SET currency_id = (SELECT id FROM currencies c WHERE c.name = customer.curr);
+              UPDATE delivery_orders SET currency_id = (SELECT id FROM currencies c WHERE c.name = delivery_orders.curr);
+              UPDATE exchangerate SET currency_id = (SELECT id FROM currencies c WHERE c.name = exchangerate.curr);
+              UPDATE vendor SET currency_id = (SELECT id FROM currencies c WHERE c.name = vendor.curr);|;
+  $self->db_query($query);
+
+  #Drop column 'curr':
+  $query = qq|ALTER TABLE ap DROP COLUMN curr;
+              ALTER TABLE ar DROP COLUMN curr;
+              ALTER TABLE oe DROP COLUMN curr;
+              ALTER TABLE customer DROP COLUMN curr;
+              ALTER TABLE delivery_orders DROP COLUMN curr;
+              ALTER TABLE exchangerate DROP COLUMN curr;
+              ALTER TABLE vendor DROP COLUMN curr;
+              ALTER TABLE defaults DROP COLUMN curr;|;
+  $self->db_query($query);
+
+  #Set NOT NULL constraints:
+  $query = qq|ALTER TABLE ap ALTER COLUMN currency_id SET NOT NULL;
+              ALTER TABLE ar ALTER COLUMN currency_id SET NOT NULL;
+              ALTER TABLE oe ALTER COLUMN currency_id SET NOT NULL;
+              ALTER TABLE customer ALTER COLUMN currency_id SET NOT NULL;
+              ALTER TABLE delivery_orders ALTER COLUMN currency_id SET NOT NULL;
+              ALTER TABLE exchangerate ALTER COLUMN currency_id SET NOT NULL;
+              ALTER TABLE vendor ALTER COLUMN currency_id SET NOT NULL;
+              ALTER TABLE defaults ALTER COLUMN currency_id SET NOT NULL;|;
+  $self->db_query($query);
+
+  #Set foreign keys:
+  $query = qq|ALTER TABLE ap ADD FOREIGN KEY (currency_id) REFERENCES currencies(id);
+              ALTER TABLE ar ADD FOREIGN KEY (currency_id) REFERENCES currencies(id);
+              ALTER TABLE oe ADD FOREIGN KEY (currency_id) REFERENCES currencies(id);
+              ALTER TABLE customer ADD FOREIGN KEY (currency_id) REFERENCES currencies(id);
+              ALTER TABLE delivery_orders ADD FOREIGN KEY (currency_id) REFERENCES currencies(id);
+              ALTER TABLE exchangerate ADD FOREIGN KEY (currency_id) REFERENCES currencies(id);
+              ALTER TABLE vendor ADD FOREIGN KEY (currency_id) REFERENCES currencies(id);
+              ALTER TABLE defaults ADD FOREIGN KEY (currency_id) REFERENCES currencies(id);|;
+  $self->db_query($query);
+
+};
+
+sub print_no_default_currency {
+  print $main::form->parse_html_template("dbupgrade/no_default_currency");
+};
+
+sub print_orphaned_currencies {
+  print $main::form->parse_html_template("dbupgrade/orphaned_currencies");
+};
+
+1;
diff --git a/sql/Pg-upgrade2/default_bin_parts.pl b/sql/Pg-upgrade2/default_bin_parts.pl
new file mode 100644 (file)
index 0000000..89ec619
--- /dev/null
@@ -0,0 +1,120 @@
+# @tag: default_bin_parts
+# @description: Freitext Feld Lagerplatz nach Lager und Lagerplatz migrieren
+# @depends: release_3_0_0 add_warehouse_defaults
+
+package SL::DBUpgrade2::default_bin_parts;
+
+use strict;
+use utf8;
+use Data::Dumper;
+use SL::DBUtils;
+use parent qw(SL::DBUpgrade2::Base);
+
+sub run {
+  my ($self) = @_;
+  $::form->get_lists('warehouses' => { 'key'    => 'WAREHOUSES',
+                                     'bins'   => 'BINS', });
+  if (scalar @{ $::form->{WAREHOUSES} }) {
+    $::form->{warehouse_id} ||= $::form->{WAREHOUSES}->[0]->{id};
+    $::form->{bin_id}       ||= $::form->{WAREHOUSES}->[0]->{BINS}->[0]->{id};
+  } else {
+    $::form->{NO_WAREHOUSE} = 1;
+  }
+    $::form->{warehouse_id} = 0;    # 0 ist die ID für leere Option
+
+  if ( $::form->{'continued'} ) {
+    my $CREATE_BINS      = 0;
+    my $CREATE_WAREHOUSE = 0;
+    if (!defined($::form->{NO_WAREHOUSE}) && defined($::form->{create_new_bins}) && $::form->{warehouse_id_default}) {
+      $CREATE_BINS = 1;
+    }
+    if (defined($::form->{NO_WAREHOUSE}) && defined($::form->{create_new_bins}) && $::form->{new_warehouse}) {
+      $CREATE_WAREHOUSE = 1;
+      $CREATE_BINS      = 1;
+    }
+
+    # Lager anlegen
+    my $insert_warehouse_query = qq|INSERT into warehouse (description, invalid, sortkey) VALUES (?, 'false', 1) |;
+    my $prepared_insert_warehouse_query   = $self->dbh->prepare($insert_warehouse_query)   || $self->db_error($insert_warehouse_query);
+
+    # Lagerplatz anlegen
+    my $insert_bin_query = qq|INSERT into bin (description, warehouse_id) VALUES (?, ?) |;
+    my $prepared_insert_bin_query   = $self->dbh->prepare($insert_bin_query)   || $self->db_error($insert_bin_query);
+
+    # Lagerplatz aus Liste zuweisen
+    my $update_query = qq|UPDATE parts SET warehouse_id = ?, bin_id = ? WHERE id = ?|;
+    my $prepared_update_query   = $self->dbh->prepare($update_query)   || $self->db_error($update_query);
+
+
+    # gerade angelegten Lagerplatz zuweisen
+    my $update_new_bin_query = qq|UPDATE parts SET warehouse_id = (SELECT warehouse_id from bin where description = ?),
+                                           bin_id       = (SELECT id from bin where description       = ?)
+                                           WHERE id = ?|;
+    my $prepared_update_new_bin_query   = $self->dbh->prepare($update_new_bin_query)   || $self->db_error($update_new_bin_query);
+
+
+    # kein lager vorhanden, aber wir legen ein neues an.
+    if ($CREATE_WAREHOUSE && $CREATE_BINS) {
+      $prepared_insert_warehouse_query->execute($::form->{new_warehouse}) || $self->db_error($insert_warehouse_query);
+      $prepared_insert_warehouse_query->finish();
+      my $query = qq|SELECT id FROM warehouse LIMIT 1;|;
+      my $sth = $self->dbh->prepare($query);
+      $sth->execute || $::form->dberror($query);
+      $::form->{warehouse_id_default} = $sth->fetchrow_array();
+    }
+
+    foreach my $i (1 .. $::form->{rowcount}) {
+
+      # Best Case: Lagerplatz aus Liste gewählt
+      if ($::form->{"bin_id_$i"}) {
+        $prepared_update_query->execute($::form->{"warehouse_id_$i"}, $::form->{"bin_id_$i"}, $::form->{"partid_$i"}) || $self->db_error($update_query);
+      } elsif ($CREATE_BINS) {
+        # Lager vorhanden, bzw. vorher erstellt.  alte bins automatisch hinzufügen und zum Standardlagerplatz verdrahten
+        $prepared_insert_bin_query->execute($::form->{"bin_$i"}, $::form->{warehouse_id_default}) || $self->db_error($insert_bin_query);
+        $prepared_update_new_bin_query->execute($::form->{"bin_$i"}, $::form->{"bin_$i"}, $::form->{"partid_$i"}) || $self->db_error($update_new_bin_query);
+      }
+    }
+    $prepared_insert_bin_query->finish();
+    $prepared_update_new_bin_query->finish();
+    $prepared_update_query->finish();
+    $::form->{FINISH} = 1;
+    # das alte textfeld entfernen
+    #my $query = qq|ALTER TABLE parts drop COLUMN bin|;
+    #$self->db_query($query);
+    #return 1;
+  }
+
+  my $query = qq|SELECT id, partnumber, description, bin
+                   FROM parts pa
+                   WHERE '' <> NULLIF ( bin, '')
+                   ORDER BY partnumber;|;
+
+  my $sth = $self->dbh->prepare($query);
+  $sth->execute || $::form->dberror($query);
+
+  $::form->{PARTS} = [ selectall_hashref_query($::form, $self->dbh, $query) ];
+
+  if ( (scalar @{ $::form->{PARTS} } > 0 ) && !$::form->{NO_WAREHOUSE} && !$::form->{FINISH} )  {
+    &print_error_message;
+    return 2;
+  } elsif ( (scalar @{ $::form->{PARTS} } > 0 ) && $::form->{NO_WAREHOUSE} && !$::form->{FINISH} ) {
+    &print_error_message_no_warehouse;
+    return 2;
+  }
+  # das alte textfeld entfernen
+  # hier nochmal, da oben schon ein return 1 gesetzt ist
+  my $query = qq|ALTER TABLE parts drop COLUMN bin|;
+  $self->db_query($query);
+  return 1;
+}
+
+sub print_error_message {
+  print $::form->parse_html_template("dbupgrade/default_bin_parts");
+}
+
+sub print_error_message_no_warehouse {
+  print $::form->parse_html_template("dbupgrade/default_bin_parts_no_warehouse");
+}
+
+
+1;
index 81c0241..4146894 100644 (file)
@@ -8,6 +8,8 @@ use utf8;
 
 use parent qw(SL::DBUpgrade2::Base);
 
+use SL::DBUtils;
+
 sub run {
   my ($self) = @_;
 
@@ -21,7 +23,7 @@ sub run {
     $self->dbh->commit();
   }
 
-  my $query = qq|SELECT id, partnumber, description, unit, notes, assembly, ean, inventory_accno_id
+  my $query = qq|SELECT id, partnumber, description, unit, notes, assembly, ean, inventory_accno_id, obsolete
                    FROM parts pa
                    WHERE (SELECT COUNT(*)
                           FROM parts p
@@ -29,14 +31,7 @@ sub run {
                           > 1
                    ORDER BY partnumber;|;
 
-  my $sth = $self->dbh->prepare($query);
-  $sth->execute || $::form->dberror($query);
-
-  $::form->{PARTS} = [];
-  while (my $ref = $sth->fetchrow_hashref("NAME_lc")) {
-    map {$ref->{$_} = $::locale->{iconv_utf8}->convert($ref->{$_})} keys %$ref;
-    push @{ $::form->{PARTS} }, $ref;
-  }
+  $::form->{PARTS} = [ selectall_hashref_query($::form, $self->dbh, $query) ];
 
   if ( scalar @{ $::form->{PARTS} } > 0 ) {
     &print_error_message;
diff --git a/sql/Pg-upgrade2/rm_whitespaces.pl b/sql/Pg-upgrade2/rm_whitespaces.pl
new file mode 100644 (file)
index 0000000..c63758a
--- /dev/null
@@ -0,0 +1,31 @@
+# @tag: rm_whitespaces
+# @description: Entfernt mögliche Leerzeichen am Anfang und Ende jeder Währung
+# @depends: release_3_0_0
+
+package SL::DBUpgrade2::rm_whitespaces;
+
+use parent qw(SL::DBUpgrade2::Base);
+
+use utf8;
+use strict;
+
+sub run {
+  my ($self) = @_;
+
+  my $query;
+
+  foreach my $table (qw(ar ap oe customer delivery_orders exchangerate vendor)){
+    $self->db_query(qq|UPDATE ${table} SET curr=BTRIM(curr)|)
+  }
+
+  $query = qq|SELECT curr FROM defaults|;
+  my ($curr)     = $self->dbh->selectrow_array($query);
+
+  $curr  =~ s/ //g;
+
+  $query = qq|UPDATE defaults SET curr = '$curr'|;
+  $self->db_query($query);
+  return 1;
+};
+
+1;
index 741c6ad..5fc8f32 100644 (file)
@@ -1,6 +1,6 @@
 # @tag: steuerfilterung
 # @description: Steuern in Dialogbuchungen filtern.
-# @depends: release_3_0_0
+# @depends: release_3_0_0 tax_constraints
 package SL::DBUpgrade2::steuerfilterung;
 
 use strict;
index 23d9b84..cc2ccd2 100644 (file)
@@ -193,20 +193,19 @@ SQL
   $sth->finish;
 
   for my $i (0 .. $rowcount-1){
-    $query= qq|
+    $query= <<SQL;
       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  = | . $::form->{TAXKEYS}[$i]->{chart_id} . qq|
-                        AND   startdate = '| . $::form->{TAXKEYS}[$i]->{startdate} . qq|'
-                        LIMIT 1)
-|;
+      WHERE (tk1.chart_id  = ?)
+        AND (tk1.startdate = ?)
+        AND (tk1.id <> (
+          SELECT id
+          FROM taxkeys
+          WHERE (chart_id  = ?)
+          AND   (startdate = ?)
+          LIMIT 1))
+SQL
 
-    $self->db_query($query);
+    $self->db_query($query, bind => [ ($::form->{TAXKEYS}[$i]->{chart_id}, $::form->{TAXKEYS}[$i]->{startdate}) x 2 ]);
   }
 
   #END CHECK OF taxkeys
index 1bc3a00..c72c33f 100644 (file)
@@ -1,12 +1,14 @@
 use lib 't';
 
-use Test::More tests => 18;
+use Test::More tests => 23;
 use Test::Deep;
 use Data::Dumper;
 
 use_ok 'Support::TestSetup';
 use_ok 'SL::Controller::Helper::ParseFilter';
 
+use SL::DB::OrderItem;
+
 undef *::any; # Test::Deep exports any (for junctions) and MoreCommon exports any (like in List::Moreutils)
 
 Support::TestSetup::login();
@@ -140,7 +142,7 @@ test {
   ]
 }, {
   'sellprice:number' => [ '123,4', '2,34', '0,4' ],
-  'sellprice_number' => [ '123,4', '2,34', '0,4' ],
+  'sellprice_number_' => { '123,4' => 1, '2,34' => 1, '0,4' => 1 },
 }, 'laundering with array', target => 'filter';
 
 my %args = (
@@ -190,3 +192,58 @@ test {
   with_objects => bag('order.customer', 'order'),
 }, 'sub objects have to retain their prefix';
 
+### class filter dispatch
+#
+test {
+  name => 'Test',
+  whut => 'moof',
+}, {
+  query => bag(
+    name => 'Test',
+    whut => 'moof'
+  ),
+}, 'object test simple', class => 'SL::DB::Manager::Part';
+
+test {
+  'type' => 'assembly',
+}, {
+  query => [
+    'assembly' => 1
+  ],
+}, 'object test without prefix', class => 'SL::DB::Manager::Part';
+
+test {
+  'part.type' => 'assembly',
+}, {
+  query => [
+    'part.assembly' => 1
+  ],
+}, 'object test with prefix', class => 'SL::DB::Manager::OrderItem';
+
+test {
+  'type' => [ 'part', 'assembly' ],
+}, {
+  query => [
+    or => [
+     and => [ or => [ assembly => 0, assembly => undef ],
+              "!inventory_accno_id" => 0,
+              "!inventory_accno_id" => undef,
+     ],
+     assembly => 1,
+    ]
+  ],
+}, 'object test without prefix but complex value', class => 'SL::DB::Manager::Part';
+
+test {
+  'part.type' => [ 'part', 'assembly' ],
+}, {
+  query => [
+    or => [
+     and => [ or => [ 'part.assembly' => 0, 'part.assembly' => undef ],
+              "!part.inventory_accno_id" => 0,
+              "!part.inventory_accno_id" => undef,
+     ],
+     'part.assembly' => 1,
+    ]
+  ],
+}, 'object test with prefix but complex value', class => 'SL::DB::Manager::OrderItem';
index 3023833..088ed68 100644 (file)
@@ -299,7 +299,7 @@ is_deeply $csv->get_data, [ { description => 'Kaffee' } ], 'case insensitive hea
 #####
 
 $csv = SL::Helper::Csv->new(
-  file   => \"\x{FEFF}description\nKaffee",
+  file   => \"\x{EF}\x{BB}\x{BF}description\nKaffee",
   class  => 'SL::DB::Part',
   encoding => 'utf8',
 );
index 1f5da1a..21d6bf8 100644 (file)
@@ -56,25 +56,25 @@ automatisch berechnet.</small></p>
 <table width="100%">
 <tr align="left">
   <td class="text">Steuernummer: <%steuernummer%></td>
-  <td class="text" width="100px">&nbsp</td>
+  <td class="text" width="100px">&nbsp;</td>
   <td class="text" align="right">Datum (<%Datum_heute%>)</td>
 </tr>
 <tr>
-  <td class="text" colspan="3"><br /></td>
+  <td class="text" colspan="3"><br></td>
 </tr>
 <tr align="left">
   <td class="text">
-    Finanzamt <%FA_Name%><br />
-    <%FA_Strasse%><br />
-    <%FA_PLZ%> <%FA_Ort%><br />
+    Finanzamt <%FA_Name%><br>
+    <%FA_Strasse%><br>
+    <%FA_PLZ%> <%FA_Ort%><br>
     Fax: <%FA_FAX%>
   </td>
   <td class="text">&nbsp;</td>
   <td class="text">
-    Firma <%company%><br />
+    Firma <%company%><br>
     <%if company_street%>
-      <%company_street%><br />
-      <%company_city%><br />
+      <%company_street%><br>
+      <%company_city%><br>
     <%end company_street%>
     <%if not company_street%>
     <%address%><!--used Address-->
@@ -82,7 +82,7 @@ automatisch berechnet.</small></p>
   </td>
 </tr>
 <tr>
-  <td class="text" colspan="3"><br />
+  <td class="text" colspan="3"><br>
   </td>
 </tr>
 </table>
@@ -127,7 +127,7 @@ Umsatzsteuer-Vorauszahlung </b></td>
     </tr>
     <tr>
       <td class="text2">Steuerfreie Ums&auml;tze ohne
-Vorsteuerabzug. </b><br />Ums&auml;tze nach &sect; 4 Nr. 8 bis 20 UStG</td>
+Vorsteuerabzug. </b><br>Ums&auml;tze nach &sect; 4 Nr. 8 bis 20 UStG</td>
       <td class="spalte ausfuellen"><span class="nodis">(Spalte </span>48<span class="nodis">)</span></td>
       <td class="betrag ausfuellen" width="70"><%48%><br></td>
       <td class="spalte"><span class="nodis"></span></td>
@@ -426,10 +426,10 @@ Vorsteuerabzug. </b><br />Ums&auml;tze nach &sect; 4 Nr. 8 bis 20 UStG</td>
 </table>
 <%if FA_steuerberater%>
 <p>
-Steuerberater:<br />
-<%FA_steuerberater_name%><br />
-<%FA_steuerberater_street%><br />
-<%FA_steuerberater_city%><br />
+Steuerberater:<br>
+<%FA_steuerberater_name%><br>
+<%FA_steuerberater_street%><br>
+<%FA_steuerberater_city%><br>
 Tel:&nbsp;<%FA_steuerberater_tel%></p>
 <%end FA_steuerberater%>
 </body>
index 3e4c6e7..991bbc9 100644 (file)
      </td>
     </tr>
 
+    <tr>
+     <th align="right" nowrap>[% 'Default currency' | $T8 %]</th>
+     <td><input name="defaultcurrency" value="EUR"></td>
+     </td>
+   </tr>
+
     <tr>
      <th valign="top" align="right" nowrap>[% 'Create Chart of Accounts' | $T8 %]</th>
      <td>
index 38652b7..d3e4fe7 100644 (file)
@@ -88,9 +88,25 @@ $(function() {
 [% IF ChartTypeIsAccount %]
 <fieldset class="DEPENDS ON charttype BEING A">
   <legend>[% 'Is this a summary account to record' | $T8 %]</legend>
-    [% L.checkbox_tag('AR', value => 'AR', checked => AR, class => 'checkbox', disabled => AccountIsPosted) %] &nbsp;[% 'AR' | $T8 %]
-    [% L.checkbox_tag('AP', value => 'AP', checked => AP, class => 'checkbox', disabled => AccountIsPosted) %] &nbsp;[% 'AP' | $T8 %]
-    [% L.checkbox_tag('IC', value => 'IC', checked => IC, class => 'checkbox', disabled => AccountIsPosted) %] &nbsp;[% 'Inventory' | $T8 %]
+        [% L.radio_button_tag('summary_account',
+          value => 'AR',
+          disabled => AccountIsPosted,
+          checked  => AR) %]
+        &nbsp;[% 'AR' | $T8 %]
+        [% L.radio_button_tag('summary_account',
+          value => 'AP',
+          disabled => AccountIsPosted,
+          checked  => AP) %]
+        &nbsp;[% 'AP' | $T8 %]
+        [% L.radio_button_tag('summary_account',
+          value => 'IC',
+          disabled => AccountIsPosted,
+          checked  => IC) %]
+        &nbsp;[% 'Inventory' | $T8 %]
+        [% L.radio_button_tag('summary_account',
+          value => '',
+          disabled => AccountIsPosted) %]
+        &nbsp;[% 'No summary account' | $T8 %]
 
 [% IF AccountIsPosted %]
     [% IF AR %] [% L.hidden_tag('AR', 'AR') %] [% END %]
index 248953c..c8c91f6 100644 (file)
     </tr>
 
     <tr>
-     <th align="right">[% 'Currencies' | $T8 %] <sup>(1)</sup></th>
-     <td colspan="3"><input name="curr" size="20" value="[% HTML.escape(defaults_curr) %]"></td>
+     <th align="right">[% 'Default currency' | $T8 %]</th>
+     <td colspan="3">[% HTML.escape(defaultcurrency) %]</td>
+    </tr>
+
+    <tr>
+     <input type="hidden" name="rowcount" value="[% CURRENCIES.size %]">
+     <th align="right">[% 'Currencies' | $T8 %]</th>
+     <td colspan="3">[% FOREACH row = CURRENCIES %]<input name="curr_[% loop.count %]" size="3" value="[% HTML.escape(row.curr) %]">
+                                                   <input type=hidden name="old_curr_[% loop.count %]" value="[% HTML.escape(row.curr) %]">
+                     [% END %]
+                     <input name="new_curr" size="3" value="">
+     </td>
     </tr>
 
     <tr>
 
   <hr height="3" noshade>
 
-  <p>
-   (1) [% 'Enter the abbreviations separated by a colon (i.e CAD:USD:EUR) for your native and foreign currencies' | $T8 %]
-       [% 'IMPORTANT NOTE: You cannot safely change currencies, IF you have already booking entries!' | $T8 %]
   </p>
  </form>
index 6a3c0f5..bcbf845 100644 (file)
@@ -1,5 +1,6 @@
 [%- USE T8 %][%- USE L %][% USE LxERP %]
 
+[% PROCESS 'common/select_warehouse_bin.html'  WAREHOUSES=SELF.WAREHOUSES warehouse_id=SELF.warehouse_id bin_id=SELF.bin_id %]
 <h1>[% title | html %]</h1>
 
 [% PROCESS 'common/flash.html' %]
      [% 'Any stock contents containing a best before date will be impossible to stock out otherwise.' | $T8 %]
    </td>
  </tr>
+ <tr> </tr>
+ <tr> </tr>
+ <tr>
+  <th align="right" nowrap="true">[% 'Default Warehouse' | $T8 %]</th>
+  <td>
+   <select name="warehouse_id" onchange="warehouse_selected(warehouses[this.selectedIndex]['id'], 0)">
+    [%- FOREACH warehouse = SELF.WAREHOUSES %]
+      <option value="[% HTML.escape(warehouse.id) %]"[% IF SELF.warehouse_id == warehouse.id %] selected[% END %]>[% warehouse.description %]</option>
+    [%- END %]
+   </select>
+ </td>
+  <td>
+     [% 'This is the default bin for parts' | $T8 %]<br>
+  </td>
+ </tr>
+ <tr>
+  <th align="right" nowrap="true">[% 'Default Bin' | $T8 %]</th>
+  <td><select id="bin_id" name="bin_id"></select></td>
+ </tr>
+
 
  <tr class='listheading'>
    <th colspan="3">[% 'Weight' | $T8 %]</th>
diff --git a/templates/webpages/common/select_warehouse_bin.html b/templates/webpages/common/select_warehouse_bin.html
new file mode 100644 (file)
index 0000000..f7d854a
--- /dev/null
@@ -0,0 +1,57 @@
+[%- USE T8 %]
+[%- USE HTML %]
+[%- USE LxERP %]
+[%- USE JavaScript -%]
+ <script type="text/javascript" src="js/common.js"></script>
+ <script type="text/javascript" src="js/parts_language_selection.js"></script>
+ <script type="text/javascript">
+  <!--
+      warehouses = new Array();
+      [%- USE WAREHOUSES_it = Iterator(WAREHOUSES) %][%- FOREACH warehouse = WAREHOUSES_it %]
+      warehouses[[% WAREHOUSES_it.count - 1 %]] = new Array();
+      warehouses[[% WAREHOUSES_it.count - 1 %]]['id'] = [% warehouse.id %];
+      warehouses[[% WAREHOUSES_it.count - 1 %]]['bins'] = new Array();
+      [% USE BINS_it = Iterator(warehouse.BINS) %][% FOREACH bin = BINS_it %]
+      warehouses[[% WAREHOUSES_it.count - 1 %]]['bins'][[% BINS_it.count - 1 %]] = new Array();
+      warehouses[[% WAREHOUSES_it.count - 1 %]]['bins'][[% BINS_it.count - 1 %]]['description'] = "[% JavaScript.escape(bin.description) %]";
+      warehouses[[% WAREHOUSES_it.count - 1 %]]['bins'][[% BINS_it.count - 1 %]]['id'] = [% bin.id %];
+      [% END %]
+      [% END %]
+
+      function warehouse_selected(warehouse_id, bin_id) {
+        var control = document.getElementById("bin_id");
+
+        for (var i = control.options.length - 1; i >= 0; i--) {
+          control.options[i] = null;
+        }
+
+        var warehouse_index = 0;
+
+        for (i = 0; i < warehouses.length; i++)
+          if (warehouses[i]['id'] == warehouse_id) {
+            warehouse_index = i;
+            break;
+          }
+
+        var warehouse = warehouses[warehouse_index];
+        var bin_index = 0;
+
+        for (i = 0; i < warehouse['bins'].length; i++)
+          if (warehouse['bins'][i]['id'] == bin_id) {
+            bin_index = i;
+            break;
+          }
+
+        for (i = 0; i < warehouse['bins'].length; i++) {
+          control.options[i] = new Option(warehouse['bins'][i]['description'], warehouse['bins'][i]['id']);
+        }
+
+
+        control.options[bin_index].selected = true;
+      }
+
+      $(function() {
+        warehouse_selected([% warehouse_id %], [% bin_id %]);
+      })
+    -->
+ </script>
index 825a4b8..ff2d36b 100644 (file)
       <td>[% L.input_tag('bic', bic, maxlength=100, size=30) %]</td>
       [%- IF ALL_CURRENCIES.size %]
         <th align="right">[% 'Currency' | $T8 %]</th>
-        <td>[% L.select_tag('currency', ALL_CURRENCIES, default = currency, with_empty = 1) %]</td>
+        <td>[% L.select_tag('currency', ALL_CURRENCIES, default = currency) %]</td>
       [%- END %]
      </tr>
 
diff --git a/templates/webpages/dbupgrade/default_bin_parts.html b/templates/webpages/dbupgrade/default_bin_parts.html
new file mode 100644 (file)
index 0000000..eb0c549
--- /dev/null
@@ -0,0 +1,154 @@
+[%- USE T8 %]
+[%- USE HTML %]
+[%- USE LxERP %]
+[%- USE JavaScript %]
+ <script type="text/javascript" src="js/common.js"></script>
+ <script type="text/javascript" src="js/parts_language_selection.js"></script>
+ <script type="text/javascript">
+  <!--
+     warehouses = new Array();
+     [%- USE WAREHOUSES_it = Iterator(WAREHOUSES) %][%- FOREACH warehouse = WAREHOUSES_it %]
+      warehouses[[% WAREHOUSES_it.count - 1 %]] = new Array();
+      warehouses[[% WAREHOUSES_it.count - 1 %]]['id'] = [% warehouse.id %];
+      warehouses[[% WAREHOUSES_it.count - 1 %]]['bins'] = new Array();
+      [% USE BINS_it = Iterator(warehouse.BINS) %][% FOREACH bin = BINS_it %]
+      warehouses[[% WAREHOUSES_it.count - 1%]]['bins'][[% BINS_it.count - 1 %]] = new Array();
+      warehouses[[% WAREHOUSES_it.count - 1%]]['bins'][[% BINS_it.count - 1 %]]['description'] = "[% JavaScript.escape(bin.description) %]";
+      warehouses[[% WAREHOUSES_it.count - 1%]]['bins'][[% BINS_it.count - 1 %]]['id'] = [% bin.id %];
+      [% END %]
+      [% END %]
+      //var rowcount = [% rowcount %];
+      //var rowcount = 3; //[% rowcount %];
+      function warehouse_selected(warehouse_id, bin_id, loop) {
+        var control = document.getElementById("bin_id_" + loop);
+
+        for (var i = control.options.length - 1; i >= 0; i--) {
+          control.options[i] = null;
+        }
+
+        var warehouse_index = 0;
+
+        for (i = 0; i < warehouses.length; i++)
+          if (warehouses[i]['id'] == warehouse_id) {
+            warehouse_index = i;
+            break;
+          }
+
+        var warehouse = warehouses[warehouse_index];
+        var bin_index = 0;
+
+        for (i = 0; i < warehouse['bins'].length; i++)
+          if (warehouse['bins'][i]['id'] == bin_id) {
+            bin_index = i;
+            break;
+          }
+
+        for (i = 0; i < warehouse['bins'].length; i++) {
+          control.options[i] = new Option(warehouse['bins'][i]['description'], warehouse['bins'][i]['id']);
+        }
+
+
+        control.options[bin_index].selected = true;
+      }
+      function bin_match(rowcount) {
+        for (i = 1; i < rowcount + 1; i++) {              // Ã¼ber alle parts_id
+          var lagerplatz = document.getElementById("bin_" + i).value;
+          var control = document.getElementById("bin_id_" + i);
+          var bin_index = 0;
+          //alert(lagerplatz);
+          for (j = 0; j < warehouses.length; j++) {       // Ã¼ber alle lager
+            var warehouse = warehouses[j];
+
+            for (k = 0; k < warehouse['bins'].length; k++) {       // Ã¼ber alle lagerplätze
+
+              if (lagerplatz == warehouse['bins'][k]['description']) {
+                //alert('ware ' + warehouse['bins'][k]['description']);
+                var lager = document.getElementById("warehouse_id_" + i);
+                lager.selectedIndex = j;
+                bin_index = k;
+                break;
+                /*var lagerplatz = document.getElementById("bin_id_" + i);
+                alert('lagerplatz ' + lagerplatz.value);
+                lagerplatz.selectedIndex = k; */
+              }
+
+            }
+          }
+        }
+        for (i = 0; i < warehouse['bins'].length; i++) {
+                control.options[i] = new Option(warehouse['bins'][i]['description'], warehouse['bins'][i]['id']);
+              }
+              control.options[bin_index].selected = true;
+
+
+      }
+
+
+      $(function() {
+        warehouse_selected([% warehouse_id %], [% bin_id %]);
+      })
+    -->
+ </script>
+
+
+
+<div class="listtop">[% '<b>Default Bins Migration !READ CAREFULLY!</b>' | $T8 %]</div>
+<form name="Form" method="post" action="login.pl">
+<input type="hidden" name="action" value="login">
+<input type="hidden" name="continued" value="1">
+
+
+
+<p>[% 'There are Bins defined in your Inventory.' | $T8 %]</p>
+<p>[% 'The Bins in Inventory were only a information text field.' | $T8 %]</p>
+<p>[% 'This has been changed in this version, therefore please change the "old" bins to some real warehouse bins.' | $T8 %]</p>
+<p>[% 'If your old bins match exactly Bins in the Warehouse CLICK on <b>AUTOMATICALLY MATCH BINS</b>.' | $T8 %]</p>
+<p>[% 'Otherwise you can simply check create warehouse and bins and define a name for the warehouse (Bins will be created automatically) and then continue'  | $T8 %]</p>
+
+<p>[% 'Please change the partnumber of the following parts and run the update again:' | $T8 %]</p>
+<table>
+  <tr>
+    <th class="listheading">[% 'Partnumber' | $T8 %]</th>
+    <th class="listheading">[% 'Description' | $T8 %]</th>
+    <th class="listheading">[% 'Bin' | $T8 %]</th>
+    <th class="listheading">[% 'Default Warehouse' | $T8 %]</th>
+    <th class="listheading">[% 'Default Bin' | $T8 %]</th>
+  </tr>
+
+  [% SET row_odd = '1' %][% FOREACH row = PARTS %]
+  <tr class="listrow[% IF row_odd %]1[% SET row_odd = '0' %][% ELSE %]0[% SET row_odd = '1' %][% END %]">
+   <td align="left"> [% HTML.escape(row.partnumber) %]</a></td>
+    <td align="left"> [% HTML.escape(row.description) %]</a></td>
+    <td align="right">[% HTML.escape(row.bin) %]
+    <input type="hidden" id="bin_[% loop.count %]" name="bin_[% loop.count %]" value="[% HTML.escape(row.bin) %]">
+    </td>
+    <td>
+    <input type="hidden" name='partid_[% loop.count %]' value='[% HTML.escape(row.id) %]'>
+     <select id="warehouse_id_[% loop.count %]" name="warehouse_id_[% loop.count %]" onchange="warehouse_selected(warehouses[this.selectedIndex]['id'], 0, [% loop.count %])">
+             [%- FOREACH warehouse = WAREHOUSES %]
+               <option value="[% HTML.escape(warehouse.id) %]"[% IF warehouse_id  == warehouse.id %] selected[% END %]>[% warehouse.description %]</option>
+             [%- END %]
+                <option value=""  [% IF warehouse_id  == 0 %] selected[% END %] ></option>
+     </select>
+    </td>
+    <td><select id="bin_id_[% loop.count %]" name="bin_id_[% loop.count %]"></select></td>
+  </tr>
+  [% SET rowcount = loop.count %]
+  [% END %]
+  <input type="hidden" name="rowcount" value="[% rowcount %]">
+  <tr><td colspan="5"><hr/></td></tr>
+  <tr><td colspan="5">
+         <input type="checkbox" name="create_new_bins"> [% '<b>Automatically create new bins</b> in the following warehouse if not selected in the list above' | $T8 %]
+          <select id="warehouse_id_default" name="warehouse_id_default">
+             [%- FOREACH warehouse = WAREHOUSES %]
+               <option value="[% HTML.escape(warehouse.id) %]"[% IF warehouse_id  == warehouse.id %] selected[% END %]>[% warehouse.description %]</option>
+             [%- END %]
+                <option value=""  [% IF warehouse_id  == 0 %] selected[% END %] ></option>
+         </select>
+      </td>
+  </tr>
+
+  <tr><td colspan="5"><hr/></td></tr>
+ <tr><td><input type="button" class="submit" onclick="history.back()" value="[% 'Back' | $T8 %]"> </td><td colspan="3" align="center"><b><input style="background-color:#FFEE66" type="button" value="[% 'AUTOMATICALLY MATCH BINS' | $T8 %]"  onclick="bin_match([% rowcount %])"></b> </td><td><input type="submit" value="[% 'Continue' | $T8 %]"></td></tr>
+</table>
+</form>
diff --git a/templates/webpages/dbupgrade/default_bin_parts_no_warehouse.html b/templates/webpages/dbupgrade/default_bin_parts_no_warehouse.html
new file mode 100644 (file)
index 0000000..23a7b63
--- /dev/null
@@ -0,0 +1,43 @@
+[%- USE T8 %]
+[%- USE HTML %]
+[%- USE LxERP %]
+
+<div class="listtop">[% '<b>Default Bins Migration !READ CAREFULLY!</b>' | $T8 %]</div>
+<form name="Form" method="post" action="login.pl">
+<input type="hidden" name="action" value="login">
+<input type="hidden" name="continued" value="1">
+
+<p>[% 'There are Bins defined in your master data.' | $T8 %]</p>
+<p>[% 'The Bins in master data were only a information text field.' | $T8 %]</p>
+<p>[% 'This has been changed in this version.' | $T8 %]</p>
+<p>[% 'Unfortunately you have no warehouse defined.' | $T8 %]</p>
+
+<p>[% 'Three Options:' | $T8 %]</p>
+<p>[% '<b> I DO NOT CARE</b> Please click continue and the following data (see list) will be deleted:' | $T8 %]</p>
+<p>[% '<b> I DO CARE!</b> Please click back and cancel the update and come back after there has been at least one warehouse defined with bin(s).:' | $T8 %]</p>
+<p>[% '<b> I DO CARE!</b> Please check create warehouse and bins and define a name for the warehouse (Bins will be created automatically) and then continue'  | $T8 %]</p>
+
+<table>
+  <tr>
+    <th class="listheading">[% 'Partnumber' | $T8 %]</th>
+    <th class="listheading">[% 'Description' | $T8 %]</th>
+    <th class="listheading">[% 'Master Data Bin Text Deleted' | $T8 %]</th>
+  </tr>
+
+  [% SET row_odd = '1' %][% FOREACH row = PARTS %]
+  <tr class="listrow[% IF row_odd %]1[% SET row_odd = '0' %][% ELSE %]0[% SET row_odd = '1' %][% END %]">
+   <td align="left"> [% HTML.escape(row.partnumber) %]</a></td>
+    <td align="left"> [% HTML.escape(row.description) %]</a></td>
+    <td align="right">[% HTML.escape(row.bin) %]
+    <input type="hidden" name='partid_[% loop.count %]' value='[% HTML.escape(row.id) %]'>
+    <input type="hidden" id="bin_[% loop.count %]" name="bin_[% loop.count %]" value="[% HTML.escape(row.bin) %]">
+   </tr>
+  [% SET rowcount = loop.count %]
+  [% END %]
+  <input type="hidden" name="rowcount" value="[% rowcount %]">
+<tr><td colspan="5"><hr/></td></tr>
+<tr><td colspan="5"><input type="checkbox" name="create_new_bins"> [% '<b>Automatically create new bins</b> in the following new warehouse ' | $T8 %] <input type="text" name="new_warehouse"></td></tr>
+<tr><td colspan="5"><hr/></td></tr>
+<tr><td><input type="button" class="submit" onclick="history.back()" value="[% 'Back' | $T8 %]"> </td><td colspan="3" align="center"><td><input style="background-color:#FA1400" type="submit" value="[% 'Continue' | $T8 %]"></td></tr>
+</table>
+</form>
index e5a7ec6..6466854 100644 (file)
@@ -1,5 +1,5 @@
 [%- USE T8 %]
-[% USE HTML %]<div class="listtop">[% 'Double partnumbers' | $T8 %]</div>
+[% USE HTML %][%- USE LxERP -%]<div class="listtop">[% 'Double partnumbers' | $T8 %]</div>
 
 <form name="Form" method="post" action="login.pl">
 <input type="hidden" name="action" value="login">
@@ -8,7 +8,7 @@
 <p>[% 'There are double partnumbers in your database.' | $T8 %]</p>
 <p>[% 'From this version on the partnumber of services, articles and assemblies have to be unique.' | $T8 %]</p>
 <p>[% 'So far you could use one partnumber for severel parts, for example a service and an article.' | $T8 %]</p>
-<p>[% 'Because the useability gets worth if one partnumber is used for several parts (for example if you are searching a position for an invoice), partnumbers should be unique.' | $T8 %]</p>
+<p>[% 'Because the useability gets worse if one partnumber is used for several parts (for example if you are searching a position for an invoice), partnumbers should be unique.' | $T8 %]</p>
 
 <p>[% 'Please change the partnumber of the following parts and run the update again:' | $T8 %]</p>
 <table>
@@ -19,6 +19,7 @@
     <th class="listheading">[% 'Notes' | $T8 %]</th>
     <th class="listheading">[% 'EAN' | $T8 %]</th>
     <th class="listheading">[% 'Service, assembly or part' | $T8 %]</th>
+    <th class="listheading">[% 'Obsolete' | $T8 %]</th>
   </tr>
 
   [% SET row_odd = '1' %][% FOREACH row = PARTS %]
@@ -30,6 +31,7 @@
     <td align="right">[% HTML.escape(row.notes) %]</td>
     <td align="right">[% HTML.escape(row.ean) %]</td>
     <td align="right">[% IF row.assembly %] [% 'assembly' | $T8 %] [% ELSE %] [% IF row.inventory_accno_id %] [% 'part' | $T8 %] [% ELSE %] [% 'service' | $T8 %] [% END %] [% END %]</td>
+    <td>[% IF row.obsolete %][%- LxERP.t8("Obsolete") %][%- ELSE %][%- LxERP.t8("Not obsolete") %][%- END %]</td>
   </tr>
   [% SET rowcount = loop.count %]
   [% END %]
diff --git a/templates/webpages/dbupgrade/no_default_currency.html b/templates/webpages/dbupgrade/no_default_currency.html
new file mode 100644 (file)
index 0000000..e5bbe5d
--- /dev/null
@@ -0,0 +1,16 @@
+[%- USE T8 %]
+[% USE HTML %]<div class="listtop">[% 'No default currency' | $T8 %]</div>
+
+<form name="Form" method="post" action="login.pl">
+<input type="hidden" name="action" value="login">
+
+<p>[% 'You have never worked with currencies.' | $T8 %]</p>
+<p>[% 'That is why kivitendo could not find a default currency.' | $T8 %]</p>
+<p>[% 'From this version on it is necessary to name a default value.' | $T8 %]</p>
+<p>[% 'Please enter the currency you are working with.' | $T8 %]</p>
+
+<input name="defaultcurrency" value="EUR">
+<input type="submit" value="[% 'Continue' | $T8 %]">
+
+</form>
+
diff --git a/templates/webpages/dbupgrade/orphaned_currencies.html b/templates/webpages/dbupgrade/orphaned_currencies.html
new file mode 100644 (file)
index 0000000..ef613d0
--- /dev/null
@@ -0,0 +1,43 @@
+[%- USE T8 %]
+[%- USE L %]
+[% USE HTML %]<div class="listtop">[% 'Orphaned currencies' | $T8 %]</div>
+
+<form name="Form" method="post" action="login.pl">
+<input type="hidden" name="action" value="login">
+<input type="hidden" name="defaultcurrency" value="[% HTML.escape(defaultcurrency) %]">
+
+<p>[% 'There are undefined currencies in your system.' | $T8 %]</p>
+<p>[% 'The following currencies have been used, but they are not defined:' | $T8 %]</p>
+[% FOREACH row = ORPHANED_CURRENCIES %]
+<input name="[% row.name %]" value="[% HTML.escape(row.curr) %]"><br>
+<input type="hidden" name="old_[% row.name %]" value="[% HTML.escape(row.curr) %]">
+[% END %]
+<p>[% 'There are several options you can handle this problem, please select one:' | $T8 %]</p>
+
+<table width="100%">
+
+<tr>
+<td>
+[% L.radio_button_tag('continue_options',
+    value => 'insert',
+    disabled => 0,
+    checked  => 0) %]
+&nbsp;[% '(recommended) Insert the used currencies in the system. You can simply change the name of the currencies by editing the textfields above. Do not use a name of a currency that is already in use.' | $T8 %] <br>
+[% L.radio_button_tag('continue_options',
+    value => 'replace',
+    disabled => 0,
+    checked  => 0) %]
+&nbsp;[% 'Replace the orphaned currencies by other not orphaned currencies. To do so, please delete the currency in the textfields above and replace it by another currency. You could loose or change unintentionally exchangerates. Go on very carefully since you could destroy transactions.' | $T8 %] <br>
+[% L.radio_button_tag('continue_options',
+    value => 'break_up',
+    disabled => 0,
+    checked  => 0) %]
+&nbsp;[% 'Break up the update and contact a service provider.' | $T8 %]
+</tr>
+</td>
+</table>
+
+<input type="submit" value="[% 'Continue' | $T8 %]">
+
+</form>
+
index 2bd7450..087e821 100644 (file)
@@ -45,9 +45,9 @@
   <tr>
    <th align="right">[% 'Type' | $T8 %]</th>
    <td>
-     [% L.checkbox_tag('filter.part.type.part',     checked=filter.part.type.part,     label=LxERP.t8('Part')) %]
-     [% L.checkbox_tag('filter.part.type.service',  checked=filter.part.type.service,  label=LxERP.t8('Service')) %]
-     [% L.checkbox_tag('filter.part.type.assembly', checked=filter.part.type.assembly, label=LxERP.t8('Assembly')) %]
+     [% L.checkbox_tag('filter.part.type[]', checked=filter.part.type_.part,     value='part',     label=LxERP.t8('Part')) %]
+     [% L.checkbox_tag('filter.part.type[]', checked=filter.part.type_.service,  value='service',  label=LxERP.t8('Service')) %]
+     [% L.checkbox_tag('filter.part.type[]', checked=filter.part.type_.assembly, value='assembly', label=LxERP.t8('Assembly')) %]
    </td>
   </tr>
  </table>
index 99b5db1..f371de1 100644 (file)
       }
 
       $(function(){
+        var bin_id_index       = 0;
+        var warehouse_id_index = 0;
         [%- USE STOCK_INFO_it = Iterator(STOCK_INFO) %][%- FOREACH si = STOCK_INFO_it %]
           // new si for wh [% si.warehouse_id %] bin [% si.bin_id %]
           [%- SET warehouse_selected = '0' %]
           [%- USE WAREHOUSES_it = Iterator(WAREHOUSES) %][%- FOREACH wh = WAREHOUSES_it %]
             // wh [% wh.id %]
+            [% IF HTML.escape(PART_INFO.warehouse_id) == wh.id %]
+              warehouse_id_index = [% WAREHOUSES_it.count - 1%];
+            [% END %]
             [%- USE BINS_it = Iterator(wh.BINS) %][%- FOREACH bin = BINS_it %]
               // bin [% bin.id %]
+              [% IF HTML.escape(PART_INFO.bin_id) == bin.id %]
+                bin_id_index = [% BINS_it.count - 1%];
+              [% END %]
               [%- IF bin.id == si.bin_id %]
                 warehouse_selected([% STOCK_INFO_it.count %], [% WAREHOUSES_it.count - 1 %], [% BINS_it.count - 1%]);
                 [%- SET warehouse_selected = '1' %]
@@ -43,7 +51,7 @@
             [%- END %]
           [%- END %]
           [%- UNLESS warehouse_selected %]
-          warehouse_selected([% STOCK_INFO_it.count %], 0);
+            warehouse_selected([% STOCK_INFO_it.count %], warehouse_id_index, bin_id_index);
           [%- END %]
         [%- END %]
       });
      <td>
       <select name="warehouse_id_[% loop.count %]" onchange="warehouse_selected([% loop.count %], this.selectedIndex)">
        [%- FOREACH wh = WAREHOUSES %]
-       <option value="[% HTML.escape(wh.id) %]"[% IF wh.id == row.warehouse_id %] selected[% END %]>[% HTML.escape(wh.description) %]</option>
+     <option value="[% HTML.escape(wh.id) %]"[% IF wh.id == row.warehouse_id %] selected[% END %] [% IF wh.id == PART_INFO.warehouse_id %] selected[% END %]>[% HTML.escape(wh.description) %]</option>
        [%- END %]
       </select>
      </td>
index ddf5511..fe18fe6 100644 (file)
@@ -39,6 +39,8 @@
      <td>
       <input type="hidden" name="new_id_[% loop.count %]" value="[% HTML.escape(part.id) %]">
       <input type="hidden" name="new_number_[% loop.count %]" value="[% HTML.escape(part.number) %]">
+      <input type="hidden" name="new_warehouse_id_[% loop.count %]" value="[% HTML.escape(part.warehouse_id) %]">
+      <input type="hidden" name="new_bin_id_[% loop.count %]" value="[% HTML.escape(part.bin_id) %]">
       [% HTML.escape(part.number) %]
      </td>
 
index 3263ee7..705d8f6 100644 (file)
@@ -12,7 +12,7 @@ chart_balances[[% loop.count - 1 %]] = '[% JavaScript.escape(LxERP.format_amount
 
 function show_chart_balance(obj) {
   var row = $(obj).attr('name').replace(/.*_/, '');
-  var idx = $('#accno_' + row).attr('selectedIndex');
+  var idx = $('#accno_' + row).prop('selectedIndex');
   $('#chart_balance_' + row).html(chart_balances[idx]);
 }
 
index d1d80c3..d3344a5 100644 (file)
@@ -1,9 +1,7 @@
 [%- USE T8 %]
 [%- USE HTML %]
 [%- USE LxERP %]
- <script type="text/javascript" src="js/common.js"></script>
- <script type="text/javascript" src="js/parts_language_selection.js"></script>
-
+[% PROCESS 'common/select_warehouse_bin.html' %]
  <p><div class="listtop">[% title %]  [% HTML.escape(partnumber) %]  [% HTML.escape(description) %]</div></p>
 
 [% PROCESS 'common/flash.html' %]
            <td><input name="rop" size="10" value="[% LxERP.format_amount(rop) %]"></td>
           </tr>
           <tr>
-           <th align="right" nowrap="true">[% 'Bin' | $T8 %]</th>
-           <td><input name="bin" size="10" value="[% HTML.escape(bin) %]"></td>
+           <th align="right" nowrap="true">[% 'Default Warehouse' | $T8 %]</th>
+           <td>
+            <select name="warehouse_id" onchange="warehouse_selected(warehouses[this.selectedIndex]['id'], 0)">
+             [%- FOREACH warehouse = WAREHOUSES %]
+               <option value="[% HTML.escape(warehouse.id) %]"[% IF warehouse_id == warehouse.id %] selected[% END %]>[% warehouse.description %]</option>
+             [%- END %]
+            </select>
+          </td>
+          </tr>
+          <tr>
+           <th align="right" nowrap="true">[% 'Default Bin' | $T8 %]</th>
+           <td><select id="bin_id" name="bin_id"></select></td>
           </tr>
         [%- END %]
           <tr>
index 62ded76..5a7aabf 100644 (file)
      <td>[% 'Optional comment' | $T8 %]:</td>
      <td><input name="comment" size="20"></td>
     </tr>
+    <tr>
+     <td>[% 'Change default bin for this parts' | $T8 %]:</td>
+    <td><input type="checkbox" name="change_default_bin"<td>
+    </tr>
    </table>
   </p>