Projekt

Allgemein

Profil

Herunterladen (84,2 KB) Statistiken
| Zweig: | Markierung: | Revision:
package SL::Controller::DeliveryOrder;

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

use SL::Helper::Flash qw(flash_later);
use SL::Helper::Number qw(_format_number _parse_number);
use SL::Presenter::Tag qw(select_tag hidden_tag div_tag);
use SL::Presenter::DeliveryOrder qw(delivery_order_status_line);
use SL::Locale::String qw(t8);
use SL::SessionFile::Random;
use SL::PriceSource;
use SL::Webdav;
use SL::File;
use SL::MIME;
use SL::YAML;
use SL::DB::History;
use SL::DB::Order;
use SL::DB::Default;
use SL::DB::Unit;
use SL::DB::Order;
use SL::DB::Part;
use SL::DB::PartClassification;
use SL::DB::PartsGroup;
use SL::DB::Printer;
use SL::DB::Language;
use SL::DB::Reclamation;
use SL::DB::RecordLink;
use SL::DB::Shipto;
use SL::DB::Translation;
use SL::DB::TransferType;
use SL::DB::ValidityToken;
use SL::DB::Warehouse;
use SL::DB::Helper::RecordLink qw(set_record_link_conversions);
use SL::DB::Helper::TypeDataProxy;
use SL::DB::DeliveryOrder;
use SL::DB::DeliveryOrder::TypeData qw(:types);
use SL::DB::DeliveryOrderItemsStock;
use SL::Model::Record;

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(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 type_data) ],
);


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

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

__PACKAGE__->run_before('get_unalterable_data',
only => [ qw(save save_as_new save_and_delivery_order save_and_invoice save_and_ap_transaction
print send_email) ]);

#
# actions
#

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

$self->order(SL::Model::Record->update_after_new($self->order, $self->type));

$self->pre_render();

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

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

sub action_add_from_order {
my ($self) = @_;
# this interfers with init_order
$self->{converted_from_oe_id} = delete $::form->{id};

$self->type_data->validate;

my $order = SL::DB::Order->new(id => $self->{converted_from_oe_id})->load;

my $target_type = $::form->{type};
my $delivery_order = SL::Model::Record->new_from_workflow($order, $target_type);
$self->order($delivery_order);

$self->action_add;
}

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

my $reclamation = SL::DB::Reclamation->new(id => $::form->{from_id})->load;
my $target_type = $reclamation->is_sales ? 'rma_delivery_order'
: 'supplier_delivery_order';
my $delivery_order = SL::Model::Record->new_from_workflow($reclamation, $target_type);
$self->{converted_from_reclamation_id} = $::form->{from_id};
$self->order($delivery_order);

$self->action_add;
}

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

if ($::form->{id}) {
$self->load_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_DELIVERY_ORDER_SAVE())->token;
}
}

$self->pre_render();
$self->render(
'delivery_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::DeliveryOrder->new(id => $multi_ids[0])->load);
$self->action_save_as_new();
return;
}

# make new order from given orders
my @multi_orders = map { SL::DB::DeliveryOrder->new(id => $_)->load } @multi_ids;
$self->{converted_from_oe_id} = join ' ', map { $_->id } @multi_orders;
my $target_type = SALES_DELIVERY_ORDER_TYPE();
my $delivery_order = SL::Model::Record->new_from_workflow_multi(\@multi_orders, $target_type, sort_sources_by => 'transdate');
$self->order($delivery_order);

$self->action_edit();
}

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

SL::Model::Record->delete($self->order);
flash_later('info', $self->type_data->text("delete"));

my @redirect_params = (
action => 'add',
type => $"DeliveryOrder.pm.html#L1568" data-txt="1568">
$order->taxincluded(defined($order->customer->taxincluded_checked)
? $order->customer->taxincluded_checked
: $::myconfig{taxincluded_checked});
}

}

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

# get data for saving, printing, ..., that is not changed in the form
#
# Only cvars for now.
sub get_unalterable_data {
my ($self) = @_;

foreach my $item (@{ $self->order->items }) {
# autovivify all cvars that are not in the form (cvars_by_config can do it).
# workaround to pre-parse number-cvars (parse_custom_variable_values does not parse number values).
foreach my $var (@{ $item->cvars_by_config }) {
$var->unparsed_value($::form->parse_amount(\%::myconfig, $var->{__unparsed_value})) if ($var->config->type eq 'number' && exists($var->{__unparsed_value}));
}
$item->parse_custom_variable_values;
}
}

# save the order
#
# And delete items that are deleted in the form.
sub save {
my ($self) = @_;

my $errors = [];
my $db = $self->order->db;

if (scalar @{$self->order->items} == 0 && !grep { $self->type eq $_ } @{$::instance_conf->get_allowed_documents_with_no_positions() || []}) {
return [t8('The action you\'ve chosen has not been executed because the document does not contain any item yet.')];
}

# link records
if ($::form->{converted_from_oe_id}) {
my @converted_from_oe_ids = split ' ', $::form->{converted_from_oe_id};
set_record_link_conversions(
$self->order,
'SL::DB::Order' => \@converted_from_oe_ids,
'SL::DB::OrderItem' => $::form->{converted_from_orderitems_ids},
);
}
if ($::form->{converted_from_reclamation_id}) {
my @converted_from_reclamation_ids = split ' ', $::form->{converted_from_reclamation_id};
set_record_link_conversions(
$self->order,
'SL::DB::Reclamation' => \@converted_from_reclamation_ids,
'SL::DB::ReclamationItem' => $::form->{converted_from_reclamation_items_ids},
);
}

$db->with_transaction(sub {
my $validity_token;
if (!$self->order->id) {
$validity_token = SL::DB::Manager::ValidityToken->fetch_valid_token(
scope => SL::DB::ValidityToken::SCOPE_DELIVERY_ORDER_SAVE(),
token => $::form->{form_validity_token},
);

die $::locale->text('The form is not valid anymore.') if !$validity_token;
}

# 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::DeliveryOrderItem->new(id => $_)->delete for @{$self->item_ids_to_delete || []};
$self->order->save(cascade => 1);

$self->save_history('SAVED');

$validity_token->delete if $validity_token;
delete $::form->{form_validity_token};

1;
}) || push(@{$errors}, $db->error);

return $errors;
}

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

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

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

my $destination_type = $self->type_data->workflow("to_quotation_type");

my $delivery_order = SL::Model::Record->new_from_workflow($self->order, $destination_type, {});
$self->order($delivery_order);
$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->get_unalterable_data();
$self->pre_render();

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

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

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

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

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

my $destination_type = $self->type_data->workflow("to_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 ($self->type_data->workflow("to_order_copy_shipto") && $::form->{use_shipto} && $self->order->shipto) {
$custom_shipto = $self->order->shipto->clone('SL::DB::DeliveryOrder');
}

my $delivery_order = SL::Model::Record->new_from_workflow($self->order, $destination_type, {});
$self->order($delivery_order);
$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);
}

if ($self->type_data->workflow("to_order_copy_shipto")) {
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);
$self->cv ($self->init_cv);
$self->check_auth;

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

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

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

sub pre_render {
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_languages} = SL::DB::Manager::Language->get_all_sorted( query => [ or => [ obsolete => 0, id => $self->order->language_id ] ] );
$self->{all_employees} = SL::DB::Manager::Employee->get_all(where => [ or => [ id => $self->order->employee_id,
deleted => 0 ] ],
sort_by => 'name');
$self->{all_salesmen} = SL::DB::Manager::Employee->get_all(where => [ or => [ id => $self->order->salesman_id,
deleted => 0 ] ],
sort_by => 'name');
$self->{all_payment_terms} = SL::DB::Manager::PaymentTerm->get_all_sorted(where => [ or => [ id => $self->order->payment_id,
obsolete => 0 ] ]);
$self->{all_delivery_terms} = SL::DB::Manager::DeliveryTerm->get_all_sorted();
$self->{current_employee_id} = SL::DB::Manager::Employee->current->id;
$self->{order_probabilities} = [ map { { title => ($_ * 10) . '%', id => $_ * 10 } } (0..10) ];
$self->{positions_scrollbar_height} = SL::Helper::UserPreferences::PositionsScrollbar->new()->get_height();

my $print_form = Form->new('');
$print_form->{type} = $self->type;
$print_form->{printers} = SL::DB::Manager::Printer->get_all_sorted;
$self->{print_options} = SL::Helper::PrintOptions->get_print_options(
form => $print_form,
options => {dialog_name_prefix => 'print_options.',
show_headers => 1,
no_queue => 1,
no_postscript => 1,
no_opendocument => 0,
no_html => 1},
);

foreach my $item (@{$self->order->orderitems}) {
my $price_source = SL::PriceSource->new(record_item => $item, record => $self->order);
$item->active_price_source( $price_source->price_from_source( $item->active_price_source ));
$item->active_discount_source($price_source->discount_from_source($item->active_discount_source));
}

if ($self->order->${\ $self->type_data->properties("nr_key") } && $::instance_conf->get_webdav) {
my $webdav = SL::Webdav->new(
type => $self->type,
number => $self->order->number,
);
my @all_objects = $webdav->get_all_objects;
@{ $self->{template_args}->{WEBDAV} } = map { { name => $_->filename,
type => t8('File'),
link => File::Spec->catfile($_->full_filedescriptor),
} } @all_objects;
}

$self->{template_args}{in_out} = $self->type_data->properties("transfer");
$self->{template_args}{longdescription_dialog_size_percentage} = SL::Helper::UserPreferences::DisplayPreferences->new()->get_longdescription_dialog_size_percentage();

$self->get_item_cvpartnumber($_) for @{$self->order->items_sorted};

$::request->{layout}->use_javascript("${_}.js") for qw(kivi.SalesPurchase kivi.DeliveryOrder kivi.File
calculate_qty kivi.Validator follow_up show_history);
$self->setup_edit_action_bar;
}

sub setup_edit_action_bar {
my ($self, %params) = @_;

my $deletion_allowed = $self->type_data->show_menu("delete");
my $may_edit_create = $::auth->assert($self->type_data->rights('edit') || 'DOES_NOT_EXIST', 1);

for my $bar ($::request->layout->get('actionbar')) {
$bar->add(
combobox => [
action => [
t8('Save'),
id => 'save_action',
call => [ 'kivi.DeliveryOrder.save', { action => 'save',
warn_on_duplicates => $::instance_conf->get_order_warn_duplicate_parts,
warn_on_reqdate => $::instance_conf->get_order_warn_no_deliverydate },
],
disabled => !$may_edit_create ? t8('You do not have the permissions to access this function.')
: $self->order->delivered ? t8('This record has already been delivered.')
: undef,
],
action => [
t8('Save as new'),
call => [ 'kivi.DeliveryOrder.save', { action => 'save_as_new',
warn_on_duplicates => $::instance_conf->get_order_warn_duplicate_parts },
],
disabled => !$may_edit_create ? t8('You do not have the permissions to access this function.')
: $self->type eq 'supplier_delivery_order' ? t8('Need a workflow for Supplier Delivery Order')
: $self->type eq 'rma_delivery_order' ? t8('Need a workflow for RMA Delivery Order.')
: !$self->order->id ? t8('This object has not been saved yet.')
: undef,
],
], # end of combobox "Save"

combobox => [
action => [
t8('Workflow'),
],
action => [
t8('Save and Quotation'),
submit => [ '#order_form', { action => "DeliveryOrder/sales_quotation" } ],
only_if => $self->type_data->show_menu("save_and_quotation"),
disabled => !$may_edit_create ? t8('You do not have the permissions to access this function.') : undef,
],
action => [
t8('Save and RFQ'),
submit => [ '#order_form', { action => "DeliveryOrder/request_for_quotation" } ],
only_if => $self->type_data->show_menu("save_and_rfq"),
disabled => !$may_edit_create ? t8('You do not have the permissions to access this function.') : undef,
],
action => [
t8('Save and Sales Order'),
submit => [ '#order_form', { action => "DeliveryOrder/sales_order" } ],
only_if => $self->type_data->show_menu("save_and_sales_order"),
disabled => !$may_edit_create ? t8('You do not have the permissions to access this function.') : undef,
],
action => [
t8('Save and Purchase Order'),
call => [ 'kivi.DeliveryOrder.purchase_order_check_for_direct_delivery' ],
only_if => $self->type_data->show_menu("save_and_purchase_order"),
disabled => !$may_edit_create ? t8('You do not have the permissions to access this function.') : undef,
],
action => [
t8('Save and Delivery Order'),
call => [ 'kivi.DeliveryOrder.save', { action => 'save_and_delivery_order',
warn_on_duplicates => $::instance_conf->get_order_warn_duplicate_parts,
warn_on_reqdate => $::instance_conf->get_order_warn_no_deliverydate },
],
only_if => $self->type_data->show_menu("save_and_delivery_order"),
disabled => !$may_edit_create ? t8('You do not have the permissions to access this function.') : undef,
],
action => [
t8('Save and Invoice'),
call => [ 'kivi.DeliveryOrder.save', { action => 'save_and_invoice',
warn_on_duplicates => $::instance_conf->get_order_warn_duplicate_parts },
],
only_if => $self->type_data->show_menu("save_and_invoice"),
disabled => !$may_edit_create ? t8('You do not have the permissions to access this function.') : undef,
],
action => [
t8('Save and AP Transaction'),
call => [ 'kivi.DeliveryOrder.save', { action => 'save_and_ap_transaction',
warn_on_duplicates => $::instance_conf->get_order_warn_duplicate_parts },
],
only_if => $self->type_data->show_menu("save_and_ap_transaction"),
disabled => !$may_edit_create ? t8('You do not have the permissions to access this function.') : undef,
],

], # end of combobox "Workflow"

combobox => [
action => [
t8('Export'),
],
action => [
t8('Save and preview PDF'),
call => [ 'kivi.DeliveryOrder.save', { action => 'preview_pdf',
warn_on_duplicates => $::instance_conf->get_order_warn_duplicate_parts,
warn_on_reqdate => $::instance_conf->get_order_warn_no_deliverydate },
],
disabled => !$may_edit_create ? t8('You do not have the permissions to access this function.') : undef,
],
action => [
t8('Save and print'),
call => [ 'kivi.DeliveryOrder.show_print_options', { warn_on_duplicates => $::instance_conf->get_order_warn_duplicate_parts,