Kunden-Spezifische Artikeleigenschaften
authorMartin Helmling martin.helmling@octosoft.eu <martin.helmling@octosoft.eu>
Mon, 9 Jan 2017 10:55:21 +0000 (11:55 +0100)
committerSven Schöling <s.schoeling@linet-services.de>
Mon, 8 Jan 2018 17:02:26 +0000 (18:02 +0100)
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

21 files changed:
SL/Controller/Part.pm
SL/DB/Helper/ALL.pm
SL/DB/Helper/Mappings.pm
SL/DB/Manager/PartCustomerPrice.pm [new file with mode: 0644]
SL/DB/MetaSetup/Part.pm
SL/DB/MetaSetup/PartCustomerPrice.pm [new file with mode: 0644]
SL/DB/Part.pm
SL/DB/PartCustomerPrice.pm [new file with mode: 0644]
SL/IR.pm
SL/IS.pm
SL/PriceSource/ALL.pm
SL/PriceSource/CustomerPrice.pm [new file with mode: 0644]
bin/mozilla/ic.pl
bin/mozilla/is.pl
js/kivi.Part.js
locale/de/all
sql/Pg-upgrade2/create_part_customerprices.sql [new file with mode: 0644]
templates/webpages/part/_basic_data.html
templates/webpages/part/_customerprice_row.html [new file with mode: 0644]
templates/webpages/part/_customerprices.html [new file with mode: 0644]
templates/webpages/part/_makemodel.html

index e4bb712..7b4a2bc 100644 (file)
@@ -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;
index 4429f8f..0e6ed07 100644 (file)
@@ -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;
index f9d4745..6290bd3 100644 (file)
@@ -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 (file)
index 0000000..4f93ddc
--- /dev/null
@@ -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;
index 6e758e4..ef6ed69 100644 (file)
@@ -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 (file)
index 0000000..c3d4243
--- /dev/null
@@ -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;
+;
index 6dde98f..d7bc6db 100644 (file)
@@ -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 (file)
index 0000000..f3fcde4
--- /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::PartCustomerPrice;
+
+use strict;
+
+use SL::DB::MetaSetup::PartCustomerPrice;
+use SL::DB::Manager::PartCustomerPrice;
+
+__PACKAGE__->meta->initialize;
+
+1;
index 45cd7de..ebb29fd 100644 (file)
--- 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;
 
   }
index 609d3c0..e1eb443 100644 (file)
--- 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;
index dd6ee2f..e7c553e 100644 (file)
@@ -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 (file)
index 0000000..666c37c
--- /dev/null
@@ -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;
index 9fd0589..35affe9 100644 (file)
@@ -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'),
index 26fb0ea..0b82cd0 100644 (file)
@@ -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();
       }
     }
index dc05a9a..9e79932 100644 (file)
@@ -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();
index 843720d..4a9ca5d 100755 (executable)
@@ -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 (file)
index 0000000..98d2303
--- /dev/null
@@ -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);
index 06a7041..322acff 100644 (file)
  [% 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 (file)
index 0000000..00ec8f0
--- /dev/null
@@ -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 (file)
index 0000000..d7c74bc
--- /dev/null
@@ -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>
index 6f3ecc7..b6bd29f 100644 (file)
@@ -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>