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
->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) = @_;
$self->part->prices([]);
$self->parse_form_prices;
+ $self->parse_form_customerprices;
$self->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',
# 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});
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;
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;
parts => 'part',
partsgroup => 'parts_group',
part_classifications => 'PartClassification',
+ part_customer_prices => 'PartCustomerPrice',
parts_price_history => 'PartsPriceHistory',
payment_terms => 'payment_term',
periodic_invoices => 'periodic_invoice',
--- /dev/null
+# 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;
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' ]);
--- /dev/null
+# 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;
+;
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',
--- /dev/null
+# 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;
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);
$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;
}
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;
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;
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',
);
vendor_discount
pricegroup
makemodel
+ customerprice
business
price_rules
);
--- /dev/null
+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;
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'),
# 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"} = "";
} else {
$form->{"id_$i"} = 0;
+ if ( $form->{is_wrong_ptype} > 0 ) {
+ $form->{"partnumber_$i"} = "";
+ $form->{"description_$i"} = "";
+ }
new_item();
}
}
$("#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);
}
}
});
+ $('.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();
'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',
'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',
'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 ',
'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',
--- /dev/null
+-- @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);
[% 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' %]
--- /dev/null
+[%- 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>
--- /dev/null
+[%- 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>
<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>