X-Git-Url: http://wagnertech.de/git?a=blobdiff_plain;f=SL%2FController%2FOrder.pm;h=eac3ad9db0befe6b16b3951ef09055ab8445c792;hb=f0ddfbfe848f0d94a7a28b8b5593786217c5dd39;hp=a79e3653c0a5b36744c37a23cb3678854411dec4;hpb=92f6fae1e8c4861460ae364ac7dc243b429a9cb6;p=kivitendo-erp.git diff --git a/SL/Controller/Order.pm b/SL/Controller/Order.pm index a79e3653c..eac3ad9db 100644 --- a/SL/Controller/Order.pm +++ b/SL/Controller/Order.pm @@ -13,14 +13,17 @@ use SL::File; use SL::MIME; use SL::Util qw(trim); use SL::YAML; +use SL::DB::History; use SL::DB::Order; use SL::DB::Default; use SL::DB::Unit; use SL::DB::Part; +use SL::DB::PartClassification; use SL::DB::PartsGroup; use SL::DB::Printer; use SL::DB::Language; use SL::DB::RecordLink; +use SL::DB::RequirementSpec; use SL::DB::Shipto; use SL::DB::Translation; @@ -43,7 +46,7 @@ use Sort::Naturally; use Rose::Object::MakeMethods::Generic ( scalar => [ qw(item_ids_to_delete is_custom_shipto_to_delete) ], - 'scalar --get_set_init' => [ qw(order valid_types type cv p all_price_factors search_cvpartnumber show_update_button) ], + 'scalar --get_set_init' => [ qw(order valid_types type cv p all_price_factors search_cvpartnumber show_update_button part_picker_classification_ids) ], ); @@ -67,9 +70,14 @@ sub action_add { my ($self) = @_; $self->order->transdate(DateTime->now_local()); - my $extra_days = $self->{type} eq 'sales_quotation' ? $::instance_conf->get_reqdate_interval : - $self->{type} eq 'sales_order' ? $::instance_conf->get_delivery_date_interval : 1; - $self->order->reqdate(DateTime->today_local->next_workday(extra_days => $extra_days)) if !$self->order->reqdate; + my $extra_days = $self->type eq sales_quotation_type() ? $::instance_conf->get_reqdate_interval : + $self->type eq sales_order_type() ? $::instance_conf->get_delivery_date_interval : 1; + + if ( ($self->type eq sales_order_type() && $::instance_conf->get_deliverydate_on) + || ($self->type eq sales_quotation_type() && $::instance_conf->get_reqdate_on) + && (!$self->order->reqdate)) { + $self->order->reqdate(DateTime->today_local->next_workday(extra_days => $extra_days)); + } $self->pre_render(); @@ -218,11 +226,17 @@ sub action_save_as_new { ? DateTime->today_local : $order->transdate; - # Set new reqdate unless changed + # Set new reqdate unless changed if it is enabled in client config if ($order->reqdate == $saved_order->reqdate) { - my $extra_days = $self->{type} eq 'sales_quotation' ? $::instance_conf->get_reqdate_interval : - $self->{type} eq 'sales_order' ? $::instance_conf->get_delivery_date_interval : 1; - $new_attrs{reqdate} = DateTime->today_local->next_workday(extra_days => $extra_days); + my $extra_days = $self->type eq sales_quotation_type() ? $::instance_conf->get_reqdate_interval : + $self->type eq sales_order_type() ? $::instance_conf->get_delivery_date_interval : 1; + + if ( ($self->type eq sales_order_type() && !$::instance_conf->get_deliverydate_on) + || ($self->type eq sales_quotation_type() && !$::instance_conf->get_reqdate_on)) { + $new_attrs{reqdate} = ''; + } else { + $new_attrs{reqdate} = DateTime->today_local->next_workday(extra_days => $extra_days); + } } else { $new_attrs{reqdate} = $order->reqdate; } @@ -314,38 +328,59 @@ sub action_print { $self->js->flash('info', t8('The PDF has been printed')); } - # copy file to webdav folder - if ($self->order->number && $::instance_conf->get_webdav_documents) { - my $webdav = SL::Webdav->new( - type => $self->type, - number => $self->order->number, - ); - my $webdav_file = SL::Webdav::File->new( - webdav => $webdav, - filename => $pdf_filename, - ); - eval { - $webdav_file->store(data => \$pdf); - 1; - } or do { - $self->js->flash('error', t8('Storing PDF to webdav folder failed: #1', $@)); - } + my @warnings = store_pdf_to_webdav_and_filemanagement($self->order, $pdf, $pdf_filename); + if (scalar @warnings) { + $self->js->flash('warning', $_) for @warnings; } - if ($self->order->number && $::instance_conf->get_doc_storage) { - eval { - SL::File->save(object_id => $self->order->id, - object_type => $self->type, - mime_type => 'application/pdf', - source => 'created', - file_type => 'document', - file_name => $pdf_filename, - file_contents => $pdf); - 1; - } or do { - $self->js->flash('error', t8('Storing PDF in storage backend failed: #1', $@)); - } + + $self->save_history('PRINTED'); + + $self->js + ->run('kivi.ActionBar.setEnabled', '#save_and_email_action') + ->render; +} +sub action_preview_pdf { + my ($self) = @_; + + my $errors = $self->save(); + if (scalar @{ $errors }) { + $self->js->flash('error', $_) foreach @{ $errors }; + return $self->js->render(); } - $self->js->render; + + $self->js_reset_order_and_item_ids_after_save; + + my $format = 'pdf'; + my $media = 'screen'; + my $formname = $self->type; + + # only pdf + # create a form for generate_attachment_filename + my $form = Form->new; + $form->{$self->nr_key()} = $self->order->number; + $form->{type} = $self->type; + $form->{format} = $format; + $form->{formname} = $formname; + $form->{language} = '_' . $self->order->language->template_code if $self->order->language; + my $pdf_filename = $form->generate_attachment_filename(); + + my $pdf; + my @errors = generate_pdf($self->order, \$pdf, { format => $format, + formname => $formname, + language => $self->order->language, + }); + if (scalar @errors) { + return $self->js->flash('error', t8('Conversion to PDF failed: #1', $errors[0]))->render; + } + $self->save_history('PREVIEWED'); + $self->js->flash('info', t8('The PDF has been previewed')); + # screen/download + $self->send_file( + \$pdf, + type => SL::MIME->mime_type_from_ext($pdf_filename), + name => $pdf_filename, + js_no_render => 0, + ); } # open the email dialog @@ -381,6 +416,7 @@ sub action_save_and_show_email_dialog { $form->{language} = '_' . $self->order->language->template_code if $self->order->language; $form->{language_id} = $self->order->language->id if $self->order->language; $form->{format} = 'pdf'; + $form->{cp_id} = $self->order->contact->cp_id if $self->order->contact; $email_form->{subject} = $form->generate_email_subject(); $email_form->{attachment_filename} = $form->generate_attachment_filename(); @@ -388,11 +424,13 @@ sub action_save_and_show_email_dialog { $email_form->{js_send_function} = 'kivi.Order.send_email()'; my %files = $self->get_files_for_email_dialog(); + $self->{all_employees} = SL::DB::Manager::Employee->get_all(query => [ deleted => 0 ]); my $dialog_html = $self->render('common/_send_email_dialog', { output => 0 }, email_form => $email_form, show_bcc => $::auth->assert('email_bcc', 'may fail'), FILES => \%files, is_customer => $self->cv eq 'customer', + ALL_EMPLOYEES => $self->{all_employees}, ); $self->js @@ -441,6 +479,11 @@ sub action_send_email { return $self->js->flash('error', t8('Conversion to PDF failed: #1', $errors[0]))->render($self); } + my @warnings = store_pdf_to_webdav_and_filemanagement($self->order, $pdf, $::form->{attachment_filename}); + if (scalar @warnings) { + flash_later('warning', $_) for @warnings; + } + my $sfile = SL::SessionFile::Random->new(mode => "w"); $sfile->fh->print($pdf); $sfile->fh->close; @@ -465,6 +508,8 @@ sub action_send_email { $self->order->update_attributes(intnotes => $intnotes); + $self->save_history('MAILED'); + flash_later('info', t8('The email has been sent.')); my @redirect_params = ( @@ -508,7 +553,10 @@ sub action_show_periodic_invoices_config_dialog { if ($::form->{customer_id}) { $::form->{ALL_CONTACTS} = SL::DB::Manager::Contact->get_all_sorted(where => [ cp_cv_id => $::form->{customer_id} ]); - $::form->{email_recipient_invoice_address} = SL::DB::Manager::Customer->find_by(id => $::form->{customer_id})->invoice_mail; + my $customer_object = SL::DB::Manager::Customer->find_by(id => $::form->{customer_id}); + $::form->{postal_invoice} = $customer_object->postal_invoice; + $::form->{email_recipient_invoice_address} = $::form->{postal_invoice} ? '' : $customer_object->invoice_mail; + $config->send_email(0) if $::form->{postal_invoice}; } $self->render('oe/edit_periodic_invoices_config', { layout => 0 }, @@ -730,6 +778,8 @@ sub action_unit_changed { sub action_add_item { my ($self) = @_; + delete $::form->{add_item}->{create_part_type}; + my $form_attr = $::form->{add_item}; return unless $form_attr->{parts_id}; @@ -929,6 +979,61 @@ sub action_price_popup { $self->render_price_dialog($item); } +# save the order in a session variable and redirect to the part controller +sub action_create_part { + my ($self) = @_; + + my $previousform = $::auth->save_form_in_session(non_scalars => 1); + + my $callback = $self->url_for( + action => 'return_from_create_part', + type => $self->type, # type is needed for check_auth on return + previousform => $previousform, + ); + + flash_later('info', t8('You are adding a new part while you are editing another document. You will be redirected to your document when saving the new part or aborting this form.')); + + my @redirect_params = ( + controller => 'Part', + action => 'add', + part_type => $::form->{add_item}->{create_part_type}, + callback => $callback, + show_abort => 1, + ); + + $self->redirect_to(@redirect_params); +} + +sub action_return_from_create_part { + my ($self) = @_; + + $self->{created_part} = SL::DB::Part->new(id => delete $::form->{new_parts_id})->load if $::form->{new_parts_id}; + + $::auth->restore_form_from_session(delete $::form->{previousform}); + + # set item ids to new fake id, to identify them as new items + foreach my $item (@{$self->order->items_sorted}) { + $item->{new_fake_id} = join('_', 'new', Time::HiRes::gettimeofday(), int rand 1000000000000); + } + + $self->recalc(); + $self->get_unalterable_data(); + $self->pre_render(); + + # trigger rendering values for second row/longdescription as hidden, + # because they are loaded only on demand. So we need to keep the values + # from the source. + $_->{render_second_row} = 1 for @{ $self->order->items_sorted }; + $_->{render_longdescription} = 1 for @{ $self->order->items_sorted }; + + $self->render( + 'order/form', + title => $self->get_title_for('edit'), + %{$self->{template_args}} + ); + +} + # load the second row for one or more items # # This action gets the html code for all items second rows by rendering a template for @@ -1170,6 +1275,13 @@ sub init_all_price_factors { SL::DB::Manager::PriceFactor->get_all; } +sub init_part_picker_classification_ids { + my ($self) = @_; + my $attribute = 'used_for_' . ($self->type =~ m{sales} ? 'sale' : 'purchase'); + + return [ map { $_->id } @{ SL::DB::Manager::PartClassification->get_all(where => [ $attribute => 1 ]) } ]; +} + sub check_auth { my ($self) = @_; @@ -1537,6 +1649,8 @@ sub delete { my $spool = $::lx_office_conf{paths}->{spool}; unlink map { "$spool/$_" } @spoolfiles if $spool; + $self->save_history('DELETED'); + 1; }) || push(@{$errors}, $db->error); @@ -1565,6 +1679,7 @@ sub save { # link records if ($::form->{converted_from_oe_id}) { my @converted_from_oe_ids = split ' ', $::form->{converted_from_oe_id}; + foreach my $converted_from_oe_id (@converted_from_oe_ids) { my $src = SL::DB::Order->new(id => $converted_from_oe_id)->load; $src->update_attributes(closed => 1) if $src->type =~ /_quotation$/; @@ -1583,7 +1698,12 @@ sub save { $idx++; } } + + $self->link_requirement_specs_linking_to_created_from_objects(@converted_from_oe_ids); } + + $self->save_history('SAVED'); + 1; }) || push(@{$errors}, $db->error); @@ -1739,8 +1859,10 @@ sub pre_render { } if (any { $self->type eq $_ } (sales_order_type(), purchase_order_type())) { - # calculate shipped qtys here to prevent calling calculate for every item via the items method - SL::Helper::ShippedQty->new->calculate($self->order)->write_to_objects; + # Calculate shipped qtys here to prevent calling calculate for every item via the items method. + # Do not use write_to_objects to prevent order->delivered to be set, because this should be + # the value from db, which can be set manually or is set when linked delivery orders are saved. + SL::Helper::ShippedQty->new->calculate($self->order)->write_to(\@{$self->order->items}); } if ($self->order->number && $::instance_conf->get_webdav) { @@ -1755,10 +1877,15 @@ sub pre_render { } } @all_objects; } + if ( (any { $self->type eq $_ } (sales_quotation_type(), sales_order_type())) + && $::instance_conf->get_transport_cost_reminder_article_number_id ) { + $self->{template_args}->{transport_cost_reminder_article} = SL::DB::Part->new(id => $::instance_conf->get_transport_cost_reminder_article_number_id)->load; + } + $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 kivi.Validator follow_up); + edit_periodic_invoices_config calculate_qty kivi.Validator follow_up show_history); $self->setup_edit_action_bar; } @@ -1769,6 +1896,9 @@ sub setup_edit_action_bar { || (($self->type eq sales_order_type()) && $::instance_conf->get_sales_order_show_delete) || (($self->type eq purchase_order_type()) && $::instance_conf->get_purchase_order_show_delete); + my @req_trans_cost_art = qw(kivi.Order.check_transport_cost_article_presence) x!!$::instance_conf->get_transport_cost_reminder_article_number_id; + my @req_cusordnumber = qw(kivi.Order.check_cusordnumber_presence) x($self->type eq sales_order_type() && $::instance_conf->get_order_warn_no_cusordnumber); + for my $bar ($::request->layout->get('actionbar')) { $bar->add( combobox => [ @@ -1776,13 +1906,17 @@ sub setup_edit_action_bar { t8('Save'), 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', ['kivi.validate_form','#order_form'] ], + ], + checks => [ 'kivi.Order.check_save_active_periodic_invoices', ['kivi.validate_form','#order_form'], + @req_trans_cost_art, @req_cusordnumber, + ], ], action => [ t8('Save as new'), call => [ 'kivi.Order.save', 'save_as_new', $::instance_conf->get_order_warn_duplicate_parts ], - checks => [ 'kivi.Order.check_save_active_periodic_invoices' ], + checks => [ 'kivi.Order.check_save_active_periodic_invoices', + @req_trans_cost_art, @req_cusordnumber, + ], disabled => !$self->order->id ? t8('This object has not been saved yet.') : undef, ], ], # end of combobox "Save" @@ -1794,6 +1928,7 @@ sub setup_edit_action_bar { action => [ t8('Save and Quotation'), submit => [ '#order_form', { action => "Order/sales_quotation" } ], + checks => [ @req_trans_cost_art, @req_cusordnumber ], only_if => (any { $self->type eq $_ } (sales_order_type())), ], action => [ @@ -1804,11 +1939,13 @@ sub setup_edit_action_bar { action => [ t8('Save and Sales Order'), submit => [ '#order_form', { action => "Order/sales_order" } ], + checks => [ @req_trans_cost_art ], only_if => (any { $self->type eq $_ } (sales_quotation_type(), purchase_order_type())), ], action => [ t8('Save and Purchase Order'), call => [ 'kivi.Order.purchase_order_check_for_direct_delivery' ], + checks => [ @req_trans_cost_art, @req_cusordnumber ], only_if => (any { $self->type eq $_ } (sales_order_type(), request_quotation_type())), ], action => [ @@ -1816,13 +1953,17 @@ sub setup_edit_action_bar { call => [ 'kivi.Order.save', 'save_and_delivery_order', $::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', + @req_trans_cost_art, @req_cusordnumber, + ], only_if => (any { $self->type eq $_ } (sales_order_type(), purchase_order_type())) ], action => [ t8('Save and Invoice'), call => [ 'kivi.Order.save', 'save_and_invoice', $::instance_conf->get_order_warn_duplicate_parts ], - checks => [ 'kivi.Order.check_save_active_periodic_invoices' ], + checks => [ 'kivi.Order.check_save_active_periodic_invoices', + @req_trans_cost_art, @req_cusordnumber, + ], ], action => [ t8('Save and AP Transaction'), @@ -1836,13 +1977,26 @@ sub setup_edit_action_bar { action => [ t8('Export'), ], + action => [ + t8('Save and preview PDF'), + call => [ 'kivi.Order.save', 'preview_pdf', $::instance_conf->get_order_warn_duplicate_parts, + $::instance_conf->get_order_warn_no_deliverydate, + ], + checks => [ @req_trans_cost_art, @req_cusordnumber ], + ], action => [ t8('Save and print'), - call => [ 'kivi.Order.show_print_options', $::instance_conf->get_order_warn_duplicate_parts ], + call => [ 'kivi.Order.show_print_options', $::instance_conf->get_order_warn_duplicate_parts, + $::instance_conf->get_order_warn_no_deliverydate, + ], + checks => [ @req_trans_cost_art, @req_cusordnumber ], ], action => [ t8('Save and E-mail'), - call => [ 'kivi.Order.save', 'save_and_show_email_dialog', $::instance_conf->get_order_warn_duplicate_parts ], + id => 'save_and_email_action', + call => [ 'kivi.Order.save', 'save_and_show_email_dialog', $::instance_conf->get_order_warn_duplicate_parts, + $::instance_conf->get_order_warn_no_deliverydate, + ], disabled => !$self->order->id ? t8('This object has not been saved yet.') : undef, ], action => [ @@ -1865,6 +2019,11 @@ sub setup_edit_action_bar { action => [ t8('more') ], + action => [ + t8('History'), + call => [ 'set_history_window', $self->order->id, 'id' ], + disabled => !$self->order->id ? t8('This record has not been saved yet.') : undef, + ], action => [ t8('Follow-Up'), call => [ 'kivi.Order.follow_up_window' ], @@ -1948,6 +2107,7 @@ sub get_files_for_email_dialog { $files{versions} = [ SL::File->get_all_versions(object_id => $self->order->id, object_type => $self->order->type, file_type => 'document') ]; $files{files} = [ SL::File->get_all( object_id => $self->order->id, object_type => $self->order->type, file_type => 'attachment') ]; $files{vc_files} = [ SL::File->get_all( object_id => $self->order->{$self->cv}->id, object_type => $self->cv, file_type => 'attachment') ]; + $files{project_files} = [ SL::File->get_all( object_id => $self->order->globalproject_id, object_type => 'project', file_type => 'attachment') ]; } my @parts = @@ -2098,6 +2258,76 @@ sub save_and_redirect_to { $self->redirect_to(%params, id => $self->order->id); } +sub save_history { + my ($self, $addition) = @_; + + my $number_type = $self->order->type =~ m{order} ? 'ordnumber' : 'quonumber'; + my $snumbers = $number_type . '_' . $self->order->$number_type; + + SL::DB::History->new( + trans_id => $self->order->id, + employee_id => SL::DB::Manager::Employee->current->id, + what_done => $self->order->type, + snumbers => $snumbers, + addition => $addition, + )->save; +} + +sub store_pdf_to_webdav_and_filemanagement { + my($order, $content, $filename) = @_; + + my @errors; + + # copy file to webdav folder + if ($order->number && $::instance_conf->get_webdav_documents) { + my $webdav = SL::Webdav->new( + type => $order->type, + number => $order->number, + ); + my $webdav_file = SL::Webdav::File->new( + webdav => $webdav, + filename => $filename, + ); + eval { + $webdav_file->store(data => \$content); + 1; + } or do { + push @errors, t8('Storing PDF to webdav folder failed: #1', $@); + }; + } + if ($order->id && $::instance_conf->get_doc_storage) { + eval { + SL::File->save(object_id => $order->id, + object_type => $order->type, + mime_type => 'application/pdf', + source => 'created', + file_type => 'document', + file_name => $filename, + file_contents => $content); + 1; + } or do { + push @errors, t8('Storing PDF in storage backend failed: #1', $@); + }; + } + + return @errors; +} + +sub link_requirement_specs_linking_to_created_from_objects { + my ($self, @converted_from_oe_ids) = @_; + + return unless @converted_from_oe_ids; + + my $rs_orders = SL::DB::Manager::RequirementSpecOrder->get_all(where => [ order_id => \@converted_from_oe_ids ]); + foreach my $rs_order (@{ $rs_orders }) { + SL::DB::RequirementSpecOrder->new( + order_id => $self->order->id, + requirement_spec_id => $rs_order->requirement_spec_id, + version_id => $rs_order->version_id, + )->save; + } +} + 1; __END__ @@ -2211,8 +2441,6 @@ java script functions =item * testing -=item * credit limit - =item * price sources: little symbols showing better price / better discount =item * select units in input row? @@ -2223,14 +2451,12 @@ java script functions =item * display weights -=item * history - =item * mtime check =item * optional client/user behaviour (transactions has to be set - department has to be set - - force project if enabled in client config - transport cost reminder) + force project if enabled in client config) =back