X-Git-Url: http://wagnertech.de/gitweb/gitweb.cgi/mfinanz.git/blobdiff_plain/5c859d6455cd282a69bc354bd270efb87e1b8760..7ff8f33c49bc503d16ddda159c9848f88b80d5ad:/SL/Controller/Order.pm diff --git a/SL/Controller/Order.pm b/SL/Controller/Order.pm index ac619d5bd..ace7accba 100644 --- a/SL/Controller/Order.pm +++ b/SL/Controller/Order.pm @@ -10,13 +10,14 @@ use SL::SessionFile::Random; use SL::PriceSource; use SL::Webdav; use SL::File; - +use SL::Util qw(trim); use SL::DB::Order; use SL::DB::Default; use SL::DB::Unit; use SL::DB::Part; use SL::DB::Printer; use SL::DB::Language; +use SL::DB::RecordLink; use SL::Helper::CreatePDF qw(:all); use SL::Helper::PrintOptions; @@ -41,10 +42,10 @@ use Rose::Object::MakeMethods::Generic __PACKAGE__->run_before('_check_auth'); __PACKAGE__->run_before('_recalc', - only => [ qw(save save_and_delivery_order save_and_invoice print create_pdf send_email) ]); + only => [ qw(save save_as_new save_and_delivery_order save_and_invoice print create_pdf send_email) ]); __PACKAGE__->run_before('_get_unalterable_data', - only => [ qw(save save_and_delivery_order save_and_invoice print create_pdf send_email) ]); + only => [ qw(save save_as_new save_and_delivery_order save_and_invoice print create_pdf send_email) ]); # # actions @@ -55,14 +56,13 @@ sub action_add { my ($self) = @_; $self->order->transdate(DateTime->now_local()); - $self->order->reqdate(DateTime->today_local->next_workday) if !$self->order->reqdate; + my $extra_days = $self->type eq _sales_quotation_type() ? $::instance_conf->get_reqdate_interval : 1; + $self->order->reqdate(DateTime->today_local->next_workday(extra_days => $extra_days)) if !$self->order->reqdate; $self->_pre_render(); $self->render( 'order/form', - title => $self->type eq _sales_order_type() ? $::locale->text('Add Sales Order') - : $self->type eq _purchase_order_type() ? $::locale->text('Add Purchase Order') - : '', + title => $self->_get_title_for('add'), %{$self->{template_args}} ); } @@ -76,9 +76,7 @@ sub action_edit { $self->_pre_render(); $self->render( 'order/form', - title => $self->type eq _sales_order_type() ? $::locale->text('Edit Sales Order') - : $self->type eq _purchase_order_type() ? $::locale->text('Edit Purchase Order') - : '', + title => $self->_get_title_for('edit'), %{$self->{template_args}} ); } @@ -94,7 +92,13 @@ sub action_delete { return $self->js->render(); } - flash_later('info', $::locale->text('The order has been deleted')); + my $text = $self->type eq _sales_order_type() ? $::locale->text('The order has been deleted') + : $self->type eq _purchase_order_type() ? $::locale->text('The order has been deleted') + : $self->type eq _sales_quotation_type() ? $::locale->text('The quotation has been deleted') + : $self->type eq _request_quotation_type() ? $::locale->text('The rfq has been deleted') + : ''; + flash_later('info', $text); + my @redirect_params = ( action => 'add', type => $self->type, @@ -114,7 +118,13 @@ sub action_save { return $self->js->render(); } - flash_later('info', $::locale->text('The order has been saved')); + my $text = $self->type eq _sales_order_type() ? $::locale->text('The order has been saved') + : $self->type eq _purchase_order_type() ? $::locale->text('The order has been saved') + : $self->type eq _sales_quotation_type() ? $::locale->text('The quotation has been saved') + : $self->type eq _request_quotation_type() ? $::locale->text('The rfq has been saved') + : ''; + flash_later('info', $text); + my @redirect_params = ( action => 'edit', type => $self->type, @@ -124,6 +134,53 @@ sub action_save { $self->redirect_to(@redirect_params); } +# save the order as new document an open it for edit +sub action_save_as_new { + my ($self) = @_; + + my $order = $self->order; + + if (!$order->id) { + $self->js->flash('error', t8('This object has not been saved yet.')); + return $self->js->render(); + } + + # load order from db to check if values changed + my $saved_order = SL::DB::Order->new(id => $order->id)->load; + + my %new_attrs; + # Lets assign a new number if the user hasn't changed the previous one. + # If it has been changed manually then use it as-is. + $new_attrs{number} = (trim($order->number) eq $saved_order->number) + ? '' + : trim($order->number); + + # Clear transdate unless changed + $new_attrs{transdate} = ($order->transdate == $saved_order->transdate) + ? DateTime->today_local + : $order->transdate; + + # Set new reqdate unless changed + if ($order->reqdate == $saved_order->reqdate) { + my $extra_days = $self->type eq _sales_quotation_type() ? $::instance_conf->get_reqdate_interval : 1; + $new_attrs{reqdate} = DateTime->today_local->next_workday(extra_days => $extra_days); + } else { + $new_attrs{reqdate} = $order->reqdate; + } + + # Update employee + $new_attrs{employee} = SL::DB::Manager::Employee->current; + + # Create new record from current one + $self->order(SL::DB::Order->new_from($order, destination_type => $order->type, attributes => \%new_attrs)); + + # no linked records on save as new + delete $::form->{$_} for qw(converted_from_oe_id converted_from_orderitems_ids); + + # save + $self->action_save(); +} + # print the order # # This is called if "print" is pressed in the print dialog. @@ -458,7 +515,13 @@ sub action_save_and_delivery_order { $self->js->flash('error', $_) foreach @{ $errors }; return $self->js->render(); } - flash_later('info', $::locale->text('The order has been saved')); + + my $text = $self->type eq _sales_order_type() ? $::locale->text('The order has been saved') + : $self->type eq _purchase_order_type() ? $::locale->text('The order has been saved') + : $self->type eq _sales_quotation_type() ? $::locale->text('The quotation has been saved') + : $self->type eq _request_quotation_type() ? $::locale->text('The rfq has been saved') + : ''; + flash_later('info', $text); my @redirect_params = ( controller => 'oe.pl', @@ -480,7 +543,13 @@ sub action_save_and_invoice { $self->js->flash('error', $_) foreach @{ $errors }; return $self->js->render(); } - flash_later('info', $::locale->text('The order has been saved')); + + my $text = $self->type eq _sales_order_type() ? $::locale->text('The order has been saved') + : $self->type eq _purchase_order_type() ? $::locale->text('The order has been saved') + : $self->type eq _sales_quotation_type() ? $::locale->text('The quotation has been saved') + : $self->type eq _request_quotation_type() ? $::locale->text('The rfq has been saved') + : ''; + flash_later('info', $text); my @redirect_params = ( controller => 'oe.pl', @@ -491,12 +560,25 @@ sub action_save_and_invoice { $self->redirect_to(@redirect_params); } +# workflow from sales quotation to sales order +sub action_sales_order { + $_[0]->_workflow_sales_or_purchase_order(); +} + +# workflow from rfq to purchase order +sub action_purchase_order { + $_[0]->_workflow_sales_or_purchase_order(); +} + # set form elements in respect to a changed customer or vendor # # This action is called on an change of the customer/vendor picker. sub action_customer_vendor_changed { my ($self) = @_; + _setup_order_from_cv($self->order); + $self->_recalc(); + my $cv_method = $self->cv; if ($self->order->$cv_method->contacts && scalar @{ $self->order->$cv_method->contacts } > 0) { @@ -511,34 +593,57 @@ sub action_customer_vendor_changed { $self->js->hide('#shipto_row'); } - $self->order->taxzone_id($self->order->$cv_method->taxzone_id); - - if ($self->order->is_sales) { - $self->order->taxincluded(defined($self->order->$cv_method->taxincluded_checked) - ? $self->order->$cv_method->taxincluded_checked - : $::myconfig{taxincluded_checked}); - $self->js->val('#order_salesman_id', $self->order->$cv_method->salesman_id); - } - - $self->order->payment_id($self->order->$cv_method->payment_id); - $self->order->delivery_term_id($self->order->$cv_method->delivery_term_id); - - $self->_recalc(); + $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('#business_info_row', $self->build_business_info_row) ->val( '#order_taxzone_id', $self->order->taxzone_id) ->val( '#order_taxincluded', $self->order->taxincluded) ->val( '#order_payment_id', $self->order->payment_id) ->val( '#order_delivery_term_id', $self->order->delivery_term_id) - ->val( '#order_intnotes', $self->order->$cv_method->notes) + ->val( '#order_intnotes', $self->order->intnotes) ->focus( '#order_' . $self->cv . '_id'); $self->_js_redisplay_amounts_and_taxes; $self->js->render(); } +# open the dialog for customer/vendor details +sub action_show_customer_vendor_details_dialog { + my ($self) = @_; + + my $is_customer = 'customer' eq $::form->{vc}; + my $cv; + if ($is_customer) { + $cv = SL::DB::Customer->new(id => $::form->{vc_id})->load; + } else { + $cv = SL::DB::Vendor->new(id => $::form->{vc_id})->load; + } + + my %details = map { $_ => $cv->$_ } @{$cv->meta->columns}; + $details{discount_as_percent} = $cv->discount_as_percent; + $details{creditlimt} = $cv->creditlimit_as_number; + $details{business} = $cv->business->description if $cv->business; + $details{language} = $cv->language_obj->description if $cv->language_obj; + $details{delivery_terms} = $cv->delivery_term->description if $cv->delivery_term; + $details{payment_terms} = $cv->payment->description if $cv->payment; + $details{pricegroup} = $cv->pricegroup->pricegroup if $is_customer && $cv->pricegroup; + + foreach my $entry (@{ $cv->shipto }) { + push @{ $details{SHIPTO} }, { map { $_ => $entry->$_ } @{$entry->meta->columns} }; + } + foreach my $entry (@{ $cv->contacts }) { + push @{ $details{CONTACTS} }, { map { $_ => $entry->$_ } @{$entry->meta->columns} }; + } + + $_[0]->render('common/show_vc_details', { layout => 0 }, + is_customer => $is_customer, + %details); + +} + # called if a unit in an existing item row is changed sub action_unit_changed { my ($self) = @_; @@ -856,7 +961,7 @@ sub _js_redisplay_amounts_and_taxes { # sub init_valid_types { - [ _sales_order_type(), _purchase_order_type() ]; + [ _sales_order_type(), _purchase_order_type(), _sales_quotation_type(), _request_quotation_type() ]; } sub init_type { @@ -872,8 +977,8 @@ sub init_type { sub init_cv { my ($self) = @_; - my $cv = $self->type eq _sales_order_type() ? 'customer' - : $self->type eq _purchase_order_type() ? 'vendor' + my $cv = (any { $self->type eq $_ } (_sales_order_type(), _sales_quotation_type())) ? 'customer' + : (any { $self->type eq $_ } (_purchase_order_type(), _request_quotation_type())) ? 'vendor' : die "Not a valid type for order"; return $cv; @@ -950,6 +1055,14 @@ sub build_shipto_select { ); } +# render the info line for business +# +# Needed, if customer/vendor changed. +sub build_business_info_row +{ + $_[0]->p->render('order/tabs/_business_info_row', SELF => $_[0]); +} + # build the rows for displaying taxes # # Called if amounts where recalculated and redisplayed. @@ -1007,7 +1120,14 @@ sub _make_order { # order here solves this problem. my $order; $order = SL::DB::Manager::Order->find_by(id => $::form->{id}) if $::form->{id}; - $order ||= SL::DB::Order->new(orderitems => []); + $order ||= SL::DB::Order->new(orderitems => [], + quotation => (any { $self->type eq $_ } (_sales_quotation_type(), _request_quotation_type()))); + + my $cv_id_method = $self->cv . '_id'; + if (!$::form->{id} && $::form->{$cv_id_method}) { + $order->$cv_id_method($::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}; @@ -1058,9 +1178,9 @@ sub _make_item { $item ||= SL::DB::OrderItem->new(custom_variables => []); $item->assign_attributes(%$attr); - $item->longdescription($item->part->notes) if $is_new && !defined $attr->{longdescription}; - $item->project_id($record->globalproject_id) if $is_new && !defined $attr->{project_id}; - $item->lastcost($item->part->lastcost) if $is_new && !defined $attr->{lastcost_as_number}; + $item->longdescription($item->part->notes) if $is_new && !defined $attr->{longdescription}; + $item->project_id($record->globalproject_id) if $is_new && !defined $attr->{project_id}; + $item->lastcost($record->is_sales ? $item->part->lastcost : 0) if $is_new && !defined $attr->{lastcost_as_number}; return $item; } @@ -1116,7 +1236,7 @@ sub _new_item { $new_attr{active_discount_source} = $discount_src; $new_attr{longdescription} = $part->notes if ! defined $attr->{longdescription}; $new_attr{project_id} = $record->globalproject_id; - $new_attr{lastcost} = $part->lastcost; + $new_attr{lastcost} = $record->is_sales ? $part->lastcost : 0; # add_custom_variables adds cvars to an orderitem with no cvars for saving, but # they cannot be retrieved via custom_variables until the order/orderitem is @@ -1128,6 +1248,22 @@ sub _new_item { return $item; } +sub _setup_order_from_cv { + my ($order) = @_; + + $order->$_($order->customervendor->$_) for (qw(taxzone_id payment_id delivery_term_id)); + + $order->intnotes($order->customervendor->notes); + + if ($order->is_sales) { + $order->salesman_id($order->customer->salesman_id); + $order->taxincluded(defined($order->customer->taxincluded_checked) + ? $order->customer->taxincluded_checked + : $::myconfig{taxincluded_checked}); + } + +} + # recalculate prices and taxes # # Using the PriceTaxCalculator. Store linetotals in the item objects. @@ -1201,11 +1337,71 @@ sub _save { $db->with_transaction(sub { SL::DB::OrderItem->new(id => $_)->delete for @{$self->item_ids_to_delete}; $self->order->save(cascade => 1); + + # link records + if ($::form->{converted_from_oe_id}) { + SL::DB::Order->new(id => $::form->{converted_from_oe_id})->load->link_to_record($self->order); + + if (scalar @{ $::form->{converted_from_orderitems_ids} || [] }) { + my $idx = 0; + foreach (@{ $self->order->items_sorted }) { + my $from_id = $::form->{converted_from_orderitems_ids}->[$idx]; + next if !$from_id; + SL::DB::RecordLink->new(from_table => 'orderitems', + from_id => $from_id, + to_table => 'orderitems', + to_id => $_->id + )->save; + $idx++; + } + } + } + 1; }) || push(@{$errors}, $db->error); return $errors; } +sub _workflow_sales_or_purchase_order { + my ($self) = @_; + + my $destination_type = $::form->{type} eq _sales_quotation_type() ? _sales_order_type() + : $::form->{type} eq _request_quotation_type() ? _purchase_order_type() + : $::form->{type} eq _purchase_order_type() ? _sales_order_type() + : $::form->{type} eq _sales_order_type() ? _purchase_order_type() + : ''; + + $self->order(SL::DB::Order->new_from($self->order, destination_type => $destination_type)); + $self->{converted_from_oe_id} = delete $::form->{id}; + + # 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); + } + + # change form type + $::form->{type} = $destination_type; + $self->type($self->init_type); + $self->cv ($self->init_cv); + $self->_check_auth; + + $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}} + ); +} + sub _pre_render { my ($self) = @_; @@ -1226,6 +1422,7 @@ sub _pre_render { $self->{all_delivery_terms} = SL::DB::Manager::DeliveryTerm->get_all_sorted(); $self->{current_employee_id} = SL::DB::Manager::Employee->current->id; $self->{periodic_invoices_status} = $self->_get_periodic_invoices_status($self->order->periodic_invoices_config); + $self->{order_probabilities} = [ map { { title => ($_ * 10) . '%', id => $_ * 10 } } (0..10) ]; my $print_form = Form->new(''); $print_form->{type} = $self->type; @@ -1259,38 +1456,62 @@ sub _pre_render { } } @all_objects; } - $::request->{layout}->use_javascript("${_}.js") for qw(kivi.SalesPurchase kivi.Order kivi.File ckeditor/ckeditor ckeditor/adapters/jquery edit_periodic_invoices_config); + $::request->{layout}->use_javascript("${_}.js") for qw(kivi.SalesPurchase kivi.Order kivi.File ckeditor/ckeditor ckeditor/adapters/jquery edit_periodic_invoices_config calculate_qty); $self->_setup_edit_action_bar; } sub _setup_edit_action_bar { my ($self, %params) = @_; - my $deletion_allowed = (($self->cv eq 'customer') && $::instance_conf->get_sales_order_show_delete) - || (($self->cv eq 'vendor') && $::instance_conf->get_purchase_order_show_delete); + my $deletion_allowed = (any { $self->type eq $_ } (_sales_quotation_type(), _request_quotation_type())) + || (($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); for my $bar ($::request->layout->get('actionbar')) { $bar->add( combobox => [ action => [ t8('Save'), - call => [ 'kivi.Order.save', $::instance_conf->get_order_warn_duplicate_parts ], + call => [ 'kivi.Order.save', 'save', $::instance_conf->get_order_warn_duplicate_parts ], checks => [ 'kivi.Order.check_save_active_periodic_invoices' ], - accesskey => 'enter', + ], + 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' ], + disabled => !$self->order->id ? t8('This object has not been saved yet.') : undef, ], action => [ t8('Save and Delivery Order'), - call => [ 'kivi.Order.save_and_delivery_order', $::instance_conf->get_order_warn_duplicate_parts ], + call => [ 'kivi.Order.save', 'save_and_delivery_order', $::instance_conf->get_order_warn_duplicate_parts ], checks => [ 'kivi.Order.check_save_active_periodic_invoices' ], + only_if => (any { $self->type eq $_ } (_sales_order_type(), _purchase_order_type())) ], action => [ t8('Save and Invoice'), - call => [ 'kivi.Order.save_and_invoice', $::instance_conf->get_order_warn_duplicate_parts ], + call => [ 'kivi.Order.save', 'save_and_invoice', $::instance_conf->get_order_warn_duplicate_parts ], checks => [ 'kivi.Order.check_save_active_periodic_invoices' ], ], - ], # end of combobox "Save" + combobox => [ + action => [ + t8('Workflow'), + ], + action => [ + t8('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('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, + ], + ], # end of combobox "Workflow" + combobox => [ action => [ t8('Export'), @@ -1427,6 +1648,29 @@ sub _get_periodic_invoices_status { return $active ? t8('active') : t8('inactive'); } +sub _get_title_for { + my ($self, $action) = @_; + + return '' if none { lc($action)} qw(add edit); + + # for locales: + # $::locale->text("Add Sales Order"); + # $::locale->text("Add Purchase Order"); + # $::locale->text("Add Quotation"); + # $::locale->text("Add Request for Quotation"); + # $::locale->text("Edit Sales Order"); + # $::locale->text("Edit Purchase Order"); + # $::locale->text("Edit Quotation"); + # $::locale->text("Edit Request for Quotation"); + + $action = ucfirst(lc($action)); + return $self->type eq _sales_order_type() ? $::locale->text("$action Sales Order") + : $self->type eq _purchase_order_type() ? $::locale->text("$action Purchase Order") + : $self->type eq _sales_quotation_type() ? $::locale->text("$action Quotation") + : $self->type eq _request_quotation_type() ? $::locale->text("$action Request for Quotation") + : ''; +} + sub _sales_order_type { 'sales_order'; } @@ -1435,6 +1679,14 @@ sub _purchase_order_type { 'purchase_order'; } +sub _sales_quotation_type { + 'sales_quotation'; +} + +sub _request_quotation_type { + 'request_quotation'; +} + 1; __END__ @@ -1520,6 +1772,10 @@ reused from generic code. =over 4 +=item * C