use SL::Locale::String;
use SL::SessionFile;
use SL::SessionFile::Random;
+use SL::Controller::CsvImport::AdditionalBillingAddress;
use SL::Controller::CsvImport::Contact;
use SL::Controller::CsvImport::CustomerVendor;
use SL::Controller::CsvImport::Part;
sub check_type {
my ($self) = @_;
- die "Invalid CSV import type" if none { $_ eq $::form->{profile}->{type} } qw(parts inventories customers_vendors addresses contacts projects orders delivery_orders bank_transactions ar_transactions);
+ die "Invalid CSV import type" if none { $_ eq $::form->{profile}->{type} } qw(parts inventories customers_vendors billing_addresses addresses contacts projects orders delivery_orders bank_transactions ar_transactions);
$self->type($::form->{profile}->{type});
}
}
my $title = $self->type eq 'customers_vendors' ? $::locale->text('CSV import: customers and vendors')
+ : $self->type eq 'billing_addresses' ? $::locale->text('CSV import: additional billing addresses')
: $self->type eq 'addresses' ? $::locale->text('CSV import: shipping addresses')
: $self->type eq 'contacts' ? $::locale->text('CSV import: contacts')
: $self->type eq 'parts' ? $::locale->text('CSV import: parts and services')
return $self->{type} eq 'customers_vendors' ? SL::Controller::CsvImport::CustomerVendor->new(@args)
: $self->{type} eq 'contacts' ? SL::Controller::CsvImport::Contact->new(@args)
+ : $self->{type} eq 'billing_addresses' ? SL::Controller::CsvImport::AdditionalBillingAddress->new(@args)
: $self->{type} eq 'addresses' ? SL::Controller::CsvImport::Shipto->new(@args)
: $self->{type} eq 'parts' ? SL::Controller::CsvImport::Part->new(@args)
: $self->{type} eq 'inventories' ? SL::Controller::CsvImport::Inventory->new(@args)
--- /dev/null
+package SL::Controller::CsvImport::AdditionalBillingAddress;
+
+use strict;
+
+use SL::Helper::Csv;
+
+use parent qw(SL::Controller::CsvImport::Base);
+
+use Rose::Object::MakeMethods::Generic
+(
+ scalar => [ qw(table) ],
+);
+
+sub set_profile_defaults {
+};
+
+sub init_class {
+ my ($self) = @_;
+ $self->class('SL::DB::AdditionalBillingAddress');
+}
+
+sub _hash_object {
+ my ($o) = @_;
+ return join('--', map({ s/[\s,\.\-]//g; $_ } ($o->name, $o->street)));
+}
+
+sub check_objects {
+ my ($self) = @_;
+
+ $self->controller->track_progress(phase => 'building data', progress => 0);
+
+ my %existing_by_id_name_street = map { (_hash_object($_) => $_) } @{ $self->existing_objects };
+ my $methods = $self->controller->headers->{methods};
+
+ my $i = 0;
+ my $num_data = scalar @{ $self->controller->data };
+ foreach my $entry (@{ $self->controller->data }) {
+ $self->controller->track_progress(progress => $i/$num_data * 100) if $i % 100 == 0;
+
+ $self->check_vc($entry, 'customer_id');
+
+ next if @{ $entry->{errors} };
+
+ my $object = $entry->{object};
+ my $idx = _hash_object($object);
+ my $existing = $existing_by_id_name_street{$idx};
+
+ if (!$existing) {
+ $existing_by_id_name_street{$idx} = $object;
+ } else {
+ $entry->{object_to_save} = $existing;
+
+ $existing->$_( $object->$_ ) for @{ $methods };
+
+ push @{ $entry->{information} }, $::locale->text('Updating existing entry in database');
+ }
+
+ } continue {
+ $i++;
+ }
+
+ $self->add_info_columns({ header => $::locale->text('Customer/Vendor'), method => 'vc_name' });
+}
+
+sub setup_displayable_columns {
+ my ($self) = @_;
+
+ $self->SUPER::setup_displayable_columns;
+
+ $self->add_displayable_columns(
+ { name => 'default_address', description => $::locale->text('Default address flag') },
+ { name => 'name', description => $::locale->text('Name') },
+ { name => 'department_1', description => $::locale->text('Department 1') },
+ { name => 'department_2', description => $::locale->text('Department 2') },
+ { name => 'street', description => $::locale->text('Street') },
+ { name => 'zipcode', description => $::locale->text('Zipcode') },
+ { name => 'city', description => $::locale->text('City') },
+ { name => 'country', description => $::locale->text('Country') },
+ { name => 'contact', description => $::locale->text('Contact') },
+ { name => 'email', description => $::locale->text('E-mail') },
+ { name => 'fax', description => $::locale->text('Fax') },
+ { name => 'gln', description => $::locale->text('GLN') },
+ { name => 'phone', description => $::locale->text('Phone') },
+ { name => 'customer_id', description => $::locale->text('Customer') },
+ { name => 'customer', description => $::locale->text('Customer (name)') },
+ { name => 'customernumber', description => $::locale->text('Customer Number') },
+ );
+}
+
+1;
my $size = $params{size} // 64;
my $sfile = SL::SessionFile::Random->new();
- my $command = 'pdftoppm -singlefile -scale-to ' . $size . ' -png' . ' ' . $filename . ' ' . $sfile->file_name;
+ unless (-f $filename) {
+ $::lxdebug->message(LXDebug::WARN(), "_convert_pdf_to_png failed, no file found: $filename");
+ return;
+ }
+ # quotemeta for storno case "storno\ zu\ 1020" *nix only
+ my $command = 'pdftoppm -singlefile -scale-to ' . $size . ' -png' . ' ' . quotemeta($filename) . ' ' . $sfile->file_name;
if (system($command) == -1) {
$::lxdebug->message(LXDebug::WARN(), "SL::File::_convert_pdf_to_png: system call failed: " . $ERRNO);
}
if ($CHILD_ERROR) {
$::lxdebug->message(LXDebug::WARN(), "SL::File::_convert_pdf_to_png: pdftoppm failed with error code: " . ($CHILD_ERROR >> 8));
+ $::lxdebug->message(LXDebug::WARN(), "SL::File::_convert_pdf_to_png: File: $filename");
return;
}
use SL::Util qw(trim);
use SL::YAML;
use SL::DB::AdditionalBillingAddress;
+use SL::DB::AuthUser;
use SL::DB::History;
use SL::DB::Order;
use SL::DB::Default;
my $groupitems = $::form->{print_options}->{groupitems};
my $printer_id = $::form->{print_options}->{printer_id};
- # only pdf and opendocument by now
- if (none { $format eq $_ } qw(pdf opendocument opendocument_pdf)) {
+ # only PDF, OpenDocument & HTML for now
+ if (none { $format eq $_ } qw(pdf opendocument opendocument_pdf html)) {
return $self->js->flash('error', t8('Format \'#1\' is not supported yet/anymore.', $format))->render;
}
$form->{format} = $format;
$form->{formname} = $formname;
$form->{language} = '_' . $self->order->language->template_code if $self->order->language;
- my $pdf_filename = $form->generate_attachment_filename();
+ my $doc_filename = $form->generate_attachment_filename();
- my $pdf;
- my @errors = $self->generate_pdf(\$pdf, { format => $format,
+ my $doc;
+ my @errors = $self->generate_doc(\$doc, { format => $format,
formname => $formname,
language => $self->order->language,
printer_id => $printer_id,
groupitems => $groupitems });
if (scalar @errors) {
- return $self->js->flash('error', t8('Conversion to PDF failed: #1', $errors[0]))->render;
+ return $self->js->flash('error', t8('Generating the document failed: #1', $errors[0]))->render;
}
if ($media eq 'screen') {
# screen/download
- $self->js->flash('info', t8('The PDF has been created'));
+ $self->js->flash('info', t8('The document has been created.'));
$self->send_file(
- \$pdf,
- type => SL::MIME->mime_type_from_ext($pdf_filename),
- name => $pdf_filename,
+ \$doc,
+ type => SL::MIME->mime_type_from_ext($doc_filename),
+ name => $doc_filename,
js_no_render => 1,
);
my $printer_id = $::form->{print_options}->{printer_id};
SL::DB::Printer->new(id => $printer_id)->load->print_document(
copies => $copies,
- content => $pdf,
+ content => $doc,
);
- $self->js->flash('info', t8('The PDF has been printed'));
+ $self->js->flash('info', t8('The document has been printed.'));
}
- my @warnings = $self->store_pdf_to_webdav_and_filemanagement($pdf, $pdf_filename);
+ my @warnings = $self->store_doc_to_webdav_and_filemanagement($doc, $doc_filename);
if (scalar @warnings) {
$self->js->flash('warning', $_) for @warnings;
}
my $pdf_filename = $form->generate_attachment_filename();
my $pdf;
- my @errors = $self->generate_pdf(\$pdf, { format => $format,
+ my @errors = $self->generate_doc(\$pdf, { format => $format,
formname => $formname,
language => $self->order->language,
});
$email_form->{js_send_function} = 'kivi.Order.send_email()';
my %files = $self->get_files_for_email_dialog();
- $self->{all_employees} = SL::DB::Manager::Employee->get_all(query => [ deleted => 0 ]);
+
+ my @employees_with_email = grep {
+ my $user = SL::DB::Manager::AuthUser->find_by(login => $_->login);
+ $user && !!trim($user->get_config_value('email'));
+ } @{ SL::DB::Manager::Employee->get_all_sorted(query => [ deleted => 0 ]) };
+
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',
- ALL_EMPLOYEES => $self->{all_employees},
+ email_form => $email_form,
+ show_bcc => $::auth->assert('email_bcc', 'may fail'),
+ FILES => \%files,
+ is_customer => $self->cv eq 'customer',
+ ALL_EMPLOYEES => \@employees_with_email,
);
$self->js
$::form->{media} = 'email';
if (($::form->{attachment_policy} // '') !~ m{^(?:old_file|no_file)$}) {
- my $pdf;
- my @errors = $self->generate_pdf(\$pdf, {media => $::form->{media},
+ my $doc;
+ my @errors = $self->generate_doc(\$doc, {media => $::form->{media},
format => $::form->{print_options}->{format},
formname => $::form->{print_options}->{formname},
language => $self->order->language,
printer_id => $::form->{print_options}->{printer_id},
groupitems => $::form->{print_options}->{groupitems}});
if (scalar @errors) {
- return $self->js->flash('error', t8('Conversion to PDF failed: #1', $errors[0]))->render($self);
+ return $self->js->flash('error', t8('Generating the document failed: #1', $errors[0]))->render($self);
}
- my @warnings = $self->store_pdf_to_webdav_and_filemanagement($pdf, $::form->{attachment_filename});
+ my @warnings = $self->store_doc_to_webdav_and_filemanagement($doc, $::form->{attachment_filename});
if (scalar @warnings) {
flash_later('warning', $_) for @warnings;
}
my $sfile = SL::SessionFile::Random->new(mode => "w");
- $sfile->fh->print($pdf);
+ $sfile->fh->print($doc);
$sfile->fh->close;
$::form->{tmpfile} = $sfile->file_name;
}
$::form->{id} = $self->order->id; # this is used in SL::Mailer to create a linked record to the mail
- $::form->send_email(\%::myconfig, 'pdf');
+ $::form->send_email(\%::myconfig, $::form->{print_options}->{format});
# internal notes
my $intnotes = $self->order->intnotes;
no_queue => 1,
no_postscript => 1,
no_opendocument => 0,
- no_html => 1},
+ no_html => 0},
);
foreach my $item (@{$self->order->orderitems}) {
$self->get_item_cvpartnumber($_) for @{$self->order->items_sorted};
- $::request->{layout}->use_javascript("${_}.js") for qw(kivi.SalesPurchase kivi.Order kivi.File ckeditor/ckeditor ckeditor/adapters/jquery
- edit_periodic_invoices_config calculate_qty kivi.Validator follow_up show_history);
+ $::request->{layout}->use_javascript("${_}.js") for qw(kivi.Validator kivi.SalesPurchase kivi.Order kivi.File ckeditor/ckeditor ckeditor/adapters/jquery
+ edit_periodic_invoices_config calculate_qty follow_up show_history);
$self->setup_edit_action_bar;
}
}
}
-sub generate_pdf {
- my ($self, $pdf_ref, $params) = @_;
+sub generate_doc {
+ my ($self, $doc_ref, $params) = @_;
my $order = $self->order;
my @errors = ();
if ($print_form->{format} =~ /(opendocument|oasis)/i) {
$template_ext = 'odt';
$template_type = 'OpenDocument';
+ } elsif ($print_form->{format} =~ m{html}i) {
+ $template_ext = 'html';
+ $template_type = 'HTML';
}
# search for the template
eval {
$print_form->prepare_for_printing;
- $$pdf_ref = SL::Helper::CreatePDF->create_pdf(
+ $$doc_ref = SL::Helper::CreatePDF->create_pdf(
format => $print_form->{format},
template_type => $template_type,
template => $template_file,
)->save;
}
-sub store_pdf_to_webdav_and_filemanagement {
+sub store_doc_to_webdav_and_filemanagement {
my ($self, $content, $filename) = @_;
my $order = $self->order;
$webdav_file->store(data => \$content);
1;
} or do {
- push @errors, t8('Storing PDF to webdav folder failed: #1', $@);
+ push @errors, t8('Storing the document to the WebDAV folder failed: #1', $@);
};
}
if ($order->id && $::instance_conf->get_doc_storage) {
eval {
SL::File->save(object_id => $order->id,
object_type => $order->type,
- mime_type => 'application/pdf',
+ mime_type => SL::MIME->mime_type_from_ext($filename),
source => 'created',
file_type => 'document',
file_name => $filename,
file_contents => $content);
1;
} or do {
- push @errors, t8('Storing PDF in storage backend failed: #1', $@);
+ push @errors, t8('Storing the document in the storage backend failed: #1', $@);
};
}
sub _set_javascript {
my ($self) = @_;
- $::request->layout->use_javascript("${_}.js") for qw(kivi.Part kivi.File kivi.PriceRule ckeditor/ckeditor ckeditor/adapters/jquery kivi.ShopPart);
+ $::request->layout->use_javascript("${_}.js") for qw(kivi.Part kivi.File kivi.PriceRule ckeditor/ckeditor ckeditor/adapters/jquery kivi.ShopPart kivi.Validator);
$::request->layout->add_javascripts_inline("\$(function(){kivi.PriceRule.load_price_rules_for_part(@{[ $self->part->id ]})});") if $self->part->id;
}
t8('Save'),
call => [ 'kivi.Part.save' ],
disabled => !$may_edit ? t8('You do not have the permissions to access this function.') : undef,
+ checks => ['kivi.validate_form'],
],
action => [
t8('Use as new'),
# optional KOST1 - KOST2 ?
$department_name = $row->[36];
- $department = SL::DB::Manager::Department->get_first(description => { like => $department_name . '%' });
+ if ($department_name) {
+ $department = SL::DB::Manager::Department->get_first(description => { like => $department_name . '%' });
+ }
my $amount = $::form->parse_amount({ numberformat => '1000,00' }, $row->[0]);
__PACKAGE__->meta->initialize;
+__PACKAGE__->after_save('_after_save_ensure_only_one_marked_as_default_per_customer');
+
+sub _after_save_ensure_only_one_marked_as_default_per_customer {
+ my ($self) = @_;
+
+ if ($self->id && $self->customer_id && $self->default_address) {
+ SL::DB::Manager::AdditionalBillingAddress->update_all(
+ set => { default_address => 0 },
+ where => [
+ customer_id => $self->customer_id,
+ '!id' => $self->id,
+ ],
+ );
+ }
+
+ return 1;
+}
+
sub displayable_id {
my $self = shift;
my $text = join('; ', grep { $_ } (map({ $self->$_ } qw(name street)),
return $self->name || $self->login;
}
+sub auth_user {
+ my ($self) = @_;
+
+ die 'not an accessor' if scalar(@_) > 1;
+
+ require SL::DB::AuthUser;
+
+ return SL::DB::Manager::AuthUser->find_by(login => $self->login);
+}
+
1;
my ($self) = @_;
return 'ar_transaction' if !$self->invoice;
- return 'credit_note' if $self->type eq 'credit_note' && $self->amount < 0 && !$self->storno;
+ # stornoed credit_notes are still credit notes and not invoices
+ return 'credit_note' if $self->type eq 'credit_note' && $self->amount < 0;
return 'invoice_storno' if $self->type ne 'credit_note' && $self->amount < 0 && $self->storno;
return 'credit_note_storno' if $self->type eq 'credit_note' && $self->amount > 0 && $self->storno;
return 'invoice';
die "no dbfile" unless $params{dbfile};
$main::lxdebug->message(LXDebug->DEBUG2(), "version=" .$params{version});
my ($path, undef, undef) = $self->webdav_path($params{dbfile});
- die "no file found in backend" if !-f $path;
+ die "No file found in Backend: " . $path unless -f $path;
my @st = stat($path);
my $dt = DateTime->from_epoch(epoch => $st[9])->clone();
$main::lxdebug->message(LXDebug->DEBUG2(), "dt=" .$dt);
my ($self, %params) = @_;
die "no dbfile" unless $params{dbfile};
my ($path, undef, undef) = $self->webdav_path($params{dbfile});
- die "no file" if !-f $path;
+ die "No file found in Backend: " . $path unless -f $path;
return $path;
}
}
sub enabled {
- return 0 unless $::instance_conf->get_doc_webdav;
- return 1;
+ return $::instance_conf->get_doc_webdav;
}
#
}
$main::lxdebug->message(LXDebug->DEBUG2(), "file_name=" . $dbfile->file_name ." number=".$number);
- my @fileparts = split(/_/, $dbfile->file_name);
- my $number_ext = pop @fileparts;
- my ($maynumber, $ext) = split(/\./, $number_ext, 2);
- push @fileparts, $maynumber if $maynumber ne $number;
-
- my $basename = join('_', @fileparts);
-
my $path = File::Spec->catdir($self->get_rootdir, "webdav", $::auth->client->{id}, $type, $number);
if (!-d $path) {
File::Path::make_path($path, { chmod => 0770 });
}
- my $fname = $basename . '_' . $number . '_' . $dbfile->itime->strftime('%Y%m%d_%H%M%S');
+ # simply add the timestring before the last .
+ # fails for .tar.gz but the number extraction algorithm failed for all
+ # '123 Storno zu 456' cases and doubled the name like:
+ # Rechnung_123_Storno_zu_456_202113104 Storno zu 456_20211123_113023
+ # TODO extension should be part of the File Model (filetype)
+ my ($filename, $ext) = split(/\.([^\.]+)$/, $dbfile->file_name);
+ my $fname = $filename . '_' . $dbfile->itime->strftime('%Y%m%d_%H%M%S');
$fname .= '.' . $ext if $ext;
$main::lxdebug->message(LXDebug->DEBUG2(), "webdav path=" . $path . " filename=" . $fname);
qq|SELECT h.employee_id, h.itime::timestamp(0) AS itime, h.addition, h.what_done, emp.name, h.snumbers, h.trans_id AS id | .
qq|FROM history_erp h | .
qq|LEFT JOIN employee emp ON (emp.id = h.employee_id) | .
- qq|WHERE (trans_id = | . $trans_id . qq|) $restriction | .
+ qq|WHERE (trans_id = | . $dbh->quote($trans_id) . qq|) $restriction | .
$order;
my $sth = $dbh->prepare($query) || $self->dberror($query);
sub checkbox_tag {
my ($name, %attributes) = @_;
+ my %label_attributes = map { (substr($_, 6) => $attributes{$_}) } grep { m{^label_} } keys %attributes;
+ delete @attributes{grep { m{^label_} } keys %attributes};
+
_set_id_attribute(\%attributes, $name);
$attributes{value} = 1 unless defined $attributes{value};
my $code = '';
$code .= hidden_tag($name, 0, %attributes, id => $attributes{id} . '_hidden') if $for_submit;
$code .= html_tag('input', undef, %attributes, name => $name, type => 'checkbox');
- $code .= html_tag('label', $label, for => $attributes{id}) if $label;
+ $code .= html_tag('label', $label, for => $attributes{id}, %label_attributes) if $label;
$code .= javascript(qq|\$('#$attributes{id}').checkall('$checkall');|) if $checkall;
return $code;
sub radio_button_tag {
my ($name, %attributes) = @_;
+ my %label_attributes = map { (substr($_, 6) => $attributes{$_}) } grep { m{^label_} } keys %attributes;
+ delete @attributes{grep { m{^label_} } keys %attributes};
+
$attributes{value} = 1 unless exists $attributes{value};
_set_id_attribute(\%attributes, $name, 1);
}
my $code = html_tag('input', undef, %attributes, name => $name, type => 'radio');
- $code .= html_tag('label', $label, for => $attributes{id}) if $label;
+ $code .= html_tag('label', $label, for => $attributes{id}, %label_attributes) if $label;
return $code;
}
If C<%attributes> contains a key C<label> then a HTML 'label' tag is
created with said C<label>. No attribute named C<label> is created in
-that case.
+that case. Furthermore, all attributes whose names start with
+C<label_> become attributes on the label tag without the C<label_>
+prefix. For example, C<label_style='#ff0000'> will be turned into
+C<style='#ff0000'> on the label tag, causing the text to become red.
If C<%attributes> contains a key C<checkall> then the value is taken as a
JQuery selector and clicking this checkbox will also toggle all checkboxes
If C<%attributes> contains a key C<label> then a HTML 'label' tag is
created with said C<label>. No attribute named C<label> is created in
-that case.
+that case. Furthermore, all attributes whose names start with
+C<label_> become attributes on the label tag without the C<label_>
+prefix. For example, C<label_style='#ff0000'> will be turned into
+C<style='#ff0000'> on the label tag, causing the text to become red.
=item C<select_tag $name, \@collection, %attributes>
do_query($form, $dbh, $query, @where_values);
1;
- }) or die { SL::DD->client->error };
+ }) or die { SL::DB->client->error };
$main::lxdebug->leave_sub();
}
-d ($::lx_office_conf{paths}->{templates} . "/$_")
&& !/^\.\.?$/
&& !m/\.(?:html|tex|sty|odt)$/
- && !m/^(?:webpages$|print$|mail$|\.)/
+ && !m/^(?:webpages$|mobile_webpages$|pdf$|print$|mail$|\.)/
} keys %dir_h;
tie %dir_h, 'IO::Dir', "$::lx_office_conf{paths}->{templates}/print";
use SL::File;
use SL::PriceSource;
use SL::Presenter::Part;
+use SL::Util qw(trim);
+use SL::DB::AuthUser;
use SL::DB::Contact;
use SL::DB::Currency;
use SL::DB::Customer;
$body_params{fallback_translation_type} = "preset_text_invoice";
}
- $::form->{all_employees} = SL::DB::Manager::Employee->get_all(query => [ deleted => 0 ]);
+ my @employees_with_email = grep {
+ my $user = SL::DB::Manager::AuthUser->find_by(login => $_->login);
+ $user && !!trim($user->get_config_value('email'));
+ } @{ SL::DB::Manager::Employee->get_all_sorted(query => [ deleted => 0 ]) };
+
my $email_form = {
to => $email,
cc => $email_cc,
my %files = _get_files_for_email_dialog();
my $html = $::form->parse_html_template("common/_send_email_dialog", {
- email_form => $email_form,
- show_bcc => $::auth->assert('email_bcc', 'may fail'),
- FILES => \%files,
- is_customer => $::form->{vc} eq 'customer',
+ email_form => $email_form,
+ show_bcc => $::auth->assert('email_bcc', 'may fail'),
+ FILES => \%files,
+ is_customer => $::form->{vc} eq 'customer',
is_invoice_mail => ($record_email && $::form->{type} eq 'invoice'),
- ALL_EMPLOYEES => $::form->{all_employees},
+ ALL_EMPLOYEES => \@employees_with_email,
});
print $::form->ajax_response_header, $html;
$::form->error($e->message);
}
}
+
+sub download_factur_x_xml {
+ my ($form) = @_;
+
+ my $record = _make_record();
+
+ die if !$record
+ || !$record->can('customer')
+ || !$record->customer
+ || !$record->can('create_pdf_a_print_options')
+ || !$record->can('create_zugferd_data')
+ || !$record->customer->create_zugferd_invoices_for_this_customer;
+
+ my $xml_content = eval { $record->create_zugferd_data };
+
+ if (my $e = SL::X::ZUGFeRDValidation->caught) {
+ $::form->error($e->message);
+ }
+
+ my $attachment_filename = $::form->generate_attachment_filename;
+ $attachment_filename =~ s{\.[^.]+$}{.xml};
+ my %headers = (
+ '-type' => 'application/xml',
+ '-connection' => 'close',
+ '-attachment' => $attachment_filename,
+ '-content-length' => length($xml_content),
+ );
+
+ print $::request->cgi->header(%headers);
+
+ $::locale->with_raw_io(\*STDOUT, sub { print $xml_content });
+}
}
sub setup_is_action_bar {
+ my ($tmpl_var) = @_;
my $form = $::form;
my $change_never = $::instance_conf->get_is_changeable == 0;
my $change_on_same_day_only = $::instance_conf->get_is_changeable == 2 && ($form->current_date(\%::myconfig) ne $form->{gldate});
my $payments_balanced = ($::form->{oldtotalpaid} == 0);
my $has_storno = ($::form->{storno} && !$::form->{storno_id});
my $may_edit_create = $::auth->assert('invoice_edit', 1);
+ my $factur_x_enabled = $tmpl_var->{invoice_obj} && $tmpl_var->{invoice_obj}->customer->create_zugferd_invoices_for_this_customer;
my ($is_linked_bank_transaction, $warn_unlinked_delivery_order);
if ($::form->{id}
&& SL::DB::Default->get->payments_changeable != 0
: $form->{postal_invoice} ? t8('This customer wants a postal invoices.')
: undef,
],
+ action => [ t8('Factur-X/ZUGFeRD'),
+ submit => [ '#form', { action => "download_factur_x_xml" } ],
+ checks => [ 'kivi.validate_form' ],
+ disabled => !$may_edit_create ? t8('You must not print this invoice.')
+ : !$form->{id} ? t8('This invoice has not been posted yet.')
+ : !$factur_x_enabled ? t8('Creating Factur-X/ZUGFeRD invoices is not enabled for this customer.')
+ : undef,
+ ],
], # end of combobox "Export"
combobox => [
$TMPL_VAR{payment_terms_obj} = get_payment_terms_for_invoice();
$form->{duedate} = $TMPL_VAR{payment_terms_obj}->calc_date(reference_date => $form->{invdate}, due_date => $form->{duedate})->to_kivitendo if $TMPL_VAR{payment_terms_obj};
- setup_is_action_bar();
+ setup_is_action_bar(\%TMPL_VAR);
$form->header();
$('#print_options_form table').first().remove().appendTo('#email_form_print_options');
+ $('select#format').change(kivi.Order.adjust_email_attachment_name_for_template_format);
+ kivi.Order.adjust_email_attachment_name_for_template_format();
+
var to_focus = $('#email_form_to').val() === '' ? 'to' : 'subject';
$('#email_form_' + to_focus).focus();
};
email_dialog.dialog("close");
};
+ ns.adjust_email_attachment_name_for_template_format = function() {
+ var $filename_elt = $('#email_form_attachment_filename');
+ var $format_elt = $('select#format');
+
+ if (!$filename_elt || !$format_elt)
+ return;
+
+ var format = $format_elt.val().toLowerCase();
+ var new_ext = format == 'html' ? 'html' : format == 'opendocument' ? 'odt' : 'pdf';
+ var filename = $filename_elt.val();
+
+ $filename_elt.val(filename.replace(/[^.]+$/, new_ext));
+ };
+
ns.set_number_in_title = function(elt) {
$('#nr_in_title').html($(elt).val());
};
};
ns.recalc_amounts_and_taxes = function() {
+ if (!kivi.validate_form('#order_form')) return;
+
var data = $('#order_form').serializeArray();
data.push({ name: 'action', value: 'Order/recalc_amounts_and_taxes' });
if (number_info !== '') { info += ' (' + number_info + ')' }
if (name_info !== '') { info += ' (' + name_info + ')' }
- if (!$('#follow_up_rowcount').lenght) {
+ if (!$('#follow_up_rowcount').length) {
$('<input type="hidden" name="follow_up_rowcount" id="follow_up_rowcount">').appendTo('#order_form');
$('<input type="hidden" name="follow_up_trans_id_1" id="follow_up_trans_id_1">').appendTo('#order_form');
$('<input type="hidden" name="follow_up_trans_type_1" id="follow_up_trans_type_1">').appendTo('#order_form');
ns.annotate($e_annotate);
return true;
}
+ },
+ trimmed_whitespaces: function($e, $e_annotate) {
+ $e_annotate = $e_annotate || $e;
+
+ var string = $e.val();
+
+ if ($e.hasClass('tooltipstered'))
+ $e.tooltipster('destroy');
+
+ if (string.match(/^\s|\s$/)) {
+ $e.val(string.trim());
+
+ $e.tooltipster({
+ content: kivi.t8("Leading and trailing whitespaces have been removed."),
+ contentAsHTML: true,
+ theme: 'tooltipster-light',
+ });
+ $e.tooltipster('show');
+ }
+ return true;
}
};
"July":"Juli",
"Jun":"Jun",
"June":"Juni",
+"Leading and trailing whitespaces have been removed.":"Leerzeichen wurden vorne und hinten entfernt",
"Loading...":"Wird geladen...",
"Map":"Karte",
"Mar":"März",
"July":"",
"Jun":"",
"June":"",
+"Leading and trailing whitespaces have been removed.":"",
"Loading...":"",
"Map":"",
"Mar":"",
'CSV Export successful!' => 'CSV-Export erfolgreich!',
'CSV export' => 'CSV-Export',
'CSV export -- options' => 'CSV-Export -- Optionen',
+ 'CSV import: additional billing addresses' => 'CSV-Import: zusätzliche Rechnungsadressen',
'CSV import: ar transactions' => 'CSV Import: Debitorenbuchungen',
'CSV import: bank transactions' => 'CSV Import: Bankbewegungen',
'CSV import: contacts' => 'CSV-Import: Ansprechpersonen',
'Created for' => 'Erstellt für',
'Created on' => 'Erstellt am',
'Creating Documents' => 'Erzeuge Dokumente',
+ 'Creating Factur-X/ZUGFeRD invoices is not enabled for this customer.' => 'Das Erzeugen von Factur-X/ZUGFeRD-Rechnungen ist für diesen Kunden nicht aktiviert.',
'Creating invoices' => 'Erzeuge Rechnungen',
'Creating the PDF failed:' => 'PDF-Erzeugung fehlgeschlagen:',
'Creation Date' => 'Erstelldatum',
'Default Transfer with services' => 'Ein- /Auslagern von Dienstleistungen über Standard-Lagerplatz',
'Default Warehouse' => 'Standard-Lager',
'Default Warehouse with ignoring onhand' => 'Standard-Lager für Auslagern ohne Prüfung auf Bestand',
+ 'Default address flag' => 'Standard-Adresse-Schalter',
'Default article for converting into quotations and orders' => 'Standardartikel für Konvertierung von Pflichtenheften in Angebote und Aufträge',
'Default booking group' => 'Standardbuchungsgruppe',
'Default client' => 'Standardmandant',
'Extended status' => 'Erweiterter Status',
'Extension Of Time' => 'Dauerfristverlängerung',
'Factor' => 'Faktor',
+ 'Factur-X/ZUGFeRD' => 'Factur-X/ZUGFeRD',
'Factur-X/ZUGFeRD import' => 'Factur-X-/ZUGFeRD-Import',
'Factur-X/ZUGFeRD invoice' => 'Factur-X-/ZUGFeRD-Rechnung',
'Factur-X/ZUGFeRD notes for each invoice' => 'Factur-X-/ZUGFeRD-Notizen für jede Rechnung',
'General ledger transactions can only be changed on the day they are posted.' => 'Dialogbuchungen können nur am Buchungstag geändert werden.',
'General settings' => 'Allgemeine Einstellungen',
'Generate and print sales delivery orders' => 'Erzeuge und drucke Lieferscheine',
+ 'Generating the document failed: #1' => 'Das Dokument konnte nicht erzeugt werden: #1',
'Germany' => 'Deutschland',
'Get one order' => 'Hole eine Bestellung',
'Get one order by shopordernumber' => 'Hole eine Bestellung über Shopbestellnummer',
'Lastcost' => 'Einkaufspreis',
'Lastcost (with X being a number)' => 'Einkaufspreis (X ist eine fortlaufende Zahl)',
'Lastname' => 'Nachname',
+ 'Leading and trailing whitespaces have been removed.' => 'Leerzeichen wurden vorne und hinten entfernt',
'Left' => 'Links',
'Letter' => 'Brief',
'Letter Draft' => 'Briefentwurf',
'Storage Type for shopimages' => 'Speichertyp für Shopbilder',
'Storing PDF in storage backend failed: #1' => 'Speichern der PDF-Datei im Datei-Speicher fehlgeschlagen: #1',
'Storing PDF to webdav folder failed: #1' => 'Speichern der PDF im WebDAV Ordner fehlgeschlagen: #1',
+ 'Storing the document in the storage backend failed: #1' => 'Das Ablegen des Dokuments im Dokumentenspeicher schlug fehl: #1',
+ 'Storing the document to the WebDAV folder failed: #1' => 'Das Ablegen des Dokuments im WebDAV-Ordner schlug fehl: #1',
'Storing the emails in the journal is currently disabled in the client configuration.' => 'Das Speichern von versendeten E-Mails ist derzeit in der Mandantenkonfigurierung abgeschaltet.',
'Storno' => 'Storno',
'Storno (one letter abbreviation)' => 'S',
'The display of (mainly) picker results can be configured. To insert the value of one option use <%Name%>.' => 'Die Anzeigenamen von (hauptsächlich) Auswahl-Ergebnissen (Picker) können konfiguriert werden. Um einen Wert einer Option in die Anzeige aufzunehmen, verwenden Sie <%Name%>.',
'The document has been changed by another user. No mail was sent. Please reopen it in another window and copy the changes to the new window' => 'Die Daten wurden bereits von einem anderen Benutzer verändert. Deshalb ist das Dokument ungültig und es wurde keine E-Mail verschickt. Bitte öffnen Sie das Dokument erneut in einem extra Fenster und übertragen Sie die Daten',
'The document has been changed by another user. Please reopen it in another window and copy the changes to the new window' => 'Die Daten wurden bereits von einem anderen Benutzer verändert. Deshalb ist das Dokument ungültig. Bitte öffnen Sie das Dokument erneut in einem extra Fenster und übertragen Sie die Daten',
+ 'The document has been created.' => 'Das Dokument wurde erzeugt.',
+ 'The document has been printed.' => 'Das Dokument wurde gedruckt.',
'The documents have been sent to the printer \'#1\'.' => 'Die Dokumente sind zum Drucker \'#1\' geschickt',
'The dunnings have been printed.' => 'Die Mahnung(en) wurden gedruckt.',
'The email has been sent.' => 'Die E-Mail wurde verschickt.',
'CSV Export successful!' => '',
'CSV export' => '',
'CSV export -- options' => '',
+ 'CSV import: additional billing addresses' => '',
'CSV import: ar transactions' => '',
'CSV import: bank transactions' => '',
'CSV import: contacts' => '',
'Created for' => '',
'Created on' => '',
'Creating Documents' => '',
+ 'Creating Factur-X/ZUGFeRD invoices is not enabled for this customer.' => '',
'Creating invoices' => '',
'Creating the PDF failed:' => '',
'Creation Date' => '',
'Default Transfer with services' => '',
'Default Warehouse' => '',
'Default Warehouse with ignoring onhand' => '',
+ 'Default address flag' => '',
'Default article for converting into quotations and orders' => '',
'Default booking group' => '',
'Default client' => '',
'Extended status' => '',
'Extension Of Time' => '',
'Factor' => '',
+ 'Factur-X/ZUGFeRD' => '',
'Factur-X/ZUGFeRD import' => '',
'Factur-X/ZUGFeRD invoice' => '',
'Factur-X/ZUGFeRD notes for each invoice' => '',
'Lastcost' => '',
'Lastcost (with X being a number)' => '',
'Lastname' => '',
+ 'Leading and trailing whitespaces have been removed.' => '',
'Left' => '',
'Letter' => '',
'Letter Draft' => '',
params:
action: CsvImport/new
profile.type: contacts
+- parent: system_import_csv
+ id: system_import_csv_additional_billing_address
+ name: Additional Billing Addresses
+ order: 250
+ params:
+ action: CsvImport/new
+ profile.type: billing_addresses
- parent: system_import_csv
id: system_import_csv_shipto
name: Shipto
dir="$(dirname "$0")"
-perl "-I${dir}/../modules/override" "-I${dir}/.." "-I${dir}/../modules/fallback" "$@"
+for TEST in "$@"; do
+ perl "-I${dir}/../modules/override" "-I${dir}/.." "-I${dir}/../modules/fallback" "$TEST"
+done
\let\ExtraDescription\__kivi_addExtraDescription:n
\setlength{\tabcolsep}{\g_kivi_tabcolsep_dim}
\seq_map_inline:Nn \l_kivi_PricingTable_seq {
+ \if_mode_horizontal: \par \fi
\bool_if:NT \g__kivi_Tabular_rowcolor_bool {
\int_gincr:N \g__kivi_PricingTable_rowcolor_int
\int_if_odd:nTF {\g__kivi_PricingTable_rowcolor_int}
[% USE LxERP %]
<h1>[% title | html %]</h1>
-<form method=post action="[% script %]">
+<form method=post action="[% script | html %]">
[% L.hidden_tag('accno', accno) %]
[% L.hidden_tag('description', description) %]
[%- IF ALL_EMPLOYEES.size %]
<tr class="hidden" data-toggle-recipients="1">
<th align="right" nowrap>[% LxERP.t8("CC to Employee") %]</th>
- <td>[% L.select_tag('email_form.cc_employee', ALL_EMPLOYEES, value_key='login' title_key='safe_name', with_empty=1, style=style) %]</td>
+ <td>[% L.select_tag('email_form.cc_employee', ALL_EMPLOYEES, value_key='login' title_key='safe_name', with_empty=1, style=style) %]</td>
</tr>
[%- END %]
</p>
[%- END %]
[%- END %][%# IF SELF.type == … %]
+ </div>
- <p>
- [%- L.submit_tag('action_download_sample', LxERP.t8('Download sample file')) %]
- </p>
-
+ <div>
+ <p>
+ [%- L.submit_tag('action_download_sample', LxERP.t8('Download sample file')) %]
+ </p>
</div>
<hr>
<label for="l_id">[% 'ID' | $T8 %]</label>
</td>
<td>
- <input name="l_[% db %]number" id="l_[% db %]number" type="checkbox" class="checkbox" value="Y" checked>
- <label for="l_[% db %]number">[% IF IS_CUSTOMER %][% 'Customer Number' | $T8 %][% ELSE %][% 'Vendor Number' | $T8 %][% END %]</label>
+ <input name="l_[% db | html %]number" id="l_[% db | html %]number" type="checkbox" class="checkbox" value="Y" checked>
+ <label for="l_[% db | html %]number">[% IF IS_CUSTOMER %][% 'Customer Number' | $T8 %][% ELSE %][% 'Vendor Number' | $T8 %][% END %]</label>
</td>
<td>
<input name="l_name" id="l_name" type="checkbox" class="checkbox" value="Y" checked>
[%- USE T8 %]
[%- USE L %]
-<h1>[% 'DATEX - Export Assistent' | $T8 %]</h1>
+<h1>[% 'DATEV - Export Assistent' | $T8 %]</h1>
-<form method='post' action='[% script %]' id='form'>
+<form method='post' action='[% script | html %]' id='form'>
<table width=100%>
<tr>
<table>
<tr>
<td align=left nowrap>[% 'Beraternummer' | $T8 %]</td>
- <td><input name=beraternr size=10 maxlength=7 value="[% beraternr %]"></td>
+ <td>[% L.input_tag("beraternr", beraternr, size=10, maxlength=7) %]</td>
<td align=left nowrap>[% 'DFV-Kennzeichen' | $T8 %]</td>
- <td><input name=dfvkz size=5 maxlength=2 value="[% dfvkz %]"></td>
+ <td>[% L.input_tag("dfvkz", dfvkz, size=5, maxlength=2) %]</td>
</tr>
<tr>
<td align=left nowrap>[% 'Beratername' | $T8 %]</td>
- <td><input name=beratername size=10 maxlength=9 value="[% beratername %]"></td>
+ <td>[% L.input_tag("beratername", berater, size=10, maxlength=9) %]</td>
<td align=left nowrap>[% 'Password' | $T8 %]</td>
- <td><input name=passwort size=5 maxlength=4 value="[% passwort %]"></td>
+ <td>[% L.input_tag("passwort", passwort, size=5, maxlength=4) %]</td>
</tr>
<tr>
<td align=left nowrap>[% 'Mandantennummer' | $T8 %]</td>
- <td><input name=mandantennr size=10 maxlength=5 value="[% mandantennr %]"></td>
+ <td>[% L.input_tag("mandantennr", mandantennr, size=10, maxlength=5) %]</td>
<td align=left nowrap>[% 'Medium Number' | $T8 %]</td>
- <td><input name=datentraegernr size=5 maxlength=3 value="[% datentraegernr %]"></td>
+ <td>[% L.input_tag("datentraegernr", datentraegernr, size=5, maxlength=3) %]</td>
</tr>
<tr>
<td></td>
<td></td>
<td align=left nowrap>[% 'Abrechnungsnummer' | $T8 %]</td>
- <td><input name=abrechnungsnr size=5 maxlength=3 value="[% abrechnungsnr %]"></td>
+ <td>[% L.input_tag("abrechnungsnr", abrechnungsnr, size=5, maxlength=3) %]</td>
</tr>
</table>
</td>
[% IF show_pk_option %]
<td align=left>[% 'Export with CV Charts' | $T8 %]</td>
<td align=left></td>
- <td>[% L.checkbox_tag('use_pk', value => 1, checked => 0) %]</td>
+ <td>[% L.checkbox_tag('use_pk', value = 1, checked = 0) %]</td>
[% ELSE %]
<td align=left><font color="gray">[% 'Export with CV Charts' | $T8 %]</font></td>
<td align=left></td>
- <td>[% L.checkbox_tag('use_pk', value => 1, checked => 0, disabled => 1) %] </td>
+ <td>[% L.checkbox_tag('use_pk', value = 1, checked = 0, disabled = 1) %] </td>
<td colspan="2"><font color="gray">[% 'Hint: Not all VC Numbers are personal accounts compliant' | $T8 %]</font></td>
[% END %]
</tr>
</tr>
</table>
-<input type=hidden name=beraternr value="[% beraternr %]">
-<input type=hidden name=dfvkz value="[% dfvkz %]">
-<input type=hidden name=beratername value="[% beratername %]">
-<input type=hidden name=passwort value="[% passwort %]">
-<input type=hidden name=mandantennr value="[% mandantennr %]">
-<input type=hidden name=datentraegernr value="[% datentraegernr %]">
-<input type=hidden name=exportformat value="[% exportformat %]">
-<input type=hidden name=abrechnungsnr value="[% abrechnungsnr %]">
+<input type=hidden name=beraternr value="[% beraternr | html %]">
+<input type=hidden name=dfvkz value="[% dfvkz | html %]">
+<input type=hidden name=beratername value="[% beratername | html %]">
+<input type=hidden name=passwort value="[% passwort | html %]">
+<input type=hidden name=mandantennr value="[% mandantennr | html %]">
+<input type=hidden name=datentraegernr value="[% datentraegernr | html %]">
+<input type=hidden name=exportformat value="[% exportformat | html %]">
+<input type=hidden name=abrechnungsnr value="[% abrechnungsnr | html %]">
-<input type=hidden name=exporttype value="[% exporttype %]">
+<input type=hidden name=exporttype value="[% exporttype | html %]">
</form>
</p>
<p>
- In dem gerade durchgeführten Export gab es [% net_gross_differences.size %]
+ In dem gerade durchgeführten Export gab es [% net_gross_differences.size | html %]
solcher Fälle. Die Summe aller Abweichungen beläuft sich auf
[% LxERP.format_amount(sum_net_gross_differences, 2) %].
</p>
[%- END -%]
<form name="Form" action="oe.pl" method="post">
+ [%- IF popup_dialog -%]
+ [% L.button_tag(popup_js_assign_function, LxERP.t8('Assign')) %]
+ [% L.button_tag(popup_js_close_function, LxERP.t8('Cancel')) %]
+
+ [%- ELSE -%]
+ [% L.hidden_tag('action', 'save_periodic_invoices_config') %]
+
+ <p>
+ [% L.submit_tag('', LxERP.t8('Assign')) %]
+ [% L.submit_tag('', LxERP.t8('Cancel'), onclick => "self.close(); return false;") %]
+ </p>
+ [%- END -%]
<p>
<table border="0">
<p>(2): [% LxERP.t8("If missing then the start date will be used.") %]</p>
<p>(3): [% LxERP.t8("Multiple addresses can be entered separated by commas.") %]</p>
<p>(4): [% LxERP.t8("If left empty the default sender from the kivitendo configuration will be used (key 'email_from' in section 'periodic_invoices'; current value: #1).", HTML.escape(LXCONFIG.periodic_invoices.email_from)) %]</p>
-
- [%- IF popup_dialog -%]
- [% L.button_tag(popup_js_assign_function, LxERP.t8('Assign')) %]
- [% L.button_tag(popup_js_close_function, LxERP.t8('Cancel')) %]
-
- [%- ELSE -%]
- [% L.hidden_tag('action', 'save_periodic_invoices_config') %]
-
- <p>
- [% L.submit_tag('', LxERP.t8('Assign')) %]
- [% L.submit_tag('', LxERP.t8('Cancel'), onclick => "self.close(); return false;") %]
- </p>
- [%- END -%]
</form>
<script type="text/javascript">
<table id="ic3">
<tr>
<th align="right">[% 'Part Number' | $T8 %]</th>
- <td>[% L.input_tag("part.partnumber", SELF.part.partnumber, size=40, class="initial_focus") %]</td>
+ <td>[% L.input_tag("part.partnumber", SELF.part.partnumber, size=40, class="initial_focus", "data-validate"="trimmed_whitespaces") %]</td>
</tr>
<tr>
<th align="right">[% 'Part Classification' | $T8 %]</th>
</tr>
<tr>
<th align="right">[% 'EAN-Code' | $T8 %]</th>
- <td>[% L.input_tag("part.ean", SELF.part.ean, size=40) %]</td>
+ <td>[% L.input_tag("part.ean", SELF.part.ean, size=40, "data-validate"="trimmed_whitespaces") %]</td>
</tr>
<tr>
[%- IF SELF.all_partsgroups.size %]
<table id="ic6">
<tr>
<th align="right" nowrap>[% 'Image' | $T8 %]</th>
- <td>[% L.input_tag("part.image", SELF.part.image, size=40) %]</td>
+ <td>[% L.input_tag("part.image", SELF.part.image, size=40, "data-validate"="trimmed_whitespaces") %]</td>
<th align="right" nowrap>[% 'Microfiche' | $T8 %]</th>
- <td>[% L.input_tag("part.microfiche", SELF.part.microfiche, size=20) %]</td>
+ <td>[% L.input_tag("part.microfiche", SELF.part.microfiche, size=20, "data-validate"="trimmed_whitespaces") %]</td>
</tr>
<tr>
<th align="right" nowrap>[% 'Drawing' | $T8 %]</th>
- <td>[% L.input_tag("part.drawing", SELF.part.drawing, size=40) %]</td>
+ <td>[% L.input_tag("part.drawing", SELF.part.drawing, size=40, "data-validate"="trimmed_whitespaces") %]</td>
</tr>
</table>
</td>