Projekt

Allgemein

Profil

Herunterladen (124 KB) Statistiken
| Zweig: | Markierung: | Revision:
package SL::Controller::Order;

use strict;
use parent qw(SL::Controller::Base);

use SL::Helper::Flash qw(flash_later);
use SL::HTML::Util;
use SL::Presenter::Tag qw(select_tag hidden_tag div_tag);
use SL::Locale::String qw(t8);
use SL::SessionFile::Random;
use SL::IMAPClient;
use SL::PriceSource;
use SL::Webdav;
use SL::File;
use SL::MIME;
use SL::Util qw(trim);
use SL::YAML;
use SL::DB::AdditionalBillingAddress;
use SL::DB::AuthUser;
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::Note;
use SL::DB::Language;
use SL::DB::Reclamation;
use SL::DB::RecordLink;
use SL::DB::RequirementSpec;
use SL::DB::Shipto;
use SL::DB::Translation;
use SL::DB::ValidityToken;
use SL::DB::EmailJournal;
use SL::DB::EmailJournalAttachment;

use SL::Helper::CreatePDF qw(:all);
use SL::Helper::PrintOptions;
use SL::Helper::ShippedQty;
use SL::Helper::UserPreferences::DisplayPreferences;
use SL::Helper::UserPreferences::PositionsScrollbar;
use SL::Helper::UserPreferences::UpdatePositions;

use SL::Controller::Helper::GetModels;

use List::Util qw(first sum0);
use List::UtilsBy qw(sort_by uniq_by);
use List::MoreUtils qw(uniq any none pairwise first_index);
use English qw(-no_match_vars);
use File::Spec;
use Cwd;
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
part_picker_classification_ids
is_final_version) ],
);


# safety
__PACKAGE__->run_before('check_auth',
except => [ qw(close_quotations) ]);

__PACKAGE__->run_before('check_auth_for_edit',
except => [ qw(edit show_customer_vendor_details_dialog price_popup load_second_rows close_quotations) ]);

#
# actions
#

# add a new order
sub action_add {
my ($self) = @_;

$self->order->transdate(DateTime->now_local());
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 :
$self->type eq sales_order_intake_type() ? $::instance_conf->get_delivery_date_interval : 1;

if (($self->type eq sales_order_intake_type() && $::instance_conf->get_deliverydate_on)
|| ($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();

if (!$::form->{form_validity_token}) {
$::form->{form_validity_token} = SL::DB::ValidityToken->create(scope => SL::DB::ValidityToken::SCOPE_ORDER_SAVE())->token;
}

$self->render(
'order/form',
title => $self->get_title_for('add'),
%{$self->{template_args}}
);
}

sub action_add_from_reclamation {
my ($self) = @_;

my $reclamation = SL::DB::Reclamation->new(id => $::form->{from_id})->load;
my %params;
$params{destination_type} = $reclamation->is_sales ? 'sales_order'
: 'purchase_order';
my $order = SL::DB::Order->new_from($reclamation, %params);
$self->{converted_from_reclamation_id} = $::form->{from_id};

$self->order($order);

$self->recalc();
$self->pre_render();

if (!$::form->{form_validity_token}) {
$::form->{form_validity_token} = SL::DB::ValidityToken->create(scope => SL::DB::ValidityToken::SCOPE_ORDER_SAVE())->token;
}

$self->render(
'order/form',
title => $self->get_title_for('edit'),
%{$self->{template_args}}
);
}

sub action_add_from_email_journal {
my ($self) = @_;
my $email_journal_id = $::form->{from_id};
my $email_attachment_id = $::form->{email_attachment_id};

$self->{converted_from_email_journal_id} = $email_journal_id;
$self->{email_attachment_id} = $email_attachment_id;

$self->action_add();
}

# edit an existing order
sub action_edit {
my ($self) = @_;

if ($::form->{id}) {
$self->load_order;

if ($self->order->is_sales) {
my $imap_client = SL::IMAPClient->new();
if ($imap_client) {
$imap_client->update_email_files_for_record($self->order);
}
}

} else {
# this is to edit an order from an unsaved order object

# 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);
}
# trigger rendering values for second row 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 };

if (!$::form->{form_validity_token}) {
$::form->{form_validity_token} = SL::DB::ValidityToken->create(scope => SL::DB::ValidityToken::SCOPE_ORDER_SAVE())->token;
}
}

$self->recalc();
$self->pre_render();
$self->render(
'order/form',
title => $self->get_title_for('edit'),
%{$self->{template_args}}
);
}

# edit a collective order (consisting of one or more existing orders)
sub action_edit_collective {
my ($self) = @_;

# collect order ids
my @multi_ids = map {
$_ =~ m{^multi_id_(\d+)$} && $::form->{'multi_id_' . $1} && $::form->{'trans_id_' . $1} && $::form->{'trans_id_' . $1}
} grep { $_ =~ m{^multi_id_\d+$} } keys %$::form;

# fall back to add if no ids are given
if (scalar @multi_ids == 0) {
$self->action_add();
return;
}

# fall back to save as new if only one id is given
if (scalar @multi_ids == 1) {
$self->order(SL::DB::Order->new(id => $multi_ids[0])->load);
$self->action_save_as_new();
return;
}

# make new order from given orders
my @multi_orders = map { SL::DB::Order->new(id => $_)->load } @multi_ids;
$self->{converted_from_oe_id} = join ' ', map { $_->id } @multi_orders;
$self->order(SL::DB::Order->new_from_multi(\@multi_orders, sort_sources_by => 'transdate'));

$self->action_edit();
}

# delete the order
sub action_delete {
my ($self) = @_;

my $errors = $self->delete();

if (scalar @{ $errors }) {
$self->js->flash('error', $_) foreach @{ $errors };
return $self->js->render();
}

my $text = $self->type eq sales_order_intake_type() ? $::locale->text('The order intake has been deleted')
: $self->type eq sales_order_type() ? $::locale->text('The order confirmation 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')
: $self->type eq purchase_quotation_intake_type() ? $::locale->text('The quotation intake has been deleted')
: '';
flash_later('info', $text);

my @redirect_params = (
action => 'add',
type => $self->type,
);

$self->redirect_to(@redirect_params);
}

# save the order
sub action_save {
my ($self) = @_;

my $errors = $self->save();

if (scalar @{ $errors }) {
$self->js->flash('error', $_) foreach @{ $errors };
return $self->js->render();
}

my $text = $self->type eq sales_order_intake_type() ? $::locale->text('The order intake has been saved')
: $self->type eq sales_order_type() ? $::locale->text('The order confirmation 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')
: $self->type eq purchase_quotation_intake_type() ? $::locale->text('The quotation intake has been saved')
: '';
flash_later('info', $text);

my @redirect_params;
if ($::form->{back_to_caller}) {
@redirect_params = $::form->{callback} ? ($::form->{callback})
: (controller => 'LoginScreen', action => 'user_login');

} else {
@redirect_params = (
action => 'edit',
type => $self->type,
id => $self->order->id,
callback => $::form->{callback},
);
}

$self->redirect_to(@redirect_params);
}

# create new version and set version number
sub action_add_subversion {
my ($self) = @_;

my $current_version_number = $self->order->current_version_number;
my $new_version_number = $current_version_number + 1;

my $new_number = $self->order->number;
$new_number =~ s/-$current_version_number$//;
$self->order->number($new_number . '-' . $new_version_number);
$self->order->add_order_version(SL::DB::OrderVersion->new(oe_id => $self->order->id,
version => $new_version_number));

# call the save action
$self->action_save();

}

# save the order as new document and 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 it is enabled in client config
if ($order->reqdate == $saved_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 :
$self->type eq sales_order_intake_type() ? $::instance_conf->get_delivery_date_interval : 1;

if ( ($self->type eq sales_order_intake_type() && !$::instance_conf->get_deliverydate_on)
|| ($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;
}

# Update employee
$new_attrs{employee} = SL::DB::Manager::Employee->current;

# Warn on obsolete items
my @obsolete_positions = map { $_->position } grep { $_->part->obsolete } @{ $order->items_sorted };
flash_later('warning', t8('This record containts obsolete items at position #1', join ', ', @obsolete_positions)) if @obsolete_positions;

# 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);

if (!$::form->{form_validity_token}) {
$::form->{form_validity_token} = SL::DB::ValidityToken->create(scope => SL::DB::ValidityToken::SCOPE_ORDER_SAVE())->token;
}

# save
$self->action_save();
}

# print the order
#
# This is called if "print" is pressed in the print dialog.
# If PDF creation was requested and succeeded, the pdf is offered for download
# via send_file (which uses ajax in this case).
sub action_print {
my ($self) = @_;

my $errors = $self->save();

if (scalar @{ $errors }) {
$self->js->flash('error', $_) foreach @{ $errors };
return $self->js->render();
}

$self->js_reset_order_and_item_ids_after_save;

my $redirect_url = $self->url_for(
action => 'edit',
type => $self->type,
id => $self->order->id,
);

my $format = $::form->{print_options}->{format};
my $media = $::form->{print_options}->{media};
my $formname = $::form->{print_options}->{formname};
my $copies = $::form->{print_options}->{copies};
my $groupitems = $::form->{print_options}->{groupitems};
my $printer_id = $::form->{print_options}->{printer_id};

# only PDF, OpenDocument & HTML for now
if (none { $format eq $_ } qw(pdf opendocument opendocument_pdf html)) {
flash_later('error', t8('Format \'#1\' is not supported yet/anymore.', $format));
return $self->js->redirect_to($redirect_url)->render;
}

# only screen or printer by now
if (none { $media eq
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 => '