Projekt

Allgemein

Profil

Herunterladen (47,5 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);
0aa885f4 Sven Schöling
use SL::Presenter::Tag qw(select_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;
099fc63b Bernd Bleßmann
use SL::DB::Order;
use SL::DB::Default;
use SL::DB::Unit;
91abaf6c Bernd Bleßmann
use SL::DB::Part;
b1b3cdeb Bernd Bleßmann
use SL::DB::Printer;
use SL::DB::Language;
099fc63b Bernd Bleßmann
aa36021a Bernd Bleßmann
use SL::Helper::CreatePDF qw(:all);
b1b3cdeb Bernd Bleßmann
use SL::Helper::PrintOptions;
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);
f275cac9 Bernd Bleßmann
use List::MoreUtils qw(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
__PACKAGE__->run_before('_check_auth');

__PACKAGE__->run_before('_recalc',
07dd84c0 Bernd Bleßmann
only => [ qw(save save_and_delivery_order save_and_invoice print create_pdf send_email) ]);
099fc63b Bernd Bleßmann
__PACKAGE__->run_before('_get_unalterable_data',
07dd84c0 Bernd Bleßmann
only => [ qw(save save_and_delivery_order save_and_invoice print create_pdf 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());
3df0bf06 Bernd Bleßmann
$self->order->reqdate(DateTime->today_local->next_workday) if !$self->order->reqdate;
099fc63b Bernd Bleßmann
$self->_pre_render();
$self->render(
'order/form',
title => $self->type eq _sales_order_type() ? $::locale->text('Add Sales Order')
: $self->type eq _purchase_order_type() ? $::locale->text('Add Purchase Order')
: '',
%{$self->{template_args}}
);
}

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

5dd5e97b Bernd Bleßmann
$self->_load_order;
$self->_recalc();
099fc63b Bernd Bleßmann
$self->_pre_render();
$self->render(
'order/form',
title => $self->type eq _sales_order_type() ? $::locale->text('Edit Sales Order')
: $self->type eq _purchase_order_type() ? $::locale->text('Edit Purchase Order')
: '',
%{$self->{template_args}}
);
}

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

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

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

flash_later('info', $::locale->text('The order has been deleted'));
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) = @_;

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

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

flash_later('info', $::locale->text('The order has been saved'));
my @redirect_params = (
action => 'edit',
type => $self->type,
id => $self->order->id,
);

$self->redirect_to(@redirect_params);
}

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

# only pdf by now
if (none { $format eq $_ } qw(pdf)) {
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
aa36021a Bernd Bleßmann
my $form = Form->new;
$form->{ordnumber} = $self->order->ordnumber;
$form->{type} = $self->type;
b1b3cdeb Bernd Bleßmann
$form->{format} = $format;
$form->{formname} = $formname;
$form->{language} = '_' . $language->template_code if $language;
my $pdf_filename = $form->generate_attachment_filename();

my $pdf;
my @errors = _create_pdf($self->order, \$pdf, { format => $format,
formname => $formname,
language => $language,
groupitems => $groupitems });
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);
$::auth->set_session_value("Order::create_pdf-${key}" => $sfile->file_name);

$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
if ($self->order->ordnumber && $::instance_conf->get_webdav_documents) {
my $webdav = SL::Webdav->new(
type => $self->type,
number => $self->order->ordnumber,
);
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', $@));
}
}
1ce68041 Martin Helmling
if ($self->order->ordnumber && $::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};
my $tmp_filename = $::auth->get_session_value("Order::create_pdf-${key}");
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;
$form->{ordnumber} = $self->order->ordnumber;
$form->{formname} = $self->type;
$form->{type} = $self->type;
$form->{language} = 'de';
$form->{format} = 'pdf';

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()';

my %files = $self->_get_files_for_email_dialog();
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
d83928f0 Bernd Bleßmann
$::form->{media} = 'email';

if (($::form->{attachment_policy} // '') eq 'normal') {
my $language;
$language = SL::DB::Language->new(id => $::form->{print_options}->{language_id})->load if $::form->{print_options}->{language_id};

my $pdf;
my @errors = _create_pdf($self->order, \$pdf, {media => $::form->{media},
format => $::form->{print_options}->{format},
formname => $::form->{print_options}->{formname},
language => $language,
groupitems => $::form->{print_options}->{groupitems}});
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);
}

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) = @_;

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

if (scalar @{ $errors }) {
$self->js->flash('error', $_) foreach @{ $errors };
return $self->js->render();
}
flash_later('info', $::locale->text('The order has been saved'));

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) = @_;

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

if (scalar @{ $errors }) {
$self->js->flash('error', $_) foreach @{ $errors };
return $self->js->render();
}
flash_later('info', $::locale->text('The order has been saved'));

my @redirect_params = (
controller => 'oe.pl',
action => 'oe_invoice_from_order',
id => $self->order->id,
);

$self->redirect_to(@redirect_params);
}

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) = @_;

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');
}

9af3ce1c Bernd Bleßmann
$self->order->taxzone_id($self->order->$cv_method->taxzone_id);

if ($self->order->is_sales) {
$self->order->taxincluded(defined($self->order->$cv_method->taxincluded_checked)
? $self->order->$cv_method->taxincluded_checked
: $::myconfig{taxincluded_checked});
fdebfd5d Bernd Bleßmann
$self->js->val('#order_salesman_id', $self->order->$cv_method->salesman_id);
9af3ce1c Bernd Bleßmann
}

b47574cb Bernd Bleßmann
$self->order->payment_id($self->order->$cv_method->payment_id);
$self->order->delivery_term_id($self->order->$cv_method->delivery_term_id);

9af3ce1c Bernd Bleßmann
$self->_recalc();

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)
->val( '#order_taxzone_id', $self->order->taxzone_id)
->val( '#order_taxincluded', $self->order->taxincluded)
->val( '#order_payment_id', $self->order->payment_id)
->val( '#order_delivery_term_id', $self->order->delivery_term_id)
->val( '#order_intnotes', $self->order->$cv_method->notes)
->focus( '#order_' . $self->cv . '_id');

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

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

$self->_recalc();

$self->js
0935b012 Bernd Bleßmann
->run('kivi.Order.update_sellprice', $::form->{item_id}, $item->sellprice_as_number);
5737ce39 Bernd Bleßmann
$self->_js_redisplay_line_values;
2d50590b Bernd Bleßmann
$self->_js_redisplay_amounts_and_taxes;
$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};

5dd5e97b 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
91abaf6c 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,
};
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 );
$self->_recalc();
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
91abaf6c Bernd Bleßmann
$self->_js_redisplay_amounts_and_taxes;
$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 {
require SL::DB::PartsGroup;
$_[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) {
4b518cdb Geoffrey Richardson
my $item = _new_item($self->order, $attr);
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,
};
my $item = _new_item($self->order, $attr);

# 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
$self->_recalc();

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

$self->_js_redisplay_amounts_and_taxes;
$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) = @_;

$self->_recalc();

5737ce39 Bernd Bleßmann
$self->_js_redisplay_line_values;
099fc63b Bernd Bleßmann
$self->_js_redisplay_amounts_and_taxes;
$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) = @_;

5737ce39 Bernd Bleßmann
$self->_recalc() if $self->order->is_sales; # for margin calculation

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];

$self->_js_load_second_row($item, $item_id, 0);
}

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();
}

sub _js_load_second_row {
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
}

5737ce39 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
}

sub _js_redisplay_amounts_and_taxes {
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 {
[ _sales_order_type(), _purchase_order_type() ];
}

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) = @_;

my $cv = $self->type eq _sales_order_type() ? 'customer'
: $self->type eq _purchase_order_type() ? 'vendor'
: die "Not a valid type for order";

return $cv;
}

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

sub init_order {
5dd5e97b 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;
}

099fc63b Bernd Bleßmann
sub _check_auth {
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
);
}

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

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

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

$self->order(SL::DB::Manager::Order->find_by(id => $::form->{id}));
}

5ef1fa84 Bernd Bleßmann
# load or create a new order object
#
# And assign changes from the for to this object.
# 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.
# Then create/update items from form (via _make_item) and add them.
099fc63b Bernd Bleßmann
sub _make_order {
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};
$order ||= SL::DB::Order->new(orderitems => []);

5dd5e97b Bernd Bleßmann
my $form_orderitems = delete $::form->{order}->{orderitems};
099fc63b Bernd Bleßmann
$order->assign_attributes(%{$::form->{order}});

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}) {
my $item = _make_item($order, $form_attr);
$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.
91abaf6c Bernd Bleßmann
sub _make_item {
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);
e0a47f33 Bernd Bleßmann
$item->longdescription($item->part->notes) if $is_new && !defined $attr->{longdescription};
0b20f337 Bernd Bleßmann
$item->project_id($record->globalproject_id) if $is_new && !defined $attr->{project_id};
f2461e14 Bernd Bleßmann
$item->lastcost($item->part->lastcost) 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
5dd5e97b Bernd Bleßmann
sub _new_item {
my ($record, $attr) = @_;

91abaf6c Bernd Bleßmann
my $item = SL::DB::OrderItem->new;
$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);
} elsif ($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;
if ($item->discount) {
$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;
f2461e14 Bernd Bleßmann
$new_attr{lastcost} = $part->lastcost;
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;
}

5ef1fa84 Bernd Bleßmann
# recalculate prices and taxes
#
9a128e8b Sven Schöling
# Using the PriceTaxCalculator. Store linetotals in the item objects.
099fc63b Bernd Bleßmann
sub _recalc {
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.
099fc63b Bernd Bleßmann
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;
}
}

5ef1fa84 Bernd Bleßmann
# delete the order
#
# And remove related files in the spool directory
da55cfa0 Bernd Bleßmann
sub _delete {
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.
099fc63b Bernd Bleßmann
sub _save {
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);
099fc63b Bernd Bleßmann
}) || push(@{$errors}, $db->error);

return $errors;
}


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

b47574cb Bernd Bleßmann
$self->{all_taxzones} = SL::DB::Manager::TaxZone->get_all_sorted();
3914f665 Bernd Bleßmann
$self->{all_departments} = SL::DB::Manager::Department->get_all_sorted();
b47574cb Bernd Bleßmann
$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_projects} = SL::DB::Manager::Project->get_all(where => [ or => [ id => $self->order->globalproject_id,
active => 1 ] ],
sort_by => 'projectnumber');
77807bf5 Geoffrey Richardson
$self->{all_payment_terms} = SL::DB::Manager::PaymentTerm->get_all_sorted(where => [ or => [ id => $self->order->payment_id,
obsolete => 0 ] ]);

b47574cb Bernd Bleßmann
$self->{all_delivery_terms} = SL::DB::Manager::DeliveryTerm->get_all_sorted();
099fc63b Bernd Bleßmann
$self->{current_employee_id} = SL::DB::Manager::Employee->current->id;
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,
no_opendocument => 1,
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));
}

6550f507 Bernd Bleßmann
if ($self->order->ordnumber && $::instance_conf->get_webdav) {
my $webdav = SL::Webdav->new(
type => $self->type,
number => $self->order->ordnumber,
);
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;
}

1ce68041 Martin Helmling
$::request->{layout}->use_javascript("${_}.js") for qw(kivi.SalesPurchase kivi.Order kivi.File ckeditor/ckeditor ckeditor/adapters/jquery);
7d020076 Moritz Bunkus
$self->_setup_edit_action_bar;
}

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

my $deletion_allowed = (($self->cv eq 'customer') && $::instance_conf->get_sales_order_show_delete)
|| (($self->cv eq 'vendor') && $::instance_conf->get_purchase_order_show_delete);

for my $bar ($::request->layout->get('actionbar')) {
$bar->add(
combobox => [
action => [
t8('Save'),
call => [ 'kivi.Order.save', $::instance_conf->get_order_warn_duplicate_parts ],
accesskey => 'enter',
],
action => [
t8('Save and Delivery Order'),
call => [ 'kivi.Order.save_and_delivery_order', $::instance_conf->get_order_warn_duplicate_parts ],
],
07dd84c0 Bernd Bleßmann
action => [
t8('Save and Invoice'),
call => [ 'kivi.Order.save_and_invoice', $::instance_conf->get_order_warn_duplicate_parts ],
],
7d020076 Moritz Bunkus
], # end of combobox "Save"

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
}

aa36021a Bernd Bleßmann
sub _create_pdf {
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);

b1b3cdeb Bernd Bleßmann
# search for the template
my ($template_file, @template_files) = SL::Helper::CreatePDF->find_template(
name => $print_form->{formname},
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(
b1b3cdeb Bernd Bleßmann
template => $template_file,
aa36021a Bernd Bleßmann
variables => $print_form,
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;
}

d83928f0 Bernd Bleßmann
sub _get_files_for_email_dialog {
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;
}

099fc63b Bernd Bleßmann
sub _sales_order_type {
'sales_order';
}

sub _purchase_order_type {
'purchase_order';
}

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
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 * customer/vendor details ('D'-button)
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
9a128e8b Sven Schöling
=item * periodic invoices
683dc060 Bernd Bleßmann
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