commit 62f2141002f5b8c11fa9d881473fd3bc469b4eef
Author: Martin Helmling <martin.helmling@octosoft.eu>
Date:   Mon Jan 9 11:55:21 2017 +0100

    Kunden-Spezifische Artikeleigenschaften
    
    neue Tabelle "PartCustomerPrices" mit SL/DB Dateien
    in Artikelstammdaten eingebaut,
    
    in Preisquellen analog zu den Lieferantenpreisen nun Kundenpreise eingebaut
    (Unklar ist was bei Kundenpreisen der beste Preis ist !)
    
    Analog zu Lieferanten "make" und "model" aus kundenspezifischen Daten
    "customer" und "custnumber" zum Drucken pro Artikel anbieten

diff --git a/SL/Controller/Part.pm b/SL/Controller/Part.pm
index e4bb71226..7b4a2bc81 100644
--- a/SL/Controller/Part.pm
+++ b/SL/Controller/Part.pm
@@ -24,6 +24,7 @@ use SL::Presenter::EscapedText qw(escape is_escaped);
 use Rose::Object::MakeMethods::Generic (
   'scalar --get_set_init' => [ qw(parts models part p warehouses multi_items_models
                                   makemodels shops_not_assigned
+                                  customerprices
                                   orphaned
                                   assortment assortment_items assembly assembly_items
                                   all_pricegroups all_translations all_partsgroups all_units
@@ -455,6 +456,39 @@ sub action_add_makemodel_row {
     ->render;
 }
 
+sub action_add_customerprice_row {
+  my ($self) = @_;
+
+  my $customer_id = $::form->{add_customerprice};
+
+  my $customer = SL::DB::Manager::Customer->find_by(id => $customer_id)
+    or return $self->js->error(t8("No customer selected or found!"))->render;
+
+  if (grep { $customer_id == $_->customer_id } @{ $self->customerprices }) {
+    $self->js->flash('info', t8("This customer has already been added."));
+  }
+
+  my $position = scalar @{ $self->customerprices } + 1;
+
+  my $cu = SL::DB::PartCustomerPrice->new(
+                      customer_id         => $customer->id,
+                      customer_partnumber => '',
+                      price               => 0,
+                      sortorder           => $position,
+  ) or die "Can't create Customerprice object";
+
+  my $row_as_html = $self->p->render(
+                                     'part/_customerprice_row',
+                                      customerprice => $cu,
+                                      listrow       => $position % 2 ? 0
+                                                                     : 1,
+  );
+
+  $self->js->append('#customerprice_rows', $row_as_html)    # append in tbody
+           ->val('.add_customerprice_input', '')
+           ->run('kivi.Part.focus_last_customerprice_input')->render;
+}
+
 sub action_reorder_items {
   my ($self) = @_;
 
@@ -713,6 +747,7 @@ sub parse_form {
   $self->part->prices([]);
   $self->parse_form_prices;
 
+  $self->parse_form_customerprices;
   $self->parse_form_makemodels;
 }
 
@@ -782,6 +817,45 @@ sub parse_form_makemodels {
   };
 }
 
+sub parse_form_customerprices {
+  my ($self) = @_;
+
+  my $customerprices_map;
+  if ( $self->part->customerprices ) { # check for new parts or parts without customerprices
+    $customerprices_map = { map { $_->id => Rose::DB::Object::Helpers::clone($_) } @{$self->part->customerprices} };
+  };
+
+  $self->part->customerprices([]);
+
+  my $position = 0;
+  my $customerprices = delete($::form->{customerprices}) || [];
+  foreach my $customerprice ( @{$customerprices} ) {
+    next unless $customerprice->{customer_id};
+    $position++;
+    my $customer = SL::DB::Manager::Customer->find_by(id => $customerprice->{customer_id}) || die "Can't find customer from id";
+
+    my $cu = SL::DB::PartCustomerPrice->new( # parts_id   => $self->part->id, # will be assigned by row add_customerprices
+                                     id                   => $customerprice->{id},
+                                     customer_id          => $customerprice->{customer_id},
+                                     customer_partnumber  => $customerprice->{customer_partnumber} || '',
+                                     price                => $::form->parse_amount(\%::myconfig, $customerprice->{price_as_number}),
+                                     sortorder            => $position,
+                                   );
+    if ($customerprices_map->{$cu->id} && !$customerprices_map->{$cu->id}->lastupdate && $customerprices_map->{$cu->id}->price == 0 && $cu->price == 0) {
+      # lastupdate isn't set, original price is 0 and new lastcost is 0
+      # don't change lastupdate
+    } elsif ( !$customerprices_map->{$cu->id} && $cu->price == 0 ) {
+      # new customerprice, no lastcost entered, leave lastupdate empty
+    } elsif ($customerprices_map->{$cu->id} && $customerprices_map->{$cu->id}->price == $cu->price) {
+      # price hasn't changed, use original lastupdate
+      $cu->lastupdate($customerprices_map->{$cu->id}->lastupdate);
+    } else {
+      $cu->lastupdate(DateTime->now);
+    };
+    $self->part->add_customerprices($cu);
+  };
+}
+
 sub build_bin_select {
   $_[0]->p->select_tag('part.bin_id', [ $_[0]->warehouse->bins ],
     title_key => 'description',
@@ -806,7 +880,7 @@ sub init_part {
   # used by edit, save, delete and add
 
   if ( $::form->{part}{id} ) {
-    return SL::DB::Part->new(id => $::form->{part}{id})->load(with => [ qw(makemodels prices translations partsgroup shop_parts shop_parts.shop) ]);
+    return SL::DB::Part->new(id => $::form->{part}{id})->load(with => [ qw(makemodels customerprices prices translations partsgroup shop_parts shop_parts.shop) ]);
   } else {
     die "part_type missing" unless $::form->{part}{part_type};
     return SL::DB::Part->new(part_type => $::form->{part}{part_type});
@@ -885,6 +959,29 @@ sub init_makemodels {
   return \@makemodel_array;
 }
 
+sub init_customerprices {
+  my ($self) = @_;
+
+  my $position = 0;
+  my @customerprice_array = ();
+  my $customerprices = delete($::form->{customerprices}) || [];
+
+  foreach my $customerprice ( @{$customerprices} ) {
+    next unless $customerprice->{customer_id};
+    $position++;
+    my $cu = SL::DB::PartCustomerPrice->new( # parts_id   => $self->part->id, # will be assigned by row add_customerprices
+                                    id                  => $customerprice->{id},
+                                    customer_partnumber => $customerprice->{customer_partnumber},
+                                    customer_id         => $customerprice->{customer_id} || '',
+                                    price               => $::form->parse_amount(\%::myconfig, $customerprice->{price_as_number} || 0),
+                                    sortorder           => $position,
+                                  ) or die "Can't create cu";
+    # $cu->id($customerprice->{id}) if $customerprice->{id};
+    push(@customerprice_array, $cu);
+  };
+  return \@customerprice_array;
+}
+
 sub init_assembly_items {
   my ($self) = @_;
   my $position = 0;
diff --git a/SL/DB/Helper/ALL.pm b/SL/DB/Helper/ALL.pm
index 4429f8f8b..0e6ed0739 100644
--- a/SL/DB/Helper/ALL.pm
+++ b/SL/DB/Helper/ALL.pm
@@ -75,6 +75,7 @@ use SL::DB::Order;
 use SL::DB::OrderItem;
 use SL::DB::Part;
 use SL::DB::PartClassification;
+use SL::DB::PartCustomerPrice;
 use SL::DB::PartsGroup;
 use SL::DB::PartsPriceHistory;
 use SL::DB::PaymentTerm;
diff --git a/SL/DB/Helper/Mappings.pm b/SL/DB/Helper/Mappings.pm
index f9d474593..6290bd3f4 100644
--- a/SL/DB/Helper/Mappings.pm
+++ b/SL/DB/Helper/Mappings.pm
@@ -157,6 +157,7 @@ my %kivitendo_package_names = (
   parts                          => 'part',
   partsgroup                     => 'parts_group',
   part_classifications           => 'PartClassification',
+  part_customer_prices           => 'PartCustomerPrice',
   parts_price_history            => 'PartsPriceHistory',
   payment_terms                  => 'payment_term',
   periodic_invoices              => 'periodic_invoice',
diff --git a/SL/DB/Manager/PartCustomerPrice.pm b/SL/DB/Manager/PartCustomerPrice.pm
new file mode 100644
index 000000000..4f93ddcf3
--- /dev/null
+++ b/SL/DB/Manager/PartCustomerPrice.pm
@@ -0,0 +1,15 @@
+# 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::Manager::PartCustomerPrice;
+
+use strict;
+
+use SL::DB::Helper::Manager;
+use base qw(SL::DB::Helper::Manager);
+
+sub object_class { 'SL::DB::PartCustomerPrice' }
+
+__PACKAGE__->make_manager_methods;
+
+1;
diff --git a/SL/DB/MetaSetup/Part.pm b/SL/DB/MetaSetup/Part.pm
index 6e758e499..ef6ed69f5 100644
--- a/SL/DB/MetaSetup/Part.pm
+++ b/SL/DB/MetaSetup/Part.pm
@@ -37,14 +37,14 @@ __PACKAGE__->meta->columns(
   payment_id         => { type => 'integer' },
   price_factor_id    => { type => 'integer' },
   priceupdate        => { type => 'date', default => 'now' },
-  rop                => { type => 'float', scale => 4 },
+  rop                => { type => 'float', precision => 4, scale => 4 },
   sellprice          => { type => 'numeric', precision => 15, scale => 5 },
   shop               => { type => 'boolean', default => 'false' },
   stockable          => { type => 'boolean', default => 'false' },
   unit               => { type => 'varchar', length => 20, not_null => 1 },
   ve                 => { type => 'integer' },
   warehouse_id       => { type => 'integer' },
-  weight             => { type => 'float', scale => 4 },
+  weight             => { type => 'float', precision => 4, scale => 4 },
 );
 
 __PACKAGE__->meta->primary_key_columns([ 'id' ]);
diff --git a/SL/DB/MetaSetup/PartCustomerPrice.pm b/SL/DB/MetaSetup/PartCustomerPrice.pm
new file mode 100644
index 000000000..c3d424344
--- /dev/null
+++ b/SL/DB/MetaSetup/PartCustomerPrice.pm
@@ -0,0 +1,38 @@
+# This file has been auto-generated. Do not modify it; it will be overwritten
+# by rose_auto_create_model.pl automatically.
+package SL::DB::PartCustomerPrice;
+
+use strict;
+
+use parent qw(SL::DB::Object);
+
+__PACKAGE__->meta->table('part_customer_prices');
+
+__PACKAGE__->meta->columns(
+  customer_id         => { type => 'integer', not_null => 1 },
+  customer_partnumber => { type => 'text', default => '' },
+  id                  => { type => 'serial', not_null => 1 },
+  lastupdate          => { type => 'date', default => 'now()' },
+  parts_id            => { type => 'integer', not_null => 1 },
+  price               => { type => 'numeric', default => '0', precision => 15, scale => 5 },
+  sortorder           => { type => 'integer', default => '0' },
+);
+
+__PACKAGE__->meta->primary_key_columns([ 'id' ]);
+
+__PACKAGE__->meta->allow_inline_column_values(1);
+
+__PACKAGE__->meta->foreign_keys(
+  customer => {
+    class       => 'SL::DB::Customer',
+    key_columns => { customer_id => 'id' },
+  },
+
+  parts => {
+    class       => 'SL::DB::Part',
+    key_columns => { parts_id => 'id' },
+  },
+);
+
+1;
+;
diff --git a/SL/DB/Part.pm b/SL/DB/Part.pm
index 6dde98f4d..d7bc6db9c 100644
--- a/SL/DB/Part.pm
+++ b/SL/DB/Part.pm
@@ -37,6 +37,11 @@ __PACKAGE__->meta->add_relationships(
     manager_args => { sort_by => 'sortorder' },
     column_map   => { id => 'parts_id' },
   },
+  customerprices => {
+    type         => 'one to many',
+    class        => 'SL::DB::PartCustomerPrice',
+    column_map   => { id => 'parts_id' },
+  },
   translations   => {
     type         => 'one to many',
     class        => 'SL::DB::Translation',
diff --git a/SL/DB/PartCustomerPrice.pm b/SL/DB/PartCustomerPrice.pm
new file mode 100644
index 000000000..f3fcde430
--- /dev/null
+++ b/SL/DB/PartCustomerPrice.pm
@@ -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::PartCustomerPrice;
+
+use strict;
+
+use SL::DB::MetaSetup::PartCustomerPrice;
+use SL::DB::Manager::PartCustomerPrice;
+
+__PACKAGE__->meta->initialize;
+
+1;
diff --git a/SL/IR.pm b/SL/IR.pm
index 45cd7de7d..ebb29fdc7 100644
--- a/SL/IR.pm
+++ b/SL/IR.pm
@@ -49,6 +49,7 @@ use SL::IO;
 use SL::MoreCommon;
 use SL::DB::Default;
 use SL::DB::TaxZone;
+use SL::DB::MakeModel;
 use SL::DB;
 use SL::Presenter::Part qw(type_abbreviation classification_abbreviation);
 use List::Util qw(min);
@@ -1377,8 +1378,19 @@ sub retrieve_item {
     $stw->finish();
     chop $ref->{taxaccounts};
 
-    $ref->{onhand} *= 1;
+    ## vendor_id ggf beim ersten mal noch nicht gesetzt nur vendor <name>--<id>
+    ($form->{vendor}, $form->{vendor_id}) = split(/--/, $form->{vendor}) if  ! $form->{vendor_id};
 
+    if ( $form->{vendor_id} ) {
+      my $mm = SL::DB::Manager::MakeModel->get_first(
+        query => [ parts_id => $ref->{id} , make => $form->{vendor_id} ] );
+      if ( $mm ) {
+        $::lxdebug->message(LXDebug->DEBUG2(), "mm id=".$mm->{id}." price=".$mm->{lastcost});
+        $ref->{lastcost} = $mm->{lastcost};
+      }
+    }
+
+    $ref->{onhand} *= 1;
     push @{ $form->{item_list} }, $ref;
 
   }
diff --git a/SL/IS.pm b/SL/IS.pm
index 609d3c015..e1eb4438b 100644
--- a/SL/IS.pm
+++ b/SL/IS.pm
@@ -2435,9 +2435,18 @@ sub retrieve_item {
         last;
       }
     }
+    ## customer_id ggf beim ersten mal noch nicht gesetzt nur customer <name>--<id>
+    ($form->{customer}, $form->{customer_id}) = split(/--/, $form->{customer}) if  ! $form->{customer_id};
+    if ( $form->{customer_id} ) {
+        my $cprice = SL::DB::Manager::PartCustomerPrice->get_first(
+            query => [ parts_id => $ref->{id} , customer_id => $form->{customer_id} ] );
+        if ( $cprice ) {
+            $::lxdebug->message(LXDebug->DEBUG2(), "cprice id=".$cprice->{id}." price=".$cprice->{price});
+            $ref->{sellprice} = $cprice->{price};
+        }
+    }
 
     $ref->{onhand} *= 1;
-
     push @{ $form->{item_list} }, $ref;
   }
   $sth->finish;
diff --git a/SL/PriceSource/ALL.pm b/SL/PriceSource/ALL.pm
index dd6ee2faa..e7c553e0a 100644
--- a/SL/PriceSource/ALL.pm
+++ b/SL/PriceSource/ALL.pm
@@ -4,6 +4,7 @@ use strict;
 use SL::PriceSource::Pricegroup;
 use SL::PriceSource::MasterData;
 use SL::PriceSource::Makemodel;
+use SL::PriceSource::CustomerPrice;
 use SL::PriceSource::Customer;
 use SL::PriceSource::Vendor;
 use SL::PriceSource::Business;
@@ -15,6 +16,7 @@ my %price_sources_by_name = (
   vendor_discount   => 'SL::PriceSource::Vendor',
   pricegroup        => 'SL::PriceSource::Pricegroup',
   makemodel         => 'SL::PriceSource::Makemodel',
+  customerprice     => 'SL::PriceSource::CustomerPrice',
   business          => 'SL::PriceSource::Business',
   price_rules       => 'SL::PriceSource::PriceRules',
 );
@@ -25,6 +27,7 @@ my @price_sources_order = qw(
   vendor_discount
   pricegroup
   makemodel
+  customerprice
   business
   price_rules
 );
diff --git a/SL/PriceSource/CustomerPrice.pm b/SL/PriceSource/CustomerPrice.pm
new file mode 100644
index 000000000..666c37cbd
--- /dev/null
+++ b/SL/PriceSource/CustomerPrice.pm
@@ -0,0 +1,62 @@
+package SL::PriceSource::CustomerPrice;
+
+use strict;
+use parent qw(SL::PriceSource::Base);
+
+use SL::PriceSource::Price;
+use SL::Locale::String;
+use SL::DB::PartCustomerPrice;
+# use List::UtilsBy qw(min_by max_by);
+
+sub name { 'customer_price' }
+
+sub description { t8('Customer specific Price') }
+
+sub available_prices {
+  my ($self, %params) = @_;
+
+  return () if !$self->part;
+  return () if !$self->record->is_sales;
+
+  map { $self->make_price_from_customerprice($_) }
+  grep { $_->customer_id == $self->record->customer_id }
+  $self->part->customerprices;
+}
+
+sub available_discounts { }
+
+sub price_from_source {
+  my ($self, $source, $spec) = @_;
+
+  my $customerprice = SL::DB::Manager::PartCustomerPrice->find_by(id => $spec);
+
+  return $self->make_price_from_customerprice($customerprice);
+
+}
+
+sub best_price {
+  my ($self, %params) = @_;
+
+  return () if !$self->record->is_sales;
+
+#  min_by { $_->price } $self->available_prices;
+#  max_by { $_->price } $self->available_prices;
+  &available_prices;
+
+}
+
+sub best_discount { }
+
+sub make_price_from_customerprice {
+  my ($self, $customerprice) = @_;
+
+  return SL::PriceSource::Price->new(
+    price        => $customerprice->price,
+    spec         => $customerprice->id,
+    description  => $customerprice->customer_partnumber,
+    price_source => $self,
+  );
+}
+
+
+1;
diff --git a/bin/mozilla/ic.pl b/bin/mozilla/ic.pl
index 9fd05898d..35affe9a0 100644
--- a/bin/mozilla/ic.pl
+++ b/bin/mozilla/ic.pl
@@ -281,6 +281,8 @@ sub generate_report {
     description   => $locale->text('Part Description') . ": '$form->{description}'",
     make          => $locale->text('Make')             . ": '$form->{make}'",
     model         => $locale->text('Model')            . ": '$form->{model}'",
+    customername  => $locale->text('Customer')         . ": '$form->{customername}'",
+    customernumber=> $locale->text('Customer Part Number').": '$form->{customernumber}'",
     drawing       => $locale->text('Drawing')          . ": '$form->{drawing}'",
     microfiche    => $locale->text('Microfiche')       . ": '$form->{microfiche}'",
     l_soldtotal   => $locale->text('Qty in Selected Records'),
diff --git a/bin/mozilla/is.pl b/bin/mozilla/is.pl
index 26fb0ea8e..0b82cd0d5 100644
--- a/bin/mozilla/is.pl
+++ b/bin/mozilla/is.pl
@@ -776,7 +776,7 @@ sub update {
       # ask if it is a part or service item
 
       if (   $form->{"partsgroup_$i"}
-          && ($form->{"partsnumber_$i"} eq "")
+          && ($form->{"partnumber_$i" } eq "")
           && ($form->{"description_$i"} eq "")) {
         $form->{rowcount}--;
         $form->{"discount_$i"} = "";
@@ -784,6 +784,10 @@ sub update {
 
       } else {
         $form->{"id_$i"}   = 0;
+        if ( $form->{is_wrong_ptype} > 0 ) {
+          $form->{"partnumber_$i"}  = "";
+          $form->{"description_$i"} = "";
+        }
         new_item();
       }
     }
diff --git a/js/kivi.Part.js b/js/kivi.Part.js
index dc05a9add..9e79932ef 100644
--- a/js/kivi.Part.js
+++ b/js/kivi.Part.js
@@ -253,6 +253,35 @@ namespace('kivi.Part', function(ns) {
     $("#makemodel_rows tr:last").find('input[type=text]').filter(':visible:first').focus();
   };
 
+
+  // customerprice
+  ns.customerprice_renumber_positions = function() {
+    $('.customerprice_row [name="position"]').each(function(idx, elt) {
+      $(elt).html(idx+1);
+    });
+  };
+
+  ns.delete_customerprice_row = function(clicked) {
+    var row = $(clicked).closest('tr');
+    $(row).remove();
+
+    ns.customerprice_renumber_positions();
+  };
+
+  ns.add_customerprice_row = function() {
+    if ($('#add_customerpriceid').val() === '') return;
+
+    var data = $('#customerprice_table :input').serializeArray();
+    data.push({ name: 'action', value: 'Part/add_customerprice_row' });
+
+    $.post("controller.pl", data, kivi.eval_json_result);
+  };
+
+  ns.focus_last_customerprice_input = function () {
+    $("#customerprice_rows tr:last").find('input[type=text]').filter(':visible:first').focus();
+  };
+
+
   ns.reload_bin_selection = function() {
     $.post("controller.pl", { action: 'Part/warehouse_changed', warehouse_id: function(){ return $('#part_warehouse_id').val() } },   kivi.eval_json_result);
   }
@@ -734,6 +763,14 @@ namespace('kivi.Part', function(ns) {
       }
     });
 
+    $('.add_customerprice_input').keydown(function(event) {
+      if(event.keyCode == 13) {
+        event.preventDefault();
+        ns.add_customerprice_row();
+        return false;
+      }
+    });
+
     $('#part_warehouse_id').change(kivi.Part.reload_bin_selection);
 
     ns.init();
diff --git a/locale/de/all b/locale/de/all
index 843720d05..4a9ca5dae 100755
--- a/locale/de/all
+++ b/locale/de/all
@@ -807,12 +807,15 @@ $self->{texts} = {
   'Customer Name'               => 'Kundenname',
   'Customer Number'             => 'Kundennummer',
   'Customer Order Number'       => 'Bestellnummer des Kunden',
+  'Customer Part Number'        => 'Kunden-Art-Nr.',
+  'Customer Price'              => 'Kundenpreis',
   'Customer deleted!'           => 'Kunde gelöscht!',
   'Customer details'            => 'Kundendetails',
   'Customer missing!'           => 'Kundenname fehlt!',
   'Customer not found'          => 'Kunde nicht gefunden',
   'Customer saved'              => 'Kunde gespeichert',
   'Customer saved!'             => 'Kunde gespeichert!',
+  'Customer specific Price'     => 'Kundenpreis',
   'Customer type'               => 'Kundentyp',
   'Customer variables'          => 'Kundenvariablen',
   'Customer\'s Mandate Date of Signature' => 'Mandatsunterschriftsdatum des Kunden',
@@ -1960,6 +1963,7 @@ $self->{texts} = {
   'No contra account selected!' => 'Kein Gegenkonto ausgewählt!',
   'No custom data exports have been created yet.' => 'Es wurden noch keine benutzerdefinierten Datenexporte angelegt.',
   'No customer has been selected yet.' => 'Es wurde noch kein Kunde ausgewählt.',
+  'No customer selected or found!' => 'Kein Kunde selektiert oder keinen gefunden!',
   'No data was found.'          => 'Es wurden keine Daten gefunden.',
   'No default currency'         => 'Keine Standardwährung',
   'No default value'            => 'Kein Standardwert',
@@ -2206,7 +2210,7 @@ $self->{texts} = {
   'Payable account'             => 'Verbindlichkeitskonto',
   'Payables'                    => 'Verbindlichkeiten',
   'Payment'                     => 'Zahlungsausgang',
-  'Payment / Delivery Options'  => 'Zahlungs- und Lieferoptionen',
+  'Payment / Delivery Options'  => '',
   'Payment Reminder'            => 'Zahlungserinnerung',
   'Payment Terms'               => 'Zahlungsbedingungen',
   'Payment Terms missing in row ' => 'Zahlungsfrist fehlt in Zeile ',
@@ -3442,6 +3446,7 @@ $self->{texts} = {
   'This Price Rule is no longer valid' => 'Diese Preisregel ist nicht mehr gültig',
   'This can be done with the following query:' => 'Dies kann mit der folgenden Datenbankabfrage erreicht werden:',
   'This could have happened for two reasons:' => 'Dies kann aus zwei Gründen geschehen sein:',
+  'This customer has already been added.' => 'Für diesen Kunden ist bereits ein Preis hinzugefügt.',
   'This customer number is already in use.' => 'Diese Kundennummer wird bereits verwendet.',
   'This discount has since gone down' => 'Dieser Rabatt ist mittlerweile niedriger',
   'This discount has since gone up' => 'Dieser Rabatt ist mittlerweile höher',
diff --git a/sql/Pg-upgrade2/create_part_customerprices.sql b/sql/Pg-upgrade2/create_part_customerprices.sql
new file mode 100644
index 000000000..98d230346
--- /dev/null
+++ b/sql/Pg-upgrade2/create_part_customerprices.sql
@@ -0,0 +1,18 @@
+-- @tag: create_part_customerprices
+-- @description: VK-Preis für jeden Kunden speichern und das Datum der Eingabe
+-- @depends: release_3_5_1
+
+CREATE TABLE part_customer_prices (
+  id          SERIAL PRIMARY KEY,
+  parts_id    integer NOT NULL,
+  customer_id integer NOT NULL,
+  customer_partnumber text DEFAULT '',
+  price      numeric(15,5) DEFAULT 0,
+  sortorder  integer DEFAULT 0,
+  lastupdate date DEFAULT now(),
+
+  FOREIGN KEY (parts_id)    REFERENCES parts (id),
+  FOREIGN KEY (customer_id) REFERENCES customer (id)
+);
+CREATE INDEX part_customer_prices_parts_id_key    ON part_customer_prices USING btree (parts_id);
+CREATE INDEX part_customer_prices_customer_id_key ON part_customer_prices USING btree (customer_id);
diff --git a/templates/webpages/part/_basic_data.html b/templates/webpages/part/_basic_data.html
index 06a704180..322acff38 100644
--- a/templates/webpages/part/_basic_data.html
+++ b/templates/webpages/part/_basic_data.html
@@ -223,6 +223,9 @@
  [% PROCESS 'part/_pricegroup_prices.html' %]
 </div>
 
+<div id="customerprices">
+ [% PROCESS 'part/_customerprices.html' %]
+</div>
 [%- UNLESS SELF.part.is_assembly %]
 <div id="makemodel">
  [% PROCESS 'part/_makemodel.html' %]
diff --git a/templates/webpages/part/_customerprice_row.html b/templates/webpages/part/_customerprice_row.html
new file mode 100644
index 000000000..00ec8f089
--- /dev/null
+++ b/templates/webpages/part/_customerprice_row.html
@@ -0,0 +1,23 @@
+[%- USE T8 %]
+[%- USE L %]
+[%- USE HTML %]
+[%- USE LxERP %]
+        <tr class="listrow[% listrow % 2 %] customerprice_row">
+         <td style='display:none'>
+         [% L.hidden_tag("customerprices[+].customer_id", customerprice.customer_id) %]
+         [% L.hidden_tag("customerprices[].id"   , customerprice.id) %]
+         </td>
+         <td align="center">
+           [%- L.button_tag("kivi.Part.delete_customerprice_row(this)",
+                            LxERP.t8("X")) %] [% # , confirm=LxERP.t8("Are you sure?")) %]
+         </td>
+         <td><span name="position" class="numeric">[% HTML.escape(customerprice.sortorder) %]</span></td>
+         <td align="center">
+           <img src="image/updown.png" alt="[%- LxERP.t8('reorder item') %]" class="dragdrop">
+         </td>
+         <td>[% customerprice.customer.customernumber | html %]</td>
+         <td>[% customerprice.customer.name         | html %] </td>
+         <td>[% L.input_tag('customerprices[].customer_partnumber', customerprice.customer_partnumber, size=30 ) %]</td>
+         <td>[% L.input_tag('customerprices[].price_as_number'    , customerprice.price_as_number    , size=15 , class="reformat_number numeric") %]</td>
+         <td>[% L.hidden_tag('customerprices[].lastupdate'         , customerprice.lastupdate.to_kivitendo) %][% customerprice.lastupdate.to_kivitendo | html %]</td>
+        </tr>
diff --git a/templates/webpages/part/_customerprices.html b/templates/webpages/part/_customerprices.html
new file mode 100644
index 000000000..d7c74bcea
--- /dev/null
+++ b/templates/webpages/part/_customerprices.html
@@ -0,0 +1,50 @@
+[%- USE T8 %]
+[%- USE L %]
+[%- USE HTML %]
+[%- USE LxERP %]
+  <tr>
+  </tr>
+  <tr>
+    <td>
+      <table id="customerprice_table">
+        <thead>
+          <th class="listheading" style='text-align:center' nowrap width="1"><img src="image/close.png" alt="[%- LxERP.t8('delete item') %]"></th>
+          <th class="listheading">[% 'position'     | $T8 %]</th>
+          <th class="listheading" style='text-align:center' nowrap width="1"><img src="image/updown.png" alt="[%- LxERP.t8('reorder item') %]"></th>
+          <th class="listheading" style='width:12em'>[% 'Customer Number'      | $T8 %]</th>
+          <th class="listheading">[% 'Customer'             | $T8 %]</th>
+          <th class="listheading">[% 'Customer Part Number' | $T8 %]</th>
+          <th class="listheading">[% 'Customer Price'       | $T8 %]</th>
+          <th class="listheading">[% 'Updated'              | $T8 %]</th>
+        </thead>
+        <tbody id="customerprice_rows">
+        [% SET listrow = 0 %]
+        [%- FOREACH customerprice = SELF.part.customerprices %]
+        [% listrow = listrow + 1 %]
+        [% PROCESS 'part/_customerprice_row.html' customerprice=customerprice listrow=listrow %]
+        [%- END %]
+       </tbody>
+       <tbody>
+        <tr>
+         <td></td>
+         <td></td>
+         <td></td>
+         <td align="right">[% 'Customer' | $T8 %]</td>
+         <td rowspan="2">[% P.customer_vendor.customer_picker('add_customerprice', '', style='width: 300px', class="add_customerprice_input") %]</td>
+         <td rowspan="2" align="right">[% L.button_tag('kivi.Part.add_customerprice_row()', LxERP.t8('Add')) %]</td>
+        </tr>
+       </tbody>
+      </table>
+    </td>
+  </tr>
+  [% L.sortable_element('#customerprice_rows') %]
+
+  <script type="text/javascript">
+  $(function() {
+
+    $('#customerprice_rows').on('sortstop', function(event, ui) {
+      kivi.Part.customerprice_renumber_positions();
+    });
+
+  })
+  </script>
diff --git a/templates/webpages/part/_makemodel.html b/templates/webpages/part/_makemodel.html
index 6f3ecc7a8..b6bd29f27 100644
--- a/templates/webpages/part/_makemodel.html
+++ b/templates/webpages/part/_makemodel.html
@@ -12,7 +12,7 @@
           <th class="listheading" style='text-align:center' nowrap width="1"><img src="image/close.png" alt="[%- LxERP.t8('delete item') %]"></th>
           <th class="listheading">[% 'position'     | $T8 %]</th>
           <th class="listheading" style='text-align:center' nowrap width="1"><img src="image/updown.png" alt="[%- LxERP.t8('reorder item') %]"></th>
-          <th class="listheading">[% 'Vendor Number' | $T8 %]</th>
+          <th class="listheading" style='width:12em'>[% 'Vendor Number' | $T8 %]</th>
           <th class="listheading">[% 'Vendor'        | $T8 %]</th>
           <th class="listheading">[% 'Model'         | $T8 %]</th>
           <th class="listheading">[% 'Last Cost'     | $T8 %]</th>
