Projekt

Allgemein

Profil

Herunterladen (66,8 KB) Statistiken
| Zweig: | Markierung: | Revision:
099fc63b Bernd Bleßmann
package SL::Controller::Order;

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

95278e0a Sven Schöling
use SL::Helper::Flash qw(flash_later);
5c859d64 Bernd Bleßmann
use SL::Presenter::Tag qw(select_tag hidden_tag);
95278e0a Sven Schöling
use SL::Locale::String qw(t8);
aa36021a Bernd Bleßmann
use SL::SessionFile::Random;
099fc63b Bernd Bleßmann
use SL::PriceSource;
6550f507 Bernd Bleßmann
use SL::Webdav;
1ce68041 Martin Helmling
use SL::File;
00c3a0ed Bernd Bleßmann
use SL::Util qw(trim);
099fc63b Bernd Bleßmann
use SL::DB::Order;
use SL::DB::Default;
use SL::DB::Unit;
91abaf6c Bernd Bleßmann
use SL::DB::Part;
bba88a1b Bernd Bleßmann
use SL::DB::PartsGroup;
b1b3cdeb Bernd Bleßmann
use SL::DB::Printer;
use SL::DB::Language;
b6185e74 Bernd Bleßmann
use SL::DB::RecordLink;
099fc63b Bernd Bleßmann
aa36021a Bernd Bleßmann
use SL::Helper::CreatePDF qw(:all);
b1b3cdeb Bernd Bleßmann
use SL::Helper::PrintOptions;
03d66bba Bernd Bleßmann
use SL::Helper::ShippedQty;
099fc63b Bernd Bleßmann
91abaf6c Bernd Bleßmann
use SL::Controller::Helper::GetModels;

95278e0a Sven Schöling
use List::Util qw(first);
d83928f0 Bernd Bleßmann
use List::UtilsBy qw(sort_by uniq_by);
5c859d64 Bernd Bleßmann
use List::MoreUtils qw(any none pairwise first_index);
aa36021a Bernd Bleßmann
use English qw(-no_match_vars);
use File::Spec;
d83928f0 Bernd Bleßmann
use Cwd;
099fc63b Bernd Bleßmann
use Rose::Object::MakeMethods::Generic
(
5dd5e97b Bernd Bleßmann
scalar => [ qw(item_ids_to_delete) ],
32951b1f Bernd Bleßmann
'scalar --get_set_init' => [ qw(order valid_types type cv p multi_items_models all_price_factors) ],
099fc63b Bernd Bleßmann
);


# safety
1d242038 Bernd Bleßmann
__PACKAGE__->run_before('check_auth');
099fc63b Bernd Bleßmann
1d242038 Bernd Bleßmann
__PACKAGE__->run_before('recalc',
a66cafd9 Bernd Bleßmann
only => [ qw(save save_as_new save_and_delivery_order save_and_invoice print send_email) ]);
099fc63b Bernd Bleßmann
1d242038 Bernd Bleßmann
__PACKAGE__->run_before('get_unalterable_data',
a66cafd9 Bernd Bleßmann
only => [ qw(save save_as_new save_and_delivery_order save_and_invoice print send_email) ]);
099fc63b Bernd Bleßmann
#
# actions
#

5ef1fa84 Bernd Bleßmann
# add a new order
099fc63b Bernd Bleßmann
sub action_add {
my ($self) = @_;

$self->order->transdate(DateTime->now_local());
1d242038 Bernd Bleßmann
my $extra_days = $self->type eq sales_quotation_type() ? $::instance_conf->get_reqdate_interval : 1;
00d97258 Jan Büren
$self->order->reqdate(DateTime->today_local->next_workday(extra_days => $extra_days)) if !$self->order->reqdate;
099fc63b Bernd Bleßmann
1d242038 Bernd Bleßmann
$self->pre_render();
099fc63b Bernd Bleßmann
$self->render(
'order/form',
1d242038 Bernd Bleßmann
title => $self->get_title_for('add'),
099fc63b Bernd Bleßmann
%{$self->{template_args}}
);
}

5ef1fa84 Bernd Bleßmann
# edit an existing order
099fc63b Bernd Bleßmann
sub action_edit {
my ($self) = @_;

9aa58dc1 Bernd Bleßmann
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/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 };
}

1d242038 Bernd Bleßmann
$self->recalc();
$self->pre_render();
099fc63b Bernd Bleßmann
$self->render(
'order/form',
1d242038 Bernd Bleßmann
title => $self->get_title_for('edit'),
099fc63b Bernd Bleßmann
%{$self->{template_args}}
);
}

5ef1fa84 Bernd Bleßmann
# delete the order
da55cfa0 Bernd Bleßmann
sub action_delete {
my ($self) = @_;

1d242038 Bernd Bleßmann
my $errors = $self->delete();
da55cfa0 Bernd Bleßmann
if (scalar @{ $errors }) {
$self->js->flash('error', $_) foreach @{ $errors };
return $self->js->render();
}

1d242038 Bernd Bleßmann
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')
8e77bd29 Bernd Bleßmann
: '';
flash_later('info', $text);

da55cfa0 Bernd Bleßmann
my @redirect_params = (
4e03a13b Moritz Bunkus
action => 'add',
da55cfa0 Bernd Bleßmann
type => $self->type,
);

$self->redirect_to(@redirect_params);
}

5ef1fa84 Bernd Bleßmann
# save the order
099fc63b Bernd Bleßmann
sub action_save {
my ($self) = @_;

1d242038 Bernd Bleßmann
my $errors = $self->save();
099fc63b Bernd Bleßmann
if (scalar @{ $errors }) {
$self->js->flash('error', $_) foreach @{ $errors };
return $self->js->render();
}

1d242038 Bernd Bleßmann
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')
8e77bd29 Bernd Bleßmann
: '';
flash_later('info', $text);

099fc63b Bernd Bleßmann
my @redirect_params = (
action => 'edit',
type => $self->type,
id => $self->order->id,
);

$self->redirect_to(@redirect_params);
}

00c3a0ed Bernd Bleßmann
# save the order as new document an open it for edit
sub action_save_as_new {
my ($self) = @_;

89a2d4f1 Bernd Bleßmann
my $order = $self->order;

if (!$order->id) {
00c3a0ed Bernd Bleßmann
$self->js->flash('error', t8('This object has not been saved yet.'));
return $self->js->render();
}

89a2d4f1 Bernd Bleßmann
# load order from db to check if values changed
my $saved_order = SL::DB::Order->new(id => $order->id)->load;
00c3a0ed Bernd Bleßmann
89a2d4f1 Bernd Bleßmann
my %new_attrs;
00c3a0ed Bernd Bleßmann
# 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.
89a2d4f1 Bernd Bleßmann
$new_attrs{number} = (trim($order->number) eq $saved_order->number)
? ''
: trim($order->number);
00c3a0ed Bernd Bleßmann
89a2d4f1 Bernd Bleßmann
# 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) {
1d242038 Bernd Bleßmann
my $extra_days = $self->type eq sales_quotation_type() ? $::instance_conf->get_reqdate_interval : 1;
89a2d4f1 Bernd Bleßmann
$new_attrs{reqdate} = DateTime->today_local->next_workday(extra_days => $extra_days);
} else {
$new_attrs{reqdate} = $order->reqdate;
00c3a0ed Bernd Bleßmann
}

# Update employee
89a2d4f1 Bernd Bleßmann
$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);
00c3a0ed Bernd Bleßmann
# save
$self->action_save();
}

5ef1fa84 Bernd Bleßmann
# print the order
#
# This is called if "print" is pressed in the print dialog.
# If PDF creation was requested and succeeded, the pdf is stored in a session
# file and the filename is stored as session value with an unique key. A
# javascript function with this key is then called. This function calls the
# download action below (action_download_pdf), which offers the file for
# download.
b1b3cdeb Bernd Bleßmann
sub action_print {
aa36021a Bernd Bleßmann
my ($self) = @_;

b1b3cdeb Bernd Bleßmann
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};

34540341 Bernd Bleßmann
# only pdf and opendocument by now
if (none { $format eq $_ } qw(pdf opendocument opendocument_pdf)) {
b1b3cdeb Bernd Bleßmann
return $self->js->flash('error', t8('Format \'#1\' is not supported yet/anymore.', $format))->render;
aa36021a Bernd Bleßmann
}

b1b3cdeb Bernd Bleßmann
# only screen or printer by now
if (none { $media eq $_ } qw(screen printer)) {
return $self->js->flash('error', t8('Media \'#1\' is not supported yet/anymore.', $media))->render;
}
aa36021a Bernd Bleßmann
b1b3cdeb Bernd Bleßmann
my $language;
$language = SL::DB::Language->new(id => $::form->{print_options}->{language_id})->load if $::form->{print_options}->{language_id};
aa36021a Bernd Bleßmann
9ec05722 Bernd Bleßmann
# create a form for generate_attachment_filename
2e9dddf1 Bernd Bleßmann
my $form = Form->new;
1d242038 Bernd Bleßmann
$form->{$self->nr_key()} = $self->order->number;
2e9dddf1 Bernd Bleßmann
$form->{type} = $self->type;
$form->{format} = $format;
$form->{formname} = $formname;
$form->{language} = '_' . $language->template_code if $language;
my $pdf_filename = $form->generate_attachment_filename();
b1b3cdeb Bernd Bleßmann
my $pdf;
1d242038 Bernd Bleßmann
my @errors = generate_pdf($self->order, \$pdf, { format => $format,
formname => $formname,
language => $language,
groupitems => $groupitems });
b1b3cdeb Bernd Bleßmann
if (scalar @errors) {
return $self->js->flash('error', t8('Conversion to PDF failed: #1', $errors[0]))->render;
}

if ($media eq 'screen') {
# screen/download
my $sfile = SL::SessionFile::Random->new(mode => "w");
$sfile->fh->print($pdf);
$sfile->fh->close;

my $key = join('_', Time::HiRes::gettimeofday(), int rand 1000000000000);
a66cafd9 Bernd Bleßmann
$::auth->set_session_value("Order::print-${key}" => $sfile->file_name);
b1b3cdeb Bernd Bleßmann
$self->js
0935b012 Bernd Bleßmann
->run('kivi.Order.download_pdf', $pdf_filename, $key)
b1b3cdeb Bernd Bleßmann
->flash('info', t8('The PDF has been created'));

} elsif ($media eq 'printer') {
# printer
my $printer_id = $::form->{print_options}->{printer_id};
f08036d7 Moritz Bunkus
SL::DB::Printer->new(id => $printer_id)->load->print_document(
copies => $copies,
content => $pdf,
);
b1b3cdeb Bernd Bleßmann
$self->js->flash('info', t8('The PDF has been printed'));
}
aa36021a Bernd Bleßmann
6550f507 Bernd Bleßmann
# copy file to webdav folder
2e9dddf1 Bernd Bleßmann
if ($self->order->number && $::instance_conf->get_webdav_documents) {
6550f507 Bernd Bleßmann
my $webdav = SL::Webdav->new(
type => $self->type,
2e9dddf1 Bernd Bleßmann
number => $self->order->number,
6550f507 Bernd Bleßmann
);
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', $@));
}
}
2e9dddf1 Bernd Bleßmann
if ($self->order->number && $::instance_conf->get_doc_storage) {
29675d6b Bernd Bleßmann
eval {
SL::File->save(object_id => $self->order->id,
1ce68041 Martin Helmling
object_type => $self->type,
mime_type => 'application/pdf',
source => 'created',
file_type => 'document',
file_name => $pdf_filename,
file_contents => $pdf);
29675d6b Bernd Bleßmann
1;
} or do {
$self->js->flash('error', t8('Storing PDF in storage backend failed: #1', $@));
}
1ce68041 Martin Helmling
}
b1b3cdeb Bernd Bleßmann
$self->js->render;
aa36021a Bernd Bleßmann
}

5ef1fa84 Bernd Bleßmann
# offer pdf for download
#
# It needs to get the key for the session value to get the pdf file.
aa36021a Bernd Bleßmann
sub action_download_pdf {
my ($self) = @_;

my $key = $::form->{key};
a66cafd9 Bernd Bleßmann
my $tmp_filename = $::auth->get_session_value("Order::print-${key}");
aa36021a Bernd Bleßmann
return $self->send_file(
$tmp_filename,
type => 'application/pdf',
name => $::form->{pdf_filename},
);
}

5ef1fa84 Bernd Bleßmann
# open the email dialog
aa36021a Bernd Bleßmann
sub action_show_email_dialog {
my ($self) = @_;

my $cv_method = $self->cv;

if (!$self->order->$cv_method) {
return $self->js->flash('error', $self->cv eq 'customer' ? t8('Cannot send E-mail without customer given') : t8('Cannot send E-mail without vendor given'))
->render($self);
}

d83928f0 Bernd Bleßmann
my $email_form;
$email_form->{to} = $self->order->contact->cp_email if $self->order->contact;
$email_form->{to} ||= $self->order->$cv_method->email;
$email_form->{cc} = $self->order->$cv_method->cc;
$email_form->{bcc} = join ', ', grep $_, $self->order->$cv_method->bcc, SL::DB::Default->get->global_bcc;
aa36021a Bernd Bleßmann
# Todo: get addresses from shipto, if any

my $form = Form->new;
1d242038 Bernd Bleßmann
$form->{$self->nr_key()} = $self->order->number;
2e9dddf1 Bernd Bleßmann
$form->{formname} = $self->type;
$form->{type} = $self->type;
$form->{language} = 'de';
$form->{format} = 'pdf';
aa36021a Bernd Bleßmann
d83928f0 Bernd Bleßmann
$email_form->{subject} = $form->generate_email_subject();
$email_form->{attachment_filename} = $form->generate_attachment_filename();
$email_form->{message} = $form->generate_email_body();
$email_form->{js_send_function} = 'kivi.Order.send_email()';

1d242038 Bernd Bleßmann
my %files = $self->get_files_for_email_dialog();
d83928f0 Bernd Bleßmann
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',
);
aa36021a Bernd Bleßmann
$self->js
0935b012 Bernd Bleßmann
->run('kivi.Order.show_email_dialog', $dialog_html)
aa36021a Bernd Bleßmann
->reinit_widgets
->render($self);
}

5ef1fa84 Bernd Bleßmann
# send email
#
aa36021a Bernd Bleßmann
# Todo: handling error messages: flash is not displayed in dialog, but in the main form
sub action_send_email {
my ($self) = @_;

d83928f0 Bernd Bleßmann
my $email_form = delete $::form->{email_form};
my %field_names = (to => 'email');
aa36021a Bernd Bleßmann
d83928f0 Bernd Bleßmann
$::form->{ $field_names{$_} // $_ } = $email_form->{$_} for keys %{ $email_form };
aa36021a Bernd Bleßmann
d83928f0 Bernd Bleßmann
# for Form::cleanup which may be called in Form::send_email
$::form->{cwd} = getcwd();
$::form->{tmpdir} = $::lx_office_conf{paths}->{userspath};
aa36021a Bernd Bleßmann
fc75610c Bernd Bleßmann
$::form->{$_} = $::form->{print_options}->{$_} for keys %{ $::form->{print_options} };
d83928f0 Bernd Bleßmann
$::form->{media} = 'email';

4624b187 Bernd Bleßmann
if (($::form->{attachment_policy} // '') !~ m{^(?:old_file|no_file)$}) {
d83928f0 Bernd Bleßmann
my $language;
$language = SL::DB::Language->new(id => $::form->{print_options}->{language_id})->load if $::form->{print_options}->{language_id};

my $pdf;
1d242038 Bernd Bleßmann
my @errors = genereate_pdf($self->order, \$pdf, {media => $::form->{media},
format => $::form->{print_options}->{format},
formname => $::form->{print_options}->{formname},
language => $language,
groupitems => $::form->{print_options}->{groupitems}});
d83928f0 Bernd Bleßmann
if (scalar @errors) {
return $self->js->flash('error', t8('Conversion to PDF failed: #1', $errors[0]))->render($self);
}

my $sfile = SL::SessionFile::Random->new(mode => "w");
$sfile->fh->print($pdf);
$sfile->fh->close;

$::form->{tmpfile} = $sfile->file_name;
$::form->{tmpdir} = $sfile->get_path; # for Form::cleanup which may be called in Form::send_email
aa36021a Bernd Bleßmann
}

d83928f0 Bernd Bleßmann
$::form->send_email(\%::myconfig, 'pdf');

aa36021a Bernd Bleßmann
# internal notes
my $intnotes = $self->order->intnotes;
$intnotes .= "\n\n" if $self->order->intnotes;
$intnotes .= t8('[email]') . "\n";
$intnotes .= t8('Date') . ": " . $::locale->format_date_object(DateTime->now_local, precision => 'seconds') . "\n";
d83928f0 Bernd Bleßmann
$intnotes .= t8('To (email)') . ": " . $::form->{email} . "\n";
$intnotes .= t8('Cc') . ": " . $::form->{cc} . "\n" if $::form->{cc};
$intnotes .= t8('Bcc') . ": " . $::form->{bcc} . "\n" if $::form->{bcc};
$intnotes .= t8('Subject') . ": " . $::form->{subject} . "\n\n";
$intnotes .= t8('Message') . ": " . $::form->{message};
aa36021a Bernd Bleßmann
$self->js
->val('#order_intnotes', $intnotes)
0935b012 Bernd Bleßmann
->run('kivi.Order.close_email_dialog')
d83928f0 Bernd Bleßmann
->flash('info', t8('The email has been sent.'))
aa36021a Bernd Bleßmann
->render($self);
}

5c859d64 Bernd Bleßmann
# open the periodic invoices config dialog
#
# If there are values in the form (i.e. dialog was opened before),
# then use this values. Create new ones, else.
sub action_show_periodic_invoices_config_dialog {
my ($self) = @_;

1d242038 Bernd Bleßmann
my $config = make_periodic_invoices_config_from_yaml(delete $::form->{config});
5c859d64 Bernd Bleßmann
$config ||= SL::DB::Manager::PeriodicInvoicesConfig->find_by(oe_id => $::form->{id}) if $::form->{id};
$config ||= SL::DB::PeriodicInvoicesConfig->new(periodicity => 'm',
order_value_periodicity => 'p', # = same as periodicity
start_date_as_date => $::form->{transdate} || $::form->current_date,
extend_automatically_by => 12,
active => 1,
email_subject => GenericTranslations->get(
language_id => $::form->{language_id},
translation_type =>"preset_text_periodic_invoices_email_subject"),
email_body => GenericTranslations->get(
language_id => $::form->{language_id},
translation_type =>"preset_text_periodic_invoices_email_body"),
);
$config->periodicity('m') if none { $_ eq $config->periodicity } @SL::DB::PeriodicInvoicesConfig::PERIODICITIES;
$config->order_value_periodicity('p') if none { $_ eq $config->order_value_periodicity } ('p', @SL::DB::PeriodicInvoicesConfig::ORDER_VALUE_PERIODICITIES);

$::form->get_lists(printers => "ALL_PRINTERS",
charts => { key => 'ALL_CHARTS',
transdate => 'current_date' });

$::form->{AR} = [ grep { $_->{link} =~ m/(?:^|:)AR(?::|$)/ } @{ $::form->{ALL_CHARTS} } ];

if ($::form->{customer_id}) {
$::form->{ALL_CONTACTS} = SL::DB::Manager::Contact->get_all_sorted(where => [ cp_cv_id => $::form->{customer_id} ]);
}

$self->render('oe/edit_periodic_invoices_config', { layout => 0 },
popup_dialog => 1,
popup_js_close_function => 'kivi.Order.close_periodic_invoices_config_dialog()',
popup_js_assign_function => 'kivi.Order.assign_periodic_invoices_config()',
config => $config,
%$::form);
}

# assign the values of the periodic invoices config dialog
# as yaml in the hidden tag and set the status.
sub action_assign_periodic_invoices_config {
my ($self) = @_;

$::form->isblank('start_date_as_date', $::locale->text('The start date is missing.'));

a328c2e2 Bernd Bleßmann
my $config = { active => $::form->{active} ? 1 : 0,
terminated => $::form->{terminated} ? 1 : 0,
direct_debit => $::form->{direct_debit} ? 1 : 0,
periodicity => (any { $_ eq $::form->{periodicity} } @SL::DB::PeriodicInvoicesConfig::PERIODICITIES) ? $::form->{periodicity} : 'm',
order_value_periodicity => (any { $_ eq $::form->{order_value_periodicity} } ('p', @SL::DB::PeriodicInvoicesConfig::ORDER_VALUE_PERIODICITIES)) ? $::form->{order_value_periodicity} : 'p',
start_date_as_date => $::form->{start_date_as_date},
end_date_as_date => $::form->{end_date_as_date},
5c859d64 Bernd Bleßmann
first_billing_date_as_date => $::form->{first_billing_date_as_date},
a328c2e2 Bernd Bleßmann
print => $::form->{print} ? 1 : 0,
printer_id => $::form->{print} ? $::form->{printer_id} * 1 : undef,
copies => $::form->{copies} * 1 ? $::form->{copies} : 1,
extend_automatically_by => $::form->{extend_automatically_by} * 1 || undef,
ar_chart_id => $::form->{ar_chart_id} * 1,
5c859d64 Bernd Bleßmann
send_email => $::form->{send_email} ? 1 : 0,
email_recipient_contact_id => $::form->{email_recipient_contact_id} * 1 || undef,
email_recipient_address => $::form->{email_recipient_address},
email_sender => $::form->{email_sender},
email_subject => $::form->{email_subject},
email_body => $::form->{email_body},
};

my $periodic_invoices_config = YAML::Dump($config);

1d242038 Bernd Bleßmann
my $status = $self->get_periodic_invoices_status($config);
5c859d64 Bernd Bleßmann
$self->js
->remove('#order_periodic_invoices_config')
->insertAfter(hidden_tag('order.periodic_invoices_config', $periodic_invoices_config), '#periodic_invoices_status')
->run('kivi.Order.close_periodic_invoices_config_dialog')
->html('#periodic_invoices_status', $status)
->flash('info', t8('The periodic invoices config has been assigned.'))
->render($self);
}

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

1d242038 Bernd Bleßmann
my $config = make_periodic_invoices_config_from_yaml(delete $::form->{config});
5c859d64 Bernd Bleßmann
$config ||= SL::DB::Manager::PeriodicInvoicesConfig->find_by(oe_id => $::form->{id}) if $::form->{id};

my $has_active_periodic_invoices =
1d242038 Bernd Bleßmann
$self->type eq sales_order_type()
5c859d64 Bernd Bleßmann
&& $config
&& $config->active
&& (!$config->end_date || ($config->end_date > DateTime->today_local))
&& $config->get_previous_billed_period_start_date;

$_[0]->render(\ !!$has_active_periodic_invoices, { type => 'text' });
}

5ef1fa84 Bernd Bleßmann
# save the order and redirect to the frontend subroutine for a new
# delivery order
048a4ee5 Bernd Bleßmann
sub action_save_and_delivery_order {
my ($self) = @_;

1d242038 Bernd Bleßmann
my $errors = $self->save();
048a4ee5 Bernd Bleßmann
if (scalar @{ $errors }) {
$self->js->flash('error', $_) foreach @{ $errors };
return $self->js->render();
}
8e77bd29 Bernd Bleßmann
1d242038 Bernd Bleßmann
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')
8e77bd29 Bernd Bleßmann
: '';
flash_later('info', $text);
048a4ee5 Bernd Bleßmann
my @redirect_params = (
controller => 'oe.pl',
action => 'oe_delivery_order_from_order',
id => $self->order->id,
);

$self->redirect_to(@redirect_params);
}
aa36021a Bernd Bleßmann
07dd84c0 Bernd Bleßmann
# save the order and redirect to the frontend subroutine for a new
# invoice
sub action_save_and_invoice {
my ($self) = @_;

1d242038 Bernd Bleßmann
my $errors = $self->save();
07dd84c0 Bernd Bleßmann
if (scalar @{ $errors }) {
$self->js->flash('error', $_) foreach @{ $errors };
return $self->js->render();
}
8e77bd29 Bernd Bleßmann
1d242038 Bernd Bleßmann
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')
8e77bd29 Bernd Bleßmann
: '';
flash_later('info', $text);
07dd84c0 Bernd Bleßmann
my @redirect_params = (
controller => 'oe.pl',
action => 'oe_invoice_from_order',
id => $self->order->id,
);

$self->redirect_to(@redirect_params);
}

b6185e74 Bernd Bleßmann
# workflow from sales quotation to sales order
sub action_sales_order {
1d242038 Bernd Bleßmann
$_[0]->workflow_sales_or_purchase_order();
b6185e74 Bernd Bleßmann
}

# workflow from rfq to purchase order
sub action_purchase_order {
1d242038 Bernd Bleßmann
$_[0]->workflow_sales_or_purchase_order();
b6185e74 Bernd Bleßmann
}

b647f3f3 Geoffrey Richardson
# set form elements in respect to a changed customer or vendor
5ef1fa84 Bernd Bleßmann
#
# This action is called on an change of the customer/vendor picker.
099fc63b Bernd Bleßmann
sub action_customer_vendor_changed {
my ($self) = @_;

1d242038 Bernd Bleßmann
setup_order_from_cv($self->order);
$self->recalc();
370f1355 Bernd Bleßmann
099fc63b Bernd Bleßmann
my $cv_method = $self->cv;

if ($self->order->$cv_method->contacts && scalar @{ $self->order->$cv_method->contacts } > 0) {
$self->js->show('#cp_row');
} else {
$self->js->hide('#cp_row');
}

if ($self->order->$cv_method->shipto && scalar @{ $self->order->$cv_method->shipto } > 0) {
$self->js->show('#shipto_row');
} else {
$self->js->hide('#shipto_row');
}

370f1355 Bernd Bleßmann
$self->js->val( '#order_salesman_id', $self->order->salesman_id) if $self->order->is_sales;
9af3ce1c Bernd Bleßmann
099fc63b Bernd Bleßmann
$self->js
9af3ce1c Bernd Bleßmann
->replaceWith('#order_cp_id', $self->build_contact_select)
->replaceWith('#order_shipto_id', $self->build_shipto_select)
9474d0ec Bernd Bleßmann
->replaceWith('#business_info_row', $self->build_business_info_row)
9af3ce1c Bernd Bleßmann
->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)
370f1355 Bernd Bleßmann
->val( '#order_intnotes', $self->order->intnotes)
9af3ce1c Bernd Bleßmann
->focus( '#order_' . $self->cv . '_id');

1d242038 Bernd Bleßmann
$self->js_redisplay_amounts_and_taxes;
9af3ce1c Bernd Bleßmann
$self->js->render();
099fc63b Bernd Bleßmann
}

aacd6696 Bernd Bleßmann
# 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;
7ff8f33c Bernd Bleßmann
$details{pricegroup} = $cv->pricegroup->pricegroup if $is_customer && $cv->pricegroup;
aacd6696 Bernd Bleßmann
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);

}

5ef1fa84 Bernd Bleßmann
# called if a unit in an existing item row is changed
2d50590b Bernd Bleßmann
sub action_unit_changed {
my ($self) = @_;

my $idx = first_index { $_ eq $::form->{item_id} } @{ $::form->{orderitem_ids} };
5dd5e97b Bernd Bleßmann
my $item = $self->order->items_sorted->[$idx];
2d50590b Bernd Bleßmann
my $old_unit_obj = SL::DB::Unit->new(name => $::form->{old_unit})->load;
$item->sellprice($item->unit_obj->convert_to($item->sellprice, $old_unit_obj));

1d242038 Bernd Bleßmann
$self->recalc();
2d50590b Bernd Bleßmann
$self->js
0935b012 Bernd Bleßmann
->run('kivi.Order.update_sellprice', $::form->{item_id}, $item->sellprice_as_number);
1d242038 Bernd Bleßmann
$self->js_redisplay_line_values;
$self->js_redisplay_amounts_and_taxes;
2d50590b Bernd Bleßmann
$self->js->render();
}

5ef1fa84 Bernd Bleßmann
# add an item row for a new item entered in the input row
099fc63b Bernd Bleßmann
sub action_add_item {
my ($self) = @_;

my $form_attr = $::form->{add_item};

return unless $form_attr->{parts_id};

1d242038 Bernd Bleßmann
my $item = new_item($self->order, $form_attr);
340b402a Geoffrey Richardson
91abaf6c Bernd Bleßmann
$self->order->add_items($item);
099fc63b Bernd Bleßmann
1d242038 Bernd Bleßmann
$self->recalc();
f275cac9 Bernd Bleßmann
91abaf6c Bernd Bleßmann
my $item_id = join('_', 'new', Time::HiRes::gettimeofday(), int rand 1000000000000);
32951b1f Bernd Bleßmann
my $row_as_html = $self->p->render('order/tabs/_row',
ITEM => $item,
ID => $item_id,
78e36cfd Bernd Bleßmann
TYPE => $self->type,
32951b1f Bernd Bleßmann
ALL_PRICE_FACTORS => $self->all_price_factors
);
f275cac9 Bernd Bleßmann
91abaf6c Bernd Bleßmann
$self->js
340b402a Geoffrey Richardson
->append('#row_table_id', $row_as_html);

if ( $item->part->is_assortment ) {
$form_attr->{qty_as_number} = 1 unless $form_attr->{qty_as_number};
foreach my $assortment_item ( @{$item->part->assortment_items} ) {
my $attr = { parts_id => $assortment_item->parts_id,
qty => $assortment_item->qty * $::form->parse_amount(\%::myconfig, $form_attr->{qty_as_number}), # TODO $form_attr->{unit}
unit => $assortment_item->unit,
description => $assortment_item->part->description,
};
1d242038 Bernd Bleßmann
my $item = new_item($self->order, $attr);
4b518cdb Geoffrey Richardson
# set discount to 100% if item isn't supposed to be charged, overwriting any customer discount
$item->discount(1) unless $assortment_item->charge;

340b402a Geoffrey Richardson
$self->order->add_items( $item );
1d242038 Bernd Bleßmann
$self->recalc();
340b402a Geoffrey Richardson
my $item_id = join('_', 'new', Time::HiRes::gettimeofday(), int rand 1000000000000);
my $row_as_html = $self->p->render('order/tabs/_row',
ITEM => $item,
ID => $item_id,
78e36cfd Bernd Bleßmann
TYPE => $self->type,
340b402a Geoffrey Richardson
ALL_PRICE_FACTORS => $self->all_price_factors
);
$self->js
->append('#row_table_id', $row_as_html);
};
};

$self->js
91abaf6c Bernd Bleßmann
->val('.add_item_input', '')
0935b012 Bernd Bleßmann
->run('kivi.Order.init_row_handlers')
->run('kivi.Order.row_table_scroll_down')
->run('kivi.Order.renumber_positions')
91abaf6c Bernd Bleßmann
->focus('#add_item_parts_id_name');
f275cac9 Bernd Bleßmann
1d242038 Bernd Bleßmann
$self->js_redisplay_amounts_and_taxes;
91abaf6c Bernd Bleßmann
$self->js->render();
}

5ef1fa84 Bernd Bleßmann
# open the dialog for entering multiple items at once
91abaf6c Bernd Bleßmann
sub action_show_multi_items_dialog {
$_[0]->render('order/tabs/_multi_items_dialog', { layout => 0 },
all_partsgroups => SL::DB::Manager::PartsGroup->get_all);
}

5ef1fa84 Bernd Bleßmann
# update the filter results in the multi item dialog
91abaf6c Bernd Bleßmann
sub action_multi_items_update_result {
my $max_count = 100;
9fff4a29 Bernd Bleßmann
$::form->{multi_items}->{filter}->{obsolete} = 0;

91abaf6c Bernd Bleßmann
my $count = $_[0]->multi_items_models->count;

if ($count == 0) {
my $text = SL::Presenter::EscapedText->new(text => $::locale->text('No results.'));
$_[0]->render($text, { layout => 0 });
} elsif ($count > $max_count) {
my $text = SL::Presenter::EscapedText->new(text => $::locale->text('Too many results (#1 from #2).', $count, $max_count));
$_[0]->render($text, { layout => 0 });
f275cac9 Bernd Bleßmann
} else {
91abaf6c Bernd Bleßmann
my $multi_items = $_[0]->multi_items_models->get;
$_[0]->render('order/tabs/_multi_items_result', { layout => 0 },
multi_items => $multi_items);
f275cac9 Bernd Bleßmann
}
91abaf6c Bernd Bleßmann
}
099fc63b Bernd Bleßmann
b647f3f3 Geoffrey Richardson
# add item rows for multiple items at once
91abaf6c Bernd Bleßmann
sub action_add_multi_items {
my ($self) = @_;
099fc63b Bernd Bleßmann
91abaf6c Bernd Bleßmann
my @form_attr = grep { $_->{qty_as_number} } @{ $::form->{add_multi_items} };
return $self->js->render() unless scalar @form_attr;
099fc63b Bernd Bleßmann
91abaf6c Bernd Bleßmann
my @items;
foreach my $attr (@form_attr) {
1d242038 Bernd Bleßmann
my $item = new_item($self->order, $attr);
4b518cdb Geoffrey Richardson
push @items, $item;
if ( $item->part->is_assortment ) {
foreach my $assortment_item ( @{$item->part->assortment_items} ) {
my $attr = { parts_id => $assortment_item->parts_id,
qty => $assortment_item->qty * $item->qty, # TODO $form_attr->{unit}
unit => $assortment_item->unit,
description => $assortment_item->part->description,
};
1d242038 Bernd Bleßmann
my $item = new_item($self->order, $attr);
4b518cdb Geoffrey Richardson
# set discount to 100% if item isn't supposed to be charged, overwriting any customer discount
$item->discount(1) unless $assortment_item->charge;
01e7e978 Bernd Bleßmann
push @items, $item;
4b518cdb Geoffrey Richardson
}
}
91abaf6c Bernd Bleßmann
}
$self->order->add_items(@items);
099fc63b Bernd Bleßmann
1d242038 Bernd Bleßmann
$self->recalc();
099fc63b Bernd Bleßmann
91abaf6c Bernd Bleßmann
foreach my $item (@items) {
my $item_id = join('_', 'new', Time::HiRes::gettimeofday(), int rand 1000000000000);
32951b1f Bernd Bleßmann
my $row_as_html = $self->p->render('order/tabs/_row',
ITEM => $item,
ID => $item_id,
78e36cfd Bernd Bleßmann
TYPE => $self->type,
32951b1f Bernd Bleßmann
ALL_PRICE_FACTORS => $self->all_price_factors
);
91abaf6c Bernd Bleßmann
d8a1906b Bernd Bleßmann
$self->js->append('#row_table_id', $row_as_html);
91abaf6c Bernd Bleßmann
}
099fc63b Bernd Bleßmann
$self->js
0935b012 Bernd Bleßmann
->run('kivi.Order.close_multi_items_dialog')
->run('kivi.Order.init_row_handlers')
->run('kivi.Order.row_table_scroll_down')
->run('kivi.Order.renumber_positions')
099fc63b Bernd Bleßmann
->focus('#add_item_parts_id_name');

1d242038 Bernd Bleßmann
$self->js_redisplay_amounts_and_taxes;
099fc63b Bernd Bleßmann
$self->js->render();
}

5ef1fa84 Bernd Bleßmann
# recalculate all linetotals, amounts and taxes and redisplay them
099fc63b Bernd Bleßmann
sub action_recalc_amounts_and_taxes {
my ($self) = @_;

1d242038 Bernd Bleßmann
$self->recalc();
099fc63b Bernd Bleßmann
1d242038 Bernd Bleßmann
$self->js_redisplay_line_values;
$self->js_redisplay_amounts_and_taxes;
099fc63b Bernd Bleßmann
$self->js->render();
}

b647f3f3 Geoffrey Richardson
# redisplay item rows if they are sorted by an attribute
e8889e47 Bernd Bleßmann
sub action_reorder_items {
my ($self) = @_;

my %sort_keys = (
partnumber => sub { $_[0]->part->partnumber },
description => sub { $_[0]->description },
qty => sub { $_[0]->qty },
sellprice => sub { $_[0]->sellprice },
discount => sub { $_[0]->discount },
);

my $method = $sort_keys{$::form->{order_by}};
my @to_sort = map { { old_pos => $_->position, order_by => $method->($_) } } @{ $self->order->items_sorted };
if ($::form->{sort_dir}) {
@to_sort = sort { $a->{order_by} cmp $b->{order_by} } @to_sort;
} else {
@to_sort = sort { $b->{order_by} cmp $a->{order_by} } @to_sort;
}
$self->js
0935b012 Bernd Bleßmann
->run('kivi.Order.redisplay_items', \@to_sort)
e8889e47 Bernd Bleßmann
->render;
}

5ef1fa84 Bernd Bleßmann
# show the popup to choose a price/discount source
f275cac9 Bernd Bleßmann
sub action_price_popup {
my ($self) = @_;

my $idx = first_index { $_ eq $::form->{item_id} } @{ $::form->{orderitem_ids} };
5dd5e97b Bernd Bleßmann
my $item = $self->order->items_sorted->[$idx];
f275cac9 Bernd Bleßmann
$self->render_price_dialog($item);
}

5ef1fa84 Bernd Bleßmann
# get the longdescription for an item if the dialog to enter/change the
# longdescription was opened and the longdescription is empty
#
# If this item is new, get the longdescription from Part.
b647f3f3 Geoffrey Richardson
# Otherwise get it from OrderItem.
ed04f337 Bernd Bleßmann
sub action_get_item_longdescription {
my $longdescription;

if ($::form->{item_id}) {
$longdescription = SL::DB::OrderItem->new(id => $::form->{item_id})->load->longdescription;
} elsif ($::form->{parts_id}) {
$longdescription = SL::DB::Part->new(id => $::form->{parts_id})->load->notes;
}
$_[0]->render(\ $longdescription, { type => 'text' });
}

a143bb85 Bernd Bleßmann
# load the second row for one or more items
28a7a539 Bernd Bleßmann
#
9eb765a5 Bernd Bleßmann
# This action gets the html code for all items second rows by rendering a template for
# the second row and sets the html code via client js.
sub action_load_second_rows {
28a7a539 Bernd Bleßmann
my ($self) = @_;

1d242038 Bernd Bleßmann
$self->recalc() if $self->order->is_sales; # for margin calculation
5737ce39 Bernd Bleßmann
9eb765a5 Bernd Bleßmann
foreach my $item_id (@{ $::form->{item_ids} }) {
my $idx = first_index { $_ eq $item_id } @{ $::form->{orderitem_ids} };
my $item = $self->order->items_sorted->[$idx];

1d242038 Bernd Bleßmann
$self->js_load_second_row($item, $item_id, 0);
9eb765a5 Bernd Bleßmann
}

f2461e14 Bernd Bleßmann
$self->js->run('kivi.Order.init_row_handlers') if $self->order->is_sales; # for lastcosts change-callback

9eb765a5 Bernd Bleßmann
$self->js->render();
}

1d242038 Bernd Bleßmann
sub js_load_second_row {
9eb765a5 Bernd Bleßmann
my ($self, $item, $item_id, $do_parse) = @_;
28a7a539 Bernd Bleßmann
9eb765a5 Bernd Bleßmann
if ($do_parse) {
# Parse values from form (they are formated while rendering (template)).
# Workaround to pre-parse number-cvars (parse_custom_variable_values does not parse number values).
# This parsing is not necessary at all, if we assure that the second row/cvars are only loaded once.
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;
}
28a7a539 Bernd Bleßmann
78e36cfd Bernd Bleßmann
my $row_as_html = $self->p->render('order/tabs/_second_row', ITEM => $item, TYPE => $self->type);
28a7a539 Bernd Bleßmann
$self->js
9eb765a5 Bernd Bleßmann
->html('.row_entry:has(#item_' . $item_id . ') [name = "second_row"]', $row_as_html)
->data('.row_entry:has(#item_' . $item_id . ') [name = "second_row"]', 'loaded', 1);
28a7a539 Bernd Bleßmann
}

1d242038 Bernd Bleßmann
sub js_redisplay_line_values {
099fc63b Bernd Bleßmann
my ($self) = @_;

5737ce39 Bernd Bleßmann
my $is_sales = $self->order->is_sales;

# sales orders with margins
my @data;
if ($is_sales) {
@data = map {
[
$::form->format_amount(\%::myconfig, $_->{linetotal}, 2, 0),
$::form->format_amount(\%::myconfig, $_->{marge_total}, 2, 0),
$::form->format_amount(\%::myconfig, $_->{marge_percent}, 2, 0),
]} @{ $self->order->items_sorted };
} else {
@data = map {
[
$::form->format_amount(\%::myconfig, $_->{linetotal}, 2, 0),
]} @{ $self->order->items_sorted };
}

099fc63b Bernd Bleßmann
$self->js
5737ce39 Bernd Bleßmann
->run('kivi.Order.redisplay_line_values', $is_sales, \@data);
099fc63b Bernd Bleßmann
}

1d242038 Bernd Bleßmann
sub js_redisplay_amounts_and_taxes {
099fc63b Bernd Bleßmann
my ($self) = @_;

9af3ce1c Bernd Bleßmann
if (scalar @{ $self->{taxes} }) {
$self->js->show('#taxincluded_row_id');
} else {
$self->js->hide('#taxincluded_row_id');
}

if ($self->order->taxincluded) {
$self->js->hide('#subtotal_row_id');
} else {
$self->js->show('#subtotal_row_id');
}

099fc63b Bernd Bleßmann
$self->js
->html('#netamount_id', $::form->format_amount(\%::myconfig, $self->order->netamount, -2))
->html('#amount_id', $::form->format_amount(\%::myconfig, $self->order->amount, -2))
->remove('.tax_row')
->insertBefore($self->build_tax_rows, '#amount_row_id');
}

#
# helpers
#

sub init_valid_types {
1d242038 Bernd Bleßmann
[ sales_order_type(), purchase_order_type(), sales_quotation_type(), request_quotation_type() ];
099fc63b Bernd Bleßmann
}

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

if (none { $::form->{type} eq $_ } @{$self->valid_types}) {
die "Not a valid type for order";
}

$self->type($::form->{type});
}

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

1d242038 Bernd Bleßmann
my $cv = (any { $self->type eq $_ } (sales_order_type(), sales_quotation_type())) ? 'customer'
: (any { $self->type eq $_ } (purchase_order_type(), request_quotation_type())) ? 'vendor'
099fc63b Bernd Bleßmann
: die "Not a valid type for order";

return $cv;
}

sub init_p {
SL::Presenter->get;
}

sub init_order {
1d242038 Bernd Bleßmann
$_[0]->make_order;
099fc63b Bernd Bleßmann
}

5ef1fa84 Bernd Bleßmann
# model used to filter/display the parts in the multi-items dialog
91abaf6c Bernd Bleßmann
sub init_multi_items_models {
SL::Controller::Helper::GetModels->new(
controller => $_[0],
model => 'Part',
with_objects => [ qw(unit_obj) ],
disable_plugin => 'paginated',
source => $::form->{multi_items},
sorted => {
_default => {
by => 'partnumber',
dir => 1,
},
partnumber => t8('Partnumber'),
description => t8('Description')}
);
}

32951b1f Bernd Bleßmann
sub init_all_price_factors {
SL::DB::Manager::PriceFactor->get_all;
}

1d242038 Bernd Bleßmann
sub check_auth {
099fc63b Bernd Bleßmann
my ($self) = @_;

my $right_for = { map { $_ => $_.'_edit' } @{$self->valid_types} };

my $right = $right_for->{ $self->type };
$right ||= 'DOES_NOT_EXIST';

$::auth->assert($right);
}

5ef1fa84 Bernd Bleßmann
# build the selection box for contacts
#
# Needed, if customer/vendor changed.
099fc63b Bernd Bleßmann
sub build_contact_select {
my ($self) = @_;

0aa885f4 Sven Schöling
select_tag('order.cp_id', [ $self->order->{$self->cv}->contacts ],
value_key => 'cp_id',
title_key => 'full_name_dep',
default => $self->order->cp_id,
with_empty => 1,
style => 'width: 300px',
099fc63b Bernd Bleßmann
);
}

5ef1fa84 Bernd Bleßmann
# build the selection box for shiptos
#
# Needed, if customer/vendor changed.
099fc63b Bernd Bleßmann
sub build_shipto_select {
my ($self) = @_;

0aa885f4 Sven Schöling
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',
099fc63b Bernd Bleßmann
);
}

9474d0ec Bernd Bleßmann
# 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]);
}

5ef1fa84 Bernd Bleßmann
# build the rows for displaying taxes
#
# Called if amounts where recalculated and redisplayed.
099fc63b Bernd Bleßmann
sub build_tax_rows {
my ($self) = @_;

my $rows_as_html;
foreach my $tax (sort { $a->{tax}->rate cmp $b->{tax}->rate } @{ $self->{taxes} }) {
9af3ce1c Bernd Bleßmann
$rows_as_html .= $self->p->render('order/tabs/_tax_row', TAX => $tax, TAXINCLUDED => $self->order->taxincluded);
099fc63b Bernd Bleßmann
}
return $rows_as_html;
}


f275cac9 Bernd Bleßmann
sub render_price_dialog {
my ($self, $record_item) = @_;

my $price_source = SL::PriceSource->new(record_item => $record_item, record => $self->order);

$self->js
->run(
'kivi.io.price_chooser_dialog',
t8('Available Prices'),
$self->render('order/tabs/_price_sources_dialog', { output => 0 }, price_source => $price_source)
)
->reinit_widgets;

# if (@errors) {
# $self->js->text('#dialog_flash_error_content', join ' ', @errors);
# $self->js->show('#dialog_flash_error');
# }

$self->js->render;
}

1d242038 Bernd Bleßmann
sub load_order {
5dd5e97b Bernd Bleßmann
my ($self) = @_;

return if !$::form->{id};

0135f09f Bernd Bleßmann
$self->order(SL::DB::Order->new(id => $::form->{id})->load);
5dd5e97b Bernd Bleßmann
}

5ef1fa84 Bernd Bleßmann
# load or create a new order object
#
5c859d64 Bernd Bleßmann
# And assign changes from the form to this object.
5ef1fa84 Bernd Bleßmann
# If the order is loaded from db, check if items are deleted in the form,
# remove them form the object and collect them for removing from db on saving.
1d242038 Bernd Bleßmann
# Then create/update items from form (via make_item) and add them.
sub make_order {
099fc63b Bernd Bleßmann
my ($self) = @_;

# add_items adds items to an order with no items for saving, but they cannot
# be retrieved via items until the order is saved. Adding empty items to new
# order here solves this problem.
my $order;
$order = SL::DB::Manager::Order->find_by(id => $::form->{id}) if $::form->{id};
8e77bd29 Bernd Bleßmann
$order ||= SL::DB::Order->new(orderitems => [],
1d242038 Bernd Bleßmann
quotation => (any { $self->type eq $_ } (sales_quotation_type(), request_quotation_type())));
099fc63b Bernd Bleßmann
370f1355 Bernd Bleßmann
my $cv_id_method = $self->cv . '_id';
if (!$::form->{id} && $::form->{$cv_id_method}) {
$order->$cv_id_method($::form->{$cv_id_method});
1d242038 Bernd Bleßmann
setup_order_from_cv($order);
370f1355 Bernd Bleßmann
}

5c859d64 Bernd Bleßmann
my $form_orderitems = delete $::form->{order}->{orderitems};
my $form_periodic_invoices_config = delete $::form->{order}->{periodic_invoices_config};

099fc63b Bernd Bleßmann
$order->assign_attributes(%{$::form->{order}});

1d242038 Bernd Bleßmann
my $periodic_invoices_config = make_periodic_invoices_config_from_yaml($form_periodic_invoices_config);
5c859d64 Bernd Bleßmann
$order->periodic_invoices_config($periodic_invoices_config) if $periodic_invoices_config;

5dd5e97b Bernd Bleßmann
# remove deleted items
$self->item_ids_to_delete([]);
foreach my $idx (reverse 0..$#{$order->orderitems}) {
my $item = $order->orderitems->[$idx];
if (none { $item->id == $_->{id} } @{$form_orderitems}) {
splice @{$order->orderitems}, $idx, 1;
push @{$self->item_ids_to_delete}, $item->id;
}
}

my @items;
my $pos = 1;
foreach my $form_attr (@{$form_orderitems}) {
1d242038 Bernd Bleßmann
my $item = make_item($order, $form_attr);
5dd5e97b Bernd Bleßmann
$item->position($pos);
push @items, $item;
$pos++;
}
$order->add_items(grep {!$_->id} @items);

099fc63b Bernd Bleßmann
return $order;
}

5ef1fa84 Bernd Bleßmann
# create or update items from form
#
5dd5e97b Bernd Bleßmann
# Make item objects from form values. For items already existing read from db.
# Create a new item else. And assign attributes.
1d242038 Bernd Bleßmann
sub make_item {
91abaf6c Bernd Bleßmann
my ($record, $attr) = @_;

5dd5e97b Bernd Bleßmann
my $item;
$item = first { $_->id == $attr->{id} } @{$record->items} if $attr->{id};

ed04f337 Bernd Bleßmann
my $is_new = !$item;

5dd5e97b Bernd Bleßmann
# 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
# saved. Adding empty custom_variables to new orderitem here solves this problem.
$item ||= SL::DB::OrderItem->new(custom_variables => []);
ed04f337 Bernd Bleßmann
5dd5e97b Bernd Bleßmann
$item->assign_attributes(%$attr);
b6185e74 Bernd Bleßmann
$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};
5dd5e97b Bernd Bleßmann
return $item;
}

5ef1fa84 Bernd Bleßmann
# create a new item
#
e0a47f33 Bernd Bleßmann
# This is used to add one item
1d242038 Bernd Bleßmann
sub new_item {
5dd5e97b Bernd Bleßmann
my ($record, $attr) = @_;

91abaf6c Bernd Bleßmann
my $item = SL::DB::OrderItem->new;
7d2700f0 Bernd Bleßmann
# Remove attributes where the user left or set the inputs empty.
# So these attributes will be undefined and we can distinguish them
# from zero later on.
for (qw(qty_as_number sellprice_as_number discount_as_percent)) {
delete $attr->{$_} if $attr->{$_} eq '';
}

91abaf6c Bernd Bleßmann
$item->assign_attributes(%$attr);

my $part = SL::DB::Part->new(id => $attr->{parts_id})->load;
my $price_source = SL::PriceSource->new(record_item => $item, record => $record);

$item->unit($part->unit) if !$item->unit;

my $price_src;
ea1df49d Geoffrey Richardson
if ( $part->is_assortment ) {
# add assortment items with price 0, as the components carry the price
$price_src = $price_source->price_from_source("");
$price_src->price(0);
7d2700f0 Bernd Bleßmann
} elsif (defined $item->sellprice) {
91abaf6c Bernd Bleßmann
$price_src = $price_source->price_from_source("");
$price_src->price($item->sellprice);
} else {
$price_src = $price_source->best_price
? $price_source->best_price
: $price_source->price_from_source("");
$price_src->price(0) if !$price_source->best_price;
}

my $discount_src;
7d2700f0 Bernd Bleßmann
if (defined $item->discount) {
91abaf6c Bernd Bleßmann
$discount_src = $price_source->discount_from_source("");
$discount_src->discount($item->discount);
} else {
$discount_src = $price_source->best_discount
? $price_source->best_discount
: $price_source->discount_from_source("");
$discount_src->discount(0) if !$price_source->best_discount;
}

my %new_attr;
$new_attr{part} = $part;
32951b1f Bernd Bleßmann
$new_attr{description} = $part->description if ! $item->description;
$new_attr{qty} = 1.0 if ! $item->qty;
$new_attr{price_factor_id} = $part->price_factor_id if ! $item->price_factor_id;
91abaf6c Bernd Bleßmann
$new_attr{sellprice} = $price_src->price;
$new_attr{discount} = $discount_src->discount;
$new_attr{active_price_source} = $price_src;
$new_attr{active_discount_source} = $discount_src;
e0a47f33 Bernd Bleßmann
$new_attr{longdescription} = $part->notes if ! defined $attr->{longdescription};
0b20f337 Bernd Bleßmann
$new_attr{project_id} = $record->globalproject_id;
b6185e74 Bernd Bleßmann
$new_attr{lastcost} = $record->is_sales ? $part->lastcost : 0;
ed04f337 Bernd Bleßmann
91abaf6c Bernd Bleßmann
# 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
# saved. Adding empty custom_variables to new orderitem here solves this problem.
$new_attr{custom_variables} = [];

$item->assign_attributes(%new_attr);

return $item;
}

1d242038 Bernd Bleßmann
sub setup_order_from_cv {
370f1355 Bernd Bleßmann
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});
}

}

5ef1fa84 Bernd Bleßmann
# recalculate prices and taxes
#
9a128e8b Sven Schöling
# Using the PriceTaxCalculator. Store linetotals in the item objects.
1d242038 Bernd Bleßmann
sub recalc {
099fc63b Bernd Bleßmann
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} = [];
foreach my $tax_chart_id (keys %{ $pat{taxes} }) {
my $tax = SL::DB::Manager::Tax->find_by(chart_id => $tax_chart_id);
9af3ce1c Bernd Bleßmann
my @amount_keys = grep { $pat{amounts}->{$_}->{tax_id} == $tax->id } keys %{ $pat{amounts} };
push(@{ $self->{taxes} }, { amount => $pat{taxes}->{$tax_chart_id},
netamount => $pat{amounts}->{$amount_keys[0]}->{amount},
tax => $tax });
099fc63b Bernd Bleßmann
}

pairwise { $a->{linetotal} = $b->{linetotal} } @{$self->order->items}, @{$pat{items}};
}

5ef1fa84 Bernd Bleßmann
# get data for saving, printing, ..., that is not changed in the form
#
# Only cvars for now.
1d242038 Bernd Bleßmann
sub get_unalterable_data {
099fc63b Bernd Bleßmann
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;
}
}

5ef1fa84 Bernd Bleßmann
# delete the order
#
# And remove related files in the spool directory
1d242038 Bernd Bleßmann
sub delete {
da55cfa0 Bernd Bleßmann
my ($self) = @_;

my $errors = [];
96670fe8 Moritz Bunkus
my $db = $self->order->db;
da55cfa0 Bernd Bleßmann
96670fe8 Moritz Bunkus
$db->with_transaction(
da55cfa0 Bernd Bleßmann
sub {
my @spoolfiles = grep { $_ } map { $_->spoolfile } @{ SL::DB::Manager::Status->get_all(where => [ trans_id => $self->order->id ]) };
$self->order->delete;
my $spool = $::lx_office_conf{paths}->{spool};
unlink map { "$spool/$_" } @spoolfiles if $spool;

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

return $errors;
}

5ef1fa84 Bernd Bleßmann
# save the order
#
# And delete items that are deleted in the form.
1d242038 Bernd Bleßmann
sub save {
099fc63b Bernd Bleßmann
my ($self) = @_;

my $errors = [];
96670fe8 Moritz Bunkus
my $db = $self->order->db;
099fc63b Bernd Bleßmann
96670fe8 Moritz Bunkus
$db->with_transaction(sub {
SL::DB::OrderItem->new(id => $_)->delete for @{$self->item_ids_to_delete};
$self->order->save(cascade => 1);
b6185e74 Bernd Bleßmann
# 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;
099fc63b Bernd Bleßmann
}) || push(@{$errors}, $db->error);

return $errors;
}

1d242038 Bernd Bleßmann
sub workflow_sales_or_purchase_order {
b6185e74 Bernd Bleßmann
my ($self) = @_;

1d242038 Bernd Bleßmann
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()
b6185e74 Bernd Bleßmann
: '';

$self->order(SL::DB::Order->new_from($self->order, destination_type => $destination_type));
$self->{converted_from_oe_id} = delete $::form->{id};

a823f2b0 Bernd Bleßmann
# 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);
}

b6185e74 Bernd Bleßmann
# change form type
$::form->{type} = $destination_type;
69966b4c Bernd Bleßmann
$self->type($self->init_type);
$self->cv ($self->init_cv);
1d242038 Bernd Bleßmann
$self->check_auth;
b6185e74 Bernd Bleßmann
1d242038 Bernd Bleßmann
$self->recalc();
$self->get_unalterable_data();
$self->pre_render();
b6185e74 Bernd Bleßmann
# 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',
1d242038 Bernd Bleßmann
title => $self->get_title_for('edit'),
b6185e74 Bernd Bleßmann
%{$self->{template_args}}
);
}

099fc63b Bernd Bleßmann
1d242038 Bernd Bleßmann
sub pre_render {
099fc63b Bernd Bleßmann
my ($self) = @_;

5c859d64 Bernd Bleßmann
$self->{all_taxzones} = SL::DB::Manager::TaxZone->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 ] ],
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;
1d242038 Bernd Bleßmann
$self->{periodic_invoices_status} = $self->get_periodic_invoices_status($self->order->periodic_invoices_config);
8e77bd29 Bernd Bleßmann
$self->{order_probabilities} = [ map { { title => ($_ * 10) . '%', id => $_ * 10 } } (0..10) ];
2ff1c023 Bernd Bleßmann
b1b3cdeb Bernd Bleßmann
my $print_form = Form->new('');
$print_form->{type} = $self->type;
$print_form->{printers} = SL::DB::Manager::Printer->get_all_sorted;
$print_form->{languages} = SL::DB::Manager::Language->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,
34540341 Bernd Bleßmann
no_opendocument => 0,
b1b3cdeb Bernd Bleßmann
no_html => 1},
);

5dd5e97b Bernd Bleßmann
foreach my $item (@{$self->order->orderitems}) {
f275cac9 Bernd Bleßmann
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));
}

03d66bba Bernd Bleßmann
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;
}

2e9dddf1 Bernd Bleßmann
if ($self->order->number && $::instance_conf->get_webdav) {
6550f507 Bernd Bleßmann
my $webdav = SL::Webdav->new(
type => $self->type,
2e9dddf1 Bernd Bleßmann
number => $self->order->number,
6550f507 Bernd Bleßmann
);
my @all_objects = $webdav->get_all_objects;
@{ $self->{template_args}->{WEBDAV} } = map { { name => $_->filename,
type => t8('File'),
359506e5 Jan Büren
link => File::Spec->catfile($_->full_filedescriptor),
6550f507 Bernd Bleßmann
} } @all_objects;
}

00402ae6 Bernd Bleßmann
$::request->{layout}->use_javascript("${_}.js") for qw(kivi.SalesPurchase kivi.Order kivi.File ckeditor/ckeditor ckeditor/adapters/jquery edit_periodic_invoices_config calculate_qty);
1d242038 Bernd Bleßmann
$self->setup_edit_action_bar;
7d020076 Moritz Bunkus
}

1d242038 Bernd Bleßmann
sub setup_edit_action_bar {
7d020076 Moritz Bunkus
my ($self, %params) = @_;

1d242038 Bernd Bleßmann
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);
7d020076 Moritz Bunkus
for my $bar ($::request->layout->get('actionbar')) {
$bar->add(
combobox => [
action => [
t8('Save'),
1d1f3140 Jan Büren
call => [ 'kivi.Order.save', 'save', $::instance_conf->get_order_warn_duplicate_parts,
$::instance_conf->get_order_warn_no_deliverydate,
],
5c859d64 Bernd Bleßmann
checks => [ 'kivi.Order.check_save_active_periodic_invoices' ],
7d020076 Moritz Bunkus
],
00c3a0ed Bernd Bleßmann
action => [
t8('Save as new'),
06806380 Bernd Bleßmann
call => [ 'kivi.Order.save', 'save_as_new', $::instance_conf->get_order_warn_duplicate_parts ],
00c3a0ed Bernd Bleßmann
checks => [ 'kivi.Order.check_save_active_periodic_invoices' ],
disabled => !$self->order->id ? t8('This object has not been saved yet.') : undef,
],
7d020076 Moritz Bunkus
action => [
t8('Save and Delivery Order'),
1d1f3140 Jan Büren
call => [ 'kivi.Order.save', 'save_and_delivery_order', $::instance_conf->get_order_warn_duplicate_parts,
$::instance_conf->get_order_warn_no_deliverydate,
],
5c859d64 Bernd Bleßmann
checks => [ 'kivi.Order.check_save_active_periodic_invoices' ],
1d242038 Bernd Bleßmann
only_if => (any { $self->type eq $_ } (sales_order_type(), purchase_order_type()))
7d020076 Moritz Bunkus
],
07dd84c0 Bernd Bleßmann
action => [
t8('Save and Invoice'),
06806380 Bernd Bleßmann
call => [ 'kivi.Order.save', 'save_and_invoice', $::instance_conf->get_order_warn_duplicate_parts ],
5c859d64 Bernd Bleßmann
checks => [ 'kivi.Order.check_save_active_periodic_invoices' ],
07dd84c0 Bernd Bleßmann
],
7d020076 Moritz Bunkus
], # end of combobox "Save"

b6185e74 Bernd Bleßmann
combobox => [
action => [
t8('Workflow'),
],
action => [
t8('Sales Order'),
ccbd0240 Bernd Bleßmann
submit => [ '#order_form', { action => "Order/sales_order" } ],
1d242038 Bernd Bleßmann
only_if => (any { $self->type eq $_ } (sales_quotation_type(), purchase_order_type())),
ccbd0240 Bernd Bleßmann
disabled => !$self->order->id ? t8('This object has not been saved yet.') : undef,
b6185e74 Bernd Bleßmann
],
action => [
t8('Purchase Order'),
ccbd0240 Bernd Bleßmann
submit => [ '#order_form', { action => "Order/purchase_order" } ],
1d242038 Bernd Bleßmann
only_if => (any { $self->type eq $_ } (sales_order_type(), request_quotation_type())),
ccbd0240 Bernd Bleßmann
disabled => !$self->order->id ? t8('This object has not been saved yet.') : undef,
b6185e74 Bernd Bleßmann
],
], # end of combobox "Workflow"

7d020076 Moritz Bunkus
combobox => [
action => [
t8('Export'),
],
action => [
t8('Print'),
call => [ 'kivi.Order.show_print_options' ],
],
action => [
t8('E-mail'),
call => [ 'kivi.Order.email' ],
],
a59f11b0 Moritz Bunkus
action => [
t8('Download attachments of all parts'),
call => [ 'kivi.File.downloadOrderitemsFiles', $::form->{type}, $::form->{id} ],
disabled => !$self->order->id ? t8('This object has not been saved yet.') : undef,
only_if => $::instance_conf->get_doc_storage,
],
7d020076 Moritz Bunkus
], # end of combobox "Export"

action => [
t8('Delete'),
call => [ 'kivi.Order.delete_order' ],
confirm => $::locale->text('Do you really want to delete this object?'),
disabled => !$self->order->id ? t8('This object has not been saved yet.') : undef,
only_if => $deletion_allowed,
],
);
}
099fc63b Bernd Bleßmann
}

1d242038 Bernd Bleßmann
sub generate_pdf {
aa36021a Bernd Bleßmann
my ($order, $pdf_ref, $params) = @_;

b1b3cdeb Bernd Bleßmann
my @errors = ();

aa36021a Bernd Bleßmann
my $print_form = Form->new('');
b1b3cdeb Bernd Bleßmann
$print_form->{type} = $order->type;
$print_form->{formname} = $params->{formname} || $order->type;
$print_form->{format} = $params->{format} || 'pdf';
$print_form->{media} = $params->{media} || 'file';
$print_form->{groupitems} = $params->{groupitems};
$print_form->{media} = 'file' if $print_form->{media} eq 'screen';
aa36021a Bernd Bleßmann
9ec05722 Bernd Bleßmann
$order->language($params->{language});
aa36021a Bernd Bleßmann
$order->flatten_to_form($print_form, format_amounts => 1);

34540341 Bernd Bleßmann
my $template_ext;
my $template_type;
if ($print_form->{format} =~ /(opendocument|oasis)/i) {
$template_ext = 'odt';
$template_type = 'OpenDocument';
}

b1b3cdeb Bernd Bleßmann
# search for the template
my ($template_file, @template_files) = SL::Helper::CreatePDF->find_template(
name => $print_form->{formname},
34540341 Bernd Bleßmann
extension => $template_ext,
b1b3cdeb Bernd Bleßmann
email => $print_form->{media} eq 'email',
language => $params->{language},
printer_id => $print_form->{printer_id}, # todo
);

if (!defined $template_file) {
push @errors, $::locale->text('Cannot find matching template for this print request. Please contact your template maintainer. I tried these: #1.', join ', ', map { "'$_'"} @template_files);
}

return @errors if scalar @errors;

aa36021a Bernd Bleßmann
$print_form->throw_on_error(sub {
eval {
$print_form->prepare_for_printing;

$$pdf_ref = SL::Helper::CreatePDF->create_pdf(
34540341 Bernd Bleßmann
format => $print_form->{format},
template_type => $template_type,
template => $template_file,
variables => $print_form,
aa36021a Bernd Bleßmann
variable_content_types => {
longdescription => 'html',
partnotes => 'html',
notes => 'html',
},
);
1;
} || push @errors, ref($EVAL_ERROR) eq 'SL::X::FormError' ? $EVAL_ERROR->getMessage : $EVAL_ERROR;
});

return @errors;
}

1d242038 Bernd Bleßmann
sub get_files_for_email_dialog {
d83928f0 Bernd Bleßmann
my ($self) = @_;

my %files = map { ($_ => []) } qw(versions files vc_files part_files);

return %files if !$::instance_conf->get_doc_storage;

if ($self->order->id) {
$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') ];
}

my @parts =
uniq_by { $_->{id} }
map {
+{ id => $_->part->id,
partnumber => $_->part->partnumber }
} @{$self->order->items_sorted};

foreach my $part (@parts) {
my @pfiles = SL::File->get_all(object_id => $part->{id}, object_type => 'part');
push @{ $files{part_files} }, map { +{ %{ $_ }, partnumber => $part->{partnumber} } } @pfiles;
}

foreach my $key (keys %files) {
$files{$key} = [ sort_by { lc $_->{db_file}->{file_name} } @{ $files{$key} } ];
}

return %files;
}

1d242038 Bernd Bleßmann
sub make_periodic_invoices_config_from_yaml {
5c859d64 Bernd Bleßmann
my ($yaml_config) = @_;

return if !$yaml_config;
my $attr = YAML::Load($yaml_config);
return if 'HASH' ne ref $attr;
return SL::DB::PeriodicInvoicesConfig->new(%$attr);
}


1d242038 Bernd Bleßmann
sub get_periodic_invoices_status {
5c859d64 Bernd Bleßmann
my ($self, $config) = @_;

1d242038 Bernd Bleßmann
return if $self->type ne sales_order_type();
5c859d64 Bernd Bleßmann
return t8('not configured') if !$config;

my $active = ('HASH' eq ref $config) ? $config->{active}
: ('SL::DB::PeriodicInvoicesConfig' eq ref $config) ? $config->active
: die "Cannot get status of periodic invoices config";

return $active ? t8('active') : t8('inactive');
}

1d242038 Bernd Bleßmann
sub get_title_for {
b6185e74 Bernd Bleßmann
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));
1d242038 Bernd Bleßmann
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")
b6185e74 Bernd Bleßmann
: '';
}

1d242038 Bernd Bleßmann
sub sales_order_type {
099fc63b Bernd Bleßmann
'sales_order';
}

1d242038 Bernd Bleßmann
sub purchase_order_type {
099fc63b Bernd Bleßmann
'purchase_order';
}

1d242038 Bernd Bleßmann
sub sales_quotation_type {
8e77bd29 Bernd Bleßmann
'sales_quotation';
}

1d242038 Bernd Bleßmann
sub request_quotation_type {
8e77bd29 Bernd Bleßmann
'request_quotation';
}

1d242038 Bernd Bleßmann
sub nr_key {
return $_[0]->type eq sales_order_type() ? 'ordnumber'
: $_[0]->type eq purchase_order_type() ? 'ordnumber'
: $_[0]->type eq sales_quotation_type() ? 'quonumber'
: $_[0]->type eq request_quotation_type() ? 'quonumber'
2e9dddf1 Bernd Bleßmann
: '';
}

099fc63b Bernd Bleßmann
1;
683dc060 Bernd Bleßmann
__END__

=encoding utf-8

=head1 NAME

SL::Controller::Order - controller for orders

5ef1fa84 Bernd Bleßmann
=head1 SYNOPSIS

This is a new form to enter orders, completely rewritten with the use
of controller and java script techniques.

The aim is to provide the user a better expirience and a faster flow
of work. Also the code should be more readable, more reliable and
better to maintain.

9a128e8b Sven Schöling
=head2 Key Features
5ef1fa84 Bernd Bleßmann
9a128e8b Sven Schöling
=over 4
5ef1fa84 Bernd Bleßmann
=item *
9a128e8b Sven Schöling
5ef1fa84 Bernd Bleßmann
One input row, so that input happens every time at the same place.

=item *
9a128e8b Sven Schöling
5ef1fa84 Bernd Bleßmann
Use of pickers where possible.

=item *
9a128e8b Sven Schöling
5ef1fa84 Bernd Bleßmann
Possibility to enter more than one item at once.

=item *
9a128e8b Sven Schöling
5ef1fa84 Bernd Bleßmann
Save order only on "save" (and "save and delivery order"-workflow). No
9a128e8b Sven Schöling
hidden save on "print" or "email".
5ef1fa84 Bernd Bleßmann
=item *
9a128e8b Sven Schöling
5ef1fa84 Bernd Bleßmann
Item list in a scrollable area, so that the workflow buttons stay at
the bottom.

=item *
9a128e8b Sven Schöling
5ef1fa84 Bernd Bleßmann
Reordering item rows with drag and drop is possible. Sorting item rows is
possible (by partnumber, description, qty, sellprice and discount for now).

=item *
9a128e8b Sven Schöling
No C<update> is necessary. All entries and calculations are managed
with ajax-calls and the page does only reload on C<save>.
5ef1fa84 Bernd Bleßmann
=item *
9a128e8b Sven Schöling
5ef1fa84 Bernd Bleßmann
User can see changes immediately, because of the use of java script
and ajax.

=back

=head1 CODE

9a128e8b Sven Schöling
=head2 Layout
5ef1fa84 Bernd Bleßmann
9a128e8b Sven Schöling
=over 4
5ef1fa84 Bernd Bleßmann
9a128e8b Sven Schöling
=item * C<SL/Controller/Order.pm>
5ef1fa84 Bernd Bleßmann
9a128e8b Sven Schöling
the controller
5ef1fa84 Bernd Bleßmann
9a128e8b Sven Schöling
=item * C<template/webpages/order/form.html>
5ef1fa84 Bernd Bleßmann
9a128e8b Sven Schöling
main form
5ef1fa84 Bernd Bleßmann
9a128e8b Sven Schöling
=item * C<template/webpages/order/tabs/basic_data.html>
5ef1fa84 Bernd Bleßmann
9a128e8b Sven Schöling
Main tab for basic_data.
5ef1fa84 Bernd Bleßmann
9a128e8b Sven Schöling
This is the only tab here for now. "linked records" and "webdav" tabs are
reused from generic code.
5ef1fa84 Bernd Bleßmann
9a128e8b Sven Schöling
=over 4
5ef1fa84 Bernd Bleßmann
9474d0ec Bernd Bleßmann
=item * C<template/webpages/order/tabs/_business_info_row.html>

For displaying information on business type

9a128e8b Sven Schöling
=item * C<template/webpages/order/tabs/_item_input.html>
5ef1fa84 Bernd Bleßmann
9a128e8b Sven Schöling
The input line for items
5ef1fa84 Bernd Bleßmann
9a128e8b Sven Schöling
=item * C<template/webpages/order/tabs/_row.html>
5ef1fa84 Bernd Bleßmann
9a128e8b Sven Schöling
One row for already entered items
5ef1fa84 Bernd Bleßmann
9a128e8b Sven Schöling
=item * C<template/webpages/order/tabs/_tax_row.html>
5ef1fa84 Bernd Bleßmann
9a128e8b Sven Schöling
Displaying tax information
5ef1fa84 Bernd Bleßmann
9a128e8b Sven Schöling
=item * C<template/webpages/order/tabs/_multi_items_dialog.html>
5ef1fa84 Bernd Bleßmann
9a128e8b Sven Schöling
Dialog for entering more than one item at once
683dc060 Bernd Bleßmann
9a128e8b Sven Schöling
=item * C<template/webpages/order/tabs/_multi_items_result.html>
683dc060 Bernd Bleßmann
9a128e8b Sven Schöling
Results for the filter in the multi items dialog
683dc060 Bernd Bleßmann
9a128e8b Sven Schöling
=item * C<template/webpages/order/tabs/_price_sources_dialog.html>
683dc060 Bernd Bleßmann
9a128e8b Sven Schöling
Dialog for selecting price and discount sources
683dc060 Bernd Bleßmann
9a128e8b Sven Schöling
=back
683dc060 Bernd Bleßmann
44fb4fe8 Sven Schöling
=item * C<js/kivi.Order.js>
683dc060 Bernd Bleßmann
44fb4fe8 Sven Schöling
java script functions
683dc060 Bernd Bleßmann
9a128e8b Sven Schöling
=back
683dc060 Bernd Bleßmann
9a128e8b Sven Schöling
=head1 TODO
683dc060 Bernd Bleßmann
9a128e8b Sven Schöling
=over 4
683dc060 Bernd Bleßmann
9a128e8b Sven Schöling
=item * testing
683dc060 Bernd Bleßmann
9a128e8b Sven Schöling
=item * currency
683dc060 Bernd Bleßmann
9a128e8b Sven Schöling
=item * credit limit
683dc060 Bernd Bleßmann
07dd84c0 Bernd Bleßmann
=item * more workflows (save as new, quotation, purchase order)
683dc060 Bernd Bleßmann
9a128e8b Sven Schöling
=item * price sources: little symbols showing better price / better discount
683dc060 Bernd Bleßmann
da821ed2 Bernd Bleßmann
=item * select units in input row?

9a128e8b Sven Schöling
=item * custom shipto address
683dc060 Bernd Bleßmann
69966b4c Bernd Bleßmann
=item * check for direct delivery (workflow sales order -> purchase order)

9a128e8b Sven Schöling
=item * language / part translations
683dc060 Bernd Bleßmann
9a128e8b Sven Schöling
=item * access rights
683dc060 Bernd Bleßmann
9a128e8b Sven Schöling
=item * display weights
683dc060 Bernd Bleßmann
44fb4fe8 Sven Schöling
=item * history

=item * mtime check

45981e33 Jan Büren
=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)

9a128e8b Sven Schöling
=back
683dc060 Bernd Bleßmann
9a128e8b Sven Schöling
=head1 KNOWN BUGS AND CAVEATS
683dc060 Bernd Bleßmann
9a128e8b Sven Schöling
=over 4
683dc060 Bernd Bleßmann
9a128e8b Sven Schöling
=item *
683dc060 Bernd Bleßmann
9a128e8b Sven Schöling
Customer discount is not displayed as a valid discount in price source popup
(this might be a bug in price sources)
683dc060 Bernd Bleßmann
fdebfd5d Bernd Bleßmann
(I cannot reproduce this (Bernd))

9a128e8b Sven Schöling
=item *

f0359773 Bernd Bleßmann
No indication that <shift>-up/down expands/collapses second row.
683dc060 Bernd Bleßmann
=item *

9a128e8b Sven Schöling
Inline creation of parts is not currently supported

=item *
683dc060 Bernd Bleßmann
9a128e8b Sven Schöling
Table header is not sticky in the scrolling area.
683dc060 Bernd Bleßmann
=item *

9a128e8b Sven Schöling
Sorting does not include C<position>, neither does reordering.

1e9c6bbd Bernd Bleßmann
This behavior was implemented intentionally. But we can discuss, which behavior
should be implemented.

9a128e8b Sven Schöling
=item *
683dc060 Bernd Bleßmann
1e9c6bbd Bernd Bleßmann
C<show_multi_items_dialog> does not use the currently inserted string for
7e8765c6 Sven Schöling
filtering.

d83928f0 Bernd Bleßmann
=item *

The language selected in print or email dialog is not saved when the order is saved.

683dc060 Bernd Bleßmann
=back

da821ed2 Bernd Bleßmann
=head1 To discuss / Nice to have

=over 4

=item *

How to expand/collapse second row. Now it can be done clicking the icon or
<shift>-up/down.

=item *

Possibility to change longdescription in input row?

=item *

Possibility to select PriceSources in input row?

=item *

This controller uses a (changed) copy of the template for the PriceSource
dialog. Maybe there could be used one code source.

=item *

Rounding-differences between this controller (PriceTaxCalculator) and the old
form. This is not only a problem here, but also in all parts using the PTC.
There exists a ticket and a patch. This patch should be testet.

=item *

An indicator, if the actual inputs are saved (like in an
editor or on text processing application).

=item *

A warning when leaving the page without saveing unchanged inputs.

07dd84c0 Bernd Bleßmann
=item *

Workflows for delivery order and invoice are in the menu "Save", because the
order is saved before opening the new document form. Nevertheless perhaps these
workflow buttons should be put under "Workflows".


da821ed2 Bernd Bleßmann
=back

683dc060 Bernd Bleßmann
=head1 AUTHOR

Bernd Bleßmann E<lt>bernd@kivitendo-premium.deE<gt>

=cut