]> wagnertech.de Git - kivitendo-erp.git/commitdiff
Merge branch 'master' of github.com:kivitendo/kivitendo-erp
authorJan Büren <jan@kivitendo-premium.de>
Thu, 30 May 2013 11:38:24 +0000 (13:38 +0200)
committerJan Büren <jan@kivitendo-premium.de>
Thu, 30 May 2013 11:38:24 +0000 (13:38 +0200)
Conflicts:
SL/DB/MetaSetup/Default.pm
locale/de/all

62 files changed:
SL/AM.pm
SL/AP.pm
SL/AR.pm
SL/CP.pm
SL/CT.pm
SL/CVar.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/PurchaseInvoice.pm
SL/DB/MetaSetup/Vendor.pm
SL/DB/VC.pm
SL/DN.pm
SL/DO.pm
SL/Form.pm
SL/Helper/Csv.pm
SL/IR.pm
SL/IS.pm
SL/InstanceConfiguration.pm
SL/MoreCommon.pm
SL/OE.pm
SL/Presenter/Tag.pm
SL/RP.pm
SL/Template/Plugin/L.pm
SL/User.pm
bin/mozilla/admin.pl
bin/mozilla/cp.pl
bin/mozilla/ic.pl
bin/mozilla/ir.pl
locale/de/all
sql/Pg-upgrade2/currencies.pl [new file with mode: 0644]
sql/Pg-upgrade2/rm_whitespaces.pl [new file with mode: 0644]
sql/Pg-upgrade2/tax_constraints.pl
t/controllers/helpers/parse_filter.t
templates/webpages/admin/create_dataset.html
templates/webpages/am/edit_accounts.html
templates/webpages/am/edit_defaults.html
templates/webpages/ct/form_header.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

index 85080e8a18edb0d267672d3eee4191d4d6ddb7af..4934d866cbae9dd701dcbc4b77045c4ebd279701 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();
index 53c1d2f5df4a742d79b7828c6f71575e8fa165c3..bc5a362025ce9373a039afe6b8383763de5e85e8 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 5ec2b1850a5e861d3cb054f1e04a9a756a2c630d..b6a7376e1409740fb29e51a109aa642a0b9b88c2 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 18673c21597c702dee495ae9b4c9a80b5c120c1c..29ac35ab0b545756f6a330e7dc88e71fd8531b89 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 02a5522f656fc0d2651aa156fb00317dc6fe63eb..66b3e25b7042f09e368fb9d9e4ab9a3a98d7fa45 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);
index 0a91c59f6438a445a28a49c75ccdfd9f88f73b9e..02456cb0bd265ac2260ba5a6bf7d8aa65dfb17a9 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 7f4326a07141f644e0821f6aba5d72aae1f29036..db6dc4d87caae24b1631b2d1e1e816483ad140b2 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 96eb69691ae30d3800364e26fa26467675124235..47964936ef340a9393b03540713a09a8c17e8ec4 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 33bede6a0f27af2a7bfb26cf9567f70c1925749f..00adfc9f5dd85c6d9fab9d592d0b750da3a6cd51 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 e353657170a44bffdc7ea7940475027cd6627cad..8e2ede133af541cfd8dd433c38fcd6511c51667d 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 15997b8e9447bc2dbb8a5b4f5f512f76d65ad577..25c4efa09ead74929f3a9ca63d2d4e13fbe62d44 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 3bf1b65957fda6b0dada84372763096184edda34..ee5a740d1c95f47a3cf6b0c11a34ea5d42fde3e9 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 b71b0f47421129e09a14cdadc0274fe5dd284e46..4ea7be63f905c25c51ceedc040e077254b5dc66a 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 bff5089b9230d47d3439335c1dafb441531e24f5..f951bf9d0304cecaca40a8cb44e005b65eb26fa5 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 d5805b0c06bf6fa1e145f4b5ec202e3c2d5185ce..c2d8a5fe959455222dc20a5c38b309897cf0fa21 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 cc4d2abe161f192bb530c01d7943854c3c827de6..f0b8be9af65aff9b1bf17f5e7110c0847ab863ed 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 0801299fb54d01b6e2a3fb00c3ec3aac36906a9e..73613f89df80419197984dedcdc9cc909828895e 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 09c7f86e7d11aa4f75e215c5f44087beac4faa8b..d93de711aad58a09823c4f1f3d7ad8c447f46703 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 e908b6b1f6925a1c40198583cd39a849a9890195..e964f4ff3f85a11f437a34f49fe8df1eeba051c6 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 6c781a9fee2c4a4c626a244dd4785d6f28f90366..e2383462582e9040ec83c64b99702051515e04a0 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 82d808cf958705875a3b265b90ea6fbbba92b268..0bb7cd96b96ea3825c25dd488213826deaba9d81 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 8063f9ed5fd7e928111fcbb4ccc0b8d96853a0c4..1fde1bbfaf176bbf6a0fb858fe4d048e2db89b54 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' },
@@ -70,10 +69,13 @@ __PACKAGE__->meta->setup(
     assemblynumber                      => { type => 'text' },
     warehouse_id                        => { type => 'integer' },
     bin_id                              => { type => 'integer' },
+    currency_id                         => { type => 'integer', not_null => 1 },
   ],
 
   primary_key_columns => [ 'id' ],
 
+  allow_inline_column_values => 1,
+
   foreign_keys => [
     bin => {
       class       => 'SL::DB::Bin',
@@ -83,6 +85,10 @@ __PACKAGE__->meta->setup(
     warehouse => {
       class       => 'SL::DB::Warehouse',
       key_columns => { warehouse_id => 'id' },
+
+    currency => {
+      class       => 'SL::DB::Currency',
+      key_columns => { currency_id => 'id' },
     },
   ],
 );
index 523312160efbac71f3586b4448bd01ae6dff8b0f..86cdc6406733736a5ba43f41865f78602d3537f9 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 44bce842bf322dae1b7fcda5ff322e4314d6223b..ddd09438b7cbf46a4fa85becd46797d8fa184411 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 b01b1417d6334bde6fb10a18ca16c8d97ad15259..2a0e50ee23d4ed7c473e2ee781518ae4dc1afec3 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 f68c276efcf8daf6874b1666bfd81344ae76bac5..063ca0b0f547bbef07a5f794ce182da7b2420ce9 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 bee7eb2ae9087ac3bc086efc6237b9492136e863..e484fb933954c0aab0dc5d9c42c4bed8392f81ee 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 d8854fab5325f217686742ef92475388621b4af2..14fb4a929081e9341112a3c32c4792a1c9173f1f 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 44b2dbb0952057f976c957475f57603c81698f89..c41f28788fe378f08619b85e60e0fc4ab8be9104 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 46749ab37cb676c656baadfbbe13c3169e88d048..8eb407b60d1bba0b5d98865665e88d1c9812a2c1 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 a598f025d6642a87d8c3b25c7a6525912b701eac..04d5e84dd4311d72953588441ba3df3fc91a4ba3 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};
index 018526ac666d6a1abc7a91d89c91313fd3d91666..77aea90cf3d7d81dce2c2efad9a357d76df1b61e 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 });
       }
     }
   }
@@ -1482,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);
 
@@ -1520,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);
@@ -1565,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);
 
 
@@ -1609,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);
 
@@ -1624,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();
 
@@ -1639,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 {
@@ -2183,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();
 }
@@ -2696,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}
@@ -2714,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});
@@ -2800,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|;
@@ -2814,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|;
@@ -2824,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 {
 
@@ -2849,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';
@@ -2890,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 e48161492a9703e94ef544820a7cd1efcb248880..2d454be37ecac7349251acc133c4ac9837c5dcc0 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 b71555cc760e9a2077a12e388965803041c5eb9a..36f105ebdc7a1cff99d1336cf3f2a6b6eba5c1aa 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
@@ -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')|;
@@ -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 785cda9db6ba763bdee043633a965221fec34a3e..8f91a23608d3fd3cb2f82ca67d0ddeeab1b5f12d 100644 (file)
--- a/SL/IS.pm
+++ b/SL/IS.pm
@@ -458,9 +458,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|;
@@ -478,9 +479,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
@@ -536,6 +534,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
@@ -556,8 +556,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} =
@@ -570,9 +570,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 {
@@ -1085,7 +1082,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 = ?,
@@ -1559,8 +1557,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|;
 
@@ -1579,7 +1576,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,
@@ -1591,9 +1588,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
@@ -1758,13 +1752,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);
@@ -1773,11 +1768,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
@@ -1806,7 +1798,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 = ?
index 59d4035a4d6520a3e9fa0dd10e397e6c554caade..d9b7129a7d5642991978846fb6f383de220638e6 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 {
index dfc5a7d8002d63c7aa637efab70fc25f07b09824..41cac3c08ff1d08456def393fb549991264887d4 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 7302088aebb7e36d72c6b46016eeb23dd9fb40d7..f18cb06516cecd690f1abe9a978e94f99a52eef7 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
index 9ead2f24a371e07eb507982d2541e407af655830..e5690e79df63bb667274961fbfcef8fa2187cf05 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 65cb3029529ed9e4f1b205dc8a593eccd9bd0406..095f7581cf034fcbce0b0e329410a061bd84f1c3 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 5ef07b75bbfc2a5bf90d049db01caa76fa0b1c1b..3e157a3665b41dd04200d34fd7e6c786067b6265 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 46c35bf41c725c9c3482659763fab49db5587038..e11fda4ce07f5cd68987d20f47126871657c18fb 100644 (file)
@@ -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;
 
index 7f0b80de85dd3d59628a0c337f5d86f82b2f6b12..84c8b069954e2813acdc351dc9760ef1cd84cddb 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 a70c32eebdcd2b2a74882e0053f4d0bec1567c57..b9aed484aa457688a002420042950588ef3d9a16 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 5269d09fc07c354ccb18bc037a36ac065dfbc466..04257ce97178629680fa0b55dcbd487f4870343e 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};
index 143696311499868152f9b9a5402f150cc4a64e16..4209fdf6addc989e1d9f43bc19fe2abc70df7e07 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 44c068ea7c6b76f7920487ebc95c51b7b5c7755a..c416d452e14275e205a13d20dbed5e7c7ae42e5c 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:',
   '*/'                          => '*/',
@@ -325,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)',
@@ -533,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',
@@ -616,7 +619,10 @@ $self->{texts} = {
   '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',
@@ -822,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:',
@@ -839,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',
@@ -942,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',
@@ -999,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.',
@@ -1093,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.',
@@ -1298,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.',
@@ -1311,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.',
@@ -1383,6 +1392,7 @@ $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',
@@ -1467,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.',
@@ -1628,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',
@@ -1936,6 +1948,7 @@ $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.',
@@ -2017,6 +2030,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:',
@@ -2124,8 +2138,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.',
@@ -2365,6 +2381,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/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/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 23d9b84b2c0fe4faa83bdaa15b55f39460fdfaec..cc2ccd2e46359f2ac9254da2b6ae66fc7330b1dd 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 1bc3a00b8dc7317201c01557a939df8a7ae18ffe..c72c33fce83ebb89487f7cbed6cb8f8c2b540359 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 3e4c6e70749b877472fc260c342a273ad4168c87..991bbc902f0e28f683146dba6ca2021381b01f8c 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 38652b73d9eeab3345d48823ee64933639936c5f..d3e4fe7eb9bfce50a783b7b6d72b2359b7f10aa2 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 248953cf31af13f0c17778854b4aa5a999c160e6..c8c91f60af6682a6ad97e249d3af7920bf03ba63 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 825a4b82e614e1fc4d5736da550a62ef394aea9b..ff2d36b90ba672fcfd7b0554078796f8cc5b1305 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/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 2bd7450cc5cb232c32d693bea3d1b7c45447e8cc..087e821c67776ccc8e182130ed4c72278d59e9b2 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>