commit 41adf4334b0ed75b3c0ff35facc5fcfe1c5167e3
Author: Moritz Bunkus <m.bunkus@linet.de>
Date:   Tue Jun 24 09:49:19 2014 +0200

    Verkaufsbelege: optional nur Projekte des Kunden anbieten
    
    Auch dieses Feature kann über die Mandantenkonfiguration eingeschaltet
    werden.

diff --git a/SL/DB/MetaSetup/Default.pm b/SL/DB/MetaSetup/Default.pm
index 5f924cdf9..2015b9052 100644
--- a/SL/DB/MetaSetup/Default.pm
+++ b/SL/DB/MetaSetup/Default.pm
@@ -33,6 +33,7 @@ __PACKAGE__->meta->columns(
   company                                  => { type => 'text' },
   currency_id                              => { type => 'integer', not_null => 1 },
   customer_hourly_rate                     => { type => 'numeric', precision => 8, scale => 2 },
+  customer_projects_only_in_sales          => { type => 'boolean', default => 'false', not_null => 1 },
   customernumber                           => { type => 'text' },
   datev_check_on_ap_transaction            => { type => 'boolean', default => 'true' },
   datev_check_on_ar_transaction            => { type => 'boolean', default => 'true' },
diff --git a/bin/mozilla/do.pl b/bin/mozilla/do.pl
index 88ae6c387..3e5e9dedc 100644
--- a/bin/mozilla/do.pl
+++ b/bin/mozilla/do.pl
@@ -30,6 +30,7 @@
 # Delivery orders
 #======================================================================
 
+use List::MoreUtils qw(uniq);
 use List::Util qw(max sum);
 use POSIX qw(strftime);
 use YAML;
@@ -259,22 +260,31 @@ sub form_header {
   $form->{employee_id} = $form->{old_employee_id} if $form->{old_employee_id};
   $form->{salesman_id} = $form->{old_salesman_id} if $form->{old_salesman_id};
 
-  my @old_project_ids = ($form->{"globalproject_id"});
-  map({ push(@old_project_ids, $form->{"project_id_$_"})
-          if ($form->{"project_id_$_"}); } (1..$form->{"rowcount"}));
-
   my $vc = $form->{vc} eq "customer" ? "customers" : "vendors";
-  $form->get_lists("projects"       => {
-                     "key"          => "ALL_PROJECTS",
-                     "all"          => 0,
-                     "old_id"       => \@old_project_ids
-                   },
-                   $vc              => "ALL_VC",
+  $form->get_lists($vc              => "ALL_VC",
                    "price_factors"  => "ALL_PRICE_FACTORS",
                    "departments"    => "ALL_DEPARTMENTS",
                    "business_types" => "ALL_BUSINESS_TYPES",
     );
 
+  # Projects
+  my @old_project_ids = uniq grep { $_ } map { $_ * 1 } ($form->{"globalproject_id"}, map { $form->{"project_id_$_"} } 1..$form->{"rowcount"});
+  my @old_ids_cond    = @old_project_ids ? (id => \@old_project_ids) : ();
+  my @customer_cond;
+  if (($vc eq 'customers') && $::instance_conf->get_customer_projects_only_in_sales) {
+    @customer_cond = (
+      or => [
+        customer_id          => $::form->{customer_id},
+        billable_customer_id => $::form->{customer_id},
+      ]);
+  }
+  my @conditions = (
+    or => [
+      and => [ active => 1, @customer_cond ],
+      @old_ids_cond,
+    ]);
+
+  $::form->{ALL_PROJECTS}          = SL::DB::Manager::Project->get_all(query => \@conditions);
   $::form->{ALL_EMPLOYEES}         = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{employee_id},  deleted => 0 ] ]);
   $::form->{ALL_SALESMEN}          = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{salesman_id},  deleted => 0 ] ]);
   $::form->{ALL_SHIPTO}            = SL::DB::Manager::Shipto->get_all_sorted(query => [
diff --git a/bin/mozilla/is.pl b/bin/mozilla/is.pl
index 379544809..ef8f5996e 100644
--- a/bin/mozilla/is.pl
+++ b/bin/mozilla/is.pl
@@ -37,6 +37,7 @@ use SL::PE;
 use SL::OE;
 use Data::Dumper;
 use DateTime;
+use List::MoreUtils qw(uniq);
 use List::Util qw(max sum);
 
 use SL::DB::Default;
@@ -305,18 +306,30 @@ sub form_header {
 
   $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
 
-  my @old_project_ids = ($form->{"globalproject_id"});
-  map { push @old_project_ids, $form->{"project_id_$_"} if $form->{"project_id_$_"}; } 1..$form->{"rowcount"};
-
-  $form->get_lists("projects"      => { "key"    => "ALL_PROJECTS",
-                                        "all"    => 0,
-                                        "old_id" => \@old_project_ids },
-                   "taxzones"      => "ALL_TAXZONES",
+  $form->get_lists("taxzones"      => "ALL_TAXZONES",
                    "currencies"    => "ALL_CURRENCIES",
                    "customers"     => "ALL_CUSTOMERS",
                    "departments"   => "all_departments",
                    "price_factors" => "ALL_PRICE_FACTORS");
 
+  # Projects
+  my @old_project_ids = uniq grep { $_ } map { $_ * 1 } ($form->{"globalproject_id"}, map { $form->{"project_id_$_"} } 1..$form->{"rowcount"});
+  my @old_ids_cond    = @old_project_ids ? (id => \@old_project_ids) : ();
+  my @customer_cond;
+  if ($::instance_conf->get_customer_projects_only_in_sales) {
+    @customer_cond = (
+      or => [
+        customer_id          => $::form->{customer_id},
+        billable_customer_id => $::form->{customer_id},
+      ]);
+  }
+  my @conditions = (
+    or => [
+      and => [ active => 1, @customer_cond ],
+      @old_ids_cond,
+    ]);
+
+  $TMPL_VAR{ALL_PROJECTS}          = SL::DB::Manager::Project->get_all(query => \@conditions);
   $TMPL_VAR{ALL_EMPLOYEES}         = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{employee_id},  deleted => 0 ] ]);
   $TMPL_VAR{ALL_SALESMEN}          = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{salesman_id},  deleted => 0 ] ]);
   $TMPL_VAR{ALL_SHIPTO}            = SL::DB::Manager::Shipto->get_all_sorted(query => [
diff --git a/bin/mozilla/oe.pl b/bin/mozilla/oe.pl
index 2d82b9d17..a7207ff81 100644
--- a/bin/mozilla/oe.pl
+++ b/bin/mozilla/oe.pl
@@ -44,7 +44,7 @@ use SL::IS;
 use SL::MoreCommon qw(ary_diff);
 use SL::PE;
 use SL::ReportGenerator;
-use List::MoreUtils qw(any none);
+use List::MoreUtils qw(uniq any none);
 use List::Util qw(min max reduce sum);
 use Data::Dumper;
 
@@ -349,14 +349,10 @@ sub form_header {
                         $form->{"closed"}    ? "checked" : "",  $locale->text('Closed')    if $form->{id};
   $TMPL_VAR{openclosed} = sprintf qq|<tr><td colspan=%d align=center>%s</td></tr>\n|, 2 * scalar @tmp, join "\n", @tmp if @tmp;
 
-  # project ids
-  my @old_project_ids = ($form->{"globalproject_id"}, grep { $_ } map { $form->{"project_id_$_"} } 1..$form->{"rowcount"});
-
   my $vc = $form->{vc} eq "customer" ? "customers" : "vendors";
-  $form->get_lists("projects"      => { "key"      => "ALL_PROJECTS",
-                                        "all"      => 0,
-                                        "old_id"   => \@old_project_ids },
-                   "taxzones"      => "ALL_TAXZONES",
+
+  # project ids
+  $form->get_lists("taxzones"      => "ALL_TAXZONES",
                    "payments"      => "ALL_PAYMENTS",
                    "currencies"    => "ALL_CURRENCIES",
                    "departments"   => "ALL_DEPARTMENTS",
@@ -364,6 +360,25 @@ sub form_header {
                                         limit => $myconfig{vclimit} + 1 },
                    "price_factors" => "ALL_PRICE_FACTORS");
 
+  # Projects
+  my @old_project_ids = uniq grep { $_ } map { $_ * 1 } ($form->{"globalproject_id"}, map { $form->{"project_id_$_"} } 1..$form->{"rowcount"});
+  my @old_ids_cond    = @old_project_ids ? (id => \@old_project_ids) : ();
+  my @customer_cond;
+  if (($vc eq 'customers') && $::instance_conf->get_customer_projects_only_in_sales) {
+    @customer_cond = (
+      or => [
+        customer_id          => $::form->{customer_id},
+        billable_customer_id => $::form->{customer_id},
+      ]);
+  }
+  my @conditions = (
+    or => [
+      and => [ active => 1, @customer_cond ],
+      @old_ids_cond,
+    ]);
+
+  $TMPL_VAR{ALL_PROJECTS}          = SL::DB::Manager::Project->get_all(query => \@conditions);
+
   # label subs
   my $employee_list_query_gen      = sub { $::form->{$_[0]} ? [ or => [ id => $::form->{$_[0]}, deleted => 0 ] ] : [ deleted => 0 ] };
   $TMPL_VAR{ALL_EMPLOYEES}         = SL::DB::Manager::Employee->get_all_sorted(query => $employee_list_query_gen->('employee_id'));
diff --git a/locale/de/all b/locale/de/all
index 09d879988..3f9ec36a2 100755
--- a/locale/de/all
+++ b/locale/de/all
@@ -1203,6 +1203,7 @@ $self->{texts} = {
   'If disabled purchase invoices can only be created by conversion from existing requests for quotations, purchase orders and purchase delivery orders.' => 'Falls deaktiviert, so können Einkaufsrechnungen nur durch Umwandlung aus bestehenden Preisanfragen, Lieferantenaufträgen und Einkaufslieferscheinen angelegt werden.',
   'If disabled sales orders cannot be converted into sales invoices directly.' => 'Falls deaktiviert, so können Verkaufsangebote nicht direkt in Verkaufsrechnungen umgewandelt werden.',
   'If disabled sales quotations cannot be converted into sales invoices directly.' => 'Falls deaktiviert, so können Verkaufsauträge nicht direkt in Verkaufsrechnungen umgewandelt werden.',
+  'If enabled only those projects that are assigned to the currently selected customer are offered for selection in sales records.' => 'Wenn eingeschaltet, so werden in Verkaufsbelegen nur diejenigen Projekte zur Auswahl angeboten, die dem aktuell ausgewählten Kunden zugewiesen wurden.',
   'If enabled purchase and sales records cannot be saved if no transaction description has been entered.' => 'Wenn angeschaltet, so können Einkaufs- und Verkaufsbelege nicht gespeichert werden, solange keine Vorgangsbezeichnung eingegeben wurde.',
   'If missing then the start date will be used.' => 'Falls es fehlt, so wird die erste Rechnung für das Startdatum erzeugt.',
   '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.',
@@ -1612,6 +1613,7 @@ $self->{texts} = {
   'Only Warnings and Errors'    => 'Nur Warnungen und Fehler',
   'Only due follow-ups'         => 'Nur f&auml;llige Wiedervorlagen',
   'Only groups that have been configured for the client the user logs in to will be considered.' => 'Allerdings werden nur diejenigen Gruppen herangezogen, die für den Mandanten konfiguriert sind.',
+  'Only list customer\'s projects in sales records' => 'Nur Projekte des Kunden in Verkaufsbelegen anzeigen',
   'Only shown in item mode'     => 'werden nur im Artikelmodus angezeigt',
   'Oops. No valid action found to dispatch. Please report this case to the kivitendo team.' => 'Ups. Es wurde keine gültige Funktion zum Aufrufen gefunden. Bitte berichten Sie diesen Fall den kivitendo-Entwicklern.',
   'Open'                        => 'Offen',
@@ -1627,17 +1629,16 @@ $self->{texts} = {
   'Options'                     => 'Optionen',
   'Or download the whole Installation Documentation as PDF (350kB) for off-line study (currently in German Language): ' => 'Oder laden Sie die komplette Installationsbeschreibung als PDF (350kB) herunter: ',
   'Order'                       => 'Auftrag',
-  'Order Date missing!'         => 'Auftragsdatum fehlt!',
   'Order Date'                  => 'Auftragsdatum',
-  'Order Number missing!'       => 'Auftragsnummer fehlt!',
+  'Order Date missing!'         => 'Auftragsdatum fehlt!',
   'Order Number'                => 'Auftragsnummer',
+  'Order Number missing!'       => 'Auftragsnummer fehlt!',
   'Order amount'                => 'Auftragswert',
   'Order deleted!'              => 'Auftrag gelöscht!',
-  'Order probability & expected billing date' => 'Auftragswahrscheinlichkeit & vorrauss. Abrechnungsdatum',
   'Order probability'           => 'Auftragswahrscheinlichkeit',
+  'Order probability & expected billing date' => 'Auftragswahrscheinlichkeit & vorrauss. Abrechnungsdatum',
   'Order/Item row name'         => 'Name der Auftrag-/Positions-Zeilen',
   'OrderItem'                   => 'Position',
-  'Ordered'                     => 'Vom Kunde bestellt',
   'Ordered'                     => 'Von Kunden bestellt',
   'Orders'                      => 'Aufträge',
   'Orders / Delivery Orders deleteable' => 'Aufträge / Lieferscheine löschbar',
diff --git a/sql/Pg-upgrade2/defaults_only_customer_projects_in_sales.sql b/sql/Pg-upgrade2/defaults_only_customer_projects_in_sales.sql
new file mode 100644
index 000000000..feeb72c6d
--- /dev/null
+++ b/sql/Pg-upgrade2/defaults_only_customer_projects_in_sales.sql
@@ -0,0 +1,8 @@
+-- @tag: defaults_only_customer_projects_in_sales
+-- @description: Mandantenkonfiguration: in Verkaufsbelegen nur Projekte des ausgewählten Kunden anbieten
+-- @depends: release_3_1_0
+ALTER TABLE defaults ADD COLUMN customer_projects_only_in_sales BOOLEAN;
+UPDATE defaults SET customer_projects_only_in_sales = FALSE;
+ALTER TABLE defaults
+  ALTER COLUMN customer_projects_only_in_sales SET DEFAULT FALSE,
+  ALTER COLUMN customer_projects_only_in_sales SET NOT NULL;
diff --git a/templates/webpages/client_config/_features.html b/templates/webpages/client_config/_features.html
index 79ae72e37..416eb815f 100644
--- a/templates/webpages/client_config/_features.html
+++ b/templates/webpages/client_config/_features.html
@@ -65,6 +65,12 @@
    <td>[% LxERP.t8('If enabled purchase and sales records cannot be saved if no transaction description has been entered.') %]</td>
   </tr>
 
+  <tr>
+   <td align="right">[% LxERP.t8("Only list customer's projects in sales records") %]</td>
+   <td>[% L.yes_no_tag("defaults.customer_projects_only_in_sales", SELF.defaults.customer_projects_only_in_sales) %]</td>
+   <td>[% LxERP.t8("If enabled only those projects that are assigned to the currently selected customer are offered for selection in sales records.") %]</td>
+  </tr>
+
   <tr>
    <td align="right">[% LxERP.t8('Allow conversion from sales quotations to sales invoices') %]</td>
    <td>[% L.yes_no_tag('defaults.allow_sales_invoice_from_sales_quotation', SELF.defaults.allow_sales_invoice_from_sales_quotation) %]</td>
