use parent qw(SL::Controller::Base);
use SL::Helper::Flash qw(flash_later);
-use SL::Presenter::Tag qw(select_tag hidden_tag);
+use SL::Presenter::Tag qw(select_tag hidden_tag div_tag);
use SL::Locale::String qw(t8);
use SL::SessionFile::Random;
use SL::PriceSource;
use SL::DB::Printer;
use SL::DB::Language;
use SL::DB::RecordLink;
+use SL::DB::Shipto;
use SL::Helper::CreatePDF qw(:all);
use SL::Helper::PrintOptions;
use Rose::Object::MakeMethods::Generic
(
- scalar => [ qw(item_ids_to_delete) ],
+ scalar => [ qw(item_ids_to_delete is_custom_shipto_to_delete) ],
'scalar --get_set_init' => [ qw(order valid_types type cv p multi_items_models all_price_factors search_cvpartnumber show_update_button) ],
);
}
# open the email dialog
-sub action_show_email_dialog {
+sub action_save_and_show_email_dialog {
my ($self) = @_;
+ my $errors = $self->save();
+
+ if (scalar @{ $errors }) {
+ $self->js->flash('error', $_) foreach @{ $errors };
+ return $self->js->render();
+ }
+
my $cv_method = $self->cv;
if (!$self->order->$cv_method) {
my $form = Form->new;
$form->{$self->nr_key()} = $self->order->number;
+ $form->{cusordnumber} = $self->order->cusordnumber;
$form->{formname} = $self->type;
$form->{type} = $self->type;
$form->{language} = '_' . $self->order->language->template_code if $self->order->language;
$::form->{tmpdir} = $sfile->get_path; # for Form::cleanup which may be called in Form::send_email
}
+ $::form->{id} = $self->order->id; # this is used in SL::Mailer to create a linked record to the mail
$::form->send_email(\%::myconfig, 'pdf');
# internal notes
$self->order->update_attributes(intnotes => $intnotes);
- $self->js
- ->val('#order_intnotes', $intnotes)
- ->run('kivi.Order.close_email_dialog')
- ->flash('info', t8('The email has been sent.'))
- ->render($self);
+ flash_later('info', t8('The email has been sent.'));
+
+ my @redirect_params = (
+ action => 'edit',
+ type => $self->type,
+ id => $self->order->id,
+ );
+
+ $self->redirect_to(@redirect_params);
}
# open the periodic invoices config dialog
}
if ($self->order->$cv_method->shipto && scalar @{ $self->order->$cv_method->shipto } > 0) {
- $self->js->show('#shipto_row');
+ $self->js->show('#shipto_selection');
} else {
- $self->js->hide('#shipto_row');
+ $self->js->hide('#shipto_selection');
}
$self->js->val( '#order_salesman_id', $self->order->salesman_id) if $self->order->is_sales;
$self->js
->replaceWith('#order_cp_id', $self->build_contact_select)
->replaceWith('#order_shipto_id', $self->build_shipto_select)
+ ->replaceWith('#shipto_inputs ', $self->build_shipto_inputs)
->replaceWith('#business_info_row', $self->build_business_info_row)
->val( '#order_taxzone_id', $self->order->taxzone_id)
->val( '#order_taxincluded', $self->order->taxincluded)
+ ->val( '#order_currency_id', $self->order->currency_id)
->val( '#order_payment_id', $self->order->payment_id)
->val( '#order_delivery_term_id', $self->order->delivery_term_id)
->val( '#order_intnotes', $self->order->intnotes)
->val( '#language_id', $self->order->$cv_method->language_id)
- ->focus( '#order_' . $self->cv . '_id');
+ ->focus( '#order_' . $self->cv . '_id')
+ ->run('kivi.Order.update_exchangerate');
$self->js_redisplay_amounts_and_taxes;
$self->js_redisplay_cvpartnumbers;
$self->js->render();
}
+sub action_update_exchangerate {
+ my ($self) = @_;
+
+ my $data = {
+ is_standard => $self->order->currency_id == $::instance_conf->get_currency_id,
+ currency_name => $self->order->currency->name,
+ exchangerate => $self->order->daily_exchangerate_as_null_number,
+ };
+
+ $self->render(\SL::JSON::to_json($data), { type => 'json', process => 0 });
+}
+
# redisplay item rows if they are sorted by an attribute
sub action_reorder_items {
my ($self) = @_;
$price_src = $price_source->best_price
? $price_source->best_price
: $price_source->price_from_source("");
+ $price_src->price($::form->round_amount($price_src->price / $self->order->exchangerate, 5)) if $self->order->exchangerate;
$price_src->price(0) if !$price_source->best_price;
}
+
$item->sellprice($price_src->price);
$item->active_price_source($price_src);
sub build_shipto_select {
my ($self) = @_;
- select_tag('order.shipto_id', [ $self->order->{$self->cv}->shipto ],
- value_key => 'shipto_id',
- title_key => 'displayable_id',
- default => $self->order->shipto_id,
- with_empty => 1,
- style => 'width: 300px',
+ select_tag('order.shipto_id',
+ [ {displayable_id => t8("No/individual shipping address"), shipto_id => ''}, $self->order->{$self->cv}->shipto ],
+ value_key => 'shipto_id',
+ title_key => 'displayable_id',
+ default => $self->order->shipto_id,
+ with_empty => 0,
+ style => 'width: 300px',
);
}
+# build the inputs for the cusom shipto dialog
+#
+# Needed, if customer/vendor changed.
+sub build_shipto_inputs {
+ my ($self) = @_;
+
+ my $content = $self->p->render('common/_ship_to_dialog',
+ vc_obj => $self->order->customervendor,
+ cs_obj => $self->order->custom_shipto,
+ cvars => $self->order->custom_shipto->cvars_by_config,
+ id_selector => '#order_shipto_id');
+
+ div_tag($content, id => 'shipto_inputs');
+}
+
# render the info line for business
#
# Needed, if customer/vendor changed.
return if !$::form->{id};
$self->order(SL::DB::Order->new(id => $::form->{id})->load);
+
+ # Add an empty custom shipto to the order, so that the dialog can render the cvar inputs.
+ # You need a custom shipto object to call cvars_by_config to get the cvars.
+ $self->order->custom_shipto(SL::DB::Shipto->new(module => 'OE', custom_variables => [])) if !$self->order->custom_shipto;
+
+ return $self->order;
}
# load or create a new order object
# order here solves this problem.
my $order;
$order = SL::DB::Order->new(id => $::form->{id})->load(with => [ 'orderitems', 'orderitems.part' ]) if $::form->{id};
- $order ||= SL::DB::Order->new(orderitems => [],
- quotation => (any { $self->type eq $_ } (sales_quotation_type(), request_quotation_type())));
+ $order ||= SL::DB::Order->new(orderitems => [],
+ quotation => (any { $self->type eq $_ } (sales_quotation_type(), request_quotation_type())),
+ currency_id => $::instance_conf->get_currency_id(),);
my $cv_id_method = $self->cv . '_id';
if (!$::form->{id} && $::form->{$cv_id_method}) {
setup_order_from_cv($order);
}
- my $form_orderitems = delete $::form->{order}->{orderitems};
- my $form_periodic_invoices_config = delete $::form->{order}->{periodic_invoices_config};
+ my $form_orderitems = delete $::form->{order}->{orderitems};
+ my $form_periodic_invoices_config = delete $::form->{order}->{periodic_invoices_config};
$order->assign_attributes(%{$::form->{order}});
+ $self->setup_custom_shipto_from_form($order, $::form);
+
if (my $periodic_invoices_config_attrs = $form_periodic_invoices_config ? SL::YAML::Load($form_periodic_invoices_config) : undef) {
my $periodic_invoices_config = $order->periodic_invoices_config || $order->periodic_invoices_config(SL::DB::PeriodicInvoicesConfig->new);
$periodic_invoices_config->assign_attributes(%$periodic_invoices_config_attrs);
$price_src->price($item->sellprice);
} else {
$price_src = $price_source->best_price
- ? $price_source->best_price
- : $price_source->price_from_source("");
+ ? $price_source->best_price
+ : $price_source->price_from_source("");
+ $price_src->price($::form->round_amount($price_src->price / $record->exchangerate, 5)) if $record->exchangerate;
$price_src->price(0) if !$price_source->best_price;
}
sub setup_order_from_cv {
my ($order) = @_;
- $order->$_($order->customervendor->$_) for (qw(taxzone_id payment_id delivery_term_id));
+ $order->$_($order->customervendor->$_) for (qw(taxzone_id payment_id delivery_term_id currency_id));
$order->intnotes($order->customervendor->notes);
}
+# setup custom shipto from form
+#
+# The dialog returns form variables starting with 'shipto' and cvars starting
+# with 'shiptocvar_'.
+# Mark it to be deleted if a shipto from master data is selected
+# (i.e. order has a shipto).
+# Else, update or create a new custom shipto. If the fields are empty, it
+# will not be saved on save.
+sub setup_custom_shipto_from_form {
+ my ($self, $order, $form) = @_;
+
+ if ($order->shipto) {
+ $self->is_custom_shipto_to_delete(1);
+ } else {
+ my $custom_shipto = $order->custom_shipto || $order->custom_shipto(SL::DB::Shipto->new(module => 'OE', custom_variables => []));
+
+ my $shipto_cvars = {map { my ($key) = m{^shiptocvar_(.+)}; $key => delete $form->{$_}} grep { m{^shiptocvar_} } keys %$form};
+ my $shipto_attrs = {map { $_ => delete $form->{$_}} grep { m{^shipto} } keys %$form};
+
+ $custom_shipto->assign_attributes(%$shipto_attrs);
+ $custom_shipto->cvar_by_name($_)->value($shipto_cvars->{$_}) for keys %$shipto_cvars;
+ }
+}
+
# recalculate prices and taxes
#
# Using the PriceTaxCalculator. Store linetotals in the item objects.
sub recalc {
my ($self) = @_;
- # bb: todo: currency later
- $self->order->currency_id($::instance_conf->get_currency_id());
-
my %pat = $self->order->calculate_prices_and_taxes();
$self->{taxes} = [];
my $db = $self->order->db;
$db->with_transaction(sub {
+ # delete custom shipto if it is to be deleted or if it is empty
+ if ($self->order->custom_shipto && ($self->is_custom_shipto_to_delete || $self->order->custom_shipto->is_empty)) {
+ $self->order->custom_shipto->delete if $self->order->custom_shipto->shipto_id;
+ $self->order->custom_shipto(undef);
+ }
+
SL::DB::OrderItem->new(id => $_)->delete for @{$self->item_ids_to_delete || []};
$self->order->save(cascade => 1);
: $::form->{type} eq sales_order_type() ? purchase_order_type()
: '';
+ # check for direct delivery
+ # copy shipto in custom shipto (custom shipto will be copied by new_from() in case)
+ my $custom_shipto;
+ if ( $::form->{type} eq sales_order_type() && $destination_type eq purchase_order_type()
+ && $::form->{use_shipto} && $self->order->shipto) {
+ $custom_shipto = $self->order->shipto->clone('SL::DB::Order');
+ }
+
$self->order(SL::DB::Order->new_from($self->order, destination_type => $destination_type));
$self->{converted_from_oe_id} = delete $::form->{id};
$item->{new_fake_id} = join('_', 'new', Time::HiRes::gettimeofday(), int rand 1000000000000);
}
+ if ($::form->{type} eq sales_order_type() && $destination_type eq purchase_order_type()) {
+ if ($::form->{use_shipto}) {
+ $self->order->custom_shipto($custom_shipto) if $custom_shipto;
+ } else {
+ # remove any custom shipto if not wanted
+ $self->order->custom_shipto(SL::DB::Shipto->new(module => 'OE', custom_variables => []));
+ }
+ }
+
# change form type
$::form->{type} = $destination_type;
$self->type($self->init_type);
my ($self) = @_;
$self->{all_taxzones} = SL::DB::Manager::TaxZone->get_all_sorted();
+ $self->{all_currencies} = SL::DB::Manager::Currency->get_all_sorted();
$self->{all_departments} = SL::DB::Manager::Department->get_all_sorted();
$self->{all_employees} = SL::DB::Manager::Employee->get_all(where => [ or => [ id => $self->order->employee_id,
deleted => 0 ] ],
$self->get_item_cvpartnumber($_) for @{$self->order->items_sorted};
- $::request->{layout}->use_javascript("${_}.js") for qw(kivi.SalesPurchase kivi.Order kivi.File ckeditor/ckeditor ckeditor/adapters/jquery edit_periodic_invoices_config calculate_qty);
+ $::request->{layout}->use_javascript("${_}.js") for qw(kivi.SalesPurchase kivi.Order kivi.File ckeditor/ckeditor ckeditor/adapters/jquery
+ edit_periodic_invoices_config calculate_qty kivi.Validator follow_up);
$self->setup_edit_action_bar;
}
call => [ 'kivi.Order.save', 'save', $::instance_conf->get_order_warn_duplicate_parts,
$::instance_conf->get_order_warn_no_deliverydate,
],
- checks => [ 'kivi.Order.check_save_active_periodic_invoices' ],
+ checks => [ 'kivi.Order.check_save_active_periodic_invoices', ['kivi.validate_form','#order_form'] ],
],
action => [
t8('Save as new'),
t8('Save and Sales Order'),
submit => [ '#order_form', { action => "Order/sales_order" } ],
only_if => (any { $self->type eq $_ } (sales_quotation_type(), purchase_order_type())),
- disabled => !$self->order->id ? t8('This object has not been saved yet.') : undef,
],
action => [
t8('Save and Purchase Order'),
- submit => [ '#order_form', { action => "Order/purchase_order" } ],
- only_if => (any { $self->type eq $_ } (sales_order_type(), request_quotation_type())),
- disabled => !$self->order->id ? t8('This object has not been saved yet.') : undef,
+ call => [ 'kivi.Order.purchase_order_check_for_direct_delivery' ],
+ only_if => (any { $self->type eq $_ } (sales_order_type(), request_quotation_type())),
],
action => [
t8('Save and Delivery Order'),
],
action => [
t8('Save and E-mail'),
- call => [ 'kivi.Order.email', $::instance_conf->get_order_warn_duplicate_parts ],
+ call => [ 'kivi.Order.save', 'save_and_show_email_dialog', $::instance_conf->get_order_warn_duplicate_parts ],
+ disabled => !$self->order->id ? t8('This object has not been saved yet.') : undef,
],
action => [
t8('Download attachments of all parts'),
disabled => !$self->order->id ? t8('This object has not been saved yet.') : undef,
only_if => $deletion_allowed,
],
+
+ combobox => [
+ action => [
+ t8('more')
+ ],
+ action => [
+ t8('Follow-Up'),
+ call => [ 'kivi.Order.follow_up_window' ],
+ disabled => !$self->order->id ? t8('This object has not been saved yet.') : undef,
+ only_if => $::auth->assert('productivity'),
+ ],
+ ], # end of combobox "more"
);
}
}
=item * testing
-=item * currency
-
=item * credit limit
=item * more workflows (quotation, rfq)
=item * select units in input row?
-=item * custom shipto address
-
=item * check for direct delivery (workflow sales order -> purchase order)
=item * language / part translations