-### Choose a character set (just in case you like to change it here)
-### uncomment the line you wish to activate
-#AddDefaultCharset ISO-8859-15
+# Should always be the default
#AddDefaultCharset UTF-8
### simple access control by client ip
### uncomment the lines starting with <IfModule ...> until last </IfModule>
### examples for Apache >= 2.4: "Require ip 192.168" or "Require ip 192.168.1" or "Require ip 192.168.178" or "Require ip 217.84.201.2"
-### examples for Apache <= 2.2: "Allow from 192.168" or "Allow from 192.168.1" or "Allow from 192.168.178" or "Allow from 217.84.201.2"
#<IfModule mod_authz_core.c>
# # Apache 2.4
# Require ip 192.168
#</IfModule>
-#<IfModule !mod_authz_core.c>
-# # Apache 2.2
-# Order deny,allow
-# Deny from all
-# Allow from 192.168
-#</IfModule>
+
+<IfModule mod_rewrite.c>
+ RewriteEngine On
+ RewriteRule .*/(\.git|config)/.*$ - [F,NC]
+</IfModule>
+
use SL::DB;
use SL::GenericTranslations;
use SL::Helper::UserPreferences::PositionsScrollbar;
+use SL::Helper::UserPreferences::PartPickerSearch;
+use SL::Helper::UserPreferences::UpdatePositions;
use strict;
# get the taxkeys of the account
$form->{ACCOUNT_TAXKEYS} = [];
- foreach my $taxkey ( @{ $chart_obj->taxkeys } ) {
+ foreach my $taxkey ( sort { $b->startdate <=> $a->startdate } @{ $chart_obj->taxkeys } ) {
push @{ $form->{ACCOUNT_TAXKEYS} }, { id => $taxkey->id,
chart_id => $taxkey->chart_id,
tax_id => $taxkey->tax_id,
SL::Helper::UserPreferences::PositionsScrollbar->new()->get_height();
}
+sub purchase_search_makemodel {
+ SL::Helper::UserPreferences::PartPickerSearch->new()->get_purchase_search_makemodel();
+}
+
+sub sales_search_customer_partnumber {
+ SL::Helper::UserPreferences::PartPickerSearch->new()->get_sales_search_customer_partnumber();
+}
+
+sub positions_show_update_button {
+ SL::Helper::UserPreferences::UpdatePositions->new()->get_show_update_button();
+}
+
sub save_preferences {
$main::lxdebug->enter_sub();
if (exists $form->{positions_scrollbar_height}) {
SL::Helper::UserPreferences::PositionsScrollbar->new()->store_height($form->{positions_scrollbar_height})
}
+ if (exists $form->{purchase_search_makemodel}) {
+ SL::Helper::UserPreferences::PartPickerSearch->new()->store_purchase_search_makemodel($form->{purchase_search_makemodel})
+ }
+ if (exists $form->{sales_search_customer_partnumber}) {
+ SL::Helper::UserPreferences::PartPickerSearch->new()->store_sales_search_customer_partnumber($form->{sales_search_customer_partnumber})
+ }
+ if (exists $form->{positions_show_update_button}) {
+ SL::Helper::UserPreferences::UpdatePositions->new()->store_show_update_button($form->{positions_show_update_button})
+ }
$main::lxdebug->leave_sub();
t.taxkey,
t.taxdescription,
round(t.rate * 100, 2) AS rate,
- (SELECT accno FROM chart WHERE id = chart_id) AS taxnumber,
- (SELECT description FROM chart WHERE id = chart_id) AS account_description,
- (SELECT accno FROM chart WHERE id = skonto_sales_chart_id) AS skonto_chart_accno,
- (SELECT description FROM chart WHERE id = skonto_sales_chart_id) AS skonto_chart_description,
- (SELECT accno FROM chart WHERE id = skonto_purchase_chart_id) AS skonto_chart_purchase_accno,
- (SELECT description FROM chart WHERE id = skonto_purchase_chart_id) AS skonto_chart_purchase_description
+ tc.accno AS taxnumber,
+ tc.description AS account_description,
+ ssc.accno AS skonto_chart_accno,
+ ssc.description AS skonto_chart_description,
+ spc.accno AS skonto_chart_purchase_accno,
+ spc.description AS skonto_chart_purchase_description
FROM tax t
+ LEFT JOIN chart tc ON (tc.id = t.chart_id)
+ LEFT JOIN chart ssc ON (ssc.id = t.skonto_sales_chart_id)
+ LEFT JOIN chart spc ON (spc.id = t.skonto_purchase_chart_id)
ORDER BY taxkey, rate|;
my $sth = $dbh->prepare($query);
$chart_categories .= 'E' if $form->{expense};
$chart_categories .= 'C' if $form->{costs};
- my @values = ($form->{taxkey}, $form->{taxdescription}, $form->{rate}, conv_i($form->{chart_id}), conv_i($form->{chart_id}), conv_i($form->{skonto_sales_chart_id}), conv_i($form->{skonto_purchase_chart_id}), $chart_categories);
+ my @values = ($form->{taxkey}, $form->{taxdescription}, $form->{rate}, conv_i($form->{chart_id}), conv_i($form->{skonto_sales_chart_id}), conv_i($form->{skonto_purchase_chart_id}), $chart_categories);
if ($form->{id} ne "") {
$query = qq|UPDATE tax SET
taxkey = ?,
taxdescription = ?,
rate = ?,
chart_id = ?,
- taxnumber = (SELECT accno FROM chart WHERE id = ? ),
skonto_sales_chart_id = ?,
skonto_purchase_chart_id = ?,
chart_categories = ?
taxdescription,
rate,
chart_id,
- taxnumber,
skonto_sales_chart_id,
skonto_purchase_chart_id,
chart_categories,
id
)
- VALUES (?, ?, ?, ?, (SELECT accno FROM chart WHERE id = ?), ?, ?, ?, ?)|;
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)|;
}
push(@values, $form->{id});
do_query($form, $dbh, $query, @values);
use SL::MoreCommon;
use SL::DB::Default;
use SL::DB::Draft;
+use SL::DB::Order;
+use SL::DB::PurchaseInvoice;
use SL::Util qw(trim);
use SL::DB;
use Data::Dumper;
-
+use List::Util qw(sum0);
use strict;
sub post_transaction {
$query = qq|UPDATE ap SET invnumber = ?,
transdate = ?, ordnumber = ?, vendor_id = ?, taxincluded = ?,
- amount = ?, duedate = ?, paid = ?, netamount = ?,
+ amount = ?, duedate = ?, deliverydate = ?, paid = ?, netamount = ?,
currency_id = (SELECT id FROM currencies WHERE name = ?), notes = ?, department_id = ?, storno = ?, storno_id = ?,
globalproject_id = ?, direct_debit = ?
WHERE id = ?|;
@values = ($form->{invnumber}, conv_date($form->{transdate}),
$form->{ordnumber}, conv_i($form->{vendor_id}),
$form->{taxincluded} ? 't' : 'f', $form->{invtotal},
- conv_date($form->{duedate}), $form->{invpaid},
- $form->{netamount},
+ conv_date($form->{duedate}), conv_date($form->{deliverydate}),
+ $form->{invpaid}, $form->{netamount},
$form->{currency}, $form->{notes},
conv_i($form->{department_id}), $form->{storno},
$form->{storno_id}, conv_i($form->{globalproject_id}),
$form->new_lastmtime('ap');
+ # Link this record to the record it was created from.
+ my $convert_from_oe_id = delete $form->{convert_from_oe_id};
+ if (!$form->{postasnew} && $convert_from_oe_id) {
+ RecordLinks->create_links('dbh' => $dbh,
+ 'mode' => 'ids',
+ 'from_table' => 'oe',
+ 'from_ids' => $convert_from_oe_id,
+ 'to_table' => 'ap',
+ 'to_id' => $form->{id},
+ );
+
+ # Close the record it was created from if the amount of
+ # all APs create from this record equals the records amount.
+ my @links = RecordLinks->get_links('dbh' => $dbh,
+ 'from_table' => 'oe',
+ 'from_id' => $convert_from_oe_id,
+ 'to_table' => 'ap',
+ );
+
+ my $amount_sum = sum0 map { SL::DB::PurchaseInvoice->new(id => $_->{to_id})->load->amount } @links;
+ my $order = SL::DB::Order->new(id => $convert_from_oe_id)->load;
+
+ $order->update_attributes(closed => 1) if ($amount_sum - $order->amount) == 0;
+ }
+
# add individual transactions
for my $i (1 .. $form->{rowcount}) {
if ($form->{"amount_$i"} != 0) {
qq| v.vendornumber, v.country, v.ustid, | .
qq| tz.description AS taxzone, | .
qq| pt.description AS payment_terms, | .
+ qq| department.description AS department, | .
qq{ ( SELECT ch.accno || ' -- ' || ch.description
FROM acc_trans at
LEFT JOIN chart ch ON ch.id = at.chart_id
qq|LEFT JOIN employee e ON (a.employee_id = e.id) | .
qq|LEFT JOIN project pr ON (a.globalproject_id = pr.id) | .
qq|LEFT JOIN tax_zones tz ON (tz.id = a.taxzone_id)| .
- qq|LEFT JOIN payment_terms pt ON (pt.id = a.payment_id)|;
+ qq|LEFT JOIN payment_terms pt ON (pt.id = a.payment_id)| .
+ qq|LEFT JOIN department ON (department.id = a.department_id)|;
my $where = '';
# Permissions:
# - Always return invoices & AP transactions for projects the employee has "view invoices" permissions for, no matter what the other rules say.
# - Exclude AP transactions if no permissions for them exist.
- # - Filter by employee if requested.
+ # - Limit to own invoices unless may edit all invoices.
+ # - If may edit all, allow filtering by employee.
my (@permission_where, @permission_values);
if ($::auth->assert('vendor_invoice_edit', 1)) {
push @permission_where, "NOT invoice = 'f'"; # remove ap transactions from Purchase -> Reports -> Invoices
}
- if ($form->{employee_id}) {
+ if (!$::auth->assert('purchase_all_edit', 1)) {
+ # only show own invoices
push @permission_where, "a.employee_id = ?";
- push @permission_values, conv_i($form->{employee_id});
+ push @permission_values, SL::DB::Manager::Employee->current->id;
+
+ } else {
+ if ($form->{employee_id}) {
+ push @permission_where, "a.employee_id = ?";
+ push @permission_values, conv_i($form->{employee_id});
+ }
}
}
$where .= " AND a.transdate <= ?";
push(@values, trim($form->{transdateto}));
}
+ if ($form->{duedatefrom}) {
+ $where .= " AND a.duedate >= ?";
+ push(@values, trim($form->{duedatefrom}));
+ }
+ if ($form->{duedateto}) {
+ $where .= " AND a.duedate <= ?";
+ push(@values, trim($form->{duedateto}));
+ }
if ($form->{open} || $form->{closed}) {
unless ($form->{open} && $form->{closed}) {
$where .= " AND a.amount <> a.paid" if ($form->{open});
my $sortdir = !defined $form->{sortdir} ? 'ASC' : $form->{sortdir} ? 'ASC' : 'DESC';
my $sortorder = join(', ', map { "$_ $sortdir" } @a);
- if (grep({ $_ eq $form->{sort} } qw(transdate id invnumber ordnumber name netamount tax amount paid datepaid due duedate notes employee transaction_description direct_debit))) {
+ if (grep({ $_ eq $form->{sort} } qw(transdate id invnumber ordnumber name netamount tax amount paid datepaid due duedate notes employee transaction_description direct_debit department))) {
$sortorder = $form->{sort} . " $sortdir";
}
$storno_row->{netamount} *= -1;
$storno_row->{paid} = $storno_row->{amount};
- delete @$storno_row{qw(itime mtime)};
+ delete @$storno_row{qw(itime mtime gldate)};
$query = sprintf 'INSERT INTO ap (%s) VALUES (%s)', join(', ', keys %$storno_row), join(', ', map '?', values %$storno_row);
do_query($form, $dbh, $query, (values %$storno_row));
}
for my $row (@$rowref) {
- delete @$row{qw(itime mtime link acc_trans_id)};
+ delete @$row{qw(itime mtime link acc_trans_id gldate)};
$query = sprintf 'INSERT INTO acc_trans (%s) VALUES (%s)', join(', ', keys %$row), join(', ', map '?', values %$row);
$row->{trans_id} = $new_id;
$row->{amount} *= -1;
$query =
qq|UPDATE ar set
invnumber = ?, ordnumber = ?, transdate = ?, customer_id = ?,
- taxincluded = ?, amount = ?, duedate = ?, paid = ?,
+ taxincluded = ?, amount = ?, duedate = ?, deliverydate = ?, paid = ?,
currency_id = (SELECT id FROM currencies WHERE name = ?),
netamount = ?, notes = ?, department_id = ?,
employee_id = ?, storno = ?, storno_id = ?, globalproject_id = ?,
direct_debit = ?
WHERE id = ?|;
my @values = ($form->{invnumber}, $form->{ordnumber}, conv_date($form->{transdate}), conv_i($form->{customer_id}), $form->{taxincluded} ? 't' : 'f', $form->{amount},
- conv_date($form->{duedate}), $form->{paid},
+ conv_date($form->{duedate}), conv_date($form->{deliverydate}), $form->{paid},
$form->{currency},
$form->{netamount}, $form->{notes}, conv_i($form->{department_id}),
conv_i($form->{employee_id}), $form->{storno} ? 't' : 'f', $form->{storno_id},
my $sortdir = !defined $form->{sortdir} ? 'ASC' : $form->{sortdir} ? 'ASC' : 'DESC';
my $sortorder = join(', ', map { "$_ $sortdir" } @a);
- if (grep({ $_ eq $form->{sort} } qw(id transdate duedate invnumber ordnumber cusordnumber name datepaid employee shippingpoint shipvia transaction_description))) {
+ if (grep({ $_ eq $form->{sort} } qw(id transdate duedate invnumber ordnumber cusordnumber name datepaid employee shippingpoint shipvia transaction_description department))) {
$sortorder = $form->{sort} . " $sortdir";
}
+++ /dev/null
-package SL::ArchiveZipFixes;
-
-use strict;
-
-use Archive::Zip;
-use Archive::Zip::Member;
-use version;
-
-# Archive::Zip contains a bug starting with 1.31_04 which prohibits
-# re-writing Zips produced by LibreOffice (.odt). See
-# https://rt.cpan.org/Public/Bug/Display.html?id=92205
-
-sub _member_writeToFileHandle {
- my $self = shift;
- my $fh = shift;
- my $fhIsSeekable = shift;
- my $offset = shift;
-
- return _error("no member name given for $self")
- if $self->fileName() eq '';
-
- $self->{'writeLocalHeaderRelativeOffset'} = $offset;
- $self->{'wasWritten'} = 0;
-
- # Determine if I need to write a data descriptor
- # I need to do this if I can't refresh the header
- # and I don't know compressed size or crc32 fields.
- my $headerFieldsUnknown = (
- ( $self->uncompressedSize() > 0 )
- and ($self->compressionMethod() == Archive::Zip::COMPRESSION_STORED
- or $self->desiredCompressionMethod() == Archive::Zip::COMPRESSION_DEFLATED )
- );
-
- my $shouldWriteDataDescriptor =
- ( $headerFieldsUnknown and not $fhIsSeekable );
-
- $self->hasDataDescriptor(1)
- if ($shouldWriteDataDescriptor);
-
- $self->{'writeOffset'} = 0;
-
- my $status = $self->rewindData();
- ( $status = $self->_writeLocalFileHeader($fh) )
- if $status == Archive::Zip::AZ_OK;
- ( $status = $self->_writeData($fh) )
- if $status == Archive::Zip::AZ_OK;
- if ( $status == Archive::Zip::AZ_OK ) {
- $self->{'wasWritten'} = 1;
- if ( $self->hasDataDescriptor() ) {
- $status = $self->_writeDataDescriptor($fh);
- }
- elsif ($headerFieldsUnknown) {
- $status = $self->_refreshLocalFileHeader($fh);
- }
- }
-
- return $status;
-}
-
-sub fix_write_to_file_handle_1_30 {
- return if version->new("$Archive::Zip::VERSION")->numify <= version->new("1.30")->numify;
-
- no warnings 'redefine';
-
- *Archive::Zip::Member::_writeToFileHandle = \&_member_writeToFileHandle;
-}
-
-sub apply_fixes {
- fix_write_to_file_handle_1_30();
-}
-
-1;
use Digest::MD5 qw(md5_hex);
use IO::File;
use Time::HiRes qw(gettimeofday);
-use List::MoreUtils qw(uniq);
+use List::MoreUtils qw(any uniq);
use YAML;
use Regexp::IPv6 qw($IPv6_re);
delete $self->{column_information};
}
- $self->{authenticator}->reset;
+ $_->reset for @{ $self->{authenticators} };
$self->client(undef);
}
my ($self, @msg) = @_;
if ($ENV{HTTP_USER_AGENT}) {
- print Form->create_http_response(content_type => 'text/html');
+ # $::form might not be initialized yet at this point — therefore
+ # we cannot use "create_http_response" yet.
+ my $cgi = CGI->new('');
+ print $cgi->header('-type' => 'text/html', '-charset' => 'UTF-8');
print "<pre>", join ('<br>', @msg), "</pre>";
} else {
print STDERR "Error: @msg\n";
} else {
$self->{DB_config} = $::lx_office_conf{'authentication/database'};
- $self->{LDAP_config} = $::lx_office_conf{'authentication/ldap'};
}
- if ($self->{module} eq 'DB') {
- $self->{authenticator} = SL::Auth::DB->new($self);
+ $self->{authenticators} = [];
+ $self->{module} ||= 'DB';
+ $self->{module} =~ s{^ +| +$}{}g;
- } elsif ($self->{module} eq 'LDAP') {
- $self->{authenticator} = SL::Auth::LDAP->new($self);
- }
+ foreach my $module (split m{ +}, $self->{module}) {
+ my $config_name;
+ ($module, $config_name) = split m{:}, $module, 2;
+ $config_name ||= $module eq 'DB' ? 'database' : lc($module);
+ my $config = $::lx_office_conf{'authentication/' . $config_name};
- if (!$self->{authenticator}) {
- my $locale = Locale->new('en');
- $self->mini_error($locale->text('No or an unknown authenticantion module specified in "config/kivitendo.conf".'));
+ if (!$config) {
+ my $locale = Locale->new('en');
+ $self->mini_error($locale->text('Missing configuration section "authentication/#1" in "config/kivitendo.conf".', $config_name));
+ }
+
+ if ($module eq 'DB') {
+ push @{ $self->{authenticators} }, SL::Auth::DB->new($self);
+
+ } elsif ($module eq 'LDAP') {
+ push @{ $self->{authenticators} }, SL::Auth::LDAP->new($config);
+
+ } else {
+ my $locale = Locale->new('en');
+ $self->mini_error($locale->text('Unknown authenticantion module #1 specified in "config/kivitendo.conf".', $module));
+ }
}
my $cfg = $self->{DB_config};
$self->mini_error($locale->text('config/kivitendo.conf: Missing parameters in "authentication/database". Required parameters are "host", "db" and "user".'));
}
- $self->{authenticator}->verify_config();
+ $_->verify_config for @{ $self->{authenticators} };
$self->{session_timeout} *= 1;
$self->{session_timeout} = 8 * 60 if (!$self->{session_timeout});
return ERR_PASSWORD;
}
- my $result = $login ? $self->{authenticator}->authenticate($login, $password) : ERR_USER;
+ my $result = ERR_USER;
+ if ($login) {
+ foreach my $authenticator (@{ $self->{authenticators} }) {
+ $result = $authenticator->authenticate($login, $password);
+ last if $result == OK;
+ }
+ }
+
$self->set_session_value(SESSION_KEY_USER_AUTH() => $result, login => $login, client_id => $self->client->{id});
return $result;
}
sub can_change_password {
my $self = shift;
- return $self->{authenticator}->can_change_password();
+ return any { $_->can_change_password } @{ $self->{authenticators} };
}
sub change_password {
my ($self, $login, $new_password) = @_;
- my $result = $self->{authenticator}->change_password($login, $new_password);
+ my $overall_result = OK;
- return $result;
+ foreach my $authenticator (@{ $self->{authenticators} }) {
+ next unless $authenticator->can_change_password;
+
+ my $result = $authenticator->change_password($login, $new_password);
+ $overall_result = $result if $result != OK;
+ }
+
+ return $overall_result;
}
sub read_all_users {
my $negate = 0;
foreach my $el (@{$ary}) {
+ next unless defined $el;
+
if (ref $el eq "ARRAY") {
my $val = evaluate_rights_ary($el);
$val = !$val if $negate;
}
if (!$dont_abort) {
+ $::dispatcher->reply_with_json_error(error => 'access') if $::request->type eq 'json';
+
delete $::form->{title};
$::form->show_generic_error($::locale->text("You do not have the permissions to access this function."));
}
foreach my $table (qw(session session_content)) {
my $query = <<SQL;
- SELECT a.attname, format_type(a.atttypid, a.atttypmod) AS format_type, d.adsrc, a.attnotnull
- FROM pg_attribute a
- LEFT JOIN pg_attrdef d ON (a.attrelid = d.adrelid) AND (a.attnum = d.adnum)
- WHERE (a.attrelid = 'auth.${table}'::regclass)
- AND (a.attnum > 0)
- AND NOT a.attisdropped
- ORDER BY a.attnum
+ SELECT attname
+ FROM pg_attribute
+ WHERE (attrelid = 'auth.${table}'::regclass)
+ AND (attnum > 0)
+ AND NOT attisdropped
SQL
- $self->{info}->{$table} = { selectall_as_map($::form, $self->{auth}->dbconnect, $query, 'attname', [ qw(format_type adsrc attnotnull) ]) };
+ $self->{info}->{$table} = { selectall_as_map($::form, $self->{auth}->dbconnect, $query, 'attname', [ qw(attname) ]) };
}
return $self;
use English '-no_match_vars';
-use Scalar::Util qw(weaken);
use SL::Auth::Constants qw(:all);
use strict;
sub new {
- $main::lxdebug->enter_sub();
-
if (!defined eval "require Net::LDAP;") {
die 'The module "Net::LDAP" is not installed.';
}
- my $type = shift;
- my $self = {};
-
- $self->{auth} = shift;
- weaken $self->{auth};
+ my $type = shift;
+ my $self = {};
+ $self->{config} = shift;
bless $self, $type;
- $main::lxdebug->leave_sub();
-
return $self;
}
}
sub _connect {
- $main::lxdebug->enter_sub();
-
my $self = shift;
- my $cfg = $self->{auth}->{LDAP_config};
-
- if ($self->{ldap}) {
- $main::lxdebug->leave_sub();
+ my $cfg = $self->{config};
- return $self->{ldap};
- }
+ return $self->{ldap} if $self->{ldap};
- my $port = $cfg->{port} || 389;
- $self->{ldap} = Net::LDAP->new($cfg->{host}, 'port' => $port);
+ my $port = $cfg->{port} || 389;
+ my $ldap = Net::LDAP->new($cfg->{host}, port => $port, timeout => $cfg->{timeout} || 10);
- if (!$self->{ldap}) {
- $main::form->error($main::locale->text('The LDAP server "#1:#2" is unreachable. Please check config/kivitendo.conf.', $cfg->{host}, $port));
+ if (!$ldap) {
+ $::lxdebug->warn($main::locale->text('The LDAP server "#1:#2" is unreachable. Please check config/kivitendo.conf.', $cfg->{host}, $port));
+ return undef;
}
if ($cfg->{tls}) {
- my $mesg = $self->{ldap}->start_tls('verify' => 'none');
+ my $mesg = $ldap->start_tls(verify => $cfg->{verify} // 'require');
if ($mesg->is_error()) {
- $main::form->error($main::locale->text('The connection to the LDAP server cannot be encrypted (SSL/TLS startup failure). Please check config/kivitendo.conf.'));
+ $::lxdebug->warn($main::locale->text('The connection to the LDAP server cannot be encrypted (SSL/TLS startup failure). Please check config/kivitendo.conf.'));
+ return undef;
}
}
if ($cfg->{bind_dn}) {
- my $mesg = $self->{ldap}->bind($cfg->{bind_dn}, 'password' => $cfg->{bind_password});
+ my $mesg = $ldap->bind($cfg->{bind_dn}, 'password' => $cfg->{bind_password});
if ($mesg->is_error()) {
- $main::form->error($main::locale->text('Binding to the LDAP server as "#1" failed. Please check config/kivitendo.conf.', $cfg->{bind_dn}));
+ $::lxdebug->warn($main::locale->text('Binding to the LDAP server as "#1" failed. Please check config/kivitendo.conf.', $cfg->{bind_dn}));
+ return undef;
}
}
- $main::lxdebug->leave_sub();
+ $self->{ldap} = $ldap;
return $self->{ldap};
}
sub _get_filter {
- $main::lxdebug->enter_sub();
-
my $self = shift;
my $login = shift;
my ($cfg, $filter);
- $cfg = $self->{auth}->{LDAP_config};
+ $cfg = $self->{config};
$filter = "$cfg->{filter}";
$filter =~ s|^\s+||;
}
- $main::lxdebug->leave_sub();
-
return $filter;
}
sub _get_user_dn {
- $main::lxdebug->enter_sub();
-
my $self = shift;
my $ldap = shift;
my $login = shift;
$self->{dn_cache} ||= { };
- if ($self->{dn_cache}->{$login}) {
- $main::lxdebug->leave_sub();
- return $self->{dn_cache}->{$login};
- }
+ return $self->{dn_cache}->{$login} if $self->{dn_cache}->{$login};
- my $cfg = $self->{auth}->{LDAP_config};
+ my $cfg = $self->{config};
my $filter = $self->_get_filter($login);
my $mesg = $ldap->search('base' => $cfg->{base_dn}, 'scope' => 'sub', 'filter' => $filter);
- if ($mesg->is_error() || (0 == $mesg->count())) {
- $main::lxdebug->leave_sub();
- return undef;
- }
+ return undef if $mesg->is_error || !$mesg->count();
my $entry = $mesg->entry(0);
$self->{dn_cache}->{$login} = $entry->dn();
- $main::lxdebug->leave_sub();
-
return $self->{dn_cache}->{$login};
}
sub authenticate {
- $main::lxdebug->enter_sub();
-
my $self = shift;
my $login = shift;
my $password = shift;
my $is_crypted = shift;
- if ($is_crypted) {
- $main::lxdebug->leave_sub();
- return ERR_BACKEND;
- }
+ return ERR_BACKEND if $is_crypted;
my $ldap = $self->_connect();
- if (!$ldap) {
- $main::lxdebug->leave_sub();
- return ERR_BACKEND;
- }
+ return ERR_BACKEND if !$ldap;
my $dn = $self->_get_user_dn($ldap, $login);
$main::lxdebug->message(LXDebug->DEBUG2(), "LDAP authenticate: dn $dn");
- if (!$dn) {
- $main::lxdebug->leave_sub();
- return ERR_BACKEND;
- }
+ return ERR_BACKEND if !$dn;
my $mesg = $ldap->bind($dn, 'password' => $password);
$main::lxdebug->message(LXDebug->DEBUG2(), "LDAP authenticate: bind mesg " . $mesg->error());
- $main::lxdebug->leave_sub();
-
return $mesg->is_error() ? ERR_PASSWORD : OK;
}
}
sub verify_config {
- $main::lxdebug->enter_sub();
-
my $form = $main::form;
my $locale = $main::locale;
my $self = shift;
- my $cfg = $self->{auth}->{LDAP_config};
+ my $cfg = $self->{config};
if (!$cfg) {
$form->error($locale->text('config/kivitendo.conf: Key "authentication/ldap" is missing.'));
if (!$cfg->{host} || !$cfg->{attribute} || !$cfg->{base_dn}) {
$form->error($locale->text('config/kivitendo.conf: Missing parameters in "authentication/ldap". Required parameters are "host", "attribute" and "base_dn".'));
}
-
- $main::lxdebug->leave_sub();
}
1;
use English qw(-no_match_vars);
use List::MoreUtils qw(uniq);
+use SL::Common;
use SL::DB::AuthUser;
use SL::DB::Default;
use SL::DB::Order;
use SL::Helper::CreatePDF qw(create_pdf find_template);
use SL::Mailer;
use SL::Util qw(trim);
+use SL::System::Process;
sub create_job {
$_[0]->create_standard_job('0 3 1 * *'); # first day of month at 3:00 am
$mail->send;
}
+sub _store_pdf_in_webdav {
+ my ($self, $pdf_file_name, $invoice) = @_;
+
+ return unless $::instance_conf->get_webdav_documents;
+
+ my $form = Form->new('');
+
+ $form->{cwd} = SL::System::Process->exe_dir;
+ $form->{tmpdir} = ($pdf_file_name =~ m{(.+)/})[0];
+ $form->{tmpfile} = ($pdf_file_name =~ m{.+/(.+)})[0];
+ $form->{format} = 'pdf';
+ $form->{formname} = 'invoice';
+ $form->{type} = 'invoice';
+ $form->{vc} = 'customer';
+ $form->{invnumber} = $invoice->invnumber;
+ $form->{recipient_locale} = $invoice->language ? $invoice->language->template_code : '';
+
+ Common::copy_file_to_webdav_folder($form);
+}
+
sub _print_invoice {
my ($self, $data) = @_;
template => scalar($self->find_template(name => 'invoice', language => $language)),
variables => Form->new(''),
return => 'file_name',
+ record => $data->{invoice},
variable_content_types => {
longdescription => 'html',
partnotes => 'html',
eval {
$pdf_file_name = $self->create_pdf(%create_params);
+ $self->_store_pdf_in_webdav($pdf_file_name, $data->{invoice});
+
for (qw(email_subject email_body)) {
_replace_vars(
object => $data->{config},
use SL::Template;
use SL::Helper::MassPrintCreatePDF qw(:all);
use SL::Helper::CreatePDF qw(:all);
-use SL::Helper::File qw(store_pdf append_general_pdf_attachments);
+use SL::Helper::File qw(store_pdf append_general_pdf_attachments doc_storage_enabled);
use constant WAITING_FOR_EXECUTION => 0;
use constant PRINTING_DELIVERY_ORDERS => 1;
}
1;
-
use SL::Locale::String qw(t8);
use SL::Helper::MassPrintCreatePDF qw(:all);
use SL::Helper::CreatePDF qw(:all);
-use SL::Helper::File qw(store_pdf append_general_pdf_attachments);
-use SL::Webdav;
+use SL::Helper::File qw(store_pdf append_general_pdf_attachments doc_storage_enabled);
use constant WAITING_FOR_EXECUTION => 0;
use constant CONVERTING_DELIVERY_ORDERS => 1;
my $data = $job_obj->data_as_hash;
eval {
- my $invoice;
my $sales_delivery_order = SL::DB::DeliveryOrder->new(id => $delivery_order_id)->load;
$number = $sales_delivery_order->donumber;
+ my %conversion_params = $data->{transdate} ? ('attributes' => { transdate => $data->{transdate} }) : ();
+ my $invoice = $sales_delivery_order->convert_to_invoice(%conversion_params);
- if (!$db->with_transaction(sub {
- $invoice = $sales_delivery_order->convert_to_invoice(sub { $data->{transdate} ? ('attributes' => { transdate => $data->{transdate} }) :
- undef }->() ) || die $db->error;
- 1;
- })) {
- die $db->error;
- }
+ die $db->error if !$invoice;
$data->{num_created}++;
push @{ $data->{invoice_ids} }, $invoice->id;
foreach my $invoice (@{ $self->{invoices} }) {
eval {
+ my @errors = ();
my %params = (
variables => \%variables,
return => 'file_name',
document => $invoice,
+ errors => \@errors,
);
push @pdf_file_names, $self->create_massprint_pdf(%params);
-
$data->{num_printed}++;
- # OLD WebDAV Code, may be deleted:
- # copy file to webdav folder
- if ($::instance_conf->get_webdav_documents) {
- my $webdav = SL::Webdav->new(
- type => 'invoice',
- number => $invoice->invnumber,
- );
- my $webdav_file = SL::Webdav::File->new(
- webdav => $webdav,
- filename => t8('Invoice') . '_' . $invoice->invnumber . '.pdf',
- );
- eval {
- $webdav_file->store(file => $pdf_file_names[-1]);
- 1;
- } or do {
- push @{ $data->{print_errors} }, { id => $invoice->id, number => $invoice->invnumber, message => $@ };
- }
+ if (scalar @errors) {
+ push @{ $data->{print_errors} }, { id => $invoice->id, number => $invoice->invnumber, message => join(', ', @errors) };
}
1;
'add_full_diag' => { interface => 'add', hash_key => 'full_diag' },
],
scalar => [
- qw(diag tester config aggreg),
+ qw(diag tester config aggreg module_nr),
],
);
$module =~ s/[^\w:]//g;
$module = "SL::BackgroundJob::SelfTest::$module";
+ # increase module nr
+ $self->module_nr(($self->module_nr || 0) + 1);
+
# try to load module;
(my $file = $module) =~ s|::|/|g;
eval {
} or $self->add_errors($::locale->text('Could not load class #1, #2', $module, $@)) && return;
$self->add_full_diag($output);
- $self->{diag_per_module}{$module} = $output;
+ $self->{diag_per_module}{$self->module_nr . ': ' . $module} = $output;
my $parser = TAP::Parser->new({ tap => $output});
$parser->run;
use SL::BackgroundJob::SelfTest;
SL::BackgroundJob::SelfTest->new->run;;
-=head1 DESCRIPTION
-
-
-
-=head1 FUNCTIONS
-
-=head1 BUGS
-
-=head1 AUTHOR
-
=cut
$self->_setup;
- $self->tester->plan(tests => 32);
+ $self->tester->plan(tests => 34);
$self->check_konten_mit_saldo_nicht_in_guv;
$self->check_bilanzkonten_mit_pos_eur;
sub check_summe_stornobuchungen {
my ($self) = @_;
- my $query = qq|
- SELECT sum(amount) from ar a WHERE a.id IN
- (SELECT id from ap where storno is true
- AND a.transdate >= ? and a.transdate <= ?)|;
- my ($summe_stornobuchungen_ar) = selectfirst_array_query($::form, $self->dbh, $query, $self->fromdate, $self->todate);
-
- $query = qq|
- SELECT sum(amount) from ap a WHERE a.id IN
- (SELECT id from ap where storno is true
- AND a.transdate >= ? and a.transdate <= ?)|;
- my ($summe_stornobuchungen_ap) = selectfirst_array_query($::form, $self->dbh, $query, $self->fromdate, $self->todate);
-
- $self->tester->ok($summe_stornobuchungen_ap == 0, 'Summe aller Einkaufsrechnungen (stornos + stornierte) soll 0 sein');
- $self->tester->ok($summe_stornobuchungen_ar == 0, 'Summe aller Verkaufsrechnungen (stornos + stornierte) soll 0 sein');
- $self->tester->diag("Summe Verkaufsrechnungen (ar): $summe_stornobuchungen_ar") if $summe_stornobuchungen_ar;
- $self->tester->diag("Summe Einkaufsrechnungen (ap): $summe_stornobuchungen_ap") if $summe_stornobuchungen_ap;
+ my %sums_canceled;
+ my %sums_storno;
+ foreach my $table (qw(ar ap)) {
+ # check invoices canceled (stornoed) in consideration period (corresponding stornos do not have to be in this period)
+ my $query = qq|
+ SELECT sum(amount) FROM $table WHERE id IN (
+ SELECT id FROM $table WHERE storno IS TRUE AND storno_id IS NULL AND transdate >= ? AND transdate <= ?
+ UNION
+ SELECT id FROM $table WHERE storno IS TRUE AND storno_id IS NOT NULL AND storno_id IN
+ (SELECT id FROM $table WHERE storno IS TRUE AND storno_id IS NULL AND transdate >= ? AND transdate <= ?)
+ )|;
+ ($sums_canceled{$table}) = selectfirst_array_query($::form, $self->dbh, $query, $self->fromdate, $self->todate, $self->fromdate, $self->todate);
+
+ # check storno invoices in consideration period (corresponding canceled (stornoed) invoices do not have to be in this period)
+ $query = qq|
+ SELECT sum(amount) FROM $table WHERE id IN (
+ SELECT storno_id FROM $table WHERE storno IS TRUE AND storno_id IS NOT NULL AND transdate >= ? AND transdate <= ?
+ UNION
+ SELECT id FROM $table WHERE storno IS TRUE AND storno_id IS NOT NULL AND transdate >= ? AND transdate <= ?
+ )|;
+ ($sums_storno{$table}) = selectfirst_array_query($::form, $self->dbh, $query, $self->fromdate, $self->todate, $self->fromdate, $self->todate);
+
+ my $text_rg = ($table eq 'ar') ? 'Verkaufsrechnungen' : 'Einkaufsrechnungen';
+
+ $self->tester->ok($sums_canceled{$table} == 0, "Summe aller $text_rg (stornos + stornierte) soll 0 sein (für stornierte Rechnungen)");
+ $self->tester->ok($sums_storno {$table} == 0, "Summe aller $text_rg (stornos + stornierte) soll 0 sein (für Storno-Rechnungen)");
+ $self->tester->diag("Summe $text_rg ($table) (für stornierte Rechnungen) : " . $sums_canceled{$table}) if $sums_canceled{$table} != 0;
+ $self->tester->diag("Summe $text_rg ($table) (für Storno-Rechnungen) : " . $sums_storno {$table}) if $sums_storno {$table} != 0;
+ }
}
sub check_ar_paid {
my $query = qq|
SELECT purpose from bank_transactions
WHERE cleared is true
- AND id not in (SELECT bank_transaction_id from reconciliation_links)
+ AND NOT EXISTS (SELECT bank_transaction_id from reconciliation_links WHERE bank_transaction_id = bank_transactions.id)
AND transdate >= ? AND transdate <= ?|;
my $bt_cleared_no_link = selectall_hashref_query($::form, $self->dbh, $query, $self->fromdate, $self->todate);
my $query = qq|
SELECT purpose from bank_transactions
WHERE invoice_amount <> 0
- AND id not in (SELECT bank_transaction_id from bank_transaction_acc_trans)
+ AND NOT EXISTS (SELECT bank_transaction_id FROM bank_transaction_acc_trans WHERE bank_transaction_id = bank_transactions.id)
AND itime > (SELECT min(itime) from bank_transaction_acc_trans)
AND transdate >= ? AND transdate <= ?|;
SELECT purpose from bank_transactions
WHERE id in
(SELECT bank_transaction_id from bank_transaction_acc_trans
- where acc_trans_id NOT IN (select acc_trans_id from acc_trans)
+ WHERE NOT EXISTS (SELECT acc_trans.acc_trans_id FROM acc_trans WHERE acc_trans.acc_trans_id = bank_transaction_acc_trans.acc_trans_id)
AND transdate >= ? AND transdate <= ?)|;
my $bt_assigned_no_acc_trans = selectall_hashref_query($::form, $self->dbh, $query, $self->fromdate, $self->todate);
Several tests for data integrity.
-=head1 FUNCTIONS
-
-=head1 BUGS
-
=head1 AUTHOR
G. Richardson E<lt>information@richardson-bueren.deE<gt>
Sven Schoeling E<lt>s.schoeling@linet-services.deE<gt>
=cut
-
--- /dev/null
+package SL::BackgroundJob::SetNumberRange;
+
+use strict;
+
+use parent qw(SL::BackgroundJob::Base);
+
+use SL::PrefixedNumber;
+
+use DateTime::Format::Strptime;
+
+sub create_job {
+ $_[0]->create_standard_job('59 23 31 12 *'); # one minute before new year
+}
+
+
+sub run {
+ my ($self, $db_obj) = @_;
+ my $data = $db_obj->data_as_hash;
+
+ if ($data->{digits_year} && !($data->{digits_year} == 2 || $data->{digits_year} == 4)) {
+ die "No valid input for digits_year should be 2 or 4.";
+ }
+ if ($data->{multiplier} && !($data->{multiplier} % 10 == 0)) {
+ die "No valid input for multiplier should be 10, 100, .., 1000000";
+ }
+ my $next_year = DateTime->today_local->truncate(to => 'year')->add(years => 1)->year();
+ $next_year = ($data->{digits_year} == 2) ? substr($next_year, 2, 2) : $next_year;
+ my $multiplier = $data->{multiplier} || 100;
+
+ my $defaults = SL::DB::Default->get;
+
+ foreach (qw(invnumber cnnumber sonumber ponumber sqnumber rfqnumber sdonumber pdonumber)) {
+ my $current_number = SL::PrefixedNumber->new(number => $defaults->{$_});
+ $current_number->set_to($next_year * $multiplier);
+ $defaults->{$_} = $current_number->get_current;
+ }
+ $defaults->save() || die "Could not change number ranges";
+
+ return exists $data->{result} ? $data->{result} : 1;
+}
+
+1;
use parent qw(SL::BackgroundJob::Base);
+use SL::System::TaskServer;
+
sub run {
my ($self, $db_obj) = @_;
my $data = $db_obj->data_as_hash;
- $::lxdebug->message(0, "Test job is being executed.");
+ $::lxdebug->message(0, "Test job ID " . $db_obj->id . " is being executed on node " . SL::System::TaskServer::node_id() . ".");
die "Oh cruel world: " . $data->{exception} if $data->{exception};
c.pos_eur,
c.valid_from,
c.datevautomatik,
- comma(tk.startdate::text) AS startdate,
- comma(tk.taxkey_id::text) AS taxkey,
- comma(tx.taxdescription || to_char (tx.rate, '99V99' ) || '%') AS taxdescription,
- comma(tx.taxnumber::text) AS taxaccount,
- comma(tk.pos_ustva::text) AS tk_ustva,
+ array_agg(tk.startdate) AS startdates,
+ array_agg(tk.taxkey_id) AS taxkeys,
+ array_agg(tx.taxdescription || to_char (tx.rate, '99V99' ) || '%') AS taxdescriptions,
+ array_agg(taxchart.accno) AS taxaccounts,
+ array_agg(tk.pos_ustva) AS pos_ustvas,
( SELECT accno
FROM chart c2
WHERE c2.id = c.id
FROM chart c
LEFT JOIN taxkeys tk ON (c.id = tk.chart_id)
LEFT JOIN tax tx ON (tk.tax_id = tx.id)
+ LEFT JOIN chart taxchart ON (taxchart.id = tx.chart_id)
WHERE 1=1
$where
GROUP BY c.accno, c.id, c.description, c.charttype,
# get all transactions
$query =
- qq|SELECT a.id, a.reference, a.description, ac.transdate, ac.chart_id, | .
+ qq|SELECT ac.itime, a.id, a.reference, a.description, ac.transdate, ac.chart_id, | .
qq| FALSE AS invoice, ac.amount, 'gl' as module, | .
qq§(SELECT accno||'--'||rate FROM tax LEFT JOIN chart ON (tax.chart_id=chart.id) WHERE tax.id = (SELECT tax_id FROM taxkeys WHERE taxkey_id = ac.taxkey AND taxkeys.startdate <= ac.transdate ORDER BY taxkeys.startdate DESC LIMIT 1)) AS taxinfo, ac.source || ' ' || ac.memo AS memo § .
qq|FROM acc_trans ac, gl a | .
qq|UNION ALL | .
- qq|SELECT a.id, a.invnumber, c.name, ac.transdate, ac.chart_id, | .
+ qq|SELECT ac.itime, a.id, a.invnumber, c.name, ac.transdate, ac.chart_id, | .
qq| a.invoice, ac.amount, 'ar' as module, | .
qq§(SELECT accno||'--'||rate FROM tax LEFT JOIN chart ON (tax.chart_id=chart.id) WHERE tax.id = (SELECT tax_id FROM taxkeys WHERE taxkey_id = ac.taxkey AND taxkeys.startdate <= ac.transdate ORDER BY taxkeys.startdate DESC LIMIT 1)) AS taxinfo, ac.source || ' ' || ac.memo AS memo § .
qq|FROM acc_trans ac, customer c, ar a | .
qq|UNION ALL | .
- qq|SELECT a.id, a.invnumber, v.name, ac.transdate, ac.chart_id, | .
+ qq|SELECT ac.itime, a.id, a.invnumber, v.name, ac.transdate, ac.chart_id, | .
qq| a.invoice, ac.amount, 'ap' as module, | .
qq§(SELECT accno||'--'||rate FROM tax LEFT JOIN chart ON (tax.chart_id=chart.id) WHERE tax.id = (SELECT tax_id FROM taxkeys WHERE taxkey_id = ac.taxkey AND taxkeys.startdate <= ac.transdate ORDER BY taxkeys.startdate DESC LIMIT 1)) AS taxinfo, ac.source || ' ' || ac.memo AS memo § .
qq|FROM acc_trans ac, vendor v, ap a | .
$query .=
qq|UNION ALL | .
- qq|SELECT a.id, a.invnumber, c.name, a.transdate, | .
+ qq|SELECT ac.itime, a.id, a.invnumber, c.name, a.transdate, | .
qq| a.invoice, ac.qty * ac.sellprice AS sellprice, 'ar' as module, | .
qq§(SELECT accno||'--'||rate FROM tax LEFT JOIN chart ON (tax.chart_id=chart.id) WHERE tax.id = (SELECT tax_id FROM taxkeys WHERE taxkey_id = ac.taxkey AND taxkeys.startdate <= ac.transdate ORDER BY taxkeys.startdate DESC LIMIT 1)) AS taxinfo § .
qq|FROM ar a | .
$project .
qq|UNION ALL | .
- qq|SELECT a.id, a.invnumber, v.name, a.transdate, | .
+ qq|SELECT ac.itime, a.id, a.invnumber, v.name, a.transdate, | .
qq| a.invoice, ac.qty * ac.sellprice AS sellprice, 'ap' as module, | .
qq§(SELECT accno||'--'||rate FROM tax LEFT JOIN chart ON (tax.chart_id=chart.id) WHERE tax.id = (SELECT tax_id FROM taxkeys WHERE taxkey_id = ac.taxkey AND taxkeys.startdate <= ac.transdate ORDER BY taxkeys.startdate DESC LIMIT 1)) AS taxinfo § .
qq|FROM ap a | .
}
my $sort = grep({ $form->{sort} eq $_ } qw(transdate reference description)) ? $form->{sort} : 'transdate';
- my $sort2 = ($sort eq 'reference')?'transdate':'reference';
+ $sort = ($sort eq 'transdate') ? 'transdate, itime' : $sort;
+ my $sort2 = ($sort eq 'reference') ? 'transdate, itime' : 'reference';
$query .= qq|ORDER BY $sort , $sort2 |;
my $sth = prepare_execute_query($form, $dbh, $query, @values);
"salesman" => "e.name",
"payment" => "pt.description",
"pricegroup" => "pg.pricegroup",
+ "ustid" => "ct.ustid",
+ "creditlimit" => "ct.creditlimit",
+ "commercial_court" => "ct.commercial_court",
);
$form->{sort} ||= "name";
}
my $sortdir = !defined $form->{sortdir} ? 'ASC' : $form->{sortdir} ? 'ASC' : 'DESC';
- if ($sortorder !~ /(business|id|discount|itime)/ && !$join_records) {
+ if ($sortorder !~ /(business|creditlimit|id|discount|itime)/ && !$join_records) {
$sortorder = "lower($sortorder) ${sortdir}";
} else {
$sortorder .= " ${sortdir}";
# ## other stuff ##
redirect_to => 1, # window.location.href = <TARGET>
+ save_file => 4, # kivi.save_file(<TARGET>, <ARGS>)
flash => 2, # kivi.display_flash(<TARGET>, <ARGS>)
flash_detail => 2, # kivi.display_flash_detail(<TARGET>, <ARGS>)
foreach my $item (qw(tmpdir tmpfile type)){
next if $form->{$item};
$::lxdebug->message(LXDebug::WARN(), 'Missing parameter:' . $item);
- $::form->error($::locale->text("Missing parameter for WebDAV file copy"));
+ $::lxdebug->leave_sub();
+ return $::locale->text("Missing parameter for WebDAV file copy");
}
my ($webdav_folder, $document_name) = get_webdav_folder($form);
if (! $webdav_folder){
- $::lxdebug->leave_sub();
$::lxdebug->message(LXDebug::WARN(), 'Cannot check correct WebDAV folder');
- $::form->error($::locale->text("Cannot check correct WebDAV folder"));
- return undef;
+ $::lxdebug->leave_sub();
+ return $::locale->text("Cannot check correct WebDAV folder")
}
$complete_path = File::Spec->catfile($form->{cwd}, $webdav_folder);
chdir($current_dir);
}
- opendir my $dh, $complete_path or die "Could not open $complete_path: $!";
+ my $dh;
+ if (!opendir $dh, $complete_path) {
+ $::lxdebug->leave_sub();
+ return "Could not open $complete_path: $!";
+ }
my ($newest_name, $newest_time);
while ( defined( my $file = readdir( $dh ) ) ) {
if (!File::Copy::copy($current_file, $new_file)) {
$::lxdebug->message(LXDebug::WARN(), "Copy file from $current_file to $new_file failed: $ERRNO");
- $::form->error($::locale->text("Copy file from #1 to #2 failed: #3", $current_file, $new_file, $ERRNO));
+ $::lxdebug->leave_sub();
+ return $::locale->text("Copy file from #1 to #2 failed: #3", $current_file, $new_file, $ERRNO);
}
+ return;
$::lxdebug->leave_sub();
}
$self->report_generator_list_objects(report => $self->{report}, objects => $self->models->get);
}
-sub action_list {
- my ($self) = @_;
-
- if (!$::form->{filter}{bank_account}) {
- flash('error', t8('No bank account chosen!'));
- $self->action_search;
- return;
- }
+sub gather_bank_transactions_and_proposals {
+ my ($self, %params) = @_;
- my $sort_by = $::form->{sort_by} || 'transdate';
+ my $sort_by = $params{sort_by} || 'transdate';
$sort_by = 'transdate' if $sort_by eq 'proposal';
- $sort_by .= $::form->{sort_dir} ? ' DESC' : ' ASC';
-
- my $fromdate = $::locale->parse_date_to_object($::form->{filter}->{fromdate});
- my $todate = $::locale->parse_date_to_object($::form->{filter}->{todate});
- $todate->add( days => 1 ) if $todate;
+ $sort_by .= $params{sort_dir} ? ' DESC' : ' ASC';
my @where = ();
- push @where, (transdate => { ge => $fromdate }) if ($fromdate);
- push @where, (transdate => { lt => $todate }) if ($todate);
- my $bank_account = SL::DB::Manager::BankAccount->find_by( id => $::form->{filter}{bank_account} );
+ push @where, (transdate => { ge => $params{fromdate} }) if $params{fromdate};
+ push @where, (transdate => { lt => $params{todate} }) if $params{todate};
# bank_transactions no younger than starting date,
# including starting date (same search behaviour as fromdate)
# but OPEN invoices to be matched may be from before
- if ( $bank_account->reconciliation_starting_date ) {
- push @where, (transdate => { ge => $bank_account->reconciliation_starting_date });
+ if ( $params{bank_account}->reconciliation_starting_date ) {
+ push @where, (transdate => { ge => $params{bank_account}->reconciliation_starting_date });
};
my $bank_transactions = SL::DB::Manager::BankTransaction->get_all(
limit => 10000,
where => [
amount => {ne => \'invoice_amount'},
- local_bank_account_id => $::form->{filter}{bank_account},
+ local_bank_account_id => $params{bank_account}->id,
cleared => 0,
@where
],
with_objects => ['customer','payment_terms']);
my $all_open_ap_invoices = SL::DB::Manager::PurchaseInvoice->get_all(where => [amount => { ne => \'paid' }], with_objects => ['vendor' ,'payment_terms']);
- my $all_open_sepa_export_items = SL::DB::Manager::SepaExportItem->get_all(where => [chart_id => $bank_account->chart_id ,
+ my $all_open_sepa_export_items = SL::DB::Manager::SepaExportItem->get_all(where => [chart_id => $params{bank_account}->chart_id ,
'sepa_export.executed' => 0, 'sepa_export.closed' => 0 ], with_objects => ['sepa_export']);
my @all_open_invoices;
push @proposals, @otherproposals;
# sort bank transaction proposals by quality (score) of proposal
- if ($::form->{sort_by} && $::form->{sort_by} eq 'proposal') {
- if ($::form->{sort_dir}) {
- $bank_transactions = [ sort { $a->{agreement} <=> $b->{agreement} } @{ $bank_transactions } ];
- } else {
- $bank_transactions = [ sort { $b->{agreement} <=> $a->{agreement} } @{ $bank_transactions } ];
- }
+ if ($params{sort_by} && $params{sort_by} eq 'proposal') {
+ my $dir = $params{sort_dir} ? 1 : -1;
+ $bank_transactions = [ sort { ($a->{agreement} <=> $b->{agreement}) * $dir } @{ $bank_transactions } ];
}
- # for testing with t/bank/banktransaction.t :
- if ( $::form->{dont_render_for_test} ) {
- return ( $bank_transactions , \@proposals );
+ return ( $bank_transactions , \@proposals );
+}
+
+sub action_list {
+ my ($self) = @_;
+
+ if (!$::form->{filter}{bank_account}) {
+ flash('error', t8('No bank account chosen!'));
+ $self->action_search;
+ return;
}
+ my $bank_account = SL::DB::BankAccount->load_cached($::form->{filter}->{bank_account});
+ my $fromdate = $::locale->parse_date_to_object($::form->{filter}->{fromdate});
+ my $todate = $::locale->parse_date_to_object($::form->{filter}->{todate});
+ $todate->add( days => 1 ) if $todate;
+
+ my ($bank_transactions, $proposals) = $self->gather_bank_transactions_and_proposals(
+ bank_account => $bank_account,
+ fromdate => $fromdate,
+ todate => $todate,
+ sort_by => $::form->{sort_by},
+ sort_dir => $::form->{sort_dir},
+ );
+
$::request->layout->add_javascripts("kivi.BankTransaction.js");
$self->render('bank_transactions/list',
title => t8('Bank transactions MT940'),
BANK_TRANSACTIONS => $bank_transactions,
- PROPOSALS => \@proposals,
+ PROPOSALS => $proposals,
bank_account => $bank_account,
- ui_tab => scalar(@proposals) > 0?1:0,
+ ui_tab => scalar(@{ $proposals }) > 0 ? 1 : 0,
);
}
use Carp;
use IO::File;
use List::Util qw(first);
+use MIME::Base64;
use SL::Request qw(flatten);
use SL::MoreCommon qw(uri_encode);
use SL::Presenter;
SL::Helper::Flash::delay_flash();
}
- return $self->render(SL::ClientJS->new->redirect_to($self->url_for(@_))) if $::request->is_ajax;
+ return $self->render(SL::ClientJS->new->redirect_to($url)) if $::request->is_ajax;
print $::request->{cgi}->redirect($url);
}
header => 1,
layout => 1,
process => 1,
+ status => '200 ok',
);
$options->{$_} //= $defaults{$_} for keys %defaults;
$options->{type} = lc $options->{type};
: 'application/json';
print $::form->create_http_response(content_type => $content_type,
- charset => 'UTF-8');
+ charset => 'UTF-8',
+ (status => $options->{status}) x !!$options->{status});
}
}
my $attachment_name = $params{name} || (!ref($file_name_or_content) ? $file_name_or_content : '');
$attachment_name =~ s:.*//::g;
- print $::form->create_http_response(content_type => $content_type,
- content_disposition => 'attachment; filename="' . $attachment_name . '"',
- content_length => $size);
-
- if (!ref $file_name_or_content) {
- $::locale->with_raw_io(\*STDOUT, sub { print while <$file> });
- $file->close;
- unlink $file_name_or_content if $params{unlink};
+ if ($::request->is_ajax || $params{ajax}) {
+ my $octets = ref $file_name_or_content ? $file_name_or_content : \ do { local $/ = undef; <$file> };
+ $self->js->save_file(MIME::Base64::encode_base64($$octets), $content_type, $size, $attachment_name);
+ $self->js->render unless $params{js_no_render};
} else {
- $::locale->with_raw_io(\*STDOUT, sub { print $$file_name_or_content });
+ print $::form->create_http_response(content_type => $content_type,
+ content_disposition => 'attachment; filename="' . $attachment_name . '"',
+ content_length => $size);
+
+ if (!ref $file_name_or_content) {
+ $::locale->with_raw_io(\*STDOUT, sub { print while <$file> });
+ $file->close;
+ unlink $file_name_or_content if $params{unlink};
+ } else {
+ $::locale->with_raw_io(\*STDOUT, sub { print $$file_name_or_content });
+ }
}
return 1;
use SL::PriceSource::ALL;
use SL::Template;
use SL::Controller::TopQuickSearch;
+use SL::DB::Helper::AccountingPeriod qw(get_balance_startdate_method_options);
use SL::Helper::ShippedQty;
+use SL::VATIDNr;
__PACKAGE__->run_before('check_auth');
}
}
+ my $cleaned_ustid = SL::VATIDNr->clean($defaults->{co_ustid});
+ if ($cleaned_ustid && !SL::VATIDNr->validate($cleaned_ustid)) {
+ push @errors, t8("The VAT ID number '#1' is invalid.", $defaults->{co_ustid});
+ }
+
# Show form again if there were any errors. Nothing's been changed
# yet in the database.
if (@errors) {
}
sub init_balance_startdate_method_options {
- [ { title => t8("After closed period"), value => "closed_to" },
- { title => t8("Start of year"), value => "start_of_year" },
- { title => t8("All transactions"), value => "all_transactions" },
- { title => t8("Last opening balance or all transactions"), value => "last_ob_or_all_transactions" },
- { title => t8("Last opening balance or start of year"), value => "last_ob_or_start_of_year" }, ]
+ return SL::DB::Helper::AccountingPeriod::get_balance_startdate_method_options;
}
sub init_all_price_sources {
use SL::Controller::CsvImport::Shipto;
use SL::Controller::CsvImport::Project;
use SL::Controller::CsvImport::Order;
+use SL::Controller::CsvImport::DeliveryOrder;
use SL::Controller::CsvImport::ARTransaction;
use SL::JSON;
use SL::Controller::CsvImport::BankTransaction;
use SL::BackgroundJob::CsvImport;
use SL::System::TaskServer;
-use List::MoreUtils qw(none);
+use List::MoreUtils qw(any none);
use List::Util qw(min);
use parent qw(SL::Controller::Base);
$::form->error(t8('No report with id #1', $report_id));
}
- my $num_rows = $self->{report}->numrows;
+ my $show_info_err = ($self->{report}->profile->get('full_preview', 0) == 1);
+ my $show_first_20 = ($self->{report}->profile->get('full_preview', 0) == 2);
+
+ my $num_rows = 0;
+ if ($show_first_20) {
+ $num_rows = min($self->{report}->numrows, 20);
+ } elsif ($show_info_err) {
+ # count each status row only once
+ $num_rows = SL::DB::Manager::CsvImportReportStatus->get_all_count(query => [csv_import_report_id => $report_id],
+ select => ['row'],
+ distinct => 1,);
+ } else {
+ # show all
+ $num_rows = $self->{report}->numrows;
+ }
# manual paginating, yuck
my $page = $::form->{page} || 1;
my $last_row_header = $self->{report_numheaders} - 1;
my $first_row_data = $pages->{per_page} * ($pages->{page}-1) + $self->{report_numheaders};
my $last_row_data = min($pages->{per_page} * $pages->{page}, $num_rows) + $self->{report_numheaders} - 1;
- $self->{display_rows} = [
- $first_row_header
- ..
- $last_row_header,
- $first_row_data
- ..
- $last_row_data
- ];
+
+
+ $self->{display_rows} = [];
+ if ($show_info_err) {
+ my $limit = $last_row_data - $first_row_data + 1;
+ my $offset = $first_row_data - $self->{report_numheaders};
+ my @err_rows = map { $_->row } @{SL::DB::Manager::CsvImportReportStatus->get_all(query => [csv_import_report_id => $report_id],
+ distinct => 1,
+ select => ['row'],
+ limit => $limit,
+ offset => $offset,
+ sort_by => 'row')};
+ $self->{display_rows} = [ $first_row_header .. $last_row_header,
+ @err_rows ];
+
+ } else {
+
+ $self->{display_rows} = [ $first_row_header .. $last_row_header,
+ $first_row_data .. $last_row_data ];
+ }
my @query = (
+ row => $self->{display_rows},
csv_import_report_id => $report_id,
- or => [
- and => [
- row => { ge => $first_row_header },
- row => { le => $last_row_header },
- ],
- and => [
- row => { ge => $first_row_data },
- row => { le => $last_row_data },
- ]
- ]
);
- my $rows = SL::DB::Manager::CsvImportReportRow ->get_all(query => \@query);
- my $status = SL::DB::Manager::CsvImportReportStatus->get_all(query => \@query);
+ my $rows = SL::DB::Manager::CsvImportReportRow ->get_all(query => \@query, sort_by => 'row');
+ my $status = SL::DB::Manager::CsvImportReportStatus->get_all(query => \@query, sort_by => 'row');
+ $self->{num_errors} = SL::DB::Manager::CsvImportReportStatus->get_all_count(query => [csv_import_report_id => $report_id, type => 'errors']);
$self->{report_rows} = $self->{report}->folded_rows(rows => $rows);
$self->{report_status} = $self->{report}->folded_status(status => $status);
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 bank_transactions ar_transactions);
+ 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);
$self->type($::form->{profile}->{type});
}
: $self->type eq 'inventories' ? $::locale->text('CSV import: inventories')
: $self->type eq 'projects' ? $::locale->text('CSV import: projects')
: $self->type eq 'orders' ? $::locale->text('CSV import: orders')
+ : $self->type eq 'delivery_orders' ? $::locale->text('CSV import: delivery orders')
: $self->type eq 'bank_transactions' ? $::locale->text('CSV import: bank transactions')
: $self->type eq 'ar_transactions' ? $::locale->text('CSV import: ar transactions')
: die;
- if ($self->{type} eq 'customers_vendors' or $self->{type} eq 'orders' or $self->{type} eq 'ar_transactions' ) {
+ if ( any { $_ eq $self->{type} } qw(customers_vendors orders delivery_orders ar_transactions) ) {
$self->all_taxzones(SL::DB::Manager::TaxZone->get_all_sorted(query => [ obsolete => 0 ]));
};
$::form->{settings}->{sellprice_adjustment} = $::form->parse_amount(\%::myconfig, $::form->{settings}->{sellprice_adjustment});
}
- if ($self->type eq 'orders') {
+ if ($self->type eq 'orders' or $self->{type} eq 'ar_transactions') {
$::form->{settings}->{max_amount_diff} = $::form->parse_amount(\%::myconfig, $::form->{settings}->{max_amount_diff});
}
: $self->{type} eq 'inventories' ? SL::Controller::CsvImport::Inventory->new(@args)
: $self->{type} eq 'projects' ? SL::Controller::CsvImport::Project->new(@args)
: $self->{type} eq 'orders' ? SL::Controller::CsvImport::Order->new(@args)
+ : $self->{type} eq 'delivery_orders' ? SL::Controller::CsvImport::DeliveryOrder->new(@args)
: $self->{type} eq 'bank_transactions' ? SL::Controller::CsvImport::BankTransaction->new(@args)
: $self->{type} eq 'ar_transactions' ? SL::Controller::CsvImport::ARTransaction->new(@args)
: die "Program logic error";
{ name => 'projectnumber', description => $::locale->text('Project (number)') },
{ name => 'project', description => $::locale->text('Project (description)') },
{ name => 'amount', description => $::locale->text('Amount') },
- { name => 'chart', description => $::locale->text('Account number') },
+ { name => 'accno', description => $::locale->text('Account number') },
{ name => 'taxkey', description => $::locale->text('Taxkey') },
);
}
my $i = 0;
my $num_data = scalar @{ $self->controller->data };
+ my $invoice_entry;
foreach my $entry (@{ $self->controller->data }) {
$self->controller->track_progress(progress => $i/$num_data * 100) if $i % 100 == 0;
if ($entry->{raw_data}->{datatype} eq $self->_ar_column) {
$self->handle_invoice($entry);
+ $invoice_entry = $entry;
} elsif ($entry->{raw_data}->{datatype} eq $self->_transaction_column ) {
- $self->handle_transaction($entry);
+ die "Cannot process transaction row without an invoice row" if !$invoice_entry;
+ $self->handle_transaction($entry, $invoice_entry);
} else {
die "unknown datatype";
};
}
sub check_taxkey {
- my ($self, $entry, $chart) = @_;
+ my ($self, $entry, $invoice_entry, $chart) = @_;
die "check_taxkey needs chart object as an argument" unless ref($chart) eq 'SL::DB::Chart';
# problem: taxkey is not unique in table tax, normally one of those entries is chosen directly from a dropdown
# so we check if the chart has an active taxkey, and if it matches the taxkey from the import, use the active taxkey
# if the chart doesn't have an active taxkey, use the first entry from Tax that matches the taxkey
- my $object = $entry->{object};
+ my $object = $entry->{object};
+ my $invoice_object = $invoice_entry->{object};
+
unless ( defined $entry->{raw_data}->{taxkey} ) {
push @{ $entry->{errors} }, $::locale->text('Error: taxkey missing'); # don't just assume 0, force taxkey in import
return 0;
};
- my $tax;
-
- if ( $entry->{raw_data}->{taxkey} == $chart->get_active_taxkey->tax->taxkey ) {
- $tax = $chart->get_active_taxkey->tax;
- } else {
+ my $tax = $chart->get_active_taxkey($invoice_object->deliverydate // $invoice_object->transdate // DateTime->today_local)->tax;
+ if ( $entry->{raw_data}->{taxkey} != $tax->taxkey ) {
# assume there is only one tax entry with that taxkey, can't guess
$tax = SL::DB::Manager::Tax->get_first( where => [ taxkey => $entry->{raw_data}->{taxkey} ]);
};
};
sub handle_transaction {
- my ($self, $entry) = @_;
+ my ($self, $entry, $invoice_entry) = @_;
# Prepare acc_trans data. amount is dealt with in add_transactions_to_ar
return 0;
};
- if ( $self->check_taxkey($entry, $chart_obj) ) {
+ if ( $self->check_taxkey($entry, $invoice_entry, $chart_obj) ) {
# do nothing, taxkey was assigned, just continue
} else {
# missing taxkey, don't do anything
my $object = $entry->{object};
- my $purpose = join(' ', $entry->{raw_data}->{purpose},
- $entry->{raw_data}->{purpose1},
- $entry->{raw_data}->{purpose2},
- $entry->{raw_data}->{purpose3},
- $entry->{raw_data}->{purpose4},
- $entry->{raw_data}->{purpose5},
- $entry->{raw_data}->{purpose6},
- $entry->{raw_data}->{purpose7},
- $entry->{raw_data}->{purpose8},
- $entry->{raw_data}->{purpose9},
- $entry->{raw_data}->{purpose10},
- $entry->{raw_data}->{purpose11},
- $entry->{raw_data}->{purpose12},
- $entry->{raw_data}->{purpose13} );
+ my $purpose =
+ join ' ',
+ grep { ($_ // '') !~ m{^ *$} }
+ map { $entry->{raw_data}->{"purpose$_"} }
+ ('', 1..13);
+
$object->purpose($purpose);
}
push @{ $entry->{errors} }, $::locale->text('Error when saving: #1', $object->db->error);
} else {
$self->_save_history($object);
+ $self->save_additions($object);
$self->controller->num_imported($self->controller->num_imported + 1);
}
}
return @cleaned_fields;
}
+sub save_additions {
+ my ($self, $object) = @_;
+
+ # Can be overridden by derived specialized importer classes to save
+ # additional tables (e.g. record links).
+ # This sub is called after the object is saved successfully in an transaction.
+
+ return;
+}
+
sub _save_history {
my ($self, $object) = @_;
- if (any { $self->controller->{type} && $_ eq $self->controller->{type} } qw(parts customers_vendors orders ar_transactions)) {
+ if (any { $self->controller->{type} && $_ eq $self->controller->{type} } qw(parts customers_vendors orders delivery_orders ar_transactions)) {
my $snumbers = $self->controller->{type} eq 'parts' ? 'partnumber_' . $object->partnumber
: $self->controller->{type} eq 'customers_vendors' ?
($self->table eq 'customer' ? 'customernumber_' . $object->customernumber : 'vendornumber_' . $object->vendornumber)
: $self->controller->{type} eq 'orders' ? 'ordnumber_' . $object->ordnumber
+ : $self->controller->{type} eq 'delivery_orders' ? 'donumber_' . $object->donumber
: $self->controller->{type} eq 'ar_transactions' ? 'invnumber_' . $object->invnumber
: '';
if ($self->controller->{type} eq 'orders') {
$what_done = $object->customer_id ? 'sales_order' : 'purchase_order';
}
+ if ($self->controller->{type} eq 'delivery_orders') {
+ $what_done = $object->customer_id ? 'sales_delivery_order' : 'purchase_delivery_order';
+ }
SL::DB::History->new(
trans_id => $object->id,
$::myconfig{numberformat} = $old_numberformat;
}
+sub init_manager_class {
+ my ($self) = @_;
+
+ my @manager_classes;
+ foreach my $class (@{ $self->class }) {
+ $class =~ m/^SL::DB::(.+)/;
+ push @manager_classes, "SL::DB::Manager::" . $1;
+ }
+ $self->manager_class(\@manager_classes);
+}
+
sub add_columns {
my ($self, $row_ident, @columns) = @_;
--- /dev/null
+package SL::Controller::CsvImport::DeliveryOrder;
+
+
+use strict;
+
+use List::Util qw(first);
+use List::MoreUtils qw(any none uniq);
+use DateTime;
+
+use SL::Controller::CsvImport::Helper::Consistency;
+use SL::DB::DeliveryOrder;
+use SL::DB::DeliveryOrderItem;
+use SL::DB::DeliveryOrderItemsStock;
+use SL::DB::Part;
+use SL::DB::PaymentTerm;
+use SL::DB::Contact;
+use SL::DB::PriceFactor;
+use SL::DB::Pricegroup;
+use SL::DB::Shipto;
+use SL::DB::Unit;
+use SL::DB::Inventory;
+use SL::DB::TransferType;
+use SL::DBUtils;
+use SL::PriceSource;
+use SL::TransNumber;
+use SL::Util qw(trim);
+
+use parent qw(SL::Controller::CsvImport::BaseMulti);
+
+
+use Rose::Object::MakeMethods::Generic
+(
+ 'scalar --get_set_init' => [ qw(settings languages_by all_parts parts_by part_counts_by
+ contacts_by ct_shiptos_by
+ price_factors_by pricegroups_by units_by
+ warehouses_by bins_by transfer_types_by) ],
+);
+
+
+sub init_class {
+ my ($self) = @_;
+ $self->class(['SL::DB::DeliveryOrder', 'SL::DB::DeliveryOrderItem', 'SL::DB::DeliveryOrderItemsStock']);
+}
+
+sub set_profile_defaults {
+ my ($self) = @_;
+
+ $self->controller->profile->_set_defaults(
+ order_column => $::locale->text('DeliveryOrder'),
+ item_column => $::locale->text('OrderItem'),
+ stock_column => $::locale->text('StockInfo'),
+ ignore_faulty_positions => 0,
+ );
+};
+
+sub init_settings {
+ my ($self) = @_;
+
+ return { map { ( $_ => $self->controller->profile->get($_) ) } qw(order_column item_column stock_column ignore_faulty_positions) };
+}
+
+sub init_cvar_configs_by {
+ my ($self) = @_;
+
+ my $item_cvar_configs = SL::DB::Manager::CustomVariableConfig->get_all(where => [ module => 'IC' ]);
+ $item_cvar_configs = [grep { $_->has_flag('editable') } @{ $item_cvar_configs }];
+
+ my $ccb;
+ $ccb->{class}->{$self->class->[0]} = [];
+ $ccb->{class}->{$self->class->[1]} = $item_cvar_configs;
+ $ccb->{class}->{$self->class->[2]} = [];
+ $ccb->{row_ident}->{$self->_order_column} = [];
+ $ccb->{row_ident}->{$self->_item_column} = $item_cvar_configs;
+ $ccb->{row_ident}->{$self->_stock_column} = [];
+
+ return $ccb;
+}
+
+sub init_profile {
+ my ($self) = @_;
+
+ my $profile = $self->SUPER::init_profile;
+
+ # SUPER::init_profile sets row_ident to the translated class name
+ # overwrite it with the user specified settings
+ foreach my $p (@{ $profile }) {
+ $p->{row_ident} = $self->_order_column if $p->{class} eq $self->class->[0];
+ $p->{row_ident} = $self->_item_column if $p->{class} eq $self->class->[1];
+ $p->{row_ident} = $self->_stock_column if $p->{class} eq $self->class->[2];
+ }
+
+ foreach my $p (@{ $profile }) {
+ my $prof = $p->{profile};
+ if ($p->{row_ident} eq $self->_order_column) {
+ # no need to handle
+ delete @{$prof}{qw(oreqnumber)};
+ }
+ if ($p->{row_ident} eq $self->_item_column) {
+ # no need to handle
+ delete @{$prof}{qw(delivery_order_id)};
+ }
+ if ($p->{row_ident} eq $self->_stock_column) {
+ # no need to handle
+ delete @{$prof}{qw(delivery_order_item_id)};
+ delete @{$prof}{qw(bestbefore)} if !$::instance_conf->get_show_bestbefore;
+ }
+ }
+
+ return $profile;
+}
+
+sub init_existing_objects {
+ my ($self) = @_;
+
+ # only use objects of main class (the first one)
+ eval "require " . $self->class->[0];
+ $self->existing_objects($self->manager_class->[0]->get_all);
+}
+
+sub get_duplicate_check_fields {
+ return {
+ donumber => {
+ label => $::locale->text('Delivery Order Number'),
+ default => 1,
+ std_check => 1,
+ maker => sub {
+ my ($object, $worker) = @_;
+ return if ref $object ne $worker->class->[0];
+ return $object->donumber;
+ },
+ },
+ };
+}
+
+sub check_std_duplicates {
+ my $self = shift;
+
+ my $duplicates = {};
+
+ my $all_fields = $self->get_duplicate_check_fields();
+
+ foreach my $key (keys(%{ $all_fields })) {
+ if ( $self->controller->profile->get('duplicates_'. $key) && (!exists($all_fields->{$key}->{std_check}) || $all_fields->{$key}->{std_check} ) ) {
+ $duplicates->{$key} = {};
+ }
+ }
+
+ my @duplicates_keys = keys(%{ $duplicates });
+
+ if ( !scalar(@duplicates_keys) ) {
+ return;
+ }
+
+ if ( $self->controller->profile->get('duplicates') eq 'check_db' ) {
+ foreach my $object (@{ $self->existing_objects }) {
+ foreach my $key (@duplicates_keys) {
+ my $value = exists($all_fields->{$key}->{maker}) ? $all_fields->{$key}->{maker}->($object, $self) : $object->$key;
+ $duplicates->{$key}->{$value} = 'db';
+ }
+ }
+ }
+
+ # only check order rows
+ foreach my $entry (@{ $self->controller->data }) {
+ if ($entry->{raw_data}->{datatype} ne $self->_order_column) {
+ next;
+ }
+ if ( @{ $entry->{errors} } ) {
+ next;
+ }
+
+ my $object = $entry->{object};
+
+ foreach my $key (@duplicates_keys) {
+ my $value = exists($all_fields->{$key}->{maker}) ? $all_fields->{$key}->{maker}->($object, $self) : $object->$key;
+
+ if ( exists($duplicates->{$key}->{$value}) ) {
+ push(@{ $entry->{errors} }, $duplicates->{$key}->{$value} eq 'db' ? $::locale->text('Duplicate in database') : $::locale->text('Duplicate in CSV file'));
+ last;
+ } else {
+ $duplicates->{$key}->{$value} = 'csv';
+ }
+
+ }
+ }
+}
+
+sub setup_displayable_columns {
+ my ($self) = @_;
+
+ $self->SUPER::setup_displayable_columns;
+
+ $self->add_cvar_columns_to_displayable_columns($self->_order_column);
+
+ $self->add_displayable_columns($self->_order_column,
+ { name => 'datatype', description => $self->_order_column . ' [1]' },
+ { name => 'closed', description => $::locale->text('Closed') },
+ { name => 'contact', description => $::locale->text('Contact Person (name)') },
+ { name => 'cp_id', description => $::locale->text('Contact Person (database ID)') },
+ { name => 'currency', description => $::locale->text('Currency') },
+ { name => 'currency_id', description => $::locale->text('Currency (database ID)') },
+ { name => 'customer', description => $::locale->text('Customer (name)') },
+ { name => 'customernumber', description => $::locale->text('Customer Number') },
+ { name => 'customer_id', description => $::locale->text('Customer (database ID)') },
+ { name => 'cusordnumber', description => $::locale->text('Customer Order Number') },
+ { name => 'delivered', description => $::locale->text('Delivered') },
+ { name => 'delivery_term', description => $::locale->text('Delivery terms (name)') },
+ { name => 'delivery_term_id', description => $::locale->text('Delivery terms (database ID)') },
+ { name => 'department_id', description => $::locale->text('Department (database ID)') },
+ { name => 'department', description => $::locale->text('Department (description)') },
+ { name => 'donumber', description => $::locale->text('Delivery Order Number') },
+ { name => 'employee_id', description => $::locale->text('Employee (database ID)') },
+ { name => 'globalproject', description => $::locale->text('Document Project (description)') },
+ { name => 'globalprojectnumber', description => $::locale->text('Document Project (number)') },
+ { name => 'globalproject_id', description => $::locale->text('Document Project (database ID)') },
+ { name => 'intnotes', description => $::locale->text('Internal Notes') },
+ { name => 'is_sales', description => $::locale->text('Is sales') },
+ { name => 'language', description => $::locale->text('Language (name)') },
+ { name => 'language_id', description => $::locale->text('Language (database ID)') },
+ { name => 'notes', description => $::locale->text('Notes') },
+ { name => 'ordnumber', description => $::locale->text('Order Number') },
+ { name => 'payment', description => $::locale->text('Payment terms (name)') },
+ { name => 'payment_id', description => $::locale->text('Payment terms (database ID)') },
+ { name => 'reqdate', description => $::locale->text('Reqdate') },
+ { name => 'salesman_id', description => $::locale->text('Salesman (database ID)') },
+ { name => 'shippingpoint', description => $::locale->text('Shipping Point') },
+ { name => 'shipvia', description => $::locale->text('Ship via') },
+ { name => 'shipto_id', description => $::locale->text('Ship to (database ID)') },
+ { name => 'taxincluded', description => $::locale->text('Tax Included') },
+ { name => 'taxzone', description => $::locale->text('Tax zone (description)') },
+ { name => 'taxzone_id', description => $::locale->text('Tax zone (database ID)') },
+ { name => 'transaction_description', description => $::locale->text('Transaction description') },
+ { name => 'transdate', description => $::locale->text('Order Date') },
+ { name => 'vendor', description => $::locale->text('Vendor (name)') },
+ { name => 'vendornumber', description => $::locale->text('Vendor Number') },
+ { name => 'vendor_id', description => $::locale->text('Vendor (database ID)') },
+ );
+
+ $self->add_cvar_columns_to_displayable_columns($self->_item_column);
+
+ $self->add_displayable_columns($self->_item_column,
+ { name => 'datatype', description => $self->_item_column . ' [1]' },
+ { name => 'cusordnumber', description => $::locale->text('Customer Order Number') },
+ { name => 'description', description => $::locale->text('Description') },
+ { name => 'discount', description => $::locale->text('Discount') },
+ { name => 'lastcost', description => $::locale->text('Lastcost') },
+ { name => 'longdescription', description => $::locale->text('Long Description') },
+ { name => 'ordnumber', description => $::locale->text('Order Number') },
+ { name => 'partnumber', description => $::locale->text('Part Number') },
+ { name => 'parts_id', description => $::locale->text('Part (database ID)') },
+ { name => 'position', description => $::locale->text('position') },
+ { name => 'price_factor', description => $::locale->text('Price factor (name)') },
+ { name => 'price_factor_id', description => $::locale->text('Price factor (database ID)') },
+ { name => 'pricegroup', description => $::locale->text('Price group (name)') },
+ { name => 'pricegroup_id', description => $::locale->text('Price group (database ID)') },
+ { name => 'project', description => $::locale->text('Project (description)') },
+ { name => 'projectnumber', description => $::locale->text('Project (number)') },
+ { name => 'project_id', description => $::locale->text('Project (database ID)') },
+ { name => 'qty', description => $::locale->text('Quantity') },
+ { name => 'reqdate', description => $::locale->text('Reqdate') },
+ { name => 'sellprice', description => $::locale->text('Sellprice') },
+ { name => 'serialnumber', description => $::locale->text('Serial No.') },
+ { name => 'transdate', description => $::locale->text('Order Date') },
+ { name => 'unit', description => $::locale->text('Unit') },
+ );
+
+ $self->add_cvar_columns_to_displayable_columns($self->_stock_column);
+
+ $self->add_displayable_columns($self->_stock_column,
+ { name => 'datatype', description => $self->_stock_column . ' [1]' },
+ { name => 'warehouse', description => $::locale->text('Warehouse') },
+ { name => 'warehouse_id', description => $::locale->text('Warehouse (database ID)') },
+ { name => 'bin', description => $::locale->text('Bin') },
+ { name => 'bin_id', description => $::locale->text('Bin (database ID)') },
+ { name => 'chargenumber', description => $::locale->text('Charge number') },
+ { name => 'qty', description => $::locale->text('Quantity') },
+ { name => 'unit', description => $::locale->text('Unit') },
+ );
+ if ($::instance_conf->get_show_bestbefore) {
+ $self->add_displayable_columns($self->_stock_column,
+ { name => 'bestbefore', description => $::locale->text('Best Before') });
+ }
+}
+
+
+sub init_languages_by {
+ my ($self) = @_;
+
+ return { map { my $col = $_; ( $col => { map { ( $_->$col => $_ ) } @{ $self->all_languages } } ) } qw(id description article_code) };
+}
+
+sub init_all_parts {
+ my ($self) = @_;
+
+ return SL::DB::Manager::Part->get_all(where => [or => [ obsolete => 0, obsolete => undef ]]);
+}
+
+sub init_parts_by {
+ my ($self) = @_;
+
+ return { map { my $col = $_; ( $col => { map { ( trim($_->$col) => $_ ) } @{ $self->all_parts } } ) } qw(id partnumber ean description) };
+}
+
+sub init_part_counts_by {
+ my ($self) = @_;
+
+ my $part_counts_by;
+
+ $part_counts_by->{ean}-> {trim($_->ean)}++ for @{ $self->all_parts };
+ $part_counts_by->{description}->{trim($_->description)}++ for @{ $self->all_parts };
+
+ return $part_counts_by;
+}
+
+sub init_contacts_by {
+ my ($self) = @_;
+
+ my $all_contacts = SL::DB::Manager::Contact->get_all;
+
+ my $cby;
+ # by customer/vendor id _and_ contact person id
+ $cby->{'cp_cv_id+cp_id'} = { map { ( $_->cp_cv_id . '+' . $_->cp_id => $_ ) } @{ $all_contacts } };
+ # by customer/vendor id _and_ contact person name
+ $cby->{'cp_cv_id+cp_name'} = { map { ( $_->cp_cv_id . '+' . $_->cp_name => $_ ) } @{ $all_contacts } };
+
+ return $cby;
+}
+
+sub init_ct_shiptos_by {
+ my ($self) = @_;
+
+ my $all_ct_shiptos = SL::DB::Manager::Shipto->get_all(query => [module => 'CT']);
+
+ my $sby;
+ # by trans_id _and_ shipto_id
+ $sby->{'trans_id+shipto_id'} = { map { ( $_->trans_id . '+' . $_->shipto_id => $_ ) } @{ $all_ct_shiptos } };
+
+ return $sby;
+}
+
+sub init_price_factors_by {
+ my ($self) = @_;
+
+ my $all_price_factors = SL::DB::Manager::PriceFactor->get_all;
+ return { map { my $col = $_; ( $col => { map { ( $_->$col => $_ ) } @{ $all_price_factors } } ) } qw(id description) };
+}
+
+sub init_pricegroups_by {
+ my ($self) = @_;
+
+ my $all_pricegroups = SL::DB::Manager::Pricegroup->get_all;
+ return { map { my $col = $_; ( $col => { map { ( $_->$col => $_ ) } @{ $all_pricegroups } } ) } qw(id pricegroup) };
+}
+
+sub init_units_by {
+ my ($self) = @_;
+
+ my $all_units = SL::DB::Manager::Unit->get_all;
+ return { map { my $col = $_; ( $col => { map { ( $_->$col => $_ ) } @{ $all_units } } ) } qw(name) };
+}
+
+sub init_warehouses_by {
+ my ($self) = @_;
+
+ my $all_warehouses = SL::DB::Manager::Warehouse->get_all(query => [ or => [ invalid => 0, invalid => undef ]]);
+ return { map { my $col = $_; ( $col => { map { ( $_->$col => $_ ) } @{ $all_warehouses } } ) } qw(id description) };
+}
+
+sub init_bins_by {
+ my ($self) = @_;
+
+ my $all_bins = SL::DB::Manager::Bin->get_all();
+ my $bins_by;
+ $bins_by->{_wh_id_and_id_ident()} = { map { ( _wh_id_and_id_maker($_->warehouse_id, $_->id) => $_ ) } @{ $all_bins } };
+ $bins_by->{_wh_id_and_description_ident()} = { map { ( _wh_id_and_description_maker($_->warehouse_id, $_->description) => $_ ) } @{ $all_bins } };
+
+ return $bins_by;
+}
+
+sub init_transfer_types_by {
+ my ($self) = @_;
+
+ my $all_transfer_types = SL::DB::Manager::TransferType->get_all();
+ my $transfer_types_by;
+ $transfer_types_by->{_transfer_type_dir_and_description_ident()} = {
+ map { ( _transfer_type_dir_and_description_maker($_->direction, $_->description) => $_ ) } @{ $all_transfer_types }
+ };
+
+ return $transfer_types_by;
+}
+
+sub check_objects {
+ my ($self) = @_;
+
+ $self->controller->track_progress(phase => 'building data', progress => 0);
+
+ my $i = 0;
+ my $num_data = scalar @{ $self->controller->data };
+ my $order_entry;
+ my $item_entry;
+ foreach my $entry (@{ $self->controller->data }) {
+ $self->controller->track_progress(progress => $i/$num_data * 100) if $i % 100 == 0;
+
+ $entry->{info_data}->{datatype} = $entry->{raw_data}->{datatype};
+
+ if ($entry->{raw_data}->{datatype} eq $self->_order_column) {
+ $self->handle_order($entry);
+ $order_entry = $entry;
+ } elsif ($entry->{raw_data}->{datatype} eq $self->_item_column && $entry->{object}->can('part')) {
+ $self->handle_item($entry, $order_entry);
+ $item_entry = $entry;
+ } elsif ($entry->{raw_data}->{datatype} eq $self->_stock_column) {
+ $self->handle_stock($entry, $item_entry, $order_entry);
+ push @{ $order_entry->{errors} }, $::locale->text('Error: Stock problem') if scalar(@{$entry->{errors}}) > 0;
+ } else {
+ $order_entry = undef;
+ $item_entry = undef;
+ }
+
+ $self->handle_cvars($entry, sub_module => 'delivery_order_items');
+
+ } continue {
+ $i++;
+ }
+
+ $self->add_info_columns($self->_order_column,
+ { header => $::locale->text('Data type'), method => 'datatype' });
+ $self->add_info_columns($self->_item_column,
+ { header => $::locale->text('Data type'), method => 'datatype' });
+ $self->add_info_columns($self->_stock_column,
+ { header => $::locale->text('Data type'), method => 'datatype' });
+
+ $self->add_info_columns($self->_order_column,
+ { header => $::locale->text('Customer/Vendor'), method => 'vc_name' });
+ # Todo: access via ->[0] ok? Better: search first order column and use this
+ $self->add_columns($self->_order_column,
+ map { "${_}_id" } grep { exists $self->controller->data->[0]->{raw_data}->{$_} } qw(payment delivery_term language department globalproject taxzone cp currency));
+ $self->add_columns($self->_order_column, 'globalproject_id') if exists $self->controller->data->[0]->{raw_data}->{globalprojectnumber};
+ $self->add_columns($self->_order_column, 'cp_id') if exists $self->controller->data->[0]->{raw_data}->{contact};
+
+ $self->add_info_columns($self->_item_column,
+ { header => $::locale->text('Part Number'), method => 'partnumber' });
+ # Todo: access via ->[1] ok? Better: search first item column and use this
+ $self->add_columns($self->_item_column,
+ map { "${_}_id" } grep { exists $self->controller->data->[1]->{raw_data}->{$_} } qw(project price_factor pricegroup));
+ $self->add_columns($self->_item_column, 'project_id') if exists $self->controller->data->[1]->{raw_data}->{projectnumber};
+
+ $self->add_cvar_raw_data_columns();
+
+
+ # Check overall qtys for sales delivery orders, because they are
+ # stocked out in the end and a stock underrun can occure.
+ # Todo: let it work even with bestbefore turned off.
+ $order_entry = undef;
+ $item_entry = undef;
+ my %wanted_qtys_by_part_wh_bin_charge_bestbefore;
+ my %stock_entries_with_part_wh_bin_charge_bestbefore;
+ my %order_entries_with_part_wh_bin_charge_bestbefore;
+ foreach my $entry (@{ $self->controller->data }) {
+ if ($entry->{raw_data}->{datatype} eq $self->_order_column) {
+ if (scalar(@{ $entry->{errors} }) || !$entry->{object}->is_sales) {
+ $order_entry = undef;
+ $item_entry = undef;
+ next;
+ }
+ $order_entry = $entry;
+
+ } elsif (defined $order_entry && $entry->{raw_data}->{datatype} eq $self->_item_column) {
+ if (scalar(@{ $entry->{errors} })) {
+ $item_entry = undef;
+ next;
+ }
+ $item_entry = $entry;
+
+ } elsif (defined $item_entry && $entry->{raw_data}->{datatype} eq $self->_stock_column) {
+ my $object = $entry->{object};
+ my $key = join('+',
+ $item_entry->{object}->parts_id,
+ $object->warehouse_id,
+ $object->bin_id,
+ $object->chargenumber,
+ $object->bestbefore);
+ $wanted_qtys_by_part_wh_bin_charge_bestbefore{$key} += $object->qty;
+ push @{$order_entries_with_part_wh_bin_charge_bestbefore{$key}}, $order_entry;
+ push @{$stock_entries_with_part_wh_bin_charge_bestbefore{$key}}, $entry;
+ }
+ }
+
+ foreach my $key (keys %wanted_qtys_by_part_wh_bin_charge_bestbefore) {
+ my ($parts_id, $wh_id, $bin_id, $chargenumber, $bestbefore) = split '\+', $key;
+ my $qty = $self->get_stocked_qty($parts_id, $wh_id, $bin_id, $chargenumber, $bestbefore);
+ if ($wanted_qtys_by_part_wh_bin_charge_bestbefore{$key} > $qty) {
+
+ foreach my $stock_entry (@{ $stock_entries_with_part_wh_bin_charge_bestbefore{$key} }) {
+ push @{ $stock_entry->{errors} }, $::locale->text('Error: Stocking out would result in stock underrun');
+ }
+
+ foreach my $order_entry (uniq @{ $order_entries_with_part_wh_bin_charge_bestbefore{$key} }) {
+ my $part = $self->parts_by->{id}->{$parts_id}->displayable_name;
+ my $stock = $self->bins_by->{_wh_id_and_id_ident()}->{_wh_id_and_id_maker($wh_id, $bin_id)}->full_description;
+ my $bestbefore_obj = $::locale->parse_date_to_object($bestbefore, dateformat=>'yyyy-mm-dd');
+ my $bestbefore_text = $bestbefore_obj? $::locale->parse_date_to_object($bestbefore_obj, dateformat=>'yyyy-mm-dd')->to_kivitendo: '-';
+ my $wanted_qty = $wanted_qtys_by_part_wh_bin_charge_bestbefore{$key};
+ my $details_text = sprintf('%s (%s / %s / %s): %s > %s',
+ $part,
+ $stock,
+ $chargenumber,
+ $bestbefore_text,
+ $::form->format_amount(\%::myconfig, $wanted_qty, 2),
+ $::form->format_amount(\%::myconfig, $qty, 2));
+ push @{ $order_entry->{errors} }, $::locale->text('Error: Stocking out would result in stock underrun: #1', $details_text);
+ }
+
+ }
+ }
+
+}
+
+sub handle_order {
+ my ($self, $entry) = @_;
+
+ my $object = $entry->{object};
+
+ $object->orderitems([]);
+
+ $self->handle_order_sources($entry);
+ my $first_source_order = $object->{source_orders}->[0];
+
+ my $vc_obj;
+ if (any { $entry->{raw_data}->{$_} } qw(customer customernumber customer_id)) {
+ $self->check_vc($entry, 'customer_id');
+ $vc_obj = SL::DB::Customer->new(id => $object->customer_id)->load if $object->customer_id;
+
+ } elsif (any { $entry->{raw_data}->{$_} } qw(vendor vendornumber vendor_id)) {
+ $self->check_vc($entry, 'vendor_id');
+ $vc_obj = SL::DB::Vendor->new(id => $object->vendor_id)->load if $object->vendor_id;
+
+ } else {
+ # customer / vendor from (first) source order if not given
+ if ($first_source_order) {
+ if ($first_source_order->customer) {
+ $vc_obj = $first_source_order->customer;
+ $object->customer($first_source_order->customer);
+ } elsif ($first_source_order->vendor) {
+ $vc_obj = $first_source_order->vendor;
+ $object->vendor($first_source_order->vendor);
+ }
+ }
+ }
+
+ if (!$vc_obj) {
+ push @{ $entry->{errors} }, $::locale->text('Error: Customer/vendor missing');
+ }
+
+ $self->handle_is_sales($entry);
+ $self->check_contact($entry);
+ $self->check_language($entry);
+ $self->check_payment($entry);
+ $self->check_delivery_term($entry);
+ $self->check_department($entry);
+ $self->check_project($entry, global => 1);
+ $self->check_ct_shipto($entry);
+ $self->check_taxzone($entry);
+ $self->check_currency($entry, take_default => 0);
+
+ # copy from (first) source order if not given
+ # if no source order, then copy some values from customer/vendor
+ if ($first_source_order) {
+ foreach (qw(cusordnumber notes intnotes shippingpoint shipvia
+ transaction_description currency_id delivery_term_id
+ department_id language_id payment_id globalproject_id shipto_id
+ taxzone_id)) {
+ $object->$_($first_source_order->$_) unless $object->$_;
+ }
+ } elsif ($vc_obj) {
+ foreach (qw(currency_id delivery_term_id language_id payment_id taxzone_id)) {
+ $object->$_($vc_obj->$_) unless $object->$_;
+ }
+ $object->intnotes($vc_obj->notes) unless $object->intnotes;
+ }
+
+ $self->handle_salesman($entry);
+ $self->handle_employee($entry);
+}
+
+sub handle_item {
+ my ($self, $entry, $order_entry) = @_;
+
+ return unless $order_entry;
+
+ my $order_obj = $order_entry->{object};
+ my $object = $entry->{object};
+ $object->delivery_order_stock_entries([]);
+
+ if (!$self->check_part($entry)) {
+ if ($self->controller->profile->get('ignore_faulty_positions')) {
+ push @{ $order_entry->{information} }, $::locale->text('Warning: Faulty position ignored');
+ } else {
+ push @{ $order_entry->{errors} }, $::locale->text('Error: Faulty position in this delivery order');
+ }
+ return;
+ }
+
+ $order_obj->add_items($object);
+
+ my $part_obj = SL::DB::Part->new(id => $object->parts_id)->load;
+
+ $self->handle_item_source($entry, $order_entry);
+ $object->position($object->{source_item}->position) if $object->{source_item};
+
+ $self->handle_unit($entry);
+
+ # copy from part if not given
+ $object->description($part_obj->description) unless $object->description;
+ $object->longdescription($part_obj->notes) unless $object->longdescription;
+ $object->lastcost($part_obj->lastcost) unless defined $object->lastcost;
+
+ $self->check_project($entry, global => 0);
+ $self->check_price_factor($entry);
+ $self->check_pricegroup($entry);
+
+ $self->handle_sellprice($entry, $order_entry);
+ $self->handle_discount($entry, $order_entry);
+
+ push @{ $order_entry->{errors} }, $::locale->text('Error: Faulty position in this delivery order') if scalar(@{$entry->{errors}}) > 0;
+}
+
+sub handle_stock {
+ my ($self, $entry, $item_entry, $order_entry) = @_;
+
+ return unless $item_entry;
+
+ my $item_obj = $item_entry->{object};
+ return unless $item_obj->part;
+
+ my $order_obj = $order_entry->{object};
+ my $object = $entry->{object};
+
+ $item_obj->add_delivery_order_stock_entries($object);
+
+ $self->check_warehouse($entry);
+ $self->check_bin($entry);
+
+ $self->handle_unit($entry, $item_obj->part);
+
+ # check if enough is stocked
+ # not necessary, because overall stock underrun is checked later
+ # if ($order_obj->is_sales) {
+ # my $stocked_qty = $self->get_stocked_qty($item_obj->parts_id,
+ # $object->warehouse_id,
+ # $object->bin_id,
+ # $object->chargenumber,
+ # $object->bestbefore);
+ # if ($stocked_qty < $object->qty) {
+ # push @{ $entry->{errors} }, $::locale->text('Error: Not enough parts in stock');
+ # }
+ # }
+
+ my ($stock_info_entry, $part) = @_;
+
+ # Todo: option: should stock?
+ if (1) {
+ my $tt_key = $order_obj->is_sales
+ ? _transfer_type_dir_and_description_maker('out', 'shipped')
+ : _transfer_type_dir_and_description_maker('in', 'stock');
+ my $trans_type_id = $self->transfer_types_by->{_transfer_type_dir_and_description_ident()}{$tt_key}->id;
+
+ my $qty = $order_obj->is_sales ? -1*($object->qty) : $object->qty;
+ my $inventory = SL::DB::Inventory->new(
+ parts_id => $item_obj->parts_id,
+ warehouse_id => $object->warehouse_id,
+ bin_id => $object->bin_id,
+ trans_type_id => $trans_type_id,
+ qty => $qty,
+ chargenumber => $object->chargenumber,
+ employee_id => $order_obj->employee_id,
+ shippingdate => ($order_obj->reqdate || DateTime->today_local),
+ comment => $order_obj->transaction_description,
+ project_id => ($order_obj->globalproject_id || $item_obj->project_id),
+ );
+ $inventory->bestbefore($object->bestbefore) if $::instance_conf->get_show_bestbefore;
+ $object->{inventory_obj} = $inventory;
+ $order_obj->delivered(1);
+ }
+}
+
+sub handle_is_sales {
+ my ($self, $entry) = @_;
+
+ if (!exists $entry->{raw_data}->{is_sales}) {
+ $entry->{object}->is_sales(!!$entry->{object}->customer_id);
+ }
+}
+
+sub handle_order_sources {
+ my ($self, $entry) = @_;
+
+ my $record = $entry->{object};
+
+ $record->{source_orders} = [];
+ return $record->{source_orders} if !$record->ordnumber;
+
+ my @order_numbers = split ' ', $record->ordnumber;
+
+ my $orders = SL::DB::Manager::Order->get_all(where => [ordnumber => \@order_numbers]);
+
+ if (scalar @$orders == 0) {
+ push @{ $entry->{errors} }, $::locale->text('Error: Source order not found');
+ } elsif (scalar @$orders > 1) {
+ push @{ $entry->{errors} }, $::locale->text('Error: More than one source order found');
+ }
+
+ $record->{source_orders} = $orders;
+}
+
+sub handle_item_source {
+ my ($self, $entry, $record_entry) = @_;
+
+ my $item = $entry->{object};
+ my $record = $record_entry->{object};
+
+ return if !@{ $record->{source_orders} };
+
+ foreach my $order (@{ $record->{source_orders} }) {
+ $item->{source_item} = first { $item->parts_id == $_->parts_id && $item->qty == $_->qty} @{ $order->items_sorted };
+ last if $item->{source_item};
+ }
+}
+
+sub handle_unit {
+ my ($self, $entry, $part) = @_;
+
+ my $object = $entry->{object};
+
+ $part ||= $object->part;
+
+ # Set unit from part if not given.
+ if (!$object->unit) {
+ $object->unit($part->unit);
+ return 1;
+ }
+
+ # Check whether or not unit is valid.
+ if ($object->unit && !$self->units_by->{name}->{ $object->unit }) {
+ push @{ $entry->{errors} }, $::locale->text('Error: Invalid unit');
+ return 0;
+ }
+
+ # Check whether unit is convertible to parts unit
+ if (none { $object->unit eq $_ } map { $_->name } @{ $part->unit_obj->convertible_units }) {
+ push @{ $entry->{errors} }, $::locale->text('Error: Invalid unit');
+ return 0;
+ }
+
+ return 1;
+}
+
+sub handle_sellprice {
+ my ($self, $entry, $record_entry) = @_;
+
+ my $item = $entry->{object};
+ my $record = $record_entry->{object};
+
+ return if !$record->customervendor;
+
+ # If sellprice is given, set price source to pricegroup if given or to none.
+ if (exists $entry->{raw_data}->{sellprice}) {
+ my $price_source = SL::PriceSource->new(record_item => $item, record => $record);
+ my $price_source_spec = $item->pricegroup_id ? 'pricegroup' . '/' . $item->pricegroup_id : '';
+ my $price = $price_source->price_from_source($price_source_spec);
+ $item->active_price_source($price->source);
+
+ } else {
+
+ if ($item->{source_item}) {
+ # Set sellprice from source order item if not given. Convert with respect to unit.
+ my $sellprice = $item->{source_item}->sellprice;
+ if ($item->unit ne $item->{source_item}->unit) {
+ $sellprice = $item->unit_obj->convert_to($sellprice, $item->{source_item}->unit_obj);
+ }
+ $item->sellprice($sellprice);
+ $item->active_price_source($item->{source_item}->active_price_source);
+
+ } else {
+ # Set sellprice the best price of price source
+ my $price_source = SL::PriceSource->new(record_item => $item, record => $record);
+ my $price = $price_source->best_price;
+ if ($price) {
+ $item->sellprice($price->price);
+ $item->active_price_source($price->source);
+ } else {
+ $item->sellprice(0);
+ $item->active_price_source($price_source->price_from_source('')->source);
+ }
+ }
+ }
+}
+
+sub handle_discount {
+ my ($self, $entry, $record_entry) = @_;
+
+ my $item = $entry->{object};
+ my $record = $record_entry->{object};
+
+ return if !$record->customervendor;
+
+ # If discount is given, set discount to none.
+ if (exists $entry->{raw_data}->{discount}) {
+ my $price_source = SL::PriceSource->new(record_item => $item, record => $record);
+ my $discount = $price_source->price_from_source('');
+ $item->active_discount_source($discount->source);
+
+ } else {
+
+ if ($item->{source_item}) {
+ # Set discount from source order item if not given.
+ $item->discount($item->{source_item}->discount);
+ $item->active_discount_source($item->{source_item}->active_discount_source);
+
+ } else {
+ # Set discount the best discount of price source
+ my $price_source = SL::PriceSource->new(record_item => $item, record => $record);
+ my $discount = $price_source->best_discount;
+ if ($discount) {
+ $item->discount($discount->discount);
+ $item->active_discount_source($discount->source);
+ } else {
+ $item->discount(0);
+ $item->active_discount_source($price_source->discount_from_source('')->source);
+ }
+ }
+ }
+}
+
+sub check_contact {
+ my ($self, $entry) = @_;
+
+ my $object = $entry->{object};
+
+ my $cp_cv_id = $object->customer_id || $object->vendor_id;
+ return 0 unless $cp_cv_id;
+
+ # Check whether or not contact ID is valid.
+ if ($object->cp_id && !$self->contacts_by->{'cp_cv_id+cp_id'}->{ $cp_cv_id . '+' . $object->cp_id }) {
+ push @{ $entry->{errors} }, $::locale->text('Error: Invalid contact');
+ return 0;
+ }
+
+ # Map name to ID if given.
+ if (!$object->cp_id && $entry->{raw_data}->{contact}) {
+ my $cp = $self->contacts_by->{'cp_cv_id+cp_name'}->{ $cp_cv_id . '+' . $entry->{raw_data}->{contact} };
+ if (!$cp) {
+ push @{ $entry->{errors} }, $::locale->text('Error: Invalid contact');
+ return 0;
+ }
+
+ $object->cp_id($cp->cp_id);
+ }
+
+ if ($object->cp_id) {
+ $entry->{info_data}->{contact} = $self->contacts_by->{'cp_cv_id+cp_id'}->{ $cp_cv_id . '+' . $object->cp_id }->cp_name;
+ }
+
+ return 1;
+}
+
+sub check_language {
+ my ($self, $entry) = @_;
+
+ my $object = $entry->{object};
+
+ # Check whether or not language ID is valid.
+ if ($object->language_id && !$self->languages_by->{id}->{ $object->language_id }) {
+ push @{ $entry->{errors} }, $::locale->text('Error: Invalid language');
+ return 0;
+ }
+
+ # Map name to ID if given.
+ if (!$object->language_id && $entry->{raw_data}->{language}) {
+ my $language = $self->languages_by->{description}->{ $entry->{raw_data}->{language} }
+ || $self->languages_by->{article_code}->{ $entry->{raw_data}->{language} };
+
+ if (!$language) {
+ push @{ $entry->{errors} }, $::locale->text('Error: Invalid language');
+ return 0;
+ }
+
+ $object->language_id($language->id);
+ }
+
+ if ($object->language_id) {
+ $entry->{info_data}->{language} = $self->languages_by->{id}->{ $object->language_id }->description;
+ }
+
+ return 1;
+}
+
+sub check_ct_shipto {
+ my ($self, $entry) = @_;
+
+ my $object = $entry->{object};
+
+ my $trans_id = $object->customer_id || $object->vendor_id;
+ return 0 unless $trans_id;
+
+ # Check whether or not shipto ID is valid.
+ if ($object->shipto_id && !$self->ct_shiptos_by->{'trans_id+shipto_id'}->{ $trans_id . '+' . $object->shipto_id }) {
+ push @{ $entry->{errors} }, $::locale->text('Error: Invalid shipto');
+ return 0;
+ }
+
+ return 1;
+}
+
+sub check_part {
+ my ($self, $entry) = @_;
+
+ my $object = $entry->{object};
+ my $is_ambiguous;
+
+ # Check whether or not part ID is valid.
+ if ($object->parts_id && !$self->parts_by->{id}->{ $object->parts_id }) {
+ push @{ $entry->{errors} }, $::locale->text('Error: Part not found');
+ return 0;
+ }
+
+ # Map number to ID if given.
+ if (!$object->parts_id && $entry->{raw_data}->{partnumber}) {
+ my $part = $self->parts_by->{partnumber}->{ trim($entry->{raw_data}->{partnumber}) };
+ if (!$part) {
+ push @{ $entry->{errors} }, $::locale->text('Error: Part not found');
+ return 0;
+ }
+
+ $object->parts_id($part->id);
+ }
+
+ # Map description to ID if given.
+ if (!$object->parts_id && $entry->{raw_data}->{description}) {
+ my $part = $self->parts_by->{description}->{ trim($entry->{raw_data}->{description}) };
+ if (!$part) {
+ push @{ $entry->{errors} }, $::locale->text('Error: Part not found');
+ return 0;
+ }
+
+ if ($self->part_counts_by->{description}->{ trim($entry->{raw_data}->{description}) } > 1) {
+ $is_ambiguous = 1;
+ } else {
+ $object->parts_id($part->id);
+ }
+ }
+
+ # Map ean to ID if given.
+ if (!$object->parts_id && $entry->{raw_data}->{ean}) {
+ my $part = $self->parts_by->{ean}->{ trim($entry->{raw_data}->{ean}) };
+ if (!$part) {
+ push @{ $entry->{errors} }, $::locale->text('Error: Part not found');
+ return 0;
+ }
+
+ if ($self->part_counts_by->{ean}->{ trim($entry->{raw_data}->{ean}) } > 1) {
+ $is_ambiguous = 1;
+ } else {
+ $object->parts_id($part->id);
+ }
+ }
+
+ if ($object->parts_id) {
+ $entry->{info_data}->{partnumber} = $self->parts_by->{id}->{ $object->parts_id }->partnumber;
+ } else {
+ if ($is_ambiguous) {
+ push @{ $entry->{errors} }, $::locale->text('Error: Part is ambiguous');
+ } else {
+ push @{ $entry->{errors} }, $::locale->text('Error: Part not found');
+ }
+ return 0;
+ }
+
+ return 1;
+}
+
+sub check_price_factor {
+ my ($self, $entry) = @_;
+
+ my $object = $entry->{object};
+
+ # Check whether or not price_factor ID is valid.
+ if ($object->price_factor_id && !$self->price_factors_by->{id}->{ $object->price_factor_id }) {
+ push @{ $entry->{errors} }, $::locale->text('Error: Invalid price factor');
+ return 0;
+ }
+
+ # Map description to ID if given.
+ if (!$object->price_factor_id && $entry->{raw_data}->{price_factor}) {
+ my $price_factor = $self->price_factors_by->{description}->{ $entry->{raw_data}->{price_factor} };
+ if (!$price_factor) {
+ push @{ $entry->{errors} }, $::locale->text('Error: Invalid price factor');
+ return 0;
+ }
+
+ $object->price_factor_id($price_factor->id);
+ }
+
+ return 1;
+}
+
+sub check_pricegroup {
+ my ($self, $entry) = @_;
+
+ my $object = $entry->{object};
+
+ # Check whether or not pricegroup ID is valid.
+ if ($object->pricegroup_id && !$self->pricegroups_by->{id}->{ $object->pricegroup_id }) {
+ push @{ $entry->{errors} }, $::locale->text('Error: Invalid price group');
+ return 0;
+ }
+
+ # Map pricegroup to ID if given.
+ if (!$object->pricegroup_id && $entry->{raw_data}->{pricegroup}) {
+ my $pricegroup = $self->pricegroups_by->{pricegroup}->{ $entry->{raw_data}->{pricegroup} };
+ if (!$pricegroup) {
+ push @{ $entry->{errors} }, $::locale->text('Error: Invalid price group');
+ return 0;
+ }
+
+ $object->pricegroup_id($pricegroup->id);
+ }
+
+ return 1;
+}
+
+sub check_warehouse {
+ my ($self, $entry) = @_;
+
+ my $object = $entry->{object};
+
+ # Check whether or not warehouse ID is valid.
+ if ($object->warehouse_id && !$self->warehouses_by->{id}->{ $object->warehouse_id }) {
+ push @{ $entry->{errors} }, $::locale->text('Error: Invalid warehouse');
+ return 0;
+ }
+
+ # Map description to ID if given.
+ if (!$object->warehouse_id && $entry->{raw_data}->{warehouse}) {
+ my $wh = $self->warehouses_by->{description}->{ $entry->{raw_data}->{warehouse} };
+ if (!$wh) {
+ push @{ $entry->{errors} }, $::locale->text('Error: Invalid warehouse');
+ return 0;
+ }
+
+ $object->warehouse_id($wh->id);
+ }
+
+ if ($object->warehouse_id) {
+ $entry->{info_data}->{warehouse} = $self->warehouses_by->{id}->{ $object->warehouse_id }->description;
+ } else {
+ push @{ $entry->{errors} }, $::locale->text('Error: Warehouse not found');
+ return 0;
+ }
+
+ return 1;
+}
+
+# Check bin for given warehouse, so check_warehouse must be called first.
+sub check_bin {
+ my ($self, $entry) = @_;
+
+ my $object = $entry->{object};
+
+ # Check whether or not bin ID is valid.
+ if ($object->bin_id && !$self->bins_by->{_wh_id_and_id_ident()}->{ _wh_id_and_id_maker($object->warehouse_id, $object->bin_id) }) {
+ push @{ $entry->{errors} }, $::locale->text('Error: Invalid bin');
+ return 0;
+ }
+
+ # Map description to ID if given.
+ if (!$object->bin_id && $entry->{raw_data}->{bin}) {
+ my $bin = $self->bins_by->{_wh_id_and_description_ident()}->{ _wh_id_and_description_maker($object->warehouse_id, $entry->{raw_data}->{bin}) };
+ if (!$bin) {
+ push @{ $entry->{errors} }, $::locale->text('Error: Invalid bin');
+ return 0;
+ }
+
+ $object->bin_id($bin->id);
+ }
+
+ if ($object->bin_id) {
+ $entry->{info_data}->{bin} = $self->bins_by->{_wh_id_and_id_ident()}->{ _wh_id_and_id_maker($object->warehouse_id, $object->bin_id) }->description;
+ } else {
+ push @{ $entry->{errors} }, $::locale->text('Error: Bin not found');
+ return 0;
+ }
+
+ return 1;
+}
+
+sub save_additions {
+ my ($self, $object) = @_;
+
+ # record links
+ my $orders = delete $object->{source_orders};
+
+ if (scalar(@$orders)) {
+
+ $_->link_to_record($object) for @$orders;
+
+ foreach my $item (@{ $object->items }) {
+ my $orderitem = delete $item->{source_item};
+ $orderitem->link_to_record($item) if $orderitem;
+ }
+ }
+
+ # delivery order for all positions created?
+ if (scalar(@$orders)) {
+ foreach my $order (@{ $orders }) {
+ my $all_deliverd;
+ foreach my $orderitem (@{ $order->items }) {
+ my $delivered_qty = 0;
+ foreach my $do_item (@{$orderitem->linked_records(to => 'DeliveryOrderItem')}) {
+ $delivered_qty += $do_item->unit_obj->convert_to($do_item->qty, $orderitem->unit_obj);
+ }
+ $all_deliverd = $orderitem->qty <= $delivered_qty;
+ last if !$all_deliverd;
+ }
+ $order->update_attributes(delivered => !!$all_deliverd);
+ }
+ }
+
+ # inventory (or use WH->transfer?)
+ foreach my $item (@{ $object->items }) {
+ foreach my $stock_info (@{ $item->delivery_order_stock_entries }) {
+ my $inventory = delete $stock_info->{inventory_obj};
+ next if !$inventory;
+ my ($trans_id) = selectrow_query($::form, $object->db->dbh, qq|SELECT nextval('id')|);
+ $inventory->trans_id($trans_id);
+ $inventory->oe_id($object->id);
+ $inventory->delivery_order_items_stock_id($stock_info->id);
+ $inventory->save;
+ }
+ }
+}
+
+sub save_objects {
+ my ($self, %params) = @_;
+
+ # Collect orders without errors to save.
+ my $entries_to_save = [];
+ foreach my $entry (@{ $self->controller->data }) {
+ next if $entry->{raw_data}->{datatype} ne $self->_order_column;
+ next if @{ $entry->{errors} };
+
+ push @{ $entries_to_save }, $entry;
+ }
+
+ $self->SUPER::save_objects(data => $entries_to_save);
+}
+
+sub get_stocked_qty {
+ my ($self, $parts_id, $wh_id, $bin_id, $chargenumber, $bestbefore) = @_;
+
+ my $key = join '+', $parts_id, $wh_id, $bin_id, $chargenumber, $bestbefore;
+ return $self->{stocked_qty}->{$key} if exists $self->{stocked_qty}->{$key};
+
+ my $bestbefore_filter = '';
+ my $bestbefore_val_cnt = 0;
+ if ($::instance_conf->get_show_bestbefore) {
+ $bestbefore_filter = ($bestbefore) ? 'AND bestbefore = ?' : 'AND bestbefore IS NULL';
+ $bestbefore_val_cnt = ($bestbefore) ? 1 : 0;
+ }
+
+ my $query = <<SQL;
+ SELECT sum(qty) FROM inventory
+ WHERE parts_id = ? AND warehouse_id = ? AND bin_id = ? AND chargenumber = ? $bestbefore_filter
+ GROUP BY warehouse_id, bin_id, chargenumber
+SQL
+
+ my @values = ($parts_id,
+ $wh_id,
+ $bin_id,
+ $chargenumber);
+ push @values, $bestbefore if $bestbefore_val_cnt;
+
+ my $dbh = $self->controller->data->[0]{object}->db->dbh;
+ my ($stocked_qty) = selectrow_query($::form, $dbh, $query, @values);
+
+ $self->{stocked_qty}->{$key} = $stocked_qty;
+ return $stocked_qty;
+}
+
+sub _wh_id_and_description_ident {
+ return 'wh_id+description';
+}
+
+sub _wh_id_and_description_maker {
+ return join '+', $_[0], $_[1]
+}
+
+sub _wh_id_and_id_ident {
+ return 'wh_id+id';
+}
+
+sub _wh_id_and_id_maker {
+ return join '+', $_[0], $_[1]
+}
+
+sub _transfer_type_dir_and_description_ident {
+ return 'dir+description';
+}
+
+sub _transfer_type_dir_and_description_maker {
+ return join '+', $_[0], $_[1]
+}
+
+sub _order_column {
+ $_[0]->settings->{'order_column'}
+}
+
+sub _item_column {
+ $_[0]->settings->{'item_column'}
+}
+
+sub _stock_column {
+ $_[0]->settings->{'stock_column'}
+}
+
+1;
use SL::DBUtils;
use SL::Helper::Flash;
use SL::Locale::String;
+use SL::Util qw(trim);
+use SL::Webdav;
use SL::Controller::Helper::GetModels;
use SL::Controller::Helper::ReportGenerator;
use SL::Controller::Helper::ParseFilter;
use SL::DB::Customer;
use SL::DB::Vendor;
use SL::DB::Business;
+use SL::DB::ContactDepartment;
+use SL::DB::ContactTitle;
use SL::DB::Employee;
+use SL::DB::Greeting;
use SL::DB::Language;
use SL::DB::TaxZone;
use SL::DB::Note;
use SL::DB::PaymentTerm;
use SL::DB::Pricegroup;
+use SL::DB::Price;
use SL::DB::Contact;
use SL::DB::FollowUp;
use SL::DB::FollowUpLink;
'update',
'ajaj_get_shipto',
'ajaj_get_contact',
+ 'ajax_list_prices',
]
);
$::dispatcher->end_request;
}
+ $self->{cv}->greeting(trim $self->{cv}->greeting);
+ my $save_greeting = $self->{cv}->greeting
+ && $::instance_conf->get_vc_greetings_use_textfield
+ && SL::DB::Manager::Greeting->get_all_count(where => [description => $self->{cv}->greeting]) == 0;
+
+ $self->{contact}->cp_title(trim($self->{contact}->cp_title));
+ my $save_contact_title = $self->{contact}->cp_title
+ && $::instance_conf->get_contact_titles_use_textfield
+ && SL::DB::Manager::ContactTitle->get_all_count(where => [description => $self->{contact}->cp_title]) == 0;
+
+ $self->{contact}->cp_abteilung(trim($self->{contact}->cp_abteilung));
+ my $save_contact_department = $self->{contact}->cp_abteilung
+ && $::instance_conf->get_contact_departments_use_textfield
+ && SL::DB::Manager::ContactDepartment->get_all_count(where => [description => $self->{contact}->cp_abteilung]) == 0;
+
my $db = $self->{cv}->db;
$db->with_transaction(sub {
$self->{cv}->save(cascade => 1);
+ SL::DB::Greeting->new(description => $self->{cv}->greeting)->save if $save_greeting;
+
$self->{contact}->cp_cv_id($self->{cv}->id);
if( $self->{contact}->cp_name ne '' || $self->{contact}->cp_givenname ne '' ) {
+ SL::DB::ContactTitle ->new(description => $self->{contact}->cp_title) ->save if $save_contact_title;
+ SL::DB::ContactDepartment->new(description => $self->{contact}->cp_abteilung)->save if $save_contact_department;
+
$self->{contact}->save(cascade => 1);
}
sub action_get_delivery {
my ($self) = @_;
- $::auth->assert('sales_all_edit');
+ $::auth->assert('sales_all_edit') if $self->is_customer();
+ $::auth->assert('purchase_all_edit') if $self->is_vendor();
my $dbh = $::form->get_standard_dbh();
}
# if someone types something, and hits enter, assume he entered the full name.
- # if something matches, treat that as sole match
- # unfortunately get_models can't do more than one per package atm, so we d it
+ # if something matches, treat that as the sole match
+ # unfortunately get_models can't do more than one per package atm, so we do it
# the oldfashioned way.
if ($::form->{prefer_exact}) {
my $exact_matches;
$_[0]->render('customer_vendor/test_page');
}
+sub action_ajax_list_prices {
+ my ($self, %params) = @_;
+
+ my $report = SL::ReportGenerator->new(\%::myconfig, $::form);
+ my @columns = qw(partnumber description price);
+ my @visible = qw(partnumber description price);
+ my @sortable = qw(partnumber description price);
+
+ my %column_defs = (
+ partnumber => { text => $::locale->text('Part Number'), sub => sub { $_[0]->parts->partnumber } },
+ description => { text => $::locale->text('Part Description'), sub => sub { $_[0]->parts->description } },
+ price => { text => $::locale->text('Price'), sub => sub { $::form->format_amount(\%::myconfig, $_[0]->price, 2) }, align => 'right' },
+ );
+
+ $::form->{sort_by} ||= 'partnumber';
+ $::form->{sort_dir} //= 1;
+
+ for my $col (@sortable) {
+ $column_defs{$col}{link} = $self->url_for(
+ action => 'ajax_list_prices',
+ callback => $::form->{callback},
+ db => $::form->{db},
+ id => $self->{cv}->id,
+ sort_by => $col,
+ sort_dir => ($::form->{sort_by} eq $col ? 1 - $::form->{sort_dir} : $::form->{sort_dir})
+ );
+ }
+
+ map { $column_defs{$_}{visible} = 1 } @visible;
+
+ my $pricegroup;
+ $pricegroup = $self->{cv}->pricegroup->pricegroup if $self->{cv}->pricegroup;
+
+ $report->set_columns(%column_defs);
+ $report->set_column_order(@columns);
+ $report->set_options(allow_pdf_export => 0, allow_csv_export => 0);
+ $report->set_sort_indicator($::form->{sort_by}, $::form->{sort_dir});
+ $report->set_export_options(@{ $params{report_generator_export_options} || [] });
+ $report->set_options(
+ %{ $params{report_generator_options} || {} },
+ output_format => 'HTML',
+ top_info_text => $::locale->text('Pricegroup') . ': ' . $pricegroup,
+ title => $::locale->text('Price List'),
+ );
+
+ my $sort_param = $::form->{sort_by} eq 'price' ? 'price' :
+ $::form->{sort_by} eq 'description' ? 'parts.description' :
+ 'parts.partnumber';
+ $sort_param .= ' ' . ($::form->{sort_dir} ? 'ASC' : 'DESC');
+ my $prices = SL::DB::Manager::Price->get_all(where => [ pricegroup_id => $self->{cv}->pricegroup_id ],
+ sort_by => $sort_param,
+ with_objects => 'parts');
+
+ $self->report_generator_list_objects(report => $report, objects => $prices, layout => 0, header => 0);
+}
+
sub is_vendor {
return $::form->{db} eq 'vendor';
}
$self->{all_employees} = SL::DB::Manager::Employee->get_all(query => [ deleted => 0 ]);
- $query =
- 'SELECT DISTINCT(greeting)
- FROM customer
- WHERE greeting IS NOT NULL AND greeting != \'\'
- UNION
- SELECT DISTINCT(greeting)
- FROM vendor
- WHERE greeting IS NOT NULL AND greeting != \'\'
- ORDER BY greeting';
- $self->{all_greetings} = [
- map(
- { $_->{greeting}; }
- selectall_hashref_query($::form, $dbh, $query)
- )
- ];
-
- $query =
- 'SELECT DISTINCT(cp_title) AS title
- FROM contacts
- WHERE cp_title IS NOT NULL AND cp_title != \'\'
- ORDER BY cp_title';
- $self->{all_titles} = [
- map(
- { $_->{title}; }
- selectall_hashref_query($::form, $dbh, $query)
- )
- ];
+ $self->{all_greetings} = SL::DB::Manager::Greeting->get_all_sorted();
+ if ($self->{cv}->id && $self->{cv}->greeting && !grep {$self->{cv}->greeting eq $_->description} @{$self->{all_greetings}}) {
+ unshift @{$self->{all_greetings}}, (SL::DB::Greeting->new(description => $self->{cv}->greeting));
+ }
+
+ $self->{all_contact_titles} = SL::DB::Manager::ContactTitle->get_all_sorted();
+ foreach my $contact (@{ $self->{cv}->contacts }) {
+ if ($contact->cp_title && !grep {$contact->cp_title eq $_->description} @{$self->{all_contact_titles}}) {
+ unshift @{$self->{all_contact_titles}}, (SL::DB::ContactTitle->new(description => $contact->cp_title));
+ }
+ }
+
+ $self->{all_contact_departments} = SL::DB::Manager::ContactDepartment->get_all_sorted();
+ foreach my $contact (@{ $self->{cv}->contacts }) {
+ if ($contact->cp_abteilung && !grep {$contact->cp_abteilung eq $_->description} @{$self->{all_contact_departments}}) {
+ unshift @{$self->{all_contact_departments}}, (SL::DB::ContactDepartment->new(description => $contact->cp_abteilung));
+ }
+ }
$self->{all_currencies} = SL::DB::Manager::Currency->get_all();
$self->{all_pricegroups} = SL::DB::Manager::Pricegroup->get_all_sorted(query => [ or => [ id => $self->{cv}->pricegroup_id, obsolete => 0 ] ]);
}
- $query =
- 'SELECT DISTINCT(cp_abteilung) AS department
- FROM contacts
- WHERE cp_abteilung IS NOT NULL AND cp_abteilung != \'\'
- ORDER BY cp_abteilung';
- $self->{all_departments} = [
- map(
- { $_->{department}; }
- selectall_hashref_query($::form, $dbh, $query)
- )
- ];
-
$self->{contacts} = $self->{cv}->contacts;
$self->{contacts} ||= [];
],
);
}
+
+ if ($self->{cv}->number && $::instance_conf->get_webdav) {
+ my $webdav = SL::Webdav->new(
+ type => $self->is_customer ? 'customer'
+ : $self->is_vendor ? 'vendor'
+ : undef,
+ number => $self->{cv}->number,
+ );
+ my @all_objects = $webdav->get_all_objects;
+ @{ $self->{template_args}->{WEBDAV} } = map { { name => $_->filename,
+ type => t8('File'),
+ link => File::Spec->catfile($_->full_filedescriptor),
+ } } @all_objects;
+ }
+
$self->{template_args} ||= {};
$::request->{layout}->add_javascripts('kivi.CustomerVendor.js');
amount => { lt => \'paid'},
],
],
+ sort_by => 'transdate DESC',
with_objects => [ 'dunnings' ],
);
} else {
amount => { lt => \'paid'},
],
],
- sort_by => 'invnumber DESC',
+ sort_by => 'transdate DESC',
);
}
my $open_items;
if (@{$open_invoices}) {
- return $self->render(\'', { type => 'json' }) unless scalar @{$open_invoices};
$open_items = $self->_list_open_items($open_invoices);
}
my $open_orders = $self->_get_open_orders;
if ( $::form->{db} eq 'customer' ) {
$invoices = SL::DB::Manager::Invoice->get_all(
query => [ customer_id => $cv, ],
- sort_by => 'invnumber DESC',
+ sort_by => 'transdate DESC',
);
} else {
$invoices = SL::DB::Manager::PurchaseInvoice->get_all(
query => [ vendor_id => $cv, ],
- sort_by => 'invnumber DESC',
+ sort_by => 'transdate DESC',
);
}
$self->render('customer_vendor_turnover/invoices_statistic', { layout => 0 }, invoices => $invoices);
customer_id => $cv,
quotation => ($type eq 'quotation' ? 'T' : 'F')
],
- sort_by => ( $type eq 'order' ? 'ordnumber DESC' : 'quonumber DESC'),
+ sort_by => 'transdate DESC',
);
} else {
$orders = SL::DB::Manager::Order->get_all(
vendor_id => $cv,
quotation => ($type eq 'quotation' ? 'T' : 'F')
],
- sort_by => ( $type eq 'order' ? 'ordnumber DESC' : 'quonumber DESC'),
+ sort_by => 'transdate DESC',
);
}
if ( $type eq 'order') {
customer_id => $cv,
closed => 'F',
],
- sort_by => 'ordnumber DESC',
+ sort_by => 'transdate DESC',
);
} else {
$open_orders = SL::DB::Manager::Order->get_all(
vendor_id => $cv,
closed => 'F',
],
- sort_by => 'ordnumber DESC',
+ sort_by => 'transdate DESC',
);
}
}
sub init_all_edit_right {
- $::auth->assert('sales_all_edit', 1)
+ return $_[0]->vc eq 'customer' ? $::auth->assert('sales_all_edit', 1) : $::auth->assert('purchase_all_edit', 1);
}
sub init_vc {
return $::form->{vc} if ($::form->{vc} eq 'customer' || $::form->{vc} eq 'vendor') || croak "self (DeliveryPlan) has no vc defined";
use SL::Helper::Flash;
use SL::Controller::Helper::ReportGenerator;
use SL::Controller::Helper::GetModels;
+use List::MoreUtils qw(uniq);
use English qw(-no_match_vars);
$::form->{title} = t8('Stock');
+ # Sometimes we want to open stock_in with a part already selected, but only
+ # the parts_id is passed in the url (and not also warehouse, bin and unit).
+ # Setting select_default_bin in the form will make sure the default warehouse
+ # and bin of that part will already be preselected, as normally
+ # set_target_from_part is only called when a part is changed.
+ $self->set_target_from_part if $::form->{select_default_bin};
$::request->layout->focus('#part_id_name');
my $transfer_types = WH->retrieve_transfer_types('in');
map { $_->{description} = $main::locale->text($_->{description}) } @{ $transfer_types };
sub mini_journal {
my ($self) = @_;
- # get last 10 transaction ids
- my $query = 'SELECT trans_id, max(itime) FROM inventory GROUP BY trans_id ORDER BY max(itime) DESC LIMIT 10';
- my @ids = selectall_array_query($::form, $::form->get_standard_dbh, $query);
+ # We want to fetch the last 10 inventory events (inventory rows with the same trans_id)
+ # To prevent a Seq Scan on inventory set an index on inventory.itime
+ # Each event may have one (transfer_in/out) or two (transfer) inventory rows
+ # So fetch the last 20, group by trans_id, limit to the last 10 trans_ids,
+ # and then extract the inventory ids from those 10 trans_ids
+ # By querying Inventory->get_all via the id instead of trans_id we can make
+ # use of the existing index on id
- my $objs;
- $objs = SL::DB::Manager::Inventory->get_all(query => [ trans_id => \@ids ]) if @ids;
+ # inventory ids of the most recent 10 inventory trans_ids
+ my $query = <<SQL;
+with last_inventories as (
+ select id,
+ trans_id,
+ itime
+ from inventory
+ order by itime desc
+ limit 20
+),
+grouped_ids as (
+ select trans_id,
+ array_agg(id) as ids
+ from last_inventories
+ group by trans_id
+ order by max(itime)
+ desc limit 10
+)
+select unnest(ids)
+ from grouped_ids
+ limit 20 -- so the planner knows how many ids to expect, the cte is an optimisation fence
+SQL
- # at most 2 of them belong to a transaction and the qty determins in or out.
- # sort them for display
+ my $objs = SL::DB::Manager::Inventory->get_all(
+ query => [ id => [ \"$query" ] ], # " make emacs happy
+ with_objects => [ 'parts', 'trans_type', 'bin', 'bin.warehouse' ], # prevent lazy loading in template
+ sort_by => 'itime DESC',
+ );
+ # remember order of trans_ids from query, for ordering hash later
+ my @sorted_trans_ids = uniq map { $_->trans_id } @$objs;
+
+ # at most 2 of them belong to a transaction and the qty determines in or out.
my %transactions;
for (@$objs) {
$transactions{ $_->trans_id }{ $_->qty > 0 ? 'in' : 'out' } = $_;
$transactions{ $_->trans_id }{base} = $_;
}
- # and get them into order again
- my @sorted = map { $transactions{$_} } @ids;
+
+ # because the inventory transactions were built in a hash, we need to sort the
+ # hash by using the original sort order of the trans_ids
+ my @sorted = map { $transactions{$_} } @sorted_trans_ids;
return \@sorted;
}
my %bestbefore_filter;
if ($::instance_conf->get_show_bestbefore) {
- %bestbefore_filter = (bestbefore => $params{bestbefore});
+ %bestbefore_filter = (bestbefore => ($params{bestbefore} || undef));
}
SL::DB::Manager::Stocktaking->get_all(query => [and => [parts_id => $part->id,
return $self->js
->replaceWith(
'#letter_cp_id',
- SL::Presenter->get->select_tag('letter.cp_id', [], value_key => 'cp_id', title_key => 'full_name')
+ select_tag('letter.cp_id', [], value_key => 'cp_id', title_key => 'full_name')
)
->render;
}
my ($self, %params) = @_;
if (!$self->letter->delete) {
- flash('error', t8('An error occured. Letter could not be deleted.'));
+ flash('error', t8('An error occurred. Letter could not be deleted.'));
return $self->action_update;
}
%::myconfig = $login ? $::auth->read_user(login => $login) : ();
$::locale = Locale->new($::myconfig{countrycode}) if $::myconfig{countrycode};
- SL::Dispatcher::AuthHandler::User->new->handle;
+ my $auth_result = SL::Dispatcher::AuthHandler::User->new->handle(callback => $::form->{callback});
+
+ $::dispatcher->end_request unless $auth_result;
$::request->layout(SL::Layout::Dispatcher->new(style => $::myconfig{menustyle}));
sub show_login_form {
my ($self, %params) = @_;
- $self->render('login_screen/user_login', %params, version => SL::Version->get_version );
+ $self->render('login_screen/user_login', %params, version => SL::Version->get_version, callback => $::form->{callback});
}
1;
use SL::System::TaskServer;
use Rose::Object::MakeMethods::Generic
(
- 'scalar --get_set_init' => [ qw(invoice_models invoice_ids sales_delivery_order_models printers default_printer_id today) ],
+ 'scalar --get_set_init' => [ qw(invoice_models invoice_ids sales_delivery_order_models printers default_printer_id today all_businesses) ],
);
__PACKAGE__->run_before('setup');
return $self->redirect_to(action => 'list_invoices');
}
- $self->download_or_print_documents(printer_id => $::form->{printer_id}, invoices => \@invoices);
+ $self->download_or_print_documents(printer_id => $::form->{printer_id}, invoices => \@invoices, bothsided => $::form->{bothsided});
}
sub action_create_print_all_start {
return $pr ? $pr->id : undef;
}
+sub init_all_businesses {
+ return SL::DB::Manager::Business->get_all_sorted;
+}
+
sub setup {
my ($self) = @_;
$::auth->assert('invoice_edit');
});
@pdf_file_names = $self->create_pdfs(%pdf_params);
- my $merged_pdf = $self->merge_pdfs(file_names => \@pdf_file_names);
+ my $merged_pdf = $self->merge_pdfs(file_names => \@pdf_file_names, bothsided => $params{bothsided});
unlink @pdf_file_names;
if (!$params{printer_id}) {
use parent qw(SL::Controller::Base);
use SL::Helper::Flash qw(flash_later);
-use SL::Presenter::Tag qw(select_tag hidden_tag);
+use SL::Presenter::Tag qw(select_tag hidden_tag div_tag);
use SL::Locale::String qw(t8);
use SL::SessionFile::Random;
use SL::PriceSource;
use SL::Webdav;
use SL::File;
+use SL::MIME;
use SL::Util qw(trim);
use SL::YAML;
use SL::DB::Order;
use SL::DB::Printer;
use SL::DB::Language;
use SL::DB::RecordLink;
+use SL::DB::Shipto;
use SL::Helper::CreatePDF qw(:all);
use SL::Helper::PrintOptions;
use SL::Helper::ShippedQty;
use SL::Helper::UserPreferences::PositionsScrollbar;
+use SL::Helper::UserPreferences::UpdatePositions;
use SL::Controller::Helper::GetModels;
-use List::Util qw(first);
+use List::Util qw(first sum0);
use List::UtilsBy qw(sort_by uniq_by);
use List::MoreUtils qw(any none pairwise first_index);
use English qw(-no_match_vars);
use Rose::Object::MakeMethods::Generic
(
- scalar => [ qw(item_ids_to_delete) ],
- 'scalar --get_set_init' => [ qw(order valid_types type cv p multi_items_models all_price_factors) ],
+ scalar => [ qw(item_ids_to_delete is_custom_shipto_to_delete) ],
+ 'scalar --get_set_init' => [ qw(order valid_types type cv p multi_items_models all_price_factors search_cvpartnumber show_update_button) ],
);
__PACKAGE__->run_before('check_auth');
__PACKAGE__->run_before('recalc',
- only => [ qw(save save_as_new save_and_delivery_order save_and_invoice print send_email) ]);
+ only => [ qw(save save_as_new save_and_delivery_order save_and_invoice save_and_ap_transaction
+ print send_email) ]);
__PACKAGE__->run_before('get_unalterable_data',
- only => [ qw(save save_as_new save_and_delivery_order save_and_invoice print send_email) ]);
+ only => [ qw(save save_as_new save_and_delivery_order save_and_invoice save_and_ap_transaction
+ print send_email) ]);
#
# actions
# 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.
+# If PDF creation was requested and succeeded, the pdf is offered for download
+# via send_file (which uses ajax in this case).
sub action_print {
my ($self) = @_;
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::print-${key}" => $sfile->file_name);
-
- $self->js
- ->run('kivi.Order.download_pdf', $pdf_filename, $key)
- ->flash('info', t8('The PDF has been created'));
+ $self->js->flash('info', t8('The PDF has been created'));
+ $self->send_file(
+ \$pdf,
+ type => SL::MIME->mime_type_from_ext($pdf_filename),
+ name => $pdf_filename,
+ js_no_render => 1,
+ );
} elsif ($media eq 'printer') {
# printer
$self->js->render;
}
-# offer pdf for download
-#
-# It needs to get the key for the session value to get the pdf file.
-sub action_download_pdf {
+# open the email dialog
+sub action_save_and_show_email_dialog {
my ($self) = @_;
- my $key = $::form->{key};
- my $tmp_filename = $::auth->get_session_value("Order::print-${key}");
- return $self->send_file(
- $tmp_filename,
- type => 'application/pdf',
- name => $::form->{pdf_filename},
- );
-}
+ my $errors = $self->save();
-# open the email dialog
-sub action_show_email_dialog {
- my ($self) = @_;
+ if (scalar @{ $errors }) {
+ $self->js->flash('error', $_) foreach @{ $errors };
+ return $self->js->render();
+ }
my $cv_method = $self->cv;
my $form = Form->new;
$form->{$self->nr_key()} = $self->order->number;
+ $form->{cusordnumber} = $self->order->cusordnumber;
$form->{formname} = $self->type;
$form->{type} = $self->type;
- $form->{language} = 'de';
+ $form->{language} = '_' . $self->order->language->template_code if $self->order->language;
+ $form->{language_id} = $self->order->language->id if $self->order->language;
$form->{format} = 'pdf';
$email_form->{subject} = $form->generate_email_subject();
$::form->{tmpdir} = $sfile->get_path; # for Form::cleanup which may be called in Form::send_email
}
+ $::form->{id} = $self->order->id; # this is used in SL::Mailer to create a linked record to the mail
$::form->send_email(\%::myconfig, 'pdf');
# internal notes
$self->order->update_attributes(intnotes => $intnotes);
- $self->js
- ->val('#order_intnotes', $intnotes)
- ->run('kivi.Order.close_email_dialog')
- ->flash('info', t8('The email has been sent.'))
- ->render($self);
+ flash_later('info', t8('The email has been sent.'));
+
+ my @redirect_params = (
+ action => 'edit',
+ type => $self->type,
+ id => $self->order->id,
+ );
+
+ $self->redirect_to(@redirect_params);
}
# open the periodic invoices config dialog
$_[0]->workflow_sales_or_purchase_order();
}
+# workflow from purchase order to ap transaction
+sub action_save_and_ap_transaction {
+ my ($self) = @_;
+
+ my $errors = $self->save();
+
+ if (scalar @{ $errors }) {
+ $self->js->flash('error', $_) foreach @{ $errors };
+ return $self->js->render();
+ }
+
+ my $text = $self->type eq sales_order_type() ? $::locale->text('The order has been saved')
+ : $self->type eq purchase_order_type() ? $::locale->text('The order has been saved')
+ : $self->type eq sales_quotation_type() ? $::locale->text('The quotation has been saved')
+ : $self->type eq request_quotation_type() ? $::locale->text('The rfq has been saved')
+ : '';
+ flash_later('info', $text);
+
+ my @redirect_params = (
+ controller => 'ap.pl',
+ action => 'add_from_purchase_order',
+ id => $self->order->id,
+ );
+
+ $self->redirect_to(@redirect_params);
+}
+
# set form elements in respect to a changed customer or vendor
#
# This action is called on an change of the customer/vendor picker.
}
if ($self->order->$cv_method->shipto && scalar @{ $self->order->$cv_method->shipto } > 0) {
- $self->js->show('#shipto_row');
+ $self->js->show('#shipto_selection');
} else {
- $self->js->hide('#shipto_row');
+ $self->js->hide('#shipto_selection');
}
$self->js->val( '#order_salesman_id', $self->order->salesman_id) if $self->order->is_sales;
$self->js
->replaceWith('#order_cp_id', $self->build_contact_select)
->replaceWith('#order_shipto_id', $self->build_shipto_select)
+ ->replaceWith('#shipto_inputs ', $self->build_shipto_inputs)
->replaceWith('#business_info_row', $self->build_business_info_row)
->val( '#order_taxzone_id', $self->order->taxzone_id)
->val( '#order_taxincluded', $self->order->taxincluded)
+ ->val( '#order_currency_id', $self->order->currency_id)
->val( '#order_payment_id', $self->order->payment_id)
->val( '#order_delivery_term_id', $self->order->delivery_term_id)
->val( '#order_intnotes', $self->order->intnotes)
->val( '#language_id', $self->order->$cv_method->language_id)
- ->focus( '#order_' . $self->cv . '_id');
+ ->focus( '#order_' . $self->cv . '_id')
+ ->run('kivi.Order.update_exchangerate');
$self->js_redisplay_amounts_and_taxes;
+ $self->js_redisplay_cvpartnumbers;
$self->js->render();
}
$self->recalc();
+ $self->get_item_cvpartnumber($item);
+
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,
- TYPE => $self->type,
- ALL_PRICE_FACTORS => $self->all_price_factors
+ ITEM => $item,
+ ID => $item_id,
+ SELF => $self,
);
- $self->js
- ->append('#row_table_id', $row_as_html);
+ if ($::form->{insert_before_item_id}) {
+ $self->js
+ ->before ('.row_entry:has(#item_' . $::form->{insert_before_item_id} . ')', $row_as_html);
+ } else {
+ $self->js
+ ->append('#row_table_id', $row_as_html);
+ }
if ( $item->part->is_assortment ) {
$form_attr->{qty_as_number} = 1 unless $form_attr->{qty_as_number};
$self->order->add_items( $item );
$self->recalc();
+ $self->get_item_cvpartnumber($item);
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,
- TYPE => $self->type,
- ALL_PRICE_FACTORS => $self->all_price_factors
+ ITEM => $item,
+ ID => $item_id,
+ SELF => $self,
);
- $self->js
- ->append('#row_table_id', $row_as_html);
+ if ($::form->{insert_before_item_id}) {
+ $self->js
+ ->before ('.row_entry:has(#item_' . $::form->{insert_before_item_id} . ')', $row_as_html);
+ } else {
+ $self->js
+ ->append('#row_table_id', $row_as_html);
+ }
};
};
$self->js
->val('.add_item_input', '')
->run('kivi.Order.init_row_handlers')
- ->run('kivi.Order.row_table_scroll_down')
->run('kivi.Order.renumber_positions')
->focus('#add_item_parts_id_name');
+ $self->js->run('kivi.Order.row_table_scroll_down') if !$::form->{insert_before_item_id};
+
$self->js_redisplay_amounts_and_taxes;
$self->js->render();
}
$self->recalc();
foreach my $item (@items) {
+ $self->get_item_cvpartnumber($item);
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,
- TYPE => $self->type,
- ALL_PRICE_FACTORS => $self->all_price_factors
+ ITEM => $item,
+ ID => $item_id,
+ SELF => $self,
);
- $self->js->append('#row_table_id', $row_as_html);
+ if ($::form->{insert_before_item_id}) {
+ $self->js
+ ->before ('.row_entry:has(#item_' . $::form->{insert_before_item_id} . ')', $row_as_html);
+ } else {
+ $self->js
+ ->append('#row_table_id', $row_as_html);
+ }
}
$self->js
->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')
->focus('#add_item_parts_id_name');
+ $self->js->run('kivi.Order.row_table_scroll_down') if !$::form->{insert_before_item_id};
+
$self->js_redisplay_amounts_and_taxes;
$self->js->render();
}
$self->js->render();
}
+sub action_update_exchangerate {
+ my ($self) = @_;
+
+ my $data = {
+ is_standard => $self->order->currency_id == $::instance_conf->get_currency_id,
+ currency_name => $self->order->currency->name,
+ exchangerate => $self->order->daily_exchangerate_as_null_number,
+ };
+
+ $self->render(\SL::JSON::to_json($data), { type => 'json', process => 0 });
+}
+
# redisplay item rows if they are sorted by an attribute
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 },
+ partnumber => sub { $_[0]->part->partnumber },
+ description => sub { $_[0]->description },
+ qty => sub { $_[0]->qty },
+ sellprice => sub { $_[0]->sellprice },
+ discount => sub { $_[0]->discount },
+ cvpartnumber => sub { $_[0]->{cvpartnumber} },
);
+ $self->get_item_cvpartnumber($_) for @{$self->order->items_sorted};
+
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}) {
$self->js->render();
}
+# update description, notes and sellprice from master data
+sub action_update_row_from_master_data {
+ my ($self) = @_;
+
+ foreach my $item_id (@{ $::form->{item_ids} }) {
+ my $idx = first_index { $_ eq $item_id } @{ $::form->{orderitem_ids} };
+ my $item = $self->order->items_sorted->[$idx];
+
+ $item->description($item->part->description);
+ $item->longdescription($item->part->notes);
+
+ my $price_source = SL::PriceSource->new(record_item => $item, record => $self->order);
+
+ my $price_src;
+ if ($item->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);
+ } else {
+ $price_src = $price_source->best_price
+ ? $price_source->best_price
+ : $price_source->price_from_source("");
+ $price_src->price($::form->round_amount($price_src->price / $self->order->exchangerate, 5)) if $self->order->exchangerate;
+ $price_src->price(0) if !$price_source->best_price;
+ }
+
+
+ $item->sellprice($price_src->price);
+ $item->active_price_source($price_src);
+
+ $self->js
+ ->run('kivi.Order.update_sellprice', $item_id, $item->sellprice_as_number)
+ ->html('.row_entry:has(#item_' . $item_id . ') [name = "partnumber"] a', $item->part->partnumber)
+ ->val ('.row_entry:has(#item_' . $item_id . ') [name = "order.orderitems[].description"]', $item->description)
+ ->val ('.row_entry:has(#item_' . $item_id . ') [name = "order.orderitems[].longdescription"]', $item->longdescription);
+
+ if ($self->search_cvpartnumber) {
+ $self->get_item_cvpartnumber($item);
+ $self->js->html('.row_entry:has(#item_' . $item_id . ') [name = "cvpartnumber"]', $item->{cvpartnumber});
+ }
+ }
+
+ $self->recalc();
+ $self->js_redisplay_line_values;
+ $self->js_redisplay_amounts_and_taxes;
+
+ $self->js->render();
+}
+
sub js_load_second_row {
my ($self, $item, $item_id, $do_parse) = @_;
->insertBefore($self->build_tax_rows, '#amount_row_id');
}
+sub js_redisplay_cvpartnumbers {
+ my ($self) = @_;
+
+ $self->get_item_cvpartnumber($_) for @{$self->order->items_sorted};
+
+ my @data = map {[$_->{cvpartnumber}]} @{ $self->order->items_sorted };
+
+ $self->js
+ ->run('kivi.Order.redisplay_cvpartnumbers', \@data);
+}
+
sub js_reset_order_and_item_ids_after_save {
my ($self) = @_;
return $cv;
}
+sub init_search_cvpartnumber {
+ my ($self) = @_;
+
+ my $user_prefs = SL::Helper::UserPreferences::PartPickerSearch->new();
+ my $search_cvpartnumber;
+ $search_cvpartnumber = !!$user_prefs->get_sales_search_customer_partnumber() if $self->cv eq 'customer';
+ $search_cvpartnumber = !!$user_prefs->get_purchase_search_makemodel() if $self->cv eq 'vendor';
+
+ return $search_cvpartnumber;
+}
+
+sub init_show_update_button {
+ my ($self) = @_;
+
+ !!SL::Helper::UserPreferences::UpdatePositions->new()->get_show_update_button();
+}
+
sub init_p {
SL::Presenter->get;
}
sub build_shipto_select {
my ($self) = @_;
- 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',
+ select_tag('order.shipto_id',
+ [ {displayable_id => t8("No/individual shipping address"), shipto_id => ''}, $self->order->{$self->cv}->shipto ],
+ value_key => 'shipto_id',
+ title_key => 'displayable_id',
+ default => $self->order->shipto_id,
+ with_empty => 0,
+ style => 'width: 300px',
);
}
+# build the inputs for the cusom shipto dialog
+#
+# Needed, if customer/vendor changed.
+sub build_shipto_inputs {
+ my ($self) = @_;
+
+ my $content = $self->p->render('common/_ship_to_dialog',
+ vc_obj => $self->order->customervendor,
+ cs_obj => $self->order->custom_shipto,
+ cvars => $self->order->custom_shipto->cvars_by_config,
+ id_selector => '#order_shipto_id');
+
+ div_tag($content, id => 'shipto_inputs');
+}
+
# render the info line for business
#
# Needed, if customer/vendor changed.
return if !$::form->{id};
$self->order(SL::DB::Order->new(id => $::form->{id})->load);
+
+ # Add an empty custom shipto to the order, so that the dialog can render the cvar inputs.
+ # You need a custom shipto object to call cvars_by_config to get the cvars.
+ $self->order->custom_shipto(SL::DB::Shipto->new(module => 'OE', custom_variables => [])) if !$self->order->custom_shipto;
+
+ return $self->order;
}
# load or create a new order object
# order here solves this problem.
my $order;
$order = SL::DB::Order->new(id => $::form->{id})->load(with => [ 'orderitems', 'orderitems.part' ]) if $::form->{id};
- $order ||= SL::DB::Order->new(orderitems => [],
- quotation => (any { $self->type eq $_ } (sales_quotation_type(), request_quotation_type())));
+ $order ||= SL::DB::Order->new(orderitems => [],
+ quotation => (any { $self->type eq $_ } (sales_quotation_type(), request_quotation_type())),
+ currency_id => $::instance_conf->get_currency_id(),);
my $cv_id_method = $self->cv . '_id';
if (!$::form->{id} && $::form->{$cv_id_method}) {
setup_order_from_cv($order);
}
- my $form_orderitems = delete $::form->{order}->{orderitems};
- my $form_periodic_invoices_config = delete $::form->{order}->{periodic_invoices_config};
+ my $form_orderitems = delete $::form->{order}->{orderitems};
+ my $form_periodic_invoices_config = delete $::form->{order}->{periodic_invoices_config};
$order->assign_attributes(%{$::form->{order}});
+ $self->setup_custom_shipto_from_form($order, $::form);
+
if (my $periodic_invoices_config_attrs = $form_periodic_invoices_config ? SL::YAML::Load($form_periodic_invoices_config) : undef) {
my $periodic_invoices_config = $order->periodic_invoices_config || $order->periodic_invoices_config(SL::DB::PeriodicInvoicesConfig->new);
$periodic_invoices_config->assign_attributes(%$periodic_invoices_config_attrs);
$price_src->price($item->sellprice);
} else {
$price_src = $price_source->best_price
- ? $price_source->best_price
- : $price_source->price_from_source("");
+ ? $price_source->best_price
+ : $price_source->price_from_source("");
+ $price_src->price($::form->round_amount($price_src->price / $record->exchangerate, 5)) if $record->exchangerate;
$price_src->price(0) if !$price_source->best_price;
}
sub setup_order_from_cv {
my ($order) = @_;
- $order->$_($order->customervendor->$_) for (qw(taxzone_id payment_id delivery_term_id));
+ $order->$_($order->customervendor->$_) for (qw(taxzone_id payment_id delivery_term_id currency_id));
$order->intnotes($order->customervendor->notes);
}
+# setup custom shipto from form
+#
+# The dialog returns form variables starting with 'shipto' and cvars starting
+# with 'shiptocvar_'.
+# Mark it to be deleted if a shipto from master data is selected
+# (i.e. order has a shipto).
+# Else, update or create a new custom shipto. If the fields are empty, it
+# will not be saved on save.
+sub setup_custom_shipto_from_form {
+ my ($self, $order, $form) = @_;
+
+ if ($order->shipto) {
+ $self->is_custom_shipto_to_delete(1);
+ } else {
+ my $custom_shipto = $order->custom_shipto || $order->custom_shipto(SL::DB::Shipto->new(module => 'OE', custom_variables => []));
+
+ my $shipto_cvars = {map { my ($key) = m{^shiptocvar_(.+)}; $key => delete $form->{$_}} grep { m{^shiptocvar_} } keys %$form};
+ my $shipto_attrs = {map { $_ => delete $form->{$_}} grep { m{^shipto} } keys %$form};
+
+ $custom_shipto->assign_attributes(%$shipto_attrs);
+ $custom_shipto->cvar_by_name($_)->value($shipto_cvars->{$_}) for keys %$shipto_cvars;
+ }
+}
+
# recalculate prices and taxes
#
# Using the PriceTaxCalculator. Store linetotals in the item objects.
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);
+ foreach my $tax_id (keys %{ $pat{taxes_by_tax_id} }) {
+ my $netamount = sum0 map { $pat{amounts}->{$_}->{amount} } grep { $pat{amounts}->{$_}->{tax_id} == $tax_id } keys %{ $pat{amounts} };
- 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 });
+ push(@{ $self->{taxes} }, { amount => $pat{taxes_by_tax_id}->{$tax_id},
+ netamount => $netamount,
+ tax => SL::DB::Tax->new(id => $tax_id)->load });
}
-
pairwise { $a->{linetotal} = $b->{linetotal} } @{$self->order->items_sorted}, @{$pat{items}};
}
my $db = $self->order->db;
$db->with_transaction(sub {
+ # delete custom shipto if it is to be deleted or if it is empty
+ if ($self->order->custom_shipto && ($self->is_custom_shipto_to_delete || $self->order->custom_shipto->is_empty)) {
+ $self->order->custom_shipto->delete if $self->order->custom_shipto->shipto_id;
+ $self->order->custom_shipto(undef);
+ }
+
SL::DB::OrderItem->new(id => $_)->delete for @{$self->item_ids_to_delete || []};
$self->order->save(cascade => 1);
: $::form->{type} eq sales_order_type() ? purchase_order_type()
: '';
+ # check for direct delivery
+ # copy shipto in custom shipto (custom shipto will be copied by new_from() in case)
+ my $custom_shipto;
+ if ( $::form->{type} eq sales_order_type() && $destination_type eq purchase_order_type()
+ && $::form->{use_shipto} && $self->order->shipto) {
+ $custom_shipto = $self->order->shipto->clone('SL::DB::Order');
+ }
+
$self->order(SL::DB::Order->new_from($self->order, destination_type => $destination_type));
$self->{converted_from_oe_id} = delete $::form->{id};
$item->{new_fake_id} = join('_', 'new', Time::HiRes::gettimeofday(), int rand 1000000000000);
}
+ if ($::form->{type} eq sales_order_type() && $destination_type eq purchase_order_type()) {
+ if ($::form->{use_shipto}) {
+ $self->order->custom_shipto($custom_shipto) if $custom_shipto;
+ } else {
+ # remove any custom shipto if not wanted
+ $self->order->custom_shipto(SL::DB::Shipto->new(module => 'OE', custom_variables => []));
+ }
+ }
+
# change form type
$::form->{type} = $destination_type;
$self->type($self->init_type);
my ($self) = @_;
$self->{all_taxzones} = SL::DB::Manager::TaxZone->get_all_sorted();
+ $self->{all_currencies} = SL::DB::Manager::Currency->get_all_sorted();
$self->{all_departments} = SL::DB::Manager::Department->get_all_sorted();
$self->{all_employees} = SL::DB::Manager::Employee->get_all(where => [ or => [ id => $self->order->employee_id,
deleted => 0 ] ],
$self->{positions_scrollbar_height} = SL::Helper::UserPreferences::PositionsScrollbar->new()->get_height();
my $print_form = Form->new('');
- $print_form->{type} = $self->type;
- $print_form->{printers} = SL::DB::Manager::Printer->get_all_sorted;
- $print_form->{languages} = SL::DB::Manager::Language->get_all_sorted;
- $self->{print_options} = SL::Helper::PrintOptions->get_print_options(
+ $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;
+ $print_form->{language_id} = $self->order->language_id;
+ $self->{print_options} = SL::Helper::PrintOptions->get_print_options(
form => $print_form,
options => {dialog_name_prefix => 'print_options.',
show_headers => 1,
} } @all_objects;
}
- $::request->{layout}->use_javascript("${_}.js") for qw(kivi.SalesPurchase kivi.Order kivi.File ckeditor/ckeditor ckeditor/adapters/jquery edit_periodic_invoices_config calculate_qty);
+ $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);
$self->setup_edit_action_bar;
}
call => [ 'kivi.Order.save', 'save', $::instance_conf->get_order_warn_duplicate_parts,
$::instance_conf->get_order_warn_no_deliverydate,
],
- checks => [ 'kivi.Order.check_save_active_periodic_invoices' ],
+ checks => [ 'kivi.Order.check_save_active_periodic_invoices', ['kivi.validate_form','#order_form'] ],
],
action => [
t8('Save as new'),
t8('Save and Sales Order'),
submit => [ '#order_form', { action => "Order/sales_order" } ],
only_if => (any { $self->type eq $_ } (sales_quotation_type(), purchase_order_type())),
- disabled => !$self->order->id ? t8('This object has not been saved yet.') : undef,
],
action => [
t8('Save and Purchase Order'),
- submit => [ '#order_form', { action => "Order/purchase_order" } ],
- only_if => (any { $self->type eq $_ } (sales_order_type(), request_quotation_type())),
- disabled => !$self->order->id ? t8('This object has not been saved yet.') : undef,
+ call => [ 'kivi.Order.purchase_order_check_for_direct_delivery' ],
+ only_if => (any { $self->type eq $_ } (sales_order_type(), request_quotation_type())),
],
action => [
t8('Save and Delivery Order'),
call => [ 'kivi.Order.save', 'save_and_invoice', $::instance_conf->get_order_warn_duplicate_parts ],
checks => [ 'kivi.Order.check_save_active_periodic_invoices' ],
],
+ action => [
+ t8('Save and AP Transaction'),
+ call => [ 'kivi.Order.save', 'save_and_ap_transaction', $::instance_conf->get_order_warn_duplicate_parts ],
+ only_if => (any { $self->type eq $_ } (purchase_order_type()))
+ ],
+
], # end of combobox "Workflow"
combobox => [
],
action => [
t8('Save and E-mail'),
- call => [ 'kivi.Order.email', $::instance_conf->get_order_warn_duplicate_parts ],
+ call => [ 'kivi.Order.save', 'save_and_show_email_dialog', $::instance_conf->get_order_warn_duplicate_parts ],
+ disabled => !$self->order->id ? t8('This object has not been saved yet.') : undef,
],
action => [
t8('Download attachments of all parts'),
: '';
}
+sub get_item_cvpartnumber {
+ my ($self, $item) = @_;
+
+ return if !$self->search_cvpartnumber;
+ return if !$self->order->customervendor;
+
+ if ($self->cv eq 'vendor') {
+ my @mms = grep { $_->make eq $self->order->customervendor->id } @{$item->part->makemodels};
+ $item->{cvpartnumber} = $mms[0]->model if scalar @mms;
+ } elsif ($self->cv eq 'customer') {
+ my @cps = grep { $_->customer_id eq $self->order->customervendor->id } @{$item->part->customerprices};
+ $item->{cvpartnumber} = $cps[0]->customer_partnumber if scalar @cps;
+ }
+}
+
sub sales_order_type {
'sales_order';
}
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.
+The aim is to provide the user a better experience and a faster workflow. Also
+the code should be more readable, more reliable and better to maintain.
=head2 Key Features
=item *
No C<update> is necessary. All entries and calculations are managed
-with ajax-calls and the page does only reload on C<save>.
+with ajax-calls and the page only reloads on C<save>.
=item *
=item * testing
-=item * currency
-
=item * credit limit
=item * more workflows (quotation, rfq)
=item * select units in input row?
-=item * custom shipto address
-
=item * check for direct delivery (workflow sales order -> purchase order)
=item * language / part translations
C<show_multi_items_dialog> does not use the currently inserted string for
filtering.
-=item *
-
-The language selected in print or email dialog is not saved when the order is saved.
-
=back
=head1 To discuss / Nice to have
all_buchungsgruppen all_payment_terms all_warehouses
parts_classification_filter
all_languages all_units all_price_factors) ],
- 'scalar' => [ qw(warehouse bin) ],
+ 'scalar' => [ qw(warehouse bin stock_amounts journal) ],
);
# safety
history_entries => $history_entries);
}
+sub action_inventory {
+ my ($self) = @_;
+
+ $::auth->assert('warehouse_contents');
+
+ $self->stock_amounts($self->part->get_simple_stock_sql);
+ $self->journal($self->part->get_mini_journal);
+
+ $_[0]->render('part/_inventory_data', { layout => 0 });
+};
+
sub action_update_item_totals {
my ($self) = @_;
}
sub action_show_multi_items_dialog {
+ my ($self) = @_;
+
+ my $search_term = $self->models->filtered->laundered->{all_substr_multi__ilike};
+ $search_term ||= $self->models->filtered->laundered->{all_with_makemodel_substr_multi__ilike};
+ $search_term ||= $self->models->filtered->laundered->{all_with_customer_partnumber_substr_multi__ilike};
+
$_[0]->render('part/_multi_items_dialog', { layout => 0 },
- all_partsgroups => SL::DB::Manager::PartsGroup->get_all
+ all_partsgroups => SL::DB::Manager::PartsGroup->get_all,
+ search_term => $search_term
);
}
sub action_multi_items_update_result {
my $max_count = 100;
- $::form->{multi_items}->{filter}->{obsolete} = 0;
-
my $count = $_[0]->multi_items_models->count;
if ($count == 0) {
my $text = escape($::locale->text('No results.'));
$_[0]->render($text, { layout => 0 });
} elsif ($count > $max_count) {
- my $text = escpae($::locale->text('Too many results (#1 from #2).', $count, $max_count));
+ my $text = escape($::locale->text('Too many results (#1 from #2).', $count, $max_count));
$_[0]->render($text, { layout => 0 });
} else {
my $multi_items = $_[0]->multi_items_models->get;
# since we need a second get models instance with different filters for that,
# we only modify the original filter temporarily in place
if ($::form->{prefer_exact}) {
- local $::form->{filter}{'all::ilike'} = delete local $::form->{filter}{'all:substr:multi::ilike'};
+ local $::form->{filter}{'all::ilike'} = delete local $::form->{filter}{'all:substr:multi::ilike'};
+ local $::form->{filter}{'all_with_makemodel::ilike'} = delete local $::form->{filter}{'all_with_makemodel:substr:multi::ilike'};
+ local $::form->{filter}{'all_with_customer_partnumber::ilike'} = delete local $::form->{filter}{'all_with_customer_partnumber:substr:multi::ilike'};
my $exact_models = SL::Controller::Helper::GetModels->new(
controller => $self,
}
sub action_part_picker_search {
- $_[0]->render('part/part_picker_search', { layout => 0 });
+ my ($self) = @_;
+
+ my $search_term = $self->models->filtered->laundered->{all_substr_multi__ilike};
+ $search_term ||= $self->models->filtered->laundered->{all_with_makemodel_substr_multi__ilike};
+ $search_term ||= $self->models->filtered->laundered->{all_with_customer_partnumber_substr_multi__ilike};
+
+ $_[0]->render('part/part_picker_search', { layout => 0 }, search_term => $search_term);
}
sub action_part_picker_result {
if ( $::form->{part}{id} ) {
return SL::DB::Part->new(id => $::form->{part}{id})->load(with => [ qw(makemodels customerprices prices translations partsgroup shop_parts shop_parts.shop) ]);
+ } elsif ( $::form->{id} ) {
+ return SL::DB::Part->new(id => $::form->{id})->load; # used by inventory tab
} else {
die "part_type missing" unless $::form->{part}{part_type};
return SL::DB::Part->new(part_type => $::form->{part}{part_type});
#
sub check_auth {
- $::auth->assert('edit_prices');
+ if ($::form->{vc} eq 'customer') {
+ $::auth->assert('sales_edit_prices');
+ } elsif ($::form->{vc} eq 'vendor') {
+ $::auth->assert('purchase_edit_prices');
+ } else {
+ $::auth->assert('no_such_right');
+ }
}
sub init_record {
}
1;
-
flash_later('error', $::locale->text('The project is in use and cannot be deleted.'));
}
- $self->redirect_to(action => 'search');
+ $self->redirect_to(action => 'list');
}
sub action_ajax_autocomplete {
$::form->{filter}{'all:substr:multi::ilike'} =~ s{[\(\)]+}{}g;
# if someone types something, and hits enter, assume he entered the full name.
- # if something matches, treat that as sole match
- # unfortunately get_models can't do more than one per package atm, so we d it
+ # if something matches, treat that as the sole match
+ # unfortunately get_models can't do more than one per package atm, so we do it
# the oldfashioned way.
if ($::form->{prefer_exact}) {
my $exact_matches;
my $counter = 0;
- foreach my $bt_id ( @{ $::form->{bt_ids} }) {
- my $rec_group = SL::DB::Manager::ReconciliationLink->get_new_rec_group();
- my $bank_transaction = SL::DB::Manager::BankTransaction->find_by(id => $bt_id);
- $bank_transaction->cleared('1');
- if ( $bank_transaction->isa('SL::DB::BankTransaction') ) {
- $bank_transaction->invoice_amount($bank_transaction->amount);
- }
- $bank_transaction->save;
- foreach my $acc_trans_id (@{ $::form->{proposal_list}->{$bt_id}->{BB} }) {
- SL::DB::ReconciliationLink->new(
- rec_group => $rec_group,
- bank_transaction_id => $bt_id,
- acc_trans_id => $acc_trans_id
- )->save;
- my $acc_trans = SL::DB::Manager::AccTransaction->find_by(acc_trans_id => $acc_trans_id);
- $acc_trans->cleared('1');
- $acc_trans->save;
+ # reconcile transaction safe
+ SL::DB->client->with_transaction(sub {
+ foreach my $bt_id ( @{ $::form->{bt_ids} }) {
+ my $rec_group = SL::DB::Manager::ReconciliationLink->get_new_rec_group();
+ my $bank_transaction = SL::DB::Manager::BankTransaction->find_by(id => $bt_id);
+ $bank_transaction->cleared('1');
+ $bank_transaction->save;
+ foreach my $acc_trans_id (@{ $::form->{proposal_list}->{$bt_id}->{BB} }) {
+ SL::DB::ReconciliationLink->new(
+ rec_group => $rec_group,
+ bank_transaction_id => $bt_id,
+ acc_trans_id => $acc_trans_id
+ )->save;
+ my $acc_trans = SL::DB::Manager::AccTransaction->find_by(acc_trans_id => $acc_trans_id);
+ $acc_trans->cleared('1');
+ $acc_trans->save;
+ }
+ $counter++;
}
- $counter++;
- }
+
+ 1;
+ }) or die t8('Unable to reconcile, database transaction failure');
flash('ok', t8('#1 proposal(s) saved.', $counter));
sub _reconcile {
my ($self) = @_;
- # 1. step: set AccTrans and BankTransactions to 'cleared'
- foreach my $element (@{ $self->{ELEMENTS} }) {
- $element->cleared('1');
- # veto either invoice_amount is fully assigned or not! No state tricks in later workflow!
- # invoice_amount should be a distinct sign, that some bookings were really made from a bank transaction
- # $element->invoice_amount($element->amount) if $element->isa('SL::DB::BankTransaction');
- $element->save;
- }
+ # reconcile transaction safe
+ SL::DB->client->with_transaction(sub {
- # 2. step: insert entry in reconciliation_links
- my $rec_group = SL::DB::Manager::ReconciliationLink->get_new_rec_group();
- #There is either a 1:n relation or a n:1 relation
- if (scalar @{ $::form->{bt_ids} } == 1) {
- my $bt_id = @{ $::form->{bt_ids} }[0];
- foreach my $bb_id (@{ $::form->{bb_ids} }) {
- my $rec_link = SL::DB::ReconciliationLink->new(bank_transaction_id => $bt_id,
- acc_trans_id => $bb_id,
- rec_group => $rec_group);
- $rec_link->save;
+ # 1. step: set AccTrans and BankTransactions to 'cleared'
+ foreach my $element (@{ $self->{ELEMENTS} }) {
+ $element->cleared('1');
+ # veto either invoice_amount is fully assigned or not! No state tricks in later workflow!
+ $element->save;
}
- } else {
- my $bb_id = @{ $::form->{bb_ids} }[0];
- foreach my $bt_id (@{ $::form->{bt_ids} }) {
- my $rec_link = SL::DB::ReconciliationLink->new(bank_transaction_id => $bt_id,
- acc_trans_id => $bb_id,
- rec_group => $rec_group);
- $rec_link->save;
+ # 2. step: insert entry in reconciliation_links
+ my $rec_group = SL::DB::Manager::ReconciliationLink->get_new_rec_group();
+ #There is either a 1:n relation or a n:1 relation
+ if (scalar @{ $::form->{bt_ids} } == 1) {
+ my $bt_id = @{ $::form->{bt_ids} }[0];
+ foreach my $bb_id (@{ $::form->{bb_ids} }) {
+ my $rec_link = SL::DB::ReconciliationLink->new(bank_transaction_id => $bt_id,
+ acc_trans_id => $bb_id,
+ rec_group => $rec_group);
+ $rec_link->save;
+ }
+ } else {
+ my $bb_id = @{ $::form->{bb_ids} }[0];
+ foreach my $bt_id (@{ $::form->{bt_ids} }) {
+ my $rec_link = SL::DB::ReconciliationLink->new(bank_transaction_id => $bt_id,
+ acc_trans_id => $bb_id,
+ rec_group => $rec_group);
+ $rec_link->save;
+ }
}
- }
+
+ 1;
+ }) or die t8('Unable to reconcile, database transaction failure');
}
sub _filter_to_where {
use SL::Helper::CreatePDF qw();
use SL::Helper::Flash;
use SL::Locale::String;
+use SL::System::Process;
use SL::Template::LaTeX;
use Rose::Object::MakeMethods::Generic
sub action_create_pdf {
my ($self, %params) = @_;
+ my $keep_temp_files = $::lx_office_conf{debug} && $::lx_office_conf{debug}->{keep_temp_files};
+ my $temp_dir = File::Temp->newdir(
+ "kivitendo-print-XXXXXX",
+ DIR => SL::System::Process::exe_dir() . "/" . $::lx_office_conf{paths}->{userspath},
+ CLEANUP => !$keep_temp_files,
+ );
+
my $base_name = $self->requirement_spec->type->template_file_name || 'requirement_spec';
- my @pictures = $self->prepare_pictures_for_printing;
- my %result = SL::Template::LaTeX->parse_and_create_pdf("${base_name}.tex", SELF => $self, rspec => $self->requirement_spec);
+ my @pictures = $self->prepare_pictures_for_printing($temp_dir->dirname);
+ my %result = SL::Template::LaTeX->parse_and_create_pdf("${base_name}.tex", SELF => $self, rspec => $self->requirement_spec, userspath => $temp_dir->dirname);
unlink @pictures unless ($::lx_office_conf{debug} || {})->{keep_temp_files};
}
sub prepare_pictures_for_printing {
- my ($self) = @_;
+ my ($self, $userspath) = @_;
my @files;
- my $userspath = File::Spec->rel2abs($::lx_office_conf{paths}->{userspath});
- my $target = "${userspath}/kivitendo-print-requirement-spec-picture-" . Common::unique_id() . '-';
+ $userspath ||= SL::System::Process::exe_dir() . "/" . $::lx_office_conf{paths}->{userspath};
+ my $target = "${userspath}/kivitendo-print-requirement-spec-picture-" . Common::unique_id() . '-';
foreach my $picture (map { @{ $_->pictures } } @{ $self->requirement_spec->text_blocks }) {
my $output_file_name = $target . $picture->id . '.' . $picture->get_default_file_name_extension;
invnumber => $::form->{invnumber},
vendor_id => $::form->{vendor_id},
);
+ # we are modifying a existing daily booking - allow this if
+ # booking conditions are not super strict
+ undef $exists_ap if ($::instance_conf->get_ap_changeable != 0
+ && $exists_ap->gldate == DateTime->today_local);
+
$_[0]->render(\ !!$exists_ap, { type => 'text' });
}
{ method => 'bank', title => t8('Bank'), },
{ method => 'bank_code', title => t8('Bank code'), },
{ method => 'bic', title => t8('BIC'), },
+ { title => t8('Use for ZUGFeRD'), formatter => sub { $_[0]->use_for_zugferd ? t8('yes') : t8('no') } },
{ method => 'reconciliation_starting_date_as_date', title => t8('Date'), align => 'right' },
{ method => 'reconciliation_starting_balance_as_number', title => t8('Balance'), align => 'right' },
],
],
},
+ contact_department => {
+ class => 'ContactDepartment',
+ auth => 'config',
+ titles => {
+ list => t8('Contact Departments'),
+ add => t8('Add department'),
+ edit => t8('Edit department'),
+ },
+ },
+
+ contact_title => {
+ class => 'ContactTitle',
+ auth => 'config',
+ titles => {
+ list => t8('Contact Titles'),
+ add => t8('Add title'),
+ edit => t8('Edit title'),
+ },
+ },
+
department => {
class => 'Department',
titles => {
},
},
+ greeting => {
+ class => 'Greeting',
+ auth => 'config',
+ titles => {
+ list => t8('Greetings'),
+ add => t8('Add greeting'),
+ edit => t8('Edit greeting'),
+ },
+ },
+
language => {
# Make locales.pl happy: $self->render("simple_system_setting/_language_form")
class => 'Language',
use parent qw(SL::Controller::Base);
+use utf8; # Umlauts in hardcoded German default texts
use DateTime;
use SL::Locale::String qw(t8);
-use SL::ReportGenerator;
use SL::Helper::Flash;
use SL::DBUtils;
+use Data::Dumper;
+use List::Util qw(sum);
+use SL::ClientJS;
use SL::DB::Chart;
use SL::DB::GLTransaction;
use SL::DB::AccTransaction;
-use SL::DB::Helper::AccountingPeriod qw(get_balance_starting_date);
-
-use SL::Presenter::Tag qw(checkbox_tag);
+use SL::DB::Employee;
+use SL::DB::Helper::AccountingPeriod qw(get_balance_starting_date get_balance_startdate_method_options);
use Rose::Object::MakeMethods::Generic (
- 'scalar --get_set_init' => [ qw(charts charts9000 cbob_chart cb_date cb_startdate ob_date cb_reference ob_reference cb_description ob_description) ],
+ 'scalar --get_set_init' => [ qw(cb_date cb_startdate ob_date) ],
);
__PACKAGE__->run_before('check_auth');
-sub action_filter {
+sub action_form {
my ($self) = @_;
- $self->ob_date(DateTime->today->truncate(to => 'year')) if !$self->ob_date;
- $self->cb_date(DateTime->today->truncate(to => 'year')->add(days => -1)) if !$self->cb_date;
- $self->ob_reference(t8('OB Transaction')) if !$self->ob_reference;
- $self->cb_reference(t8('CB Transaction')) if !$self->cb_reference;
- $self->ob_description(t8('OB Transaction')) if !$self->ob_description;
- $self->cb_description(t8('CB Transaction')) if !$self->cb_description;
-
- $self->setup_filter_action_bar;
- $self->render('gl/yearend_filter',
- title => t8('CB/OB Transactions'),
- make_title_of_chart => sub { $_[0]->accno.' '.$_[0]->description }
+
+ $self->cb_startdate($::locale->parse_date_to_object($self->get_balance_starting_date($self->cb_date)));
+
+ my $defaults = SL::DB::Default->get;
+ my $carry_over_chart = SL::DB::Manager::Chart->find_by( id => $defaults->carry_over_account_chart_id );
+ my $profit_chart = SL::DB::Manager::Chart->find_by( id => $defaults->profit_carried_forward_chart_id );
+ my $loss_chart = SL::DB::Manager::Chart->find_by( id => $defaults->loss_carried_forward_chart_id );
+
+ $self->render('yearend/form',
+ title => t8('Year-end closing'),
+ carry_over_chart => $carry_over_chart,
+ profit_chart => $profit_chart,
+ loss_chart => $loss_chart,
+ balance_startdate_method_options => get_balance_startdate_method_options(),
);
+};
+sub action_year_end_bookings {
+ my ($self) = @_;
+
+ $self->_parse_form;
+
+ eval {
+ _year_end_bookings( start_date => $self->cb_startdate,
+ cb_date => $self->cb_date,
+ );
+ 1;
+ } or do {
+ $self->js->flash('error', t8('Error while applying year-end bookings!') . ' ' . $@);
+ return $self->js->render;
+ };
+
+ my ($report_data, $profit_loss_sum) = _report(
+ cb_date => $self->cb_date,
+ start_date => $self->cb_startdate,
+ );
+
+ my $html = $self->render('yearend/_charts', { layout => 0 , process => 1, output => 0 },
+ charts => $report_data,
+ profit_loss_sum => $profit_loss_sum,
+ );
+ return $self->js->flash('info', t8('Year-end bookings were successfully completed!'))
+ ->html('#charts', $html)
+ ->render;
}
-sub action_list {
+sub action_get_start_date {
my ($self) = @_;
- $main::lxdebug->enter_sub();
- my $report = SL::ReportGenerator->new(\%::myconfig, $::form);
+ my $cb_date = $self->cb_date; # parse from form via init
+ unless ( $self->cb_date ) {
+ return $self->hide('#apply_year_end_bookings_button')
+ ->flash('error', t8('Year-end date missing'))
+ ->render;
+ }
- $self->prepare_report($report);
+ $self->cb_startdate($::locale->parse_date_to_object($self->get_balance_starting_date($self->cb_date, $::form->{'balance_startdate_method'})));
- $report->set_options(
- output_format => 'HTML',
- raw_top_info_text => $::form->parse_html_template('gl/yearend_top', { SELF => $self }),
- raw_bottom_info_text => $::form->parse_html_template('gl/yearend_bottom', { SELF => $self }),
- allow_pdf_export => 0,
- allow_csv_export => 0,
- title => $::locale->text('CB/OB Transactions'),
- );
+ # $main::lxdebug->message(0, "found start date: ", $self->cb_startdate->to_kivitendo);
- $self->setup_list_action_bar;
- $report->generate_with_headers();
- $main::lxdebug->leave_sub();
+ return $self->js->val('#cb_startdate', $self->cb_startdate->to_kivitendo)
+ ->show('#apply_year_end_bookings_button')
+ ->show('.startdate')
+ ->render;
}
-sub action_generate {
+sub action_update_charts {
my ($self) = @_;
- if ($self->cb_date > $self->ob_date) {
- flash ('error', $::locale->text('CB date #1 is higher than OB date #2. Please select again.', $self->cb_date, $self->ob_date));
- } else {
- my $cnt = $self->make_booking();
- flash('info', $::locale->text('#1 CB transactions and #1 OB transactions generated.',$cnt)) if $cnt > 0;
- }
- $self->action_list;
-}
+ $self->_parse_form;
-sub check_auth {
- $::auth->assert('general_ledger');
+ my ($report_data, $profit_loss_sum) = _report(
+ cb_date => $self->cb_date,
+ start_date => $self->cb_startdate,
+ );
+
+ $self->render('yearend/_charts', { layout => 0 , process => 1 },
+ charts => $report_data,
+ profit_loss_sum => $profit_loss_sum,
+ );
}
#
# helpers
#
-sub make_booking {
+sub _parse_form {
my ($self) = @_;
- $main::lxdebug->enter_sub();
- my @ids = map { $::form->{"multi_id_$_"} } grep { $::form->{"multi_id_$_"} } (1..$::form->{rowcount});
- my $cnt = 0;
- $main::lxdebug->message(LXDebug->DEBUG2(),"generate for ".$::form->{cbob_chart}." # ".scalar(@ids)." charts");
- if (scalar(@ids) && $::form->{cbob_chart}) {
- my $carryoverchart = SL::DB::Manager::Chart->get_first( query => [ id => $::form->{cbob_chart} ] );
- my $charts = SL::DB::Manager::Chart->get_all( query => [ id => \@ids ] );
- foreach my $chart (@{ $charts }) {
- $main::lxdebug->message(LXDebug->DEBUG2(),"chart_id=".$chart->id." accno=".$chart->accno);
- my $balance = $self->get_balance($chart);
- if ( $balance != 0 ) {
- # SB
- $self->gl_booking($balance,$self->cb_date,$::form->{cb_reference},$::form->{cb_description},$chart,$carryoverchart,0,1);
- # EB
- $self->gl_booking($balance,$self->ob_date,$::form->{ob_reference},$::form->{ob_description},$carryoverchart,$chart,1,0);
- $cnt++;
- }
- }
- }
- $main::lxdebug->leave_sub();
- return $cnt;
+
+ # parse dates
+ $self->cb_startdate($::locale->parse_date_to_object($self->get_balance_starting_date($self->cb_date)));
+
+ die "cb_date must come after start_date" unless $self->cb_date > $self->cb_startdate;
}
+sub _year_end_bookings {
+ my (%params) = @_;
-sub prepare_report {
- my ($self,$report) = @_;
- $main::lxdebug->enter_sub();
- my $idx = 1;
+ my $start_date = delete $params{start_date};
+ my $cb_date = delete $params{cb_date};
- my %column_defs = (
- 'ids' => { raw_header_data => checkbox_tag("", id => "check_all",
- checkall => "[data-checkall=1]"), 'align' => 'center' },
- 'chart' => { text => $::locale->text('Account'), },
- 'description' => { text => $::locale->text('Description'), },
- 'saldo' => { text => $::locale->text('Saldo'), 'align' => 'right'},
- 'sum_cb' => { text => $::locale->text('Sum CB Transactions'), 'align' => 'right'}, ##close == Schluss
- 'sum_ob' => { text => $::locale->text('Sum OB Transactions'), 'align' => 'right'}, ##open == Eingang
- );
- my @columns = qw(ids chart description saldo sum_cb sum_ob);
- map { $column_defs{$_}->{visible} = 1 } @columns;
+ my $defaults = SL::DB::Default->get;
+ my $carry_over_chart = SL::DB::Manager::Chart->find_by( id => $defaults->carry_over_account_chart_id ) // die t8('No carry-over chart configured!');
+ my $profit_chart = SL::DB::Manager::Chart->find_by( id => $defaults->profit_carried_forward_chart_id ) // die t8('No profit carried forward chart configured!');
+ my $loss_chart = SL::DB::Manager::Chart->find_by( id => $defaults->loss_carried_forward_chart_id ) // die t8('No profit and loss carried forward chart configured!');
- my $ob_next_date = $self->ob_date->clone();
- $ob_next_date->add(years => 1)->add(days => -1);
+ my ($report_data, $profit_loss_sum) = _report(
+ start_date => $start_date,
+ cb_date => $cb_date,
+ );
- $self->cb_startdate($::locale->parse_date_to_object($self->get_balance_starting_date($self->cb_date)));
+ # load all charts from report as objects and store them in a hash
+ my @report_chart_ids = map { $_->{chart_id} } @{ $report_data };
+ my %charts_by_id = map { ( $_->id => $_ ) } @{ SL::DB::Manager::Chart->get_all(where => [ id => \@report_chart_ids ]) };
- my @custom_headers = ();
- # Zeile 1:
- push @custom_headers, [
- { 'text' => ' ', 'colspan' => 3 },
- { 'text' => $::locale->text("Timerange")."<br />".$self->cb_startdate->to_kivitendo." - ".$self->cb_date->to_kivitendo, 'colspan' => 2, 'align' => 'center'},
- { 'text' => $::locale->text("Timerange")."<br />".$self->ob_date->to_kivitendo." - ".$ob_next_date->to_kivitendo, 'align' => 'center'},
- ];
-
- # Zeile 2:
- my @line_2 = ();
- map { push @line_2 , $column_defs{$_} } grep { $column_defs{$_}->{visible} } @columns;
- push @custom_headers, [ @line_2 ];
-
- $report->set_custom_headers(@custom_headers);
- $report->set_columns(%column_defs);
- $report->set_column_order(@columns);
-
- my $chart9actual = SL::DB::Manager::Chart->get_first( query => [ id => $self->cbob_chart ] );
- $self->{cbob_chartaccno} = $chart9actual->accno.' '.$chart9actual->description;
-
- foreach my $chart (@{ $self->charts }) {
- my $balance = $self->get_balance($chart);
- if ( $balance != 0 ) {
- my $chart_id = $chart->id;
- my $row = { map { $_ => { 'data' => '' } } @columns };
- $row->{ids} = {
- 'raw_data' => checkbox_tag("multi_id_${idx}", value => $chart_id, "data-checkall" => 1),
- 'valign' => 'center',
- 'align' => 'center',
- };
- $row->{chart}->{data} = $chart->accno;
- $row->{description}->{data} = $chart->description;
- if ( $balance > 0 ) {
- $row->{saldo}->{data} = $::form->format_amount(\%::myconfig, $balance, 2)." H";
- } elsif ( $balance < 0 ) {
- $row->{saldo}->{data} = $::form->format_amount(\%::myconfig,-$balance, 2)." S";
- } else {
- $row->{saldo}->{data} = $::form->format_amount(\%::myconfig,0, 2)." ";
- }
- my $sum_cb = 0;
- foreach my $acc ( @{ SL::DB::Manager::AccTransaction->get_all(where => [ chart_id => $chart->id, cb_transaction => 't',
- transdate => { ge => $self->cb_startdate},
- transdate => { le => $self->cb_date }
- ]) }) {
- $sum_cb += $acc->amount;
- }
- my $sum_ob = 0;
- foreach my $acc ( @{ SL::DB::Manager::AccTransaction->get_all(where => [ chart_id => $chart->id, ob_transaction => 't',
- transdate => { ge => $self->ob_date},
- transdate => { le => $ob_next_date }
- ]) }) {
- $sum_ob += $acc->amount;
- }
- if ( $sum_cb > 0 ) {
- $row->{sum_cb}->{data} = $::form->format_amount(\%::myconfig, $sum_cb, 2)." H";
- } elsif ( $sum_cb < 0 ) {
- $row->{sum_cb}->{data} = $::form->format_amount(\%::myconfig,-$sum_cb, 2)." S";
- } else {
- $row->{sum_cb}->{data} = $::form->format_amount(\%::myconfig,0, 2)." ";
- }
- if ( $sum_ob > 0 ) {
- $row->{sum_ob}->{data} = $::form->format_amount(\%::myconfig, $sum_ob, 2)." H";
- } elsif ( $sum_ob < 0 ) {
- $row->{sum_ob}->{data} = $::form->format_amount(\%::myconfig,-$sum_ob, 2)." S";
- } else {
- $row->{sum_ob}->{data} = $::form->format_amount(\%::myconfig,0, 2)." ";
- }
- $report->add_data($row);
- }
- $idx++;
- }
+ my @asset_accounts = grep { $_->{account_type} eq 'asset_account' } @{ $report_data };
+ my @profit_loss_accounts = grep { $_->{account_type} eq 'profit_loss_account' } @{ $report_data };
- $self->{row_count} = $idx;
- $main::lxdebug->leave_sub();
-}
+ my $ob_date = $cb_date->clone->add(days => 1);
-sub get_balance {
- $main::lxdebug->enter_sub();
- my ($self,$chart) = @_;
+ my ($credit_sum, $debit_sum) = (0,0);
- #$main::lxdebug->message(LXDebug->DEBUG2(),"get_balance from=".$self->cb_startdate->to_kivitendo." to=".$self->cb_date->to_kivitendo);
- my $balance = $chart->get_balance(fromdate => $self->cb_startdate, todate => $self->cb_date);
- $main::lxdebug->leave_sub();
- return 0 if !defined $balance || $balance == 0;
- return $balance;
-}
+ my $employee_id = SL::DB::Manager::Employee->current->id;
-sub gl_booking {
- my ($self, $amount, $transdate, $reference, $description, $konto, $gegenkonto, $ob, $cb) = @_;
- $::form->get_employee();
- my $employee_id = $::form->{employee_id};
- $main::lxdebug->message(LXDebug->DEBUG2(),"employee_id=".$employee_id." ob=".$ob." cb=".$cb);
- my $gl_entry = SL::DB::GLTransaction->new(
- employee_id => $employee_id,
- transdate => $transdate,
- reference => $reference,
- description => $description,
- ob_transaction => $ob,
- cb_transaction => $cb,
- );
- #$gl_entry->save;
- my $kto_trans1 = SL::DB::AccTransaction->new(
- trans_id => $gl_entry->id,
- transdate => $transdate,
- ob_transaction => $ob,
- cb_transaction => $cb,
- chart_id => $gegenkonto->id,
- chart_link => $konto->link,
- tax_id => 0,
- taxkey => 0,
- amount => $amount,
- );
- #$kto_trans1->save;
- my $kto_trans2 = SL::DB::AccTransaction->new(
- trans_id => $gl_entry->id,
- transdate => $transdate,
- ob_transaction => $ob,
- cb_transaction => $cb,
- chart_id => $konto->id,
- chart_link => $konto->link,
- tax_id => 0,
- taxkey => 0,
- amount => -$amount,
- );
- #$kto_trans2->save;
- $gl_entry->add_transactions($kto_trans1);
- $gl_entry->add_transactions($kto_trans2);
- $gl_entry->save;
-}
+ # rather than having one gl transaction for each asset account, we group all
+ # the debit sums and credit sums for cb and ob bookings, so we will have 4 gl
+ # transactions:
-sub init_cbob_chart { $::form->{cbob_chart} }
-sub init_ob_date { $::locale->parse_date_to_object($::form->{ob_date}) }
-sub init_ob_reference { $::form->{ob_reference} }
-sub init_ob_description { $::form->{ob_description} }
-sub init_cb_startdate { $::locale->parse_date_to_object($::form->{cb_startdate}) }
-sub init_cb_date { $::locale->parse_date_to_object($::form->{cb_date}) }
-sub init_cb_reference { $::form->{cb_reference} }
-sub init_cb_description { $::form->{cb_description} }
+ # * cb for credit
+ # * cb for debit
+ # * ob for credit
+ # * ob for debit
-sub init_charts9000 {
- SL::DB::Manager::Chart->get_all( query => [ accno => { like => '9%'}] );
-}
+ my $db = SL::DB->client;
+ $db->with_transaction(sub {
-sub init_charts {
- # wie geht 'not like' in rose ?
- SL::DB::Manager::Chart->get_all( query => [ \ "accno not like '9%'"], sort_by => 'accno ASC' );
-}
+ ######### asset accounts ########
+ # need cb and ob transactions
-sub setup_filter_action_bar {
- my ($self) = @_;
+ my $debit_balance = 0;
+ my $credit_balance = 0;
- for my $bar ($::request->layout->get('actionbar')) {
- $bar->add(
- action => [
- t8('Continue'),
- submit => [ '#filter_form', { action => 'YearEndTransactions/list' } ],
- accesskey => 'enter',
- ],
+ my $asset_cb_debit_entry = SL::DB::GLTransaction->new(
+ employee_id => $employee_id,
+ transdate => $cb_date,
+ reference => 'SB ' . $cb_date->year,
+ description => 'Automatische SB-Buchungen Bestandskonten Soll für ' . $cb_date->year,
+ ob_transaction => 0,
+ cb_transaction => 1,
+ taxincluded => 0,
+ transactions => [],
+ );
+ my $asset_ob_debit_entry = SL::DB::GLTransaction->new(
+ employee_id => $employee_id,
+ transdate => $ob_date,
+ reference => 'EB ' . $ob_date->year,
+ description => 'Automatische EB-Buchungen Bestandskonten Haben für ' . $ob_date->year,
+ ob_transaction => 1,
+ cb_transaction => 0,
+ taxincluded => 0,
+ transactions => [],
+ );
+ my $asset_cb_credit_entry = SL::DB::GLTransaction->new(
+ employee_id => $employee_id,
+ transdate => $cb_date,
+ reference => 'SB ' . $cb_date->year,
+ description => 'Automatische SB-Buchungen Bestandskonten Haben für ' . $cb_date->year,
+ ob_transaction => 0,
+ cb_transaction => 1,
+ taxincluded => 0,
+ transactions => [],
+ );
+ my $asset_ob_credit_entry = SL::DB::GLTransaction->new(
+ employee_id => $employee_id,
+ transdate => $ob_date,
+ reference => 'EB ' . $ob_date->year,
+ description => 'Automatische EB-Buchungen Bestandskonten Soll für ' . $ob_date->year,
+ ob_transaction => 1,
+ cb_transaction => 0,
+ taxincluded => 0,
+ transactions => [],
);
- }
-}
-sub setup_list_action_bar {
- my ($self) = @_;
+ foreach my $asset_account ( @asset_accounts ) {
+ next if $asset_account->{amount_with_cb} == 0;
+ my $ass_acc = $charts_by_id{ $asset_account->{chart_id} };
+
+ if ( $asset_account->{amount_with_cb} < 0 ) {
+ # $main::lxdebug->message(0, sprintf("adding accno %s with balance %s to debit", $asset_account->{accno}, $asset_account->{amount_with_cb}));
+ $debit_balance += $asset_account->{amount_with_cb};
+
+ $asset_cb_debit_entry->add_chart_booking(
+ chart => $ass_acc,
+ credit => - $asset_account->{amount_with_cb},
+ tax_id => 0
+ );
+ $asset_ob_debit_entry->add_chart_booking(
+ chart => $ass_acc,
+ debit => - $asset_account->{amount_with_cb},
+ tax_id => 0
+ );
- for my $bar ($::request->layout->get('actionbar')) {
- $bar->add(
- action => [
- t8('Post'),
- submit => [ '#form', { action => 'YearEndTransactions/generate' } ],
- tooltip => t8('generate cb/ob transactions for selected charts'),
- confirm => t8('Are you sure to generate cb/ob transactions?'),
- accesskey => 'enter',
- ],
- action => [
- t8('Back'),
- call => [ 'kivi.history_back' ],
- ],
+ } else {
+ # $main::lxdebug->message(0, sprintf("adding accno %s with balance %s to credit", $asset_account->{accno}, $asset_account->{amount_with_cb}));
+ $credit_balance += $asset_account->{amount_with_cb};
+
+ $asset_cb_credit_entry->add_chart_booking(
+ chart => $ass_acc,
+ debit => $asset_account->{amount_with_cb},
+ tax_id => 0
+ );
+ $asset_ob_credit_entry->add_chart_booking(
+ chart => $ass_acc,
+ credit => $asset_account->{amount_with_cb},
+ tax_id => 0
+ );
+ };
+ };
+
+ if ( $debit_balance ) {
+ $asset_cb_debit_entry->add_chart_booking(
+ chart => $carry_over_chart,
+ debit => -1 * $debit_balance,
+ tax_id => 0,
+ );
+
+ $asset_ob_debit_entry->add_chart_booking(
+ chart => $carry_over_chart,
+ credit => -1 * $debit_balance,
+ tax_id => 0,
+ );
+ };
+
+ if ( $credit_balance ) {
+ $asset_cb_credit_entry->add_chart_booking(
+ chart => $carry_over_chart,
+ credit => $credit_balance,
+ tax_id => 0,
+ );
+ $asset_ob_credit_entry->add_chart_booking(
+ chart => $carry_over_chart,
+ debit => $credit_balance,
+ tax_id => 0,
+ );
+ };
+
+ $asset_cb_debit_entry->post if scalar @{ $asset_cb_debit_entry->transactions } > 1;
+ $asset_ob_debit_entry->post if scalar @{ $asset_ob_debit_entry->transactions } > 1;
+ $asset_cb_credit_entry->post if scalar @{ $asset_cb_credit_entry->transactions } > 1;
+ $asset_ob_credit_entry->post if scalar @{ $asset_ob_credit_entry->transactions } > 1;
+
+ ####### profit-loss accounts #######
+ # these only have a closing balance, the balance is transferred to the profit-loss account
+
+ # need to know if profit or loss first!
+ # use amount_with_cb, so it can be run several times. So sum may be 0 the second time.
+ my $profit_loss_sum = sum map { $_->{amount_with_cb} }
+ grep { $_->{account_type} eq 'profit_loss_account' }
+ @{$report_data};
+ $profit_loss_sum ||= 0;
+ my $pl_chart;
+ if ( $profit_loss_sum > 0 ) {
+ $pl_chart = $profit_chart;
+ } else {
+ $pl_chart = $loss_chart;
+ };
+
+ my $pl_debit_balance = 0;
+ my $pl_credit_balance = 0;
+ # soll = debit, haben = credit
+ my $pl_cb_debit_entry = SL::DB::GLTransaction->new(
+ employee_id => $employee_id,
+ transdate => $cb_date,
+ reference => 'SB ' . $cb_date->year,
+ description => 'Automatische SB-Buchungen Erfolgskonten Soll für ' . $cb_date->year,
+ ob_transaction => 0,
+ cb_transaction => 1,
+ taxincluded => 0,
+ transactions => [],
);
- }
+ my $pl_cb_credit_entry = SL::DB::GLTransaction->new(
+ employee_id => $employee_id,
+ transdate => $cb_date,
+ reference => 'SB ' . $cb_date->year,
+ description => 'Automatische SB-Buchungen Erfolgskonten Haben für ' . $cb_date->year,
+ ob_transaction => 0,
+ cb_transaction => 1,
+ taxincluded => 0,
+ transactions => [],
+ );
+
+ foreach my $profit_loss_account ( @profit_loss_accounts ) {
+ # $main::lxdebug->message(0, sprintf("found chart %s with balance %s", $profit_loss_account->{accno}, $profit_loss_account->{amount_with_cb}));
+ my $chart = $charts_by_id{ $profit_loss_account->{chart_id} };
+
+ next if $profit_loss_account->{amount_with_cb} == 0;
+
+ if ( $profit_loss_account->{amount_with_cb} < 0 ) {
+ $pl_debit_balance -= $profit_loss_account->{amount_with_cb};
+ $pl_cb_debit_entry->add_chart_booking(
+ chart => $chart,
+ tax_id => 0,
+ credit => - $profit_loss_account->{amount_with_cb},
+ );
+ } else {
+ $pl_credit_balance += $profit_loss_account->{amount_with_cb};
+ $pl_cb_credit_entry->add_chart_booking(
+ chart => $chart,
+ tax_id => 0,
+ debit => $profit_loss_account->{amount_with_cb},
+ );
+ };
+ };
+
+ # $main::lxdebug->message(0, "pl_debit_balance = $pl_debit_balance");
+ # $main::lxdebug->message(0, "pl_credit_balance = $pl_credit_balance");
+
+ $pl_cb_debit_entry->add_chart_booking(
+ chart => $pl_chart,
+ tax_id => 0,
+ debit => $pl_debit_balance,
+ ) if $pl_debit_balance;
+
+ $pl_cb_credit_entry->add_chart_booking(
+ chart => $pl_chart,
+ tax_id => 0,
+ credit => $pl_credit_balance,
+ ) if $pl_credit_balance;
+
+ # printf("debit : %s -> %s\n", $_->chart->displayable_name, $_->amount) foreach @{ $pl_cb_debit_entry->transactions };
+ # printf("credit: %s -> %s\n", $_->chart->displayable_name, $_->amount) foreach @{ $pl_cb_credit_entry->transactions };
+
+ $pl_cb_debit_entry->post if scalar @{ $pl_cb_debit_entry->transactions } > 1;
+ $pl_cb_credit_entry->post if scalar @{ $pl_cb_credit_entry->transactions } > 1;
+
+ ######### profit-loss transfer #########
+ # and finally transfer the new balance of the profit-loss account via the carry-over account
+ # we want to use profit_loss_sum with cb!
+
+ if ( $profit_loss_sum != 0 ) {
+
+ my $carry_over_cb_entry = SL::DB::GLTransaction->new(
+ employee_id => $employee_id,
+ transdate => $cb_date,
+ reference => 'SB ' . $cb_date->year,
+ description => sprintf('Automatische SB-Buchung für %s %s',
+ $profit_loss_sum >= 0 ? 'Gewinnvortrag' : 'Verlustvortrag',
+ $cb_date->year,
+ ),
+ ob_transaction => 0,
+ cb_transaction => 1,
+ taxincluded => 0,
+ transactions => [],
+ );
+ my $carry_over_ob_entry = SL::DB::GLTransaction->new(
+ employee_id => $employee_id,
+ transdate => $ob_date,
+ reference => 'EB ' . $ob_date->year,
+ description => sprintf('Automatische EB-Buchung für %s %s',
+ $profit_loss_sum >= 0 ? 'Gewinnvortrag' : 'Verlustvortrag',
+ $ob_date->year,
+ ),
+ ob_transaction => 1,
+ cb_transaction => 0,
+ taxincluded => 0,
+ transactions => [],
+ );
+
+ my ($amount1, $amount2);
+ if ( $profit_loss_sum < 0 ) {
+ $amount1 = 'debit';
+ $amount2 = 'credit';
+ } else {
+ $amount1 = 'credit';
+ $amount2 = 'debit';
+ };
+
+ $carry_over_cb_entry->add_chart_booking(
+ chart => $carry_over_chart,
+ tax_id => 0,
+ $amount1 => abs($profit_loss_sum),
+ );
+ $carry_over_cb_entry->add_chart_booking(
+ chart => $pl_chart,
+ tax_id => 0,
+ $amount2 => abs($profit_loss_sum),
+ );
+ $carry_over_ob_entry->add_chart_booking(
+ chart => $carry_over_chart,
+ tax_id => 0,
+ $amount2 => abs($profit_loss_sum),
+ );
+ $carry_over_ob_entry->add_chart_booking(
+ chart => $pl_chart,
+ tax_id => 0,
+ $amount1 => abs($profit_loss_sum),
+ );
+
+ # printf("debit : %s -> %s\n", $_->chart->displayable_name, $_->amount) foreach @{ $carry_over_ob_entry->transactions };
+ # printf("credit: %s -> %s\n", $_->chart->displayable_name, $_->amount) foreach @{ $carry_over_ob_entry->transactions };
+
+ $carry_over_cb_entry->post if scalar @{ $carry_over_cb_entry->transactions } > 1;
+ $carry_over_ob_entry->post if scalar @{ $carry_over_ob_entry->transactions } > 1;
+ };
+
+ my $consistency_query = <<SQL;
+select sum(amount)
+ from acc_trans
+ where (ob_transaction is true or cb_transaction is true)
+ and (transdate = ? or transdate = ?)
+SQL
+ my ($sum) = selectrow_query($::form, $db->dbh, $consistency_query,
+ $cb_date,
+ $ob_date
+ );
+ die "acc_trans transactions don't add up to zero" unless $sum == 0;
+
+ 1;
+ }) or die $db->error;
}
+sub _report {
+ my (%params) = @_;
+
+ my $start_date = delete $params{start_date};
+ my $cb_date = delete $params{cb_date};
+
+ my $defaults = SL::DB::Default->get;
+ die "no carry over account defined"
+ unless defined $defaults->carry_over_account_chart_id
+ and $defaults->carry_over_account_chart_id > 0;
+
+ my $salden_query = <<SQL;
+select c.id as chart_id,
+ c.accno,
+ c.description,
+ c.category,
+ sum(a.amount) filter (where cb_transaction is false and ob_transaction is false) as amount,
+ sum(a.amount) filter (where ob_transaction is true ) as ob_amount,
+ sum(a.amount) filter (where cb_transaction is false ) as amount_without_cb,
+ sum(a.amount) filter (where cb_transaction is true ) as cb_amount,
+ sum(a.amount) as amount_with_cb,
+ case when c.category = ANY( '{I,E}' ) then 'profit_loss_account'
+ when c.category = ANY( '{A,C,L,Q}' ) then 'asset_account'
+ else null
+ end as account_type
+ from acc_trans a
+ inner join chart c on (c.id = a.chart_id)
+ where a.transdate >= ?
+ and a.transdate <= ?
+ and a.chart_id != ?
+ group by c.id, c.accno, c.category
+ order by account_type, c.accno
+SQL
+
+ my $dbh = SL::DB->client->dbh;
+ my $report = selectall_hashref_query($::form, $dbh, $salden_query,
+ $start_date,
+ $cb_date,
+ $defaults->carry_over_account_chart_id,
+ );
+ # profit_loss_sum is the actual profit/loss for the year, without cb, use "amount_without_cb")
+ my $profit_loss_sum = sum map { $_->{amount_without_cb} }
+ grep { $_->{account_type} eq 'profit_loss_account' }
+ @{$report};
+
+ return ($report, $profit_loss_sum);
+}
+
+#
+# auth
+#
+
+sub check_auth {
+ $::auth->assert('general_ledger');
+}
+
+
+#
+# inits
+#
+
+sub init_ob_date { $::locale->parse_date_to_object($::form->{ob_date}) }
+sub init_cb_startdate { $::locale->parse_date_to_object($::form->{cb_startdate}) }
+sub init_cb_date { $::locale->parse_date_to_object($::form->{cb_date}) }
+
1;
--- /dev/null
+package SL::Controller::ZUGFeRD;
+use strict;
+use parent qw(SL::Controller::Base);
+
+use SL::DB::RecordTemplate;
+use SL::Locale::String qw(t8);
+use SL::Helper::DateTime;
+use SL::VATIDNr;
+use SL::ZUGFeRD;
+
+use XML::LibXML;
+
+
+__PACKAGE__->run_before('check_auth');
+
+sub action_upload_zugferd {
+ my ($self, %params) = @_;
+
+ $self->setup_zugferd_action_bar;
+ $self->render('zugferd/form', title => $::locale->text('ZUGFeRD import'));
+}
+
+sub action_import_zugferd {
+ my ($self, %params) = @_;
+
+ die t8("missing file for action import") unless $::form->{file};
+ die t8("can only parse a pdf file") unless $::form->{file} =~ m/^%PDF/;
+
+ my $info = SL::ZUGFeRD->extract_from_pdf($::form->{file});
+
+ if ($info->{result} != SL::ZUGFeRD::RES_OK()) {
+ # An error occurred; log message from parser:
+ $::lxdebug->message(LXDebug::DEBUG1(), "Could not extract ZUGFeRD data, error message: " . $info->{message});
+ die t8("Could not extract ZUGFeRD data, data and error message:") . $info->{message};
+ }
+ # valid ZUGFeRD metadata
+ my $dom = XML::LibXML->load_xml(string => $info->{invoice_xml});
+
+ # 1. check if ZUGFeRD SellerTradeParty has a VAT-ID
+ my $ustid = $dom->findnodes('//ram:SellerTradeParty/ram:SpecifiedTaxRegistration')->string_value;
+ die t8("No VAT Info for this ZUGFeRD invoice," .
+ " please ask your vendor to add this for his ZUGFeRD data.") unless $ustid;
+
+ $ustid = SL::VATIDNr->normalize($ustid);
+
+ # 1.1 check if we a have a vendor with this VAT-ID (vendor.ustid)
+ my $vc = $dom->findnodes('//ram:SellerTradeParty/ram:Name')->string_value;
+ my $vendor = SL::DB::Manager::Vendor->find_by(
+ ustid => $ustid,
+ or => [
+ obsolete => undef,
+ obsolete => 0,
+ ]);
+
+ if (!$vendor) {
+ # 1.2 If no vendor with the exact VAT ID number is found, the
+ # number might be stored slightly different in the database
+ # (e.g. with spaces breaking up groups of numbers). Iterate over
+ # all existing vendors with VAT ID numbers, normalize their
+ # representation and compare those.
+
+ my $vendors = SL::DB::Manager::Vendor->get_all(
+ where => [
+ '!ustid' => undef,
+ '!ustid' => '',
+ or => [
+ obsolete => undef,
+ obsolete => 0,
+ ],
+ ]);
+
+ foreach my $other_vendor (@{ $vendors }) {
+ next unless SL::VATIDNr->normalize($other_vendor->ustid) eq $ustid;
+
+ $vendor = $other_vendor;
+ last;
+ }
+ }
+
+ die t8("Please add a valid VAT-ID for this vendor: " . $vc) unless (ref $vendor eq 'SL::DB::Vendor');
+
+ # 2. check if we have a ap record template for this vendor (TODO only the oldest template is choosen)
+ my $template_ap = SL::DB::Manager::RecordTemplate->get_first(where => [vendor_id => $vendor->id]);
+ die t8("No AP Record Template for this vendor found, please add one") unless (ref $template_ap eq 'SL::DB::RecordTemplate');
+
+
+ # 3. parse the zugferd data and fill the ap record template
+ # -> no need to check sign (credit notes will be negative) just record thei ZUGFeRD type in ap.notes
+ # -> check direct debit (defaults to no)
+ # -> set amount (net amount) and unset taxincluded
+ # (template and user cares for tax and if there is more than one booking accno)
+ # -> date (can be empty)
+ # -> duedate (may be empty)
+ # -> compare record iban and generate a warning if this differs from vendor's master data iban
+ my $total = $dom->findnodes('//ram:SpecifiedTradeSettlementHeaderMonetarySummation' .
+ '/ram:TaxBasisTotalAmount')->string_value;
+
+ my $invnumber = $dom->findnodes('//rsm:ExchangedDocument/ram:ID')->string_value;
+
+ # parse dates to kivi if set/valid
+ my ($transdate, $duedate, $dt_to_kivi, $due_dt_to_kivi);
+ $transdate = $dom->findnodes('//ram:IssueDateTime')->string_value;
+ $duedate = $dom->findnodes('//ram:DueDateDateTime')->string_value;
+ $transdate =~ s/^\s+|\s+$//g;
+ $duedate =~ s/^\s+|\s+$//g;
+
+ if ($transdate =~ /^[0-9]{8}$/) {
+ $dt_to_kivi = DateTime->new(year => substr($transdate,0,4),
+ month => substr ($transdate,4,2),
+ day => substr($transdate,6,2))->to_kivitendo;
+ }
+ if ($duedate =~ /^[0-9]{8}$/) {
+ $due_dt_to_kivi = DateTime->new(year => substr($duedate,0,4),
+ month => substr ($duedate,4,2),
+ day => substr($duedate,6,2))->to_kivitendo;
+ }
+
+ my $type = $dom->findnodes('//rsm:ExchangedDocument/ram:TypeCode')->string_value;
+
+ my $dd = $dom->findnodes('//ram:ApplicableHeaderTradeSettlement' .
+ '/ram:SpecifiedTradeSettlementPaymentMeans/ram:TypeCode')->string_value;
+ my $direct_debit = $dd == 59 ? 1 : 0;
+
+ my $iban = $dom->findnodes('//ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementPaymentMeans' .
+ '/ram:PayeePartyCreditorFinancialAccount/ram:IBANID')->string_value;
+ my $ibanmessage;
+ $ibanmessage = $iban ne $vendor->iban ? "Record IBAN $iban doesn't match vendor IBAN " . $vendor->iban : $iban if $iban;
+
+ my $url = $self->url_for(
+ controller => 'ap.pl',
+ action => 'load_record_template',
+ id => $template_ap->id,
+ 'form_defaults.amount_1' => $::form->format_amount(\%::myconfig, $total, 2),
+ 'form_defaults.transdate' => $dt_to_kivi,
+ 'form_defaults.invnumber' => $invnumber,
+ 'form_defaults.duedate' => $due_dt_to_kivi,
+ 'form_defaults.no_payment_bookings' => 0,
+ 'form_defaults.paid_1_suggestion' => $::form->format_amount(\%::myconfig, $total, 2),
+ 'form_defaults.notes' => "ZUGFeRD Import. Type: $type\nIBAN: " . $ibanmessage,
+ 'form_defaults.taxincluded' => 0,
+ 'form_defaults.direct_debit' => $direct_debit,
+ );
+
+ $self->redirect_to($url);
+
+}
+
+sub check_auth {
+ $::auth->assert('ap_transactions');
+}
+sub setup_zugferd_action_bar {
+ my ($self) = @_;
+
+ for my $bar ($::request->layout->get('actionbar')) {
+ $bar->add(
+ action => [
+ $::locale->text('Import'),
+ submit => [ '#form', { action => 'ZUGFeRD/import_zugferd' } ],
+ accesskey => 'enter',
+ ],
+ );
+ }
+}
+
+
+1;
+__END__
+
+=pod
+
+=encoding utf8
+
+=head1 NAME
+
+SL::Controller::ZUGFeRD
+Controller for importing ZUGFeRD pdf files to kivitendo
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item C<action_upload_zugferd>
+
+Creates a web from with a single upload dialog.
+
+=item C<action_import_zugferd $pdf>
+
+Expects a single pdf with ZUGFeRD 2.0 metadata.
+Checks if the param <C$pdf> is set and a valid pdf file.
+Calls helper functions to validate and extract the ZUGFeRD data.
+Needs a valid VAT ID (EU) for this vendor and
+expects one ap template for this vendor in kivitendo.
+
+Parses some basic ZUGFeRD data (invnumber, total net amount,
+transdate, duedate, vendor VAT ID, IBAN) and uses the first
+found ap template for this vendor to fill this template with
+ZUGFeRD data.
+If the vendor's master data contain a IBAN and the
+ZUGFeRD record has a IBAN also these values will be compared.
+If they don't match a warning will be writte in ap.notes.
+Furthermore the ZUGFeRD type code will be written to ap.notes.
+No callback implemented.
+
+=back
+
+=head1 TODO and CAVEAT
+
+This is just a very basic Parser for ZUGFeRD data.
+We assume that the ZUGFeRD generator is a company with a
+valid European VAT ID. Furthermore this vendor needs only
+one and just noe ap template (the first match will be used).
+
+The ZUGFeRD data should also be extracted in the helper package
+and maybe a model should be used for this.
+The user should set one ap template as a default for ZUGFeRD.
+The ZUGFeRD pdf should be written to WebDAV or DMS.
+If the ZUGFeRD data has a payment purpose set, this should
+be the default for the SEPA-XML export.
+
+
+=head1 AUTHOR
+
+Jan Büren E<lt>jan@kivitendo-premium.deE<gt>,
+
+=cut
use SL::HTML::Util ();
use SL::Iconv;
use SL::Locale::String qw(t8);
+use SL::VATIDNr;
use Data::Dumper;
use DateTime;
UNION ALL
SELECT ac.acc_trans_id, ac.transdate, ac.gldate, ac.trans_id,gl.id, ac.amount, ac.taxkey, ac.memo,
- gl.reference AS invnumber, NULL AS duedate, ac.amount as umsatz, NULL as deliverydate, gl.itime::date,
+ gl.reference AS invnumber, NULL AS duedate, ac.amount as umsatz, gl.deliverydate, gl.itime::date,
gl.description AS name, NULL as ustid, '' AS vcname, NULL AS customer_id, NULL AS vendor_id,
c.accno, c.description AS accname, c.taxkey_id as charttax, c.datevautomatik, c.id, ac.chart_link AS link,
FALSE AS invoice,
$datev_data{buchungstext} = $transaction->[$haben]->{'name'};
}
if (($transaction->[$haben]->{'ustid'} // '') ne "") {
- $datev_data{ustid} = $transaction->[$haben]->{'ustid'};
+ $datev_data{ustid} = SL::VATIDNr->normalize($transaction->[$haben]->{'ustid'});
}
if (($transaction->[$haben]->{'duedate'} // '') ne "") {
$datev_data{belegfeld2} = $transaction->[$haben]->{'duedate'};
}
+
+ # if deliverydate exists, add it to datev export if it is
+ # * an ar/ap booking that is not a payment
+ # * a gl booking
+ if ( ($transaction->[$haben]->{'deliverydate'} // '') ne ''
+ && (
+ ( $transaction->[$haben]->{'table'} =~ /^(ar|ap)$/
+ && $transaction->[$haben]->{'link'} !~ m/_paid/
+ && $transaction->[$soll]->{'link'} !~ m/_paid/
+ )
+ || $transaction->[$haben]->{'table'} eq 'gl'
+ )
+ ) {
+ $datev_data{leistungsdatum} = $transaction->[$haben]->{'deliverydate'};
+ }
}
$datev_data{umsatz} = abs($umsatz); # sales invoices without tax have a different sign???
# $datev_data{buchungsschluessel} = !$datevautomatik ? $taxkey : "4";
$datev_data{buchungsschluessel} = $taxkey;
}
+ # set lock for each transaction
+ $datev_data{locked} = $self->locked;
push(@datev_lines, \%datev_data) if $datev_data{umsatz};
}
$name =~ s/\ *$//;
$kne_file->add_block("\x1E" . $name . "\x1C");
- $kne_file->add_block("\xBA" . $kne->{'ustid'} . "\x1C") if $kne->{'ustid'};
+ $kne_file->add_block("\xBA" . SL::VATIDNr->normalize($kne->{'ustid'}) . "\x1C") if $kne->{'ustid'};
$kne_file->add_block("\xB3" . $kne->{'waehrung'} . "\x1C" . "\x79");
};
=item errors
-Returns a list of errors that occured. If no errors occured, the export was a success.
+Returns a list of errors that occurred. If no errors occurred, the export was a success.
=item export
use SL::Helper::DateTime;
use SL::Locale::String qw(t8);
use SL::Util qw(trim);
+use SL::VATIDNr;
use Rose::Object::MakeMethods::Generic (
scalar => [ qw(datev_lines from to locked warnings) ],
my ($text) = @_; check_encoding($text); },
valid_check => sub { return 1 if $::instance_conf->get_datev_export_format eq 'cp1252';
my ($text) = @_; check_encoding($text); },
+ formatter => sub { my ($input) = @_; return substr($input, 0, 60) },
}, # pos 14
{
kivi_datev_name => 'not yet implemented',
input_check => sub {
my ($ustid) = @_;
return 1 if ('' eq $ustid);
- $ustid =~ s{\s+}{}g;
- return ($ustid =~ m/^CH|^[A-Z]{2}\w{5,13}$/);
+ return SL::VATIDNr->validate($ustid);
},
formatter => sub { my ($input) = @_; $input =~ s/\s//g; return $input },
valid_check => sub {
my ($ustid) = @_;
return 1 if ('' eq $ustid);
- return ($ustid =~ m/^CH|^[A-Z]{2}\w{5,13}$/);
+ return SL::VATIDNr->validate($ustid);
},
}, # pos 40
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ }, # pos 50
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ }, # pos 60
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ }, # pos 70
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ }, # pos 80
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ }, # pos 90
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ }, # pos 100
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ }, # pos 110
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'locked',
+ csv_header_name => t8('Lock'),
+ max_length => 1,
+ type => 'Number',
+ default => 1,
+ valid_check => sub { my ($check) = @_; return ($check =~ m/^(0|1)$/) },
+ }, # pos 114
+ {
+ kivi_datev_name => 'leistungsdatum',
+ csv_header_name => t8('Payment Date'),
+ max_length => 8,
+ type => 'Date',
+ default => '',
+ input_check => sub { my ($check) = @_; return 1 if ('' eq $check); return (ref (DateTime->from_kivitendo($check)) eq 'DateTime') },
+ formatter => sub { my ($input) = @_; return '' if ('' eq $input); return DateTime->from_kivitendo($input)->strftime('%d%m%Y') },
+ valid_check => sub { my ($check) = @_; return 1 if ('' eq $check); return ($check =~ m/^[0-9]{8}$/) },
+ }, # pos 115
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ },
+ {
+ kivi_datev_name => 'not yet implemented',
+ }, # pos 120
);
sub new {
}
}
-sub _kivitendo_to_datev {
- @kivitendo_to_datev, ({ kivi_datev_name => 'not yet implemented' }) x (116 - @kivitendo_to_datev);
-}
-
sub header {
my ($self) = @_;
push @header, [ @header_row_1 ];
# second header row, just the column names
- push @header, [ map { $_->{csv_header_name} } _kivitendo_to_datev() ];
+ push @header, [ map { $_->{csv_header_name} } @kivitendo_to_datev ];
return \@header;
}
my ($self) = @_;
my (@array_of_datev, @warnings);
- my @csv_columns = _kivitendo_to_datev();
foreach my $row (@{ $self->datev_lines }) {
my @current_datev_row;
# 1. check all datev_lines and see if we have a defined value
# 2. if we don't have a defined value set a default if exists
# 3. otherwise die
- foreach my $column (@csv_columns) {
+ foreach my $column (@kivitendo_to_datev) {
if ($column->{kivi_datev_name} eq 'not yet implemented') {
push @current_datev_row, '';
next;
=item Return values
C<with_transaction> adopts the behaviour of C<eval> in that it returns the
-result of the inner block, and C<undef> if an error occured. This way you can
+result of the inner block, and C<undef> if an error occurred. This way you can
use the same pattern you would normally use with C<eval> for
C<with_transaction>:
=item Error handling
-The original L<Rose::DB/do_transaction> gobbles up all execptions and expects
-the caller to manually check return value and error, and then to process all
-exceptions as strings. This is very fragile and generally a step backwards from
-proper exception handling.
+The original L<Rose::DB/do_transaction> gobbles up all exceptions and expects
+the caller to manually check the return value and error, and then to process
+all exceptions as strings. This is very fragile and generally a step backwards
+from proper exception handling.
-C<with_transaction> only gobbles up exception that are used to signal an
+C<with_transaction> only gobbles up exceptions that are used to signal an
error in the transaction, and returns undef on those. All other exceptions
-bubble out of the transaction like normal, so that it is transparent to typoes,
+bubble out of the transaction like normal, so that it is transparent to typos,
runtime exceptions and other generally wanted things.
If you just use the snippet above, your code will catch everything related to
the transaction aborting, but will not catch other errors that might have been
-thrown. The transaction will be rollbacked in both cases.
+thrown. The transaction will be rolled back in both cases.
If you want to play nice in case your transaction is embedded in another
transaction, just rethrow the error:
--- /dev/null
+# This file has been auto-generated only because it didn't exist.
+# Feel free to modify it at will; it will not be overwritten automatically.
+
+package SL::DB::ContactDepartment;
+
+use strict;
+
+use SL::Util qw(trim);
+
+use SL::DB::MetaSetup::ContactDepartment;
+use SL::DB::Manager::ContactDepartment;
+
+__PACKAGE__->meta->initialize;
+
+__PACKAGE__->before_save('_before_save_trim_content');
+
+sub _before_save_trim_content {
+ $_[0]->description(trim($_[0]->description));
+ return 1;
+}
+
+1;
--- /dev/null
+# This file has been auto-generated only because it didn't exist.
+# Feel free to modify it at will; it will not be overwritten automatically.
+
+package SL::DB::ContactTitle;
+
+use strict;
+
+use SL::Util qw(trim);
+
+use SL::DB::MetaSetup::ContactTitle;
+use SL::DB::Manager::ContactTitle;
+
+__PACKAGE__->meta->initialize;
+
+__PACKAGE__->before_save('_before_save_trim_content');
+
+sub _before_save_trim_content {
+ $_[0]->description(trim($_[0]->description));
+ return 1;
+}
+
+1;
do_query($::form, $dbh, 'DELETE FROM csv_import_reports WHERE id = ?', $self->id);
if ($self->profile_id) {
- do_query($::form, $dbh, 'DELETE FROM csv_import_profile_settings WHERE csv_import_profile_id = ?', $self->profile_id);
- do_query($::form, $dbh, 'DELETE FROM csv_import_profiles WHERE id = ?', $self->profile_id);
+ my ($is_profile_used_elsewhere) = selectfirst_array_query($::form, $dbh, <<SQL, $self->profile_id);
+ SELECT id
+ FROM csv_import_reports
+ WHERE profile_id = ?
+ LIMIT 1
+SQL
+
+ if (!$is_profile_used_elsewhere) {
+ do_query($::form, $dbh, 'DELETE FROM csv_import_profile_settings WHERE csv_import_profile_id = ?', $self->profile_id);
+ do_query($::form, $dbh, 'DELETE FROM csv_import_profiles WHERE id = ?', $self->profile_id);
+ }
}
1;
}) or do { die SL::DB->client->error };
use SL::DB::Manager::Customer;
use SL::DB::Helper::IBANValidation;
use SL::DB::Helper::TransNumberGenerator;
+use SL::DB::Helper::VATIDNrValidation;
use SL::DB::Helper::CustomVariables (
module => 'CT',
cvars_alias => 1,
my @errors;
push @errors, $::locale->text('The customer name is missing.') if !$self->name;
push @errors, $self->validate_ibans;
+ push @errors, $self->validate_vat_id_numbers;
return @errors;
}
sub payment_terms { goto &payment }
sub number { goto &customernumber }
+sub create_zugferd_invoices_for_this_customer {
+ my ($self) = @_;
+
+ no warnings 'once';
+ return $::instance_conf->get_create_zugferd_invoices if $self->create_zugferd_invoices == -1;
+ return $self->create_zugferd_invoices;
+}
+
1;
use strict;
+use Carp;
use SL::DB::MetaSetup::Default;
__PACKAGE__->meta->initialize;
return SL::DB::Manager::Default->get_all(limit => 1)->[0];
}
+sub address {
+ # Compatibility function: back in the day there was only a single
+ # address field.
+ my $self = shift;
+
+ croak("SL::DB::Default::address is a read-only accessor") if @_;
+
+ my $zipcode_city = join ' ', grep { $_ } ($self->address_zipcode, $self->address_city);
+
+ return join "\n", grep { $_ } ($self->address_street1, $self->address_street2, $zipcode_city, $self->address_country);
+}
+
1;
use SL::DB::MetaSetup::GLTransaction;
use SL::Locale::String qw(t8);
use List::Util qw(sum);
+use SL::DATEV;
+use Carp;
+use Data::Dumper;
# Creates get_all, get_all_count, get_all_iterator, delete_all and update_all.
__PACKAGE__->meta->make_manager_class;
sub date { goto &gldate }
+sub post {
+ my ($self) = @_;
+
+ my @errors = $self->validate;
+ croak t8("Errors in GL transaction:") . "\n" . join("\n", @errors) . "\n" if scalar @errors;
+
+ # make sure all the defaults are set:
+ require SL::DB::Employee;
+ my $employee_id = SL::DB::Manager::Employee->current->id;
+ $self->type(undef);
+ $self->employee_id($employee_id) unless defined $self->employee_id || defined $self->employee;
+ $self->ob_transaction('f') unless defined $self->ob_transaction;
+ $self->cb_transaction('f') unless defined $self->cb_transaction;
+ $self->gldate(DateTime->today_local) unless defined $self->gldate; # should user even be allowed to set this manually?
+ $self->transdate(DateTime->today_local) unless defined $self->transdate;
+
+ $self->db->with_transaction(sub {
+ $self->save;
+
+ if ($::instance_conf->get_datev_check_on_gl_transaction) {
+ my $datev = SL::DATEV->new(
+ dbh => $self->dbh,
+ trans_id => $self->id,
+ );
+
+ $datev->generate_datev_data;
+
+ if ($datev->errors) {
+ die join "\n", t8('DATEV check returned errors:'), $datev->errors;
+ }
+ }
+
+ require SL::DB::History;
+ SL::DB::History->new(
+ trans_id => $self->id,
+ snumbers => 'gltransaction_' . $self->id,
+ employee_id => $employee_id,
+ addition => 'POSTED',
+ what_done => 'gl transaction',
+ )->save;
+
+ 1;
+ }) or die t8("Error when saving: #1", $self->db->error);
+
+ return $self;
+}
+
+sub add_chart_booking {
+ my ($self, %params) = @_;
+
+ require SL::DB::Chart;
+ die "add_chart_booking needs a transdate" unless $self->transdate;
+ die "add_chart_booking needs taxincluded" unless defined $self->taxincluded;
+ die "chart missing" unless $params{chart} && ref($params{chart}) eq 'SL::DB::Chart';
+ die t8('Booking needs at least one debit and one credit booking!')
+ unless $params{debit} or $params{credit}; # must exist and not be 0
+ die t8('Cannot have a value in both Debit and Credit!')
+ if defined($params{debit}) and defined($params{credit});
+
+ my $chart = $params{chart};
+
+ my $dec = delete $params{dec} // 2;
+
+ my ($netamount,$taxamount) = (0,0);
+ my $amount = $params{credit} // $params{debit}; # only one can exist
+
+ croak t8('You cannot use a negative amount with debit/credit!') if $amount < 0;
+
+ require SL::DB::Tax;
+ my $tax = SL::DB::Manager::Tax->find_by(id => $params{tax_id})
+ // croak "Can't find tax with id " . $params{tax_id};
+
+ if ( $tax and $tax->rate != 0 ) {
+ ($netamount, $taxamount) = Form->calculate_tax($amount, $tax->rate, $self->taxincluded, $dec);
+ } else {
+ $netamount = $amount;
+ };
+
+ if ( $params{debit} ) {
+ $amount *= -1;
+ $netamount *= -1;
+ $taxamount *= -1;
+ };
+
+ next unless $netamount; # skip entries with netamount 0
+
+ # initialise transactions if it doesn't exist yet
+ $self->transactions([]) unless $self->transactions;
+
+ require SL::DB::AccTransaction;
+ $self->add_transactions( SL::DB::AccTransaction->new(
+ chart_id => $chart->id,
+ chart_link => $chart->link,
+ amount => $netamount,
+ taxkey => $tax->taxkey,
+ tax_id => $tax->id,
+ transdate => $self->transdate,
+ source => $params{source} // '',
+ memo => $params{memo} // '',
+ ob_transaction => $self->ob_transaction,
+ cb_transaction => $self->cb_transaction,
+ project_id => $params{project_id},
+ ));
+
+ # only add tax entry if amount is >= 0.01, defaults to 2 decimals
+ if ( $::form->round_amount(abs($taxamount), $dec) > 0 ) {
+ my $tax_chart = $tax->chart;
+ if ( $tax->chart ) {
+ $self->add_transactions(SL::DB::AccTransaction->new(
+ chart_id => $tax_chart->id,
+ chart_link => $tax_chart->link,
+ amount => $taxamount,
+ taxkey => $tax->taxkey,
+ tax_id => $tax->id,
+ transdate => $self->transdate,
+ ob_transaction => $self->ob_transaction,
+ cb_transaction => $self->cb_transaction,
+ source => $params{source} // '',
+ memo => $params{memo} // '',
+ project_id => $params{project_id},
+ ));
+ };
+ };
+ return $self;
+};
+
+sub validate {
+ my ($self) = @_;
+
+ my @errors;
+
+ if ( $self->transactions && scalar @{ $self->transactions } ) {
+ my $debit_count = map { $_->amount } grep { $_->amount > 0 } @{ $self->transactions };
+ my $credit_count = map { $_->amount } grep { $_->amount < 0 } @{ $self->transactions };
+
+ if ( $debit_count > 1 && $credit_count > 1 ) {
+ push @errors, t8('Split entry detected. The values you have entered will result in an entry with more than one position on both debit and credit. ' .
+ 'Due to known problems involving accounting software kivitendo does not allow these.');
+ } elsif ( $credit_count == 0 && $debit_count == 0 ) {
+ push @errors, t8('Booking needs at least one debit and one credit booking!');
+ } else {
+ # transactions formally ok, now check for out of balance:
+ my $sum = sum map { $_->amount } @{ $self->transactions };
+ # compare rounded amount to 0, to get around floating point problems, e.g.
+ # $sum = -2.77555756156289e-17
+ push @errors, t8('Out of balance transaction!') unless $::form->round_amount($sum,5) == 0;
+ };
+ } else {
+ push @errors, t8('Empty transaction!');
+ };
+
+ # fields enforced by interface
+ push @errors, t8('Reference missing!') unless $self->reference;
+ push @errors, t8('Description missing!') unless $self->description;
+
+ # date checks
+ push @errors, t8('Transaction Date missing!') unless $self->transdate && ref($self->transdate) eq 'DateTime';
+
+ if ( $self->transdate ) {
+ if ( $::form->date_closed( $self->transdate, \%::myconfig) ) {
+ if ( !$self->id ) {
+ push @errors, t8('Cannot post transaction for a closed period!')
+ } else {
+ push @errors, t8('Cannot change transaction in a closed period!')
+ };
+ };
+
+ push @errors, t8('Cannot post transaction above the maximum future booking date!')
+ if $::form->date_max_future($self->transdate, \%::myconfig);
+ }
+
+ return @errors;
+}
+
1;
+
+__END__
+
+=pod
+
+=encoding UTF-8
+
+=head1 NAME
+
+SL::DB::GLTransaction: Rose model for GL transactions (table "gl")
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item C<post>
+
+Takes an unsaved but initialised GLTransaction object and saves it, but first
+validates the object, sets certain defaults (e.g. employee), and then also runs
+various checks, writes history, runs DATEV check, ...
+
+Returns C<$self> on success and dies otherwise. The whole process is run inside
+a transaction. If it fails then nothing is saved to or changed in the database.
+A new transaction is only started if none are active.
+
+Example of posting a GL transaction from scratch:
+
+ my $tax_0 = SL::DB::Manager::Tax->find_by(taxkey => 0, rate => 0.00);
+ my $gl_transaction = SL::DB::GLTransaction->new(
+ taxincluded => 1,
+ description => 'bar',
+ reference => 'bla',
+ transdate => DateTime->today_local,
+ )->add_chart_booking(
+ chart => SL::DB::Manager::Chart->find_by( description => 'Kasse' ),
+ credit => 100,
+ tax_id => $tax_0->id,
+ )->add_chart_booking(
+ chart => SL::DB::Manager::Chart->find_by( description => 'Bank' ),
+ debit => 100,
+ tax_id => $tax_0->id,
+ )->post;
+
+=item C<add_chart_booking %params>
+
+Adds an acc_trans entry to an existing GL transaction, depending on the tax it
+will also automatically create the tax entry. The GL transaction already needs
+to have certain values, e.g. transdate, taxincluded, ...
+
+Mandatory params are
+
+=over 2
+
+=item * chart as an RDBO object
+
+=item * tax_id
+
+=item * either debit OR credit (positive values)
+
+=back
+
+Optional params:
+
+=over 2
+
+=item * dec - number of decimals to round to, defaults to 2
+
+=item * source
+
+=item * memo
+
+=item * project_id
+
+=back
+
+All other values are taken directly from the GL transaction.
+
+For an example, see C<post>.
+
+After adding an acc_trans entry the GL transaction shouldn't be modified (e.g.
+values affecting the acc_trans entries, such as transdate or taxincluded
+shouldn't be changed). There is currently no method for recalculating the
+acc_trans entries after they were added.
+
+Return C<$self>, so it allows chaining.
+
+=item C<validate>
+
+Runs various checks to see if the GL transaction is ready to be C<post>ed.
+
+Will return an array of error strings if any necessary conditions aren't met.
+
+=back
+
+=head1 TODO
+
+Nothing here yet.
+
+=head1 AUTHOR
+
+Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>,
+G. Richardson E<lt>grichardson@kivitec.deE<gt>
+
+=cut
--- /dev/null
+# This file has been auto-generated only because it didn't exist.
+# Feel free to modify it at will; it will not be overwritten automatically.
+
+package SL::DB::Greeting;
+
+use strict;
+
+use SL::Util qw(trim);
+
+use SL::DB::MetaSetup::Greeting;
+use SL::DB::Manager::Greeting;
+
+__PACKAGE__->meta->initialize;
+
+__PACKAGE__->before_save('_before_save_trim_content');
+
+sub _before_save_trim_content {
+ $_[0]->description(trim($_[0]->description));
+ return 1;
+}
+
+1;
use SL::DB::Business;
use SL::DB::Chart;
use SL::DB::Contact;
+use SL::DB::ContactDepartment;
+use SL::DB::ContactTitle;
use SL::DB::CsvImportProfile;
use SL::DB::CsvImportProfileSetting;
use SL::DB::CsvImportReport;
use SL::DB::FollowUpLink;
use SL::DB::GLTransaction;
use SL::DB::GenericTranslation;
+use SL::DB::Greeting;
use SL::DB::History;
use SL::DB::Inventory;
use SL::DB::Invoice;
package SL::DB::Helper::AccountingPeriod;
use strict;
+use SL::Locale::String qw(t8);
use parent qw(Exporter);
use SL::DBUtils;
-our @EXPORT = qw(get_balance_starting_date);
+our @EXPORT = qw(get_balance_starting_date get_balance_startdate_method_options);
use Carp;
+sub get_balance_startdate_method_options {
+ [
+ { title => t8("After closed period"), value => "closed_to" },
+ { title => t8("Start of year"), value => "start_of_year" },
+ { title => t8("All transactions"), value => "all_transactions" },
+ { title => t8("Last opening balance or all transactions"), value => "last_ob_or_all_transactions" },
+ { title => t8("Last opening balance or start of year"), value => "last_ob_or_start_of_year" },
+ ]
+}
+
sub get_balance_starting_date {
- my ($self,$asofdate) = @_;
+ my ($self, $asofdate, $startdate_method) = @_;
- $asofdate ||= DateTime->today_local;
+ $asofdate ||= DateTime->today_local;
+ $startdate_method ||= $::instance_conf->get_balance_startdate_method;
unless ( ref $asofdate eq 'DateTime' ) {
$asofdate = $::locale->parse_date_to_object($asofdate);
my $dbh = $::form->get_standard_dbh;
- my $startdate_method = $::instance_conf->get_balance_startdate_method;
# We could use the following objects to determine the starting date for
# calculating the balance from asofdate (the reference date for the balance):
=over 4
-=item C<get_balance_starting_date $date>
+=item C<get_balance_startdate_method_options>
+
+Returns an arrayref of translated options for determining the startdate of a
+balance period or the yearend period. To be used as the options for a dropdown.
+
+=item C<get_balance_starting_date $date $startdate_method>
Given a date this method calculates and returns the starting date of the
financial period relative to that date, according to the configured
If no argument is passed the current day is assumed as default.
+If no startdate method is passed, the default method from defaults is used.
+
=back
=head1 BUGS
sub _make_by_type {
my ($package, $name, $type) = @_;
- _as_number ($package, $name, places => -2) if $type =~ /numeric | real | float/xi;
- _as_percent($package, $name, places => 2) if $type =~ /numeric | real | float/xi;
- _as_number ($package, $name, places => 0) if $type =~ /int/xi;
- _as_date ($package, $name) if $type =~ /date | timestamp/xi;
- _as_timestamp($package, $name) if $type =~ /timestamp/xi;
- _as_bool_yn($package, $name) if $type =~ /bool/xi;
+ _as_number ($package, $name, places => -2) if $type =~ /numeric | real | float/xi;
+ _as_null_number($package, $name, places => -2) if $type =~ /numeric | real | float/xi;
+ _as_percent ($package, $name, places => 2) if $type =~ /numeric | real | float/xi;
+ _as_number ($package, $name, places => 0) if $type =~ /int/xi;
+ _as_null_number($package, $name, places => 0) if $type =~ /int/xi;
+ _as_date ($package, $name) if $type =~ /date | timestamp/xi;
+ _as_timestamp ($package, $name) if $type =~ /timestamp/xi;
+ _as_bool_yn ($package, $name) if $type =~ /bool/xi;
}
sub _as_number {
};
}
+sub _as_null_number {
+ my $package = shift;
+ my $attribute = shift;
+ my %params = @_;
+
+ $params{places} = 2 if !defined($params{places});
+
+ no strict 'refs';
+ *{ $package . '::' . $attribute . '_as_null_number' } = sub {
+ my ($self, $string) = @_;
+
+ $self->$attribute($string eq '' ? undef : $::form->parse_amount(\%::myconfig, $string)) if @_ > 1;
+
+ return defined $self->$attribute ? $::form->format_amount(\%::myconfig, $self->$attribute, $params{places}) : '';
+ };
+}
+
sub _as_percent {
my $package = shift;
my $attribute = shift;
croak 'not an accessor' if @_ > 1;
- my $next_position = (max map { $_->$position_sub // 0 } @{ $self->$unsorted_sub }) + 1;
+ my $next_position = ((max map { $_->$position_sub // 0 } @{ $self->$unsorted_sub }) // 0) + 1;
return [
map { $_->[1] }
sort { $a->[0] <=> $b->[0] }
my @vc_fields = (qw(account_number bank bank_code bic business city contact country creditlimit
department_1 department_2 discount email fax gln greeting homepage iban language name
- phone street taxnumber ustid zipcode),
+ natural_person phone street taxnumber ustid zipcode),
"${vc}number",
($vc eq 'customer')? 'c_vendor_id': 'v_customer_id');
my @vc_prefixed_fields = qw(email fax notes number phone);
- _copy($self, $form, '', '', 1, qw(amount netamount marge_total marge_percent container_remaining_weight container_remaining_volume paid));
+ _copy($self, $form, '', '', 1, qw(amount netamount marge_total marge_percent container_remaining_weight container_remaining_volume paid exchangerate));
_copy($self->$vc, $form, '', '', 0, @vc_fields);
_copy($self->$vc, $form, $vc, '', 0, @vc_prefixed_fields);
_copy($self->contact, $form, '', '', 0, grep { /^cp_/ } map { $_->name } SL::DB::Contact->meta->columns) if _has($self, 'cp_id');
- _copy($self->shipto, $form, '', '', 0, grep { /^shipto/ } map { $_->name } SL::DB::Shipto->meta->columns) if _has($self, 'shipto_id');
_copy($self->globalproject, $form, 'globalproject', '', 0, qw(number description)) if _has($self, 'globalproject_id');
_copy($self->employee, $form, 'employee_', '', 0, map { $_->name } SL::DB::Employee->meta->columns) if _has($self, 'employee_id');
_copy($self->salesman, $form, 'salesman_', '', 0, map { $_->name } SL::DB::Employee->meta->columns) if _has($self, 'salesman_id');
_copy($self->acceptance_confirmed_by, $form, 'acceptance_confirmed_by_', '', 0, map { $_->name } SL::DB::Employee->meta->columns) if _has($self, 'acceptance_confirmed_by_id');
+ # Copy selected shipto to form, if set. Else, copy custom shipto, if set.
+ my $shipto = _has($self, 'shipto_id') ? $self->shipto
+ : _has($self, 'custom_shipto') ? $self->custom_shipto
+ : undef;
+ if ($shipto) {
+ _copy($shipto, $form, '', '', 0, grep { m{^shipto(?!_id$)} } map { $_->name } SL::DB::Shipto->meta->columns);
+ _copy_custom_variables($shipto, $form, 'shiptocvar_', '');
+ }
+
_handle_user_data($self, $form);
# company is employee and login independent
sub _copy_custom_variables {
my ($src, $form, $prefix, $postfix, $cvar_validity) = @_;
- my $obj = (any { ref($src) eq $_ } qw(SL::DB::OrderItem SL::DB::DeliveryOrderItem SL::DB::InvoiceItem SL::DB::Contact))
+ my $obj = (any { ref($src) eq $_ } qw(SL::DB::OrderItem SL::DB::DeliveryOrderItem SL::DB::InvoiceItem SL::DB::Contact SL::DB::Shipto))
? $src
: $src->customervendor;
bin => 'bin',
business => 'business',
chart => 'chart',
+ contact_departments => 'contact_department',
+ contact_titles => 'contact_title',
contacts => 'contact',
customer => 'customer',
csv_import_profiles => 'csv_import_profile',
follow_ups => 'follow_up',
generic_translations => 'generic_translation',
gl => 'GLTransaction',
+ greetings => 'greeting',
history_erp => 'history',
inventory => 'inventory',
invoice => 'invoice_item',
--- /dev/null
+package SL::DB::Helper::PDF_A;
+
+use strict;
+
+use parent qw(Exporter);
+our @EXPORT = qw(create_pdf_a_print_options);
+
+use Carp;
+use Template;
+
+sub _create_xmp_data {
+ my ($self, %params) = @_;
+
+ my $template = Template->new({
+ INTERPOLATE => 0,
+ EVAL_PERL => 0,
+ ABSOLUTE => 1,
+ PLUGIN_BASE => 'SL::Template::Plugin',
+ ENCODING => 'utf8',
+ }) || croak;
+
+ my $output = '';
+ $template->process(SL::System::Process::exe_dir() . '/templates/pdf/pdf_a_metadata.xmp', \%params, \$output) || croak $template->error;
+
+ return $output;
+}
+
+sub create_pdf_a_print_options {
+ my ($self) = @_;
+
+ require SL::DB::Language;
+
+ my $language_code = $self->can('language_id') && $self->language_id ? SL::DB::Language->load_cached($self->language_id)->template_code : undef;
+ $language_code ||= 'de';
+ my $pdf_language = $language_code =~ m{deutsch|german|^de$}i ? 'de-DE'
+ : $language_code =~ m{englisch|english|^en$}i ? 'en-US'
+ : '';
+ my $author = do {
+ no warnings 'once';
+ $::instance_conf->get_company
+ };
+
+ my $timestamp = DateTime->now_local->strftime('%Y-%m-%dT%H:%M:%S%z');
+ $timestamp =~ s{(..)$}{:$1};
+
+ return {
+ version => '3b',
+ xmp => _create_xmp_data(
+ $self,
+ pdf_a_version => '3',
+ pdf_a_conformance => 'B',
+ producer => 'pdfTeX',
+ timestamp => $timestamp, # 2019-11-05T15:26:20+01:00
+ meta_data => {
+ title => $self->displayable_name,
+ author => $author,
+ language => $pdf_language,
+ },
+ zugferd => {
+ conformance_level => 'EXTENDED',
+ document_file_name => 'ZUGFeRD-invoice.xml',
+ document_type => 'INVOICE',
+ version => '1.0',
+ },
+ ),
+ };
+}
+
+1;
}
my $transdate_obj;
- if (ref($params{transdate} eq 'DateTime')) {
+ if (ref($params{transdate}) eq 'DateTime') {
$transdate_obj = $params{transdate};
} else {
- $transdate_obj = $::locale->parse_date_to_object($params{transdate});
+ $transdate_obj = $::locale->parse_date_to_object($params{transdate});
};
croak t8('Illegal date') unless ref $transdate_obj;
my $self = shift;
- my $is_sales = ref($self) eq 'SL::DB::Invoice';
-
- my $skonto_date;
-
- if ( $is_sales ) {
- return undef unless ref $self->payment_terms;
- return undef unless $self->payment_terms->terms_skonto > 0;
- $skonto_date = DateTime->from_object(object => $self->transdate)->add(days => $self->payment_terms->terms_skonto);
- } else {
- return undef unless ref $self->vendor->payment_terms;
- return undef unless $self->vendor->payment_terms->terms_skonto > 0;
- $skonto_date = DateTime->from_object(object => $self->transdate)->add(days => $self->vendor->payment_terms->terms_skonto);
- };
-
- return $skonto_date;
+ return undef unless ref $self->payment_terms;
+ return undef unless $self->payment_terms->terms_skonto > 0;
+ return DateTime->from_object(object => $self->transdate)->add(days => $self->payment_terms->terms_skonto);
};
sub reference_account {
sub percent_skonto {
my $self = shift;
- my $is_sales = ref($self) eq 'SL::DB::Invoice';
-
my $percent_skonto = 0;
- if ( $is_sales ) {
- return undef unless ref $self->payment_terms;
- return undef unless $self->payment_terms->percent_skonto > 0;
- $percent_skonto = $self->payment_terms->percent_skonto;
- } else {
- return undef unless ref $self->vendor->payment_terms;
- return undef unless $self->vendor->payment_terms->terms_skonto > 0;
- $percent_skonto = $self->vendor->payment_terms->percent_skonto;
- };
+ return undef unless ref $self->payment_terms;
+ return undef unless $self->payment_terms->percent_skonto > 0;
+ $percent_skonto = $self->payment_terms->percent_skonto;
return $percent_skonto;
};
foreach my $transaction (@{ $self->transactions }) {
# find all transactions with an AR_amount or AP_amount link
my $tax = SL::DB::Manager::Tax->get_first( where => [taxkey => $transaction->taxkey, id => $transaction->tax_id ]);
+
+ # acc_trans entries for the taxes (chart_link == A[RP]_tax) often
+ # have combinations of taxkey & tax_id that don't exist in
+ # tax. Those must be skipped.
+ next if !$tax && ($transaction->chart_link !~ m{A[RP]_amount});
+
croak "no tax for taxkey " . $transaction->{taxkey} unless ref $tax;
$transaction->{chartlinks} = { map { $_ => 1 } split(m/:/, $transaction->chart_link) };
# TODO: check whether there are negative values in invoice / acc_trans ... credited items
# don't check whether skonto applies, because user may want to override this
- # return undef unless $self->percent_skonto; # for is_sales
- # return undef unless $self->vendor->payment_terms->percent_skonto; # for purchase
+ # return undef unless $self->percent_skonto;
my $is_sales = ref($self) eq 'SL::DB::Invoice';
If successful the return value will be 1 in scalar context or in list context
the two ids (acc_trans_id) of the newly created bookings.
+
=item C<reference_account>
Returns a chart object which is the chart of the invoice with link AR or AP.
=item C<percent_skonto>
Returns the configured skonto percentage of the payment terms of an invoice,
-e.g. 0.02 for 2%. Payment terms come from invoice settings for ar, from vendor
-settings for ap.
+e.g. 0.02 for 2%. Payment terms come from invoice settingssettings for ap.
=item C<amount_less_skonto>
-If the invoice has a payment term (via ar for sales, via vendor for purchase),
+If the invoice has a payment term,
calculate the amount to be paid in the case of skonto. This doesn't check,
whether skonto applies (i.e. skonto doesn't wasn't exceeded), it just subtracts
the configured percentage (e.g. 2%) from the total amount.
last_incex_chart_id => undef,
units_by_name => \%units_by_name,
price_factors_by_id => \%price_factors_by_id,
- taxes => { },
+ taxes_by_chart_id => { },
+ taxes_by_tax_id => { },
amounts => { },
amounts_cogs => { },
allocated => { },
$self->netamount( 0);
$self->marge_total(0);
- SL::DB::Manager::Chart->cache_taxkeys(date => $self->transdate);
+ SL::DB::Manager::Chart->cache_taxkeys(date => $self->deliverydate // $self->transdate);
my $idx = 0;
foreach my $item (@{ $self->items_sorted }) {
return $self unless wantarray;
- return map { ($_ => $data{$_}) } qw(taxes amounts amounts_cogs allocated exchangerate assembly_items items rounding);
+ return map { ($_ => $data{$_}) } qw(taxes_by_chart_id taxes_by_tax_id amounts amounts_cogs allocated exchangerate assembly_items items rounding);
}
sub _get_exchangerate {
$data->{invoicediff} += $sellprice * (1 - $item->discount) * $item->qty * $data->{exchangerate} / $item->price_factor - $linetotal if $self->taxincluded;
- my $taxkey = $part->get_taxkey(date => $self->transdate, is_sales => $data->{is_sales}, taxzone => $self->taxzone_id);
+ my $taxkey = $part->get_taxkey(date => $self->deliverydate // $self->transdate, is_sales => $data->{is_sales}, taxzone => $self->taxzone_id);
my $tax_rate = $taxkey->tax->rate;
my $tax_amount = undef;
}
if ($taxkey->tax->chart_id) {
- $data->{taxes}->{ $taxkey->tax->chart_id } ||= 0;
- $data->{taxes}->{ $taxkey->tax->chart_id } += $tax_amount;
+ $data->{taxes_by_chart_id}->{ $taxkey->tax->chart_id } ||= 0;
+ $data->{taxes_by_chart_id}->{ $taxkey->tax->chart_id } += $tax_amount;
+ $data->{taxes_by_tax_id}->{ $taxkey->tax_id } ||= 0;
+ $data->{taxes_by_tax_id}->{ $taxkey->tax_id } += $tax_amount;
} elsif ($tax_amount) {
die "tax_amount != 0 but no chart_id for taxkey " . $taxkey->id . " tax " . $taxkey->tax->id;
}
my ($self, $data, %params) = @_;
my $tax_diff = 0;
- foreach my $chart_id (keys %{ $data->{taxes} }) {
- my $rounded = _round($data->{taxes}->{$chart_id} * $data->{exchangerate}, 2);
- $tax_diff += $data->{taxes}->{$chart_id} * $data->{exchangerate} - $rounded if $self->taxincluded;
- $data->{taxes}->{$chart_id} = $rounded;
+ foreach my $chart_id (keys %{ $data->{taxes_by_chart_id} }) {
+ my $rounded = _round($data->{taxes_by_chart_id}->{$chart_id} * $data->{exchangerate}, 2);
+ $tax_diff += $data->{taxes_by_chart_id}->{$chart_id} * $data->{exchangerate} - $rounded if $self->taxincluded;
+ $data->{taxes_by_chart_id}->{$chart_id} = $rounded;
}
$self->netamount(sum map { $_->{amount} } values %{ $data->{amounts} });
_dbg("Sna " . $self->netamount . " idiff " . $data->{invoicediff} . " tdiff ${tax_diff}");
- my $tax = sum values %{ $data->{taxes} };
+ my $tax = sum values %{ $data->{taxes_by_chart_id} };
$amount = $netamount + $tax;
my $grossamount = _round($amount, 2, 1);
$data->{rounding} = _round($grossamount - $amount, 2);
=over 2
-=item C<taxes>
+=item C<taxes_by_chart_id>
A hash reference with the calculated taxes. The keys are chart IDs,
+the values the rounded calculated taxes.
+
+=item C<taxes_by_tax_id>
+
+A hash reference with the calculated taxes. The keys are tax IDs,
the values the calculated taxes.
=item C<amounts>
--- /dev/null
+package SL::DB::Helper::VATIDNrValidation;
+
+use strict;
+
+use Carp;
+use SL::Locale::String qw(t8);
+use SL::VATIDNr;
+
+my $_validator;
+
+sub _validate {
+ my ($self, $attribute) = @_;
+
+ my $number = SL::VATIDNr->clean($self->$attribute);
+
+ return () unless length($number);
+ return () if SL::VATIDNr->validate($number);
+ return ($::locale->text("The VAT ID number '#1' is invalid.", $self->$attribute));
+}
+
+sub import {
+ my ($package, @attributes) = @_;
+
+ my $caller_package = caller;
+ @attributes = qw(ustid) unless @attributes;
+
+ no strict 'refs';
+
+ *{ $caller_package . '::validate_vat_id_numbers' } = sub {
+ my ($self) = @_;
+
+ return map { SL::DB::Helper::VATIDNrValidation::_validate($self, $_) } @attributes;
+ };
+}
+
+1;
+
+__END__
+
+=pod
+
+=encoding utf8
+
+=head1 NAME
+
+SL::DB::Helper::VATIDNrValidation - Mixin for validating VAT ID number attributes
+
+=head1 SYNOPSIS
+
+ package SL::DB::SomeObject;
+ use SL::DB::Helper::VATIDNrValidation [ ATTRIBUTES ];
+
+ sub validate {
+ my ($self) = @_;
+
+ my @errors;
+ …
+ push @errors, $self->validate_vat_id_numbers;
+
+ return @errors;
+ }
+
+This mixin provides a function C<validate_vat_id_numbers> that returns
+a list of error messages, one for each attribute that fails the VAT ID
+number validation. If all attributes are valid or empty then an empty
+list is returned.
+
+The names of attributes to check can be given as an import list to the
+mixin package. If no attributes are given the single attribute C<ustid>
+is used.
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item C<validate_vat_id_numbers>
+
+This function iterates over all configured attributes and validates
+their content according to how VAT ID numbers are supposed to be
+formatted in the European Union (or the enterprise identification
+numbers in Switzerland). An attribute that is undefined, empty or
+consists solely of whitespace is considered valid, too.
+
+The function returns a list of human-readable error messages suitable
+for use in a general C<validate> function (see SYNOPSIS). For each
+attribute failing the check the list will include one error message.
+
+If all attributes are valid then an empty list is returned.
+
+=back
+
+=head1 BUGS
+
+Nothing here yet.
+
+=head1 AUTHOR
+
+Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
+
+=cut
--- /dev/null
+package SL::DB::Helper::ZUGFeRD;
+
+use strict;
+use utf8;
+
+use parent qw(Exporter);
+our @EXPORT = qw(create_zugferd_data create_zugferd_xmp_data);
+
+use SL::DB::BankAccount;
+use SL::DB::GenericTranslation;
+use SL::DB::Tax;
+use SL::DB::TaxKey;
+use SL::Helper::ISO3166;
+use SL::Helper::ISO4217;
+use SL::Helper::UNECERecommendation20;
+use SL::VATIDNr;
+
+use Carp;
+use Encode qw(encode);
+use List::MoreUtils qw(any pairwise);
+use List::Util qw(first sum);
+use Template;
+use XML::Writer;
+
+my @line_names = qw(LineOne LineTwo LineThree);
+
+sub _u8 {
+ my ($value) = @_;
+ return encode('UTF-8', $value // '');
+}
+
+sub _r2 {
+ my ($value) = @_;
+ return $::form->round_amount($value, 2);
+}
+
+sub _type_name {
+ my ($self) = @_;
+ my $type = $self->invoice_type;
+
+ no warnings 'once';
+ return $type eq 'ar_transaction' ? $::locale->text('Invoice') : $self->displayable_type;
+}
+
+sub _type_code {
+ my ($self) = @_;
+ my $type = $self->invoice_type;
+
+ # 326 (Partial invoice)
+ # 380 (Commercial invoice)
+ # 384 (Corrected Invoice)
+ # 381 (Credit note)
+ # 389 (Credit note, self billed invoice)
+
+ return $type eq 'credit_note' ? 381
+ : $type eq 'invoice_storno' ? 457
+ : $type eq 'credit_note_storno' ? 458
+ : 380;
+}
+
+sub _unit_code {
+ my ($unit) = @_;
+
+ # Mapping from kivitendo's units to UN/ECE Recommendation 20 & 21.
+ my $code = SL::Helper::UNECERecommendation20::map_name_to_code($unit);
+ return $code if $code;
+
+ $::lxdebug->message(LXDebug::WARN(), "ZUGFeRD unit name mapping: no UN/ECE Recommendation 20/21 unit known for kivitendo unit '$unit'; using 'C62'");
+
+ return 'C62';
+}
+
+sub _parse_our_address {
+ my @result;
+ my @street = grep { $_ } ($::instance_conf->get_address_street1, $::instance_conf->get_address_street2);
+
+ push @result, [ 'PostcodeCode', $::instance_conf->get_address_zipcode ] if $::instance_conf->get_address_zipcode;
+ push @result, grep { $_->[1] } pairwise { [ $a, $b] } @line_names, @street;
+ push @result, [ 'CityName', $::instance_conf->get_address_city ] if $::instance_conf->get_address_city;
+ push @result, [ 'CountryID', SL::Helper::ISO3166::map_name_to_alpha_2_code($::instance_conf->get_address_country) // 'DE' ];
+
+ return @result;
+}
+
+sub _customer_postal_trade_address {
+ my (%params) = @_;
+
+ # <ram:PostalTradeAddress>
+ $params{xml}->startTag("ram:PostalTradeAddress");
+
+ my @parts = grep { $_ } map { $params{customer}->$_ } qw(department_1 department_2 street);
+
+ $params{xml}->dataElement("ram:PostcodeCode", _u8($params{customer}->zipcode));
+ $params{xml}->dataElement("ram:" . $_->[0], _u8($_->[1])) for grep { $_->[1] } pairwise { [ $a, $b] } @line_names, @parts;
+ $params{xml}->dataElement("ram:CityName", _u8($params{customer}->city));
+ $params{xml}->dataElement("ram:CountryID", _u8(SL::Helper::ISO3166::map_name_to_alpha_2_code($params{customer}->country) // 'DE'));
+ $params{xml}->endTag;
+ # </ram:PostalTradeAddress>
+}
+
+sub _tax_rate_and_code {
+ my ($taxzone, $tax) = @_;
+
+ my ($tax_rate, $tax_code) = @_;
+
+ if ($taxzone->description =~ m{Au.*erhalb}) {
+ $tax_rate = 0;
+ $tax_code = 'G';
+
+ } elsif ($taxzone->description =~ m{EU mit}) {
+ $tax_rate = 0;
+ $tax_code = 'K';
+
+ } else {
+ $tax_rate = $tax->rate * 100;
+ $tax_code = !$tax_rate ? 'Z' : 'S';
+ }
+
+ return (rate => $tax_rate, code => $tax_code);
+}
+
+sub _line_item {
+ my ($self, %params) = @_;
+
+ my $item_ptc = $params{ptc_data}->{items}->[$params{line_number}];
+
+ my $taxkey = $item_ptc->{taxkey_id} ? SL::DB::TaxKey->load_cached($item_ptc->{taxkey_id}) : undef;
+ my $tax = $item_ptc->{taxkey_id} ? SL::DB::Tax->load_cached($taxkey->tax_id) : undef;
+ my %tax_info = _tax_rate_and_code($self->taxzone, $tax);
+
+ # <ram:IncludedSupplyChainTradeLineItem>
+ $params{xml}->startTag("ram:IncludedSupplyChainTradeLineItem");
+
+ # <ram:AssociatedDocumentLineDocument>
+ $params{xml}->startTag("ram:AssociatedDocumentLineDocument");
+ $params{xml}->dataElement("ram:LineID", $params{line_number} + 1);
+ $params{xml}->endTag;
+
+ $params{xml}->startTag("ram:SpecifiedTradeProduct");
+ $params{xml}->dataElement("ram:SellerAssignedID", _u8($params{item}->part->partnumber));
+ $params{xml}->dataElement("ram:Name", _u8($params{item}->description));
+ $params{xml}->endTag;
+
+ $params{xml}->startTag("ram:SpecifiedLineTradeAgreement");
+ $params{xml}->startTag("ram:NetPriceProductTradePrice");
+ $params{xml}->dataElement("ram:ChargeAmount", _r2($item_ptc->{sellprice}));
+ $params{xml}->endTag;
+ $params{xml}->endTag;
+ # </ram:SpecifiedLineTradeAgreement>
+
+ # <ram:SpecifiedLineTradeDelivery>
+ $params{xml}->startTag("ram:SpecifiedLineTradeDelivery");
+ $params{xml}->dataElement("ram:BilledQuantity", $params{item}->qty, unitCode => _unit_code($params{item}->unit));
+ $params{xml}->endTag;
+ # </ram:SpecifiedLineTradeDelivery>
+
+ # <ram:SpecifiedLineTradeSettlement>
+ $params{xml}->startTag("ram:SpecifiedLineTradeSettlement");
+
+ # <ram:ApplicableTradeTax>
+ $params{xml}->startTag("ram:ApplicableTradeTax");
+ $params{xml}->dataElement("ram:TypeCode", "VAT");
+ $params{xml}->dataElement("ram:CategoryCode", $tax_info{code});
+ $params{xml}->dataElement("ram:RateApplicablePercent", _r2($tax_info{rate}));
+ $params{xml}->endTag;
+ # </ram:ApplicableTradeTax>
+
+ # <ram:SpecifiedTradeSettlementLineMonetarySummation>
+ $params{xml}->startTag("ram:SpecifiedTradeSettlementLineMonetarySummation");
+ $params{xml}->dataElement("ram:LineTotalAmount", _r2($item_ptc->{linetotal}));
+ $params{xml}->endTag;
+ # </ram:SpecifiedTradeSettlementLineMonetarySummation>
+
+ $params{xml}->endTag;
+ # </ram:SpecifiedLineTradeSettlement>
+
+ $params{xml}->endTag;
+ # <ram:IncludedSupplyChainTradeLineItem>
+}
+
+sub _specified_trade_settlement_payment_means {
+ my ($self, %params) = @_;
+
+ # <ram:SpecifiedTradeSettlementPaymentMeans>
+ $params{xml}->startTag('ram:SpecifiedTradeSettlementPaymentMeans');
+ $params{xml}->dataElement('ram:TypeCode', $self->direct_debit ? 59 : 58); # 59 = SEPA direct debit, 58 = SEPA credit transfer
+
+ if ($self->direct_debit) {
+ $params{xml}->startTag('ram:PayerPartyDebtorFinancialAccount');
+ $params{xml}->dataElement('ram:IBANID', $self->customer->iban);
+ $params{xml}->endTag;
+
+ } else {
+ $params{xml}->startTag('ram:PayeePartyCreditorFinancialAccount');
+ $params{xml}->dataElement('ram:IBANID', $params{bank_account}->iban);
+ $params{xml}->endTag;
+ }
+
+ $params{xml}->endTag;
+ # </ram:SpecifiedTradeSettlementPaymentMeans>
+}
+
+sub _taxes {
+ my ($self, %params) = @_;
+
+ my %taxkey_info;
+
+ foreach my $item (@{ $params{ptc_data}->{items} }) {
+ $taxkey_info{$item->{taxkey_id}} //= {
+ linetotal => 0,
+ tax_amount => 0,
+ };
+ my $info = $taxkey_info{$item->{taxkey_id}};
+ $info->{taxkey} //= SL::DB::TaxKey->load_cached($item->{taxkey_id});
+ $info->{tax} //= SL::DB::Tax->load_cached($info->{taxkey}->tax_id);
+ $info->{linetotal} += $item->{linetotal};
+ }
+
+ foreach my $taxkey_id (sort keys %taxkey_info) {
+ my $info = $taxkey_info{$taxkey_id};
+ my %tax_info = _tax_rate_and_code($self->taxzone, $info->{tax});
+
+ # <ram:ApplicableTradeTax>
+ $params{xml}->startTag("ram:ApplicableTradeTax");
+ $params{xml}->dataElement("ram:CalculatedAmount", _r2($params{ptc_data}->{taxes_by_tax_id}->{$info->{taxkey}->tax_id}));
+ $params{xml}->dataElement("ram:TypeCode", "VAT");
+ $params{xml}->dataElement("ram:BasisAmount", _r2($info->{linetotal}));
+ $params{xml}->dataElement("ram:CategoryCode", $tax_info{code});
+ $params{xml}->dataElement("ram:RateApplicablePercent", _r2($tax_info{rate}));
+ $params{xml}->endTag;
+ # </ram:ApplicableTradeTax>
+ }
+}
+
+sub _calculate_payment_terms_values {
+ my ($self) = @_;
+
+ my (%vars, %amounts, %formatted_amounts);
+
+ local $::myconfig{numberformat} = $::myconfig{numberformat};
+ local $::myconfig{dateformat} = $::myconfig{dateformat};
+
+ if ($self->language_id) {
+ my $language = SL::DB::Language->load_cached($self->language_id);
+ $::myconfig{dateformat} = $language->output_dateformat if $language->output_dateformat;
+ $::myconfig{numberformat} = $language->output_numberformat if $language->output_numberformat;
+ }
+
+ $vars{currency} = $self->currency->name if $self->currency;
+ $vars{$_} = $self->customer->$_ for qw(account_number bank bank_code bic iban mandate_date_of_signature mandator_id);
+ $vars{$_} = $self->payment_terms->$_ for qw(terms_netto terms_skonto percent_skonto);
+ $vars{payment_description} = $self->payment_terms->description;
+ $vars{netto_date} = $self->payment_terms->calc_date(reference_date => $self->transdate, due_date => $self->duedate, terms => 'net')->to_kivitendo;
+ $vars{skonto_date} = $self->payment_terms->calc_date(reference_date => $self->transdate, due_date => $self->duedate, terms => 'discount')->to_kivitendo;
+
+ $amounts{invtotal} = $self->amount;
+ $amounts{total} = $self->amount - $self->paid;
+
+ $amounts{skonto_in_percent} = 100.0 * $vars{percent_skonto};
+ $amounts{skonto_amount} = $amounts{invtotal} * $vars{percent_skonto};
+ $amounts{invtotal_wo_skonto} = $amounts{invtotal} * (1 - $vars{percent_skonto});
+ $amounts{total_wo_skonto} = $amounts{total} * (1 - $vars{percent_skonto});
+
+ foreach (keys %amounts) {
+ $amounts{$_} = $::form->round_amount($amounts{$_}, 2);
+ $formatted_amounts{$_} = $::form->format_amount(\%::myconfig, $amounts{$_}, 2);
+ }
+
+ return (
+ vars => \%vars,
+ amounts => \%amounts,
+ formatted_amounts => \%formatted_amounts,
+ );
+}
+
+sub _format_payment_terms_description {
+ my ($self, %params) = @_;
+
+ my $description = ($self->payment_terms->translated_attribute('description_long_invoice', $self->language_id) // '') || $self->payment_terms->description_long_invoice;
+ $description =~ s{<\%$_\%>}{ $params{vars}->{$_} }ge for keys %{ $params{vars} };
+ $description =~ s{<\%$_\%>}{ $params{formatted_amounts}->{$_} }ge for keys %{ $params{formatted_amounts} };
+
+ return $description;
+}
+
+sub _payment_terms {
+ my ($self, %params) = @_;
+
+ return unless $self->payment_terms;
+
+ my %payment_terms_vars = _calculate_payment_terms_values($self);
+
+ # <ram:SpecifiedTradePaymentTerms>
+ $params{xml}->startTag("ram:SpecifiedTradePaymentTerms");
+
+ $params{xml}->dataElement("ram:Description", _u8(_format_payment_terms_description($self, %payment_terms_vars)));
+
+ # <ram:DueDateDateTime>
+ $params{xml}->startTag("ram:DueDateDateTime");
+ $params{xml}->dataElement("udt:DateTimeString", $self->duedate->strftime('%Y%m%d'), format => "102");
+ $params{xml}->endTag;
+ # </ram:DueDateDateTime>
+
+ if ($self->payment_terms->percent_skonto && $self->payment_terms->terms_skonto) {
+ my $currency_id = _u8(SL::Helper::ISO4217::map_currency_name_to_code($self->currency->name) // 'EUR');
+
+ # <ram:ApplicableTradePaymentDiscountTerms>
+ $params{xml}->startTag("ram:ApplicableTradePaymentDiscountTerms");
+ $params{xml}->dataElement("ram:BasisPeriodMeasure", $self->payment_terms->terms_skonto, unitCode => "DAY");
+ $params{xml}->dataElement("ram:BasisAmount", _r2($payment_terms_vars{amounts}->{invtotal}), currencyID => $currency_id);
+ $params{xml}->dataElement("ram:CalculationPercent", _r2($self->payment_terms->percent_skonto * 100));
+ $params{xml}->endTag;
+ # </ram:ApplicableTradePaymentDiscountTerms>
+ }
+
+ $params{xml}->endTag;
+ # </ram:SpecifiedTradePaymentTerms>
+}
+
+sub _totals {
+ my ($self, %params) = @_;
+
+ # <ram:SpecifiedTradeSettlementHeaderMonetarySummation>
+ $params{xml}->startTag("ram:SpecifiedTradeSettlementHeaderMonetarySummation");
+
+ $params{xml}->dataElement("ram:LineTotalAmount", _r2($self->netamount));
+ $params{xml}->dataElement("ram:TaxBasisTotalAmount", _r2($self->netamount));
+ $params{xml}->dataElement("ram:TaxTotalAmount", _r2(sum(values %{ $params{ptc_data}->{taxes_by_tax_id} })), currencyID => "EUR");
+ $params{xml}->dataElement("ram:GrandTotalAmount", _r2($self->amount));
+ $params{xml}->dataElement("ram:TotalPrepaidAmount", _r2($self->paid));
+ $params{xml}->dataElement("ram:DuePayableAmount", _r2($self->amount - $self->paid));
+
+ $params{xml}->endTag;
+ # </ram:SpecifiedTradeSettlementHeaderMonetarySummation>
+}
+
+sub _exchanged_document_context {
+ my ($self, %params) = @_;
+
+ # <rsm:ExchangedDocumentContext>
+ $params{xml}->startTag("rsm:ExchangedDocumentContext");
+
+ if ($self->customer->create_zugferd_invoices_for_this_customer == 2) {
+ $params{xml}->startTag("ram:TestIndicator");
+ $params{xml}->dataElement("udt:Indicator", "true");
+ $params{xml}->endTag;
+ }
+
+ $params{xml}->startTag("ram:GuidelineSpecifiedDocumentContextParameter");
+ $params{xml}->dataElement("ram:ID", "urn:cen.eu:en16931:2017#conformant#urn:zugferd.de:2p0:extended");
+ $params{xml}->endTag;
+ $params{xml}->endTag;
+ # </rsm:ExchangedDocumentContext>
+}
+
+sub _included_note {
+ my ($self, %params) = @_;
+
+ $params{xml}->startTag("ram:IncludedNote");
+ $params{xml}->dataElement("ram:Content", _u8($params{note}));
+ $params{xml}->endTag;
+}
+
+sub _exchanged_document {
+ my ($self, %params) = @_;
+
+ # <rsm:ExchangedDocument>
+ $params{xml}->startTag("rsm:ExchangedDocument");
+
+ $params{xml}->dataElement("ram:ID", _u8($self->invnumber));
+ $params{xml}->dataElement("ram:Name", _u8(_type_name($self)));
+ $params{xml}->dataElement("ram:TypeCode", _u8(_type_code($self)));
+
+ # <ram:IssueDateTime>
+ $params{xml}->startTag("ram:IssueDateTime");
+ $params{xml}->dataElement("udt:DateTimeString", $self->transdate->strftime('%Y%m%d'), format => "102");
+ $params{xml}->endTag;
+ # </ram:IssueDateTime>
+
+ if ($self->language && (($self->language->template_code // '') =~ m{^(de|en)}i)) {
+ $params{xml}->dataElement("ram:LanguageID", uc($1));
+ }
+
+ my $std_notes = SL::DB::Manager::GenericTranslation->get_all(
+ where => [
+ translation_type => 'ZUGFeRD/notes',
+ or => [
+ language_id => undef,
+ language_id => $self->language_id,
+ ],
+ '!translation' => undef,
+ '!translation' => '',
+ ],
+ );
+
+ my $std_note = first { $_->language_id == $self->language_id } @{ $std_notes };
+ $std_note //= first { !defined $_->language_id } @{ $std_notes };
+
+ my $notes = $self->notes_as_stripped_html;
+
+ _included_note($self, %params, note => $self->transaction_description) if $self->transaction_description;
+ _included_note($self, %params, note => $notes) if $notes;
+ _included_note($self, %params, note => $std_note->translation) if $std_note;
+
+ $params{xml}->endTag;
+ # </rsm:ExchangedDocument>
+}
+
+sub _specified_tax_registration {
+ my ($ustid_nr, %params) = @_;
+
+ # <ram:SpecifiedTaxRegistration>
+ $params{xml}->startTag("ram:SpecifiedTaxRegistration");
+ $params{xml}->dataElement("ram:ID", _u8(SL::VATIDNr->normalize($ustid_nr)), schemeID => "VA");
+ $params{xml}->endTag;
+ # </ram:SpecifiedTaxRegistration>
+}
+
+sub _seller_trade_party {
+ my ($self, %params) = @_;
+
+ my @our_address = _parse_our_address();
+
+ my $sales_person = $self->salesman;
+ my $sales_person_auth = SL::DB::Manager::AuthUser->find_by(login => $sales_person->login);
+ my %sales_person_cfg = $sales_person_auth ? %{ $sales_person_auth->config_values } : ();
+ $sales_person_cfg{email} ||= $sales_person->deleted_email;
+ $sales_person_cfg{tel} ||= $sales_person->deleted_tel;
+
+ # <ram:SellerTradeParty>
+ $params{xml}->startTag("ram:SellerTradeParty");
+ $params{xml}->dataElement("ram:ID", _u8($self->customer->c_vendor_id)) if ($self->customer->c_vendor_id // '') ne '';
+ $params{xml}->dataElement("ram:Name", _u8($::instance_conf->get_company));
+
+ # <ram:DefinedTradeContact>
+ $params{xml}->startTag("ram:DefinedTradeContact");
+
+ $params{xml}->dataElement("ram:PersonName", _u8($sales_person_cfg{name} || $sales_person_cfg{login}));
+
+ if ($sales_person_cfg{tel}) {
+ $params{xml}->startTag("ram:TelephoneUniversalCommunication");
+ $params{xml}->dataElement("ram:CompleteNumber", _u8($sales_person_cfg{tel}));
+ $params{xml}->endTag;
+ }
+
+ if ($sales_person_cfg{email}) {
+ $params{xml}->startTag("ram:EmailURIUniversalCommunication");
+ $params{xml}->dataElement("ram:URIID", _u8($sales_person_cfg{email}));
+ $params{xml}->endTag;
+ }
+
+ $params{xml}->endTag;
+ # </ram:DefinedTradeContact>
+
+ if (@our_address) {
+ # <ram:PostalTradeAddress>
+ $params{xml}->startTag("ram:PostalTradeAddress");
+ foreach my $element (@our_address) {
+ $params{xml}->dataElement("ram:" . $element->[0], _u8($element->[1]));
+ }
+ $params{xml}->endTag;
+ # </ram:PostalTradeAddress>
+ }
+
+ _specified_tax_registration($::instance_conf->get_co_ustid, %params);
+
+ $params{xml}->endTag;
+ # </ram:SellerTradeParty>
+}
+
+sub _buyer_trade_party {
+ my ($self, %params) = @_;
+
+ # <ram:BuyerTradeParty>
+ $params{xml}->startTag("ram:BuyerTradeParty");
+ $params{xml}->dataElement("ram:ID", _u8($self->customer->customernumber));
+ $params{xml}->dataElement("ram:Name", _u8($self->customer->name));
+
+ _customer_postal_trade_address(%params, customer => $self->customer);
+ _specified_tax_registration($self->customer->ustid, %params);
+
+ $params{xml}->endTag;
+ # </ram:BuyerTradeParty>
+}
+
+sub _included_supply_chain_trade_line_item {
+ my ($self, %params) = @_;
+
+ my $line_number = 0;
+ foreach my $item (@{ $self->items }) {
+ _line_item($self, %params, item => $item, line_number => $line_number);
+ $line_number++;
+ }
+}
+
+sub _applicable_header_trade_agreement {
+ my ($self, %params) = @_;
+
+ # <ram:ApplicableHeaderTradeAgreement>
+ $params{xml}->startTag("ram:ApplicableHeaderTradeAgreement");
+
+ _seller_trade_party($self, %params);
+ _buyer_trade_party($self, %params);
+
+ if ($self->cusordnumber) {
+ # <ram:BuyerOrderReferencedDocument>
+ $params{xml}->startTag("ram:BuyerOrderReferencedDocument");
+ $params{xml}->dataElement("ram:IssuerAssignedID", _u8($self->cusordnumber));
+ $params{xml}->endTag;
+ # </ram:BuyerOrderReferencedDocument>
+ }
+
+ $params{xml}->endTag;
+ # </ram:ApplicableHeaderTradeAgreement>
+}
+
+sub _applicable_header_trade_delivery {
+ my ($self, %params) = @_;
+
+ # <ram:ApplicableHeaderTradeDelivery>
+ $params{xml}->startTag("ram:ApplicableHeaderTradeDelivery");
+ # <ram:ActualDeliverySupplyChainEvent>
+ $params{xml}->startTag("ram:ActualDeliverySupplyChainEvent");
+
+ $params{xml}->startTag("ram:OccurrenceDateTime");
+ $params{xml}->dataElement("udt:DateTimeString", ($self->deliverydate // $self->transdate)->strftime('%Y%m%d'), format => "102");
+ $params{xml}->endTag;
+
+ $params{xml}->endTag;
+ # </ram:ActualDeliverySupplyChainEvent>
+ $params{xml}->endTag;
+ # </ram:ApplicableHeaderTradeDelivery>
+}
+
+sub _applicable_header_trade_settlement {
+ my ($self, %params) = @_;
+
+ # <ram:ApplicableHeaderTradeSettlement>
+ $params{xml}->startTag("ram:ApplicableHeaderTradeSettlement");
+ $params{xml}->dataElement("ram:InvoiceCurrencyCode", _u8(SL::Helper::ISO4217::map_currency_name_to_code($self->currency->name) // 'EUR'));
+
+ _specified_trade_settlement_payment_means($self, %params);
+ _taxes($self, %params);
+ _payment_terms($self, %params);
+ _totals($self, %params);
+
+ $params{xml}->endTag;
+ # </ram:ApplicableHeaderTradeSettlement>
+}
+
+sub _supply_chain_trade_transaction {
+ my ($self, %params) = @_;
+
+ # <rsm:SupplyChainTradeTransaction>
+ $params{xml}->startTag("rsm:SupplyChainTradeTransaction");
+
+ _included_supply_chain_trade_line_item($self, %params);
+ _applicable_header_trade_agreement($self, %params);
+ _applicable_header_trade_delivery($self, %params);
+ _applicable_header_trade_settlement($self, %params);
+
+ $params{xml}->endTag;
+ # </rsm:SupplyChainTradeTransaction>
+}
+
+sub _validate_data {
+ my ($self) = @_;
+
+ my %result;
+ my $prefix = $::locale->text('The ZUGFeRD invoice data cannot be generated because the data validation failed.') . ' ';
+
+ if (!$::instance_conf->get_co_ustid) {
+ SL::X::ZUGFeRDValidation->throw(message => $prefix . $::locale->text('The VAT registration number is missing in the client configuration.'));
+ }
+
+ if (!SL::VATIDNr->validate($::instance_conf->get_co_ustid)) {
+ SL::X::ZUGFeRDValidation->throw(message => $prefix . $::locale->text("The VAT ID number in the client configuration is invalid."));
+ }
+
+ if (!$::instance_conf->get_company || any { my $get = "get_address_$_"; !$::instance_conf->$get } qw(street1 zipcode city)) {
+ SL::X::ZUGFeRDValidation->throw(message => $prefix . $::locale->text('The company\'s address information is incomplete in the client configuration.'));
+ }
+
+ if ($::instance_conf->get_address_country && !SL::Helper::ISO3166::map_name_to_alpha_2_code($::instance_conf->get_address_country)) {
+ SL::X::ZUGFeRDValidation->throw(message => $prefix . $::locale->text('The country from the company\'s address in the client configuration cannot be mapped to an ISO 3166-1 alpha 2 code.'));
+ }
+
+ if ($self->customer->country && !SL::Helper::ISO3166::map_name_to_alpha_2_code($self->customer->country)) {
+ SL::X::ZUGFeRDValidation->throw(message => $prefix . $::locale->text('The country from the customer\'s address cannot be mapped to an ISO 3166-1 alpha 2 code.'));
+ }
+
+ if (!SL::Helper::ISO4217::map_currency_name_to_code($self->currency->name)) {
+ SL::X::ZUGFeRDValidation->throw(message => $prefix . $::locale->text('The currency "#1" cannot be mapped to an ISO 4217 currency code.', $self->currency->name));
+ }
+
+ my $failed_unit = first { !SL::Helper::UNECERecommendation20::map_name_to_code($_) } map { $_->unit } @{ $self->items };
+ if ($failed_unit) {
+ SL::X::ZUGFeRDValidation->throw(message => $prefix . $::locale->text('One of the units used (#1) cannot be mapped to a known unit code from the UN/ECE Recommendation 20 list.', $failed_unit));
+ }
+
+ if ($self->direct_debit) {
+ if (!$self->customer->iban) {
+ SL::X::ZUGFeRDValidation->throw(message => $prefix . $::locale->text('The customer\'s bank account number (IBAN) is missing.'));
+ }
+
+ } else {
+ my $bank_accounts = SL::DB::Manager::BankAccount->get_all;
+ $result{bank_account} = scalar(@{ $bank_accounts }) == 1 ? $bank_accounts->[0] : first { $_->use_for_zugferd } @{ $bank_accounts };
+
+ if (!$result{bank_account}) {
+ SL::X::ZUGFeRDValidation->throw(message => $prefix . $::locale->text('No bank account flagged for ZUGFeRD usage was found.'));
+ }
+ }
+
+ return %result;
+}
+
+sub create_zugferd_data {
+ my ($self) = @_;
+
+ my $output = '';
+
+ my %params = _validate_data($self);
+ $params{ptc_data} = { $self->calculate_prices_and_taxes };
+ $params{xml} = XML::Writer->new(
+ OUTPUT => \$output,
+ DATA_MODE => 1,
+ DATA_INDENT => 2,
+ ENCODING => 'utf-8',
+ );
+
+ $params{xml}->xmlDecl();
+
+ # <rsm:CrossIndustryInvoice>
+ $params{xml}->startTag("rsm:CrossIndustryInvoice",
+ "xmlns:a" => "urn:un:unece:uncefact:data:standard:QualifiedDataType:100",
+ "xmlns:rsm" => "urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100",
+ "xmlns:qdt" => "urn:un:unece:uncefact:data:standard:QualifiedDataType:10",
+ "xmlns:ram" => "urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100",
+ "xmlns:xs" => "http://www.w3.org/2001/XMLSchema",
+ "xmlns:udt" => "urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100");
+
+ _exchanged_document_context($self, %params);
+ _exchanged_document($self, %params);
+ _supply_chain_trade_transaction($self, %params);
+
+ $params{xml}->endTag;
+ # </rsm:CrossIndustryInvoice>
+
+ return $output;
+}
+
+sub create_zugferd_xmp_data {
+ my ($self) = @_;
+
+ return {
+ conformance_level => 'EXTENDED',
+ document_file_name => 'ZUGFeRD-invoice.xml',
+ document_type => 'INVOICE',
+ version => '1.0',
+ };
+}
+
+1;
use SL::DB::Helper::AttrSorted;
use SL::DB::Helper::FlattenToForm;
use SL::DB::Helper::LinkedRecords;
+use SL::DB::Helper::PDF_A;
use SL::DB::Helper::PriceTaxCalculator;
use SL::DB::Helper::PriceUpdater;
use SL::DB::Helper::TransNumberGenerator;
+use SL::DB::Helper::ZUGFeRD;
use SL::Locale::String qw(t8);
use SL::DB::CustomVariable;
my %args = ( map({ ( $_ => $source->$_ ) } qw(customer_id taxincluded shippingpoint shipvia notes intnotes salesman_id cusordnumber ordnumber department_id
cp_id language_id taxzone_id globalproject_id transaction_description currency_id delivery_term_id), @columns),
- transdate => DateTime->today_local,
+ transdate => $params{transdate} // DateTime->today_local,
gldate => DateTime->today_local,
duedate => $terms ? $terms->calc_date(reference_date => DateTime->today_local) : DateTime->today_local,
invoice => 1,
$args{payment_id} = ( $terms ? $terms->id : $source->payment_id);
- if ($source->type =~ /_order$/) {
+ if ($source->type =~ /_delivery_order$/) {
+ $args{deliverydate} = $source->reqdate;
+ if (my $order = SL::DB::Manager::Order->find_by(ordnumber => $source->ordnumber)) {
+ $args{orddate} = $order->transdate;
+ }
+
+ } elsif ($source->type =~ /_order$/) {
$args{deliverydate} = $source->reqdate;
$args{orddate} = $source->transdate;
+
} else {
$args{quodate} = $source->transdate;
}
$self->_post_add_acctrans($data{amounts_cogs});
$self->_post_add_acctrans($data{amounts});
- $self->_post_add_acctrans($data{taxes});
+ $self->_post_add_acctrans($data{taxes_by_chart_id});
$self->_post_add_acctrans({ $params{ar_id} => $self->amount * -1 });
$chart_link = SL::DB::Manager::Chart->find_by(id => $chart_id)->{'link'};
$chart_link ||= '';
- SL::DB::AccTransaction->new(trans_id => $self->id,
- chart_id => $chart_id,
- amount => $spec->{amount},
- tax_id => $spec->{tax_id},
- taxkey => $spec->{taxkey},
- project_id => $self->globalproject_id,
- transdate => $self->transdate,
- chart_link => $chart_link)->save;
+ if ($spec->{amount} != 0) {
+ SL::DB::AccTransaction->new(trans_id => $self->id,
+ chart_id => $chart_id,
+ amount => $spec->{amount},
+ tax_id => $spec->{tax_id},
+ taxkey => $spec->{taxkey},
+ project_id => $self->globalproject_id,
+ transdate => $self->transdate,
+ chart_link => $chart_link)->save;
+ }
}
}
chart_id => $params{chart}->id,
chart_link => $params{chart}->link,
transdate => $self->transdate,
+ gldate => $self->gldate,
taxkey => $tax->taxkey,
tax_id => $tax->id,
project_id => $params{project_id},
chart_id => $tax->chart_id,
chart_link => $tax->chart->link,
transdate => $self->transdate,
+ gldate => $self->gldate,
taxkey => $tax->taxkey,
tax_id => $tax->id,
);
use SL::DB::Helper::Paginated;
use SL::DB::Helper::Sorted;
+use SL::System::TaskServer;
+
sub object_class { 'SL::DB::BackgroundJob' }
__PACKAGE__->make_manager_methods;
cron_spec => '',
next_run_at => undef,
next_run_at => { le => $now } ] ]);
-
- return $class->get_all(query => [ or => [ @interval_args, @once_args ] ]);
+ my @node_filter;
+
+ my $node_id = SL::System::TaskServer->node_id;
+ if ($::lx_office_conf{task_server}->{only_run_tasks_for_this_node}) {
+ @node_filter = (node_id => $node_id);
+ } else {
+ @node_filter = (
+ or => [
+ node_id => undef,
+ node_id => '',
+ node_id => $node_id,
+ ]);
+ }
+
+ return $class->get_all(query => [ or => [ @interval_args, @once_args ], @node_filter ]);
}
1;
my $rows = selectall_hashref_query($::form, $::form->get_standard_dbh, <<"", $date);
SELECT DISTINCT ON (chart_id) chart_id, startdate, id
FROM taxkeys
- WHERE startdate < ?
+ WHERE startdate <= ?
ORDER BY chart_id, startdate DESC;
for (@$rows) {
--- /dev/null
+# This file has been auto-generated only because it didn't exist.
+# Feel free to modify it at will; it will not be overwritten automatically.
+
+package SL::DB::Manager::ContactDepartment;
+
+use strict;
+
+use parent qw(SL::DB::Helper::Manager);
+
+use SL::DB::Helper::Sorted;
+
+sub object_class { 'SL::DB::ContactDepartment' }
+
+__PACKAGE__->make_manager_methods;
+
+sub _sort_spec {
+ return ( default => [ 'description', 1 ],
+ columns => { SIMPLE => 'ALL',
+ map { ( $_ => "lower(contact_departments.$_)" ) } qw(description)
+ });
+}
+
+1;
--- /dev/null
+# This file has been auto-generated only because it didn't exist.
+# Feel free to modify it at will; it will not be overwritten automatically.
+
+package SL::DB::Manager::ContactTitle;
+
+use strict;
+
+use parent qw(SL::DB::Helper::Manager);
+
+use SL::DB::Helper::Sorted;
+
+sub object_class { 'SL::DB::ContactTitle' }
+
+__PACKAGE__->make_manager_methods;
+
+sub _sort_spec {
+ return ( default => [ 'description', 1 ],
+ columns => { SIMPLE => 'ALL',
+ map { ( $_ => "lower(contact_titles.$_)" ) } qw(description)
+ });
+}
+
+1;
--- /dev/null
+# This file has been auto-generated only because it didn't exist.
+# Feel free to modify it at will; it will not be overwritten automatically.
+
+package SL::DB::Manager::Greeting;
+
+use strict;
+
+use parent qw(SL::DB::Helper::Manager);
+
+use SL::DB::Helper::Sorted;
+
+sub object_class { 'SL::DB::Greeting' }
+
+__PACKAGE__->make_manager_methods;
+
+sub _sort_spec {
+ return ( default => [ 'description', 1 ],
+ columns => { SIMPLE => 'ALL',
+ map { ( $_ => "lower(greetings.$_)" ) } qw(description)
+ });
+}
+
+1;
all => sub {
my ($key, $value, $prefix) = @_;
return or => [ map { $prefix . $_ => $value } qw(partnumber description ean) ]
- }
+ },
+ all_with_makemodel => sub {
+ my ($key, $value, $prefix) = @_;
+ return or => [ map { $prefix . $_ => $value } qw(partnumber description ean makemodels.model) ],
+ $prefix . 'makemodels';
+ },
+ all_with_customer_partnumber => sub {
+ my ($key, $value, $prefix) = @_;
+ return or => [ map { $prefix . $_ => $value } qw(partnumber description ean customerprices.customer_partnumber) ],
+ $prefix . 'customerprices';
+ },
);
sub type_filter {
my ($self, %params) = @_;
my ($query, @values) = $self->get_matching_filter(%params);
- my @ids = selectall_ids($::form, $::form->get_standard_dbh, $query, 0, @values);
-
+ my @ids = selectcol_array_query($::form, SL::DB->client->dbh, $query, @values);
return [] unless @ids;
$self->get_all(query => [ id => \@ids ]);
my ($max) = selectfirst_array_query($::form, $class->object_class->init_db->dbh, $query);
- return $max + 1;
+ return ($max // 0) + 1;
}
1;
mtime => { type => 'timestamp' },
parts_id => { type => 'integer', not_null => 1 },
position => { type => 'integer' },
- qty => { type => 'float', scale => 4 },
+ qty => { type => 'float', precision => 4, scale => 4 },
);
__PACKAGE__->meta->primary_key_columns([ 'assembly_id' ]);
mtime => { type => 'timestamp' },
parts_id => { type => 'integer', not_null => 1 },
position => { type => 'integer', not_null => 1 },
- qty => { type => 'float', not_null => 1, scale => 4 },
+ qty => { type => 'float', not_null => 1, precision => 4, scale => 4 },
unit => { type => 'varchar', length => 20, not_null => 1 },
);
id => { type => 'serial', not_null => 1 },
last_run_at => { type => 'timestamp' },
next_run_at => { type => 'timestamp' },
+ node_id => { type => 'text' },
package_name => { type => 'varchar', length => 255 },
type => { type => 'varchar', length => 255 },
);
reconciliation_starting_balance => { type => 'numeric', precision => 15, scale => 5 },
reconciliation_starting_date => { type => 'date' },
sortkey => { type => 'integer', not_null => 1 },
+ use_for_zugferd => { type => 'boolean', default => 'false', not_null => 1 },
);
__PACKAGE__->meta->primary_key_columns([ 'id' ]);
ar_id => { type => 'integer' },
bank_transaction_id => { type => 'integer', not_null => 1 },
gl_id => { type => 'integer' },
- id => { type => 'serial', not_null => 1 },
itime => { type => 'timestamp', default => 'now()' },
mtime => { type => 'timestamp' },
);
-__PACKAGE__->meta->primary_key_columns([ 'id' ]);
+__PACKAGE__->meta->primary_key_columns([ 'bank_transaction_id', 'acc_trans_id' ]);
__PACKAGE__->meta->allow_inline_column_values(1);
__PACKAGE__->meta->columns(
customernumberinit => { type => 'text' },
description => { type => 'text' },
- discount => { type => 'float', scale => 4 },
+ discount => { type => 'float', precision => 4, scale => 4 },
id => { type => 'integer', not_null => 1, sequence => 'id' },
itime => { type => 'timestamp', default => 'now()' },
mtime => { type => 'timestamp' },
cp_gender => { type => 'character', length => 1 },
cp_givenname => { type => 'text' },
cp_id => { type => 'integer', not_null => 1, sequence => 'id' },
- cp_main => { type => 'boolean' },
+ cp_main => { type => 'boolean', default => 'false' },
cp_mobile1 => { type => 'text' },
cp_mobile2 => { type => 'text' },
cp_name => { type => 'text' },
--- /dev/null
+# This file has been auto-generated. Do not modify it; it will be overwritten
+# by rose_auto_create_model.pl automatically.
+package SL::DB::ContactDepartment;
+
+use strict;
+
+use parent qw(SL::DB::Object);
+
+__PACKAGE__->meta->table('contact_departments');
+
+__PACKAGE__->meta->columns(
+ description => { type => 'text', not_null => 1 },
+ id => { type => 'serial', not_null => 1 },
+);
+
+__PACKAGE__->meta->primary_key_columns([ 'id' ]);
+
+__PACKAGE__->meta->unique_keys([ 'description' ]);
+
+1;
+;
--- /dev/null
+# This file has been auto-generated. Do not modify it; it will be overwritten
+# by rose_auto_create_model.pl automatically.
+package SL::DB::ContactTitle;
+
+use strict;
+
+use parent qw(SL::DB::Object);
+
+__PACKAGE__->meta->table('contact_titles');
+
+__PACKAGE__->meta->columns(
+ description => { type => 'text', not_null => 1 },
+ id => { type => 'serial', not_null => 1 },
+);
+
+__PACKAGE__->meta->primary_key_columns([ 'id' ]);
+
+__PACKAGE__->meta->unique_keys([ 'description' ]);
+
+1;
+;
contact => { type => 'text' },
contact_origin => { type => 'text' },
country => { type => 'text' },
+ create_zugferd_invoices => { type => 'integer', default => '-1', not_null => 1 },
creditlimit => { type => 'numeric', default => '0', precision => 15, scale => 5 },
currency_id => { type => 'integer', not_null => 1 },
customernumber => { type => 'text' },
mandator_id => { type => 'text' },
mtime => { type => 'timestamp' },
name => { type => 'text', not_null => 1 },
+ natural_person => { type => 'boolean', default => 'false' },
notes => { type => 'text' },
obsolete => { type => 'boolean', default => 'false' },
order_lock => { type => 'boolean', default => 'false' },
__PACKAGE__->meta->columns(
accounting_method => { type => 'text' },
- address => { type => 'text' },
+ address_city => { type => 'text' },
+ address_country => { type => 'text' },
+ address_street1 => { type => 'text' },
+ address_street2 => { type => 'text' },
+ address_zipcode => { type => 'text' },
allow_new_purchase_delivery_order => { type => 'boolean', default => 'true', not_null => 1 },
allow_new_purchase_invoice => { type => 'boolean', default => 'true', not_null => 1 },
allow_sales_invoice_from_sales_order => { type => 'boolean', default => 'true', not_null => 1 },
bin_id => { type => 'integer' },
bin_id_ignore_onhand => { type => 'integer' },
businessnumber => { type => 'text' },
+ carry_over_account_chart_id => { type => 'integer' },
closedto => { type => 'date' },
cnnumber => { type => 'text' },
co_ustid => { type => 'text' },
coa => { type => 'text' },
company => { type => 'text' },
+ contact_departments_use_textfield => { type => 'boolean' },
+ contact_titles_use_textfield => { type => 'boolean' },
create_part_if_not_found => { type => 'boolean', default => 'false' },
+ create_zugferd_invoices => { type => 'integer' },
currency_id => { type => 'integer', not_null => 1 },
customer_hourly_rate => { type => 'numeric', precision => 8, scale => 2 },
customer_projects_only_in_sales => { type => 'boolean', default => 'false', not_null => 1 },
itime => { type => 'timestamp', default => 'now()' },
language_id => { type => 'integer' },
letternumber => { type => 'integer' },
+ loss_carried_forward_chart_id => { type => 'integer' },
max_future_booking_interval => { type => 'integer', default => 360 },
mtime => { type => 'timestamp' },
normalize_part_descriptions => { type => 'boolean', default => 'true' },
pdonumber => { type => 'text' },
ponumber => { type => 'text' },
precision => { type => 'numeric', default => '0.01', not_null => 1, precision => 15, scale => 5 },
+ profit_carried_forward_chart_id => { type => 'integer' },
profit_determination => { type => 'text' },
project_status_id => { type => 'integer' },
project_type_id => { type => 'integer' },
sales_delivery_order_show_delete => { type => 'boolean', default => 'true' },
sales_order_show_delete => { type => 'boolean', default => 'true' },
sales_purchase_order_ship_missing_column => { type => 'boolean', default => 'false' },
+ sales_serial_eq_charge => { type => 'boolean', default => 'false', not_null => 1 },
sdonumber => { type => 'text' },
sepa_creditor_id => { type => 'text' },
sepa_reference_add_vc_vc_id => { type => 'boolean', default => 'false' },
transfer_default_use_master_default_bin => { type => 'boolean', default => 'false' },
transfer_default_warehouse_for_assembly => { type => 'boolean', default => 'false' },
transport_cost_reminder_article_number_id => { type => 'integer' },
+ vc_greetings_use_textfield => { type => 'boolean' },
vendornumber => { type => 'text' },
version => { type => 'varchar', length => 8 },
vertreter => { type => 'boolean', default => 'false' },
webdav => { type => 'boolean', default => 'false' },
webdav_documents => { type => 'boolean', default => 'false' },
weightunit => { type => 'varchar', length => 5 },
+ workflow_po_ap_chart_id => { type => 'integer' },
);
__PACKAGE__->meta->primary_key_columns([ 'id' ]);
key_columns => { bin_id_ignore_onhand => 'id' },
},
+ carry_over_account_chart => {
+ class => 'SL::DB::Chart',
+ key_columns => { carry_over_account_chart_id => 'id' },
+ },
+
currency => {
class => 'SL::DB::Currency',
key_columns => { currency_id => 'id' },
},
+ loss_carried_forward_chart => {
+ class => 'SL::DB::Chart',
+ key_columns => { loss_carried_forward_chart_id => 'id' },
+ },
+
+ profit_carried_forward_chart => {
+ class => 'SL::DB::Chart',
+ key_columns => { profit_carried_forward_chart_id => 'id' },
+ },
+
project_status => {
class => 'SL::DB::ProjectStatus',
key_columns => { project_status_id => 'id' },
__PACKAGE__->meta->columns(
active_discount_source => { type => 'text', default => '', not_null => 1 },
active_price_source => { type => 'text', default => '', not_null => 1 },
- base_qty => { type => 'float', scale => 4 },
+ base_qty => { type => 'float', precision => 4, scale => 4 },
cusordnumber => { type => 'text' },
delivery_order_id => { type => 'integer', not_null => 1 },
description => { type => 'text' },
- discount => { type => 'float', scale => 4 },
+ discount => { type => 'float', precision => 4, scale => 4 },
id => { type => 'integer', not_null => 1, sequence => 'delivery_order_items_id' },
itime => { type => 'timestamp', default => 'now()' },
lastcost => { type => 'numeric', precision => 15, scale => 5 },
id => { type => 'integer', not_null => 1, sequence => 'id' },
interest_rate => { type => 'numeric', precision => 15, scale => 5 },
payment_terms => { type => 'integer' },
+ print_original_invoice => { type => 'boolean' },
template => { type => 'text' },
terms => { type => 'integer' },
);
__PACKAGE__->meta->columns(
cb_transaction => { type => 'boolean' },
+ deliverydate => { type => 'date' },
department_id => { type => 'integer' },
description => { type => 'text' },
employee_id => { type => 'integer' },
--- /dev/null
+# This file has been auto-generated. Do not modify it; it will be overwritten
+# by rose_auto_create_model.pl automatically.
+package SL::DB::Greeting;
+
+use strict;
+
+use parent qw(SL::DB::Object);
+
+__PACKAGE__->meta->table('greetings');
+
+__PACKAGE__->meta->columns(
+ description => { type => 'text', not_null => 1 },
+ id => { type => 'serial', not_null => 1 },
+);
+
+__PACKAGE__->meta->primary_key_columns([ 'id' ]);
+
+__PACKAGE__->meta->unique_keys([ 'description' ]);
+
+1;
+;
__PACKAGE__->meta->columns(
active_discount_source => { type => 'text', default => '', not_null => 1 },
active_price_source => { type => 'text', default => '', not_null => 1 },
- allocated => { type => 'float', scale => 4 },
+ allocated => { type => 'float', precision => 4, scale => 4 },
assemblyitem => { type => 'boolean', default => 'false' },
- base_qty => { type => 'float', scale => 4 },
+ base_qty => { type => 'float', precision => 4, scale => 4 },
cusordnumber => { type => 'text' },
deliverydate => { type => 'date' },
description => { type => 'text' },
- discount => { type => 'float', scale => 4 },
+ discount => { type => 'float', precision => 4, scale => 4 },
donumber => { type => 'text' },
fxsellprice => { type => 'numeric', precision => 15, scale => 5 },
id => { type => 'integer', not_null => 1, sequence => 'invoiceid' },
price_factor_id => { type => 'integer' },
pricegroup_id => { type => 'integer' },
project_id => { type => 'integer' },
- qty => { type => 'float', scale => 4 },
+ qty => { type => 'float', precision => 4, scale => 4 },
sellprice => { type => 'numeric', precision => 15, scale => 5 },
serialnumber => { type => 'text' },
subtotal => { type => 'boolean', default => 'false' },
delivery_vendor_id => { type => 'integer' },
department_id => { type => 'integer' },
employee_id => { type => 'integer' },
+ exchangerate => { type => 'numeric', precision => 15, scale => 5 },
expected_billing_date => { type => 'date' },
globalproject_id => { type => 'integer' },
id => { type => 'integer', not_null => 1, sequence => 'id' },
__PACKAGE__->meta->columns(
active_discount_source => { type => 'text', default => '', not_null => 1 },
active_price_source => { type => 'text', default => '', not_null => 1 },
- base_qty => { type => 'float', scale => 4 },
+ base_qty => { type => 'float', precision => 4, scale => 4 },
cusordnumber => { type => 'text' },
description => { type => 'text' },
- discount => { type => 'float', scale => 4 },
+ discount => { type => 'float', precision => 4, scale => 4 },
id => { type => 'integer', not_null => 1, sequence => 'orderitemsid' },
itime => { type => 'timestamp', default => 'now()' },
lastcost => { type => 'numeric', precision => 15, scale => 5 },
price_factor_id => { type => 'integer' },
pricegroup_id => { type => 'integer' },
project_id => { type => 'integer' },
- qty => { type => 'float', scale => 4 },
+ qty => { type => 'float', precision => 4, scale => 4 },
reqdate => { type => 'date' },
sellprice => { type => 'numeric', precision => 15, scale => 5 },
serialnumber => { type => 'text' },
- ship => { type => 'float', scale => 4 },
+ ship => { type => 'float', precision => 4, scale => 4 },
subtotal => { type => 'boolean', default => 'false' },
trans_id => { type => 'integer' },
transdate => { type => 'text' },
itime => { type => 'timestamp', default => 'now()' },
mtime => { type => 'timestamp' },
obsolete => { type => 'boolean', default => 'false' },
- percent_skonto => { type => 'float', scale => 4 },
+ percent_skonto => { type => 'float', precision => 4, scale => 4 },
sortkey => { type => 'integer', not_null => 1 },
terms_netto => { type => 'integer' },
terms_skonto => { type => 'integer' },
skonto_sales_chart_id => { type => 'integer' },
taxdescription => { type => 'text', not_null => 1 },
taxkey => { type => 'integer', not_null => 1 },
- taxnumber => { type => 'text' },
);
__PACKAGE__->meta->primary_key_columns([ 'id' ]);
department_2 => { type => 'text' },
depositor => { type => 'text' },
direct_debit => { type => 'boolean', default => 'false' },
- discount => { type => 'float', scale => 4 },
+ discount => { type => 'float', precision => 4, scale => 4 },
email => { type => 'text' },
fax => { type => 'text' },
gln => { type => 'text' },
language_id => { type => 'integer' },
mtime => { type => 'timestamp' },
name => { type => 'text', not_null => 1 },
+ natural_person => { type => 'boolean', default => 'false' },
notes => { type => 'text' },
obsolete => { type => 'boolean', default => 'false' },
payment_id => { type => 'integer' },
Customer => 'CustomerVendor',
PurchaseInvoice => 'Invoice',
Vendor => 'CustomerVendor',
+ GLTransaction => 'GL',
);
sub new {
use SL::DB::MetaSetup::Order;
use SL::DB::Manager::Order;
+use SL::DB::Helper::Attr;
use SL::DB::Helper::AttrHTML;
use SL::DB::Helper::AttrSorted;
use SL::DB::Helper::FlattenToForm;
use SL::DB::Helper::PriceTaxCalculator;
use SL::DB::Helper::PriceUpdater;
use SL::DB::Helper::TransNumberGenerator;
+use SL::Locale::String qw(t8);
use SL::RecordLinks;
use Rose::DB::Object::Helpers qw(as_tree);
column_map => { id => 'trans_id' },
query_args => [ module => 'OE' ],
},
+ exchangerate_obj => {
+ type => 'one to one',
+ class => 'SL::DB::Exchangerate',
+ column_map => { currency_id => 'currency_id', transdate => 'transdate' },
+ },
);
+SL::DB::Helper::Attr::make(__PACKAGE__, daily_exchangerate => 'numeric');
+
__PACKAGE__->meta->initialize;
__PACKAGE__->attr_html('notes');
__PACKAGE__->attr_sorted('items');
__PACKAGE__->before_save('_before_save_set_ord_quo_number');
+__PACKAGE__->before_save('_before_save_create_new_project');
+__PACKAGE__->before_save('_before_save_remove_empty_custom_shipto');
+__PACKAGE__->before_save('_before_save_set_custom_shipto_module');
# hooks
return 1;
}
+sub _before_save_create_new_project {
+ my ($self) = @_;
+
+ # force new project, if not set yet
+ if ($::instance_conf->get_order_always_project && !$self->globalproject_id && ($self->type eq 'sales_order')) {
+
+ die t8("Error while creating project with project number of new order number, project number #1 already exists!", $self->ordnumber)
+ if SL::DB::Manager::Project->find_by(projectnumber => $self->ordnumber);
+
+ eval {
+ my $new_project = SL::DB::Project->new(
+ projectnumber => $self->ordnumber,
+ description => $self->customer->name,
+ customer_id => $self->customer->id,
+ active => 1,
+ project_type_id => $::instance_conf->get_project_type_id,
+ project_status_id => $::instance_conf->get_project_status_id,
+ );
+ $new_project->save;
+ $self->globalproject_id($new_project->id);
+ } or die t8('Could not create new project #1', $@);
+ }
+ return 1;
+}
+
+
+sub _before_save_remove_empty_custom_shipto {
+ my ($self) = @_;
+
+ $self->custom_shipto(undef) if $self->custom_shipto && $self->custom_shipto->is_empty;
+
+ return 1;
+}
+
+sub _before_save_set_custom_shipto_module {
+ my ($self) = @_;
+
+ $self->custom_shipto->module('OE') if $self->custom_shipto;
+
+ return 1;
+}
# methods
return shift->type eq shift;
}
+sub deliverydate {
+ # oe doesn't have deliverydate, but PTC checks for deliverydate or transdate to determine tax
+ # oe can't deal with deviating tax rates, but at least make sure PTC doesn't barf
+ return shift->transdate;
+}
+
sub displayable_type {
my $type = shift->type;
return !!shift->customer_id;
}
+sub daily_exchangerate {
+ my ($self, $val) = @_;
+
+ return 1 if $self->currency_id == $::instance_conf->get_currency_id;
+
+ my $rate = (any { $self->is_type($_) } qw(sales_quotation sales_order)) ? 'buy'
+ : (any { $self->is_type($_) } qw(request_quotation purchase_order)) ? 'sell'
+ : undef;
+ return if !$rate;
+
+ if (defined $val) {
+ croak t8('exchange rate has to be positive') if $val <= 0;
+ if (!$self->exchangerate_obj) {
+ $self->exchangerate_obj(SL::DB::Exchangerate->new(
+ currency_id => $self->currency_id,
+ transdate => $self->transdate,
+ $rate => $val,
+ ));
+ } elsif (!defined $self->exchangerate_obj->$rate) {
+ $self->exchangerate_obj->$rate($val);
+ } else {
+ croak t8('exchange rate already exists, no update allowed');
+ }
+ }
+ return $self->exchangerate_obj->$rate if $self->exchangerate_obj;
+}
+
sub invoices {
my $self = shift;
my %params = @_;
}
my %args = ( map({ ( $_ => $source->$_ ) } qw(amount cp_id currency_id cusordnumber customer_id delivery_customer_id delivery_term_id delivery_vendor_id
- department_id employee_id globalproject_id intnotes marge_percent marge_total language_id netamount notes
+ department_id employee_id exchangerate globalproject_id intnotes marge_percent marge_total language_id netamount notes
ordnumber payment_id quonumber reqdate salesman_id shippingpoint shipvia taxincluded taxzone_id
transaction_description vendor_id
)),
if ( $is_abbr_any->(qw(sopo poso)) ) {
$args{ordnumber} = undef;
+ $args{quonumber} = undef;
$args{reqdate} = DateTime->today_local->next_workday();
$args{employee} = SL::DB::Manager::Employee->current;
}
}
foreach my $attr (qw(cp_id currency_id employee_id salesman_id department_id
delivery_customer_id delivery_vendor_id shipto_id
- globalproject_id)) {
+ globalproject_id exchangerate)) {
$attributes{$attr} = undef if any { ($sources->[0]->$attr||0) != ($_->$attr||0) } @$sources;
}
Returns true if the order is of the given type.
+=head2 C<daily_exchangerate $val>
+
+Gets or sets the exchangerate object's value. This is the value from the
+table C<exchangerate> depending on the order's currency, the transdate and
+if it is a sales or purchase order.
+
+The order object (respectively the table C<oe>) has an own column
+C<exchangerate> which can be get or set with the accessor C<exchangerate>.
+
+The idea is to drop the legacy table C<exchangerate> in the future and to
+give all relevant tables it's own C<exchangerate> column.
+
+So, this method is here if you need to access the "legacy" exchangerate via
+an order object.
+
+=over 4
+
+=item C<$val>
+
+(optional) If given, the exchangerate in the "legacy" table is set to this
+value, depending on currency, transdate and sales or purchase.
+
+=back
+
=head2 C<convert_to_delivery_order %params>
Creates a new delivery order with C<$self> as the basis by calling
use strict;
-use List::Util qw(sum);
-
use SL::DB::MetaSetup::OrderItem;
use SL::DB::Manager::OrderItem;
-use SL::DB::DeliveryOrderItemsStock;
use SL::DB::Helper::ActsAsList;
use SL::DB::Helper::LinkedRecords;
use SL::DB::Helper::RecordItem;
G. Richardson E<lt>grichardson@kivitendo-premium.deE<gt>
=cut
-
-
use strict;
use Carp;
-use List::MoreUtils qw(any);
+use List::MoreUtils qw(any uniq);
use Rose::DB::Object::Helpers qw(as_tree);
use SL::Locale::String qw(t8);
assemblies => {
type => 'one to many',
class => 'SL::DB::Assembly',
- manager_args => { sort_by => 'position, oid' },
+ manager_args => { sort_by => 'position' },
column_map => { id => 'id' },
},
prices => {
type => 'one to many',
class => 'SL::DB::AssortmentItem',
column_map => { id => 'assortment_id' },
+ manager_args => { sort_by => 'position' },
},
history_entries => {
type => 'one to many',
__PACKAGE__->meta->initialize;
__PACKAGE__->attr_html('notes');
-__PACKAGE__->attr_sorted({ unsorted => 'makemodels', position => 'sortorder' });
+__PACKAGE__->attr_sorted({ unsorted => 'makemodels', position => 'sortorder' });
+__PACKAGE__->attr_sorted({ unsorted => 'customerprices', position => 'sortorder' });
__PACKAGE__->before_save('_before_save_set_partnumber');
sub bin { require SL::DB::Bin; SL::DB::Manager::Bin ->find_by_or_create(id => $_[0]->{bin_id}) }
}
+sub get_simple_stock_sql {
+ my ($self, %params) = @_;
+
+ return [] unless $self->id;
+
+ my $query = <<SQL;
+ SELECT w.description AS warehouse_description,
+ b.description AS bin_description,
+ SUM(i.qty) AS qty,
+ SUM(i.qty * p.lastcost) AS stock_value,
+ p.unit AS unit,
+ LEAD(w.description) OVER pt AS wh_lead, -- to detect warehouse changes for subtotals in template
+ SUM( SUM(i.qty) ) OVER pt AS run_qty, -- running total of total qty
+ SUM( SUM(i.qty) ) OVER wh AS wh_run_qty, -- running total of warehouse qty
+ SUM( SUM(i.qty * p.lastcost)) OVER pt AS run_stock_value, -- running total of total stock_value
+ SUM( SUM(i.qty * p.lastcost)) OVER wh AS wh_run_stock_value -- running total of warehouse stock_value
+ FROM inventory i
+ LEFT JOIN parts p ON (p.id = i.parts_id)
+ LEFT JOIN warehouse w ON (i.warehouse_id = w.id)
+ LEFT JOIN bin b ON (i.bin_id = b.id)
+ WHERE parts_id = ?
+ GROUP BY w.description, w.sortkey, b.description, p.unit, i.parts_id
+ HAVING SUM(qty) != 0
+ WINDOW pt AS (PARTITION BY i.parts_id ORDER BY w.sortkey, b.description, p.unit),
+ wh AS (PARTITION by w.description ORDER BY w.sortkey, b.description, p.unit)
+ ORDER BY w.sortkey, b.description, p.unit
+SQL
+
+ my $stock_info = selectall_hashref_query($::form, $self->db->dbh, $query, $self->id);
+ return $stock_info;
+}
+
+sub get_mini_journal {
+ my ($self) = @_;
+
+ # inventory ids of the most recent 10 inventory trans_ids
+
+ # duplicate code copied from SL::Controller::Inventory mini_journal, except
+ # for the added filter on parts_id
+
+ my $parts_id = $self->id;
+ my $query = <<"SQL";
+with last_inventories as (
+ select id,
+ trans_id,
+ itime
+ from inventory
+ where parts_id = $parts_id
+ order by itime desc
+ limit 20
+),
+grouped_ids as (
+ select trans_id,
+ array_agg(id) as ids
+ from last_inventories
+ group by trans_id
+ order by max(itime)
+ desc limit 10
+)
+select unnest(ids)
+ from grouped_ids
+ limit 20 -- so the planner knows how many ids to expect, the cte is an optimisation fence
+SQL
+
+ my $objs = SL::DB::Manager::Inventory->get_all(
+ query => [ id => [ \"$query" ] ],
+ with_objects => [ 'parts', 'trans_type', 'bin', 'bin.warehouse' ], # prevent lazy loading in template
+ sort_by => 'itime DESC',
+ );
+ # remember order of trans_ids from query, for ordering hash later
+ my @sorted_trans_ids = uniq map { $_->trans_id } @$objs;
+
+ # at most 2 of them belong to a transaction and the qty determines in or out.
+ my %transactions;
+ for (@$objs) {
+ $transactions{ $_->trans_id }{ $_->qty > 0 ? 'in' : 'out' } = $_;
+ $transactions{ $_->trans_id }{base} = $_;
+ }
+
+ # because the inventory transactions were built in a hash, we need to sort the
+ # hash by using the original sort order of the trans_ids
+ my @sorted = map { $transactions{$_} } @sorted_trans_ids;
+
+ return \@sorted;
+}
+
sub clone_and_reset_deep {
my ($self) = @_;
Please note, that this is a write only accessor, the original Buchungsgruppe can
not be retrieved from an article once set.
+=item C<get_simple_stock_sql>
+
+Fetches the qty and the stock value for the current part for each bin and
+warehouse where the part is in stock (or rather different from 0, might be
+negative).
+
+Runs some additional window functions to add the running totals (total running
+total and total per warehouse) for qty and stock value to each line.
+
+Using the LEAD(w.description) the template can check if the warehouse
+description is about to change, i.e. the next line will contain numbers from a
+different warehouse, so that a subtotal line can be added.
+
+The last row will contain the running qty total (run_qty) and the running total
+stock value (run_stock_value) over all warehouses/bins and can be used to add a
+line for the grand totals.
+
=item C<items_lastcost_sum>
Non-recursive lastcost sum of all the items in an assembly or assortment.
my $period_len = $self->get_billing_period_length;
my $cur_date = ($self->first_billing_date || $self->start_date)->clone;
- my $end_date = $self->terminated ? $self->end_date : undef;
+ my $end_date = $self->terminated || !$self->extend_automatically_by ? $self->end_date : undef;
$end_date //= DateTime->today_local->add(years => 100);
my $start_date = $params{past_dates} ? undef : $self->get_previous_billed_period_start_date;
$start_date = $start_date ? $start_date->clone->add(days => 1) : $cur_date->clone;
chart_id => $params{chart}->id,
chart_link => $params{chart}->link,
transdate => $self->transdate,
+ gldate => $self->gldate,
taxkey => $tax->taxkey,
tax_id => $tax->id,
project_id => $params{project_id},
chart_id => $tax->chart_id,
chart_link => $tax->chart->link,
transdate => $self->transdate,
+ gldate => $self->gldate,
taxkey => $tax->taxkey,
tax_id => $tax->id,
project_id => $params{project_id},
use strict;
use Carp;
+use List::MoreUtils qw(all);
+
+use SL::Util qw(trim);
use SL::DB::MetaSetup::Shipto;
use SL::DB::Manager::Shipto;
__PACKAGE__->meta->initialize;
+
sub displayable_id {
my $self = shift;
my $text = join('; ', grep { $_ } (map({ $self->$_ } qw(shiptoname shiptostreet)),
|| SL::DB::Manager::DeliveryOrder->get_all_count(query => [ shipto_id => $self->shipto_id ]);
}
+sub is_empty {
+ my ($self) = @_;
+
+ # todo: consider cvars
+ my @fields_to_consider = grep { !m{^ (?: itime | mtime | shipto_id | trans_id | shiptocp_gender | module ) $}x } map {$_->name} $self->meta->columns;
+
+ return all { trim($self->$_) eq '' } @fields_to_consider;
+}
+
sub detach {
$_[0]->trans_id(undef);
$_[0];
=over 4
+=item C<is_empty>
+
+Returns truish if all fields to consider are empty, falsish if not.
+Fields are trimmed before the test is performed.
+C<shiptocp_gender> is not considered because in forms this is usually
+a selection with 'm' as default value.
+CVar fields are not considered by now.
+
+=back
+
+=over 4
+
=item C<clone $target>
Creates and returns a clone of the current object. The mandatory
sub convert_to_sales_order {
my ($self, %params) = @_;
- my $customer = delete $params{customer};
- my $employee = delete $params{employee};
+ my $customer = delete $params{customer};
+ my $employee = delete $params{employee};
+ my $transdate = delete $params{transdate} // DateTime->today_local;
croak "param customer is missing" unless ref($customer) eq 'SL::DB::Customer';
croak "param employee is missing" unless ref($employee) eq 'SL::DB::Employee';
taxzone_id => $customer->taxzone_id,
currency_id => $customer->currency_id,
transaction_description => $shop->transaction_description,
- transdate => DateTime->today_local
+ transdate => $transdate,
);
return $order;
}else{
my $my_base_factor = $self->base_factor || 1;
my $other_base_factor = $other_unit->base_factor || 1;
- return $qty * $my_base_factor / $other_base_factor;
+ return ($qty // 0) * $my_base_factor / $other_base_factor;
}
sub is_time_based {
use SL::DB::Manager::Vendor;
use SL::DB::Helper::IBANValidation;
use SL::DB::Helper::TransNumberGenerator;
+use SL::DB::Helper::VATIDNrValidation;
use SL::DB::Helper::CustomVariables (
module => 'CT',
cvars_alias => 1,
my @errors;
push @errors, $::locale->text('The vendor name is missing.') if !$self->name;
push @errors, $self->validate_ibans;
+ push @errors, $self->validate_vat_id_numbers;
return @errors;
}
$file =~ s|.*/||;
my $control = {
- "priority" => 1000,
- "depends" => [],
- "locales" => [],
+ priority => 1000,
+ depends => [],
+ required_by => [],
+ locales => [],
};
while (<IN>) {
my @fields = split(/\s*:\s*/, $_, 2);
next unless (scalar(@fields) == 2);
- if ($fields[0] eq "depends") {
- push(@{$control->{"depends"}}, split(/\s+/, $fields[1]));
+ if ($fields[0] =~ m{^(?:depends|required_by)$}) {
+ push(@{$control->{$fields[0]}}, split(/\s+/, $fields[1]));
} elsif ($fields[0] eq "locales") {
push @{$control->{locales}}, $fields[1];
} else {
delete @{$control}{qw(depth applied)};
- my @unknown_keys = grep { !m{^ (?: depends | description | file | ignore | locales | may_fail | priority | superuser_privileges | tag ) $}x } keys %{ $control };
+ my @unknown_keys = grep { !m{^ (?: depends | required_by | description | file | ignore | locales | may_fail | priority | superuser_privileges | tag ) $}x } keys %{ $control };
if (@unknown_keys) {
_control_error($form, $file_name, sprintf($locale->text("Unknown control fields: #1", join(' ', sort({ lc $a cmp lc $b } @unknown_keys)))));
}
close(IN);
}
+ foreach my $name (keys %all_controls) {
+ my $control = $all_controls{$name};
+
+ foreach my $dependency (@{ delete $control->{required_by} }) {
+ _control_error($form, $control->{"file"}, sprintf($locale->text("Unknown dependency '%s'."), $dependency)) if (!defined($all_controls{$dependency}));
+ push @{ $all_controls{$dependency}->{depends} }, $name;
+ }
+ }
+
foreach my $control (values(%all_controls)) {
foreach my $dependency (@{$control->{"depends"}}) {
_control_error($form, $control->{"file"}, sprintf($locale->text("Unknown dependency '%s'."), $dependency)) if (!defined($all_controls{$dependency}));
our @EXPORT = qw(conv_i conv_date conv_dateq do_query selectrow_query do_statement
dump_query quote_db_date like
selectfirst_hashref_query selectfirst_array_query
- selectall_hashref_query selectall_array_query
+ selectall_hashref_query selectall_array_query selectcol_array_query
selectall_as_map
selectall_ids
prepare_execute_query prepare_query
my $self_filename = 'SL/DBUtils.pm';
my $filename = $self_filename;
my ($caller_level, $line, $subroutine);
- while ($filename eq $self_filename) {
+ while ($filename =~ m{$self_filename$}) {
(undef, $filename, $line, $subroutine) = caller $caller_level++;
}
return wantarray ? @{ $result } : $result;
}
-sub selectall_array_query {
+sub selectall_array_query { goto &selectcol_array_query; }
+
+sub selectcol_array_query {
$main::lxdebug->enter_sub(2);
my ($form, $dbh, $query) = splice(@_, 0, 3);
my $sth = prepare_execute_query($form, $dbh, $query, @_);
- my @result;
- while (my ($value) = $sth->fetchrow_array()) {
- push(@result, $value);
- }
+ my @result = @{ $dbh->selectcol_arrayref($sth) };
$sth->finish();
$main::lxdebug->leave_sub(2);
=head1 NAME
-SL::DBUTils.pm: All about database connections in kivitendo
+SL::DBUtils.pm: All about database connections in kivitendo
=head1 SYNOPSIS
conv_dateq($str)
quote_db_date($date)
+ my $dbh = SL::DB->client->dbh;
+
do_query($form, $dbh, $query)
do_statement($form, $sth, $query)
my $all_results_ref = selectall_hashref_query($form, $dbh, $query)
my $first_result_hash_ref = selectfirst_hashref_query($form, $dbh, $query);
- my @first_result = selectfirst_array_query($form, $dbh, $query); # ==
+ my @first_result = selectfirst_array_query($form, $dbh, $query);
my @first_result = selectrow_query($form, $dbh, $query);
+ my @values = selectcol_array_query($form, $dbh, $query);
+
my %sort_spec = create_sort_spec(%params);
=head1 DESCRIPTION
C<DBH> is a handle to the database, as returned by the C<DBI::connect> routine.
If you don't have an active connection, you can use
-C<<$::form->get_standard_dbh>> to get a generic no_auto connection or get a
-C<Rose::DB::Object> handle from any RDBO class with
-C<<SL::DB::Part->new->db->dbh>>. The former will be without autocommit, the
-latter with autocommit.
+C<SL::DB->client->dbh> or get a C<Rose::DB::Object> handle from any RDBO class with
+C<<SL::DB::Part->new->db->dbh>>. In both cases the handle will have AutoCommit set.
See C<PITFALLS AND CAVEATS> for common errors.
selectall_hashref_query(...)
-=head2 Peformance
+=head2 Performance
Since it is really easy to write something like
people do so from time to time. When writing code, consider this a ticking
timebomb. Someone out there has a database with 1mio parts in it, and this
-statement just shovelled ate 2GB of memory and timeouted the request.
+statement just gobbled up 2GB of memory and timeouted the request.
Parts may be the obvious example, but the same applies to customer, vendors,
records, projects or custom variables.
the data structure will actually be a reference to an array, containing
hashrefs for each row.
+
+=item selectall_array_query FORM,DBH,QUERY,ARRAY
+
+Deprecated, see C<selectcol_array_query>
+
+=item selectcol_array_query FORM,DBH,QUERY,ARRAY
+
+Prepares and executes a query using DBUtils functions, retrieves the values of
+the first result column and returns the values as an array.
+
=item selectall_as_map FORM,DBH,QUERY,KEY_COL,VALUE_COL,ARRAY
Prepares and executes a query using DBUtils functions, retrieves all data from
$query = qq|SELECT nextval('glid')|;
($new_id) = selectrow_query($form, $dbh, $query);
+=item Retrieving all values from a column:
+
+ $query = qq|SELECT id FROM units|;
+ @units = selectcol_array_query($form, $dbh, $query);
+
=item Using binding values:
$query = qq|UPDATE ar SET paid = amount + paid, storno = 't' WHERE id = ?|;
use SL::Util qw(trim);
use SL::DB;
+use File::Copy;
+
use strict;
sub get_config {
$form->{"template_$i"}, $form->{"fee_$i"}, $form->{"interest_rate_$i"},
$form->{"active_$i"} ? 't' : 'f', $form->{"auto_$i"} ? 't' : 'f', $form->{"email_$i"} ? 't' : 'f',
$form->{"email_attachment_$i"} ? 't' : 'f', conv_i($form->{"payment_terms_$i"}), conv_i($form->{"terms_$i"}),
- $form->{"create_invoices_for_fees_$i"} ? 't' : 'f');
+ $form->{"create_invoices_for_fees_$i"} ? 't' : 'f',
+ $form->{"print_original_invoice_$i"} ? 't' : 'f');
if ($form->{"id_$i"}) {
$query =
qq|UPDATE dunning_config SET
template = ?, fee = ?, interest_rate = ?,
active = ?, auto = ?, email = ?,
email_attachment = ?, payment_terms = ?, terms = ?,
- create_invoices_for_fees = ?
+ create_invoices_for_fees = ?,
+ print_original_invoice = ?
WHERE id = ?|;
push(@values, conv_i($form->{"id_$i"}));
} else {
qq|INSERT INTO dunning_config
(dunning_level, dunning_description, email_subject, email_body,
template, fee, interest_rate, active, auto, email,
- email_attachment, payment_terms, terms, create_invoices_for_fees)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)|;
+ email_attachment, payment_terms, terms, create_invoices_for_fees,
+ print_original_invoice)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)|;
}
do_query($form, $dbh, $query, @values);
}
my @invoice_ids;
my ($next_dunning_config_id, $customer_id);
- my $send_email = 0;
+ my ($send_email, $print_invoice) = (0, 0);
foreach my $row (@{ $rows }) {
+ if ($row->{credit_note}) {
+ my $i = $row->{row};
+ %{ $form->{LIST_CREDIT_NOTES}{$row->{customer_id}}{$row->{invoice_id}} } = (
+ open_amount => $form->{"open_amount_$i"},
+ amount => $form->{"amount_$i"},
+ invnumber => $form->{"invnumber_$i"},
+ invdate => $form->{"invdate_$i"},
+ );
+ next;
+ }
push @invoice_ids, $row->{invoice_id};
$next_dunning_config_id = $row->{next_dunning_config_id};
$customer_id = $row->{customer_id};
@values = ($row->{next_dunning_config_id}, $row->{invoice_id});
do_statement($form, $h_update_ar, $q_update_ar, @values);
- $send_email |= $row->{email};
+ $send_email |= $row->{email};
+ $print_invoice |= $row->{print_invoice};
my $next_config_id = conv_i($row->{next_dunning_config_id});
my $invoice_id = conv_i($row->{invoice_id});
$next_config_id, $next_config_id);
do_statement($form, $h_insert_dunning, $q_insert_dunning, @values);
}
+ # die this transaction, because for this customer only credit notes are
+ # selected ...
+ return unless $customer_id;
$h_update_ar->finish();
$h_insert_dunning->finish();
$self->print_invoice_for_fees($myconfig, $form, $dunning_id, $dbh);
$self->print_dunning($myconfig, $form, $dunning_id, $dbh);
+ if ($print_invoice) {
+ $self->print_original_invoices($myconfig, $form, $_, $dbh) for @invoice_ids;
+ }
if ($send_email) {
$self->send_email($myconfig, $form, $dunning_id, $dbh);
push(@values, like($form->{customer}));
}
+ if ($form->{department_id}) {
+ $where .= qq| AND (a.department_id = ?)|;
+ push(@values, $form->{department_id});
+ }
+
my %columns = (
"ordnumber" => "a.ordnumber",
"invnumber" => "a.invnumber",
if (!$form->{l_include_direct_debit}) {
$where .= qq| AND NOT COALESCE(a.direct_debit, FALSE) |;
}
+ my $paid = ($form->{l_include_credit_notes}) ? "WHERE (a.paid <> a.amount)" : "WHERE (a.paid < a.amount)";
$query =
qq|SELECT
ct.name AS customername, a.customer_id, a.duedate,
a.amount - a.paid AS open_amount,
a.direct_debit,
+ dep.description as departmentname,
cfg.dunning_description, cfg.dunning_level,
nextcfg.dunning_description AS next_dunning_description,
nextcfg.id AS next_dunning_config_id,
- nextcfg.terms, nextcfg.active, nextcfg.email
+ nextcfg.terms, nextcfg.active, nextcfg.email, nextcfg.print_original_invoice
FROM ar a
LEFT JOIN customer ct ON (a.customer_id = ct.id)
+ LEFT JOIN department dep ON (a.department_id = dep.id)
LEFT JOIN dunning_config cfg ON (a.dunning_config_id = cfg.id)
LEFT JOIN dunning_config nextcfg ON
(nextcfg.id =
WHERE (d2.trans_id = a.id)
AND (d2.dunning_level = cfg.dunning_level)
))
-
- WHERE (a.paid < a.amount)
- AND (a.duedate < current_date)
+ $paid
+ AND (a.duedate < current_date)
$where
while (my $ref = $sth->fetchrow_hashref("NAME_lc")) {
next if ($ref->{pastdue} < $ref->{terms});
-
+ $ref->{credit_note} = 1 if ($ref->{amount} < 0 && $form->{l_include_credit_notes});
$ref->{interest} = $form->round_amount($ref->{interest}, 2);
push(@{ $form->{DUNNINGS} }, $ref);
}
}
$sth->finish();
+ # if we have some credit notes to add, do a safety check on the first customer id
+ # and add one entry for each credit note
+ if ($form->{LIST_CREDIT_NOTES} && $form->{LIST_CREDIT_NOTES}->{$form->{TEMPLATE_ARRAYS}->{"dn_customer_id"}[0]}) {
+ my $first_customer_id = $form->{TEMPLATE_ARRAYS}->{"dn_customer_id"}[0];
+ while ( my ($cred_id, $value) = each(%{ $form->{LIST_CREDIT_NOTES}->{$first_customer_id} } ) ) {
+ map { push @{ $form->{TEMPLATE_ARRAYS}->{"dn_$_"} }, $value->{$_} } keys %{ $value };
+ }
+ }
$query =
qq|SELECT
c.id AS customer_id, c.name, c.street, c.zipcode, c.city,
$form->{interest_rate} = $form->format_amount($myconfig, $ref->{interest_rate} * 100);
$form->{fee} = $form->format_amount($myconfig, $ref->{fee}, 2);
$form->{total_interest} = $form->format_amount($myconfig, $form->round_amount($ref->{total_interest}, 2), 2);
- $form->{total_open_amount} = $form->format_amount($myconfig, $form->round_amount($ref->{total_open_amount}, 2), 2);
- $form->{total_amount} = $form->format_amount($myconfig, $form->round_amount($ref->{fee} + $ref->{total_interest} + $ref->{total_open_amount}, 2), 2);
+ my $total_open_amount = $ref->{total_open_amount};
+ if ($form->{l_include_credit_notes}) {
+ # a bit stupid, but redo calc because of credit notes
+ $total_open_amount = 0;
+ foreach my $amount (@{ $form->{TEMPLATE_ARRAYS}->{dn_open_amount} }) {
+ $total_open_amount += $form->parse_amount($myconfig, $amount, 2);
+ }
+ }
+ $form->{total_open_amount} = $form->format_amount($myconfig, $form->round_amount($total_open_amount, 2), 2);
+ $form->{total_amount} = $form->format_amount($myconfig, $form->round_amount($ref->{fee} + $ref->{total_interest} + $total_open_amount, 2), 2);
$::form->format_dates($output_dateformat, $output_longdates,
qw(dn_dunning_date dn_dunning_duedate dn_transdate dn_duedate
}
+sub print_original_invoices {
+ my ($self, $myconfig, $form, $invoice_id) = @_;
+ # get one invoice as object and print to pdf
+ my $invoice = SL::DB::Invoice->new(id => $invoice_id)->load;
+
+ die "Invalid invoice object" unless ref($invoice) eq 'SL::DB::Invoice';
+
+ my $print_form = Form->new('');
+ $print_form->{type} = 'invoice';
+ $print_form->{formname} = 'invoice',
+ $print_form->{format} = 'pdf',
+ $print_form->{media} = 'file';
+ # no language override, should always be the object's language
+ $invoice->flatten_to_form($print_form, format_amounts => 1);
+ for my $i (1 .. $print_form->{rowcount}) {
+ $print_form->{"sellprice_$i"} = $print_form->{"fxsellprice_$i"};
+ }
+ $print_form->prepare_for_printing;
+
+ my $filename = SL::Helper::CreatePDF->create_pdf(
+ template => 'invoice.tex',
+ variables => $print_form,
+ return => 'file_name',
+ variable_content_types => {
+ longdescription => 'html',
+ partnotes => 'html',
+ notes => 'html',
+ },
+ );
+
+ my $spool = $::lx_office_conf{paths}->{spool};
+ my ($volume, $directory, $file_name) = File::Spec->splitpath($filename);
+ my $full_file_name = File::Spec->catfile($spool, $file_name);
+
+ move($filename, $full_file_name) or die "The move operation failed: $!";
+
+ # form get_formname_translation should use language_id_$i
+ my $saved_reicpient_locale = $form->{recipient_locale};
+ $form->{recipient_locale} = $invoice->language;
+
+ push @{ $form->{DUNNING_PDFS} }, $file_name;
+ push @{ $form->{DUNNING_PDFS_EMAIL} }, { 'path' => "${spool}/$file_name",
+ 'name' => $form->get_formname_translation('invoice') . "_" . $invoice->invnumber . ".pdf" };
+
+ $form->{recipient_locale} = $saved_reicpient_locale;
+}
+
1;
push @where, "dord.$item = ?";
push @values, conv_i($form->{$item});
}
- if (!$main::auth->assert('sales_all_edit', 1)) {
+ if ( !(($vc eq 'customer' && $main::auth->assert('sales_all_edit', 1)) || ($vc eq 'vendor' && $main::auth->assert('purchase_all_edit', 1))) ) {
push @where, qq|dord.employee_id = (select id from employee where login= ?)|;
push @values, $::myconfig{login};
}
my $sortorder = "";
if ($form->{groupitems}) {
$sortorder =
- qq|ORDER BY pg.partsgroup, a.oid|;
+ qq|ORDER BY pg.partsgroup, a.position|;
} else {
- $sortorder = qq|ORDER BY a.oid|;
+ $sortorder = qq|ORDER BY a.position|;
}
do_statement($form, $h_pg, $q_pg, conv_i($form->{"id_$i"}));
Minimal usage, default values, without saving to database:
- my $vendor = SL::Dev::CustomerVendor::create_vendor();
+ my $vendor = SL::Dev::CustomerVendor::new_vendor();
Complex usage, overwriting some defaults, and save to database:
- SL::Dev::CustomerVendor::create_vendor(name => 'Test vendor',
- taxzone_id => 2,
- notes => "Order for 100$ for free delivery",
- payment_id => 5,
- )->save;
+ SL::Dev::CustomerVendor::new_vendor(name => 'Test vendor',
+ taxzone_id => 2,
+ notes => "Order for 100$ for free delivery",
+ payment_id => 5,
+ )->save;
=head1 BUGS
} else {
for my $i ( 1 .. delete $params{number_of_parts} || 3) {
my $part = new_part(partnumber => "$base_partnumber $i",
- description => "Testpart $i",
- )->save;
+ description => "Testpart $i",
+ )->save;
push( @{$assembly_items}, SL::DB::Assembly->new(parts_id => $part->id,
qty => 1,
position => $i,
} else {
for my $i ( 1 .. delete $params{number_of_parts} || 3) {
my $part = new_part(partnumber => "$base_partnumber $i",
- description => "Testpart $i",
- )->save;
+ description => "Testpart $i",
+ )->save;
push( @{$assortment_items}, SL::DB::AssortmentItem->new(parts_id => $part->id,
qty => 1,
position => $i,
$bank_chart = SL::DB::Manager::Chart->find_by(description => 'Bank') or die "Can't find bank chart";
}
my $bank_account = SL::DB::Manager::BankAccount->find_by( chart_id => $bank_chart->id );
+ die "bank account missing" unless $bank_account;
my $bt = SL::DB::BankTransaction->new(
local_bank_account_id => $bank_account->id,
use strict;
use base qw(Exporter);
-our @EXPORT_OK = qw(create_invoice_item create_sales_invoice create_credit_note create_order_item create_sales_order create_purchase_order create_delivery_order_item create_sales_delivery_order create_purchase_delivery_order create_project create_department);
+our @EXPORT_OK = qw(create_invoice_item
+ create_sales_invoice
+ create_credit_note
+ create_order_item
+ create_sales_order
+ create_purchase_order
+ create_delivery_order_item
+ create_sales_delivery_order
+ create_purchase_delivery_order
+ create_project create_department
+ create_ap_transaction
+ create_ar_transaction
+ create_gl_transaction
+ );
our %EXPORT_TAGS = (ALL => \@EXPORT_OK);
use SL::DB::Invoice;
use SL::DB::Project;
use SL::DB::ProjectStatus;
use SL::DB::ProjectType;
+use SL::Form;
use DateTime;
+use List::Util qw(sum);
+use Data::Dumper;
+use SL::Locale::String qw(t8);
+use SL::DATEV;
my %record_type_to_item_type = ( sales_invoice => 'SL::DB::InvoiceItem',
credit_note => 'SL::DB::InvoiceItem',
return $department;
}
+
+sub create_ap_transaction {
+ my (%params) = @_;
+
+ my $vendor = delete $params{vendor};
+ if ( $vendor ) {
+ die "vendor missing or not a SL::DB::Vendor object" unless ref($vendor) eq 'SL::DB::Vendor';
+ } else {
+ # use default SL/Dev vendor if it exists, or create a new one
+ $vendor = SL::DB::Manager::Vendor->find_by(name => 'Testlieferant') // new_vendor->save;
+ };
+
+ my $taxincluded = $params{taxincluded} // 1;
+ delete $params{taxincluded};
+
+ my $bookings = delete $params{bookings};
+ # default bookings
+ unless ( $bookings ) {
+ my $chart_postage = SL::DB::Manager::Chart->find_by(description => 'Porto');
+ my $chart_telephone = SL::DB::Manager::Chart->find_by(description => 'Telefon');
+ $bookings = [
+ {
+ chart => $chart_postage,
+ amount => 1000,
+ },
+ {
+ chart => $chart_telephone,
+ amount => $taxincluded ? 1190 : 1000,
+ },
+ ]
+ };
+
+ # optional params:
+ my $project_id = delete $params{globalproject_id};
+
+ # if amount or netamount are given, then it compares them to the final values, and dies if they don't match
+ my $expected_amount = delete $params{amount};
+ my $expected_netamount = delete $params{netamount};
+
+ my $dec = delete $params{dec} // 2;
+
+ my $today = DateTime->today_local;
+ my $transdate = delete $params{transdate} // $today;
+ die "transdate hat to be DateTime object" unless ref($transdate) eq 'DateTime';
+
+ my $gldate = delete $params{gldate} // $today;
+ die "gldate hat to be DateTime object" unless ref($gldate) eq 'DateTime';
+
+ my $ap_chart = delete $params{ap_chart} // SL::DB::Manager::Chart->find_by( accno => '1600' );
+ die "no ap_chart found or not an AP chart" unless $ap_chart and $ap_chart->link eq 'AP';
+
+ my $ap_transaction = SL::DB::PurchaseInvoice->new(
+ vendor_id => $vendor->id,
+ invoice => 0,
+ transactions => [],
+ globalproject_id => $project_id,
+ invnumber => delete $params{invnumber} // 'test ap_transaction',
+ notes => delete $params{notes} // 'test ap_transaction',
+ transdate => $transdate,
+ gldate => $gldate,
+ taxincluded => $taxincluded,
+ taxzone_id => $vendor->taxzone_id, # taxzone_id shouldn't have any effect on ap transactions
+ currency_id => $::instance_conf->get_currency_id,
+ type => undef, # isn't set for ap
+ employee_id => SL::DB::Manager::Employee->current->id,
+ );
+ # assign any parameters that weren't explicitly handled above, e.g. itime
+ $ap_transaction->assign_attributes(%params) if %params;
+
+ foreach my $booking ( @{$bookings} ) {
+ my $chart = delete $booking->{chart};
+ die "illegal chart" unless ref($chart) eq 'SL::DB::Chart';
+
+ my $tax = _transaction_tax_helper($booking, $chart, $transdate); # will die if tax can't be found
+
+ $ap_transaction->add_ap_amount_row(
+ amount => $booking->{amount}, # add_ap_amount_row expects the user input amount, does its own calculate_tax
+ chart => $chart,
+ tax_id => $tax->id,
+ project_id => $booking->{project_id},
+ );
+ }
+
+ my $acc_trans_sum = sum map { $_->amount } grep { $_->chart_link =~ 'AP_amount' } @{$ap_transaction->transactions};
+ # $main::lxdebug->message(0, sprintf("accno: %s amount: %s chart_link: %s\n",
+ # $_->amount,
+ # $_->chart->accno,
+ # $_->chart_link
+ # )) foreach @{$ap_transaction->transactions};
+
+ # determine netamount and amount from the transactions that were added via bookings
+ $ap_transaction->netamount( -1 * sum map { $_->amount } grep { $_->chart_link =~ 'AP_amount' } @{$ap_transaction->transactions} );
+ # $main::lxdebug->message(0, sprintf('found netamount %s', $ap_transaction->netamount));
+
+ my $taxamount = -1 * sum map { $_->amount } grep { $_->chart_link =~ /tax/ } @{$ap_transaction->transactions};
+ $ap_transaction->amount( $ap_transaction->netamount + $taxamount );
+ # additional check, add up all transactions before AP-transaction is added
+ my $refamount = -1 * sum map { $_->amount } @{$ap_transaction->transactions};
+ die "refamount = $refamount, ap_transaction->amount = " . $ap_transaction->amount unless $refamount == $ap_transaction->amount;
+
+ # if amount or netamount were passed as params, check if the values are still
+ # the same after recalculating them from the acc_trans entries
+ if (defined $expected_amount) {
+ die "amount doesn't match acc_trans amounts: $expected_amount != " . $ap_transaction->amount unless $expected_amount == $ap_transaction->amount;
+ }
+ if (defined $expected_netamount) {
+ die "netamount doesn't match acc_trans netamounts: $expected_netamount != " . $ap_transaction->netamount unless $expected_netamount == $ap_transaction->netamount;
+ }
+
+ $ap_transaction->create_ap_row(chart => $ap_chart);
+ $ap_transaction->save;
+ # $main::lxdebug->message(0, sprintf("created ap_transaction with invnumber %s and trans_id %s",
+ # $ap_transaction->invnumber,
+ # $ap_transaction->id));
+ return $ap_transaction;
+}
+
+sub create_ar_transaction {
+ my (%params) = @_;
+
+ my $customer = delete $params{customer};
+ if ( $customer ) {
+ die "customer missing or not a SL::DB::Customer object" unless ref($customer) eq 'SL::DB::Customer';
+ } else {
+ # use default SL/Dev vendor if it exists, or create a new one
+ $customer = SL::DB::Manager::Customer->find_by(name => 'Testkunde') // new_customer->save;
+ };
+
+ my $taxincluded = $params{taxincluded} // 1;
+ delete $params{taxincluded};
+
+ my $bookings = delete $params{bookings};
+ # default bookings
+ unless ( $bookings ) {
+ my $chart_19 = SL::DB::Manager::Chart->find_by(accno => '8400');
+ my $chart_7 = SL::DB::Manager::Chart->find_by(accno => '8300');
+ my $chart_0 = SL::DB::Manager::Chart->find_by(accno => '8200');
+ $bookings = [
+ {
+ chart => $chart_19,
+ amount => $taxincluded ? 119 : 100,
+ },
+ {
+ chart => $chart_7,
+ amount => $taxincluded ? 107 : 100,
+ },
+ {
+ chart => $chart_0,
+ amount => 100,
+ },
+ ]
+ };
+
+ # optional params:
+ my $project_id = delete $params{globalproject_id};
+
+ # if amount or netamount are given, then it compares them to the final values, and dies if they don't match
+ my $expected_amount = delete $params{amount};
+ my $expected_netamount = delete $params{netamount};
+
+ my $dec = delete $params{dec} // 2;
+
+ my $today = DateTime->today_local;
+ my $transdate = delete $params{transdate} // $today;
+ die "transdate hat to be DateTime object" unless ref($transdate) eq 'DateTime';
+
+ my $gldate = delete $params{gldate} // $today;
+ die "gldate hat to be DateTime object" unless ref($gldate) eq 'DateTime';
+
+ my $ar_chart = delete $params{ar_chart} // SL::DB::Manager::Chart->find_by( accno => '1400' );
+ die "no ar_chart found or not an AR chart" unless $ar_chart and $ar_chart->link eq 'AR';
+
+ my $ar_transaction = SL::DB::Invoice->new(
+ customer_id => $customer->id,
+ invoice => 0,
+ transactions => [],
+ globalproject_id => $project_id,
+ invnumber => delete $params{invnumber} // 'test ar_transaction',
+ notes => delete $params{notes} // 'test ar_transaction',
+ transdate => $transdate,
+ gldate => $gldate,
+ taxincluded => $taxincluded,
+ taxzone_id => $customer->taxzone_id, # taxzone_id shouldn't have any effect on ar transactions
+ currency_id => $::instance_conf->get_currency_id,
+ type => undef, # isn't set for ar
+ employee_id => SL::DB::Manager::Employee->current->id,
+ );
+ # assign any parameters that weren't explicitly handled above, e.g. itime
+ $ar_transaction->assign_attributes(%params) if %params;
+
+ foreach my $booking ( @{$bookings} ) {
+ my $chart = delete $booking->{chart};
+ die "illegal chart" unless ref($chart) eq 'SL::DB::Chart';
+
+ my $tax = _transaction_tax_helper($booking, $chart, $transdate); # will die if tax can't be found
+
+ $ar_transaction->add_ar_amount_row(
+ amount => $booking->{amount}, # add_ar_amount_row expects the user input amount, does its own calculate_tax
+ chart => $chart,
+ tax_id => $tax->id,
+ project_id => $booking->{project_id},
+ );
+ }
+
+ my $acc_trans_sum = sum map { $_->amount } grep { $_->chart_link =~ 'AR_amount' } @{$ar_transaction->transactions};
+ # $main::lxdebug->message(0, sprintf("accno: %s amount: %s chart_link: %s\n",
+ # $_->amount,
+ # $_->chart->accno,
+ # $_->chart_link
+ # )) foreach @{$ar_transaction->transactions};
+
+ # determine netamount and amount from the transactions that were added via bookings
+ $ar_transaction->netamount( 1 * sum map { $_->amount } grep { $_->chart_link =~ 'AR_amount' } @{$ar_transaction->transactions} );
+ # $main::lxdebug->message(0, sprintf('found netamount %s', $ar_transaction->netamount));
+
+ my $taxamount = 1 * sum map { $_->amount } grep { $_->chart_link =~ /tax/ } @{$ar_transaction->transactions};
+ $ar_transaction->amount( $ar_transaction->netamount + $taxamount );
+ # additional check, add up all transactions before AP-transaction is added
+ my $refamount = 1 * sum map { $_->amount } @{$ar_transaction->transactions};
+ die "refamount = $refamount, ar_transaction->amount = " . $ar_transaction->amount unless $refamount == $ar_transaction->amount;
+
+ # if amount or netamount were passed as params, check if the values are still
+ # the same after recalculating them from the acc_trans entries
+ if (defined $expected_amount) {
+ die "amount doesn't match acc_trans amounts: $expected_amount != " . $ar_transaction->amount unless $expected_amount == $ar_transaction->amount;
+ }
+ if (defined $expected_netamount) {
+ die "netamount doesn't match acc_trans netamounts: $expected_netamount != " . $ar_transaction->netamount unless $expected_netamount == $ar_transaction->netamount;
+ }
+
+ $ar_transaction->create_ar_row(chart => $ar_chart);
+ $ar_transaction->save;
+ # $main::lxdebug->message(0, sprintf("created ar_transaction with invnumber %s and trans_id %s",
+ # $ar_transaction->invnumber,
+ # $ar_transaction->id));
+ return $ar_transaction;
+}
+
+sub create_gl_transaction {
+ my (%params) = @_;
+
+ my $ob_transaction = delete $params{ob_transaction} // 0;
+ my $cb_transaction = delete $params{cb_transaction} // 0;
+ my $dec = delete $params{rec} // 2;
+
+ my $taxincluded = defined $params{taxincluded} ? $params{taxincluded} : 1;
+
+ my $today = DateTime->today_local;
+ my $transdate = delete $params{transdate} // $today;
+ my $gldate = delete $params{gldate} // $today;
+
+ my $reference = delete $params{reference} // 'reference';
+ my $description = delete $params{description} // 'description';
+
+ my $department_id = delete $params{department_id};
+
+ my $bookings = delete $params{bookings};
+ unless ( $bookings && scalar @{$bookings} ) {
+ # default bookings if left empty
+ my $expense_chart = SL::DB::Manager::Chart->find_by(accno => '4660') or die "Can't find expense chart 4660\n"; # Reisekosten
+ my $cash_chart = SL::DB::Manager::Chart->find_by(accno => '1000') or die "Can't find cash chart 1000\n"; # Kasse
+
+ $taxincluded = 0;
+
+ $reference = 'Reise';
+ $description = 'Reise';
+
+ $bookings = [
+ {
+ chart => $expense_chart, # has default tax of 19%
+ credit => 84.03,
+ taxkey => 9,
+ },
+ {
+ chart => $cash_chart,
+ debit => 100,
+ taxkey => 0,
+ },
+ ];
+ }
+
+ my $gl_transaction = SL::DB::GLTransaction->new(
+ reference => $reference,
+ description => $description,
+ transdate => $transdate,
+ gldate => $gldate,
+ taxincluded => $taxincluded,
+ type => undef,
+ ob_transaction => $ob_transaction,
+ cb_transaction => $cb_transaction,
+ storno => 0,
+ storno_id => undef,
+ transactions => [],
+ );
+ # assign any parameters that weren't explicitly handled above, e.g. itime
+ $gl_transaction->assign_attributes(%params) if %params;
+
+ my @acc_trans;
+ if ( scalar @{$bookings} ) {
+ # there are several ways of determining the tax:
+ # * tax_id : fetches SL::DB::Tax object via id (as used in dropdown in interface)
+ # * tax : SL::DB::Tax object (where $tax->id = tax_id)
+ # * taxkey : tax is determined from startdate
+ # * none of the above defined: use the default tax for that chart
+
+ foreach my $booking ( @{$bookings} ) {
+ my $chart = delete $booking->{chart};
+ die "illegal chart" unless ref($chart) eq 'SL::DB::Chart';
+
+ die t8('Empty transaction!')
+ unless $booking->{debit} or $booking->{credit}; # must exist and not be 0
+ die t8('Cannot post transaction with a debit and credit entry for the same account!')
+ if defined($booking->{debit}) and defined($booking->{credit});
+
+ my $tax = _transaction_tax_helper($booking, $chart, $transdate); # will die if tax can't be found
+
+ $gl_transaction->add_chart_booking(
+ chart => $chart,
+ debit => $booking->{debit},
+ credit => $booking->{credit},
+ tax_id => $tax->id,
+ source => $booking->{source} // '',
+ memo => $booking->{memo} // '',
+ project_id => $booking->{project_id}
+ );
+ }
+ };
+
+ $gl_transaction->post;
+
+ return $gl_transaction;
+}
+
+sub _transaction_tax_helper {
+ # checks for hash-entries with key tax, tax_id or taxkey
+ # returns an SL::DB::Tax object
+ # can be used for booking hashref in ar_transaction, ap_transaction and gl_transaction
+ # will modify hashref, e.g. removing taxkey if tax_id was also supplied
+
+ my ($booking, $chart, $transdate) = @_;
+
+ die "_transaction_tax_helper: chart missing" unless $chart && ref($chart) eq 'SL::DB::Chart';
+ die "_transaction_tax_helper: transdate missing" unless $transdate && ref($transdate) eq 'DateTime';
+
+ my $tax;
+
+ if ( defined $booking->{tax_id} ) { # tax_id may be 0
+ delete $booking->{taxkey}; # ignore any taxkeys that may have been added, tax_id has precedence
+ $tax = SL::DB::Tax->new(id => $booking->{tax_id})->load( with => [ 'chart' ] );
+ } elsif ( $booking->{tax} ) {
+ die "illegal tax entry" unless ref($booking->{tax}) eq 'SL::DB::Tax';
+ $tax = $booking->{tax};
+ } elsif ( defined $booking->{taxkey} ) {
+ # If a taxkey is given, find the taxkey entry for that chart that
+ # matches the stored taxkey and with the correct transdate. This will only work
+ # if kivitendo has that taxkey configured for that chart, i.e. it should barf if
+ # e.g. the bank chart is called with taxkey 3.
+
+ # example query:
+ # select *
+ # from taxkeys
+ # where taxkey_id = 3
+ # and chart_id = (select id from chart where accno = '8400')
+ # and startdate <= '2018-01-01'
+ # order by startdate desc
+ # limit 1;
+
+ my $taxkey = SL::DB::Manager::TaxKey->get_first(
+ query => [ and => [ chart_id => $chart->id,
+ startdate => { le => $transdate },
+ taxkey => $booking->{taxkey}
+ ]
+ ],
+ sort_by => "startdate DESC",
+ limit => 1,
+ with_objects => [ qw(tax) ],
+ );
+ die sprintf("Chart %s doesn't have a taxkey chart configured for taxkey %s", $chart->accno, $booking->{taxkey})
+ unless $taxkey;
+
+ $tax = $taxkey->tax;
+ } else {
+ # use default tax for that chart if neither tax_id, tax or taxkey were defined
+ my $active_taxkey = $chart->get_active_taxkey($transdate);
+ $tax = $active_taxkey->tax;
+ # $main::lxdebug->message(0, sprintf("found default taxrate %s for chart %s", $tax->rate, $chart->displayable_name));
+ };
+
+ die "no tax" unless $tax && ref($tax) eq 'SL::DB::Tax';
+ return $tax;
+};
+
1;
__END__
C<%params> should only contain alterable keys from the object Department.
+=head2 C<create_ap_transaction %PARAMS>
+
+Creates a new AP transaction (table ap, invoice = 0), and will try to add as
+many defaults as possible.
+
+Possible parameters:
+ * vendor (SL::DB::Vendor object, defaults to SL::Dev default vendor)
+ * taxincluded (0 or 1, defaults to 1)
+ * transdate (DateTime object, defaults to current date)
+ * bookings (arrayref for the charts to be booked, see examples below)
+ * amount (to check if final amount matches this amount)
+ * netamount (to check if final amount matches this amount)
+ * dec (number of decimals to round to, defaults to 2)
+ * ap_chart (SL::DB::Chart object, default to accno 1600)
+ * invnumber (defaults to 'test ap_transaction')
+ * notes (defaults to 'test ap_transaction')
+ * globalproject_id
+
+Currently doesn't support exchange rates.
+
+Minimal usage example, creating an AP transaction with a default vendor and
+default bookings (telephone, postage):
+
+ use SL::Dev::Record qw(create_ap_transaction);
+ my $invoice = create_ap_transaction();
+
+Create an AP transaction with a specific vendor and specific charts:
+
+ my $vendor = SL::Dev::CustomerVendor::new_vendor(name => 'My Vendor')->save;
+ my $chart_postage = SL::DB::Manager::Chart->find_by(description => 'Porto');
+ my $chart_telephone = SL::DB::Manager::Chart->find_by(description => 'Telefon');
+
+ my $ap_transaction = create_ap_transaction(
+ vendor => $vendor,
+ invnumber => 'test invoice taxincluded',
+ taxincluded => 1,
+ amount => 2190, # optional param for checking whether final amount matches
+ netamount => 2000, # optional param for checking whether final netamount matches
+ bookings => [
+ {
+ chart => $chart_postage,
+ amount => 1000,
+ },
+ {
+ chart => $chart_telephone,
+ amount => 1190,
+ },
+ ]
+ );
+
+Or the same example with tax not included, but an old transdate and old taxrate (16%):
+
+ my $ap_transaction = create_ap_transaction(
+ vendor => $vendor,
+ invnumber => 'test invoice tax not included',
+ transdate => DateTime->new(year => 2000, month => 10, day => 1),
+ taxincluded => 0,
+ amount => 2160, # optional param for checking whether final amount matches
+ netamount => 2000, # optional param for checking whether final netamount matches
+ bookings => [
+ {
+ chart => $chart_postage,
+ amount => 1000,
+ },
+ {
+ chart => $chart_telephone,
+ amount => 1000,
+ },
+ ]
+ );
+
+Don't use the default tax, e.g. postage with 19%:
+
+ my $tax_9 = SL::DB::Manager::Tax->find_by(taxkey => 9, rate => 0.19);
+ my $chart_postage = SL::DB::Manager::Chart->find_by(description => 'Porto');
+ my $ap_transaction = create_ap_transaction(
+ invnumber => 'postage with tax',
+ taxincluded => 0,
+ bookings => [
+ {
+ chart => $chart_postage,
+ amount => 1000,
+ tax => $tax_9,
+ },
+ ],
+ );
+
+=head2 C<create_ar_transaction %PARAMS>
+
+See C<create_ap_transaction>, except use customer instead of vendor.
+
+=head2 C<create_gl_transaction %PARAMS>
+
+Creates a new GL transaction (table gl), which is basically a wrapper around
+SL::DB::GLTransaction->new(...) and add_chart_booking and post, while setting
+as many defaults as possible.
+
+Possible parameters:
+
+ * taxincluded (0 or 1, defaults to 1)
+ * transdate (DateTime object, defaults to current date)
+ * dec (number of decimals to round to, defaults to 2)
+ * bookings (arrayref for the charts and taxes to be booked, see examples below)
+
+bookings must include a least:
+
+ * chart as an SL::DB::Chart object
+ * credit or debit, as positive numbers
+ * tax_id, tax (an SL::DB::Tax object) or taxkey (e.g. 9)
+
+Can't be used to create storno transactions.
+
+Minimal usage example, using all the defaults, creating a GL transaction with
+travel expenses:
+
+ use SL::Dev::Record qw(create_gl_transaction);
+ $gl_transaction = create_gl_transaction();
+
+Create a GL transaction with a specific charts and taxes (the default taxes for
+those charts are used if none are explicitly given in bookings):
+
+ my $cash = SL::DB::Manager::Chart->find_by( description => 'Kasse' );
+ my $betriebsbedarf = SL::DB::Manager::Chart->find_by( description => 'Betriebsbedarf' );
+ $gl_transaction = create_gl_transaction(
+ reference => 'betriebsbedarf',
+ taxincluded => 1,
+ bookings => [
+ {
+ chart => $betriebsbedarf,
+ memo => 'foo 1',
+ source => 'foo 1',
+ credit => 119,
+ },
+ {
+ chart => $betriebsbedarf,
+ memo => 'foo 2',
+ source => 'foo 2',
+ credit => 119,
+ },
+ {
+ chart => $cash,
+ debit => 238,
+ memo => 'foo 1+2',
+ source => 'foo 1+2',
+ },
+ ],
+ );
+
=head1 BUGS
=head1 AUTHOR
-G. Richardson E<lt>grichardson@kivitendo-premium.deE<gt>
+G. Richardson E<lt>grichardson@kivitec.deE<gt>
=cut
use List::MoreUtils qw(all);
use List::Util qw(first);
use POSIX qw(setlocale);
-use SL::ArchiveZipFixes;
use SL::Auth;
use SL::Dispatcher::AuthHandler;
use SL::LXDebug;
use SL::Form;
use SL::Helper::DateTime;
use SL::InstanceConfiguration;
+use SL::MoreCommon qw(uri_encode);
use SL::Template::Plugin::HTMLFixes;
use SL::User;
$self->{interface} = lc($interface || 'cgi');
$self->{auth_handler} = SL::Dispatcher::AuthHandler->new;
- SL::ArchiveZipFixes->apply_fixes;
-
# Initialize character type locale to be UTF-8 instead of C:
foreach my $locale (qw(de_DE.UTF-8 en_US.UTF-8)) {
last if setlocale('LC_CTYPE', $locale);
my $session_result = $self->pre_request_initialization;
- $::form->read_cgi_input;
+ $::request->read_cgi_input($::form);
my %routing;
eval { %routing = $self->_route_request($ENV{SCRIPT_NAME}); 1; } or return;
if ( (($script eq 'login') && !$action)
|| ($script eq 'admin')
|| (SL::Auth::SESSION_EXPIRED() == $session_result)) {
- $self->redirect_to_login(script => $script, error => 'session');
-
+ $self->handle_login_error(routing_type => $routing_type,
+ script => $script,
+ controller => $script_name,
+ action => $action,
+ error => 'session');
}
my %auth_result = $self->{auth_handler}->handle(
$::lxdebug->leave_sub;
}
-sub redirect_to_login {
+sub reply_with_json_error {
my ($self, %params) = @_;
+
+ my %errors = (
+ session => { code => '401 Unauthorized', text => 'session expired' },
+ password => { code => '401 Unauthorized', text => 'incorrect username or password' },
+ action => { code => '400 Bad request', text => 'incorrect or missing action' },
+ access => { code => '403 Forbidden', text => 'no permissions for accessing this function' },
+ _default => { code => '500 Internal server error', text => 'general server-side error' },
+ );
+
+ my $error = $errors{$params{error}} // $errors{_default};
+ my $reply = SL::JSON::to_json({ status => 'failed', error => $error->{text} });
+
+ print $::request->cgi->header(
+ -type => 'application/json',
+ -charset => 'utf-8',
+ -status => $error->{code},
+ );
+
+ print $reply;
+
+ $self->end_request;
+}
+
+sub handle_login_error {
+ my ($self, %params) = @_;
+
+ return $self->reply_with_json_error(error => $params{error}) if $::request->type eq 'json';
+
my $action = ($params{script} // '') =~ m/^admin/i ? 'Admin/login' : 'LoginScreen/user_login';
$action .= '&error=' . $params{error} if $params{error};
- print $::request->cgi->redirect("controller.pl?action=${action}");
+ my $redirect_url = "controller.pl?action=${action}";
+
+ if ( $action =~ m/LoginScreen\/user_login/
+ && $params{action}
+ && 'get' eq lc($ENV{REQUEST_METHOD})
+ && !_is_callback_blacklisted(map {$_ => $params{$_}} qw(routing_type script controller action) )
+ ) {
+
+ require SL::Controller::Base;
+ my $controller = SL::Controller::Base->new;
+
+ delete $params{error};
+ delete $params{routing_type};
+ delete @{ $::form }{ grep { m/^\{AUTH\}/ } keys %{ $::form } };
+
+ my $callback = $controller->url_for(%params, %{$::form});
+ $redirect_url .= '&callback=' . uri_encode($callback);
+ }
+
+ print $::request->cgi->redirect($redirect_url);
$self->end_request;
}
+sub _is_callback_blacklisted {
+ my (%params) = @_;
+
+ # You can give a name only, then all actions are blackisted.
+ # Or you can give name and action, then only this action is blacklisted
+ # examples:
+ # {name => 'is', action => 'edit'}
+ # {name => 'Project', action => 'edit'},
+ my @script_blacklist = (
+ {name => 'admin'},
+ {name => 'login'},
+ );
+
+ my @controller_blacklist = (
+ {name => 'Admin'},
+ {name => 'LoginScreen'},
+ );
+
+ my ($name, $blacklist);
+ if ('old' eq ($params{routing_type} // '')) {
+ $name = $params{script};
+ $blacklist = \@script_blacklist;
+ } else {
+ $name = $params{controller};
+ $blacklist = \@controller_blacklist;
+ }
+
+ foreach my $bl (@$blacklist) {
+ return 1 if _is_name_action_blacklisted($bl->{name}, $bl->{action}, $name, $params{action});
+ }
+
+ return;
+}
+
+sub _is_name_action_blacklisted {
+ my ($blacklisted_name, $blacklisted_action, $name, $action) = @_;
+
+ return 1 if ($name // '') eq $blacklisted_name && !$blacklisted_action;
+ return 1 if ($name // '') eq $blacklisted_name && ($action // '') eq $blacklisted_action;
+ return;
+}
+
sub unrequire_bin_mozilla {
my $self = shift;
return unless $self->_interface_is_fcgi;
eval {
# Redirect simple requests to controller.pl without any GET/POST
# param to the login page.
- $self->redirect_to_login(error => 'action') if !$::form->{action};
+ $self->handle_login_error(error => 'action') if !$::form->{action};
# Show an error if the »action« parameter doesn't match the
# pattern »Controller/action«.
}
sub _error {
- my $self = shift;
+ my ($self, %param) = @_;
$::auth->punish_wrong_login;
+ $::dispatcher->handle_login_error(%param, error => 'password');
- require SL::Controller::Base;
- SL::Controller::Base->new->redirect_to('controller.pl?action=LoginScreen/user_login&error=password');
return 0;
}
use parent qw(Rose::Object);
-use Clone qw(clone);
use SL::File::Backend;
use SL::File::Object;
use SL::DB::History;
for my $version (2..$maxversion) {
$main::lxdebug->message(LXDebug->DEBUG2(), "clone for version=".($maxversion-$version+1));
eval {
- my $clone = clone($fileobj);
+ my $clone = $fileobj->clone;
$clone->version($maxversion-$version+1);
$clone->newest(0);
$main::lxdebug->message(LXDebug->DEBUG2(), "clone version=".$clone->version." mtime=". $clone->mtime);
gl_transaction => 'dialogbuchungen',
accounts_payable => 'kreditorenbuchungen',
shop_image => 'shopbilder',
+ customer => 'kunden',
+ vendor => 'lieferanten',
);
my %type_to_model = (
gl_transaction => 'GLTransaction',
accounts_payable => 'GLTransaction',
shop_image => 'Part',
+ customer => 'Customer',
+ vendor => 'Vendor',
);
my %model_to_number = (
Letter => 'letternumber',
GLTransaction => 'reference',
ShopImage => 'partnumber',
+ Customer => 'customernumber',
+ Vendor => 'vendornumber',
);
sub webdav_path {
$_[0]->db_file;
}
+sub clone {
+ bless +{ %{ $_[0] } }, __PACKAGE__;
+}
+
sub init_db_file { die 'must always have a db file'; }
sub init_loaded { 0 }
use Cwd;
use Encode;
use File::Copy;
+use File::Temp ();
use IO::File;
use Math::BigInt;
use POSIX qw(strftime);
return $self;
}
-sub read_cgi_input {
- my ($self) = @_;
- SL::Request::read_cgi_input($self);
-}
-
sub _flatten_variables_rec {
$main::lxdebug->enter_sub(2);
$cgi_params{'-charset'} = $params{charset} if ($params{charset});
$cgi_params{'-cookie'} = $session_cookie if ($session_cookie);
- map { $cgi_params{'-' . $_} = $params{$_} if exists $params{$_} } qw(content_disposition content_length);
+ map { $cgi_params{'-' . $_} = $params{$_} if exists $params{$_} } qw(content_disposition content_length status);
my $output = $cgi->header(%cgi_params);
local (*IN, *OUT);
- my $defaults = SL::DB::Default->get;
- my $userspath = $::lx_office_conf{paths}->{userspath};
+ my $defaults = SL::DB::Default->get;
+
+ my $keep_temp_files = $::lx_office_conf{debug} && $::lx_office_conf{debug}->{keep_temp_files};
+ $self->{cwd} = getcwd();
+ my $temp_dir = File::Temp->newdir(
+ "kivitendo-print-XXXXXX",
+ DIR => $self->{cwd} . "/" . $::lx_office_conf{paths}->{userspath},
+ CLEANUP => !$keep_temp_files,
+ );
- $self->{"cwd"} = getcwd();
- $self->{"tmpdir"} = $self->{cwd} . "/${userspath}";
+ my $userspath = File::Spec->abs2rel($temp_dir->dirname);
+ $self->{tmpdir} = $temp_dir->dirname;
my $ext_for_format;
$template_type = 'HTML';
$ext_for_format = 'html';
- } elsif (($self->{"format"} =~ /xml/i) || (!$self->{"format"} && ($self->{"IN"} =~ /xml$/i))) {
- $template_type = 'XML';
- $ext_for_format = 'xml';
-
- } elsif ( $self->{"format"} =~ /elster(?:winston|taxbird)/i ) {
- $template_type = 'XML';
-
} elsif ( $self->{"format"} =~ /excel/i ) {
$template_type = 'Excel';
$ext_for_format = 'xls';
# OUT is used for the media, screen, printer, email
# for postscript we store a copy in a temporary file
- my $keep_temp_files = $::lx_office_conf{debug} && $::lx_office_conf{debug}->{keep_temp_files};
my ($temp_fh, $suffix);
$suffix = $self->{IN};
}
if ($self->{media} eq 'file') {
copy(join('/', $self->{cwd}, $userspath, $self->{tmpfile}), $out =~ m|^/| ? $out : join('/', $self->{cwd}, $out)) if $template->uses_temp_file;
- Common::copy_file_to_webdav_folder($self) if $copy_to_webdav;
+
+ if ($copy_to_webdav) {
+ if (my $error = Common::copy_file_to_webdav_folder($self)) {
+ chdir("$self->{cwd}");
+ $self->error($error);
+ }
+ }
+
if (!$self->{preview} && $self->doc_storage_enabled)
{
$self->{attachment_filename} ||= $self->generate_attachment_filename;
return;
}
- Common::copy_file_to_webdav_folder($self) if $copy_to_webdav;
+ if ($copy_to_webdav) {
+ if (my $error = Common::copy_file_to_webdav_folder($self)) {
+ chdir("$self->{cwd}");
+ $self->error($error);
+ }
+ }
if ( !$self->{preview} && $ext_for_format eq 'pdf' && $self->doc_storage_enabled) {
$self->{attachment_filename} ||= $self->generate_attachment_filename;
if (($self->{format} eq 'html') && ($self->{sendmode} eq 'inline')) {
$mail->{content_type} = "text/html";
$mail->{message} =~ s/\r//g;
- $mail->{message} =~ s/\n/<br>\n/g;
- $full_signature =~ s/\n/<br>\n/g;
+ $mail->{message} =~ s{\n}{<br>\n}g;
+ $full_signature =~ s{\n}{<br>\n}g;
$mail->{message} .= $full_signature;
open(IN, "<", $self->{tmpfile})
} elsif (($self->{attachment_policy} // '') ne 'no_file') {
my $attachment_name = $self->{attachment_filename} || $self->{tmpfile};
- $attachment_name =~ s/\.(.+?)$/.${ext_for_format}/ if ($ext_for_format);
+ $attachment_name =~ s{\.(.+?)$}{.${ext_for_format}} if ($ext_for_format);
if (($self->{attachment_policy} // '') eq 'old_file') {
my ( $attfile ) = SL::File->get_all(object_id => $self->{id},
return $formname_translations{$formname};
}
+sub get_cusordnumber_translation {
+ $main::lxdebug->enter_sub();
+ my ($self, $formname) = @_;
+
+ $formname ||= $self->{formname};
+
+ $self->{recipient_locale} ||= Locale->lang_to_locale($self->{language});
+ local $::locale = Locale->new($self->{recipient_locale});
+
+
+ $main::lxdebug->leave_sub();
+ return $main::locale->text('Your Order');
+}
+
sub get_number_prefix_for_type {
$main::lxdebug->enter_sub();
my ($self) = @_;
$subject .= " " . $self->{"${prefix}number"}
}
+ if ($self->{cusordnumber}) {
+ $subject = $self->get_cusordnumber_translation() . ' ' . $self->{cusordnumber} . ' / ' . $subject;
+ }
+
$main::lxdebug->leave_sub();
return $subject;
}
return undef unless $body;
- $body .= GenericTranslations->get(translation_type =>"salutation_punctuation_mark", language_id => $self->{language_id}) . "\n";
- $body .= GenericTranslations->get(translation_type =>"preset_text_$self->{formname}", language_id => $self->{language_id});
+ my $translation_type = $params{translation_type} // "preset_text_$self->{formname}";
+ my $main_body = GenericTranslations->get(translation_type => $translation_type, language_id => $self->{language_id});
+ $main_body = GenericTranslations->get(translation_type => $params{fallback_translation_type}, language_id => $self->{language_id}) if !$main_body && $params{fallback_translation_type};
+ $body .= GenericTranslations->get(translation_type => "salutation_punctuation_mark", language_id => $self->{language_id}) . "\n\n";
+ $body .= $main_body;
$body = $main::locale->unquote_special_chars('HTML', $body);
my @values;
foreach my $item (qw(name department_1 department_2 street zipcode city country gln
- contact cp_gender phone fax email)) {
+ contact phone fax email)) {
if ($self->{"shipto$item"}) {
$shipto = 1 if ($self->{$item} ne $self->{"shipto$item"});
}
return if !$shipto;
+ # shiptocp_gender only makes sense, if any other shipto attribute is set.
+ # Because shiptocp_gender is set to 'm' by default in forms
+ # it must not be considered above to decide if shiptos has to be added or
+ # updated, but must be inserted or updated as well in case.
+ push(@values, $self->{shiptocp_gender});
+
my $shipto_id = $self->{shipto_id};
if ($self->{shipto_id}) {
shiptocountry = ?,
shiptogln = ?,
shiptocontact = ?,
- shiptocp_gender = ?,
shiptophone = ?,
shiptofax = ?,
shiptoemail = ?
+ shiptocp_gender = ?,
WHERE shipto_id = ?|;
do_query($self, $dbh, $query, @values, $self->{shipto_id});
} else {
shiptocountry = ? AND
shiptogln = ? AND
shiptocontact = ? AND
- shiptocp_gender = ? AND
shiptophone = ? AND
shiptofax = ? AND
shiptoemail = ? AND
+ shiptocp_gender = ? AND
module = ? AND
trans_id = ?|;
my $insert_check = selectfirst_hashref_query($self, $dbh, $query, @values, $module, $id);
my $insert_query =
qq|INSERT INTO shipto (trans_id, shiptoname, shiptodepartment_1, shiptodepartment_2,
shiptostreet, shiptozipcode, shiptocity, shiptocountry, shiptogln,
- shiptocontact, shiptocp_gender, shiptophone, shiptofax, shiptoemail, module)
+ shiptocontact, shiptophone, shiptofax, shiptoemail, shiptocp_gender, module)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)|;
do_query($self, $dbh, $insert_query, $id, @values, $module);
$main::lxdebug->leave_sub();
}
-sub _get_shipto {
- $main::lxdebug->enter_sub();
-
- my ($self, $dbh, $vc_id, $key) = @_;
-
- $key = "all_shipto" unless ($key);
-
- if ($vc_id) {
- # get shipping addresses
- my $query = qq|SELECT * FROM shipto WHERE trans_id = ?|;
-
- $self->{$key} = selectall_hashref_query($self, $dbh, $query, $vc_id);
-
- } else {
- $self->{$key} = [];
- }
-
- $main::lxdebug->leave_sub();
-}
-
sub _get_printers {
$main::lxdebug->enter_sub();
$main::lxdebug->leave_sub();
}
-sub _get_taxcharts {
- $main::lxdebug->enter_sub();
-
- my ($self, $dbh, $params) = @_;
-
- my $key = "all_taxcharts";
- my @where;
-
- if (ref $params eq 'HASH') {
- $key = $params->{key} if ($params->{key});
- if ($params->{module} eq 'AR') {
- push @where, 'chart_categories ~ \'[ACILQ]\'';
-
- } elsif ($params->{module} eq 'AP') {
- push @where, 'chart_categories ~ \'[ACELQ]\'';
- }
-
- } elsif ($params) {
- $key = $params;
- }
-
- my $where = @where ? ' WHERE ' . join(' AND ', map { "($_)" } @where) : '';
-
- my $query = qq|SELECT * FROM tax $where ORDER BY taxkey, rate|;
-
- $self->{$key} = selectall_hashref_query($self, $dbh, $query);
-
- $main::lxdebug->leave_sub();
-}
-
sub _get_taxzones {
$main::lxdebug->enter_sub();
$main::lxdebug->leave_sub();
}
-#sub _get_groups {
-# $main::lxdebug->enter_sub();
-#
-# my ($self, $dbh, $key) = @_;
-#
-# $key ||= "all_groups";
-#
-# my $groups = $main::auth->read_groups();
-#
-# $self->{$key} = selectall_hashref_query($self, $dbh, $query);
-#
-# $main::lxdebug->leave_sub();
-#}
-
sub get_lists {
$main::lxdebug->enter_sub();
my $self = shift;
my %params = @_;
+ croak "get_lists: shipto is no longer supported" if $params{shipto};
+
my $dbh = $self->get_standard_dbh(\%main::myconfig);
my ($sth, $query, $ref);
my ($vc, $vc_id);
- if ($params{contacts} || $params{shipto}) {
+ if ($params{contacts}) {
$vc = 'customer' if $self->{"vc"} eq "customer";
$vc = 'vendor' if $self->{"vc"} eq "vendor";
die "invalid use of get_lists, need 'vc'" unless $vc;
$self->_get_contacts($dbh, $vc_id, $params{"contacts"});
}
- if ($params{"shipto"}) {
- $self->_get_shipto($dbh, $vc_id, $params{"shipto"});
- }
-
if ($params{"projects"} || $params{"all_projects"}) {
$self->_get_projects($dbh, $params{"all_projects"} ?
$params{"all_projects"} : $params{"projects"},
$self->_get_charts($dbh, $params{"charts"});
}
- if ($params{"taxcharts"}) {
- $self->_get_taxcharts($dbh, $params{"taxcharts"});
- }
-
if ($params{"taxzones"}) {
$self->_get_taxzones($dbh, $params{"taxzones"});
}
$self->_get_warehouses($dbh, $params{warehouses});
}
-# if ($params{groups}) {
-# $self->_get_groups($dbh, $params{groups});
-# }
-
if ($params{partsgroup}) {
$self->get_partsgroup(\%main::myconfig, { all => 1, target => $params{partsgroup} });
}
if ($self->{id}) {
$query =
qq|SELECT
- a.cp_id, a.invnumber, a.transdate, a.${table}_id, a.datepaid,
+ a.cp_id, a.invnumber, a.transdate, a.${table}_id, a.datepaid, a.deliverydate,
a.duedate, a.ordnumber, a.taxincluded, (SELECT cu.name FROM currencies cu WHERE cu.id=a.currency_id) AS currency, a.notes,
a.mtime, a.itime,
a.intnotes, a.department_id, a.amount AS oldinvtotal,
$self->{"employee_${_}"} = $defaults->$_ for qw(address businessnumber co_ustid company duns sepa_creditor_id taxnumber);
}
- # Load shipping address from database. If shipto_id is set then it's
- # one from the customer's/vendor's master data. Otherwise look an a
- # customized address linking back to the current record.
- my $shipto_module = $self->{type} =~ /_delivery_order$/ ? 'DO'
- : $self->{type} =~ /sales_order|sales_quotation|request_quotation|purchase_order/ ? 'OE'
- : 'AR';
- my $shipto = $self->{shipto_id} ? SL::DB::Shipto->new(shipto_id => $self->{shipto_id})->load
- : SL::DB::Manager::Shipto->get_first(where => [ module => $shipto_module, trans_id => $self->{id} ]);
- if ($shipto) {
- $self->{$_} = $shipto->$_ for grep { m{^shipto} } map { $_->name } @{ $shipto->meta->columns };
- $self->{"shiptocvar_" . $_->config->name} = $_->value_as_text for @{ $shipto->cvars_by_config };
- }
-
my $language = $self->{language} ? '_' . $self->{language} : '';
my ($language_tc, $output_numberformat, $output_dateformat, $output_longdates);
$self->reformat_numbers($output_numberformat, $precision, @{ $field_list });
}
+ # Translate units
+ if (($self->{language} // '') ne '') {
+ my $template_arrays = $self->{TEMPLATE_ARRAYS} || $self;
+ for my $idx (0..scalar(@{ $template_arrays->{unit} }) - 1) {
+ $template_arrays->{unit}->[$idx] = AM->translate_units($self, $self->{language}, $template_arrays->{unit}->[$idx], $template_arrays->{qty}->[$idx])
+ }
+ }
+
$self->{template_meta} = {
formname => $self->{formname},
language => SL::DB::Manager::Language->find_by_or_create(id => $self->{language_id} || undef),
$query =
qq|UPDATE gl SET
reference = ?, description = ?, notes = ?,
- transdate = ?, department_id = ?, taxincluded = ?,
+ transdate = ?, deliverydate = ?, department_id = ?, taxincluded = ?,
storno = ?, storno_id = ?, ob_transaction = ?, cb_transaction = ?
WHERE id = ?|;
@values = ($form->{reference}, $form->{description}, $form->{notes},
- conv_date($form->{transdate}), conv_i($form->{department_id}), $form->{taxincluded} ? 't' : 'f',
+ conv_date($form->{transdate}), conv_date($form->{deliverydate}), conv_i($form->{department_id}), $form->{taxincluded} ? 't' : 'f',
$form->{storno} ? 't' : 'f', conv_i($form->{storno_id}), $form->{ob_transaction} ? 't' : 'f', $form->{cb_transaction} ? 't' : 'f',
conv_i($form->{id}));
do_query($form, $dbh, $query, @values);
if ($form->{id}) {
$query =
- qq|SELECT g.reference, g.description, g.notes, g.transdate, g.storno, g.storno_id,
+ qq|SELECT g.reference, g.description, g.notes, g.transdate, g.deliverydate,
+ g.storno, g.storno_id,
g.department_id, d.description AS department,
e.name AS employee, g.taxincluded, g.gldate,
g.ob_transaction, g.cb_transaction
}
sub get_active_taxes_for_chart {
- my ($self, $chart_id, $transdate) = @_;
+ my ($self, $chart_id, $transdate, $tax_id) = @_;
my $chart = SL::DB::Chart->new(id => $chart_id)->load;
my $active_taxkey = $chart->get_active_taxkey($transdate);
+
+ my $where = [ chart_categories => { like => '%' . $chart->category . '%' } ];
+
+ if ( defined $tax_id && $tax_id >= 0 ) {
+ $where = [ or => [ chart_categories => { like => '%' . $chart->category . '%' },
+ id => $tax_id
+ ]
+ ];
+ }
+
my $taxes = SL::DB::Manager::Tax->get_all(
- where => [ chart_categories => { like => '%' . $chart->category . '%' }],
+ where => $where,
sort_by => 'taxkey, rate',
);
}
1;
+
+__END__
+
+=pod
+
+=encoding utf8
+
+=head1 NAME
+
+SL::GL - some useful GL functions
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item C<get_active_taxes_for_chart> $transdate $tax_id
+
+Returns a list of valid taxes for a certain chart.
+
+If the optional param transdate exists one entry in the returning list
+may get the attribute C<is_default> for this specific tax-dependent date.
+The possible entries are filtered by the charttype of the tax, i.e. only taxes
+whose chart_categories match the category of the chart will be shown.
+
+In the case of existing records, e.g. when opening an old ar record, due to
+changes in the configurations the desired tax might not be available in the
+dropdown anymore. If we are loading an old record and know its tax_id (from
+acc_trans), we can pass $tax_id as the third parameter and be sure that the
+original tax always appears in the dropdown.
+
+The functions returns an array which may be used for building dropdowns in ar/ap/gl code.
+
+=back
+
+=head1 TODO
+
+=head1 BUGS
+
+Nothing here yet.
+
+=head1 AUTHOR
+
+G. Richardson E<lt>grichardson@kivitec.de<gt>
+
+=cut
use File::Copy qw(move);
use List::MoreUtils qw(uniq);
use List::Util qw(first);
+use Scalar::Util qw(blessed);
use String::ShellQuote ();
use SL::Common;
use SL::DB::Language;
use SL::DB::Printer;
use SL::MoreCommon;
+use SL::System::Process;
use SL::Template;
use SL::Template::LaTeX;
+use SL::X;
use Exporter 'import';
our @EXPORT_OK = qw(create_pdf merge_pdfs find_template);
sub create_parsed_file {
my ($class, %params) = @_;
- my $userspath = $::lx_office_conf{paths}->{userspath};
+ my $keep_temp_files = $::lx_office_conf{debug} && $::lx_office_conf{debug}->{keep_temp_files};
+ my $userspath = SL::System::Process::exe_dir() . "/" . $::lx_office_conf{paths}->{userspath};
+ my $temp_dir = File::Temp->newdir(
+ "kivitendo-print-XXXXXX",
+ DIR => $userspath,
+ CLEANUP => !$keep_temp_files,
+ );
+
my $vars = $params{variables} || {};
my $form = Form->new('');
$form->{$_} = $vars->{$_} for keys %{$vars};
$form->{format} = lc($params{format} || 'pdf');
- $form->{cwd} = getcwd();
+ $form->{cwd} = SL::System::Process::exe_dir();
$form->{templates} = $::instance_conf->get_templates;
$form->{IN} = $params{template};
- $form->{tmpdir} = $form->{cwd} . '/' . $userspath;
+ $form->{tmpdir} = $temp_dir->dirname;
my $tmpdir = $form->{tmpdir};
my ($suffix) = $params{template} =~ m{\.(.+)};
'kivitendo-printXXXXXX',
SUFFIX => ".${suffix}",
DIR => $form->{tmpdir},
- UNLINK => ($::lx_office_conf{debug} && $::lx_office_conf{debug}->{keep_temp_files})? 0 : 1,
+ UNLINK => !$keep_temp_files,
);
$form->{tmpfile} = $tmpfile;
(undef, undef, $form->{template_meta}{tmpfile}) = File::Spec->splitpath($tmpfile);
+ my %driver_options;
+ eval {
+ %driver_options = _maybe_attach_zugferd_data($params{record});
+ };
+
+ if (my $e = SL::X::ZUGFeRDValidation->caught) {
+ $form->cleanup;
+ die $e->message;
+ }
+
my $parser = SL::Template::create(
type => ($params{template_type} || 'LaTeX'),
source => $form->{IN},
myconfig => \%::myconfig,
userspath => $tmpdir,
variable_content_types => $params{variable_content_types},
+ %driver_options,
);
my $result = $parser->parse($temp_fh);
my ($volume, $directory, $file_name) = File::Spec->splitpath($form->{tmpfile});
my $full_file_name = File::Spec->catfile($tmpdir, $file_name);
if (($params{return} || 'content') eq 'file_name') {
- my $new_name = File::Spec->catfile($tmpdir, 'keep-' . $form->{tmpfile});
+ my $new_name = File::Spec->catfile($userspath, 'keep-' . $form->{tmpfile});
rename $full_file_name, $new_name;
$form->cleanup;
return wantarray ? ($template, @template_files) : $template;
}
+sub _maybe_attach_zugferd_data {
+ my ($record) = @_;
+
+ return if !blessed($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 $xmlfile = File::Temp->new;
+ $xmlfile->print($record->create_zugferd_data);
+ $xmlfile->close;
+
+ my %driver_options = (
+ pdf_a => $record->create_pdf_a_print_options(zugferd_xmp_data => $record->create_zugferd_xmp_data),
+ pdf_attachments => [
+ { source => $xmlfile,
+ name => 'ZUGFeRD-invoice.xml',
+ description => $::locale->text('ZUGFeRD invoice'),
+ relationship => '/Alternative',
+ mime_type => 'text/xml',
+ }
+ ],
+ );
+
+ return %driver_options;
+}
+
1;
__END__
--- /dev/null
+package SL::Helper::ISO3166;
+
+use strict;
+use warnings;
+use utf8;
+
+use Exporter qw(import);
+our @EXPORT_OK = qw(map_name_to_alpha_2_code);
+
+use List::Util qw(first);
+
+my @alpha_2_mappings = (
+ [ 'AD', qr{^(?:AD|Andorra)$}i ],
+ [ 'AE', qr{^(?:AE|United Arab Emirates)$}i ],
+ [ 'AF', qr{^(?:AF|Afghanistan)$}i ],
+ [ 'AG', qr{^(?:AG|Antigua and Barbuda)$}i ],
+ [ 'AI', qr{^(?:AI|Anguilla)$}i ],
+ [ 'AL', qr{^(?:AL|Albania)$}i ],
+ [ 'AM', qr{^(?:AM|Armenia)$}i ],
+ [ 'AO', qr{^(?:AO|Angola)$}i ],
+ [ 'AQ', qr{^(?:AQ|Antarctica)$}i ],
+ [ 'AR', qr{^(?:AR|Argentina)$}i ],
+ [ 'AS', qr{^(?:AS|American Samoa)$}i ],
+ [ 'AT', qr{^(?:AT|A|Austria|Österreich)$}i ],
+ [ 'AU', qr{^(?:AU|Australia)$}i ],
+ [ 'AW', qr{^(?:AW|Aruba)$}i ],
+ [ 'AX', qr{^(?:AX|Åland Islands)$}i ],
+ [ 'AZ', qr{^(?:AZ|Azerbaijan)$}i ],
+ [ 'BA', qr{^(?:BA|Bosnia and Herzegovina)$}i ],
+ [ 'BB', qr{^(?:BB|Barbados)$}i ],
+ [ 'BD', qr{^(?:BD|Bangladesh)$}i ],
+ [ 'BE', qr{^(?:BE|Belgium|Belgien)$}i ],
+ [ 'BF', qr{^(?:BF|Burkina Faso)$}i ],
+ [ 'BG', qr{^(?:BG|Bulgaria)$}i ],
+ [ 'BH', qr{^(?:BH|Bahrain)$}i ],
+ [ 'BI', qr{^(?:BI|Burundi)$}i ],
+ [ 'BJ', qr{^(?:BJ|Benin)$}i ],
+ [ 'BL', qr{^(?:BL|Saint Barthélemy)$}i ],
+ [ 'BM', qr{^(?:BM|Bermuda)$}i ],
+ [ 'BN', qr{^(?:BN|Brunei Darussalam)$}i ],
+ [ 'BO', qr{^(?:BO|Bolivia \(Plurinational State of\))$}i ],
+ [ 'BQ', qr{^(?:BQ|Bonaire, Sint Eustatius and Saba)$}i ],
+ [ 'BR', qr{^(?:BR|Brazil)$}i ],
+ [ 'BS', qr{^(?:BS|Bahamas)$}i ],
+ [ 'BT', qr{^(?:BT|Bhutan)$}i ],
+ [ 'BV', qr{^(?:BV|Bouvet Island)$}i ],
+ [ 'BW', qr{^(?:BW|Botswana)$}i ],
+ [ 'BY', qr{^(?:BY|Belarus)$}i ],
+ [ 'BZ', qr{^(?:BZ|Belize)$}i ],
+ [ 'CA', qr{^(?:CA|Canada)$}i ],
+ [ 'CC', qr{^(?:CC|Cocos \(Keeling\) Islands|Cocos Islands|Keeling Islands)$}i ],
+ [ 'CD', qr{^(?:CD|Congo, Democratic Republic of the)$}i ],
+ [ 'CF', qr{^(?:CF|Central African Republic)$}i ],
+ [ 'CG', qr{^(?:CG|Congo)$}i ],
+ [ 'CH', qr{^(?:CH|Switzerland|Schweiz)$}i ],
+ [ 'CI', qr{^(?:CI|Côte d'Ivoire)$}i ],
+ [ 'CK', qr{^(?:CK|Cook Islands)$}i ],
+ [ 'CL', qr{^(?:CL|Chile)$}i ],
+ [ 'CM', qr{^(?:CM|Cameroon)$}i ],
+ [ 'CN', qr{^(?:CN|China)$}i ],
+ [ 'CO', qr{^(?:CO|Colombia)$}i ],
+ [ 'CR', qr{^(?:CR|Costa Rica)$}i ],
+ [ 'CU', qr{^(?:CU|Cuba)$}i ],
+ [ 'CV', qr{^(?:CV|Cabo Verde)$}i ],
+ [ 'CW', qr{^(?:CW|Curaçao)$}i ],
+ [ 'CX', qr{^(?:CX|Christmas Island)$}i ],
+ [ 'CY', qr{^(?:CY|Cyprus)$}i ],
+ [ 'CZ', qr{^(?:CZ|Czechia)$}i ],
+ [ 'DE', qr{^(?:DE|Germany|D|Deutschland)$}i ],
+ [ 'DJ', qr{^(?:DJ|Djibouti)$}i ],
+ [ 'DK', qr{^(?:DK|Denmark)$}i ],
+ [ 'DM', qr{^(?:DM|Dominica)$}i ],
+ [ 'DO', qr{^(?:DO|Dominican Republic)$}i ],
+ [ 'DZ', qr{^(?:DZ|Algeria)$}i ],
+ [ 'EC', qr{^(?:EC|Ecuador)$}i ],
+ [ 'EE', qr{^(?:EE|Estonia)$}i ],
+ [ 'EG', qr{^(?:EG|Egypt)$}i ],
+ [ 'EH', qr{^(?:EH|Western Sahara)$}i ],
+ [ 'ER', qr{^(?:ER|Eritrea)$}i ],
+ [ 'ES', qr{^(?:ES|Spain)$}i ],
+ [ 'ET', qr{^(?:ET|Ethiopia)$}i ],
+ [ 'FI', qr{^(?:FI|Finland)$}i ],
+ [ 'FJ', qr{^(?:FJ|Fiji)$}i ],
+ [ 'FK', qr{^(?:FK|Falkland Islands \(Malvinas\)|Falkland Islands|Falklands)$}i ],
+ [ 'FM', qr{^(?:FM|Micronesia \(Federated States of\)|Micronesia)$}i ],
+ [ 'FO', qr{^(?:FO|Faroe Islands)$}i ],
+ [ 'FR', qr{^(?:FR|France)$}i ],
+ [ 'GA', qr{^(?:GA|Gabon)$}i ],
+ [ 'GB', qr{^(?:GB|United Kingdom of Great Britain and Northern Ireland)$}i ],
+ [ 'GD', qr{^(?:GD|Grenada)$}i ],
+ [ 'GE', qr{^(?:GE|Georgia)$}i ],
+ [ 'GF', qr{^(?:GF|French Guiana)$}i ],
+ [ 'GG', qr{^(?:GG|Guernsey)$}i ],
+ [ 'GH', qr{^(?:GH|Ghana)$}i ],
+ [ 'GI', qr{^(?:GI|Gibraltar)$}i ],
+ [ 'GL', qr{^(?:GL|Greenland)$}i ],
+ [ 'GM', qr{^(?:GM|Gambia)$}i ],
+ [ 'GN', qr{^(?:GN|Guinea)$}i ],
+ [ 'GP', qr{^(?:GP|Guadeloupe)$}i ],
+ [ 'GQ', qr{^(?:GQ|Equatorial Guinea)$}i ],
+ [ 'GR', qr{^(?:GR|Greece)$}i ],
+ [ 'GS', qr{^(?:GS|South Georgia and the South Sandwich Islands)$}i ],
+ [ 'GT', qr{^(?:GT|Guatemala)$}i ],
+ [ 'GU', qr{^(?:GU|Guam)$}i ],
+ [ 'GW', qr{^(?:GW|Guinea-Bissau)$}i ],
+ [ 'GY', qr{^(?:GY|Guyana)$}i ],
+ [ 'HK', qr{^(?:HK|Hong Kong)$}i ],
+ [ 'HM', qr{^(?:HM|Heard Island and McDonald Islands)$}i ],
+ [ 'HN', qr{^(?:HN|Honduras)$}i ],
+ [ 'HR', qr{^(?:HR|Croatia)$}i ],
+ [ 'HT', qr{^(?:HT|Haiti)$}i ],
+ [ 'HU', qr{^(?:HU|Hungary)$}i ],
+ [ 'ID', qr{^(?:ID|Indonesia)$}i ],
+ [ 'IE', qr{^(?:IE|Ireland)$}i ],
+ [ 'IL', qr{^(?:IL|Israel)$}i ],
+ [ 'IM', qr{^(?:IM|Isle of Man)$}i ],
+ [ 'IN', qr{^(?:IN|India)$}i ],
+ [ 'IO', qr{^(?:IO|British Indian Ocean Territory)$}i ],
+ [ 'IQ', qr{^(?:IQ|Iraq)$}i ],
+ [ 'IR', qr{^(?:IR|Iran \(Islamic Republic of\)|Iran)$}i ],
+ [ 'IS', qr{^(?:IS|Iceland)$}i ],
+ [ 'IT', qr{^(?:IT|Italy)$}i ],
+ [ 'JE', qr{^(?:JE|Jersey)$}i ],
+ [ 'JM', qr{^(?:JM|Jamaica)$}i ],
+ [ 'JO', qr{^(?:JO|Jordan)$}i ],
+ [ 'JP', qr{^(?:JP|Japan)$}i ],
+ [ 'KE', qr{^(?:KE|Kenya)$}i ],
+ [ 'KG', qr{^(?:KG|Kyrgyzstan)$}i ],
+ [ 'KH', qr{^(?:KH|Cambodia)$}i ],
+ [ 'KI', qr{^(?:KI|Kiribati)$}i ],
+ [ 'KM', qr{^(?:KM|Comoros)$}i ],
+ [ 'KN', qr{^(?:KN|Saint Kitts and Nevis)$}i ],
+ [ 'KP', qr{^(?:KP|Korea \(Democratic People's Republic of\))$}i ],
+ [ 'KR', qr{^(?:KR|Korea, Republic of|Republic of Korea|Korea)$}i ],
+ [ 'KW', qr{^(?:KW|Kuwait)$}i ],
+ [ 'KY', qr{^(?:KY|Cayman Islands)$}i ],
+ [ 'KZ', qr{^(?:KZ|Kazakhstan)$}i ],
+ [ 'LA', qr{^(?:LA|Lao People's Democratic Republic)$}i ],
+ [ 'LB', qr{^(?:LB|Lebanon)$}i ],
+ [ 'LC', qr{^(?:LC|Saint Lucia)$}i ],
+ [ 'LI', qr{^(?:LI|Liechtenstein)$}i ],
+ [ 'LK', qr{^(?:LK|Sri Lanka)$}i ],
+ [ 'LR', qr{^(?:LR|Liberia)$}i ],
+ [ 'LS', qr{^(?:LS|Lesotho)$}i ],
+ [ 'LT', qr{^(?:LT|Lithuania)$}i ],
+ [ 'LU', qr{^(?:LU|Luxembourg)$}i ],
+ [ 'LV', qr{^(?:LV|Latvia)$}i ],
+ [ 'LY', qr{^(?:LY|Libya)$}i ],
+ [ 'MA', qr{^(?:MA|Morocco)$}i ],
+ [ 'MC', qr{^(?:MC|Monaco)$}i ],
+ [ 'MD', qr{^(?:MD|Moldova, Republic of)$}i ],
+ [ 'ME', qr{^(?:ME|Montenegro)$}i ],
+ [ 'MF', qr{^(?:MF|Saint Martin \(French part\)|Saint Martin)$}i ],
+ [ 'MG', qr{^(?:MG|Madagascar)$}i ],
+ [ 'MH', qr{^(?:MH|Marshall Islands)$}i ],
+ [ 'MK', qr{^(?:MK|North Macedonia)$}i ],
+ [ 'ML', qr{^(?:ML|Mali)$}i ],
+ [ 'MM', qr{^(?:MM|Myanmar)$}i ],
+ [ 'MN', qr{^(?:MN|Mongolia)$}i ],
+ [ 'MO', qr{^(?:MO|Macao)$}i ],
+ [ 'MP', qr{^(?:MP|Northern Mariana Islands)$}i ],
+ [ 'MQ', qr{^(?:MQ|Martinique)$}i ],
+ [ 'MR', qr{^(?:MR|Mauritania)$}i ],
+ [ 'MS', qr{^(?:MS|Montserrat)$}i ],
+ [ 'MT', qr{^(?:MT|Malta)$}i ],
+ [ 'MU', qr{^(?:MU|Mauritius)$}i ],
+ [ 'MV', qr{^(?:MV|Maldives)$}i ],
+ [ 'MW', qr{^(?:MW|Malawi)$}i ],
+ [ 'MX', qr{^(?:MX|Mexico)$}i ],
+ [ 'MY', qr{^(?:MY|Malaysia)$}i ],
+ [ 'MZ', qr{^(?:MZ|Mozambique)$}i ],
+ [ 'NA', qr{^(?:NA|Namibia)$}i ],
+ [ 'NC', qr{^(?:NC|New Caledonia)$}i ],
+ [ 'NE', qr{^(?:NE|Niger)$}i ],
+ [ 'NF', qr{^(?:NF|Norfolk Island)$}i ],
+ [ 'NG', qr{^(?:NG|Nigeria)$}i ],
+ [ 'NI', qr{^(?:NI|Nicaragua)$}i ],
+ [ 'NL', qr{^(?:NL|Netherlands)$}i ],
+ [ 'NO', qr{^(?:NO|Norway)$}i ],
+ [ 'NP', qr{^(?:NP|Nepal)$}i ],
+ [ 'NR', qr{^(?:NR|Nauru)$}i ],
+ [ 'NU', qr{^(?:NU|Niue)$}i ],
+ [ 'NZ', qr{^(?:NZ|New Zealand)$}i ],
+ [ 'OM', qr{^(?:OM|Oman)$}i ],
+ [ 'PA', qr{^(?:PA|Panama)$}i ],
+ [ 'PE', qr{^(?:PE|Peru)$}i ],
+ [ 'PF', qr{^(?:PF|French Polynesia)$}i ],
+ [ 'PG', qr{^(?:PG|Papua New Guinea)$}i ],
+ [ 'PH', qr{^(?:PH|Philippines)$}i ],
+ [ 'PK', qr{^(?:PK|Pakistan)$}i ],
+ [ 'PL', qr{^(?:PL|Poland)$}i ],
+ [ 'PM', qr{^(?:PM|Saint Pierre and Miquelon)$}i ],
+ [ 'PN', qr{^(?:PN|Pitcairn)$}i ],
+ [ 'PR', qr{^(?:PR|Puerto Rico)$}i ],
+ [ 'PS', qr{^(?:PS|Palestine, State of)$}i ],
+ [ 'PT', qr{^(?:PT|Portugal)$}i ],
+ [ 'PW', qr{^(?:PW|Palau)$}i ],
+ [ 'PY', qr{^(?:PY|Paraguay)$}i ],
+ [ 'QA', qr{^(?:QA|Qatar)$}i ],
+ [ 'RE', qr{^(?:RE|Réunion)$}i ],
+ [ 'RO', qr{^(?:RO|Romania)$}i ],
+ [ 'RS', qr{^(?:RS|Serbia)$}i ],
+ [ 'RU', qr{^(?:RU|Russian Federation)$}i ],
+ [ 'RW', qr{^(?:RW|Rwanda)$}i ],
+ [ 'SA', qr{^(?:SA|Saudi Arabia)$}i ],
+ [ 'SB', qr{^(?:SB|Solomon Islands)$}i ],
+ [ 'SC', qr{^(?:SC|Seychelles)$}i ],
+ [ 'SD', qr{^(?:SD|Sudan)$}i ],
+ [ 'SE', qr{^(?:SE|Sweden)$}i ],
+ [ 'SG', qr{^(?:SG|Singapore)$}i ],
+ [ 'SH', qr{^(?:SH|Saint Helena, Ascension and Tristan da Cunha)$}i ],
+ [ 'SI', qr{^(?:SI|Slovenia)$}i ],
+ [ 'SJ', qr{^(?:SJ|Svalbard and Jan Mayen)$}i ],
+ [ 'SK', qr{^(?:SK|Slovakia)$}i ],
+ [ 'SL', qr{^(?:SL|Sierra Leone)$}i ],
+ [ 'SM', qr{^(?:SM|San Marino)$}i ],
+ [ 'SN', qr{^(?:SN|Senegal)$}i ],
+ [ 'SO', qr{^(?:SO|Somalia)$}i ],
+ [ 'SR', qr{^(?:SR|Suriname)$}i ],
+ [ 'SS', qr{^(?:SS|South Sudan)$}i ],
+ [ 'ST', qr{^(?:ST|Sao Tome and Principe)$}i ],
+ [ 'SV', qr{^(?:SV|El Salvador)$}i ],
+ [ 'SX', qr{^(?:SX|Sint Maarten \(Dutch part\)|Sint Maarten)$}i ],
+ [ 'SY', qr{^(?:SY|Syrian Arab Republic)$}i ],
+ [ 'SZ', qr{^(?:SZ|Eswatini)$}i ],
+ [ 'TC', qr{^(?:TC|Turks and Caicos Islands)$}i ],
+ [ 'TD', qr{^(?:TD|Chad)$}i ],
+ [ 'TF', qr{^(?:TF|French Southern Territories)$}i ],
+ [ 'TG', qr{^(?:TG|Togo)$}i ],
+ [ 'TH', qr{^(?:TH|Thailand)$}i ],
+ [ 'TJ', qr{^(?:TJ|Tajikistan)$}i ],
+ [ 'TK', qr{^(?:TK|Tokelau)$}i ],
+ [ 'TL', qr{^(?:TL|Timor-Leste)$}i ],
+ [ 'TM', qr{^(?:TM|Turkmenistan)$}i ],
+ [ 'TN', qr{^(?:TN|Tunisia)$}i ],
+ [ 'TO', qr{^(?:TO|Tonga)$}i ],
+ [ 'TR', qr{^(?:TR|Turkey)$}i ],
+ [ 'TT', qr{^(?:TT|Trinidad and Tobago)$}i ],
+ [ 'TV', qr{^(?:TV|Tuvalu)$}i ],
+ [ 'TW', qr{^(?:TW|Taiwan, Province of China)$}i ],
+ [ 'TZ', qr{^(?:TZ|Tanzania, United Republic of)$}i ],
+ [ 'UA', qr{^(?:UA|Ukraine)$}i ],
+ [ 'UG', qr{^(?:UG|Uganda)$}i ],
+ [ 'UM', qr{^(?:UM|United States Minor Outlying Islands)$}i ],
+ [ 'US', qr{^(?:US|United States of America)$}i ],
+ [ 'UY', qr{^(?:UY|Uruguay)$}i ],
+ [ 'UZ', qr{^(?:UZ|Uzbekistan)$}i ],
+ [ 'VA', qr{^(?:VA|Holy See)$}i ],
+ [ 'VC', qr{^(?:VC|Saint Vincent and the Grenadines)$}i ],
+ [ 'VE', qr{^(?:VE|Venezuela \(Bolivian Republic of\)|Venezuela)$}i ],
+ [ 'VG', qr{^(?:VG|Virgin Islands \(British\))$}i ],
+ [ 'VI', qr{^(?:VI|Virgin Islands \(U\.?S\.?\))$}i ],
+ [ 'VN', qr{^(?:VN|Viet Nam)$}i ],
+ [ 'VU', qr{^(?:VU|Vanuatu)$}i ],
+ [ 'WF', qr{^(?:WF|Wallis and Futuna)$}i ],
+ [ 'WS', qr{^(?:WS|Samoa)$}i ],
+ [ 'YE', qr{^(?:YE|Yemen)$}i ],
+ [ 'YT', qr{^(?:YT|Mayotte)$}i ],
+ [ 'ZA', qr{^(?:ZA|South Africa)$}i ],
+ [ 'ZM', qr{^(?:ZM|Zambia)$}i ],
+ [ 'ZW', qr{^(?:ZW|Zimbabwe)$}i ],
+);
+
+sub map_name_to_alpha_2_code {
+ my ($country) = @_;
+
+ return undef if ($country // '') eq '';
+
+ my $code = first { $country =~ $_->[1] } @alpha_2_mappings;
+ return $code->[0] if $code;
+
+ no warnings 'once';
+ $::lxdebug->message(LXDebug::WARN(), "ISO3166::map_name_to_alpha_2_code: no mapping found for '$country'");
+
+ return undef;
+}
+
+1;
--- /dev/null
+package SL::Helper::ISO4217;
+
+use strict;
+use warnings;
+use utf8;
+
+use Exporter qw(import);
+our @EXPORT_OK = qw(map_currency_name_to_code);
+
+use List::Util qw(first);
+
+my @currency_name_to_code_mappings = (
+ [ 'AED', qr{^(?:UAE Dirham|AED)$}i ],
+ [ 'AFN', qr{^(?:Afghani|AFN)$}i ],
+ [ 'ALL', qr{^(?:Lek|ALL)$}i ],
+ [ 'AMD', qr{^(?:Armenian Dram|AMD)$}i ],
+ [ 'ANG', qr{^(?:Netherlands Antillean Guilder|ANG)$}i ],
+ [ 'AOA', qr{^(?:Kwanza|AOA)$}i ],
+ [ 'ARS', qr{^(?:Argentine Peso|ARS)$}i ],
+ [ 'AUD', qr{^(?:Australian Dollar|AUD)$}i ],
+ [ 'AWG', qr{^(?:Aruban Florin|AWG)$}i ],
+ [ 'AZN', qr{^(?:Azerbaijan Manat|AZN)$}i ],
+ [ 'BAM', qr{^(?:Convertible Mark|BAM)$}i ],
+ [ 'BBD', qr{^(?:Barbados Dollar|BBD)$}i ],
+ [ 'BDT', qr{^(?:Taka|BDT)$}i ],
+ [ 'BGN', qr{^(?:Bulgarian Lev|BGN)$}i ],
+ [ 'BHD', qr{^(?:Bahraini Dinar|BHD)$}i ],
+ [ 'BIF', qr{^(?:Burundi Franc|BIF)$}i ],
+ [ 'BMD', qr{^(?:Bermudian Dollar|BMD)$}i ],
+ [ 'BND', qr{^(?:Brunei Dollar|BND)$}i ],
+ [ 'BOB', qr{^(?:Boliviano|BOB)$}i ],
+ [ 'BOV', qr{^(?:US Dollar|BOV)$}i ],
+ [ 'BRL', qr{^(?:Brazilian Real|BRL)$}i ],
+ [ 'BSD', qr{^(?:Bahamian Dollar|BSD)$}i ],
+ [ 'BTN', qr{^(?:Ngultrum|BTN)$}i ],
+ [ 'BWP', qr{^(?:Pula|BWP)$}i ],
+ [ 'BYN', qr{^(?:Belarusian Ruble|BYN)$}i ],
+ [ 'BZD', qr{^(?:Belize Dollar|BZD)$}i ],
+ [ 'CAD', qr{^(?:Canadian Dollar|CAD)$}i ],
+ [ 'CDF', qr{^(?:Congolese Franc|CDF)$}i ],
+ [ 'CHE', qr{^(?:Syrian Pound|CHE)$}i ],
+ [ 'CHF', qr{^(?:Swiss Franc|CHF)$}i ],
+ [ 'CHW', qr{^(?:Syrian Pound|CHW)$}i ],
+ [ 'CLF', qr{^(?:Yuan Renminbi|CLF)$}i ],
+ [ 'CLP', qr{^(?:Chilean Peso|CLP)$}i ],
+ [ 'CNY', qr{^(?:Yuan Renminbi|CNY)$}i ],
+ [ 'COP', qr{^(?:Colombian Peso|COP)$}i ],
+ [ 'COU', qr{^(?:Comorian Franc |COU)$}i ],
+ [ 'CRC', qr{^(?:Costa Rican Colon|CRC)$}i ],
+ [ 'CUC', qr{^(?:Peso Convertible|CUC)$}i ],
+ [ 'CUP', qr{^(?:Cuban Peso|CUP)$}i ],
+ [ 'CVE', qr{^(?:Cabo Verde Escudo|CVE)$}i ],
+ [ 'CZK', qr{^(?:Czech Koruna|CZK)$}i ],
+ [ 'DJF', qr{^(?:Djibouti Franc|DJF)$}i ],
+ [ 'DKK', qr{^(?:Danish Krone|DKK)$}i ],
+ [ 'DOP', qr{^(?:Dominican Peso|DOP)$}i ],
+ [ 'DZD', qr{^(?:Algerian Dinar|DZD)$}i ],
+ [ 'EGP', qr{^(?:Egyptian Pound|EGP)$}i ],
+ [ 'ERN', qr{^(?:Nakfa|ERN)$}i ],
+ [ 'ETB', qr{^(?:Ethiopian Birr|ETB)$}i ],
+ [ 'EUR', qr{^(?:Euro|EUR|€)$}i ],
+ [ 'FJD', qr{^(?:Fiji Dollar|FJD)$}i ],
+ [ 'FKP', qr{^(?:Falkland Islands Pound|FKP)$}i ],
+ [ 'GBP', qr{^(?:Pound Sterling|GBP)$}i ],
+ [ 'GEL', qr{^(?:Lari|GEL)$}i ],
+ [ 'GHS', qr{^(?:Ghana Cedi|GHS)$}i ],
+ [ 'GIP', qr{^(?:Gibraltar Pound|GIP)$}i ],
+ [ 'GMD', qr{^(?:Dalasi|GMD)$}i ],
+ [ 'GNF', qr{^(?:Guinean Franc|GNF)$}i ],
+ [ 'GTQ', qr{^(?:Quetzal|GTQ)$}i ],
+ [ 'GYD', qr{^(?:Guyana Dollar|GYD)$}i ],
+ [ 'HKD', qr{^(?:Hong Kong Dollar|HKD)$}i ],
+ [ 'HNL', qr{^(?:Lempira|HNL)$}i ],
+ [ 'HRK', qr{^(?:Kuna|HRK)$}i ],
+ [ 'HTG', qr{^(?:Gourde|HTG)$}i ],
+ [ 'HUF', qr{^(?:Forint|HUF)$}i ],
+ [ 'IDR', qr{^(?:Rupiah|IDR)$}i ],
+ [ 'ILS', qr{^(?:New Israeli Sheqel|ILS)$}i ],
+ [ 'INR', qr{^(?:Indian Rupee|INR)$}i ],
+ [ 'IQD', qr{^(?:Iraqi Dinar|IQD)$}i ],
+ [ 'IRR', qr{^(?:Iranian Rial|IRR)$}i ],
+ [ 'ISK', qr{^(?:Iceland Krona|ISK)$}i ],
+ [ 'JMD', qr{^(?:Jamaican Dollar|JMD)$}i ],
+ [ 'JOD', qr{^(?:Jordanian Dinar|JOD)$}i ],
+ [ 'JPY', qr{^(?:Yen|JPY|¥)$}i ],
+ [ 'KES', qr{^(?:Kenyan Shilling|KES)$}i ],
+ [ 'KGS', qr{^(?:Som|KGS)$}i ],
+ [ 'KHR', qr{^(?:Riel|KHR)$}i ],
+ [ 'KMF', qr{^(?:Comorian Franc |KMF)$}i ],
+ [ 'KPW', qr{^(?:North Korean Won|KPW)$}i ],
+ [ 'KRW', qr{^(?:Won|KRW)$}i ],
+ [ 'KWD', qr{^(?:Kuwaiti Dinar|KWD)$}i ],
+ [ 'KYD', qr{^(?:Cayman Islands Dollar|KYD)$}i ],
+ [ 'KZT', qr{^(?:Tenge|KZT)$}i ],
+ [ 'LAK', qr{^(?:Lao Kip|LAK)$}i ],
+ [ 'LBP', qr{^(?:Lebanese Pound|LBP)$}i ],
+ [ 'LKR', qr{^(?:Sri Lanka Rupee|LKR)$}i ],
+ [ 'LRD', qr{^(?:Liberian Dollar|LRD)$}i ],
+ [ 'LSL', qr{^(?:Loti|LSL)$}i ],
+ [ 'LYD', qr{^(?:Libyan Dinar|LYD)$}i ],
+ [ 'MAD', qr{^(?:Moroccan Dirham|MAD)$}i ],
+ [ 'MDL', qr{^(?:Moldovan Leu|MDL)$}i ],
+ [ 'MGA', qr{^(?:Malagasy Ariary|MGA)$}i ],
+ [ 'MKD', qr{^(?:Denar|MKD)$}i ],
+ [ 'MMK', qr{^(?:Kyat|MMK)$}i ],
+ [ 'MNT', qr{^(?:Tugrik|MNT)$}i ],
+ [ 'MOP', qr{^(?:Pataca|MOP)$}i ],
+ [ 'MRU', qr{^(?:Ouguiya|MRU)$}i ],
+ [ 'MUR', qr{^(?:Mauritius Rupee|MUR)$}i ],
+ [ 'MVR', qr{^(?:Rufiyaa|MVR)$}i ],
+ [ 'MWK', qr{^(?:Malawi Kwacha|MWK)$}i ],
+ [ 'MXN', qr{^(?:Mexican Peso|MXN)$}i ],
+ [ 'MXV', qr{^(?:US Dollar|MXV)$}i ],
+ [ 'MYR', qr{^(?:Malaysian Ringgit|MYR)$}i ],
+ [ 'MZN', qr{^(?:Mozambique Metical|MZN)$}i ],
+ [ 'NAD', qr{^(?:Namibia Dollar|NAD)$}i ],
+ [ 'NGN', qr{^(?:Naira|NGN)$}i ],
+ [ 'NIO', qr{^(?:Cordoba Oro|NIO)$}i ],
+ [ 'NOK', qr{^(?:Norwegian Krone|NOK)$}i ],
+ [ 'NPR', qr{^(?:Nepalese Rupee|NPR)$}i ],
+ [ 'NZD', qr{^(?:New Zealand Dollar|NZD)$}i ],
+ [ 'OMR', qr{^(?:Rial Omani|OMR)$}i ],
+ [ 'PAB', qr{^(?:Balboa|PAB)$}i ],
+ [ 'PAB', qr{^(?:No universal currency|PAB)$}i ],
+ [ 'PEN', qr{^(?:Sol|PEN)$}i ],
+ [ 'PGK', qr{^(?:Kina|PGK)$}i ],
+ [ 'PHP', qr{^(?:Philippine Peso|PHP)$}i ],
+ [ 'PKR', qr{^(?:Pakistan Rupee|PKR)$}i ],
+ [ 'PLN', qr{^(?:Zloty|PLN)$}i ],
+ [ 'PYG', qr{^(?:Guarani|PYG)$}i ],
+ [ 'QAR', qr{^(?:Qatari Rial|QAR)$}i ],
+ [ 'RON', qr{^(?:Romanian Leu|RON)$}i ],
+ [ 'RSD', qr{^(?:Serbian Dinar|RSD)$}i ],
+ [ 'RUB', qr{^(?:Russian Ruble|RUB)$}i ],
+ [ 'RWF', qr{^(?:Rwanda Franc|RWF)$}i ],
+ [ 'SAR', qr{^(?:Saudi Riyal|SAR)$}i ],
+ [ 'SBD', qr{^(?:Solomon Islands Dollar|SBD)$}i ],
+ [ 'SCR', qr{^(?:Seychelles Rupee|SCR)$}i ],
+ [ 'SDG', qr{^(?:Sudanese Pound|SDG)$}i ],
+ [ 'SEK', qr{^(?:Swedish Krona|SEK)$}i ],
+ [ 'SGD', qr{^(?:Singapore Dollar|SGD)$}i ],
+ [ 'SHP', qr{^(?:Saint Helena Pound|SHP)$}i ],
+ [ 'SLL', qr{^(?:Leone|SLL)$}i ],
+ [ 'SOS', qr{^(?:Somali Shilling|SOS)$}i ],
+ [ 'SRD', qr{^(?:Surinam Dollar|SRD)$}i ],
+ [ 'SSP', qr{^(?:No universal currency|SSP)$}i ],
+ [ 'SSP', qr{^(?:South Sudanese Pound|SSP)$}i ],
+ [ 'STN', qr{^(?:Dobra|STN)$}i ],
+ [ 'SVC', qr{^(?:El Salvador Colon|SVC)$}i ],
+ [ 'SYP', qr{^(?:Syrian Pound|SYP)$}i ],
+ [ 'SZL', qr{^(?:Lilangeni|SZL)$}i ],
+ [ 'THB', qr{^(?:Baht|THB)$}i ],
+ [ 'TJS', qr{^(?:Somoni|TJS)$}i ],
+ [ 'TMT', qr{^(?:Turkmenistan New Manat|TMT)$}i ],
+ [ 'TND', qr{^(?:Tunisian Dinar|TND)$}i ],
+ [ 'TOP', qr{^(?:Pa’anga|TOP)$}i ],
+ [ 'TRY', qr{^(?:Turkish Lira|TRY)$}i ],
+ [ 'TTD', qr{^(?:Trinidad and Tobago Dollar|TTD)$}i ],
+ [ 'TWD', qr{^(?:New Taiwan Dollar|TWD)$}i ],
+ [ 'TZS', qr{^(?:Tanzanian Shilling|TZS)$}i ],
+ [ 'UAH', qr{^(?:Hryvnia|UAH)$}i ],
+ [ 'UGX', qr{^(?:Uganda Shilling|UGX)$}i ],
+ [ 'USD', qr{^(?:US Dollar|USD|\$)$}i ],
+ [ 'USN', qr{^(?:Peso Uruguayo|USN)$}i ],
+ [ 'UYI', qr{^(?:Unidad Previsional|UYI)$}i ],
+ [ 'UYU', qr{^(?:Peso Uruguayo|UYU)$}i ],
+ [ 'UYW', qr{^(?:Unidad Previsional|UYW)$}i ],
+ [ 'UZS', qr{^(?:Uzbekistan Sum|UZS)$}i ],
+ [ 'VES', qr{^(?:Bolívar Soberano|VES)$}i ],
+ [ 'VND', qr{^(?:Dong|VND)$}i ],
+ [ 'VUV', qr{^(?:Vatu|VUV)$}i ],
+ [ 'WST', qr{^(?:Tala|WST)$}i ],
+ [ 'XAF', qr{^(?:CFA Franc BEAC|XAF)$}i ],
+ [ 'XAG', qr{^(?:Silver|XAG)$}i ],
+ [ 'XAU', qr{^(?:Gold|XAU)$}i ],
+ [ 'XBA', qr{^(?:Bond Markets Unit European Composite Unit \(EURCO\)|XBA)$}i ],
+ [ 'XBB', qr{^(?:Bond Markets Unit European Monetary Unit \(E\.?M\.?U\.?-6\)|XBB)$}i ],
+ [ 'XBC', qr{^(?:Bond Markets Unit European Unit of Account 9 \(E\.?U\.?A\.?-9\)|XBC)$}i ],
+ [ 'XBD', qr{^(?:Bond Markets Unit European Unit of Account 17 \(E\.?U\.?A\.?-17\)|XBD)$}i ],
+ [ 'XCD', qr{^(?:East Caribbean Dollar|XCD)$}i ],
+ [ 'XCD', qr{^(?:No universal currency|XCD)$}i ],
+ [ 'XDR', qr{^(?:SDR \(Special Drawing Right\)|SDR|XDR)$}i ],
+ [ 'XOF', qr{^(?:CFA Franc BCEAO|XOF)$}i ],
+ [ 'XPD', qr{^(?:Palladium|XPD)$}i ],
+ [ 'XPF', qr{^(?:CFP Franc|XPF)$}i ],
+ [ 'XPT', qr{^(?:Platinum|XPT)$}i ],
+ [ 'XSU', qr{^(?:Sucre|XSU)$}i ],
+ [ 'XTS', qr{^(?:Codes specifically reserved for testing purposes|XTS)$}i ],
+ [ 'XUA', qr{^(?:ADB Unit of Account|XUA)$}i ],
+ [ 'XXX', qr{^(?:The codes assigned for transactions where no currency is involved|XXX)$}i ],
+ [ 'YER', qr{^(?:Yemeni Rial|YER)$}i ],
+ [ 'ZAR', qr{^(?:Rand|ZAR)$}i ],
+ [ 'ZMW', qr{^(?:Zambian Kwacha|ZMW)$}i ],
+ [ 'ZWL', qr{^(?:Zimbabwe Dollar|ZWL)$}i ],
+);
+
+sub map_currency_name_to_code {
+ my ($currency) = @_;
+
+ return undef if ($currency // '') eq '';
+
+ my $code = first { $currency =~ $_->[1] } @currency_name_to_code_mappings;
+ return $code->[0] if $code;
+
+ no warnings 'once';
+ $::lxdebug->message(LXDebug::WARN(), "ISO4217::map_currency_name_to_code: no mapping found for '$currency'");
+
+ return undef;
+}
+
+1;
$sfile->fh->print($mt940_data);
$sfile->fh->close;
+ # create needed dir structure for aqbanking 5.x and 6.x
my $todir = $sfile->get_path . '/imexporters/csv/profiles';
mkpath $todir;
+ die "Cannot create $todir" unless -d $todir;
+
File::Copy::copy('users/aqbanking.conf', $todir.'/kivi.conf');
+ die "Cannot create local aqbanking conf " unless -f $todir.'/kivi.conf';
+
+ mkpath $sfile->get_path . '/settings6/aqbanking';
my $aqbin = $::lx_office_conf{applications}->{aqbanking};
die "Can't find aqbanking-cli, please check your configuration file.\n" unless -f $aqbin;
my $cmd = "$aqbin --cfgdir=\"" . $sfile->get_path . "\" import --importer=\"swift\" --profile=\"SWIFT-MT940\" -f " .
- $sfile->get_path . "/$import_filename | $aqbin --cfgdir=\"" . $sfile->get_path . "\" listtrans --exporter=\"csv\" --profile=kivi 2> /dev/null ";
+ $sfile->get_path . "/$import_filename | $aqbin --cfgdir=\"" . $sfile->get_path . "\" export --profile=kivi 2> /dev/null ";
my $converted_data = '"empty";"local_bank_code";"local_account_number";"remote_bank_code";"remote_account_number";"transdate";"valutadate";"amount";'.
'"currency";"remote_name";"remote_name_1";"purpose";"purpose1";"purpose2";"purpose3";"purpose4";"purpose5";"purpose6";"purpose7";"purpose8";"purpose9";'.
use strict;
+use SL::Webdav;
+
use Exporter 'import';
our @EXPORT_OK = qw(create_massprint_pdf merge_massprint_pdf create_pdfs print_pdfs);
our %EXPORT_TAGS = (
my ($self, %params) = @_;
my $form = Form->new('');
my %create_params = (
- variables => $form,
- return => 'file_name',
+ variables => $form,
+ record => $params{document},
+ return => 'file_name',
);
## find_template may return a list !
$create_params{template} = $self->find_template(name => $params{variables}->{formname}, printer_id => $params{printer_id});
}
$form->prepare_for_printing;
+
+ $form->{language} = '_' . $form->{language};
$form->{attachment_filename} = $form->generate_attachment_filename;
my $pdf_filename = $self->create_pdf(%create_params);
+ if ($::instance_conf->get_webdav_documents && !$form->{preview}) {
+ my $webdav = SL::Webdav->new(
+ type => $params{document}->type,
+ number => $params{document}->record_number,
+ );
+ my $webdav_file = SL::Webdav::File->new(
+ webdav => $webdav,
+ filename => $form->{attachment_filename},
+ );
+ eval {
+ $webdav_file->store(file => $pdf_filename);
+ 1;
+ } or do {
+ push @{ $params{errors} }, $@ if exists $params{errors};
+ }
+ }
+
if ( $::instance_conf->get_doc_storage && ! $form->{preview}) {
$self->append_general_pdf_attachments(filepath => $pdf_filename, type => $form->{type} );
$form->{tmpfile} = $pdf_filename;
before printing is done
+Recognized parameters are (not a complete list):
+
+=over 2
+
+=item * C<errors> – optional. If given, it must be an array ref. This will be
+filled with potential errors.
+
+=back
+
=head1 AUTHOR
--- /dev/null
+package SL::Helper::UNECERecommendation20;
+
+use strict;
+use warnings;
+use utf8;
+
+use Exporter qw(import);
+our @EXPORT_OK = qw(map_name_to_alpha_2_code);
+
+use List::Util qw(first);
+
+my @mappings = (
+ # space and time
+ # areas
+ [ 'MTK', qr{^(?:m²|qm|quadrat *meter|quadrat *metre)$}i ],
+
+ # distances
+ [ 'CMT', qr{^(?:cm|centi *meter|centi *metre)$}i ],
+ [ 'MTR', qr{^(?:m|meter|metre)$}i ],
+ [ 'KMT', qr{^(?:km|kilo *meter|kilo *metre)$}i ],
+
+ # durations
+ [ 'SEC', qr{^(?:s|sec|second|sek|sekunde)$}i ],
+ [ 'MIN', qr{^min(?:ute)?$}i ],
+ [ 'HUR', qr{^(?:h(?:our)?|std(?:unde)?)$}i ],
+ [ 'DAY', qr{^(?:day|tag)$}i ],
+ [ 'MON', qr{^mon(?:th|at|atlich)?$}i ],
+ [ 'QAN', qr{^quart(?:er|al|alsweise)?$}i ],
+ [ 'ANN', qr{^(?:yearly|annually|jährlich|Jahr)?$}i ],
+
+ # mass
+ [ 'MGM', qr{^(?:mg|milli *gramm?)$}i ],
+ [ 'GRM', qr{^(?:g|gramm?)$}i ],
+ [ 'KGM', qr{^(?:kg|kilo *gramm?)$}i ],
+ [ 'KTN', qr{^(?:t|tonne|kilo *tonne)$}i ],
+
+ # volumes
+ [ 'MLT', qr{^(?:ml|milli *liter|milli *litre)$}i ],
+ [ 'LTR', qr{^(?:l|liter|litre)$}i ],
+
+ # miscellaneous
+ [ 'C62', qr{^(?:stck|stück|pieces?|pc|psch|pauschal)$}i ],
+);
+
+sub map_name_to_code {
+ my ($unit) = @_;
+
+ return undef if ($unit // '') eq '';
+
+ my $code = first { $unit =~ $_->[1] } @mappings;
+ return $code->[0] if $code;
+
+ no warnings 'once';
+ $::lxdebug->message(LXDebug::WARN(), "UNECERecommendation20::map_name_code: no mapping found for '$unit'");
+
+ return undef;
+}
+
+1;
use parent qw(Rose::Object);
use version;
-use SL::DBUtils qw(selectall_hashref_query selectfirst_hashref_query do_query selectall_ids);
+use SL::DBUtils qw(selectall_hashref_query selectfirst_hashref_query do_query selectcol_array_query);
use SL::DB;
use Rose::Object::MakeMethods::Generic (
sub get_keys {
my ($self) = @_;
- my @keys = selectall_ids($::form, $::form->get_standard_dbh, <<"", 0, $self->login, $self->namespace);
+ my @keys = selectcol_array_query($::form, SL::DB->client->dbh, <<"", $self->login, $self->namespace);
SELECT key FROM user_preferences WHERE login = ? AND namespace = ?
return @keys;
--- /dev/null
+package SL::Helper::UserPreferences::PartPickerSearch;
+
+use strict;
+use parent qw(Rose::Object);
+
+use Carp;
+use List::MoreUtils qw(none);
+
+use SL::Helper::UserPreferences;
+
+use Rose::Object::MakeMethods::Generic (
+ 'scalar --get_set_init' => [ qw(user_prefs) ],
+);
+
+sub get_sales_search_customer_partnumber {
+ !!$_[0]->user_prefs->get('sales_search_customer_partnumber');
+}
+
+sub get_purchase_search_makemodel {
+ !!$_[0]->user_prefs->get('purchase_search_makemodel');
+}
+
+sub store_sales_search_customer_partnumber {
+ $_[0]->user_prefs->store('sales_search_customer_partnumber', $_[1]);
+}
+
+sub store_purchase_search_makemodel {
+ $_[0]->user_prefs->store('purchase_search_makemodel', $_[1]);
+}
+
+sub init_user_prefs {
+ SL::Helper::UserPreferences->new(
+ namespace => $_[0]->namespace,
+ )
+}
+
+# read only stuff
+sub namespace { 'PartPickerSearch' }
+sub version { 1 }
+
+1;
+
+__END__
+
+=pod
+
+=encoding utf-8
+
+=head1 NAME
+
+SL::Helper::UserPreferences::PartPickerSearch - preferences intended
+to store user settings for the behavior of a partpicker search.
+
+=head1 SYNOPSIS
+
+ use SL::Helper::UserPreferences::PartPickerSearch;
+ my $prefs = SL::Helper::UserPreferences::PartPickerSearch->new();
+
+ $prefs->store_purchase_search_makemodel(1);
+ my $value = $prefs->get_purchase_search_makemodel;
+
+=head1 DESCRIPTION
+
+This module manages storing the settings for the part picker to search for
+customer/vendor partnumber in sales/purchase forms (new order controller).
+
+=head1 BUGS
+
+None yet :)
+
+=head1 AUTHOR
+
+Bernd Bleßmann E<lt>bernd@kivitendo-premium.deE<gt>
+
+=cut
--- /dev/null
+package SL::Helper::UserPreferences::UpdatePositions;
+
+use strict;
+use parent qw(Rose::Object);
+
+use Carp;
+use List::MoreUtils qw(none);
+
+use SL::Helper::UserPreferences;
+
+use Rose::Object::MakeMethods::Generic (
+ 'scalar --get_set_init' => [ qw(user_prefs) ],
+);
+
+sub get_show_update_button {
+ !!$_[0]->user_prefs->get('show_update_button');
+}
+
+sub store_show_update_button {
+ $_[0]->user_prefs->store('show_update_button', $_[1]);
+}
+
+sub init_user_prefs {
+ SL::Helper::UserPreferences->new(
+ namespace => $_[0]->namespace,
+ )
+}
+
+# read only stuff
+sub namespace { 'UpdatePositions' }
+sub version { 1 }
+
+1;
+
+__END__
+
+=pod
+
+=encoding utf-8
+
+=head1 NAME
+
+SL::Helper::UserPreferences::UpdatePositions - preferences intended
+to store user settings for displaying an update button for the postions
+of document forms to update the positions (parts) from master data.
+
+=head1 SYNOPSIS
+
+ use SL::Helper::UserPreferences::UpdatePositions;
+ my $prefs = SL::Helper::UserPreferences::UpdatePositions->new();
+
+ $prefs->store_show_update_button(1);
+ my $value = $prefs->get_show_update_button;
+
+=head1 DESCRIPTION
+
+This module manages storing the user's choise for displaying an update button
+in the positions area in forms (new order controller).
+
+=head1 BUGS
+
+None yet :)
+
+=head1 AUTHOR
+
+Bernd Bleßmann E<lt>bernd@kivitendo-premium.deE<gt>
+
+=cut
push @bind_vars, @cvar_values;
}
+ # simple search for assemblies by items used in assemblies
+ if ($form->{bom} eq '2' && $form->{l_assembly}) {
+ # nuke where clause and bind vars
+ $where_clause = ' 1=1 AND p.id in (SELECT id from assembly where parts_id IN ' .
+ ' (select id from parts where 1=1 AND ';
+ @bind_vars = ();
+ # use only like filter for items used in assemblies
+ foreach (@like_filters) {
+ next unless $form->{$_};
+ $form->{"l_$_"} = '1'; # show the column
+ $where_clause .= " $_ ILIKE ? ";
+ push @bind_vars, like($form->{$_});
+ }
+ $where_clause .='))';
+ }
+
my $query = <<" SQL";
SELECT DISTINCT $select_clause
FROM parts p
SQL
my $query_tax = <<SQL;
- SELECT c.accno, t.taxdescription AS description, t.rate, t.taxnumber
+ SELECT c.accno, t.taxdescription AS description, t.id as tax_id, t.rate,
+ c.accno as taxnumber
FROM tax t
LEFT JOIN chart c ON c.id = t.chart_id
WHERE t.id IN
$form->{"taxaccounts_$index"} = $ref->{"accno"};
$form->{"taxaccounts"} .= "$ref->{accno} "if $form->{"taxaccounts"} !~ /$ref->{accno}/;
- $form->{"$ref->{accno}_${_}"} = $ref->{$_} for qw(rate description taxnumber);
+ $form->{"$ref->{accno}_${_}"} = $ref->{$_} for qw(rate description taxnumber tax_id);
}
$sth_tax->finish;
$sth->finish();
+ $query = qq|SELECT
+ cp.parts_id,
+ cp.customer_partnumber AS customer_model,
+ c.name AS customer_make
+ FROM part_customer_prices cp
+ LEFT JOIN customer c ON (cp.customer_id = c.id)
+ WHERE cp.parts_id IN ($placeholders)|;
+
+ my %customermodel = ();
+
+ $sth = prepare_execute_query($form, $dbh, $query, @part_ids);
+
+ while (my $ref = $sth->fetchrow_hashref()) {
+ $customermodel{$ref->{parts_id}} ||= [];
+ push @{ $customermodel{$ref->{parts_id}} }, $ref;
+ }
+
+ $sth->finish();
+
my @columns = qw(ean image microfiche drawing);
$query = qq|SELECT id, | . join(', ', @columns) . qq|
my %data = selectall_as_map($form, $dbh, $query, 'id', \@columns, @part_ids);
my %template_arrays;
- map { $template_arrays{$_} = [] } (qw(make model), @columns);
+ map { $template_arrays{$_} = [] } (qw(make model customer_make customer_model), @columns);
foreach my $i (1 .. $rowcount) {
my $id = $form->{"${prefix}${i}"};
push @{ $template_arrays{make} }, [];
push @{ $template_arrays{model} }, [];
- next if (!$makemodel{$id});
+ if ($makemodel{$id}) {
+ foreach my $ref (@{ $makemodel{$id} }) {
+ map { push @{ $template_arrays{$_}->[-1] }, $ref->{$_} } qw(make model);
+ }
+ }
+
+ push @{ $template_arrays{customer_make} }, [];
+ push @{ $template_arrays{customer_model} }, [];
- foreach my $ref (@{ $makemodel{$id} }) {
- map { push @{ $template_arrays{$_}->[-1] }, $ref->{$_} } qw(make model);
+ if ($customermodel{$id}) {
+ foreach my $ref (@{ $customermodel{$id} }) {
+ push @{ $template_arrays{$_}->[-1] }, $ref->{$_} for qw(customer_make customer_model);
+ }
}
+
}
my $parts = SL::DB::Manager::Part->get_all(query => [ id => \@part_ids ]);
if ($form->{currency} ne $defaultcurrency) && !$exchangerate;
# record acc_trans transactions
+ my $taxdate = $form->{deliverydate} ? $form->{deliverydate} : $form->{invdate};
foreach my $trans_id (keys %{ $form->{amount} }) {
foreach my $accno (keys %{ $form->{amount}{$trans_id} }) {
$form->{amount}{$trans_id}{$accno} = $form->round_amount($form->{amount}{$trans_id}{$accno}, 2);
ORDER BY startdate DESC LIMIT 1),
(SELECT link FROM chart WHERE accno = ?))|;
@values = ($trans_id, $accno, $form->{amount}{$trans_id}{$accno},
- conv_date($form->{invdate}), $accno, conv_date($form->{invdate}), $project_id, $accno, conv_date($form->{invdate}), $accno);
+ conv_date($form->{invdate}), $accno, conv_date($taxdate), $project_id, $accno, conv_date($taxdate), $accno);
do_query($form, $dbh, $query, @values);
}
}
# save AP record
$query = qq|UPDATE ap SET
- invnumber = ?, ordnumber = ?, quonumber = ?, transdate = ?,
- orddate = ?, quodate = ?, vendor_id = ?, amount = ?,
- netamount = ?, paid = ?, duedate = ?,
- invoice = ?, taxzone_id = ?, notes = ?, taxincluded = ?,
+ invnumber = ?, ordnumber = ?, quonumber = ?, transdate = ?,
+ orddate = ?, quodate = ?, vendor_id = ?, amount = ?,
+ netamount = ?, paid = ?, duedate = ?, deliverydate = ?,
+ invoice = ?, taxzone_id = ?, notes = ?, taxincluded = ?,
intnotes = ?, storno_id = ?, storno = ?,
cp_id = ?, employee_id = ?, department_id = ?, delivery_term_id = ?,
+ payment_id = ?,
currency_id = (SELECT id FROM currencies WHERE name = ?),
globalproject_id = ?, direct_debit = ?
WHERE id = ?|;
@values = (
$form->{invnumber}, $form->{ordnumber}, $form->{quonumber}, conv_date($form->{invdate}),
conv_date($form->{orddate}), conv_date($form->{quodate}), conv_i($form->{vendor_id}), $amount,
- $netamount, $form->{paid}, conv_date($form->{duedate}),
+ $netamount, $form->{paid}, conv_date($form->{duedate}), conv_date($form->{deliverydate}),
'1', $taxzone_id, $restricter->process($form->{notes}), $form->{taxincluded} ? 't' : 'f',
$form->{intnotes}, conv_i($form->{storno_id}), $form->{storno} ? 't' : 'f',
conv_i($form->{cp_id}), conv_i($form->{employee_id}), conv_i($form->{department_id}), conv_i($form->{delivery_term_id}),
+ conv_i($form->{payment_id}),
$form->{"currency"},
conv_i($form->{globalproject_id}),
$form->{direct_debit} ? 't' : 'f',
# retrieve invoice
$query = qq|SELECT cp_id, invnumber, transdate AS invdate, duedate,
- orddate, quodate, globalproject_id,
+ orddate, quodate, deliverydate, globalproject_id,
ordnumber, quonumber, paid, taxincluded, notes, taxzone_id, storno, gldate,
mtime, itime,
intnotes, (SELECT cu.name FROM currencies cu WHERE cu.id=ap.currency_id) AS currency, direct_debit,
- delivery_term_id
+ payment_id, delivery_term_id
FROM ap
WHERE id = ?|;
$ref = selectfirst_hashref_query($form, $dbh, $query, conv_i($form->{id}));
# get tax rates and description
my $accno_id = ($form->{vc} eq "customer") ? $ref->{income_accno} : $ref->{expense_accno};
$query =
- qq|SELECT c.accno, t.taxdescription, t.rate, t.taxnumber FROM tax t
+ qq|SELECT c.accno, t.taxdescription, t.rate, t.id as tax_id,
+ c.accno as taxnumber -- taxnumber is same as accno, but still accessed as taxnumber in code
+ FROM tax t
LEFT JOIN chart c ON (c.id = t.chart_id)
WHERE t.id in
(SELECT tk.tax_id FROM taxkeys tk
$form->{"$ptr->{accno}_rate"} = $ptr->{rate};
$form->{"$ptr->{accno}_description"} = $ptr->{taxdescription};
$form->{"$ptr->{accno}_taxnumber"} = $ptr->{taxnumber};
+ $form->{"$ptr->{accno}_tax_id"} = $ptr->{tax_id};
$form->{taxaccounts} .= "$ptr->{accno} ";
}
# get tax rates and description
my $accno_id = ($form->{vc} eq "customer") ? $ref->{income_accno} : $ref->{expense_accno};
$query =
- qq|SELECT c.accno, t.taxdescription, t.rate, t.taxnumber
+ qq|SELECT c.accno, t.taxdescription, t.rate, c.accno as taxnumber, t.id as tax_id
FROM tax t
LEFT JOIN chart c on (c.id = t.chart_id)
WHERE t.id IN
$form->{"$ptr->{accno}_rate"} = $ptr->{rate};
$form->{"$ptr->{accno}_description"} = $ptr->{taxdescription};
$form->{"$ptr->{accno}_taxnumber"} = $ptr->{taxnumber};
+ $form->{"$ptr->{accno}_tax_id"} = $ptr->{tax_id};
$form->{taxaccounts} .= "$ptr->{accno} ";
}
push @arrays, map { "ic_cvar_$_->{name}" } @{ $ic_cvar_configs };
push @arrays, map { "project_cvar_$_->{name}" } @{ $project_cvar_configs };
- my @tax_arrays = qw(taxbase tax taxdescription taxrate taxnumber);
+ my @tax_arrays = qw(taxbase tax taxdescription taxrate taxnumber tax_id);
my @payment_arrays = qw(payment paymentaccount paymentdate paymentsource paymentmemo);
my $sortorder = "";
if ($form->{groupitems}) {
$sortorder =
- qq|ORDER BY pg.partsgroup, a.oid|;
+ qq|ORDER BY pg.partsgroup, a.position|;
} else {
- $sortorder = qq|ORDER BY a.oid|;
+ $sortorder = qq|ORDER BY a.position|;
}
my $query =
push(@{ $form->{TEMPLATE_ARRAYS}->{taxrate} }, $form->format_amount($myconfig, $form->{"${item}_rate"} * 100));
push(@{ $form->{TEMPLATE_ARRAYS}->{taxrate_nofmt} }, $form->{"${item}_rate"} * 100);
push(@{ $form->{TEMPLATE_ARRAYS}->{taxnumber} }, $form->{"${item}_taxnumber"});
+ push(@{ $form->{TEMPLATE_ARRAYS}->{tax_id} }, $form->{"${item}_tax_id"});
+
+ # taxnumber (= accno) is used for grouping the amounts of the various taxes and as a prefix in form
+
+ # This code used to assume that at most one tax entry can point to the same
+ # chart_id, even though chart_id does not have a unique constraint!
+
+ # This chart_id was then looked up via its accno, which is the key that is
+ # used to group the different taxes by for a record
+
+ # As we now also store the tax_id we can use that to look up the tax
+ # instead, this is only done here to get the (translated) taxdescription.
+
+ if ( $form->{"${item}_tax_id"} ) {
+ my $tax_obj = SL::DB::Manager::Tax->find_by(id => $form->{"${item}_tax_id"}) or die "Can't find tax with id " . $form->{"${item}_tax_id"};
+ my $description = $tax_obj ? $tax_obj->translated_attribute('taxdescription', $form->{language_id}, 0) : '';
+ push(@{ $form->{TEMPLATE_ARRAYS}->{taxdescription} }, $description . q{ } . 100 * $form->{"${item}_rate"} . q{%});
+ }
- my $tax_obj = SL::DB::Manager::Tax->find_by(taxnumber => $form->{"${item}_taxnumber"});
- my $description = $tax_obj ? $tax_obj->translated_attribute('taxdescription', $form->{language_id}, 0) : '';
- push(@{ $form->{TEMPLATE_ARRAYS}->{taxdescription} }, $description . q{ } . 100 * $form->{"${item}_rate"} . q{%});
}
for my $i (1 .. $form->{paidaccounts}) {
my (@errors, @transfers);
- # do nothing, if transfer default is not requeseted at all
+ # do nothing, if transfer default is not requested at all
if (!$::instance_conf->get_transfer_default) {
$::lxdebug->leave_sub;
return \@errors;
foreach my $i (1 .. $form->{rowcount}) {
next if !$form->{"id_$i"};
- my ($err, $wh_id, $bin_id) = _determine_wh_and_bin($dbh, $::instance_conf,
- $form->{"id_$i"},
- $form->{"qty_$i"},
- $form->{"unit_$i"});
- if (!@{ $err } && $wh_id && $bin_id) {
- push @transfers, {
- 'parts_id' => $form->{"id_$i"},
- 'qty' => $form->{"qty_$i"},
- 'unit' => $form->{"unit_$i"},
- 'transfer_type' => 'shipped',
- 'src_warehouse_id' => $wh_id,
- 'src_bin_id' => $bin_id,
- 'project_id' => $form->{"project_id_$i"},
- 'invoice_id' => $form->{"invoice_id_$i"},
- 'comment' => $::locale->text("Default transfer invoice"),
- };
- }
+ my ($err, $qty, $wh_id, $bin_id, $chargenumber);
+
+ if ($::instance_conf->get_sales_serial_eq_charge) {
+ next unless $form->{"serialnumber_$i"};
+ my @serials = split(" ", $form->{"serialnumber_$i"});
+ if (scalar @serials != $form->{"qty_$i"}) {
+ push @errors, $::locale->text("Cannot transfer #1 qty with #2 serial number(s)", $form->{"qty_$i"}, scalar @serials);
+ last;
+ }
+ foreach my $serial (@serials) {
+ ($qty, $wh_id, $bin_id, $chargenumber) = WH->get_wh_and_bin_for_charge(chargenumber => $serial);
+ if (!$qty) {
+ push @errors, $::locale->text("Not enough in stock for the serial number #1", $serial);
+ last;
+ }
+ push @transfers, {
+ 'parts_id' => $form->{"id_$i"},
+ 'qty' => 1,
+ 'unit' => $form->{"unit_$i"},
+ 'transfer_type' => 'shipped',
+ 'src_warehouse_id' => $wh_id,
+ 'src_bin_id' => $bin_id,
+ 'chargenumber' => $chargenumber,
+ 'project_id' => $form->{"project_id_$i"},
+ 'invoice_id' => $form->{"invoice_id_$i"},
+ 'comment' => $::locale->text("Default transfer invoice with charge number"),
+ };
+ }
+ $err = []; # error handling uses @errors direct
+ } else {
+ ($err, $wh_id, $bin_id) = _determine_wh_and_bin($dbh, $::instance_conf,
+ $form->{"id_$i"},
+ $form->{"qty_$i"},
+ $form->{"unit_$i"});
+ if (!@{ $err } && $wh_id && $bin_id) {
+ push @transfers, {
+ 'parts_id' => $form->{"id_$i"},
+ 'qty' => $form->{"qty_$i"},
+ 'unit' => $form->{"unit_$i"},
+ 'transfer_type' => 'shipped',
+ 'src_warehouse_id' => $wh_id,
+ 'src_bin_id' => $bin_id,
+ 'project_id' => $form->{"project_id_$i"},
+ 'invoice_id' => $form->{"invoice_id_$i"},
+ 'comment' => $::locale->text("Default transfer invoice"),
+ };
+ }
+ }
push @errors, @{ $err };
- }
+ } # end form rowcount
if (!@errors) {
WH->transfer(@transfers);
# get tax rates and description
my $accno_id = ($form->{vc} eq "customer") ? $ref->{income_accno} : $ref->{expense_accno};
$query =
- qq|SELECT c.accno, t.taxdescription, t.rate, t.taxnumber FROM tax t
+ qq|SELECT c.accno, t.taxdescription, t.rate, t.id as tax_id, c.accno as taxnumber
+ FROM tax t
LEFT JOIN chart c ON (c.id = t.chart_id)
WHERE t.id IN
(SELECT tk.tax_id FROM taxkeys tk
if (!($form->{taxaccounts} =~ /\Q$ptr->{accno}\E/)) {
$form->{"$ptr->{accno}_rate"} = $ptr->{rate};
$form->{"$ptr->{accno}_description"} = $ptr->{taxdescription};
- $form->{"$ptr->{accno}_taxnumber"} = $ptr->{taxnumber};
+ $form->{"$ptr->{accno}_taxnumber"} = $ptr->{taxnumber}; # don't use this anymore
+ $form->{"$ptr->{accno}_tax_id"} = $ptr->{tax_id};
$form->{taxaccounts} .= "$ptr->{accno} ";
}
# get tax rates and description
my $accno_id = ($form->{vc} eq "customer") ? $ref->{income_accno} : $ref->{expense_accno};
$query =
- qq|SELECT c.accno, t.taxdescription, t.rate, t.taxnumber
+ qq|SELECT c.accno, t.taxdescription, t.id as tax_id, t.rate, c.accno as taxnumber
FROM tax t
LEFT JOIN chart c ON (c.id = t.chart_id)
WHERE t.id in
$form->{"$ptr->{accno}_rate"} = $ptr->{rate};
$form->{"$ptr->{accno}_description"} = $ptr->{taxdescription};
$form->{"$ptr->{accno}_taxnumber"} = $ptr->{taxnumber};
+ $form->{"$ptr->{accno}_tax_id"} = $ptr->{tax_id};
$form->{taxaccounts} .= "$ptr->{accno} ";
}
@required_modules = (
{ name => "parent", url => "http://search.cpan.org/~corion/", debian => 'libparent-perl' },
{ name => "Algorithm::CheckDigits", url => "http://search.cpan.org/~mamawe/", debian => 'libalgorithm-checkdigits-perl' },
- { name => "Archive::Zip", version => '1.16', url => "http://search.cpan.org/~phred/", debian => 'libarchive-zip-perl' },
+ { name => "Archive::Zip", version => '1.40', url => "http://search.cpan.org/~phred/", debian => 'libarchive-zip-perl' },
+ { name => "CAM::PDF", url => "https://metacpan.org/pod/CAM::PDF", debian => 'libcam-pdf-perl' },
{ name => "CGI", version => '3.43', url => "http://search.cpan.org/~leejo/", debian => 'libcgi-pm-perl' }, # 4.09 is not core anymore (perl 5.20)
{ name => "Clone", url => "http://search.cpan.org/~rdf/", debian => 'libclone-perl' },
{ name => "Config::Std", url => "http://search.cpan.org/~dconway/", debian => 'libconfig-std-perl' },
{ name => "DBI", version => '1.50', url => "http://search.cpan.org/~timb/", debian => 'libdbi-perl' },
{ name => "DBD::Pg", version => '1.49', url => "http://search.cpan.org/~dbdpg/", debian => 'libdbd-pg-perl' },
{ name => "Digest::SHA", url => "http://search.cpan.org/~mshelor/", debian => 'libdigest-sha-perl' },
- { name => "Exception::Class", url => "https://metacpan.org/pod/Exception::Class", debian => 'libexception-class-perl' },
+ { name => "Exception::Class", version => '1.44', url => "https://metacpan.org/pod/Exception::Class", debian => 'libexception-class-perl' },
{ name => "Email::Address", version => '1.888', url => "http://search.cpan.org/~rjbs/", debian => 'libemail-address-perl' },
{ name => "Email::MIME", url => "http://search.cpan.org/~rjbs/", debian => 'libemail-mime-perl' },
{ name => "FCGI", version => '0.72', url => "http://search.cpan.org/~mstrout/", debian => 'libfcgi-perl' },
{ name => "Text::Iconv", version => '1.2', url => "http://search.cpan.org/~mpiotr/", debian => 'libtext-iconv-perl' },
{ name => "Text::Unidecode", url => "http://search.cpan.org/~sburke/", debian => 'libtext-unidecode-perl' },
{ name => "URI", version => '1.35', url => "http://search.cpan.org/~gaas/", debian => 'liburi-perl' },
+ { name => "XML::LibXML", url => "https://metacpan.org/pod/XML::LibXML", debian => 'libxml-libxml-perl' },
{ name => "XML::Writer", version => '0.602', url => "http://search.cpan.org/~josephw/", debian => 'libxml-writer-perl' },
{ name => "YAML", version => '0.62', url => "http://search.cpan.org/~ingy/", debian => 'libyaml-perl' },
);
return @{ $self->currencies };
}
+sub get_address {
+ # Compatibility function: back in the day there was only a single
+ # address field.
+ my ($self) = @_;
+
+ my $zipcode_city = join ' ', grep { $_ } ($self->get_address_zipcode, $self->get_address_city);
+
+ return join "\n", grep { $_ } ($self->get_address_street1, $self->get_address_street2, $zipcode_city, $self->get_address_country);
+}
+
sub AUTOLOAD {
our $AUTOLOAD;
=item C<get_ap_show_mark_as_paid>
-Returns the default behavior for showing the mark as paid button for the
+Returns the default behavior for showing the "mark as paid" button for the
corresponding record type (true or false).
=item C<get_sales_order_show_delete>
=item C<get_bin_id_ignore_onhand>
-Returns the default bin_id for transfers without checking the.
+Returns the default bin_id for transfers without checking the
current stock quantity
-
-
=item C<get_transfer_default>
=item C<get_transfer_default_use_master_default_bin>
=item C<get_feature_experimental_order>
-Returns the configuration for experimental feature "order"
+Returns the configuration for the experimental feature "order"
=item C<get_parts_show_image>
=item C<get_parts_listing_image>
-Returns the configuartion for showing the picture in the results when you search for parts
+Returns the configuration for showing the picture in the results when you search for parts
=back
my ($src, $dumped) = @_;
return undef if !defined($src);
+ return $src if !ref($src);
$dumped ||= {};
my $addr = refaddr($src);
my $file_id = 0;
my $email_journal = $::instance_conf->get_email_journal;
- $::lxdebug->message(LXDebug->DEBUG2(), "mail5 att=" . $attachment . " email_journal=" . $email_journal . " id=" . $attachment->{id});
-
if (ref($attachment) eq "HASH") {
$attributes{filename} = $attachment->{name};
$file_id = $attachment->{id} || '0';
$attachment_content ||= ' ';
$attributes{charset} = $self->{charset} if $self->{charset} && ($attributes{content_type} =~ m{^text/});
- $::lxdebug->message(LXDebug->DEBUG2(), "mail6 mtype=" . $attributes{content_type} . " filename=" . $attributes{filename});
-
my $ent;
if ( $attributes{content_type} eq 'message/rfc822' ) {
$ent = Email::MIME->new($attachment_content);
# Set defaults & headers
$self->{charset} = 'UTF-8';
$self->{content_type} ||= "text/plain";
- $self->{headers} = [
+ $self->{headers} ||= [];
+ push @{ $self->{headers} }, (
Subject => $self->{subject},
'Message-ID' => '<' . $self->_create_message_id . '>',
'X-Mailer' => "kivitendo " . SL::Version->get_version,
- ];
+ );
$self->{mail_attachments} = [];
$self->{content_by_name} = $::instance_conf->get_email_journal == 1 && $::instance_conf->get_doc_files;
my $email = $self->_create_message;
- #$::lxdebug->message(0, "message: " . $email->as_string);
- # return "boom";
-
- $::lxdebug->message(LXDebug->DEBUG2(), "mail1 from=".$self->{from}." to=".$self->{to});
my $from_obj = (Email::Address->parse($self->{from}))[0];
$self->{driver}->start_mail(from => $from_obj->address, to => [ $self->_all_recipients ]);
FROM record_links rl1
LEFT JOIN record_links rl2 ON (rl1.to_table = rl2.from_table AND rl1.to_id = rl2.from_id)
WHERE rl1.from_table = 'oe' AND rl2.to_table = 'ar'
+ UNION
+ SELECT rl1.from_id, rl3.to_id
+ FROM record_links rl1
+ JOIN record_links rl2 ON (rl1.to_table = rl2.from_table AND rl1.to_id = rl2.from_id)
+ JOIN record_links rl3 ON (rl2.to_table = rl3.from_table AND rl2.to_id = rl3.from_id)
+ WHERE rl1.from_table = 'oe' AND rl2.to_table = 'ar' AND rl3.to_table = 'ar'
) rl
LEFT JOIN ar ON ar.id = rl.to_id
qq| o.closed, o.delivered, o.quonumber, o.cusordnumber, o.shippingpoint, o.shipvia, | .
qq| o.transaction_description, | .
qq| o.marge_total, o.marge_percent, | .
+ qq| o.exchangerate, | .
qq| o.itime::DATE AS insertdate, | .
- qq| ex.$rate AS exchangerate, | .
+ qq| department.description as department, | .
+ qq| ex.$rate AS daily_exchangerate, | .
qq| pt.description AS payment_terms, | .
qq| pr.projectnumber AS globalprojectnumber, | .
qq| e.name AS employee, s.name AS salesman, | .
qq|LEFT JOIN project pr ON (o.globalproject_id = pr.id) | .
qq|LEFT JOIN payment_terms pt ON (pt.id = o.payment_id)| .
qq|LEFT JOIN tax_zones tz ON (o.taxzone_id = tz.id) | .
+ qq|LEFT JOIN department ON (o.department_id = department.id) | .
qq|$periodic_invoices_joins | .
qq|WHERE (o.quotation = ?) |;
push(@values, $quotation);
push(@values, (like($form->{"cp_name"}))x2);
}
- if (!$main::auth->assert('sales_all_edit', 1)) {
+ if ( !(($vc eq 'customer' && $main::auth->assert('sales_all_edit', 1)) || ($vc eq 'vendor' && $main::auth->assert('purchase_all_edit', 1))) ) {
$query .= " AND o.employee_id = (select id from employee where login= ?)";
push @values, $::myconfig{login};
}
"insertdate" => "o.itime",
"taxzone" => "tz.description",
"payment_terms" => "pt.description",
+ "department" => "department.description",
);
if ($form->{sort} && grep($form->{sort}, keys(%allowed_sort_columns))) {
$sortorder = $allowed_sort_columns{$form->{sort}} . " ${sortdir}" . ", o.itime ${sortdir}";
while (my $ref = $sth->fetchrow_hashref("NAME_lc")) {
$ref->{billed_amount} = $billed_amount{$ref->{id}};
$ref->{billed_netamount} = $billed_netamount{$ref->{id}};
- $ref->{remaining_amount} = $ref->{amount} - $ref->{billed_amount};
- $ref->{remaining_netamount} = $ref->{netamount} - $ref->{billed_netamount};
- $ref->{exchangerate} = 1 unless $ref->{exchangerate};
+ if ($ref->{billed_amount} < 0) { # case: credit note(s) higher than invoices
+ $ref->{remaining_amount} = $ref->{amount} + $ref->{billed_amount};
+ $ref->{remaining_netamount} = $ref->{netamount} + $ref->{billed_netamount};
+ } else {
+ $ref->{remaining_amount} = $ref->{amount} - $ref->{billed_amount};
+ $ref->{remaining_netamount} = $ref->{netamount} - $ref->{billed_netamount};
+ }
+ $ref->{exchangerate} ||= $ref->{daily_exchangerate};
+ $ref->{exchangerate} ||= 1;
push @{ $form->{OE} }, $ref if $ref->{id} != $id{ $ref->{id} };
$id{ $ref->{id} } = $ref->{id};
}
require SL::DB::Customer;
my $customer = SL::DB::Manager::Customer->find_by(id => $form->{customer_id});
die "Can't find customer" unless $customer;
+ die $main::locale->text("Error while creating project with project number of new order number, project number #1 already exists!", $form->{ordnumber})
+ if SL::DB::Manager::Project->find_by(projectnumber => $form->{ordnumber});
+
my $new_project = SL::DB::Project->new(
projectnumber => $form->{ordnumber},
description => $customer->name,
);
$new_project->save;
$form->{"globalproject_id"} = $new_project->id;
- };
+ }
CVar->get_non_editable_ic_cvars(form => $form,
dbh => $dbh,
my $transdate = $form->{transdate} ? $dbh->quote($form->{transdate}) : "current_date";
$form->{taxzone_id} = 0 unless ($form->{taxzone_id});
+ unshift @values, ($form->{taxzone_id}) x 2;
# retrieve individual items
# this query looks up all information about the items
JOIN parts p ON (o.parts_id = p.id)
JOIN oe ON (o.trans_id = oe.id)
LEFT JOIN chart c1 ON ((SELECT inventory_accno_id FROM buchungsgruppen WHERE id=p.buchungsgruppen_id) = c1.id)
- LEFT JOIN chart c2 ON ((SELECT tc.income_accno_id FROM taxzone_charts tc WHERE tc.taxzone_id = '$form->{taxzone_id}' and tc.buchungsgruppen_id = p.buchungsgruppen_id) = c2.id)
- LEFT JOIN chart c3 ON ((SELECT tc.expense_accno_id FROM taxzone_charts tc WHERE tc.taxzone_id = '$form->{taxzone_id}' and tc.buchungsgruppen_id = p.buchungsgruppen_id) = c3.id)
+ LEFT JOIN chart c2 ON ((SELECT tc.income_accno_id FROM taxzone_charts tc WHERE tc.taxzone_id = ? and tc.buchungsgruppen_id = p.buchungsgruppen_id) = c2.id)
+ LEFT JOIN chart c3 ON ((SELECT tc.expense_accno_id FROM taxzone_charts tc WHERE tc.taxzone_id = ? and tc.buchungsgruppen_id = p.buchungsgruppen_id) = c3.id)
LEFT JOIN project pr ON (o.project_id = pr.id)
LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id) | .
($form->{id}
# get tax rates and description
my $accno_id = ($form->{vc} eq "customer") ? $ref->{income_accno} : $ref->{expense_accno};
$query =
- qq|SELECT c.accno, t.taxdescription, t.rate, t.taxnumber | .
- qq|FROM tax t LEFT JOIN chart c on (c.id = t.chart_id) | .
+ qq|SELECT c.accno, t.taxdescription, t.rate, t.id as tax_id, c.accno as taxnumber | .
+ qq|FROM tax t | .
+ qq|LEFT JOIN chart c on (c.id = t.chart_id) | .
qq|WHERE t.id IN (SELECT tk.tax_id FROM taxkeys tk | .
qq| WHERE tk.chart_id = (SELECT id FROM chart WHERE accno = ?) | .
qq| AND startdate <= $transdate ORDER BY startdate DESC LIMIT 1) | .
$form->{"$ptr->{accno}_rate"} = $ptr->{rate};
$form->{"$ptr->{accno}_description"} = $ptr->{taxdescription};
$form->{"$ptr->{accno}_taxnumber"} = $ptr->{taxnumber};
+ $form->{"$ptr->{accno}_tax_id"} = $ptr->{tax_id};
$form->{taxaccounts} .= "$ptr->{accno} ";
}
# get parts and push them onto the stack
my $sortorder = "";
if ($form->{groupitems}) {
- $sortorder = qq|ORDER BY pg.partsgroup, a.oid|;
+ $sortorder = qq|ORDER BY pg.partsgroup, a.position|;
} else {
- $sortorder = qq|ORDER BY a.oid|;
+ $sortorder = qq|ORDER BY a.position|;
}
$query = qq|SELECT p.partnumber, p.description, p.unit, a.qty, | .
push(@{ $form->{TEMPLATE_ARRAYS}->{taxrate} }, $form->format_amount($myconfig, $form->{"${item}_rate"} * 100));
push(@{ $form->{TEMPLATE_ARRAYS}->{taxrate_nofmt} }, $form->{"${item}_rate"} * 100);
push(@{ $form->{TEMPLATE_ARRAYS}->{taxnumber} }, $form->{"${item}_taxnumber"});
+ push(@{ $form->{TEMPLATE_ARRAYS}->{tax_id} }, $form->{"${item}_tax_id"});
- my $tax_obj = SL::DB::Manager::Tax->find_by(taxnumber => $form->{"${item}_taxnumber"});
- my $description = $tax_obj ? $tax_obj->translated_attribute('taxdescription', $form->{language_id}, 0) : '';
- push(@{ $form->{TEMPLATE_ARRAYS}->{taxdescription} }, $description . q{ } . 100 * $form->{"${item}_rate"} . q{%});
+ if ( $form->{"${item}_tax_id"} ) {
+ my $tax_obj = SL::DB::Manager::Tax->find_by(id => $form->{"${item}_tax_id"}) or die "Can't find tax with id " . $form->{"${item}_tax_id"};
+ my $description = $tax_obj ? $tax_obj->translated_attribute('taxdescription', $form->{language_id}, 0) : '';
+ push(@{ $form->{TEMPLATE_ARRAYS}->{taxdescription} }, $description . q{ } . 100 * $form->{"${item}_rate"} . q{%});
+ }
}
$form->{nodiscount_subtotal} = $form->format_amount($myconfig, $form->{nodiscount_total}, 2);
If C<%params> contains C<convertible_unit> only parts with a unit
that's convertible to unit will be used for autocompletion.
+If C<%params> contains C<with_makemodel> or C<with_customer_partnumber> even
+parts will be used for autocompletion which partnumber is a vendor partnumber
+(makemodel) or a customer partnumber.
+
Obsolete parts will by default not be displayed for selection. However they are
accepted as default values and can persist during updates. As with other
selectors though, they are not selectable once overridden.
html_tag input_tag hidden_tag javascript man_days_tag name_to_id select_tag
checkbox_tag button_tag submit_tag ajax_submit_tag input_number_tag
stringify_attributes restricted_html textarea_tag link_tag date_tag
-);
+ div_tag);
our %EXPORT_TAGS = (ALL => \@EXPORT_OK);
use Carp;
);
}
+sub div_tag {
+ my ($content, %params) = @_;
+ return html_tag('div', $content, %params);
+}
+
1;
__END__
while (my ($key, $value) = each %options) {
if ($key eq 'pdf_export') {
- map { $self->{options}->{pdf_export}->{$_} = $value->{$_} } keys %{ $value };
+ $self->{options}->{pdf_export}->{$_} = $value->{$_} for keys %{ $value };
+ } elsif ($key eq 'csv_export') {
+ $self->{options}->{csv_export}->{$_} = $value->{$_} for keys %{ $value };
} else {
$self->{options}->{$key} = $value;
}
use Exporter qw(import);
use SL::Common;
+use SL::JSON;
use SL::MoreCommon qw(uri_encode uri_decode);
use SL::Layout::None;
use SL::Presenter;
-our @EXPORT_OK = qw(flatten unflatten read_cgi_input);
+our @EXPORT_OK = qw(flatten unflatten);
use Rose::Object::MakeMethods::Generic
(
- scalar => [ qw(applying_database_upgrades) ],
+ scalar => [ qw(applying_database_upgrades post_data) ],
'scalar --get_set_init' => [ qw(cgi layout presenter is_ajax type) ],
);
$::lxdebug->leave_sub(2);
}
+sub _parse_json_formdata {
+ my ($content) = @_;
+
+ return $content ? SL::JSON::decode_json($content) : undef;
+}
+
sub _recode_recursively {
$::lxdebug->enter_sub;
my ($iconv, $from, $to) = @_;
sub read_cgi_input {
$::lxdebug->enter_sub;
- my ($target) = @_;
+ my ($self, $target) = @_;
# yes i know, copying all those values around isn't terribly efficient, but
# the old version of dumping everything into form and then launching a
# this way the data can at least be recoded on the fly as soon as we get to
# know the source encoding and only in the cases where encoding may be hidden
# among the payload we take the hit of copying the request around
- my $temp_target = { };
+ $self->post_data(undef);
+ my $data_to_decode = { };
+ my $iconv = SL::Iconv->new('UTF-8', 'UTF-8');
- # since both of these can potentially bring their encoding in INPUT_ENCODING
- # they get dumped into temp_target
- _input_to_hash($temp_target, $ENV{QUERY_STRING}, 1) if $ENV{QUERY_STRING};
- _input_to_hash($temp_target, $ARGV[0], 1) if @ARGV && $ARGV[0];
+ _input_to_hash($data_to_decode, $ENV{QUERY_STRING}, 1) if $ENV{QUERY_STRING};
+ _input_to_hash($data_to_decode, $ARGV[0], 1) if @ARGV && $ARGV[0];
if ($ENV{CONTENT_LENGTH}) {
my $content;
read STDIN, $content, $ENV{CONTENT_LENGTH};
+
if ($ENV{'CONTENT_TYPE'} && $ENV{'CONTENT_TYPE'} =~ /multipart\/form-data/) {
+ $self->post_data({});
+ my $post_data_to_decode = { };
+
# multipart formdata can bring it's own encoding, so give it both
# and let it decide on it's own
- _parse_multipart_formdata($target, $temp_target, $content, 1);
+ _parse_multipart_formdata($self->post_data, $post_data_to_decode, $content, 1);
+ _recode_recursively($iconv, $post_data_to_decode, $self->post_data) if keys %$post_data_to_decode;
+
+ $target->{$_} = $self->post_data->{$_} for keys %{ $self->post_data };
+
+ } elsif (($ENV{CONTENT_TYPE} // '') =~ m{^application/json}i) {
+ $self->post_data(_parse_json_formdata($content));
+
} else {
# normal encoding must be recoded
- _input_to_hash($temp_target, $content, 1);
+ _input_to_hash($data_to_decode, $content, 1);
}
}
- my $encoding = delete $temp_target->{INPUT_ENCODING} || 'UTF-8';
-
- _recode_recursively(SL::Iconv->new($encoding, 'UTF-8'), $temp_target => $target) if keys %$temp_target;
+ _recode_recursively($iconv, $data_to_decode, $target) if keys %$data_to_decode;
if ($target->{RESTORE_FORM_FROM_SESSION_ID}) {
my %temp_form;
information about the request, such as whether or not it was done via AJAX,
or the requested content type.
- use SL::Request qw(read_cgi_input);
+ use SL::Request;
# read cgi input depending on request type, unflatten and recode
- read_cgi_input($target_hash_ref);
+ $::request->read_cgi_input($target_hash_ref);
# $hashref and $new_hashref should be identical
my $new_arrayref = flatten($hashref);
Returns the cached item.
+=item C<post_data>
+
+If the client sends data in the request body with the content type of
+either C<application/json> or C<multipart/form-data>, the content will
+be stored in the global request object, too. It can be retrieved via
+the C<post_data> function.
+
+For content type C<multipart/form-data> the same data is additionally
+stored in the global C<$::form> instance, potentially overwriting
+parameters given in the URL. This is done primarily for compatibility
+purposes with existing code that expects all parameters to be present
+in C<$::form>.
+
+For content type C<application/json> the data is only available in
+C<$::request>. The reason is that the top-level data in a JSON
+documents doesn't have to be an object which could be mapped to the
+hash C<$::form>. Instead, the top-level data can also be an
+array. Additionally keeping the namespaces of URL and POST parameters
+separate is cleaner and allows for fewer accidental conflicts.
+
=back
=head1 SPECIAL FUNCTIONS
my $mandate = $params{vc} eq 'customer' ? " AND COALESCE(vc.mandator_id, '') <> '' AND vc.mandate_date_of_signature IS NOT NULL " : '';
- # in query: for customers, use payment terms from invoice, for vendors use
- # payment terms from vendor settings
- # currently there is no option in vendor invoices for setting payment terms,
- # so the vendor settings are always used
-
- my $payment_term_type = $params{vc} eq 'customer' ? "${arap}" : 'vc';
-
# open_amount is not the current open amount according to bookkeeping, but
# the open amount minus the SEPA transfer amounts that haven't been closed yet
my $query =
GROUP BY sei.${arap}_id)
AS open_transfers ON (${arap}.id = open_transfers.${arap}_id)
- LEFT JOIN payment_terms pt ON (${payment_term_type}.payment_id = pt.id)
+ LEFT JOIN payment_terms pt ON (${arap}.payment_id = pt.id)
WHERE ${arap}.amount > (COALESCE(open_transfers.amount, 0) + ${arap}.paid)
Otherwise the function just dies with a short notice of the id.
=cut
-
-
-
-
use File::Slurp;
use File::Spec::Functions qw(:ALL);
use File::Temp;
+use Sys::Hostname ();
use SL::System::Process;
use constant PID_BASE => "users/pid";
+my $node_id;
+
sub status {
my ($self) = @_;
return kill('ALRM', $pid) ? 1 : undef;
}
+sub node_id {
+ return $node_id if $node_id;
+
+ $node_id = ($::lx_office_conf{task_server} // {})->{node_id} || Sys::Hostname::hostname();
+
+ return $node_id;
+}
+
#
# private methods
#
if (!$self->{handles}->{get_tax_info}) {
$self->{queries}->{get_tax_info} = qq|
- SELECT t.rate AS taxrate, t.taxnumber, t.taxdescription, t.chart_id AS taxchart_id,
+ SELECT t.rate AS taxrate, c.accno as taxnumber, t.taxdescription, t.chart_id AS taxchart_id,
c.accno AS taxaccno, c.description AS taxaccount
FROM taxkeys tk
LEFT JOIN tax t ON (tk.tax_id = t.id)
use SL::Template::OpenDocument;
use SL::Template::PlainText;
use SL::Template::ShellCommand;
-use SL::Template::XML;
sub create {
my %params = @_;
my @alldir = sort grep {
-d ($::lx_office_conf{paths}->{templates} . "/$_")
&& !/^\.\.?$/
- && !m/\.(?:html|tex|sty|odt|xml|txb)$/
+ && !m/\.(?:html|tex|sty|odt)$/
&& !m/^(?:webpages$|print$|mail$|\.)/
} keys %dir_h;
use File::Temp;
use HTML::Entities ();
use List::MoreUtils qw(any);
+use Scalar::Util qw(blessed);
use Unicode::Normalize qw();
use SL::DB::Default;
+use SL::System::Process;
my %text_markup_replace = (
b => 'textbf',
}
}
+sub _embed_file_directive {
+ my ($self, $file) = @_;
+
+ # { source => $xmlfile,
+ # name => 'ZUGFeRD-invoice.xml',
+ # description => $::locale->text('ZUGFeRD invoice'), }
+
+ my $file_name = blessed($file->{source}) && $file->{source}->can('filename') ? $file->{source}->filename : "" . $file->{source}->filename;
+ my $embed_name = $file->{name} // $file_name;
+ $embed_name =~ s{.*/}{};
+
+ my $embed_name_ascii = $::locale->quote_special_chars('filenames', $embed_name);
+ $embed_name_ascii =~ s{[^a-z0-9!@#$%^&*(){}[\],.+'"=_-]+}{}gi;
+
+ my @options;
+
+ my $add_opt = sub {
+ my ($name, $value) = @_;
+ return if ($value // '') eq '';
+ push @options, sprintf('%s={%s}', $name, $value); # TODO: escaping
+ };
+
+ $add_opt->('filespec', $embed_name_ascii);
+ $add_opt->('ucfilespec', $embed_name);
+ $add_opt->('desc', $file->{description});
+ $add_opt->('afrelationship', $file->{relationship});
+ $add_opt->('mimetype', $file->{mime_type});
+
+ return sprintf('\embedfile[%s]{%s}', join(',', @options), $file_name);
+}
+
sub _force_mandatory_packages {
- my $self = shift;
- my $lines = shift;
+ my ($self, @lines) = @_;
+ my @new_lines;
- my (%used_packages, $document_start_line, $last_usepackage_line);
+ my %used_packages;
+ my @required_packages = qw(textcomp ulem);
+ push @required_packages, 'embedfile' if $self->{pdf_a};
- foreach my $i (0 .. scalar @{ $lines } - 1) {
- if ($lines->[$i] =~ m/\\usepackage[^\{]*{(.*?)}/) {
+ foreach my $line (@lines) {
+ if ($line =~ m/\\usepackage[^\{]*{(.*?)}/) {
$used_packages{$1} = 1;
- $last_usepackage_line = $i;
- } elsif ($lines->[$i] =~ m/\\begin\{document\}/) {
- $document_start_line = $i;
- last;
+ } elsif ($line =~ m/\\begin\{document\}/) {
+ if ($self->{pdf_a} && $self->{pdf_a}->{xmp}) {
+ my $version = $self->{pdf_a}->{version} // '3a';
+ my $xmp_file_name = $self->{userspath} . "/pdfa.xmp";
+ my $out = IO::File->new($xmp_file_name, ">:encoding(utf-8)") || croak "Error creating ${xmp_file_name}: $!";
+ $out->print(Encode::encode('utf-8', $self->{pdf_a}->{xmp}));
+ $out->close;
+
+ push @new_lines, (
+ "\\usepackage[a-${version},mathxmp]{pdfx}[2018/12/22]\n",
+ "\\usepackage[genericmode]{tagpdf}\n",
+ "\\tagpdfsetup{activate-all}\n",
+ "\\hypersetup{pdfstartview=}\n",
+ );
+ }
- }
- }
+ push @new_lines, map { "\\usepackage{$_}\n" } grep { !$used_packages{$_} } @required_packages;
+ push @new_lines, $line;
+ push @new_lines, map { $self->_embed_file_directive($_) } @{ $self->{pdf_attachments} // [] };
- my $insertion_point = defined($document_start_line) ? $document_start_line
- : defined($last_usepackage_line) ? $last_usepackage_line
- : scalar @{ $lines } - 1;
+ next;
+ }
- foreach my $package (qw(textcomp ulem)) {
- next if $used_packages{$package};
- splice @{ $lines }, $insertion_point, 0, "\\usepackage{${package}}\n";
- $insertion_point++;
+ push @new_lines, $line;
}
+
+ return @new_lines;
}
sub parse {
close(IN);
$self->_parse_config_lines(\@lines);
- $self->_force_mandatory_packages(\@lines) if (ref $self eq 'SL::Template::LaTeX');
+ @lines = $self->_force_mandatory_packages(@lines) if (ref $self eq 'SL::Template::LaTeX');
my $contents = join("", @lines);
}
}
+sub _texinputs_path {
+ my ($self, $templates_path) = @_;
+
+ my $exe_dir = SL::System::Process::exe_dir();
+ $templates_path = $exe_dir . '/' . $templates_path unless $templates_path =~ m{^/};
+
+ return join(':', grep({ $_ } ('.', $exe_dir . '/texmf', $exe_dir . '/users', $templates_path, $ENV{TEXINPUTS})), '');
+}
+
sub convert_to_postscript {
my ($self) = @_;
my ($form, $userspath) = ($self->{"form"}, $self->{"userspath"});
# Convert the tex file to postscript
- local $ENV{TEXINPUTS} = ".:" . $form->{cwd} . "/" . $form->{templates} . ":" . $ENV{TEXINPUTS};
+ local $ENV{TEXINPUTS} = $self->_texinputs_path($form->{templates});
if (!chdir("$userspath")) {
$self->{"error"} = "chdir : $!";
my ($form, $userspath) = ($self->{"form"}, $self->{"userspath"});
# Convert the tex file to PDF
- local $ENV{TEXINPUTS} = ".:" . $form->{cwd} . "/" . $form->{templates} . ":" . $ENV{TEXINPUTS};
+ local $ENV{TEXINPUTS} = $self->_texinputs_path($form->{templates});
if (!chdir("$userspath")) {
$self->{"error"} = "chdir : $!";
sub parse_and_create_pdf {
my ($class, $template_file_name, %params) = @_;
+ my $userspath = delete($params{userspath}) || $::lx_office_conf{paths}->{userspath};
my $keep_temp = $::lx_office_conf{debug} && $::lx_office_conf{debug}->{keep_temp_files};
my ($tex_fh, $tex_file_name) = File::Temp::tempfile(
'kivitendo-printXXXXXX',
SUFFIX => '.tex',
- DIR => $::lx_office_conf{paths}->{userspath},
+ DIR => $userspath,
UNLINK => $keep_temp ? 0 : 1,,
);
my $local_form = Form->new('');
$local_form->{cwd} = $old_wd;
$local_form->{IN} = $template_file_name;
- $local_form->{tmpdir} = $::lx_office_conf{paths}->{userspath};
+ $local_form->{tmpdir} = $userspath;
$local_form->{tmpfile} = $tex_file_name;
$local_form->{templates} = SL::DB::Default->get->templates;
my $error;
eval {
- my $template = SL::Template::LaTeX->new(file_name => $template_file_name, form => $local_form);
+ my $template = SL::Template::LaTeX->new(file_name => $template_file_name, form => $local_form, userspath => $userspath);
my $result = $template->parse($tex_fh) && $template->convert_to_pdf;
die $template->{error} unless $result;
sub input_number_tag { return _call_presenter('input_number_tag', @_); }
sub textarea_tag { return _call_presenter('textarea_tag', @_); }
sub date_tag { return _call_presenter('date_tag', @_); }
+sub div_tag { return _call_presenter('div_tag', @_); }
sub _set_id_attribute {
my ($attributes, $name, $unique) = @_;
return $code;
}
-sub div_tag {
- my ($self, $content, @slurp) = @_;
- return $self->html_tag('div', $content, @slurp);
-}
-
sub ul_tag {
my ($self, $content, @slurp) = @_;
return $self->html_tag('ul', $content, @slurp);
$filter .= ".map(function(idx, str) { return str.replace('$params{with}_', ''); })";
my $params_js = $params{params} ? qq| + ($params{params})| : '';
+ my $ajax_return = '';
+ if ($params{ajax_return}) {
+ $ajax_return = 'kivi.eval_json_result';
+ }
$stop_event = <<JAVASCRIPT;
- \$.post('$params{url}'${params_js}, { '${as}[]': \$(\$('${selector}').sortable('toArray'))${filter}.toArray() });
+ \$.post('$params{url}'${params_js}, { '${as}[]': \$(\$('${selector}').sortable('toArray'))${filter}.toArray() }, $ajax_return);
JAVASCRIPT
}
=item C<url>
The URL to POST an AJAX request to after a dragged element has been
-dropped. The AJAX request's return value is ignored. If given then
+dropped. The AJAX request's return value is ignored by default. If given then
C<$params{with}> must be given as well.
+=item C<ajax_return>
+
+If trueish then the AJAX request's return is accepted.
+
=item C<with>
A string that is interpreted as the prefix of the children's ID. Upon
+++ /dev/null
-package SL::Template::XML;
-
-use parent qw(SL::Template::HTML);
-
-use strict;
-
-sub new {
- #evtl auskommentieren
- my $type = shift;
-
- return $type->SUPER::new(@_);
-}
-
-sub format_string {
- my ($self, $variable) = @_;
- my $form = $self->{"form"};
-
- $variable = $main::locale->quote_special_chars('Template/XML', $variable);
-
- # Allow no markup to be converted into the output format
- my @markup_replace = ('b', 'i', 's', 'u', 'sub', 'sup');
-
- foreach my $key (@markup_replace) {
- $variable =~ s/\<(\/?)${key}\>//g;
- }
-
- return $variable;
-}
-
-sub get_mime_type() {
- my ($self) = @_;
-
- return "text";
-
-}
-
-sub uses_temp_file {
- # tempfile needet for XML Output
- return 1;
-}
-
-1;
package USTVA;
+use Carp;
+use Data::Dumper;
use List::Util qw(first);
use SL::DB;
use SL::DBUtils;
use SL::DB::Default;
use SL::DB::Finanzamt;
+use SL::Locale::String qw(t8);
use utf8;
use strict;
$main::lxdebug->leave_sub();
}
-# 20.10.2009 sschoeling: this sub seems to be orphaned.
-sub stichtag {
- $main::lxdebug->enter_sub();
-
- # noch nicht fertig
- # soll mal eine Erinnerungsfunktion für USTVA Abgaben werden, die automatisch
- # den Termin der nächsten USTVA anzeigt.
- #
- #
- my ($today, $FA_dauerfrist, $FA_voranmeld) = @_;
-
- #$today zerlegen:
-
- #$today =today * 1;
- $today =~ /(\d\d\d\d)(\d\d)(\d\d)/;
- my $year = $1;
- my $month = $2;
- my $day = $3;
- my $yy = $year;
- my $mm = $month;
- my $yymmdd = "$year$month$day" * 1;
- my $mmdd = "$month$day" * 1;
- my $stichtag = '';
-
- #$tage_bis = '1234';
- #$ical = '...vcal format';
-
- #if ($FA_voranmeld eq 'month'){
-
- my %liste = (
- "0110" => 'December',
- "0210" => 'January',
- "0310" => 'February',
- "0410" => 'March',
- "0510" => 'April',
- "0610" => 'May',
- "0710" => 'June',
- "0810" => 'July',
- "0910" => 'August',
- "1010" => 'September',
- "1110" => 'October',
- "1210" => 'November',
- );
-
- #$mm += $dauerfrist
- #$month *= 1;
- $month += 1 if ($day > 10);
- $month = sprintf("%02d", $month);
- $stichtag = $year . $month . "10";
- my $ust_va = $month . "10";
-
- foreach my $date (%liste) {
- $ust_va = $liste{$date} if ($date eq $stichtag);
- }
-
- #} elsif ($FA_voranmeld eq 'quarter'){
- #1;
-
- #}
-
- #@stichtag = ('10.04.2004', '10.05.2004');
-
- #@liste = ['0110', '0210', '0310', '0410', '0510', '0610', '0710', '0810', '0910',
- # '1010', '1110', '1210', ];
- #
- #foreach $key (@liste){
- # #if ($ddmm < ('0110' * 1));
- # if ($ddmm ){}
- # $stichtag = $liste[$key - 1] if ($ddmm > $key);
- #
- #}
- #
- #$stichtag =~ /([\d]\d)(\d\d)$/
- #$stichtag = "$1.$2.$yy"
- #$stichtag=$1;
- our $description; # most probably not existent.
- our $tage_bis; # most probably not existent.
- our $ical; # most probably not existent.
-
- $main::lxdebug->leave_sub();
- return ($stichtag, $description, $tage_bis, $ical);
-}
-
sub query_finanzamt {
$main::lxdebug->enter_sub();
sub process_query {
$main::lxdebug->enter_sub();
- # Copyright D. Simander -> SL::Form under Gnu GPL.
my ($form, $dbh, $filename) = @_;
- # return unless (-f $filename);
-
open my $FH, "<", "$filename" or $form->error("$filename : $!\n");
my $query = "";
my $sth;
$form->{coa} = $::instance_conf->get_coa;
+ unless ($form->{coa} eq 'Germany-DATEV-SKR03EU' or $form->{coa} eq 'Germany-DATEV-SKR04EU') {
+ croak t8("Advance turnover tax return only valid for SKR03 or SKR04");
+ }
my @category_cent = USTVA->report_variables({
myconfig => $myconfig,
form => $form,
attribute => 'position',
dec_places => '2',
});
-
+ push @category_cent, ("pos_ustva_811b_kivi", "pos_ustva_861b_kivi");
if ( $form->{coa} eq 'Germany-DATEV-SKR03EU' or $form->{coa} eq 'Germany-DATEV-SKR04EU') {
push @category_cent, qw(Z43 Z45 Z53 Z54 Z62 Z65 Z67);
}
attribute => 'position',
dec_places => '0',
});
-
+ push @category_euro, ("pos_ustva_81b_kivi", "pos_ustva_86b_kivi");
@{$form->{category_cent}} = @category_cent;
@{$form->{category_euro}} = @category_euro;
$form->{decimalplaces} *= 1;
# Germany
- if ( $form->{coa} eq 'Germany-DATEV-SKR03EU' or $form->{coa} eq 'Germany-DATEV-SKR04EU'){
+ if ( $form->{coa} eq 'Germany-DATEV-SKR03EU' or $form->{coa} eq 'Germany-DATEV-SKR04EU') {
# 16%/19% Umstellung
# Umordnen der Kennziffern
# Fixme: Wird auch noch für Oesterreich gebraucht,
# weil kein eigenes Ausgabeformular
- # sotte aber aus der allgeméinen Steuerberechnung verschwinden
+ # sollte aber aus der allgemeinen Steuerberechnung verschwinden
#
# Berechnung der USTVA Formularfelder laut Bogen 207
#
1=1
$ARwhere
AND acc.trans_id = ac.trans_id
- )
- /
+ ) /
(
SELECT amount FROM ar WHERE id = ac.trans_id
)
) AS amount,
- tk.pos_ustva
+ tk.pos_ustva, t.rate, c.accno
FROM acc_trans ac
LEFT JOIN chart c ON (c.id = ac.chart_id)
LEFT JOIN ar ON (ar.id = ac.trans_id)
+ LEFT JOIN tax t ON (t.id = ac.tax_id)
LEFT JOIN taxkeys tk ON (
tk.id = (
SELECT id FROM taxkeys
)
WHERE
$acc_trans_where
- GROUP BY tk.pos_ustva
+ GROUP BY tk.pos_ustva, t.rate, c.accno
|;
} elsif ($form->{accounting_method} eq 'accrual') {
-- Alle Einnahmen AR und pos_ustva erfassen
SELECT
- sum(ac.amount) AS amount,
- tk.pos_ustva
+ tk.pos_ustva, t.rate, c.accno
FROM acc_trans ac
JOIN chart c ON (c.id = ac.chart_id)
JOIN ar ON (ar.id = ac.trans_id)
+ JOIN tax t ON (t.id = ac.tax_id)
JOIN taxkeys tk ON (
tk.id = (
SELECT id FROM taxkeys
$dpt_join
WHERE 1 = 1
$where
- GROUP BY tk.pos_ustva
+ GROUP BY tk.pos_ustva, t.rate, c.accno
|;
} else {
SELECT
sum(ac.amount) AS amount,
- tk.pos_ustva
+ tk.pos_ustva, t.rate, c.accno
FROM acc_trans ac
JOIN ap ON (ap.id = ac.trans_id )
JOIN chart c ON (c.id = ac.chart_id)
+ JOIN tax t ON (t.id = ac.tax_id)
LEFT JOIN taxkeys tk ON (
tk.id = (
SELECT id FROM taxkeys
WHERE
1=1
$where
- GROUP BY tk.pos_ustva
+ GROUP BY tk.pos_ustva, t.rate, c.accno
UNION -- Einnahmen direkter gl Buchungen erfassen
SELECT sum
( - ac.amount) AS amount,
- tk.pos_ustva
+ tk.pos_ustva, t.rate, c.accno
FROM acc_trans ac
JOIN chart c ON (c.id = ac.chart_id)
JOIN gl a ON (a.id = ac.trans_id)
+ JOIN tax t ON (t.id = ac.tax_id)
LEFT JOIN taxkeys tk ON (
tk.id = (
SELECT id FROM taxkeys
$dpt_join
WHERE 1 = 1
$where
- GROUP BY tk.pos_ustva
+ GROUP BY tk.pos_ustva, t.rate, c.accno
UNION -- Ausgaben direkter gl Buchungen erfassen
SELECT sum
(ac.amount) AS amount,
- tk.pos_ustva
+ tk.pos_ustva, t.rate, c.accno
FROM acc_trans ac
JOIN chart c ON (c.id = ac.chart_id)
JOIN gl a ON (a.id = ac.trans_id)
+ JOIN tax t ON (t.id = ac.tax_id)
LEFT JOIN taxkeys tk ON (
tk.id = (
SELECT id FROM taxkeys
$dpt_join
WHERE 1 = 1
$where
- GROUP BY tk.pos_ustva
+ GROUP BY tk.pos_ustva, t.rate, c.accno
|;
- my @accno;
- my $accno;
- my $ref;
-
# Show all $query in Debuglevel LXDebug::QUERY
my $callingdetails = (caller (0))[3];
$main::lxdebug->message(LXDebug->QUERY(), "$callingdetails \$query=\n $query");
my $sth = $dbh->prepare($query);
$sth->execute || $form->dberror($query);
+ # ugly, but we need to use static accnos
+ my ($accno_five, $accno_sixteen, $corr);
+
+ if ($form->{coa} eq 'Germany-DATEV-SKR03EU') {
+ $accno_five = 1773;
+ $accno_sixteen = 1775;
+ } elsif (($form->{coa} eq 'Germany-DATEV-SKR04EU')) {
+ $accno_five = 3803; # SKR04
+ $accno_sixteen = 3805; # SKR04
+ } else {die "wrong call"; }
while (my $ref = $sth->fetchrow_hashref("NAME_lc")) {
- # Bug 365 solved?!
+ next unless $ref->{$category};
+ $corr = 0;
$ref->{amount} *= -1;
- $form->{ $ref->{$category} } += $ref->{amount};
+ # USTVA Pos 35
+ if ($ref->{pos_ustva} eq '35') {
+ if ($ref->{rate} == 0.16) {
+ $form->{"pos_ustva_81b_kivi"} += $ref->{amount};
+ } elsif ($ref->{rate} == 0.05) {
+ $form->{"pos_ustva_86b_kivi"} += $ref->{amount};
+ } elsif ($ref->{rate} == 0.19) {
+ # pos_ustva says 16, but rate says 19
+ # (pos_ustva should be tax dependent and not taxkeys dependent)
+ # correction hotfix for this case:
+ # bookings exists with 19% ->
+ # move 19% bookings to the 19% position
+ # Dont rely on dates of taxkeys
+ $corr = 1;
+ $form->{"81"} += $ref->{amount};
+ } elsif ($ref->{rate} == 0.07) {
+ # pos_ustva says 5, but rate says 7
+ # see comment above:
+ # Dont rely on dates of taxkeys
+ $corr = 1;
+ $form->{"86"} += $ref->{amount};
+ } else {die ("No valid tax rate for pos 35" . Dumper($ref)); }
+ }
+ # USTVA Pos 36 (Steuerkonten)
+ if ($ref->{pos_ustva} eq '36') {
+ if ($ref->{accno} =~ /^$accno_sixteen/) {
+ $form->{"pos_ustva_811b_kivi"} += $ref->{amount};
+ } elsif ($ref->{accno} =~ /^$accno_five/) {
+ $form->{"pos_ustva_861b_kivi"} += $ref->{amount};
+ } else { die ("No valid accno for pos 36" . Dumper($ref)); }
+ }
+ $form->{ $ref->{$category} } += $ref->{amount} unless $corr;
}
$sth->finish;
--- /dev/null
+package SL::VATIDNr;
+
+use strict;
+use warnings;
+
+use Algorithm::CheckDigits;
+
+sub clean {
+ my ($class, $ustid) = @_;
+
+ $ustid //= '';
+ $ustid =~ s{[[:space:].-]+}{}g;
+
+ return $ustid;
+}
+
+sub normalize {
+ my ($class, $ustid) = @_;
+
+ $ustid = $class->clean($ustid);
+
+ if ($ustid =~ m{^CHE(\d{3})(\d{3})(\d{3})$}) {
+ return sprintf('CHE-%s.%s.%s', $1, $2, $3);
+ }
+
+ return $ustid;
+}
+
+sub _validate_switzerland {
+ my ($ustid) = @_;
+
+ return $ustid =~ m{^CHE\d{9}$} ? 1 : 0;
+}
+
+sub _validate_european_union {
+ my ($ustid) = @_;
+
+ # 1. Two upper-case letters with the ISO 3166-1 Alpha-2 country code (exception: Greece uses EL instead of GR)
+ # 2. Up to twelve alphanumeric characters
+
+ return 0 unless $ustid =~ m{^(?:AT|BE|BG|CY|CZ|DE|DK|EE|EL|ES|FI|FR|GB|HR|HU|IE|IT|LT|LU|LV|MT|NL|PL|PT|RO|SE|SI|SK|SM)[[:alnum:]]{1,12}$};
+
+ my $algo_name = "ustid_" . lc(substr($ustid, 0, 2));
+ my $checker = eval { CheckDigits($algo_name) };
+
+ return $checker->is_valid(substr($ustid, 2)) if $checker;
+ return 1;
+}
+
+sub validate {
+ my ($class, $ustid) = @_;
+
+ $ustid = $class->clean($ustid);
+
+ return _validate_switzerland($ustid) if $ustid =~ m{^CHE};
+ return _validate_european_union($ustid);
+}
+
+1;
+__END__
+
+=pod
+
+=encoding utf8
+
+=head1 NAME
+
+SL::VATIDNr - Helper routines for dealing with VAT ID numbers
+("Umsatzsteuer-Identifikationsnummern", "UStID-Nr" in German) and
+Switzerland's enterprise identification numbers (UIDs)
+
+=head1 SYNOPSIS
+
+ my $is_valid = SL::VATIDNr->validate($ustid);
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item C<clean> C<$ustid>
+
+Returns the number with all spaces, dashes & points removed.
+
+=item C<normalize> C<$ustid>
+
+Normalizes the given number to the format usually used in the country
+given by the country code at the start of the number
+(e.g. C<CHE-123.456.789> for a Swiss UID or DE123456789 for a German
+VATIDNr).
+
+=item C<validate> C<$ustid>
+
+Returns whether or not a number is valid. Depending on the country
+code at the start several tests are done including check digit
+validation.
+
+The number in question is first run through the L</clean> function and
+may therefore contain certain ignored characters.
+
+=back
+
+=head1 BUGS
+
+Nothing here yet.
+
+=head1 AUTHOR
+
+Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
+
+=cut
# so we extract both versions in our query and later overwrite the description in article mode
my $query =
- qq|SELECT ct.id as customerid, ct.name as customername,ct.customernumber,ct.country,ar.invnumber,ar.id,ar.transdate,p.partnumber,p.description as description, pg.partsgroup,i.parts_id,i.qty,i.price_factor,i.discount,i.description as invoice_description,i.lastcost,i.sellprice,i.fxsellprice,i.marge_total,i.marge_percent,i.unit,b.description as business,e.name as employee,e2.name as salesman, to_char(ar.transdate,'Month') as month, to_char(ar.transdate, 'YYYYMM') as nummonth, p.unit as parts_unit, p.weight, ar.taxincluded | .
+ qq|SELECT ct.id as customerid, ct.name as customername,ct.customernumber,ct.country,ar.invnumber,ar.shipvia,ar.id,ar.transdate,p.partnumber,p.description as description, pg.partsgroup,i.parts_id,i.qty,i.price_factor,i.discount,i.description as invoice_description,i.lastcost,i.sellprice,i.fxsellprice,i.marge_total,i.marge_percent,i.unit,b.description as business,e.name as employee,e2.name as salesman, to_char(ar.transdate,'Month') as month, to_char(ar.transdate, 'YYYYMM') as nummonth, p.unit as parts_unit, p.weight, ar.taxincluded | .
qq|, COALESCE(er.buy, 1) | .
qq|FROM invoice i | .
qq|RIGHT JOIN ar on (i.trans_id = ar.id) | .
$where .= " AND i.assemblyitem is not true ";
# filter allowed parameters for mainsort and subsort as passed by POST
- my @databasefields = qw(description customername country partsgroup business salesman month);
+ my @databasefields = qw(description customername country partsgroup business salesman month shipvia);
my ($mainsort) = grep { /^$form->{mainsort}$/ } @databasefields;
my ($subsort) = grep { /^$form->{subsort}$/ } @databasefields;
die "illegal parameter for mainsort or subsort" unless $mainsort and $subsort;
package WH;
+use Carp qw(croak);
+
use SL::AM;
use SL::DBUtils;
+use SL::DB::Inventory;
use SL::Form;
+use SL::Locale::String qw(t8);
use SL::Util qw(trim);
use warnings;
require SL::DB::TransferType;
require SL::DB::Part;
require SL::DB::Employee;
- require SL::DB::Inventory;
my $employee = SL::DB::Manager::Employee->find_by(login => $::myconfig{login});
my ($now) = selectrow_query($::form, $::form->get_standard_dbh, qq|SELECT current_date|);
"chargeid" => "c.id",
"warehousedescription" => "w.description",
"partunit" => "p.unit",
- "stock_value" => "p.lastcost / COALESCE(pfac.factor, 1)",
+ "stock_value" => ($form->{stock_value_basis} // '') eq 'list_price' ? "p.listprice / COALESCE(pfac.factor, 1)" : "p.lastcost / COALESCE(pfac.factor, 1)",
"purchase_price" => "p.lastcost",
+ "list_price" => "p.listprice",
);
$form->{l_classification_id} = 'Y';
$form->{l_part_type} = 'Y';
return ($max_qty_parts, $error);
}
+sub get_wh_and_bin_for_charge {
+ $main::lxdebug->enter_sub();
+
+ my $self = shift;
+ my %params = @_;
+ my %bin_qty;
+
+ croak t8('Need charge number!') unless $params{chargenumber};
+
+ my $inv_items = SL::DB::Manager::Inventory->get_all(where => [chargenumber => $params{chargenumber} ]);
+
+ croak t8("Invalid charge number: #1", $params{chargenumber}) unless (ref @{$inv_items}[0] eq 'SL::DB::Inventory');
+ # add all qty for one bin and add wh_id
+ ($bin_qty{$_->bin_id}{qty}, $bin_qty{$_->bin_id}{wh}) = ($bin_qty{$_->bin_id}{qty} + $_->qty, $_->warehouse_id) for @{ $inv_items };
+
+ while (my ($bin, $value) = each (%bin_qty)) {
+ if ($value->{qty} > 0) {
+ $main::lxdebug->leave_sub();
+ return ($value->{qty}, $value->{wh}, $bin, $params{chargenumber});
+ }
+ }
+
+ $main::lxdebug->leave_sub();
+ return undef;
+}
1;
__END__
'comment' => $form->{comment}
);
+
+=head2 get_wh_and_bin_for_charge C<$params{chargenumber}>
+
+Gets the current qty from the inventory entries with the mandatory chargenumber: C<$params{chargenumber}>.
+Croaks if the chargenumber is missing or no entry currently exists.
+If there is one bin and warehouse with a positive qty, this fields are returned:
+C<qty> C<warehouse_id>, C<bin_id>, C<chargenumber>.
+Otherwise returns undef.
+
+
=head3 Prerequisites
All of these prerequisites have to be trueish, otherwise the function will exit
letter => 'briefe',
general_ledger => 'dialogbuchungen',
accounts_payable => 'kreditorenbuchungen',
+ customer => 'kunden',
+ vendor => 'lieferanten',
);
sub get_all_files {
'SL::X::DBRoseError' => {
isa => 'SL::X::DBError',
fields => [ qw(class metaobject object) ],
- defaults => { error_template => [ '\'%s\' in object of type \'%s\' occured', qw(db_error class) ] },
+ defaults => { error_template => [ '\'%s\' in object of type \'%s\' occurred', qw(db_error class) ] },
},
'SL::X::DBUtilsError' => {
isa => 'SL::X::DBError',
},
+ 'SL::X::ZUGFeRDValidation' => {
+ isa => 'SL::X::Base',
+ },
);
1;
--- /dev/null
+package SL::ZUGFeRD;
+
+use strict;
+use warnings;
+use utf8;
+
+use CAM::PDF;
+use Data::Dumper;
+use List::Util qw(first);
+use XML::LibXML;
+
+use constant RES_OK => 0;
+use constant RES_ERR_FILE_OPEN => 1;
+use constant RES_ERR_NO_XMP_METADATA => 2;
+use constant RES_ERR_NO_XML_INVOICE => 3;
+use constant RES_ERR_NOT_ZUGFERD => 4;
+use constant RES_ERR_UNSUPPORTED_ZUGFERD_VERSION => 5;
+
+sub _extract_zugferd_invoice_xml {
+ my $doc = shift;
+ my $names_dict = $doc->getValue($doc->getRootDict->{Names}) or return {};
+ my $files_tree = $names_dict->{EmbeddedFiles} or return {};
+ my @agenda = $files_tree;
+ my $ret = {};
+
+ # Hardly ever more than single leaf, but...
+
+ while (@agenda) {
+ my $item = $doc->getValue(shift @agenda);
+
+ if ($item->{Kids}) {
+ my $kids = $doc->getValue($item->{Kids});
+ push @agenda, @$kids
+
+ } else {
+ my $nodes = $doc->getValue($item->{Names});
+ my @names = map { $doc->getValue($_)} @$nodes;
+
+ while (@names) {
+ my ($k, $v) = splice @names, 0, 2;
+ my $ef_node = $v->{EF};
+ my $ef_dict = $doc->getValue($ef_node);
+ my $fnode = (values %$ef_dict)[0];
+ my $any_num = $fnode->{value};
+ my $obj_node = $doc->dereference($any_num);
+ my $content = $doc->decodeOne($obj_node->{value}, 0) // '';
+
+ #print "1\n";
+
+ next if $content !~ m{<rsm:CrossIndustryInvoice};
+ #print "2\n";
+
+ my $dom = eval { XML::LibXML->load_xml(string => $content) };
+ return $content if $dom && ($dom->documentElement->nodeName eq 'rsm:CrossIndustryInvoice');
+ }
+ }
+ }
+
+ return undef;
+}
+
+sub _get_xmp_metadata {
+ my ($doc) = @_;
+
+ my $node = $doc->getValue($doc->getRootDict->{Metadata});
+ if ($node && $node->{StreamData} && defined($node->{StreamData}->{value})) {
+ return $node->{StreamData}->{value};
+ }
+
+ return undef;
+}
+
+sub extract_from_pdf {
+ my ($self, $file_name) = @_;
+
+ my $pdf_doc = CAM::PDF->new($file_name);
+
+ if (!$pdf_doc) {
+ return {
+ result => RES_ERR_FILE_OPEN(),
+ message => $::locale->text('The file \'#1\' could not be opened for reading.', $file_name),
+ };
+ }
+
+ my $xmp = _get_xmp_metadata($pdf_doc);
+ if (!defined $xmp) {
+ return {
+ result => RES_ERR_NO_XMP_METADATA(),
+ message => $::locale->text('The file \'#1\' does not contain the required XMP meta data.', $file_name),
+ };
+ }
+
+ my $bad = {
+ result => RES_ERR_NO_XMP_METADATA(),
+ message => $::locale->text('Parsing the XMP metadata failed.'),
+ };
+
+ my $dom = eval { XML::LibXML->load_xml(string => $xmp) };
+
+ return $bad if !$dom;
+
+ my $xpc = XML::LibXML::XPathContext->new($dom);
+ $xpc->registerNs('rdf', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#');
+
+ my $zugferd_version;
+
+ foreach my $node ($xpc->findnodes('/x:xmpmeta/rdf:RDF/rdf:Description')) {
+ my $ns = first { ref($_) eq 'XML::LibXML::Namespace' } $node->attributes;
+ next unless $ns;
+
+ if ($ns->getData =~ m{urn:zugferd:pdfa:CrossIndustryDocument:invoice:2p0}) {
+ $zugferd_version = '2p0';
+ last;
+ }
+
+ if ($ns->getData =~ m{zugferd}i) {
+ $zugferd_version = 'unsupported';
+ last;
+ }
+ }
+
+ if (!$zugferd_version) {
+ return {
+ result => RES_ERR_NOT_ZUGFERD(),
+ message => $::locale->text('The XMP metadata does not declare the ZUGFeRD data.'),
+ };
+ }
+
+ if ($zugferd_version !~ m{^2p}) {
+ return {
+ result => RES_ERR_UNSUPPORTED_ZUGFERD_VERSION(),
+ message => $::locale->text('The ZUGFeRD version used is not supported.'),
+ };
+ }
+
+ my $invoice_xml = _extract_zugferd_invoice_xml($pdf_doc);
+
+ if (!defined $invoice_xml) {
+ return {
+ result => RES_ERR_NO_XML_INVOICE(),
+ message => $::locale->text('The ZUGFeRD XML invoice was not found.'),
+ };
+ }
+
+ return {
+ result => RES_OK(),
+ metadata_xmp => $xmp,
+ invoice_xml => $invoice_xml,
+ };
+}
+
+1;
+
+__END__
+
+=pod
+
+=encoding utf8
+
+=head1 NAME
+
+SL::ZUGFeRD - Helper functions for dealing with PDFs containing ZUGFeRD invoice data
+
+=head1 SYNOPSIS
+
+ my $pdf = '/path/to/my.pdf';
+ my $info = SL::ZUGFeRD->extract_from_pdf($pdf);
+
+ if ($info->{result} != SL::ZUGFeRD::RES_OK()) {
+ # An error occurred; log message from parser:
+ $::lxdebug->message(LXDebug::DEBUG1(), "Could not extract ZUGFeRD data from $pdf: " . $info->{message});
+ return;
+ }
+
+ # Parse & handle invoice XML:
+ my $dom = XML::LibXML->load_xml(string => $info->{invoice_xml});
+
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item C<extract_from_pdf> C<$file_name>
+
+Opens an existing PDF in the file system and tries to extract ZUGFeRD
+invoice data from it. First it'll parse the XMP metadata and look for
+the ZUGFeRD declaration inside. If the declaration isn't found or the
+declared version isn't 2p0, an error is returned.
+
+Otherwise it'll continue to look through all embedded files in the
+PDF. The first embedded XML file with a root node of
+C<rsm:CrossCountryInvoice> will be returnd.
+
+Always returns a hash ref containing the key C<result>, a number that
+can be one of the following constants:
+
+=over 4
+
+=item C<RES_OK> (0): parsing was OK; the returned hash will also
+contain the keys C<xmp_metadata> and C<invoice_xml> which will contain
+the XML text of the metadata & the ZUGFeRD invoice.
+
+=item C<RES_ERR_…> (all values E<gt> 0): parsing failed; the hash will
+also contain a key C<message> which contains a human-readable
+information about what exactly failed.
+
+=back
+
+=back
+
+=head1 BUGS
+
+Nothing here yet.
+
+=head1 AUTHOR
+
+Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
+
+=cut
$form->{displayable_name_specs_by_module} = AM->displayable_name_specs_by_module();
$form->{positions_scrollbar_height} = AM->positions_scrollbar_height();
+ $form->{purchase_search_makemodel} = AM->purchase_search_makemodel();
+ $form->{sales_search_customer_partnumber} = AM->sales_search_customer_partnumber();
+ $form->{positions_show_update_button} = AM->positions_show_update_button();
$myconfig{show_form_details} = 1 unless (defined($myconfig{show_form_details}));
$form->{CAN_CHANGE_PASSWORD} = $main::auth->can_change_password();
$form->{translations} = { map { $_ =~ '^translation_(\d+)'; $1 => $form->{$_} } @translation_keys };
AM->save_tax(\%myconfig, \%$form);
- $form->redirect($locale->text('Tax saved!'));
+ flash_later('info', $locale->text("Tax saved!"));
+
+ print $form->redirect_header('am.pl?action=list_tax');
$main::lxdebug->leave_sub();
}
use SL::DB::Chart;
use SL::DB::Currency;
use SL::DB::Default;
+use SL::DB::Order;
use SL::DB::PurchaseInvoice;
use SL::DB::RecordTemplate;
use SL::DB::Tax;
"old_id" => \@old_project_ids },
"charts" => { "key" => "ALL_CHARTS",
"transdate" => $form->{transdate} },
- "taxcharts" => { "key" => "ALL_TAXCHARTS",
- "module" => "AP" },);
+ );
map(
{ $_->{link_split} = [ split(/:/, $_->{link}) ]; }
my $follow_up_trans_info = "$form->{invnumber} ($follow_up_vc)";
$::request->layout->add_javascripts("autocomplete_chart.js", "show_vc_details.js", "show_history.js", "follow_up.js", "kivi.Draft.js", "kivi.GL.js", "kivi.RecordTemplate.js", "kivi.File.js", "kivi.AP.js", "kivi.CustomerVendor.js", "kivi.Validator.js");
- my $transdate = $::form->{transdate} ? DateTime->from_kivitendo($::form->{transdate}) : DateTime->today_local;
- my $first_taxchart;
-
# $form->{totalpaid} is used by the action bar setup to determine
# whether or not canceling is allowed. Therefore it must be
# calculated prior to the action bar setup.
setup_ap_display_form_action_bar();
$form->header();
+ # get the correct date for tax
+ my $transdate = $::form->{transdate} ? DateTime->from_kivitendo($::form->{transdate}) : DateTime->today_local;
+ my $deliverydate = $::form->{deliverydate} ? DateTime->from_kivitendo($::form->{deliverydate}) : undef;
+ my $taxdate = $deliverydate ? $deliverydate : $transdate;
+ # helper for loop
+ my $first_taxchart;
for my $i (1 .. $form->{rowcount}) {
$form->{"tax_$i"} = $form->format_amount(\%myconfig, $form->{"tax_$i"}, 2);
my ($default_taxchart, $taxchart_to_use);
+ my $used_tax_id;
+ if ( $form->{"taxchart_$i"} ) {
+ ($used_tax_id) = split(/--/, $form->{"taxchart_$i"});
+ }
my $amount_chart_id = $form->{"AP_amount_chart_id_$i"} || $default_ap_amount_chart_id;
- my @taxcharts = GL->get_active_taxes_for_chart($amount_chart_id, $transdate);
+ my @taxcharts = GL->get_active_taxes_for_chart($amount_chart_id, $taxdate, $used_tax_id);
foreach my $item (@taxcharts) {
my $key = $item->id . "--" . $item->rate;
$first_taxchart //= $item;
my $item = shift;
return [
$item->{id} .'--'. $item->{rate},
- $item->{taxdescription} .' '. ($item->{rate} * 100) .' %',
+ $item->{taxkey} . ' - ' . $item->{taxdescription} .' '. ($item->{rate} * 100) .' %',
];
};
# calculate tax exactly the same way as AP in post_transaction via form->calculate_tax
my $tmpnetamount;
($tmpnetamount,$form->{"tax_$i"}) = $form->calculate_tax($form->{"amount_$i"},$rate,$form->{taxincluded},2);
-
$totaltax += $form->{"tax_$i"};
map { $a[$j]->{$_} = $form->{"${_}_$i"} } @flds;
$count++;
$main::auth->assert('ap_transactions');
- map { delete $form->{$_} } qw(printed emailed queued invnumber deliverydate id datepaid_1 gldate_1 acc_trans_id_1 source_1 memo_1 paid_1 exchangerate_1 AP_paid_1 storno);
+ map { delete $form->{$_} } qw(printed emailed queued invnumber deliverydate id datepaid_1 gldate_1 acc_trans_id_1 source_1 memo_1 paid_1 exchangerate_1 AP_paid_1 storno convert_from_oe_id);
$form->{paidaccounts} = 1;
$form->{rowcount}--;
my @columns =
qw(transdate id type invnumber ordnumber name netamount tax amount paid datepaid
- due duedate transaction_description notes employee globalprojectnumber
+ due duedate transaction_description notes employee globalprojectnumber department
vendornumber country ustid taxzone payment_terms charts direct_debit);
my @hidden_variables = map { "l_${_}" } @columns;
push @hidden_variables, "l_subtotal", qw(open closed vendor invnumber ordnumber transaction_description notes project_id transdatefrom transdateto
- parts_partnumber parts_description);
+ parts_partnumber parts_description department_id);
my $href = build_std_url('action=ap_transactions', grep { $form->{$_} } @hidden_variables);
'notes' => { 'text' => $locale->text('Notes'), },
'employee' => { 'text' => $locale->text('Employee'), },
'globalprojectnumber' => { 'text' => $locale->text('Document Project Number'), },
+ 'department' => { 'text' => $locale->text('Department'), },
'vendornumber' => { 'text' => $locale->text('Vendor Number'), },
'country' => { 'text' => $locale->text('Country'), },
'ustid' => { 'text' => $locale->text('USt-IdNr.'), },
'direct_debit' => { 'text' => $locale->text('direct debit'), },
);
- foreach my $name (qw(id transdate duedate invnumber ordnumber name datepaid employee shippingpoint shipvia transaction_description direct_debit)) {
+ foreach my $name (qw(id transdate duedate invnumber ordnumber name datepaid employee shippingpoint shipvia transaction_description direct_debit department)) {
my $sortdir = $form->{sort} eq $name ? 1 - $form->{sortdir} : $form->{sortdir};
$column_defs{$name}->{link} = $href . "&sort=$name&sortdir=$sortdir";
}
$report->set_sort_indicator($form->{sort}, $form->{sortdir});
+ my $department_description;
+ $department_description = SL::DB::Manager::Department->find_by(id => $form->{department_id})->description if $form->{department_id};
+
my @options;
push @options, $locale->text('Vendor') . " : $form->{vendor}" if ($form->{vendor});
push @options, $locale->text('Contact Person') . " : $form->{cp_name}" if ($form->{cp_name});
- push @options, $locale->text('Department') . " : $form->{department}" if ($form->{department});
+ push @options, $locale->text('Department') . " : $department_description" if ($form->{department_id});
push @options, $locale->text('Invoice Number') . " : $form->{invnumber}" if ($form->{invnumber});
push @options, $locale->text('Order Number') . " : $form->{ordnumber}" if ($form->{ordnumber});
push @options, $locale->text('Notes') . " : $form->{notes}" if ($form->{notes});
$main::lxdebug->leave_sub();
}
+sub add_from_purchase_order {
+ $main::auth->assert('ap_transactions');
+
+ return if !$::form->{id};
+
+ my $order_id = delete $::form->{id};
+ my $order = SL::DB::Order->new(id => $order_id)->load(with => [ 'vendor', 'currency', 'payment_terms' ]);
+
+ return if $order->type ne 'purchase_order';
+
+ my $today = DateTime->today_local;
+ $::form->{title} = "Add";
+ $::form->{vc} = 'vendor';
+ $::form->{vendor_id} = $order->customervendor->id;
+ $::form->{vendor} = $order->vendor->name;
+ $::form->{convert_from_oe_id} = $order->id;
+ $::form->{globalproject_id} = $order->globalproject_id;
+ $::form->{ordnumber} = $order->number;
+ $::form->{department_id} = $order->department_id;
+ $::form->{currency} = $order->currency->name;
+ $::form->{taxincluded} = 1; # we use amount below, so tax is included
+ $::form->{transdate} = $today->to_kivitendo;
+ $::form->{duedate} = $today->to_kivitendo;
+ $::form->{duedate} = $order->payment_terms->calc_date(reference_date => $today)->to_kivitendo if $order->payment_terms;
+ $::form->{deliverydate} = $order->reqdate->to_kivitendo if $order->reqdate;
+ create_links();
+
+ my $config_po_ap_workflow_chart_id = $::instance_conf->get_workflow_po_ap_chart_id;
+
+ my ($first_taxchart, $default_taxchart, $taxchart_to_use);
+ my @taxcharts = ();
+ @taxcharts = GL->get_active_taxes_for_chart($config_po_ap_workflow_chart_id, $::form->{transdate}) if (defined $config_po_ap_workflow_chart_id);
+ foreach my $item (@taxcharts) {
+ $first_taxchart //= $item;
+ $default_taxchart = $item if $item->{is_default};
+ }
+ $taxchart_to_use = $default_taxchart // $first_taxchart;
+
+ my %pat = $order->calculate_prices_and_taxes;
+ my $row = 1;
+ foreach my $amount_chart (keys %{$pat{amounts}}) {
+ my $tax = SL::DB::Manager::Tax->find_by(id => $pat{amounts}->{$amount_chart}->{tax_id});
+ # If tax chart from order for this amount is active, use it. Use default or first tax chart for selected chart else.
+ if (defined $config_po_ap_workflow_chart_id) {
+ $taxchart_to_use = (first {$_->{id} == $tax->id} @taxcharts) // $taxchart_to_use;
+ } else {
+ $taxchart_to_use = $tax;
+ }
+
+ $::form->{"AP_amount_chart_id_$row"} = $config_po_ap_workflow_chart_id // $amount_chart;
+ $::form->{"previous_AP_amount_chart_id_$row"} = $::form->{"AP_amount_chart_id_$row"};
+ $::form->{"amount_$row"} = $::form->format_amount(\%::myconfig, $pat{amounts}->{$amount_chart}->{amount} * (1 + $tax->rate), 2);
+ $::form->{"taxchart_$row"} = $taxchart_to_use->id . '--' . $taxchart_to_use->rate;
+ $::form->{"project_id_$row"} = $order->globalproject_id;
+
+ $row++;
+ }
+
+ my $last_used_ap_chart = SL::DB::Vendor->load_cached($::form->{vendor_id})->last_used_ap_chart;
+ $::form->{"AP_amount_chart_id_$row"} = $last_used_ap_chart->id if $last_used_ap_chart;
+ $::form->{rowcount} = $row;
+
+ update(
+ keep_rows_without_amount => 1,
+ dont_add_new_row => 1,
+ );
+}
+
sub setup_ap_search_action_bar {
my %params = @_;
$form->{forex} = $form->check_exchangerate( \%myconfig, $form->{currency}, $form->{transdate}, 'buy');
$form->{exchangerate} = $form->{forex} if $form->{forex};
- # format exchangerate
- $form->{exchangerate} = $form->{exchangerate} ? $form->format_amount(\%myconfig, $form->{exchangerate}) : '';
-
$rows = max 2, $form->numtextrows($form->{notes}, 50);
my @old_project_ids = grep { $_ } map { $form->{"project_id_$_"} } 1..$form->{rowcount};
"old_id" => \@old_project_ids },
"charts" => { "key" => "ALL_CHARTS",
"transdate" => $form->{transdate} },
- "taxcharts" => { "key" => "ALL_TAXCHARTS",
- "module" => "AR" },);
+ );
$form->{ALL_DEPARTMENTS} = SL::DB::Manager::Department->get_all_sorted;
my $follow_up_trans_info = "$form->{invnumber} ($follow_up_vc)";
$::request->layout->add_javascripts("autocomplete_chart.js", "show_vc_details.js", "show_history.js", "follow_up.js", "kivi.Draft.js", "kivi.GL.js", "kivi.File.js", "kivi.RecordTemplate.js", "kivi.AR.js", "kivi.CustomerVendor.js", "kivi.Validator.js");
-
- my $transdate = $::form->{transdate} ? DateTime->from_kivitendo($::form->{transdate}) : DateTime->today_local;
+ # get the correct date for tax
+ my $transdate = $::form->{transdate} ? DateTime->from_kivitendo($::form->{transdate}) : DateTime->today_local;
+ my $deliverydate = $::form->{deliverydate} ? DateTime->from_kivitendo($::form->{deliverydate}) : undef;
+ my $taxdate = $deliverydate ? $deliverydate : $transdate;
+ # helpers for loop
my $first_taxchart;
-
my @transactions;
+
for my $i (1 .. $form->{rowcount}) {
my $transaction = {
amount => $form->{"amount_$i"},
my (%taxchart_labels, @taxchart_values, $default_taxchart, $taxchart_to_use);
my $amount_chart_id = $form->{"AR_amount_chart_id_$i"} // $default_ar_amount_chart_id;
- foreach my $item ( GL->get_active_taxes_for_chart($amount_chart_id, $transdate) ) {
+ my $used_tax_id;
+ if ( $form->{"taxchart_$i"} ) {
+ ($used_tax_id) = split(/--/, $form->{"taxchart_$i"});
+ }
+ foreach my $item ( GL->get_active_taxes_for_chart($amount_chart_id, $taxdate, $used_tax_id) ) {
my $key = $item->id . "--" . $item->rate;
$first_taxchart //= $item;
$default_taxchart = $item if $item->{is_default};
$taxchart_to_use = $item if $key eq $form->{"taxchart_$i"};
push(@taxchart_values, $key);
- $taxchart_labels{$key} = $item->taxdescription . " " . $item->rate * 100 . ' %';
+ $taxchart_labels{$key} = $item->taxkey . " - " . $item->taxdescription . " " . $item->rate * 100 . ' %';
}
$taxchart_to_use //= $default_taxchart // $first_taxchart;
%column_defs_cvars,
);
- foreach my $name (qw(id transdate duedate invnumber ordnumber cusordnumber name datepaid employee shippingpoint shipvia transaction_description direct_debit)) {
+ foreach my $name (qw(id transdate duedate invnumber ordnumber cusordnumber name datepaid employee shippingpoint shipvia transaction_description direct_debit department)) {
my $sortdir = $form->{sort} eq $name ? 1 - $form->{sortdir} : $form->{sortdir};
$column_defs{$name}->{link} = $href . "&sort=$name&sortdir=$sortdir";
}
DN->get_config(\%myconfig, \%$form);
+ $form->get_lists("departments" => "ALL_DEPARTMENTS");
+
$form->{SHOW_DUNNING_LEVEL_SELECTION} = $form->{DUNNING} && scalar @{ $form->{DUNNING} };
- $form->{SHOW_DEPARTMENT_SELECTION} = $form->{all_departments} && scalar @{ $form->{all_departments} || [] };
+ $form->{SHOW_DEPARTMENT_SELECTION} = $form->{ALL_DEPARTMENTS} && scalar @{ $form->{ALL_DEPARTMENTS} || [] };
$form->{title} = $locale->text('Start Dunning Process');
my $saved_language_id = $form->{language_id};
- if ($form->{groupinvoices}) {
+ if ($form->{groupinvoices} || $form->{l_include_credit_notes}) {
my %dunnings_for;
for my $i (1 .. $form->{rowcount}) {
push @{ $level }, { "row" => $i,
"invoice_id" => $form->{"inv_id_$i"},
+ "credit_note" => $form->{"credit_note_$i"},
"customer_id" => $form->{"customer_id_$i"},
"language_id" => $form->{"language_id_$i"},
"next_dunning_config_id" => $form->{"next_dunning_config_id_$i"},
+ "print_invoice" => $form->{"include_invoice_$i"},
"email" => $form->{"email_$i"}, };
}
"customer_id" => $form->{"customer_id_$i"},
"language_id" => $form->{"language_id_$i"},
"next_dunning_config_id" => $form->{"next_dunning_config_id_$i"},
+ "print_invoice" => $form->{"include_invoice_$i"},
"email" => $form->{"email_$i"}, } ];
if (!$form->{force_lang}) {
$form->{language_id} = @{$level}[0]->{language_id};
'checkbox' => { 'text' => '', 'visible' => 'HTML' },
'dunning_description' => { 'text' => $locale->text('Dunning Level') },
'customername' => { 'text' => $locale->text('Customername') },
+ 'departmentname' => { 'text' => $locale->text('Department') },
'language' => { 'text' => $locale->text('Language') },
'invnumber' => { 'text' => $locale->text('Invnumber') },
'transdate' => { 'text' => $locale->text('Invdate') },
t8('E Mail'),
call => [ 'kivi.SalesPurchase.show_email_dialog' ],
checks => [ 'kivi.validate_form' ],
+ disabled => !$::form->{id} ? t8('This record has not been saved yet.') : undef,
],
], # end of combobox "Export"
$::request->{layout}->use_javascript(map { "${_}.js" } qw(kivi.File kivi.MassDeliveryOrderPrint kivi.SalesPurchase kivi.Part kivi.CustomerVendor kivi.Validator ckeditor/ckeditor ckeditor/adapters/jquery kivi.io));
- my @custom_hidden;
- push @custom_hidden, map { "shiptocvar_" . $_->name } @{ SL::DB::Manager::CustomVariableConfig->get_all(where => [ module => 'ShipTo' ]) };
-
- $::form->{HIDDENS} = [ map { +{ name => $_, value => $::form->{$_} } } (@custom_hidden) ];
-
setup_do_action_bar();
$form->header();
$form->{PRINT_OPTIONS} = setup_sales_purchase_print_options();
$form->{ALL_DELIVERY_TERMS} = SL::DB::Manager::DeliveryTerm->get_all_sorted();
+ my $shipto_cvars = SL::DB::Shipto->new->cvars_by_config;
+ foreach my $var (@{ $shipto_cvars }) {
+ my $name = "shiptocvar_" . $var->config->name;
+ $var->value($form->{$name}) if exists $form->{$name};
+ }
+
print $form->parse_html_template('do/form_footer',
- {transfer_default => ($::instance_conf->get_transfer_default)});
+ {transfer_default => ($::instance_conf->get_transfer_default),
+ shipto_cvars => $shipto_cvars});
$main::lxdebug->leave_sub();
}
preset_text_sales_order => t8('Preset email text for sales orders'),
preset_text_sales_delivery_order => t8('Preset email text for sales delivery orders'),
preset_text_invoice => t8('Preset email text for sales invoices'),
+ preset_text_invoice_direct_debit => t8('Preset email text for sales invoices with direct debit'),
preset_text_request_quotation => t8('Preset email text for requests (rfq)'),
preset_text_purchase_order => t8('Preset email text for purchase orders'),
preset_text_periodic_invoices_email_body => t8('Preset email body for periodic invoices'),
$main::lxdebug->leave_sub();
}
+sub edit_zugferd_notes {
+ $::auth->assert('config');
+
+ $::form->get_lists('languages' => 'LANGUAGES');
+
+ my $translation_list = GenericTranslations->list(translation_type => 'ZUGFeRD/notes');
+ my %translations = map { ( ($_->{language_id} || 'default') => $_->{translation} ) } @{ $translation_list };
+
+ unshift @{ $::form->{LANGUAGES} }, { 'id' => 'default', };
+
+ foreach my $language (@{ $::form->{LANGUAGES} }) {
+ $language->{translation} = $translations{$language->{id}};
+ }
+
+ setup_generictranslations_edit_zugferd_notes_action_bar();
+
+ $::form->{title} = $::locale->text('Edit ZUGFeRD notes');
+ $::form->header;
+ print $::form->parse_html_template('generictranslations/edit_zugferd_notes');
+}
+
+sub save_zugferd_notes {
+ $::auth->assert('config');
+
+ $::form->get_lists('languages' => 'LANGUAGES');
+
+ unshift @{ $::form->{LANGUAGES} }, { };
+
+ foreach my $language (@{ $::form->{LANGUAGES} }) {
+ GenericTranslations->save(
+ translation_type => 'ZUGFeRD/notes',
+ translation_id => undef,
+ language_id => $language->{id},
+ translation => $::form->{"translation__" . ($language->{id} || 'default')},
+ );
+ }
+
+ $::form->{message} = $::locale->text('The ZUGFeRD notes have been saved.');
+
+ edit_zugferd_notes();
+}
+
sub setup_generictranslations_edit_greetings_action_bar {
my %params = @_;
);
}
}
+
sub setup_generictranslations_edit_email_strings_action_bar {
my %params = @_;
}
}
+sub setup_generictranslations_edit_zugferd_notes_action_bar {
+ my %params = @_;
+
+ for my $bar ($::request->layout->get('actionbar')) {
+ $bar->add(
+ action => [
+ t8('Save'),
+ submit => [ '#form', { action => "save_zugferd_notes" } ],
+ accesskey => 'enter',
+ ],
+ );
+ }
+}
+
1;
my %charts_by_id = map { ($_->{id} => $_) } @{ $::form->{ALL_CHARTS} };
my $default_chart = $::form->{ALL_CHARTS}[0];
my $transdate = $::form->{transdate} ? DateTime->from_kivitendo($::form->{transdate}) : DateTime->today_local;
+ my $deliverydate = $::form->{deliverydate} ? DateTime->from_kivitendo($::form->{deliverydate}) : undef;
my ($source, $memo, $source_hidden, $memo_hidden);
for my $i (1 .. $form->{rowcount}) {
$accno_id = $chart->{id};
my ($first_taxchart, $default_taxchart, $taxchart_to_use);
- foreach my $item ( GL->get_active_taxes_for_chart($accno_id, $transdate) ) {
+ my $used_tax_id;
+ if ( $form->{"taxchart_$i"} ) {
+ ($used_tax_id) = split(/--/, $form->{"taxchart_$i"});
+ }
+
+ my $taxdate = $deliverydate ? $deliverydate : $transdate;
+ foreach my $item ( GL->get_active_taxes_for_chart($accno_id, $taxdate, $used_tax_id) ) {
my $key = $item->id . "--" . $item->rate;
$first_taxchart //= $item;
$default_taxchart = $item if $item->{is_default};
$taxchart_to_use = $item if $key eq $form->{"taxchart_$i"};
push(@taxchart_values, $key);
- $taxchart_labels{$key} = $item->taxdescription . " " . $item->rate * 100 . ' %';
+ $taxchart_labels{$key} = $item->taxkey . " - " . $item->taxdescription . " " . $item->rate * 100 . ' %';
}
$taxchart_to_use //= $default_taxchart // $first_taxchart;
}
sub get_tax_dropdown {
- my $transdate = $::form->{transdate} ? DateTime->from_kivitendo($::form->{transdate}) : DateTime->today_local;
- my @tax_accounts = GL->get_active_taxes_for_chart($::form->{accno_id}, $transdate);
+ my $transdate = $::form->{transdate} ? DateTime->from_kivitendo($::form->{transdate}) : DateTime->today_local;
+ my $deliverydate = $::form->{deliverydate} ? DateTime->from_kivitendo($::form->{deliverydate}) : undef;
+ my @tax_accounts = GL->get_active_taxes_for_chart($::form->{accno_id}, $deliverydate // $transdate);
my $html = $::form->parse_html_template("gl/update_tax_accounts", { TAX_ACCOUNTS => \@tax_accounts });
print $::form->ajax_response_header, $html;
use Carp;
use CGI;
use List::MoreUtils qw(any uniq apply);
-use List::Util qw(min max first);
+use List::Util qw(sum min max first);
use List::UtilsBy qw(sort_by uniq_by);
use SL::ClientJS;
use SL::Presenter::Part;
use SL::DB::Contact;
+use SL::DB::Currency;
use SL::DB::Customer;
use SL::DB::Default;
use SL::DB::Language;
}
}
- my $edit_prices = $main::auth->assert('edit_prices', 1) && (!$::form->{"active_price_source_$i"} || !$price || $price->editable);
- my $edit_discounts = $main::auth->assert('edit_prices', 1) && !$::form->{"active_discount_source_$i"};
+ my $right_to_edit_prices = (!$is_purchase && $main::auth->assert('sales_edit_prices', 1)) || ($is_purchase && $main::auth->assert('purchase_edit_prices', 1));
+ my $edit_prices = $right_to_edit_prices && (!$::form->{"active_price_source_$i"} || !$price || $price->editable);
+ my $edit_discounts = $right_to_edit_prices && !$::form->{"active_discount_source_$i"};
$column_data{sellprice} = (!$edit_prices)
? $cgi->hidden( -name => "sellprice_$i", -id => "sellprice_$i", -value => $sellprice_value) . $sellprice_value
: $cgi->textfield(-name => "sellprice_$i", -id => "sellprice_$i", -size => 10, -class => "numeric", -value => $sellprice_value);
_order();
if ($::instance_conf->get_feature_experimental_order) {
+
+ # At this point, the record is saved and the exchangerate contains
+ # an unformatted value. _make_record uses RDBO attributes (i.e. _as_number)
+ # to assign values and thus expects an formatted value.
+ $::form->{exchangerate} = $::form->format_amount(\%::myconfig, $::form->{exchangerate});
+
my $order = _make_record();
- $order->globalproject_id(undef) if !$order->globalproject_id;
- $order->payment_id(undef) if !$order->payment_id;
+
+ $order->currency(SL::DB::Currency->new(name => $::form->{currency})->load) if $::form->{currency};
+ $order->globalproject_id(undef) if !$order->globalproject_id;
+ $order->payment_id(undef) if !$order->payment_id;
+
my $row = 1;
foreach my $item (@{$order->items_sorted}) {
$item->custom_variables([]);
$item->price_factor_id(undef) if !$item->price_factor_id;
$item->project_id(undef) if !$item->project_id;
- $item->discount($item->discount/100.0);
# 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).
require SL::Controller::Order;
my $c = SL::Controller::Order->new(order => $order);
+ $c->setup_custom_shipto_from_form($order, $::form);
$c->action_edit();
$main::lxdebug->leave_sub();
$form->{TEMPLATE_DRIVER_OPTIONS}->{variable_content_types} = $form->get_variable_content_types();
}
+ if ($form->{format} =~ m{pdf}) {
+ _maybe_attach_zugferd_data($form);
+ }
+
$form->isblank("email", $locale->text('E-mail address missing!'))
if ($form->{media} eq 'email');
$form->isblank("${inv}date",
$form->{"taxaccounts"} =~ s/\s*$//;
$form->{"taxaccounts"} =~ s/^\s*//;
foreach my $accno (split(/\s*/, $form->{"taxaccounts"})) {
- map({ delete($form->{"${accno}_${_}"}); } qw(rate description taxnumber));
+ map({ delete($form->{"${accno}_${_}"}); } qw(rate description taxnumber tax_id)); # add tax_id ?
}
$form->{"taxaccounts"} = "";
# TODO: both of these are makeshift so that price sources can operate on rdbo objects. if
# this ever gets rewritten in controller style, throw this out
sub _make_record_item {
- my ($row) = @_;
+ my ($row, %params) = @_;
my $class = {
sales_order => 'OrderItem',
if ($obj->meta->column($method)->isa('Rose::DB::Object::Metadata::Column::Date')) {
$obj->${\"$method\_as_date"}($value);
} elsif ((ref $obj->meta->column($method)) =~ /^Rose::DB::Object::Metadata::Column::(?:Numeric|Float|DoublePrecsion)$/) {
- $obj->${\"$method\_as_number"}($value);
+ $obj->${\"$method\_as_number"}(($value // '') eq '' ? undef : $value);
} elsif ((ref $obj->meta->column($method)) =~ /^Rose::DB::Object::Metadata::Column::Boolean$/) {
$obj->$method(!!$value);
+ } elsif ((ref $obj->meta->column($method)) =~ /^Rose::DB::Object::Metadata::Column::(?:Big)?(?:Int(?:eger)?|Serial)$/) {
+ $obj->$method(($value // '') eq '' ? undef : $value * 1);
} else {
$obj->$method($value);
}
+
+ if ($method eq 'discount') {
+ $obj->discount($obj->discount / 100.0);
+ }
+
} else {
$obj->{__additional_form_attributes}{$method} = $value;
}
$obj->part(SL::DB::Part->load_cached($::form->{"id_$row"}));
}
+ if ($obj->can('qty')) {
+ $obj->qty( $obj->qty * $params{factor});
+ $obj->base_qty($obj->base_qty * $params{factor});
+ }
+
return $obj;
}
: do { die 'unknown invoice type' };
}
+ my $factor = $::form->{type} =~ m{credit_note} ? -1 : 1;
+
return unless $class;
$class = 'SL::DB::' . $class;
if ($obj->meta->column($method)->isa('Rose::DB::Object::Metadata::Column::Date')) {
$obj->${\"$method\_as_date"}($::form->{$method});
} elsif ((ref $obj->meta->column($method)) =~ /^Rose::DB::Object::Metadata::Column::(?:Numeric|Float|DoublePrecsion)$/) {
- $obj->${\"$method\_as_number"}($::form->{$method});
+ $obj->${\"$method\_as_number"}(($::form->{$method} // '') eq '' ? undef : $::form->{$method});
} elsif ((ref $obj->meta->column($method)) =~ /^Rose::DB::Object::Metadata::Column::Boolean$/) {
$obj->$method(!!$::form->{$method});
+ } elsif ((ref $obj->meta->column($method)) =~ /^Rose::DB::Object::Metadata::Column::(?:Big)?(?:Int(?:eger)?|Serial)$/) {
+ $obj->$method(($::form->{$method} // '') eq '' ? undef : $::form->{$method} * 1);
} else {
$obj->$method($::form->{$method});
}
my @items;
for my $i (1 .. $::form->{rowcount}) {
next unless $::form->{"id_$i"};
- push @items, _make_record_item($i);
+ push @items, _make_record_item($i, factor => $factor);
}
$obj->items(@items) if @items;
$obj->is_sales(!!$obj->customer_id) if $class eq 'SL::DB::DeliveryOrder';
+ if ($class eq 'SL::DB::Invoice') {
+ my $paid = $factor *
+ sum
+ map { $::form->parse_amount(\%::myconfig, $::form->{$_}) }
+ grep { m{^paid_\d+$} }
+ keys %{ $::form };
+ $obj->paid($paid);
+ }
+
return $obj;
}
$print_form->{printers} = SL::DB::Manager::Printer->get_all_sorted;
$print_form->{languages} = SL::DB::Manager::Language->get_all_sorted;
- $print_form->{$_} = $::form->{$_} for qw(type media language_id printer_id storno);
+ $print_form->{$_} = $::form->{$_} for qw(type media language_id printer_id storno formname groupitems);
return SL::Helper::PrintOptions->get_print_options(
form => $print_form,
$email = '' if $::form->{type} eq 'purchase_delivery_order';
+ $::form->{language} = $::form->get_template_language(\%::myconfig);
+ $::form->{language} = "_" . $::form->{language};
+
+ my %body_params = (record_email => $record_email);
+ if (($::form->{type} eq 'invoice') && $::form->{direct_debit}) {
+ $body_params{translation_type} = "preset_text_invoice_direct_debit";
+ $body_params{fallback_translation_type} = "preset_text_invoice";
+ }
+
my $email_form = {
to => $email,
cc => $email_cc,
subject => $::form->generate_email_subject,
- message => $::form->generate_email_body('record_email' => $record_email),
+ message => $::form->generate_email_body(%body_params),
attachment_filename => $::form->generate_attachment_filename,
js_send_function => 'kivi.SalesPurchase.send_email()',
};
print $::form->redirect_header($script . '?action=edit&id=' . $::form->escape($id) . '&type=' . $::form->escape($type));
}
+
+sub _maybe_attach_zugferd_data {
+ my ($form) = @_;
+
+ my $record = _make_record();
+
+ return 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;
+
+ eval {
+ my $xmlfile = File::Temp->new;
+ $xmlfile->print($record->create_zugferd_data);
+ $xmlfile->close;
+
+ $form->{TEMPLATE_DRIVER_OPTIONS}->{pdf_a} = $record->create_pdf_a_print_options(zugferd_xmp_data => $record->create_zugferd_xmp_data);
+ $form->{TEMPLATE_DRIVER_OPTIONS}->{pdf_attachments} = [
+ { source => $xmlfile,
+ name => 'ZUGFeRD-invoice.xml',
+ description => $::locale->text('ZUGFeRD invoice'),
+ relationship => '/Alternative',
+ mime_type => 'text/xml',
+ }
+ ];
+ };
+
+ if (my $e = SL::X::ZUGFeRDValidation->caught) {
+ $::form->error($e->message);
+ }
+}
shiptoemail shiptodepartment_1 shiptodepartment_2 message email subject cc bcc taxaccounts cursor_fokus
convert_from_do_ids convert_from_oe_ids convert_from_ap_ids show_details gldate useasnew
), @custom_hiddens,
- map { $_.'_rate', $_.'_description', $_.'_taxnumber' } split / /, $form->{taxaccounts}];
+ map { $_.'_rate', $_.'_description', $_.'_taxnumber', $_.'_tax_id' } split / /, $form->{taxaccounts}];
$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};
invoice_id
show_details
), @custom_hiddens,
- map { $_.'_rate', $_.'_description', $_.'_taxnumber' } split / /, $form->{taxaccounts}];
+ map { $_.'_rate', $_.'_description', $_.'_taxnumber', $_.'_tax_id' } split / /, $form->{taxaccounts}];
$::request->{layout}->use_javascript(map { "${_}.js" } qw(kivi.Draft kivi.File kivi.SalesPurchase kivi.Part kivi.CustomerVendor kivi.Validator ckeditor/ckeditor ckeditor/adapters/jquery kivi.io client_js));
for my $i (1 .. $form->{paidaccounts}) {
next unless $form->{"paid_$i"};
- map { $form->{"${_}_$i"} = $form->parse_amount(\%myconfig, $form->{"${_}_$i"}) } qw(paid exchangerate);
- if (!$form->{"forex_$i"}) { #read exchangerate from input field (not hidden)
- $form->{exchangerate} = $form->{"exchangerate_$i"};
- }
+ map { $form->{"${_}_$i"} = $form->parse_amount(\%myconfig, $form->{"${_}_$i"}) } qw(paid exchangerate);
$form->{"forex_$i"} = $form->check_exchangerate(\%myconfig, $form->{currency}, $form->{"datepaid_$i"}, 'buy');
$form->{"exchangerate_$i"} = $form->{"forex_$i"} if $form->{"forex_$i"};
}
my $git = SL::Git->new;
($form->{git_head}) = $git->get_log(since => 'HEAD~1', until => 'HEAD') if $git->is_git_installation;
$form->{xmas} = '_xmas' if (DateTime->today->month == 12 && DateTime->today->day < 27);
+ $form->{xmas} = '_corona' if (DateTime->today->month >= 7 && DateTime->today->year == 2020
+ && DateTime->today->month <= 11);
# create the logo screen
$form->header() unless $form->{noheader};
],
action => [
t8('E Mail'),
- call => [ 'kivi.SalesPurchase.show_email_dialog' ],
- checks => [ 'kivi.validate_form' ],
+ call => [ 'kivi.SalesPurchase.show_email_dialog' ],
+ checks => [ 'kivi.validate_form' ],
+ disabled => !$form->{id} ? t8('This record has not been saved yet.') : undef,
],
action => [
t8('Download attachments of all parts'),
taxpart taxservice taxaccounts cursor_fokus
show_details useasnew),
@custom_hiddens,
- map { $_.'_rate', $_.'_description', $_.'_taxnumber' } split / /, $form->{taxaccounts} ]; # deleted: discount
+ map { $_.'_rate', $_.'_description', $_.'_taxnumber', $_.'_tax_id' } split / /, $form->{taxaccounts} ]; # deleted: discount
$TMPL_VAR->{$_} = $type_check_vars{$_} for keys %type_check_vars;
"curr", "employee",
"salesman",
"shipvia", "globalprojectnumber",
- "transaction_description", "open",
+ "transaction_description", "department", "open",
"delivered", "periodic_invoices",
"marge_total", "marge_percent",
"vcnumber", "ustid",
'shipvia' => { 'text' => $locale->text('Ship via'), },
'globalprojectnumber' => { 'text' => $locale->text('Project Number'), },
'transaction_description' => { 'text' => $locale->text('Transaction description'), },
+ 'department' => { 'text' => $locale->text('Department'), },
'open' => { 'text' => $locale->text('Open'), },
'delivered' => { 'text' => $locale->text('Delivery Order created'), },
'marge_total' => { 'text' => $locale->text('Ertrag'), },
%column_defs_cvars,
);
- foreach my $name (qw(id transdate reqdate quonumber ordnumber cusordnumber name employee salesman shipvia transaction_description shippingpoint taxzone insertdate payment_terms)) {
+ foreach my $name (qw(id transdate reqdate quonumber ordnumber cusordnumber name employee salesman shipvia transaction_description shippingpoint taxzone insertdate payment_terms department)) {
my $sortdir = $form->{sort} eq $name ? 1 - $form->{sortdir} : $form->{sortdir};
$column_defs{$name}->{link} = $href . "&sort=$name&sortdir=$sortdir";
}
$config = SL::YAML::Load($::form->{periodic_invoices_config}) if $::form->{periodic_invoices_config};
if ('HASH' ne ref $config) {
- my $lang_id = $::form->{language_id};
$config = { periodicity => 'm',
order_value_periodicity => 'p', # = same as periodicity
start_date_as_date => $::form->{transdate} || $::form->current_date,
extend_automatically_by => 12,
active => 1,
- email_subject => GenericTranslations->get(language_id => $lang_id,
- translation_type =>"preset_text_periodic_invoices_email_subject"),
- email_body => GenericTranslations->get(language_id => $lang_id,
- translation_type =>"preset_text_periodic_invoices_email_body"),
};
}
+ # for older configs, replace email preset text if not yet set.
+ $config->{email_subject} ||= GenericTranslations->get(language_id => $::form->{lanuage_id},
+ translation_type =>"preset_text_periodic_invoices_email_subject");
+ $config->{email_body} ||= GenericTranslations->get(language_id => $::form->{lanuage_id},
+ translation_type =>"preset_text_periodic_invoices_email_body");
$config->{periodicity} = 'm' if none { $_ eq $config->{periodicity} } @SL::DB::PeriodicInvoicesConfig::PERIODICITIES;
$config->{order_value_periodicity} = 'p' if none { $_ eq $config->{order_value_periodicity} } ('p', @SL::DB::PeriodicInvoicesConfig::ORDER_VALUE_PERIODICITIES);
if ($::form->{customer_id}) {
$::form->{ALL_CONTACTS} = SL::DB::Manager::Contact->get_all_sorted(where => [ cp_cv_id => $::form->{customer_id} ]);
+ $::form->{email_recipient_invoice_address} = SL::DB::Manager::Customer->find_by(id => $::form->{customer_id})->invoice_mail;
}
$::form->header(no_layout => 1);
my $arap_id = $vc eq 'customer' ? 'ar_id' : 'ap_id';
my $invoices = SL::SEPA->retrieve_open_invoices(vc => $vc);
- # load all open invoices (again), but grep out the ones that were selected with checkboxes beforehand ($_->selected). At this stage we again have all the invoice information, including dropdown with payment_type options
- # all the information from retrieve_open_invoices is then ADDED to what was passed via @{ $form->{bank_transfers} }
- # parse amount from the entry in the form, but take skonto_amount from PT again
- # the map inserts the values of invoice_map directly into the array of hashes
+ # Load all open invoices (again), but grep out the ones that were selected with checkboxes beforehand ($_->selected).
+ # At this stage we again have all the invoice information, including dropdown with payment_type options.
+ # All the information from retrieve_open_invoices is then ADDED to what was passed via @{ $form->{bank_transfers} }.
+ # Parse amount from the entry in the form, but take skonto_amount from PT again.
+ # The map inserts the values of invoice_map directly into the array of hashes.
my %selected_ids = map { ($_ => 1) } @{ $form->{ids} || [] };
my %invoices_map = map { $_->{id} => $_ } @{ $invoices };
my @bank_transfers =
require "bin/mozilla/common.pl";
-#use strict;
-#no strict 'refs';
-#use diagnostics;
-#use warnings; # FATAL=> 'all';
-#use vars qw($locale $form %myconfig);
-#our ($myconfig);
-#use CGI::Carp "fatalsToBrowser";
-
use List::Util qw(first);
use SL::DB::Default;
}
-
-sub help {
- $::lxdebug->enter_sub();
-
- $::auth->assert('advance_turnover_tax_return');
-
- # parse help documents under doc
- $::form->{templates} = 'doc';
- $::form->{help} = 'ustva';
- $::form->{type} = 'help';
- $::form->{format} = 'html';
- generate_ustva();
-
- $::lxdebug->leave_sub();
-}
-
-sub show {
- $::lxdebug->enter_sub();
-
- $::auth->assert('advance_turnover_tax_return');
-
- #generate_ustva();
- $::lxdebug->leave_sub();
- call_sub($::form->{"nextsub"});
-}
-
sub ustva_vorauswahl {
$::lxdebug->enter_sub();
return $select_vorauswahl;
}
-#sub config {
-# $::lxdebug->enter_sub();
-# config_step1();
-# $::lxdebug->leave_sub();
-#}
-
sub show_options {
$::lxdebug->enter_sub();
$::auth->assert('advance_turnover_tax_return');
my $defaults = SL::DB::Default->get;
- $form->error($::locale->text('No print templates have been created for this client yet. Please do so in the client configuration.')) if !$defaults->templates;
- $form->{templates} = $defaults->templates;
-
my $ustva = USTVA->new();
$ustva->get_config();
$form->{co_city} =~ s/\\n//g;
}
- ################################
- #
- # Nation specific customisations
- #
- ################################
-
- # Germany
-
- if ( $form->{coa} eq 'Germany-DATEV-SKR03EU' or $form->{coa} eq 'Germany-DATEV-SKR04EU') {
-
$form->{id} = [];
$form->{amount} = [];
- if ( $form->{format} eq 'pdf' or $form->{format} eq 'postscript') {
-
- $form->{IN} = "$form->{type}-$form->{year}.tex";
- $form->{padding} = "~~";
- $form->{bold} = "\textbf{";
- $form->{endbold} = "}";
- $form->{br} = '\\\\';
-
- # Zahlenformatierung für Latex USTVA Formulare
-
- foreach my $number (@{$::form->{category_euro}}) {
- $form->{$number} = $form->format_amount(\%myconfig, $form->{$number}, '0', '');
- }
-
- my ${decimal_comma} = ( $myconfig{numberformat} eq '1.000,00'
- or $myconfig{numberformat} eq '1000,00' ) ? ',':'.';
-
- foreach my $number (@{$::form->{category_cent}}) {
- $form->{$number} = $form->format_amount(\%myconfig, $form->{$number}, '2', '');
- $form->{$number} =~ s/${decimal_comma}/~~/g;
- }
-
- } elsif ( $form->{format} eq 'html') { # Formatierungen für HTML Ausgabe
+ if ( $form->{format} eq 'html') { # Formatierungen für HTML Ausgabe
$form->{IN} = $form->{type} . '.html';
$form->{padding} = " ";
foreach my $number (@{$::form->{category_euro}}) {
$form->{$number} = $form->format_amount(\%myconfig, $form->{$number}, '0', '0');
}
- } elsif ( $form->{format} eq '' ){ # No format error.
-
- $form->header;
- USTVA::error( $locale->text('Application Error. No Format given' ) . "!");
- $::dispatcher->end_request;
-
- } else { # All other Formats are wrong
+ } else { # we have only html
$form->header;
USTVA::error( $locale->text('Application Error. Wrong Format') . ": " . $form->{format} );
$::dispatcher->end_request;
- }
-
-
- } else # Outputformat for generic output
- {
-
- $form->{USTVA} = [];
-
- if ( $form->{format} eq 'generic') { # Formatierungen für HTML Ausgabe
-
- my $rec_ref = {};
- for my $kennziffer (@{$::form->{category_cent}}, @{$::form->{category_euro}}) {
- $rec_ref = {};
- $rec_ref->{id} = $kennziffer;
- $rec_ref->{amount} = $form->format_amount(\%myconfig, $form->{$kennziffer}, 2, '0');
-
- $::lxdebug->message($LXDebug::DEBUG, "Kennziffer $kennziffer: '$form->{$kennziffer}'" );
- $::lxdebug->dump($LXDebug::DEBUG, $rec_ref );
- push @ { $form->{USTVA} }, $rec_ref;
- }
-
- }
-
- }
-
+ }
if ( $form->{period} eq '13' and $form->{format} ne 'html') {
$form->header;
USTVA::info(
. '!');
}
- $form->{templates} = "doc" if ( $form->{type} eq 'help' );
-
- if ($form->{format} eq 'generic'){
-
- $form->header();
+ # add a prefix for ustva pos numbers, i.e.: 81 -> post_ustva_81
+ $form->{"pos_ustva_$_"} = $form->{$_} for grep { m{^\d+} } keys %{ $form };
+ $form->{title} = $locale->text('Advance turnover tax return');
- my $template_ref = {
- taxnumber => $defaults->taxnumber,
- };
-
- print($form->parse_html_template('ustva/generic_taxreport', $template_ref));
-
- } elsif ( $form->{format} eq 'elstertaxbird' ) {
- $form->parse_template(\%myconfig);
- } else
- {
- # add a prefix for ustva pos numbers, i.e.: 81 -> post_ustva_81
- $form->{"pos_ustva_$_"} = $form->{$_} for grep { m{^\d+} } keys %{ $form };
- $form->{title} = $locale->text('Advance turnover tax return');
-
- $form->header;
- print $form->parse_html_template('ustva/ustva');
-
-
- }
+ $form->header;
+ print $form->parse_html_template('ustva/ustva');
$::lxdebug->leave_sub();
}
$form->{title} = $locale->text('Sales Report');
@columns =
- qw(description invnumber transdate customernumber customername partnumber partsgroup country business transdate qty parts_unit weight sellprice sellprice_total discount lastcost lastcost_total marge_total marge_percent employee salesman);
+ qw(description invnumber transdate shipvia customernumber customername partnumber partsgroup country business transdate qty parts_unit weight sellprice sellprice_total discount lastcost lastcost_total marge_total marge_percent employee salesman);
my @includeable_custom_variables = grep { $_->{includeable} } @{ $cvar_configs_ic }, @{ $cvar_configs_ct };
my @searchable_custom_variables = grep { $_->{searchable} } @{ $cvar_configs_ic }, @{ $cvar_configs_ct };
'salesman' => { 'text' => $locale->text('Salesperson'), },
'invnumber' => { 'text' => $locale->text('Invoice Number'), },
'transdate' => { 'text' => $locale->text('Invoice Date'), },
+ 'shipvia' => { 'text' => $locale->text('Ship via'), },
'qty' => { 'text' => $locale->text('Quantity'), },
'parts_unit' => { 'text' => $locale->text('Base unit'), },
'weight' => { 'text' => $locale->text('Weight'), },
push @options, $locale->text('Department') . " : " . SL::DB::Department->new(id => $form->{department_id})->load->description if $form->{department_id};
push @options, $locale->text('Invoice Number') . " : $form->{invnumber}" if $form->{invnumber};
push @options, $locale->text('Invoice Date') . " : $form->{invdate}" if $form->{invdate};
+ push @options, $locale->text('Ship via') . " : $form->{shipvia}" if $form->{shipvia};
push @options, $locale->text('Part Number') . " : $form->{partnumber}" if $form->{partnumber};
push @options, $locale->text('Partsgroup') . " : " . SL::DB::PartsGroup->new(id => $form->{partsgroup_id})->load->partsgroup if $form->{partsgroup_id};
push @options, $locale->text('Country') . " : $form->{country}" if $form->{country};
my $sort_col = $form->{sort};
my %filter;
- my @columns = qw(warehousedescription bindescription partnumber type_and_classific partdescription chargenumber bestbefore comment qty partunit purchase_price stock_value);
+ my @columns = qw(warehousedescription bindescription partnumber type_and_classific partdescription chargenumber bestbefore comment qty partunit list_price purchase_price stock_value);
# filter stuff
map { $filter{$_} = $form->{$_} if ($form->{$_}) } qw(warehouse_id bin_id classification_id partnumber description chargenumber bestbefore date include_invalid_warehouses);
$form->{report_generator_output_format} = 'HTML' if !$form->{report_generator_output_format};
# manual paginating
- my $allrows = !!($form->{report_generator_output_format} ne 'HTML') ;
+ my $allrows = $form->{report_generator_output_format} eq 'HTML' ? $form->{allrows} : 1;
my $page = $::form->{page} || 1;
my $pages = {};
$pages->{per_page} = $::form->{per_page} || 20;
my @hidden_variables = map { "l_${_}" } @columns;
push @hidden_variables, qw(warehouse_id bin_id partnumber partstypes_id description chargenumber bestbefore qty_op qty qty_unit partunit l_warehousedescription l_bindescription);
push @hidden_variables, qw(include_empty_bins subtotal include_invalid_warehouses date);
- push @hidden_variables, qw(classification_id);
+ push @hidden_variables, qw(classification_id stock_value_basis);
my %column_defs = (
'warehousedescription' => { 'text' => $locale->text('Warehouse'), },
'partunit' => { 'text' => $locale->text('Unit'), },
'stock_value' => { 'text' => $locale->text('Stock value'), },
'purchase_price' => { 'text' => $locale->text('Purchase price'), },
+ 'list_price' => { 'text' => $locale->text('List Price'), },
);
my $href = build_std_url('action=generate_report', grep { $form->{$_} } @hidden_variables);
map { $column_defs{$_}->{link} = $href . "&page=".$page."&sort=${_}&order=" . Q($_ eq $sort_col ? 1 - $form->{order} : $form->{order}) } @columns;
- my %column_alignment = map { $_ => 'right' } qw(qty purchase_price stock_value);
+ my %column_alignment = map { $_ => 'right' } qw(qty list_price purchase_price stock_value);
map { $column_defs{$_}->{visible} = $form->{"l_${_}"} ? 1 : 0 } @columns;
# 'conv_units' => 'convertible');
$entry->{stock_value} = $form->format_amount(\%myconfig, $entry->{stock_value} * 1, 2);
$entry->{purchase_price} = $form->format_amount(\%myconfig, $entry->{purchase_price} * 1, 2);
+ $entry->{list_price} = $form->format_amount(\%myconfig, $entry->{list_price} * 1, 2);
my $row_set = [ { map { $_ => { 'data' => $entry->{$_}, 'align' => $column_alignment{$_} } } @columns } ];
# 'conv_units' => 'convertible');
$row->{stock_value}->{data} = $form->format_amount(\%myconfig, $subtotals{stock_value} * 1, 2);
$row->{purchase_price}->{data} = $form->format_amount(\%myconfig, $subtotals{purchase_price} * 1, 2);
+ $row->{list_price}->{data} = $form->format_amount(\%myconfig, $subtotals{list_price} * 1, 2);
%subtotals = map { $_ => 0 } @subtotals_columns;
# interface.
admin_password = admin123
-# Which module to use for authentication. Valid values are 'DB' and
-# 'LDAP'. If 'LDAP' is used then users cannot change their password
-# via kivitendo.
+# Which modules to use for authentication. Valid values are 'DB' and
+# 'LDAP'. You can use multiple modules separated by spaces.
+#
+# Multiple LDAP modules with different configurations can be used by
+# postfixing 'LDAP' with the name of the configuration section to use:
+# 'LDAP:ldap_fallback' would use the data from
+# '[authentication/ldap_fallback]'. The name defaults to 'ldap' if it
+# isn't given.
+#
+# Note that the LDAP module doesn't support changing the password.
module = DB
# The cookie name can be changed if desired.
# specified.
#
# tls: Activate encryption via TLS
+# verify: If 'tls' is used, how to verify the server's certificate.
+# Can be one of 'require' or 'none'.
# attribute: Name of the LDAP attribute containing the user's login name
# base_dn: Base DN the LDAP searches start from
# filter: An optional LDAP filter specification. The string '<%login%>'
# If searching the LDAP tree requires user credentials
# (e.g. ActiveDirectory) then these two parameters specify
# the user name and password to use.
+# timeout: Timeout when connecting to the server in seconds.
+#
+# You can specify a fallback LDAP server to use in case the main one
+# isn't reachable by duplicating this whole section as
+# "[authentication/ldap_fallback]".
+#
host = localhost
port = 389
tls = 0
filter =
bind_dn =
bind_password =
+timeout = 10
+verify = require
[system]
# Set language for login and admin forms. Currently "de" (German)
debug = 0
# Chose a system user the daemon should run under when started as root.
run_as =
+# Task servers can run on multiple machines. Each needs its own unique
+# ID. If unset, it defaults to the host name. All but one task server
+# must have 'only_run_tasks_for_this_node' set to 1.
+node_id =
+only_run_tasks_for_this_node = 0
[task_server/notify_on_failure]
# If you want email notifications for failed jobs then set this to a
max-width: 16px;
max-height: 16px;
}
+#update_from_master {
+ cursor: pointer;
+ display: block;
+ max-width: 16px;
+ max-height: 16px;
+}
+#update_from_master:hover {
+ background: #ddd;
+}
/* Bank transactions */
#bank_transactions_proposals .invoice_number_highlight a,
max-width: 16px;
max-height: 16px;
}
+#update_from_master {
+ cursor: pointer;
+ display: block;
+ max-width: 16px;
+ max-height: 16px;
+}
+#update_from_master:hover {
+ background: darkgrey;
+}
/* Bank transactions */
#bank_transactions_proposals .invoice_number_highlight a,
===================================================
+Die Abwärtskompatibilität zur Lagermengen-Berechnung in Lieferscheinen wurde
+aufgehoben. Wer nicht mit Workflows arbeitet (nicht empfohlen) muss diese
+explizit in der Mandantenkonfiguration wieder aktivieren.
+
+
** BITTE FERTIGEN SIE VOR DEM UPGRADE EIN BACKUP IHRER DATENBANK(EN) AN! **
+Upgrade auf v3.5.6
+
+In dieser Version sind die Mehrwertsteueranpassungen für den SKR03 und SKR04
+ab 1.7.2020 vorhanden. Wer diese Anpassungen schon manuell eingestellt hat, sollte
+die Upgrade-Skripte deaktivieren.
+Dies betrifft diese drei Skripte "sql/Pg-upgrade2/konjunkturpaket_2020*"
+
+Folgender sed-Einzeiler erledigt das:
+
+ sed -i 's/ignore: 0/ignore: 1/g' sql/Pg-upgrade2/konjunkturpaket_2020*
+
+Alternativ sollten die Datenbank-Upgrade-Skripte gegen einen Testdatenbestand ausgeführt werden
+und der kivitendo-Dienstleister Ihres Vertrauens griffbereit sein.
+
+Weitere Änderungen:
+
+Für den MT940-Import erwartet kivitendo aqbanking ab Version 6.
+
+Für die Erzeugung von ZUGFeRD 2.0 fähigen PDFs wird ein aktuelles TexLive ab Version 2018 benötigt.
+Details hierzu auch in der Dokumentation (HTML oder Dokumentation.pdf).
+
+Bitte wie immer vor dem Anmelden an der Weboberfläche 'scripts/installation_check.pl -v' ausführen.
+
+Es sollten mindestens zwei Perl-Module "CAM::PDF" und "XML::LibXML" dort erscheinen, falls noch nicht installiert.
+
+Diese Version ist ferner mit Postgres Datenbanken ab Version 12 kompatibel, da die Abhängigkeit von oids entfernt wurde.
+
+Sicherheitshinweis:
+
+Für git-Installation sollte geprüft werden ob das Verzeichnis .git/ für den Webserver auslesbar ist.
+Gleiches gilt für alle Installation für den Ordner config/, der die Datei kivitendo.conf beinhaltet.
+Die Standard-Konfiguration des Apache2 Webservers sollte letzteres verhindern, aber wir weisen darauf hin
+dies einmal zu überprüfen.
+
+Ab dieser Version wird eine globale .htaccess ausgeliefert die beide Verzeichnisse mittels rewrite sichert.
+Dafür muss einmalig das Modul rewrite für den Apache, bspw. mit "a2enmode rewrite" aktiviert werden.
+Regeldetails:
+
+<IfModule mod_rewrite.c>
+ RewriteEngine On
+ RewriteRule .*(\.git|config).*$ - [F,NC]
+</IfModule>
+
+Ferner wurde ein Security-Audit der kivitendo Version 3.1 veröffentlicht.
+Hierfür empfehlen wir die Ausarbeitung eines Sicherheitskonzept mit einem kivitendo Partner Eurer Wahl.
+Falls dies nicht möglich sein sollte, weisen wir darauf hin, dass ein SQL-Backup tages- und wochenaktuell
+für einen etwaigen Restore zu Verfügung stehen sollte. Ferner besteht die Gefahr, dass angemeldete
+Benutzer Formfelder mißbrauchen können, Abhilfe schafft hier zum Beispiel der Einsatz von modsecurity unter
+Apache2 (https://doxsec.wordpress.com/2017/06/11/using-modsecurity-web-application-firewall-to-prevent-sql-injection-and-xss-using-blocking-rules/)
Upgrade auf v3.5.4
# Veränderungen von kivitendo #
###############################
+2020-10-02 - Release 3.5.6.1
+
+
+Mittelgroße neue Features:
+
+ - USTVA: Konjunkturpaket erwarte Pos. 35 und Pos. 36 für Voranmeldung
+ - Währung und Wechselkurs können in der (neuen/experimentellen)
+ Angebots-/Auftrags-Maske angegeben werden. Der Wechselkurs wird hier
+ pro Beleg (und nicht pro Tag) gespeichert.
+ - individuelle Lieferadresse in der (neuen/experimentellen) Angebots-/
+ Auftrags-Maske
+
+Kleinere neue Features und Detailverbesserungen:
+
+ - Beim automatischen Auslagern über die Verkaufsrechnung kann zusätzlich
+ ein Auslagern über das Attribut Seriennummer entspricht Chargennummer
+ gemacht werden. Falls die Beleg-Seriennummer nicht auslagerbar ist wird
+ eine entsprechende Fehlermeldung generiert (einstellbar in der Mandanten-
+ konfiguration).
+ - Zahlungsbedingungen auch in Ek-Rechnung angeben können
+
+
+Bugfixes (Tracker: https://www.kivitendo.de/redmine):
+438 individuelle Lieferadresse gerät beim Speichern durcheinander
+358 segmentation fault in DBI.so beim versenden einer Rechnung per E-Mail
+365 Neuer Order Controller "Individuelle Lieferadresse fehlt"
+ 35 Zahlungsbedingungen bei Lieferanten nicht in EK-Rechnung
+
+2020-07-20 - Release 3.5.6
+
+
+Mittelgroße neue Features:
+
+ - komplette Überarbeitung der Standard-LaTeX-Druckvorlagen von PeiTeX
+ S.a.: templates/print/marei/Readme.md
+
+ - Erstellung von ZUGFeRD 2.0 fähigen PDFs
+ - Verarbeitung von ZUGFeRD 2.0 kompatiblen Eingangsrechnungen über
+ Kreditorenbuchungsvorlagen
+
+ - CSV-Import für Lieferscheine
+
+Kleinere neue Features und Detailverbesserungen:
+
+ - Suche nach Erzeugnissen über die dort verbauten Artikel
+ - neues Flag "natürliche Person" bei Kunden/Lieferanten welches z.B. in den
+ Druckvorlagen für eine Weiche für die Anrede verwendet werden kann.
+ - eigene Tabellen für Anrede von Kunden/Lieferanten und Titel und Abteilung
+ von Ansprechpersonen. Auswahl in Mandantenkonfiguration, ob in den Stammdaten
+ nur eine Auswahlliste angezeigt werden soll, oder wie bisher Freitext-Feld
+ und Auswahlliste. Anrede, Titel und Abteilung können im System-Menü bearbeitet
+ werden.
+ - Kompatibel mit Postgres Version 12 (keine Abhängigkeit von oids mehr)
+ - Leistungszeitraum (Periode) durchgängig in allen Buchungsmasken verfügbar und
+ im DATEV-Export als neues Feld vorhanden
+ - Automatische Kontenrahmen-Anpassungen für Konjunkturpaket des Bundes ab 1.7.2020
+ - die Einfüge-Position beim Hinzufügen von Artikeln in der neuen Angebots-/Auftragsmaske
+ (neuer Auftrags-Controller) kann angegeben werden
+
+Administrative Änderungen
+
+ - Die zwei Perl-Module "CAM::PDF" und "XML::LibXML" werden nun benötigt.
+
+Bugfixes (Tracker: https://www.kivitendo.de/redmine):
+
+436 Kontoauszug verbuchen fehlerhafter Rechnungsbetrag 16%/19% Mehrwertsteuer
+430 Steuer erfassen wirft SQL-Bind Fehler
+428 alte/falsche Tabellen in LaTex-Vorlagen, die package filecontents u. lxtable verwenden
+266 Kontenabgleich mit Bank ist nicht Transaktionssicher
+415 Inkompatibilitäten mit postgres 12
+418 Angebote/Aufträge (alte Maske)/Lieferscheine E-Mail ohne vorher speichern kaputt
+416 Tests datev
+411 Massenerstellen Rechnungen aus Lieferscheinen: Pflege-Commit verloren gegangen
+
+
+2019-12-11 - Release 3.5.5
+
+Mittelgroße neue Features:
+
+- In den Benutzereinstellungen kann ausgewählt werden, ob der Part-Picker in
+ der neuen Angebots-/Auftragsmaske (neuer Auftrags-Controller) auch nach
+ Kunden-Artikelnummern (Verkauf) und Lieferanten-Artikelnummern (Einkauf)
+ suchen soll. Ist dieses Feature eingeschaltet, so werden auch die Kunden-
+ bzw. Lieferanten-Artikelnummern als Spalte in den Positionen angezeigt.
+
+- Part Controller - neuer Tab mit Lagerinformationen - was ist wo gelagert
+
+- Neuer Workflow Lieferantenauftrag->Kreditorenbuchung: Für jedes Aufwandskonto
+ der Positionen im Lieferantenauftrag wird eine Zeile in der Kreditorenbuchung
+ erstellt. Gebucht wird standardmäßig auf das entsprechende Aufwandskonto. In
+ der Mandantenkonfiguration kann unter Standardkonten ein Konto ausgewählt
+ werden, auf das dann alle Zeilen gebucht werden.
+ Die Steuern werden übernommen, sofern diese für das ausgewählte Aufwandskonto
+ gültig sind. Ansonsten wird die Default-Steuer für das Aufwandskonto gesetzt.
+ Der Quellauftrag wird geschlossen, wenn der Betrag aller Kreditorenbuchungen,
+ die aus Workflows aus dem Quellauftrag entstanden sind, gleich dem Betrag
+ des Quellauftrags ist.
+
+- Der Jahresabschluß wurde komplett überarbeitet, es wird nun zwischen
+ Bestands- und Erfolgskonten unterschieden und ein Gewinn- bzw. Verlustvortrag
+ übertragen.
+
+Kleinere neue Features und Detailverbesserungen:
+
+- Mahnungen nach Abteilung filtern
+
+- Anzeige einer Kundenpreisliste in den Kundenstammdaten als Reiter.
+ Hier werden die Preisgruppenpreise angezeigt, falls einem Kunden eine
+ Preisgruppe zugeordnet ist.
+
+- In der neuen Angebots-/Auftragsmaske (neuer Auftrags-Controller) kann
+ ein Update-Knopf angezeigt werden, der die Positionen aus den
+ Artikelstammdaten aktualisiert (alle oder pro Position). Aktualisiert werden
+ Preis, Beschreibung und Langtext. Das Feature kann in den
+ Benutzereinstellungen eingeschaltet werden.
+
+- In der neuen Angebots-/Auftragsmaske (neuer Auftrags-Controller) ist die
+ Artikelnummer ein Link, der die Artikelstammdaten in einem neuen Tab öffnet.
+
+- Neuer Hintergrund-Job, der die Jahreszahl in Nummernkreisen jährlich hochsetzt
+ (Einstellung und Konfiguration s.a. Kapitel 2.7.5 Exemplarische Konf. Hintergrund-Job)
+
+- Weiterleitung zur Zielseite, wenn man ausgeloggt war und sich einloggt.
+ Falls z.B. der Timeout greift, man in der noch geöffneten kivi aber etwas
+ anklickt, so wird man zur Login-Seite weitergeleitet. Vorher landete man nach
+ dem login in einem solchen Fall auf der Startseite (Logo/Version/Todo-Liste).
+ Nun gelangt man zu der Seite, die man ursprünglich angeklickt hat (nur
+ POST-Requests).
+ Das kann z.B. auch dazu verwendet werden, jmd. einen Link in der kivi (z.B. zu
+ einem Auftrag) zu schicken. Wenn derjenige nicht eingeloggt ist, gelangt er
+ nach dem Login dennoch auf die Zielseite.
+
+Bugfixes (Tracker: https://www.kivitendo.de/redmine):
+
+407 Test ./t/db_helper/with_transaction.t läuft nicht durch; Rose-Fehlermeldung nur "generic exception"
+406 abzurechnender (Netto-)Betrag bei Aufträgen rechnet falsch wenn Rechnungs-Gutschriften vorhanden sind
+379 Einkauf Lieferanten-Artikelnummer in zweiter (erster) Spalte anzeigen
+377 PartPicker-Suche im Einkauf um Hersteller-Artikelnummer erweitern
+
+
2019-08-07 - Release 3.5.4
Das Feld wird beim Bericht mitexportiert
- Kundenstammdaten um Feld E-Mail Rechnungsempfänger erweitert
Viele Kunden besitzen für den Rechnungseingang eine generische E-Mail-Adresse, die nicht
- mit der allgemeine E-Mail-Adresse identisch ist. Falls dieses Feld gesetzt ist, so hat dieser
+ mit der allgemeinen E-Mail-Adresse identisch ist. Falls dieses Feld gesetzt ist, so hat dieser
Wert beim manuellen E-Mail Versand der Rechnung Priorität (mandantenweit konfigurierbar).
Für die wiederkehrende Rechnung wird diese E-Mail-Adresse zusätzlich gesetzt.
- In den entsprechenden vorgelagerten Masken, wird dies auch visuell angezeigt (nicht bei alter Auftragsmaske!).
+ In den entsprechenden vorgelagerten Masken, wird dies auch visuell angezeigt (nicht bei alter Auftragsmaske!).
+- Kundenstammdaten um Feld "Herkunft der personenbezogenen Daten" erweitert
+ Um Details zum Erstkontakt des Kunden zu erfassen.
- Kundenstammdaten um Feld Amtsgericht erweitert
Falls das Feld Steuernummer mit dem Wert der Hr-Nr gefüllt wurde, wird auch das zuständige
Registierungs-Gericht benötigt.
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
<book id="kivitendo-documentation" lang="de">
- <title>kivitendo 3.5.4: Installation, Konfiguration,
+ <title>kivitendo 3.5.6.1: Installation, Konfiguration,
Entwicklung</title>
<chapter id="Aktuelle-Hinweise">
<itemizedlist>
<listitem>
<para>im Community-Forum: <ulink
- url="https://forum.kivitendo.de:32443">https://forum.kivitendo.de:32443</ulink></para>
+ url="https://forum.kivitendo.de">https://forum.kivitendo.de</ulink></para>
</listitem>
<listitem>
ohne große Probleme auf den derzeit aktuellen verbreiteten
Distributionen läuft.</para>
- <para>Anfang 2019 sind das folgende Systeme, von denen bekannt ist,
- dass kivitendo auf ihnen läuft:</para>
+ <para>Mitte 2020 (ab Version 3.5.6) empfehlen wir:</para>
<itemizedlist>
<listitem>
<itemizedlist>
<listitem>
- <para>8.0 "Jessie"</para>
- </listitem>
- <listitem>
- <para>9.0 "Stretch"</para>
+ <para>10.0 "Buster"</para>
</listitem>
<listitem>
- <para>10.0 "Buster"</para>
+ <para>11.0 "Bullseye"</para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
- <para>16.04 "Xenial Xerus" LTS und 18.04 "Bionic Beaver" LTS
+ <para>20.04 "Focal Fossa" LTS
</para>
</listitem>
<listitem>
- <para>openSUSE 15.0</para>
+ <para>openSUSE Leap 15.x und SUSE Linux Enterprise Server 15 GA</para>
</listitem>
<listitem>
<para><literal>Archive::Zip</literal></para>
</listitem>
+ <listitem>
+ <para><literal>CAM::PDF</literal></para>
+ </listitem>
+
<listitem>
<para><literal>CGI</literal></para>
</listitem>
<para><literal>XML::Writer</literal></para>
</listitem>
+ <listitem>
+ <para><literal>XML::LibXML</literal></para>
+ </listitem>
+
<listitem>
<para><literal>YAML::XS</literal> oder <literal>YAML</literal></para>
</listitem>
</itemizedlist>
+
+ <para>Seit Version größer v3.5.6 sind die folgenden Pakete hinzugekommen: <literal>XML::LibXML</literal>, <literal>CAM::PDF</literal></para>
<para>Seit Version größer v3.5.3 sind die folgenden Pakete hinzugekommen: <literal>Exception::Class</literal></para>
<para>Seit Version größer v3.5.1 sind die folgenden Pakete hinzugekommen: <literal>Set::Infinite</literal>,
libdatetime-set-perl libset-infinite-perl liblist-utilsby-perl\
libdaemon-generic-perl libfile-flock-perl libfile-slurp-perl\
libfile-mimeinfo-perl libpbkdf2-tiny-perl libregexp-ipv6-perl \
- libdatetime-event-cron-perl libexception-class-perl
+ libdatetime-event-cron-perl libexception-class-perl libcam-pdf-perl \
+ libxml-libxml-perl
</programlisting>
<para>Sollten Pakete nicht zu Verfügung stehen, so können diese auch mittels CPAN installiert werden. Ferner muss für Ubuntu das Repository "Universe" aktiv sein (s.a. Anmerkungen).</para>
<note id="ubuntu-universe">
<para>Die Perl Pakete für Ubuntu befinden sich im "Universe" Repository. Falls dies nicht aktiv ist, kann dies mit folgendem Aufruf aktiviert werden:
<programlisting>add-apt-repository universe</programlisting></para>
</note>
- <note id="build-essential">
- <para>Für ältere Ubuntu/Debians müßen einige Pakete per CPAN installiert werden.
- Das geht bspw. für das benötige Paket HTML::Restrict mit:</para>
- <programlisting>apt-get install build-essential
-cpan HTML::Restrict</programlisting>
- </note>
</sect3>
<sect3>
</sect3>
<sect3>
- <title>openSUSE</title>
+ <title>openSUSE Leap 15.x und SUSE Linux Enterprise Server 15 GA</title>
- <para>Für openSUSE stehen die meisten der benötigten Perl-Pakete als RPM-Pakete zur Verfügung.</para>
- <para>Dies setzt voraus, das eben die erforderlichen Repositories dem System bekannt gemacht worden sind.</para>
- <para>Um zusätzliche Repositories für die Installation zur Verfügung zu stellen, kann man diese mit YaST oder auch in einem Terminal auf der Konsole bekannt geben. Wir beschränken uns hier mit der Eingabe auf der Konsole. Da wahrscheinlich für die Administration eine SSH-Verbindung zum Server benutzt wird.</para>
+ <para>Für openSUSE Leap 15.x stehen die meisten der benötigten Perl-Pakete als RPM-Pakete zur Verfügung.</para>
+ <para>Damit diese installiert werden können, muß das System die erforderlichen Repositories kennen und Zugriff über das Internet darauf haben.</para>
+ <para>Daher machen wir die Repositories dem System bekannt.</para>
+ <para>Um die zusätzlichen Repositories für die Installation zur Verfügung zu stellen, kann man diese mit YaST oder auch in einem Terminal auf der Konsole bekannt geben. Wir beschränken uns hier mit der Eingabe auf der Konsole. In den allermeisten Fällen verwenden die Administratoren eine sichere SSH-Verbindung zum zu administrierenden Server.</para>
<para>Dazu geben wir folgenden Befehl ein:</para>
<programlisting>zypper addrepo -f \
- http://download.opensuse.org/repositories/devel:languages:perl/openSUSE_Leap_15.0/ \
+ http://download.opensuse.org/repositories/devel:languages:perl/openSUSE_Leap_15.2/ \
"devel:languages:perl"
</programlisting>
<programlisting>zypper addrepo -f \
<para>Sollte zypper eine Meldung ausgeben, ob der Repositorie Key abgelehnt, nicht vertraut oder für immer akzeptiert werden soll, ist die Beantwortung durch drücken der "i" Taste am besten geeignet. Wer noch mehr über zypper erfahren möchte, kann sich einmal die zypper Hilfe anschauen.</para>
<programlisting>zypper --help</programlisting>
<note>
- <para>Offiziell wird von openSUSE nur noch Versionen ab 15.0 unterstützt. Die SuSE Macher haben ab Version 15.0 einen größen Umbau in der Verwaltung der Pakete vorgenommen, das heißt, der Paketumfang ist der SLES 15 als kleinsten Nenner angepasst. Dies gilt besonders der openSUE Distribution. Es gibt ja einmal die openSUSE Distri und die Professionelle SLES Version. Dadurch sind viele Pakete aus dem Repositorie enfernt worden, aber auch viele auf aktuellen Stand gehalten.</para>
+ <para>Offiziell wird von openSUSE nur noch Versionen ab 15.2 unterstützt. Die SuSE Macher haben ab Version 15.x einen großen Umbau in der Verwaltung der Pakete vorgenommen, das heißt, der Paketumfang ist der SLES 15 als Programmunterbau angepasst. Dies gilt besonders der openSUSE Distribution. Es gibt ja einmal die openSUSE Distri und die Professionelle SLES Version. Dadurch sind viele Pakete aus dem ursprünglich nur für die openSUSE geltenen Repositorie enfernt worden, aber auch viele auf aktuellem Stand gehalten.</para>
</note>
- <para>Das überprüfen wir mit YaST. Sollte openSUSE bis zur Version 15.0 zum Einsatz kommen und der Administrator bei der Installation der Distribution die KDE Oberfläche aktiviert hat, loggen wir uns am Server direkt ein, starten das Verwaltungsprogram in einer Konsole wie folgt:</para>
+ <para>Ab openSUSE Leap 15.x kann man die Distribution auch als reine Text Version, also ohne KDE Oberfläche aufsetzen. Vorteil hierbei ist, dass weniger Balast und unnötige Pakte installiert werden.</para>
+ <para>Bei openSUSE Versionen bis 15.x, also 10.x, 11.x, 12.x, 13.x hatte der Administrator die Möglichkeit, bei der Installation der Distribution die KDE Oberfläche zu aktivieren. In dieser Konstellation hat man die Möglichkeit, eine VNC Verbindung vom administrativen Client zu verwenden. Ist das nicht eingerichtet, arbeitet der Admin dann direkt am Bildschirm des Servers. Nun loggen wir uns am Server direkt ein, starten Yast2 in einer Konsole wie folgt:</para>
<para>yast2 return.</para>
- <para>Oder über die Menüführung wie folgt: Ein Klick auf das runde Icon, ganz links unten in der Menüleiste dann die Maus Verfahren auf System und YaST.</para>
- <para>Sie können mit folgendem Befehl installiert werden:</para>
+ <para>Oder über die Menüführung wie folgt: Ein Klick auf das runde Icon, ganz links unten in der Menüleiste, dann die Maus verfahren auf System und YaST.</para>
+ <para>Im weiteren Verlauf der Installation, beschränken wir uns mit dem Installations Werkzeug zypper. Zypper ist ein Komandozeilen basiertes Installations Tool, welches bei openSUSE Standard ist. Zypper weist ein etwas eigenartiges Verhalten auf, dass sich in etwa wie folgt darstellt. Hat man die Repositories eingerichtet, kann man diese mit Yast als auch mit Zypper benutzen, setzt man einen Befehl wie etwa: zypper up ab, so findet zypper mehr neuere Programmversionen als Yast. Ich habe im allgemeinen noch keine Nachteile damit erlebt.</para>
+ <para>Programmpakete können mit folgendem Befehl installiert werden:</para>
<para>zypper install Paketname</para>
<para>Es wird empfohlen zusätzliche Pakete nicht direkt mit CPAN zu installieren, da man diese auch über andere Repositories beziehen kann, die bei openSUSE zur Verfügung stehen. Dadurch hat man den Vorteil, dass die Pakete mit YaST verwaltet werden, also wieder deinstalliert oder durch neuere ersetzt werden können. Zudem kann man auch noch eventuelle Bugs an openSUSE senden und diese dem Maintainer melden.</para>
perl-Test-LongString perl-File-Find-Rule
</programlisting>
- <para>Zusätzlich müssen einige Pakete für den Umgang mit Latex installiert werden. Die Latex Module barcodes sind nützliche Helfer um auch Barcodes im Dokument zu plazieren, der Vollständigkeit halber hier für die Installation mit angegeben.
+ <para>Zusätzlich müssen einige Pakete für den Umgang mit Latex installiert werden. Die Latex Module barcodes sind nützliche Helfer um auch Barcodes im Dokument zu platzieren, der Vollständigkeit halber hier für die Installation mit angegeben.
Dazu können Sie die folgenden Befehle nutzen:</para>
<programlisting>zypper install texlive-wallpaper texlive-colortbl \
</programlisting>
<para>Zusätzlich müssen einige Pakete aus dem CPAN installiert
- werden. Dazu können Sie die folgenden Befehle nutzen:</para>
+ werden. Dazu können Sie die folgenden Befehle anwenden:</para>
<programlisting>cpan DateTime::event::Cron DateTime::Set FCGI \
HTML::Restrict PBKDF2::Tiny Rose::Db::Object Set::Infinite</programlisting>
<itemizedlist>
<listitem>
- <para><literal>aqbanking-tools</literal> Für das Parsen des MT940 Bankformats</para>
+ <para><literal>aqbanking-tools</literal> Für das Parsen des MT940 Bankformats (Version 6 oder höher)</para>
</listitem>
<listitem>
<para><literal>poppler-utils</literal> 'pdfinfo' zum Erkennen der Seitenanzahl bei der PDF-Generierung</para>
3.4.1 nach 3.5: <programlisting>
$ git clone https://github.com/kivitendo/kivitendo-erp.git
$ cd kivitendo-erp/
-$ git checkout release-3.4.1 # das ist der aktuelle release, den wir wollen
-$ git add templates/fullhouse # das sind unsere druckvorlagen inkl. produktbilder
-$ git commit -m "juhu tolle ändernungen"
+$ git checkout release-3.4.1 # das ist ein alter release aus dem wir starten ...
+$ git checkout -b meine_eigene_änderungen # unser lokaler branch - unabhängig von allen anderen
+$ git add templates/mein_druck # das sind unsere druckvorlagen inkl. produktbilder
+$ git commit -m "juhu tolle änderungen"
+
[meine_aenderungen 1d89e41] juhu tolle ändernungen
4 files changed, 380 insertions(+)
- create mode 100644 templates/fullhouse/img/webdav/tesla.png
- create mode 100644 templates/fullhouse/mahnung.tex
- create mode 100644 templates/fullhouse/zahlungserinnerung_zwei.tex
- create mode 100644 templates/fullhouse/zahlungserinnerung_zwei_invoice.tex
+ create mode 100644 templates/mein_druck/img/webdav/tesla.png
+ create mode 100644 templates/mein_druck/mahnung.tex
+ create mode 100644 templates/mein_druck/zahlungserinnerung_zwei.tex
+ create mode 100644 templates/mein_druck/zahlungserinnerung_zwei_invoice.tex
# 5 Jahre später ...
+# webserver abschalten!
+
+$ git checkout master
+$ git pull # oder git fetch und danach ein stable release tag auswählen (s.o.)
+$ git checkout meine_eigenen_änderungen
+$ git rebase master
-$ git fetch
-$ git rebase --onto release-3.5.0 release-3.4.1 meine_aenderungen
Zunächst wird der Branch zurückgespult, um Ihre Änderungen
darauf neu anzuwenden ...
-Wende an: juhu tolle ändernungen
-$ service apache2 restart
+Wende an: juhu tolle änderungen
+$ service apache2 restart # webserver starten!
</programlisting></para>
</note>
</sect1>
<listitem>
<para>Apache 2.4.29 (Ubuntu 18.04 LTS) und mod_fcgid</para>
</listitem>
+ <listitem>
+ <para>Apache 2.4.41 (Ubuntu 20.04 LTS) und mod_fcgid</para>
+ </listitem>
+
</itemizedlist>
<para>Als Perl Backend wird das Modul <filename>FCGI.pm</filename>
<programlisting>SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1</programlisting>
</sect2>
+ <sect2>
+ <title>Aktivierung von mod_rewrite/directory_match für git basierte Installationen</title>
+
+ <para>
+ Aufgrund von aktuellen (Mitte 2020) Sicherheitswarnungen für git basierte Webanwendungen ist die mitausgelieferte .htaccess
+ restriktiver geworden und verhindert somit das Auslesen von git basierten Daten.
+ Für debian/ubuntu muss das Modul mod_rewrite einmalig so aktiviert werden:
+ <programlisting>a2enmod rewrite</programlisting>
+ Alternativ und für Installationen ohne Apache ist folgender Artikel interessant:
+ <ulink url="https://www.cyberscan.io/blog/git-luecke">git-lücke</ulink>.
+ Anstelle des dort beschriebenen DirectoryMatch für Apache2 würden wir etwas weitergehend auch noch das Verzeichnis config miteinbeziehen
+ sowie ferner auch die Möglichkeit nicht ausschließen, dass es in Unterverzeichnissen auch noch .git Repositories geben kann.
+ Die Empfehlung für Apache 2.4 wäre damit:
+ <programlisting>
+ <DirectoryMatch "/(\.git|config)/">
+ Require all denied
+ </DirectoryMatch></programlisting>
+ </para>
+ </sect2>
+
<sect2>
<title>Weitergehende Konfiguration</title>
url="https://mozilla.github.io/server-side-tls/ssl-config-generator/">
SSL-Konfigurations-Generator</ulink>.</para>
</sect2>
+ <sect3>
+ <title>Aktivierung von Apache2 modsecurity</title>
+
+ <para>Aufgrund des OpenSource Charakters ist kivitendo nicht "out of the box" sicher.
+ Organisatorisch empfehlen wir hier die enge Zusammenarbeit mit einem kivitendo Partner der auch in der
+Lage ist weiterführende Fragen in Bezug auf Datenschutz und Datensicherheit zu beantworten.
+Unabhängig davon empfehlen wir im Webserver Bereich die Aktivierung und Konfiguration des Moduls modsecurity für den Apache2, damit
+XSS und SQL-Injections verhindert werden.</para>
+<para> Als Idee hierfür sei dieser Blog-Eintrag genannt:
+<ulink url="https://doxsec.wordpress.com/2017/06/11/using-modsecurity-web-application-firewall-to-prevent-sql-injection-and-xss-using-blocking-rules/">
+ Test Apache2 modsecurity for SQL Injection</ulink>.</para>
+ </sect3>
+
</sect1>
<sect1 id="config.task-server">
<listitem>
<para><literal>status</literal> berichtet, ob der Task-Server
läuft.</para>
- </listitem>
+ </listitem>yy
</itemizedlist>
<para>Der Task-Server wechselt beim Starten automatisch in das
so startet dieser nach Beendigung automatisch erneut.</para>
</sect2>
- </sect1>
+ <sect2 id="Tasks konfigurieren">
+ <title>Exemplarische Konfiguration eines Hintergrund-Jobs, der die Jahreszahl in allen Nummernkreisen zum Jahreswechsel erhöht</title>
+ <para>Hintergrund-Jobs werden über System -> Hintergrund-Jobs und Task-Server -> Aktuelle Hintergrund-Jobs anzeigen -> Aktions-Knopf 'erfassen' angelegt. </para>
+ <para>Nachdem wir über das Menü dort angelangt sind, legen wir unseren exemplarischen Hintergrund-Jobs "Erhöhung der Nummernkreise" mit folgenden Werten an:</para>
+ <itemizedlist>
+ <listitem>
+ <para><literal>Aktiv:</literal> Hier ein 'Ja' auswählen</para>
+ </listitem>
+ <listitem>
+ <para><literal>Ausführungsart:</literal> 'wiederholte Ausführung' auswählen</para>
+ </listitem>
+ <listitem>
+ <para><literal>Paketname:</literal> 'SetNumberRange' auswählen</para>
+ </listitem>
+ <listitem>
+ <para><literal>Ausführungszeitplan:</literal> Hier entsprechend Werte wie in der crontab eingeben.</para><para>Syntax:</para>
+<programlisting>* * * * *
+┬ ┬ ┬ ┬ ┬
+│ │ │ │ │
+│ │ │ │ └──── Wochentag (0-7, Sonntag ist 0 oder 7)
+│ │ │ └────── Monat (1-12)
+│ │ └──────── Tag (1-31)
+│ └────────── Stunde (0-23)
+└──────────── Minute (0-59) </programlisting>
+ <para>Die Sterne können folgende Werte haben:</para>
+ <programlisting>
+1 2 3 4 5
+
+1 = Minute (0-59)
+2 = Stunde (0-23)
+3 = Tag (0-31)
+4 = Monat (1-12)
+5 = Wochentag (0-7, Sonntag ist 0 oder 7)
+</programlisting>
+<para>Um die Ausführung auf eine Minute vor Sylvester zu setzen, müssen die folgenden Werte eingetragen werden:</para>
+<programlisting>59 23 31 12 *</programlisting>
+ </listitem>
+ <listitem>
+ <para><literal>Daten:</literal>In diesem Feld können optionale Parameter für den Hintergrund im JSON-Format gesetzt werden. Der Hintergrund-Job <literal>SetNumberRange</literal> akzeptiert zwei Variable nämlich <literal>digit_year</literal> sowieso <literal>multiplier</literal>.</para><para> <literal>digit_year</literal> kann zwei Werte haben entweder 2 oder 4, darüber wird gesteuert ob die Jahreszahl zwei oder vierstellig kodiert wird (für 2019, dann entweder 19 oder 2019). Der Standardwert ist vierstellig.</para><para> <literal>multiplier</literal> ist ein Vielfaches von 10, darüber wird die erste Nummer im Nummernkreis (die Anzahl der Stellen) wie folgt bestimmt:</para>
+<programlisting>
+multiplier Nummernkreis 2020
+10 -> 20200
+100 -> 202000
+1000 -> 2020000
+</programlisting>
+<para>Wir gehen jetzt beispielhaft von einer letzten Rechnungsnummer von RE2019456 aus. Demnach sollte ab Januar 2020 die erste Nummer RE2020001 sein. Da der Task auch Präfixe berücksichtigt, kann dies mit folgenden JSON-kodierten Werten umgesetzt werden:</para>
+<literal>Daten:</literal><programlisting>multiplier: 100
+digits_year: 4</programlisting>
+ </listitem>
+ </itemizedlist>
+ </sect2>
+ </sect1>
<sect1 id="Benutzerauthentifizierung-und-Administratorpasswort">
<title>Benutzerauthentifizierung und Administratorpasswort</title>
Datenbank, in der sowohl die Benutzerinformationen als auch die Daten
abgelegt werden.</para>
- <para>Zusätzlich ermöglicht es kivitendo, dass die Benutzerpasswörter
- entweder gegen die Authentifizierungsdatenbank oder gegen einen
- LDAP-Server überprüft werden.</para>
+ <para>Zusätzlich ermöglicht es kivitendo, dass die Benutzerpasswörter gegen die Authentifizierungsdatenbank oder gegen einen oder
+ mehrere LDAP-Server überprüft werden.</para>
<para>Welche Art der Passwortüberprüfung kivitendo benutzt und wie
kivitendo die Authentifizierungsdatenbank erreichen kann, wird in der
<title>Passwortüberprüfung</title>
<para>kivitendo unterstützt Passwortüberprüfung auf zwei Arten: gegen
- die Authentifizierungsdatenbank und gegen einen externen LDAP- oder
+ die Authentifizierungsdatenbank und gegen externe LDAP- oder
Active-Directory-Server. Welche davon benutzt wird, regelt der
Parameter <varname>module</varname> im Abschnitt
<varname>[authentication]</varname>.</para>
- <para>Sollen die Benutzerpasswörter in der Authentifizierungsdatenbank
- gespeichert werden, so muss der Parameter <varname>module</varname>
- den Wert <literal>DB</literal> enthalten. In diesem Fall können sowohl
- der Administrator als auch die Benutzer selber ihre Passwörter in
- kivitendo ändern.</para>
+ <para>Dieser Parameter listet die zu verwendenden Authentifizierungsmodule auf. Es muss mindestens ein Modul angegeben werden, es
+ können aber auch mehrere angegeben werden. Weiterhin ist es möglich, das LDAP-Modul mehrfach zu verwenden und für jede Verwendung
+ eine unterschiedliche Konfiguration zu nutzen, z.B. um einen Fallback-Server anzugeben, der benutzt wird, sofern der Hauptserver
+ nicht erreichbar ist.</para>
- <para>Soll hingegen ein externer LDAP- oder Active-Directory-Server
- benutzt werden, so muss der Parameter <varname>module</varname> auf
- <literal>LDAP</literal> gesetzt werden. In diesem Fall müssen
- zusätzliche Informationen über den LDAP-Server im Abschnitt
- <literal>[authentication/ldap]</literal> angegeben werden:</para>
+ <para>Sollen die Benutzerpasswörter in der Authentifizierungsdatenbank geprüft werden, so muss der Parameter
+ <varname>module</varname> das Modul <literal>DB</literal> enthalten. Sofern das Modul in der Liste enthalten ist, egal an welcher
+ Position, können sowohl der Administrator als auch die Benutzer selber ihre Passwörter in kivitendo ändern.</para>
+
+ <para>Wenn Passwörter gegen einen oder mehrere externe LDAP- oder Active-Directory-Server geprüft werden, so muss der Parameter
+ <varname>module</varname> den Wert <literal>LDAP</literal> enthalten. In diesem Fall müssen zusätzliche Informationen über den
+ LDAP-Server im Abschnitt <literal>[authentication/ldap]</literal> angegeben werden. Das Modul kann auch mehrfach angegeben werden,
+ wobei jedes Modul eine eigene Konfiguration bekommen sollte. Der Name der Konfiguration wird dabei mit einem Doppelpunkt getrennt an
+ den Modulnamen angehängt (<literal>LDAP:Name-der-Konfiguration</literal>). Der entsprechende Abschnitt in der Konfigurationsdatei
+ lautet dann <literal>[authentication/Name-der-Konfiguration]</literal>.</para>
+
+ <para>Die verfügbaren Parameter für die LDAP-Konfiguration lauten:</para>
<variablelist>
<varlistentry>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>verify</literal></term>
+
+ <listitem>
+ <para>Wenn Verbindungsverschlüsselung gewünscht und der Parameter <parameter>tls</parameter> gesetzt ist, so gibt dieser
+ Parameter an, ob das Serverzertifikat auf Gültigkeit geprüft wird. Mögliche Werte sind <literal>require</literal> (Zertifikat
+ wird überprüft und muss gültig sei; dies ist der Standard) und <literal>none</literal> (Zertifikat wird nicht
+ überpfüft).</para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>attribute</literal></term>
also ‘<literal>Martin Mustermann</literal>’.</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><literal>timeout</literal></term>
+
+ <listitem>
+ <para>Timeout beim Verbindungsversuch, bevor der Server als nicht erreichbar gilt; Standardwert: 10</para>
+ </listitem>
+ </varlistentry>
</variablelist>
</sect2>
texlive-collection-latexrecommended texlive-collection-langgerman \
texlive-collection-langenglish</programlisting></para>
+ <note>kivitendo erwartet eine aktuelle TeX Live Umgebung, um PDF/A zu erzeugen. Aktuelle Distributionen von 2020 erfüllen diese. Überprüfbar ist dies mit dem Aufruf des installation_check.pl mit Parameter -l: <programlisting>scripts/installations_check.pl -l</programlisting> </note>
<para>kivitendo bringt drei alternative Vorlagensätze mit:</para>
<itemizedlist>
<listitem>
<para>RB</para>
</listitem>
-
<listitem>
- <para>f-tex</para>
+ <para>marei</para>
</listitem>
-
<listitem>
<para>rev-odt</para>
</listitem>
</itemizedlist>
- <para>Der ehemalige Druckvorlagensatz "Standard" wurde mit der Version
- 3.3 entfernt, da er nicht mehr gepflegt wurde.</para>
+ <para>Der ehemalige Druckvorlagensatz "f-tex" wurde mit der Version
+ 3.6 entfernt, da er nicht mehr gepflegt wird.</para>
<sect2 id="Vorlagenverzeichnis-anlegen"
xreflabel="Vorlagenverzeichnis anlegen">
<listitem>
<para><option>Vorlagen auswählen</option>: Wählen Sie hier den
Vorlagensatz aus, der kopiert werden soll
- (<filename>RB</filename>, <filename>f-tex</filename> oder
+ (<filename>RB</filename>, <filename>marei</filename> oder
<filename>odt-rev</filename>.)</para>
</listitem>
</itemizedlist>
</sect2>
- <sect2 id="f-tex">
- <title>f-tex</title>
-
- <para>Ein Vorlagensatz, der in wenigen Minuten alle Dokumente zur
- Verfügung stellt.</para>
-
- <sect3 id="f-tex-Feature-Übersicht">
- <title>Feature-Übersicht</title>
-
- <itemizedlist>
- <listitem>
- <para>Keine Redundanz. Es wird ein- und dieselbe LaTeX-Vorlage
- für alle briefartigen Dokumente verwendet. Also Angebot,
- Rechnung, Proformarechnung, Lieferschein, aber eben nicht für
- Paketaufkleber etc.</para>
- </listitem>
-
- <listitem>
- <para>Leichte Anpassung an das Firmen-Layout durch Verwendung
- eines Hintergrund-PDFs. Dieses kann leicht mit dem eigenen
- Lieblingsprogramm erstellt werden (Openoffice, Inkscape, Gimp,
- Adobe*)</para>
- </listitem>
-
- <listitem>
- <para>Hintergrund-PDF umschaltbar auf "nur erste Seite"
- (Standard) oder "alle Seiten" (Option
- "<option>bgPdfFirstPageOnly</option>" in Datei
- <filename>letter.lco</filename>)</para>
- </listitem>
-
- <listitem>
- <para>Hintergrund-PDF für Ausdruck auf bereits bedrucktem
- Briefpapier abschaltbar. Es wird dann nur bei per E-Mail
- versendeten Dokumenten eingebunden (Option
- "<option>bgPdfEmailOnly</option>" in Datei
- <filename>letter.lco</filename>).</para>
- </listitem>
-
- <listitem>
- <para>Nutzung der Layout-Funktionen von LaTeX für Seitenumbruch,
- Wiederholung von Kopfzeilen, Zwischensummen etc. (danke an
- Kai-Martin Knaak für die Vorarbeit)</para>
- </listitem>
-
- <listitem>
- <para>Anzeige des Empfängerlandes im Adressfeld nur, wenn es vom
- Land des eigenen Unternehmens abweicht (also die Rechnung das
- Land verlässt).</para>
- </listitem>
-
- <listitem>
- <para>Multisprachfähig leicht um weitere Sprachen zu erweitern,
- alle Übersetzungen in der Datei
- <filename>translatinos.tex</filename>.</para>
- </listitem>
-
- <listitem>
- <para>Auflistung von Bruttopreisen für Endverbraucher.</para>
- </listitem>
- </itemizedlist>
- </sect3>
-
- <sect3 id="f-tex-Installation">
- <title>Die Installation</title>
-
- <itemizedlist>
- <listitem>
- <para>Vorlagenverzeichnis mit Option f-tex anlegen, siehe: <xref
- linkend="Vorlagenverzeichnis-anlegen"/>. Das Vorlagensystem
- funktioniert jetzt schon, hat allerdings noch einen
- Beispiel-Briefkopf.</para>
- </listitem>
-
- <listitem>
- <para>Erstelle eine pdf-Hintergrund Datei und verlinke sie nach
- <filename>./letter_head.pdf</filename>.</para>
- </listitem>
-
- <listitem>
- <para>Editiere den Bereich "<option>settings</option>" in der
- datei <filename>letter.lco</filename>.</para>
- </listitem>
- </itemizedlist>
-
- <para>oder etwas detaillierter:</para>
-
- <para>Es wird eine Datei <filename>sample.lco</filename> erstellt
- und diese nach <filename>letter.lco</filename> verlinkt. Eigentlich
- ist dies die Datei die für die firmenspezifischen Anpassungen
- gedacht ist. Da die Einstiegshürde in LaTeX nicht ganz niedrig ist,
- wird in dieser Datei auf ein Hintergrund-PDF verwiesen. Ich empfehle
- über dieses PDF die persönlichen Layoutanpassungen vorzunehmen und
- <filename>sample.lco</filename> unverändert zu lassen. Die Anpassung
- über eine <filename>*.lco</filename>-Datei, die letztlich auf
- <filename>letter.lco</filename> verlinkt ist ist aber auch
- möglich.</para>
-
- <para>Es wird eine Datei <filename>sample_head.pdf</filename> mit
- ausgeliefert, diese wird nach <filename>letter_head.pdf</filename>
- verlinkt. Damit gibt es schon mal eine funktionsfähige Vorlage.
- Schau Dir nach Abschluss der Installation die Datei
- <filename>sample_head.pdf</filename> an und erstelle ein
- entsprechendes PDF passend zum Briefkopf Deiner Firma, diese dann im
- Template Verzeichniss ablegen und statt
- <filename>sample_head.pdf</filename> nach
- <filename>letter_head.pdf</filename> verlinken.</para>
-
- <para>Letzlich muss <filename>letter_head.pdf</filename> auf das
- passende Hintergrund-PDF verweisen, welches gewünschten Briefkopf
- enthält.</para>
-
- <para>Es wird eine Datei <filename>mydata.tex.example</filename>
- ausgeliefert, die nach <filename>mytdata.tex</filename> verlinkt
- ist. Bei verwendetem Hintergrund-PDF wird nur der Eintrag für das
- Land verwendet. Die Datei muss also nicht angefasst werden. Die
- anderen Werte sind für das Modul 'lp' (Label Print in erp - zur Zeit
- nicht im öffentlichen Zweig).</para>
-
- <para>Alle Anpassungen zum Briefkopf, Fusszeilen, Firmenlogos, etc.
- sollten über die Hintergrund-PDF-Datei oder die
- <filename>*.lco</filename>-Datei erfolgen.</para>
- </sect3>
-
- <sect3 id="f-tex-Funktionsübersicht">
- <title>f-tex Funktionsübersicht</title>
-
- <para>Das Konzept von kivitendo sieht vor, für jedes Dokument
- (Auftragsbestätigung, Lieferschein, Rechnung, etc.) eine
- LaTeX-Vorlage vorzuhalten, dies ist sehr wartungsunfreundlich. Auch
- das Einlesen einer einheitlichen Quelle für den Briefkopf bringt nur
- bedingte Vorteile, da hier leicht die Pflege der Artikel-Tabellen
- aus dem Ruder läuft. Bei dem vorliegenden Ansatz wird für alle
- briefartigen Dokumente mit Artikel-Tabellen eine einheitliche
- LaTeX-Vorlage verwendet, welche über Codeweichen die Besonderheiten
- der jeweiligen Dokumente berücksichtigt:</para>
-
- <itemizedlist>
- <listitem>
- <para>Tabellen mit oder ohne Preis</para>
- </listitem>
-
- <listitem>
- <para>Sprache der Tabellenüberschriften etc.</para>
- </listitem>
-
- <listitem>
- <para>Anpassung der Bezugs-Zeile (z.B. Rechnungsnummer versus
- Angebotsnummer)</para>
- </listitem>
-
- <listitem>
- <para>Darstellung von Brutto oder Netto-Preisen in der
- Auflistung (Endverbraucher versus gewerblicher Kunde)</para>
- </listitem>
- </itemizedlist>
-
- <para>Nachteil:</para>
-
- <para>LaTeX hat ohnehin eine sehr steile Lehrnkurve. Die Datei
- <filename>letter.tex</filename> ist sehr komplex und verstärkt damit
- diesen Effekt noch einmal erheblich. Wer LaTeX-Erfahrung hat, oder
- geübt ist Scriptsparachen nachzuvollziehen kann natürlich auch
- innerhalb der Tabellendarstellung gut persönliche Anpassungen
- vornehmen. Aber man kann sich hier bei Veränderungen sehr schnell
- heftig in den Fuss schiessen.</para>
-
- <para>Wer nicht so tief in die Materie einsteigen will oder leicht
- zu frustrieren ist, sollte sein Hintergrund-PDF auf Basis der
- mitglieferten Datei <filename>sample_head.pdf</filename> erstellen,
- und sich an der Form der dargestellten Tabellen, wie sie
- ausgeliefert werden, erfreuen.</para>
-
- <para>Kleiner Tipp: Nicht zu viel auf einmal wollen, lieber kleine,
- kontinuierliche Schritte gehen.</para>
- </sect3>
-
- <sect3 id="f-tex-Bruttopreise">
- <title>Bruttopreise für Endverbraucher</title>
-
- <para>Der auszuweisende Bruttopreis wird innerhalb der
- LaTeX-Umgebung berechnet. Es gibt zwar ein Feld, um bei Aufträgen
- "alle Preise Brutto" auszuwählen, aber:</para>
-
- <itemizedlist>
- <listitem>
- <para>hierfür müssen die Preise auch in Brutto in der Datenbank
- stehen (ja - das lässt sich über die Preisgruppen und die
- Zuordung einer Default-Preisgruppe handhaben)</para>
- </listitem>
-
- <listitem>
- <para>man darf beim Anlegen des Vorgangs nicht vergessen, dieses
- Häkchen zu setzen. (Das ist in der Praxis, wenn man sowohl
- Endverbraucher als auch Gewerbekunden beliefert, der eigentliche
- Knackpunkt)</para>
- </listitem>
- </itemizedlist>
-
- <para>Es gibt mit f-tex eine weitere Alternative. Die Information ob
- Brutto oder Nettorechnung wird mit den Zahlarten verknüpft.
- Zahlarten bei denen Rechnungen, Angebote, etc, in Brutto ausgegeben
- werden sollen, enden mit "_E" (für Endverbraucher). Falls identische
- Zahlarten für Gewerbekunden und Endverbraucher vorhanden sind, legt
- man diese einfach doppelt an (einmal mit der Namensendung "_E").
- Gewinn:</para>
-
- <itemizedlist>
- <listitem>
- <para>Die Entscheidung, ob Nettopreise ausgewiesen werden, ist
- nicht mehr fix mit einer Preisliste verbunden.</para>
- </listitem>
-
- <listitem>
- <para>Die Default-Zahlart kann im Kundendatensatz hinterlegt
- werden, und man muss nicht mehr daran denken, "alle Preise
- Netto" auszuwählen.</para>
- </listitem>
-
- <listitem>
- <para>Die Entscheidung, ob Netto- oder Bruttopreise ausgewiesen
- werden, kann direkt beim Drucken revidiert werden, ohne dass
- sich der Auftragswert ändert.</para>
- </listitem>
- </itemizedlist>
- </sect3>
-
- <sect3 id="f-tex-lieferadressen">
- <title>Lieferadressen</title>
-
- <para>In Lieferscheinen kommen <varname>shipto*</varname>-Variablen
- im Adressfeld zum Einsatz. Wenn die
- <varname>shipto*</varname>-Variable leer ist, wird die entsprechende
- Adressvariable eingesetzt. Wenn also die Lieferadresse in Straße,
- Hausnummer und Ort abweicht, müssen auch nur diese Felder in der
- Lieferadresse ausgefüllt werden. Für den Firmenname wird der Wert
- der Hauptadresse angezeigt.</para>
- </sect3>
- </sect2>
-
- <sect2 id="Vorlagen-rev-odt">
+ <sect2 id="Vorlagen-rev-odt">
<title>Der Druckvorlagensatz rev-odt</title>
<para>Hierbei handelt es sich um einen Dokumentensatz der mit
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>natural_person</varname></term>
+
+ <listitem>
+ <para>Flag "natürliche Person"; Siehe auch
+ <xref linkend="dokumentenvorlagen-und-variablen.anrede"/></para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>payment_description</varname></term>
<term><varname>longdescription</varname></term>
<listitem>
- <para>Langtext</para>
+ <para>Langtext, vorbelegt mit dem Feld Bemerkungen der entsprechenden Ware</para>
</listitem>
</varlistentry>
<para>Der Befehl <command><bullet></command> funktioniert
momentan auch nur in Latex-Vorlagen.</para>
</sect2>
+
+ <sect2 id="dokumentenvorlagen-und-variablen.anrede"
+ xreflabel="Hinweise zur Anrede">
+ <title>Hinweise zur Anrede</title>
+
+ <para>Das Flag "natürliche Person"
+ (<varname>natural_person</varname>) aus den Kunden- oder
+ Lieferantenstammdaten kann in den Druckvorlagen zusammen mit
+ dem Feld "Anrede" (<varname>greeting</varname>) z.B. dafür
+ verwendet werden, die Anrede zwischen einer allgemeinen und
+ einer persönlichen Anrede zu unterscheiden.
+ <programlisting><%if natural_person%><%greeting%> <%name%><%else%>Sehr geehrte Damen und Herren<%end if%></programlisting>
+ </para>
+ </sect2>
+
</sect1>
<sect1 id="excel-templates">
url="https://developers.shopware.com/developers-guide/rest-api/">https://developers.shopware.com/developers-guide/rest-api/</ulink></para>
</sect2>
</sect1>
+ <sect1 id="features.zugferd">
+ <title>ZUGFeRD Rechnungen</title>
+ <sect2 id="features.zugferd.preamble">
+ <title>Vorbedingung</title>
+ <para>
+ Für die Erstellung von ZUGFeRD PDFs wird TexLive2018 oder höher benötigt.
+ </para>
+ <note>
+ <para>
+ Wer kein TexLive2018 oder höher installieren kann, kann eine lokale Umgebung nur für kivitendo wie folgt erzeugen:
+ </para>
+ <programlisting>
+ 1. Download des offiziellen Installers von https://www.tug.org/texlive/quickinstall.html
+
+ 2. Installer ausführen, Standard-Ort für Installation belassen, evtl. ein paar Pakete abwählen, installieren lassen
+
+ 3. Ein kleine Script »run_pdflatex.sh« anlegen, das den PATH auf das Installationsverezichnis setzt und pdflatex ausführt:
+
+ ------------------------------------------------------------
+ #!/bin/bash
+
+ export PATH=/usr/local/texlive/2020/bin/x86_64-linux:$PATH
+ hash -r
+
+ exec pdflatex "$@"
+ ------------------------------------------------------------
+
+ 4. In config/kivitendo.conf den Parameter »latex« auf den Pfad zu »run_pdflatex.sh« setzen
+
+ 5. Webserver neu starten
+ </programlisting>
+ </note>
+ </sect2>
+ <sect2 id="features.zugferd.summary">
+ <title>Übersicht</title>
+
+ <para>Mit der Version 3.5.6 bietet kivitendo die Möglichkeit ZUGFeRD
+ Rechnungen zu erstellen, sowie auch ZUGFeRD Rechnungen direkt in
+ kivitendo einzulesen. </para>
+
+ <para>Bei ZUGFeRD Rechnungen handelt es sich um eine PDF Datei in
+ der eine XML-Datei eingebettet ist. Der Aufbau der XML-Datei ist
+ standardisiert und ermöglicht so den Austausch zwischen
+ den verschiedenen Softwareprodukten. Kivitendo setzt mit der
+ Version 3.5.6 den ZUGFeRD 2.1 Standard um.</para>
+
+ <para>Weiter Details zu ZUGFeRD sind unter diesem Link zu finden:
+ <ulink>https://www.ferd-net.de/standards/was-ist-zugferd/index.html</ulink>
+ </para>
+ </sect2>
+
+ <sect2 id="features.zugferd.create_zugferd_bills">
+
+ <title>Erstellen von ZUGFeRD Rechnungen in Kivitendo</title>
+ <para>Für die Erstellung von ZUGFeRD Rechnungen bedarf es in
+ kivitendo zwei Dinge:</para>
+
+ <orderedlist>
+
+ <listitem>
+ <para>Die Erstellung muss in der Mandantenkonfiguration
+ aktiviert sein</para>
+ </listitem>
+
+ <listitem>
+ <para>Beim mindestens einem Bankkonto muss die Option
+ „Nutzung von ZUGFeRD“ aktiviert sein</para>
+ </listitem>
+
+ </orderedlist>
+ <sect3>
+ <title>Mandantenkonfiguration</title>
+
+ <para>Die Einstellung für die Erstellung von ZUGFeRD Rechnungen
+ erfolgt unter „System“ → „Mandatenkonfiguration“ → „Features“.
+ Im Abschnitt „Einkauf und Verkauf“ finden Sie die Einstellung
+ „Verkaufsrechnungen mit ZUGFeRD-Daten erzeugen“.
+ Hier besteht die Auswahl zwischen:</para>
+
+ <itemizedlist>
+ <listitem>
+ <para>ZUGFeRD-Rechnungen erzeugen</para>
+ </listitem>
+
+ <listitem>
+ <para>ZUGFeRD-Rechnungen im Testmodus erzeugen</para>
+ </listitem>
+
+ <listitem>
+ <para>Keine ZUGFeRD Rechnungen erzeugen</para>
+ </listitem>
+
+ </itemizedlist>
+
+ <para>Rechnungen die als PDF erzeugt werden, werden je nach
+ Einstellung nun im ZUGFeRD Format ausgegeben.</para>
+
+ </sect3>
+
+ <sect3>
+ <title>Konfiguration der Bankkonten</title>
+
+ <para>Unter „System → Bankkonten“ muss bei mindestens einem
+ Bankkonto die Option „Nutzung mit ZUGFeRD“ auf „Ja“ gestellt
+ werden.</para>
+ </sect3>
+
+ </sect2>
+
+ <sect2 id="features.zugferd.read_zugferd_bills">
+
+ <title>Einlesen von ZUGFeRD Rechnungen in Kivitendo</title>
+
+ <para>Es lassen sich auch Rechnungen von Kreditoren, die im
+ ZUGFeRD Format erstellt wurden, nach Kivitendo importieren.
+ Hierfür müssen auch zwei Voraussetzungen erfüllt werden:
+ </para>
+
+ <orderedlist>
+
+ <listitem>
+ <para>Beim Lieferanten muss die Umsatzsteuer-ID und das
+ Bankkonto hinterlegt sein</para>
+ </listitem>
+
+ <listitem>
+ <para>Für den Kreditoren muss eine Buchungsvorlage existieren.</para>
+ </listitem>
+
+ </orderedlist>
+
+ <para>Wenn diese Voraussetzungen erfüllt sind, kann die Rechnung
+ über „Finanzbuchhaltung“ → „ZUGFeRD Import“ über die „Durchsuchen“
+ Schaltfläche ausgewählt werden und über die Schaltfläche „Import“
+ eingeladen werden. Es öffnet sich daraufhin die Kreditorenbuchung.
+ Die auslesbaren Daten aus dem eingebetteten XML der PDF Datei
+ werden in der Kreditorenbuchung ergänzt.</para>
+
+ </sect2>
+
+ </sect1>
+
</chapter>
<chapter>
Template/OpenDocument
filenames</programlisting>
- <para>The last of which is very machine dependant. Remember that
+ <para>The last of which is very machine dependent. Remember that
a lot of characters are forbidden by some filesystems, for
example MS Windows doesn't like ':' in its files where Linux
doesn't mind that. If you want the files created with your
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>Kapitel 1. Aktuelle Hinweise</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="up" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="prev" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="next" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">Kapitel 1. Aktuelle Hinweise</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="index.html">Zurück</a> </td><th width="60%" align="center"> </th><td width="20%" align="right"> <a accesskey="n" href="ch02.html">Weiter</a></td></tr></table><hr></div><div class="chapter" title="Kapitel 1. Aktuelle Hinweise"><div class="titlepage"><div><div><h2 class="title"><a name="Aktuelle-Hinweise"></a>Kapitel 1. Aktuelle Hinweise</h2></div></div></div><p>Aktuelle Installations- und Konfigurationshinweise gibt es:</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>im Community-Forum: <a class="ulink" href="https://forum.kivitendo.de:32443" target="_top">https://forum.kivitendo.de:32443</a>
+ <title>Kapitel 1. Aktuelle Hinweise</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="prev" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="next" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">Kapitel 1. Aktuelle Hinweise</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="index.html">Zurück</a> </td><th width="60%" align="center"> </th><td width="20%" align="right"> <a accesskey="n" href="ch02.html">Weiter</a></td></tr></table><hr></div><div class="chapter" title="Kapitel 1. Aktuelle Hinweise"><div class="titlepage"><div><div><h2 class="title"><a name="Aktuelle-Hinweise"></a>Kapitel 1. Aktuelle Hinweise</h2></div></div></div><p>Aktuelle Installations- und Konfigurationshinweise gibt es:</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>im Community-Forum: <a class="ulink" href="https://forum.kivitendo.de" target="_top">https://forum.kivitendo.de</a>
</p></li><li class="listitem"><p>im Kunden-Forum: <a class="ulink" href="http://redmine.kivitendo-premium.de/projects/forum/boards/" target="_top">http://redmine.kivitendo-premium.de/projects/forum/boards/</a>
</p></li><li class="listitem"><p>in der doc/UPGRADE Datei im doc-Verzeichnis der
Installation</p></li><li class="listitem"><p>Im Schulungs- und Dienstleistungsangebot der entsprechenden
kivitendo-Partner: <a class="ulink" href="http://www.kivitendo.de/partner.html" target="_top">http://www.kivitendo.de/partner.html</a>
- </p></li></ul></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="index.html">Zurück</a> </td><td width="20%" align="center"> </td><td width="40%" align="right"> <a accesskey="n" href="ch02.html">Weiter</a></td></tr><tr><td width="40%" align="left" valign="top">kivitendo 3.5.4: Installation, Konfiguration,
+ </p></li></ul></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="index.html">Zurück</a> </td><td width="20%" align="center"> </td><td width="40%" align="right"> <a accesskey="n" href="ch02.html">Weiter</a></td></tr><tr><td width="40%" align="left" valign="top">kivitendo 3.5.6.1: Installation, Konfiguration,
Entwicklung </td><td width="20%" align="center"><a accesskey="h" href="index.html">Zum Anfang</a></td><td width="40%" align="right" valign="top"> Kapitel 2. Installation und Grundkonfiguration</td></tr></table></div></body></html>
\ No newline at end of file
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>Kapitel 2. Installation und Grundkonfiguration</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="up" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="prev" href="ch01.html" title="Kapitel 1. Aktuelle Hinweise"><link rel="next" href="ch02s02.html" title="2.2. Benötigte Software und Pakete"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">Kapitel 2. Installation und Grundkonfiguration</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch01.html">Zurück</a> </td><th width="60%" align="center"> </th><td width="20%" align="right"> <a accesskey="n" href="ch02s02.html">Weiter</a></td></tr></table><hr></div><div class="chapter" title="Kapitel 2. Installation und Grundkonfiguration"><div class="titlepage"><div><div><h2 class="title"><a name="config"></a>Kapitel 2. Installation und Grundkonfiguration</h2></div></div></div><div class="sect1" title="2.1. Übersicht"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="Installation-%C3%9Cbersicht"></a>2.1. Übersicht</h2></div></div></div><p>Die Installation von kivitendo umfasst mehrere Schritte. Die
+ <title>Kapitel 2. Installation und Grundkonfiguration</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="prev" href="ch01.html" title="Kapitel 1. Aktuelle Hinweise"><link rel="next" href="ch02s02.html" title="2.2. Benötigte Software und Pakete"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">Kapitel 2. Installation und Grundkonfiguration</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch01.html">Zurück</a> </td><th width="60%" align="center"> </th><td width="20%" align="right"> <a accesskey="n" href="ch02s02.html">Weiter</a></td></tr></table><hr></div><div class="chapter" title="Kapitel 2. Installation und Grundkonfiguration"><div class="titlepage"><div><div><h2 class="title"><a name="config"></a>Kapitel 2. Installation und Grundkonfiguration</h2></div></div></div><div class="sect1" title="2.1. Übersicht"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="Installation-%C3%9Cbersicht"></a>2.1. Übersicht</h2></div></div></div><p>Die Installation von kivitendo umfasst mehrere Schritte. Die
folgende Liste kann sowohl für Neulinge als auch für alte Hasen als
Übersicht und Stichpunktliste zum Abhaken dienen, um eine Version mit
minimalen Features möglichst schnell zum Laufen zu kriegen.</p><div class="orderedlist"><ol class="orderedlist" type="1"><li class="listitem"><p>
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>2.2. Benötigte Software und Pakete</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="next" href="ch02s03.html" title="2.3. Manuelle Installation des Programmpaketes"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.2. Benötigte Software und Pakete</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02.html">Zurück</a> </td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right"> <a accesskey="n" href="ch02s03.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.2. Benötigte Software und Pakete"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="Ben%C3%B6tigte-Software-und-Pakete"></a>2.2. Benötigte Software und Pakete</h2></div></div></div><div class="sect2" title="2.2.1. Betriebssystem"><div class="titlepage"><div><div><h3 class="title"><a name="Betriebssystem"></a>2.2.1. Betriebssystem</h3></div></div></div><p>kivitendo ist für Linux konzipiert, und sollte auf jedem
+ <title>2.2. Benötigte Software und Pakete</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="next" href="ch02s03.html" title="2.3. Manuelle Installation des Programmpaketes"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.2. Benötigte Software und Pakete</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02.html">Zurück</a> </td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right"> <a accesskey="n" href="ch02s03.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.2. Benötigte Software und Pakete"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="Ben%C3%B6tigte-Software-und-Pakete"></a>2.2. Benötigte Software und Pakete</h2></div></div></div><div class="sect2" title="2.2.1. Betriebssystem"><div class="titlepage"><div><div><h3 class="title"><a name="Betriebssystem"></a>2.2.1. Betriebssystem</h3></div></div></div><p>kivitendo ist für Linux konzipiert, und sollte auf jedem
unixoiden Betriebssystem zum Laufen zu kriegen sein. Getestet ist
diese Version im speziellen auf Debian und Ubuntu, grundsätzlich wurde
bei der Auswahl der Pakete aber darauf Rücksicht genommen, dass es
ohne große Probleme auf den derzeit aktuellen verbreiteten
- Distributionen läuft.</p><p>Anfang 2019 sind das folgende Systeme, von denen bekannt ist,
- dass kivitendo auf ihnen läuft:</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Debian</p><div class="itemizedlist"><ul class="itemizedlist" type="circle"><li class="listitem"><p>8.0 "Jessie"</p></li><li class="listitem"><p>9.0 "Stretch"</p></li><li class="listitem"><p>10.0 "Buster"</p></li></ul></div></li><li class="listitem"><p>16.04 "Xenial Xerus" LTS und 18.04 "Bionic Beaver" LTS
- </p></li><li class="listitem"><p>openSUSE 15.0</p></li><li class="listitem"><p>Fedora 29</p></li></ul></div></div><div class="sect2" title="2.2.2. Benötigte Perl-Pakete installieren"><div class="titlepage"><div><div><h3 class="title"><a name="Pakete"></a>2.2.2. Benötigte Perl-Pakete installieren</h3></div></div></div><p>Zum Betrieb von kivitendo werden zwingend ein Webserver (meist
+ Distributionen läuft.</p><p>Mitte 2020 (ab Version 3.5.6) empfehlen wir:</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Debian</p><div class="itemizedlist"><ul class="itemizedlist" type="circle"><li class="listitem"><p>10.0 "Buster"</p></li><li class="listitem"><p>11.0 "Bullseye"</p></li></ul></div></li><li class="listitem"><p>20.04 "Focal Fossa" LTS
+ </p></li><li class="listitem"><p>openSUSE Leap 15.x und SUSE Linux Enterprise Server 15 GA</p></li><li class="listitem"><p>Fedora 29</p></li></ul></div></div><div class="sect2" title="2.2.2. Benötigte Perl-Pakete installieren"><div class="titlepage"><div><div><h3 class="title"><a name="Pakete"></a>2.2.2. Benötigte Perl-Pakete installieren</h3></div></div></div><p>Zum Betrieb von kivitendo werden zwingend ein Webserver (meist
Apache) und ein Datenbankserver (PostgreSQL) in einer aktuellen
Version (s.a. Liste der unterstützten Betriebssysteme)
benötigt.</p><p>Zusätzlich benötigt kivitendo einige Perl-Pakete, die nicht
<code class="literal">Algorithm::CheckDigits</code>
</p></li><li class="listitem"><p>
<code class="literal">Archive::Zip</code>
+ </p></li><li class="listitem"><p>
+ <code class="literal">CAM::PDF</code>
</p></li><li class="listitem"><p>
<code class="literal">CGI</code>
</p></li><li class="listitem"><p>
<code class="literal">URI</code>
</p></li><li class="listitem"><p>
<code class="literal">XML::Writer</code>
+ </p></li><li class="listitem"><p>
+ <code class="literal">XML::LibXML</code>
</p></li><li class="listitem"><p>
<code class="literal">YAML::XS</code> oder <code class="literal">YAML</code>
- </p></li></ul></div><p>Seit Version größer v3.5.3 sind die folgenden Pakete hinzugekommen: <code class="literal">Exception::Class</code>
+ </p></li></ul></div><p>Seit Version größer v3.5.6 sind die folgenden Pakete hinzugekommen: <code class="literal">XML::LibXML</code>, <code class="literal">CAM::PDF</code>
+ </p><p>Seit Version größer v3.5.3 sind die folgenden Pakete hinzugekommen: <code class="literal">Exception::Class</code>
</p><p>Seit Version größer v3.5.1 sind die folgenden Pakete hinzugekommen: <code class="literal">Set::Infinite</code>,
<code class="literal">List::UtilsBy</code>, <code class="literal">DateTime::Set</code>, <code class="literal">DateTime::Event::Cron</code>
sind auch in 2.6.1 weiterhin mit ausgeliefert, wurden in einer
zukünftigen Version aber aus dem Paket entfernt werden. Es wird
empfohlen diese Module zusammen mit den anderen als Bibliotheken zu
- installieren.</p><div class="sect3" title="2.2.2.1. Debian und Ubuntu"><div class="titlepage"><div><div><h4 class="title"><a name="d0e548"></a>2.2.2.1. Debian und Ubuntu</h4></div></div></div><p>Für Debian und Ubuntu stehen die meisten der benötigten
+ installieren.</p><div class="sect3" title="2.2.2.1. Debian und Ubuntu"><div class="titlepage"><div><div><h4 class="title"><a name="d0e565"></a>2.2.2.1. Debian und Ubuntu</h4></div></div></div><p>Für Debian und Ubuntu stehen die meisten der benötigten
Pakete als Debian-Pakete zur Verfügung. Sie können mit
folgendem Befehl installiert werden:</p><pre class="programlisting">apt install apache2 libarchive-zip-perl libclone-perl \
libconfig-std-perl libdatetime-perl libdbd-pg-perl libdbi-perl \
libdatetime-set-perl libset-infinite-perl liblist-utilsby-perl\
libdaemon-generic-perl libfile-flock-perl libfile-slurp-perl\
libfile-mimeinfo-perl libpbkdf2-tiny-perl libregexp-ipv6-perl \
- libdatetime-event-cron-perl libexception-class-perl
+ libdatetime-event-cron-perl libexception-class-perl libcam-pdf-perl \
+ libxml-libxml-perl
</pre><p>Sollten Pakete nicht zu Verfügung stehen, so können diese auch mittels CPAN installiert werden. Ferner muss für Ubuntu das Repository "Universe" aktiv sein (s.a. Anmerkungen).</p><div class="note" title="Anmerkung" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Anmerkung]" src="system/docbook-xsl/images/note.png"></td><th align="left"><a name="ubuntu-universe"></a>Anmerkung</th></tr><tr><td align="left" valign="top"><p>Die Perl Pakete für Ubuntu befinden sich im "Universe" Repository. Falls dies nicht aktiv ist, kann dies mit folgendem Aufruf aktiviert werden:
</p><pre class="programlisting">add-apt-repository universe</pre><p>
- </p></td></tr></table></div><div class="note" title="Anmerkung" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Anmerkung]" src="system/docbook-xsl/images/note.png"></td><th align="left"><a name="build-essential"></a>Anmerkung</th></tr><tr><td align="left" valign="top"><p>Für ältere Ubuntu/Debians müßen einige Pakete per CPAN installiert werden.
- Das geht bspw. für das benötige Paket HTML::Restrict mit:</p><pre class="programlisting">apt-get install build-essential
-cpan HTML::Restrict</pre></td></tr></table></div></div><div class="sect3" title="2.2.2.2. Fedora"><div class="titlepage"><div><div><h4 class="title"><a name="d0e568"></a>2.2.2.2. Fedora</h4></div></div></div><p>Für Fedora stehen die meisten der benötigten Perl-Pakete als
+ </p></td></tr></table></div></div><div class="sect3" title="2.2.2.2. Fedora"><div class="titlepage"><div><div><h4 class="title"><a name="d0e580"></a>2.2.2.2. Fedora</h4></div></div></div><p>Für Fedora stehen die meisten der benötigten Perl-Pakete als
RPM-Pakete zur Verfügung. Sie können mit folgendem Befehl
installiert werden:</p><pre class="programlisting">dnf install httpd mod_fcgid postgresql-server postgresql-contrib\
perl-Algorithm-CheckDigits perl-Archive-Zip perl-CPAN perl-Class-XSAccessor \
perl-Params-Validate perl-Regexp-IPv6 perl-Rose-DB perl-Rose-DB-Object \
perl-Rose-Object perl-Sort-Naturally perl-String-ShellQuote \
perl-Template-Toolkit perl-Text-CSV_XS perl-Text-Iconv perl-URI perl-XML-Writer \
- perl-YAML perl-libwww-perl</pre></div><div class="sect3" title="2.2.2.3. openSUSE"><div class="titlepage"><div><div><h4 class="title"><a name="d0e575"></a>2.2.2.3. openSUSE</h4></div></div></div><p>Für openSUSE stehen die meisten der benötigten Perl-Pakete als RPM-Pakete zur Verfügung.</p><p>Dies setzt voraus, das eben die erforderlichen Repositories dem System bekannt gemacht worden sind.</p><p>Um zusätzliche Repositories für die Installation zur Verfügung zu stellen, kann man diese mit YaST oder auch in einem Terminal auf der Konsole bekannt geben. Wir beschränken uns hier mit der Eingabe auf der Konsole. Da wahrscheinlich für die Administration eine SSH-Verbindung zum Server benutzt wird.</p><p>Dazu geben wir folgenden Befehl ein:</p><pre class="programlisting">zypper addrepo -f \
- http://download.opensuse.org/repositories/devel:languages:perl/openSUSE_Leap_15.0/ \
+ perl-YAML perl-libwww-perl</pre></div><div class="sect3" title="2.2.2.3. openSUSE Leap 15.x und SUSE Linux Enterprise Server 15 GA"><div class="titlepage"><div><div><h4 class="title"><a name="d0e587"></a>2.2.2.3. openSUSE Leap 15.x und SUSE Linux Enterprise Server 15 GA</h4></div></div></div><p>Für openSUSE Leap 15.x stehen die meisten der benötigten Perl-Pakete als RPM-Pakete zur Verfügung.</p><p>Damit diese installiert werden können, muß das System die erforderlichen Repositories kennen und Zugriff über das Internet darauf haben.</p><p>Daher machen wir die Repositories dem System bekannt.</p><p>Um die zusätzlichen Repositories für die Installation zur Verfügung zu stellen, kann man diese mit YaST oder auch in einem Terminal auf der Konsole bekannt geben. Wir beschränken uns hier mit der Eingabe auf der Konsole. In den allermeisten Fällen verwenden die Administratoren eine sichere SSH-Verbindung zum zu administrierenden Server.</p><p>Dazu geben wir folgenden Befehl ein:</p><pre class="programlisting">zypper addrepo -f \
+ http://download.opensuse.org/repositories/devel:languages:perl/openSUSE_Leap_15.2/ \
"devel:languages:perl"
</pre><pre class="programlisting">zypper addrepo -f \
https://download.opensuse.org/repositories/devel:languages:haskell:lts:13/\
</pre><pre class="programlisting">zypper addrepo -f \
https://download.opensuse.org/repositories/devel:languages:haskell:lts:13/\
openSUSE_Leap_15.0/ "devel:languages:haskell:lts:13"
- </pre><p>Danach geben wir noch die beiden folgenden Befehle ein:</p><pre class="programlisting">zypper clean</pre><pre class="programlisting">zypper refresh</pre><p>Sollte zypper eine Meldung ausgeben, ob der Repositorie Key abgelehnt, nicht vertraut oder für immer akzeptiert werden soll, ist die Beantwortung durch drücken der "i" Taste am besten geeignet. Wer noch mehr über zypper erfahren möchte, kann sich einmal die zypper Hilfe anschauen.</p><pre class="programlisting">zypper --help</pre><div class="note" title="Anmerkung" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Anmerkung]" src="system/docbook-xsl/images/note.png"></td><th align="left">Anmerkung</th></tr><tr><td align="left" valign="top"><p>Offiziell wird von openSUSE nur noch Versionen ab 15.0 unterstützt. Die SuSE Macher haben ab Version 15.0 einen größen Umbau in der Verwaltung der Pakete vorgenommen, das heißt, der Paketumfang ist der SLES 15 als kleinsten Nenner angepasst. Dies gilt besonders der openSUE Distribution. Es gibt ja einmal die openSUSE Distri und die Professionelle SLES Version. Dadurch sind viele Pakete aus dem Repositorie enfernt worden, aber auch viele auf aktuellen Stand gehalten.</p></td></tr></table></div><p>Das überprüfen wir mit YaST. Sollte openSUSE bis zur Version 15.0 zum Einsatz kommen und der Administrator bei der Installation der Distribution die KDE Oberfläche aktiviert hat, loggen wir uns am Server direkt ein, starten das Verwaltungsprogram in einer Konsole wie folgt:</p><p>yast2 return.</p><p>Oder über die Menüführung wie folgt: Ein Klick auf das runde Icon, ganz links unten in der Menüleiste dann die Maus Verfahren auf System und YaST.</p><p>Sie können mit folgendem Befehl installiert werden:</p><p>zypper install Paketname</p><p>Es wird empfohlen zusätzliche Pakete nicht direkt mit CPAN zu installieren, da man diese auch über andere Repositories beziehen kann, die bei openSUSE zur Verfügung stehen. Dadurch hat man den Vorteil, dass die Pakete mit YaST verwaltet werden, also wieder deinstalliert oder durch neuere ersetzt werden können. Zudem kann man auch noch eventuelle Bugs an openSUSE senden und diese dem Maintainer melden.</p><pre class="programlisting">zypper install perl-threads-shared ghc-pdfinfo apache2-mod_fcgid \
+ </pre><p>Danach geben wir noch die beiden folgenden Befehle ein:</p><pre class="programlisting">zypper clean</pre><pre class="programlisting">zypper refresh</pre><p>Sollte zypper eine Meldung ausgeben, ob der Repositorie Key abgelehnt, nicht vertraut oder für immer akzeptiert werden soll, ist die Beantwortung durch drücken der "i" Taste am besten geeignet. Wer noch mehr über zypper erfahren möchte, kann sich einmal die zypper Hilfe anschauen.</p><pre class="programlisting">zypper --help</pre><div class="note" title="Anmerkung" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Anmerkung]" src="system/docbook-xsl/images/note.png"></td><th align="left">Anmerkung</th></tr><tr><td align="left" valign="top"><p>Offiziell wird von openSUSE nur noch Versionen ab 15.2 unterstützt. Die SuSE Macher haben ab Version 15.x einen großen Umbau in der Verwaltung der Pakete vorgenommen, das heißt, der Paketumfang ist der SLES 15 als Programmunterbau angepasst. Dies gilt besonders der openSUSE Distribution. Es gibt ja einmal die openSUSE Distri und die Professionelle SLES Version. Dadurch sind viele Pakete aus dem ursprünglich nur für die openSUSE geltenen Repositorie enfernt worden, aber auch viele auf aktuellem Stand gehalten.</p></td></tr></table></div><p>Ab openSUSE Leap 15.x kann man die Distribution auch als reine Text Version, also ohne KDE Oberfläche aufsetzen. Vorteil hierbei ist, dass weniger Balast und unnötige Pakte installiert werden.</p><p>Bei openSUSE Versionen bis 15.x, also 10.x, 11.x, 12.x, 13.x hatte der Administrator die Möglichkeit, bei der Installation der Distribution die KDE Oberfläche zu aktivieren. In dieser Konstellation hat man die Möglichkeit, eine VNC Verbindung vom administrativen Client zu verwenden. Ist das nicht eingerichtet, arbeitet der Admin dann direkt am Bildschirm des Servers. Nun loggen wir uns am Server direkt ein, starten Yast2 in einer Konsole wie folgt:</p><p>yast2 return.</p><p>Oder über die Menüführung wie folgt: Ein Klick auf das runde Icon, ganz links unten in der Menüleiste, dann die Maus verfahren auf System und YaST.</p><p>Im weiteren Verlauf der Installation, beschränken wir uns mit dem Installations Werkzeug zypper. Zypper ist ein Komandozeilen basiertes Installations Tool, welches bei openSUSE Standard ist. Zypper weist ein etwas eigenartiges Verhalten auf, dass sich in etwa wie folgt darstellt. Hat man die Repositories eingerichtet, kann man diese mit Yast als auch mit Zypper benutzen, setzt man einen Befehl wie etwa: zypper up ab, so findet zypper mehr neuere Programmversionen als Yast. Ich habe im allgemeinen noch keine Nachteile damit erlebt.</p><p>Programmpakete können mit folgendem Befehl installiert werden:</p><p>zypper install Paketname</p><p>Es wird empfohlen zusätzliche Pakete nicht direkt mit CPAN zu installieren, da man diese auch über andere Repositories beziehen kann, die bei openSUSE zur Verfügung stehen. Dadurch hat man den Vorteil, dass die Pakete mit YaST verwaltet werden, also wieder deinstalliert oder durch neuere ersetzt werden können. Zudem kann man auch noch eventuelle Bugs an openSUSE senden und diese dem Maintainer melden.</p><pre class="programlisting">zypper install perl-threads-shared ghc-pdfinfo apache2-mod_fcgid \
yast2-http-server postgresql-server postgresql-contrib perl-Algorithm-CheckDigits \
perl-Archive-Zip perl-CGI perl-CGI-Ajax perl-Clone \
perl-Config-Std perl-Class-XSAccessor perl-Daemon-Generic perl-DateTime \
perl-Error-Pure perl-File-Object perl-Readonly perl-Test-Warnings \
perl-Test-NoWarnings perl-Test-Deep perl-Test-Output perl-Test-Strict \
perl-Test-LongString perl-File-Find-Rule
- </pre><p>Zusätzlich müssen einige Pakete für den Umgang mit Latex installiert werden. Die Latex Module barcodes sind nützliche Helfer um auch Barcodes im Dokument zu plazieren, der Vollständigkeit halber hier für die Installation mit angegeben.
+ </pre><p>Zusätzlich müssen einige Pakete für den Umgang mit Latex installiert werden. Die Latex Module barcodes sind nützliche Helfer um auch Barcodes im Dokument zu platzieren, der Vollständigkeit halber hier für die Installation mit angegeben.
Dazu können Sie die folgenden Befehle nutzen:</p><pre class="programlisting">zypper install texlive-wallpaper texlive-colortbl \
texlive-scrlttr2copy texlive-eurosym \
texlive-geometry texlive-german texlive-graphbox texlive-hyperref \
texlive-GS1 texlive-ean texlive-makebarcode texlive-pst-barcode \
texlive-upca
</pre><p>Zusätzlich müssen einige Pakete aus dem CPAN installiert
- werden. Dazu können Sie die folgenden Befehle nutzen:</p><pre class="programlisting">cpan DateTime::event::Cron DateTime::Set FCGI \
- HTML::Restrict PBKDF2::Tiny Rose::Db::Object Set::Infinite</pre></div></div><div class="sect2" title="2.2.3. Andere Pakete installieren"><div class="titlepage"><div><div><h3 class="title"><a name="d0e631"></a>2.2.3. Andere Pakete installieren</h3></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>
- <code class="literal">aqbanking-tools</code> Für das Parsen des MT940 Bankformats</p></li><li class="listitem"><p>
+ werden. Dazu können Sie die folgenden Befehle anwenden:</p><pre class="programlisting">cpan DateTime::event::Cron DateTime::Set FCGI \
+ HTML::Restrict PBKDF2::Tiny Rose::Db::Object Set::Infinite</pre></div></div><div class="sect2" title="2.2.3. Andere Pakete installieren"><div class="titlepage"><div><div><h3 class="title"><a name="d0e649"></a>2.2.3. Andere Pakete installieren</h3></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>
+ <code class="literal">aqbanking-tools</code> Für das Parsen des MT940 Bankformats (Version 6 oder höher)</p></li><li class="listitem"><p>
<code class="literal">poppler-utils</code> 'pdfinfo' zum Erkennen der Seitenanzahl bei der PDF-Generierung</p></li><li class="listitem"><p>
<code class="literal">Postgres Trigram-Index</code> Für datenbankoptimierte Suchanfragen. Bspw. im Paket <code class="literal">postgresql-contrib</code> enthalten</p></li></ul></div><p>Debian und Ubuntu: </p><pre class="programlisting">apt install aqbanking-tools postgresql-contrib poppler-utils</pre><p>
</p><p>Fedora: </p><pre class="programlisting">dnf install aqbanking poppler-utils postgresql-contrib</pre><p>
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>2.3. Manuelle Installation des Programmpaketes</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s02.html" title="2.2. Benötigte Software und Pakete"><link rel="next" href="ch02s04.html" title="2.4. kivitendo-Konfigurationsdatei"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.3. Manuelle Installation des Programmpaketes</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s02.html">Zurück</a> </td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right"> <a accesskey="n" href="ch02s04.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.3. Manuelle Installation des Programmpaketes"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="Manuelle-Installation-des-Programmpaketes"></a>2.3. Manuelle Installation des Programmpaketes</h2></div></div></div><p>Der aktuelle Stable-Release, bzw. beta Release wird bei github
+ <title>2.3. Manuelle Installation des Programmpaketes</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s02.html" title="2.2. Benötigte Software und Pakete"><link rel="next" href="ch02s04.html" title="2.4. kivitendo-Konfigurationsdatei"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.3. Manuelle Installation des Programmpaketes</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s02.html">Zurück</a> </td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right"> <a accesskey="n" href="ch02s04.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.3. Manuelle Installation des Programmpaketes"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="Manuelle-Installation-des-Programmpaketes"></a>2.3. Manuelle Installation des Programmpaketes</h2></div></div></div><p>Der aktuelle Stable-Release, bzw. beta Release wird bei github
gehostet und kann <a class="ulink" href="https://github.com/kivitendo/kivitendo-erp/releases" target="_top">hier</a>
heruntergeladen werden.</p><p>Das aktuelleste kivitendo ERP-Archiv
(<code class="filename">kivitendo-erp-*.tgz</code>) wird dann im
3.4.1 nach 3.5: </p><pre class="programlisting">
$ git clone https://github.com/kivitendo/kivitendo-erp.git
$ cd kivitendo-erp/
-$ git checkout release-3.4.1 # das ist der aktuelle release, den wir wollen
-$ git add templates/fullhouse # das sind unsere druckvorlagen inkl. produktbilder
-$ git commit -m "juhu tolle ändernungen"
+$ git checkout release-3.4.1 # das ist ein alter release aus dem wir starten ...
+$ git checkout -b meine_eigene_änderungen # unser lokaler branch - unabhängig von allen anderen
+$ git add templates/mein_druck # das sind unsere druckvorlagen inkl. produktbilder
+$ git commit -m "juhu tolle änderungen"
+
[meine_aenderungen 1d89e41] juhu tolle ändernungen
4 files changed, 380 insertions(+)
- create mode 100644 templates/fullhouse/img/webdav/tesla.png
- create mode 100644 templates/fullhouse/mahnung.tex
- create mode 100644 templates/fullhouse/zahlungserinnerung_zwei.tex
- create mode 100644 templates/fullhouse/zahlungserinnerung_zwei_invoice.tex
+ create mode 100644 templates/mein_druck/img/webdav/tesla.png
+ create mode 100644 templates/mein_druck/mahnung.tex
+ create mode 100644 templates/mein_druck/zahlungserinnerung_zwei.tex
+ create mode 100644 templates/mein_druck/zahlungserinnerung_zwei_invoice.tex
# 5 Jahre später ...
+# webserver abschalten!
+
+$ git checkout master
+$ git pull # oder git fetch und danach ein stable release tag auswählen (s.o.)
+$ git checkout meine_eigenen_änderungen
+$ git rebase master
-$ git fetch
-$ git rebase --onto release-3.5.0 release-3.4.1 meine_aenderungen
Zunächst wird der Branch zurückgespult, um Ihre Änderungen
darauf neu anzuwenden ...
-Wende an: juhu tolle ändernungen
-$ service apache2 restart
+Wende an: juhu tolle änderungen
+$ service apache2 restart # webserver starten!
</pre><p>
</p></td></tr></table></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="ch02s02.html">Zurück</a> </td><td width="20%" align="center"><a accesskey="u" href="ch02.html">Nach oben</a></td><td width="40%" align="right"> <a accesskey="n" href="ch02s04.html">Weiter</a></td></tr><tr><td width="40%" align="left" valign="top">2.2. Benötigte Software und Pakete </td><td width="20%" align="center"><a accesskey="h" href="index.html">Zum Anfang</a></td><td width="40%" align="right" valign="top"> 2.4. kivitendo-Konfigurationsdatei</td></tr></table></div></body></html>
\ No newline at end of file
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>2.4. kivitendo-Konfigurationsdatei</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s03.html" title="2.3. Manuelle Installation des Programmpaketes"><link rel="next" href="ch02s05.html" title="2.5. Anpassung der PostgreSQL-Konfiguration"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.4. kivitendo-Konfigurationsdatei</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s03.html">Zurück</a> </td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right"> <a accesskey="n" href="ch02s05.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.4. kivitendo-Konfigurationsdatei"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="config.config-file"></a>2.4. kivitendo-Konfigurationsdatei</h2></div></div></div><div class="sect2" title="2.4.1. Einführung"><div class="titlepage"><div><div><h3 class="title"><a name="config.config-file.introduction"></a>2.4.1. Einführung</h3></div></div></div><p>In kivitendo gibt es nur noch eine Konfigurationsdatei, die
+ <title>2.4. kivitendo-Konfigurationsdatei</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s03.html" title="2.3. Manuelle Installation des Programmpaketes"><link rel="next" href="ch02s05.html" title="2.5. Anpassung der PostgreSQL-Konfiguration"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.4. kivitendo-Konfigurationsdatei</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s03.html">Zurück</a> </td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right"> <a accesskey="n" href="ch02s05.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.4. kivitendo-Konfigurationsdatei"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="config.config-file"></a>2.4. kivitendo-Konfigurationsdatei</h2></div></div></div><div class="sect2" title="2.4.1. Einführung"><div class="titlepage"><div><div><h3 class="title"><a name="config.config-file.introduction"></a>2.4.1. Einführung</h3></div></div></div><p>In kivitendo gibt es nur noch eine Konfigurationsdatei, die
benötigt wird: <code class="filename">config/kivitendo.conf</code> (kurz: "die
Hauptkonfigurationsdatei"). Diese muss bei der Erstinstallation von
kivitendo bzw. der Migration von älteren Versionen angelegt
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>2.5. Anpassung der PostgreSQL-Konfiguration</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s04.html" title="2.4. kivitendo-Konfigurationsdatei"><link rel="next" href="ch02s06.html" title="2.6. Webserver-Konfiguration"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.5. Anpassung der PostgreSQL-Konfiguration</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s04.html">Zurück</a> </td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right"> <a accesskey="n" href="ch02s06.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.5. Anpassung der PostgreSQL-Konfiguration"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="Anpassung-der-PostgreSQL-Konfiguration"></a>2.5. Anpassung der PostgreSQL-Konfiguration</h2></div></div></div><p>PostgreSQL muss auf verschiedene Weisen angepasst werden.</p><p>Dies variert je nach eingesetzter Distribution, da distributionsabhängig unterschiedliche Strategien beim Upgrade der Postgres Version eingesetzt werden.
+ <title>2.5. Anpassung der PostgreSQL-Konfiguration</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s04.html" title="2.4. kivitendo-Konfigurationsdatei"><link rel="next" href="ch02s06.html" title="2.6. Webserver-Konfiguration"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.5. Anpassung der PostgreSQL-Konfiguration</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s04.html">Zurück</a> </td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right"> <a accesskey="n" href="ch02s06.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.5. Anpassung der PostgreSQL-Konfiguration"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="Anpassung-der-PostgreSQL-Konfiguration"></a>2.5. Anpassung der PostgreSQL-Konfiguration</h2></div></div></div><p>PostgreSQL muss auf verschiedene Weisen angepasst werden.</p><p>Dies variert je nach eingesetzter Distribution, da distributionsabhängig unterschiedliche Strategien beim Upgrade der Postgres Version eingesetzt werden.
Als Hinweis einige Links zu den drei Distribution (Stand Dezember 2018):</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>
<a class="ulink" href="https://fedoraproject.org/wiki/PostgreSQL" target="_top">Fedora (Postgres-Installation unter Fedora)</a>
</p></li><li class="listitem"><p>
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>2.6. Webserver-Konfiguration</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s05.html" title="2.5. Anpassung der PostgreSQL-Konfiguration"><link rel="next" href="ch02s07.html" title="2.7. Der Task-Server"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.6. Webserver-Konfiguration</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s05.html">Zurück</a> </td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right"> <a accesskey="n" href="ch02s07.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.6. Webserver-Konfiguration"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="Apache-Konfiguration"></a>2.6. Webserver-Konfiguration</h2></div></div></div><div class="sect2" title="2.6.1. Grundkonfiguration mittels CGI"><div class="titlepage"><div><div><h3 class="title"><a name="d0e1111"></a>2.6.1. Grundkonfiguration mittels CGI</h3></div></div></div><div class="note" title="Anmerkung" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Anmerkung]" src="system/docbook-xsl/images/note.png"></td><th align="left">Anmerkung</th></tr><tr><td align="left" valign="top"><p>Für einen deutlichen Performanceschub sorgt die Ausführung
+ <title>2.6. Webserver-Konfiguration</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s05.html" title="2.5. Anpassung der PostgreSQL-Konfiguration"><link rel="next" href="ch02s07.html" title="2.7. Der Task-Server"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.6. Webserver-Konfiguration</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s05.html">Zurück</a> </td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right"> <a accesskey="n" href="ch02s07.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.6. Webserver-Konfiguration"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="Apache-Konfiguration"></a>2.6. Webserver-Konfiguration</h2></div></div></div><div class="sect2" title="2.6.1. Grundkonfiguration mittels CGI"><div class="titlepage"><div><div><h3 class="title"><a name="d0e1129"></a>2.6.1. Grundkonfiguration mittels CGI</h3></div></div></div><div class="note" title="Anmerkung" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Anmerkung]" src="system/docbook-xsl/images/note.png"></td><th align="left">Anmerkung</th></tr><tr><td align="left" valign="top"><p>Für einen deutlichen Performanceschub sorgt die Ausführung
mittels FastCGI/FCGI. Die Einrichtung wird ausführlich im Abschnitt
<a class="xref" href="ch02s06.html#Apache-Konfiguration.FCGI" title="2.6.2. Konfiguration für FastCGI/FCGI">Konfiguration für FastCGI/FCGI</a> beschrieben.</p></td></tr></table></div><p>Der Zugriff auf das Programmverzeichnis muss in der Apache
Webserverkonfigurationsdatei <code class="literal">httpd.conf</code> eingestellt
führt dazu dass ein kivitendo Aufruf der Kernmasken mittlerweile
deutlich länger dauert als früher, und dass davon 90% für das Laden
der Module verwendet wird.</p><p>Mit FastCGI werden nun die Module einmal geladen, und danach
- wird nur die eigentliche Programmlogik ausgeführt.</p></div><div class="sect3" title="2.6.2.3. Getestete Kombinationen aus Webservern und Plugin"><div class="titlepage"><div><div><h4 class="title"><a name="Apache-Konfiguration.FCGI.WebserverUndPlugin"></a>2.6.2.3. Getestete Kombinationen aus Webservern und Plugin</h4></div></div></div><p>Folgende Kombinationen sind getestet:</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Apache 2.4.7 (Ubuntu 14.04.2 LTS) und mod_fcgid.</p></li><li class="listitem"><p>Apache 2.4.18 (Ubuntu 16.04 LTS) und mod_fcgid</p></li><li class="listitem"><p>Apache 2.4.29 (Ubuntu 18.04 LTS) und mod_fcgid</p></li></ul></div><p>Als Perl Backend wird das Modul <code class="filename">FCGI.pm</code>
+ wird nur die eigentliche Programmlogik ausgeführt.</p></div><div class="sect3" title="2.6.2.3. Getestete Kombinationen aus Webservern und Plugin"><div class="titlepage"><div><div><h4 class="title"><a name="Apache-Konfiguration.FCGI.WebserverUndPlugin"></a>2.6.2.3. Getestete Kombinationen aus Webservern und Plugin</h4></div></div></div><p>Folgende Kombinationen sind getestet:</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Apache 2.4.7 (Ubuntu 14.04.2 LTS) und mod_fcgid.</p></li><li class="listitem"><p>Apache 2.4.18 (Ubuntu 16.04 LTS) und mod_fcgid</p></li><li class="listitem"><p>Apache 2.4.29 (Ubuntu 18.04 LTS) und mod_fcgid</p></li><li class="listitem"><p>Apache 2.4.41 (Ubuntu 20.04 LTS) und mod_fcgid</p></li></ul></div><p>Als Perl Backend wird das Modul <code class="filename">FCGI.pm</code>
verwendet.</p><div class="warning" title="Warnung" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Warning"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Warnung]" src="system/docbook-xsl/images/warning.png"></td><th align="left">Warnung</th></tr><tr><td align="left" valign="top"><p>FCGI-Versionen ab 0.69 und bis zu 0.71 inklusive sind extrem
strict in der Behandlung von Unicode, und verweigern bestimmte
Eingaben von kivitendo. Falls es Probleme mit Umlauten in Ihrer
Alias /url/for/kivitendo-erp-fcgid/ /path/to/kivitendo-erp/</pre><p>Dann ist unter <code class="filename">/url/for/kivitendo-erp/</code>
die normale Version erreichbar, und unter
<code class="constant">/url/for/kivitendo-erp-fcgid/</code> die
- FastCGI-Version.</p></div></div><div class="sect2" title="2.6.3. Authentifizierung mittels HTTP Basic Authentication"><div class="titlepage"><div><div><h3 class="title"><a name="d0e1262"></a>2.6.3. Authentifizierung mittels HTTP Basic Authentication</h3></div></div></div><p>
+ FastCGI-Version.</p></div></div><div class="sect2" title="2.6.3. Authentifizierung mittels HTTP Basic Authentication"><div class="titlepage"><div><div><h3 class="title"><a name="d0e1283"></a>2.6.3. Authentifizierung mittels HTTP Basic Authentication</h3></div></div></div><p>
Kivitendo unterstützt, dass Benutzerauthentifizierung über den Webserver mittels des »Basic«-HTTP-Authentifizierungs-Schema erfolgt
(siehe <a class="ulink" href="https://tools.ietf.org/html/rfc7617" target="_top">RFC 7617</a>). Dazu ist es aber nötig, dass der dabei vom Client
mitgeschickte Header <code class="constant">Authorization</code> vom Webserver an Kivitendo über die Umgebungsvariable
<code class="constant">HTTP_AUTHORIZATION</code> weitergegeben wird, was standardmäßig nicht der Fall ist. Für Apache kann dies über die
folgende Konfigurationsoption aktiviert werden:
- </p><pre class="programlisting">SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1</pre></div><div class="sect2" title="2.6.4. Weitergehende Konfiguration"><div class="titlepage"><div><div><h3 class="title"><a name="d0e1278"></a>2.6.4. Weitergehende Konfiguration</h3></div></div></div><p>Für einen deutlichen Sicherheitsmehrwert sorgt die Ausführung
+ </p><pre class="programlisting">SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1</pre></div><div class="sect2" title="2.6.4. Aktivierung von mod_rewrite/directory_match für git basierte Installationen"><div class="titlepage"><div><div><h3 class="title"><a name="d0e1299"></a>2.6.4. Aktivierung von mod_rewrite/directory_match für git basierte Installationen</h3></div></div></div><p>
+ Aufgrund von aktuellen (Mitte 2020) Sicherheitswarnungen für git basierte Webanwendungen ist die mitausgelieferte .htaccess
+ restriktiver geworden und verhindert somit das Auslesen von git basierten Daten.
+ Für debian/ubuntu muss das Modul mod_rewrite einmalig so aktiviert werden:
+ </p><pre class="programlisting">a2enmod rewrite</pre><p>
+ Alternativ und für Installationen ohne Apache ist folgender Artikel interessant:
+ <a class="ulink" href="https://www.cyberscan.io/blog/git-luecke" target="_top">git-lücke</a>.
+ Anstelle des dort beschriebenen DirectoryMatch für Apache2 würden wir etwas weitergehend auch noch das Verzeichnis config miteinbeziehen
+ sowie ferner auch die Möglichkeit nicht ausschließen, dass es in Unterverzeichnissen auch noch .git Repositories geben kann.
+ Die Empfehlung für Apache 2.4 wäre damit:
+ </p><pre class="programlisting">
+ <DirectoryMatch "/(\.git|config)/">
+ Require all denied
+ </DirectoryMatch></pre><p>
+
+ </p></div><div class="sect2" title="2.6.5. Weitergehende Konfiguration"><div class="titlepage"><div><div><h3 class="title"><a name="d0e1313"></a>2.6.5. Weitergehende Konfiguration</h3></div></div></div><p>Für einen deutlichen Sicherheitsmehrwert sorgt die Ausführung
von kivitendo nur über https-verschlüsselten Verbindungen, sowie
weiteren Zusatzmassnahmen, wie beispielsweise Basic Authenticate. Die
Konfigurationsmöglichkeiten sprengen allerdings den Rahmen dieser
Anleitung, hier ein Hinweis auf einen entsprechenden <a class="ulink" href="http://redmine.kivitendo-premium.de/boards/1/topics/142" target="_top">Foreneintrag
(Stand Sept. 2015)</a> und einen aktuellen (Stand Mai 2017) <a class="ulink" href="https://mozilla.github.io/server-side-tls/ssl-config-generator/" target="_top">
- SSL-Konfigurations-Generator</a>.</p></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="ch02s05.html">Zurück</a> </td><td width="20%" align="center"><a accesskey="u" href="ch02.html">Nach oben</a></td><td width="40%" align="right"> <a accesskey="n" href="ch02s07.html">Weiter</a></td></tr><tr><td width="40%" align="left" valign="top">2.5. Anpassung der PostgreSQL-Konfiguration </td><td width="20%" align="center"><a accesskey="h" href="index.html">Zum Anfang</a></td><td width="40%" align="right" valign="top"> 2.7. Der Task-Server</td></tr></table></div></body></html>
\ No newline at end of file
+ SSL-Konfigurations-Generator</a>.</p></div><div class="sect3" title="2.6.1. Aktivierung von Apache2 modsecurity"><div class="titlepage"><div><div><h4 class="title"><a name="d0e1324"></a>2.6.1. Aktivierung von Apache2 modsecurity</h4></div></div></div><p>Aufgrund des OpenSource Charakters ist kivitendo nicht "out of the box" sicher.
+ Organisatorisch empfehlen wir hier die enge Zusammenarbeit mit einem kivitendo Partner der auch in der
+Lage ist weiterführende Fragen in Bezug auf Datenschutz und Datensicherheit zu beantworten.
+Unabhängig davon empfehlen wir im Webserver Bereich die Aktivierung und Konfiguration des Moduls modsecurity für den Apache2, damit
+XSS und SQL-Injections verhindert werden.</p><p> Als Idee hierfür sei dieser Blog-Eintrag genannt:
+<a class="ulink" href="https://doxsec.wordpress.com/2017/06/11/using-modsecurity-web-application-firewall-to-prevent-sql-injection-and-xss-using-blocking-rules/" target="_top">
+ Test Apache2 modsecurity for SQL Injection</a>.</p></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="ch02s05.html">Zurück</a> </td><td width="20%" align="center"><a accesskey="u" href="ch02.html">Nach oben</a></td><td width="40%" align="right"> <a accesskey="n" href="ch02s07.html">Weiter</a></td></tr><tr><td width="40%" align="left" valign="top">2.5. Anpassung der PostgreSQL-Konfiguration </td><td width="20%" align="center"><a accesskey="h" href="index.html">Zum Anfang</a></td><td width="40%" align="right" valign="top"> 2.7. Der Task-Server</td></tr></table></div></body></html>
\ No newline at end of file
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>2.7. Der Task-Server</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s06.html" title="2.6. Webserver-Konfiguration"><link rel="next" href="ch02s08.html" title="2.8. Benutzerauthentifizierung und Administratorpasswort"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.7. Der Task-Server</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s06.html">Zurück</a> </td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right"> <a accesskey="n" href="ch02s08.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.7. Der Task-Server"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="config.task-server"></a>2.7. Der Task-Server</h2></div></div></div><p>Der Task-Server ist ein Prozess, der im Hintergrund läuft, in
+ <title>2.7. Der Task-Server</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s06.html" title="2.6. Webserver-Konfiguration"><link rel="next" href="ch02s08.html" title="2.8. Benutzerauthentifizierung und Administratorpasswort"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.7. Der Task-Server</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s06.html">Zurück</a> </td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right"> <a accesskey="n" href="ch02s08.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.7. Der Task-Server"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="config.task-server"></a>2.7. Der Task-Server</h2></div></div></div><p>Der Task-Server ist ein Prozess, der im Hintergrund läuft, in
regelmäßigen Abständen nach abzuarbeitenden Aufgaben sucht und diese zu
festgelegten Zeitpunkten abarbeitet (ähnlich wie Cron). Dieser Prozess
wird u.a. für die Erzeugung der wiederkehrenden Rechnungen und weitere
Links aus einem der Runlevel-Verzeichnisse heraus in den Boot-Prozess
einzubinden. Da das bei neueren Linux-Distributionen aber nicht
zwangsläufig funktioniert, werden auch Start-Scripte mitgeliefert, die
- anstelle eines symbolischen Links verwendet werden können.</p><div class="sect3" title="2.7.3.1. SystemV-basierende Systeme (z.B. ältere Debian, ältere openSUSE, ältere Fedora)"><div class="titlepage"><div><div><h4 class="title"><a name="d0e1352"></a>2.7.3.1. SystemV-basierende Systeme (z.B. ältere Debian, ältere
+ anstelle eines symbolischen Links verwendet werden können.</p><div class="sect3" title="2.7.3.1. SystemV-basierende Systeme (z.B. ältere Debian, ältere openSUSE, ältere Fedora)"><div class="titlepage"><div><div><h4 class="title"><a name="d0e1397"></a>2.7.3.1. SystemV-basierende Systeme (z.B. ältere Debian, ältere
openSUSE, ältere Fedora)</h4></div></div></div><p>Kopieren Sie die Datei
<code class="filename">scripts/boot/system-v/kivitendo-task-server</code>
nach <code class="filename">/etc/init.d/kivitendo-task-server</code>. Passen
<code class="literal">DAEMON=....</code>). Binden Sie das Script in den
Boot-Prozess ein. Dies ist distributionsabhängig:</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Debian-basierende Systeme:</p><pre class="programlisting">update-rc.d kivitendo-task-server defaults
insserv kivitendo-task-server</pre></li><li class="listitem"><p>Ältere openSUSE und ältere Fedora:</p><pre class="programlisting">chkconfig --add kivitendo-task-server</pre></li></ul></div><p>Danach kann der Task-Server mit dem folgenden Befehl gestartet
- werden:</p><pre class="programlisting">/etc/init.d/kivitendo-task-server start</pre></div><div class="sect3" title="2.7.3.2. Upstart-basierende Systeme (z.B. Ubuntu bis 14.04)"><div class="titlepage"><div><div><h4 class="title"><a name="d0e1381"></a>2.7.3.2. Upstart-basierende Systeme (z.B. Ubuntu bis 14.04)</h4></div></div></div><p>Kopieren Sie die Datei
+ werden:</p><pre class="programlisting">/etc/init.d/kivitendo-task-server start</pre></div><div class="sect3" title="2.7.3.2. Upstart-basierende Systeme (z.B. Ubuntu bis 14.04)"><div class="titlepage"><div><div><h4 class="title"><a name="d0e1426"></a>2.7.3.2. Upstart-basierende Systeme (z.B. Ubuntu bis 14.04)</h4></div></div></div><p>Kopieren Sie die Datei
<code class="filename">scripts/boot/upstart/kivitendo-task-server.conf</code>
nach <code class="filename">/etc/init/kivitendo-task-server.conf</code>.
Passen Sie in der kopierten Datei den Pfad zum Task-Server an (Zeile
<code class="literal">exec ....</code>).</p><p>Danach kann der Task-Server mit dem folgenden Befehl gestartet
- werden:</p><pre class="programlisting">service kivitendo-task-server start</pre></div><div class="sect3" title="2.7.3.3. systemd-basierende Systeme (z.B. neure openSUSE, neuere Fedora, neuere Ubuntu und neuere Debians)"><div class="titlepage"><div><div><h4 class="title"><a name="d0e1399"></a>2.7.3.3. systemd-basierende Systeme (z.B. neure openSUSE, neuere
+ werden:</p><pre class="programlisting">service kivitendo-task-server start</pre></div><div class="sect3" title="2.7.3.3. systemd-basierende Systeme (z.B. neure openSUSE, neuere Fedora, neuere Ubuntu und neuere Debians)"><div class="titlepage"><div><div><h4 class="title"><a name="d0e1444"></a>2.7.3.3. systemd-basierende Systeme (z.B. neure openSUSE, neuere
Fedora, neuere Ubuntu und neuere Debians)</h4></div></div></div><p>Kopieren Sie die Datei
<code class="filename">scripts/boot/systemd/kivitendo-task-server.service</code>
nach <code class="filename">/etc/systemd/system/</code>. Passen Sie in der
läuft.</p></li></ul></div><p>Der Task-Server wechselt beim Starten automatisch in das
kivitendo-Installationsverzeichnis.</p><p>Dieselben Optionen können auch für die SystemV-basierenden
Runlevel-Scripte benutzt werden (siehe oben).</p><p>Wurde der Task-Server als systemd-Service eingerichtet (s.o.),
- so startet dieser nach Beendigung automatisch erneut.</p></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="ch02s06.html">Zurück</a> </td><td width="20%" align="center"><a accesskey="u" href="ch02.html">Nach oben</a></td><td width="40%" align="right"> <a accesskey="n" href="ch02s08.html">Weiter</a></td></tr><tr><td width="40%" align="left" valign="top">2.6. Webserver-Konfiguration </td><td width="20%" align="center"><a accesskey="h" href="index.html">Zum Anfang</a></td><td width="40%" align="right" valign="top"> 2.8. Benutzerauthentifizierung und Administratorpasswort</td></tr></table></div></body></html>
\ No newline at end of file
+ so startet dieser nach Beendigung automatisch erneut.</p></div><div class="sect2" title="2.7.5. Exemplarische Konfiguration eines Hintergrund-Jobs, der die Jahreszahl in allen Nummernkreisen zum Jahreswechsel erhöht"><div class="titlepage"><div><div><h3 class="title"><a name="Tasks konfigurieren"></a>2.7.5. Exemplarische Konfiguration eines Hintergrund-Jobs, der die Jahreszahl in allen Nummernkreisen zum Jahreswechsel erhöht</h3></div></div></div><p>Hintergrund-Jobs werden über System -> Hintergrund-Jobs und Task-Server -> Aktuelle Hintergrund-Jobs anzeigen -> Aktions-Knopf 'erfassen' angelegt. </p><p>Nachdem wir über das Menü dort angelangt sind, legen wir unseren exemplarischen Hintergrund-Jobs "Erhöhung der Nummernkreise" mit folgenden Werten an:</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>
+ <code class="literal">Aktiv:</code> Hier ein 'Ja' auswählen</p></li><li class="listitem"><p>
+ <code class="literal">Ausführungsart:</code> 'wiederholte Ausführung' auswählen</p></li><li class="listitem"><p>
+ <code class="literal">Paketname:</code> 'SetNumberRange' auswählen</p></li><li class="listitem"><p>
+ <code class="literal">Ausführungszeitplan:</code> Hier entsprechend Werte wie in der crontab eingeben.</p><p>Syntax:</p><pre class="programlisting">* * * * *
+┬ ┬ ┬ ┬ ┬
+│ │ │ │ │
+│ │ │ │ └──── Wochentag (0-7, Sonntag ist 0 oder 7)
+│ │ │ └────── Monat (1-12)
+│ │ └──────── Tag (1-31)
+│ └────────── Stunde (0-23)
+└──────────── Minute (0-59) </pre><p>Die Sterne können folgende Werte haben:</p><pre class="programlisting">
+1 2 3 4 5
+
+1 = Minute (0-59)
+2 = Stunde (0-23)
+3 = Tag (0-31)
+4 = Monat (1-12)
+5 = Wochentag (0-7, Sonntag ist 0 oder 7)
+</pre><p>Um die Ausführung auf eine Minute vor Sylvester zu setzen, müssen die folgenden Werte eingetragen werden:</p><pre class="programlisting">59 23 31 12 *</pre></li><li class="listitem"><p>
+ <code class="literal">Daten:</code>In diesem Feld können optionale Parameter für den Hintergrund im JSON-Format gesetzt werden. Der Hintergrund-Job <code class="literal">SetNumberRange</code> akzeptiert zwei Variable nämlich <code class="literal">digit_year</code> sowieso <code class="literal">multiplier</code>.</p><p>
+ <code class="literal">digit_year</code> kann zwei Werte haben entweder 2 oder 4, darüber wird gesteuert ob die Jahreszahl zwei oder vierstellig kodiert wird (für 2019, dann entweder 19 oder 2019). Der Standardwert ist vierstellig.</p><p>
+ <code class="literal">multiplier</code> ist ein Vielfaches von 10, darüber wird die erste Nummer im Nummernkreis (die Anzahl der Stellen) wie folgt bestimmt:</p><pre class="programlisting">
+multiplier Nummernkreis 2020
+10 -> 20200
+100 -> 202000
+1000 -> 2020000
+</pre><p>Wir gehen jetzt beispielhaft von einer letzten Rechnungsnummer von RE2019456 aus. Demnach sollte ab Januar 2020 die erste Nummer RE2020001 sein. Da der Task auch Präfixe berücksichtigt, kann dies mit folgenden JSON-kodierten Werten umgesetzt werden:</p><code class="literal">Daten:</code><pre class="programlisting">multiplier: 100
+digits_year: 4</pre></li></ul></div></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="ch02s06.html">Zurück</a> </td><td width="20%" align="center"><a accesskey="u" href="ch02.html">Nach oben</a></td><td width="40%" align="right"> <a accesskey="n" href="ch02s08.html">Weiter</a></td></tr><tr><td width="40%" align="left" valign="top">2.6. Webserver-Konfiguration </td><td width="20%" align="center"><a accesskey="h" href="index.html">Zum Anfang</a></td><td width="40%" align="right" valign="top"> 2.8. Benutzerauthentifizierung und Administratorpasswort</td></tr></table></div></body></html>
\ No newline at end of file
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>2.8. Benutzerauthentifizierung und Administratorpasswort</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s07.html" title="2.7. Der Task-Server"><link rel="next" href="ch02s09.html" title="2.9. Mandanten-, Benutzer- und Gruppenverwaltung"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.8. Benutzerauthentifizierung und Administratorpasswort</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s07.html">Zurück</a> </td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right"> <a accesskey="n" href="ch02s09.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.8. Benutzerauthentifizierung und Administratorpasswort"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="Benutzerauthentifizierung-und-Administratorpasswort"></a>2.8. Benutzerauthentifizierung und Administratorpasswort</h2></div></div></div><p>Informationen über die Einrichtung der Benutzerauthentifizierung,
+ <title>2.8. Benutzerauthentifizierung und Administratorpasswort</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s07.html" title="2.7. Der Task-Server"><link rel="next" href="ch02s09.html" title="2.9. Mandanten-, Benutzer- und Gruppenverwaltung"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.8. Benutzerauthentifizierung und Administratorpasswort</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s07.html">Zurück</a> </td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right"> <a accesskey="n" href="ch02s09.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.8. Benutzerauthentifizierung und Administratorpasswort"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="Benutzerauthentifizierung-und-Administratorpasswort"></a>2.8. Benutzerauthentifizierung und Administratorpasswort</h2></div></div></div><p>Informationen über die Einrichtung der Benutzerauthentifizierung,
über die Verwaltung von Gruppen und weitere Einstellungen</p><div class="sect2" title="2.8.1. Grundlagen zur Benutzerauthentifizierung"><div class="titlepage"><div><div><h3 class="title"><a name="Grundlagen-zur-Benutzerauthentifizierung"></a>2.8.1. Grundlagen zur Benutzerauthentifizierung</h3></div></div></div><p>kivitendo verwaltet die Benutzerinformationen in einer
Datenbank, die im folgenden “Authentifizierungsdatenbank” genannt
wird. Für jeden Benutzer kann dort eine eigene Datenbank für die
eigentlichen Finanzdaten hinterlegt sein. Diese beiden Datenbanken
können, müssen aber nicht unterschiedlich sein.</p><p>Im einfachsten Fall gibt es für kivitendo nur eine einzige
Datenbank, in der sowohl die Benutzerinformationen als auch die Daten
- abgelegt werden.</p><p>Zusätzlich ermöglicht es kivitendo, dass die Benutzerpasswörter
- entweder gegen die Authentifizierungsdatenbank oder gegen einen
- LDAP-Server überprüft werden.</p><p>Welche Art der Passwortüberprüfung kivitendo benutzt und wie
+ abgelegt werden.</p><p>Zusätzlich ermöglicht es kivitendo, dass die Benutzerpasswörter gegen die Authentifizierungsdatenbank oder gegen einen oder
+ mehrere LDAP-Server überprüft werden.</p><p>Welche Art der Passwortüberprüfung kivitendo benutzt und wie
kivitendo die Authentifizierungsdatenbank erreichen kann, wird in der
Konfigurationsdatei <code class="filename">config/kivitendo.conf</code>
festgelegt. Diese muss bei der Installation und bei einem Upgrade von
<code class="literal">password</code>
</span></dt><dd><p>Das Passwort für den Datenbankbenutzer</p></dd></dl></div><p>Die Datenbank muss noch nicht existieren. kivitendo kann sie
automatisch anlegen (mehr dazu siehe unten).</p></div><div class="sect2" title="2.8.4. Passwortüberprüfung"><div class="titlepage"><div><div><h3 class="title"><a name="Passwort%C3%BCberpr%C3%BCfung"></a>2.8.4. Passwortüberprüfung</h3></div></div></div><p>kivitendo unterstützt Passwortüberprüfung auf zwei Arten: gegen
- die Authentifizierungsdatenbank und gegen einen externen LDAP- oder
+ die Authentifizierungsdatenbank und gegen externe LDAP- oder
Active-Directory-Server. Welche davon benutzt wird, regelt der
Parameter <code class="varname">module</code> im Abschnitt
- <code class="varname">[authentication]</code>.</p><p>Sollen die Benutzerpasswörter in der Authentifizierungsdatenbank
- gespeichert werden, so muss der Parameter <code class="varname">module</code>
- den Wert <code class="literal">DB</code> enthalten. In diesem Fall können sowohl
- der Administrator als auch die Benutzer selber ihre Passwörter in
- kivitendo ändern.</p><p>Soll hingegen ein externer LDAP- oder Active-Directory-Server
- benutzt werden, so muss der Parameter <code class="varname">module</code> auf
- <code class="literal">LDAP</code> gesetzt werden. In diesem Fall müssen
- zusätzliche Informationen über den LDAP-Server im Abschnitt
- <code class="literal">[authentication/ldap]</code> angegeben werden:</p><div class="variablelist"><dl><dt><span class="term">
+ <code class="varname">[authentication]</code>.</p><p>Dieser Parameter listet die zu verwendenden Authentifizierungsmodule auf. Es muss mindestens ein Modul angegeben werden, es
+ können aber auch mehrere angegeben werden. Weiterhin ist es möglich, das LDAP-Modul mehrfach zu verwenden und für jede Verwendung
+ eine unterschiedliche Konfiguration zu nutzen, z.B. um einen Fallback-Server anzugeben, der benutzt wird, sofern der Hauptserver
+ nicht erreichbar ist.</p><p>Sollen die Benutzerpasswörter in der Authentifizierungsdatenbank geprüft werden, so muss der Parameter
+ <code class="varname">module</code> das Modul <code class="literal">DB</code> enthalten. Sofern das Modul in der Liste enthalten ist, egal an welcher
+ Position, können sowohl der Administrator als auch die Benutzer selber ihre Passwörter in kivitendo ändern.</p><p>Wenn Passwörter gegen einen oder mehrere externe LDAP- oder Active-Directory-Server geprüft werden, so muss der Parameter
+ <code class="varname">module</code> den Wert <code class="literal">LDAP</code> enthalten. In diesem Fall müssen zusätzliche Informationen über den
+ LDAP-Server im Abschnitt <code class="literal">[authentication/ldap]</code> angegeben werden. Das Modul kann auch mehrfach angegeben werden,
+ wobei jedes Modul eine eigene Konfiguration bekommen sollte. Der Name der Konfiguration wird dabei mit einem Doppelpunkt getrennt an
+ den Modulnamen angehängt (<code class="literal">LDAP:Name-der-Konfiguration</code>). Der entsprechende Abschnitt in der Konfigurationsdatei
+ lautet dann <code class="literal">[authentication/Name-der-Konfiguration]</code>.</p><p>Die verfügbaren Parameter für die LDAP-Konfiguration lauten:</p><div class="variablelist"><dl><dt><span class="term">
<code class="literal">host</code>
</span></dt><dd><p>Der Rechnername oder die IP-Adresse des LDAP- oder
Active-Directory-Servers. Diese Angabe ist zwingend
</span></dt><dd><p>Wenn Verbindungsverschlüsselung gewünscht ist, so diesen
Wert auf ‘<code class="literal">1</code>’ setzen, andernfalls auf
‘<code class="literal">0</code>’ belassen</p></dd><dt><span class="term">
+ <code class="literal">verify</code>
+ </span></dt><dd><p>Wenn Verbindungsverschlüsselung gewünscht und der Parameter <em class="parameter"><code>tls</code></em> gesetzt ist, so gibt dieser
+ Parameter an, ob das Serverzertifikat auf Gültigkeit geprüft wird. Mögliche Werte sind <code class="literal">require</code> (Zertifikat
+ wird überprüft und muss gültig sei; dies ist der Standard) und <code class="literal">none</code> (Zertifikat wird nicht
+ überpfüft).</p></dd><dt><span class="term">
<code class="literal">attribute</code>
</span></dt><dd><p>Das LDAP-Attribut, in dem der Benutzername steht, den der
Benutzer eingegeben hat. Für Active-Directory-Server ist dies
z.B. ‘<code class="literal">cn=Martin
Mustermann,cn=Users,dc=firmendomain</code>’ auch nur der
volle Name des Benutzers eingegeben werden; in diesem Beispiel
- also ‘<code class="literal">Martin Mustermann</code>’.</p></dd></dl></div></div><div class="sect2" title="2.8.5. Name des Session-Cookies"><div class="titlepage"><div><div><h3 class="title"><a name="Name-des-Session-Cookies"></a>2.8.5. Name des Session-Cookies</h3></div></div></div><p>Sollen auf einem Server mehrere kivitendo-Installationen
+ also ‘<code class="literal">Martin Mustermann</code>’.</p></dd><dt><span class="term">
+ <code class="literal">timeout</code>
+ </span></dt><dd><p>Timeout beim Verbindungsversuch, bevor der Server als nicht erreichbar gilt; Standardwert: 10</p></dd></dl></div></div><div class="sect2" title="2.8.5. Name des Session-Cookies"><div class="titlepage"><div><div><h3 class="title"><a name="Name-des-Session-Cookies"></a>2.8.5. Name des Session-Cookies</h3></div></div></div><p>Sollen auf einem Server mehrere kivitendo-Installationen
aufgesetzt werden, so müssen die Namen der Session-Cookies für alle
Installationen unterschiedlich sein. Der Name des Cookies wird mit dem
Parameter <code class="varname">cookie_name</code> im Abschnitt
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>2.9. Mandanten-, Benutzer- und Gruppenverwaltung</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s08.html" title="2.8. Benutzerauthentifizierung und Administratorpasswort"><link rel="next" href="ch02s10.html" title="2.10. Drucker- und Systemverwaltung"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.9. Mandanten-, Benutzer- und Gruppenverwaltung</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s08.html">Zurück</a> </td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right"> <a accesskey="n" href="ch02s10.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.9. Mandanten-, Benutzer- und Gruppenverwaltung"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="Benutzer--und-Gruppenverwaltung"></a>2.9. Mandanten-, Benutzer- und Gruppenverwaltung</h2></div></div></div><p>Nach der Installation müssen Mandanten, Benutzer, Gruppen und
+ <title>2.9. Mandanten-, Benutzer- und Gruppenverwaltung</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s08.html" title="2.8. Benutzerauthentifizierung und Administratorpasswort"><link rel="next" href="ch02s10.html" title="2.10. Drucker- und Systemverwaltung"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.9. Mandanten-, Benutzer- und Gruppenverwaltung</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s08.html">Zurück</a> </td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right"> <a accesskey="n" href="ch02s10.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.9. Mandanten-, Benutzer- und Gruppenverwaltung"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="Benutzer--und-Gruppenverwaltung"></a>2.9. Mandanten-, Benutzer- und Gruppenverwaltung</h2></div></div></div><p>Nach der Installation müssen Mandanten, Benutzer, Gruppen und
Datenbanken angelegt werden. Dieses geschieht im Administrationsmenü,
das Sie unter folgender URL finden:</p><p>
<a class="ulink" href="http://localhost/kivitendo-erp/controller.pl?action=Admin/login" target="_top">http://localhost/kivitendo-erp/controller.pl?action=Admin/login</a>
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>2.10. Drucker- und Systemverwaltung</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s09.html" title="2.9. Mandanten-, Benutzer- und Gruppenverwaltung"><link rel="next" href="ch02s11.html" title="2.11. E-Mail-Versand aus kivitendo heraus"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.10. Drucker- und Systemverwaltung</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s09.html">Zurück</a> </td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right"> <a accesskey="n" href="ch02s11.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.10. Drucker- und Systemverwaltung"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="Drucker--Systemverwaltung"></a>2.10. Drucker- und Systemverwaltung</h2></div></div></div><p>Im Administrationsmenü gibt es ferner noch die beiden Menüpunkte
+ <title>2.10. Drucker- und Systemverwaltung</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s09.html" title="2.9. Mandanten-, Benutzer- und Gruppenverwaltung"><link rel="next" href="ch02s11.html" title="2.11. E-Mail-Versand aus kivitendo heraus"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.10. Drucker- und Systemverwaltung</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s09.html">Zurück</a> </td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right"> <a accesskey="n" href="ch02s11.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.10. Drucker- und Systemverwaltung"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="Drucker--Systemverwaltung"></a>2.10. Drucker- und Systemverwaltung</h2></div></div></div><p>Im Administrationsmenü gibt es ferner noch die beiden Menüpunkte
Druckeradministration und System.</p><div class="sect2" title="2.10.1. Druckeradministration"><div class="titlepage"><div><div><h3 class="title"><a name="Druckeradministration"></a>2.10.1. Druckeradministration</h3></div></div></div><p>Unter dem Menüpunkt Druckeradministration lassen sich beliebig
viele "Druckbefehle" im System verwalten. Diese Befehle werden
mandantenweise zugeordnet. Unter Druckerbeschreibung wird der Namen
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>2.11. E-Mail-Versand aus kivitendo heraus</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s10.html" title="2.10. Drucker- und Systemverwaltung"><link rel="next" href="ch02s12.html" title="2.12. Drucken mit kivitendo"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.11. E-Mail-Versand aus kivitendo heraus</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s10.html">Zurück</a> </td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right"> <a accesskey="n" href="ch02s12.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.11. E-Mail-Versand aus kivitendo heraus"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="config.sending-email"></a>2.11. E-Mail-Versand aus kivitendo heraus</h2></div></div></div><p>kivitendo kann direkt aus dem Programm heraus E-Mails versenden,
+ <title>2.11. E-Mail-Versand aus kivitendo heraus</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s10.html" title="2.10. Drucker- und Systemverwaltung"><link rel="next" href="ch02s12.html" title="2.12. Drucken mit kivitendo"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.11. E-Mail-Versand aus kivitendo heraus</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s10.html">Zurück</a> </td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right"> <a accesskey="n" href="ch02s12.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.11. E-Mail-Versand aus kivitendo heraus"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="config.sending-email"></a>2.11. E-Mail-Versand aus kivitendo heraus</h2></div></div></div><p>kivitendo kann direkt aus dem Programm heraus E-Mails versenden,
z.B. um ein Angebot direkt an einen Kunden zu verschicken. Damit dies
funktioniert, muss eingestellt werden, über welchen Server die E-Mails
verschickt werden sollen. kivitendo unterstützt dabei zwei Mechanismen:
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>2.12. Drucken mit kivitendo</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s11.html" title="2.11. E-Mail-Versand aus kivitendo heraus"><link rel="next" href="ch02s13.html" title="2.13. OpenDocument-Vorlagen"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.12. Drucken mit kivitendo</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s11.html">Zurück</a> </td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right"> <a accesskey="n" href="ch02s13.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.12. Drucken mit kivitendo"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="Drucken-mit-kivitendo"></a>2.12. Drucken mit kivitendo</h2></div></div></div><p>Das Drucksystem von kivitendo benutzt von Haus aus LaTeX-Vorlagen.
+ <title>2.12. Drucken mit kivitendo</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s11.html" title="2.11. E-Mail-Versand aus kivitendo heraus"><link rel="next" href="ch02s13.html" title="2.13. OpenDocument-Vorlagen"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.12. Drucken mit kivitendo</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s11.html">Zurück</a> </td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right"> <a accesskey="n" href="ch02s13.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.12. Drucken mit kivitendo"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="Drucken-mit-kivitendo"></a>2.12. Drucken mit kivitendo</h2></div></div></div><p>Das Drucksystem von kivitendo benutzt von Haus aus LaTeX-Vorlagen.
Um drucken zu können, braucht der Server ein geeignetes LaTeX System. Am
einfachsten ist dazu eine <code class="literal">texlive</code> Installation. Unter
debianoiden Betriebssystemen installiert man die Pakete mit:</p><p>
</p><pre class="programlisting">zypper install texlive-collection-latex texlive-collection-latexextra \
texlive-collection-latexrecommended texlive-collection-langgerman \
texlive-collection-langenglish</pre><p>
- </p><p>kivitendo bringt drei alternative Vorlagensätze mit:</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>RB</p></li><li class="listitem"><p>f-tex</p></li><li class="listitem"><p>rev-odt</p></li></ul></div><p>Der ehemalige Druckvorlagensatz "Standard" wurde mit der Version
- 3.3 entfernt, da er nicht mehr gepflegt wurde.</p><div class="sect2" title="2.12.1. Vorlagenverzeichnis anlegen"><div class="titlepage"><div><div><h3 class="title"><a name="Vorlagenverzeichnis-anlegen"></a>2.12.1. Vorlagenverzeichnis anlegen</h3></div></div></div><p>Es lässt sich ein initialer Vorlagensatz erstellen. Die
+ </p><div class="note" title="Anmerkung" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Anmerkung]" src="system/docbook-xsl/images/note.png"></td><th align="left">Anmerkung</th></tr><tr><td align="left" valign="top">kivitendo erwartet eine aktuelle TeX Live Umgebung, um PDF/A zu erzeugen. Aktuelle Distributionen von 2020 erfüllen diese. Überprüfbar ist dies mit dem Aufruf des installation_check.pl mit Parameter -l: <pre class="programlisting">scripts/installations_check.pl -l</pre></td></tr></table></div><p>kivitendo bringt drei alternative Vorlagensätze mit:</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>RB</p></li><li class="listitem"><p>marei</p></li><li class="listitem"><p>rev-odt</p></li></ul></div><p>Der ehemalige Druckvorlagensatz "f-tex" wurde mit der Version
+ 3.6 entfernt, da er nicht mehr gepflegt wird.</p><div class="sect2" title="2.12.1. Vorlagenverzeichnis anlegen"><div class="titlepage"><div><div><h3 class="title"><a name="Vorlagenverzeichnis-anlegen"></a>2.12.1. Vorlagenverzeichnis anlegen</h3></div></div></div><p>Es lässt sich ein initialer Vorlagensatz erstellen. Die
LaTeX-System-Abhängigkeiten hierfür kann man prüfen mit:</p><pre class="programlisting">./scripts/installation_check.pl -lv</pre><p>Der Angemeldete Benutzer muss in einer Gruppe sein, die über das
Recht "Konfiguration -> Mandantenverwaltung" verfügt. Siehe auch
<a class="xref" href="ch02s09.html#Gruppen-anlegen" title="2.9.4. Gruppen anlegen">Abschnitt 2.9.4, „Gruppen anlegen“</a>.</p><p>Im Userbereich lässt sich unter: "<span class="guimenu">System</span>
Druckvorlagen aus Vorlagensatz erstellen" auswählen.</p><div class="orderedlist"><ol class="orderedlist" type="1"><li class="listitem"><p>
<code class="option">Vorlagen auswählen</code>: Wählen Sie hier den
Vorlagensatz aus, der kopiert werden soll
- (<code class="filename">RB</code>, <code class="filename">f-tex</code> oder
+ (<code class="filename">RB</code>, <code class="filename">marei</code> oder
<code class="filename">odt-rev</code>.)</p></li><li class="listitem"><p>
<code class="option">Neuer Name</code>: Der Verzeichnisname für den
neuen Vorlagensatz. Dieser kann im Rahmen der üblichen Bedingungen
werden, z.B. für Kopf- und Fußzeilen, und Infos wie
Bankdaten</p></li><li class="listitem"><p>mehrere vordefinierte Varianten für
Logos/Hintergrundbilder</p></li><li class="listitem"><p>Berücksichtigung für Steuerzonen "EU mit USt-ID Nummer" oder
- "Außerhalb EU"</p></li></ul></div></div><div class="sect2" title="2.12.3. f-tex"><div class="titlepage"><div><div><h3 class="title"><a name="f-tex"></a>2.12.3. f-tex</h3></div></div></div><p>Ein Vorlagensatz, der in wenigen Minuten alle Dokumente zur
- Verfügung stellt.</p><div class="sect3" title="2.12.3.1. Feature-Übersicht"><div class="titlepage"><div><div><h4 class="title"><a name="f-tex-Feature-%C3%9Cbersicht"></a>2.12.3.1. Feature-Übersicht</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Keine Redundanz. Es wird ein- und dieselbe LaTeX-Vorlage
- für alle briefartigen Dokumente verwendet. Also Angebot,
- Rechnung, Proformarechnung, Lieferschein, aber eben nicht für
- Paketaufkleber etc.</p></li><li class="listitem"><p>Leichte Anpassung an das Firmen-Layout durch Verwendung
- eines Hintergrund-PDFs. Dieses kann leicht mit dem eigenen
- Lieblingsprogramm erstellt werden (Openoffice, Inkscape, Gimp,
- Adobe*)</p></li><li class="listitem"><p>Hintergrund-PDF umschaltbar auf "nur erste Seite"
- (Standard) oder "alle Seiten" (Option
- "<code class="option">bgPdfFirstPageOnly</code>" in Datei
- <code class="filename">letter.lco</code>)</p></li><li class="listitem"><p>Hintergrund-PDF für Ausdruck auf bereits bedrucktem
- Briefpapier abschaltbar. Es wird dann nur bei per E-Mail
- versendeten Dokumenten eingebunden (Option
- "<code class="option">bgPdfEmailOnly</code>" in Datei
- <code class="filename">letter.lco</code>).</p></li><li class="listitem"><p>Nutzung der Layout-Funktionen von LaTeX für Seitenumbruch,
- Wiederholung von Kopfzeilen, Zwischensummen etc. (danke an
- Kai-Martin Knaak für die Vorarbeit)</p></li><li class="listitem"><p>Anzeige des Empfängerlandes im Adressfeld nur, wenn es vom
- Land des eigenen Unternehmens abweicht (also die Rechnung das
- Land verlässt).</p></li><li class="listitem"><p>Multisprachfähig leicht um weitere Sprachen zu erweitern,
- alle Übersetzungen in der Datei
- <code class="filename">translatinos.tex</code>.</p></li><li class="listitem"><p>Auflistung von Bruttopreisen für Endverbraucher.</p></li></ul></div></div><div class="sect3" title="2.12.3.2. Die Installation"><div class="titlepage"><div><div><h4 class="title"><a name="f-tex-Installation"></a>2.12.3.2. Die Installation</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Vorlagenverzeichnis mit Option f-tex anlegen, siehe: <a class="xref" href="ch02s12.html#Vorlagenverzeichnis-anlegen" title="2.12.1. Vorlagenverzeichnis anlegen">Vorlagenverzeichnis anlegen</a>. Das Vorlagensystem
- funktioniert jetzt schon, hat allerdings noch einen
- Beispiel-Briefkopf.</p></li><li class="listitem"><p>Erstelle eine pdf-Hintergrund Datei und verlinke sie nach
- <code class="filename">./letter_head.pdf</code>.</p></li><li class="listitem"><p>Editiere den Bereich "<code class="option">settings</code>" in der
- datei <code class="filename">letter.lco</code>.</p></li></ul></div><p>oder etwas detaillierter:</p><p>Es wird eine Datei <code class="filename">sample.lco</code> erstellt
- und diese nach <code class="filename">letter.lco</code> verlinkt. Eigentlich
- ist dies die Datei die für die firmenspezifischen Anpassungen
- gedacht ist. Da die Einstiegshürde in LaTeX nicht ganz niedrig ist,
- wird in dieser Datei auf ein Hintergrund-PDF verwiesen. Ich empfehle
- über dieses PDF die persönlichen Layoutanpassungen vorzunehmen und
- <code class="filename">sample.lco</code> unverändert zu lassen. Die Anpassung
- über eine <code class="filename">*.lco</code>-Datei, die letztlich auf
- <code class="filename">letter.lco</code> verlinkt ist ist aber auch
- möglich.</p><p>Es wird eine Datei <code class="filename">sample_head.pdf</code> mit
- ausgeliefert, diese wird nach <code class="filename">letter_head.pdf</code>
- verlinkt. Damit gibt es schon mal eine funktionsfähige Vorlage.
- Schau Dir nach Abschluss der Installation die Datei
- <code class="filename">sample_head.pdf</code> an und erstelle ein
- entsprechendes PDF passend zum Briefkopf Deiner Firma, diese dann im
- Template Verzeichniss ablegen und statt
- <code class="filename">sample_head.pdf</code> nach
- <code class="filename">letter_head.pdf</code> verlinken.</p><p>Letzlich muss <code class="filename">letter_head.pdf</code> auf das
- passende Hintergrund-PDF verweisen, welches gewünschten Briefkopf
- enthält.</p><p>Es wird eine Datei <code class="filename">mydata.tex.example</code>
- ausgeliefert, die nach <code class="filename">mytdata.tex</code> verlinkt
- ist. Bei verwendetem Hintergrund-PDF wird nur der Eintrag für das
- Land verwendet. Die Datei muss also nicht angefasst werden. Die
- anderen Werte sind für das Modul 'lp' (Label Print in erp - zur Zeit
- nicht im öffentlichen Zweig).</p><p>Alle Anpassungen zum Briefkopf, Fusszeilen, Firmenlogos, etc.
- sollten über die Hintergrund-PDF-Datei oder die
- <code class="filename">*.lco</code>-Datei erfolgen.</p></div><div class="sect3" title="2.12.3.3. f-tex Funktionsübersicht"><div class="titlepage"><div><div><h4 class="title"><a name="f-tex-Funktions%C3%BCbersicht"></a>2.12.3.3. f-tex Funktionsübersicht</h4></div></div></div><p>Das Konzept von kivitendo sieht vor, für jedes Dokument
- (Auftragsbestätigung, Lieferschein, Rechnung, etc.) eine
- LaTeX-Vorlage vorzuhalten, dies ist sehr wartungsunfreundlich. Auch
- das Einlesen einer einheitlichen Quelle für den Briefkopf bringt nur
- bedingte Vorteile, da hier leicht die Pflege der Artikel-Tabellen
- aus dem Ruder läuft. Bei dem vorliegenden Ansatz wird für alle
- briefartigen Dokumente mit Artikel-Tabellen eine einheitliche
- LaTeX-Vorlage verwendet, welche über Codeweichen die Besonderheiten
- der jeweiligen Dokumente berücksichtigt:</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Tabellen mit oder ohne Preis</p></li><li class="listitem"><p>Sprache der Tabellenüberschriften etc.</p></li><li class="listitem"><p>Anpassung der Bezugs-Zeile (z.B. Rechnungsnummer versus
- Angebotsnummer)</p></li><li class="listitem"><p>Darstellung von Brutto oder Netto-Preisen in der
- Auflistung (Endverbraucher versus gewerblicher Kunde)</p></li></ul></div><p>Nachteil:</p><p>LaTeX hat ohnehin eine sehr steile Lehrnkurve. Die Datei
- <code class="filename">letter.tex</code> ist sehr komplex und verstärkt damit
- diesen Effekt noch einmal erheblich. Wer LaTeX-Erfahrung hat, oder
- geübt ist Scriptsparachen nachzuvollziehen kann natürlich auch
- innerhalb der Tabellendarstellung gut persönliche Anpassungen
- vornehmen. Aber man kann sich hier bei Veränderungen sehr schnell
- heftig in den Fuss schiessen.</p><p>Wer nicht so tief in die Materie einsteigen will oder leicht
- zu frustrieren ist, sollte sein Hintergrund-PDF auf Basis der
- mitglieferten Datei <code class="filename">sample_head.pdf</code> erstellen,
- und sich an der Form der dargestellten Tabellen, wie sie
- ausgeliefert werden, erfreuen.</p><p>Kleiner Tipp: Nicht zu viel auf einmal wollen, lieber kleine,
- kontinuierliche Schritte gehen.</p></div><div class="sect3" title="2.12.3.4. Bruttopreise für Endverbraucher"><div class="titlepage"><div><div><h4 class="title"><a name="f-tex-Bruttopreise"></a>2.12.3.4. Bruttopreise für Endverbraucher</h4></div></div></div><p>Der auszuweisende Bruttopreis wird innerhalb der
- LaTeX-Umgebung berechnet. Es gibt zwar ein Feld, um bei Aufträgen
- "alle Preise Brutto" auszuwählen, aber:</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>hierfür müssen die Preise auch in Brutto in der Datenbank
- stehen (ja - das lässt sich über die Preisgruppen und die
- Zuordung einer Default-Preisgruppe handhaben)</p></li><li class="listitem"><p>man darf beim Anlegen des Vorgangs nicht vergessen, dieses
- Häkchen zu setzen. (Das ist in der Praxis, wenn man sowohl
- Endverbraucher als auch Gewerbekunden beliefert, der eigentliche
- Knackpunkt)</p></li></ul></div><p>Es gibt mit f-tex eine weitere Alternative. Die Information ob
- Brutto oder Nettorechnung wird mit den Zahlarten verknüpft.
- Zahlarten bei denen Rechnungen, Angebote, etc, in Brutto ausgegeben
- werden sollen, enden mit "_E" (für Endverbraucher). Falls identische
- Zahlarten für Gewerbekunden und Endverbraucher vorhanden sind, legt
- man diese einfach doppelt an (einmal mit der Namensendung "_E").
- Gewinn:</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Die Entscheidung, ob Nettopreise ausgewiesen werden, ist
- nicht mehr fix mit einer Preisliste verbunden.</p></li><li class="listitem"><p>Die Default-Zahlart kann im Kundendatensatz hinterlegt
- werden, und man muss nicht mehr daran denken, "alle Preise
- Netto" auszuwählen.</p></li><li class="listitem"><p>Die Entscheidung, ob Netto- oder Bruttopreise ausgewiesen
- werden, kann direkt beim Drucken revidiert werden, ohne dass
- sich der Auftragswert ändert.</p></li></ul></div></div><div class="sect3" title="2.12.3.5. Lieferadressen"><div class="titlepage"><div><div><h4 class="title"><a name="f-tex-lieferadressen"></a>2.12.3.5. Lieferadressen</h4></div></div></div><p>In Lieferscheinen kommen <code class="varname">shipto*</code>-Variablen
- im Adressfeld zum Einsatz. Wenn die
- <code class="varname">shipto*</code>-Variable leer ist, wird die entsprechende
- Adressvariable eingesetzt. Wenn also die Lieferadresse in Straße,
- Hausnummer und Ort abweicht, müssen auch nur diese Felder in der
- Lieferadresse ausgefüllt werden. Für den Firmenname wird der Wert
- der Hauptadresse angezeigt.</p></div></div><div class="sect2" title="2.12.4. Der Druckvorlagensatz rev-odt"><div class="titlepage"><div><div><h3 class="title"><a name="Vorlagen-rev-odt"></a>2.12.4. Der Druckvorlagensatz rev-odt</h3></div></div></div><p>Hierbei handelt es sich um einen Dokumentensatz der mit
+ "Außerhalb EU"</p></li></ul></div></div><div class="sect2" title="2.12.3. Der Druckvorlagensatz rev-odt"><div class="titlepage"><div><div><h3 class="title"><a name="Vorlagen-rev-odt"></a>2.12.3. Der Druckvorlagensatz rev-odt</h3></div></div></div><p>Hierbei handelt es sich um einen Dokumentensatz der mit
odt-Vorlagen erstellt wurde. Es gibt in dem Verzeichnis eine
Readme-Datei, die eventuell aktueller als die Dokumentation hier ist.
Die odt-Vorlagen in diesem Verzeichnis "rev-odt" wurden von revamp-it,
die verrechneten Mahngebühren und Verzugszinsen.</p><p>Zur Zeit gibt es in kivitendo noch keine Möglichkeit,
odt-Vorlagen bei Briefen und Pflichtenheften einzusetzen.
Entsprechende Vorlagen sind deshalb nicht vorhanden.</p><p>Fehlermeldungen, Anregungen und Wünsche bitte senden an:
- empfang@revamp-it.ch</p></div><div class="sect2" title="2.12.5. Allgemeine Hinweise zu LaTeX Vorlagen"><div class="titlepage"><div><div><h3 class="title"><a name="allgemeine-hinweise-zu-latex"></a>2.12.5. Allgemeine Hinweise zu LaTeX Vorlagen</h3></div></div></div><p>In den allermeisten Installationen sollte das Drucken jetzt
+ empfang@revamp-it.ch</p></div><div class="sect2" title="2.12.4. Allgemeine Hinweise zu LaTeX Vorlagen"><div class="titlepage"><div><div><h3 class="title"><a name="allgemeine-hinweise-zu-latex"></a>2.12.4. Allgemeine Hinweise zu LaTeX Vorlagen</h3></div></div></div><p>In den allermeisten Installationen sollte das Drucken jetzt
schon funktionieren. Sollte ein Fehler auftreten, wirft TeX sehr lange
Fehlerbeschreibungen, der eigentliche Fehler ist immer die erste
Zeile, die mit einem Ausrufezeichen anfängt. Häufig auftretende Fehler
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>2.13. OpenDocument-Vorlagen</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s12.html" title="2.12. Drucken mit kivitendo"><link rel="next" href="ch02s14.html" title="2.14. Nomenklatur"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.13. OpenDocument-Vorlagen</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s12.html">Zurück</a> </td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right"> <a accesskey="n" href="ch02s14.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.13. OpenDocument-Vorlagen"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="OpenDocument-Vorlagen"></a>2.13. OpenDocument-Vorlagen</h2></div></div></div><p>kivitendo unterstützt die Verwendung von Vorlagen im
+ <title>2.13. OpenDocument-Vorlagen</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s12.html" title="2.12. Drucken mit kivitendo"><link rel="next" href="ch02s14.html" title="2.14. Nomenklatur"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.13. OpenDocument-Vorlagen</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s12.html">Zurück</a> </td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right"> <a accesskey="n" href="ch02s14.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.13. OpenDocument-Vorlagen"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="OpenDocument-Vorlagen"></a>2.13. OpenDocument-Vorlagen</h2></div></div></div><p>kivitendo unterstützt die Verwendung von Vorlagen im
OpenDocument-Format, wie es LibreOffice oder OpenOffice (ab Version 2)
erzeugen. kivitendo kann dabei sowohl neue OpenDocument-Dokumente als
auch aus diesen direkt PDF-Dateien erzeugen. Um die Unterstützung von
Verzeichnis umbenannt werden.</p><p>Dieses Verzeichnis, wie auch das komplette
<code class="literal">users</code>-Verzeichnis, muss vom Webserver beschreibbar
sein. Dieses wurde bereits erledigt (siehe <a class="xref" href="ch02s03.html" title="2.3. Manuelle Installation des Programmpaketes">Manuelle Installation des Programmpaketes</a>), kann aber erneut
- überprüft werden, wenn die Konvertierung nach PDF fehlschlägt.</p><div class="sect2" title="2.13.1. OpenDocument (odt) Druckvorlagen mit Makros"><div class="titlepage"><div><div><h3 class="title"><a name="d0e2449"></a>2.13.1. OpenDocument (odt) Druckvorlagen mit Makros</h3></div></div></div><p>OpenDocument Vorlagen können Makros enthalten, welche komplexere
+ überprüft werden, wenn die Konvertierung nach PDF fehlschlägt.</p><div class="sect2" title="2.13.1. OpenDocument (odt) Druckvorlagen mit Makros"><div class="titlepage"><div><div><h3 class="title"><a name="d0e2420"></a>2.13.1. OpenDocument (odt) Druckvorlagen mit Makros</h3></div></div></div><p>OpenDocument Vorlagen können Makros enthalten, welche komplexere
Aufgaben erfüllen.</p><p>Der Vorlagensatz "rev-odt" enthält solche Vorlagen mit <span class="bold"><strong>Schweizer Bank-Einzahlungsscheinen (BESR)</strong></span>.
Diese Makros haben die Aufgabe, die in den Einzahlungsscheinen
benötigte Referenznummer und Kodierzeile zu erzeugen. Hier eine kurze
Beschreibung, wie die Makros aufgebaut sind, und was bei ihrer Nutzung
zu beachten ist (<span class="bold"><strong>in fett sind nötige einmalige
- Anpassungen aufgeführt</strong></span>):</p><div class="sect3" title="2.13.1.1. Bezeichnung der Vorlagen"><div class="titlepage"><div><div><h4 class="title"><a name="d0e2462"></a>2.13.1.1. Bezeichnung der Vorlagen</h4></div></div></div><p>Rechnung: invoice_besr.odt, Auftrag:
- sales_order_besr.odt</p></div><div class="sect3" title="2.13.1.2. Vorbereitungen im Adminbereich"><div class="titlepage"><div><div><h4 class="title"><a name="d0e2467"></a>2.13.1.2. Vorbereitungen im Adminbereich</h4></div></div></div><p>Damit beim Erstellen von Rechnungen und Aufträgen neben der
+ Anpassungen aufgeführt</strong></span>):</p><div class="sect3" title="2.13.1.1. Bezeichnung der Vorlagen"><div class="titlepage"><div><div><h4 class="title"><a name="d0e2433"></a>2.13.1.1. Bezeichnung der Vorlagen</h4></div></div></div><p>Rechnung: invoice_besr.odt, Auftrag:
+ sales_order_besr.odt</p></div><div class="sect3" title="2.13.1.2. Vorbereitungen im Adminbereich"><div class="titlepage"><div><div><h4 class="title"><a name="d0e2438"></a>2.13.1.2. Vorbereitungen im Adminbereich</h4></div></div></div><p>Damit beim Erstellen von Rechnungen und Aufträgen neben der
Standardvorlage ohne Einzahlungsschein weitere Vorlagen (z.B. mit
Einzahlungsschein) auswählbar sind, muss für jedes Vorlagen-Suffix
ein Drucker eingerichtet werden:</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Druckeradministration → Drucker hinzufügen</p></li><li class="listitem"><p>Mandant wählen</p></li><li class="listitem"><p>Druckerbeschreibung → aussagekräftiger Text: wird in der
Aufträgen oder Rechnungen als odt-Datei keine Bedeutung, darf
aber nicht leer sein)</p></li><li class="listitem"><p>Vorlagenkürzel → besr bzw. selbst gewähltes Vorlagensuffix
(muss genau der Zeichenfolge entsprechen, die zwischen
- "invoice_" bzw. "sales_order_" und ".odt" steht.)</p></li><li class="listitem"><p>speichern</p></li></ul></div></div><div class="sect3" title="2.13.1.3. Benutzereinstellungen"><div class="titlepage"><div><div><h4 class="title"><a name="d0e2491"></a>2.13.1.3. Benutzereinstellungen</h4></div></div></div><p>Wer den Ausdruck mit Einzahlungsschein als Standardeinstellung
+ "invoice_" bzw. "sales_order_" und ".odt" steht.)</p></li><li class="listitem"><p>speichern</p></li></ul></div></div><div class="sect3" title="2.13.1.3. Benutzereinstellungen"><div class="titlepage"><div><div><h4 class="title"><a name="d0e2462"></a>2.13.1.3. Benutzereinstellungen</h4></div></div></div><p>Wer den Ausdruck mit Einzahlungsschein als Standardeinstellung
im Rechnungs- bzw. Auftragsformular angezeigt haben möchte, kann
dies persönlich für sich bei den Benutzereinstellungen
konfigurieren:</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Programm → Benutzereinstellungen → Druckoptionen</p></li><li class="listitem"><p>Standardvorlagenformat → OpenDocument/OASIS</p></li><li class="listitem"><p>Standardausgabekanal → Bildschirm</p></li><li class="listitem"><p>Standarddrucker → gewünschte Druckerbeschreibung auswählen
- (z.B. mit Einzahlungsschein Bank xy)</p></li><li class="listitem"><p>Anzahl Kopien → leer</p></li><li class="listitem"><p>speichern</p></li></ul></div></div><div class="sect3" title="2.13.1.4. Aufbau und nötige Anpassungen der Vorlagen"><div class="titlepage"><div><div><h4 class="title"><a name="d0e2515"></a>2.13.1.4. Aufbau und nötige Anpassungen der Vorlagen</h4></div></div></div><p>In der Vorlage sind als Modul "BESR" 4 Makros gespeichert, die
+ (z.B. mit Einzahlungsschein Bank xy)</p></li><li class="listitem"><p>Anzahl Kopien → leer</p></li><li class="listitem"><p>speichern</p></li></ul></div></div><div class="sect3" title="2.13.1.4. Aufbau und nötige Anpassungen der Vorlagen"><div class="titlepage"><div><div><h4 class="title"><a name="d0e2486"></a>2.13.1.4. Aufbau und nötige Anpassungen der Vorlagen</h4></div></div></div><p>In der Vorlage sind als Modul "BESR" 4 Makros gespeichert, die
aus dem von kivitendo erzeugten odt-Dokument die korrekte
Referenznummer inklusive Prüfziffer sowie die Kodierzeile in
OCRB-Schrift erzeugen und am richtigen Ort ins Dokument
angepasst werden.</strong></span> Dabei ist darauf zu achten, dass
sich die Positionen der Postkonto-Nummern der Bank, sowie der
Zeichenfolgen dddfr, DDDREF1, DDDREF2, 609, DDDKODIERZEILE nicht
- verschieben.</p></li></ul></div><div class="screenshot"><div class="mediaobject"><img src="images/Einzahlungsschein_Makro.png"></div></div></div><div class="sect3" title="2.13.1.5. Auswahl der Druckvorlage in kivitendo beim Erzeugen einer odt-Rechnung (analog bei Auftrag)"><div class="titlepage"><div><div><h4 class="title"><a name="d0e2579"></a>2.13.1.5. Auswahl der Druckvorlage in kivitendo beim Erzeugen einer
+ verschieben.</p></li></ul></div><div class="screenshot"><div class="mediaobject"><img src="images/Einzahlungsschein_Makro.png"></div></div></div><div class="sect3" title="2.13.1.5. Auswahl der Druckvorlage in kivitendo beim Erzeugen einer odt-Rechnung (analog bei Auftrag)"><div class="titlepage"><div><div><h4 class="title"><a name="d0e2550"></a>2.13.1.5. Auswahl der Druckvorlage in kivitendo beim Erzeugen einer
odt-Rechnung (analog bei Auftrag)</h4></div></div></div><p>Im Fussbereich der Rechnungsmaske muss neben Rechnung,
OpenDocument/OASIS und Bildschirm die im Adminbereich erstellte
Druckerbeschreibung ausgewählt werden, falls diese nicht bereits bei
den Benutzereinstellungen als persönlicher Standard gewählt
- wurde.</p></div><div class="sect3" title="2.13.1.6. Makroeinstellungen in LibreOffice anpassen"><div class="titlepage"><div><div><h4 class="title"><a name="d0e2584"></a>2.13.1.6. Makroeinstellungen in LibreOffice anpassen</h4></div></div></div><p>Falls beim Öffnen einer von kivitendo erzeugten odt-Rechnung
+ wurde.</p></div><div class="sect3" title="2.13.1.6. Makroeinstellungen in LibreOffice anpassen"><div class="titlepage"><div><div><h4 class="title"><a name="d0e2555"></a>2.13.1.6. Makroeinstellungen in LibreOffice anpassen</h4></div></div></div><p>Falls beim Öffnen einer von kivitendo erzeugten odt-Rechnung
die Meldung kommt, dass Makros aus Sicherheitsgründen nicht
ausgeführt werden, so müssen folgende Einstellungen in LibreOffice
angepasst werden:</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Extras → Optionen → Sicherheit → Makrosicherheit</p></li><li class="listitem"><p>Sicherheitslevel auf "Mittel" einstellen (Diese
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>2.14. Nomenklatur</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s13.html" title="2.13. OpenDocument-Vorlagen"><link rel="next" href="ch02s15.html" title="2.15. Konfiguration zur Einnahmenüberschussrechnung/Bilanzierung: EUR"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.14. Nomenklatur</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s13.html">Zurück</a> </td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right"> <a accesskey="n" href="ch02s15.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.14. Nomenklatur"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="nomenclature"></a>2.14. Nomenklatur</h2></div></div></div><div class="sect2" title="2.14.1. Datum bei Buchungen"><div class="titlepage"><div><div><h3 class="title"><a name="booking.dates"></a>2.14.1. Datum bei Buchungen</h3></div></div></div><p>Seit der Version 3.5 werden für Buchungen in kivitendo
+ <title>2.14. Nomenklatur</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s13.html" title="2.13. OpenDocument-Vorlagen"><link rel="next" href="ch02s15.html" title="2.15. Konfiguration zur Einnahmenüberschussrechnung/Bilanzierung: EUR"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.14. Nomenklatur</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s13.html">Zurück</a> </td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right"> <a accesskey="n" href="ch02s15.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.14. Nomenklatur"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="nomenclature"></a>2.14. Nomenklatur</h2></div></div></div><div class="sect2" title="2.14.1. Datum bei Buchungen"><div class="titlepage"><div><div><h3 class="title"><a name="booking.dates"></a>2.14.1. Datum bei Buchungen</h3></div></div></div><p>Seit der Version 3.5 werden für Buchungen in kivitendo
einheitlich folgende Bezeichnungen verwendet:</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>
<code class="option">Erfassungsdatum</code> (en: <code class="option">Entry
Date</code>, code: <code class="option">Gldate</code>)</p><p>bezeichnet das Datum, an dem die Buchung in kivitendo
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>2.15. Konfiguration zur Einnahmenüberschussrechnung/Bilanzierung: EUR</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s14.html" title="2.14. Nomenklatur"><link rel="next" href="ch02s16.html" title="2.16. SKR04 19% Umstellung für innergemeinschaftlichen Erwerb"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.15. Konfiguration zur Einnahmenüberschussrechnung/Bilanzierung:
+ <title>2.15. Konfiguration zur Einnahmenüberschussrechnung/Bilanzierung: EUR</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s14.html" title="2.14. Nomenklatur"><link rel="next" href="ch02s16.html" title="2.16. SKR04 19% Umstellung für innergemeinschaftlichen Erwerb"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.15. Konfiguration zur Einnahmenüberschussrechnung/Bilanzierung:
EUR</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s14.html">Zurück</a> </td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right"> <a accesskey="n" href="ch02s16.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.15. Konfiguration zur Einnahmenüberschussrechnung/Bilanzierung: EUR"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="config.eur"></a>2.15. Konfiguration zur Einnahmenüberschussrechnung/Bilanzierung:
EUR</h2></div></div></div><div class="sect2" title="2.15.1. Einführung"><div class="titlepage"><div><div><h3 class="title"><a name="config.eur.introduction"></a>2.15.1. Einführung</h3></div></div></div><p>kivitendo besaß bis inklusive Version 2.6.3 einen
Konfigurationsparameter namens <code class="varname">eur</code>, der sich in der
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>2.16. SKR04 19% Umstellung für innergemeinschaftlichen Erwerb</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s15.html" title="2.15. Konfiguration zur Einnahmenüberschussrechnung/Bilanzierung: EUR"><link rel="next" href="ch02s17.html" title="2.17. Verhalten des Bilanzberichts"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.16. SKR04 19% Umstellung für innergemeinschaftlichen Erwerb</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s15.html">Zurück</a> </td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right"> <a accesskey="n" href="ch02s17.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.16. SKR04 19% Umstellung für innergemeinschaftlichen Erwerb"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="config.skr04-update-3804"></a>2.16. SKR04 19% Umstellung für innergemeinschaftlichen Erwerb</h2></div></div></div><div class="sect2" title="2.16.1. Einführung"><div class="titlepage"><div><div><h3 class="title"><a name="config.skr04-update-3804.introduction"></a>2.16.1. Einführung</h3></div></div></div><p>Die Umsatzsteuerumstellung auf 19% für SKR04 für die
+ <title>2.16. SKR04 19% Umstellung für innergemeinschaftlichen Erwerb</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s15.html" title="2.15. Konfiguration zur Einnahmenüberschussrechnung/Bilanzierung: EUR"><link rel="next" href="ch02s17.html" title="2.17. Verhalten des Bilanzberichts"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.16. SKR04 19% Umstellung für innergemeinschaftlichen Erwerb</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s15.html">Zurück</a> </td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right"> <a accesskey="n" href="ch02s17.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.16. SKR04 19% Umstellung für innergemeinschaftlichen Erwerb"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="config.skr04-update-3804"></a>2.16. SKR04 19% Umstellung für innergemeinschaftlichen Erwerb</h2></div></div></div><div class="sect2" title="2.16.1. Einführung"><div class="titlepage"><div><div><h3 class="title"><a name="config.skr04-update-3804.introduction"></a>2.16.1. Einführung</h3></div></div></div><p>Die Umsatzsteuerumstellung auf 19% für SKR04 für die
Steuerschlüssel "EU ohne USt-ID Nummer" ist erst 2010 erfolgt.
kivitendo beinhaltet ein Upgradeskript, das das Konto 3804 automatisch
erstellt und die Steuereinstellungen korrekt einstellt. Hat der
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>2.17. Verhalten des Bilanzberichts</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s16.html" title="2.16. SKR04 19% Umstellung für innergemeinschaftlichen Erwerb"><link rel="next" href="ch02s18.html" title="2.18. Erfolgsrechnung"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.17. Verhalten des Bilanzberichts</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s16.html">Zurück</a> </td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right"> <a accesskey="n" href="ch02s18.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.17. Verhalten des Bilanzberichts"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="config.bilanz"></a>2.17. Verhalten des Bilanzberichts</h2></div></div></div><p>Bis Version 3.0 wurde "closedto" ("Bücher schließen zum") als
+ <title>2.17. Verhalten des Bilanzberichts</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s16.html" title="2.16. SKR04 19% Umstellung für innergemeinschaftlichen Erwerb"><link rel="next" href="ch02s18.html" title="2.18. Erfolgsrechnung"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.17. Verhalten des Bilanzberichts</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s16.html">Zurück</a> </td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right"> <a accesskey="n" href="ch02s18.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.17. Verhalten des Bilanzberichts"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="config.bilanz"></a>2.17. Verhalten des Bilanzberichts</h2></div></div></div><p>Bis Version 3.0 wurde "closedto" ("Bücher schließen zum") als
Grundlage für das Startdatum benutzt. Schließt man die Bücher allerdings
monatsweise führt dies zu falschen Werten.</p><p>In der Mandantenkonfiguration kann man dieses Verhalten genau
einstellen indem man:</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>weiterhin closed_to benutzt (Default, es ändert sich nichts zu
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>2.18. Erfolgsrechnung</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s17.html" title="2.17. Verhalten des Bilanzberichts"><link rel="next" href="ch02s19.html" title="2.19. Rundung in Verkaufsbelegen"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.18. Erfolgsrechnung</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s17.html">Zurück</a> </td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right"> <a accesskey="n" href="ch02s19.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.18. Erfolgsrechnung"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="config.erfolgsrechnung"></a>2.18. Erfolgsrechnung</h2></div></div></div><p>Seit der Version 3.4.1 existiert in kivitendo der Bericht
+ <title>2.18. Erfolgsrechnung</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s17.html" title="2.17. Verhalten des Bilanzberichts"><link rel="next" href="ch02s19.html" title="2.19. Rundung in Verkaufsbelegen"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.18. Erfolgsrechnung</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s17.html">Zurück</a> </td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right"> <a accesskey="n" href="ch02s19.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.18. Erfolgsrechnung"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="config.erfolgsrechnung"></a>2.18. Erfolgsrechnung</h2></div></div></div><p>Seit der Version 3.4.1 existiert in kivitendo der Bericht
<span class="bold"><strong> Erfolgsrechnung</strong></span>.</p><p>Die Erfolgsrechnung kann in der Mandantenkonfiguration unter
Features an- oder abgeschaltet werden. Mit der Einstellung
<code class="varname">default_manager = swiss </code> in der
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>2.19. Rundung in Verkaufsbelegen</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s18.html" title="2.18. Erfolgsrechnung"><link rel="next" href="ch02s20.html" title="2.20. Einstellungen pro Mandant"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.19. Rundung in Verkaufsbelegen</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s18.html">Zurück</a> </td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right"> <a accesskey="n" href="ch02s20.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.19. Rundung in Verkaufsbelegen"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="config.rounding"></a>2.19. Rundung in Verkaufsbelegen</h2></div></div></div><p>In der Schweiz hat die kleinste aktuell benutzte Münze den Wert
+ <title>2.19. Rundung in Verkaufsbelegen</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s18.html" title="2.18. Erfolgsrechnung"><link rel="next" href="ch02s20.html" title="2.20. Einstellungen pro Mandant"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.19. Rundung in Verkaufsbelegen</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s18.html">Zurück</a> </td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right"> <a accesskey="n" href="ch02s20.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.19. Rundung in Verkaufsbelegen"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="config.rounding"></a>2.19. Rundung in Verkaufsbelegen</h2></div></div></div><p>In der Schweiz hat die kleinste aktuell benutzte Münze den Wert
von 5 Rappen (0.05 CHF).</p><p>Auch wenn im elektronischen Zahlungsverkehr Beträge mit einer
Genauigkeit von 0.01 CHF verwendet werden können, ist es trotzdem nach
wie vor üblich, Rechnungen mit auf 0.05 CHF gerundeten Beträgen
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>2.20. Einstellungen pro Mandant</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s19.html" title="2.19. Rundung in Verkaufsbelegen"><link rel="next" href="ch02s21.html" title="2.21. kivitendo ERP verwenden"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.20. Einstellungen pro Mandant</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s19.html">Zurück</a> </td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right"> <a accesskey="n" href="ch02s21.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.20. Einstellungen pro Mandant"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="config.client"></a>2.20. Einstellungen pro Mandant</h2></div></div></div><p>Einige Einstellungen können von einem Benutzer mit dem <a class="link" href="ch02s09.html#Zusammenh%C3%A4nge" title="2.9.1. Zusammenhänge">Recht</a> "Administration (Für die Verwaltung
+ <title>2.20. Einstellungen pro Mandant</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s19.html" title="2.19. Rundung in Verkaufsbelegen"><link rel="next" href="ch02s21.html" title="2.21. kivitendo ERP verwenden"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.20. Einstellungen pro Mandant</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s19.html">Zurück</a> </td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right"> <a accesskey="n" href="ch02s21.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.20. Einstellungen pro Mandant"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="config.client"></a>2.20. Einstellungen pro Mandant</h2></div></div></div><p>Einige Einstellungen können von einem Benutzer mit dem <a class="link" href="ch02s09.html#Zusammenh%C3%A4nge" title="2.9.1. Zusammenhänge">Recht</a> "Administration (Für die Verwaltung
der aktuellen Instanz aus einem Userlogin heraus)" gemacht werden. Diese
Einstellungen sind dann für die aktuellen Mandanten-Datenbank gültig.
Die Einstellungen sind unter <span class="guimenu">System</span> →
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>2.21. kivitendo ERP verwenden</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s20.html" title="2.20. Einstellungen pro Mandant"><link rel="next" href="ch03.html" title="Kapitel 3. Features und Funktionen"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.21. kivitendo ERP verwenden</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s20.html">Zurück</a> </td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right"> <a accesskey="n" href="ch03.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.21. kivitendo ERP verwenden"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="kivitendo-ERP-verwenden"></a>2.21. kivitendo ERP verwenden</h2></div></div></div><p>Nach erfolgreicher Installation ist der Loginbildschirm unter
+ <title>2.21. kivitendo ERP verwenden</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s20.html" title="2.20. Einstellungen pro Mandant"><link rel="next" href="ch03.html" title="Kapitel 3. Features und Funktionen"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.21. kivitendo ERP verwenden</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s20.html">Zurück</a> </td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right"> <a accesskey="n" href="ch03.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.21. kivitendo ERP verwenden"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="kivitendo-ERP-verwenden"></a>2.21. kivitendo ERP verwenden</h2></div></div></div><p>Nach erfolgreicher Installation ist der Loginbildschirm unter
folgender URL erreichbar:</p><p>
<a class="ulink" href="http://localhost/kivitendo-erp/login.pl" target="_top">http://localhost/kivitendo-erp/login.pl</a>
</p><p>Die Administrationsseite erreichen Sie unter:</p><p>
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>Kapitel 3. Features und Funktionen</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="up" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="prev" href="ch02s21.html" title="2.21. kivitendo ERP verwenden"><link rel="next" href="ch03s02.html" title="3.2. Bankerweiterung"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">Kapitel 3. Features und Funktionen</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s21.html">Zurück</a> </td><th width="60%" align="center"> </th><td width="20%" align="right"> <a accesskey="n" href="ch03s02.html">Weiter</a></td></tr></table><hr></div><div class="chapter" title="Kapitel 3. Features und Funktionen"><div class="titlepage"><div><div><h2 class="title"><a name="features"></a>Kapitel 3. Features und Funktionen</h2></div></div></div><div class="sect1" title="3.1. Wiederkehrende Rechnungen"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="features.periodic-invoices"></a>3.1. Wiederkehrende Rechnungen</h2></div></div></div><div class="sect2" title="3.1.1. Einführung"><div class="titlepage"><div><div><h3 class="title"><a name="features.periodic-invoices.introduction"></a>3.1.1. Einführung</h3></div></div></div><p>Wiederkehrende Rechnungen werden als normale Aufträge definiert
+ <title>Kapitel 3. Features und Funktionen</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="prev" href="ch02s21.html" title="2.21. kivitendo ERP verwenden"><link rel="next" href="ch03s02.html" title="3.2. Bankerweiterung"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">Kapitel 3. Features und Funktionen</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s21.html">Zurück</a> </td><th width="60%" align="center"> </th><td width="20%" align="right"> <a accesskey="n" href="ch03s02.html">Weiter</a></td></tr></table><hr></div><div class="chapter" title="Kapitel 3. Features und Funktionen"><div class="titlepage"><div><div><h2 class="title"><a name="features"></a>Kapitel 3. Features und Funktionen</h2></div></div></div><div class="sect1" title="3.1. Wiederkehrende Rechnungen"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="features.periodic-invoices"></a>3.1. Wiederkehrende Rechnungen</h2></div></div></div><div class="sect2" title="3.1.1. Einführung"><div class="titlepage"><div><div><h3 class="title"><a name="features.periodic-invoices.introduction"></a>3.1.1. Einführung</h3></div></div></div><p>Wiederkehrende Rechnungen werden als normale Aufträge definiert
und konfiguriert, mit allen dazugehörigen Kunden- und Artikelangaben.
Die konfigurierten Aufträge werden später automatisch in Rechnungen
umgewandelt, so als ob man den Workflow benutzen würde, und auch die
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>3.2. Bankerweiterung</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch03.html" title="Kapitel 3. Features und Funktionen"><link rel="prev" href="ch03.html" title="Kapitel 3. Features und Funktionen"><link rel="next" href="ch03s03.html" title="3.3. Dokumentenvorlagen und verfügbare Variablen"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">3.2. Bankerweiterung</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch03.html">Zurück</a> </td><th width="60%" align="center">Kapitel 3. Features und Funktionen</th><td width="20%" align="right"> <a accesskey="n" href="ch03s03.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="3.2. Bankerweiterung"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="features.bank"></a>3.2. Bankerweiterung</h2></div></div></div><div class="sect2" title="3.2.1. Einführung"><div class="titlepage"><div><div><h3 class="title"><a name="features.bank.introduction"></a>3.2.1. Einführung</h3></div></div></div><p>Die Beschreibung der Bankerweiterung befindet sich derzeit noch
+ <title>3.2. Bankerweiterung</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch03.html" title="Kapitel 3. Features und Funktionen"><link rel="prev" href="ch03.html" title="Kapitel 3. Features und Funktionen"><link rel="next" href="ch03s03.html" title="3.3. Dokumentenvorlagen und verfügbare Variablen"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">3.2. Bankerweiterung</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch03.html">Zurück</a> </td><th width="60%" align="center">Kapitel 3. Features und Funktionen</th><td width="20%" align="right"> <a accesskey="n" href="ch03s03.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="3.2. Bankerweiterung"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="features.bank"></a>3.2. Bankerweiterung</h2></div></div></div><div class="sect2" title="3.2.1. Einführung"><div class="titlepage"><div><div><h3 class="title"><a name="features.bank.introduction"></a>3.2.1. Einführung</h3></div></div></div><p>Die Beschreibung der Bankerweiterung befindet sich derzeit noch
im Wiki und soll von dort später hierhin übernommen werden:</p><p>
<a class="ulink" href="http://redmine.kivitendo-premium.de/projects/forum/wiki/Bankerweiterung" target="_top">http://redmine.kivitendo-premium.de/projects/forum/wiki/Bankerweiterung</a>
</p></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="ch03.html">Zurück</a> </td><td width="20%" align="center"><a accesskey="u" href="ch03.html">Nach oben</a></td><td width="40%" align="right"> <a accesskey="n" href="ch03s03.html">Weiter</a></td></tr><tr><td width="40%" align="left" valign="top">Kapitel 3. Features und Funktionen </td><td width="20%" align="center"><a accesskey="h" href="index.html">Zum Anfang</a></td><td width="40%" align="right" valign="top"> 3.3. Dokumentenvorlagen und verfügbare Variablen</td></tr></table></div></body></html>
\ No newline at end of file
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>3.3. Dokumentenvorlagen und verfügbare Variablen</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch03.html" title="Kapitel 3. Features und Funktionen"><link rel="prev" href="ch03s02.html" title="3.2. Bankerweiterung"><link rel="next" href="ch03s04.html" title="3.4. Excel-Vorlagen"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">3.3. Dokumentenvorlagen und verfügbare Variablen</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch03s02.html">Zurück</a> </td><th width="60%" align="center">Kapitel 3. Features und Funktionen</th><td width="20%" align="right"> <a accesskey="n" href="ch03s04.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="3.3. Dokumentenvorlagen und verfügbare Variablen"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="dokumentenvorlagen-und-variablen"></a>3.3. Dokumentenvorlagen und verfügbare Variablen</h2></div></div></div><div class="sect2" title="3.3.1. Einführung"><div class="titlepage"><div><div><h3 class="title"><a name="dokumentenvorlagen-und-variablen.einf%C3%BChrung"></a>3.3.1. Einführung</h3></div></div></div><p>Dies ist eine Auflistung der Standard-Dokumentenvorlagen und
+ <title>3.3. Dokumentenvorlagen und verfügbare Variablen</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch03.html" title="Kapitel 3. Features und Funktionen"><link rel="prev" href="ch03s02.html" title="3.2. Bankerweiterung"><link rel="next" href="ch03s04.html" title="3.4. Excel-Vorlagen"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">3.3. Dokumentenvorlagen und verfügbare Variablen</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch03s02.html">Zurück</a> </td><th width="60%" align="center">Kapitel 3. Features und Funktionen</th><td width="20%" align="right"> <a accesskey="n" href="ch03s04.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="3.3. Dokumentenvorlagen und verfügbare Variablen"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="dokumentenvorlagen-und-variablen"></a>3.3. Dokumentenvorlagen und verfügbare Variablen</h2></div></div></div><div class="sect2" title="3.3.1. Einführung"><div class="titlepage"><div><div><h3 class="title"><a name="dokumentenvorlagen-und-variablen.einf%C3%BChrung"></a>3.3.1. Einführung</h3></div></div></div><p>Dies ist eine Auflistung der Standard-Dokumentenvorlagen und
aller zur Bearbeitung verfügbaren Variablen. Eine Variable wird in
einer Vorlage durch ihren Inhalt ersetzt, wenn sie in der Form
<code class="function"><%variablenname%></code> verwendet wird. Für
</span></dt><dd><p>Sprache</p></dd><dt><span class="term">
<code class="varname">name</code>
</span></dt><dd><p>Firmenname</p></dd><dt><span class="term">
+ <code class="varname">natural_person</code>
+ </span></dt><dd><p>Flag "natürliche Person"; Siehe auch
+ <a class="xref" href="ch03s03.html#dokumentenvorlagen-und-variablen.anrede" title="3.3.13. Hinweise zur Anrede">Hinweise zur Anrede</a>
+ </p></dd><dt><span class="term">
<code class="varname">payment_description</code>
</span></dt><dd><p>Name der Zahlart</p></dd><dt><span class="term">
<code class="varname">payment_terms</code>
<code class="varname">linetotal</code>
</span></dt><dd><p>Zeilensumme (Anzahl * Einzelpreis)</p></dd><dt><span class="term">
<code class="varname">longdescription</code>
- </span></dt><dd><p>Langtext</p></dd><dt><span class="term">
+ </span></dt><dd><p>Langtext, vorbelegt mit dem Feld Bemerkungen der entsprechenden Ware</p></dd><dt><span class="term">
<code class="varname">microfiche</code>
</span></dt><dd><p>Mikrofilm</p></dd><dt><span class="term">
<code class="varname">netprice</code>
<code class="varname">invdate</code>
</span></dt><dd><p>Rechnungsdatum</p></dd><dt><span class="term">
<code class="varname">invnumber</code>
- </span></dt><dd><p>Rechnungsnummer</p></dd></dl></div></div></div><div class="sect2" title="3.3.10. Variablen in anderen Vorlagen"><div class="titlepage"><div><div><h3 class="title"><a name="dokumentenvorlagen-und-variablen.andere-vorlagen"></a>3.3.10. Variablen in anderen Vorlagen</h3></div></div></div><div class="sect3" title="3.3.10.1. Einführung"><div class="titlepage"><div><div><h4 class="title"><a name="d0e5851"></a>3.3.10.1. Einführung</h4></div></div></div><p>Die Variablen in anderen Vorlagen sind ähnlich wie in der
+ </span></dt><dd><p>Rechnungsnummer</p></dd></dl></div></div></div><div class="sect2" title="3.3.10. Variablen in anderen Vorlagen"><div class="titlepage"><div><div><h3 class="title"><a name="dokumentenvorlagen-und-variablen.andere-vorlagen"></a>3.3.10. Variablen in anderen Vorlagen</h3></div></div></div><div class="sect3" title="3.3.10.1. Einführung"><div class="titlepage"><div><div><h4 class="title"><a name="d0e5833"></a>3.3.10.1. Einführung</h4></div></div></div><p>Die Variablen in anderen Vorlagen sind ähnlich wie in der
Rechnung. Allerdings heißen die Variablen, die mit
<code class="varname">inv</code> beginnen, jetzt anders. Bei den Angeboten
fangen sie mit <code class="varname">quo</code> für "quotation" an:
(HTML oder PDF über LaTeX) umgesetzt.</p><p>Die unterstützen Formatierungen sind:</p><div class="variablelist"><dl><dt><span class="term"><b>Text</b></span></dt><dd><p>Text wird in Fettdruck gesetzt.</p></dd><dt><span class="term"><i>Text</i></span></dt><dd><p>Text wird kursiv gesetzt.</p></dd><dt><span class="term"><u>Text</u></span></dt><dd><p>Text wird unterstrichen.</p></dd><dt><span class="term"><s>Text</s></span></dt><dd><p>Text wird durchgestrichen. Diese Formatierung ist nicht
bei der Ausgabe als PDF über LaTeX verfügbar.</p></dd><dt><span class="term"><bullet></span></dt><dd><p>Erzeugt einen ausgefüllten Kreis für Aufzählungen (siehe
unten).</p></dd></dl></div><p>Der Befehl <span class="command"><strong><bullet></strong></span> funktioniert
- momentan auch nur in Latex-Vorlagen.</p></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="ch03s02.html">Zurück</a> </td><td width="20%" align="center"><a accesskey="u" href="ch03.html">Nach oben</a></td><td width="40%" align="right"> <a accesskey="n" href="ch03s04.html">Weiter</a></td></tr><tr><td width="40%" align="left" valign="top">3.2. Bankerweiterung </td><td width="20%" align="center"><a accesskey="h" href="index.html">Zum Anfang</a></td><td width="40%" align="right" valign="top"> 3.4. Excel-Vorlagen</td></tr></table></div></body></html>
\ No newline at end of file
+ momentan auch nur in Latex-Vorlagen.</p></div><div class="sect2" title="3.3.13. Hinweise zur Anrede"><div class="titlepage"><div><div><h3 class="title"><a name="dokumentenvorlagen-und-variablen.anrede"></a>3.3.13. Hinweise zur Anrede</h3></div></div></div><p>Das Flag "natürliche Person"
+ (<code class="varname">natural_person</code>) aus den Kunden- oder
+ Lieferantenstammdaten kann in den Druckvorlagen zusammen mit
+ dem Feld "Anrede" (<code class="varname">greeting</code>) z.B. dafür
+ verwendet werden, die Anrede zwischen einer allgemeinen und
+ einer persönlichen Anrede zu unterscheiden.
+ </p><pre class="programlisting"><%if natural_person%><%greeting%> <%name%><%else%>Sehr geehrte Damen und Herren<%end if%></pre><p>
+
+ </p></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="ch03s02.html">Zurück</a> </td><td width="20%" align="center"><a accesskey="u" href="ch03.html">Nach oben</a></td><td width="40%" align="right"> <a accesskey="n" href="ch03s04.html">Weiter</a></td></tr><tr><td width="40%" align="left" valign="top">3.2. Bankerweiterung </td><td width="20%" align="center"><a accesskey="h" href="index.html">Zum Anfang</a></td><td width="40%" align="right" valign="top"> 3.4. Excel-Vorlagen</td></tr></table></div></body></html>
\ No newline at end of file
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>3.4. Excel-Vorlagen</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch03.html" title="Kapitel 3. Features und Funktionen"><link rel="prev" href="ch03s03.html" title="3.3. Dokumentenvorlagen und verfügbare Variablen"><link rel="next" href="ch03s05.html" title="3.5. Mandantenkonfiguration Lager"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">3.4. Excel-Vorlagen</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch03s03.html">Zurück</a> </td><th width="60%" align="center">Kapitel 3. Features und Funktionen</th><td width="20%" align="right"> <a accesskey="n" href="ch03s05.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="3.4. Excel-Vorlagen"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="excel-templates"></a>3.4. Excel-Vorlagen</h2></div></div></div><div class="sect2" title="3.4.1. Zusammenfassung"><div class="titlepage"><div><div><h3 class="title"><a name="excel-templates.summary"></a>3.4.1. Zusammenfassung</h3></div></div></div><p>Dieses Dokument beschreibt den Mechanismus, mit dem
+ <title>3.4. Excel-Vorlagen</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch03.html" title="Kapitel 3. Features und Funktionen"><link rel="prev" href="ch03s03.html" title="3.3. Dokumentenvorlagen und verfügbare Variablen"><link rel="next" href="ch03s05.html" title="3.5. Mandantenkonfiguration Lager"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">3.4. Excel-Vorlagen</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch03s03.html">Zurück</a> </td><th width="60%" align="center">Kapitel 3. Features und Funktionen</th><td width="20%" align="right"> <a accesskey="n" href="ch03s05.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="3.4. Excel-Vorlagen"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="excel-templates"></a>3.4. Excel-Vorlagen</h2></div></div></div><div class="sect2" title="3.4.1. Zusammenfassung"><div class="titlepage"><div><div><h3 class="title"><a name="excel-templates.summary"></a>3.4.1. Zusammenfassung</h3></div></div></div><p>Dieses Dokument beschreibt den Mechanismus, mit dem
Exceltemplates abgearbeitet werden, und die Einschränkungen, die damit
einhergehen.</p></div><div class="sect2" title="3.4.2. Bedienung"><div class="titlepage"><div><div><h3 class="title"><a name="excel-templates.usage"></a>3.4.2. Bedienung</h3></div></div></div><p>Der Excel Mechanismus muss in der Konfigurationsdatei aktiviert
werden. Die Konfigurationsoption heißt <code class="varname">excel_templates =
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>3.5. Mandantenkonfiguration Lager</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch03.html" title="Kapitel 3. Features und Funktionen"><link rel="prev" href="ch03s04.html" title="3.4. Excel-Vorlagen"><link rel="next" href="ch03s06.html" title="3.6. Schweizer Kontenpläne"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">3.5. Mandantenkonfiguration Lager</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch03s04.html">Zurück</a> </td><th width="60%" align="center">Kapitel 3. Features und Funktionen</th><td width="20%" align="right"> <a accesskey="n" href="ch03s06.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="3.5. Mandantenkonfiguration Lager"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="features.warehouse"></a>3.5. Mandantenkonfiguration Lager</h2></div></div></div><p>Die Lagerverwaltung in kivitendo funktioniert standardmässig wie
+ <title>3.5. Mandantenkonfiguration Lager</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch03.html" title="Kapitel 3. Features und Funktionen"><link rel="prev" href="ch03s04.html" title="3.4. Excel-Vorlagen"><link rel="next" href="ch03s06.html" title="3.6. Schweizer Kontenpläne"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">3.5. Mandantenkonfiguration Lager</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch03s04.html">Zurück</a> </td><th width="60%" align="center">Kapitel 3. Features und Funktionen</th><td width="20%" align="right"> <a accesskey="n" href="ch03s06.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="3.5. Mandantenkonfiguration Lager"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="features.warehouse"></a>3.5. Mandantenkonfiguration Lager</h2></div></div></div><p>Die Lagerverwaltung in kivitendo funktioniert standardmässig wie
folgt: Wird ein Lager mit einem Lagerplatz angelegt, so gibt es die
Möglichkeit hier über den Menüpunkt Lager entsprechende Warenbewegungen
durchzuführen. Ferner kann jede Position eines Lieferscheins ein-, bzw.
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>3.6. Schweizer Kontenpläne</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch03.html" title="Kapitel 3. Features und Funktionen"><link rel="prev" href="ch03s05.html" title="3.5. Mandantenkonfiguration Lager"><link rel="next" href="ch03s07.html" title="3.7. Artikelklassifizierung"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">3.6. Schweizer Kontenpläne</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch03s05.html">Zurück</a> </td><th width="60%" align="center">Kapitel 3. Features und Funktionen</th><td width="20%" align="right"> <a accesskey="n" href="ch03s07.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="3.6. Schweizer Kontenpläne"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="features.swiss-charts-of-accounts"></a>3.6. Schweizer Kontenpläne</h2></div></div></div><p>Seit der Version 3.5 stehen in kivitendo 3 Kontenpläne für den
+ <title>3.6. Schweizer Kontenpläne</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch03.html" title="Kapitel 3. Features und Funktionen"><link rel="prev" href="ch03s05.html" title="3.5. Mandantenkonfiguration Lager"><link rel="next" href="ch03s07.html" title="3.7. Artikelklassifizierung"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">3.6. Schweizer Kontenpläne</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch03s05.html">Zurück</a> </td><th width="60%" align="center">Kapitel 3. Features und Funktionen</th><td width="20%" align="right"> <a accesskey="n" href="ch03s07.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="3.6. Schweizer Kontenpläne"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="features.swiss-charts-of-accounts"></a>3.6. Schweizer Kontenpläne</h2></div></div></div><p>Seit der Version 3.5 stehen in kivitendo 3 Kontenpläne für den
Einsatz in der Schweiz zur Verfügung, einer für Firmen und
Organisationen, die nicht mehrwertsteuerpflichtig sind, einer für
Firmen, die mehrwertsteuerpflichtig sind und einer speziell für
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>3.7. Artikelklassifizierung</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch03.html" title="Kapitel 3. Features und Funktionen"><link rel="prev" href="ch03s06.html" title="3.6. Schweizer Kontenpläne"><link rel="next" href="ch03s08.html" title="3.8. Dateiverwaltung (Mini-DMS)"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">3.7. Artikelklassifizierung</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch03s06.html">Zurück</a> </td><th width="60%" align="center">Kapitel 3. Features und Funktionen</th><td width="20%" align="right"> <a accesskey="n" href="ch03s08.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="3.7. Artikelklassifizierung"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="features.part_classification"></a>3.7. Artikelklassifizierung</h2></div></div></div><div class="sect2" title="3.7.1. Übersicht"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6519"></a>3.7.1. Übersicht</h3></div></div></div><p>Die Klassifizierung von Artikeln dient einer weiteren
+ <title>3.7. Artikelklassifizierung</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch03.html" title="Kapitel 3. Features und Funktionen"><link rel="prev" href="ch03s06.html" title="3.6. Schweizer Kontenpläne"><link rel="next" href="ch03s08.html" title="3.8. Dateiverwaltung (Mini-DMS)"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">3.7. Artikelklassifizierung</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch03s06.html">Zurück</a> </td><th width="60%" align="center">Kapitel 3. Features und Funktionen</th><td width="20%" align="right"> <a accesskey="n" href="ch03s08.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="3.7. Artikelklassifizierung"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="features.part_classification"></a>3.7. Artikelklassifizierung</h2></div></div></div><div class="sect2" title="3.7.1. Übersicht"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6515"></a>3.7.1. Übersicht</h3></div></div></div><p>Die Klassifizierung von Artikeln dient einer weiteren
Gliederung, um zum Beispiel den Einkauf vom Verkauf zu trennen,
gekennzeichnet durch eine Beschreibung (z.B. "Einkauf") und ein Kürzel
(z.B. "E"). Für jede Klassifizierung besteht eine Beschreibung und
eine Abkürzung die normalerweise aus einem Zeichen besteht, kann aber
auf mehrere Zeichen erweitert werden, falls zur Unterscheidung
- notwendig. Sinnvoll sind jedoch nur maximal 2 Zeichen.</p></div><div class="sect2" title="3.7.2. Basisklassifizierung"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6524"></a>3.7.2. Basisklassifizierung</h3></div></div></div><p>Als Basisklassifizierungen gibt es</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Einkauf</p></li><li class="listitem"><p>Verkauf</p></li><li class="listitem"><p>Handelsware</p></li><li class="listitem"><p>Produktion</p></li><li class="listitem"><p>- keine - (diese wird bei einer Aktualisierung für alle
+ notwendig. Sinnvoll sind jedoch nur maximal 2 Zeichen.</p></div><div class="sect2" title="3.7.2. Basisklassifizierung"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6520"></a>3.7.2. Basisklassifizierung</h3></div></div></div><p>Als Basisklassifizierungen gibt es</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Einkauf</p></li><li class="listitem"><p>Verkauf</p></li><li class="listitem"><p>Handelsware</p></li><li class="listitem"><p>Produktion</p></li><li class="listitem"><p>- keine - (diese wird bei einer Aktualisierung für alle
existierenden Artikel verwendet und ist gültig für Verkauf und
Einkauf)</p></li></ul></div><p>Es können weitere Klassifizierungen angelegt werden. So kann es
- z.B. für separat auszuweisende Artikel folgende Klassen geben:</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Lieferung (Logistik, Transport) mit Kürzel L</p></li><li class="listitem"><p>Material (Verpackungsmaterial) mit Kürzel M</p></li></ul></div></div><div class="sect2" title="3.7.3. Attribute"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6554"></a>3.7.3. Attribute</h3></div></div></div><p>Bisher haben die Klassifizierungen folgende Attribute, die auch
+ z.B. für separat auszuweisende Artikel folgende Klassen geben:</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Lieferung (Logistik, Transport) mit Kürzel L</p></li><li class="listitem"><p>Material (Verpackungsmaterial) mit Kürzel M</p></li></ul></div></div><div class="sect2" title="3.7.3. Attribute"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6550"></a>3.7.3. Attribute</h3></div></div></div><p>Bisher haben die Klassifizierungen folgende Attribute, die auch
alle gleichzeitg gültig sein können</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>gültig für Verkauf - dieser Artikel kann im Verkauf genutzt
werden</p></li><li class="listitem"><p>gültig für Einkauf - dieser Artikel kann im Einkauf genutzt
werden</p></li><li class="listitem"><p>separat ausweisen - hierzu gibt es zur Dokumentengenerierung
pro separat auszuweisenden Klassifizierungen die Variable<span class="bold"><strong>< %separate_X_subtotal%></strong></span>, wobei X das
Kürzel der Klassifizierung ist.</p><p>Im obigen Beispiel wäre das für Lieferkosten <span class="bold"><strong><%separate_L_subtotal%></strong></span> und für
Verpackungsmaterial <span class="bold"><strong>
- <%separate_M_subtotal%></strong></span>.</p></div><div class="sect2" title="3.7.4. Zwei-Zeichen Abkürzung"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6585"></a>3.7.4. Zwei-Zeichen Abkürzung</h3></div></div></div><p>Der Typ des Artikels und die Klassifizierung werden durch zwei
+ <%separate_M_subtotal%></strong></span>.</p></div><div class="sect2" title="3.7.4. Zwei-Zeichen Abkürzung"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6581"></a>3.7.4. Zwei-Zeichen Abkürzung</h3></div></div></div><p>Der Typ des Artikels und die Klassifizierung werden durch zwei
Buchstaben dargestellt. Der erste Buchstabe ist eine Lokalisierung des
Artikel-Typs ('P','A','S'), deutsch 'W', 'E', und 'D' für Ware
Erzeugnis oder Dienstleistung und ggf. weiterer Typen.</p><p>Der zweite Buchstabe (und ggf. auch ein dritter, falls nötig)
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>3.8. Dateiverwaltung (Mini-DMS)</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch03.html" title="Kapitel 3. Features und Funktionen"><link rel="prev" href="ch03s07.html" title="3.7. Artikelklassifizierung"><link rel="next" href="ch03s09.html" title="3.9. Webshop-Api"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">3.8. Dateiverwaltung (Mini-DMS)</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch03s07.html">Zurück</a> </td><th width="60%" align="center">Kapitel 3. Features und Funktionen</th><td width="20%" align="right"> <a accesskey="n" href="ch03s09.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="3.8. Dateiverwaltung (Mini-DMS)"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="features.file_managment"></a>3.8. Dateiverwaltung (Mini-DMS)</h2></div></div></div><div class="sect2" title="3.8.1. Übersicht"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6597"></a>3.8.1. Übersicht</h3></div></div></div><p>Parallel zum alten WebDAV gibt es ein Datei-Management-System,
+ <title>3.8. Dateiverwaltung (Mini-DMS)</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch03.html" title="Kapitel 3. Features und Funktionen"><link rel="prev" href="ch03s07.html" title="3.7. Artikelklassifizierung"><link rel="next" href="ch03s09.html" title="3.9. Webshop-Api"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">3.8. Dateiverwaltung (Mini-DMS)</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch03s07.html">Zurück</a> </td><th width="60%" align="center">Kapitel 3. Features und Funktionen</th><td width="20%" align="right"> <a accesskey="n" href="ch03s09.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="3.8. Dateiverwaltung (Mini-DMS)"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="features.file_managment"></a>3.8. Dateiverwaltung (Mini-DMS)</h2></div></div></div><div class="sect2" title="3.8.1. Übersicht"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6593"></a>3.8.1. Übersicht</h3></div></div></div><p>Parallel zum alten WebDAV gibt es ein Datei-Management-System,
das Dateien verschiedenen Typs verwaltet. Dies können</p><div class="orderedlist"><ol class="orderedlist" type="1"><li class="listitem"><p>aus ERP-Daten per LaTeX Template erzeugte
PDF-Dokumente,</p></li><li class="listitem"><p>zu bestimmten ERP-Daten gehörende Anhangdateien
unterschiedlichen Formats,</p></li><li class="listitem"><p>per Scanner eingelesene PDF-Dateien,</p></li><li class="listitem"><p>per E-Mail empfangene Dateianhänge unterschiedlichen
- Formats,</p></li><li class="listitem"><p>sowie speziel für Artikel hochgeladene Bilder sein.</p></li></ol></div><div class="screenshot"><div class="mediaobject"><img src="images/DMS-Overview.png"></div></div></div><div class="sect2" title="3.8.2. Struktur"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6624"></a>3.8.2. Struktur</h3></div></div></div><p>Über eine vom Speichermedium unabhängige Zwischenschicht werden
+ Formats,</p></li><li class="listitem"><p>sowie speziel für Artikel hochgeladene Bilder sein.</p></li></ol></div><div class="screenshot"><div class="mediaobject"><img src="images/DMS-Overview.png"></div></div></div><div class="sect2" title="3.8.2. Struktur"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6620"></a>3.8.2. Struktur</h3></div></div></div><p>Über eine vom Speichermedium unabhängige Zwischenschicht werden
die Dateien und ihre Versionen in der Datenbank verwaltet. Darunter
können verschiedene Implementierungen (Backends) gleichzeitig
existieren:</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Dateisystem</p></li><li class="listitem"><p>WebDAV</p></li><li class="listitem"><p>Schnittstelle zu externen
für "attachment" und "image" nur die Quelle "uploaded". Für "document"
gibt es auf jeden Fall die Quelle "created". Die Quellen "scanner" und
"email" müssen derzeit in der Datenbank konfiguriert werden (siehe
- <a class="xref" href="ch03s08.html#file_management.dbconfig" title="3.8.4.2. Datenbank-Konfigurierung">Datenbank-Konfigurierung</a>).</p></div><div class="sect2" title="3.8.3. Anwendung"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6676"></a>3.8.3. Anwendung</h3></div></div></div><p>Die Daten werden bei den ERP-Objekten als extra Reiter
+ <a class="xref" href="ch03s08.html#file_management.dbconfig" title="3.8.4.2. Datenbank-Konfigurierung">Datenbank-Konfigurierung</a>).</p></div><div class="sect2" title="3.8.3. Anwendung"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6672"></a>3.8.3. Anwendung</h3></div></div></div><p>Die Daten werden bei den ERP-Objekten als extra Reiter
dargestellt. Eine Verkaufsrechnung z.B. hat die Reiter "Dokumente" und
"Dateianhänge".</p><div class="screenshot"><div class="mediaobject"><img src="images/DMS-Anhaenge.png"></div></div><p>Bei den Dateianhängen wird immer nur die aktuelle Version einer
Datei angezeigt. Wird eine Datei mit gleichem Namen hochgeladen, so
so sind diese z.B. bei Einkaufsrechnungen sichtbar:</p><div class="screenshot"><div class="mediaobject"><img src="images/DMS-Dokumente-Scanner.png"></div></div><p>Statt des Löschens wird hier die Datei zurück zur Quelle
verschoben. Somit kann die Datei anschließend an ein anderes
ERP-Objekt angehängt werden.</p><p>Derzeit sind "Titel" und "Beschreibung" noch nicht genutzt. Sie
- sind bisher nur bei Bildern relevant.</p></div><div class="sect2" title="3.8.4. Konfigurierung"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6719"></a>3.8.4. Konfigurierung</h3></div></div></div><div class="sect3" title="3.8.4.1. Mandantenkonfiguration"><div class="titlepage"><div><div><h4 class="title"><a name="file_management.clientconfig"></a>3.8.4.1. Mandantenkonfiguration</h4></div></div></div><div class="sect4" title="3.8.4.1.1. Reiter "Features""><div class="titlepage"><div><div><h5 class="title"><a name="d0e6725"></a>3.8.4.1.1. Reiter "Features"</h5></div></div></div><p>Unter dem Reiter <span class="bold"><strong>Features</strong></span>
+ sind bisher nur bei Bildern relevant.</p></div><div class="sect2" title="3.8.4. Konfigurierung"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6715"></a>3.8.4. Konfigurierung</h3></div></div></div><div class="sect3" title="3.8.4.1. Mandantenkonfiguration"><div class="titlepage"><div><div><h4 class="title"><a name="file_management.clientconfig"></a>3.8.4.1. Mandantenkonfiguration</h4></div></div></div><div class="sect4" title="3.8.4.1.1. Reiter "Features""><div class="titlepage"><div><div><h5 class="title"><a name="d0e6721"></a>3.8.4.1.1. Reiter "Features"</h5></div></div></div><p>Unter dem Reiter <span class="bold"><strong>Features</strong></span>
im Abschnitt Dateimanagement ist neben dem "alten" WebDAV das
Dateimangement generell zu- und abschaltbar, sowie die Zuordnung
der Dateitypen zu Backends. Die Löschbarkeit von Dateien, sowie
die maximale Uploadgröße sind Backend-unabhängig</p><div class="screenshot"><div class="mediaobject"><img src="images/DMS-ClientConfig.png"></div></div><p>Die einzelnen Backends sind einzeln einschaltbar.
Spezifische Backend-Konfigurierungen sind hier noch
- ergänzbar.</p></div><div class="sect4" title="3.8.4.1.2. Reiter "Allgemeine Dokumentenanhänge""><div class="titlepage"><div><div><h5 class="title"><a name="d0e6741"></a>3.8.4.1.2. Reiter "Allgemeine Dokumentenanhänge"</h5></div></div></div><p>Unter dem Reiter <span class="bold"><strong>Allgemeine
+ ergänzbar.</p></div><div class="sect4" title="3.8.4.1.2. Reiter "Allgemeine Dokumentenanhänge""><div class="titlepage"><div><div><h5 class="title"><a name="d0e6737"></a>3.8.4.1.2. Reiter "Allgemeine Dokumentenanhänge"</h5></div></div></div><p>Unter dem Reiter <span class="bold"><strong>Allgemeine
Dokumentenanhänge</strong></span> kann für alle ERP-Dokumente (
Angebote, Aufträge, Lieferscheine, Rechnungen im Verkauf und
Einkauf ) allgemeingültige Anhänge hochgeladen werden.</p><div class="screenshot"><div class="mediaobject"><img src="images/DMS-Allgemeine-Dokumentenanhaenge.png"></div></div><p>Diese Anhänge werden beim Generieren von PDF-Dateien an die
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>3.9. Webshop-Api</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch03.html" title="Kapitel 3. Features und Funktionen"><link rel="prev" href="ch03s08.html" title="3.8. Dateiverwaltung (Mini-DMS)"><link rel="next" href="ch04.html" title="Kapitel 4. Entwicklerdokumentation"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">3.9. Webshop-Api</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch03s08.html">Zurück</a> </td><th width="60%" align="center">Kapitel 3. Features und Funktionen</th><td width="20%" align="right"> <a accesskey="n" href="ch04.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="3.9. Webshop-Api"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="d0e6775"></a>3.9. Webshop-Api</h2></div></div></div><p>Das Shopmodul bietet die Möglichkeit Onlineshopartikel und
+ <title>3.9. Webshop-Api</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch03.html" title="Kapitel 3. Features und Funktionen"><link rel="prev" href="ch03s08.html" title="3.8. Dateiverwaltung (Mini-DMS)"><link rel="next" href="ch03s10.html" title="3.10. ZUGFeRD Rechnungen"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">3.9. Webshop-Api</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch03s08.html">Zurück</a> </td><th width="60%" align="center">Kapitel 3. Features und Funktionen</th><td width="20%" align="right"> <a accesskey="n" href="ch03s10.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="3.9. Webshop-Api"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="d0e6771"></a>3.9. Webshop-Api</h2></div></div></div><p>Das Shopmodul bietet die Möglichkeit Onlineshopartikel und
Onlineshopbestellungen zu verwalten und zu bearbeiten.</p><p>Es ist Multishopfähig, d.h. Artikel können mehreren oder
unterschiedlichen Shops zugeordnet werden. Bestellungen können aus
mehreren Shops geholt werden.</p><p>Zur Zeit bietet das Modul nur einen Connector zur REST-Api von
Shopware. Weitere Connectoren können dazu programmiert und eingerichtet
- werden.</p><div class="sect2" title="3.9.1. Rechte für die Webshopapi"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6784"></a>3.9.1. Rechte für die Webshopapi</h3></div></div></div><p>In der Administration können folgende Rechte vergeben
- werden</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Webshopartikel anlegen und bearbeiten</p></li><li class="listitem"><p>Shopbestellungen holen und bearbeiten</p></li><li class="listitem"><p>Shop anlegen und bearbeiten</p></li></ul></div></div><div class="sect2" title="3.9.2. Konfiguration"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6799"></a>3.9.2. Konfiguration</h3></div></div></div><p>Unter System->Webshops können Shops angelegt und konfiguriert
- werden</p><div class="mediaobject"><img src="images/Shop_Listing.png"></div></div><div class="sect2" title="3.9.3. Webshopartikel"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6807"></a>3.9.3. Webshopartikel</h3></div></div></div><div class="sect3" title="3.9.3.1. Shopvariablenreiter in Artikelstammdaten"><div class="titlepage"><div><div><h4 class="title"><a name="d0e6810"></a>3.9.3.1. Shopvariablenreiter in Artikelstammdaten</h4></div></div></div><p>Mit dem Recht "Shopartikel anlegen und bearbeiten" und des
+ werden.</p><div class="sect2" title="3.9.1. Rechte für die Webshopapi"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6780"></a>3.9.1. Rechte für die Webshopapi</h3></div></div></div><p>In der Administration können folgende Rechte vergeben
+ werden</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Webshopartikel anlegen und bearbeiten</p></li><li class="listitem"><p>Shopbestellungen holen und bearbeiten</p></li><li class="listitem"><p>Shop anlegen und bearbeiten</p></li></ul></div></div><div class="sect2" title="3.9.2. Konfiguration"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6795"></a>3.9.2. Konfiguration</h3></div></div></div><p>Unter System->Webshops können Shops angelegt und konfiguriert
+ werden</p><div class="mediaobject"><img src="images/Shop_Listing.png"></div></div><div class="sect2" title="3.9.3. Webshopartikel"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6803"></a>3.9.3. Webshopartikel</h3></div></div></div><div class="sect3" title="3.9.3.1. Shopvariablenreiter in Artikelstammdaten"><div class="titlepage"><div><div><h4 class="title"><a name="d0e6806"></a>3.9.3.1. Shopvariablenreiter in Artikelstammdaten</h4></div></div></div><p>Mit dem Recht "Shopartikel anlegen und bearbeiten" und des
Markers <span class="bold"><strong>"Shopartikel" in den Basisdaten
</strong></span>zeigt sich der Reiter "Shopvariablen" in den
Artikelstammdaten. Hier können jetzt die Artikel mit
Stelle können auch beliebig viele Bilder dem Shopartikel zugeordnet
werden. Artikelbilder gelten für alle Shops.</p><div class="mediaobject"><img src="images/Shop_Artikel.png"></div><p>Die Artikelgruppen werden direkt vom Shopsystem geholt somit
ist es möglich einen Artikel auch mehreren Gruppen
- zuzuordenen</p></div><div class="sect3" title="3.9.3.2. Shopartikelliste"><div class="titlepage"><div><div><h4 class="title"><a name="d0e6823"></a>3.9.3.2. Shopartikelliste</h4></div></div></div><p>Unter dem Menu Webshop->Webshop Artikel hat man nochmal
+ zuzuordenen</p></div><div class="sect3" title="3.9.3.2. Shopartikelliste"><div class="titlepage"><div><div><h4 class="title"><a name="d0e6819"></a>3.9.3.2. Shopartikelliste</h4></div></div></div><p>Unter dem Menu Webshop->Webshop Artikel hat man nochmal
eine Gesamtübersicht. Von hier aus ist es möglich Artikel im Stapel
unter verschiedenen Kriterien <alles><nur Preis><nur
Bestand><Preis und Bestand> an die jeweiligen Shops
- hochzuladen.</p><div class="mediaobject"><img src="images/Shop_Artikel_Listing.png"></div></div></div><div class="sect2" title="3.9.4. Bestellimport"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6831"></a>3.9.4. Bestellimport</h3></div></div></div><p>Unter dem Menupunkt Webshop->Webshop Import öffnet sich die
+ hochzuladen.</p><div class="mediaobject"><img src="images/Shop_Artikel_Listing.png"></div></div></div><div class="sect2" title="3.9.4. Bestellimport"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6827"></a>3.9.4. Bestellimport</h3></div></div></div><p>Unter dem Menupunkt Webshop->Webshop Import öffnet sich die
Bestellimportsliste. Hier ist sind Möglichkeiten gegeben Neue
Bestellungen vom Shop abzuholen, geholte Bestellungen im Stapel oder
einzeln als Auftrag zu transferieren. Die Liste kann nach
auch der Grund für die Auftragssperre sein.</p></li><li class="listitem"><p>Die Buttons "Auftrag erstellen" und "Kunde mit
Rechnungsadresse überschreiben" zeigen sich erst, wenn ein Kunde
aus dem Listing ausgewählt ist.</p></li><li class="listitem"><p>Es ist aber möglich die Shopbestellung zu löschen.</p></li><li class="listitem"><p>Ist eine Bestellung schon übernommen, zeigen sich an dieser
- Stelle, die dazugehörigen Belegverknüpfungen.</p></li></ul></div></div><div class="sect2" title="3.9.5. Mapping der Daten"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6884"></a>3.9.5. Mapping der Daten</h3></div></div></div><p>Das Mapping der kivitendo Daten mit den Shopdaten geschieht in
+ Stelle, die dazugehörigen Belegverknüpfungen.</p></li></ul></div></div><div class="sect2" title="3.9.5. Mapping der Daten"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6880"></a>3.9.5. Mapping der Daten</h3></div></div></div><p>Das Mapping der kivitendo Daten mit den Shopdaten geschieht in
der Datei SL/ShopConnector/<SHOPCONNECTORNAME>.pm
z.B.:SL/ShopConnector/Shopware.pm</p><p>In dieser Datei gibt es einen Bereich wo die Bestellpostionen,
die Bestellkopfdaten und die Artikeldaten gemapt werden. In dieser
Datei kann ein individelles Mapping dann gemacht werden. Zu Shopware
gibt es hier eine sehr gute Dokumentation: <a class="ulink" href="https://developers.shopware.com/developers-guide/rest-api/" target="_top">https://developers.shopware.com/developers-guide/rest-api/</a>
- </p></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="ch03s08.html">Zurück</a> </td><td width="20%" align="center"><a accesskey="u" href="ch03.html">Nach oben</a></td><td width="40%" align="right"> <a accesskey="n" href="ch04.html">Weiter</a></td></tr><tr><td width="40%" align="left" valign="top">3.8. Dateiverwaltung (Mini-DMS) </td><td width="20%" align="center"><a accesskey="h" href="index.html">Zum Anfang</a></td><td width="40%" align="right" valign="top"> Kapitel 4. Entwicklerdokumentation</td></tr></table></div></body></html>
\ No newline at end of file
+ </p></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="ch03s08.html">Zurück</a> </td><td width="20%" align="center"><a accesskey="u" href="ch03.html">Nach oben</a></td><td width="40%" align="right"> <a accesskey="n" href="ch03s10.html">Weiter</a></td></tr><tr><td width="40%" align="left" valign="top">3.8. Dateiverwaltung (Mini-DMS) </td><td width="20%" align="center"><a accesskey="h" href="index.html">Zum Anfang</a></td><td width="40%" align="right" valign="top"> 3.10. ZUGFeRD Rechnungen</td></tr></table></div></body></html>
\ No newline at end of file
--- /dev/null
+<html><head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <title>3.10. ZUGFeRD Rechnungen</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch03.html" title="Kapitel 3. Features und Funktionen"><link rel="prev" href="ch03s09.html" title="3.9. Webshop-Api"><link rel="next" href="ch04.html" title="Kapitel 4. Entwicklerdokumentation"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">3.10. ZUGFeRD Rechnungen</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch03s09.html">Zurück</a> </td><th width="60%" align="center">Kapitel 3. Features und Funktionen</th><td width="20%" align="right"> <a accesskey="n" href="ch04.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="3.10. ZUGFeRD Rechnungen"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="features.zugferd"></a>3.10. ZUGFeRD Rechnungen</h2></div></div></div><div class="sect2" title="3.10.1. Vorbedingung"><div class="titlepage"><div><div><h3 class="title"><a name="features.zugferd.preamble"></a>3.10.1. Vorbedingung</h3></div></div></div><p>
+ Für die Erstellung von ZUGFeRD PDFs wird TexLive2018 oder höher benötigt.
+ </p><div class="note" title="Anmerkung" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Anmerkung]" src="system/docbook-xsl/images/note.png"></td><th align="left">Anmerkung</th></tr><tr><td align="left" valign="top"><p>
+ Wer kein TexLive2018 oder höher installieren kann, kann eine lokale Umgebung nur für kivitendo wie folgt erzeugen:
+ </p><pre class="programlisting">
+ 1. Download des offiziellen Installers von https://www.tug.org/texlive/quickinstall.html
+
+ 2. Installer ausführen, Standard-Ort für Installation belassen, evtl. ein paar Pakete abwählen, installieren lassen
+
+ 3. Ein kleine Script »run_pdflatex.sh« anlegen, das den PATH auf das Installationsverezichnis setzt und pdflatex ausführt:
+
+ ------------------------------------------------------------
+ #!/bin/bash
+
+ export PATH=/usr/local/texlive/2020/bin/x86_64-linux:$PATH
+ hash -r
+
+ exec pdflatex "$@"
+ ------------------------------------------------------------
+
+ 4. In config/kivitendo.conf den Parameter »latex« auf den Pfad zu »run_pdflatex.sh« setzen
+
+ 5. Webserver neu starten
+ </pre></td></tr></table></div></div><div class="sect2" title="3.10.2. Übersicht"><div class="titlepage"><div><div><h3 class="title"><a name="features.zugferd.summary"></a>3.10.2. Übersicht</h3></div></div></div><p>Mit der Version 3.5.6 bietet kivitendo die Möglichkeit ZUGFeRD
+ Rechnungen zu erstellen, sowie auch ZUGFeRD Rechnungen direkt in
+ kivitendo einzulesen. </p><p>Bei ZUGFeRD Rechnungen handelt es sich um eine PDF Datei in
+ der eine XML-Datei eingebettet ist. Der Aufbau der XML-Datei ist
+ standardisiert und ermöglicht so den Austausch zwischen
+ den verschiedenen Softwareprodukten. Kivitendo setzt mit der
+ Version 3.5.6 den ZUGFeRD 2.1 Standard um.</p><p>Weiter Details zu ZUGFeRD sind unter diesem Link zu finden:
+ <a class="ulink" href="" target="_top">https://www.ferd-net.de/standards/was-ist-zugferd/index.html</a>
+
+ </p></div><div class="sect2" title="3.10.3. Erstellen von ZUGFeRD Rechnungen in Kivitendo"><div class="titlepage"><div><div><h3 class="title"><a name="features.zugferd.create_zugferd_bills"></a>3.10.3. Erstellen von ZUGFeRD Rechnungen in Kivitendo</h3></div></div></div><p>Für die Erstellung von ZUGFeRD Rechnungen bedarf es in
+ kivitendo zwei Dinge:</p><div class="orderedlist"><ol class="orderedlist" type="1"><li class="listitem"><p>Die Erstellung muss in der Mandantenkonfiguration
+ aktiviert sein</p></li><li class="listitem"><p>Beim mindestens einem Bankkonto muss die Option
+ „Nutzung von ZUGFeRD“ aktiviert sein</p></li></ol></div><div class="sect3" title="3.10.3.1. Mandantenkonfiguration"><div class="titlepage"><div><div><h4 class="title"><a name="d0e6927"></a>3.10.3.1. Mandantenkonfiguration</h4></div></div></div><p>Die Einstellung für die Erstellung von ZUGFeRD Rechnungen
+ erfolgt unter „System“ → „Mandatenkonfiguration“ → „Features“.
+ Im Abschnitt „Einkauf und Verkauf“ finden Sie die Einstellung
+ „Verkaufsrechnungen mit ZUGFeRD-Daten erzeugen“.
+ Hier besteht die Auswahl zwischen:</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>ZUGFeRD-Rechnungen erzeugen</p></li><li class="listitem"><p>ZUGFeRD-Rechnungen im Testmodus erzeugen</p></li><li class="listitem"><p>Keine ZUGFeRD Rechnungen erzeugen</p></li></ul></div><p>Rechnungen die als PDF erzeugt werden, werden je nach
+ Einstellung nun im ZUGFeRD Format ausgegeben.</p></div><div class="sect3" title="3.10.3.2. Konfiguration der Bankkonten"><div class="titlepage"><div><div><h4 class="title"><a name="d0e6944"></a>3.10.3.2. Konfiguration der Bankkonten</h4></div></div></div><p>Unter „System → Bankkonten“ muss bei mindestens einem
+ Bankkonto die Option „Nutzung mit ZUGFeRD“ auf „Ja“ gestellt
+ werden.</p></div></div><div class="sect2" title="3.10.4. Einlesen von ZUGFeRD Rechnungen in Kivitendo"><div class="titlepage"><div><div><h3 class="title"><a name="features.zugferd.read_zugferd_bills"></a>3.10.4. Einlesen von ZUGFeRD Rechnungen in Kivitendo</h3></div></div></div><p>Es lassen sich auch Rechnungen von Kreditoren, die im
+ ZUGFeRD Format erstellt wurden, nach Kivitendo importieren.
+ Hierfür müssen auch zwei Voraussetzungen erfüllt werden:
+ </p><div class="orderedlist"><ol class="orderedlist" type="1"><li class="listitem"><p>Beim Lieferanten muss die Umsatzsteuer-ID und das
+ Bankkonto hinterlegt sein</p></li><li class="listitem"><p>Für den Kreditoren muss eine Buchungsvorlage existieren.</p></li></ol></div><p>Wenn diese Voraussetzungen erfüllt sind, kann die Rechnung
+ über „Finanzbuchhaltung“ → „ZUGFeRD Import“ über die „Durchsuchen“
+ Schaltfläche ausgewählt werden und über die Schaltfläche „Import“
+ eingeladen werden. Es öffnet sich daraufhin die Kreditorenbuchung.
+ Die auslesbaren Daten aus dem eingebetteten XML der PDF Datei
+ werden in der Kreditorenbuchung ergänzt.</p></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="ch03s09.html">Zurück</a> </td><td width="20%" align="center"><a accesskey="u" href="ch03.html">Nach oben</a></td><td width="40%" align="right"> <a accesskey="n" href="ch04.html">Weiter</a></td></tr><tr><td width="40%" align="left" valign="top">3.9. Webshop-Api </td><td width="20%" align="center"><a accesskey="h" href="index.html">Zum Anfang</a></td><td width="40%" align="right" valign="top"> Kapitel 4. Entwicklerdokumentation</td></tr></table></div></body></html>
\ No newline at end of file
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>Kapitel 4. Entwicklerdokumentation</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="up" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="prev" href="ch03s09.html" title="3.9. Webshop-Api"><link rel="next" href="ch04s02.html" title="4.2. Entwicklung unter FastCGI"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">Kapitel 4. Entwicklerdokumentation</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch03s09.html">Zurück</a> </td><th width="60%" align="center"> </th><td width="20%" align="right"> <a accesskey="n" href="ch04s02.html">Weiter</a></td></tr></table><hr></div><div class="chapter" title="Kapitel 4. Entwicklerdokumentation"><div class="titlepage"><div><div><h2 class="title"><a name="d0e6894"></a>Kapitel 4. Entwicklerdokumentation</h2></div></div></div><div class="sect1" title="4.1. Globale Variablen"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="devel.globals"></a>4.1. Globale Variablen</h2></div></div></div><div class="sect2" title="4.1.1. Wie sehen globale Variablen in Perl aus?"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6900"></a>4.1.1. Wie sehen globale Variablen in Perl aus?</h3></div></div></div><p>Globale Variablen liegen in einem speziellen namespace namens
+ <title>Kapitel 4. Entwicklerdokumentation</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="prev" href="ch03s10.html" title="3.10. ZUGFeRD Rechnungen"><link rel="next" href="ch04s02.html" title="4.2. Entwicklung unter FastCGI"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">Kapitel 4. Entwicklerdokumentation</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch03s10.html">Zurück</a> </td><th width="60%" align="center"> </th><td width="20%" align="right"> <a accesskey="n" href="ch04s02.html">Weiter</a></td></tr></table><hr></div><div class="chapter" title="Kapitel 4. Entwicklerdokumentation"><div class="titlepage"><div><div><h2 class="title"><a name="d0e6963"></a>Kapitel 4. Entwicklerdokumentation</h2></div></div></div><div class="sect1" title="4.1. Globale Variablen"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="devel.globals"></a>4.1. Globale Variablen</h2></div></div></div><div class="sect2" title="4.1.1. Wie sehen globale Variablen in Perl aus?"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6969"></a>4.1.1. Wie sehen globale Variablen in Perl aus?</h3></div></div></div><p>Globale Variablen liegen in einem speziellen namespace namens
"main", der von überall erreichbar ist. Darüber hinaus sind bareword
globs global und die meisten speziellen Variablen sind...
speziell.</p><p>Daraus ergeben sich folgende Formen:</p><div class="variablelist"><dl><dt><span class="term">
<code class="varname">$PACKAGE::form</code>.</p></dd><dt><span class="term">
<code class="literal">local $form</code>
</span></dt><dd><p>Alle Änderungen an <code class="varname">$form</code> werden am Ende
- des scopes zurückgesetzt</p></dd></dl></div></div><div class="sect2" title="4.1.2. Warum sind globale Variablen ein Problem?"><div class="titlepage"><div><div><h3 class="title"><a name="d0e7001"></a>4.1.2. Warum sind globale Variablen ein Problem?</h3></div></div></div><p>Das erste Problem ist <span class="productname">FCGI</span>™.</p><p>
+ des scopes zurückgesetzt</p></dd></dl></div></div><div class="sect2" title="4.1.2. Warum sind globale Variablen ein Problem?"><div class="titlepage"><div><div><h3 class="title"><a name="d0e7070"></a>4.1.2. Warum sind globale Variablen ein Problem?</h3></div></div></div><p>Das erste Problem ist <span class="productname">FCGI</span>™.</p><p>
<span class="productname">SQL-Ledger</span>™ hat fast alles im globalen
namespace abgelegt, und erwartet, dass es da auch wiederzufinden ist.
Unter <span class="productname">FCGI</span>™ müssen diese Sachen aber wieder
dies hat, seit der Einführung, u.a. schon so manche langwierige
Bug-Suche verkürzt. Da globale Variablen aber implizit mit Package
angegeben werden, werden die nicht geprüft, und somit kann sich
- schnell ein Tippfehler einschleichen.</p></div><div class="sect2" title="4.1.3. Kanonische globale Variablen"><div class="titlepage"><div><div><h3 class="title"><a name="d0e7034"></a>4.1.3. Kanonische globale Variablen</h3></div></div></div><p>Um dieses Problem im Griff zu halten gibt es einige wenige
+ schnell ein Tippfehler einschleichen.</p></div><div class="sect2" title="4.1.3. Kanonische globale Variablen"><div class="titlepage"><div><div><h3 class="title"><a name="d0e7103"></a>4.1.3. Kanonische globale Variablen</h3></div></div></div><p>Um dieses Problem im Griff zu halten gibt es einige wenige
globale Variablen, die kanonisch sind, d.h. sie haben bestimmte
vorgegebenen Eigenschaften, und alles andere sollte anderweitig
umhergereicht werden.</p><p>Diese Variablen sind im Moment die folgenden neun:</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>
<code class="varname">$::request</code>
</p></li></ul></div><p>Damit diese nicht erneut als Müllhalde missbraucht werden, im
Folgenden eine kurze Erläuterung der bestimmten vorgegebenen
- Eigenschaften (Konventionen):</p><div class="sect3" title="4.1.3.1. $::form"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7098"></a>4.1.3.1. $::form</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Ist ein Objekt der Klasse
+ Eigenschaften (Konventionen):</p><div class="sect3" title="4.1.3.1. $::form"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7167"></a>4.1.3.1. $::form</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Ist ein Objekt der Klasse
"<code class="classname">Form</code>"</p></li><li class="listitem"><p>Wird nach jedem Request gelöscht</p></li><li class="listitem"><p>Muss auch in Tests und Konsolenscripts vorhanden
sein.</p></li><li class="listitem"><p>Enthält am Anfang eines Requests die Requestparameter vom
User</p></li><li class="listitem"><p>Kann zwar intern über Requestgrenzen ein Datenbankhandle
push @{ $form->{TEMPLATE_ARRAYS}{number} }, $form->{"partnumber_$i"};
push @{ $form->{TEMPLATE_ARRAYS}{description} }, $form->{"description_$i"};
# ...
-}</pre></div><div class="sect3" title="4.1.3.2. %::myconfig"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7182"></a>4.1.3.2. %::myconfig</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Das einzige Hash unter den globalen Variablen</p></li><li class="listitem"><p>Wird spätestens benötigt wenn auf die Datenbank
+}</pre></div><div class="sect3" title="4.1.3.2. %::myconfig"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7251"></a>4.1.3.2. %::myconfig</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Das einzige Hash unter den globalen Variablen</p></li><li class="listitem"><p>Wird spätestens benötigt wenn auf die Datenbank
zugegriffen wird</p></li><li class="listitem"><p>Wird bei jedem Request neu erstellt.</p></li><li class="listitem"><p>Enthält die Userdaten des aktuellen Logins</p></li><li class="listitem"><p>Sollte nicht ohne Filterung irgendwo gedumpt werden oder
extern serialisiert werden, weil da auch der Datenbankzugriff
für diesen user drinsteht.</p></li><li class="listitem"><p>Enthält unter anderem Datumsformat dateformat und
überwiegend die Daten, die sich unter <span class="guimenu">Programm</span>
-> <span class="guimenuitem">Einstellungen</span> befinden, bzw. die
Informationen über den Benutzer die über die
- Administrator-Schnittstelle eingegeben wurden.</p></div><div class="sect3" title="4.1.3.3. $::locale"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7221"></a>4.1.3.3. $::locale</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Objekt der Klasse "Locale"</p></li><li class="listitem"><p>Wird pro Request erstellt</p></li><li class="listitem"><p>Muss auch für Tests und Scripte immer verfügbar
+ Administrator-Schnittstelle eingegeben wurden.</p></div><div class="sect3" title="4.1.3.3. $::locale"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7290"></a>4.1.3.3. $::locale</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Objekt der Klasse "Locale"</p></li><li class="listitem"><p>Wird pro Request erstellt</p></li><li class="listitem"><p>Muss auch für Tests und Scripte immer verfügbar
sein.</p></li><li class="listitem"><p>Cached intern über Requestgrenzen hinweg benutzte
Locales</p></li></ul></div><p>Lokalisierung für den aktuellen User. Alle Übersetzungen,
- Zahlen- und Datumsformatierungen laufen über dieses Objekt.</p></div><div class="sect3" title="4.1.3.4. $::lxdebug"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7239"></a>4.1.3.4. $::lxdebug</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Objekt der Klasse "LXDebug"</p></li><li class="listitem"><p>Wird global gecached</p></li><li class="listitem"><p>Muss immer verfügbar sein, in nahezu allen
+ Zahlen- und Datumsformatierungen laufen über dieses Objekt.</p></div><div class="sect3" title="4.1.3.4. $::lxdebug"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7308"></a>4.1.3.4. $::lxdebug</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Objekt der Klasse "LXDebug"</p></li><li class="listitem"><p>Wird global gecached</p></li><li class="listitem"><p>Muss immer verfügbar sein, in nahezu allen
Funktionen</p></li></ul></div><p>
<code class="varname">$::lxdebug</code> stellt Debuggingfunktionen
bereit, wie "<code class="function">enter_sub</code>" und
"<code class="function">message</code>" und "<code class="function">dump</code>" mit
denen man flott Informationen ins Log (tmp/kivitendo-debug.log)
packen kann.</p><p>Beispielsweise so:</p><pre class="programlisting">$main::lxdebug->message(0, 'Meine Konfig:' . Dumper (%::myconfig));
-$main::lxdebug->message(0, 'Wer bin ich? Kunde oder Lieferant:' . $form->{vc});</pre></div><div class="sect3" title="4.1.3.5. $::auth"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7276"></a>4.1.3.5. $::auth</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Objekt der Klasse "SL::Auth"</p></li><li class="listitem"><p>Wird global gecached</p></li><li class="listitem"><p>Hat eine permanente DB Verbindung zur Authdatenbank</p></li><li class="listitem"><p>Wird nach jedem Request resettet.</p></li></ul></div><p>
+$main::lxdebug->message(0, 'Wer bin ich? Kunde oder Lieferant:' . $form->{vc});</pre></div><div class="sect3" title="4.1.3.5. $::auth"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7345"></a>4.1.3.5. $::auth</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Objekt der Klasse "SL::Auth"</p></li><li class="listitem"><p>Wird global gecached</p></li><li class="listitem"><p>Hat eine permanente DB Verbindung zur Authdatenbank</p></li><li class="listitem"><p>Wird nach jedem Request resettet.</p></li></ul></div><p>
<code class="varname">$::auth</code> stellt Funktionen bereit um die
Rechte des aktuellen Users abzufragen. Obwohl diese Informationen
vom aktuellen User abhängen wird das Objekt aus
Dessen Einstellungen können über
<code class="literal">$::auth->client</code> abgefragt werden; Rückgabewert
ist ein Hash mit den Werten aus der Tabelle
- <code class="literal">auth.clients</code>.</p></div><div class="sect3" title="4.1.3.6. $::lx_office_conf"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7305"></a>4.1.3.6. $::lx_office_conf</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Objekt der Klasse
+ <code class="literal">auth.clients</code>.</p></div><div class="sect3" title="4.1.3.6. $::lx_office_conf"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7374"></a>4.1.3.6. $::lx_office_conf</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Objekt der Klasse
"<code class="classname">SL::LxOfficeConf</code>"</p></li><li class="listitem"><p>Global gecached</p></li><li class="listitem"><p>Repräsentation der
<code class="filename">config/kivitendo.conf[.default]</code>-Dateien</p></li></ul></div><p>Globale Konfiguration. Configdateien werden zum Start gelesen
und danach nicht mehr angefasst. Es ist derzeit nicht geplant, dass
file_name = /tmp/kivitendo-debug.log</pre><p>ist der Key <code class="varname">file</code> im Programm als
<code class="varname">$::lx_office_conf->{debug}{file}</code>
erreichbar.</p><div class="warning" title="Warnung" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Warning"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Warnung]" src="system/docbook-xsl/images/warning.png"></td><th align="left">Warnung</th></tr><tr><td align="left" valign="top"><p>Zugriff auf die Konfiguration erfolgt im Moment über
- Hashkeys, sind also nicht gegen Tippfehler abgesichert.</p></td></tr></table></div></div><div class="sect3" title="4.1.3.7. $::instance_conf"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7341"></a>4.1.3.7. $::instance_conf</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Objekt der Klasse
+ Hashkeys, sind also nicht gegen Tippfehler abgesichert.</p></td></tr></table></div></div><div class="sect3" title="4.1.3.7. $::instance_conf"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7410"></a>4.1.3.7. $::instance_conf</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Objekt der Klasse
"<code class="classname">SL::InstanceConfiguration</code>"</p></li><li class="listitem"><p>wird pro Request neu erstellt</p></li></ul></div><p>Funktioniert wie <code class="varname">$::lx_office_conf</code>,
speichert aber Daten die von der Instanz abhängig sind. Eine Instanz
ist hier eine Mandantendatenbank. Beispielsweise überprüft
</p><pre class="programlisting">$::instance_conf->get_inventory_system eq 'perpetual'</pre><p>
- ob die berüchtigte Bestandsmethode zur Anwendung kommt.</p></div><div class="sect3" title="4.1.3.8. $::dispatcher"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7362"></a>4.1.3.8. $::dispatcher</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Objekt der Klasse
+ ob die berüchtigte Bestandsmethode zur Anwendung kommt.</p></div><div class="sect3" title="4.1.3.8. $::dispatcher"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7431"></a>4.1.3.8. $::dispatcher</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Objekt der Klasse
"<code class="varname">SL::Dispatcher</code>"</p></li><li class="listitem"><p>wird pro Serverprozess erstellt.</p></li><li class="listitem"><p>enthält Informationen über die technische Verbindung zum
Server</p></li></ul></div><p>Der dritte Punkt ist auch der einzige Grund warum das Objekt
global gespeichert wird. Wird vermutlich irgendwann in einem anderen
- Objekt untergebracht.</p></div><div class="sect3" title="4.1.3.9. $::request"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7380"></a>4.1.3.9. $::request</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Hashref (evtl später Objekt)</p></li><li class="listitem"><p>Wird pro Request neu initialisiert.</p></li><li class="listitem"><p>Keine Unterstruktur garantiert.</p></li></ul></div><p>
+ Objekt untergebracht.</p></div><div class="sect3" title="4.1.3.9. $::request"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7449"></a>4.1.3.9. $::request</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Hashref (evtl später Objekt)</p></li><li class="listitem"><p>Wird pro Request neu initialisiert.</p></li><li class="listitem"><p>Keine Unterstruktur garantiert.</p></li></ul></div><p>
<code class="varname">$::request</code> ist ein generischer Platz um
Daten "für den aktuellen Request" abzulegen. Sollte nicht für action
at a distance benutzt werden, sondern um lokales memoizing zu
<code class="varname">$::request</code>
</p></li><li class="listitem"><p>Muss ich von anderen Teilen des Programms lesend drauf
zugreifen? Dann <code class="varname">$::request</code>, aber Zugriff über
- Wrappermethode</p></li></ul></div></div></div><div class="sect2" title="4.1.4. Ehemalige globale Variablen"><div class="titlepage"><div><div><h3 class="title"><a name="d0e7422"></a>4.1.4. Ehemalige globale Variablen</h3></div></div></div><p>Die folgenden Variablen waren einmal im Programm, und wurden
- entfernt.</p><div class="sect3" title="4.1.4.1. $::cgi"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7427"></a>4.1.4.1. $::cgi</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>war nötig, weil cookie Methoden nicht als
+ Wrappermethode</p></li></ul></div></div></div><div class="sect2" title="4.1.4. Ehemalige globale Variablen"><div class="titlepage"><div><div><h3 class="title"><a name="d0e7491"></a>4.1.4. Ehemalige globale Variablen</h3></div></div></div><p>Die folgenden Variablen waren einmal im Programm, und wurden
+ entfernt.</p><div class="sect3" title="4.1.4.1. $::cgi"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7496"></a>4.1.4.1. $::cgi</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>war nötig, weil cookie Methoden nicht als
Klassenfunktionen funktionieren</p></li><li class="listitem"><p>Aufruf als Klasse erzeugt Dummyobjekt was im
Klassennamespace gehalten wird und über Requestgrenzen
leaked</p></li><li class="listitem"><p>liegt jetzt unter
<code class="varname">$::request->{cgi}</code>
- </p></li></ul></div></div><div class="sect3" title="4.1.4.2. $::all_units"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7443"></a>4.1.4.2. $::all_units</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>war nötig, weil einige Funktionen in Schleifen zum Teil
+ </p></li></ul></div></div><div class="sect3" title="4.1.4.2. $::all_units"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7512"></a>4.1.4.2. $::all_units</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>war nötig, weil einige Funktionen in Schleifen zum Teil
ein paar hundert mal pro Request eine Liste der Einheiten
brauchen, und de als Parameter durch einen Riesenstack von
Funktionen geschleift werden müssten.</p></li><li class="listitem"><p>Liegt jetzt unter
<code class="varname">$::request->{cache}{all_units}</code>
</p></li><li class="listitem"><p>Wird nur in
<code class="function">AM->retrieve_all_units()</code> gesetzt oder
- gelesen.</p></li></ul></div></div><div class="sect3" title="4.1.4.3. %::called_subs"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7462"></a>4.1.4.3. %::called_subs</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>wurde benutzt um callsub deep recursions
+ gelesen.</p></li></ul></div></div><div class="sect3" title="4.1.4.3. %::called_subs"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7531"></a>4.1.4.3. %::called_subs</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>wurde benutzt um callsub deep recursions
abzufangen.</p></li><li class="listitem"><p>Wurde entfernt, weil callsub nur einen Bruchteil der
möglichen Rekursioenen darstellt, und da nie welche
- auftreten.</p></li><li class="listitem"><p>komplette recursion protection wurde entfernt.</p></li></ul></div></div></div></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="ch03s09.html">Zurück</a> </td><td width="20%" align="center"> </td><td width="40%" align="right"> <a accesskey="n" href="ch04s02.html">Weiter</a></td></tr><tr><td width="40%" align="left" valign="top">3.9. Webshop-Api </td><td width="20%" align="center"><a accesskey="h" href="index.html">Zum Anfang</a></td><td width="40%" align="right" valign="top"> 4.2. Entwicklung unter FastCGI</td></tr></table></div></body></html>
\ No newline at end of file
+ auftreten.</p></li><li class="listitem"><p>komplette recursion protection wurde entfernt.</p></li></ul></div></div></div></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="ch03s10.html">Zurück</a> </td><td width="20%" align="center"> </td><td width="40%" align="right"> <a accesskey="n" href="ch04s02.html">Weiter</a></td></tr><tr><td width="40%" align="left" valign="top">3.10. ZUGFeRD Rechnungen </td><td width="20%" align="center"><a accesskey="h" href="index.html">Zum Anfang</a></td><td width="40%" align="right" valign="top"> 4.2. Entwicklung unter FastCGI</td></tr></table></div></body></html>
\ No newline at end of file
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>4.2. Entwicklung unter FastCGI</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch04.html" title="Kapitel 4. Entwicklerdokumentation"><link rel="prev" href="ch04.html" title="Kapitel 4. Entwicklerdokumentation"><link rel="next" href="ch04s03.html" title="4.3. Programmatische API-Aufrufe"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">4.2. Entwicklung unter FastCGI</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch04.html">Zurück</a> </td><th width="60%" align="center">Kapitel 4. Entwicklerdokumentation</th><td width="20%" align="right"> <a accesskey="n" href="ch04s03.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="4.2. Entwicklung unter FastCGI"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="devel.fcgi"></a>4.2. Entwicklung unter FastCGI</h2></div></div></div><div class="sect2" title="4.2.1. Allgemeines"><div class="titlepage"><div><div><h3 class="title"><a name="devel.fcgi.general"></a>4.2.1. Allgemeines</h3></div></div></div><p>Wenn Änderungen in der Konfiguration von kivitendo gemacht
+ <title>4.2. Entwicklung unter FastCGI</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch04.html" title="Kapitel 4. Entwicklerdokumentation"><link rel="prev" href="ch04.html" title="Kapitel 4. Entwicklerdokumentation"><link rel="next" href="ch04s03.html" title="4.3. Programmatische API-Aufrufe"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">4.2. Entwicklung unter FastCGI</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch04.html">Zurück</a> </td><th width="60%" align="center">Kapitel 4. Entwicklerdokumentation</th><td width="20%" align="right"> <a accesskey="n" href="ch04s03.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="4.2. Entwicklung unter FastCGI"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="devel.fcgi"></a>4.2. Entwicklung unter FastCGI</h2></div></div></div><div class="sect2" title="4.2.1. Allgemeines"><div class="titlepage"><div><div><h3 class="title"><a name="devel.fcgi.general"></a>4.2.1. Allgemeines</h3></div></div></div><p>Wenn Änderungen in der Konfiguration von kivitendo gemacht
werden, muss der Webserver neu gestartet werden.</p><p>Bei der Entwicklung für FastCGI ist auf ein paar Fallstricke zu
achten. Dadurch, dass das Programm in einer Endlosschleife läuft,
müssen folgende Aspekte beachtet werden.</p></div><div class="sect2" title="4.2.2. Programmende und Ausnahmen"><div class="titlepage"><div><div><h3 class="title"><a name="devel.fcgi.exiting"></a>4.2.2. Programmende und Ausnahmen</h3></div></div></div><p>Betrifft die Funktionen <code class="function">warn</code>,
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>4.3. Programmatische API-Aufrufe</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch04.html" title="Kapitel 4. Entwicklerdokumentation"><link rel="prev" href="ch04s02.html" title="4.2. Entwicklung unter FastCGI"><link rel="next" href="ch04s04.html" title="4.4. SQL-Upgradedateien"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">4.3. Programmatische API-Aufrufe</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch04s02.html">Zurück</a> </td><th width="60%" align="center">Kapitel 4. Entwicklerdokumentation</th><td width="20%" align="right"> <a accesskey="n" href="ch04s04.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="4.3. Programmatische API-Aufrufe"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="dev-programmatic-api-calls"></a>4.3. Programmatische API-Aufrufe</h2></div></div></div><div class="sect2" title="4.3.1. Einführung"><div class="titlepage"><div><div><h3 class="title"><a name="dev-programmatic-api-calls.introduction"></a>4.3.1. Einführung</h3></div></div></div><p>
+ <title>4.3. Programmatische API-Aufrufe</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch04.html" title="Kapitel 4. Entwicklerdokumentation"><link rel="prev" href="ch04s02.html" title="4.2. Entwicklung unter FastCGI"><link rel="next" href="ch04s04.html" title="4.4. SQL-Upgradedateien"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">4.3. Programmatische API-Aufrufe</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch04s02.html">Zurück</a> </td><th width="60%" align="center">Kapitel 4. Entwicklerdokumentation</th><td width="20%" align="right"> <a accesskey="n" href="ch04s04.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="4.3. Programmatische API-Aufrufe"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="dev-programmatic-api-calls"></a>4.3. Programmatische API-Aufrufe</h2></div></div></div><div class="sect2" title="4.3.1. Einführung"><div class="titlepage"><div><div><h3 class="title"><a name="dev-programmatic-api-calls.introduction"></a>4.3.1. Einführung</h3></div></div></div><p>
Es ist möglich, Funktionen in kivitendo programmatisch aus anderen Programmen aufzurufen. Dazu ist nötig, dass
Authentifizierungsinformationen in jedem Aufruf mitgegeben werden. Dafür gibt es zwei Methoden: die HTTP-»Basic«-Authentifizierung
oder die Übergabe als spziell benannte GET-Parameter. Neben den Authentifizierungsinformationen muss auch der zu verwendende Mandant
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>4.4. SQL-Upgradedateien</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch04.html" title="Kapitel 4. Entwicklerdokumentation"><link rel="prev" href="ch04s03.html" title="4.3. Programmatische API-Aufrufe"><link rel="next" href="ch04s05.html" title="4.5. Translations and languages"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">4.4. SQL-Upgradedateien</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch04s03.html">Zurück</a> </td><th width="60%" align="center">Kapitel 4. Entwicklerdokumentation</th><td width="20%" align="right"> <a accesskey="n" href="ch04s05.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="4.4. SQL-Upgradedateien"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="db-upgrade-files"></a>4.4. SQL-Upgradedateien</h2></div></div></div><div class="sect2" title="4.4.1. Einführung"><div class="titlepage"><div><div><h3 class="title"><a name="db-upgrade-files.introduction"></a>4.4.1. Einführung</h3></div></div></div><p>Datenbankupgrades werden über einzelne Upgrade-Scripte
+ <title>4.4. SQL-Upgradedateien</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch04.html" title="Kapitel 4. Entwicklerdokumentation"><link rel="prev" href="ch04s03.html" title="4.3. Programmatische API-Aufrufe"><link rel="next" href="ch04s05.html" title="4.5. Translations and languages"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">4.4. SQL-Upgradedateien</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch04s03.html">Zurück</a> </td><th width="60%" align="center">Kapitel 4. Entwicklerdokumentation</th><td width="20%" align="right"> <a accesskey="n" href="ch04s05.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="4.4. SQL-Upgradedateien"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="db-upgrade-files"></a>4.4. SQL-Upgradedateien</h2></div></div></div><div class="sect2" title="4.4.1. Einführung"><div class="titlepage"><div><div><h3 class="title"><a name="db-upgrade-files.introduction"></a>4.4.1. Einführung</h3></div></div></div><p>Datenbankupgrades werden über einzelne Upgrade-Scripte
gesteuert, die sich im Verzeichnis
<code class="filename">sql/Pg-upgrade2</code> befinden. In diesem Verzeichnis
muss pro Datenbankupgrade eine Datei existieren, die neben den
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>4.5. Translations and languages</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch04.html" title="Kapitel 4. Entwicklerdokumentation"><link rel="prev" href="ch04s04.html" title="4.4. SQL-Upgradedateien"><link rel="next" href="ch04s06.html" title="4.6. Die kivitendo-Test-Suite"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">4.5. Translations and languages</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch04s04.html">Zurück</a> </td><th width="60%" align="center">Kapitel 4. Entwicklerdokumentation</th><td width="20%" align="right"> <a accesskey="n" href="ch04s06.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="4.5. Translations and languages"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="translations-languages"></a>4.5. Translations and languages</h2></div></div></div><div class="sect2" title="4.5.1. Introduction"><div class="titlepage"><div><div><h3 class="title"><a name="translations-languages.introduction"></a>4.5.1. Introduction</h3></div></div></div><div class="note" title="Anmerkung" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Anmerkung]" src="system/docbook-xsl/images/note.png"></td><th align="left">Anmerkung</th></tr><tr><td align="left" valign="top"><p>Dieser Abschnitt ist in Englisch geschrieben, um
+ <title>4.5. Translations and languages</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch04.html" title="Kapitel 4. Entwicklerdokumentation"><link rel="prev" href="ch04s04.html" title="4.4. SQL-Upgradedateien"><link rel="next" href="ch04s06.html" title="4.6. Die kivitendo-Test-Suite"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">4.5. Translations and languages</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch04s04.html">Zurück</a> </td><th width="60%" align="center">Kapitel 4. Entwicklerdokumentation</th><td width="20%" align="right"> <a accesskey="n" href="ch04s06.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="4.5. Translations and languages"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="translations-languages"></a>4.5. Translations and languages</h2></div></div></div><div class="sect2" title="4.5.1. Introduction"><div class="titlepage"><div><div><h3 class="title"><a name="translations-languages.introduction"></a>4.5.1. Introduction</h3></div></div></div><div class="note" title="Anmerkung" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Anmerkung]" src="system/docbook-xsl/images/note.png"></td><th align="left">Anmerkung</th></tr><tr><td align="left" valign="top"><p>Dieser Abschnitt ist in Englisch geschrieben, um
internationalen Übersetzern die Arbeit zu erleichtern.</p></td></tr></table></div><p>This section describes how localization packages in kivitendo
are built. Currently the only language fully supported is German, and
since most of the internal messages are held in English the English
Template/XML
Template/LaTeX
Template/OpenDocument
-filenames</pre><p>The last of which is very machine dependant. Remember that
+filenames</pre><p>The last of which is very machine dependent. Remember that
a lot of characters are forbidden by some filesystems, for
example MS Windows doesn't like ':' in its files where Linux
doesn't mind that. If you want the files created with your
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>4.6. Die kivitendo-Test-Suite</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch04.html" title="Kapitel 4. Entwicklerdokumentation"><link rel="prev" href="ch04s05.html" title="4.5. Translations and languages"><link rel="next" href="ch04s07.html" title="4.7. Stil-Richtlinien"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">4.6. Die kivitendo-Test-Suite</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch04s05.html">Zurück</a> </td><th width="60%" align="center">Kapitel 4. Entwicklerdokumentation</th><td width="20%" align="right"> <a accesskey="n" href="ch04s07.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="4.6. Die kivitendo-Test-Suite"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="devel.testsuite"></a>4.6. Die kivitendo-Test-Suite</h2></div></div></div><div class="sect2" title="4.6.1. Einführung"><div class="titlepage"><div><div><h3 class="title"><a name="devel.testsuite.intro"></a>4.6.1. Einführung</h3></div></div></div><p>kivitendo enthält eine Suite für automatisierte Tests. Sie
+ <title>4.6. Die kivitendo-Test-Suite</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch04.html" title="Kapitel 4. Entwicklerdokumentation"><link rel="prev" href="ch04s05.html" title="4.5. Translations and languages"><link rel="next" href="ch04s07.html" title="4.7. Stil-Richtlinien"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">4.6. Die kivitendo-Test-Suite</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch04s05.html">Zurück</a> </td><th width="60%" align="center">Kapitel 4. Entwicklerdokumentation</th><td width="20%" align="right"> <a accesskey="n" href="ch04s07.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="4.6. Die kivitendo-Test-Suite"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="devel.testsuite"></a>4.6. Die kivitendo-Test-Suite</h2></div></div></div><div class="sect2" title="4.6.1. Einführung"><div class="titlepage"><div><div><h3 class="title"><a name="devel.testsuite.intro"></a>4.6.1. Einführung</h3></div></div></div><p>kivitendo enthält eine Suite für automatisierte Tests. Sie
basiert auf dem Standard-Perl-Modul
<code class="literal">Test::More</code>.</p><p>Die grundlegenden Fakten sind:</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Alle Tests liegen im Unterverzeichnis
<code class="filename">t/</code>.</p></li><li class="listitem"><p>Ein Script (bzw. ein Test) in <code class="filename">t/</code>
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>4.7. Stil-Richtlinien</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch04.html" title="Kapitel 4. Entwicklerdokumentation"><link rel="prev" href="ch04s06.html" title="4.6. Die kivitendo-Test-Suite"><link rel="next" href="ch04s08.html" title="4.8. Dokumentation erstellen"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">4.7. Stil-Richtlinien</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch04s06.html">Zurück</a> </td><th width="60%" align="center">Kapitel 4. Entwicklerdokumentation</th><td width="20%" align="right"> <a accesskey="n" href="ch04s08.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="4.7. Stil-Richtlinien"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="devel.style-guide"></a>4.7. Stil-Richtlinien</h2></div></div></div><p>Die folgenden Regeln haben das Ziel, den Code möglichst gut les-
+ <title>4.7. Stil-Richtlinien</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch04.html" title="Kapitel 4. Entwicklerdokumentation"><link rel="prev" href="ch04s06.html" title="4.6. Die kivitendo-Test-Suite"><link rel="next" href="ch04s08.html" title="4.8. Dokumentation erstellen"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">4.7. Stil-Richtlinien</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch04s06.html">Zurück</a> </td><th width="60%" align="center">Kapitel 4. Entwicklerdokumentation</th><td width="20%" align="right"> <a accesskey="n" href="ch04s08.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="4.7. Stil-Richtlinien"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="devel.style-guide"></a>4.7. Stil-Richtlinien</h2></div></div></div><p>Die folgenden Regeln haben das Ziel, den Code möglichst gut les-
und wartbar zu machen. Dazu gehört zum Einen, dass der Code einheitlich
eingerückt ist, aber auch, dass Mehrdeutigkeit so weit es geht vermieden
wird (Stichworte "Klammern" oder "Hash-Keys").</p><p>Diese Regeln sind keine Schikane sondern erleichtern allen das
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>4.8. Dokumentation erstellen</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch04.html" title="Kapitel 4. Entwicklerdokumentation"><link rel="prev" href="ch04s07.html" title="4.7. Stil-Richtlinien"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">4.8. Dokumentation erstellen</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch04s07.html">Zurück</a> </td><th width="60%" align="center">Kapitel 4. Entwicklerdokumentation</th><td width="20%" align="right"> </td></tr></table><hr></div><div class="sect1" title="4.8. Dokumentation erstellen"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="devel.build-doc"></a>4.8. Dokumentation erstellen</h2></div></div></div><div class="sect2" title="4.8.1. Einführung"><div class="titlepage"><div><div><h3 class="title"><a name="devel.build-doc.introduction"></a>4.8.1. Einführung</h3></div></div></div><p>Diese Dokumentation ist in <span class="productname">DocBook</span>™
+ <title>4.8. Dokumentation erstellen</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch04.html" title="Kapitel 4. Entwicklerdokumentation"><link rel="prev" href="ch04s07.html" title="4.7. Stil-Richtlinien"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">4.8. Dokumentation erstellen</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch04s07.html">Zurück</a> </td><th width="60%" align="center">Kapitel 4. Entwicklerdokumentation</th><td width="20%" align="right"> </td></tr></table><hr></div><div class="sect1" title="4.8. Dokumentation erstellen"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="devel.build-doc"></a>4.8. Dokumentation erstellen</h2></div></div></div><div class="sect2" title="4.8.1. Einführung"><div class="titlepage"><div><div><h3 class="title"><a name="devel.build-doc.introduction"></a>4.8.1. Einführung</h3></div></div></div><p>Diese Dokumentation ist in <span class="productname">DocBook</span>™
XML geschrieben. Zum Bearbeiten reicht grundsätzlich ein Text-Editor.
Mehr Komfort bekommt man, wenn man einen dedizierten XML-fähigen
Editor nutzt, der spezielle Unterstützung für
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>kivitendo 3.5.4: Installation, Konfiguration, Entwicklung</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><link rel="next" href="ch01.html" title="Kapitel 1. Aktuelle Hinweise"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">kivitendo 3.5.4: Installation, Konfiguration,
- Entwicklung</th></tr><tr><td width="20%" align="left"> </td><th width="60%" align="center"> </th><td width="20%" align="right"> <a accesskey="n" href="ch01.html">Weiter</a></td></tr></table><hr></div><div lang="de" class="book" title="kivitendo 3.5.4: Installation, Konfiguration, Entwicklung"><div class="titlepage"><div><div><h1 class="title"><a name="kivitendo-documentation"></a>kivitendo 3.5.4: Installation, Konfiguration,
- Entwicklung</h1></div></div><hr></div><div class="toc"><p><b>Inhaltsverzeichnis</b></p><dl><dt><span class="chapter"><a href="ch01.html">1. Aktuelle Hinweise</a></span></dt><dt><span class="chapter"><a href="ch02.html">2. Installation und Grundkonfiguration</a></span></dt><dd><dl><dt><span class="sect1"><a href="ch02.html#Installation-%C3%9Cbersicht">2.1. Übersicht</a></span></dt><dt><span class="sect1"><a href="ch02s02.html">2.2. Benötigte Software und Pakete</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s02.html#Betriebssystem">2.2.1. Betriebssystem</a></span></dt><dt><span class="sect2"><a href="ch02s02.html#Pakete">2.2.2. Benötigte Perl-Pakete installieren</a></span></dt><dt><span class="sect2"><a href="ch02s02.html#d0e631">2.2.3. Andere Pakete installieren</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s03.html">2.3. Manuelle Installation des Programmpaketes</a></span></dt><dt><span class="sect1"><a href="ch02s04.html">2.4. kivitendo-Konfigurationsdatei</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s04.html#config.config-file.introduction">2.4.1. Einführung</a></span></dt><dt><span class="sect2"><a href="ch02s04.html#config.config-file.sections-parameters">2.4.2. Abschnitte und Parameter</a></span></dt><dt><span class="sect2"><a href="ch02s04.html#config.config-file.prior-versions">2.4.3. Versionen vor 2.6.3</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s05.html">2.5. Anpassung der PostgreSQL-Konfiguration</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s05.html#Zeichens%C3%A4tze-die-Verwendung-von-UTF-8">2.5.1. Zeichensätze/die Verwendung von Unicode/UTF-8</a></span></dt><dt><span class="sect2"><a href="ch02s05.html#%C3%84nderungen-an-Konfigurationsdateien">2.5.2. Änderungen an Konfigurationsdateien</a></span></dt><dt><span class="sect2"><a href="ch02s05.html#Erweiterung-f%C3%BCr-servergespeicherte-Prozeduren">2.5.3. Erweiterung für servergespeicherte Prozeduren</a></span></dt><dt><span class="sect2"><a href="ch02s05.html#Erweiterung-f%C3%BCr-trigram">2.5.4. Erweiterung für Trigram Prozeduren</a></span></dt><dt><span class="sect2"><a href="ch02s05.html#Datenbankbenutzer-anlegen">2.5.5. Datenbankbenutzer anlegen</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s06.html">2.6. Webserver-Konfiguration</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s06.html#d0e1111">2.6.1. Grundkonfiguration mittels CGI</a></span></dt><dt><span class="sect2"><a href="ch02s06.html#Apache-Konfiguration.FCGI">2.6.2. Konfiguration für FastCGI/FCGI</a></span></dt><dt><span class="sect2"><a href="ch02s06.html#d0e1262">2.6.3. Authentifizierung mittels HTTP Basic Authentication</a></span></dt><dt><span class="sect2"><a href="ch02s06.html#d0e1278">2.6.4. Weitergehende Konfiguration</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s07.html">2.7. Der Task-Server</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s07.html#Konfiguration-des-Task-Servers">2.7.1. Verfügbare und notwendige Konfigurationsoptionen</a></span></dt><dt><span class="sect2"><a href="ch02s07.html#Konfiguration-der-Mandanten-fuer-den-Task-Servers">2.7.2. Konfiguration der Mandanten für den Task-Server</a></span></dt><dt><span class="sect2"><a href="ch02s07.html#Einbinden-in-den-Boot-Prozess">2.7.3. Automatisches Starten des Task-Servers beim Booten</a></span></dt><dt><span class="sect2"><a href="ch02s07.html#Prozesskontrolle">2.7.4. Wie der Task-Server gestartet und beendet wird</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s08.html">2.8. Benutzerauthentifizierung und Administratorpasswort</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s08.html#Grundlagen-zur-Benutzerauthentifizierung">2.8.1. Grundlagen zur Benutzerauthentifizierung</a></span></dt><dt><span class="sect2"><a href="ch02s08.html#Administratorpasswort">2.8.2. Administratorpasswort</a></span></dt><dt><span class="sect2"><a href="ch02s08.html#Authentifizierungsdatenbank">2.8.3. Authentifizierungsdatenbank</a></span></dt><dt><span class="sect2"><a href="ch02s08.html#Passwort%C3%BCberpr%C3%BCfung">2.8.4. Passwortüberprüfung</a></span></dt><dt><span class="sect2"><a href="ch02s08.html#Name-des-Session-Cookies">2.8.5. Name des Session-Cookies</a></span></dt><dt><span class="sect2"><a href="ch02s08.html#Anlegen-der-Authentifizierungsdatenbank">2.8.6. Anlegen der Authentifizierungsdatenbank</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s09.html">2.9. Mandanten-, Benutzer- und Gruppenverwaltung</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s09.html#Zusammenh%C3%A4nge">2.9.1. Zusammenhänge</a></span></dt><dt><span class="sect2"><a href="ch02s09.html#Mandanten-Benutzer-Gruppen">2.9.2. Mandanten, Benutzer und Gruppen</a></span></dt><dt><span class="sect2"><a href="ch02s09.html#Datenbanken-anlegen">2.9.3. Datenbanken anlegen</a></span></dt><dt><span class="sect2"><a href="ch02s09.html#Gruppen-anlegen">2.9.4. Gruppen anlegen</a></span></dt><dt><span class="sect2"><a href="ch02s09.html#Benutzer-anlegen">2.9.5. Benutzer anlegen</a></span></dt><dt><span class="sect2"><a href="ch02s09.html#Mandanten-anlegen">2.9.6. Mandanten anlegen</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s10.html">2.10. Drucker- und Systemverwaltung</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s10.html#Druckeradministration">2.10.1. Druckeradministration</a></span></dt><dt><span class="sect2"><a href="ch02s10.html#System">2.10.2. System sperren / entsperren</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s11.html">2.11. E-Mail-Versand aus kivitendo heraus</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s11.html#config.sending-email.sendmail">2.11.1. Versand über lokalen E-Mail-Server</a></span></dt><dt><span class="sect2"><a href="ch02s11.html#config.sending-email.smtp">2.11.2. Versand über einen SMTP-Server</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s12.html">2.12. Drucken mit kivitendo</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s12.html#Vorlagenverzeichnis-anlegen">2.12.1. Vorlagenverzeichnis anlegen</a></span></dt><dt><span class="sect2"><a href="ch02s12.html#Vorlagen-RB">2.12.2. Der Druckvorlagensatz RB</a></span></dt><dt><span class="sect2"><a href="ch02s12.html#f-tex">2.12.3. f-tex</a></span></dt><dt><span class="sect2"><a href="ch02s12.html#Vorlagen-rev-odt">2.12.4. Der Druckvorlagensatz rev-odt</a></span></dt><dt><span class="sect2"><a href="ch02s12.html#allgemeine-hinweise-zu-latex">2.12.5. Allgemeine Hinweise zu LaTeX Vorlagen</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s13.html">2.13. OpenDocument-Vorlagen</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s13.html#d0e2449">2.13.1. OpenDocument (odt) Druckvorlagen mit Makros</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s14.html">2.14. Nomenklatur</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s14.html#booking.dates">2.14.1. Datum bei Buchungen</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s15.html">2.15. Konfiguration zur Einnahmenüberschussrechnung/Bilanzierung:
+ <title>kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="next" href="ch01.html" title="Kapitel 1. Aktuelle Hinweise"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">kivitendo 3.5.6.1: Installation, Konfiguration,
+ Entwicklung</th></tr><tr><td width="20%" align="left"> </td><th width="60%" align="center"> </th><td width="20%" align="right"> <a accesskey="n" href="ch01.html">Weiter</a></td></tr></table><hr></div><div lang="de" class="book" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><div class="titlepage"><div><div><h1 class="title"><a name="kivitendo-documentation"></a>kivitendo 3.5.6.1: Installation, Konfiguration,
+ Entwicklung</h1></div></div><hr></div><div class="toc"><p><b>Inhaltsverzeichnis</b></p><dl><dt><span class="chapter"><a href="ch01.html">1. Aktuelle Hinweise</a></span></dt><dt><span class="chapter"><a href="ch02.html">2. Installation und Grundkonfiguration</a></span></dt><dd><dl><dt><span class="sect1"><a href="ch02.html#Installation-%C3%9Cbersicht">2.1. Übersicht</a></span></dt><dt><span class="sect1"><a href="ch02s02.html">2.2. Benötigte Software und Pakete</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s02.html#Betriebssystem">2.2.1. Betriebssystem</a></span></dt><dt><span class="sect2"><a href="ch02s02.html#Pakete">2.2.2. Benötigte Perl-Pakete installieren</a></span></dt><dt><span class="sect2"><a href="ch02s02.html#d0e649">2.2.3. Andere Pakete installieren</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s03.html">2.3. Manuelle Installation des Programmpaketes</a></span></dt><dt><span class="sect1"><a href="ch02s04.html">2.4. kivitendo-Konfigurationsdatei</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s04.html#config.config-file.introduction">2.4.1. Einführung</a></span></dt><dt><span class="sect2"><a href="ch02s04.html#config.config-file.sections-parameters">2.4.2. Abschnitte und Parameter</a></span></dt><dt><span class="sect2"><a href="ch02s04.html#config.config-file.prior-versions">2.4.3. Versionen vor 2.6.3</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s05.html">2.5. Anpassung der PostgreSQL-Konfiguration</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s05.html#Zeichens%C3%A4tze-die-Verwendung-von-UTF-8">2.5.1. Zeichensätze/die Verwendung von Unicode/UTF-8</a></span></dt><dt><span class="sect2"><a href="ch02s05.html#%C3%84nderungen-an-Konfigurationsdateien">2.5.2. Änderungen an Konfigurationsdateien</a></span></dt><dt><span class="sect2"><a href="ch02s05.html#Erweiterung-f%C3%BCr-servergespeicherte-Prozeduren">2.5.3. Erweiterung für servergespeicherte Prozeduren</a></span></dt><dt><span class="sect2"><a href="ch02s05.html#Erweiterung-f%C3%BCr-trigram">2.5.4. Erweiterung für Trigram Prozeduren</a></span></dt><dt><span class="sect2"><a href="ch02s05.html#Datenbankbenutzer-anlegen">2.5.5. Datenbankbenutzer anlegen</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s06.html">2.6. Webserver-Konfiguration</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s06.html#d0e1129">2.6.1. Grundkonfiguration mittels CGI</a></span></dt><dt><span class="sect2"><a href="ch02s06.html#Apache-Konfiguration.FCGI">2.6.2. Konfiguration für FastCGI/FCGI</a></span></dt><dt><span class="sect2"><a href="ch02s06.html#d0e1283">2.6.3. Authentifizierung mittels HTTP Basic Authentication</a></span></dt><dt><span class="sect2"><a href="ch02s06.html#d0e1299">2.6.4. Aktivierung von mod_rewrite/directory_match für git basierte Installationen</a></span></dt><dt><span class="sect2"><a href="ch02s06.html#d0e1313">2.6.5. Weitergehende Konfiguration</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s07.html">2.7. Der Task-Server</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s07.html#Konfiguration-des-Task-Servers">2.7.1. Verfügbare und notwendige Konfigurationsoptionen</a></span></dt><dt><span class="sect2"><a href="ch02s07.html#Konfiguration-der-Mandanten-fuer-den-Task-Servers">2.7.2. Konfiguration der Mandanten für den Task-Server</a></span></dt><dt><span class="sect2"><a href="ch02s07.html#Einbinden-in-den-Boot-Prozess">2.7.3. Automatisches Starten des Task-Servers beim Booten</a></span></dt><dt><span class="sect2"><a href="ch02s07.html#Prozesskontrolle">2.7.4. Wie der Task-Server gestartet und beendet wird</a></span></dt><dt><span class="sect2"><a href="ch02s07.html#Tasks konfigurieren">2.7.5. Exemplarische Konfiguration eines Hintergrund-Jobs, der die Jahreszahl in allen Nummernkreisen zum Jahreswechsel erhöht</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s08.html">2.8. Benutzerauthentifizierung und Administratorpasswort</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s08.html#Grundlagen-zur-Benutzerauthentifizierung">2.8.1. Grundlagen zur Benutzerauthentifizierung</a></span></dt><dt><span class="sect2"><a href="ch02s08.html#Administratorpasswort">2.8.2. Administratorpasswort</a></span></dt><dt><span class="sect2"><a href="ch02s08.html#Authentifizierungsdatenbank">2.8.3. Authentifizierungsdatenbank</a></span></dt><dt><span class="sect2"><a href="ch02s08.html#Passwort%C3%BCberpr%C3%BCfung">2.8.4. Passwortüberprüfung</a></span></dt><dt><span class="sect2"><a href="ch02s08.html#Name-des-Session-Cookies">2.8.5. Name des Session-Cookies</a></span></dt><dt><span class="sect2"><a href="ch02s08.html#Anlegen-der-Authentifizierungsdatenbank">2.8.6. Anlegen der Authentifizierungsdatenbank</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s09.html">2.9. Mandanten-, Benutzer- und Gruppenverwaltung</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s09.html#Zusammenh%C3%A4nge">2.9.1. Zusammenhänge</a></span></dt><dt><span class="sect2"><a href="ch02s09.html#Mandanten-Benutzer-Gruppen">2.9.2. Mandanten, Benutzer und Gruppen</a></span></dt><dt><span class="sect2"><a href="ch02s09.html#Datenbanken-anlegen">2.9.3. Datenbanken anlegen</a></span></dt><dt><span class="sect2"><a href="ch02s09.html#Gruppen-anlegen">2.9.4. Gruppen anlegen</a></span></dt><dt><span class="sect2"><a href="ch02s09.html#Benutzer-anlegen">2.9.5. Benutzer anlegen</a></span></dt><dt><span class="sect2"><a href="ch02s09.html#Mandanten-anlegen">2.9.6. Mandanten anlegen</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s10.html">2.10. Drucker- und Systemverwaltung</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s10.html#Druckeradministration">2.10.1. Druckeradministration</a></span></dt><dt><span class="sect2"><a href="ch02s10.html#System">2.10.2. System sperren / entsperren</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s11.html">2.11. E-Mail-Versand aus kivitendo heraus</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s11.html#config.sending-email.sendmail">2.11.1. Versand über lokalen E-Mail-Server</a></span></dt><dt><span class="sect2"><a href="ch02s11.html#config.sending-email.smtp">2.11.2. Versand über einen SMTP-Server</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s12.html">2.12. Drucken mit kivitendo</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s12.html#Vorlagenverzeichnis-anlegen">2.12.1. Vorlagenverzeichnis anlegen</a></span></dt><dt><span class="sect2"><a href="ch02s12.html#Vorlagen-RB">2.12.2. Der Druckvorlagensatz RB</a></span></dt><dt><span class="sect2"><a href="ch02s12.html#Vorlagen-rev-odt">2.12.3. Der Druckvorlagensatz rev-odt</a></span></dt><dt><span class="sect2"><a href="ch02s12.html#allgemeine-hinweise-zu-latex">2.12.4. Allgemeine Hinweise zu LaTeX Vorlagen</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s13.html">2.13. OpenDocument-Vorlagen</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s13.html#d0e2420">2.13.1. OpenDocument (odt) Druckvorlagen mit Makros</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s14.html">2.14. Nomenklatur</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s14.html#booking.dates">2.14.1. Datum bei Buchungen</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s15.html">2.15. Konfiguration zur Einnahmenüberschussrechnung/Bilanzierung:
EUR</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s15.html#config.eur.introduction">2.15.1. Einführung</a></span></dt><dt><span class="sect2"><a href="ch02s15.html#config.eur.parameters">2.15.2. Konfigurationsparameter</a></span></dt><dt><span class="sect2"><a href="ch02s15.html#config.eur.setting-parameters">2.15.3. Festlegen der Parameter</a></span></dt><dt><span class="sect2"><a href="ch02s15.html#config.eur.inventory-system-perpetual">2.15.4. Bemerkungen zur Bestandsmethode</a></span></dt><dt><span class="sect2"><a href="ch02s15.html#config.eur.knonw-issues">2.15.5. Bekannte Probleme</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s16.html">2.16. SKR04 19% Umstellung für innergemeinschaftlichen Erwerb</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s16.html#config.skr04-update-3804.introduction">2.16.1. Einführung</a></span></dt><dt><span class="sect2"><a href="ch02s16.html#config.skr04-update-3804.create-chart">2.16.2. Konto 3804 manuell anlegen</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s17.html">2.17. Verhalten des Bilanzberichts</a></span></dt><dt><span class="sect1"><a href="ch02s18.html">2.18. Erfolgsrechnung</a></span></dt><dt><span class="sect1"><a href="ch02s19.html">2.19. Rundung in Verkaufsbelegen</a></span></dt><dt><span class="sect1"><a href="ch02s20.html">2.20. Einstellungen pro Mandant</a></span></dt><dt><span class="sect1"><a href="ch02s21.html">2.21. kivitendo ERP verwenden</a></span></dt></dl></dd><dt><span class="chapter"><a href="ch03.html">3. Features und Funktionen</a></span></dt><dd><dl><dt><span class="sect1"><a href="ch03.html#features.periodic-invoices">3.1. Wiederkehrende Rechnungen</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch03.html#features.periodic-invoices.introduction">3.1.1. Einführung</a></span></dt><dt><span class="sect2"><a href="ch03.html#features.periodic-invoices.configuration">3.1.2. Konfiguration</a></span></dt><dt><span class="sect2"><a href="ch03.html#features.periodic-invoices.variables">3.1.3. Spezielle Variablen</a></span></dt><dt><span class="sect2"><a href="ch03.html#features.periodic-invoices.reports">3.1.4. Auflisten</a></span></dt><dt><span class="sect2"><a href="ch03.html#features.periodic-invoices.task-server">3.1.5. Erzeugung der eigentlichen Rechnungen</a></span></dt><dt><span class="sect2"><a href="ch03.html#features.periodic-invoices.create-for-current-month">3.1.6. Erste Rechnung für aktuellen Monat erstellen</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch03s02.html">3.2. Bankerweiterung</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch03s02.html#features.bank.introduction">3.2.1. Einführung</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch03s03.html">3.3. Dokumentenvorlagen und verfügbare Variablen</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch03s03.html#dokumentenvorlagen-und-variablen.einf%C3%BChrung">3.3.1. Einführung</a></span></dt><dt><span class="sect2"><a href="ch03s03.html#dokumentenvorlagen-und-variablen.variablen-ausgeben">3.3.2. Variablen ausgeben</a></span></dt><dt><span class="sect2"><a href="ch03s03.html#dokumentenvorlagen-und-variablen.verwendung-in-druckbefehlen">3.3.3. Verwendung in Druckbefehlen</a></span></dt><dt><span class="sect2"><a href="ch03s03.html#dokumentenvorlagen-und-variablen.tag-style">3.3.4. Anfang und Ende der Tags verändern</a></span></dt><dt><span class="sect2"><a href="ch03s03.html#dokumentenvorlagen-und-variablen.zuordnung-dateinamen">3.3.5. Zuordnung von den Dateinamen zu den Funktionen</a></span></dt><dt><span class="sect2"><a href="ch03s03.html#dokumentenvorlagen-und-variablen.dateinamen-erweitert">3.3.6. Sprache, Drucker und E-Mail</a></span></dt><dt><span class="sect2"><a href="ch03s03.html#dokumentenvorlagen-und-variablen.allgemeine-variablen">3.3.7. Allgemeine Variablen, die in allen Vorlagen vorhanden
sind</a></span></dt><dt><span class="sect2"><a href="ch03s03.html#dokumentenvorlagen-und-variablen.invoice">3.3.8. Variablen in Rechnungen</a></span></dt><dt><span class="sect2"><a href="ch03s03.html#dokumentenvorlagen-und-variablen.dunning">3.3.9. Variablen in Mahnungen und Rechnungen über Mahngebühren</a></span></dt><dt><span class="sect2"><a href="ch03s03.html#dokumentenvorlagen-und-variablen.andere-vorlagen">3.3.10. Variablen in anderen Vorlagen</a></span></dt><dt><span class="sect2"><a href="ch03s03.html#dokumentenvorlagen-und-variablen.bloecke">3.3.11. Blöcke, bedingte Anweisungen und Schleifen</a></span></dt><dt><span class="sect2"><a href="ch03s03.html#dokumentenvorlagen-und-variablen.markup">3.3.12. Markup-Code zur Textformatierung innerhalb von
- Formularen</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch03s04.html">3.4. Excel-Vorlagen</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch03s04.html#excel-templates.summary">3.4.1. Zusammenfassung</a></span></dt><dt><span class="sect2"><a href="ch03s04.html#excel-templates.usage">3.4.2. Bedienung</a></span></dt><dt><span class="sect2"><a href="ch03s04.html#excel-templates.syntax">3.4.3. Variablensyntax</a></span></dt><dt><span class="sect2"><a href="ch03s04.html#excel-templates.limitations">3.4.4. Einschränkungen</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch03s05.html">3.5. Mandantenkonfiguration Lager</a></span></dt><dt><span class="sect1"><a href="ch03s06.html">3.6. Schweizer Kontenpläne</a></span></dt><dt><span class="sect1"><a href="ch03s07.html">3.7. Artikelklassifizierung</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch03s07.html#d0e6519">3.7.1. Übersicht</a></span></dt><dt><span class="sect2"><a href="ch03s07.html#d0e6524">3.7.2. Basisklassifizierung</a></span></dt><dt><span class="sect2"><a href="ch03s07.html#d0e6554">3.7.3. Attribute</a></span></dt><dt><span class="sect2"><a href="ch03s07.html#d0e6585">3.7.4. Zwei-Zeichen Abkürzung</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch03s08.html">3.8. Dateiverwaltung (Mini-DMS)</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch03s08.html#d0e6597">3.8.1. Übersicht</a></span></dt><dt><span class="sect2"><a href="ch03s08.html#d0e6624">3.8.2. Struktur</a></span></dt><dt><span class="sect2"><a href="ch03s08.html#d0e6676">3.8.3. Anwendung</a></span></dt><dt><span class="sect2"><a href="ch03s08.html#d0e6719">3.8.4. Konfigurierung</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch03s09.html">3.9. Webshop-Api</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch03s09.html#d0e6784">3.9.1. Rechte für die Webshopapi</a></span></dt><dt><span class="sect2"><a href="ch03s09.html#d0e6799">3.9.2. Konfiguration</a></span></dt><dt><span class="sect2"><a href="ch03s09.html#d0e6807">3.9.3. Webshopartikel</a></span></dt><dt><span class="sect2"><a href="ch03s09.html#d0e6831">3.9.4. Bestellimport</a></span></dt><dt><span class="sect2"><a href="ch03s09.html#d0e6884">3.9.5. Mapping der Daten</a></span></dt></dl></dd></dl></dd><dt><span class="chapter"><a href="ch04.html">4. Entwicklerdokumentation</a></span></dt><dd><dl><dt><span class="sect1"><a href="ch04.html#devel.globals">4.1. Globale Variablen</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch04.html#d0e6900">4.1.1. Wie sehen globale Variablen in Perl aus?</a></span></dt><dt><span class="sect2"><a href="ch04.html#d0e7001">4.1.2. Warum sind globale Variablen ein Problem?</a></span></dt><dt><span class="sect2"><a href="ch04.html#d0e7034">4.1.3. Kanonische globale Variablen</a></span></dt><dt><span class="sect2"><a href="ch04.html#d0e7422">4.1.4. Ehemalige globale Variablen</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch04s02.html">4.2. Entwicklung unter FastCGI</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch04s02.html#devel.fcgi.general">4.2.1. Allgemeines</a></span></dt><dt><span class="sect2"><a href="ch04s02.html#devel.fcgi.exiting">4.2.2. Programmende und Ausnahmen</a></span></dt><dt><span class="sect2"><a href="ch04s02.html#devel.fcgi.globals">4.2.3. Globale Variablen</a></span></dt><dt><span class="sect2"><a href="ch04s02.html#devel.fcgi.performance">4.2.4. Performance und Statistiken</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch04s03.html">4.3. Programmatische API-Aufrufe</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch04s03.html#dev-programmatic-api-calls.introduction">4.3.1. Einführung</a></span></dt><dt><span class="sect2"><a href="ch04s03.html#dev-programmatic-api-calls.client_selection">4.3.2. Wahl des Mandanten</a></span></dt><dt><span class="sect2"><a href="ch04s03.html#dev-programmatic-api-calls.http_basic_authentication">4.3.3. HTTP-»Basic«-Authentifizierung</a></span></dt><dt><span class="sect2"><a href="ch04s03.html#dev-programmatic-api-calls.authentication_via_parameters">4.3.4. Authentifizierung mit Parametern</a></span></dt><dt><span class="sect2"><a href="ch04s03.html#dev-programmatic-api-calls.examples">4.3.5. Beispiele</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch04s04.html">4.4. SQL-Upgradedateien</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch04s04.html#db-upgrade-files.introduction">4.4.1. Einführung</a></span></dt><dt><span class="sect2"><a href="ch04s04.html#db-upgrade-files.format">4.4.2. Format der Kontrollinformationen</a></span></dt><dt><span class="sect2"><a href="ch04s04.html#db-upgrade-files.format-perl-files">4.4.3. Format von in Perl geschriebenen
+ Formularen</a></span></dt><dt><span class="sect2"><a href="ch03s03.html#dokumentenvorlagen-und-variablen.anrede">3.3.13. Hinweise zur Anrede</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch03s04.html">3.4. Excel-Vorlagen</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch03s04.html#excel-templates.summary">3.4.1. Zusammenfassung</a></span></dt><dt><span class="sect2"><a href="ch03s04.html#excel-templates.usage">3.4.2. Bedienung</a></span></dt><dt><span class="sect2"><a href="ch03s04.html#excel-templates.syntax">3.4.3. Variablensyntax</a></span></dt><dt><span class="sect2"><a href="ch03s04.html#excel-templates.limitations">3.4.4. Einschränkungen</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch03s05.html">3.5. Mandantenkonfiguration Lager</a></span></dt><dt><span class="sect1"><a href="ch03s06.html">3.6. Schweizer Kontenpläne</a></span></dt><dt><span class="sect1"><a href="ch03s07.html">3.7. Artikelklassifizierung</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch03s07.html#d0e6515">3.7.1. Übersicht</a></span></dt><dt><span class="sect2"><a href="ch03s07.html#d0e6520">3.7.2. Basisklassifizierung</a></span></dt><dt><span class="sect2"><a href="ch03s07.html#d0e6550">3.7.3. Attribute</a></span></dt><dt><span class="sect2"><a href="ch03s07.html#d0e6581">3.7.4. Zwei-Zeichen Abkürzung</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch03s08.html">3.8. Dateiverwaltung (Mini-DMS)</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch03s08.html#d0e6593">3.8.1. Übersicht</a></span></dt><dt><span class="sect2"><a href="ch03s08.html#d0e6620">3.8.2. Struktur</a></span></dt><dt><span class="sect2"><a href="ch03s08.html#d0e6672">3.8.3. Anwendung</a></span></dt><dt><span class="sect2"><a href="ch03s08.html#d0e6715">3.8.4. Konfigurierung</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch03s09.html">3.9. Webshop-Api</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch03s09.html#d0e6780">3.9.1. Rechte für die Webshopapi</a></span></dt><dt><span class="sect2"><a href="ch03s09.html#d0e6795">3.9.2. Konfiguration</a></span></dt><dt><span class="sect2"><a href="ch03s09.html#d0e6803">3.9.3. Webshopartikel</a></span></dt><dt><span class="sect2"><a href="ch03s09.html#d0e6827">3.9.4. Bestellimport</a></span></dt><dt><span class="sect2"><a href="ch03s09.html#d0e6880">3.9.5. Mapping der Daten</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch03s10.html">3.10. ZUGFeRD Rechnungen</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch03s10.html#features.zugferd.preamble">3.10.1. Vorbedingung</a></span></dt><dt><span class="sect2"><a href="ch03s10.html#features.zugferd.summary">3.10.2. Übersicht</a></span></dt><dt><span class="sect2"><a href="ch03s10.html#features.zugferd.create_zugferd_bills">3.10.3. Erstellen von ZUGFeRD Rechnungen in Kivitendo</a></span></dt><dt><span class="sect2"><a href="ch03s10.html#features.zugferd.read_zugferd_bills">3.10.4. Einlesen von ZUGFeRD Rechnungen in Kivitendo</a></span></dt></dl></dd></dl></dd><dt><span class="chapter"><a href="ch04.html">4. Entwicklerdokumentation</a></span></dt><dd><dl><dt><span class="sect1"><a href="ch04.html#devel.globals">4.1. Globale Variablen</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch04.html#d0e6969">4.1.1. Wie sehen globale Variablen in Perl aus?</a></span></dt><dt><span class="sect2"><a href="ch04.html#d0e7070">4.1.2. Warum sind globale Variablen ein Problem?</a></span></dt><dt><span class="sect2"><a href="ch04.html#d0e7103">4.1.3. Kanonische globale Variablen</a></span></dt><dt><span class="sect2"><a href="ch04.html#d0e7491">4.1.4. Ehemalige globale Variablen</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch04s02.html">4.2. Entwicklung unter FastCGI</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch04s02.html#devel.fcgi.general">4.2.1. Allgemeines</a></span></dt><dt><span class="sect2"><a href="ch04s02.html#devel.fcgi.exiting">4.2.2. Programmende und Ausnahmen</a></span></dt><dt><span class="sect2"><a href="ch04s02.html#devel.fcgi.globals">4.2.3. Globale Variablen</a></span></dt><dt><span class="sect2"><a href="ch04s02.html#devel.fcgi.performance">4.2.4. Performance und Statistiken</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch04s03.html">4.3. Programmatische API-Aufrufe</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch04s03.html#dev-programmatic-api-calls.introduction">4.3.1. Einführung</a></span></dt><dt><span class="sect2"><a href="ch04s03.html#dev-programmatic-api-calls.client_selection">4.3.2. Wahl des Mandanten</a></span></dt><dt><span class="sect2"><a href="ch04s03.html#dev-programmatic-api-calls.http_basic_authentication">4.3.3. HTTP-»Basic«-Authentifizierung</a></span></dt><dt><span class="sect2"><a href="ch04s03.html#dev-programmatic-api-calls.authentication_via_parameters">4.3.4. Authentifizierung mit Parametern</a></span></dt><dt><span class="sect2"><a href="ch04s03.html#dev-programmatic-api-calls.examples">4.3.5. Beispiele</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch04s04.html">4.4. SQL-Upgradedateien</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch04s04.html#db-upgrade-files.introduction">4.4.1. Einführung</a></span></dt><dt><span class="sect2"><a href="ch04s04.html#db-upgrade-files.format">4.4.2. Format der Kontrollinformationen</a></span></dt><dt><span class="sect2"><a href="ch04s04.html#db-upgrade-files.format-perl-files">4.4.3. Format von in Perl geschriebenen
Datenbankupgradescripten</a></span></dt><dt><span class="sect2"><a href="ch04s04.html#db-upgrade-files.dbupgrade-tool">4.4.4. Hilfsscript dbupgrade2_tool.pl</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch04s05.html">4.5. Translations and languages</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch04s05.html#translations-languages.introduction">4.5.1. Introduction</a></span></dt><dt><span class="sect2"><a href="ch04s05.html#translations-languages.character-set">4.5.2. Character set</a></span></dt><dt><span class="sect2"><a href="ch04s05.html#translations-languages.file-structure">4.5.3. File structure</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch04s06.html">4.6. Die kivitendo-Test-Suite</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch04s06.html#devel.testsuite.intro">4.6.1. Einführung</a></span></dt><dt><span class="sect2"><a href="ch04s06.html#devel.testsuite.prerequisites">4.6.2. Voraussetzungen</a></span></dt><dt><span class="sect2"><a href="ch04s06.html#devel.testsuite.execution">4.6.3. Existierende Tests ausführen</a></span></dt><dt><span class="sect2"><a href="ch04s06.html#devel.testsuite.meaning_of_scripts">4.6.4. Bedeutung der verschiedenen Test-Scripte</a></span></dt><dt><span class="sect2"><a href="ch04s06.html#devel.testsuite.create_new">4.6.5. Neue Test-Scripte erstellen</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch04s07.html">4.7. Stil-Richtlinien</a></span></dt><dt><span class="sect1"><a href="ch04s08.html">4.8. Dokumentation erstellen</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch04s08.html#devel.build-doc.introduction">4.8.1. Einführung</a></span></dt><dt><span class="sect2"><a href="ch04s08.html#devel.build-doc.required-software">4.8.2. Benötigte Software</a></span></dt><dt><span class="sect2"><a href="ch04s08.html#devel.build-doc.build">4.8.3. PDFs und HTML-Seiten erstellen</a></span></dt><dt><span class="sect2"><a href="ch04s08.html#devel.build-doc.repository">4.8.4. Einchecken in das Git-Repository</a></span></dt></dl></dd></dl></dd></dl></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"> </td><td width="20%" align="center"> </td><td width="40%" align="right"> <a accesskey="n" href="ch01.html">Weiter</a></td></tr><tr><td width="40%" align="left" valign="top"> </td><td width="20%" align="center"> </td><td width="40%" align="right" valign="top"> Kapitel 1. Aktuelle Hinweise</td></tr></table></div></body></html>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
+"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<!-- Created with Inkscape (http://www.inkscape.org/) --><svg height="60.0000000" id="svg1" inkscape:version="0.38.1" sodipodi:docbase="/home/danny/flat/scalable/actions" sodipodi:docname="rotate_cw.svg" sodipodi:version="0.32" version="1.0" width="60.0000000" x="0" xmlns="http://www.w3.org/2000/svg" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:xlink="http://www.w3.org/1999/xlink" y="0">
+ <metadata>
+ <rdf:RDF xmlns:cc="http://web.resource.org/cc/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+ <cc:Work rdf:about="">
+ <dc:title>Part of the Flat Icon Collection (Wed Aug 25 23:29:46 2004)</dc:title>
+ <dc:description></dc:description>
+ <dc:subject>
+ <rdf:Bag>
+ <rdf:li>hash</rdf:li>
+ <rdf:li></rdf:li>
+ <rdf:li>action</rdf:li>
+ <rdf:li>computer</rdf:li>
+ <rdf:li>icons</rdf:li>
+ <rdf:li>theme</rdf:li>
+ </rdf:Bag>
+ </dc:subject>
+ <dc:publisher>
+ <cc:Agent rdf:about="http://www.openclipart.org">
+ <dc:title>Danny Allen</dc:title>
+ </cc:Agent>
+ </dc:publisher>
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Danny Allen</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:rights>
+ <cc:Agent>
+ <dc:title>Danny Allen</dc:title>
+ </cc:Agent>
+ </dc:rights>
+ <dc:date></dc:date>
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+ <cc:license rdf:resource="http://web.resource.org/cc/PublicDomain"/>
+ <dc:language>en</dc:language>
+ </cc:Work>
+ <cc:License rdf:about="http://web.resource.org/cc/PublicDomain">
+ <cc:permits rdf:resource="http://web.resource.org/cc/Reproduction"/>
+ <cc:permits rdf:resource="http://web.resource.org/cc/Distribution"/>
+ <cc:permits rdf:resource="http://web.resource.org/cc/DerivativeWorks"/>
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview bordercolor="#666666" borderopacity="1.0" id="base" inkscape:cx="33.984503" inkscape:cy="18.129014" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:window-height="685" inkscape:window-width="1016" inkscape:window-x="0" inkscape:window-y="0" inkscape:zoom="6.9465337" pagecolor="#ffffff" showguides="true" snaptoguides="true"/>
+ <defs id="defs3"/>
+ <path d="M 7.9315240,4.7740988 C 37.959278,18.463222 30.893925,41.646412 30.893925,41.867205 L 20.958272,41.867205 L 36.965712,55.225139 L 50.434041,41.756809 L 40.387993,41.756809 C 40.387993,41.756809 47.894929,9.6315299 7.9315240,4.7740988 z " id="path968" sodipodi:nodetypes="ccccccc" sodipodi:stroke-cmyk="(0 0 0 0.8)" style="font-size:12.000000;fill:#7f7f7f;fill-rule:evenodd;stroke:#333333;stroke-width:3.1249931;stroke-linecap:round;stroke-linejoin:round;" transform="translate(1.079675,0.000000)"/>
+</svg>
// ## other stuff ##
else if (action[0] == 'redirect_to') window.location.href = action[1];
+ else if (action[0] == 'save_file') kivi.save_file(action[1], action[2], action[3], action[4]);
else if (action[0] == 'flash') kivi.display_flash(action[1], action[2]);
else if (action[0] == 'flash_detail') kivi.display_flash_detail(action[1], action[2]);
else if (action[0] == 'clear_flash') kivi.clear_flash(action[1], action[2]);
}
};
- k.removeTooltip = function($e) {
+ k.removeTooltip = function(e) {
+ var $e = $(e);
if ($e.hasClass('tooltipstered'))
$e.tooltipster('destroy');
$e.prop('title', '');
};
- k.setTooltip = function($e, tooltip) {
+ k.setTooltip = function(e, tooltip) {
+ var $e = $(e);
if ($e.hasClass('tooltipstered'))
$e.tooltipster('content', tooltip);
else
$e.tooltipster({ content: tooltip, theme: 'tooltipster-light' });
};
- k.setDisabled = function($e, tooltip) {
- var data = $e.data('action');
+ k.setDisabled = function(e, tooltip) {
+ var $e = $(e);
$e.addClass(CLASSES.disabled);
kivi.ActionBar.removeTooltip($e);
};
- k.setEnabled = function($e) {
+ k.setEnabled = function(e) {
+ var $e = $(e);
var data = $e.data('action');
$e.removeClass(CLASSES.disabled);
this.selectContact = function(params) {
var contactId = $('#contact_cp_id').val();
- var url = 'controller.pl?action=CustomerVendor/ajaj_get_contact&id='+ $('#cv_id').val() +'&db='+ $('#db').val() +'&contact_id='+ contactId;
+ var url = 'controller.pl?action=CustomerVendor/ajaj_get_contact&id='+ $('#cv_id').val() +'&db='+ $('#db').val() +'&contact_id='+ contactId;
$.getJSON(url, function(data) {
var contact = data.contact;
kivi.CustomerVendor.setCustomVariablesFromAJAJ(data.contact_cvars, 'contact_cvars_');
- if ( contactId )
+ if ( contactId ) {
$('#action_delete_contact').show();
- else
+ $('#contact_cp_title_select').val(contact['cp_title']);
+ $('#contact_cp_abteilung_select').val(contact['cp_abteilung']);
+ } else {
$('#action_delete_contact').hide();
+ $('#contact_cp_title_select, #contact_cp_abteilung_select').val('');
+ }
if (data.contact.disable_cp_main === 1)
$("#contact_cp_main").prop("disabled", true);
else
params.onFormSet();
});
- $('#contact_cp_title_select, #contact_cp_abteilung_select').val('');
};
var mapSearchStmts = [
ns.reinit_widgets();
}
+ ns.get_price_report = function(target, source, data) {
+ $.ajax({
+ url: source,
+ success: function (rsp) {
+ $(target).html(rsp);
+ $(target).find('a.report-generator-header-link').click(function(event){ ns.price_report_redirect_event(event, target) });
+ },
+ });
+ };
+
+ ns.price_report_redirect_event = function (event, target) {
+ event.preventDefault();
+ ns.get_price_report(target, event.target + '');
+ };
+
+ ns.price_list_init = function () {
+ $("#customer_vendor_tabs").on('tabsbeforeactivate', function(event, ui){
+ if (ui.newPanel.attr('id') == 'price_list') {
+ ns.get_price_report('#price_list', "controller.pl?action=CustomerVendor/ajax_list_prices&id=" + $('#cv_id').val() + "&db=" + $('#db').val() + "&callback=" + $('#callback').val());
+ }
+ return 1;
+ });
+
+ $("#customer_vendor_tabs").on('tabscreate', function(event, ui){
+ if (ui.panel.attr('id') == 'price_list') {
+ ns.get_price_report('#price_list', "controller.pl?action=CustomerVendor/ajax_list_prices&id=" + $('#cv_id').val() + "&db=" + $('#db').val() + "&callback=" + $('#callback').val());
+ }
+ return 1;
+ });
+ }
+
$(function(){
ns.init();
+ ns.price_list_init();
});
});
$.ajax({
url: 'gl.pl?action=get_tax_dropdown',
- data: { accno_id: $(obj).val(),
- transdate: $('#transdate').val() },
+ data: { accno_id: $(obj).val(),
+ transdate: $('#transdate').val(),
+ deliverydate: $('#deliverydate').val() },
dataType: 'html',
success: function (new_html) {
$("#taxchart_" + row).html(new_html);
$('#print_options').dialog('close');
$('#printer_id').val($('#print_options_printer_id').val());
+ $('#bothsided').val($('#print_options_bothsided').prop('checked') ? 1 : 0);
$('#action').val('MassInvoiceCreatePrint/print');
$('#report_form').submit();
}
if (pos.length > 0) {
- question = question || kivi.t8("Do you really want to save?");
+ question = question || kivi.t8("Do you really want to continue?");
return confirm(kivi.t8("There are duplicate parts at positions") + "\n"
+ pos.join(', ') + "\n"
+ question);
if (warn_on_reqdate && !ns.check_valid_reqdate()) return;
var data = $('#order_form').serializeArray();
+ data.push({ name: 'order.language_id', value: $('#language_id').val() }); // language from print options
data.push({ name: 'action', value: 'Order/' + action });
$.post("controller.pl", data, kivi.eval_json_result);
var data = $('#order_form').serializeArray();
data = data.concat($('#print_options_form').serializeArray());
+ data.push({ name: 'order.language_id', value: $('#language_id').val() }); // language from print options
data.push({ name: 'action', value: 'Order/print' });
$.post("controller.pl", data, kivi.eval_json_result);
};
- ns.download_pdf = function(pdf_filename, key) {
- var data = [{ name: 'action', value: 'Order/download_pdf' },
- { name: 'type', value: $('#type').val() },
- { name: 'pdf_filename', value: pdf_filename },
- { name: 'key', value: key }];
- $.download("controller.pl", data);
- };
-
- ns.email = function(warn_on_duplicates) {
- if (warn_on_duplicates && !ns.check_duplicate_parts(kivi.t8("Do you really want to send by mail?"))) return;
- if (!ns.check_cv()) return;
-
- var data = $('#order_form').serializeArray();
- data.push({ name: 'action', value: 'Order/show_email_dialog' });
-
- $.post("controller.pl", data, kivi.eval_json_result);
- };
-
var email_dialog;
ns.setup_send_email_dialog = function() {
var data = $('#order_form').serializeArray();
data = data.concat($('[name^="email_form."]').serializeArray());
data = data.concat($('[name^="print_options."]').serializeArray());
+ data.push({ name: 'order.language_id', value: $('#language_id').val() }); // language from print options
data.push({ name: 'action', value: 'Order/send_email' });
$.post("controller.pl", data, kivi.eval_json_result);
};
$('#nr_in_title').html($(elt).val());
};
- ns.reload_cv_dependant_selections = function() {
+ ns.reload_cv_dependent_selections = function() {
+ $('#order_shipto_id').val('');
var data = $('#order_form').serializeArray();
data.push({ name: 'action', value: 'Order/customer_vendor_changed' });
$(event.target).val(kivi.format_amount(kivi.parse_amount($(event.target).val()), -2));
};
+ ns.reformat_number_as_null_number = function(event) {
+ if ($(event.target).val() === '') {
+ return;
+ }
+ ns.reformat_number(event);
+ };
+
+ ns.update_exchangerate = function(event) {
+ if (!ns.check_cv()) {
+ $('#order_currency_id').val($('#old_currency_id').val());
+ return;
+ }
+
+ var rate_input = $('#order_exchangerate_as_null_number');
+ // unset exchangerate if currency changed
+ if ($('#order_currency_id').val() !== $('#old_currency_id').val()) {
+ rate_input.val('');
+ }
+
+ // only set exchangerate if unset
+ if (rate_input.val() !== '') {
+ return;
+ }
+
+ var data = $('#order_form').serializeArray();
+ data.push({ name: 'action', value: 'Order/update_exchangerate' });
+
+ $.ajax({
+ url: 'controller.pl',
+ data: data,
+ method: 'POST',
+ dataType: 'json',
+ success: function(data){
+ if (!data.is_standard) {
+ $('#currency_name').text(data.currency_name);
+ if (data.exchangerate) {
+ rate_input.val(data.exchangerate);
+ } else {
+ rate_input.val('');
+ }
+ $('#exchangerate_settings').show();
+ } else {
+ rate_input.val('');
+ $('#exchangerate_settings').hide();
+ }
+ if ($('#order_currency_id').val() != $('#old_currency_id').val() ||
+ !data.is_standard && data.exchangerate != $('#old_exchangerate').val()) {
+ kivi.display_flash('warning', kivi.t8('You have changed the currency or exchange rate. Please check prices.'));
+ }
+ $('#old_currency_id').val($('#order_currency_id').val());
+ $('#old_exchangerate').val(data.exchangerate);
+ }
+ });
+ };
+
+ ns.exchangerate_changed = function(event) {
+ if (kivi.parse_amount($('#order_exchangerate_as_null_number').val()) != kivi.parse_amount($('#old_exchangerate').val())) {
+ kivi.display_flash('warning', kivi.t8('You have changed the currency or exchange rate. Please check prices.'));
+ $('#old_exchangerate').val($('#order_exchangerate_as_null_number').val());
+ }
+ };
+
ns.recalc_amounts_and_taxes = function() {
var data = $('#order_form').serializeArray();
data.push({ name: 'action', value: 'Order/recalc_amounts_and_taxes' });
});
};
+ ns.redisplay_cvpartnumbers = function(data) {
+ $('.row_entry').each(function(idx, elt) {
+ $(elt).find('[name="cvpartnumber"]').html(data[idx][0]);
+ });
+ };
+
ns.renumber_positions = function() {
$('.row_entry [name="position"]').each(function(idx, elt) {
$(elt).html(idx+1);
});
+ $('.row_entry').each(function(idx, elt) {
+ $(elt).data("position", idx+1);
+ });
};
ns.reorder_items = function(order_by) {
ns.renumber_positions();
};
+ ns.get_insert_before_item_id = function(wanted_pos) {
+ if (wanted_pos === '') return;
+
+ var insert_before_item_id;
+ // selection by data does not seem to work if data is changed at runtime
+ // var elt = $('.row_entry [data-position="' + wanted_pos + '"]');
+ $('.row_entry').each(function(idx, elt) {
+ if ($(elt).data("position") == wanted_pos) {
+ insert_before_item_id = $(elt).find('[name="orderitem_ids[+]"]').val();
+ return false;
+ }
+ });
+
+ return insert_before_item_id;
+ };
+
ns.add_item = function() {
if ($('#add_item_parts_id').val() === '') return;
if (!ns.check_cv()) return;
$('#row_table_id thead a img').remove();
+ var insert_before_item_id = ns.get_insert_before_item_id($('#add_item_position').val());
+
var data = $('#order_form').serializeArray();
- data.push({ name: 'action', value: 'Order/add_item' });
+ data.push({ name: 'action', value: 'Order/add_item' },
+ { name: 'insert_before_item_id', value: insert_before_item_id });
$.post("controller.pl", data, kivi.eval_json_result);
};
ns.multi_items_dialog_disable_continue = function() {
// disable keydown-event and continue button to prevent
// impatient users to add parts multiple times
- $('#multi_items_result input').off("keydown");
+ $('#multi_items_result input, #multi_items_position').off("keydown");
$('#multi_items_dialog_continue_button').prop('disabled', true);
};
ns.multi_items_dialog_enable_continue = function() {
- $('#multi_items_result input').keydown(function(event) {
+ $('#multi_items_result input, #multi_items_position').keydown(function(event) {
if(event.keyCode == 13) {
event.preventDefault();
ns.add_multi_items();
ns.multi_items_dialog_disable_continue();
+ var insert_before_item_id = ns.get_insert_before_item_id($('#multi_items_position').val());
+
var data = $('#order_form').serializeArray();
data = data.concat($('#multi_items_form').serializeArray());
- data.push({ name: 'action', value: 'Order/add_multi_items' });
+ data.push({ name: 'action', value: 'Order/add_multi_items' },
+ { name: 'insert_before_item_id', value: insert_before_item_id });
$.post("controller.pl", data, kivi.eval_json_result);
};
return true;
};
+ ns.update_row_from_master_data = function(clicked) {
+ var row = $(clicked).parents("tbody").first();
+ var item_id_dom = $(row).find('[name="orderitem_ids[+]"]');
+
+ var data = $('#order_form').serializeArray();
+ data.push({ name: 'action', value: 'Order/update_row_from_master_data' });
+ data.push({ name: 'item_ids[]', value: item_id_dom.val() });
+
+ $.post("controller.pl", data, kivi.eval_json_result);
+ };
+
+ ns.update_all_rows_from_master_data = function() {
+ var item_ids = $.map($('.row_entry'), function(elt) {
+ var item_id = $(elt).find('[name="orderitem_ids[+]"]').val();
+ return { name: 'item_ids[]', value: item_id };
+ });
+
+ if (item_ids.length == 0) {
+ return;
+ }
+
+ var data = $('#order_form').serializeArray();
+ data.push({ name: 'action', value: 'Order/update_row_from_master_data' });
+ data = data.concat(item_ids);
+
+ $.post("controller.pl", data, kivi.eval_json_result);
+ };
+
ns.show_calculate_qty_dialog = function(clicked) {
var row = $(clicked).parents("tbody").first();
var input_id = $(row).find('[name="order.orderitems[].qty_as_number"]').attr('id');
return true;
};
+ ns.edit_custom_shipto = function() {
+ if (!ns.check_cv()) return;
+
+ kivi.SalesPurchase.edit_custom_shipto();
+ };
+
+ ns.purchase_order_check_for_direct_delivery = function() {
+ if ($('#type').val() != 'sales_order') {
+ kivi.submit_form_with_action($('#order_form'), 'Order/purchase_order');
+ }
+
+ var empty = true;
+ var shipto;
+ if ($('#order_shipto_id').val() !== '') {
+ empty = false;
+ shipto = $('#order_shipto_id option:selected').text();
+ } else {
+ $('#shipto_inputs [id^="shipto"]').each(function(idx, elt) {
+ if (!empty) return true;
+ if (/^shipto_to_copy/.test($(elt).prop('id'))) return true;
+ if (/^shiptocp_gender/.test($(elt).prop('id'))) return true;
+ if (/^shiptocvar_/.test($(elt).prop('id'))) return true;
+ if ($(elt).val() !== '') {
+ empty = false;
+ return false;
+ }
+ });
+ var shipto_elements = [];
+ $([$('#shiptoname').val(), $('#shiptostreet').val(), $('#shiptozipcode').val(), $('#shiptocity').val()]).each(function(idx, elt) {
+ if (elt !== '') shipto_elements.push(elt);
+ });
+ shipto = shipto_elements.join('; ');
+ }
+
+ var use_it = false;
+ if (!empty) {
+ ns.direct_delivery_dialog(shipto);
+ } else {
+ kivi.submit_form_with_action($('#order_form'), 'Order/purchase_order');
+ }
+ };
+
+ ns.direct_delivery_callback = function(accepted) {
+ $('#direct-delivery-dialog').dialog('close');
+
+ if (accepted) {
+ $('<input type="hidden" name="use_shipto">').appendTo('#order_form').val('1');
+ }
+
+ kivi.submit_form_with_action($('#order_form'), 'Order/purchase_order');
+ };
+
+ ns.direct_delivery_dialog = function(shipto) {
+ $('#direct-delivery-dialog').remove();
+
+ var text1 = kivi.t8('You have entered or selected the following shipping address for this customer:');
+ var text2 = kivi.t8('Do you want to carry this shipping address over to the new purchase order so that the vendor can deliver the goods directly to your customer?');
+ var html = '<div id="direct-delivery-dialog"><p>' + text1 + '</p><p>' + shipto + '</p><p>' + text2 + '</p>';
+ html = html + '<hr><p>';
+ html = html + '<input type="button" value="' + kivi.t8('Yes') + '" size="30" onclick="kivi.Order.direct_delivery_callback(true)">';
+ html = html + ' ';
+ html = html + '<input type="button" value="' + kivi.t8('No') + '" size="30" onclick="kivi.Order.direct_delivery_callback(false)">';
+ html = html + '</p></div>';
+ $(html).hide().appendTo('#order_form');
+
+ kivi.popup_dialog({id: 'direct-delivery-dialog',
+ dialog: {title: kivi.t8('Carry over shipping address'),
+ height: 300,
+ width: 500 }});
+ };
+
});
$(function() {
if ($('#type').val() == 'sales_order' || $('#type').val() == 'sales_quotation' ) {
- $('#order_customer_id').change(kivi.Order.reload_cv_dependant_selections);
+ $('#order_customer_id').change(kivi.Order.reload_cv_dependent_selections);
} else {
- $('#order_vendor_id').change(kivi.Order.reload_cv_dependant_selections);
+ $('#order_vendor_id').change(kivi.Order.reload_cv_dependent_selections);
}
+ $('#order_currency_id').change(kivi.Order.update_exchangerate);
+ $('#order_transdate_as_date').change(kivi.Order.update_exchangerate);
+ $('#order_exchangerate_as_null_number').change(kivi.Order.exchangerate_changed);
+
if ($('#type').val() == 'sales_order' || $('#type').val() == 'sales_quotation' ) {
$('#add_item_parts_id').on('set_item:PartPicker', function(e,o) { $('#add_item_sellprice_as_number').val(kivi.format_amount(o.sellprice, -2)) });
} else {
return false;
});
+ $('.reformat_number_as_null_number').change(kivi.Order.reformat_number_as_null_number);
+
});
},
ajax_data: function(term) {
var data = {
- 'filter.all:substr:multi::ilike': term,
current: this.$real.val(),
};
if (this.o.convertible_unit)
data['filter.unit_obj.convertible_to'] = this.o.convertible_unit;
+ var filter_name = 'all';
+ if (this.o.with_makemodel) {
+ filter_name = 'all_with_makemodel';
+ }
+ if (this.o.with_customer_partnumber) {
+ filter_name = 'all_with_customer_partnumber';
+ }
+ data['filter.' + filter_name + ':substr:multi::ilike'] = term;
+
return data;
},
set_item: function(item) {
var self = this;
var data = $('#multi_items_form').serializeArray();
data.push({ name: 'type', value: self.pp.type });
+ var ppdata = self.pp.ajax_data(function(){
+ var val = $('#multi_items_filter').val();
+ return val === undefined ? '' : val
+ });
+ $.each(Object.keys(ppdata), function() {data.push({ name: 'multi_items.' + this, value: ppdata[this]});});
$.ajax({
url: 'controller.pl?action=Part/multi_items_update_result',
data: data,
$('#shiptoname').focus();
};
- this.submit_custom_shipto = function() {
- $('#shipto_id').val('');
+ this.submit_custom_shipto = function(id_selector) {
+ id_selector = id_selector || '#shipto_id';
+ $(id_selector).val('');
$('#shipto_dialog').data('confirmed', true);
$('#shipto_dialog').dialog('close');
};
var vc = $('#vc').val();
var data = {
- action: 'show_sales_purchase_email_dialog',
- cp_id: $('#cp_id').val(),
- donumber: $('#donumber').val(),
- format: $('#format').val(),
- formname: $('#formname').val(),
- id: $('#id').val(),
- invnumber: $('#invnumber').val(),
- language_id: $('#language_id').val(),
- media: 'email',
- ordnumber: $('#ordnumber').val(),
- rowcount: $('#rowcount').val(),
- quonumber: $('#quonumber').val(),
- type: $('#type').val(),
- vc: vc,
- vc_id: $('#' + vc + '_id').val(),
+ action: 'show_sales_purchase_email_dialog',
+ cp_id: $('#cp_id').val(),
+ direct_debit: $('#direct_debit').prop('checked') ? 1 : 0,
+ donumber: $('#donumber').val(),
+ format: $('#format').val(),
+ formname: $('#formname').val(),
+ id: $('#id').val(),
+ invnumber: $('#invnumber').val(),
+ language_id: $('#language_id').val(),
+ media: 'email',
+ ordnumber: $('#ordnumber').val(),
+ cusordnumber: $('#cusordnumber').val(),
+ rowcount: $('#rowcount').val(),
+ quonumber: $('#quonumber').val(),
+ type: $('#type').val(),
+ vc: vc,
+ vc_id: $('#' + vc + '_id').val(),
};
$('[name^=id_],[name^=partnumber_]').each(function(idx, elt) {
return undefined;
};
+ ns.save_file = function(base64_data, content_type, size, attachment_name) {
+ // atob returns a unicode string with one codepoint per octet. revert this
+ const b64toBlob = (b64Data, contentType='', sliceSize=512) => {
+ const byteCharacters = atob(b64Data);
+ const byteArrays = [];
+
+ for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
+ const slice = byteCharacters.slice(offset, offset + sliceSize);
+
+ const byteNumbers = new Array(slice.length);
+ for (let i = 0; i < slice.length; i++) {
+ byteNumbers[i] = slice.charCodeAt(i);
+ }
+
+ const byteArray = new Uint8Array(byteNumbers);
+ byteArrays.push(byteArray);
+ }
+
+ const blob = new Blob(byteArrays, {type: contentType});
+ return blob;
+ }
+
+ var blob = b64toBlob(base64_data, content_type);
+ var a = $("<a style='display: none;'/>");
+ var url = window.URL.createObjectURL(blob);
+ a.attr("href", url);
+ a.attr("download", attachment_name);
+ $("body").append(a);
+ a[0].click();
+ window.URL.revokeObjectURL(url);
+ a.remove();
+ }
+
ns.detect_duplicate_ids_in_dom = function() {
var ids = {},
found = false;
"Assign invoice":"Rechnung zuweisen",
"Basic settings actions":"Aktionen zu Grundeinstellungen",
"Cancel":"Abbrechen",
+"Carry over shipping address":"Lieferadresse übernehmen",
"Chart picker":"Kontenauswahl",
"Copy":"Kopieren",
"Copy requirement spec":"Pflichtenheft kopieren",
"Delete requirement spec":"Pflichtenheft löschen",
"Delete template":"Vorlage löschen",
"Delete text block":"Textblock löschen",
-"Do you really want do continue?":"Möchten Sie wirklich fortfahren?",
"Do you really want to cancel?":"Möchten Sie wirklich abbrechen?",
+"Do you really want to continue?":"Möchten Sie wirklich fortfahren?",
"Do you really want to delete the selected documents?":"Möchten Sie wirklich diese Dateien löschen?",
"Do you really want to delete this draft?":"Möchten Sie diesen Entwurf wirklich löschen?",
"Do you really want to delete this record template?":"Möchten Sie diese Belegvorlage wirklich löschen?",
"Do you really want to print?":"Wollen Sie wirklich drucken?",
"Do you really want to revert to this version?":"Möchten Sie wirklich auf diese Version zurücksetzen?",
-"Do you really want to save?":"Möchten Sie wirklich speichern?",
-"Do you really want to send by mail?":"Wollen Sie den Beleg wirklich per Mail verschicken?",
"Do you really want to unimport the selected documents?":"Möchten Sie wirklich diese Dateien an die Quelle zurückgeben?",
+"Do you want to carry this shipping address over to the new purchase order so that the vendor can deliver the goods directly to your customer?":"Möchten Sie diese Lieferadresse in den neuen Lieferantenauftrag übernehmen, damit der Händler die Waren direkt an Ihren Kunden liefern kann?",
"Do you want to set the account number \"#1\" to \"#2\" and the name \"#3\" to \"#4\"?":"Soll die Kontonummer \"#1\" zu \"#2\" und den Name \"#3\" zu \"#4\" geändert werden?",
"Download picture":"Bild herunterladen",
"Due Date missing!":"Fälligkeitsdatum fehlt!",
"Wrong number format (#1)":"Falsches Zahlenformat (#1)",
"Wrong time format (#1)":"Falsches Zeitformat (#1)",
"Yes":"Ja",
+"You have changed the currency or exchange rate. Please check prices.":"Die Währung oder der Wechselkurs hat sich geändert. Bitte überprüfen Sie die Preise.",
+"You have entered or selected the following shipping address for this customer:":"Sie haben die folgende Lieferadresse eingegeben oder ausgewählt:",
"filename has not uploadable characters ":"Bitte Dateinamen ändern. Er hat für den Upload nicht verwendbare Sonderzeichen ",
"filesize too big: ":"Datei zu groß: ",
"flat-rate position":"Pauschalposition",
"Assign invoice":"",
"Basic settings actions":"",
"Cancel":"",
+"Carry over shipping address":"",
"Chart picker":"",
"Copy":"",
"Copy requirement spec":"",
"Delete requirement spec":"",
"Delete template":"",
"Delete text block":"",
-"Do you really want do continue?":"",
"Do you really want to cancel?":"",
+"Do you really want to continue?":"",
"Do you really want to delete the selected documents?":"",
"Do you really want to delete this draft?":"",
"Do you really want to delete this record template?":"",
"Do you really want to print?":"",
"Do you really want to revert to this version?":"",
-"Do you really want to save?":"",
-"Do you really want to send by mail?":"",
"Do you really want to unimport the selected documents?":"",
+"Do you want to carry this shipping address over to the new purchase order so that the vendor can deliver the goods directly to your customer?":"",
"Do you want to set the account number \"#1\" to \"#2\" and the name \"#3\" to \"#4\"?":"",
"Download picture":"",
"Due Date missing!":"",
"Wrong number format (#1)":"",
"Wrong time format (#1)":"",
"Yes":"",
+"You have changed the currency or exchange rate. Please check prices.":"",
+"You have entered or selected the following shipping address for this customer:":"",
"filename has not uploadable characters ":"",
"filesize too big: ":"",
"flat-rate position":"",
$('#rs-dialog-confirm').remove();
var text1 = kivi.t8('Re-numbering all sections and function blocks in the order they are currently shown cannot be undone.');
- var text2 = kivi.t8('Do you really want do continue?');
+ var text2 = kivi.t8('Do you really want to continue?');
var $dialog = $('<div id="rs-dialog-confirm"><p>' + text1 + '</p><p>' + text2 + '</p></div>').hide().appendTo('body');
var buttons = {};
' bytes, max=' => ' Bytes, Maximum=',
' missing!' => ' fehlt!',
'#1 (custom variable)' => '#1 (benutzerdefinierte Variable)',
- '#1 CB transactions and #1 OB transactions generated.' => '#1 Schluss- und #1 Eröffnungsbuchungen wurden erstellt.',
'#1 MD' => '#1 PT',
'#1 additional part(s)' => '#1 zusätzliche(r) Artikel',
'#1 bank transaction bookings undone.' => '#1 Bankbewegung(en) rückgängig gemacht',
'AP transaction posted.' => 'Kreditorenbuchung verbucht.',
'AP transactions changeable' => 'Änderbarkeit von Kreditorenbuchungen',
'AP transactions with sales taxkeys and/or AR transactions with input taxkeys' => 'Kreditorenbuchungen mit Umsatzsteuer-Steuerschlüsseln und/oder Debitorenbuchungen mit Vorsteuer-Steuerschlüsseln',
- 'AP/AR Aging & Journal' => 'Offene Forderungen/Verbindunglichkeiten & Buchungsjournal',
+ 'AP/AR Aging & Journal' => 'Offene Forderungen/Verbindlichkeiten & Buchungsjournal',
'AR' => 'Verkauf',
'AR Aging' => 'Offene Forderungen',
'AR Transaction' => 'Debitorenbuchung',
'Account deleted!' => 'Konto gelöscht!',
'Account for fees' => 'Konto für Gebühren',
'Account for interest' => 'Konto für Zinsen',
+ 'Account for workflow from purchase order to ap transaction' => 'Konto für den Workflow von Lieferantenauftrag nach Kreditorenbuchung',
'Account number' => 'Kontonummer',
'Account number not unique!' => 'Kontonummer bereits vorhanden!',
'Account number of the goal/source' => 'Ziel- oder Quellkonto',
'Add Assortment' => 'Sortiment erfassen',
'Add Client' => 'Neuer Mandant',
'Add Credit Note' => 'Gutschrift erfassen',
+ 'Add Credit Note for this dunning level:' => 'Diese Gutschrift für untenstehende Mahnstufe anzeigen',
'Add Customer' => 'Kunde erfassen',
'Add Customer/Vendor Number as a reference add-on for SEPA export.' => 'Kunden- Lieferantennummer im Verwendungszweck bei SEPA-Überweisungen anhängen',
'Add Delivery Note' => 'Lieferschein erfassen',
'Add department' => 'Abteilung hinzufügen',
'Add empty line (csv_import)' => 'Leere Zeile einfügen',
'Add function block' => 'Funktionsblock hinzufügen',
+ 'Add greeting' => 'Anrede hinzufügen',
'Add headers from last uploaded file (csv_import)' => 'Spalten aus der hochgeladenen Datei einfügen',
'Add invoices' => 'Rechnungen hinzufügen',
'Add language' => 'Sprache hinzufügen',
'Add new price rule item' => 'Neue Bedingung hinzufügen',
'Add new record template' => 'Neue Belegvorlage hinzufügen',
'Add note' => 'Notiz erfassen',
+ 'Add open Credit Notes' => 'Offene Gutschriften hinzufügen',
'Add part' => 'Artikel hinzufügen',
'Add part classification' => 'Artikel-Klassifizierung hinzufügen',
'Add partsgroup' => 'Warengruppe hinzufügen',
'Add sub function block' => 'Unterfunktionsblock hinzufügen',
'Add taxzone' => 'Steuerzone hinzufügen',
'Add text block' => 'Textblock erfassen',
+ 'Add title' => 'Titel hinzufügen',
'Add unit' => 'Einheit hinzufügen',
'Added sections and function blocks: #1' => 'Hinzugefügte Abschnitte und Funktionsblöcke: #1',
'Added text blocks: #1' => 'Hinzugefügte Textblöcke: #1',
'Administration' => 'Administration',
'Administration area' => 'Administration',
'Advance turnover tax return' => 'Umsatzsteuervoranmeldung',
+ 'Advance turnover tax return only valid for SKR03 or SKR04' => 'UstVA nur für Standardkontenrahmen SKR03 oder SKR04 möglich.',
'After closed period' => 'Ab geschlossenem Zeitraum',
'Aktion' => 'Aktion',
'All' => 'Alle',
'Allow to delete generated printfiles' => 'Löschen von erzeugten Dokumenten erlaubt',
'Already counted' => 'Bereits erfasst',
'Always edit assembly items (user can change/delete items even if assemblies are already produced)' => 'Erzeugnisbestandteile verändern (Löschen/Umsortieren) auch nachdem dieses Erzeugnis schon produziert wurde.',
- 'Always save orders with a projectnumber (create new projects)' => 'Aufträge immer mit Projektnummer speichern (neue Projekt erstellen)',
+ 'Always save orders with a projectnumber (create new projects)' => 'Aufträge immer mit Projektnummer speichern (neue Projekte erstellen)',
'Amended Advance Turnover Tax Return' => 'Berichtigte Anmeldung',
'Amount' => 'Betrag',
'Amount (for verification)' => 'Betrag (zur Überprüfung)',
'Amount payable' => 'Noch zu bezahlender Betrag',
'Amount payable less discount' => 'Noch zu bezahlender Betrag abzüglich Skonto',
'Amounts differ too much' => 'Beträge weichen zu sehr voneinander ab.',
- 'An error occured. Letter could not be deleted.' => 'Es ist ein Fehler aufgetreten. Der Brief konnte nicht gelöscht werden.',
'An error occurred while transferring the file.' => 'Bei Übertragung der Datei trat ein Fehler auf',
+ 'An error occurred. Letter could not be deleted.' => 'Es ist ein Fehler aufgetreten. Der Brief konnte nicht gelöscht werden.',
'An exception occurred during execution.' => 'Während der Ausführung trat eine Ausnahme auf.',
'An invalid character was used (invalid characters: #1).' => 'Ein ungültiges Zeichen wurde benutzt (ungültige Zeichen: #1).',
'An invalid character was used (valid characters: #1).' => 'Ein ungültiges Zeichen wurde benutzt (gültige Zeichen: #1).',
'Annotations' => 'Anmerkungen',
'Any stock contents containing a best before date will be impossible to stock out otherwise.' => 'Sonst können Artikel, bei denen ein Mindesthaltbarkeitsdatum gesetzt ist, nicht mehr ausgelagert werden.',
'Ap aging on %s' => 'Offene Verbindlichkeiten an %s',
- 'Application Error. No Format given' => 'Fehler in der Anwendung. Das Ausgabeformat fehlt.',
'Application Error. Wrong Format' => 'Fehler in der Anwendung. Falsches Format: ',
'Apply' => 'Anwenden',
'Apply customer' => 'Kunde hinzufügen',
'Apply to transfers without bin' => 'Bei allen Lagerbewegungen ohne Lagerplatz setzen',
'Apply to transfers without comment' => 'Bei allen Lagerbewegungen ohne Kommentar setzen',
'Apply to transfers without warehouse' => 'Bei allen Lagerbewegungen ohne Lager setzen',
+ 'Apply year-end bookings' => 'Jahresabschlußbuchungen durchführen',
'Applying #1:' => 'Führe #1 aus:',
'Approximately #1 prices will be updated.' => 'Ungefähr #1 Preise werden aktualisiert.',
'Apr' => 'Apr',
'April' => 'April',
'Ar aging on %s' => 'Offene Forderungen zum %s',
- 'Are you sure to generate cb/ob transactions?' => 'Sollen die EB/SB Buchungen wirklich erzeugt werden?',
+ 'Are you sure to update all positions from master data?' => 'Alle Positionen aus den Stammdaten aktualisieren?',
+ 'Are you sure to update this position from master data?' => 'Diese Position aus den Stammdaten aktualisieren?',
'Are you sure you want to delete Invoice Number' => 'Soll die Rechnung mit folgender Nummer wirklich gelöscht werden:',
'Are you sure you want to delete this letter?' => 'Sind Sie sicher, dass Sie diesen Brief löschen wollen?',
'Are you sure you want to remove the marked entries from the queue?' => 'Sind Sie sicher, dass die markierten Einträge von der Warteschlange gelöscht werden sollen?',
'At least one Perl module that kivitendo ERP requires for running is not installed on your system.' => 'Mindestes ein Perl-Modul, das kivitendo ERP zur Ausführung benötigt, ist auf Ihrem System nicht installiert.',
'At least one of the columns #1, customer, customernumber, customer_gln, vendor, vendornumber, vendor_gln (depending on the target table) is required for matching the entry to an existing customer or vendor.' => 'Mindestens eine der Spalten #1, customer, customernumber, customer_gln, vendor, vendornumber, vendor_gln (von Zieltabelle abhängig) wird benötigt, um einen Eintrag einem bestehenden Kunden bzw. Lieferanten zuzuordnen.',
'At most' => 'Höchstens',
+ 'At position' => 'An Position',
'At the moment the transaction looks like this:' => 'Aktuell sieht die Buchung wie folgt aus:',
'Attach PDF:' => 'PDF anhängen',
'Attached Filename' => 'Name des Dateianhangs',
'Attachment name' => 'Name des Anhangs',
'Attachments' => 'Dateianhänge',
'Attempt to call an undefined sub named \'%s\'' => 'Es wurde versucht, eine nicht definierte Unterfunktion namens \'%s\' aufzurufen.',
- 'Attention: Here will be generated a lot of CB/OB transactions.' => 'Hiermit werden Buchungen für den Schlussbestand (SB-Buchung) und den Eröffnungsbestand (EB-Buchung) für mehrere Konten gleichzeitig gebucht.<br>In diesem Schritt wird das Datum der Buchungen sowie das Saldovortragskonto festgelegt.<br>Das Datum der SB-Buchung wird außerdem verwendet um das Saldo der Konten zu ermitteln, welche im nächsten Schritt (nach "Weiter") angezeigt werden.',
'Audit Control' => 'Bücherkontrolle',
'Aug' => 'Aug',
'August' => 'August',
'Background jobs and task server' => 'Hintergrund-Jobs und Task-Server',
'Balance' => 'Bilanz',
'Balance Sheet' => 'Bilanz',
+ 'Balance accounts' => 'Bestandskonten',
'Balance sheet date' => 'Bilanzstichtag',
'Balance startdate method' => 'Methode zur Ermittlung des Startdatums für Bilanz',
+ 'Balance with CB' => 'Saldo mit SB',
'Balances' => 'Salden',
'Balancing' => 'Bilanzierung',
'Bank' => 'Bank',
'Booking group (database ID)' => 'Buchungsgruppe (database ID)',
'Booking group (name)' => 'Buchungsgruppe (name)',
'Booking groups' => 'Buchungsgruppen',
+ 'Booking needs at least one debit and one credit booking!' => 'Die Buchung benötigt mindestens eine Buchung im Soll eine im Haben!',
'Bookinggroup/Tax' => 'Buchungsgruppe/Steuer',
'Books are open' => 'Die Bücher sind geöffnet.',
'Books closed up to' => 'Bücher abgeschlossen bis zum',
'CANCELED' => 'Storniert',
'CB Transaction' => 'SB-Buchung',
'CB Transactions' => 'SB-Buchungen',
- 'CB date #1 is higher than OB date #2. Please select again.' => 'SB-Datum #1 ist größer als EB-Datum #2. Bitte sinnvollere Werte auswählen.',
- 'CB/OB Transactions' => 'SB/EB buchen',
'CN' => 'Kd-Nr.',
'CR' => 'H',
'CSS style for pictures' => 'CSS Style für Bilder',
'CSV import: bank transactions' => 'CSV Import: Bankbewegungen',
'CSV import: contacts' => 'CSV-Import: Ansprechpersonen',
'CSV import: customers and vendors' => 'CSV-Import: Kunden und Lieferanten',
+ 'CSV import: delivery orders' => 'CSV-Import: Lieferscheine',
'CSV import: inventories' => 'CSV-Import: Lagerbewegungen/-bestände',
'CSV import: orders' => 'CSV-Import: Aufträge',
'CSV import: parts and services' => 'CSV-Import: Waren und Dienstleistungen',
'Cancel Accounts Payables Transaction' => 'Kreditorenbuchung stornieren',
'Cancel Accounts Receivables Transaction' => 'Debitorenbuchung stornieren',
'Cancelling is disallowed. Either undo or balance the current payments until the open amount matches the invoice amount' => 'Storno verboten, da Zahlungen zum Beleg vorhanden sind. Entweder die Zahlungen löschen oder mit umgekehrten Vorzeichen ausbuchen, sodass der offene Betrag dem Rechnungsbetrag entspricht.',
+ 'Cannot change transaction in a closed period!' => 'In einem bereits abgeschlossenen Zeitraum kann keine Buchung verändert werden!',
'Cannot check correct WebDAV folder' => 'Kann nicht den richtigen WebDAV Pfad überprüfen',
'Cannot delete account!' => 'Konto kann nicht gelöscht werden!',
'Cannot delete customer!' => 'Kunde kann nicht gelöscht werden!',
'Cannot stock without amount' => 'Kann nicht ohne Menge einlagern!',
'Cannot storno invoice for a closed period!' => 'Das Rechnungsdatum der zu stornierenden Rechnung fällt in einen abgeschlossenen Zeitraum!',
'Cannot storno storno invoice!' => 'Kann eine Stornorechnung nicht stornieren',
+ 'Cannot transfer #1 qty with #2 serial number(s)' => 'Kann nicht die Menge von #1 mit #2 Seriennummer auslagern.',
'Cannot transfer negative entries.' => 'Kann keine negativen Mengen auslagern.',
'Cannot transfer negative quantities.' => 'Negative Mengen können nicht ausgelagert werden.',
'Cannot transfer. <br> Reason:<br>#1' => 'Kann nicht ein-/auslagern. <br>Grund:<br>#1',
'Cannot unlink payment for a closed period!' => 'Ein oder alle Bankbewegungen befinden sich innerhalb einer geschloßenen Periode. ',
+ 'Carry over account for year-end closing' => 'Saldenvortragskonto',
'Carry over shipping address' => 'Lieferadresse übernehmen',
'Cash' => 'Zahlungsverkehr',
'Cash accounting' => 'Ist-Versteuerung',
'Close Window' => 'Fenster Schließen',
'Close window' => 'Fenster schließen',
'Closed' => 'Geschlossen',
+ 'Closing Balance' => 'Abschlußsaldo',
'Collective Orders only work for orders from one customer!' => 'Sammelaufträge funktionieren nur für Aufträge von einem Kunden!',
'Column name' => 'Spaltenname',
'Comma' => 'Komma',
'Company' => 'Firma',
'Company Name' => 'Firmenname',
'Company name' => 'Firmenname',
+ 'Company name and address' => 'Firmenname und -adresse',
'Company settings' => 'Firmeneinstellungen',
'Compare to' => 'Gegenüberstellen zu',
'Complexities' => 'Komplexitätsgrade',
'Confirm!' => 'Bestätigen Sie!',
'Confirmation' => 'Auftragsbestätigung',
'Contact' => 'Kontakt',
+ 'Contact Departments' => 'Abteilungen von Ansprechpersonen',
'Contact Person' => 'Ansprechperson',
'Contact Person (database ID)' => 'Ansprechperson (Datenbank-ID)',
'Contact Person (name)' => 'Ansprechperson (Name)',
+ 'Contact Titles' => 'Titel von Ansprechpersonen',
'Contact deleted.' => 'Ansprechperson gelöscht.',
'Contact is in use and was flagged invalid.' => 'Die Ansprechperson ist noch in Verwendung und wurde deshalb nur als ungültig markiert.',
'Contact person (surname)' => 'Ansprechperson (Nachname)',
'Contrary to Reduced Master Data this will be shown as discount in records.' => 'Im Gegensatz zu Abschlag wird der Rabatt in Belegen ausgewiesen',
'Conversion of "birthday" contact person attribute' => 'Umstellung des Kontaktpersonenfeldes "Geburtstag"',
'Conversion to PDF failed: #1' => 'Konvertierung zu PDF schlug fehl: #1',
- 'Conversion:' => 'Konversiom',
+ 'Conversion:' => 'Konversion',
'Converting to deliveryorder' => 'Konvertiere zu Lieferschein',
'Copies' => 'Kopien',
'Copy' => 'Kopieren',
'Correct taxkey' => 'Richtiger Steuerschlüssel',
'Cost Center' => 'Kostenstelle',
'Costs' => 'Kosten',
+ 'Could not create new project #1' => 'Neues Projekt #1 kann nicht angelegt werden',
+ 'Could not extract ZUGFeRD data, data and error message:' => 'Konnte keine ZUGFeRD Daten extrahieren, folgende Fehlermeldung und das PDF:',
'Could not find an entry for this part in the pricegroup.' => 'Konnte keine Eintrag für diesen Artikel in der Preisgruppe finden.',
'Could not load class #1 (#2): "#3"' => 'Konnte Klasse #1 (#2) nicht laden: "#3"',
'Could not load class #1, #2' => 'Konnte Klasse #1 nicht laden: "#2"',
'Create Date' => 'Erstelldatum',
'Create HTML' => 'HTML erzeugen',
'Create PDF' => 'PDF erzeugen',
+ 'Create ZUGFeRD invoices' => 'ZUGFeRD-Rechnungen erzeugen',
+ 'Create ZUGFeRD invoices in test mode' => 'ZUGFeRD-Rechnungen im Testmodus erzeugen',
'Create a new background job' => 'Einen neuen Hintergrund-Job anlegen',
'Create a new client' => 'Einen neuen Mandanten anlegen',
'Create a new delivery term' => 'Neue Lieferbedingungen anlegen',
'Create and print all invoices' => 'Alle Rechnungen erzeugen und ausdrucken',
'Create and print invoices' => 'Rechnungen erzeugen und ausdrucken',
'Create and print invoices for all delivery orders matching the filter' => 'Rechnungen für alle den Suchkriterien entsprechenden Lieferscheine erzeugen und ausdrucken',
- 'Create and print invoices for all selected delivery orders' => 'Rechnungen für alle Lieferscheine erzeugen und ausdrucken',
+ 'Create and print invoices for all selected delivery orders' => 'Rechnungen für alle markierten Lieferscheine erzeugen und ausdrucken',
'Create and send a new printout for this record' => 'Neuen Belegausdruck erstellen und verschicken',
'Create bank collection' => 'Bankeinzug erstellen',
'Create bank collection via SEPA XML' => 'Bankeinzug via SEPA XML erstellen',
'Create new version' => 'Neue Version anlegen',
'Create one from the context menu by right-clicking on this text.' => 'Erstellen Sie einen aus dem Kontextmenü, indem Sie auf diesen Text rechtsklicken.',
'Create order' => 'Auftrag erstellen',
+ 'Create sales invoices with ZUGFeRD data' => 'Verkaufsrechnungen mit ZUGFeRD-Daten erzeugen',
'Create tables' => 'Tabellen anlegen',
'Created by' => 'Erstellt von',
'Created for' => 'Erstellt für',
'DATEV Export' => 'DATEV-Export',
'DATEV check returned errors:' => 'Die DATEV Prüfung dieser Buchung ergab Fehler:',
'DATEV configuration' => 'Einstellungen für DATEV',
- 'DATEV expects the encoding to be Western Europe conform (LATIN-1, cp1252). By setting this to "Strict and halt" the DATEV export halts with a error if there is a single character in "Posting Text" which is not LATIN-1 encodeable. By setting this to "Strict but replace" kivitendo will replace the character with a similar one and the export will simply warn about those fields. By setting this to relaxed (UTF-8) the DATEV export encoding will be in kivitendo (UTF-8) encoded and the external import program has to handle this (this may work for DATEV deriviates or future versions of DATEV). Background details: For example turkish characters (Ç) are not valid cp1252 charactes and armenian characters like "Գեղարդ" are probably not replaceable in cp1252' => 'DATEV erwartet westeuropäische Zeichenkodierung (LATIN-1, cp1252). Die Einstellung "Strikt und Abbruch" erlaubt keine nicht kodiebaren Zeichen im DATEV-Export und bricht diesen mit einer Fehlermeldung ab. Die Einstellung "Strikt mit Ersetzungen" versucht ähnliche Zeichen (bspw. c statt ć) zu verwenden und gibt zusätzlich eine Warnung beim DATEV-Expport aus. Die Einstellung "Lax (UTF-8)" ignoriert diese Anforderung und übergibt die Daten im kivitendo-konformen UTF-8 Format. Letzteres kann für zukünftige DATEV-Version oder DATEV-kompatible Alternativen interessant sein. Hintergrund-Info: Beispielsweise sind schon türkische Zeichen (Ç) nicht mehr im westeuropäischen Zeichensätze enthalten und armenische Zeiche wie "Գեղարդ" könnnen sicherlich überhaupt nicht mit Zeichen in cp1252 ersetzt werden.',
+ 'DATEV expects the encoding to be Western Europe conform (LATIN-1, cp1252). By setting this to "Strict and halt" the DATEV export halts with a error if there is a single character in "Posting Text" which is not LATIN-1 encodeable. By setting this to "Strict but replace" kivitendo will replace the character with a similar one and the export will simply warn about those fields. By setting this to relaxed (UTF-8) the DATEV export encoding will be in kivitendo (UTF-8) encoded and the external import program has to handle this (this may work for DATEV deriviates or future versions of DATEV). Background details: For example turkish characters (Ç) are not valid cp1252 charactes and armenian characters like "Գեղարդ" are probably not replaceable in cp1252' => 'DATEV erwartet eine westeuropäische Zeichenkodierung (LATIN-1, cp1252). Die Einstellung "Strikt und Abbruch" erlaubt keine nicht kodierbaren Zeichen im DATEV-Export und bricht diesen mit einer Fehlermeldung ab. Die Einstellung "Strikt mit Ersetzungen" versucht ähnliche Zeichen (bspw. c statt ć) zu verwenden und gibt zusätzlich eine Warnung beim DATEV-Export aus. Die Einstellung "Lax (UTF-8)" ignoriert diese Anforderung und übergibt die Daten im kivitendo-konformen UTF-8 Format. Letzteres kann für zukünftige DATEV-Version oder DATEV-kompatible Alternativen interessant sein. Hintergrund-Info: Beispielsweise sind schon türkische Zeichen (Ç) nicht mehr im westeuropäischen Zeichensätz enthalten und armenische Zeiche wie "Գեղարդ" könnnen sicherlich überhaupt nicht mit Zeichen in cp1252 ersetzt werden.',
'DATEX - Export Assistent' => 'DATEV-Exportassistent',
'DELETED' => 'Gelöscht',
'DFV-Kennzeichen' => 'DFV-Kennzeichen',
'Default template format' => 'Standardvorlagenformat',
'Default transfer delivery order' => 'Standard-Auslagern über Lieferschein',
'Default transfer invoice' => 'Standard-Auslagern über Rechnung',
+ 'Default transfer invoice with charge number' => 'Standard-Auslagern über Rechnung mit Chargennummer',
'Default transport article number' => 'Standard Versand / Transport-Erinnerungs-Artikel',
'Default unit' => 'Standardeinheit',
'Default value' => 'Standardwert',
'Delivery terms' => 'Lieferbedingungen',
'Delivery terms (database ID)' => 'Lieferbedingungen (Datenbank-ID)',
'Delivery terms (name)' => 'Lieferbedingungen (Name)',
+ 'DeliveryOrder' => 'Lieferschein',
'Denmark' => 'Dänemark',
'Department' => 'Abteilung',
'Department (database ID)' => 'Abeilung (Datenbank-ID)',
'Displayable Name Preferences' => 'Einstellungen für Anzeigenamen',
'Do not change the tax rate of taxkey 0.' => 'Ändern Sie nicht den Steuersatz vom Steuerschlüssel 0.',
'Do not check for duplicates' => 'Nicht nach Dubletten suchen',
+ 'Do not create ZUGFeRD invoices' => 'Keine ZUGFeRD-Rechnungen erzeugen',
'Do not link to a project.' => 'Nicht mit einem Projekt verknüpfen.',
'Do not modify this position' => 'Diese Position nicht verändern',
'Do not run the task server for this client' => 'Task-Server nicht für diesen Mandanten ausführen',
'Do not set this bin' => 'Diesen Lagerplatz nicht setzen',
'Do not set this comment' => 'Diesen Kommentar nicht setzen',
'Do not set this warehouse' => 'Dieses Lager nicht setzen',
- 'Do you really want do continue?' => 'Möchten Sie wirklich fortfahren?',
'Do you really want to cancel this general ledger transaction?' => 'Möchten Sie diese Dialogbuchung wirklich stornieren?',
'Do you really want to cancel this invoice?' => 'Möchten Sie diese Rechnung wirklich stornieren?',
'Do you really want to cancel?' => 'Möchten Sie wirklich abbrechen?',
'Do you really want to close the selected SEPA exports? No payment will be recorded for bank collections that haven\'t been marked as executed yet.' => 'Möchten Sie wirklich die ausgewählten SEPA-Exporte abschließen? Für Überweisungen, die noch nicht gebucht wurden, werden dann keine Zahlungen verbucht.',
'Do you really want to close the selected SEPA exports? No payment will be recorded for bank transfers that haven\'t been marked as executed yet.' => 'Möchten Sie wirklich die ausgewählten SEPA-Exporte abschließen? Für Überweisungen, die noch nicht gebucht wurden, werden dann keine Zahlungen verbucht.',
+ 'Do you really want to continue?' => 'Möchten Sie wirklich fortfahren?',
'Do you really want to delete AP transaction #1?' => 'Möchten Sie wirklich die Kreditorenbuchung #1 löschen?',
'Do you really want to delete AR transaction #1?' => 'Möchten Sie wirklich die Debitorenbuchung #1 löschen?',
'Do you really want to delete GL transaction #1?' => 'Möchten Sie wirklich die Dialogbuchung #1 löschen?',
'Do you really want to delete this record template?' => 'Möchten Sie diese Belegvorlage wirklich löschen?',
'Do you really want to print?' => 'Wollen Sie wirklich drucken?',
'Do you really want to revert to this version?' => 'Möchten Sie wirklich auf diese Version zurücksetzen?',
- 'Do you really want to save?' => 'Möchten Sie wirklich speichern?',
- 'Do you really want to send by mail?' => 'Wollen Sie den Beleg wirklich per Mail verschicken?',
'Do you really want to undo the selected SEPA exports? You have to reassign the export again.' => 'Möchten Sie wirklich die ausgewählten SEPA-Exports rückgängig machen? Der Export muss anschließend neu erzeugt werden.',
'Do you really want to unimport the selected documents?' => 'Möchten Sie wirklich diese Dateien an die Quelle zurückgeben?',
'Do you want to <b>limit</b> your search?' => 'Möchten Sie Ihre Suche <b>spezialisieren</b>?',
'Edit Vendor' => 'Lieferant editieren',
'Edit Vendor Invoice' => 'Einkaufsrechnung bearbeiten',
'Edit Warehouse' => 'Lager bearbeiten',
+ 'Edit ZUGFeRD notes' => 'ZUGFeRD-Notizen bearbeiten',
'Edit acceptance status' => 'Abnahmestatus bearbeiten',
'Edit additional articles' => 'Zusätzliche Artikel bearbeiten',
'Edit all drafts' => 'Entwürfe von allen Benutzern bearbeiten',
'Edit department' => 'Abteilung bearbeiten',
'Edit file' => 'Datei bearbeiten',
'Edit general settings' => 'Grundeinstellungen bearbeiten',
+ 'Edit greeting' => 'Anrede bearbeiten',
'Edit greetings' => 'Anreden bearbeiten',
'Edit language' => 'Sprache bearbeiten',
'Edit note' => 'Notiz bearbeiten',
'Edit the request_quotation' => 'Bearbeiten der Preisanfrage',
'Edit the sales_order' => 'Bearbeiten des Auftrags',
'Edit the sales_quotation' => 'Bearbeiten des Angebots',
+ 'Edit title' => 'Titiel bearbeiten',
'Edit units' => 'Einheiten bearbeiten',
'Edit user signature' => 'Benutzersignatur bearbeiten',
'Editable' => 'Bearbeitbar',
'Equity' => 'Passiva',
'Erfolgsrechnung' => 'Erfolgsrechnung',
'Error' => 'Fehler',
+ 'Error handling' => 'Fehlerbehandlung',
'Error in database control file \'%s\': %s' => 'Fehler in Datenbankupgradekontrolldatei \'%s\': %s',
'Error in position #1: You must either assign no stock at all or the full quantity of #2 #3.' => 'Fehler in Position #1: Sie müssen einer Position entweder gar keinen Lagereingang oder die vollständige im Lieferschein vermerkte Menge von #2 #3 zuweisen.',
'Error in position #1: You must either assign no transfer at all or the full quantity of #2 #3.' => 'Fehler in Position #1: Sie müssen einer Position entweder gar keinen Lagerausgang oder die vollständige im Lieferschein vermerkte Menge von #2 #3 zuweisen.',
'Error message from the database: #1' => 'Fehlermeldung der Datenbank: #1',
'Error message from the webshop api:' => 'Fehlermeldung der Webshop Api',
'Error when saving: #1' => 'Fehler beim Speichern: #1',
+ 'Error while applying year-end bookings!' => 'Fehler beim Durchführen der Abschlußbuchungen!',
+ 'Error while creating project with project number of new order number, project number #1 already exists!' => 'Fehler beim Erstellen eines Projekts mit der Projektnummer der neuen Auftragsnummer, Projektnummer #1 existiert bereits!',
'Error with default taxzone' => 'Ungültige Standardsteuerzone',
'Error!' => 'Fehler!',
'Error: #1' => 'Fehler: #1',
'Error: Customer/vendor is ambiguous' => 'Kunde/Lieferant ist mehrdeutig',
'Error: Customer/vendor missing' => 'Fehler: Kunde/Lieferant fehlt',
'Error: Customer/vendor not found' => 'Fehler: Kunde/Lieferant nicht gefunden',
+ 'Error: Faulty position in this delivery order' => 'Fehler: Fehlerhafte Artikel-Position in diesem Lieferschein',
'Error: Found local bank account number but local bank code doesn\'t match' => 'Fehler: Kontonummer wurde gefunden aber gespeicherte Bankleitzahl stimmt nicht überein',
'Error: Gender (cp_gender) missing or invalid' => 'Fehler: Geschlecht (cp_gender) fehlt oder ungültig',
'Error: Invalid bin' => 'Fehler: Ungültiger Lagerplatz',
'Error: Invalid warehouse' => 'Fehler: Ungültiges Lager',
'Error: Invalid warehouse id' => 'Ungültige Lager-ID',
'Error: Invalid warehouse name #1' => 'Ungültiger Lagername \'#1\'',
+ 'Error: More than one source order found' => 'Fehler: mehr als ein Quell-Auftrag gefunden',
'Error: Name missing' => 'Fehler: Name fehlt',
+ 'Error: Not enough parts in stock' => 'Fehler: Nicht genügend Artikel eingelagert',
'Error: Part is ambiguous' => 'Artikel ist mehrdeutig',
'Error: Part is obsolete' => 'Fehler: Artikel ist ungültig',
'Error: Part not found' => 'Fehler: Artikel nicht gefunden',
'Error: Quantity to transfer is zero.' => 'Fehler: Zu bewegende Menge ist Null.',
+ 'Error: Source order not found' => 'Fehler: Quell-Auftrag nicht gefunden',
+ 'Error: Stock problem' => 'Fehler: Problem bei der Lagerbewegung',
+ 'Error: Stocking out would result in stock underrun' => 'Auslagern würde zu einem negativen Lagerbestand führen',
+ 'Error: Stocking out would result in stock underrun: #1' => 'Auslagern würde zu einem negativen Lagerbestand führen: #1',
'Error: Transfer would result in a negative target quantity.' => 'Fehler: Lagerbewegung würde zu einer negativen Zielmenge führen.',
'Error: Unit missing or invalid' => 'Fehler: Einheit fehlt oder ungültig',
'Error: Warehouse not found' => 'Fehler: Lager nicht gefunden',
'Error: this feature requires that articles with a time-based unit (e.g. \'h\' or \'min\') exist.' => 'Fehler: dieses Feature setzt voraus, dass Artikel mit einer Zeit-basierenden Einheit (z.B. "Std") existieren.',
'Error: unknown local bank account' => 'Fehler: unbekannte Kontnummer',
'Error: unknown local bank account id' => 'Fehler: unbekannte Bankkonto-ID',
+ 'Errors' => 'Fehler',
'Errors during conversion:' => 'Umwandlungsfehler:',
'Errors during printing:' => 'Druckfehler:',
+ 'Errors in GL transaction:' => 'Fehler in Dialogbuchung:',
'Ertrag' => 'Ertrag',
'Ertrag prozentual' => 'Ertrag prozentual',
'Escape character' => 'Escape-Zeichen',
'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',
- 'Generic Tax Report' => 'USTVA Bericht',
'Germany' => 'Deutschland',
'Get shoporders' => 'Shopbestellungen holen und bearbeiten',
'Git revision: #1, #2 #3' => 'Git-Revision: #1, #2 #3',
'If disabled purchase invoices can only be created by conversion from existing requests for quotations, purchase orders and purchase delivery orders.' => 'Falls deaktiviert, so können Einkaufsrechnungen nur durch Umwandlung aus bestehenden Preisanfragen, Lieferantenaufträgen und Einkaufslieferscheinen angelegt werden.',
'If disabled sales orders cannot be converted into sales invoices directly.' => 'Falls deaktiviert, so können Verkaufsaufträge nicht direkt in Verkaufsrechnungen umgewandelt werden.',
'If disabled sales quotations cannot be converted into sales invoices directly.' => 'Falls deaktiviert, so können Verkaufsangebote nicht direkt in Verkaufsrechnungen umgewandelt werden.',
+ 'If enabled ZUGFeRD-conformant sales invoice PDFs will be created.' => 'Falls aktiviert, werden ZUGFeRD-konforme PDFs für Verkaufsrechnungen erzeugt.',
'If enabled a column will be shown in sales and purchase orders that lists both the amount and the value not shipped yet for each item.' => 'Falls eingeschaltet, wird für jede Position in Auftragsbestätigungen und Lieferantenaufträgen eine Spalte mit noch nicht gelieferter Menge und Wert angezeigt.',
'If enabled a warning will be shown in sales and purchase orders if there are two or more positions of the same part (new controller only).' => 'Falls eingeschaltet, wird eine Warnung angezeigt, wenn der Auftrag mehrere gleiche Artikel enthält (nur neuer Controller).',
'If enabled a warning will be shown in sales and purchase orders if there the delivery date is empty.' => 'Falls aktiviert, Warnungen ausgeben sobald Aufträge (Einkauf- und Verkauf) keinen Liefertermin haben.',
'If item not found, allow creation of new item' => 'Falls Artikel nicht gefunden, erlaube Erfassen eines Neuen',
'If left empty the default sender from the kivitendo configuration will be used (key \'email_from\' in section \'periodic_invoices\'; current value: #1).' => 'Falls leer, so wird der Standardabsender aus der kivitendo-Konfiguration genutzt (Schlüssel »email_from« in Abschnitt »periodic_invoices«; aktueller Wert: #1).',
'If missing then the start date will be used.' => 'Falls es fehlt, so wird die erste Rechnung für das Startdatum erzeugt.',
+ 'If one or more space separated serial numbers are assigned in a sales invoice, match the charge number of the inventory item. Assumes that Serial Number and Charge Number have 1:1 relation. Otherwise throw a error message for the default sales invoice transfer.' => 'Falls eine oder mehrere Leerzeichen separierte Seriennummern in Verkaufsrechnungen definiert sind, nutz diese als Chargennummern fürs Standard-Auslagern über Rechnung. Seriennummern und eingelagerte Chargen kommen jeweils exakt nur einmal vor. Falls die Chargennummer oder das Mengenverhältnis (1:1) in keinem Lagerort existiert wird eine Fehlermeldung beim Auslagern generiert.',
'If searching a part from a document and no part is found then offer to create a new part.' => 'Wenn bei der Artikelsuche aus einem Dokument heraus kein Artikel gefunden wird, dann wird ermöglicht, von dort aus einen neuen Artikel anzulegen.',
'If the article type is set to \'mixed\' then a column called \'part_type\' or called \'pclass\' must be present.' => 'Falls der Artikeltyp auf \'mixed\' gesetzt ist muss entweder eine Spalte \'part_type\' oder \'pclass\' im Import vorhanden sein',
'If the automatic creation of invoices for fees and interest is switched on for a dunning level then the following accounts will be used for the invoice.' => 'Wenn das automatische Erstellen einer Rechnung über Mahngebühren und Zinsen für ein Mahnlevel aktiviert ist, so werden die folgenden Konten für die Rechnung benutzt.',
- 'If the counted quantity differs more than this threshold from the quantity in the database, a warning will be shown. Set to 0 to switch of this feature.' => 'Wenn die gezählte Menge mehr als dieser Schwellenwert von der Menge in der Datenbank abweicht, wird eine Warnmeldung angezeigt. Setzen Sie den Schwellenwert auf 0, um dieses Feature abzuschalten.',
+ 'If the counted quantity differs more than this threshold from the quantity in the database, a warning will be shown. Set to 0 to switch of this feature.' => 'Wenn die gezählte Menge mehr als diesen Schwellenwert von der Menge in der Datenbank abweicht, wird eine Warnmeldung angezeigt. Setzen Sie den Schwellenwert auf 0, um dieses Feature abzuschalten.',
'If the database user listed above does not have the right to create a database then enter the name and password of the superuser below:' => 'Falls der oben genannte Datenbankbenutzer nicht die Berechtigung zum Anlegen neuer Datenbanken hat, so können Sie hier den Namen und das Passwort des Datenbankadministratoraccounts angeben:',
'If the default transfer out always succeed use this bin for negative stock quantity.' => 'Standardlagerplatz für Auslagern ohne Prüfung auf Bestand',
+ 'If the test mode is enabled, the ZUGFeRD invoices will be flagged so that they\'re only fit to be used for testing purposes.' => 'Wenn der Testmodus aktiviert ist, werden ZUGFeRD-Rechnungen so markiert, dass sie nur für Testzwecke dienen dürfen.',
'If yes, delivery order positions are considered "delivered" only if they have been stocked out of the inventory. Otherwise saving the delivery order is considered delivered.' => 'Wenn diese Option aktiviert ist, gelten Lieferscheinpositionen nur dann als geliefert wenn sie im Lieferschein ausgelagert wurden, und die Ware aus dem Lager ausgebucht wurde. Andernfalls gilt das Speichern des Lieferscheins als Lieferung.',
'If you enter values for the part number and / or part description then only those bins containing parts whose part number or part description match your input will be shown.' => 'Wenn Sie für die Artikelnummer und / oder die Beschreibung etwas eingeben, so werden nur die Lagerplätze angezeigt, in denen Waren eingelagert sind, die Ihre Suchbegriffe enthalten.',
'If you have not chosen for example the category revenue for a tax and you choose an revenue account to create a transfer in the general ledger, this tax will not be displayed in the tax dropdown.' => 'Wenn Sie z.B. die Kategory Erlös für eine Steuer nicht gewählt haben und ein Erlöskonto beim Erstellen einer Dialogbuchung wählen, wird diese Steuer auch nicht im Dropdown-Menü für die Steuern angezeigt.',
'If you want to delete such a dataset you have to edit the client(s) that are using the dataset in question and have them use another dataset.' => 'Wenn Sie eine solche Datenbank löschen möchten, dann müssen Sie zuerst den/die Mandanten auf eine andere Datenbank umstellen, die die zu löschende Datenbank benutzen.',
'If you want to set up the authentication database yourself then log in to the administration panel. kivitendo will then create the database and tables for you.' => 'Wenn Sie die Authentifizierungs-Datenbank selber einrichten wollen, so melden Sie sich im Administrationsbereich an. kivitendo wird dann die Datenbank und die erforderlichen Tabellen für Sie anlegen.',
'If your old bins match exactly Bins in the Warehouse CLICK on <b>AUTOMATICALLY MATCH BINS</b>.' => 'Falls die alte Lagerplatz-Beschreibung in Stammdaten genau mit einem Lagerplatz in einem vorhandenem Lager übereinstimmt, KLICK auf <b>LAGERPLÄTZE AUTOMATISCH ZUWEISEN</b>',
+ 'Ignore faulty positions' => 'Fehlerhafte Artikel-Positionen ignorieren',
'Illegal characters have been removed from the following fields: #1' => 'Ungültige Zeichen wurden aus den folgenden Feldern entfernt: #1',
'Illegal date' => 'Ungültiges Datum',
'Image' => 'Grafik',
'Import CSV' => 'CSV-Import',
'Import Status' => 'Import Status',
'Import a MT940 file:' => 'Laden Sie eine MT940 Datei hoch:',
+ 'Import a ZUGFeRD file:' => 'Eine ZUGFeRD-Datei importieren',
'Import all' => 'Importiere Alle',
'Import documents from #1' => 'Importiere Dateien von Quelle \'#1\'',
'Import file' => 'Import-Datei',
'Include in drop-down menus' => 'In Aufklappmenü aufnehmen',
'Include invalid warehouses ' => 'Ungültige Lager berücksichtigen',
'Include invoices with direct debit' => 'Inklusive Rechnungen mit Lastschrifteinzug',
+ 'Include original Invoices?' => 'Original-Rechnung hinzufügen?',
'Includeable in reports' => 'In Berichten anzeigbar',
'Included in reports by default' => 'In Berichten standardmäßig enthalten',
'Including' => 'Enthaltene',
'Introduction of clients' => 'Einführung von Mandanten',
'Inv. Duedate' => 'Rg. Fälligkeit',
'Invalid' => 'Ungültig',
+ 'Invalid charge number: #1' => 'Ungültige Chargennummer: #1',
'Invalid combination of ledger account number length. Mismatch length of #1 with length of #2. Please check your account settings. ' => 'Ungültige Kombination der Nummernkreislänge der Sachkonten. Kann nicht eine Länge von #1 und eine Länge von #2 verarbeiten. Bitte entsprechend die Konteneinstellungen überprüfen.',
'Invalid duration format' => 'Falsches Format für Zeitdauer',
'Invalid follow-up ID.' => 'Ungültige Wiedervorlage-ID.',
'Invoices with payments cannot be canceled.' => 'Rechnungen mit Zahlungen können nicht storniert werden.',
'Invoices, Credit Notes & AR Transactions' => 'Rechnungen, Gutschriften & Debitorenbuchungen',
'Is Searchable' => 'Durchsuchbar',
+ 'Is sales' => 'Verkauf',
'Is this a summary account to record' => 'Sammelkonto für',
'It can be changed later but must be unique within the installation.' => 'Er ist nachträglich änderbar, muss aber im System eindeutig sein.',
'It is not allowed that a summary account occurs in a drop-down menu!' => 'Ein Sammelkonto darf nicht in Aufklappmenüs aufgenommen werden!',
'Link to the following project:' => 'Mit dem folgenden Projekt verknüpfen:',
'Linked Records' => 'Verknüpfte Belege',
'Linked invoices' => 'Verknüpfte Rechnungen',
- 'Linked positions will always be reconciled first. If this is set to yes, unlinked positions will be reconciled in a second step. This is necessary in very old databases (with open delivery orders from before 3.4.0) and in businesses where delivery orders are frequently amended. Usually the direct links are faster and more accurate. Defaults to true for historical reasons only.' => 'Verlinkte Positionen werden immer zuerst abgeglichen. Wenn diese Option aktiviert ist, werden danach nicht verlinkte Lieferscheinpositionen mit den restlichen nicht vollständig gelieferten Auftragspositionen abgeglichen. Notwendig in alten Datenbanken (mit offenen Lieferscheinen von vor 3.4.0) und in Betrieben in denen Lieferscheine nachträglich ergänzt werden. In allen anderen Fällen ist es schneller und korrekter diese Methode zu deaktivieren. Die Voreinstellung auf "Ja" is aus Kompatibilitätsgründen.',
+ 'Linked positions will always be reconciled first. If this is set to yes, unlinked positions will be reconciled in a second step. This is necessary in very old databases (with open delivery orders from before 3.4.0) and in businesses where delivery orders are frequently amended. Usually the direct links are faster and more accurate. Defaults to true for historical reasons only.' => 'Verlinkte Positionen werden immer zuerst abgeglichen. Wenn diese Option aktiviert ist, werden danach nicht verlinkte Lieferscheinpositionen mit den restlichen nicht vollständig gelieferten Auftragspositionen abgeglichen. Notwendig in alten Datenbanken (mit offenen Lieferscheinen von vor 3.4.0) und in Betrieben in denen Lieferscheine nachträglich ergänzt werden. In allen anderen Fällen ist es schneller und korrekter diese Methode zu deaktivieren. Seit 3.5.6 standardmäßig deaktiviert.',
'Liquidity projection' => 'Liquiditätsübersicht',
'List Accounts' => 'Konten anzeigen',
'List Price' => 'Listenpreis',
'List Printers' => 'Drucker anzeigen',
'List Transactions' => 'Buchungsliste',
'List Users, Clients and User Groups' => 'Benutzer, Mandanten und Benutzergruppen anzeigen',
+ 'List all rows' => 'Alle Reihen anzeigen',
'List current background jobs' => 'Aktuelle Hintergrund-Jobs anzeigen',
'List export' => 'Export anzeigen',
'List of bank collections' => 'Bankeinzugsliste',
'Local account number' => 'Lokale Kontonummer',
'Local bank account' => 'Lokales Bankkonto',
'Local bank code' => 'Lokale Bankleitzahl',
+ 'Lock' => 'Festschreibung',
'Lock System' => 'System sperren',
'Lock and unlock installation' => 'Installation sperren/entsperren',
'Lock bookings' => 'Buchungen festschreiben',
'Long Description (quotations & orders)' => 'Langtext (Angebote & Aufträge)',
'Long Description for invoices' => 'Langtext für Rechnungen',
'Long Description for quotations & orders' => 'Langtext für Angebote & Aufträge',
+ 'Loss' => 'Verlust',
+ 'Loss carried forward account' => 'Verlustvortragskonto',
'Luxembourg' => 'Luxemburg',
'MAILED' => 'Gesendet',
'MD' => 'PT',
'Mass Create Print Sales Invoice from Delivery Order' => 'Massenerstellen und Ausdruck von Rechnungen aus Lieferscheinen',
'Master Data' => 'Stammdaten',
'Master Data Bin Text Deleted' => 'Gelöschte Stammdaten Freitext-Lagerplätze',
+ 'Match Sales Invoice Serial numbers with inventory charge numbers?' => 'Gleiche die Seriennummern aus der VK-Rechnung mit den eingelagerten Chargennummern ab?',
'Matching Price Rules can apply in one of three types:' => 'Preisregeln können Preise in drei Varianten vorschlagen:',
'Max. Dunning Level' => 'höchste Mahnstufe',
'Maximal amount difference' => 'maximale Betragsabweichung',
'Missing Method!' => 'Fehlender Voranmeldungszeitraum',
'Missing Tax Authoritys Preferences' => 'Fehlende Angaben zum Finanzamt!',
'Missing amount' => 'Fehlbetrag',
+ 'Missing configuration section "authentication/#1" in "config/kivitendo.conf".' => 'Fehlender Konfigurationsabschnitt "authentication/#1" in "config/kivitendo.conf".',
'Missing parameter #1 in call to sub #2.' => 'Fehlender Parameter \'#1\' in Funktionsaufruf \'#2\'.',
'Missing parameter (at least one of #1) in call to sub #2.' => 'Fehlernder Parameter (mindestens einer aus \'#1\') in Funktionsaufruf \'#2\'.',
'Missing parameter for WebDAV file copy' => 'Fehlender Parameter für WebDAV Datei kopieren',
'Name does not make sense without any bsooqr options' => 'Option "Name in gewählten Belegen" wird ignoriert.',
'Name in Selected Records' => 'Name in gewählten Belegen',
'Name of the goal/source (if field names remote_name and remote_name_1 exist they will be combined into field "remote_name")' => 'Name des Ziel- oder Quellkontos (wenn die Spalten remote_name und remote_name_1 existieren werden diese zu Feld "remote_name" zusammengefügt)',
+ 'Need charge number!' => 'Benötige Chargennummer!',
'Negative reductions are possible to model price increases.' => 'Negative Abschläge sind möglich um Aufschläge zu modellieren.',
'Neither sections nor function blocks have been created yet.' => 'Es wurden bisher weder Abschnitte noch Funktionsblöcke angelegt.',
'Net Income Statement' => 'Einnahmenüberschußrechnung',
'Next run at' => 'Nächste Ausführung um',
'No' => 'Nein',
'No 1:n or n:1 relation' => 'Keine 1:n oder n:1 Beziehung',
+ 'No AP Record Template for this vendor found, please add one' => 'Konnte keine Kreditorenbuchungsvorlage für diesen Lieferanten finden, bitte legen Sie eine an.',
'No AP template was found.' => 'Keine Kreditorenbuchungsvorlage gefunden.',
'No Company Address given' => 'Keine Firmenadresse hinterlegt!',
'No Company Name given' => 'Kein Firmenname hinterlegt!',
'No Journal' => 'Kein Journal',
'No Shopdescription' => 'Keine Shop-Artikelbeschreibung',
'No Shopimages' => 'Keine Shop-Bilder',
+ 'No VAT Info for this ZUGFeRD invoice, please ask your vendor to add this for his ZUGFeRD data.' => 'Konnte keine UST-ID für diese ZUGFeRD Rechnungen finden, bitte fragen Sie bei Ihren Lieferanten nach, ob dieses Feld im ZUGFeRD Datensatz gesetzt wird.',
'No Vendor was found matching the search parameters.' => 'Zu dem Suchbegriff wurde kein Händler gefunden',
'No action defined.' => 'Keine Aktion definiert.',
'No article has been selected yet.' => 'Es wurde noch kein Artikel ausgewählt.',
'No assembly has been selected yet.' => 'Es wurde noch kein Erzeugnis ausgewahlt.',
'No background job has been created yet.' => 'Es wurden noch keine Hintergrund-Jobs angelegt.',
'No bank account chosen!' => 'Kein Bankkonto ausgewählt!',
+ 'No bank account flagged for ZUGFeRD usage was found.' => 'Es wurde kein Bankkonto gefunden, das für Nutzung mit ZUGFeRD markiert ist.',
'No bank information has been entered in this customer\'s master data entry. You cannot create bank collections unless you enter bank information.' => 'Für diesen Kunden wurden in seinen Stammdaten keine Kontodaten hinterlegt. Solange dies nicht geschehen ist, können Sie keine Überweisungen für den Lieferanten anlegen.',
'No bank information has been entered in this vendor\'s master data entry. You cannot create bank transfers unless you enter bank information.' => 'Für diesen Lieferanten wurden in seinen Stammdaten keine Kontodaten hinterlegt. Solange dies nicht geschehen ist, können Sie keine Überweisungen für den Lieferanten anlegen.',
'No bins have been added to this warehouse yet.' => 'Es wurden zu diesem Lager noch keine Lagerplätze angelegt.',
+ 'No carry-over chart configured!' => 'Kein Saldenvortragskonto konfiguriert!',
'No changes since previous version.' => 'Keine Änderungen seit der letzten Version.',
'No clients have been created yet.' => 'Es wurden noch keine Mandanten angelegt.',
'No contact selected to delete' => 'Keine Ansprechperson zum Löschen ausgewählt',
'No groups have been created yet.' => 'Es wurden noch keine Gruppen angelegt.',
'No internal phone extensions have been configured yet.' => 'Es wurden noch keine internen Durchwahlen konfiguriert.',
'No invoices have been selected.' => 'Es wurden keine Rechnungen ausgewählt.',
- 'No or an unknown authenticantion module specified in "config/kivitendo.conf".' => 'Es wurde kein oder ein unbekanntes Authentifizierungsmodul in "config/kivitendo.conf" angegeben.',
'No part was selected.' => 'Es wurde kein Artikel ausgewählt',
'No payment term has been created yet.' => 'Es wurden noch keine Zahlungsbedingungen angelegt.',
'No picture has been uploaded' => 'Es wurde kein Bild hochgeladen',
'No print templates have been created for this client yet. Please do so in the client configuration.' => 'Für diesen Mandanten wurden noch keine Druckvorlagen angelegt. Bitte holen Sie dies in der Mandantenkonfiguration nach.',
'No printers have been created yet.' => 'Es wurden noch keine Drucker angelegt.',
'No problems were recognized.' => 'Es wurden keine Probleme gefunden.',
+ 'No profit and loss carried forward chart configured!' => 'Kein Verlustvortragskonto konfiguriert!',
+ 'No profit carried forward chart configured!' => 'Kein Gewinnvortragskonto konfiguriert!',
'No quotations or orders have been created yet.' => 'Es wurden noch keine Angebote oder Aufträge angelegt.',
'No report with id #1' => 'Es gibt keinen Report mit der Id #1',
'No requirement spec templates have been created yet.' => 'Es wurden noch keine Pflichtenheftvorlagen angelegt.',
'No results.' => 'Keine Artikel',
- 'No revert available.' => 'Dieser Vorgang kann nicht rückgängig gemacht werden, ggf. falsch erstellte Buchungen müssen einzeln manuell korrigiert werden.',
'No search results found!' => 'Keine Suchergebnisse gefunden!',
'No sections created yet' => 'Keine Abschnitte erstellt',
'No sections have been created so far.' => 'Bisher wurden noch keine Abschnitte angelegt.',
'Not Discountable' => 'Nicht rabattierfähig',
'Not delivered' => 'Nicht geliefert',
'Not done yet' => 'Noch nicht fertig',
+ 'Not enough in stock for the serial number #1' => 'Nicht genug auf Lager von der Seriennummer #1',
'Not obsolete' => 'Gültig',
'Note' => 'Hinweis',
'Note that parameter names must not be quoted.' => 'Beachten Sie, dass Parameternamen nicht in Anführungszeichen stehen dürfen.',
'Number pages' => 'Seiten nummerieren',
'Number variables: \'PRECISION=n\' forces numbers to be shown with exactly n decimal places.' => 'Zahlenvariablen: Mit \'PRECISION=n\' erzwingt man, dass Zahlen mit n Nachkommastellen formatiert werden.',
'OB Transaction' => 'EB-Buchung',
- 'OB Transactions' => 'EB-Buchungen',
'Objects have been imported.' => 'Objekte wurden importiert.',
'Obsolete' => 'Ungültig',
'Oct' => 'Okt',
'On Hand' => 'Auf Lager',
'On Order' => 'Ist bestellt',
'On the next page the type of all variables can be set.' => 'Auf der folgenden Seite können die Typen aller Variablen gesetzt werden.',
- 'One OB-transaction' => 'Eine EB-Buchung',
- 'One SB-transaction' => 'Eine SB-Buchung',
'One of the columns "qty" or "target_qty" must be given. If "target_qty" is given, the quantity to transfer for each transfer will be calculate, so that the quantity for this part, warehouse and bin will result in the given "target_qty" after each transfer.' => 'Eine der Spalten "qty" oder "target_qty" muss angegeben werden. Wird "target_qty" angegeben, so wird die zu bewegende Menge für jede Lagerbewegung so berechnet, dass die Lagermenge für diesen Artikel, Lager und Lagerplatz nach jeder Lagerbewegung der angegebenen Zielmenge entspricht.',
+ 'One of the units used (#1) cannot be mapped to a known unit code from the UN/ECE Recommendation 20 list.' => 'Eine der verwendeten Einheiten (#1) kann keinem der bekannten Einheiten-Codes aus der Liste UN/ECE Recommendation 20 zugeordnet werden.',
'One or more Perl modules missing' => 'Ein oder mehr Perl-Module fehlen',
'Onhand only sets the quantity in master data, not in inventory. This is only a legacy info field and will be overwritten as soon as a inventory transfer happens.' => 'Das Import-Feld Auf Lager setzt nur die Menge in den Stammdaten, nicht im Lagerbereich. Dies ist historisch gewachsen nur ein Informationsfeld was mit dem tatsächlichen Wert überschrieben wird, sobald eine wirkliche Lagerbewegung stattfindet (DB-Trigger).',
+ 'Only Lines with Notes or Errors' => 'Nur Zeilen mit Bemerkungen oder Fehlern',
'Only Price' => 'Nur Preis',
'Only Stock' => 'Nur Bestand',
- 'Only Warnings and Errors' => 'Nur Warnungen und Fehler',
'Only applies if the previous is set to true. When filling up unlinked positions, consider them matches if ALL of these fields match. For example, in a business with variants that are defined by a special description, description needs to be part of the identity. If delivering several similar order positions by delivery date is common, reqdate should be included in the identity. Serialnumber is useful when the serialnumber in the order and delivery order has to match.' => 'Ist nur relevant, wenn die vorherige Option angeschaltet ist. Zugewiesene Zeilen müssen in diesen Feldern identisch sein, und werden ansonsten als unterschiedlich behandelt. Wenn ein Betrieb mit Varianten arbeitet, die in der Beschreibung kodiert sind, muss diese mit abgeglichen werden. Wenn Positionen mit Lieferdaten versehen werden, sollten diese mit abgeglichen werden. Seriennummer abzugleichen funktioniert nur, wenn diese in Auftrag und Lieferschein gepflegt werden.',
'Only booked accounts' => 'Nur bebuchte Konten',
'Only due follow-ups' => 'Nur fällige Wiedervorlagen',
'Order probability & expected billing date' => 'Auftragswahrscheinlichkeit & vorrauss. Abrechnungsdatum',
'Order value periodicity' => 'Auftragswert basiert auf Periodizität',
'Order/Item row name' => 'Name der Auftrag-/Positions-Zeilen',
+ 'Order/Item/Stock row name' => 'Name der Auftrag-/Positions-/Lager-Zeilen',
'Order/RFQ Number' => 'Belegnummer',
'OrderItem' => 'Position',
'Ordered' => 'Von Kunden bestellt',
'Page #1/#2' => 'Seite #1/#2',
'Paid' => 'bezahlt',
'Paid amount' => 'Bezahlter Betrag',
+ 'Parsing the XMP metadata failed.' => 'Parsen der XMP-Metadaten schlug fehl.',
'Part' => 'Ware',
'Part "#1" has chargenumber or best before date set. So it cannot be transfered automatically.' => 'Bei Artikel "#1" ist eine Chargenummer oder ein Mindesthaltbarkeitsdatum vergeben. Deshalb kann dieser Artikel nicht automatisch ausgelagert werden.',
'Part (database ID)' => 'Artikel (Datenbank-ID)',
'Payables' => 'Verbindlichkeiten',
'Payment' => 'Zahlungsausgang',
'Payment / Delivery Options' => 'Zahlungs- und Lieferoptionen',
+ 'Payment Date' => 'Leistungsdatum',
'Payment Reminder' => 'Zahlungserinnerung',
'Payment Terms' => 'Zahlungsbedingungen',
'Payment Terms missing in row ' => 'Zahlungsfrist fehlt in Zeile ',
'Pictures for search parts' => 'Bilder für Warensuche',
'Please Check the bank information for each customer:' => 'Bitte überprüfen Sie die Bankinformationen der Kunden:',
'Please Check the bank information for each vendor:' => 'Bitte überprüfen Sie die Kontoinformationen der Lieferanten:',
+ 'Please add a valid VAT-ID for this vendor: ' => 'Bitte prüfen Sie ob dieser Lieferant eine valide UST-ID (Großschreibungen und Leerzeichen beachten) besitzt:',
'Please ask your administrator to create warehouses and bins.' => 'Bitten Sie Ihren Administrator, dass er Lager und Lagerplätze anlegt.',
'Please change the partnumber of the following parts and run the update again:' => 'Bitte ändern Sie daher die Artikelnummer folgender Artikel:',
'Please choose a part.' => 'Bitte wählen Sie einen Artikel aus.',
'Please choose for which categories the taxes should be displayed (otherwise remove the ticks):' => 'Bitte wählen Sie für welche Kontoart die Steuer angezeigt werden soll (ansonsten einfach die Häkchen entfernen)',
'Please choose the action to be processed for your target quantity:' => 'Bitte wählen Sie eine Aktion, die mit Ihrer gezählten Zielmenge durchgeführt werden soll:',
+ 'Please configure the carry over and profit and loss accounts for year-end closing in the client configuration!' => 'Bitte konfigurieren Sie in der Mandantenkonfiguration das Saldenvortragskonto, das Gewinnvortragskonto und das Verlustvortragskonto!',
'Please contact your administrator or a service provider.' => 'Bitte kontaktieren Sie Ihren Administrator oder einen Dienstleister.',
'Please contact your administrator.' => 'Bitte wenden Sie sich an Ihren Administrator.',
'Please correct the settings and try again or deactivate that client.' => 'Bitte korrigieren Sie die Einstellungen und versuchen Sie es erneut, oder deaktivieren Sie diesen Mandanten.',
'Preset email text for requests (rfq)' => 'Vorbelegter E-Mail-Text für Anfragen',
'Preset email text for sales delivery orders' => 'Vorbelegter E-Mail-Text für Verkaufs-Lieferscheine',
'Preset email text for sales invoices' => 'Vorbelegter E-Mail-Text für Rechnungen',
+ 'Preset email text for sales invoices with direct debit' => 'Vorbelegter E-Mail-Text für Rechnungen mit Bankeinzug',
'Preset email text for sales orders' => 'Vorbelegter E-Mail-Text für Aufträge',
'Preset email text for sales quotations' => 'Vorbelegter E-Mail-Text für Angebote',
'Preview' => 'Vorschau',
'Price #1' => 'Preis #1',
'Price Factor' => 'Preisfaktor',
'Price Factors' => 'Preisfaktoren',
+ 'Price List' => 'Preisliste',
'Price Rule' => 'Preisregel',
'Price Rules' => 'Preisregeln',
'Price Source' => 'Preisquelle',
'Production' => 'Produktion',
'Production (typeabbreviation)' => 'P',
'Productivity' => 'Produktivität',
+ 'Profit' => 'Gewinn',
+ 'Profit and loss accounts' => 'Erfolgskonten',
+ 'Profit carried forward account' => 'Gewinnvortragskonto',
'Profit determination' => 'Gewinnermittlung',
'Proforma Invoice' => 'Proformarechnung',
'Program' => 'Programm',
'SEPA strings' => 'SEPA-Überweisungen',
'SQL query' => 'SQL-Abfrage',
'SWIFT MT940 format' => 'SWIFT-MT940-Format',
- 'Saldo' => 'Saldo',
'Saldo Credit' => 'Saldo Haben',
'Saldo Debit' => 'Saldo Soll',
'Saldo neu' => 'Saldo neu',
'Search AR Aging' => 'Offene Forderungen',
'Search bank transactions' => 'Filter für Bankbuchungen',
'Search contacts' => 'Personensuche',
+ 'Search for Items used in Assemblies' => 'Suche nach in Erzeugnissen verbauten Artikeln',
+ 'Search parts by customer partnumber in sales order forms' => 'Artikel nach Kunden-Art.-Nr. in Verkaufsbelegen suchen',
+ 'Search parts by vendor partnumber (model) in purchase order forms' => 'Artikel nach Lieferanten-Art.-Nr. in Einkaufsbelegen suchen',
'Search term' => 'Suchbegriff',
'Searchable' => 'Durchsuchbar',
'Secondary sorting' => 'Untersortierung',
'Select Mulit-Item Options' => 'Multi-Treffer Auswahlliste',
'Select a Customer' => 'Endkunde auswählen',
'Select a period' => 'Bitte Zeitraum auswählen',
- 'Select charts for which the CB/OB transactions want to be posted.' => 'Wählen Sie Konten aus, zu welchen SB/EB-Buchungen erstellt werden sollen.',
'Select federal state...' => 'Bundesland auswählen...',
'Select file to upload' => 'Datei zum Hochladen auswählen',
'Select from one of the items below' => 'Wählen Sie einen der untenstehenden Einträge',
'Show the picture in the part form' => 'Bild in Warenmaske anzeigen',
'Show the pictures in the result for search parts' => 'Bilder in Suchergebnis für Stammdaten -> Berichte -> Waren anzeigen',
'Show the weights of articles and the total weight in orders, invoices and delivery notes?' => 'Sollen Warengewichte und Gesamtgewicht in Aufträgen, Rechnungen und Lieferscheinen angezeigt werden?',
+ 'Show update button for positions in order forms' => 'Aktualisieren-Knopf bei Positionen in Belegen anzeigen (neuer Auftrags-Controller)',
'Show weights' => 'Gewichte anzeigen',
'Show your TODO list after logging in' => 'Aufgabenliste nach dem Anmelden anzeigen',
'Show »not delivered qty/value« column in sales and purchase orders' => 'Spalte »Nicht gelieferte Menge/Wert« in Aufträgen anzeigen',
'Start of year' => 'Jahresanfang',
'Start process' => 'Prozess starten',
'Start the correction assistant' => 'Korrekturassistenten starten',
+ 'Startdate method' => 'Methode zur Ermittlung des Startdatums',
'Startdate_coa' => 'Gültig ab',
'Starting Balance' => 'Eröffnungsbilanzwerte',
'Starting balance' => 'Anfangssaldo',
'Stock Local/Shop' => 'Bestand Lokal/Online',
'Stock Qty for Date' => 'Lagerbestand am',
'Stock for part #1' => 'Bestand für Artikel #1',
+ 'Stock levels' => 'Lagerbestände',
'Stock value' => 'Bestandswert',
+ 'StockInfo' => 'Lagerinfo',
'Stocked Qty' => 'Lagermenge',
'Stocktaking' => 'Inventur',
'Stocktaking History' => 'Inventur Historie',
'Storno (one letter abbreviation)' => 'S',
'Storno Invoice' => 'Stornorechnung',
'Street' => 'Straße',
+ 'Street 1' => 'Straße 1',
+ 'Street 2' => 'Straße 2',
'Strict and halt' => 'Strikt und Abbruch',
'Strict but replace' => 'Strikt mit Ersetzungen',
'Style the picture with the following CSS code' => 'Bildeigenschaft mit folgendem CSS-Style versehen',
'Subtotals per quarter' => 'Zwischensummen pro Quartal',
'Such entries cannot be exported into the DATEV format and have to be fixed as well.' => 'Solche Einträge sind aber nicht DATEV-exportiertbar und müssen ebenfalls korrigiert werden.',
'Suggested invoice' => 'Rechnungsvorschlag',
- 'Sum CB Transactions' => 'Summe SB',
'Sum Credit' => 'Summe Haben',
'Sum Debit' => 'Summe Soll',
- 'Sum OB Transactions' => 'Summe EB',
'Sum for' => 'Summe für',
'Sum for #1' => 'Summe für #1',
'Sum for section' => 'Summe für Abschnitt',
'Tax Percent is a number between 0 and 100' => 'Prozentsatz muss zwischen
1% und 100% liegen',
'Tax Period' => 'Voranmeldungszeitraum',
- 'Tax Position' => 'Position',
'Tax collected' => 'vereinnahmte Steuer',
'Tax deleted!' => 'Steuer gelöscht!',
'Tax number' => 'Steuernummer',
'Taxkey_coa' => 'Steuerschlüssel',
'Taxkeys and Taxreport Preferences' => 'Steuerautomatik und UStVA',
'Taxlink_coa' => 'Steuerautomatik',
- 'Taxnumber' => 'Steuernummer',
'Taxrate missing!' => 'Prozentsatz fehlt!',
'Taxzones' => 'Steuerzonen',
'Tel' => 'Tel',
'The SQL query can be parameterized with variables named as follows: <%name%>.' => 'Die SQL-Abfrage kann mittels Variablen wie folgt parametrisiert werden: <%Variablenname%>.',
'The SQL query does not contain any parameter that need to be configured.' => 'Die SQL-Abfrage enthält keine Parameter, die angegeben werden müssten.',
'The URL is missing.' => 'URL fehlt',
+ 'The VAT ID number \'#1\' is invalid.' => 'Die UStID-Nummer »#1« ist ungültig.',
+ 'The VAT ID number in the client configuration is invalid.' => 'Die UStID-Nummer in der Mandantenkonfiguraiton ist ungültig.',
+ 'The VAT registration number is missing in the client configuration.' => 'Die Umsatzsteuer-ID-Nummer fehlt in der Mandantenkonfiguration.',
'The WebDAV feature has been used.' => 'Das WebDAV-Feature wurde benutzt.',
+ 'The XMP metadata does not declare the ZUGFeRD data.' => 'Die XMP-Metadaten enthalten keine ZUGFeRD-Deklaration.',
+ 'The ZUGFeRD XML invoice was not found.' => 'Die ZUGFeRD-XML-Rechnungsdaten wurden nicht gefunden.',
+ 'The ZUGFeRD invoice data cannot be generated because the data validation failed.' => 'Die ZUGFeRD-Rechnungsdaten können nicht erzeugt werden, da die Validierung fehlschlug.',
+ 'The ZUGFeRD notes have been saved.' => 'Die ZUGFeRD-Notizen wurden gespeichert.',
+ 'The ZUGFeRD version used is not supported.' => 'Die verwendete ZUGFeRD-Version wird nicht unterstützt.',
'The abbreviation is missing.' => 'Abkürzung fehlt',
'The access rights a user has within a client instance is still governed by his group membership.' => 'Welche Zugriffsrechte ein Benutzer innerhalb eines Mandanten hat, wird weiterhin über Gruppenmitgliedschaften geregelt.',
'The access rights have been saved.' => 'Die Zugriffsrechte wurden gespeichert.',
'The columns "Dunning Duedate", "Total Fees" and "Interest" show data for the previous dunning created for this invoice.' => 'Die Spalten "Zahlbar bis", "Kumulierte Gebühren" und "Zinsen" zeigen Daten der letzten für diese Rechnung erzeugten Mahnung.',
'The combination of database host, port and name is not unique.' => 'Die Kombination aus Datenbankhost, -port und -name ist nicht eindeutig.',
'The command is missing.' => 'Der Befehl fehlt.',
+ 'The company\'s address information is incomplete in the client configuration.' => 'Die Firmenadresse in der Mandantenkonfiguration ist unvollständig.',
'The connection to the LDAP server cannot be encrypted (SSL/TLS startup failure). Please check config/kivitendo.conf.' => 'Die Verbindung zum LDAP-Server kann nicht verschlüsselt werden (Fehler bei SSL/TLS-Initialisierung). Bitte überprüfen Sie die Angaben in config/kivitendo.conf.',
'The connection to the authentication database failed:' => 'Die Verbindung zur Authentifizierungsdatenbank schlug fehl:',
'The connection to the configured client database "#1" on host "#2:#3" failed.' => 'Die Verbindung zur konfigurierten Datenbank "#1" auf Host "#2:#3" schlug fehl.',
'The connection to the template database failed:' => 'Die Verbindung zur Vorlagendatenbank schlug fehl:',
'The connection was established successfully.' => 'Die Verbindung zur Datenbank wurde erfolgreich hergestellt.',
'The contact person attribute "birthday" is converted from a free-form text field into a date field.' => 'Das Kontaktpersonenfeld "Geburtstag" wird von einem freien Textfeld auf ein Datumsfeld umgestellt.',
+ 'The country from the company\'s address in the client configuration cannot be mapped to an ISO 3166-1 alpha 2 code.' => 'Das Land der Firmenadresse in der Mandantenkonfiguration kann keinem der bekannten ISO 3166-1 Alpha 2-Codes zugeordnet werden.',
+ 'The country from the customer\'s address cannot be mapped to an ISO 3166-1 alpha 2 code.' => 'Das Land aus der Kunden-Rechnungsadresse kann keinem der bekannten ISO 3166-1 Alpha 2-Codes zugeordnet werden.',
'The creation of the authentication database failed:' => 'Das Anlegen der Authentifizierungsdatenbank schlug fehl:',
'The credentials (username & password) for connecting database are wrong.' => 'Die Daten (Benutzername & Passwort) für das Login zur Datenbank sind falsch.',
+ 'The currency "#1" cannot be mapped to an ISO 4217 currency code.' => 'Die Währung "#1" kann keinem der bekannten ISO 4217-Codes zugeordnet werden.',
'The custom data export has been deleted.' => 'Der benutzerdefinierte Datenexport wurde gelöscht.',
'The custom data export has been saved.' => 'Der benutzerdefinierte Datenexport wurde gespeichert.',
'The custom variable has been created.' => 'Die benutzerdefinierte Variable wurde erfasst.',
'The custom variable has been saved.' => 'Die benutzerdefinierte Variable wurde gespeichert.',
'The custom variable is in use and cannot be deleted.' => 'Die benutzerdefinierte Variable ist in Benutzung und kann nicht gelöscht werden.',
'The customer name is missing.' => 'Der Kundenname fehlt.',
+ 'The customer\'s bank account number (IBAN) is missing.' => 'Die Kontonummer (IBAN) des Kunden fehlt.',
'The database for user management and authentication does not exist. You can create let kivitendo create it with the following parameters:' => 'Die Datenbank für die Benutzeranmeldung existiert nicht. Sie können Sie von kivitendo automatisch mit den folgenden Parametern anlegen lassen:',
'The database host is missing.' => 'Der Datenbankhost fehlt.',
'The database name is missing.' => 'Der Datenbankname fehlt.',
'The export failed because of malformed transactions. Please fix those before exporting.' => 'Es sind fehlerhafte Buchungen im Exportzeitraum vorhanden. Bitte korrigieren Sie diese vor dem Export.',
'The factor is missing in row %d.' => 'Der Faktor fehlt in Zeile %d.',
'The factor is missing.' => 'Der Faktor fehlt.',
+ 'The file \'#1\' could not be opened for reading.' => 'Die Datei \'#1\' konnte nicht zum Lesen geöffnet werden.',
+ 'The file \'#1\' does not contain the required XMP meta data.' => 'Die Datei \'#1\' enthält die erforderlichen XMP-Metadaten nicht.',
'The file has been sent to the printer.' => 'Die Datei wurde an den Drucker geschickt.',
'The file is available for download.' => 'Die Datei ist zum Herunterladen verfügbar.',
'The file name is missing' => 'Der Dateiname fehlt',
'There was an error saving the draft' => 'Beim Speichern des Entwurfs ist ein Fehler aufgetretetn',
'There was an error saving the letter' => 'Ein Fehler ist aufgetreten. Der Brief konnte nicht gespeichert werden.',
'There was an error saving the letter draft' => 'Ein Fehler ist aufgetreten. Der Briefentwurf konnte nicht gespeichert werden.',
- 'There will be two transactions done:' => 'Zu jedem ausgewählten Konto werden jeweils zwei Buchungen erstellt:',
'There you can let kivitendo create the basic tables for you, even in an already existing database.' => 'Dort können Sie kivitendo diese grundlegenden Tabellen erstellen lassen, selbst in einer bereits existierenden Datenbank.',
'Therefore several settings that had to be made for each user in the past have been consolidated into the client configuration.' => 'Dazu wurden gewisse Einstellungen, die vorher bei jedem Benutzer vorgenommen werden mussten, in die Konfiguration eines Mandanten verschoben.',
'Therefore the definition of "kg" with the base unit "g" and a factor of 1000 is valid while defining "g" with a base unit of "kg" and a factor of "0.001" is not.' => 'So ist die Definition von "kg" mit der Basiseinheit "g" und dem Faktor 1000 zulässig, die Definition von "g" mit der Basiseinheit "kg" und dem Faktor "0,001" hingegen nicht.',
'These wrong entries cannot be fixed automatically.' => 'Diese Einträge können nicht automatisch bereinigt werden.',
'They will be updated, new ones for additional parts without a line item added automatically.' => 'Diese Positionen werden automatisch aktualisiert bzw. ergänzt, wenn es noch keine Position zu einem zusätzlichen Artikel gibt.',
'This Price Rule is no longer valid' => 'Diese Preisregel ist nicht mehr gültig',
+ 'This also enables displaying a column with the customer partnumber (new order controller).' => 'Hiermit wird auch die Anzeige der Kunden-Art.-Nr. eingeschaltet (neuer Auftrags-Controller).',
+ 'This also enables displaying a column with the vendor partnumber (model) (new order controller).' => 'Hiermit wird auch die Anzeige der Lieferanten-Art.-Nr. eingeschaltet (neuer Auftrags-Controller).',
'This can be done with the following query:' => 'Dies kann mit der folgenden Datenbankabfrage erreicht werden:',
'This could have happened for two reasons:' => 'Dies kann aus zwei Gründen geschehen sein:',
'This customer has already been added.' => 'Für diesen Kunden ist bereits ein Preis hinzugefügt.',
'Time estimate' => 'Zeitschätzung',
'Time period for the analysis:' => 'Analysezeitraum:',
'Time/cost estimate actions' => 'Aktionen für Kosten-/Zeitabschätzung',
- 'Timerange' => 'Zeitraum',
'Timestamp' => 'Uhrzeit',
'Tired of copying always nice phrases for this message? Click here to use the new preset message option!' => 'Müde vom vielen Copy & Paste aus vorherigen Anschreiben? Hier klicken, um E-Mail-Texte vorzudefinieren!',
'Title' => 'Titel',
'UStVa' => 'UStVa',
'UStVa Einstellungen' => 'UStVa Einstellungen',
'Unable to book transactions for bank purpose #1' => 'Konnte die Transaktion für den Bank-Verwendungszweck #1 nicht erfolgreich durchführen.',
+ 'Unable to reconcile, database transaction failure' => 'Abgleich konnte nicht durchgeführt werden: Datenbank-Transaktionsfehler',
'Unbalanced Ledger' => 'Bilanzfehler',
'Unchecked custom variables will not appear in orders and invoices.' => 'Unmarkierte Variablen werden für diesen Artikel nicht in Aufträgen und Rechnungen angezeigt.',
'Undo SEPA exports' => 'SEPA-Exporte rückgängig machen',
'Units that have already been used (e.g. for parts and services or in invoices or warehouse transactions) cannot be changed.' => 'Einheiten, die bereits in Benutzung sind (z.B. bei einer Warendefinition, einer Rechnung oder bei einer Lagerbuchung) können nachträglich nicht mehr verändert werden.',
'Unknown Category' => 'Unbekannte Kategorie',
'Unknown Link' => 'Unbekannte Verknüpfung',
+ 'Unknown authenticantion module #1 specified in "config/kivitendo.conf".' => 'Unbekanntes Authentifizierungsmodul #1 angegeben in "config/kivitendo.conf".',
'Unknown control fields: #1' => 'Unbekannte Kontrollfelder: #1',
'Unknown dependency \'%s\'.' => 'Unbekannte Abhängigkeit \'%s\'.',
'Unknown module: #1' => 'Unbekanntes Modul #1',
'Update Prices' => 'Preise aktualisieren',
'Update SKR04: new tax account 3804 (19%)' => 'Update SKR04: neues Steuerkonto 3804 (19%) für innergemeinschaftlichen Erwerb',
'Update customer using billing address' => 'Kunde mit Shop-Rechnungsadresse überschreiben',
+ 'Update from master data' => 'Aktualisieren aus Stammdaten',
'Update prices' => 'Preise aktualisieren',
'Update prices of existing entries' => 'Preise von vorhandenen Artikeln aktualisieren',
'Update prices of existing entries / skip non-existent' => 'Preise von vorhandenen Artikel aktualisieren / Nicht vorhandene überspringen',
'Use UStVA' => 'UStVA verwenden',
'Use WebDAV Repository' => 'Verwende WebDAV',
'Use WebDAV Storage backend' => 'Verwende WebDAV-Backend',
+ 'Use a text field to enter (new) contact departments if enabled. Otherwise, only a drop down box is offered.' => 'Textfeld zusätzlich zur Eingabe (neuer) Abteilungen von Ansprechpersonen verwenden. Sonst wird nur eine Auswahlliste angezeigt.',
+ 'Use a text field to enter (new) contact titles if enabled. Otherwise, only a drop down box is offered.' => 'Textfeld zusätzlich zur Eingabe (neuer) Titel von Ansprechpersonen verwenden. Sonst wird nur eine Auswahlliste angezeigt.',
+ 'Use a text field to enter (new) greetings if enabled. Otherwise, only a drop down box is offered.' => 'Textfeld zusätzlich zur Eingabe (neuer) Anreden verwenden. Sonst wird nur eine Auswahlliste angezeigt.',
'Use as new' => 'Als neu verwenden',
'Use default booking group because setting is \'all\'' => 'Standardbuchungsgruppe wird verwendet',
'Use default booking group because wanted is missing' => 'Fehlende Buchungsgruppe, deshalb Standardbuchungsgruppe',
'Use default warehouse for assembly transfer' => 'Zum Fertigen Standardlager des Bestandteils verwenden',
'Use existing templates' => 'Vorhandene Druckvorlagen verwenden',
'Use fill up when calculating shipped quantities?' => 'Sollen nicht verlinkte Positionen abgeglichen werden?',
+ 'Use for ZUGFeRD' => 'Nutzung mit ZUGFeRD',
'Use linked items' => 'Verknüpfte Positionen verwenden',
'Use master default bin for Default Transfer, if no default bin for the part is configured' => 'Standardlagerplatz für Ein- / Auslagern über Standard-Lagerplatz, falls für die Ware kein expliziter Lagerplatz konfiguriert ist',
+ 'Use settings from client configuration' => 'Einstellungen aus Mandantenkonfiguration folgen',
+ 'Use text field for department of contacts' => 'Textfeld für Abteilungen von Ansprechpersonen verwenden',
+ 'Use text field for greetings' => 'Textfeld für Anreden verwenden',
+ 'Use text field for title of contacts' => 'Textfeld für Titel von Ansprechpersonen verwenden',
'Use this storage backend for all generated PDF-Files' => 'Verwende dieses Backend für generierte PDF-Dateien',
'Use this storage backend for all uploaded attachments' => 'Verwende dieses Backend für hochgeladene Dateien',
'Use this storage backend for uploaded images' => 'Verwende dieses Backend für hochgeladene Bilder',
'View background job execution result' => 'Verlauf der Hintergrund-Job-Ausführungen anzeigen',
'View sent email' => 'Verschickte E-Mail anzeigen',
'View warehouse content' => 'Lagerbestand ansehen',
+ 'View/edit all employees purchase documents' => 'Bearbeiten/ansehen der Einkaufsdokumente aller Mitarbeiter',
'View/edit all employees sales documents' => 'Bearbeiten/ansehen der Verkaufsdokumente aller Mitarbeiter',
'Von Konto: ' => 'von Konto: ',
'WHJournal' => 'Lagerbuchungen',
'Warn before saving orders without a delivery date' => 'Warnung ausgeben, falls Aufträge kein Lieferdatum haben.',
'Warning' => 'Warnung',
'Warning! Loading a draft will discard unsaved data!' => 'Achtung! Beim Laden eines Entwurfs werden ungespeicherte Daten verworfen!',
+ 'Warning: Faulty position ignored' => 'Warnung: Fehlerhafte Artikel-Position ignoriert',
'Warning: One or more field value are not in valid DATEV format at:' => 'Warnung: Ein oder mehere Felder haben ungültige Feldwerte laut DATEV-Spezifikation bei:',
'Warnings and errors' => 'Warnungen und Fehler',
'Watch status' => 'Status',
'X' => 'X',
'YYYY' => 'JJJJ',
'Year' => 'Jahr',
+ 'Year-end bookings were successfully completed!' => 'Die Jahresabschlußbuchungen wurden erfolgreich durchgeführt!',
+ 'Year-end closing' => 'Jahresabschluß',
+ 'Year-end date' => 'Jahresabschlußdatum',
+ 'Year-end date missing' => 'Jahresabschlußdatum fehlt',
'Yearly' => 'jährlich',
'Yearly taxreport not yet implemented' => 'Jährlicher Steuerreport für dieses Ausgabeformat noch nicht implementiert',
'Yes' => 'Ja',
'You cannot create an invoice for delivery orders from different vendors.' => 'Sie können keine Rechnung aus Lieferscheinen von verschiedenen Lieferanten erstellen.',
'You cannot modify individual assigments from additional articles to line items.' => 'Eine individuelle Zuordnung der zusätzlichen Artikel zu Positionen kann nicht vorgenommen werden.',
'You cannot paste function blocks or sub function blocks if there is no section.' => 'Sie können keine Funktionsblöcke oder Unterfunktionsblöcke einfügen, wenn es noch keinen Abschnitt gibt.',
+ 'You cannot use a negative amount with debit/credit!' => 'Sie dürfen für Soll/Haben keine negativen Werte benutzen!',
'You do not have access to any custom data export.' => 'Sie haben auf keine benutzerdefinierten Datenexporte Zugriff.',
'You do not have permission to access this entry.' => 'Sie verfügen nicht über die Berechtigung, auf diesen Eintrag zuzugreifen.',
'You do not have the permissions to access this function.' => 'Sie verfügen nicht über die notwendigen Rechte, um auf diese Funktion zuzugreifen.',
'You don\'t have the rights to edit this customer.' => 'Sie verfügen nicht über die erforderlichen Rechte, um diesen Kunden zu bearbeiten.',
+ 'You have changed the currency or exchange rate. Please check prices.' => 'Die Währung oder der Wechselkurs hat sich geändert. Bitte überprüfen Sie die Preise.',
'You have entered or selected the following shipping address for this customer:' => 'Sie haben die folgende Lieferadresse eingegeben oder ausgewählt:',
'You have never worked with currencies.' => 'Sie haben noch nie mit Währungen gearbeitet.',
'You have not added bank accounts yet.' => 'Sie haben noch keine Bankkonten angelegt.',
'You should create a backup of the database before proceeding because the backup might not be reversible.' => 'Sie sollten eine Sicherungskopie der Datenbank erstellen, bevor Sie fortfahren, da die Aktualisierung unter Umständen nicht umkehrbar ist.',
'You\'re not editing a file.' => 'Sie bearbeiten momentan keine Datei.',
'You\'ve already chosen the following limitations:' => 'Sie haben bereits die folgenden Einschränkungen vorgenommen:',
+ 'Your Order' => 'Ihre Bestellung',
'Your PostgreSQL installationen does not use Unicode as its encoding. This is not supported anymore.' => 'Ihre PostgreSQL-Installation benutzt ein anderes Encoding als Unicode. Dies wird nicht mehr unterstützt.',
'Your Reference' => 'Ihr Zeichen',
'Your TODO list' => 'Ihre Aufgabenliste',
'Your download does not exist anymore. Please re-run the DATEV export assistant.' => 'Ihr Download existiert nicht mehr. Bitte starten Sie den DATEV-Exportassistenten erneut.',
'Your import is being processed.' => 'Ihr Import wird verarbeitet',
'Your target quantity will be added to the stocked quantity.' => 'Ihre gezählte Zielmenge wird zum Lagerbestand hinzugezählt.',
+ 'ZUGFeRD import' => 'ZUGFeRD Import',
+ 'ZUGFeRD invoice' => 'ZUGFeRD-Rechnung',
+ 'ZUGFeRD notes for each invoice' => 'ZUGFeRD-Notizen für jede Rechnung',
'Zeitraum' => 'Zeitraum',
'Zero amount posting!' => 'Buchung ohne Wert',
'Zip' => 'PLZ',
'Zip, City' => 'PLZ, Ort',
'Zipcode' => 'PLZ',
+ 'Zipcode and city' => 'PLZ und Stadt',
+ 'ZugFeRD Import' => 'ZUGFeRD Import',
'[email]' => '[email]',
'absolute' => 'absolut',
'account_description' => 'Beschreibung',
'bank_collection_payment_list_#1' => 'bankeinzugszahlungsliste_#1',
'bank_transfer_payment_list_#1' => 'ueberweisungszahlungsliste_#1',
'banktransfers' => 'ueberweisungen',
+ 'basis for stock value' => 'Grundlage für Bestandswert',
'bestbefore #1' => 'Mindesthaltbarkeit #1',
'bin_list' => 'Lagerliste',
'bis' => 'bis',
'brutto' => 'brutto',
'building data' => 'Verarbeite Daten',
'building report' => 'Erstelle Bericht',
+ 'can only parse a pdf file' => 'Kann nur eine gültige PDF-Datei verwenden.',
'cash' => 'Ist-Versteuerung',
'chargenumber #1' => 'Chargennummer #1',
'chart_of_accounts' => 'kontenuebersicht',
'cleared' => 'Abgeglichen',
'click here to edit cvars' => 'Klicken Sie hier, um nach benutzerdefinierten Variablen zu suchen',
'close' => 'schließen',
- 'close chart' => 'Saldovortragskonto',
'closed' => 'geschlossen',
'companylogo_subtitle' => 'Lizenziert für',
'config/kivitendo.conf: Key "DB_config" is missing.' => 'config/kivitendo.conf: Das Schlüsselwort "DB_config" fehlt.',
'error while unlinking payment #1 : ' => 'Fehler beim Zurücksetzen von Zahlung #1:',
'every third month' => 'vierteljährlich',
'every time' => 'immer',
+ 'exchange rate already exists, no update allowed' => 'Wechselkurs existiert bereits und kann nicht geändert werden',
+ 'exchange rate has to be positive' => 'Wechselkurs muss positiv sein',
'executed' => 'ausgeführt',
'execution as user \'#1\'' => 'Ausführung als User »#1«',
'failed' => 'fehlgeschlagen',
'from \'#1\' imported Files' => 'Von \'#1\' importierte Dateien',
'from (time)' => 'von',
'general_ledger_list' => 'Buchungsjournal',
- 'generate cb/ob transactions for selected charts' => 'Start-/Endbuchungen für ausgewählte Konten erstellen',
'generated Files' => 'Erzeugte Dokumente',
'gobd-#1-#2.zip' => 'gobd-#1-#2.zip',
'h' => 'h',
'male' => 'männlich',
'max filesize' => 'maximale Dateigröße',
'missing' => 'Fehlbestand',
+ 'missing file for action import' => 'Es wurde keine Datei zum Hochladen ausgewählt',
'missing_br' => 'Fehl.',
'month' => 'Monatliche Abgabe',
'monthly' => 'monatlich',
'more' => 'mehr',
+ 'natural person' => 'natürliche Person',
'netto' => 'netto',
'never' => 'niemals',
'new order controller' => 'Neuer Auftrags-Controller',
>=>
\n=<br>
-[Template/XML]
-order=< > \n
-<=<
->=>
-\n=<br>
-
[Template/LaTeX]
-order=\\ <pagebreak> & \n \r " $ <bullet> % _ # ^ { } < > £ ± ² ³ ° § ® © \xad \xa0 ➔ → ← ↔ ↕ | − ≤ ≥ ‐ Ω μ Δ ~
+order=\\ <pagebreak> & \n \r " $ <bullet> % _ # ^ { } < > £ ± ² ³ ° § ® © ~ \xad \xa0 ➔ → ← ↔ ↕ | − ≤ ≥ ‐ Ω μ Δ ‑
\\=\\textbackslash\s
<pagebreak>=
"=''
Δ=$\\Delta$
Ω=$\\Omega$
~={\\raisebox{0.5ex}{\\texttildelow}}
+‑={}-{}
[Template/OpenDocument]
order=& < > " ' \x80 \n \r
' bytes, max=' => '',
' missing!' => '',
'#1 (custom variable)' => '',
- '#1 CB transactions and #1 OB transactions generated.' => '',
'#1 MD' => '',
'#1 additional part(s)' => '',
'#1 bank transaction bookings undone.' => '',
'Account deleted!' => '',
'Account for fees' => '',
'Account for interest' => '',
+ 'Account for workflow from purchase order to ap transaction' => '',
'Account number' => '',
'Account number not unique!' => '',
'Account number of the goal/source' => '',
'Add Assortment' => '',
'Add Client' => '',
'Add Credit Note' => '',
+ 'Add Credit Note for this dunning level:' => '',
'Add Customer' => '',
'Add Customer/Vendor Number as a reference add-on for SEPA export.' => '',
'Add Delivery Note' => '',
'Add department' => '',
'Add empty line (csv_import)' => '',
'Add function block' => '',
+ 'Add greeting' => '',
'Add headers from last uploaded file (csv_import)' => '',
'Add invoices' => '',
'Add language' => '',
'Add new price rule item' => '',
'Add new record template' => '',
'Add note' => '',
+ 'Add open Credit Notes' => '',
'Add part' => '',
'Add part classification' => '',
'Add partsgroup' => '',
'Add sub function block' => '',
'Add taxzone' => '',
'Add text block' => '',
+ 'Add title' => '',
'Add unit' => '',
'Added sections and function blocks: #1' => '',
'Added text blocks: #1' => '',
'Administration' => '',
'Administration area' => '',
'Advance turnover tax return' => '',
+ 'Advance turnover tax return only valid for SKR03 or SKR04' => '',
'After closed period' => '',
'Aktion' => '',
'All' => '',
'Amount payable' => '',
'Amount payable less discount' => '',
'Amounts differ too much' => '',
- 'An error occured. Letter could not be deleted.' => '',
'An error occurred while transferring the file.' => '',
+ 'An error occurred. Letter could not be deleted.' => '',
'An exception occurred during execution.' => '',
'An invalid character was used (invalid characters: #1).' => '',
'An invalid character was used (valid characters: #1).' => '',
'Annotations' => '',
'Any stock contents containing a best before date will be impossible to stock out otherwise.' => '',
'Ap aging on %s' => '',
- 'Application Error. No Format given' => '',
'Application Error. Wrong Format' => '',
'Apply' => '',
'Apply customer' => '',
'Apply to transfers without bin' => '',
'Apply to transfers without comment' => '',
'Apply to transfers without warehouse' => '',
+ 'Apply year-end bookings' => '',
'Applying #1:' => '',
'Approximately #1 prices will be updated.' => '',
'Apr' => '',
'April' => '',
'Ar aging on %s' => '',
- 'Are you sure to generate cb/ob transactions?' => '',
+ 'Are you sure to update all positions from master data?' => '',
+ 'Are you sure to update this position from master data?' => '',
'Are you sure you want to delete Invoice Number' => '',
'Are you sure you want to delete this letter?' => '',
'Are you sure you want to remove the marked entries from the queue?' => '',
'At least one Perl module that kivitendo ERP requires for running is not installed on your system.' => '',
'At least one of the columns #1, customer, customernumber, customer_gln, vendor, vendornumber, vendor_gln (depending on the target table) is required for matching the entry to an existing customer or vendor.' => '',
'At most' => '',
+ 'At position' => '',
'At the moment the transaction looks like this:' => '',
'Attach PDF:' => '',
'Attached Filename' => '',
'Attachment name' => '',
'Attachments' => '',
'Attempt to call an undefined sub named \'%s\'' => '',
- 'Attention: Here will be generated a lot of CB/OB transactions.' => '',
'Audit Control' => '',
'Aug' => '',
'August' => '',
'Background jobs and task server' => '',
'Balance' => '',
'Balance Sheet' => '',
+ 'Balance accounts' => '',
'Balance sheet date' => '',
'Balance startdate method' => '',
+ 'Balance with CB' => '',
'Balances' => '',
'Balancing' => '',
'Bank' => '',
'Booking group (database ID)' => '',
'Booking group (name)' => '',
'Booking groups' => '',
+ 'Booking needs at least one debit and one credit booking!' => '',
'Bookinggroup/Tax' => '',
'Books are open' => '',
'Books closed up to' => '',
'CANCELED' => '',
'CB Transaction' => '',
'CB Transactions' => '',
- 'CB date #1 is higher than OB date #2. Please select again.' => '',
- 'CB/OB Transactions' => '',
'CN' => '',
'CR' => '',
'CSS style for pictures' => '',
'CSV import: bank transactions' => '',
'CSV import: contacts' => '',
'CSV import: customers and vendors' => '',
+ 'CSV import: delivery orders' => '',
'CSV import: inventories' => '',
'CSV import: orders' => '',
'CSV import: parts and services' => '',
'Cancel Accounts Payables Transaction' => '',
'Cancel Accounts Receivables Transaction' => '',
'Cancelling is disallowed. Either undo or balance the current payments until the open amount matches the invoice amount' => '',
+ 'Cannot change transaction in a closed period!' => '',
'Cannot check correct WebDAV folder' => '',
'Cannot delete account!' => '',
'Cannot delete customer!' => '',
'Cannot stock without amount' => '',
'Cannot storno invoice for a closed period!' => '',
'Cannot storno storno invoice!' => '',
+ 'Cannot transfer #1 qty with #2 serial number(s)' => '',
'Cannot transfer negative entries.' => '',
'Cannot transfer negative quantities.' => '',
'Cannot transfer. <br> Reason:<br>#1' => '',
'Cannot unlink payment for a closed period!' => '',
+ 'Carry over account for year-end closing' => '',
'Carry over shipping address' => '',
'Cash' => '',
'Cash accounting' => '',
'Close Window' => '',
'Close window' => '',
'Closed' => '',
+ 'Closing Balance' => '',
'Collective Orders only work for orders from one customer!' => '',
'Column name' => '',
'Comma' => '',
'Company' => '',
'Company Name' => '',
'Company name' => '',
+ 'Company name and address' => '',
'Company settings' => '',
'Compare to' => '',
'Complexities' => '',
'Confirm!' => '',
'Confirmation' => '',
'Contact' => '',
+ 'Contact Departments' => '',
'Contact Person' => '',
'Contact Person (database ID)' => '',
'Contact Person (name)' => '',
+ 'Contact Titles' => '',
'Contact deleted.' => '',
'Contact is in use and was flagged invalid.' => '',
'Contact person (surname)' => '',
'Correct taxkey' => '',
'Cost Center' => '',
'Costs' => '',
+ 'Could not create new project #1' => '',
+ 'Could not extract ZUGFeRD data, data and error message:' => '',
'Could not find an entry for this part in the pricegroup.' => '',
'Could not load class #1 (#2): "#3"' => '',
'Could not load class #1, #2' => '',
'Create Date' => '',
'Create HTML' => '',
'Create PDF' => '',
+ 'Create ZUGFeRD invoices' => '',
+ 'Create ZUGFeRD invoices in test mode' => '',
'Create a new background job' => '',
'Create a new client' => '',
'Create a new delivery term' => '',
'Create new version' => '',
'Create one from the context menu by right-clicking on this text.' => '',
'Create order' => '',
+ 'Create sales invoices with ZUGFeRD data' => '',
'Create tables' => '',
'Created by' => '',
'Created for' => '',
'Default template format' => '',
'Default transfer delivery order' => '',
'Default transfer invoice' => '',
+ 'Default transfer invoice with charge number' => '',
'Default transport article number' => '',
'Default unit' => '',
'Default value' => '',
'Delivery terms' => '',
'Delivery terms (database ID)' => '',
'Delivery terms (name)' => '',
+ 'DeliveryOrder' => '',
'Denmark' => '',
'Department' => '',
'Department (database ID)' => '',
'Displayable Name Preferences' => '',
'Do not change the tax rate of taxkey 0.' => '',
'Do not check for duplicates' => '',
+ 'Do not create ZUGFeRD invoices' => '',
'Do not link to a project.' => '',
'Do not modify this position' => '',
'Do not run the task server for this client' => '',
'Do not set this bin' => '',
'Do not set this comment' => '',
'Do not set this warehouse' => '',
- 'Do you really want do continue?' => '',
'Do you really want to cancel this general ledger transaction?' => '',
'Do you really want to cancel this invoice?' => '',
'Do you really want to cancel?' => '',
'Do you really want to close the selected SEPA exports? No payment will be recorded for bank collections that haven\'t been marked as executed yet.' => '',
'Do you really want to close the selected SEPA exports? No payment will be recorded for bank transfers that haven\'t been marked as executed yet.' => '',
+ 'Do you really want to continue?' => '',
'Do you really want to delete AP transaction #1?' => '',
'Do you really want to delete AR transaction #1?' => '',
'Do you really want to delete GL transaction #1?' => '',
'Do you really want to delete this record template?' => '',
'Do you really want to print?' => '',
'Do you really want to revert to this version?' => '',
- 'Do you really want to save?' => '',
- 'Do you really want to send by mail?' => '',
'Do you really want to undo the selected SEPA exports? You have to reassign the export again.' => '',
'Do you really want to unimport the selected documents?' => '',
'Do you want to <b>limit</b> your search?' => '',
'Edit Vendor' => '',
'Edit Vendor Invoice' => '',
'Edit Warehouse' => '',
+ 'Edit ZUGFeRD notes' => '',
'Edit acceptance status' => '',
'Edit additional articles' => '',
'Edit all drafts' => '',
'Edit department' => '',
'Edit file' => '',
'Edit general settings' => '',
+ 'Edit greeting' => '',
'Edit greetings' => '',
'Edit language' => '',
'Edit note' => '',
'Edit the request_quotation' => '',
'Edit the sales_order' => '',
'Edit the sales_quotation' => '',
+ 'Edit title' => '',
'Edit units' => '',
'Edit user signature' => '',
'Editable' => '',
'Equity' => '',
'Erfolgsrechnung' => '',
'Error' => '',
+ 'Error handling' => '',
'Error in database control file \'%s\': %s' => '',
'Error in position #1: You must either assign no stock at all or the full quantity of #2 #3.' => '',
'Error in position #1: You must either assign no transfer at all or the full quantity of #2 #3.' => '',
'Error message from the database: #1' => '',
'Error message from the webshop api:' => '',
'Error when saving: #1' => '',
+ 'Error while applying year-end bookings!' => '',
+ 'Error while creating project with project number of new order number, project number #1 already exists!' => '',
'Error with default taxzone' => '',
'Error!' => '',
'Error: #1' => '',
'Error: Customer/vendor is ambiguous' => '',
'Error: Customer/vendor missing' => '',
'Error: Customer/vendor not found' => '',
+ 'Error: Faulty position in this delivery order' => '',
'Error: Found local bank account number but local bank code doesn\'t match' => '',
'Error: Gender (cp_gender) missing or invalid' => '',
'Error: Invalid bin' => '',
'Error: Invalid warehouse' => '',
'Error: Invalid warehouse id' => '',
'Error: Invalid warehouse name #1' => '',
+ 'Error: More than one source order found' => '',
'Error: Name missing' => '',
+ 'Error: Not enough parts in stock' => '',
'Error: Part is ambiguous' => '',
'Error: Part is obsolete' => '',
'Error: Part not found' => '',
'Error: Quantity to transfer is zero.' => '',
+ 'Error: Source order not found' => '',
+ 'Error: Stock problem' => '',
+ 'Error: Stocking out would result in stock underrun' => '',
+ 'Error: Stocking out would result in stock underrun: #1' => '',
'Error: Transfer would result in a negative target quantity.' => '',
'Error: Unit missing or invalid' => '',
'Error: Warehouse not found' => '',
'Error: this feature requires that articles with a time-based unit (e.g. \'h\' or \'min\') exist.' => '',
'Error: unknown local bank account' => '',
'Error: unknown local bank account id' => '',
+ 'Errors' => '',
'Errors during conversion:' => '',
'Errors during printing:' => '',
+ 'Errors in GL transaction:' => '',
'Ertrag' => '',
'Ertrag prozentual' => '',
'Escape character' => '',
'General ledger transactions can only be changed on the day they are posted.' => '',
'General settings' => '',
'Generate and print sales delivery orders' => '',
- 'Generic Tax Report' => '',
'Germany' => '',
'Get shoporders' => 'Get and process orders from a web shop',
'Git revision: #1, #2 #3' => '',
'If disabled purchase invoices can only be created by conversion from existing requests for quotations, purchase orders and purchase delivery orders.' => '',
'If disabled sales orders cannot be converted into sales invoices directly.' => '',
'If disabled sales quotations cannot be converted into sales invoices directly.' => '',
+ 'If enabled ZUGFeRD-conformant sales invoice PDFs will be created.' => '',
'If enabled a column will be shown in sales and purchase orders that lists both the amount and the value not shipped yet for each item.' => '',
'If enabled a warning will be shown in sales and purchase orders if there are two or more positions of the same part (new controller only).' => '',
'If enabled a warning will be shown in sales and purchase orders if there the delivery date is empty.' => '',
'If item not found, allow creation of new item' => '',
'If left empty the default sender from the kivitendo configuration will be used (key \'email_from\' in section \'periodic_invoices\'; current value: #1).' => '',
'If missing then the start date will be used.' => '',
+ 'If one or more space separated serial numbers are assigned in a sales invoice, match the charge number of the inventory item. Assumes that Serial Number and Charge Number have 1:1 relation. Otherwise throw a error message for the default sales invoice transfer.' => '',
'If searching a part from a document and no part is found then offer to create a new part.' => '',
'If the article type is set to \'mixed\' then a column called \'part_type\' or called \'pclass\' must be present.' => '',
'If the automatic creation of invoices for fees and interest is switched on for a dunning level then the following accounts will be used for the invoice.' => '',
'If the counted quantity differs more than this threshold from the quantity in the database, a warning will be shown. Set to 0 to switch of this feature.' => '',
'If the database user listed above does not have the right to create a database then enter the name and password of the superuser below:' => '',
'If the default transfer out always succeed use this bin for negative stock quantity.' => '',
+ 'If the test mode is enabled, the ZUGFeRD invoices will be flagged so that they\'re only fit to be used for testing purposes.' => '',
'If yes, delivery order positions are considered "delivered" only if they have been stocked out of the inventory. Otherwise saving the delivery order is considered delivered.' => '',
'If you enter values for the part number and / or part description then only those bins containing parts whose part number or part description match your input will be shown.' => '',
'If you have not chosen for example the category revenue for a tax and you choose an revenue account to create a transfer in the general ledger, this tax will not be displayed in the tax dropdown.' => '',
'If you want to delete such a dataset you have to edit the client(s) that are using the dataset in question and have them use another dataset.' => '',
'If you want to set up the authentication database yourself then log in to the administration panel. kivitendo will then create the database and tables for you.' => '',
'If your old bins match exactly Bins in the Warehouse CLICK on <b>AUTOMATICALLY MATCH BINS</b>.' => '',
+ 'Ignore faulty positions' => '',
'Illegal characters have been removed from the following fields: #1' => '',
'Illegal date' => '',
'Image' => '',
'Import CSV' => '',
'Import Status' => '',
'Import a MT940 file:' => '',
+ 'Import a ZUGFeRD file:' => '',
'Import all' => '',
'Import documents from #1' => '',
'Import file' => '',
'Include in drop-down menus' => '',
'Include invalid warehouses ' => '',
'Include invoices with direct debit' => '',
+ 'Include original Invoices?' => '',
'Includeable in reports' => '',
'Included in reports by default' => '',
'Including' => '',
'Introduction of clients' => '',
'Inv. Duedate' => '',
'Invalid' => '',
+ 'Invalid charge number: #1' => '',
'Invalid combination of ledger account number length. Mismatch length of #1 with length of #2. Please check your account settings. ' => '',
'Invalid duration format' => '',
'Invalid follow-up ID.' => '',
'Invoices with payments cannot be canceled.' => '',
'Invoices, Credit Notes & AR Transactions' => '',
'Is Searchable' => '',
+ 'Is sales' => '',
'Is this a summary account to record' => '',
'It can be changed later but must be unique within the installation.' => '',
'It is not allowed that a summary account occurs in a drop-down menu!' => '',
'List Printers' => '',
'List Transactions' => '',
'List Users, Clients and User Groups' => '',
+ 'List all rows' => '',
'List current background jobs' => '',
'List export' => '',
'List of bank collections' => '',
'Local account number' => '',
'Local bank account' => '',
'Local bank code' => '',
+ 'Lock' => '',
'Lock System' => '',
'Lock and unlock installation' => '',
'Lock bookings' => '',
'Long Description (quotations & orders)' => '',
'Long Description for invoices' => '',
'Long Description for quotations & orders' => '',
+ 'Loss' => '',
+ 'Loss carried forward account' => '',
'Luxembourg' => '',
'MAILED' => '',
'MD' => '',
'Mass Create Print Sales Invoice from Delivery Order' => '',
'Master Data' => '',
'Master Data Bin Text Deleted' => '',
+ 'Match Sales Invoice Serial numbers with inventory charge numbers?' => '',
'Matching Price Rules can apply in one of three types:' => '',
'Max. Dunning Level' => '',
'Maximal amount difference' => '',
'Missing Method!' => '',
'Missing Tax Authoritys Preferences' => '',
'Missing amount' => '',
+ 'Missing configuration section "authentication/#1" in "config/kivitendo.conf".' => '',
'Missing parameter #1 in call to sub #2.' => '',
'Missing parameter (at least one of #1) in call to sub #2.' => '',
'Missing parameter for WebDAV file copy' => '',
'Name does not make sense without any bsooqr options' => '',
'Name in Selected Records' => '',
'Name of the goal/source (if field names remote_name and remote_name_1 exist they will be combined into field "remote_name")' => '',
+ 'Need charge number!' => '',
'Negative reductions are possible to model price increases.' => '',
'Neither sections nor function blocks have been created yet.' => '',
'Net Income Statement' => '',
'Next run at' => '',
'No' => '',
'No 1:n or n:1 relation' => '',
+ 'No AP Record Template for this vendor found, please add one' => '',
'No AP template was found.' => '',
'No Company Address given' => '',
'No Company Name given' => '',
'No Journal' => '',
'No Shopdescription' => '',
'No Shopimages' => '',
+ 'No VAT Info for this ZUGFeRD invoice, please ask your vendor to add this for his ZUGFeRD data.' => '',
'No Vendor was found matching the search parameters.' => '',
'No action defined.' => '',
'No article has been selected yet.' => '',
'No assembly has been selected yet.' => '',
'No background job has been created yet.' => '',
'No bank account chosen!' => '',
+ 'No bank account flagged for ZUGFeRD usage was found.' => '',
'No bank information has been entered in this customer\'s master data entry. You cannot create bank collections unless you enter bank information.' => '',
'No bank information has been entered in this vendor\'s master data entry. You cannot create bank transfers unless you enter bank information.' => '',
'No bins have been added to this warehouse yet.' => '',
+ 'No carry-over chart configured!' => '',
'No changes since previous version.' => '',
'No clients have been created yet.' => '',
'No contact selected to delete' => '',
'No groups have been created yet.' => '',
'No internal phone extensions have been configured yet.' => '',
'No invoices have been selected.' => '',
- 'No or an unknown authenticantion module specified in "config/kivitendo.conf".' => '',
'No part was selected.' => '',
'No payment term has been created yet.' => '',
'No picture has been uploaded' => '',
'No print templates have been created for this client yet. Please do so in the client configuration.' => '',
'No printers have been created yet.' => '',
'No problems were recognized.' => '',
+ 'No profit and loss carried forward chart configured!' => '',
+ 'No profit carried forward chart configured!' => '',
'No quotations or orders have been created yet.' => '',
'No report with id #1' => '',
'No requirement spec templates have been created yet.' => '',
'No results.' => '',
- 'No revert available.' => '',
'No search results found!' => '',
'No sections created yet' => '',
'No sections have been created so far.' => '',
'Not Discountable' => '',
'Not delivered' => '',
'Not done yet' => '',
+ 'Not enough in stock for the serial number #1' => '',
'Not obsolete' => '',
'Note' => '',
'Note that parameter names must not be quoted.' => '',
'Number pages' => '',
'Number variables: \'PRECISION=n\' forces numbers to be shown with exactly n decimal places.' => '',
'OB Transaction' => '',
- 'OB Transactions' => '',
'Objects have been imported.' => '',
'Obsolete' => '',
'Oct' => '',
'On Hand' => '',
'On Order' => '',
'On the next page the type of all variables can be set.' => '',
- 'One OB-transaction' => '',
- 'One SB-transaction' => '',
'One of the columns "qty" or "target_qty" must be given. If "target_qty" is given, the quantity to transfer for each transfer will be calculate, so that the quantity for this part, warehouse and bin will result in the given "target_qty" after each transfer.' => '',
+ 'One of the units used (#1) cannot be mapped to a known unit code from the UN/ECE Recommendation 20 list.' => '',
'One or more Perl modules missing' => '',
'Onhand only sets the quantity in master data, not in inventory. This is only a legacy info field and will be overwritten as soon as a inventory transfer happens.' => '',
+ 'Only Lines with Notes or Errors' => '',
'Only Price' => '',
'Only Stock' => '',
- 'Only Warnings and Errors' => '',
'Only applies if the previous is set to true. When filling up unlinked positions, consider them matches if ALL of these fields match. For example, in a business with variants that are defined by a special description, description needs to be part of the identity. If delivering several similar order positions by delivery date is common, reqdate should be included in the identity. Serialnumber is useful when the serialnumber in the order and delivery order has to match.' => '',
'Only booked accounts' => '',
'Only due follow-ups' => '',
'Order probability & expected billing date' => '',
'Order value periodicity' => '',
'Order/Item row name' => '',
+ 'Order/Item/Stock row name' => '',
'Order/RFQ Number' => '',
'OrderItem' => '',
'Ordered' => '',
'Page #1/#2' => '',
'Paid' => '',
'Paid amount' => '',
+ 'Parsing the XMP metadata failed.' => '',
'Part' => '',
'Part "#1" has chargenumber or best before date set. So it cannot be transfered automatically.' => '',
'Part (database ID)' => '',
'Payables' => '',
'Payment' => '',
'Payment / Delivery Options' => '',
+ 'Payment Date' => '',
'Payment Reminder' => '',
'Payment Terms' => '',
'Payment Terms missing in row ' => '',
'Pictures for search parts' => '',
'Please Check the bank information for each customer:' => '',
'Please Check the bank information for each vendor:' => '',
+ 'Please add a valid VAT-ID for this vendor: ' => '',
'Please ask your administrator to create warehouses and bins.' => '',
'Please change the partnumber of the following parts and run the update again:' => '',
'Please choose a part.' => '',
'Please choose for which categories the taxes should be displayed (otherwise remove the ticks):' => '',
'Please choose the action to be processed for your target quantity:' => '',
+ 'Please configure the carry over and profit and loss accounts for year-end closing in the client configuration!' => '',
'Please contact your administrator or a service provider.' => '',
'Please contact your administrator.' => '',
'Please correct the settings and try again or deactivate that client.' => '',
'Preset email text for requests (rfq)' => '',
'Preset email text for sales delivery orders' => '',
'Preset email text for sales invoices' => '',
+ 'Preset email text for sales invoices with direct debit' => '',
'Preset email text for sales orders' => '',
'Preset email text for sales quotations' => '',
'Preview' => '',
'Price #1' => '',
'Price Factor' => '',
'Price Factors' => '',
+ 'Price List' => '',
'Price Rule' => '',
'Price Rules' => '',
'Price Source' => '',
'Production' => 'Production',
'Production (typeabbreviation)' => 'W',
'Productivity' => '',
+ 'Profit' => '',
+ 'Profit and loss accounts' => '',
+ 'Profit carried forward account' => '',
'Profit determination' => '',
'Proforma Invoice' => '',
'Program' => '',
'SEPA strings' => '',
'SQL query' => '',
'SWIFT MT940 format' => '',
- 'Saldo' => '',
'Saldo Credit' => '',
'Saldo Debit' => '',
'Saldo neu' => '',
'Search AR Aging' => '',
'Search bank transactions' => '',
'Search contacts' => '',
+ 'Search for Items used in Assemblies' => '',
+ 'Search parts by customer partnumber in sales order forms' => '',
+ 'Search parts by vendor partnumber (model) in purchase order forms' => '',
'Search term' => '',
'Searchable' => '',
'Secondary sorting' => '',
'Select Mulit-Item Options' => '',
'Select a Customer' => '',
'Select a period' => '',
- 'Select charts for which the CB/OB transactions want to be posted.' => '',
'Select federal state...' => '',
'Select file to upload' => '',
'Select from one of the items below' => '',
'Show the picture in the part form' => '',
'Show the pictures in the result for search parts' => '',
'Show the weights of articles and the total weight in orders, invoices and delivery notes?' => '',
+ 'Show update button for positions in order forms' => '',
'Show weights' => '',
'Show your TODO list after logging in' => '',
'Show »not delivered qty/value« column in sales and purchase orders' => '',
'Start of year' => '',
'Start process' => '',
'Start the correction assistant' => '',
+ 'Startdate method' => '',
'Startdate_coa' => '',
'Starting Balance' => '',
'Starting balance' => '',
'Stock Local/Shop' => '',
'Stock Qty for Date' => '',
'Stock for part #1' => '',
+ 'Stock levels' => '',
'Stock value' => '',
+ 'StockInfo' => '',
'Stocked Qty' => '',
'Stocktaking' => '',
'Stocktaking History' => '',
'Storno (one letter abbreviation)' => '',
'Storno Invoice' => '',
'Street' => '',
+ 'Street 1' => '',
+ 'Street 2' => '',
'Strict and halt' => '',
'Strict but replace' => '',
'Style the picture with the following CSS code' => '',
'Subtotals per quarter' => '',
'Such entries cannot be exported into the DATEV format and have to be fixed as well.' => '',
'Suggested invoice' => '',
- 'Sum CB Transactions' => '',
'Sum Credit' => '',
'Sum Debit' => '',
- 'Sum OB Transactions' => '',
'Sum for' => '',
'Sum for #1' => '',
'Sum for section' => '',
'Tax Office Preferences' => '',
'Tax Percent is a number between 0 and 100' => '',
'Tax Period' => '',
- 'Tax Position' => '',
'Tax collected' => '',
'Tax deleted!' => '',
'Tax number' => '',
'Taxkey_coa' => '',
'Taxkeys and Taxreport Preferences' => '',
'Taxlink_coa' => '',
- 'Taxnumber' => '',
'Taxrate missing!' => '',
'Taxzones' => '',
'Tel' => '',
'The SQL query can be parameterized with variables named as follows: <%name%>.' => '',
'The SQL query does not contain any parameter that need to be configured.' => '',
'The URL is missing.' => '',
+ 'The VAT ID number \'#1\' is invalid.' => '',
+ 'The VAT ID number in the client configuration is invalid.' => '',
+ 'The VAT registration number is missing in the client configuration.' => '',
'The WebDAV feature has been used.' => '',
+ 'The XMP metadata does not declare the ZUGFeRD data.' => '',
+ 'The ZUGFeRD XML invoice was not found.' => '',
+ 'The ZUGFeRD invoice data cannot be generated because the data validation failed.' => '',
+ 'The ZUGFeRD notes have been saved.' => '',
+ 'The ZUGFeRD version used is not supported.' => '',
'The abbreviation is missing.' => '',
'The access rights a user has within a client instance is still governed by his group membership.' => '',
'The access rights have been saved.' => '',
'The columns "Dunning Duedate", "Total Fees" and "Interest" show data for the previous dunning created for this invoice.' => '',
'The combination of database host, port and name is not unique.' => '',
'The command is missing.' => '',
+ 'The company\'s address information is incomplete in the client configuration.' => '',
'The connection to the LDAP server cannot be encrypted (SSL/TLS startup failure). Please check config/kivitendo.conf.' => '',
'The connection to the authentication database failed:' => '',
'The connection to the configured client database "#1" on host "#2:#3" failed.' => '',
'The connection to the template database failed:' => '',
'The connection was established successfully.' => '',
'The contact person attribute "birthday" is converted from a free-form text field into a date field.' => '',
+ 'The country from the company\'s address in the client configuration cannot be mapped to an ISO 3166-1 alpha 2 code.' => '',
+ 'The country from the customer\'s address cannot be mapped to an ISO 3166-1 alpha 2 code.' => '',
'The creation of the authentication database failed:' => '',
'The credentials (username & password) for connecting database are wrong.' => '',
+ 'The currency "#1" cannot be mapped to an ISO 4217 currency code.' => '',
'The custom data export has been deleted.' => '',
'The custom data export has been saved.' => '',
'The custom variable has been created.' => '',
'The custom variable has been saved.' => '',
'The custom variable is in use and cannot be deleted.' => '',
'The customer name is missing.' => '',
+ 'The customer\'s bank account number (IBAN) is missing.' => '',
'The database for user management and authentication does not exist. You can create let kivitendo create it with the following parameters:' => '',
'The database host is missing.' => '',
'The database name is missing.' => '',
'The export failed because of malformed transactions. Please fix those before exporting.' => '',
'The factor is missing in row %d.' => '',
'The factor is missing.' => '',
+ 'The file \'#1\' could not be opened for reading.' => '',
+ 'The file \'#1\' does not contain the required XMP meta data.' => '',
'The file has been sent to the printer.' => '',
'The file is available for download.' => '',
'The file name is missing' => '',
'There was an error saving the draft' => '',
'There was an error saving the letter' => '',
'There was an error saving the letter draft' => '',
- 'There will be two transactions done:' => '',
'There you can let kivitendo create the basic tables for you, even in an already existing database.' => '',
'Therefore several settings that had to be made for each user in the past have been consolidated into the client configuration.' => '',
'Therefore the definition of "kg" with the base unit "g" and a factor of 1000 is valid while defining "g" with a base unit of "kg" and a factor of "0.001" is not.' => '',
'These wrong entries cannot be fixed automatically.' => '',
'They will be updated, new ones for additional parts without a line item added automatically.' => '',
'This Price Rule is no longer valid' => '',
+ 'This also enables displaying a column with the customer partnumber (new order controller).' => '',
+ 'This also enables displaying a column with the vendor partnumber (model) (new order controller).' => '',
'This can be done with the following query:' => '',
'This could have happened for two reasons:' => '',
'This customer has already been added.' => '',
'Time estimate' => '',
'Time period for the analysis:' => '',
'Time/cost estimate actions' => '',
- 'Timerange' => '',
'Timestamp' => '',
'Tired of copying always nice phrases for this message? Click here to use the new preset message option!' => '',
'Title' => '',
'UStVa' => '',
'UStVa Einstellungen' => '',
'Unable to book transactions for bank purpose #1' => '',
+ 'Unable to reconcile, database transaction failure' => '',
'Unbalanced Ledger' => '',
'Unchecked custom variables will not appear in orders and invoices.' => '',
'Undo SEPA exports' => '',
'Units that have already been used (e.g. for parts and services or in invoices or warehouse transactions) cannot be changed.' => '',
'Unknown Category' => '',
'Unknown Link' => '',
+ 'Unknown authenticantion module #1 specified in "config/kivitendo.conf".' => '',
'Unknown control fields: #1' => '',
'Unknown dependency \'%s\'.' => '',
'Unknown module: #1' => '',
'Update Prices' => '',
'Update SKR04: new tax account 3804 (19%)' => '',
'Update customer using billing address' => '',
+ 'Update from master data' => '',
'Update prices' => '',
'Update prices of existing entries' => '',
'Update prices of existing entries / skip non-existent' => '',
'Use UStVA' => '',
'Use WebDAV Repository' => '',
'Use WebDAV Storage backend' => '',
+ 'Use a text field to enter (new) contact departments if enabled. Otherwise, only a drop down box is offered.' => '',
+ 'Use a text field to enter (new) contact titles if enabled. Otherwise, only a drop down box is offered.' => '',
+ 'Use a text field to enter (new) greetings if enabled. Otherwise, only a drop down box is offered.' => '',
'Use as new' => '',
'Use default booking group because setting is \'all\'' => '',
'Use default booking group because wanted is missing' => '',
'Use default warehouse for assembly transfer' => '',
'Use existing templates' => '',
'Use fill up when calculating shipped quantities?' => '',
+ 'Use for ZUGFeRD' => '',
'Use linked items' => '',
'Use master default bin for Default Transfer, if no default bin for the part is configured' => '',
+ 'Use settings from client configuration' => '',
+ 'Use text field for department of contacts' => '',
+ 'Use text field for greetings' => '',
+ 'Use text field for title of contacts' => '',
'Use this storage backend for all generated PDF-Files' => '',
'Use this storage backend for all uploaded attachments' => '',
'Use this storage backend for uploaded images' => '',
'View background job execution result' => '',
'View sent email' => '',
'View warehouse content' => '',
+ 'View/edit all employees purchase documents' => '',
'View/edit all employees sales documents' => '',
'Von Konto: ' => '',
'WHJournal' => 'Warehouse journal',
'Warn before saving orders without a delivery date' => '',
'Warning' => '',
'Warning! Loading a draft will discard unsaved data!' => '',
+ 'Warning: Faulty position ignored' => '',
'Warning: One or more field value are not in valid DATEV format at:' => '',
'Warnings and errors' => '',
'Watch status' => '',
'X' => '',
'YYYY' => '',
'Year' => '',
+ 'Year-end bookings were successfully completed!' => '',
+ 'Year-end closing' => '',
+ 'Year-end date' => '',
+ 'Year-end date missing' => '',
'Yearly' => '',
'Yearly taxreport not yet implemented' => '',
'Yes' => '',
'You cannot create an invoice for delivery orders from different vendors.' => '',
'You cannot modify individual assigments from additional articles to line items.' => '',
'You cannot paste function blocks or sub function blocks if there is no section.' => '',
+ 'You cannot use a negative amount with debit/credit!' => '',
'You do not have access to any custom data export.' => '',
'You do not have permission to access this entry.' => '',
'You do not have the permissions to access this function.' => '',
'You don\'t have the rights to edit this customer.' => '',
+ 'You have changed the currency or exchange rate. Please check prices.' => '',
'You have entered or selected the following shipping address for this customer:' => '',
'You have never worked with currencies.' => '',
'You have not added bank accounts yet.' => '',
'You should create a backup of the database before proceeding because the backup might not be reversible.' => '',
'You\'re not editing a file.' => '',
'You\'ve already chosen the following limitations:' => '',
+ 'Your Order' => '',
'Your PostgreSQL installationen does not use Unicode as its encoding. This is not supported anymore.' => '',
'Your Reference' => '',
'Your TODO list' => '',
'Your download does not exist anymore. Please re-run the DATEV export assistant.' => '',
'Your import is being processed.' => '',
'Your target quantity will be added to the stocked quantity.' => '',
+ 'ZUGFeRD import' => '',
+ 'ZUGFeRD invoice' => '',
+ 'ZUGFeRD notes for each invoice' => '',
'Zeitraum' => '',
'Zero amount posting!' => '',
'Zip' => '',
'Zip, City' => '',
'Zipcode' => '',
+ 'Zipcode and city' => '',
+ 'ZugFeRD Import' => '',
'[email]' => '',
'absolute' => '',
'account_description' => '',
'bank_collection_payment_list_#1' => '',
'bank_transfer_payment_list_#1' => '',
'banktransfers' => '',
+ 'basis for stock value' => '',
'bestbefore #1' => '',
'bin_list' => '',
'bis' => '',
'brutto' => '',
'building data' => '',
'building report' => '',
+ 'can only parse a pdf file' => '',
'cash' => '',
'chargenumber #1' => '',
'chart_of_accounts' => '',
'cleared' => '',
'click here to edit cvars' => '',
'close' => '',
- 'close chart' => '',
'closed' => '',
'companylogo_subtitle' => '',
'config/kivitendo.conf: Key "DB_config" is missing.' => '',
'error while unlinking payment #1 : ' => '',
'every third month' => '',
'every time' => '',
+ 'exchange rate already exists, no update allowed' => '',
+ 'exchange rate has to be positive' => '',
'executed' => '',
'execution as user \'#1\'' => '',
'failed' => '',
'from \'#1\' imported Files' => '',
'from (time)' => '',
'general_ledger_list' => '',
- 'generate cb/ob transactions for selected charts' => '',
'generated Files' => '',
'gobd-#1-#2.zip' => '',
'h' => '',
'male' => '',
'max filesize' => '',
'missing' => '',
+ 'missing file for action import' => '',
'missing_br' => 'missing',
'month' => '',
'monthly' => '',
'more' => '',
+ 'natural person' => '',
'netto' => '',
'never' => '',
'new order controller' => '',
\n=<br>
[Template/LaTeX]
-order=\\ <pagebreak> & \n \r " $ <bullet> % _ # ^ { } < > £ ± ² ³ ° § ® © \xad \xa0 ➔ → ← | − ≤ ≥ ‐ Ω μ Δ ~
+order=\\ <pagebreak> & \n \r " $ <bullet> % _ # ^ { } < > £ ± ² ³ ° § ® © ~ \xad \xa0 ➔ → ← | − ≤ ≥ ‐ Ω μ Δ ‑
\\=\\textbackslash\s
<pagebreak>=
"=''
Δ=$\\Delta$
Ω=$\\Omega$
~={\\raisebox{0.5ex}{\\texttildelow}}
+‑={}-{}
[Template/OpenDocument]
order=& < > " ' \x80 \n \r
name: Add Assortment
icon: assortment_add
order: 550
- access: part_service_assembly_edit & client/feature_experimental
+ access: part_service_assembly_edit & client/feature_experimental_assortment
params:
action: Part/add_assortment
- parent: master_data
params:
action: GoBD/filter
- parent: general_ledger
- id: general_ledger_cbob_transactions
- name: CB/OB Transactions
+ id: year_end_closing
+ name: Year-end closing
icon: cbob
order: 470
access: general_ledger
params:
- action: YearEndTransactions/filter
+ action: YearEndTransactions/form
+- parent: general_ledger
+ id: zugferd_import
+ name: ZugFeRD Import
+ icon: cbob
+ order: 485
+ access: ap_transactions
+ params:
+ action: ZUGFeRD/upload_zugferd
- parent: general_ledger
id: general_ledger_reports
name: Reports
params:
action: SimpleSystemSetting/list
type: price_factor
+- parent: system
+ id: system_greetings
+ name: Greetings
+ order: 1250
+ params:
+ action: SimpleSystemSetting/list
+ type: greeting
- parent: system
id: system_departments
name: Departments
params:
action: SimpleSystemSetting/list
type: business
+- parent: system
+ id: system_contact_titles
+ name: Contact Titles
+ order: 1420
+ params:
+ action: SimpleSystemSetting/list
+ type: contact_title
+- parent: system
+ id: system_contact_departments
+ name: Contact Departments
+ order: 1430
+ params:
+ action: SimpleSystemSetting/list
+ type: contact_department
- parent: system
id: system_project_types
name: Project Types
module: generictranslations.pl
params:
action: edit_sepa_strings
+- parent: system_languages_and_translations
+ id: system_languages_and_translations_zugferd_notes
+ name: ZUGFeRD notes for each invoice
+ order: 450
+ module: generictranslations.pl
+ params:
+ action: edit_zugferd_notes
- parent: system_languages_and_translations
id: system_languages_and_translations_email_strings
name: Preset email strings
params:
action: CsvImport/new
profile.type: orders
+- parent: system_import_csv
+ id: system_import_csv_delivery_orders
+ name: Delivery Orders
+ order: 720
+ params:
+ action: CsvImport/new
+ profile.type: delivery_orders
- parent: system_import_csv
id: system_import_csv_ar_transactions
name: AR Transactions
--- /dev/null
+package Algorithm::CheckDigits::M97_001;
+
+use 5.006;
+use strict;
+use warnings;
+use integer;
+
+use version; our $VERSION = 'v1.3.2';
+
+our @ISA = qw(Algorithm::CheckDigits);
+
+sub new {
+ my $proto = shift;
+ my $type = shift;
+ my $class = ref($proto) || $proto;
+ my $self = bless({}, $class);
+ $self->{type} = lc($type);
+ return $self;
+} # new()
+
+sub is_valid {
+ my ($self,$number) = @_;
+ if ($number =~ /^(\d{7,8})?(\d\d)$/i) {
+ return $2 eq $self->_compute_checkdigit($1);
+ }
+ return ''
+} # is_valid()
+
+sub complete {
+ my ($self,$number) = @_;
+ if ($number =~ /^(\d{7,8})$/i) {
+ return sprintf('%08d', $number) . $self->_compute_checkdigit($1);
+ }
+ return '';
+} # complete()
+
+sub basenumber {
+ my ($self,$number) = @_;
+ if ($number =~ /^(\d{7,8})(\d\d)$/i) {
+ return sprintf('%08d', $1) if ($2 eq $self->_compute_checkdigit($1));
+ }
+ return '';
+} # basenumber()
+
+sub checkdigit {
+ my ($self,$number) = @_;
+ if ($number =~ /^(\d{7,8})(\d\d)$/i) {
+ return $2 if (uc($2) eq $self->_compute_checkdigit($1));
+ }
+ return '';
+} # checkdigit()
+
+sub _compute_checkdigit {
+ my $self = shift;
+ my $number = shift;
+
+ if ($number =~ /^\d{7,8}$/i) {
+ return sprintf("%2.2d",97 - ($number % 97));
+ }
+ return -1;
+} # _compute_checkdigit()
+
+# Preloaded methods go here.
+
+1;
+__END__
+
+=head1 NAME
+
+CheckDigits::M97_001 - compute check digits for VAT Registration Number (BE)
+
+=head1 SYNOPSIS
+
+ use Algorithm::CheckDigits;
+
+ $ustid = CheckDigits('ustid_be');
+
+ if ($ustid->is_valid('136695962')) {
+ # do something
+ }
+
+ $cn = $ustid->complete('1366959');
+ # $cn = '136695962'
+
+ $cd = $ustid->checkdigit('136695962');
+ # $cd = '62'
+
+ $bn = $ustid->basenumber('136695962');
+ # $bn = '1366959'
+
+=head1 DESCRIPTION
+
+=head2 ALGORITHM
+
+=over 4
+
+=item 1
+
+The whole number (without checksum) is taken modulo 97.
+
+=item 2
+
+The checksum is difference of the remainder from step 1 to 97.
+
+=back
+
+=head2 METHODS
+
+=over 4
+
+=item is_valid($number)
+
+Returns true only if C<$number> consists solely of numbers and the last digit
+is a valid check digit according to the algorithm given above.
+
+Returns false otherwise,
+
+=item complete($number)
+
+The check digit for C<$number> is computed and concatenated to the end
+of C<$number>.
+
+Returns the complete number with check digit or '' if C<$number>
+does not consist solely of digits and spaces.
+
+=item basenumber($number)
+
+Returns the basenumber of C<$number> if C<$number> has a valid check
+digit.
+
+Return '' otherwise.
+
+=item checkdigit($number)
+
+Returns the checkdigits of C<$number> if C<$number> has a valid check
+digit.
+
+Return '' otherwise.
+
+=back
+
+=head2 EXPORT
+
+None by default.
+
+=head1 AUTHOR
+
+Mathias Weidner, C<< <mamawe@cpan.org> >>
+
+=head1 SEE ALSO
+
+L<perl>,
+L<CheckDigits>,
+F<www.pruefziffernberechnung.de>.
+
+=cut
upgrade file your \$EDITOR will be called with it.
--apply=tag Applies the database upgrades \'tag\' and all
upgrades it depends on. If \'--apply\' is used
- then the option \'--user\' must be used as well.
+ then the option \'--user\' and \'--client\' must be
+ used as well. Use \'--apply=ALL\' to apply all.
--applied List the applied database upgrades for the
database that the user given with \'--user\' uses.
--unapplied List the database upgrades that haven\'t been applied
$modules{$_->{name}} ||= { status => 'optional' } for @SL::InstallationCheck::optional_modules;
$modules{$_->{name}} ||= { status => 'developer' } for @SL::InstallationCheck::developer_modules;
-# build transitive closure for documented dependancies
+# build transitive closure for documented dependencies
my $changed = 1;
while ($changed) {
$changed = 0;
=item required
This module is documented in C<SL:InstallationCheck> to be necessary, or is a
-dependancy of one of these. Everything alright.
+dependency of one of these. Everything alright.
=item !missing
for my $filename (sort @files) {
my $image = `$identify_bin $filename`;
if (!defined $image) {
- warn "warning: could not identify image '$filename'. skpping...";
+ warn "warning: could not identify image '$filename'. skipping...";
next;
}
$image =~ /^(?<filename>\S+) \s (?<type>\S+) \s (?<width>\d+) x (?<height>\d+)/x;
use strict;
use Getopt::Long;
+use List::MoreUtils qw(uniq);
use Pod::Usage;
use Term::ANSIColor;
use Text::Wrap;
$| = 1;
if (!SL::LxOfficeConf->read(undef, 'may fail')) {
- print_header('Could not load the config file. If you have dependancies from any features enabled in the configuration these will still show up as optional because of this. Please rerun this script after installing the dependancies needed to load the configuration.')
+ print_header('Could not load the config file. If you have dependencies from any features enabled in the configuration these will still show up as optional because of this. Please rerun this script after installing the dependencies needed to load the configuration.')
} else {
SL::InstallationCheck::check_for_conditional_dependencies();
}
Standard check done, everything is OK and up to date. Have a look at the --help
section of this script to see some more advanced checks for developer and
-optional dependancies, as well as LaTeX packages you might need.
+optional dependencies, as well as LaTeX packages you might need.
EOL
}
}
sub check_latex {
my ($res) = check_kpsewhich();
print_result("Looking for LaTeX kpsewhich", $res);
+
+ # no pdfx -> no zugferd possible
+ my $ret = kpsewhich('template/print/', 'sty', 'pdfx');
+ die "Cannot use pdfx. Please install this package first (debian: apt install texlive-latex-extra)" if $ret;
+ # check version 2018
+ my $latex = $::lx_office_conf{applications}->{latex} || 'pdflatex';
+ my $pdfx = (system ${latex} . ' --interaction=batchmode "\documentclass{minimal} \RequirePackage{pdfx} \csname @ifpackagelater\endcsname{pdfx}{2018/12/22}{}{\show\relax} \begin{document} \end{document}"');
+
+ print_result ("Looking for pdfx version 2018 or higher", !$pdfx);
+ push @missing_modules, \(name => 'pdfx') if $pdfx;
+
if ($res) {
check_template_dir($_) for SL::InstallationCheck::template_dirs($master_templates);
}
+ print STDERR <<EOL if $pdfx;
++------------------------------------------------------------------------------+
+ Your pdfx version is too old. You cannot use ZuGFeRD or modern (2018+)
+ templates. Please consider using a more recent LaTeX environment.
+ Verify with:
+ pdflatex --interaction=batchmode "\RequirePackage{pdfx}[2018/12/22]"
++------------------------------------------------------------------------------+
+EOL
}
sub check_template_dir {
print_header("Checking LaTeX Dependencies for Master Templates '$dir'");
kpsewhich($path, 'cls', $_) for SL::InstallationCheck::classes_from_latex($path, '\documentclass');
- kpsewhich($path, 'sty', $_) for SL::InstallationCheck::classes_from_latex($path, '\usepackage');
+
+ my @sty = sort { $a cmp $b } uniq (
+ SL::InstallationCheck::classes_from_latex($path, '\usepackage'),
+ qw(textcomp ulem embedfile)
+ );
+ kpsewhich($path, 'sty', $_) for @sty;
}
our $mastertemplate_path = './templates/print/';
my ($label,$version) = split /:/,$shell_out;
if ( $label && $label eq ' AqBanking-CLI' ) {
chop $version;
- print_line($line, $version, 'green');
+ my ($number_version) = $version =~ /(\d+)/;
+ if ($number_version < 6) {
+ print_line($line, "Requires at least version 6, current version is " . $version, 'red');
+ } else {
+ print_line($line, $version, 'green');
+ }
} else {
print_line($line, 'not installed','red');
my %modinfo = ( name => 'aqbanking' );
$role{optional} ? 'It is OPTIONAL for kivitendo but RECOMMENDED for improved functionality.'
: $role{required} ? 'It is NEEDED by kivitendo and must be installed.'
: $role{devel} ? 'It is OPTIONAL for kivitendo and only useful for developers.'
- : 'It is not listed as a dependancy yet. Please tell this the developers.';
+ : 'It is not listed as a dependency yet. Please tell this the developers.';
my @source_texts = module_source_texts($module);
local $" = $/;
=head1 NAME
-scripts/installation_check.pl - check kivitendo dependancies
+scripts/installation_check.pl - check kivitendo dependencies
=head1 SYNOPSIS
=item C<-d, --devel>
-Probe for perl developer dependancies. (Used for console and tags file)
+Probe for perl developer dependencies. (Used for console and tags file)
=item C<--no-devel>
-Don't probe for perl developer dependancies. (Useful in combination with --all)
+Don't probe for perl developer dependencies. (Useful in combination with --all)
=item C<-h, --help>
=item C<-v. --verbose>
-Print additional info for missing dependancies
+Print additional info for missing dependencies
=item C<-i, --install-command>
run_single_job_for_all_clients();
return;
}
+ $::lxdebug->message(LXDebug::INFO(), "The task server for node " . SL::System::TaskServer::node_id() . " is up and running.");
while (1) {
$SIG{'ALRM'} = 'IGNORE';
# @tag: all_drafts_edit
# @description: Zugriffsrecht auf alle Entwürfe
-# @depends: release_3_4_0
+# @depends: release_3_4_0 add_master_rights master_rights_position_gaps
# @locales: Edit all drafts
# @ignore: 0
package SL::DBUpgrade2::Auth::all_drafts_edit;
--- /dev/null
+-- @tag: master_rights_positions_fix
+-- @description: Position in Rechtetabelle korrigieren (falls zutreffend)
+-- @depends: release_3_5_4 purchase_letter_rights all_drafts_edit right_purchase_all_edit rights_sales_purchase_edit_prices
+
+UPDATE auth.master_rights SET position = position/100
+ WHERE position > 10000
+ AND name IN ('purchase_letter_edit', 'purchase_letter_report', 'all_drafts_edit');
+
+UPDATE auth.master_rights SET position = (SELECT position + 10 FROM auth.master_rights WHERE name = 'purchase_letter_edit')
+ WHERE position > 10000
+ AND name LIKE 'purchase_all_edit';
+
+UPDATE auth.master_rights SET position =(SELECT position + 10 FROM auth.master_rights WHERE name = 'purchase_all_edit')
+ WHERE position > 10000
+ AND name LIKE 'purchase_edit_prices';
# @tag: purchase_letter_rights
# @description: Neue Rechte für Lieferantenbriefe
-# @depends: release_3_2_0 sales_letter_rights
+# @depends: release_3_4_0 add_master_rights master_rights_position_gaps
# @locales: Edit purchase letters
# @locales: Show purchase letters report
package SL::DBUpgrade2::Auth::purchase_letter_rights;
--- /dev/null
+-- @tag: release_3_5_5
+-- @description: Abhängigkeitsscript für Release 3.5.5
+-- @depends: release_3_5_4 master_rights_positions_fix
--- /dev/null
+-- @tag: release_3_5_6
+-- @description: Abhängigkeitsscript für Release 3.5.6
+-- @depends: release_3_5_5
--- /dev/null
+-- @tag: release_3_5_6_1
+-- @description: Abhängigkeitsscript für Release 3.5.6.1
+-- @depends: release_3_5_6
--- /dev/null
+-- @tag: right_purchase_all_edit
+-- @description: Recht zum Bearbeiten von Einkaufsdokumenten aller Mitarbeiter (Trennung nach VK u. EK)
+-- @depends: release_3_5_4
+-- @locales: View/edit all employees purchase documents
+
+INSERT INTO auth.master_rights (position, name, description, category)
+ VALUES ((SELECT position + 10 FROM auth.master_rights WHERE name = 'purchase_letter_edit'),
+ 'purchase_all_edit',
+ 'View/edit all employees purchase documents',
+ FALSE);
+
+-- same rights as sales_all_edit because sales and purchase were not distingushed before
+INSERT INTO auth.group_rights (group_id, "right", granted)
+ SELECT group_id, 'purchase_all_edit', granted FROM auth.group_rights WHERE "right" = 'sales_all_edit';
--- /dev/null
+-- @tag: rights_sales_purchase_edit_prices
+-- @description: Recht zum Bearbeiten von Preisen nach Ver- und Einkauf trennen
+-- @depends: release_3_5_4 right_purchase_all_edit
+
+INSERT INTO auth.master_rights (position, name, description, category)
+ VALUES ((SELECT position + 10 FROM auth.master_rights WHERE name = 'purchase_all_edit'),
+ 'purchase_edit_prices',
+ 'Edit prices and discount (if not used, textfield is ONLY set readonly)',
+ FALSE);
+
+-- same rights as edit_prices because sales and purchase were not distingushed before
+INSERT INTO auth.group_rights (group_id, "right", granted)
+ SELECT group_id, 'purchase_edit_prices', granted FROM auth.group_rights WHERE "right" = 'edit_prices';
+
+-- rename "edit_prices" to "sales_edit_prices"
+UPDATE auth.master_rights SET name = 'sales_edit_prices' WHERE name = 'edit_prices';
+UPDATE auth.group_rights SET "right" = 'sales_edit_prices' WHERE "right" = 'edit_prices';
-- @description: Einführen einer ID-Spalte in acc_trans
-- @depends: release_2_4_3 cb_ob_transaction
+-- INFO: Dieses Script hat früher die Spalte acc_trans_id aus der
+-- impliziten OID gesetzt. PostgreSQL 12 unterstützt aber keine OIDs
+-- mehr, daher wurde die OID hier entfernt. Das ist insofern auch kein
+-- Problem, weil dieses Upgrade-Script in Version 2.6.0 benutzt wurde,
+-- und direkte Updates auf die aktuelle kivitendo-Version von vor 3.0
+-- eh nicht mehr unterstützt werden.
+--
+-- Das Script muss aber trotzdem beim Anlegen neuer Datenbanken
+-- abgearbeitet werden und daher funktionieren.
+
CREATE SEQUENCE acc_trans_id_seq;
CREATE TABLE new_acc_trans (
mtime timestamp without time zone
);
-INSERT INTO new_acc_trans (acc_trans_id, trans_id, chart_id, amount, transdate, gldate, source, cleared,
+INSERT INTO new_acc_trans (trans_id, chart_id, amount, transdate, gldate, source, cleared,
fx_transaction, ob_transaction, cb_transaction, project_id, memo, taxkey, itime, mtime)
- SELECT oid, trans_id, chart_id, amount, transdate, gldate, source, cleared,
+ SELECT trans_id, chart_id, amount, transdate, gldate, source, cleared,
fx_transaction, ob_transaction, cb_transaction, project_id, memo, taxkey, itime, mtime
FROM acc_trans;
-SELECT setval('acc_trans_id_seq', (SELECT COALESCE((SELECT MAX(oid::integer) FROM acc_trans), 0) + 1));
-
DROP TABLE acc_trans;
ALTER TABLE new_acc_trans RENAME TO acc_trans;
--- /dev/null
+-- @tag: add_node_id_to_background_jobs
+-- @description: Spalte 'node_id' in 'background_jobs'
+-- @depends: release_3_5_4
+ALTER TABLE background_jobs
+ADD COLUMN node_id TEXT;
--- /dev/null
+-- @tag: alter_default_shipped_qty_config
+-- @description: Mandantenweite Konfiguration für das Verhalten von Liefermengenabgleich
+-- @depends: release_3_5_6
+UPDATE defaults SET shipped_qty_fill_up = 'f';
+
+
--- /dev/null
+-- @tag: ap_set_payment_term_from_vendor
+-- @description: Zahlungsbedingungen in EK-Rechnungen aus Lieferant setzen
+-- @depends: release_3_5_6
+
+UPDATE ap SET payment_id = (SELECT payment_id FROM vendor WHERE vendor.id = ap.vendor_id)
+ WHERE (SELECT payment_id FROM vendor WHERE vendor.id = ap.vendor_id) IS NOT NULL
+ AND ap.payment_id IS NULL;
+++ /dev/null
-# @tag: background_job_change_create_periodic_invoices_to_daily
-# @description: Hintergrundjob zum Erzeugen periodischer Rechnungen täglich ausführen
-# @depends: release_3_0_0
-package SL::DBUpgrade2::background_job_change_create_periodic_invoices_to_daily;
-
-use strict;
-use utf8;
-
-use parent qw(SL::DBUpgrade2::Base);
-
-use SL::DB::BackgroundJob;
-
-sub run {
- my ($self) = @_;
-
- foreach my $job (@{ SL::DB::Manager::BackgroundJob->get_all(where => [ package_name => 'CreatePeriodicInvoices' ]) }) {
- $job->update_attributes(cron_spec => '0 3 * * *', next_run_at => undef);
- }
-
- return 1;
-}
-
-1;
--- /dev/null
+-- @tag: background_job_change_create_periodic_invoices_to_daily
+-- @description: Hintergrundjob zum Erzeugen periodischer Rechnungen täglich ausführen
+-- @depends: release_3_0_0
+UPDATE background_jobs
+SET cron_spec = '0 3 * * *',
+ next_run_at = CAST(current_date AS timestamp) + CAST(
+ (CASE
+ WHEN extract('hour' FROM current_timestamp) < 3 THEN '3 hours'
+ ELSE '1 day 3 hours'
+ END) AS interval
+ )
+WHERE package_name = 'CreatePeriodicInvoices';
+++ /dev/null
-# @tag: background_jobs_3
-# @description: Backgroundjob Cleanup einrichten
-# @depends: emmvee_background_jobs_2
-package SL::DBUpgrade2::background_jobs_3;
-
-use strict;
-use utf8;
-
-use parent qw(SL::DBUpgrade2::Base);
-
-use SL::BackgroundJob::BackgroundJobCleanup;
-
-sub run {
- SL::BackgroundJob::BackgroundJobCleanup->create_job;
- return 1;
-}
-
-1;
--- /dev/null
+-- @tag: background_jobs_3
+-- @description: Backgroundjob Cleanup einrichten
+-- @depends: emmvee_background_jobs_2
+INSERT INTO background_jobs (type, package_name, active, cron_spec, next_run_at)
+VALUES ('interval', 'BackgroundJobCleanup', true, '0 3 * * *',
+ CAST(current_date AS timestamp) + CAST(
+ (CASE
+ WHEN extract('hour' FROM current_timestamp) < 3 THEN '3 hours'
+ ELSE '1 day 3 hours'
+ END) AS interval
+ )
+);
+++ /dev/null
-# @tag: background_jobs_clean_auth_sessions
-# @description: Hintergrundjob zum Löschen abgelaufener Sessions
-# @depends: release_3_1_0
-package SL::DBUpgrade2::background_jobs_clean_auth_sessions;
-
-use strict;
-use utf8;
-
-use parent qw(SL::DBUpgrade2::Base);
-
-use SL::BackgroundJob::CleanAuthSessions;
-
-sub run {
- my ($self) = @_;
-
- SL::BackgroundJob::CleanAuthSessions->create_job;
-
- return 1;
-}
-
-1;
--- /dev/null
+-- @tag: background_jobs_clean_auth_sessions
+-- @description: Hintergrundjob zum Löschen abgelaufener Sessions
+-- @depends: release_3_1_0
+INSERT INTO background_jobs (type, package_name, active, cron_spec, next_run_at)
+VALUES ('interval', 'CleanAuthSessions', true, '30 6 * * *',
+ CAST(current_date AS timestamp) + CAST(
+ (CASE
+ WHEN extract('hour' FROM current_timestamp) < 6 THEN '6 hours 30 minutes'
+ ELSE '1 day 6 hours 30 minutes'
+ END) AS interval
+ )
+);
--- /dev/null
+-- @tag: bank_account_flag_for_zugferd_usage
+-- @description: Bankkonto für die Nutzung mit ZUGFeRD markieren
+-- @depends: release_3_5_5
+ALTER TABLE bank_accounts
+ADD COLUMN use_for_zugferd BOOLEAN;
+
+UPDATE bank_accounts
+SET use_for_zugferd = (
+ SELECT COUNT(*)
+ FROM bank_accounts
+) = 1;
+
+ALTER TABLE bank_accounts
+ALTER COLUMN use_for_zugferd SET DEFAULT FALSE,
+ALTER COLUMN use_for_zugferd SET NOT NULL;
--- /dev/null
+-- @tag: bank_transaction_acc_trans_remove_wrong_primary_key
+-- @description: bank_transaction_acc_trans_remove_wrong_primary_key
+-- @depends: release_3_5_4
+ALTER TABLE bank_transaction_acc_trans
+DROP COLUMN id;
--- /dev/null
+-- @tag: bank_transactions_nuke_trailing_spaces_in_purpose
+-- @description: Banktransaktionen: überflüssige Leerzeichen am Ende des Verwendungszwecks entfernen
+-- @depends: release_3_5_4
+UPDATE bank_transactions
+SET purpose = regexp_replace(purpose, ' +$', '')
+WHERE purpose ~ ' +$';
--- /dev/null
+-- @tag: contact_departments_own_table
+-- @description: Eigene Tabelle für Abteilungen bei Ansprechpersonen
+-- @depends: release_3_5_5
+
+CREATE TABLE contact_departments (
+ id SERIAL,
+ description TEXT NOT NULL,
+ PRIMARY KEY (id),
+ UNIQUE (description)
+);
+
+UPDATE contacts SET cp_abteilung = trim(cp_abteilung) WHERE cp_abteilung NOT LIKE trim(cp_abteilung);
+
+INSERT INTO contact_departments (description)
+ SELECT DISTINCT cp_abteilung FROM contacts WHERE cp_abteilung IS NOT NULL AND cp_abteilung NOT LIKE '' ORDER BY cp_abteilung;
--- /dev/null
+-- @tag: contact_titles_own_table
+-- @description: Eigene Tabelle für Titel bei Ansprechpersonen
+-- @depends: release_3_5_5
+
+CREATE TABLE contact_titles (
+ id SERIAL,
+ description TEXT NOT NULL,
+ PRIMARY KEY (id),
+ UNIQUE (description)
+);
+
+UPDATE contacts SET cp_title = trim(cp_title) WHERE cp_title NOT LIKE trim(cp_title);
+
+INSERT INTO contact_titles (description)
+ SELECT DISTINCT cp_title FROM contacts WHERE cp_title IS NOT NULL AND cp_title NOT LIKE '' ORDER BY cp_title;
--- /dev/null
+-- @tag: customer_create_zugferd_invoices
+-- @description: Kundenstammdaten: Einstellungen für ZUGFeRD-Rechnungen
+-- @depends: release_3_5_5
+ALTER TABLE customer
+ADD COLUMN create_zugferd_invoices INTEGER
+DEFAULT -1 NOT NULL;
--- /dev/null
+-- @tag: customer_vendor_add_natural_person
+-- @description: neue Spalte für "natürliche Person" bei Kunden/Lieferanten
+-- @depends: release_3_5_5
+
+ALTER TABLE customer ADD COLUMN natural_person BOOLEAN DEFAULT FALSE;
+ALTER TABLE vendor ADD COLUMN natural_person BOOLEAN DEFAULT FALSE;
--- /dev/null
+-- @tag: defaults_contact_departments_use_textfield
+-- @description: Auswahl, ob Freitext-Feld für Abteilungen bei Ansprechpersonen im Kunden-/Lieferantenstamm angeboten wird
+-- @depends: release_3_5_5
+
+ALTER TABLE defaults ADD COLUMN contact_departments_use_textfield BOOLEAN;
+UPDATE defaults SET contact_departments_use_textfield = TRUE;
--- /dev/null
+-- @tag: defaults_contact_titles_use_textfield
+-- @description: Auswahl, ob Freitext-Feld für Titel von Ansprechpersonen im Kunden-/Lieferantenstamm angeboten wird
+-- @depends: release_3_5_5
+
+ALTER TABLE defaults ADD COLUMN contact_titles_use_textfield BOOLEAN;
+UPDATE defaults SET contact_titles_use_textfield = TRUE;
--- /dev/null
+-- @tag: defaults_create_zugferd_data
+-- @description: ZUGFeRD-Informationserzeugung option abstellen
+-- @depends: release_3_5_5
+ALTER TABLE defaults ADD COLUMN create_zugferd_invoices BOOLEAN;
+UPDATE defaults SET create_zugferd_invoices = TRUE;
--- /dev/null
+# @tag: defaults_split_address
+# @description: Adress-Feld in Mandantenkonfiguration in einzelne Bestandteile aufteilen
+# @depends: release_3_5_4
+package SL::DBUpgrade2::defaults_split_address;
+
+use strict;
+use utf8;
+
+use parent qw(SL::DBUpgrade2::Base);
+
+sub run {
+ my ($self) = @_;
+
+ my ($address) = $self->dbh->selectrow_array("SELECT address FROM defaults");
+
+ my (@street, $zipcode, $city, $country);
+ my @lines = grep { $_ } split m{\r*\n+}, $address // '';
+
+ foreach my $line (@lines) {
+ if ($line =~ m{^(?:[a-z]+[ -])?(\d+) +(.+)}i) {
+ ($zipcode, $city) = ($1, $2);
+
+ } elsif ($zipcode) {
+ $country = $line;
+
+ } else {
+ push @street, $line;
+ }
+ }
+
+ $self->db_query(<<SQL);
+ ALTER TABLE defaults
+ ADD COLUMN address_street1 TEXT,
+ ADD COLUMN address_street2 TEXT,
+ ADD COLUMN address_zipcode TEXT,
+ ADD COLUMN address_city TEXT,
+ ADD COLUMN address_country TEXT,
+ DROP COLUMN address
+SQL
+
+ $self->db_query(<<SQL, bind => [ map { $_ // '' } ($street[0], $street[1], $zipcode, $city, $country) ]);
+ UPDATE defaults
+ SET address_street1 = ?,
+ address_street2 = ?,
+ address_zipcode = ?,
+ address_city = ?,
+ address_country = ?
+SQL
+
+ return 1;
+}
+
+1;
--- /dev/null
+-- @tag: defaults_vc_greetings_use_textfield
+-- @description: Auswahl, ob Freitext-Feld für Anrede im Kunden-/Lieferantenstamm angeboten wird
+-- @depends: release_3_5_5
+
+ALTER TABLE defaults ADD COLUMN vc_greetings_use_textfield BOOLEAN;
+UPDATE defaults SET vc_greetings_use_textfield = TRUE;
--- /dev/null
+-- @tag: defaults_workflow_po_ap_chart_id
+-- @description: Voreingestelltes Konto für Workflow Lieferantenauftrag -> Kreditorenbuchung
+-- @depends: release_3_5_4
+
+ALTER TABLE defaults ADD COLUMN workflow_po_ap_chart_id INTEGER;
--- /dev/null
+-- @tag: defaults_year_end_charts
+-- @description: Standardkonten für Jahresabschluß
+-- @depends: release_3_5_4
+
+ALTER TABLE defaults ADD COLUMN carry_over_account_chart_id INTEGER REFERENCES chart(id);
+ALTER TABLE defaults ADD COLUMN profit_carried_forward_chart_id INTEGER REFERENCES chart(id);
+ALTER TABLE defaults ADD COLUMN loss_carried_forward_chart_id INTEGER REFERENCES chart(id);
--- /dev/null
+-- @tag: defaults_zugferd_test_mode
+-- @description: ZUGFeRD optional nur im Test-Modus
+-- @depends: defaults_create_zugferd_data
+ALTER TABLE defaults
+ALTER COLUMN create_zugferd_invoices TYPE INTEGER
+USING create_zugferd_invoices::INTEGER;
--- /dev/null
+-- @tag: delete_cvars_on_trans_deletion_add_shipto
+-- @description: Löschen von benutzerdefinierten Variablen via Triggerfunktionen auch für shipto
+-- @depends: delete_cvars_on_trans_deletion delete_cvars_on_trans_deletion_fix1
+
+-- 1.6 Alle benutzerdefinierten Variablen löschen, für die es keine
+-- Einträge in shipto mehr gibt.
+DELETE FROM custom_variables WHERE EXISTS
+ (SELECT cv.id FROM custom_variables cv LEFT JOIN custom_variable_configs cvc ON (cv.config_id = cvc.id)
+ WHERE module LIKE 'ShipTo'
+ AND NOT EXISTS (SELECT shipto_id FROM shipto WHERE shipto_id = cv.trans_id));
+
+
+-- 2.2. Nun die Funktionen, die als Trigger aufgerufen wird und die
+-- entscheidet, wie genau zu löschen ist:
+CREATE OR REPLACE FUNCTION delete_custom_variables_trigger()
+RETURNS TRIGGER AS $$
+ BEGIN
+ IF (TG_TABLE_NAME IN ('orderitems', 'delivery_order_items', 'invoice')) THEN
+ PERFORM delete_custom_variables_with_sub_module('IC', TG_TABLE_NAME, old.id);
+ END IF;
+
+ IF (TG_TABLE_NAME = 'parts') THEN
+ PERFORM delete_custom_variables_with_sub_module('IC', '', old.id);
+ END IF;
+
+ IF (TG_TABLE_NAME IN ('customer', 'vendor')) THEN
+ PERFORM delete_custom_variables_with_sub_module('CT', '', old.id);
+ END IF;
+
+ IF (TG_TABLE_NAME = 'contacts') THEN
+ PERFORM delete_custom_variables_with_sub_module('Contacts', '', old.cp_id);
+ END IF;
+
+ IF (TG_TABLE_NAME = 'project') THEN
+ PERFORM delete_custom_variables_with_sub_module('Projects', '', old.id);
+ END IF;
+
+ IF (TG_TABLE_NAME = 'shipto') THEN
+ PERFORM delete_custom_variables_with_sub_module('ShipTo', '', old.shipto_id);
+ END IF;
+
+ RETURN old;
+ END;
+$$ LANGUAGE plpgsql;
+
+-- 3. Die eigentlichen Trigger erstellen:
+
+-- 3.9. shipto
+DROP TRIGGER IF EXISTS shipto_delete_custom_variables_after_deletion ON shipto;
+
+CREATE TRIGGER shipto_delete_custom_variables_after_deletion
+AFTER DELETE ON shipto
+FOR EACH ROW EXECUTE PROCEDURE delete_custom_variables_trigger();
FOREIGN KEY (parts_id) REFERENCES parts (id),
FOREIGN KEY (project_id) REFERENCES project (id),
FOREIGN KEY (price_factor_id) REFERENCES price_factors (id)
-) WITH OIDS;
+);
CREATE TRIGGER mtime_delivery_order_items_id BEFORE UPDATE ON delivery_order_items
FOR EACH ROW EXECUTE PROCEDURE set_mtime();
--- /dev/null
+-- @tag: dunning_config_print_original_invoice
+-- @description: Optional die Originalrechnung bei Zahlungserinnerungen ausdrucken
+-- @depends: release_3_5_5
+ALTER TABLE dunning_config ADD COLUMN print_original_invoice boolean;
+
+++ /dev/null
-# @tag: emmvee_background_jobs_2
-# @description: Hintergrundjobs einrichten
-# @depends: emmvee_background_jobs
-package SL::DBUpgrade2::emmvee_background_jobs_2;
-
-use strict;
-use utf8;
-
-use parent qw(SL::DBUpgrade2::Base);
-
-use SL::BackgroundJob::CleanBackgroundJobHistory;
-
-sub run {
- SL::BackgroundJob::CleanBackgroundJobHistory->create_job;
- return 1;
-}
-
-1;
--- /dev/null
+-- @tag: emmvee_background_jobs_2
+-- @description: Hintergrundjobs einrichten
+-- @depends: emmvee_background_jobs
+INSERT INTO background_jobs (type, package_name, active, cron_spec, next_run_at)
+VALUES ('interval', 'CleanBackgroundJobHistory', true, '0 3 * * *',
+ CAST(current_date AS timestamp) + CAST(
+ (CASE
+ WHEN extract('hour' FROM current_timestamp) < 3 THEN '3 hours'
+ ELSE '1 day 3 hours'
+ END) AS interval
+ )
+);
--- /dev/null
+-- @tag: exchangerate_in_oe
+-- @description: Wechselkurs pro Angebot/Auftrag in Belegtabelle speichern
+-- @depends: release_3_5_5
+
+ALTER TABLE oe ADD COLUMN exchangerate NUMERIC(15,5);
+
+WITH table_ex AS
+ (SELECT oe.id, COALESCE(CASE WHEN customer_id IS NOT NULL THEN buy ELSE sell END, 1.0) AS exchangerate FROM oe
+ LEFT JOIN exchangerate ON (oe.transdate = exchangerate.transdate AND oe.currency_id = exchangerate.currency_id)
+ WHERE oe.currency_id != (SELECT currency_id FROM defaults))
+ UPDATE oe SET exchangerate = (SELECT exchangerate FROM table_ex WHERE table_ex.id = oe.id)
+ WHERE EXISTS (SELECT table_ex.exchangerate FROM table_ex WHERE table_ex.id = oe.id);
--- /dev/null
+-- @tag: gl_add_deliverydate
+-- @description: Liefer-/Leistungsdatum in Dialogbuchungen
+-- @depends: release_3_5_5
+
+ALTER TABLE gl ADD COLUMN deliverydate DATE;
--- /dev/null
+-- @tag: greetings_own_table
+-- @description: Eigene Tabelle für Anreden
+-- @depends: release_3_5_5
+
+CREATE TABLE greetings (
+ id SERIAL,
+ description TEXT NOT NULL,
+ PRIMARY KEY (id),
+ UNIQUE (description)
+);
+
+UPDATE customer SET greeting = trim(greeting) WHERE greeting NOT LIKE trim(greeting);
+UPDATE vendor SET greeting = trim(greeting) WHERE greeting NOT LIKE trim(greeting);
+
+INSERT INTO greetings (description)
+ SELECT DISTINCT greeting FROM (SELECT greeting FROM customer UNION SELECT greeting FROM vendor) AS gr WHERE greeting IS NOT NULL AND greeting NOT LIKE '' ORDER BY greeting;
--- /dev/null
+-- @tag: inventory_itime_parts_id_index
+-- @description: Index auf inventory itime und parts_id, um schnell die letzten Transaktion raussuchen zu können
+-- @depends: release_3_5_4
+
+-- increase speed of queries such as
+
+-- last 10 entries in inventory:
+-- SELECT * FROM inventory ORDER BY itime desc LIMIT 10
+
+-- last 10 inventory entries for a certain part:
+-- SELECT * FROM inventory WHERE parts_id = 1234 ORDER BY itime desc LIMIT 10
+
+CREATE INDEX inventory_itime_parts_id_idx ON inventory (itime, parts_id);
--- /dev/null
+-- @tag: inventory_parts_id_index
+-- @description: Index auf inventory parts_id, um schneller die Bestände eines Artikels in diversen Lagern zu berechnen
+-- @depends: release_3_5_4
+
+-- increase speed of queries for inventory information on one part, e.g.
+
+-- SELECT parts_id, warehouse_id, bin_id, sum(qty)
+-- FROM inventory
+-- WHERE parts_id = 1234
+-- GROUP BY parts_id, bin_id, warehouse_id;
+
+CREATE INDEX inventory_parts_id_idx ON inventory (parts_id);
--- /dev/null
+-- @tag: konjunkturpaket_2020
+-- @description: Anpassung der Steuersätze für 16%/5% für Deutsche DATEV-Kontenrahmen SKR03 und SKR04
+-- @depends: release_3_5_5 konjunkturpaket_2020_SKR03 konjunkturpaket_2020_SKR04
+-- @ignore: 0
+
+-- begin;
+
+DO $$
+
+DECLARE
+ -- variables for main taxkey creation loop, not all are needed
+ _chart_id int;
+ _accno text;
+ _description text;
+ _startdates date[];
+ _tax_ids int[];
+ _taxkeyentry_id int[];
+ _taxkey_ids int[];
+ _rates numeric[];
+ _taxcharts text[];
+
+ current_taxkey record;
+ new_taxkey record;
+ _rate numeric;
+ _tax record; -- store the new tax we need to assign to a chart, e.g. 5%, 16%
+
+ _taxkey int;
+ _old_rate numeric;
+ _old_chart text;
+ _new_chart numeric;
+ _new_rate text;
+
+ _tax_conversion record;
+
+
+BEGIN
+
+IF ( select coa from defaults ) ~ 'DATEV' THEN
+
+--begin;
+--delete from taxkeys where startdate >= '2020-01-01';
+
+-- create temp table temp_taxkey_conversions (taxkey int, old_rate numeric, new_rate numeric, tax_chart_skr03 text, tax_chart_skr04 text);
+-- insert into temp_taxkey_conversions (taxkey, old_rate, new_rate, tax_chart_skr03, tax_chart_skr04) values
+---- (2, 0.07, 0.05, '1773', '3803'), -- 5% case is handled by skr03 case -> needs different automatic chart: 1773 Umsatzsteuer 5% (SKR03, instead of 1771 Umsatzsteuer 7%) or 3803 Umsatzsteuer 5%
+-- -- (8, 0.07, 0.05, null, null),
+-- -- (3, 0.19, 0.16, null, null),
+-- -- (9, 0.19, 0.16, null, null),
+-- (13, 0.19, 0.16, null, null);
+
+
+ create temp table temp_taxkey_conversions (taxkey int, old_rate numeric, old_chart text, new_rate numeric, new_chart text);
+
+ IF ( select coa from defaults ) = 'Germany-DATEV-SKR03EU' THEN
+ insert into temp_taxkey_conversions (taxkey, old_rate, old_chart, new_rate, new_chart)
+ values (9, 0.19, '1576', 0.16, '1575'),
+ (8, 0.07, '1571', 0.05, '1568'),
+ (3, 0.19, '1776', 0.16, '1575'),
+ (2, 0.07, '1771', 0.05, '1775');
+ --1776 => 19%
+ --1775 => 16%
+ --1775 => 5%
+ --1771 => 7%
+ --
+ --VSt:
+ --1576 => 19%
+ --1575 => 16%
+ --1568 => 5%
+ --1571 => 7%
+
+ ELSE -- Germany-DATEV-SKR04EU
+ insert into temp_taxkey_conversions (taxkey, old_rate, old_chart, new_rate, new_chart)
+ values (9, 0.19, '1406', 0.16, '1405'),
+ (8, 0.07, '1401', 0.05, '1403'),
+ (3, 0.19, '3806', 0.16, '3805'),
+ (2, 0.07, '3801', 0.05, '3803');
+ END IF;
+
+ FOR _chart_id, _accno, _description, _startdates, _tax_ids, _taxkeyentry_id, _taxkey_ids, _rates, _taxcharts IN
+
+ select c.id as chart_id,
+ c.accno,
+ c.description,
+ array_agg(t.startdate order by t.startdate desc) as startdates,
+ array_agg(t.tax_id order by t.startdate desc) as tax_ids,
+ array_agg(t.id order by t.startdate desc) as taxkeyentry_id,
+ array_agg(t.taxkey_id order by t.startdate desc) as taxkey_ids,
+ array_agg(tax.rate order by t.startdate desc) as rates,
+ array_agg(tc.accno order by t.startdate desc) as taxcharts
+ from taxkeys t
+ left join chart c on (c.id = t.chart_id)
+ left join tax on (tax.id = t.tax_id)
+ left join chart tc on (tax.chart_id = tc.id)
+ where t.taxkey_id in (select taxkey from temp_taxkey_conversions) -- 2, 3, 8, 9
+ -- and (c.accno = '8400') -- debug
+ -- you can't filter for valid taxrates 19% or 7% here, as that would still leave the 16% rates as the current one
+ group by c.id,
+ c.accno,
+ c.description
+ order by c.accno
+
+ -- example output for human debugging:
+ -- chart_id | accno | description | startdates | tax_ids | taxkeyentry_id | taxkey_ids | rates | taxcharts
+ -- ----------+-------+---------------------+-------------------------+-----------+----------------+------------+-------------------+-------------
+ -- 184 | 8400 | Erlöse 16%/19% USt. | {2007-01-01,1970-01-01} | {777,379} | {793,676} | {3,3} | {0.19000,0.16000} | {1776,1775}
+
+ -- each chart with one of the applicable taxkeys should receive two new entries, one starting on 01.07.2020, the other on 01.01.2021
+ LOOP
+ -- 1. create new taxkey entry on 2020-07-01, using the active taxkey on 2020-06-30 as a template, but linking to a tax with a different tax rate
+ -- 2. create new taxkey entry on 2021-01-01, using the active taxkey on 2020-06-30 as a template, but with the new date
+
+
+ -- fetch tax information for 2020-06-30, one day before the change, this should also be the first entry in the ordered array aggregates
+ -- this can be used as the template for the reset on 2021-01-01
+
+ -- raise notice 'looking up current taxkey for chart % and taxkey %', (select accno from chart where id = _chart_id), _taxkey_ids[1];
+ select into current_taxkey tk.*, t.rate, t.taxkey
+ from taxkeys tk
+ left join tax t on (t.id = tk.tax_id)
+ where tk.taxkey_id = _taxkey_ids[1] -- assume taxkey never changed, use the first one
+ and tk.chart_id = _chart_id
+ and tk.startdate <= '2020-06-30'
+ order by tk.startdate desc
+ limit 1;
+ -- RAISE NOTICE 'found current_taxkey = %', current_taxkey;
+ IF current_taxkey is null then continue; end if;
+ -- RAISE NOTICE 'found chart % with current startdate % and taxkey % (current: %), rate = %', _accno, current_taxkey.startdate, _taxkey_ids[1], current_taxkey.taxkey, current_taxkey.rate;
+
+ -- RAISE NOTICE 'current_taxkey = %', current_taxkey;
+ -- RAISE NOTICE 'looking up tkc for chart_id % and taxkey %', _chart_id, current_taxkey.taxkey;
+
+ select into _taxkey, _old_rate, _old_chart, _new_chart, _new_rate
+ taxkey, old_rate, old_chart, new_chart, new_rate
+ from temp_taxkey_conversions tkc
+ where tkc.taxkey = current_taxkey.taxkey
+ and tkc.old_rate = current_taxkey.rate;
+ -- and tkc.new_chart = current_taxkey.new_chart;
+
+ -- raise notice '_old_rate = %, _new_rate = %', _old_rate, _new_rate;
+
+ -- don't do anything if current taxrate is 0, which might be the case for taxkey 13, if they were configured in that way
+ IF current_taxkey.rate != 0 THEN -- debug
+
+ -- _rate := null;
+
+ -- IF current_taxkey.rate = 0.19 THEN _rate := 0.16; END IF;
+ -- IF current_taxkey.rate = 0.07 THEN _rate := 0.05; END IF;
+ IF _old_rate is NULL THEN
+
+ -- option A: ignore rates which don't make sense, useful for upgrade mode
+ -- option B: throw exception, useful for manually testing script
+
+ -- A:
+ -- if the rate on 2020-06-30 is neither 19 or 7, simply ignore it, it is obviously not configured correctly
+ -- This is the case for SKR03 and chart 8315 (taxkey 13)
+ -- It might be better to throw an exception, however then the test cases don't run. Or just fix the chart via an upgrade script!
+ CONTINUE;
+
+ -- B:
+ -- RAISE EXCEPTION 'illegal current taxrate % on 2020-06-30 (startdate = %) for chart % with taxkey %, should be either 0.19 or 0.07',
+ -- current_taxkey.rate, current_taxkey.startdate,
+ -- (select accno from chart where id = current_taxkey.chart_id),
+ -- current_taxkey.taxkey_id;
+ END IF;
+ -- RAISE NOTICE 'current_taxkey.rate = %, desired rate = %, looking for taxkey_id %', current_taxkey.rate, _rate, _taxkey_ids[1];
+
+ -- if a chart was created way after 2007 and only ever configured for
+ -- 19%, never 16%, which is the case for SKR04 and taxkey 13, there will only be 3
+ -- taxkeys per chart after adding the two new ones
+
+ -- RAISE NOTICE 'searching for tax with taxkey % and rate %', _taxkey_ids[1], _rate;
+ select into _tax
+ *
+ from tax
+ where tax.rate = _old_rate
+ and tax.taxkey = _taxkey_ids[1]
+ order by itime desc
+ limit 1; -- look up tax with same taxkey but corresponding rate. As there will now be two entries for e.g. taxkey 9 with rate of 0.16, the old pre-2007 entry and the new 2020-entry. They can only be differentiated by their (automatic tax) chart_id, or during this upgrade script, via itime, use the later one
+ -- this also assumes taxkeys never change
+ -- RAISE NOTICE 'tax = %', _tax;
+
+ -- insert into taxkeys (chart_id, tax_id, taxkey_id, pos_ustva, startdate)
+ -- values ( (select id from chart where accno = 'kkkkgtkttttkk current_taxkey.chart_id, _tax.id, _tax.taxkey, current_taxkey.pos_ustva, '2020-07-01');
+ END IF;
+
+ -- raise notice 'inserting taxkey';
+ insert into taxkeys (chart_id, tax_id, taxkey_id, pos_ustva, startdate )
+ values (_chart_id,
+ (select id from tax where taxkey = current_taxkey.taxkey and rate = _new_rate::numeric),
+ current_taxkey.taxkey, -- 2, 3, 8, 9
+ current_taxkey.pos_ustva, '2020-07-01');
+
+ -- finally insert a copy of the taxkey on 2020-06-30 with the new startdate 2021-01-01, thereby resetting the tax rates again
+ insert into taxkeys (chart_id, tax_id, taxkey_id, pos_ustva, startdate)
+ values (_chart_id,
+ current_taxkey.tax_id,
+ current_taxkey.taxkey,
+ current_taxkey.pos_ustva, '2021-01-01');
+
+ -- RAISE NOTICE 'inserted 2 taxkeys for chart % with taxkey %', (select accno from chart where id = current_taxkey.chart_id), current_taxkey.taxkey_id;
+ END LOOP; --
+
+ drop table temp_taxkey_conversions;
+
+END IF;
+
+END $$;
+
+-- select * from taxkeys where startdate >= '2020-01-01';
+-- rollback;
--- /dev/null
+-- @tag: konjunkturpaket_2020_SKR03-korrekturen
+-- @description: Steuerkonten haben selber keine Steuerautomatik. USTVA-Felder korrigieren
+-- @depends: konjunkturpaket_2020_SKR03 konjunkturpaket_2020
+-- @ignore: 0
+
+DO $$
+BEGIN
+
+IF ( select coa from defaults ) = 'Germany-DATEV-SKR03EU' THEN
+
+ -- DEBUG
+ -- Konto 1771 ist in DATEV vom Typ S und hat keine Steuerautomatik S 1771 Umsatzsteuer 7 %
+ -- Weitere Liste Konten von diesem (s.u.) -> Steuerkonten haben selber keine Automatik
+ -- Der Eintrag wird leider für die pos_ustva benötigt (die könnte besser in tabelle tax sein)
+ -- S 1771 Umsatzsteuer 7 %
+ -- S 1772 Umsatzsteuer aus innergemeinschaftlichem Erwerb
+ -- S 1774 Umsatzsteuer aus innergemeinschaftlichem Erwerb 19 %
+ -- S 1775 Umsatzsteuer 16 %
+ -- S 1776 Umsatzsteuer 19 %
+ -- S 1777 Umsatzsteuer aus im Inland steuerpflichtigen EU-Lieferungen
+ -- S 1778 Umsatzsteuer aus im Inland steuerpflichtigen EU-Lieferungen 19 %
+ -- S 1779 Umsatzsteuer aus innergemeinschaftlichem Erwerb ohne Vorsteuerabzug
+ UPDATE taxkeys SET tax_id=0,taxkey_id=0 WHERE chart_id IN
+ (SELECT id FROM chart WHERE accno in ('1771','1772','1774','1775','1776','1777','1778','1779'));
+ -- Alle temporären Steuer auf Pos. 36
+ UPDATE taxkeys SET pos_ustva=36 WHERE chart_id IN
+ (SELECT id FROM chart WHERE accno in ('1773'));
+
+ -- Alle temporären 5% und 16% Erlöskonten auf Pos. 35
+ -- select accno from chart where id in (select chart_id from taxkeys where tax_id in (select id from tax where taxkey=2 and rate=0.05) and pos_ustva=86) order by accno;
+ -- accno
+ -- 2401 8300 8506 8591 8710 8731 8750 8780 8915 8930 8945
+ UPDATE taxkeys SET pos_ustva=35 WHERE tax_id in (SELECT id FROM tax WHERE taxkey=2 AND rate=0.05) AND pos_ustva=86;
+ -- select accno from chart where id in (select chart_id from taxkeys where tax_id in (select id from tax where taxkey=3 and rate=0.16) and pos_ustva=81) order by accno;
+ -- accno
+ -- 2405 2700 2750 8400 8500 8508 8540 8595 8600 8720 8735 8736 8760 8790 8800 8801 8820 8910 8920 8925 8935 8940
+ UPDATE taxkeys SET pos_ustva=35 WHERE tax_id in (SELECT id FROM tax WHERE taxkey=3 AND rate=0.16) and pos_ustva=81;
+
+END IF;
+
+END $$;
--- /dev/null
+-- @tag: konjunkturpaket_2020_SKR03
+-- @description: Anpassung des Deutschen DATEV-Kontenrahmen für SKR03 Konjunkturpaket
+-- @depends: release_3_5_5
+-- @ignore: 0
+
+DO $$
+BEGIN
+
+IF ( select coa from defaults ) = 'Germany-DATEV-SKR03EU' THEN
+
+ -- DEBUG
+ -- UPDATE tax SET taxdescription = 'OLD ' || taxdescription WHERE (taxkey = 3 or taxkey = 9) and rate = 0.16;
+
+ -- rename some of the charts, 1773 already exists in kivitendo as Umsatzsteuer 16% innergem.Erwerb
+ -- this is being used by taxkey 13, which is called "Steuerpflichtige EG-Lieferung zum vollen Steuersatz" in kivitendo
+ -- in DATEV taxkey 13 is: innergem. Lieferung ohne USt-IdNr. and should use a different chart
+ UPDATE chart SET description = 'Umsatzsteuer 5 %' where accno = '1773';
+
+ -- rename charts if they weren't already changed
+ UPDATE chart SET description = 'Erlöse 19 % / 16 % USt' where accno = '8400' and description = 'Erlöse 16%/19% USt.';
+ UPDATE chart SET description = 'Erlöse 7 % / 5 % USt' where accno = '8300' and description = 'Erlöse 7%USt';
+
+ -- there are two strategies for updating the taxkeys.
+
+ -- 1) in any case we need to add the 2 new cases for 5%: 2/0.05/1773 and 8/0.05/1568
+
+ -- 2) default kivi SKR03 already has the correct configuration for 16%, with two entries 3/0.16/1775 and 9/0.16/1575
+ -- a) we could move those to 5 and 7, and then create new 3/0.16/1775 and 9/0.16/1575 entries
+ -- b) simply keep those entries and don't use 5 and 7 (in which case ar/ap/gl must use deliverydate), or create 5 and 7 manually if needed
+
+ -- strategy a:
+ -- datev reactivated the previously reserved chart 1775 in 2020, but it still exists in kivitendo (at least for SKR03)
+ -- with a taxkey starting from 2007 and pointing to the existing automatic tax chart 1775
+
+ -- strategy b:
+ -- UPDATE tax SET taxkey = 5 WHERE taxkey = 3 and rate = 0.16;
+ -- UPDATE tax SET taxkey = 7 WHERE taxkey = 9 and rate = 0.16;
+
+ -- rename old 8735 to 8736
+ UPDATE chart SET accno = '8736', description = 'Gewährte Skonti 19 % USt' where accno = '8735' and description = 'Gewährte Skonti 16%/19% USt.';
+
+ -- new charts, each of these will need a manual taxkey entry for 2020-07-01 after their tax entries are added
+ -- 8732, 3732, 8735, 3737
+ INSERT INTO chart (accno, description, charttype, category, link, taxkey_id, pos_bwa, pos_bilanz, pos_eur, datevautomatik)
+ VALUES ('8732','Gewährte Skonti 5% USt','A', 'I', 'AR_paid', 2, 1, null,1, 't');
+
+ INSERT INTO chart (accno, description, charttype, category, link, taxkey_id, pos_bwa, pos_bilanz, pos_eur, datevautomatik)
+ VALUES ('3732','Erhaltene Skonti 5 % Vorsteuer','A', 'E', 'AP_paid', 8, 4, null, null, 't');
+
+ -- create new 16% charts Skonto
+ INSERT INTO chart(accno, description, charttype, category, link, taxkey_id, pos_bwa, pos_bilanz, pos_eur, datevautomatik, pos_er)
+ VALUES ('8735','Gewährte Skonti 16 % USt', 'A', 'I', 'AR_paid', 3, 1, null, 1, 't', 1);
+
+ INSERT INTO chart(accno, description, charttype, category, link, taxkey_id, pos_bwa, pos_bilanz, pos_eur, datevautomatik, pos_er)
+ VALUES ('3737','Erhaltene Skonti 16 % USt', 'A', 'E', 'AP_paid', 9, 4, null, null, 't', null);
+
+ -- create new chart for Abziehbare Vorsteuer 5 % with taxkey 8 for 3732
+ INSERT INTO chart (accno, description, charttype, category, link, taxkey_id, pos_bwa, pos_bilanz, pos_eur, datevautomatik, pos_er)
+ VALUES ('1568','Abziehbare Vorsteuer 5 %','A', 'E', 'AP_tax:IC_taxpart:IC_taxservice', 8, null, null, 27, 't', 27);
+ INSERT INTO taxkeys (chart_id, tax_id, taxkey_id, pos_ustva, startdate)
+ VALUES ( (select id from chart where accno = '1568'), 0, 0, 66, '1970-01-01');
+
+ -- taxkeys can't be inserted until the new taxes exist
+
+ -- new taxes:
+ -- 5% cases for 2 Umsatzsteuer and 8 Vorsteuer
+ INSERT INTO tax (chart_id, rate, taxkey, taxdescription, chart_categories, skonto_sales_chart_id, skonto_purchase_chart_id)
+ VALUES ( (select id from chart where accno = '1773'), 0.05, 2, 'Umsatzsteuer', 'I', (select id from chart where accno = '8732'), null),
+ -- don't add these two entries if we keep the original two 16% accounts, instead better to add new tax entries with taxkey 5 and 7
+ -- ( (select id from chart where accno = '1775'), 0.16, 3, 'Umsatzsteuer', 'I', (select id from chart where accno = '8735'), null),
+ -- ( (select id from chart where accno = '1575'), 0.16, 9, 'Vorsteuer', 'E', null, (select id from chart where accno = '3735')),
+ ( (select id from chart where accno = '1568'), 0.05, 8, 'Vorsteuer', 'E', null, (select id from chart where accno = '3732'));
+
+ UPDATE tax SET skonto_sales_chart_id = (select id from chart where accno = '8735') where taxkey = 3 and rate = 0.16 and skonto_sales_chart_id is null;
+ UPDATE tax SET skonto_purchase_chart_id = (select id from chart where accno = '3737') where taxkey = 9 and rate = 0.16 and skonto_purchase_chart_id is null;
+
+ -- new taxkeys for 5% charts only need one startdate, not valid before and won't change back to anything later
+ -- these taxkeys won't be valid on 2020-06-30, so won't be affected later by big taxkeys update
+ -- However, this will also cause opening the charts before 2020-07-01 via the
+ -- interface to break, as AM.pm always calls get_active_taxkey and there won't
+ -- be an active taxkey before 2020-07-01.
+ -- Alternatively you could set those active from 2020-06-01 and in the taxkey upgrade script check for taxkey entries before that date
+ INSERT INTO taxkeys (chart_id, tax_id, taxkey_id, pos_ustva, startdate)
+ VALUES ( (select id from chart where accno = '8732'), (select id from tax where rate = 0.05 and taxkey = 2 and chart_id = (select id from chart where accno = '1773')), 2, 861, '2020-07-01');
+
+ INSERT INTO taxkeys (chart_id, tax_id, taxkey_id, pos_ustva, startdate)
+ VALUES ( (select id from chart where accno = '3732'), (select id from tax where rate = 0.05 and taxkey = 8 and chart_id = (select id from chart where accno = '1568')), 8, 861, '2020-07-01');
+
+ -- 8735 / 3737 - these were never created in the original SKR03, so also start using them from 2020-07-01
+ -- taxkey for Gewährte Skonti 16 % USt pointing to tax 1775 Umsatzsteuer 16%
+ INSERT INTO taxkeys (chart_id, tax_id, taxkey_id, pos_ustva, startdate)
+ VALUES ( (select id from chart where accno = '8735'), (select id from tax where rate = 0.16 and taxkey = 3 and chart_id = (select id from chart where accno = '1775')), 3, 81, '2020-07-01');
+
+ -- taxkey for Erhaltene Skonti 16 % USt pointing to tax 1575 Vorsteuer 16%
+ INSERT INTO taxkeys (chart_id, tax_id, taxkey_id, pos_ustva, startdate)
+ VALUES ( (select id from chart where accno = '3737'), (select id from tax where rate = 0.16 and taxkey = 9 and chart_id = (select id from chart where accno = '1575')), 9, 66, '2020-07-01');
+
+END IF;
+
+END $$;
--- /dev/null
+-- @tag: konjunkturpaket_2020_SKR04-korrekturen
+-- @description: USTVA-Felder korrigieren
+-- @depends: konjunkturpaket_2020_SKR04
+-- @ignore: 0
+
+DO $$
+BEGIN
+
+IF ( select coa from defaults ) = 'Germany-DATEV-SKR04EU' THEN
+
+ -- Alle temporären Steuer auf Pos. 36
+ UPDATE taxkeys SET pos_ustva=36 WHERE chart_id IN
+ (SELECT id FROM chart WHERE accno in ('3803','3805'));
+
+ -- Alle temporären 5% und 16% Erlöskonten auf Pos. 35
+ -- select accno from chart where id in (select chart_id from taxkeys where tax_id in (select id from tax where taxkey=2 and rate=0.05) and pos_ustva=86) order by accno;
+ -- accno
+ -- 4300 4566 4610 4630 4670 4710 4731 4750 4780 4941 6281
+ UPDATE taxkeys SET pos_ustva=35 WHERE tax_id in (SELECT id FROM tax WHERE taxkey=2 AND rate=0.05) AND pos_ustva=86;
+ -- select accno from chart where id in (select chart_id from taxkeys where tax_id in (select id from tax where taxkey=3 and rate=0.16)) order by accno;
+ -- accno
+ -- 4400 4500 4510 4520 4569 4620 4640 4660 4680 4686 4720 4736 4760 4790 4830 4835 4849 4860 4945 6286 6287
+
+ UPDATE taxkeys SET pos_ustva=35 WHERE tax_id in (SELECT id FROM tax WHERE taxkey=3 AND rate=0.16);
+
+END IF;
+
+END $$;
--- /dev/null
+-- @tag: konjunkturpaket_2020_SKR04
+-- @description: Anpassung des Deutschen DATEV-Kontenrahmen für SKR04 Konjunkturpaket
+-- @depends: release_3_5_5 remove_double_tax_entries_skr04
+-- @ignore: 0
+
+DO $$
+BEGIN
+
+IF ( select coa from defaults ) = 'Germany-DATEV-SKR04EU' THEN
+
+ -- charts 1403 und 3803 for 5% taxes already existed, reconfigure them
+ UPDATE chart set description = 'Abziehbare Vorsteuer 5 %', taxkey_id = 8 where accno = '1403' and description = 'Abziehbare Vorsteuer aus innergemeinschftl. Erwerb 16%';
+ UPDATE chart set description = 'Umsatzsteuer 5 %', taxkey_id = 2 where accno = '3803' and description = 'Umsatzsteuer aus innergemeinschftl. Erwerb 16%';
+
+ -- DEBUG
+ -- UPDATE tax SET taxdescription = 'OLD ' || taxdescription WHERE (taxkey = 5 or taxkey = 7); -- and rate = 0.16;
+
+ UPDATE taxkeys SET tax_id = (SELECT id FROM tax WHERE taxkey = 5 and rate = 0.16)
+ WHERE chart_id = (SELECT id FROM chart where accno = '4400')
+ AND startdate = '1970-01-01';
+
+ -- new charts for 5%
+ -- 4732 and 5732
+ INSERT INTO chart (accno, description, charttype, category, link, taxkey_id, pos_bwa, pos_bilanz, pos_eur, datevautomatik)
+ VALUES ('4732','Gewährte Skonti 5 % USt','A', 'I', 'AR_paid', 2, 1, null, 1, 't');
+ INSERT INTO chart (accno, description, charttype, category, link, taxkey_id, pos_bwa, pos_bilanz, pos_eur, datevautomatik)
+ VALUES ('5732','Erhaltene Skonti 5 % Vorsteuer','A', 'E', 'AP_paid', 8, 4, null, null, 't');
+
+ -- Gewährte and Erhaltene Skonti 16% already exist, but rename them
+ UPDATE chart SET description = 'Gewährte Skonti 16%' where accno = '4735' and description = 'Gewährte Skonti 16%/19% USt';
+ UPDATE chart SET description = 'Erhaltene Skonti 16%' where accno = '4735' and description = 'Erhaltene Skonti 16%/19% USt';
+
+ -- taxkeys can't be inserted until the new taxes exist
+ INSERT INTO tax (chart_id, rate, taxkey, taxdescription, chart_categories, skonto_sales_chart_id, skonto_purchase_chart_id)
+ VALUES ( (select id from chart where accno = '3803'), 0.05, 2, 'Umsatzsteuer', 'I', (select id from chart where accno = '4732'), null), -- ok
+ ( (select id from chart where accno = '3805'), 0.16, 3, 'Umsatzsteuer', 'I', (select id from chart where accno = '4735'), null),
+ ( (select id from chart where accno = '1405'), 0.16, 9, 'Vorsteuer', 'E', null, (select id from chart where accno = '5735')),
+ ( (select id from chart where accno = '1403'), 0.05, 8, 'Vorsteuer', 'E', null, (select id from chart where accno = '5732'));
+
+ -- new taxkeys for 5% and 16% only need one startdate, not valid before and won't change back to anything later
+ -- these taxkeys won't be valid on 2020-06-30, so won't be affected later by big taxkeys update
+ -- 4732 and 5732
+ INSERT INTO taxkeys (chart_id, tax_id, taxkey_id, pos_ustva, startdate)
+ VALUES ( (select id from chart where accno = '4732'),
+ ( select id from tax where rate = 0.05 and taxkey = 2 and chart_id = (select id from chart where accno = '3803')), 2, 861, '2020-07-01'); -- ustva_id like 3801, is this correct?
+
+ INSERT INTO taxkeys (chart_id, tax_id, taxkey_id, pos_ustva, startdate)
+ VALUES ( (select id from chart where accno = '5732'),
+ (select id from tax where rate = 0.05 and taxkey = 8 and chart_id = (select id from chart where accno = '1403')), 8, 66, '2020-07-01'); -- ustva_id like 1401, is this correct?
+
+ -- the taxkeys for the existing charts will be updated in a later update
+END IF;
+
+END $$;
+++ /dev/null
-# @tag: periodic_invoices_background_job
-# @description: Hintergrundjob zum Erzeugen wiederkehrender Rechnungen
-# @depends: periodic_invoices
-package SL::DBUpgrade2::periodic_invoices_background_job;
-
-use strict;
-use utf8;
-
-use parent qw(SL::DBUpgrade2::Base);
-
-use SL::BackgroundJob::CreatePeriodicInvoices;
-
-sub run {
- SL::BackgroundJob::CreatePeriodicInvoices->create_job;
- return 1;
-}
-
-1;
--- /dev/null
+-- @tag: periodic_invoices_background_job
+-- @description: Hintergrundjob zum Erzeugen wiederkehrender Rechnungen
+-- @depends: periodic_invoices
+INSERT INTO background_jobs (type, package_name, active, cron_spec, next_run_at)
+VALUES ('interval', 'CreatePeriodicInvoices', true, '0 3 1 * *',
+ date_trunc('month', current_date) + CAST('1 month 3 hours' AS interval));
--- /dev/null
+-- @tag: release_3_5_5
+-- @description: Leeres Script, das alle Upgradescripte bis zum Release 3.5.5 voraussetzt, um ein fest definiertes Schema zu definieren.
+-- @depends: release_3_5_4 bank_transaction_acc_trans_remove_wrong_primary_key inventory_itime_parts_id_index add_node_id_to_background_jobs tax_removed_taxnumber bank_transactions_nuke_trailing_spaces_in_purpose remove_comma_aggregate_functions inventory_parts_id_index defaults_workflow_po_ap_chart_id defaults_year_end_charts
--- /dev/null
+-- @tag: release_3_5_6
+-- @description: Leeres Script, das alle Upgradescripte bis zum Release 3.5.6 voraussetzt, um ein fest definiertes Schema zu definieren.
+-- @depends: release_3_5_5 dunning_config_print_original_invoice customer_vendor_add_natural_person bank_account_flag_for_zugferd_usage defaults_contact_departments_use_textfield konjunkturpaket_2020 contact_departments_own_table defaults_vc_greetings_use_textfield greetings_own_table defaults_contact_titles_use_textfield contact_titles_own_table remove_taxkey_15_17_skr04 defaults_zugferd_test_mode defaults_split_address gl_add_deliverydate customer_create_zugferd_invoices
--- /dev/null
+-- @tag: release_3_5_6_1
+-- @description: Leeres Script, das alle Upgradescripte bis zum Release 3.5.6.1 voraussetzt, um ein fest definiertes Schema zu definieren.
+-- @depends: release_3_5_6 konjunkturpaket_2020_SKR04-korrekturen exchangerate_in_oe ap_set_payment_term_from_vendor konjunkturpaket_2020_SKR03-korrekturen alter_default_shipped_qty_config delete_cvars_on_trans_deletion_add_shipto transfer_out_serial_charge_number
--- /dev/null
+-- @tag: remove_comma_aggregate_functions
+-- @description: Entfernt Aggregate Funktion comma
+-- @depends: release_3_5_3
+
+DROP AGGREGATE IF EXISTS comma(text);
+DROP FUNCTION IF EXISTS comma_aggregate ( text, text) ;
--- /dev/null
+# @tag: remove_double_tax_entries_skr04
+# @description: doppelte Steuer-Einträge und alte 16% Konten für SKR04 entfernen, wenn unbebucht
+# @depends: release_3_5_5
+package SL::DBUpgrade2::remove_double_tax_entries_skr04;
+
+use strict;
+use utf8;
+
+use parent qw(SL::DBUpgrade2::Base);
+
+use SL::DBUtils;
+
+sub run {
+ my ($self) = @_;
+
+ if (!$self->check_coa('Germany-DATEV-SKR04EU')) {
+ return 1;
+ }
+
+ my $query = <<SQL;
+ SELECT id FROM tax WHERE chart_id = (SELECT id FROM chart WHERE accno LIKE ?) AND taxkey = ? AND rate = ? ORDER BY id;
+SQL
+
+ my $query2 = <<SQL;
+ DELETE FROM taxkeys WHERE tax_id = ?;
+SQL
+
+ my $query3 = <<SQL;
+ DELETE FROM tax WHERE id = ?;
+SQL
+
+ my @taxes_to_test = (
+ {accno => '3806', taxkey => 3, rate => 0.19},
+ {accno => '1406', taxkey => 9, rate => 0.19},
+ {accno => '3805', taxkey => 5, rate => 0.16},
+ {accno => '1405', taxkey => 7, rate => 0.16},
+
+ );
+
+ foreach my $tax_to_test (@taxes_to_test) {
+ my @entries = selectall_hashref_query($::form, $self->dbh, $query, ($tax_to_test->{accno}, $tax_to_test->{taxkey}, $tax_to_test->{rate}));
+
+ if (scalar @entries > 1) {
+ foreach my $tax (@entries) {
+ my ($num_acc_trans_entries) = $self->dbh->selectrow_array("SELECT COUNT(*) FROM acc_trans WHERE tax_id = ?", undef, $tax->{id});
+ next if $num_acc_trans_entries > 0;
+
+ $self->db_query($query2, bind => [ $tax->{id} ]);
+ $self->db_query($query3, bind => [ $tax->{id} ]);
+
+ last; # delete only one tax
+ }
+ }
+ }
+
+ return 1;
+}
+
+1;
--- /dev/null
+-- @tag: remove_taxkey_15_17_skr04
+-- @description: Steuer mit Schlüssel 15 und 17 (16%) für SKR04 entfernen, wenn nicht verknüpft
+-- @depends: release_3_5_5
+
+DELETE FROM tax
+ WHERE (SELECT coa FROM defaults) LIKE 'Germany-DATEV-SKR04EU'
+ AND taxkey = 17
+ AND chart_id = (SELECT chart_id FROM chart WHERE accno LIKE '1403')
+ AND rate = .16
+ AND NOT EXISTS (SELECT id FROM taxkeys WHERE tax_id = tax.id)
+ AND NOT EXISTS (SELECT id FROM acc_trans WHERE tax_id = tax.id);
+
+DELETE FROM tax
+ WHERE (SELECT coa FROM defaults) LIKE 'Germany-DATEV-SKR04EU'
+ AND taxkey = 15
+ AND chart_id = (SELECT chart_id FROM chart WHERE accno LIKE '3803')
+ AND rate = .16
+ AND NOT EXISTS (SELECT id FROM taxkeys WHERE tax_id = tax.id)
+ AND NOT EXISTS (SELECT id FROM acc_trans WHERE tax_id = tax.id);
+++ /dev/null
-# @tag: self_test_background_job
-# @description: Hintergrundjob für tägliche Selbsttests
-# @depends: release_2_7_0
-package SL::DBUpgrade2::self_test_background_job;
-
-use strict;
-use utf8;
-
-use parent qw(SL::DBUpgrade2::Base);
-
-use SL::BackgroundJob::SelfTest;
-
-sub run {
- SL::BackgroundJob::SelfTest->create_job;
- return 1;
-}
-
-1;
--- /dev/null
+-- @tag: self_test_background_job
+-- @description: Hintergrundjob für tägliche Selbsttests
+-- @depends: release_2_7_0
+INSERT INTO background_jobs (type, package_name, active, cron_spec, next_run_at)
+VALUES ('interval', 'SelfTest', true, '20 2 * * *',
+ CAST(current_date AS timestamp) + CAST(
+ (CASE
+ WHEN extract('hour' FROM current_timestamp) < 2 THEN '2 hours 20 minutes'
+ ELSE '1 day 2 hours 20 minutes'
+ END) AS interval
+ )
+);
--- /dev/null
+-- @tag: tax_removed_taxnumber
+-- @description: Spalte taxnumber aus tax entfernt
+-- @depends: release_3_5_4
+
+alter table tax drop column taxnumber;
--- /dev/null
+-- @tag: transfer_out_serial_charge_number
+-- @description: Feld für das Feature "VK-Seriennummer ist Lager-Chargennummer".
+-- @depends: release_3_5_6
+ALTER TABLE defaults ADD COLUMN sales_serial_eq_charge BOOLEAN NOT NULL DEFAULT FALSE;
--
--
--- TOC entry 5 (OID 1981863)
-- Name: id; Type: SEQUENCE; Schema: public; Owner: postgres
--
--
--- TOC entry 7 (OID 1981865)
-- Name: glid; Type: SEQUENCE; Schema: public; Owner: postgres
--
--
--- TOC entry 13 (OID 1981867)
-- Name: gl; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 14 (OID 1981879)
-- Name: chart; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 15 (OID 1981890)
-- Name: datev; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 16 (OID 1981893)
-- Name: gifi; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 17 (OID 1981898)
-- Name: parts; Type: TABLE; Schema: public; Owner: postgres
--
not_discountable boolean DEFAULT false,
buchungsgruppen_id integer,
payment_id integer
-) WITH OIDS;
+);
--
--- TOC entry 18 (OID 1981915)
-- Name: defaults; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 19 (OID 1981924)
-- Name: audittrail; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 20 (OID 1981930)
-- Name: acc_trans; Type: TABLE; Schema: public; Owner: postgres
--
taxkey integer,
itime timestamp without time zone DEFAULT now(),
mtime timestamp without time zone
-) WITH OIDS;
+);
--
--- TOC entry 21 (OID 1981944)
-- Name: invoice; Type: TABLE; Schema: public; Owner: postgres
--
base_qty real,
subtotal boolean DEFAULT false,
longdescription text
-) WITH OIDS;
+);
--
--- TOC entry 22 (OID 1981958)
-- Name: vendor; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 23 (OID 1981969)
-- Name: customer; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 24 (OID 1981982)
-- Name: contacts; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 25 (OID 1981991)
-- Name: assembly; Type: TABLE; Schema: public; Owner: postgres
--
bom boolean,
itime timestamp without time zone DEFAULT now(),
mtime timestamp without time zone
-) WITH OIDS;
+);
--
--- TOC entry 26 (OID 1981994)
-- Name: ar; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 27 (OID 1982012)
-- Name: ap; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 28 (OID 1982030)
-- Name: partstax; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 29 (OID 1982033)
-- Name: tax; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 30 (OID 1982039)
-- Name: customertax; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 31 (OID 1982042)
-- Name: vendortax; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 32 (OID 1982045)
-- Name: oe; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 33 (OID 1982058)
-- Name: orderitems; Type: TABLE; Schema: public; Owner: postgres
--
base_qty real,
subtotal boolean DEFAULT false,
longdescription text
-) WITH OIDS;
+);
--
--- TOC entry 34 (OID 1982069)
-- Name: exchangerate; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 35 (OID 1982072)
-- Name: employee; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 36 (OID 1982083)
-- Name: shipto; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 37 (OID 1982089)
-- Name: project; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 38 (OID 1982100)
-- Name: partsgroup; Type: TABLE; Schema: public; Owner: postgres
--
partsgroup text,
itime timestamp without time zone DEFAULT now(),
mtime timestamp without time zone
-) WITH OIDS;
+);
--
--- TOC entry 39 (OID 1982107)
-- Name: makemodel; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 40 (OID 1982113)
-- Name: status; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 9 (OID 1982121)
-- Name: invoiceid; Type: SEQUENCE; Schema: public; Owner: postgres
--
--
--- TOC entry 11 (OID 1982123)
-- Name: orderitemsid; Type: SEQUENCE; Schema: public; Owner: postgres
--
--
--- TOC entry 41 (OID 1982125)
-- Name: warehouse; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 42 (OID 1982134)
-- Name: inventory; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 43 (OID 1982137)
-- Name: department; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 44 (OID 1982145)
-- Name: dpt_trans; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 45 (OID 1982148)
-- Name: business; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 46 (OID 1982158)
-- Name: sic; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 47 (OID 1982164)
-- Name: license; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 48 (OID 1982174)
-- Name: licenseinvoice; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 49 (OID 1982176)
-- Name: pricegroup; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 50 (OID 1982184)
-- Name: prices; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 51 (OID 1982194)
-- Name: finanzamt; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 157 (OID 1982885)
-- Name: check_department(); Type: FUNCTION; Schema: public; Owner: postgres
--
--
--- TOC entry 158 (OID 1982886)
-- Name: del_department(); Type: FUNCTION; Schema: public; Owner: postgres
--
--
--- TOC entry 159 (OID 1982887)
-- Name: del_customer(); Type: FUNCTION; Schema: public; Owner: postgres
--
--
--- TOC entry 160 (OID 1982888)
-- Name: del_vendor(); Type: FUNCTION; Schema: public; Owner: postgres
--
--
--- TOC entry 161 (OID 1982889)
-- Name: del_exchangerate(); Type: FUNCTION; Schema: public; Owner: postgres
--
--
--- TOC entry 162 (OID 1982890)
-- Name: check_inventory(); Type: FUNCTION; Schema: public; Owner: postgres
--
--
--- TOC entry 163 (OID 1982968)
-- Name: set_datevexport(); Type: FUNCTION; Schema: public; Owner: postgres
--
--
--- TOC entry 164 (OID 1982971)
-- Name: set_mtime(); Type: FUNCTION; Schema: public; Owner: postgres
--
--
--- TOC entry 52 (OID 1983721)
-- Name: language; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 53 (OID 1983730)
-- Name: payment_terms; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 54 (OID 1983739)
-- Name: translation; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 55 (OID 1983745)
-- Name: units; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 56 (OID 1983761)
-- Name: rma; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 57 (OID 1983774)
-- Name: rmaitems; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 58 (OID 1983785)
-- Name: printers; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 59 (OID 1983798)
-- Name: tax_zones; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 60 (OID 1983807)
-- Name: buchungsgruppen; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 61 (OID 1983825)
-- Name: dunning_config; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 62 (OID 1983833)
-- Name: dunning; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 165 (OID 1983838)
-- Name: set_priceupdate_parts(); Type: FUNCTION; Schema: public; Owner: postgres
--
--
--- TOC entry 63 (OID 1983846)
-- Name: leads; Type: TABLE; Schema: public; Owner: postgres
--
--
--- TOC entry 64 (OID 1983849)
-- Name: taxkeys; Type: TABLE; Schema: public; Owner: postgres
--
--
--- Data for TOC entry 171 (OID 1981915)
-- Name: defaults; Type: TABLE DATA; Schema: public; Owner: postgres
--
INSERT INTO "defaults" ("version", "curr") VALUES ('2.4.0.0', 'EUR:USD');
--
--- Data for TOC entry 204 (OID 1982194)
-- Name: finanzamt; Type: TABLE DATA; Schema: public; Owner: postgres
--
--
--- Data for TOC entry 208 (OID 1983745)
-- Name: units; Type: TABLE DATA; Schema: public; Owner: postgres
--
--
--- Data for TOC entry 212 (OID 1983798)
-- Name: tax_zones; Type: TABLE DATA; Schema: public; Owner: postgres
--
INSERT INTO tax_zones (id, description) VALUES (3, 'Außerhalb EU');
--
--- TOC entry 143 (OID 1982173)
-- Name: license_id_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 84 (OID 1982891)
-- Name: acc_trans_trans_id_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 82 (OID 1982892)
-- Name: acc_trans_chart_id_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 85 (OID 1982893)
-- Name: acc_trans_transdate_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 83 (OID 1982894)
-- Name: acc_trans_source_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 110 (OID 1982895)
-- Name: ap_id_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 115 (OID 1982896)
-- Name: ap_transdate_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 111 (OID 1982897)
-- Name: ap_invnumber_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 112 (OID 1982898)
-- Name: ap_ordnumber_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 116 (OID 1982899)
-- Name: ap_vendor_id_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 109 (OID 1982900)
-- Name: ap_employee_id_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 103 (OID 1982901)
-- Name: ar_id_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 108 (OID 1982902)
-- Name: ar_transdate_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 104 (OID 1982903)
-- Name: ar_invnumber_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 105 (OID 1982904)
-- Name: ar_ordnumber_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 101 (OID 1982905)
-- Name: ar_customer_id_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 102 (OID 1982906)
-- Name: ar_employee_id_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 100 (OID 1982907)
-- Name: assembly_id_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 74 (OID 1982908)
-- Name: chart_id_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 71 (OID 1982909)
-- Name: chart_accno_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 72 (OID 1982910)
-- Name: chart_category_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 75 (OID 1982911)
-- Name: chart_link_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 73 (OID 1982912)
-- Name: chart_gifi_accno_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 96 (OID 1982913)
-- Name: customer_id_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 118 (OID 1982914)
-- Name: customer_customer_id_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 95 (OID 1982915)
-- Name: customer_customernumber_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 97 (OID 1982916)
-- Name: customer_name_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 94 (OID 1982917)
-- Name: customer_contact_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 128 (OID 1982918)
-- Name: employee_id_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 129 (OID 1982919)
-- Name: employee_login_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 130 (OID 1982920)
-- Name: employee_name_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 127 (OID 1982921)
-- Name: exchangerate_ct_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 77 (OID 1982922)
-- Name: gifi_accno_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 67 (OID 1982923)
-- Name: gl_id_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 70 (OID 1982924)
-- Name: gl_transdate_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 69 (OID 1982925)
-- Name: gl_reference_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 65 (OID 1982926)
-- Name: gl_description_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 66 (OID 1982927)
-- Name: gl_employee_id_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 86 (OID 1982928)
-- Name: invoice_id_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 88 (OID 1982929)
-- Name: invoice_trans_id_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 121 (OID 1982930)
-- Name: oe_id_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 124 (OID 1982931)
-- Name: oe_transdate_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 122 (OID 1982932)
-- Name: oe_ordnumber_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 120 (OID 1982933)
-- Name: oe_employee_id_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 126 (OID 1982934)
-- Name: orderitems_trans_id_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 79 (OID 1982935)
-- Name: parts_id_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 80 (OID 1982936)
-- Name: parts_partnumber_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 78 (OID 1982937)
-- Name: parts_description_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 117 (OID 1982938)
-- Name: partstax_parts_id_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 90 (OID 1982939)
-- Name: vendor_id_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 91 (OID 1982940)
-- Name: vendor_name_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 93 (OID 1982941)
-- Name: vendor_vendornumber_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 89 (OID 1982942)
-- Name: vendor_contact_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 119 (OID 1982943)
-- Name: vendortax_vendor_id_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 132 (OID 1982944)
-- Name: shipto_trans_id_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 133 (OID 1982945)
-- Name: project_id_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 107 (OID 1982946)
-- Name: ar_quonumber_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 114 (OID 1982947)
-- Name: ap_quonumber_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 138 (OID 1982948)
-- Name: makemodel_parts_id_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 136 (OID 1982949)
-- Name: makemodel_make_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 137 (OID 1982950)
-- Name: makemodel_model_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 139 (OID 1982951)
-- Name: status_trans_id_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 141 (OID 1982952)
-- Name: department_id_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 125 (OID 1982953)
-- Name: orderitems_id_key; Type: INDEX; Schema: public; Owner: postgres
--
--
--- TOC entry 68 (OID 1981877)
-- Name: gl_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
--
--- TOC entry 76 (OID 1981888)
-- Name: chart_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
--
--- TOC entry 81 (OID 1981913)
-- Name: parts_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
--
--- TOC entry 87 (OID 1981952)
-- Name: invoice_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
--
--- TOC entry 92 (OID 1981967)
-- Name: vendor_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
--
--- TOC entry 98 (OID 1981980)
-- Name: customer_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
--
--- TOC entry 99 (OID 1981989)
-- Name: contacts_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
--
--- TOC entry 106 (OID 1982006)
-- Name: ar_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
--
--- TOC entry 113 (OID 1982024)
-- Name: ap_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
--
--- TOC entry 123 (OID 1982056)
-- Name: oe_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
--
--- TOC entry 131 (OID 1982081)
-- Name: employee_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
--
--- TOC entry 134 (OID 1982096)
-- Name: project_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
--
--- TOC entry 135 (OID 1982098)
-- Name: project_projectnumber_key; Type: CONSTRAINT; Schema: public; Owner: postgres
--
--
--- TOC entry 140 (OID 1982132)
-- Name: warehouse_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
--
--- TOC entry 142 (OID 1982156)
-- Name: business_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
--
--- TOC entry 144 (OID 1982171)
-- Name: license_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
--
--- TOC entry 145 (OID 1982182)
-- Name: pricegroup_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
--
--- TOC entry 146 (OID 1983728)
-- Name: language_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
--
--- TOC entry 147 (OID 1983737)
-- Name: payment_terms_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
--
--- TOC entry 148 (OID 1983747)
-- Name: units_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
--
--- TOC entry 149 (OID 1983772)
-- Name: rma_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
--
--- TOC entry 150 (OID 1983791)
-- Name: printers_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
--
--- TOC entry 151 (OID 1983813)
-- Name: buchungsgruppen_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
--
--- TOC entry 152 (OID 1983831)
-- Name: dunning_config_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
--
--- TOC entry 153 (OID 1983836)
-- Name: dunning_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
--
--- TOC entry 154 (OID 1983852)
-- Name: taxkeys_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
--
--- TOC entry 219 (OID 1981940)
-- Name: $1; Type: FK CONSTRAINT; Schema: public; Owner: postgres
--
--
--- TOC entry 220 (OID 1981954)
-- Name: $1; Type: FK CONSTRAINT; Schema: public; Owner: postgres
--
--
--- TOC entry 221 (OID 1982008)
-- Name: $1; Type: FK CONSTRAINT; Schema: public; Owner: postgres
--
--
--- TOC entry 222 (OID 1982026)
-- Name: $1; Type: FK CONSTRAINT; Schema: public; Owner: postgres
--
--
--- TOC entry 223 (OID 1982065)
-- Name: $1; Type: FK CONSTRAINT; Schema: public; Owner: postgres
--
--
--- TOC entry 224 (OID 1982186)
-- Name: $1; Type: FK CONSTRAINT; Schema: public; Owner: postgres
--
--
--- TOC entry 225 (OID 1982190)
-- Name: $2; Type: FK CONSTRAINT; Schema: public; Owner: postgres
--
--
--- TOC entry 226 (OID 1983749)
-- Name: $1; Type: FK CONSTRAINT; Schema: public; Owner: postgres
--
--
--- TOC entry 227 (OID 1983781)
-- Name: $1; Type: FK CONSTRAINT; Schema: public; Owner: postgres
--
--
--- TOC entry 218 (OID 1984290)
-- Name: $1; Type: FK CONSTRAINT; Schema: public; Owner: postgres
--
--
--- TOC entry 243 (OID 1982954)
-- Name: check_department; Type: TRIGGER; Schema: public; Owner: postgres
--
--
--- TOC entry 247 (OID 1982955)
-- Name: check_department; Type: TRIGGER; Schema: public; Owner: postgres
--
--
--- TOC entry 228 (OID 1982956)
-- Name: check_department; Type: TRIGGER; Schema: public; Owner: postgres
--
--
--- TOC entry 252 (OID 1982957)
-- Name: check_department; Type: TRIGGER; Schema: public; Owner: postgres
--
--
--- TOC entry 244 (OID 1982958)
-- Name: del_department; Type: TRIGGER; Schema: public; Owner: postgres
--
--
--- TOC entry 248 (OID 1982959)
-- Name: del_department; Type: TRIGGER; Schema: public; Owner: postgres
--
--
--- TOC entry 229 (OID 1982960)
-- Name: del_department; Type: TRIGGER; Schema: public; Owner: postgres
--
--
--- TOC entry 254 (OID 1982961)
-- Name: del_department; Type: TRIGGER; Schema: public; Owner: postgres
--
--
--- TOC entry 240 (OID 1982962)
-- Name: del_customer; Type: TRIGGER; Schema: public; Owner: postgres
--
--
--- TOC entry 236 (OID 1982963)
-- Name: del_vendor; Type: TRIGGER; Schema: public; Owner: postgres
--
--
--- TOC entry 245 (OID 1982964)
-- Name: del_exchangerate; Type: TRIGGER; Schema: public; Owner: postgres
--
--
--- TOC entry 249 (OID 1982965)
-- Name: del_exchangerate; Type: TRIGGER; Schema: public; Owner: postgres
--
--
--- TOC entry 255 (OID 1982966)
-- Name: del_exchangerate; Type: TRIGGER; Schema: public; Owner: postgres
--
--
--- TOC entry 253 (OID 1982967)
-- Name: check_inventory; Type: TRIGGER; Schema: public; Owner: postgres
--
--
--- TOC entry 239 (OID 1982969)
-- Name: customer_datevexport; Type: TRIGGER; Schema: public; Owner: postgres
--
--
--- TOC entry 238 (OID 1982970)
-- Name: vendor_datevexport; Type: TRIGGER; Schema: public; Owner: postgres
--
--
--- TOC entry 241 (OID 1982972)
-- Name: mtime_customer; Type: TRIGGER; Schema: public; Owner: postgres
--
--
--- TOC entry 237 (OID 1982973)
-- Name: mtime_vendor; Type: TRIGGER; Schema: public; Owner: postgres
--
--
--- TOC entry 246 (OID 1982974)
-- Name: mtime_ar; Type: TRIGGER; Schema: public; Owner: postgres
--
--
--- TOC entry 250 (OID 1982975)
-- Name: mtime_ap; Type: TRIGGER; Schema: public; Owner: postgres
--
--
--- TOC entry 230 (OID 1982976)
-- Name: mtime_gl; Type: TRIGGER; Schema: public; Owner: postgres
--
--
--- TOC entry 234 (OID 1982977)
-- Name: mtime_acc_trans; Type: TRIGGER; Schema: public; Owner: postgres
--
--
--- TOC entry 256 (OID 1982978)
-- Name: mtime_oe; Type: TRIGGER; Schema: public; Owner: postgres
--
--
--- TOC entry 235 (OID 1982979)
-- Name: mtime_invoice; Type: TRIGGER; Schema: public; Owner: postgres
--
--
--- TOC entry 257 (OID 1982980)
-- Name: mtime_orderitems; Type: TRIGGER; Schema: public; Owner: postgres
--
--
--- TOC entry 231 (OID 1982981)
-- Name: mtime_chart; Type: TRIGGER; Schema: public; Owner: postgres
--
--
--- TOC entry 251 (OID 1982982)
-- Name: mtime_tax; Type: TRIGGER; Schema: public; Owner: postgres
--
--
--- TOC entry 232 (OID 1982983)
-- Name: mtime_parts; Type: TRIGGER; Schema: public; Owner: postgres
--
--
--- TOC entry 259 (OID 1982984)
-- Name: mtime_status; Type: TRIGGER; Schema: public; Owner: postgres
--
--
--- TOC entry 258 (OID 1982985)
-- Name: mtime_partsgroup; Type: TRIGGER; Schema: public; Owner: postgres
--
--
--- TOC entry 260 (OID 1982986)
-- Name: mtime_inventory; Type: TRIGGER; Schema: public; Owner: postgres
--
--
--- TOC entry 261 (OID 1982987)
-- Name: mtime_department; Type: TRIGGER; Schema: public; Owner: postgres
--
--
--- TOC entry 242 (OID 1982988)
-- Name: mtime_contacts; Type: TRIGGER; Schema: public; Owner: postgres
--
--
--- TOC entry 233 (OID 1983839)
-- Name: priceupdate_parts; Type: TRIGGER; Schema: public; Owner: postgres
--
AFTER UPDATE ON parts
FOR EACH ROW
EXECUTE PROCEDURE set_priceupdate_parts();
-
-
sub report_success {
$dbh->disconnect;
$superuser_dbh->disconnect if $superuser_dbh;
- ok(1, "Database has been setup sucessfully.");
+ ok(1, "Database has been set up successfully.");
done_testing();
}
fomr
invoce
lenght
+occured
paramater
pirce
postition
use strict;
-use Test::More tests => 6;
+use Test::More tests => 7;
use lib 't';
use Support::TestSetup;
is($number_of_acc_trans , 5 , "number of transactions");
is($inv->datepaid->to_kivitendo , DateTime->today->to_kivitendo , "datepaid");
is($inv->amount - $inv->paid , 0 , "paid = amount ");
+ is($inv->gldate->to_kivitendo, $inv->transactions->[0]->gldate->to_kivitendo, "gldate matches in ar and acc_trans");
} else {
ok 0, "couldn't find first invoice";
}
my $amount = $params{amount} || croak "ar needs param amount";
my $customer = $params{customer} || croak "ar needs param customer";
- my $date = $params{date} || DateTime->today;
+ my $transdate = $params{transdate} || DateTime->today;
+ my $gldate = $params{gldate} || DateTime->today->add(days => 1);
my $with_payment = $params{with_payment} || 0;
# SL::DB::Invoice has a _before_save_set_invnumber hook, so we don't need to pass invnumber
invoice => 0,
amount => $amount,
netamount => $amount,
- transdate => $date,
+ transdate => $transdate,
+ gldate => $gldate,
taxincluded => 'f',
customer_id => $customer->id,
taxzone_id => $customer->taxzone_id,
my $amount = $params{amount} || croak "ar needs param amount";
my $customer = $params{customer} || croak "ar needs param customer";
- my $date = $params{date} || DateTime->today;
+ my $transdate = $params{transdate} || DateTime->today;
+ my $gldate = $params{gldate} || DateTime->today->add(days => 1);
my $with_payment = $params{with_payment} || 0;
my $invoice = SL::DB::Invoice->new(
invoice => 0,
amount => $amount,
netamount => $amount,
- transdate => $date,
+ transdate => $transdate,
+ gldate => $gldate,
taxincluded => 'f',
customer_id => $customer->id,
taxzone_id => $customer->taxzone_id,
use Data::Dumper;
my ($customer, $vendor, $currency_id, $unit, $tax, $tax0, $tax7, $tax_9, $payment_terms, $bank_account);
-my ($transdate1, $transdate2, $currency);
+my ($currency);
my ($ar_chart,$bank,$ar_amount_chart, $ap_chart, $ap_amount_chart);
my ($ar_transaction, $ap_transaction);
+my ($dt, $dt_5, $dt_10, $year);
sub clear_up {
clear_up();
- $transdate1 = DateTime->today;
- $transdate2 = DateTime->today->add(days => 5);
+ $year = DateTime->today_local->year;
+ $year = 2019 if $year == 2020; # use year 2019 in 2020, because of tax rate change in Germany
+ $dt = DateTime->new(year => $year, month => 1, day => 12);
+ $dt_5 = $dt->clone->add(days => 5);
+ $dt_10 = $dt->clone->add(days => 10);
$tax = SL::DB::Manager::Tax->find_by(taxkey => 3, rate => 0.19, %{ $params{tax} }) || croak "No tax";
$tax7 = SL::DB::Manager::Tax->find_by(taxkey => 2, rate => 0.07) || croak "No tax for 7\%";
iban => 'DE12500105170648489890',
bic => 'TESTBIC',
account_number => '648489890',
- mandate_date_of_signature => $transdate1,
+ mandate_date_of_signature => $dt,
mandator_id => 'foobar',
bank => 'Geizkasse',
bank_code => 'G1235',
invnumber => $params{invnumber} || undef, # let it use its own invnumber
amount => $amount,
netamount => $netamount,
- transdate => $transdate1,
+ transdate => $dt,
taxincluded => $params{taxincluded } || 0,
customer_id => $customer->id,
taxzone_id => $customer->taxzone_id,
invnumber => $params{invnumber} || $testname,
amount => $amount,
netamount => $netamount,
- transdate => $transdate1,
+ transdate => $dt,
taxincluded => 0,
vendor_id => $vendor->id,
taxzone_id => $vendor->taxzone_id,
$ar_transaction = test_ar_transaction(invnumber => 'salesinv1');
- my $bt = create_bank_transaction(record => $ar_transaction) or die "Couldn't create bank_transaction";
+ my $bt = create_bank_transaction(record => $ar_transaction,
+ transdate => $dt,
+ valutadate => $dt) or die "Couldn't create bank_transaction";
$::form->{invoice_ids} = {
$bt->id => [ $ar_transaction->id ]
my $bt = create_bank_transaction(record => $ar_transaction,
bank_chart_id => $bank->id,
+ transdate => $dt,
+ valutadate => $dt,
amount => $ar_transaction->amount_less_skonto
) or die "Couldn't create bank_transaction";
my $bt = create_bank_transaction(record => $ar_transaction,
bank_chart_id => $bank->id,
+ transdate => $dt,
+ valutadate => $dt,
amount => 160.15,
) or die "Couldn't create bank_transaction";
$::form->{invoice_ids} = {
my $bt = create_bank_transaction(record => $ar_transaction_1,
amount => ($ar_transaction_1->amount + $ar_transaction_2->amount),
purpose => "Rechnungen " . $ar_transaction_1->invnumber . " und " . $ar_transaction_2->invnumber,
+ transdate => $dt,
+ valutadate => $dt,
bank_chart_id => $bank->id,
) or die "Couldn't create bank_transaction";
my $bt = create_bank_transaction(record => $ar_transaction_1,
bank_chart_id => $bank->id,
+ transdate => $dt,
+ valutadate => $dt,
amount => $ar_transaction_1->amount_less_skonto * 2 + $ar_transaction_3->amount
) or die "Couldn't create bank_transaction";
# amount 135 > 119
my $bt = create_bank_transaction(record => $ar_transaction,
bank_chart_id => $bank->id,
+ transdate => $dt,
+ valutadate => $dt,
amount => 135
) or die "Couldn't create bank_transaction";
my $bt_1 = create_bank_transaction(record => $ar_transaction,
bank_chart_id => $bank->id,
+ transdate => $dt,
+ valutadate => $dt,
amount => 10
) or die "Couldn't create bank_transaction";
my $bt_2 = create_bank_transaction(record => $ar_transaction,
amount => 119,
- transdate => DateTime->today->add(days => 5),
+ transdate => $dt_5,
+ valutadate => $dt_5,
bank_chart_id => $bank->id,
) or die "Couldn't create bank_transaction";
# amount 100 < 119
my $bt = create_bank_transaction(record => $ar_transaction,
bank_chart_id => $bank->id,
+ transdate => $dt,
+ valutadate => $dt,
amount => 100
) or die "Couldn't create bank_transaction";
my $credit_note = create_credit_note(
invnumber => 'cn 1',
customer => $customer,
+ transdate => $dt,
taxincluded => 0,
invoiceitems => [ create_invoice_item(part => $part1, qty => 3, sellprice => 70),
create_invoice_item(part => $part2, qty => 10, sellprice => 50),
my $bt = create_bank_transaction(record => $credit_note,
amount => $credit_note->amount,
bank_chart_id => $bank->id,
- transdate => DateTime->today->add(days => 10),
+ transdate => $dt_10,
);
my ($agreement, $rule_matches) = $bt->get_agreement_with_invoice($credit_note);
is($agreement, 13, "points for credit note ok");
invnumber => $params{invnumber} || 'test_neg_ap_transaction',
amount => $amount,
netamount => $netamount,
- transdate => $transdate1,
+ transdate => $dt,
taxincluded => 0,
vendor_id => $vendor->id,
taxzone_id => $vendor->taxzone_id,
my $bt = create_bank_transaction(record => $invoice,
amount => $invoice->amount,
bank_chart_id => $bank->id,
- transdate => DateTime->today->add(days => 10),
+ transdate => $dt_10,
);
my ($agreement, $rule_matches) = $bt->get_agreement_with_invoice($invoice);
invnumber => 'test_neg_ap_transaction',
amount => $amount,
netamount => $netamount,
- transdate => $transdate1,
+ transdate => $dt,
taxincluded => 0,
vendor_id => $vendor->id,
taxzone_id => $vendor->taxzone_id,
invnumber => 'test_neg_ap_transaction_two',
amount => $amount_two,
netamount => $netamount_two,
- transdate => $transdate1,
+ transdate => $dt,
taxincluded => 0,
vendor_id => $vendor->id,
taxzone_id => $vendor->taxzone_id,
my $bt = create_bank_transaction(record => $invoice_two,
amount => $invoice_two->amount + $invoice->amount,
bank_chart_id => $bank->id,
- transdate => DateTime->today->add(days => 10),
+ transdate => $dt_10,
);
# my ($agreement, $rule_matches) = $bt->get_agreement_with_invoice($invoice_two);
# is($agreement, 15, "points for negative ap transaction ok");
invnumber => $params{invnumber} || $testname,
amount => $amount,
netamount => $netamount,
- transdate => $transdate1,
+ transdate => $dt,
taxincluded => 0,
vendor_id => $vendor->id,
taxzone_id => $vendor->taxzone_id,
my $bt = create_bank_transaction(record => $invoice,
amount => $invoice->amount,
bank_chart_id => $bank->id,
- transdate => DateTime->today->add(days => 10),
+ transdate => $dt_10,
);
$::form->{invoice_ids} = {
$bt->id => [ $invoice->id ]
invnumber => $params{invnumber} || $testname,
amount => $amount,
netamount => $netamount,
- transdate => $transdate1,
+ transdate => $dt,
taxincluded => 0,
vendor_id => $vendor->id,
taxzone_id => $vendor->taxzone_id,
my $bt = create_bank_transaction(record => $invoice,
amount => $invoice->amount-100,
bank_chart_id => $bank->id,
- transdate => DateTime->today->add(days => 10),
+ transdate => $dt_10,
);
$::form->{invoice_ids} = {
$bt->id => [ $invoice->id ]
my $bt2 = create_bank_transaction(record => $invoice,
amount => 100,
bank_chart_id => $bank->id,
- transdate => DateTime->today->add(days => 10),
+ transdate => $dt_10,
);
$::form->{invoice_ids} = {
$bt2->id => [ $invoice->id ]
invnumber => '20172201',
customer => $customer,
taxincluded => 0,
+ transdate => $dt,
invoiceitems => [ create_invoice_item(part => $part1, qty => 3, sellprice => 70),
create_invoice_item(part => $part2, qty => 10, sellprice => -50),
]
my $bt = create_bank_transaction(record => $neg_sales_inv,
amount => $neg_sales_inv->amount,
bank_chart_id => $bank->id,
- transdate => DateTime->today,
+ transdate => $dt,
+ valutadate => $dt,
);
$::form->{invoice_ids} = {
$bt->id => [ $neg_sales_inv->id ]
my $testname = 'test_bt_rule1';
- $ar_transaction = test_ar_transaction(invnumber => 'bt_rule1');
+ $ar_transaction = test_ar_transaction(invnumber => 'bt_rule1', transdate => $dt);
- my $bt = create_bank_transaction(record => $ar_transaction) or die "Couldn't create bank_transaction";
+ my $bt = create_bank_transaction(record => $ar_transaction, transdate => $dt) or die "Couldn't create bank_transaction";
$ar_transaction->load;
$bt->load;
is($bt->invoice_amount , '0.00000' , "$testname: bt invoice amount was not assigned");
my $bt_controller = SL::Controller::BankTransaction->new;
- $::form->{dont_render_for_test} = 1;
- $::form->{filter}{bank_account} = $bank_account->id;
- my ( $bt_transactions, $proposals ) = $bt_controller->action_list;
+ my ( $bt_transactions, $proposals ) = $bt_controller->gather_bank_transactions_and_proposals(bank_account => $bank_account);
is(scalar(@$bt_transactions) , 1 , "$testname: one bank_transaction");
is($bt_transactions->[0]->{agreement}, 20 , "$testname: agreement == 20");
my $testname = 'test_sepa_export';
- $ar_transaction = test_ar_transaction(invnumber => 'sepa1');
+ $ar_transaction = test_ar_transaction(invnumber => 'sepa1', transdate => $dt);
- my $bt = create_bank_transaction(record => $ar_transaction) or die "Couldn't create bank_transaction";
+ my $bt = create_bank_transaction(record => $ar_transaction, transdate => $dt) or die "Couldn't create bank_transaction";
my $se = create_sepa_export();
my $sei = create_sepa_export_item(
chart_id => $bank->id,
is($sei->amount , '119.00000' , "$testname: sepa export amount ok");
my $bt_controller = SL::Controller::BankTransaction->new;
- $::form->{dont_render_for_test} = 1;
- $::form->{filter}{bank_account} = $bank_account->id;
- my ( $bt_transactions, $proposals ) = $bt_controller->action_list;
+ my ( $bt_transactions, $proposals ) = $bt_controller->gather_bank_transactions_and_proposals(bank_account => $bank_account);
is(scalar(@$bt_transactions) , 1 , "$testname: one bank_transaction");
is($bt_transactions->[0]->{agreement}, 25 , "$testname: agreement == 25");
my $bt1 = create_bank_transaction(record => $ar_transaction_1,
amount => $ar_transaction_1->amount,
purpose => "Rechnung10000 beinahe",
+ transdate => $dt,
bank_chart_id => $bank->id,
) or die "Couldn't create bank_transaction";
my $bt2 = create_bank_transaction(record => $ar_transaction_1,
amount => $ar_transaction_1->amount + 0.01,
purpose => "sicher salesinv20000 vielleicht",
+ transdate => $dt,
bank_chart_id => $bank->id,
) or die "Couldn't create bank_transaction";
my $bt3 = create_bank_transaction(record => $ar_transaction_3,
amount => $ar_transaction_3->amount,
purpose => "sicher Rechnung10000 vielleicht",
+ transdate => $dt,
bank_chart_id => $bank->id,
) or die "Couldn't create bank_transaction";
#nun sollten zwei gleichwertige Rechnungen $ar_transaction_1 und $ar_transaction_3 für $bt1 gefunden werden
#aber es darf keine Proposals geben mit mehreren Rechnungen
my $bt_controller = SL::Controller::BankTransaction->new;
- $::form->{dont_render_for_test} = 1;
- $::form->{filter}{bank_account} = $bank_account->id;
- my ( $bt_transactions, $proposals ) = $bt_controller->action_list;
+ my ( $bt_transactions, $proposals ) = $bt_controller->gather_bank_transactions_and_proposals(bank_account => $bank_account);
is(scalar(@$bt_transactions) , 2 , "$testname: two bank_transaction");
is(scalar(@$proposals) , 0 , "$testname: no proposals");
# Jetzt gibt es zwei Kontobewegungen mit gleichen Punkten für eine Rechnung.
# hier darf es auch keine Proposals geben
- ( $bt_transactions, $proposals ) = $bt_controller->action_list;
+ ( $bt_transactions, $proposals ) = $bt_controller->gather_bank_transactions_and_proposals(bank_account => $bank_account);
is(scalar(@$bt_transactions) , 2 , "$testname: two bank_transaction");
# odyn testfall - anforderungen so (noch) nicht in kivi
# hier darf es auch keine Proposals geben
$bt3->update_attributes( purpose => "fuer Rechnung salesinv10000");
- ( $bt_transactions, $proposals ) = $bt_controller->action_list;
+ ( $bt_transactions, $proposals ) = $bt_controller->gather_bank_transactions_and_proposals(bank_account => $bank_account);
is(scalar(@$bt_transactions) , 2 , "$testname: two bank_transaction");
# odyn testfall - anforderungen so (noch) nicht in kivi
+++ /dev/null
-use Test::More;
-
-use strict;
-
-use lib 't';
-use utf8;
-
-use Carp;
-use Support::TestSetup;
-use Test::Exception;
-use List::Util qw(sum);
-
-use SL::DB::Buchungsgruppe;
-use SL::DB::Currency;
-use SL::DB::Exchangerate;
-use SL::DB::Customer;
-use SL::DB::Vendor;
-use SL::DB::Employee;
-use SL::DB::Invoice;
-use SL::DB::Part;
-use SL::DB::Unit;
-use SL::DB::TaxZone;
-use SL::DB::BankAccount;
-use SL::DB::PaymentTerm;
-use SL::DB::PurchaseInvoice;
-use SL::DB::BankTransaction;
-use SL::DB::AccTransaction;
-use SL::Controller::YearEndTransactions;
-use Data::Dumper;
-
-my ($customer, $vendor, $currency_id, @parts, $unit, $employee, $tax, $tax7, $tax_9, $taxzone, $payment_terms, $bank_account);
-my ($transdate1, $transdate2, $currency);
-my ($ar_chart,$bank,$ar_amount_chart, $ap_chart, $ap_amount_chart, $saldo_chart);
-my ($ar_transaction, $ap_transaction);
-
-sub clear_up {
-
- SL::DB::Manager::BankTransaction->delete_all(all => 1);
- SL::DB::Manager::InvoiceItem->delete_all(all => 1);
- SL::DB::Manager::InvoiceItem->delete_all(all => 1);
- SL::DB::Manager::Invoice->delete_all(all => 1);
- SL::DB::Manager::PurchaseInvoice->delete_all(all => 1);
- SL::DB::Manager::Part->delete_all(all => 1);
- SL::DB::Manager::Customer->delete_all(all => 1);
- SL::DB::Manager::Vendor->delete_all(all => 1);
- SL::DB::Manager::BankAccount->delete_all(all => 1);
- SL::DB::Manager::AccTransaction->delete_all(all => 1);
- SL::DB::Manager::GLTransaction->delete_all(all => 1);
- SL::DB::Manager::PaymentTerm->delete_all(all => 1);
- SL::DB::Manager::Currency->delete_all(where => [ name => 'CUR' ]);
-};
-
-
-# starting test:
-Support::TestSetup::login();
-
-reset_state(); # initialise customers/vendors/bank/currency/...
-
-test1();
-
-# remove all created data at end of test
-#clear_up();
-
-done_testing();
-
-###### functions for setting up data
-
-sub reset_state {
- my %params = @_;
-
- $params{$_} ||= {} for qw(unit customer part tax vendor);
-
- clear_up();
-
- $transdate1 = DateTime->today;
- $transdate2 = DateTime->today->add(days => 5);
-
- $employee = SL::DB::Manager::Employee->current || croak "No employee";
- $tax = SL::DB::Manager::Tax->find_by(taxkey => 3, rate => 0.19, %{ $params{tax} }) || croak "No tax";
- $tax7 = SL::DB::Manager::Tax->find_by(taxkey => 2, rate => 0.07) || croak "No tax for 7\%";
- $taxzone = SL::DB::Manager::TaxZone->find_by( description => 'Inland') || croak "No taxzone";
- $tax_9 = SL::DB::Manager::Tax->find_by(taxkey => 9, rate => 0.19, %{ $params{tax} }) || croak "No tax";
-
- $currency_id = $::instance_conf->get_currency_id;
-
- $bank_account = SL::DB::BankAccount->new(
- account_number => '123',
- bank_code => '123',
- iban => '123',
- bic => '123',
- bank => '123',
- chart_id => SL::DB::Manager::Chart->find_by(description => 'Bank')->id,
- name => SL::DB::Manager::Chart->find_by(description => 'Bank')->description,
- )->save;
-
- $customer = SL::DB::Customer->new(
- name => 'Test Customer',
- currency_id => $currency_id,
- taxzone_id => $taxzone->id,
- iban => 'DE12500105170648489890',
- bic => 'TESTBIC',
- account_number => '648489890',
- mandate_date_of_signature => $transdate1,
- mandator_id => 'foobar',
- bank => 'Geizkasse',
- depositor => 'Test Customer',
- %{ $params{customer} }
- )->save;
-
- $payment_terms = SL::DB::PaymentTerm->new(
- description => 'payment',
- description_long => 'payment',
- terms_netto => '30',
- terms_skonto => '5',
- percent_skonto => '0.05',
- auto_calculation => 1,
- )->save;
-
- $vendor = SL::DB::Vendor->new(
- name => 'Test Vendor',
- currency_id => $currency_id,
- taxzone_id => $taxzone->id,
- payment_id => $payment_terms->id,
- iban => 'DE12500105170648489890',
- bic => 'TESTBIC',
- account_number => '648489890',
- bank => 'Geizkasse',
- depositor => 'Test Vendor',
- %{ $params{vendor} }
- )->save;
-
- $ar_chart = SL::DB::Manager::Chart->find_by( accno => '1400' ); # Forderungen
- $ap_chart = SL::DB::Manager::Chart->find_by( accno => '1600' ); # Verbindlichkeiten
- $bank = SL::DB::Manager::Chart->find_by( accno => '1200' ); # Bank
- $ar_amount_chart = SL::DB::Manager::Chart->find_by( accno => '8400' ); # Erlöse
- $ap_amount_chart = SL::DB::Manager::Chart->find_by( accno => '3400' ); # Wareneingang 19%
- $saldo_chart = SL::DB::Manager::Chart->find_by( accno => '9000' ); # Saldenvorträge
-
-}
-
-sub test_ar_transaction {
- my (%params) = @_;
- my $netamount = 100;
- my $amount = $params{amount} || $::form->round_amount(100 * 1.19,2);
- my $invoice = SL::DB::Invoice->new(
- invoice => 0,
- invnumber => $params{invnumber} || undef, # let it use its own invnumber
- amount => $amount,
- netamount => $netamount,
- transdate => $transdate1,
- taxincluded => 0,
- customer_id => $customer->id,
- taxzone_id => $customer->taxzone_id,
- currency_id => $currency_id,
- transactions => [],
- payment_id => $params{payment_id} || undef,
- notes => 'test_ar_transaction',
- );
- $invoice->add_ar_amount_row(
- amount => $invoice->netamount,
- chart => $ar_amount_chart,
- tax_id => $tax->id,
- );
-
- $invoice->create_ar_row(chart => $ar_chart);
- $invoice->save;
-
- is($invoice->currency_id , $currency_id , 'currency_id has been saved');
- is($invoice->netamount , 100 , 'ar amount has been converted');
- is($invoice->amount , 119 , 'ar amount has been converted');
- is($invoice->taxincluded , 0 , 'ar transaction doesn\'t have taxincluded');
-
- is(SL::DB::Manager::AccTransaction->find_by(chart_id => $ar_amount_chart->id , trans_id => $invoice->id)->amount , '100.00000' , $ar_amount_chart->accno . ': has been converted for currency');
- is(SL::DB::Manager::AccTransaction->find_by(chart_id => $ar_chart->id , trans_id => $invoice->id)->amount , '-119.00000' , $ar_chart->accno . ': has been converted for currency');
-
- return $invoice;
-};
-
-sub test_ap_transaction {
- my (%params) = @_;
- my $netamount = 100;
- my $amount = $::form->round_amount($netamount * 1.19,2);
- my $invoice = SL::DB::PurchaseInvoice->new(
- invoice => 0,
- invnumber => $params{invnumber} || 'test_ap_transaction',
- amount => $amount,
- netamount => $netamount,
- transdate => $transdate1,
- taxincluded => 0,
- vendor_id => $vendor->id,
- taxzone_id => $vendor->taxzone_id,
- currency_id => $currency_id,
- transactions => [],
- notes => 'test_ap_transaction',
- );
- $invoice->add_ap_amount_row(
- amount => $invoice->netamount,
- chart => $ap_amount_chart,
- tax_id => $tax_9->id,
- );
-
- $invoice->create_ap_row(chart => $ap_chart);
- $invoice->save;
-
- is($invoice->currency_id , $currency_id , 'currency_id has been saved');
- is($invoice->netamount , 100 , 'ap amount has been converted');
- is($invoice->amount , 119 , 'ap amount has been converted');
- is($invoice->taxincluded , 0 , 'ap transaction doesn\'t have taxincluded');
-
- is(SL::DB::Manager::AccTransaction->find_by(chart_id => $ap_amount_chart->id , trans_id => $invoice->id)->amount , '-100.00000' , $ap_amount_chart->accno . ': has been converted for currency');
- is(SL::DB::Manager::AccTransaction->find_by(chart_id => $ap_chart->id , trans_id => $invoice->id)->amount , '119.00000' , $ap_chart->accno . ': has been converted for currency');
-
- return $invoice;
-};
-
-###### test cases
-
-sub test1 {
-
- my $testname = 'test1';
-
- $ar_transaction = test_ar_transaction(invnumber => 'salesinv1');
- $ap_transaction = test_ap_transaction(invnumber => 'purchaseinv1');
- my $ar_transaction_2 = test_ar_transaction(invnumber => 'salesinv_2');
-
- my $yt_controller = SL::Controller::YearEndTransactions->new;
- my $report = SL::ReportGenerator->new(\%::myconfig, $::form);
-
- $::form->{"ob_date"} = DateTime->today->truncate(to => 'year')->add(years => 1)->to_kivitendo;
- $::form->{"cb_date"} = DateTime->today->truncate(to => 'year')->add(years => 1)->add(days => -1)->to_kivitendo;
- #print "ob_date=".$::form->{"ob_date"}." cb_date=".$::form->{"cb_date"}."\n";
- $::form->{"cb_reference"} = 'SB-Buchung';
- $::form->{"ob_reference"} = 'EB-Buchung';
- $::form->{"cb_description"} = 'SB-Buchung Beschreibung';
- $::form->{"ob_description"} = 'EB-Buchung Beschreibung';
- $::form->{"cbob_chart"} = $saldo_chart->id;
-
- $yt_controller->prepare_report($report);
-
- ## check balance of charts
-
- my $idx = 1;
- foreach my $chart (@{ $yt_controller->charts }) {
- my $balance = $yt_controller->get_balance($chart);
- if ( $balance != 0 ) {
- #print "chart_id=".$chart->id."balance=".$balance."\n";
- is($balance , '-238.00000' , $chart->accno.' has right balance') if $chart->accno eq '1400';
- is($balance , '-19.00000' , $chart->accno.' has right balance') if $chart->accno eq '1576';
- is($balance , '119.00000' , $chart->accno.' has right balance') if $chart->accno eq '1600';
- is($balance , '38.00000' , $chart->accno.' has right balance') if $chart->accno eq '1776';
- is($balance , '-100.00000' , $chart->accno.' has right balance') if $chart->accno eq '3400';
- is($balance , '200.00000' , $chart->accno.' has right balance') if $chart->accno eq '8400';
- $::form->{"multi_id_${idx}"} = $chart->id;
- $idx++ ;
- }
- }
- $::form->{"rowcount"} = $idx-1;
- #print "rowcount=". $::form->{"rowcount"}."\n";
- $::form->{"login"}="unittests";
-
- $yt_controller->make_booking;
-
- ## no check cb ob booking :
-
- my $sum_cb_p = 0;
- my $sum_cb_m = 0;
- foreach my $acc ( @{ SL::DB::Manager::AccTransaction->get_all(where => [ chart_id => $saldo_chart->id, cb_transaction => 't' ]) }) {
- #print "cb amount=".$acc->amount."\n";
- $sum_cb_p += $acc->amount if $acc->amount > 0;
- $sum_cb_m += -$acc->amount if $acc->amount < 0;
- }
- #print "chart_id=".$saldo_chart->id." sum_cb_p=".$sum_cb_p." sum_cb_m=".$sum_cb_m."\n";
- is($sum_cb_p , '357' , 'chart '.$saldo_chart->accno.' has right positive close saldo');
- is($sum_cb_m , '357' , 'chart '.$saldo_chart->accno.' has right negative close saldo');
- my $sum_ob_p = 0;
- my $sum_ob_m = 0;
- foreach my $acc ( @{ SL::DB::Manager::AccTransaction->get_all(where => [ chart_id => $saldo_chart->id, ob_transaction => 't' ]) }) {
- #print "ob amount=".$acc->amount."\n";
- $sum_ob_p += $acc->amount if $acc->amount > 0;
- $sum_ob_m += -$acc->amount if $acc->amount < 0;
- }
- #print "chart_id=".$saldo_chart->id." sum_ob_p=".$sum_ob_p." sum_ob_m=".$sum_ob_m."\n";
- is($sum_ob_p , '357' , 'chart '.$saldo_chart->accno.' has right positive open saldo');
- is($sum_ob_m , '357' , 'chart '.$saldo_chart->accno.' has right negative open saldo');
-}
-
-
-
-1;
use SL::DB::AccTransaction;
my ($customer, $currency_id, $employee, $taxzone, $project, $department);
+my ($transdate, $transdate_string);
sub reset_state {
# Create test data
my %params = @_;
+ $transdate = DateTime->today_local;
+ $transdate->set_year(2019) if $transdate->year == 2020; # hardcode for 2019 in 2020, because of tax rate change in Germany
+ $transdate_string = $transdate->to_kivitendo;
+
$params{$_} ||= {} for qw(buchungsgruppe customer tax);
clear_up();
##### manually create an ar transaction from scratch, testing the methods
$::myconfig{numberformat} = '1000.00';
+$::myconfig{dateformat} = 'dd.mm.yyyy';
my $old_locale = $::locale;
# set locale to en so we can match errors
$::locale = Locale->new('en');
currency_id => $currency_id,
taxincluded => 'f',
customer_id => $customer->id,
- transdate => DateTime->today,
+ transdate => $transdate,
employee_id => SL::DB::Manager::Employee->current->id,
transactions => [],
);
$ar->pay_invoice( chart_id => SL::DB::Manager::Chart->find_by(accno => '1200')->id, # bank
amount => $ar->open_amount,
- transdate => DateTime->now->to_kivitendo,
+ transdate => $transdate,
payment_type => 'without_skonto', # default if not specified
);
$result = $ar->validate_acc_trans(debug => 0);
# to debug errors in certain tests, run after test_import:
# die Dumper($entry->{errors});
##### basic test
-$file = \<<EOL;
-datatype,customer_id,taxzone_id,currency_id,invnumber,taxincluded,archart
+$file = \<<"EOL";
+datatype,customer_id,taxzone_id,currency_id,invnumber,taxincluded,archart,transdate
datatype,accno,amount,taxkey
-"Rechnung",960,4,1,"invoice 1",f,1400
+"Rechnung",960,4,1,"invoice 1",f,1400,"$transdate_string"
"AccTransaction",8400,159.48,3
EOL
$entries = test_import($file);
is $entry->{object}->netamount, '159.48', 'ar netamount tax not included is 159.48';
##### test for duplicate invnumber
-$file = \<<EOL;
-datatype,customer_id,taxzone_id,currency_id,invnumber,taxincluded,archart
+$file = \<<"EOL";
+datatype,customer_id,taxzone_id,currency_id,invnumber,taxincluded,archart,transdate
datatype,accno,amount,taxkey
-"Rechnung",960,4,1,"invoice 1",f,1400
+"Rechnung",960,4,1,"invoice 1",f,1400,"$transdate_string"
"AccTransaction",8400,159.48,3
EOL
$entries = test_import($file);
is $entry->{errors}->[0], 'Error: invnumber already exists', 'detects verify_amount differences';
##### test for no invnumber given
-$file = \<<EOL;
-datatype,customer_id,taxzone_id,currency_id,taxincluded,archart
+$file = \<<"EOL";
+datatype,customer_id,taxzone_id,currency_id,taxincluded,archart,transdate
datatype,accno,amount,taxkey
-"Rechnung",960,4,1,f,1400
+"Rechnung",960,4,1,f,1400,"$transdate_string"
"AccTransaction",8400,159.48,3
EOL
$entries = test_import($file);
is $entry->{object}->invnumber =~ /^\d+$/, 1, 'invnumber assigned automatically';
##### basic test without amounts in Rechnung, only specified in AccTransaction
-$file = \<<EOL;
-datatype,customer_id,taxzone_id,currency_id,invnumber,taxincluded,archart
+$file = \<<"EOL";
+datatype,customer_id,taxzone_id,currency_id,invnumber,taxincluded,archart,transdate
datatype,accno,amount,taxkey
-"Rechnung",960,4,1,"invoice 1 no amounts",f,1400
+"Rechnung",960,4,1,"invoice 1 no amounts",f,1400,"$transdate_string"
"AccTransaction",8400,159.48,3
EOL
$entries = test_import($file);
is $::form->round_amount($entry->{object}->transactions->[0]->amount, 2), 159.48, 'invoice 1 ar amount is 159.48';
##### basic test: credit_note
-$file = \<<EOL;
-datatype,customer_id,taxzone_id,currency_id,invnumber,taxincluded,archart
+$file = \<<"EOL";
+datatype,customer_id,taxzone_id,currency_id,invnumber,taxincluded,archart,transdate
datatype,accno,amount,taxkey
-"Rechnung",960,4,1,"credit note",f,1400
+"Rechnung",960,4,1,"credit note",f,1400,"$transdate_string"
"AccTransaction",8400,-159.48,3
EOL
$entries = test_import($file);
is $entry->{object}->netamount, '-159.48', 'credit note netamount tax not included is 159.48';
#### verify_amount differs: max_amount_diff = 0.02, 189.80 is ok, 189.81 is not
-$file = \<<EOL;
-datatype,customer_id,verify_amount,verify_netamount,taxzone_id,currency_id,invnumber,taxincluded,archart
+$file = \<<"EOL";
+datatype,customer_id,verify_amount,verify_netamount,taxzone_id,currency_id,invnumber,taxincluded,archart,transdate
datatype,accno,amount,taxkey
-"Rechnung",960,189.81,159.48,4,1,"invoice amounts differing",f,1400
+"Rechnung",960,189.81,159.48,4,1,"invoice amounts differing",f,1400,"$transdate_string"
"AccTransaction",8400,159.48,3
EOL
$entries = test_import($file);
is $entry->{errors}->[0], 'Amounts differ too much', 'detects verify_amount differences';
##### direct debit
-$file = \<<EOL;
-datatype,customer_id,taxzone_id,currency_id,invnumber,taxincluded,direct_debit,archart
+$file = \<<"EOL";
+datatype,customer_id,taxzone_id,currency_id,invnumber,taxincluded,direct_debit,archart,transdate
datatype,accno,amount,taxkey
-"Rechnung",960,4,1,"invoice with direct debit",f,t,1400
+"Rechnung",960,4,1,"invoice with direct debit",f,t,1400,"$transdate_string"
"AccTransaction",8400,159.48,3
EOL
is $entry->{object}->direct_debit, '1', 'direct debit';
#### tax included
-$file = \<<EOL;
-datatype,customer_id,taxzone_id,currency_id,invnumber,taxincluded,archart
+$file = \<<"EOL";
+datatype,customer_id,taxzone_id,currency_id,invnumber,taxincluded,archart,transdate
datatype,accno,amount,taxkey
-"Rechnung",960,4,1,"invoice 1 tax included no amounts",t,1400
+"Rechnung",960,4,1,"invoice 1 tax included no amounts",t,1400,"$transdate_string"
"AccTransaction",8400,189.78,3
EOL
is $::form->round_amount($entry->{object}->transactions->[0]->amount, 2), '159.48', 'taxincluded acc_trans netamount';
#### multiple tax included
-$file = \<<EOL;
-datatype,customer_id,taxzone_id,currency_id,invnumber,taxincluded,archart
+$file = \<<"EOL";
+datatype,customer_id,taxzone_id,currency_id,invnumber,taxincluded,archart,transdate
datatype,accno,amount,taxkey
-"Rechnung",960,4,1,"invoice multiple tax included",t,1400
+"Rechnung",960,4,1,"invoice multiple tax included",t,1400,"$transdate_string"
"AccTransaction",8400,94.89,3
"AccTransaction",8400,94.89,3
EOL
my $d = SL::DB::Default->get;
$d->update_attributes(datev_export_format => 'cp1252');
+my $ustid = 'DE123456788';
my $buchungsgruppe7 = SL::DB::Manager::Buchungsgruppe->find_by(description => 'Standard 7%') || die "No accounting group for 7\%";
my $date = DateTime->new(year => 2017, month => 7, day => 19);
my $department = create_department(description => 'Kästchenweiße heiße Preise');
my $project = create_project(projectnumber => 2017, description => '299');
-my $customer = new_customer(name => 'Test customer', ustid => 'DE12345678')->save();
+my $bank = SL::DB::Manager::Chart->find_by(description => 'Bank') || die 'Can\'t find chart "Bank"';
+my $customer = new_customer(name => 'Test customer', ustid => $ustid)->save();
my $part1 = new_part(partnumber => '19', description => 'Part 19%')->save;
my $part2 = new_part(
partnumber => '7',
);
# lets make a boom
-# generate_datev_* doesnt care about encoding but
+# generate_datev_* doesn't care about encoding but
# csv_buchungsexport does! all arabic will be deleted
# and no string will be left as invnumber
);
my $startdate = DateTime->new(year => 2017, month => 1, day => 1);
-my $enddate = DateTime->new(year => 2017, month => 12, day => 31);
+my $enddate = DateTime->new(year => 2017, month => 12, day => 31);
my $today = DateTime->new(year => 2017, month => 3, day => 17);
-
$datev1->from($startdate);
$datev1->to($enddate);
$invoice->invnumber('meine muh');
$invoice->save();
+$invoice->pay_invoice(chart_id => $bank->id,
+ amount => $invoice->open_amount,
+ transdate => $invoice->transdate->clone->add(days => 10),
+ memo => 'foobar',
+ source => 'barfoo',
+ );
+
my $datev4 = SL::DATEV->new(
dbh => $dbh,
trans_id => $invoice->id,
$datev4->to($enddate);
$datev4->generate_datev_data;
$datev4->generate_datev_lines;
+
my ($datev_csv4, $die_message3, $lines_aref);
eval {
$datev_csv4 = SL::DATEV::CSV->new(datev_lines => $datev4->generate_datev_lines,
ok(!($die_message3), 'no die message');
ok(scalar @{ $datev_csv4->warnings } == 0, 'no warnings');
-my @sorted = sort { $a->[0] cmp $b->[0] } @{ $lines_aref };
-cmp_deeply $sorted[0], [ '1963,5', 'S', 'EUR', '', '', '',
- '1400', '8400', '', '1907', 'meine muh',
- '', '', 'Test customer', '', '', '', '', '', '',
- '', '', '', '', '', '', '', '', '',
- '', '', '', '', '', '', '', "K\x{e4}stchen",
- '299', '','DE12345678', '', '', '',
- '', '', '', '', '', '', '', '', '',
- '', '', '', '', '', '', '', '', '',
- '', '', '', '', '', '', '', '', '',
- '', '', '', '', '', '', '', '', '',
- '', '', '', '', '', '', '', '', '',
- '', '', '', '', '', '', '', '', '',
- '', '', '', '', '', '', '', '', '',
- '', '', '', '', '', '', '', '', '',
- '',
- ];
-cmp_deeply $sorted[1], [ '535', 'S', 'EUR', '', '', '',
- '1400', '8300', '', '1907','meine muh',
- '', '', 'Test customer', '', '', '', '', '', '',
- '', '', '', '', '', '', '', '', '',
- '', '', '', '', '', '', '', "K\x{e4}stchen",
- '299', '','DE12345678', '', '', '',
- '', '', '', '', '', '', '', '', '',
- '', '', '', '', '', '', '', '', '',
- '', '', '', '', '', '', '', '', '',
- '', '', '', '', '', '', '', '', '',
- '', '', '', '', '', '', '', '', '',
- '', '', '', '', '', '', '', '', '',
- '', '', '', '', '', '', '', '', '',
- '', '', '', '', '', '', '', '', '',
- '',
- ];
+
+note('testing invoice without deliverydate');
+my @sorted = sort { $a->[0] cmp $b->[0] } @{ $lines_aref }; # sort by string-comparison of amount
+cmp_deeply $sorted[0],
+ [ '1963,5', 'S', 'EUR', '', '', '',
+ '1400', '8400', '', '1907', 'meine muh',
+ '', '', 'Test customer', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', "K\x{e4}stchen",
+ '299', '', $ustid, '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '1', '',
+ '', '', '', '', '',
+ ],
+ 'invoice without deliverydate 19% tax export ok';
+cmp_deeply $sorted[2],
+ [ '535', 'S', 'EUR', '', '', '',
+ '1400', '8300', '', '1907','meine muh',
+ '', '', 'Test customer', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', "K\x{e4}stchen",
+ '299', '', $ustid, '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '1', '',
+ '', '', '', '', '',
+ ],
+ 'invoice without deliverydate 16% tax export ok';
+cmp_deeply $sorted[1],
+ [ '2498,5', 'S', 'EUR', '', '', '',
+ '1200', '1400', '', '2907','meine muh',
+ '', '', 'Test customer', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', "K\x{e4}stchen",
+ '299', '', $ustid, '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '1', '',
+ '', '', '', '', '',
+ ],
+ 'invoice without deliverydate payment export ok';
+
# create one haben buchung with GLTransaction today
my $expense_chart = SL::DB::Manager::Chart->find_by(accno => '4660'); # Reisekosten
my $cash_chart = SL::DB::Manager::Chart->find_by(accno => '1000'); # Kasse
-my $tax_chart = SL::DB::Manager::Chart->find_by(accno => '1576'); # Vorsteuer
-my $tax_9 = SL::DB::Manager::Tax->find_by(taxkey => 9, rate => 0.19) || die "No tax";
-
-my @acc_trans;
-push(@acc_trans, SL::DB::AccTransaction->new(
- chart_id => $expense_chart->id,
- chart_link => $expense_chart->link,
- amount => -84.03,
- transdate => $today,
- source => '',
- taxkey => 9,
- tax_id => $tax_9->id,
- project_id => $project->id,
-));
-push(@acc_trans, SL::DB::AccTransaction->new(
- chart_id => $tax_chart->id,
- chart_link => $tax_chart->link,
- amount => -15.97,
- transdate => $today,
- source => '',
- taxkey => 9,
- tax_id => $tax_9->id,
- project_id => $project->id,
-));
-push(@acc_trans, SL::DB::AccTransaction->new(
- chart_id => $cash_chart->id,
- chart_link => $cash_chart->link,
- amount => 100,
- transdate => $today,
- source => '',
- taxkey => 0,
- tax_id => 0,
-));
-
-my $gl_transaction = SL::DB::GLTransaction->new(
+
+note('testing gl transaction without deliverydate');
+my $gl_transaction = create_gl_transaction(
reference => "Reise März 2018",
- description => "Reisekonsten März 2018 / Ma Schmidt",
+ description => "Reisekosten März 2018 / Ma Schmidt",
transdate => $today,
- gldate => $today,
- employee_id => SL::DB::Manager::Employee->current->id,
taxincluded => 1,
type => undef,
- ob_transaction => 0,
- cb_transaction => 0,
- storno => 0,
- storno_id => undef,
- transactions => \@acc_trans,
-)->save;
+ bookings => [
+ {
+ chart => $expense_chart,
+ taxkey => 9,
+ debit => 100, # net 84.03
+ },
+ {
+ chart => $cash_chart,
+ taxkey => 0,
+ credit => 100,
+ },
+ ],
+);
+
my $datev2 = SL::DATEV->new(
dbh => $dbh,
trans_id => $gl_transaction->id,
);
my @data_csv = sort { $a->[0] cmp $b->[0] } @{ $datev_csv3->lines };
-cmp_deeply($data_csv[0], [ '100', 'S', 'EUR', '', '', '', '4660', '1000', 9, '1703', 'Reise März 2',
- '', '', 'Reisekonsten März 2018 / Ma Schmidt', '', '', '', '', '', '', '', '',
- '', '', '', '', '', '', '', '', '', '', '', '', '',
- '', '', '', '', '', '', '', '', '', '', '',
- '', '', '', '', '', '', '', '', '', '', '', '', '',
- '', '', '', '', '', '', '', '', '', '', '', '', '',
- '', '', '', '', '', '', '', '', '', '', '', '', '',
- '', '', '', '', '', '', '', '', '', '', '', '', '',
- '', '', '', '', '', '', '', '', '', '', '', '', '',
- '', '', '', '', '' ]
- );
+cmp_deeply($data_csv[0],
+ [ '100', 'S', 'EUR', '', '', '', '4660', '1000', 9, '1703', 'Reise März 2',
+ '', '', 'Reisekosten März 2018 / Ma Schmidt', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '', '', '', '', '',
+ '', '', '1', '', '', '', '', '', '',
+ ],
+ 'gl datev export without delivery date ok');
+
+
+note('testing same invoice, but with deliverydate');
+# 8400 and 8300 should have deliverydate in datev, payment should not
+$invoice->deliverydate(DateTime->new(year => 2017, month => 7, day => 18));
+$invoice->save();
+
+$datev1 = SL::DATEV->new(
+ dbh => $dbh,
+ trans_id => $invoice->id,
+);
+
+$datev1->from($startdate);
+$datev1->to($enddate);
+$datev1->generate_datev_data;
+$datev1->generate_datev_lines;
+
+$datev_csv = SL::DATEV::CSV->new(datev_lines => $datev1->generate_datev_lines,
+ from => $startdate,
+ to => $enddate,
+ locked => $datev1->locked,
+);
+@sorted = sort { $a->[0] cmp $b->[0] } @{ $datev_csv->lines };
+cmp_deeply $sorted[0],
+ [ '1963,5', 'S', 'EUR', '', '', '',
+ '1400', '8400', '', '1907', 'meine muh',
+ '', '', 'Test customer', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', "K\x{e4}stchen",
+ '299', '', $ustid, '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '1', '18072017',
+ '', '', '', '', '',
+ ],
+ 'invoice with deliverydate 19% tax export ok';
+
+cmp_deeply $sorted[2],
+ [ '535', 'S', 'EUR', '', '', '',
+ '1400', '8300', '', '1907','meine muh',
+ '', '', 'Test customer', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', "K\x{e4}stchen",
+ '299', '', $ustid, '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '1', '18072017',
+ '', '', '', '', '',
+ ],
+ 'invoice with deliverydate 16% tax export ok';
+
+cmp_deeply $sorted[1],
+ [ '2498,5', 'S', 'EUR', '', '', '',
+ '1200', '1400', '', '2907','meine muh',
+ '', '', 'Test customer', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', "K\x{e4}stchen",
+ '299', '', $ustid, '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '1', '',
+ '', '', '', '', '',
+ ],
+ 'invoice with deliverydate payment export ok';
+
+note('testing same gl transaction with deliverydate');
+$gl_transaction->deliverydate(DateTime->new(year => 2017, month => 7, day => 18));
+$gl_transaction->save;
+
+$datev1 = SL::DATEV->new(
+ dbh => $dbh,
+ trans_id => $gl_transaction->id,
+);
+
+$datev1->from($startdate);
+$datev1->to($enddate);
+$datev1->generate_datev_data;
+
+$datev_csv = SL::DATEV::CSV->new(datev_lines => $datev1->generate_datev_lines,
+ from => $startdate,
+ to => $enddate,
+ locked => $datev1->locked,
+);
+
+@sorted = sort { $a->[0] cmp $b->[0] } @{ $datev_csv->lines };
+cmp_deeply($sorted[0],
+ [ '100', 'S', 'EUR', '', '', '', '4660', '1000', 9, '1703', 'Reise März 2',
+ '', '', 'Reisekosten März 2018 / Ma Schmidt', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '', '', '', '', '',
+ '', '', '1', '18072017', '', '', '', '', '',
+ ],
+ 'testing gl transaction with delivery date datev export ok');
# TODO warnings are not yet tested
# currently most of the valid_checks are senseless because of
'konto' => '1400',
'kost1' => 'Kostenstelle DATEV-Schnittstelle 2018',
'kost2' => 'Crowd-Funding September 2017',
+ 'locked' => undef,
'umsatz' => '249.9',
'waehrung' => 'EUR',
},
'konto' => '1400',
'kost1' => 'Kostenstelle DATEV-Schnittstelle 2018',
'kost2' => 'Crowd-Funding September 2017',
+ 'locked' => undef,
'umsatz' => 535,
'waehrung' => 'EUR',
},
{
'belegfeld1' => "\x{de} sales \x{a5}& inv\x{f6}ice",
-
-
-'buchungstext' => 'Testcustomer',
+ 'buchungstext' => 'Testcustomer',
'buchungstext' => 'Testcustomer',
'datum' => '05.01.2017',
'gegenkonto' => '1400',
'kost1' => 'Kostenstelle DATEV-Schnittstelle 2018',
'kost2' => 'Crowd-Funding September 2017',
'umsatz' => '784.9',
+ 'locked' => undef,
'waehrung' => 'EUR',
},
], "trans_id datev check ok";
'kost1' => 'Kostenstelle DATEV-Schnittstelle 2018',
'kost2' => 'Crowd-Funding September 2017',
'umsatz' => '249.9',
+ 'locked' => undef,
'waehrung' => 'EUR',
},
{
'kost1' => 'Kostenstelle DATEV-Schnittstelle 2018',
'kost2' => 'Crowd-Funding September 2017',
'umsatz' => 535,
+ 'locked' => undef,
'waehrung' => 'EUR',
},
{
'kost1' => 'Kostenstelle DATEV-Schnittstelle 2018',
'kost2' => 'Crowd-Funding September 2017',
'umsatz' => '784.9',
+ 'locked' => undef,
'waehrung' => 'EUR',
},
], "trans_id datev check use_pk ok";
'', '', '', '', '', '', '', '', '', '', '', '', '',
'', '', '', '', '', '', '', '', '', '', '', '', '',
'', '', '', '', '', '', '', '', '', '', '', '', '',
- '', '', '', '', '' ]
+ '', '', '1', '', '', '', '', '', '' ]
);
cmp_deeply($data_csv[0], [ '249,9', 'S', 'EUR', '', '', '', '1400', '8400', '', '0101', "\x{de} sales \x{a5}& i",
'', '', '', '', '', '', '', '', '', '', '', '', '',
'', '', '', '', '', '', '', '', '', '', '', '', '',
'', '', '', '', '', '', '', '', '', '', '', '', '',
- '', '', '', '', '' ]
+ '', '', '1', '', '', '', '', '', '' ]
);
cmp_deeply($data_csv[2], [ '784,9', 'S', 'EUR', '', '', '', '1200', '1400', '', '0501', "\x{de} sales \x{a5}& i",
'', '', 'Testcustomer', '', '', '', '', '', '', '', '',
'', '', '', '', '', '', '', '', '', '', '', '', '',
'', '', '', '', '', '', '', '', '', '', '', '', '',
'', '', '', '', '', '', '', '', '', '', '', '', '',
- '', '', '', '', '' ]
+ '', '', '1', '', '', '', '', '', '' ]
);
my $march_9 = DateTime->new(year => 2017, month => 3, day => 9);
my $invoice2 = create_sales_invoice(
$datev_lines = $datev->generate_datev_lines;
note('testing purchase invoice');
-my $purchase_invoice = new_purchase_invoice();
+my $purchase_invoice = create_ap_transaction(
+ vendor => $vendor,
+ invnumber => 'ap1',
+ amount => '226',
+ netamount => '200',
+ transdate => $date,
+ gldate => $date,
+ itime => $date, # make sure itime is 1.1., as gldatefrom tests for itime!
+ taxincluded => 0,
+ bookings => [
+ {
+ chart => SL::DB::Manager::Chart->find_by(accno => '3400'),
+ amount => 100,
+ },
+ {
+ chart => SL::DB::Manager::Chart->find_by(accno => '3300'),
+ amount => 100,
+ },
+ ],
+);
+
$datev1 = SL::DATEV->new(
dbh => $purchase_invoice->db->dbh,
trans_id => $purchase_invoice->id,
'kost1' => undef,
'kost2' => undef,
'umsatz' => 119,
+ 'locked' => undef,
'waehrung' => 'EUR'
},
{
'kost1' => undef,
'kost2' => undef,
'umsatz' => 107,
+ 'locked' => undef,
'waehrung' => 'EUR'
}
], "trans_id datev check purchase_invoice ok";
'kost1' => undef,
'kost2' => undef,
'umsatz' => 119,
+ 'locked' => undef,
'waehrung' => 'EUR'
},
{
'kost1' => undef,
'kost2' => undef,
'umsatz' => 107,
+ 'locked' => undef,
'waehrung' => 'EUR'
}
], "trans_id datev check purchase_invoice use_pk ok";
note('testing gldatefrom');
+# test an order with transdate in january, but that was booked in march
+# gldatefrom in DATEV.pm checks for itime, not gldate!!!
$datev = SL::DATEV->new(
dbh => $dbh,
from => $startdate,
done_testing();
clear_up();
-sub new_purchase_invoice {
- # manually create a Kreditorenbuchung from scratch, ap + acc_trans bookings, as no helper exists yet, like $invoice->post.
- # arap-Booking must come last in the acc_trans order
- # this function was essentially copied from t/db_helper/payment.t, refactor once $purchase_invoice->post exists
- my $currency_id = $::instance_conf->get_currency_id;
- my $employee = SL::DB::Manager::Employee->current || die "No employee";
- my $taxzone = SL::DB::Manager::TaxZone->find_by( description => 'Inland') || die "No taxzone";
-
- my $purchase_invoice = SL::DB::PurchaseInvoice->new(
- amount => '226',
- currency_id => $currency_id,
- employee_id => $employee->id,
- gldate => $date,
- invnumber => "ap1",
- invoice => 0,
- itime => $date,
- mtime => $date,
- netamount => '200',
- paid => '0',
- taxincluded => 0,
- taxzone_id => $taxzone->id,
- transdate => $date,
- type => 'invoice',
- vendor_id => $vendor->id,
- )->save;
-
- my $expense_chart = SL::DB::Manager::Chart->find_by(accno => '3400');
- my $expense_chart_booking= SL::DB::AccTransaction->new(
- amount => '-100',
- chart_id => $expense_chart->id,
- chart_link => $expense_chart->link,
- itime => $date,
- mtime => $date,
- source => '',
- tax_id => SL::DB::Manager::Tax->find_by(taxkey => 9)->id,
- taxkey => 9,
- transdate => $date,
- trans_id => $purchase_invoice->id,
- );
- $expense_chart_booking->save;
-
- my $tax_chart = SL::DB::Manager::Chart->find_by(accno => '1576');
- my $tax_chart_booking= SL::DB::AccTransaction->new(
- amount => '-19',
- chart_id => $tax_chart->id,
- chart_link => $tax_chart->link,
- itime => $date,
- mtime => $date,
- source => '',
- tax_id => SL::DB::Manager::Tax->find_by(taxkey => 9)->id,
- taxkey => 0,
- transdate => $date,
- trans_id => $purchase_invoice->id,
- );
- $tax_chart_booking->save;
- $expense_chart = SL::DB::Manager::Chart->find_by(accno => '3300');
- $expense_chart_booking= SL::DB::AccTransaction->new(
- amount => '-100',
- chart_id => $expense_chart->id,
- chart_link => $expense_chart->link,
- itime => $date,
- mtime => $date,
- source => '',
- tax_id => SL::DB::Manager::Tax->find_by(taxkey => 8)->id,
- taxkey => 8,
- transdate => $date,
- trans_id => $purchase_invoice->id,
- );
- $expense_chart_booking->save;
-
- $tax_chart = SL::DB::Manager::Chart->find_by(accno => '1571');
- $tax_chart_booking= SL::DB::AccTransaction->new(
- trans_id => $purchase_invoice->id,
- chart_id => $tax_chart->id,
- chart_link => $tax_chart->link,
- amount => '-7',
- transdate => $date,
- itime => $date,
- mtime => $date,
- source => '',
- taxkey => 0,
- tax_id => SL::DB::Manager::Tax->find_by(taxkey => 8)->id,
- );
- $tax_chart_booking->save;
- my $arap_chart = SL::DB::Manager::Chart->find_by(accno => '1600');
- my $arap_booking= SL::DB::AccTransaction->new(
- trans_id => $purchase_invoice->id,
- chart_id => $arap_chart->id,
- chart_link => $arap_chart->link,
- amount => '226',
- transdate => $date,
- itime => $date,
- mtime => $date,
- source => '',
- taxkey => 0,
- tax_id => SL::DB::Manager::Tax->find_by(taxkey => 0)->id,
- );
- $arap_booking->save;
-
- return $purchase_invoice;
-}
-
sub clear_up {
SL::DB::Manager::AccTransaction->delete_all(all => 1);
SL::DB::Manager::InvoiceItem->delete_all( all => 1);
use SL::Dev::ALL qw(:ALL);
my ($customer, $employee, $payment_do, $unit, @parts, $department);
+my ($transdate);
my $VISUAL_TEST = 0; # just a sleep to click around
clear_up();
+ $transdate = DateTime->today_local;
+ $transdate->set_year(2019) if $transdate->year == 2020; # use year 2019 in 2020, because of tax rate change in Germany
+
$unit = SL::DB::Manager::Unit->find_by(name => 'kg') || die "Can't find unit 'kg'";
$customer = new_customer()->save;
# we create L20199 with two items
my $do1 = create_sales_delivery_order(
'department_id' => $department->id,
+ 'transdate' => $transdate,
'donumber' => 'L20199',
'employee_id' => $employee->id,
'intnotes' => 'some intnotes',
# convert this do to invoice
-my $invoice = $do1->convert_to_invoice();
+my $invoice = $do1->convert_to_invoice(transdate => $transdate);
sleep (300) if $VISUAL_TEST; # we can do a real visual test via gui login
# test invoice afterwards
1;
-
# vim: ft=perl
# set emacs to perl mode
# Local Variables:
use Test::Exception;
use List::Util qw(sum);
-use SL::Dev::Record qw(create_invoice_item create_sales_invoice create_credit_note);
+use SL::Dev::Record qw(create_invoice_item create_sales_invoice create_credit_note create_ap_transaction);
use SL::Dev::CustomerVendor qw(new_customer new_vendor);
use SL::Dev::Part qw(new_part);
use SL::DB::Buchungsgruppe;
my ($transdate1, $transdate2, $transdate3, $transdate4, $currency, $exchangerate, $exchangerate2, $exchangerate3, $exchangerate4);
my ($ar_chart,$bank,$ar_amount_chart, $ap_chart, $ap_amount_chart, $fxloss_chart, $fxgain_chart);
-my $purchase_invoice_counter = 0; # used for generating purchase invnumber
+my $ap_transaction_counter = 0; # used for generating purchase invnumber
Support::TestSetup::login();
test_default_invoice_two_items_19_7_without_skonto();
test_default_invoice_two_items_19_7_without_skonto_incomplete_payment();
test_default_invoice_two_items_19_7_tax_without_skonto_multiple_payments();
-test_default_purchase_invoice_two_charts_19_7_without_skonto();
-test_default_purchase_invoice_two_charts_19_7_tax_partial_unrounded_payment_without_skonto();
+test_default_ap_transaction_two_charts_19_7_without_skonto();
+test_default_ap_transaction_two_charts_19_7_tax_partial_unrounded_payment_without_skonto();
test_default_invoice_one_item_19_without_skonto_overpaid();
test_credit_note_two_items_19_7_tax_tax_not_included();
test_default_invoice_two_items_19_7_tax_without_skonto_multiple_payments_final_difference_as_skonto_2cent();
test_default_invoice_one_item_19_multiple_payment_final_difference_as_skonto();
test_default_invoice_one_item_19_multiple_payment_final_difference_as_skonto_1cent();
-test_default_purchase_invoice_two_charts_19_7_tax_without_skonto_multiple_payments_final_difference_as_skonto();
+test_default_ap_transaction_two_charts_19_7_tax_without_skonto_multiple_payments_final_difference_as_skonto();
# test cases: with_skonto_pt
test_default_invoice_two_items_19_7_tax_with_skonto_50_50();
test_default_invoice_four_items_19_7_tax_with_skonto_4x_25();
test_default_invoice_four_items_19_7_tax_with_skonto_4x_25_multiple();
-test_default_purchase_invoice_two_charts_19_7_with_skonto();
+test_default_ap_transaction_two_charts_19_7_with_skonto();
test_default_invoice_four_items_19_7_tax_with_skonto_4x_25_tax_included();
test_default_invoice_two_items_19_7_tax_with_skonto_tax_included();
clear_up();
- $transdate1 = DateTime->today;
- $transdate2 = DateTime->today->add(days => 1);
- $transdate3 = DateTime->today->add(days => 2);
- $transdate4 = DateTime->today->add(days => 3);
+ $transdate1 = DateTime->today_local;
+ $transdate1->set_year(2019) if $transdate1->year == 2020; # hardcode for 2019 in 2020, because of tax rate change in Germany
+ $transdate2 = $transdate1->clone->add(days => 1);
+ $transdate3 = $transdate1->clone->add(days => 2);
+ $transdate4 = $transdate1->clone->add(days => 3);
$buchungsgruppe = SL::DB::Manager::Buchungsgruppe->find_by(description => 'Standard 19%') || croak "No accounting group";
$buchungsgruppe7 = SL::DB::Manager::Buchungsgruppe->find_by(description => 'Standard 7%') || croak "No accounting group for 7\%";
$ap_amount_chart = SL::DB::Manager::Chart->find_by( accno => '3400' ); # Wareneingang 19%
}
-sub new_purchase_invoice {
- # my %params = @_;
- # manually create a Kreditorenbuchung from scratch, ap + acc_trans bookings, as no helper exists yet, like $invoice->post.
- # arap-Booking must come last in the acc_trans order
- $purchase_invoice_counter++;
+sub new_ap_transaction {
+ $ap_transaction_counter++;
- my $purchase_invoice = SL::DB::PurchaseInvoice->new(
- vendor_id => $vendor->id,
- invnumber => 'newap ' . $purchase_invoice_counter ,
- currency_id => $currency_id,
- employee_id => $employee->id,
- gldate => $transdate1,
- taxzone_id => $taxzone->id,
- transdate => $transdate1,
- invoice => 0,
- type => 'invoice',
+ my $ap_transaction = create_ap_transaction(
+ vendor => $vendor,
+ invnumber => 'newap ' . $ap_transaction_counter,
taxincluded => 0,
amount => '226',
netamount => '200',
- paid => '0',
- # %params,
- )->save;
-
- my $expense_chart = SL::DB::Manager::Chart->find_by(accno => '3400');
- my $expense_chart_booking= SL::DB::AccTransaction->new(
- trans_id => $purchase_invoice->id,
- chart_id => $expense_chart->id,
- chart_link => $expense_chart->link,
- amount => '-100',
- transdate => $transdate1,
- source => '',
- taxkey => 9,
- tax_id => SL::DB::Manager::Tax->find_by(taxkey => 9)->id);
- $expense_chart_booking->save;
-
- my $tax_chart = SL::DB::Manager::Chart->find_by(accno => '1576');
- my $tax_chart_booking= SL::DB::AccTransaction->new(
- trans_id => $purchase_invoice->id,
- chart_id => $tax_chart->id,
- chart_link => $tax_chart->link,
- amount => '-19',
- transdate => $transdate1,
- source => '',
- taxkey => 0,
- tax_id => SL::DB::Manager::Tax->find_by(taxkey => 9)->id);
- $tax_chart_booking->save;
- $expense_chart = SL::DB::Manager::Chart->find_by(accno => '3300');
- $expense_chart_booking= SL::DB::AccTransaction->new(
- trans_id => $purchase_invoice->id,
- chart_id => $expense_chart->id,
- chart_link => $expense_chart->link,
- amount => '-100',
- transdate => $transdate1,
- source => '',
- taxkey => 8,
- tax_id => SL::DB::Manager::Tax->find_by(taxkey => 8)->id);
- $expense_chart_booking->save;
-
-
- $tax_chart = SL::DB::Manager::Chart->find_by(accno => '1571');
- $tax_chart_booking= SL::DB::AccTransaction->new(
- trans_id => $purchase_invoice->id,
- chart_id => $tax_chart->id,
- chart_link => $tax_chart->link,
- amount => '-7',
- transdate => $transdate1,
- source => '',
- taxkey => 0,
- tax_id => SL::DB::Manager::Tax->find_by(taxkey => 8)->id);
- $tax_chart_booking->save;
- my $arap_chart = SL::DB::Manager::Chart->find_by(accno => '1600');
- my $arap_booking= SL::DB::AccTransaction->new(trans_id => $purchase_invoice->id,
- chart_id => $arap_chart->id,
- chart_link => $arap_chart->link,
- amount => '226',
- transdate => $transdate1,
- source => '',
- taxkey => 0,
- tax_id => SL::DB::Manager::Tax->find_by(taxkey => 0)->id);
- $arap_booking->save;
-
- return $purchase_invoice;
+ gldate => $transdate1,
+ taxzone_id => $taxzone->id,
+ transdate => $transdate1,
+ bookings => [
+ {
+ chart => SL::DB::Manager::Chart->find_by(accno => '3400'),
+ amount => 100,
+ },
+ {
+ chart => SL::DB::Manager::Chart->find_by(accno => '3300'),
+ amount => 100,
+ },
+ ],
+ );
+
+ return $ap_transaction;
}
sub number_of_payments {
if ( $transaction->chart_link =~ /(AR_paid|AP_paid)/ ) {
$paid_amount += $transaction->amount ;
$number_of_payments++;
- };
+ }
};
return ($number_of_payments, $paid_amount);
};
my $title = 'default invoice, one item, 19% tax, without_skonto';
my $item = create_invoice_item(part => $parts[0], qty => 2.5);
my $invoice = create_sales_invoice(
+ transdate => $transdate1,
taxincluded => 0,
invoiceitems => [ $item ],
payment_id => $payment_terms->id,
);
- my $purchase_invoice = new_purchase_invoice();
+ my $ap_transaction = new_ap_transaction();
# default values
my %params = ( chart_id => $bank_account->chart_id,
- transdate => DateTime->today_local->to_kivitendo
+ transdate => $transdate1,
);
$params{amount} = '6.96';
my $item = create_invoice_item(part => $parts[0], qty => 2.5);
my $invoice = create_sales_invoice(
taxincluded => 0,
+ transdate => $transdate1,
invoiceitems => [ $item ],
payment_id => $payment_terms->id,
);
- my $purchase_invoice = new_purchase_invoice();
+ my $ap_transaction = new_ap_transaction();
# default values
my %params = ( chart_id => $bank_account->chart_id,
- transdate => DateTime->today_local->to_kivitendo
+ transdate => $transdate1,
);
$params{amount} = '16.96';
my $item2 = create_invoice_item(part => $parts[1], qty => 1.2);
my $invoice = create_sales_invoice(
taxincluded => 0,
+ transdate => $transdate1,
invoiceitems => [ $item1, $item2 ],
payment_id => $payment_terms->id,
);
# default values
my %params = ( chart_id => $bank_account->chart_id,
- transdate => DateTime->today_local->to_kivitendo
+ transdate => $transdate1,
);
$params{payment_type} = 'with_skonto_pt';
my $item1 = create_invoice_item(part => $parts[0], qty => 2.5);
my $item2 = create_invoice_item(part => $parts[1], qty => 1.2);
my $invoice = create_sales_invoice(
+ transdate => $transdate1,
taxincluded => 1,
invoiceitems => [ $item1, $item2 ],
payment_id => $payment_terms->id,
# default values
my %params = ( chart_id => $bank_account->chart_id,
- transdate => DateTime->today_local->to_kivitendo
+ transdate => $transdate1,
);
$params{payment_type} = 'with_skonto_pt';
my $item1 = create_invoice_item(part => $parts[0], qty => 2.5);
my $item2 = create_invoice_item(part => $parts[1], qty => 1.2);
my $invoice = create_sales_invoice(
+ transdate => $transdate1,
taxincluded => 0,
invoiceitems => [ $item1, $item2 ],
payment_id => $payment_terms->id,
# default values
my %params = ( chart_id => $bank_account->chart_id,
- transdate => DateTime->today_local->to_kivitendo
+ transdate => $transdate1,
);
$params{amount} = '19.44'; # pass full amount
my $item2 = create_invoice_item(part => $parts[1], qty => 1.2);
my $invoice = create_sales_invoice(
taxincluded => 0,
+ transdate => $transdate1,
invoiceitems => [ $item1, $item2 ],
payment_id => $payment_terms->id,
);
$invoice->pay_invoice( amount => '9.44',
payment_type => 'without_skonto',
chart_id => $bank_account->chart_id,
- transdate => DateTime->today_local->to_kivitendo,
+ transdate => $transdate1,
);
my ($number_of_payments, $paid_amount) = number_of_payments($invoice);
my $item1 = create_invoice_item(part => $parts[0], qty => 2.5);
my $item2 = create_invoice_item(part => $parts[1], qty => 1.2);
my $invoice = create_sales_invoice(
+ transdate => $transdate1,
taxincluded => 0,
invoiceitems => [ $item1, $item2 ],
payment_id => $payment_terms->id,
$invoice->pay_invoice( amount => '9.44',
payment_type => 'without_skonto',
chart_id => $bank_account->chart_id,
- transdate => DateTime->today_local->to_kivitendo
+ transdate => $transdate1,
);
$invoice->pay_invoice( amount => '10.00',
chart_id => $bank_account->chart_id,
- transdate => DateTime->today_local->to_kivitendo
+ transdate => $transdate1,
);
my ($number_of_payments, $paid_amount) = number_of_payments($invoice);
my $item2 = create_invoice_item(part => $parts[1], qty => 1.2);
my $invoice = create_sales_invoice(
taxincluded => 0,
+ transdate => $transdate1,
invoiceitems => [ $item1, $item2 ],
payment_id => $payment_terms->id,
);
$invoice->pay_invoice( amount => '9.44',
payment_type => 'without_skonto',
chart_id => $bank_account->chart_id,
- transdate => DateTime->today_local->to_kivitendo
+ transdate => $transdate1,
);
$invoice->pay_invoice( amount => '8.73',
payment_type => 'without_skonto',
chart_id => $bank_account->chart_id,
- transdate => DateTime->today_local->to_kivitendo
+ transdate => $transdate1,
);
$invoice->pay_invoice( amount => $invoice->open_amount,
payment_type => 'difference_as_skonto',
chart_id => $bank_account->chart_id,
- transdate => DateTime->today_local->to_kivitendo
+ transdate => $transdate1,
);
my ($number_of_payments, $paid_amount) = number_of_payments($invoice);
my $item2 = create_invoice_item(part => $parts[1], qty => 1.2);
my $invoice = create_sales_invoice(
taxincluded => 0,
+ transdate => $transdate1,
invoiceitems => [ $item1, $item2 ],
payment_id => $payment_terms->id,
);
$invoice->pay_invoice( amount => '19.42',
payment_type => 'without_skonto',
chart_id => $bank_account->chart_id,
- transdate => DateTime->today_local->to_kivitendo
+ transdate => $transdate1,
);
$invoice->pay_invoice( amount => $invoice->open_amount,
payment_type => 'difference_as_skonto',
chart_id => $bank_account->chart_id,
- transdate => DateTime->today_local->to_kivitendo
+ transdate => $transdate1,
);
my ($number_of_payments, $paid_amount) = number_of_payments($invoice);
my $item2 = create_invoice_item(part => $parts[1], qty => 1.2);
my $invoice = create_sales_invoice(
taxincluded => 0,
+ transdate => $transdate1,
invoiceitems => [ $item1, $item2 ],
payment_id => $payment_terms->id,
);
$invoice->pay_invoice( amount => '19.42',
payment_type => 'without_skonto',
chart_id => $bank_account->chart_id,
- transdate => DateTime->today_local->to_kivitendo
+ transdate => $transdate1,
);
$invoice->pay_invoice( amount => $invoice->open_amount,
payment_type => 'difference_as_skonto',
chart_id => $bank_account->chart_id,
- transdate => DateTime->today_local->to_kivitendo
+ transdate => $transdate1,
);
my ($number_of_payments, $paid_amount) = number_of_payments($invoice);
my $item = create_invoice_item(part => $parts[0], qty => 2.5);
my $invoice = create_sales_invoice(
taxincluded => 0,
+ transdate => $transdate1,
invoiceitems => [ $item ],
payment_id => $payment_terms->id,
);
# default values
my %params = ( chart_id => $bank_account->chart_id,
- transdate => DateTime->today_local->to_kivitendo
+ transdate => $transdate1,
);
$params{amount} = '2.32';
my $item = create_invoice_item(part => $parts[0], qty => 2.5);
my $invoice = create_sales_invoice(
taxincluded => 0,
+ transdate => $transdate1,
invoiceitems => [ $item ],
payment_id => $payment_terms->id,
);
# default values
my %params = ( chart_id => $bank_account->chart_id,
- transdate => DateTime->today_local->to_kivitendo
+ transdate => $transdate1,
);
$params{amount} = '6.95';
}
# test 3 : two items, without skonto
-sub test_default_purchase_invoice_two_charts_19_7_without_skonto {
+sub test_default_ap_transaction_two_charts_19_7_without_skonto {
my $title = 'default invoice, two items, 19/7% tax without skonto';
- my $purchase_invoice = new_purchase_invoice();
+ my $ap_transaction = new_ap_transaction();
my %params = ( chart_id => $bank_account->chart_id,
- transdate => DateTime->today_local->to_kivitendo
+ transdate => $transdate1,
);
$params{amount} = '226'; # pass full amount
$params{payment_type} = 'without_skonto';
- $purchase_invoice->pay_invoice( %params );
+ $ap_transaction->pay_invoice( %params );
- my ($number_of_payments, $paid_amount) = number_of_payments($purchase_invoice);
- my $total = total_amount($purchase_invoice);
+ my ($number_of_payments, $paid_amount) = number_of_payments($ap_transaction);
+ my $total = total_amount($ap_transaction);
is($paid_amount, 226, "${title}: paid amount");
is($number_of_payments, 1, "${title}: 1 AP_paid bookings");
}
-sub test_default_purchase_invoice_two_charts_19_7_with_skonto {
+sub test_default_ap_transaction_two_charts_19_7_with_skonto {
my $title = 'default invoice, two items, 19/7% tax without skonto';
- my $purchase_invoice = new_purchase_invoice();
+ my $ap_transaction = new_ap_transaction();
my %params = ( chart_id => $bank_account->chart_id,
- transdate => DateTime->today_local->to_kivitendo
+ transdate => $transdate1,
);
# $params{amount} = '226'; # pass full amount
$params{payment_type} = 'with_skonto_pt';
- $purchase_invoice->pay_invoice( %params );
+ $ap_transaction->payment_terms($ap_transaction->vendor->payment);
+ $ap_transaction->pay_invoice( %params );
- my ($number_of_payments, $paid_amount) = number_of_payments($purchase_invoice);
- my $total = total_amount($purchase_invoice);
+ my ($number_of_payments, $paid_amount) = number_of_payments($ap_transaction);
+ my $total = total_amount($ap_transaction);
is($paid_amount, 226, "${title}: paid amount");
is($number_of_payments, 3, "${title}: 1 AP_paid bookings");
}
-sub test_default_purchase_invoice_two_charts_19_7_tax_partial_unrounded_payment_without_skonto {
- my $title = 'default purchase_invoice, two charts, 19/7% tax multiple payments with final difference as skonto';
+sub test_default_ap_transaction_two_charts_19_7_tax_partial_unrounded_payment_without_skonto {
+ my $title = 'default ap_transaction, two charts, 19/7% tax multiple payments with final difference as skonto';
# check whether unrounded amounts passed via $params{amount} are rounded for without_skonto case
- my $purchase_invoice = new_purchase_invoice();
- $purchase_invoice->pay_invoice(
- amount => ( $purchase_invoice->amount / 3 * 2),
+ my $ap_transaction = new_ap_transaction();
+ $ap_transaction->pay_invoice(
+ amount => ( $ap_transaction->amount / 3 * 2),
payment_type => 'without_skonto',
chart_id => $bank_account->chart_id,
- transdate => DateTime->today_local->to_kivitendo
+ transdate => $transdate1,
);
- my ($number_of_payments, $paid_amount) = number_of_payments($purchase_invoice);
- my $total = total_amount($purchase_invoice);
+ my ($number_of_payments, $paid_amount) = number_of_payments($ap_transaction);
+ my $total = total_amount($ap_transaction);
is($paid_amount, 150.67, "${title}: paid amount");
is($number_of_payments, 1, "${title}: 1 AP_paid bookings");
};
-sub test_default_purchase_invoice_two_charts_19_7_tax_without_skonto_multiple_payments_final_difference_as_skonto {
- my $title = 'default purchase_invoice, two charts, 19/7% tax multiple payments with final difference as skonto';
+sub test_default_ap_transaction_two_charts_19_7_tax_without_skonto_multiple_payments_final_difference_as_skonto {
+ my $title = 'default ap_transaction, two charts, 19/7% tax multiple payments with final difference as skonto';
- my $purchase_invoice = new_purchase_invoice();
+ my $ap_transaction = new_ap_transaction();
# pay 2/3 and 1/5, leaves 3.83% to be used as Skonto
- $purchase_invoice->pay_invoice(
- amount => ( $purchase_invoice->amount / 3 * 2),
+ $ap_transaction->pay_invoice(
+ amount => ( $ap_transaction->amount / 3 * 2),
payment_type => 'without_skonto',
chart_id => $bank_account->chart_id,
- transdate => DateTime->today_local->to_kivitendo
+ transdate => $transdate1,
);
- $purchase_invoice->pay_invoice(
- amount => ( $purchase_invoice->amount / 5 ),
+ $ap_transaction->pay_invoice(
+ amount => ( $ap_transaction->amount / 5 ),
payment_type => 'without_skonto',
chart_id => $bank_account->chart_id,
- transdate => DateTime->today_local->to_kivitendo
+ transdate => $transdate1,
);
- $purchase_invoice->pay_invoice(
+ $ap_transaction->pay_invoice(
payment_type => 'difference_as_skonto',
chart_id => $bank_account->chart_id,
- transdate => DateTime->today_local->to_kivitendo
+ transdate => $transdate1,
);
- my ($number_of_payments, $paid_amount) = number_of_payments($purchase_invoice);
- my $total = total_amount($purchase_invoice);
+ my ($number_of_payments, $paid_amount) = number_of_payments($ap_transaction);
+ my $total = total_amount($ap_transaction);
is($paid_amount, 226, "${title}: paid amount");
is($number_of_payments, 4, "${title}: 1 AP_paid bookings");
my $item2 = create_invoice_item(part => $parts[3], qty => 1);
my $invoice = create_sales_invoice(
taxincluded => 0,
+ transdate => $transdate1,
invoiceitems => [ $item1, $item2 ],
payment_id => $payment_terms->id,
);
# default values
my %params = ( chart_id => $bank_account->chart_id,
- transdate => DateTime->today_local->to_kivitendo
+ transdate => $transdate1,
);
$params{amount} = $invoice->amount_less_skonto;
my $item4 = create_invoice_item(part => $parts[3], qty => 0.5);
my $invoice = create_sales_invoice(
taxincluded => 0,
+ transdate => $transdate1,
invoiceitems => [ $item1, $item2, $item3, $item4 ],
payment_id => $payment_terms->id,
);
# default values
my %params = ( chart_id => $bank_account->chart_id,
- transdate => DateTime->today_local->to_kivitendo
+ transdate => $transdate1,
);
$params{amount} = $invoice->amount_less_skonto;
my $item4 = create_invoice_item(part => $parts[3], qty => 0.5);
my $invoice = create_sales_invoice(
taxincluded => 1,
+ transdate => $transdate1,
invoiceitems => [ $item1, $item2, $item3, $item4 ],
payment_id => $payment_terms->id,
);
# default values
my %params = ( chart_id => $bank_account->chart_id,
- transdate => DateTime->today_local->to_kivitendo
+ transdate => $transdate1,
);
$params{amount} = $invoice->amount_less_skonto;
my $item4 = create_invoice_item(part => $parts[3], qty => 0.5);
my $invoice = create_sales_invoice(
taxincluded => 0,
+ transdate => $transdate1,
invoiceitems => [ $item1, $item2, $item3, $item4 ],
payment_id => $payment_terms->id,
);
$invoice->pay_invoice( amount => '90',
payment_type => 'without_skonto',
chart_id => $bank_account->chart_id,
- transdate => DateTime->today_local->to_kivitendo
+ transdate => $transdate1,
);
$invoice->pay_invoice( payment_type => 'difference_as_skonto',
chart_id => $bank_account->chart_id,
- transdate => DateTime->today_local->to_kivitendo
+ transdate => $transdate1,
);
my ($number_of_payments, $paid_amount) = number_of_payments($invoice);
my $item2 = create_invoice_item(part => $parts[1], qty => 3);
my $invoice = create_credit_note(
invnumber => 'cn1',
+ transdate => $transdate1,
taxincluded => 0,
invoiceitems => [ $item1, $item2 ],
);
# default values
my %params = ( chart_id => $bank_account->chart_id,
- transdate => DateTime->today_local->to_kivitendo,
+ transdate => $transdate1,
);
$params{amount} = $invoice->amount,
use SL::DB::TaxZone;
my ($customer, @parts, $buchungsgruppe, $buchungsgruppe7, $unit, $employee, $tax, $tax7, $taxzone);
+my ($transdate);
sub clear_up {
SL::DB::Manager::Order->delete_all(all => 1);
my %params = @_;
return create_sales_invoice(
+ transdate => $transdate,
taxzone_id => $taxzone->id,
%params,
);
invoiceitems => [ $item ],
);
- my $taxkey = $item->part->get_taxkey(date => DateTime->today_local, is_sales => 1, taxzone => $invoice->taxzone_id);
+ my $taxkey = $item->part->get_taxkey(date => $transdate, is_sales => 1, taxzone => $invoice->taxzone_id);
# sellprice 2.34 * qty 2.5 = 5.85
# 19%(5.85) = 1.1115; rounded = 1.11
[],
],
exchangerate => 1,
- taxes => {
+ taxes_by_chart_id => {
$tax->chart_id => 1.11,
},
+ taxes_by_tax_id => {
+ $tax->id => 1.1115,
+ },
items => [
{ linetotal => 5.85,
linetotal_cost => 4.83,
invoiceitems => [ $item1, $item2 ],
);
- my $taxkey1 = $item1->part->get_taxkey(date => DateTime->today_local, is_sales => 1, taxzone => $invoice->taxzone_id);
- my $taxkey2 = $item2->part->get_taxkey(date => DateTime->today_local, is_sales => 1, taxzone => $invoice->taxzone_id);
+ my $taxkey1 = $item1->part->get_taxkey(date => $transdate, is_sales => 1, taxzone => $invoice->taxzone_id);
+ my $taxkey2 = $item2->part->get_taxkey(date => $transdate, is_sales => 1, taxzone => $invoice->taxzone_id);
# item 1:
# sellprice 2.34 * qty 2.5 = 5.85
# item 2:
# sellprice 9.714 * qty 1.2 = 11.6568 rounded 11.66
# 7%(11.6568) = 0.815976; rounded = 0.82
+ # 7%(11.66) = 0.8162
# total rounded = 12.48
# lastcost 5.473 * qty 1.2 = 6.5676; rounded 6.57
[], [],
],
exchangerate => 1,
- taxes => {
+ taxes_by_chart_id => {
$tax->chart_id => 1.11,
$tax7->chart_id => 0.82,
},
+ taxes_by_tax_id => {
+ $tax->id => 1.1115,
+ $tax7->id => 0.8162,
+ },
items => [
{ linetotal => 5.85,
linetotal_cost => 4.83,
invoiceitems => [ $item1, $item2, $item3 ],
);
- my %taxkeys = map { ($_->id => $_->get_taxkey(date => DateTime->today_local, is_sales => 1, taxzone => $invoice->taxzone_id)) } uniq map { $_->part } ($item1, $item2, $item3);
+ my %taxkeys = map { ($_->id => $_->get_taxkey(date => $transdate, is_sales => 1, taxzone => $invoice->taxzone_id)) } uniq map { $_->part } ($item1, $item2, $item3);
# item 1:
# discount = sellprice 5.55 * discount (0.05) = 0.2775; rounded 0.28
[], [], [],
],
exchangerate => 1,
- taxes => {
+ taxes_by_chart_id => {
$tax->chart_id => 2.9,
},
+ taxes_by_tax_id => {
+ $tax->id => 2.89750,
+ },
items => [
{ linetotal => 5.27,
linetotal_cost => 1.93,
invoiceitems => [ $item ],
);
- my %taxkeys = map { ($_->id => $_->get_taxkey(date => DateTime->today_local, is_sales => 1, taxzone => $invoice->taxzone_id)) } uniq map { $_->part } ($item);
+ my %taxkeys = map { ($_->id => $_->get_taxkey(date => $transdate, is_sales => 1, taxzone => $invoice->taxzone_id)) } uniq map { $_->part } ($item);
# 6 parts for 0.60 with 3% discount
#
[],
],
exchangerate => 1,
- taxes => {
+ taxes_by_chart_id => {
$tax->chart_id => 0.66,
},
+ taxes_by_tax_id => {
+ $tax->id => 0.66310,
+ },
items => [
{ linetotal => 3.49,
linetotal_cost => 0,
invoiceitems => [ $item ],
);
- my %taxkeys = map { ($_->id => $_->get_taxkey(date => DateTime->today_local, is_sales => 1, taxzone => $invoice->taxzone_id)) } uniq map { $_->part } ($item);
+ my %taxkeys = map { ($_->id => $_->get_taxkey(date => $transdate, is_sales => 1, taxzone => $invoice->taxzone_id)) } uniq map { $_->part } ($item);
my $title = 'default invoice, one item, 19% tax not included, rounding, discount, huge qty';
my %data = $invoice->calculate_prices_and_taxes;
[],
],
exchangerate => 1,
- taxes => {
+ taxes_by_chart_id => {
$tax->chart_id => 1843,
},
+ taxes_by_tax_id => {
+ $tax->id => 1843,
+ },
items => [
{ linetotal => 9700,
linetotal_cost => 0,
invoiceitems => [ $item ],
);
- my %taxkeys = map { ($_->id => $_->get_taxkey(date => DateTime->today_local, is_sales => 1, taxzone => $invoice->taxzone_id)) } uniq map { $_->part } ($item);
+ my %taxkeys = map { ($_->id => $_->get_taxkey(date => $transdate, is_sales => 1, taxzone => $invoice->taxzone_id)) } uniq map { $_->part } ($item);
# item 1:
# discount = sellprice 0.007 * discount (0.035) = 0.000245; rounded 0.00
[],
],
exchangerate => 1,
- taxes => {
+ taxes_by_chart_id => {
$tax->chart_id => 12.84,
},
+ taxes_by_tax_id => {
+ $tax->id => 12.8364,
+ },
items => [
{ linetotal => 67.56,
linetotal_cost => 19301.93,
Support::TestSetup::login();
+$transdate = DateTime->today_local;
+$transdate->set_year(2019) if $transdate->year == 2020; # use year 2019 in 2020, because of tax rate change in Germany
+
test_default_invoice_one_item_19_tax_not_included();
test_default_invoice_two_items_19_7_tax_not_included();
test_default_invoice_three_items_sellprice_rounding_discount();
--- /dev/null
+use strict;
+use Test::More tests => 4;
+
+use lib 't';
+use Support::TestSetup;
+use Carp;
+use Test::Exception;
+use SL::DB::Chart;
+use SL::DB::TaxKey;
+use SL::DB::GLTransaction;
+use Data::Dumper;
+use SL::DBUtils qw(selectall_hashref_query);
+
+Support::TestSetup::login();
+
+clear_up();
+
+my $cash = SL::DB::Manager::Chart->find_by( description => 'Kasse' );
+my $bank = SL::DB::Manager::Chart->find_by( description => 'Bank' );
+my $betriebsbedarf = SL::DB::Manager::Chart->find_by( description => 'Betriebsbedarf' );
+
+my $tax_9 = SL::DB::Manager::Tax->find_by(taxkey => 9, rate => 0.19);
+my $tax_8 = SL::DB::Manager::Tax->find_by(taxkey => 8, rate => 0.07);
+my $tax_0 = SL::DB::Manager::Tax->find_by(taxkey => 0, rate => 0.00);
+
+my $dbh = SL::DB->client->dbh;
+
+# example with chaining of add_chart_booking
+my $gl_transaction = SL::DB::GLTransaction->new(
+ taxincluded => 1,
+ reference => 'bank/cash',
+ description => 'bank/cash',
+ transdate => DateTime->today_local,
+)->add_chart_booking(
+ chart => $cash,
+ credit => 100,
+ tax_id => $tax_0->id,
+)->add_chart_booking(
+ chart => $bank,
+ debit => 100,
+ tax_id => $tax_0->id,
+)->post;
+
+# example where bookings is prepared separately as an arrayref
+my $gl_transaction_2 = SL::DB::GLTransaction->new(
+ reference => 'betriebsbedarf several rows',
+ description => 'betriebsbedarf',
+ taxincluded => 1,
+ transdate => DateTime->today_local,
+);
+
+my $bookings = [
+ {
+ chart => $betriebsbedarf,
+ memo => 'foo 1',
+ source => 'foo 1',
+ debit => 119,
+ tax_id => $tax_9->id,
+ },
+ {
+ chart => $betriebsbedarf,
+ memo => 'foo 2',
+ source => 'foo 2',
+ debit => 119,
+ tax_id => $tax_9->id,
+ },
+ {
+ chart => $cash,
+ credit => 238,
+ memo => 'foo 1+2',
+ source => 'foo 1+2',
+ tax_id => $tax_0->id,
+ },
+ ];
+$gl_transaction_2->add_chart_booking(%{$_}) foreach @{ $bookings };
+$gl_transaction_2->post;
+
+
+# example where add_chart_booking is called via a foreach
+my $gl_transaction_3 = SL::DB::GLTransaction->new(
+ reference => 'betriebsbedarf tax included',
+ description => 'bar',
+ taxincluded => 1,
+ transdate => DateTime->today_local,
+);
+$gl_transaction_3->add_chart_booking(%{$_}) foreach (
+ {
+ chart => $betriebsbedarf,
+ debit => 119,
+ tax_id => $tax_9->id,
+ },
+ {
+ chart => $betriebsbedarf,
+ debit => 107,
+ tax_id => $tax_8->id,
+ },
+ {
+ chart => $betriebsbedarf,
+ debit => 100,
+ tax_id => $tax_0->id,
+ },
+ {
+ chart => $cash,
+ credit => 326,
+ tax_id => $tax_0->id,
+ },
+);
+$gl_transaction_3->post;
+
+my $gl_transaction_4 = SL::DB::GLTransaction->new(
+ reference => 'betriebsbedarf tax not included',
+ description => 'bar',
+ taxincluded => 0,
+ transdate => DateTime->today_local,
+);
+$gl_transaction_4->add_chart_booking(%{$_}) foreach (
+ {
+ chart => $betriebsbedarf,
+ debit => 100,
+ tax_id => $tax_9->id,
+ },
+ {
+ chart => $betriebsbedarf,
+ debit => 100,
+ tax_id => $tax_8->id,
+ },
+ {
+ chart => $betriebsbedarf,
+ debit => 100,
+ tax_id => $tax_0->id,
+ },
+ {
+ chart => $cash,
+ credit => 326,
+ tax_id => $tax_0->id,
+ },
+);
+$gl_transaction_4->post;
+
+is(SL::DB::Manager::GLTransaction->get_all_count(), 4, "gl transactions created ok");
+
+is_deeply(&get_account_balances,
+ [
+ {
+ 'accno' => '1000',
+ 'sum' => '990.00000'
+ },
+ {
+ 'accno' => '1200',
+ 'sum' => '-100.00000'
+ },
+ {
+ 'accno' => '1571',
+ 'sum' => '-14.00000'
+ },
+ {
+ 'accno' => '1576',
+ 'sum' => '-76.00000'
+ },
+ {
+ 'accno' => '4980',
+ 'sum' => '-800.00000'
+ }
+ ],
+ "chart balances ok"
+ );
+
+
+note('testing subcent');
+
+my $gl_transaction_5_taxinc = SL::DB::GLTransaction->new(
+ taxincluded => 1,
+ reference => 'subcent tax included',
+ description => 'subcent tax included',
+ transdate => DateTime->today_local,
+)->add_chart_booking(
+ chart => $betriebsbedarf,
+ debit => 0.02,
+ tax_id => $tax_9->id,
+)->add_chart_booking(
+ chart => $cash,
+ credit => 0.02,
+ tax_id => $tax_0->id,
+)->post;
+
+my $gl_transaction_5_taxnoinc = SL::DB::GLTransaction->new(
+ taxincluded => 0,
+ reference => 'subcent tax not included',
+ description => 'subcent tax not included',
+ transdate => DateTime->today_local,
+)->add_chart_booking(
+ chart => $betriebsbedarf,
+ debit => 0.02,
+ tax_id => $tax_9->id,
+)->add_chart_booking(
+ chart => $cash,
+ credit => 0.02,
+ tax_id => $tax_0->id,
+)->post;
+
+my $gl_transaction_6_taxinc = SL::DB::GLTransaction->new(
+ taxincluded => 1,
+ reference => 'cent tax included',
+ description => 'cent tax included',
+ transdate => DateTime->today_local,
+)->add_chart_booking(
+ chart => $betriebsbedarf,
+ debit => 0.05,
+ tax_id => $tax_9->id,
+)->add_chart_booking(
+ chart => $cash,
+ credit => 0.05,
+ tax_id => $tax_0->id,
+)->post;
+
+my $gl_transaction_6_taxnoinc = SL::DB::GLTransaction->new(
+ taxincluded => 0,
+ reference => 'cent tax included',
+ description => 'cent tax included',
+ transdate => DateTime->today_local,
+)->add_chart_booking(
+ chart => $betriebsbedarf,
+ debit => 0.04,
+ tax_id => $tax_9->id,
+)->add_chart_booking(
+ chart => $cash,
+ credit => 0.05,
+ tax_id => $tax_0->id,
+)->post;
+
+is(SL::DB::Manager::GLTransaction->get_all_count(), 8, "gl transactions created ok");
+
+
+is_deeply(&get_account_balances,
+ [
+ {
+ 'accno' => '1000',
+ 'sum' => '990.14000'
+ },
+ {
+ 'accno' => '1200',
+ 'sum' => '-100.00000'
+ },
+ {
+ 'accno' => '1571',
+ 'sum' => '-14.00000'
+ },
+ {
+ 'accno' => '1576',
+ 'sum' => '-76.02000'
+ },
+ {
+ 'accno' => '4980',
+ 'sum' => '-800.12000'
+ }
+ ],
+ "chart balances ok"
+ );
+
+done_testing;
+clear_up();
+
+1;
+
+sub clear_up {
+ "SL::DB::Manager::${_}"->delete_all(all => 1) for qw(
+ AccTransaction
+ GLTransaction
+ );
+};
+
+sub get_account_balances {
+ my $query = <<SQL;
+ select c.accno,
+ sum(a.amount)
+ from acc_trans a
+ left join chart c on (c.id = a.chart_id)
+group by c.accno
+order by c.accno;
+SQL
+
+ my $result = selectall_hashref_query($::form, $dbh, $query);
+ return $result;
+};
-use Test::More tests => 32;
+use Test::More tests => 44;
use lib 't';
is($p->sellprice_as_number('2,3442'), '2,3442');
is($p->sellprice, 2.3442);
is($p->sellprice_as_number, '2,3442');
+is($p->listprice_as_null_number('2,30'), '2,30');
+is($p->listprice, 2.30);
+is($p->listprice_as_null_number, '2,30');
+is($p->listprice_as_null_number('2,3442'), '2,3442');
+is($p->listprice, 2.3442);
+is($p->listprice_as_null_number, '2,3442');
+is($p->listprice_as_null_number(''), '');
+is($p->listprice, undef);
+is($p->listprice_as_null_number, '');
+is($p->listprice_as_null_number('0'), '0,00');
+is($p->listprice, 0);
+is($p->listprice_as_null_number, '0,00');
my $o = new_ok 'SL::DB::Order';
is($o->reqdate_as_date('11.12.2007'), '11.12.2007');
# defaults according to the database
$i->taxincluded(undef);
is $i->taxincluded_as_bool_yn, '', 'bool 3';
-
use Data::Dumper;
my ($shop, $shop_order, $shop_part, $part, $customer, $employee);
+my ($transdate);
sub reset_state {
my %params = @_;
clear_up();
+ $transdate = DateTime->today_local;
+ $transdate->set_year(2019) if $transdate->year == 2020; # use year 2019 in 2020, because of tax rate change in Germany
+
$shop = new_shop->save;
$part = new_part->save;
$shop_part = new_shop_part(part => $part, shop => $shop)->save;
)->save;
}
-sub save_shorcontroller_to_string {
+sub save_shopcontroller_to_string {
my $output;
open(my $outputFH, '>', \$output) or die "OUTPUT";
my $oldFH = select $outputFH;
- my $shor_controller = SL::Controller::ShopOrder->new;
- $shor_controller->action_transfer;
+ my $shop_controller = SL::Controller::ShopOrder->new;
+ $shop_controller->action_transfer;
select $oldFH;
close $outputFH;
$::form->{import_id} = $params{import_id};
$::form->{customer} = $params{customer};
my $test_name = 'Test Controller Action Transfer';
- save_shorcontroller_to_string();
+ save_shopcontroller_to_string();
my @links_record = RecordLinks->get_links( 'from_table' => 'shop_orders',
'from_id' => $params{import_id},
'to_table' => 'oe',
$shop_order = new_shop_order(
shop => $shop,
+ transfer_date => $transdate,
shop_trans_id => $shop_trans_id,
+ order_date => $transdate->datetime,
amount => 59.5,
billing_lastname => 'Schmidt',
billing_firstname => 'Sven',
is($shop_order->shop_id , $shop->id , "shop_id ok");
note('testing convert_to_sales_order');
-my $order = $shop_order->convert_to_sales_order(employee => $employee, customer => $customer);
+my $order = $shop_order->convert_to_sales_order(employee => $employee, customer => $customer, transdate => $shop_order->order_date);
$order->calculate_prices_and_taxes;
$order->save;
use Test::More;
my %default_columns;
+my %compatibility_functions = map { ($_ => 1) } qw(address);
sub read_default_columns {
my $content = read_file('SL/DB/MetaSetup/Default.pm');
my $content = read_file($file);
while ($content =~ m{(?:INSTANCE_CONF\.|\$(?:main)?::instance_conf->)get_([a-z0-9_]+)}gi) {
- ok($default_columns{$1}, "'get_${1}' is a valid method call on \$::instance_conf in $file");
+ ok($default_columns{$1} || $compatibility_functions{$1}, "'get_${1}' is a valid method call on \$::instance_conf in $file");
}
}
--- /dev/null
+use Test::More tests => 48;
+use Test::Deep qw(cmp_deeply);
+
+use strict;
+
+use lib 't';
+use utf8;
+
+use Support::TestSetup;
+use Test::Exception;
+
+use SL::DB::Customer;
+use SL::DB::Vendor;
+use SL::DB::Invoice;
+use SL::DB::GLTransaction;
+use SL::DB::AccTransaction;
+use SL::DB::Part;
+use SL::DB::PaymentTerm;
+use SL::DBUtils qw(selectall_hashref_query);
+use SL::Dev::Record qw(:ALL);
+use SL::Dev::CustomerVendor qw(new_customer new_vendor);
+use SL::Dev::Part qw(new_part);
+use SL::Dev::Payment qw(create_payment_terms);
+use Data::Dumper;
+
+Support::TestSetup::login();
+my $dbh = SL::DB->client->dbh;
+
+clear_up();
+
+# TODOs: Storno muß noch korrekt funktionieren
+# neue Konten für 5% anlegen
+# Leistungszeitraum vs. Datum Zuord. Steuerperiodest
+
+note('checking if all tax entries exist for Konjunkturprogramm');
+
+# create dates to test on
+my $date_2006 = DateTime->new(year => 2006, month => 6, day => 15);
+my $date_2020_1 = DateTime->new(year => 2020, month => 6, day => 15);
+my $date_2020_2 = DateTime->new(year => 2020, month => 7, day => 15);
+my $date_2021 = DateTime->new(year => 2021, month => 1, day => 15);
+
+# The only way to discern the pre-2007 16% tax from the 2020 16% tax is by
+# their configured automatic tax charts, so look them up here:
+
+my ($chart_vst_19, $chart_vst_16, $chart_vst_5, $chart_vst_7);
+my ($chart_ust_19, $chart_ust_16, $chart_ust_5, $chart_ust_7);
+my ($income_19_accno, $income_7_accno);
+my ($ar_accno, $ap_accno);
+my ($chart_reisekosten_accno, $chart_cash_accno, $chart_bank_accno);
+
+my ($skonto_5, $skonto_16, $skonto_7, $skonto_19); # store acc_trans entries during tests
+
+my $test_kontenrahmen = $::instance_conf->get_coa eq 'Germany-DATEV-SKR04EU' ? 'skr04' : 'skr03';
+
+if ( $test_kontenrahmen eq 'skr03' ) {
+
+ is(SL::DB::Default->get->coa, 'Germany-DATEV-SKR03EU', "coa SKR03 ok");
+
+ $chart_ust_19 = '1776';
+ $chart_ust_16 = '1775';
+ $chart_ust_5 = '1773';
+ $chart_ust_7 = '1771';
+
+ $chart_vst_19 = '1576';
+ $chart_vst_16 = '1575';
+ $chart_vst_5 = '1568';
+ $chart_vst_7 = '1571';
+
+ $income_19_accno = '8400';
+ $income_7_accno = '8300';
+
+ $chart_reisekosten_accno = 4660;
+ $chart_cash_accno = 1000;
+ $chart_bank_accno = 1200;
+
+ $ar_accno = 1400;
+ $ap_accno = 1600;
+
+} elsif ( $test_kontenrahmen eq 'skr04') { # skr04 - test can be ran manually by running t/000setup_database.t with coa for SKR04
+ is(SL::DB::Default->get->coa, 'Germany-DATEV-SKR04EU', "coa SKR04 ok");
+ $chart_vst_19 = '1406';
+ $chart_vst_16 = '1405';
+ $chart_vst_5 = '1403';
+ $chart_vst_7 = '1401';
+
+ $chart_ust_19 = '3806';
+ $chart_ust_16 = '3805';
+ $chart_ust_5 = '3803';
+ $chart_ust_7 = '3801';
+
+ $income_19_accno = '4400';
+ $income_7_accno = '4300';
+
+ $chart_reisekosten_accno = 6650;
+ $chart_cash_accno = 1600;
+ $chart_bank_accno = 1800;
+
+ $ar_accno = 1200;
+ $ap_accno = 3300;
+}
+
+my $tax_vst_19 = SL::DB::Manager::Chart->find_by(accno => $chart_vst_19) or die; # 19%
+my $tax_vst_16 = SL::DB::Manager::Chart->find_by(accno => $chart_vst_16) or die; # 16%
+my $tax_vst_5 = SL::DB::Manager::Chart->find_by(accno => $chart_vst_5 ) or die; # 5%
+my $tax_vst_7 = SL::DB::Manager::Chart->find_by(accno => $chart_vst_7 ) or die; # 7%
+
+my $tax_ust_19 = SL::DB::Manager::Chart->find_by(accno => $chart_ust_19) or die; # 19%
+my $tax_ust_16 = SL::DB::Manager::Chart->find_by(accno => $chart_ust_16) or die; # 16%
+my $tax_ust_5 = SL::DB::Manager::Chart->find_by(accno => $chart_ust_5) or die; # 5%
+my $tax_ust_7 = SL::DB::Manager::Chart->find_by(accno => $chart_ust_7) or die; # 7%
+
+my $chart_income_19 = SL::DB::Manager::Chart->find_by(accno => $income_19_accno) or die;
+my $chart_income_7 = SL::DB::Manager::Chart->find_by(accno => $income_7_accno) or die;
+
+my $chart_reisekosten = SL::DB::Manager::Chart->find_by(accno => $chart_reisekosten_accno) or die;
+my $chart_cash = SL::DB::Manager::Chart->find_by(accno => $chart_cash_accno) or die;
+my $chart_bank = SL::DB::Manager::Chart->find_by(accno => $chart_bank_accno) or die;
+
+my $payment_terms = create_payment_terms();
+
+is(defined SL::DB::Manager::Tax->find_by(taxkey => 2, rate => 0.05), 1, "tax for taxkey 2 with 5% was created ok");
+is(defined SL::DB::Manager::Tax->find_by(taxkey => 3, rate => 0.16, chart_id => $tax_ust_16->id), 1, "new sales tax for taxkey 3 with 16% exists ok");
+is(defined SL::DB::Manager::Tax->find_by(taxkey => 3, rate => 0.19, chart_id => $tax_ust_19->id), 1, "old sales tax for taxkey 3 with 19% exists ok");
+# is(defined SL::DB::Manager::Tax->find_by(taxkey => 5, rate => 0.16, chart_id => $tax_ust_16->id), 1, "new sales tax for taxkey 5 with 16% exists ok");
+
+# is(defined SL::DB::Manager::Tax->find_by(taxkey => 7, rate => 0.16, chart_id => $tax_ust_16->id), 1, "old purchase tax for taxkey 7 with 16% exists ok");
+is(defined SL::DB::Manager::Tax->find_by(taxkey => 8, rate => 0.07, chart_id => $tax_vst_7->id ), 1, "purchase tax for taxkey 8 with 7% exists ok");
+is(defined SL::DB::Manager::Tax->find_by(taxkey => 9, rate => 0.19, chart_id => $tax_vst_19->id), 1, "old purchase tax for taxkey 9 with 19% exists ok");
+is(defined SL::DB::Manager::Tax->find_by(taxkey => 9, rate => 0.16, chart_id => $tax_vst_16->id), 1, "new purchase tax for taxkey 9 with 16% exists ok");
+
+my $vendor = new_vendor( name => 'Testvendor', payment_id => $payment_terms->id)->save;
+my $customer = new_customer(name => 'Testcustomer', payment_id => $payment_terms->id)->save;
+
+cmp_ok($chart_income_7->get_active_taxkey($date_2020_1)->tax->rate, '==', 0.07, "get_active_taxkey rate for 8300 in 2020_1 ok");
+cmp_ok($chart_income_7->get_active_taxkey($date_2020_2)->tax->rate, '==', 0.05, "get_active_taxkey rate for 8300 in 2020_2 ok");
+cmp_ok($chart_income_7->get_active_taxkey($date_2021 )->tax->rate, '==', 0.07, "get_active_taxkey rate for 8300 in 2021 ok");
+cmp_ok($chart_income_7->get_active_taxkey($date_2020_1)->tax->rate, '==', 0.07, "get_active_taxkey rate for $income_7_accno in 2020_1 ok");
+cmp_ok($chart_income_7->get_active_taxkey($date_2020_2)->tax->rate, '==', 0.05, "get_active_taxkey rate for $income_7_accno in 2020_2 ok");
+cmp_ok($chart_income_7->get_active_taxkey($date_2021 )->tax->rate, '==', 0.07, "get_active_taxkey rate for $income_7_accno in 2021 ok");
+cmp_ok($chart_income_7->get_active_taxkey($date_2006 )->tax->rate, '==', 0.07, "get_active_taxkey rate for $income_7_accno in 2016 ok");
+
+cmp_ok($chart_income_19->get_active_taxkey($date_2020_1)->tax->rate, '==', 0.19, "get_active_taxkey rate for $income_19_accno in 2020_1 ok");
+cmp_ok($chart_income_19->get_active_taxkey($date_2020_2)->tax->rate, '==', 0.16, "get_active_taxkey rate for $income_19_accno in 2020_2 ok");
+cmp_ok($chart_income_19->get_active_taxkey($date_2021 )->tax->rate, '==', 0.19, "get_active_taxkey rate for $income_19_accno in 2021 ok");
+cmp_ok($chart_income_19->get_active_taxkey($date_2006 )->tax->rate, '==', 0.16, "get_active_taxkey rate for $income_19_accno in 2016 ok");
+
+my $bugru19 = SL::DB::Manager::Buchungsgruppe->find_by(description => 'Standard 19%') or die "Can't find bugru19";
+my $bugru7 = SL::DB::Manager::Buchungsgruppe->find_by(description => 'Standard 7%' ) or die "Can't find bugru7";
+
+my $part1 = new_part(partnumber => '1', description => 'part19', buchungsgruppen_id => $bugru19->id)->save;
+my $part2 = new_part(partnumber => '2', description => 'part7', buchungsgruppen_id => $bugru7->id )->save;
+
+note('sales invoices');
+my $sales_invoice_2006 = create_invoice_for_date('2006', $date_2006);
+my $sales_invoice_2020_1 = create_invoice_for_date('2020_1', $date_2020_1);
+my $sales_invoice_2020_2 = create_invoice_for_date('2020_2', $date_2020_2);
+my $sales_invoice_2021 = create_invoice_for_date('2021', $date_2021);
+
+is($sales_invoice_2006->amount, 223, '2006 sales invoice has 16% and 7% tax ok' ); # 116 + 7
+is($sales_invoice_2020_1->amount, 226, '2020_01 sales invoice has 19% and 7% tax ok'); # 119 + 7
+is($sales_invoice_2020_2->amount, 221, '2020_02 sales invoice has 16% and 5% tax ok'); # 116 + 5
+is($sales_invoice_2021->amount, 226, '2021 sales invoice has 19% and 7% tax ok' ); # 119 + 7
+
+&datev_test($sales_invoice_2020_2,
+ [
+ {
+ 'belegfeld1' => 'test is 2020_2',
+ 'buchungstext' => 'Testcustomer',
+ 'datum' => '15.07.2020',
+ 'leistungsdatum' => '15.07.2020', # should leistungsdatum be empty if it doesn't exist?
+ 'gegenkonto' => $income_7_accno,
+ 'konto' => $ar_accno,
+ 'kost1' => undef,
+ 'kost2' => undef,
+ 'locked' => undef,
+ 'umsatz' => 105,
+ 'waehrung' => 'EUR'
+ },
+ {
+ 'belegfeld1' => 'test is 2020_2',
+ 'buchungstext' => 'Testcustomer',
+ 'datum' => '15.07.2020',
+ 'leistungsdatum' => '15.07.2020',
+ 'gegenkonto' => $income_19_accno,
+ 'konto' => $ar_accno,
+ 'kost1' => undef,
+ 'kost2' => undef,
+ 'locked' => undef,
+ 'umsatz' => 116,
+ 'waehrung' => 'EUR'
+ }
+ ],
+ "datev check for 16/5 ok, no taxkey"
+);
+
+note('sales invoice with differing delivery dates');
+my $sales_invoice_2020_1_with_delivery_date_2020_2 = create_invoice_for_date('deliverydate 2020_1', $date_2020_1, $date_2020_2);
+is($sales_invoice_2020_1_with_delivery_date_2020_2->amount, 221, "sales_invoice from 2020_1 with future delivery_date 2020_2 tax ok");
+
+my $sales_invoice_2020_2_with_delivery_date_2020_1 = create_invoice_for_date('deliverydate 2020_2', $date_2020_2, $date_2020_1);
+is($sales_invoice_2020_2_with_delivery_date_2020_1->amount, 226, "sales_invoice from 2020_2 with past delivery_date 2020_1 tax ok");
+
+&datev_test($sales_invoice_2020_2_with_delivery_date_2020_1,
+ [
+ {
+ 'belegfeld1' => 'test is deliverydate 2020_2',
+ 'buchungstext' => 'Testcustomer',
+ 'datum' => '15.07.2020',
+ 'gegenkonto' => $income_7_accno,
+ 'konto' => $ar_accno,
+ 'kost1' => undef,
+ 'kost2' => undef,
+ 'leistungsdatum' => '15.06.2020',
+ 'locked' => undef,
+ 'umsatz' => 107,
+ 'waehrung' => 'EUR'
+ },
+ {
+ 'belegfeld1' => 'test is deliverydate 2020_2',
+ 'buchungstext' => 'Testcustomer',
+ 'datum' => '15.07.2020',
+ 'gegenkonto' => $income_19_accno,
+ 'konto' => $ar_accno,
+ 'kost1' => undef,
+ 'kost2' => undef,
+ 'leistungsdatum' => '15.06.2020',
+ 'locked' => undef,
+ 'umsatz' => 119,
+ 'waehrung' => 'EUR'
+ }
+ ],
+ "datev check for datev export with delivery_date 19/7 ok, no taxkey"
+);
+
+my $sales_invoice_2021_with_delivery_date_2020_2 = create_invoice_for_date('deliverydate 2020_2', $date_2021, $date_2020_2);
+is($sales_invoice_2021_with_delivery_date_2020_2->amount, 221, "sales_invoice from 2021 with past delivery_date 2020_2 tax ok");
+
+my $sales_invoice_2020_2_with_delivery_date_2021 = create_invoice_for_date('deliverydate 2021', $date_2020_2, $date_2021);
+is($sales_invoice_2020_2_with_delivery_date_2021->amount, 226, "sales_invoice from 2020_2 with future delivery_date 2021 tax ok");
+
+
+note('ap transactions');
+# in the test we want to test for Reisekosten with 19% and 7%. Normally the user
+# would select the entries from the dropdown, as they may differ from the
+# default, so we have to pass the tax we want to create_ap_transaction
+
+# my $tax_9_16_old = SL::DB::Manager::Tax->find_by(taxkey => 7, rate => 0.16, chart_id => $tax_vst_16->id);
+my $tax_9_19 = SL::DB::Manager::Tax->find_by(taxkey => 9, rate => 0.19, chart_id => $tax_vst_19->id) or die "missing 9_19";
+my $tax_9_16 = SL::DB::Manager::Tax->find_by(taxkey => 9, rate => 0.16, chart_id => $tax_vst_16->id) or die "missing 9_16";
+my $tax_8_7 = SL::DB::Manager::Tax->find_by(taxkey => 8, rate => 0.07, chart_id => $tax_vst_7->id) or die "missing 8_7";
+my $tax_8_5 = SL::DB::Manager::Tax->find_by(taxkey => 8, rate => 0.05, chart_id => $tax_vst_5->id) or die "missing 8_5";
+
+# simulate user selecting the "correct" taxes in dropdown:
+my $ap_transaction_2006 = create_ap_transaction_for_date('2006', $date_2006, undef, $tax_9_16, $tax_8_7);
+my $ap_transaction_2020_1 = create_ap_transaction_for_date('2020_1', $date_2020_1, undef, $tax_9_19, $tax_8_7);
+my $ap_transaction_2020_2 = create_ap_transaction_for_date('2020_2', $date_2020_2, undef, $tax_9_16, $tax_8_5);
+my $ap_transaction_2021 = create_ap_transaction_for_date('2021', $date_2021, undef, $tax_9_19, $tax_8_7);
+
+
+is($ap_transaction_2006->amount, 223, '2006 ap transaction has 16% and 7% tax ok'); # 116 + 7
+is($ap_transaction_2020_1->amount, 226, '2020_01 ap transaction has 19% and 7% tax ok'); # 119 + 7
+is($ap_transaction_2020_2->amount, 221, '2020_02 ap transaction has 16% and 5% tax ok'); # 116 + 5
+is($ap_transaction_2021->amount, 226, '2021 ap transaction has 19% and 7% tax ok'); # 119 + 7
+
+# ap transaction in july, but use old tax
+my $ap_transaction_2020_2_with_delivery_date_2020_1 = create_ap_transaction_for_date('2020_2 with delivery date 2020_1', $date_2020_2, $date_2020_1, $tax_9_19, $tax_8_7);
+is($ap_transaction_2020_2_with_delivery_date_2020_1->amount, 226, 'ap transaction 2020_2 with delivery date 2020_1, 19% and 7% tax ok'); # 119 + 7
+&datev_test($ap_transaction_2020_2_with_delivery_date_2020_1,
+ [
+ {
+ 'belegfeld1' => 'test ap_transaction 2020_2 with delivery date 2020_1',
+ 'buchungsschluessel' => 8,
+ 'buchungstext' => 'Testvendor',
+ 'datum' => '15.07.2020',
+ 'gegenkonto' => $ap_accno,
+ 'konto' => $chart_reisekosten_accno,
+ 'kost1' => undef,
+ 'kost2' => undef,
+ 'leistungsdatum' => '15.06.2020',
+ 'locked' => undef,
+ 'umsatz' => 107,
+ 'waehrung' => 'EUR'
+ },
+ {
+ 'belegfeld1' => 'test ap_transaction 2020_2 with delivery date 2020_1',
+ 'buchungsschluessel' => 9,
+ 'buchungstext' => 'Testvendor',
+ 'datum' => '15.07.2020',
+ 'gegenkonto' => $ap_accno,
+ 'konto' => $chart_reisekosten_accno,
+ 'kost1' => undef,
+ 'kost2' => undef,
+ 'leistungsdatum' => '15.06.2020',
+ 'locked' => undef,
+ 'umsatz' => 119,
+ 'waehrung' => 'EUR'
+ }
+ ],
+ "datev check for ap transaction 2020_2 with delivery date 2020_1, 19% and 7% tax ok"
+);
+
+note('ar transactions');
+
+my $ar_transaction_2006 = create_ar_transaction_for_date('2006', $date_2006);
+my $ar_transaction_2020_1 = create_ar_transaction_for_date('2020_1', $date_2020_1);
+my $ar_transaction_2020_2 = create_ar_transaction_for_date('2020_2', $date_2020_2);
+my $ar_transaction_2021 = create_ar_transaction_for_date('2021', $date_2021);
+
+is($ar_transaction_2006->amount, 223, '2006 ar transaction has 16% and 7% tax ok'); # 116 + 7
+is($ar_transaction_2020_1->amount, 226, '2020_01 ar transaction has 19% and 7% tax ok'); # 119 + 7
+is($ar_transaction_2020_2->amount, 221, '2020_02 ar transaction has 16% and 5% tax ok'); # 116 + 5
+is($ar_transaction_2021->amount, 226, '2021 ar transaction has 19% and 7% tax ok'); # 119 + 7
+
+note('gl transactions');
+
+my $gl_2006 = create_gl_transaction_for_date('glincome 2006', $date_2006, 223);
+my $gl_2020_1 = create_gl_transaction_for_date('glincome 2020_1', $date_2020_1, 226);
+my $gl_2020_2 = create_gl_transaction_for_date('glincome 2020_2', $date_2020_2, 221);
+my $gl_2021 = create_gl_transaction_for_date('glincome 2021', $date_2021, 226);
+
+is(SL::DB::Manager::GLTransaction->get_all_count(), 4, "4 gltransactions created correctly");
+
+my $result = &get_account_balances;
+# print Dumper($result);
+is_deeply( &get_account_balances,
+ [
+ # {
+ # 'accno' => '1000',
+ # # 'description' => 'Kasse',
+ # 'sum' => '-896.00000'
+ # },
+ # {
+ # 'accno' => '1400',
+ # # 'description' => 'Ford. a.Lieferungen und Leistungen',
+ # 'sum' => '-2686.00000'
+ # },
+ {
+ 'accno' => '1568',
+ # 'description' => 'Abziehbare Vorsteuer 7%',
+ 'sum' => '-5.00000'
+ },
+ {
+ 'accno' => '1571',
+ # 'description' => 'Abziehbare Vorsteuer 7%',
+ 'sum' => '-28.00000'
+ },
+ {
+ 'accno' => '1575',
+ # 'description' => 'Abziehbare Vorsteuer 16%',
+ 'sum' => '-32.00000'
+ },
+ {
+ 'accno' => '1576',
+ # 'description' => 'Abziehbare Vorsteuer 19 %',
+ 'sum' => '-57.00000'
+ },
+ # {
+ # 'accno' => '1600',
+ # # 'description' => 'Verbindlichkeiten aus Lief.u.Leist.',
+ # 'sum' => '896.00000'
+ # },
+ {
+ 'accno' => '1771',
+ # 'description' => 'Umsatzsteuer 7%',
+ 'sum' => '77.00000'
+ },
+ {
+ 'accno' => '1773',
+ # 'description' => 'Umsatzsteuer 5 %',
+ 'sum' => '25.00000'
+ },
+ {
+ 'accno' => '1775',
+ # 'description' => 'Umsatzsteuer 16%',
+ 'sum' => '128.00000'
+ },
+ {
+ 'accno' => '1776',
+ # 'description' => 'Umsatzsteuer 19 %',
+ 'sum' => '152.00000'
+ },
+ # {
+ # 'accno' => '4660',
+ # # 'description' => 'Reisekosten Arbeitnehmer',
+ # 'sum' => '-800.00000'
+ # },
+ # {
+ # 'accno' => $income_7_accno,
+ # # 'description' => "Erl\x{f6}se 7%USt",
+ # 'sum' => '1600.00000'
+ # },
+ # {
+ # 'accno' => $income_19_accno,
+ # # 'description' => "Erl\x{f6}se 16%/19% USt.",
+ # 'sum' => '1600.00000'
+ # }
+ ],
+ 'account balances after invoices'
+);
+
+note('testing payments with skonto');
+
+my %params = ( chart_id => $chart_bank->id,
+ payment_type => 'with_skonto_pt',
+ );
+
+$sales_invoice_2020_2->pay_invoice( %params,
+ amount => $sales_invoice_2020_2->amount_less_skonto,
+ transdate => $date_2020_2->to_kivitendo,
+ );
+
+
+$skonto_5 = SL::DB::Manager::AccTransaction->find_by(trans_id => $sales_invoice_2020_2->id, amount => -5.25);
+like($skonto_5->chart->description, qr/Skonti.*5/, "sales_invoice 2020_2 paid in 2020_2 - skonto 5% ok");
+$skonto_16 = SL::DB::Manager::AccTransaction->find_by(trans_id => $sales_invoice_2020_2->id, amount => -5.80);
+like($skonto_16->chart->description, qr/Skonti.*16/, "sales_invoice 2020_2 paid in 2020_2 - skonto 16% ok");
+
+$sales_invoice_2020_1->pay_invoice( %params,
+ amount => $sales_invoice_2020_1->amount_less_skonto,
+ transdate => $date_2020_2->to_kivitendo,
+ );
+$skonto_7 = SL::DB::Manager::AccTransaction->find_by(trans_id => $sales_invoice_2020_1->id, amount => -5.35);
+like($skonto_7->chart->description, qr/Skonti.*7/, "sales_invoice 2020_1 paid with skonto in 2020_2 - skonto 7% ok");
+$skonto_19 = SL::DB::Manager::AccTransaction->find_by(trans_id => $sales_invoice_2020_1->id, amount => -5.95);
+like($skonto_19->chart->description, qr/Skonti.*19/, "sales_invoice 2020_1 paid with skonto in 2020_2 - skonto 19% ok");
+
+$ap_transaction_2020_1->pay_invoice( %params,
+ amount => $ap_transaction_2020_1->amount_less_skonto,
+ transdate => $date_2020_2->to_kivitendo,
+ );
+$skonto_7 = SL::DB::Manager::AccTransaction->find_by(trans_id => $ap_transaction_2020_1->id, amount => 5.35);
+like($skonto_7->chart->description, qr/Skonti.*7/, "ap transaction 2020_1 paid with skonto in 2020_2 - skonto 7% ok");
+$skonto_19 = SL::DB::Manager::AccTransaction->find_by(trans_id => $ap_transaction_2020_1->id, amount => 5.95);
+like($skonto_19->chart->description, qr/Skonti.*19/, "ap transaction 2020_1 paid with skonto in 2020_2 - skonto 19% ok");
+
+
+$ap_transaction_2020_2->pay_invoice( %params,
+ amount => $ap_transaction_2020_2->amount_less_skonto,
+ transdate => $date_2021->to_kivitendo,
+ );
+$skonto_5 = SL::DB::Manager::AccTransaction->find_by(trans_id => $ap_transaction_2020_2->id, amount => 5.25);
+like($skonto_5->chart->description, qr/Skonti.*5/, "ap transaction 2020_2 paid in 2021 - skonto 5% ok");
+
+$skonto_16 = SL::DB::Manager::AccTransaction->find_by(trans_id => $ap_transaction_2020_2->id, amount => 5.80);
+like($skonto_16->chart->description, qr/Skonti.*16/, "sales_invoice 2020_2 paid in 2021 - skonto 16% ok");
+
+clear_up();
+
+done_testing();
+
+###### functions for setting up data
+
+sub create_invoice_for_date {
+ my ($invnumber, $transdate, $deliverydate) = @_;
+
+ $deliverydate = $transdate unless defined $deliverydate;
+
+ my $sales_invoice = create_sales_invoice(
+ invnumber => 'test is ' . $invnumber,
+ transdate => $transdate,
+ customer => $customer,
+ deliverydate => $deliverydate,
+ payment_terms => $payment_terms,
+ taxincluded => 0,
+ invoiceitems => [ create_invoice_item(part => $part1, qty => 10, sellprice => 10),
+ create_invoice_item(part => $part2, qty => 10, sellprice => 10),
+ ]
+ );
+ return $sales_invoice;
+}
+
+sub create_ar_transaction_for_date {
+ my ($invnumber, $transdate) = @_;
+
+ my $ar_transaction = create_ar_transaction(
+ customer => $customer,
+ invnumber => 'test ar' . $invnumber,
+ taxincluded => 0,
+ transdate => $transdate,
+ ar_chart => SL::DB::Manager::Chart->find_by(accno => $ar_accno), # pass ar_chart, as it is hardcoded for SKR03 in SL::Dev::Record
+ bookings => [
+ {
+ chart => $chart_income_19,
+ amount => 100,
+ },
+ {
+ chart => $chart_income_7,
+ amount => 100,
+ },
+ ]
+ );
+ return $ar_transaction;
+}
+
+sub create_ap_transaction_for_date {
+ my ($invnumber, $transdate, $deliverydate, $tax_high, $tax_low) = @_;
+
+ # printf("invnumber = %s tax_high = %s tax_low = %s\n", $invnumber, $tax_high->accno , $tax_low->accno);
+ my $taxkey_ = $chart_reisekosten->get_active_taxkey($transdate);
+
+ my $ap_transaction = create_ap_transaction(
+ vendor => $vendor,
+ invnumber => 'test ap_transaction ' . $invnumber,
+ taxincluded => 0,
+ transdate => $transdate,
+ deliverydate => $deliverydate,
+ payment_id => $payment_terms->id,
+ ap_chart => SL::DB::Manager::Chart->find_by(accno => $ap_accno), # pass ap_chart, as it is hardcoded for SKR03 in SL::Dev::Record
+ bookings => [
+ {
+ chart => $chart_reisekosten,
+ amount => 100,
+ tax_id => $tax_high->id,
+ },
+ {
+ chart => $chart_reisekosten,
+ amount => 100,
+ tax_id => $tax_low->id,
+ },
+ ]
+ );
+ return $ap_transaction;
+}
+
+sub create_gl_transaction_for_date {
+ my ($reference, $transdate, $debitamount) = @_;
+
+ my $gl_transaction = create_gl_transaction(
+ reference => $reference,
+ taxincluded => 0,
+ transdate => $transdate,
+ bookings => [
+ {
+ chart => $chart_income_19,
+ memo => 'gl 19',
+ source => 'gl 19',
+ credit => 100,
+ },
+ {
+ chart => $chart_income_7,
+ memo => 'gl 7',
+ source => 'gl 7',
+ credit => 100,
+ },
+ {
+ chart => $chart_cash,
+ debit => $debitamount,
+ memo => 'gl 19+7',
+ source => 'gl 19+7',
+ },
+ ],
+ );
+ return $gl_transaction;
+}
+
+sub get_account_balances {
+ my $query = <<SQL;
+ select c.accno, sum(a.amount)
+ from acc_trans a
+ left join chart c on (c.id = a.chart_id)
+ where c.accno ~ '^17' or c.accno ~ '^15'
+group by c.accno, c.description
+order by c.accno
+SQL
+
+ my $result = selectall_hashref_query($::form, $dbh, $query);
+ return $result;
+};
+
+sub datev_test {
+ my ($invoice, $expected_data, $msg) = @_;
+
+ my $datev = SL::DATEV->new(
+ dbh => $invoice->db->dbh,
+ trans_id => $invoice->id,
+ );
+
+ $datev->generate_datev_data;
+ my @data_datev = sort { $a->{umsatz} <=> $b->{umsatz} } @{ $datev->generate_datev_lines() };
+
+ # print Dumper(\@data_datev);
+
+ cmp_deeply(\@data_datev, $expected_data, $msg);
+}
+
+sub clear_up {
+ SL::DB::Manager::OrderItem->delete_all(all => 1);
+ SL::DB::Manager::Order->delete_all(all => 1);
+ SL::DB::Manager::InvoiceItem->delete_all(all => 1);
+ SL::DB::Manager::Invoice->delete_all(all => 1);
+ SL::DB::Manager::PurchaseInvoice->delete_all(all => 1);
+ SL::DB::Manager::GLTransaction->delete_all(all => 1);
+ SL::DB::Manager::Part->delete_all(all => 1);
+ SL::DB::Manager::Customer->delete_all(all => 1);
+ SL::DB::Manager::Vendor->delete_all(all => 1);
+ SL::DB::Manager::PaymentTerm->delete_all(all => 1);
+};
+
+1;
--- /dev/null
+use strict;
+use warnings;
+
+use Test::More tests => 18;
+use lib 't';
+use utf8;
+
+use Carp;
+use Data::Dumper;
+use Support::TestSetup;
+use Test::Exception;
+use SL::DBUtils qw(selectall_hashref_query);
+
+use SL::DB::BankAccount;
+use SL::DB::Chart;
+use SL::DB::Invoice;
+use SL::DB::PurchaseInvoice;
+
+use SL::Dev::Record qw(create_ar_transaction create_ap_transaction create_gl_transaction);
+
+use SL::Controller::YearEndTransactions;
+
+Support::TestSetup::login();
+
+clear_up();
+
+# comments:
+
+# * in the default test client the tax accounts are configured as I/E rather than A/L
+# * also the default test client has the accounting method "cash" rather than "accrual"
+# (Ist-versteuerung, rather than Soll-versteuerung)
+
+# use 2019 instead of 2020 because of tax changes in Germany (19/16 and 7/5) because we check for account sums
+my $year = 2019 if DateTime->today_local->year == 2020;
+my $start_of_year = DateTime->new(year => $year, month => 01, day => 01);
+my $booking_date = DateTime->new(year => $year, month => 12, day => 22);
+
+note('configuring accounts');
+my $bank_account = SL::DB::BankAccount->new(
+ account_number => '123',
+ bank_code => '123',
+ iban => '123',
+ bic => '123',
+ bank => '123',
+ chart_id => SL::DB::Manager::Chart->find_by(description => 'Bank')->id,
+ name => SL::DB::Manager::Chart->find_by(description => 'Bank')->description,
+)->save;
+
+my $profit_account = SL::DB::Manager::Chart->find_by(accno => '0890') //
+ SL::DB::Chart->new(
+ accno => '0890',
+ description => 'Gewinnvortrag vor Verwendung',
+ charttype => 'A',
+ category => 'Q',
+ link => '',
+ taxkey_id => '0',
+ datevautomatik => 'f',
+ )->save;
+
+my $loss_account = SL::DB::Manager::Chart->find_by(accno => '0868') //
+ SL::DB::Chart->new(
+ accno => '0868',
+ description => 'Verlustvortrag vor Verwendung',
+ charttype => 'A',
+ category => 'Q',
+ link => '',
+ taxkey_id => '0',
+ datevautomatik => 'f',
+ )->save;
+
+my $carry_over_chart = SL::DB::Manager::Chart->find_by(accno => 9000);
+my $income_chart = SL::DB::Manager::Chart->find_by(accno => '8400'); # income 19%, taxkey 3
+my $bank = SL::DB::Manager::Chart->find_by(description => 'Bank');
+my $cash = SL::DB::Manager::Chart->find_by(description => 'Kasse');
+my $privateinlagen = SL::DB::Manager::Chart->find_by(description => 'Privateinlagen');
+my $betriebsbedarf = SL::DB::Manager::Chart->find_by(description => 'Betriebsbedarf');
+
+my $dbh = SL::DB->client->dbh;
+$dbh->do('UPDATE defaults SET carry_over_account_chart_id = ' . $carry_over_chart->id);
+$dbh->do('UPDATE defaults SET profit_carried_forward_chart_id = ' . $profit_account->id);
+$dbh->do('UPDATE defaults SET loss_carried_forward_chart_id = ' . $loss_account->id);
+
+
+note('creating transactions');
+my $ar_transaction = create_ar_transaction(
+ taxincluded => 0,
+ transdate => $booking_date,
+ bookings => [
+ {
+ chart => $income_chart, # income 19%, taxkey 3
+ amount => 140,
+ }
+ ],
+);
+
+$ar_transaction->pay_invoice(
+ chart_id => $bank_account->chart_id,
+ amount => $ar_transaction->amount,
+ transdate => $booking_date,
+ payment_type => 'without_skonto',
+ );
+
+my $ar_transaction2 = create_ar_transaction(
+ taxincluded => 1,
+ transdate => $booking_date,
+ bookings => [
+ {
+ chart => $income_chart, # income 19%, taxkey 3
+ amount => 166.60,
+ }
+ ],
+);
+
+my $ap_transaction = create_ap_transaction(
+ taxincluded => 0,
+ transdate => $booking_date,
+ bookings => [
+ {
+ chart => SL::DB::Manager::Chart->find_by( accno => '3400' ), # Wareneingang 19%, taxkey 9
+ amount => 100,
+ }
+ ],
+);
+
+gl_booking(40, $start_of_year, 'foo', 'bar', $bank, $privateinlagen, 1, 0);
+
+is(SL::DB::Manager::AccTransaction->get_all_count( ), 13, 'acc_trans transactions created ok');
+is(SL::DB::Manager::AccTransaction->get_all_count(where => [ ob_transaction => 1 ]), 2, 'acc_trans ob_transactions created ok');
+is(SL::DB::Manager::AccTransaction->get_all_count(where => [ cb_transaction => 1 ]), 0, 'no cb_transactions created ok');
+
+is_deeply( &get_account_balances,
+ [
+ {
+ 'accno' => '1200',
+ 'account_type' => 'asset_account',
+ 'sum' => '-206.60000'
+ },
+ {
+ 'accno' => '1400',
+ 'account_type' => 'asset_account',
+ 'sum' => '-166.60000'
+ },
+ {
+ 'accno' => '1600',
+ 'account_type' => 'asset_account',
+ 'sum' => '119.00000'
+ },
+ {
+ 'accno' => '1890',
+ 'account_type' => 'asset_account',
+ 'sum' => '40.00000'
+ },
+ {
+ 'accno' => '1576',
+ 'account_type' => 'profit_loss_account',
+ 'sum' => '-19.00000'
+ },
+ {
+ 'accno' => '1776',
+ 'account_type' => 'profit_loss_account',
+ 'sum' => '53.20000'
+ },
+ {
+ 'accno' => '3400',
+ 'account_type' => 'profit_loss_account',
+ 'sum' => '-100.00000'
+ },
+ {
+ 'accno' => '8400',
+ 'account_type' => 'profit_loss_account',
+ 'sum' => '280.00000'
+ }
+ ],
+ 'account balances before year_end bookings ok',
+);
+
+# accno | account_type | sum
+# -------+---------------------+------------
+# 1200 | asset_account | -206.60000
+# 1400 | asset_account | -166.60000
+# 1600 | asset_account | 119.00000
+# 1890 | asset_account | 40.00000
+# 1576 | profit_loss_account | -19.00000
+# 1776 | profit_loss_account | 53.20000
+# 3400 | profit_loss_account | -100.00000
+# 8400 | profit_loss_account | 280.00000
+
+
+note('running year-end transactions');
+my $start_date = DateTime->new(year => $year, month => 1, day => 1);
+my $cb_date = DateTime->new(year => $year, month => 12, day => 31);
+my $ob_date = $cb_date->clone->add(days => 1);
+
+SL::Controller::YearEndTransactions::_year_end_bookings( start_date => $start_date,
+ cb_date => $cb_date,
+ );
+
+is(SL::DB::Manager::AccTransaction->get_all_count(where => [ cb_transaction => 1 ]), 14, 'acc_trans cb_transactions created ok');
+is(SL::DB::Manager::AccTransaction->get_all_count(where => [ ob_transaction => 1 ]), 10, 'acc_trans ob_transactions created ok');
+is(SL::DB::Manager::GLTransaction->get_all_count( where => [ cb_transaction => 1 ]), 5, 'GL cb_transactions created ok');
+is(SL::DB::Manager::GLTransaction->get_all_count( where => [ ob_transaction => 1 ]), 4, 'GL ob_transactions created ok');
+
+my $final_account_balances = [
+ {
+ 'accno' => '0890',
+ 'amount' => undef,
+ 'amount_with_cb' => '0.00000',
+ 'cat' => 'Q',
+ 'cb_amount' => '0.00000',
+ 'ob_amount' => undef,
+ 'ob_next_year' => '214.20000',
+ 'type' => 'asset',
+ 'year_end_amount' => undef
+ },
+ {
+ 'accno' => '1200',
+ 'amount' => '-166.60000',
+ 'amount_with_cb' => '0.00000',
+ 'cat' => 'A',
+ 'cb_amount' => '-206.60000',
+ 'ob_amount' => '-40.00000',
+ 'ob_next_year' => '-206.60000',
+ 'type' => 'asset',
+ 'year_end_amount' => '-206.60000'
+ },
+ {
+ 'accno' => '1400',
+ 'amount' => '-166.60000',
+ 'amount_with_cb' => '0.00000',
+ 'cat' => 'A',
+ 'cb_amount' => '-166.60000',
+ 'ob_amount' => undef,
+ 'ob_next_year' => '-166.60000',
+ 'type' => 'asset',
+ 'year_end_amount' => '-166.60000'
+ },
+ {
+ 'accno' => '1600',
+ 'amount' => '119.00000',
+ 'amount_with_cb' => '0.00000',
+ 'cat' => 'L',
+ 'cb_amount' => '119.00000',
+ 'ob_amount' => undef,
+ 'ob_next_year' => '119.00000',
+ 'type' => 'asset',
+ 'year_end_amount' => '119.00000'
+ },
+ {
+ 'accno' => '1890',
+ 'amount' => undef,
+ 'amount_with_cb' => '0.00000',
+ 'cat' => 'Q',
+ 'cb_amount' => '40.00000',
+ 'ob_amount' => '40.00000',
+ 'ob_next_year' => '40.00000',
+ 'type' => 'asset',
+ 'year_end_amount' => '40.00000'
+ },
+ {
+ 'accno' => '9000',
+ 'amount' => undef,
+ 'amount_with_cb' => '0.00000',
+ 'cat' => 'A',
+ 'cb_amount' => '0.00000',
+ 'ob_amount' => undef,
+ 'ob_next_year' => '0.00000',
+ 'type' => 'asset',
+ 'year_end_amount' => undef
+ },
+ {
+ 'accno' => '1576',
+ 'amount' => '-19.00000',
+ 'amount_with_cb' => '0.00000',
+ 'cat' => 'E',
+ 'cb_amount' => '-19.00000',
+ 'ob_amount' => undef,
+ 'ob_next_year' => undef,
+ 'type' => 'pl',
+ 'year_end_amount' => '-19.00000'
+ },
+ {
+ 'accno' => '1776',
+ 'amount' => '53.20000',
+ 'amount_with_cb' => '0.00000',
+ 'cat' => 'I',
+ 'cb_amount' => '53.20000',
+ 'ob_amount' => undef,
+ 'ob_next_year' => undef,
+ 'type' => 'pl',
+ 'year_end_amount' => '53.20000'
+ },
+ {
+ 'accno' => '3400',
+ 'amount' => '-100.00000',
+ 'amount_with_cb' => '0.00000',
+ 'cat' => 'E',
+ 'cb_amount' => '-100.00000',
+ 'ob_amount' => undef,
+ 'ob_next_year' => undef,
+ 'type' => 'pl',
+ 'year_end_amount' => '-100.00000'
+ },
+ {
+ 'accno' => '8400',
+ 'amount' => '280.00000',
+ 'amount_with_cb' => '0.00000',
+ 'cat' => 'I',
+ 'cb_amount' => '280.00000',
+ 'ob_amount' => undef,
+ 'ob_next_year' => undef,
+ 'type' => 'pl',
+ 'year_end_amount' => '280.00000'
+ }
+ ];
+
+# running _year_end_bookings several times shouldn't change the anything, the
+# second and third run should be no-ops, at least while no further bookings where
+# made
+
+SL::Controller::YearEndTransactions::_year_end_bookings( start_date => $start_date,
+ cb_date => $cb_date,
+ );
+
+is(SL::DB::Manager::AccTransaction->get_all_count(where => [ cb_transaction => 1 ]), 14, 'acc_trans cb_transactions created ok');
+is(SL::DB::Manager::AccTransaction->get_all_count(where => [ ob_transaction => 1 ]), 10, 'acc_trans ob_transactions created ok');
+is(SL::DB::Manager::GLTransaction->get_all_count( where => [ cb_transaction => 1 ]), 5, 'GL cb_transactions created ok');
+is(SL::DB::Manager::GLTransaction->get_all_count( where => [ ob_transaction => 1 ]), 4, 'GL ob_transactions created ok');
+
+
+# all asset accounts should be the same, except 0890, which should be the sum of p/l-accounts
+# all p/l account should be 0
+
+# accno | account_type | sum
+# -------+---------------------+------------
+# 0890 | asset_account | 214.20000
+# 1200 | asset_account | -206.60000
+# 1400 | asset_account | -166.60000
+# 1600 | asset_account | 119.00000
+# 1890 | asset_account | 40.00000
+# 9000 | asset_account | 0.00000
+# 1576 | profit_loss_account | 0.00000
+# 1776 | profit_loss_account | 0.00000
+# 3400 | profit_loss_account | 0.00000
+# 8400 | profit_loss_account | 0.00000
+# (10 rows)
+
+is_deeply( &get_final_balances,
+ $final_account_balances,
+ 'balances after second year_end ok (nothing changed)');
+
+
+# select c.accno,
+# c.description,
+# c.category as cat,
+# sum(a.amount ) filter (where ob_transaction is true and a.transdate < '2020-01-01') as ob_amount,
+# sum(a.amount ) filter (where cb_transaction is false and ob_transaction is false and a.transdate < '2020-01-01') as amount,
+# sum(a.amount ) filter (where cb_transaction is false and a.transdate < '2020-01-01') as year_end_amount,
+# sum(a.amount ) filter (where a.transdate < '2020-01-01') as amount_with_cb,
+# sum(a.amount * -1) filter (where cb_transaction is true and a.transdate < '2020-01-01') as cb_amount,
+# sum(a.amount ) filter (where ob_transaction is true and a.transdate >= '2020-01-01') as ob_next_year,
+# case when c.category = ANY( '{I,E}' ) then 'pl'
+# when c.category = ANY( '{A,C,L,Q}' ) then 'asset'
+# else null
+# end as type
+# from acc_trans a
+# inner join chart c on (c.id = a.chart_id)
+# where a.transdate >= '2019-01-01'
+# and a.transdate <= '2020-01-01'
+# group by c.id, c.accno, c.category
+# order by type, c.accno;
+# accno | description | cat | ob_amount | amount | year_end_amount | amount_with_cb | cb_amount | ob_next_year | type
+# -------+-------------------------------------+-----+-----------+------------+-----------------+----------------+------------+--------------+-------
+# 0890 | Gewinnvortrag vor Verwendung | Q | | | | 0.00000 | 0.00000 | 214.20000 | asset
+# 1200 | Bank | A | -40.00000 | -166.60000 | -206.60000 | 0.00000 | -206.60000 | -206.60000 | asset
+# 1400 | Ford. a.Lieferungen und Leistungen | A | | -166.60000 | -166.60000 | 0.00000 | -166.60000 | -166.60000 | asset
+# 1600 | Verbindlichkeiten aus Lief.u.Leist. | L | | 119.00000 | 119.00000 | 0.00000 | 119.00000 | 119.00000 | asset
+# 1890 | Privateinlagen | Q | 40.00000 | | 40.00000 | 0.00000 | 40.00000 | 40.00000 | asset
+# 9000 | Saldenvorträge,Sachkonten | A | | | | 0.00000 | 0.00000 | 0.00000 | asset
+# 1576 | Abziehbare Vorsteuer 19 % | E | | -19.00000 | -19.00000 | 0.00000 | -19.00000 | | pl
+# 1776 | Umsatzsteuer 19 % | I | | 53.20000 | 53.20000 | 0.00000 | 53.20000 | | pl
+# 3400 | Wareneingang 16%/19% Vorsteuer | E | | -100.00000 | -100.00000 | 0.00000 | -100.00000 | | pl
+# 8400 | Erlöse 16%/19% USt. | I | | 280.00000 | 280.00000 | 0.00000 | 280.00000 | | pl
+# (10 rows)
+
+# ob_amount + amount = year_end_amount
+# amount_with_cb should be 0 after year-end transactions
+# year_end_amount and cb_amount should be the same (will be true with amount_with_cb = 0)
+# cb_amount should match ob_next_year for asset accounts, except for profit-carried-forward
+# ob_next_year should be empty for profit-loss-accounts
+
+# Oops, we forgot some bookings, lets quickly add them and run
+#_year_end_bookings again.
+
+# Just these new bookings by themselves will lead to a loss, so the loss account
+# will be booked rather than the profit account.
+# It would probably be better to check the total profit/loss so far, and
+# adjust that profit-loss-carry-over # chart, rather than creating a new entry
+# for the loss.
+
+gl_booking(10, $booking_date, 'foo', 'bar', $cash, $bank, 0, 0);
+gl_booking(5, $booking_date, 'foo', 'bar', $betriebsbedarf, $cash, 0, 0);
+
+SL::Controller::YearEndTransactions::_year_end_bookings( start_date => $start_date,
+ cb_date => $cb_date,
+ );
+
+is(SL::DB::Manager::AccTransaction->get_all_count(where => [ cb_transaction => 1 ]), 23, 'acc_trans cb_transactions created ok');
+is(SL::DB::Manager::AccTransaction->get_all_count(where => [ ob_transaction => 1 ]), 16, 'acc_trans ob_transactions created ok');
+is(SL::DB::Manager::GLTransaction->get_all_count( where => [ cb_transaction => 1 ]), 9, 'GL cb_transactions created ok');
+is(SL::DB::Manager::GLTransaction->get_all_count( where => [ ob_transaction => 1 ]), 7, 'GL ob_transactions created ok');
+
+is_deeply( &get_final_balances,
+ [
+ {
+ 'accno' => '0868',
+ 'amount' => undef,
+ 'amount_with_cb' => '0.00000',
+ 'cat' => 'Q',
+ 'cb_amount' => '0.00000',
+ 'ob_amount' => undef,
+ 'ob_next_year' => '-5.00000',
+ 'type' => 'asset',
+ 'year_end_amount' => undef
+ },
+ {
+ 'accno' => '0890',
+ 'amount' => undef,
+ 'amount_with_cb' => '0.00000',
+ 'cat' => 'Q',
+ 'cb_amount' => '0.00000',
+ 'ob_amount' => undef,
+ 'ob_next_year' => '214.20000',
+ 'type' => 'asset',
+ 'year_end_amount' => undef
+ },
+ {
+ 'accno' => '1000',
+ 'amount' => '-5.00000',
+ 'amount_with_cb' => '0.00000',
+ 'cat' => 'A',
+ 'cb_amount' => '-5.00000',
+ 'ob_amount' => undef,
+ 'ob_next_year' => '-5.00000',
+ 'type' => 'asset',
+ 'year_end_amount' => '-5.00000'
+ },
+ {
+ 'accno' => '1200',
+ 'amount' => '-156.60000',
+ 'amount_with_cb' => '0.00000',
+ 'cat' => 'A',
+ 'cb_amount' => '-196.60000',
+ 'ob_amount' => '-40.00000',
+ 'ob_next_year' => '-196.60000',
+ 'type' => 'asset',
+ 'year_end_amount' => '-196.60000'
+ },
+ {
+ 'accno' => '1400',
+ 'amount' => '-166.60000',
+ 'amount_with_cb' => '0.00000',
+ 'cat' => 'A',
+ 'cb_amount' => '-166.60000',
+ 'ob_amount' => undef,
+ 'ob_next_year' => '-166.60000',
+ 'type' => 'asset',
+ 'year_end_amount' => '-166.60000'
+ },
+ {
+ 'accno' => '1600',
+ 'amount' => '119.00000',
+ 'amount_with_cb' => '0.00000',
+ 'cat' => 'L',
+ 'cb_amount' => '119.00000',
+ 'ob_amount' => undef,
+ 'ob_next_year' => '119.00000',
+ 'type' => 'asset',
+ 'year_end_amount' => '119.00000'
+ },
+ {
+ 'accno' => '1890',
+ 'amount' => undef,
+ 'amount_with_cb' => '0.00000',
+ 'cat' => 'Q',
+ 'cb_amount' => '40.00000',
+ 'ob_amount' => '40.00000',
+ 'ob_next_year' => '40.00000',
+ 'type' => 'asset',
+ 'year_end_amount' => '40.00000'
+ },
+ {
+ 'accno' => '9000',
+ 'amount' => undef,
+ 'amount_with_cb' => '0.00000',
+ 'cat' => 'A',
+ 'cb_amount' => '0.00000',
+ 'ob_amount' => undef,
+ 'ob_next_year' => '0.00000',
+ 'type' => 'asset',
+ 'year_end_amount' => undef
+ },
+ {
+ 'accno' => '1576',
+ 'amount' => '-19.80000',
+ 'amount_with_cb' => '0.00000',
+ 'cat' => 'E',
+ 'cb_amount' => '-19.80000',
+ 'ob_amount' => undef,
+ 'ob_next_year' => undef,
+ 'type' => 'pl',
+ 'year_end_amount' => '-19.80000'
+ },
+ {
+ 'accno' => '1776',
+ 'amount' => '53.20000',
+ 'amount_with_cb' => '0.00000',
+ 'cat' => 'I',
+ 'cb_amount' => '53.20000',
+ 'ob_amount' => undef,
+ 'ob_next_year' => undef,
+ 'type' => 'pl',
+ 'year_end_amount' => '53.20000'
+ },
+ {
+ 'accno' => '3400',
+ 'amount' => '-100.00000',
+ 'amount_with_cb' => '0.00000',
+ 'cat' => 'E',
+ 'cb_amount' => '-100.00000',
+ 'ob_amount' => undef,
+ 'ob_next_year' => undef,
+ 'type' => 'pl',
+ 'year_end_amount' => '-100.00000'
+ },
+ {
+ 'accno' => '4980',
+ 'amount' => '-4.20000',
+ 'amount_with_cb' => '0.00000',
+ 'cat' => 'E',
+ 'cb_amount' => '-4.20000',
+ 'ob_amount' => undef,
+ 'ob_next_year' => undef,
+ 'type' => 'pl',
+ 'year_end_amount' => '-4.20000'
+ },
+ {
+ 'accno' => '8400',
+ 'amount' => '280.00000',
+ 'amount_with_cb' => '0.00000',
+ 'cat' => 'I',
+ 'cb_amount' => '280.00000',
+ 'ob_amount' => undef,
+ 'ob_next_year' => undef,
+ 'type' => 'pl',
+ 'year_end_amount' => '280.00000'
+ },
+ ],
+ 'balances after third year_end ok');
+
+clear_up();
+done_testing;
+
+1;
+
+sub clear_up {
+ foreach (qw(BankAccount
+ GLTransaction
+ AccTransaction
+ InvoiceItem
+ Invoice
+ PurchaseInvoice
+ Part
+ Customer
+ )
+ ) {
+ "SL::DB::Manager::${_}"->delete_all(all => 1);
+ }
+};
+
+sub get_account_balances {
+ my $query = <<SQL;
+ select c.accno,
+ case when c.category = ANY( '{I,E}' ) then 'profit_loss_account'
+ when c.category = ANY( '{A,C,L,Q}' ) then 'asset_account'
+ else null
+ end as account_type,
+ sum(a.amount)
+ from acc_trans a
+ left join chart c on (c.id = a.chart_id)
+group by c.accno, account_type
+order by account_type, c.accno;
+SQL
+
+ my $result = selectall_hashref_query($::form, $dbh, $query);
+ return $result;
+};
+
+sub get_final_balances {
+ my $query = <<SQL;
+ select c.accno,
+ c.category as cat,
+ sum(a.amount ) filter (where ob_transaction is true and a.transdate < ?) as ob_amount,
+ sum(a.amount ) filter (where cb_transaction is false and ob_transaction is false and a.transdate < ?) as amount,
+ sum(a.amount ) filter (where cb_transaction is false and a.transdate < ?) as year_end_amount,
+ sum(a.amount ) filter (where a.transdate < ?) as amount_with_cb,
+ sum(a.amount * -1) filter (where cb_transaction is true and a.transdate < ?) as cb_amount,
+ sum(a.amount ) filter (where ob_transaction is true and a.transdate = ?) as ob_next_year,
+ case when c.category = ANY( '{I,E}' ) then 'pl'
+ when c.category = ANY( '{A,C,L,Q}' ) then 'asset'
+ else null
+ end as type
+ from acc_trans a
+ inner join chart c on (c.id = a.chart_id)
+ where a.transdate >= ?
+ and a.transdate <= ?
+ group by c.id, c.accno, c.category
+ order by type, c.accno
+SQL
+
+ my $result = selectall_hashref_query($::form, $dbh, $query, $ob_date, $ob_date, $ob_date, $ob_date, $ob_date, $ob_date, $start_date, $ob_date);
+ return $result;
+}
+
+sub gl_booking {
+ # wrapper around SL::Dev::Record::create_gl_transaction for quickly creating transactions
+ my ($amount, $date, $reference, $description, $gegenkonto, $konto, $ob, $cb) = @_;
+
+ # my $transdate = $::locale->parse_date_to_object($date);
+
+ return create_gl_transaction(
+ ob_transaction => $ob,
+ cb_transaction => $cb,
+ transdate => $date,
+ reference => $reference,
+ description => $description,
+ bookings => [
+ {
+ chart => $konto,
+ credit => $amount,
+ },
+ {
+ chart => $gegenkonto,
+ debit => $amount,
+ },
+ ],
+ );
+};
kivitendo selftest report.
[% IF errors %]
- General error(s) have occured.
+ General error(s) have occurred.
[% errors %]
[% END %]
Full report:
------------
-[% FOREACH module = SELF.diag_per_module.keys %]
+[% FOREACH module = SELF.diag_per_module.keys.sort %]
Module: [% module %]
--------------------
--- /dev/null
+<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d' ?>
+
+<x:xmpmeta xmlns:x="adobe:ns:meta/"
+ x:xmptk="Adobe XMP Core 4.0-c316 44.253921, Sun Oct 01 2006 17:14:39">
+ <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+ <rdf:Description rdf:about=""
+ xmlns:pdfaExtension="http://www.aiim.org/pdfa/ns/extension/"
+ xmlns:pdfaSchema="http://www.aiim.org/pdfa/ns/schema#"
+ xmlns:pdfaProperty="http://www.aiim.org/pdfa/ns/property#"
+ >
+ <pdfaExtension:schemas>
+ <rdf:Bag>
+ <rdf:li rdf:parseType="Resource">
+ <pdfaSchema:namespaceURI>http://ns.adobe.com/pdfx/1.3/</pdfaSchema:namespaceURI>
+ <pdfaSchema:prefix>pdfx</pdfaSchema:prefix>
+ <pdfaSchema:schema>PDF/X Schema</pdfaSchema:schema>
+ <pdfaSchema:property><rdf:Seq>
+ <rdf:li rdf:parseType="Resource">
+ <pdfaProperty:category>external</pdfaProperty:category>
+ <pdfaProperty:description>URL to an online version or preprint</pdfaProperty:description>
+ <pdfaProperty:name>AuthoritativeDomain</pdfaProperty:name>
+ <pdfaProperty:valueType>Text</pdfaProperty:valueType>
+ </rdf:li></rdf:Seq>
+ </pdfaSchema:property>
+ </rdf:li>
+ <rdf:li rdf:parseType="Resource">
+ <pdfaSchema:namespaceURI>http://www.aiim.org/pdfua/ns/id/</pdfaSchema:namespaceURI>
+ <pdfaSchema:prefix>pdfuaid</pdfaSchema:prefix>
+ <pdfaSchema:schema>PDF/UA ID Schema</pdfaSchema:schema>
+ <pdfaSchema:property><rdf:Seq>
+ <rdf:li rdf:parseType="Resource">
+ <pdfaProperty:category>internal</pdfaProperty:category>
+ <pdfaProperty:description>Part of PDF/UA standard</pdfaProperty:description>
+ <pdfaProperty:name>part</pdfaProperty:name>
+ <pdfaProperty:valueType>Integer</pdfaProperty:valueType>
+ </rdf:li></rdf:Seq>
+ </pdfaSchema:property>
+ </rdf:li>
+ <rdf:li rdf:parseType="Resource">
+ <pdfaSchema:schema>PRISM metadata</pdfaSchema:schema>
+ <pdfaSchema:namespaceURI>http://prismstandard.org/namespaces/basic/2.2/</pdfaSchema:namespaceURI>
+ <pdfaSchema:prefix>prism</pdfaSchema:prefix>
+ <pdfaSchema:property><rdf:Seq>
+ <rdf:li rdf:parseType="Resource">
+ <pdfaProperty:name>aggregationType</pdfaProperty:name>
+ <pdfaProperty:valueType>Text</pdfaProperty:valueType>
+ <pdfaProperty:category>external</pdfaProperty:category>
+ <pdfaProperty:description>The type of publication. If defined, must be one of book, catalog, feed, journal, magazine, manual, newsletter, pamphlet.</pdfaProperty:description>
+ </rdf:li>
+ <rdf:li rdf:parseType="Resource">
+ <pdfaProperty:name>url</pdfaProperty:name>
+ <pdfaProperty:valueType>URL</pdfaProperty:valueType>
+ <pdfaProperty:category>external</pdfaProperty:category>
+ <pdfaProperty:description>URL for the article or unit of content</pdfaProperty:description>
+ </rdf:li>
+ </rdf:Seq></pdfaSchema:property>
+ </rdf:li>
+[% IF zugferd %]
+ <rdf:li rdf:parseType="Resource">
+ <pdfaSchema:schema>ZUGFeRD PDFA Extension Schema</pdfaSchema:schema>
+ <pdfaSchema:namespaceURI>urn:zugferd:pdfa:CrossIndustryDocument:invoice:2p0#</pdfaSchema:namespaceURI>
+ <pdfaSchema:prefix>fx</pdfaSchema:prefix>
+ <pdfaSchema:property>
+ <rdf:Seq>
+ <rdf:li rdf:parseType="Resource">
+ <pdfaProperty:name>DocumentFileName</pdfaProperty:name>
+ <pdfaProperty:valueType>Text</pdfaProperty:valueType>
+ <pdfaProperty:category>external</pdfaProperty:category>
+ <pdfaProperty:description>name of the embedded XML invoice file</pdfaProperty:description>
+ </rdf:li>
+ <rdf:li rdf:parseType="Resource">
+ <pdfaProperty:name>DocumentType</pdfaProperty:name>
+ <pdfaProperty:valueType>Text</pdfaProperty:valueType>
+ <pdfaProperty:category>external</pdfaProperty:category>
+ <pdfaProperty:description>INVOICE</pdfaProperty:description>
+ </rdf:li>
+ <rdf:li rdf:parseType="Resource">
+ <pdfaProperty:name>Version</pdfaProperty:name>
+ <pdfaProperty:valueType>Text</pdfaProperty:valueType>
+ <pdfaProperty:category>external</pdfaProperty:category>
+ <pdfaProperty:description>The actual version of the ZUGFeRD data</pdfaProperty:description>
+ </rdf:li>
+ <rdf:li rdf:parseType="Resource">
+ <pdfaProperty:name>ConformanceLevel</pdfaProperty:name>
+ <pdfaProperty:valueType>Text</pdfaProperty:valueType>
+ <pdfaProperty:category>external</pdfaProperty:category>
+ <pdfaProperty:description>The conformance level of the ZUGFeRD data</pdfaProperty:description>
+ </rdf:li>
+ </rdf:Seq>
+ </pdfaSchema:property>
+ </rdf:li>
+[% END %]
+ </rdf:Bag>
+ </pdfaExtension:schemas>
+ </rdf:Description>
+ <rdf:Description rdf:about="" xmlns:pdf="http://ns.adobe.com/pdf/1.3/">
+ <pdf:Producer>[% producer | xml %]</pdf:Producer>
+ </rdf:Description>
+ <rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <dc:format>application/pdf</dc:format>
+[% IF meta_data.title %]
+ <dc:title><rdf:Alt><rdf:li xml:lang="x-default">[% meta_data.title | xml %]</rdf:li></rdf:Alt></dc:title>
+[% END %]
+ <dc:creator><rdf:Seq><rdf:li>v3</rdf:li></rdf:Seq></dc:creator>
+[% IF meta_data.language %]
+ <dc:language><rdf:Bag><rdf:li>[% meta_data.language | xml %]</rdf:li></rdf:Bag></dc:language>
+[% END %]
+ </rdf:Description>
+ <rdf:Description rdf:about="" xmlns:prism="http://prismstandard.org/namespaces/basic/2.2/">
+ </rdf:Description>
+ <rdf:Description rdf:about="" xmlns:pdfx="http://ns.adobe.com/pdfx/1.3/">
+ </rdf:Description>
+ <rdf:Description rdf:about="" xmlns:pdfaid="http://www.aiim.org/pdfa/ns/id/">
+ <pdfaid:part>[% pdf_a_version | xml %]</pdfaid:part>
+ <pdfaid:conformance>[% pdf_a_conformance | xml %]</pdfaid:conformance>
+ </rdf:Description>
+ <rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/">
+ <xmp:CreatorTool>[% producer | xml %]</xmp:CreatorTool>
+ <xmp:ModifyDate>[% timestamp | xml %]</xmp:ModifyDate>
+ <xmp:CreateDate>[% timestamp | xml %]</xmp:CreateDate>
+ <xmp:MetadataDate>[% timestamp | xml %]</xmp:MetadataDate>
+ </rdf:Description>
+ <rdf:Description rdf:about="" xmlns:xmpRights = "http://ns.adobe.com/xap/1.0/rights/">
+ </rdf:Description>
+
+[% IF zugferd %]
+ <rdf:Description xmlns:fx="urn:zugferd:pdfa:CrossIndustryDocument:invoice:2p0#"
+ fx:ConformanceLevel="[% zugferd.conformance_level | xml %]"
+ fx:DocumentFileName="[% zugferd.document_file_name | xml %]"
+ fx:DocumentType="[% zugferd.document_type | xml %]"
+ fx:Version="[% zugferd.version %]"
+ rdf:about=""/>
+[% END %]
+
+ </rdf:RDF>
+</x:xmpmeta>
+
+<?xpacket end='w'?>
\hfill
-% Anrede nach Geschlecht unterscheiden
-\ifthenelse{\equal{<%cp_name%>}{}}{\anrede}{
+% Bei Kontaktperson Anrede nach Geschlecht unterscheiden.
+% Bei natürlichen Personen persönliche Anrede, sonst allgemeine Anrede.
+\ifthenelse{\equal{<%cp_name%>}{}}{
+ <%if natural_person%><%greeting%> <%name%>,<%else%>\anrede<%end if%>}{
\ifthenelse{\equal{<%cp_gender%>}{f}}
{\anredefrau}{\anredeherr} <%cp_title%> <%cp_name%>,}\\
\newcommand{\position} {Pos.}
\newcommand{\artikelnummer} {Art.-Nr.}
+\newcommand{\kundenartnr} {Ihre Art.-Nr.}
\newcommand{\bild} {Bild}
\newcommand{\keinbild} {kein Bild}
\newcommand{\menge} {Menge}
\newcommand{\position} {Pos.}
\newcommand{\artikelnummer} {Part No.}
+\newcommand{\kundenartnr} {Your Part No.}
\newcommand{\bild} {Picture}
\newcommand{\keinbild} {n/a}
\newcommand{\menge} {Qty}
\hfill
-% Anrede nach Geschlecht unterscheiden
-\ifthenelse{\equal{<%cp_name%>}{}}{\anrede}{
+% Bei Kontaktperson Anrede nach Geschlecht unterscheiden.
+% Bei natürlichen Personen persönliche Anrede, sonst allgemeine Anrede.
+\ifthenelse{\equal{<%cp_name%>}{}}{
+ <%if natural_person%><%greeting%> <%name%>,<%else%>\anrede<%end if%>}{
\ifthenelse{\equal{<%cp_gender%>}{f}}
{\anredefrau}{\anredeherr} <%cp_title%> <%cp_name%>,}\\
%\rechnungsformel\\
<%if serialnumber%> && \scriptsize \seriennummer: <%serialnumber%>\\<%end serialnumber%>
<%if ean%> && \scriptsize \ean: <%ean%>\\<%end ean%>
<%if projectnumber%> && \scriptsize \projektnummer: <%projectnumber%>\\<%end projectnumber%>
+ <%if customer_make%>
+ <%foreach customer_make%>
+ \ifthenelse{\equal{<%customer_make%>}{<%name%>}}{&& \kundenartnr: <%customer_model%>\\}{}
+ <%end foreach%>
+ <%end if%>
\\[-0.8em]
<%end number%>
\hfill
-% Anrede nach Geschlecht unterscheiden
-\ifthenelse{\equal{<%cp_name%>}{}}{\anrede}{
+% Bei Kontaktperson Anrede nach Geschlecht unterscheiden.
+% Bei natürlichen Personen persönliche Anrede, sonst allgemeine Anrede.
+\ifthenelse{\equal{<%cp_name%>}{}}{
+ <%if natural_person%><%greeting%> <%name%>,<%else%>\anrede<%end if%>}{
\ifthenelse{\equal{<%cp_gender%>}{f}}
{\anredefrau}{\anredeherr} <%cp_title%> <%cp_name%>,}\\
\hfill
-% Anrede nach Geschlecht unterscheiden
-\ifthenelse{\equal{<%cp_name%>}{}}{\anrede}{
+% Bei Kontaktperson Anrede nach Geschlecht unterscheiden.
+% Bei natürlichen Personen persönliche Anrede, sonst allgemeine Anrede.
+\ifthenelse{\equal{<%cp_name%>}{}}{
+ <%if natural_person%><%greeting%> <%name%>,<%else%>\anrede<%end if%>}{
\ifthenelse{\equal{<%cp_gender%>}{f}}
{\anredefrau}{\anredeherr} <%cp_title%> <%cp_name%>,}\\
\hfill
-% Anrede nach Geschlecht unterscheiden
-\ifthenelse{\equal{<%cp_name%>}{}}{\anrede}{
+% Bei Kontaktperson Anrede nach Geschlecht unterscheiden.
+% Bei natürlichen Personen persönliche Anrede, sonst allgemeine Anrede.
+\ifthenelse{\equal{<%cp_name%>}{}}{
+ <%if natural_person%><%greeting%> <%name%>,<%else%>\anrede<%end if%>}{
\ifthenelse{\equal{<%cp_gender%>}{f}}
{\anredefrau}{\anredeherr} <%cp_title%> <%cp_name%>,}\\
<%if serialnumber%> && \scriptsize \seriennummer: <%serialnumber%>\\<%end serialnumber%>
<%if ean%> && \scriptsize \ean: <%ean%>\\<%end ean%>
<%if projectnumber%> && \scriptsize \projektnummer: <%projectnumber%>\\<%end projectnumber%>
+ <%if customer_make%>
+ <%foreach customer_make%>
+ \ifthenelse{\equal{<%customer_make%>}{<%name%>}}{&& \kundenartnr: <%customer_model%>\\}{}
+ <%end foreach%>
+ <%end if%>
<%foreach si_number%><%if si_chargenumber%> && \scriptsize \charge: <%si_chargenumber%> <%if si_bestbefore%> \mhd: <%si_bestbefore%><%end if%> <%si_qty%>~<%si_unit%><%end si_chargenumber%>\\<%end si_number%>
\\[-0.8em]
\hfill
-% Anrede nach Geschlecht unterscheiden
-\ifthenelse{\equal{<%cp_name%>}{}}{\anrede}{
+% Bei Kontaktperson Anrede nach Geschlecht unterscheiden.
+% Bei natürlichen Personen persönliche Anrede, sonst allgemeine Anrede.
+\ifthenelse{\equal{<%cp_name%>}{}}{
+ <%if natural_person%><%greeting%> <%name%>,<%else%>\anrede<%end if%>}{
\ifthenelse{\equal{<%cp_gender%>}{f}}
{\anredefrau}{\anredeherr} <%cp_title%> <%cp_name%>,}\\
<%if serialnumber%> && \scriptsize \seriennummer: <%serialnumber%>\\<%end serialnumber%>
<%if ean%> && \scriptsize \ean: <%ean%>\\<%end ean%>
<%if projectnumber%> && \scriptsize \projektnummer: <%projectnumber%>\\<%end projectnumber%>
+ <%if customer_make%>
+ <%foreach customer_make%>
+ \ifthenelse{\equal{<%customer_make%>}{<%name%>}}{&& \kundenartnr: <%customer_model%>\\}{}
+ <%end foreach%>
+ <%end if%>
\\[-0.8em]
<%end number%>
\hfill
-% Anrede nach Geschlecht unterscheiden
-\ifthenelse{\equal{<%cp_name%>}{}}{\anrede}{
- \ifthenelse{\equal{<%cp_gender%>}{f}}
- {\anredefrau}{\anredeherr} <%cp_title%> <%cp_name%>,}\\
+% Bei Kontaktperson Anrede nach Geschlecht unterscheiden.
+% Bei natürlichen Personen persönliche Anrede, sonst allgemeine Anrede.
+\ifthenelse{\equal{<%cp_name%>}{}}{
+ <%if natural_person%><%greeting%> <%name%>,<%else%>\anrede<%end if%>}{
+ \ifthenelse{\equal{<%cp_gender%>}{f}}
+ {\anredefrau}{\anredeherr} <%cp_title%> <%cp_name%>,}\\
\angebotsformel\\
<%if serialnumber%> && \scriptsize \seriennummer: <%serialnumber%>\\<%end serialnumber%>
<%if ean%> && \scriptsize \ean: <%ean%>\\<%end ean%>
<%if projectnumber%> && \scriptsize \projektnummer: <%projectnumber%>\\<%end projectnumber%>
+ <%if customer_make%>
+ <%foreach customer_make%>
+ \ifthenelse{\equal{<%customer_make%>}{<%name%>}}{&& \kundenartnr: <%customer_model%>\\}{}
+ <%end foreach%>
+ <%end if%>
\\[-0.8em]
<%end number%>
\hfill
-% Anrede nach Geschlecht unterscheiden
-\ifthenelse{\equal{<%cp_name%>}{}}{\anrede}{
+% Bei Kontaktperson Anrede nach Geschlecht unterscheiden.
+% Bei natürlichen Personen persönliche Anrede, sonst allgemeine Anrede.
+\ifthenelse{\equal{<%cp_name%>}{}}{
+ <%if natural_person%><%greeting%> <%name%>,<%else%>\anrede<%end if%>}{
\ifthenelse{\equal{<%cp_gender%>}{f}}
{\anredefrau}{\anredeherr} <%cp_title%> <%cp_name%>,}\\
\hfill
-% Anrede nach Geschlecht unterscheiden
-\ifthenelse{\equal{<%cp_name%>}{}}{\anrede}{
+% Bei Kontaktperson Anrede nach Geschlecht unterscheiden.
+% Bei natürlichen Personen persönliche Anrede, sonst allgemeine Anrede.
+\ifthenelse{\equal{<%cp_name%>}{}}{
+ <%if natural_person%><%greeting%> <%name%>,<%else%>\anrede<%end if%>}{
\ifthenelse{\equal{<%cp_gender%>}{f}}
{\anredefrau}{\anredeherr} <%cp_title%> <%cp_name%>,}\\
\hfill
-% Anrede nach Geschlecht unterscheiden
-\ifthenelse{\equal{<%cp_name%>}{}}{\anrede}{
+% Bei Kontaktperson Anrede nach Geschlecht unterscheiden.
+% Bei natürlichen Personen persönliche Anrede, sonst allgemeine Anrede.
+\ifthenelse{\equal{<%cp_name%>}{}}{
+ <%if natural_person%><%greeting%> <%name%>,<%else%>\anrede<%end if%>}{
\ifthenelse{\equal{<%cp_gender%>}{f}}
{\anredefrau}{\anredeherr} <%cp_title%> <%cp_name%>,}\\
-RB
\ No newline at end of file
+marei/
\ No newline at end of file
+++ /dev/null
-<body bgcolor=ffffff>
-
-<table width=100%>
- <tr>
- <td width=10> </td>
-
- <td>
- <table width=100%>
- <tr>
- <td>
- <h4>
- <%company%>
- <br><%address%>
- </h4>
- </td>
-
- <th><img src=http://localhost/lx-erp/lx-office-erp.png border=0 width=64 height=58></th>
-
- <th align=right>
- <h4>
- Tel: <%tel%>
- <br>Fax: <%fax%>
- </h4>
- </td>
- </tr>
-
- <tr>
- <th colspan=3>
- <h4>L A G E R L I S T E</h4>
- </th>
- </tr>
- </table>
- </td>
- </tr>
-
- <tr>
- <td> </td>
-
- <td>
- <table width=100% cellspacing=0 cellpadding=0>
- <tr bgcolor=000000>
- <th align=left width=50%><font color=ffffff>Absender</th>
- <th align=left width=50%><font color=ffffff>Lieferanschrift</th>
- </tr>
-
- <tr valign=top>
- <td><%name%>
- <br><%street%>
- <br><%zipcode%>
- <br><%city%>
- <br><%country%>
- <br>
-
- <%if contact%>
- <br>Kontakt: <%contact%>
- <%end contact%>
-
- <%if vendorphone%>
- <br>Tel: <%vendorphone%>
- <%end vendorphone%>
-
- <%if vendorfax%>
- <br>Fax: <%vendorfax%>
- <%end vendorfax%>
-
- <%if email%>
- <br><%email%>
- <%end email%>
-
- </td>
-
- <td><%shiptoname%>
- <br><%shiptostreet%>
- <br><%shiptozipcode%>
- <br><%shiptocity%>
- <br><%shiptocountry%>
-
- <br>
- <%if shiptocontact%>
- <br>Kontakt: <%shiptocontact%>
- <%end shiptocontact%>
-
- <%if shiptophone%>
- <br>Tel: <%shiptophone%>
- <%end shiptophone%>
-
- <%if shiptofax%>
- <br>Fax: <%shiptofax%>
- <%end shiptofax%>
- </td>
- </tr>
- </table>
- </td>
- </tr>
-
- <tr height=5></tr>
-
- <tr>
- <td> </td>
-
- <td>
- <table width=100% border=1>
- <tr>
- <th width=17% align=left nowrap>BestellNr. #</th>
- <th width=17% align=left nowrap>Datum</th>
- <th width=17% align=left nowrap>Kontakt</th>
- <%if warehouse%>
- <th width=17% align=left nowrap>Lager</th>
- <%end warehouse%>
- <th width=17% align=left>Versandort</th>
- <th width=15% align=left>Lieferung durch</th>
- </tr>
-
- <tr>
- <td><%ordnumber%> </td>
-
- <%if shippingdate%>
- <td><%shippingdate%></td>
- <%end shippingdate%>
-
- <%if not shippingdate%>
- <td><%orddate%></td>
- <%end shippingdate%>
-
- <td><%employee%> </td>
-
- <%if warehouse%>
- <td><%warehouse%></td>
- <%end warehouse%>
-
- <td><%shippingpoint%> </td>
- <td><%shipvia%> </td>
- </tr>
- </table>
- </td>
- </tr>
-
- <tr>
- <td> </td>
-
- <td>
- <table width=100%>
- <tr bgcolor=000000>
- <th align=left><font color=ffffff>Pos</th>
- <th align=left><font color=ffffff>ArtNr.</th>
- <th align=left><font color=ffffff>Beschreibung</th>
- <th><font color=ffffff>Seriennummer</th>
- <th> </th>
- <th><font color=ffffff>Menge</th>
- <th><font color=ffffff>Erh</th>
- <th> </th>
- <th><font color=ffffff>Lagerplatz</th>
- </tr>
-
- <%foreach number%>
- <tr valign=top>
- <td><%runningnumber%></td>
- <td><%number%></td>
- <td><%description%></td>
- <td><%serialnumber%></td>
- <td><%deliverydate%></td>
- <td align=right><%qty%></td>
- <td align=right><%ship%></td>
- <td><%unit%></td>
- <td><%bin%></td>
- </tr>
- <%end number%>
-
- </table>
- </td>
- </tr>
-
- <tr>
- <td> </td>
-
- <td><hr noshade></td>
- </tr>
-
-</table>
-
+++ /dev/null
-% ----------------------------------------------------------
-% letter.tex
-% Globale Vorlage fuer Briefartige Documente LX-Office 2.6
-%
-% Changelog: see gitlog
- \newcommand{\ftLetterVersion}{1.2-u (05.12.2012)}
-%
-% Lizenz
-% http://www.gnu.de/licenses/gpl-3.0.html
-%
-% Siehe ./README
-%
-% Autor: Wulf Coulmann scripts_at_gpl.coulmann.de
-% Aufgebaut auf invoice.tex 0.1 kmk@lilalaser.de
-%
-% ----------------------------------------------------------
-
-\documentclass[letter,fontsize=11pt]{scrlttr2}
-
-
-\begingroup
- \makeatletter
- \@latex@warning@no@line{ #### this is default.tex \ftLetterVersion #####}
-\endgroup
-
-
-\usepackage{ifpdf}
-\usepackage{graphicx}
-\usepackage{german}
-\usepackage{textcomp}
-\usepackage{lastpage}
-\usepackage{filecontents}
-\usepackage{etex}
-\usepackage{ltxtable}
-\usepackage{tabularx}
-\usepackage{longtable}
-\usepackage{booktabs}
-\usepackage{numprint}
-\usepackage{xstring}
-\newcommand{\leer}{}
-\usepackage{zwischensumme}
-\ifthenelse{\isundefined{\employeecountry}}{\input{mydata}}{}
-
-%% meta infos
-\newcommand{\docname}{<%template_meta.formname NOESCAPE%>}
-\newcommand{\TemplateMetaLanguageDescription}{<%template_meta.language.description NOESCAPE%>}
-\newcommand{\LangCode}{<%template_meta.language.template_code NOESCAPE%>}
-\newcommand{\TemplateMetaLanguageOutputNumberformat}{<%template_meta.language.output_numberformat NOESCAPE%>}
-\newcommand{\TemplateMetaLanguageOutputDateformat}{<%template_meta.language.output_dateformat NOESCAPE%>}
-\newcommand{\TemplateMetaFormat}{<%template_meta.format NOESCAPE%>}
-\newcommand{\TemplateMetaExtension}{<%template_meta.extension NOESCAPE%>}
-\newcommand{\TemplateMetaMedia}{<%template_meta.media NOESCAPE%>}
-\newcommand{\TemplateMetaPrinterDescription}{<%template_meta.printer.description NOESCAPE%>}
-\newcommand{\TemplateMetaPrinterTemplateCode}{<%template_meta.printer.template_code NOESCAPE%>}
-
-%%%%%%%%% Report-Variablen umsetzen, damit latex sie in lxbriefkopf.tex sieht.
-%%%% Die eigenen Daten
-\newcommand{\employeename}{<%employee_name%>}
-\newcommand{\employeecompany}{<%employee_company%>}
-\newcommand{\employeeaddress}{<%employee_address%>}
-\newcommand{\employeetel}{<%employee_tel%>}
-\newcommand{\employeefax}{<%employee_fax%>}
-\newcommand{\employeecoustid}{<%employee_co_ustid%>}
-\newcommand{\employeetaxnumber}{<%employee_taxnumber%>}
-\newcommand{\media}{<%media%>}
-
-
-%%%% Adressat
-\newcommand{\name}{<%name%>}
-\newcommand{\Shipname}{\ifthenelse{\equal{<%shiptoname%>}{\leer}}{<%name%>}{<%shiptoname%>}}
-\newcommand{\departmentone}{<%department_1%>}
-\newcommand{\departmenttwo}{<%department_2%>}
-\newcommand{\cpgreeting}{<%cp_greeting%>}
-\newcommand{\cptitle}{<%cp_title%>}
-\newcommand{\cpgivenname}{<%cp_givenname%>}
-\newcommand{\cpname}{<%cp_name%>}
-\newcommand{\street}{<%street%>}
-\newcommand{\Shipstreet}{\ifthenelse{\equal{<%shiptostreet%>}{\leer}}{<%street%>}{<%shiptostreet%>}}
-\newcommand{\country}{<%country%>}
-\newcommand{\Shipcountry}{<%shiptocountry%>}
-\newcommand{\UstId}{<%ustid%>}
-\newcommand{\zipcode}{<%zipcode%>}
-\newcommand{\Shipzipcode}{\ifthenelse{\equal{<%shiptozipcode%>}{\leer}}{<%zipcode%>}{<%shiptozipcode%>}}
-\newcommand{\city}{<%city%>}
-\newcommand{\Shipcity}{\ifthenelse{\equal{<%shiptocity%>}{\leer}}{<%city%>}{<%shiptocity%>}}
-\newcommand{\phone}{<%customerphone%>}
-\newcommand{\fax}{<%customerfax%>}
-
-%%%% Variablen, die sich auf das ganze Dokument beziehen
-\newcommand{\kundennummer}{<%customernumber%>}
-\newcommand{\vendornumber}{<%vendornumber%>}
-\newcommand{\quonumber}{<%quonumber%>} % Angebotsnummer
-\newcommand{\ordnumber}{<%ordnumber%>} % Auftragsnummer bei uns
-\newcommand{\cusordnumber}{<%cusordnumber%>} % Auftragsnummer beim Kunden
-\newcommand{\invnumber}{<%invnumber%>} % Rechnungsnummer
-\newcommand{\donumber}{<%donumber%>} % Lieferscheinnummer
-%\newcommand{\docnumber}{Rechnungsnummer: \invnumber}
-\newcommand{\quodate}{<%quodate%>} % Angebotsdatum
-\newcommand{\orddate}{<%orddate%>} % Auftragsdatum
-\newcommand{\reqdate}{<%reqdate%>} % gewuenschtes Lieferdatum
-\newcommand{\deliverydate}{<%deliverydate%>} % Lieferdatum
-\newcommand{\invdate}{<%invdate%>} % Rechnungsdatum
-\newcommand{\transdate}{<%transdate%>} % Lieferscheindatum
-\newcommand{\terms}{<%terms%>} % Zahlungsfrist
-\newcommand{\duedate}{<%duedate%>} % Fälligkeitsdatum
-\newcommand{\invtotal}{<%invtotal NOFORMAT%>} % Gesamtbetrag
-\newcommand{\paid}{<%paid NOFORMAT%>} % Schon bezahlt
-\newcommand{\total}{<%total NOFORMAT%>} % Restbetrag
-\newcommand{\subtotal}{<%subtotal NOFORMAT%>} % Restbetrag
-\newcommand{\paymentterms}{<%payment_terms%>} % Zahlungsbedingungen
-\newcommand{\paymentPrivatEnd}{E} % Endung bei Privatkunden
-\newcommand{\paymenttype}{<%payment_description%>} % name der Zahlungs-art - fuer Steuerung brutto/netto
-
-
-%%%% Lieferadresse
-\newcommand{\shiptoname}{<%shiptoname%>}
-\newcommand{\shiptocontact}{<%shiptocontact%>}
-\newcommand{\shiptodepartmentone}{<%shiptodepartment_1%>}
-\newcommand{\shiptodepartmenttwo}{<%shiptodepartment_2%>}
-\newcommand{\shiptostreet}{<%shiptostreet%>}
-\newcommand{\shiptocity}{<%shiptocity%>}
-\newcommand{\shiptocountry}{<%shiptocountry%>}
-\newcommand{\shiptophone}{<%shiptophone%>}
-\newcommand{\shiptozipcode}{<%shiptozipcode%>}
-\newcommand{\shiptofax}{<%shiptofax%>}
-
-%%%% Die Waehrungsvariable in Waehrunszeichen umsetzen
-\newcommand{\currency}{<%currency%>}
-\ifthenelse{\equal{\currency}{EUR}}{\let\currency\euro}{}
-\ifthenelse{\equal{\currency}{YEN}}{\let\currency\textyen}{}
-\ifthenelse{\equal{\currency}{GBP}}{\let\currency\pounds}{}
-\ifthenelse{\equal{\currency}{USD}}{\let\currency\$}{}
-
-%%%%%%%%%%%%% Ende Reportvariablen-Umsetzung
-
-\newcommand{\NoValue}{0}
-\newcommand{\Picklist}{0}
-\newcommand{\PurchaseOrder}{0}
-\newcommand{\trash}{0}
-\newcommand{\nonemptyline}[2]{\ifthenelse{\equal{#2}{\leer}}{}{#1#2~\\}}
-\newcommand{\MyAdress}{\IfStrEq{\docname}{sales_delivery_order}{\Shipname~\\
- % lieferadresse wenn Lieferschein
- \nonemptyline{\cpgreeting{ }\cpgivenname{ }}{\cpname}
- \nonemptyline{}{\departmentone}
- \Shipstreet ~\\
- \Shipzipcode{ }\Shipcity
- \ifthenelse{\equal{\Shipcountry}{\employeecountry}}{}{~\\ \Shipcountry} % Laenderangabe wird nur gedruckt,
- ~ % wenn der Empfaenger nicht im eigenen Land sitzt.
- }{
- \name~\\
- \nonemptyline{\cpgreeting{ }\cpgivenname{ }}{\cpname}
- \nonemptyline{}{\departmentone}
- \street ~\\
- \zipcode{ }\city
- \ifthenelse{\equal{\country} {\employeecountry}}{}{
- \ifthenelse{\equal{\country}{\leer}}{}{ ~\\ \country} } % Laenderangabe wird nur gedruckt,
- ~ % wenn der Empfaenger nicht im eigenen Land sitzt.
- }
-}
-
-
-
-\begin{document}
-
-%%% dei folgenden Funktionen lesen den Dokumentennamen aus und _muessen_nach_ \begin{dokument} stehen.
-
-% ==== statische Begriffe in der aktuellen Sprache einlesen
-\input{translations}
-
-
-\ifthenelse{\bgPdfEmailOnly = 1 }{
- \ifthenelse{\equal{\media}{email}}{
- }{
- \firsthead{}
- \watermark{}
- }
-}{}
-
-
-% ==== dokumenttyp ermitteln
-\IfStrEq{\docname}{pick_list}{
- % Sammelliste
- \setkomavar{backaddress}{\DeliveryAddress}
- \firsthead{
- \hspace{-3mm}
- \resizebox{\useplength{firstheadwidth}-50mm}{!}{%
- \huge \TitlePicklist
- }
- }
- \renewcommand{\NoValue}{1}
- \renewcommand{\Picklist}{1}
- \newcommand{\doctype}{}
- \newcommand{\MyDocdate}{\transdate}
- \newcommand{\DocNoTitle}{\DelorderNumber}
- \newcommand{\docnumber}{\donumber}
- \renewcommand{\deliverydate}{\transdate}
- % 2. Documentnummer
- \ifthenelse{\equal{\ordnumber}{\leer}}{
- % wenn keine Auftragsnummer -> Angebotsnummer
- \newcommand{\SecNoTitle}{\QuotationNumber}
- \newcommand{\secnumber}{\quonumber}
- }{
- \newcommand{\SecNoTitle}{\OrderNumber}
- \newcommand{\secnumber}{\ordnumber}
- }
-}{}
-\IfStrEq{\docname}{sales_delivery_order}{
- % Lieferschein
- \renewcommand{\NoValue}{1}
- \newcommand{\doctype}{\TitleDelorder}
- \newcommand{\MyDocdate}{\transdate}
- \newcommand{\DocNoTitle}{\DelorderNumber}
- \newcommand{\docnumber}{\donumber}
- \renewcommand{\deliverydate}{\transdate}
- % 2. Documentnummer
- \ifthenelse{\equal{\ordnumber}{\leer}}{
- % wenn keine Auftragsnummer -> Angebotsnummer
- \newcommand{\SecNoTitle}{\QuotationNumber}
- \newcommand{\secnumber}{\quonumber}
- }{
- \newcommand{\SecNoTitle}{\OrderNumber}
- \newcommand{\secnumber}{\ordnumber}
- }
-}{}
-\IfStrEq{\docname}{invoice}{
- % Rechnung
- \newcommand{\doctype}{\TitleInv}
- \newcommand{\MyDocdate}{\invdate}
- \newcommand{\DocNoTitle}{\InvNumber}
- \newcommand{\docnumber}{\invnumber}
- % 2. Documentnummer
- \ifthenelse{\equal{\ordnumber}{\leer}}{
- % wenn keine Auftragsnummer -> Angebotsnummer
- \newcommand{\SecNoTitle}{\QuotationNumber}
- \newcommand{\secnumber}{\quonumber}
- }{
- \newcommand{\SecNoTitle}{\OrderNumber}
- \newcommand{\secnumber}{\ordnumber}
- }
-}{}
-\IfStrEq{\docname}{proforma}{
- \newcommand{\doctype}{\TitleProforma}
- \newcommand{\MyDocdate}{\invdate}
- \newcommand{\DocNoTitle}{\InvNumber}
- \newcommand{\docnumber}{\invnumber}
- % 2. Documentnummer
- \ifthenelse{\equal{\ordnumber}{\leer}}{
- % wenn keine Auftragsnummer -> Angebotsnummer
- \newcommand{\SecNoTitle}{\QuotationNumber}
- \newcommand{\secnumber}{\quonumber}
- }{
- \newcommand{\SecNoTitle}{\OrderNumber}
- \newcommand{\secnumber}{\ordnumber}
- }
-}{}
-\IfStrEq{\docname}{purchase_order}{
- \renewcommand{\PurchaseOrder}{1}
- \newcommand{\doctype}{\TitlePurchaseOrder}
- \newcommand{\MyDocdate}{\orddate}
- \newcommand{\DocNoTitle}{\RequestOrderNumber}
- \newcommand{\docnumber}{\ordnumber}
- \renewcommand{\deliverydate}{\reqdate}
- \renewcommand{\DelDate}{\ReqByTitle}
- \renewcommand{\CustomerID}{\VendorID}
- \renewcommand{\kundennummer}{\vendornumber}
- \newcommand{\SecNoTitle}{}
- \newcommand{\secnumber}{}
-}{}
-\IfStrEq{\docname}{credit_note}{
- \newcommand{\doctype}{\TitleCreditNote}
- \newcommand{\MyDocdate}{\invdate}
- \newcommand{\DocNoTitle}{\CredNumber}
- \newcommand{\docnumber}{\invnumber}
- % keine 2. Documentnummer
- \newcommand{\SecNoTitle}{}
- \newcommand{\secnumber}{}
-}{}
-\IfStrEq{\docname}{sales_order}{
- % Auftragsbestaetigung
- \newcommand{\doctype}{\TitleSalesOrder}
- \newcommand{\MyDocdate}{\orddate}
- \renewcommand{\deliverydate}{\reqdate}
- \newcommand{\DocNoTitle}{\OrderNumber}
- \newcommand{\docnumber}{\ordnumber}
- % 2. Documentnummer
- \ifthenelse{\equal{\ordnumber}{\leer}}{
- % wenn keine Angebotsnummer -> leer
- \newcommand{\SecNoTitle}{}
- \newcommand{\secnumber}{}
- }{
- \newcommand{\SecNoTitle}{\QuotationNumber}
- \newcommand{\secnumber}{\quonumber}
- }
-}{ }
-\IfStrEq{\docname}{sales_quotation}{
- % Angebot
- \newcommand{\doctype}{\TitleSalesQuotation}
- \newcommand{\MyDocdate}{\quodate}
- \renewcommand{\DelDate}{\ValidUntil}
- \renewcommand{\deliverydate}{\reqdate}
- \newcommand{\DocNoTitle}{\QuotationNumber}
- \newcommand{\docnumber}{\quonumber}
- % 2. Documentnummer
- \newcommand{\SecNoTitle}{}
- \newcommand{\secnumber}{}
-}{ }
-
-
-
-% ==== \paid auf 0.00 falls leer
-\IfSubStr{\paid}{\DecimalSign}{}{\renewcommand{\paid}{0{\DecimalSign}00}}
-
-
-
-\setkomavar{date}{}
-
-
-\begin{letter}{{\ifthenelse{\isnamedefined{MyAdressfield}}{\MyAdressfield
- }{\MyAdress
- }}
-}
-\opening{}
-
-%========Datum und Nummern====================================================
-
-\newcommand{\DocId}{
- \begin{tabular*}{\textwidth+1em }{@{\extracolsep{\fill}}llllr}
- \MakeUppercase{\tiny \DocNoTitle} &
- \MakeUppercase{\tiny \CustomerID} &
- \MakeUppercase{\tiny \SecNoTitle } &
- \MakeUppercase{\tiny \DelDate } &
- \MakeUppercase{\tiny \Date}~\\
- \mainfont\docnumber &
- \mainfont\kundennummer &
- \mainfont\secnumber &
- \mainfont\deliverydate &
- \mainfont\MyDocdate~\\
-\end{tabular*} ~\\
-}
-
-\hspace{-0.5em} \DocId
-
-
-
-
-\nexthead{
- \ifthenelse{\bgPdfFirstPageOnly = 1 }{
- \hspace{-4mm} \DocId
- }{}
-}
-\vspace{ 5mm}
-
-{\noindent\textbf\doctype}~\\
-\IfEndWith{\paymenttype}{\paymentPrivatEnd}{\PriceInclTax }{ }
-
-
-%======Die eigentliche-Tabelle========================================
-
-% temporaere Datei mit Tabelle anlegen
-\begin{filecontents}{<%template_meta.tmpfile NOESCAPE%>.table.tex}
-\mainfont
-\resetlaufsumme
-
-
-
- \ifthenelse{\NoValue > 0 }
- { % Tabelle ohne Preisen
- \ifthenelse{\Picklist = 1 }{
-
- \begin{longtable}{@{}rlX@{ }rlrrrl@{}}
- }{
- \begin{longtable}{@{}rlX@{ }rlrr@{}}
-
- }
- % Kopfzeile der Tabelle
-
- {\Pos} &
- {\Number} &
- {\ItemNo} &
- {\Count} &
- {\Unit} \hspace{2mm}
- \ifthenelse{\Picklist = 1 }{& {\Take} & {\Storage} }{}
- ~\\
- \midrule
- \endfirsthead
-
- % Tabellenkopf nach dem Umbruch
- {\Pos} &
- {\Number} &
- {\ItemNo} &
- {\Count} &
- {\Unit} \hspace{2mm}
- \ifthenelse{\Picklist = 1 }{& {\Take} & {\Storage} }{}
- ~\\
-
- \midrule
- \endhead
-
- <%foreach number%>
- <%runningnumber%> % Laufende Positionsnummer
- &
- <%number%> % Artikelnummer
- &
- <%description%> % Kurzbeschreibung des Artikels
- \ifthenelse{\equal{<%longdescription%>}{\leer}}{}{ \newline <%longdescription%>}
- % Ein zeilenweises Auslieferdatum, wenn es gesetzt bei der Position hinterlegt ist.
- \ifthenelse{\equal{<%deliverydate_oe%>}{\leer}}{}{
- \newline \DelDate:~<%deliverydate_oe%>}
- &
- <%qty NOFORMAT%> % Menge
- &
- <%unit%> % Einheit
- %\ifthenelse{\Picklist = 1 }{& {x} & {x} }{}
- %\ifthenelse{\Picklist = 1 }{& {x} & {x} \hhline{~~~~~--} }{~\\}
- \ifthenelse{\Picklist = 1 }{& {\underline{;~~~~~~~~~}} & {\underline{;~~~~~~~~~}}~\\ }{~\\}
- %~\\ %
- <%end number%>
- \end{longtable} % Ende der zentralen Tabelle
- }{ % Tabelle mit Preisen
- \begin{longtable}{@{}rlX@{ }rlrrr@{}}
- % Kopfzeile der Tabelle
-
- {\Pos} &
- {\Number} &
- {\ItemNo} &
- {\Count} &
- {\Unit} &
- {\Fee} &
- {\Dis} &
- {\Total} \hspace{2mm} ~\\
- \midrule
- \endfirsthead
-
- % Tabellenkopf nach dem Umbruch
- {\Pos} &
- {\Number} &
- {\ItemNo} &
- {\Count} &
- {\Unit} &
- {\Fee} &
- {\Dis} &
- {\Total} \hspace{2mm} ~\\
- \midrule
- \multicolumn{7}{r}{ \rule{0mm}{5mm} \TabCarry{:} \MarkZwsumPos}
- \endhead
-
-
- % Fuss der Teiltabellen
- \multicolumn{7}{r}{ \rule{0mm}{5mm} \TabSubTotal{:} \MarkZwsumPos } ~\\
- \endfoot
-
- % Das Ende der Tabelle
- \midrule
- \multicolumn{7}{r}{ \rule{0mm}{5mm} \TabSubTotal{:} \MarkZwsumPos} ~\\
- \endlastfoot
-
- <%foreach number%>
- <%runningnumber%> % Laufende Positionsnummer
- &
- <%number%> % Artikelnummer
- &
- <%description%> % Kurzbeschreibung des Artikels
- \ifthenelse{\equal{<%longdescription%>}{\leer}}{}{ \newline <%longdescription%>}
- % Ein zeilenweises Auslieferdatum, wenn es gesetzt ist.
- \ifthenelse{\equal{<%reqdate%>}{\leer}}{}{
- \newline \DelDate:~<%reqdate%>}
- &
- <%qty NOFORMAT%> % Menge
- &
- <%unit%> % Einheit
- &
- %\IfEndWith{\paymentterms}{_e}{EN}{\brutto{<%sellprice NOFORMAT%>}{<%qty NOFORMAT%>}{<%p_discount%>}}
- \IfEndWith{\paymenttype}{\paymentPrivatEnd}{
- \BruttoSellPrice{<%sellprice NOFORMAT%>}{<%tax_rate%>}
- &
- \ifthenelse{\equal{<%p_discount%>}{0}}{}{ -<%p_discount%>\%}
- &
- \BruttoWert{<%linetotal NOFORMAT%>}{<%tax_rate%>}
- }{
- \numprint{<%sellprice NOFORMAT%>}
- &
- \ifthenelse{\equal{<%p_discount%>}{0}}{}{ -<%p_discount%>\%}
- &
- \Wert{<%linetotal NOFORMAT%>} % Zeilensumme addieren
- }
- ~\\ %
- <%end number%>
- \end{longtable} % Ende der zentralen Tabelle
- }
-\end{filecontents} % Ende der Hilfsdatei.
-
-\LTXtable{\textwidth}{<%template_meta.tmpfile NOESCAPE%>.table.tex}
-
-\rule{\textwidth}{0pt} % Ein (unsichtbarer) Strich quer ueber die Seite
-\vspace{ 5mm}
-\vspace{-2em plus 10em minus 2em}~\\
-\ifthenelse{\NoValue > 0 }
-{ % wenn keine Zahlen
-}{ % Wenn Zahlen
- \parbox{\textwidth}{
- \mainfont
- %
- %
- \setlength{\tabcolsep}{0.2em}
- \ifthenelse{\equal{\paid}{0{\DecimalSign}00} }
- { % Wenn noch nichts gezahlt wurde
- \IfSubStr{\invtotal}{\DecimalSign}{}{
- \fpAdd{\invtotal}{0}{<%subtotal NOFORMAT%>}
- <%foreach tax%>
- \fpAdd{\invtotal}{\invtotal}{<%tax NOFORMAT%>}
- <%end tax%>
- }
- \hfill
- \begin{tabular}{@{}rrr@{}}
- %{Summe vor Steuern:}& {\numprint{<%subtotal NOFORMAT%>}} & ~\\
-
- % Die unterschiedlichen Steueranteile getrennt ausweisen
- <%foreach tax%>
- { \IfEndWith{\paymenttype}{\paymentPrivatEnd}{\TaxInc }{ } <%taxdescription%>}
- &
- {\numprint{<%tax NOFORMAT%>}}& ~\\
- <%end tax%>
- \midrule[1pt]
- {\Sum~ \currency:} & \textbf{\numprint{\invtotal}}
- \end{tabular}
- }
- { % Wenn bereits etwas gezahlt wurde
- \hfill
- \begin{tabular}{@{}rrr@{}}
-
- {\EbT}& {\numprint{<%subtotal NOFORMAT%>}} & ~\\
-
- % Die unterschiedlichen Steueranteile getrennt ausweisen
- <%foreach tax%>
- {<%taxdescription%>}
- &
- {\numprint{<%tax NOFORMAT%>}}& ~\\
- <%end tax%>
-
- \midrule % Ein dünner Strich
- \Sum & \numprint{\invtotal} & ~\\
-
- <%foreach payment%>
- \AlreadyPayed~ {<%paymentdate%>}:& -{\numprint{<%payment%>}} & ~\\
- <%end paymentdate%>
-
- \midrule[2pt] % Ein etwas dickerer Strich
- {\Left~ \currency:} & \numprint{\total}
- \end{tabular}
- }% ende ithenelse
-
- } %Ende des Summenkasten
-}
-
-\vfill % Den Rest-Text soweit wie möglich nach unten schieben
-\ifthenelse{\isempty{<%notes%>}}{}{
- \mainfont
-\noindent <%notes%> ~\\[2em]
- }%
-\small
-\noindent \YourOrder
-\ifthenelse{\Picklist = 0}{\noindent \ifthenelse{\equal{<%ustid%>}{\leer}}{}{\UstidTitle} \UstId}{}
-\noindent \paymenthints % ist in translations.tex deffiniert
-\ifthenelse{\PurchaseOrder = 0}{\noindent \paymentterms}{}
-
-
-\end{letter}
-\end{document}
+++ /dev/null
-sample.lco
\ No newline at end of file
+++ /dev/null
-sample_head.pdf
\ No newline at end of file
+++ /dev/null
-mydata.tex.example
\ No newline at end of file
+++ /dev/null
-
-% \employeecountry wird fuer lxo fancy LaTeX benoetigt
-\newcommand{\employeecountry}{Deutschland}
-
-
-
-% die folgenden definitionen koennten auch direkt in der Steuerdatei *.lco stehen
-\newcommand{\MYfromname}{Die globalen Problemlöser}
-\newcommand{\MYaddrsecrow}{Gesellschaft für anderer Leute Sorgen mbH}
-\newcommand{\MYrechtsform}{Handelsregister: HRA 123456789 }
-\newcommand{\MYfromaddress}{Hauptstraße 5\\12345 Hier}
-\newcommand{\MYfromphone}{Tel: +49 (0)12 3456780}
-\newcommand{\MYfromfax}{Fax: +49 (0)12 3456781}
-\newcommand{\MYfromemail}{mail@g-problemloeser.com}
-\newcommand{\MYsignature}{Herbert Wichtig - Geschäftsführer}
-\newcommand{\MYustid}{UstID: DE 123 456 789}
-\newcommand{\MYfrombank}{Bankverbindung\\
- Ensifera Bank\\
- Kto 1234567800\\
- BLZ 123 456 78
-}
+++ /dev/null
-% ----------------------------------------------------------
-% letter.lco
-% Steuerdatei Briefklasse f-tex
-%
-% Changelog: see gitlog
- \newcommand{\ftLcoVTversion}{1.1-u (03.01.2012)}
-%
-% Lizenz
-% http://www.gnu.de/licenses/gpl-3.0.html
-%
-% Siehe ./README
-%
-% Autor: Wulf Coulmann scripts_at_gpl.coulmann.de
-%
-%
-% ----------------------------------------------------------
-
-
-\begingroup
- \makeatletter
- \@latex@warning@no@line{ #### this is letter.lco \ftLcoVTversion #####}
-\endgroup
-
-
-
-\ProvidesFile{letter.lco}[%
- 2002/07/09 v0.9a LaTeX2e unsupported letter-class-option]
-
-\KOMAoptions{foldmarks=false}
-\usepackage{graphicx}
-\usepackage[utf8]{inputenc}
-\usepackage{ngerman}
-\usepackage{lmodern}
-\usepackage{xcolor}
-\usepackage{watermark}
-\usepackage{xifthen}
-
-
-% ================== settings ==============================
-
- % Name der pdf Datei die den Briefkopf enthaelt
- \newcommand{\bgPdfName}{letter_head.pdf}
-
- % Hintergrund pdf nur bei erster Dokumentseite [1|0]
- \newcommand{\bgPdfFirstPageOnly}{1}
-
- % Hintergrundpdf nur bei versand per email [1|0]
- % (setze diesen Wert auf 1, wenn auf bereits Bedruktes Briefpapier ausgedruckt werden soll)
- \newcommand{\bgPdfEmailOnly}{0}
-
- % Trennlienie unter der Seitenkopfzeile ab Seite 2 ff.
- \KOMAoptions{headsepline=on}
-
- % der Abstand zu den Fusszeilen
- \addtolength{\textheight}{23mm}
-
- % zusaetzlicher Zwischenraum zur Fusszeile ab Seite 2 ff.
- % (nur bei bgPdfFirstPageOnly = 1)
- \addtolength{\footskip}{10mm}
-
-
-% ================== end settings ==============================
-
-
-
-\setkomavar{backaddress}{}
-
-\setkomavar{fromname}{\MYfromname}
-\newcommand\addrsecrow{\MYaddrsecrow}
-\newcommand\rechtsform{\MYrechtsform}
-\setkomavar{fromaddress}{\MYfromaddress}
-\setkomavar{fromphone}{\MYfromphone}
-\setkomavar{fromfax}{\MYfromfax}
-\setkomavar{fromemail}{\MYfromemail}
-\setkomavar{signature}{\MYsignature}
-\newcommand\ustid{\MYustid}
-\setkomavar{frombank}{\MYfrombank}
-
-\renewcommand{\rmdefault}{cmss}
-\newlength\entrytblsub
-\setlength\entrytblsub{\dimexpr\tabcolsep+1.3mm+\arrayrulewidth\relax}
-\setlength\textwidth{166mm}
-\oddsidemargin -0.4mm
-\KOMAoptions{headsepline=on}
-
-\pagestyle{myheadings}
-\@addtoplength{firstfootvpos}{18mm}
-\@addtoplength{foldmarkhpos}{5mm}
-\@setplength{firstheadvpos}{0mm}
-\@setplength{firstheadwidth}{165mm}
-\@setplength{firstfootwidth}{165mm}
-\@setplength{toaddrhpos}{25mm}
-\@setplength{toaddrvpos}{38mm}
-\@setplength{refhpos}{26mm}
-\@addtoplength{refvpos}{-18mm}
-
-\font\mainfont=cmss9
-
-
-
-\ifthenelse{\bgPdfFirstPageOnly = 0 }{
- \addtolength{\headheight}{50mm}
- \watermark{
- \setlength{\unitlength}{1mm}
- \put(-22,-226){
- \includegraphics[width=210mm]{\bgPdfName}
- }
- }
-}{}
-
-\firsthead{
- \ifthenelse{\bgPdfFirstPageOnly = 1 }{
- \put(-69,0){ % Mit diesem put-Befehl wird die Position des Logos bestimmt.
- \includegraphics[width=210mm]{\bgPdfName}
- }
- }{}
-}
-
-
-
-
-\firstfoot{%
-}
-
-\nextfoot{%
- \parbox{\useplength{firstfootwidth}}{
- \hspace{-\entrytblsub}
- \begin{tabular}{l}
- \usekomavar{fromname}
- \end{tabular}\hfill
- \begin{tabular}{r}
- \thepage
- \end{tabular}
- \hspace{-\entrytblsub}
- }
- \vspace{10mm}
-}
-
-
-
-\endinput
-% vim: set filetype=tex :EOF
+++ /dev/null
-
-<body bgcolor=ffffff>
-
-<table width=100%>
- <tr>
- <td width=10> </td>
- <td>
- <table width=100%>
- <tr>
- <td>
- <h4>
- <%company%>
- <br><%address%>
- </h4>
- </td>
- <th></th>
- <td align=right>
- <h4>
- Tel: <%tel%>
- <br>Fax: <%fax%>
- </h4>
- </td>
- </tr>
- <tr>
- <th colspan=3><h4>S T A T E M E N T</h4></th>
- </tr>
- <tr>
- <td colspan=3 align=right><%statementdate%></td>
- </tr>
- </table>
- </td>
- </tr>
- <tr>
- <td> </td>
- <td>
- <table width=100%>
- <tr valign=top>
- <td><%name%>
- <br><%street%>
- <br><%zipcode%>
- <br><%city%>
- <br><%country%>
- <br>
-<%if customerphone%>
- <br>Tel: <%customerphone%>
-<%end customerphone%>
-<%if customerfax%>
- <br>Fax: <%customerfax%>
-<%end customerfax%>
-<%if email%>
- <br><%email%>
-<%end email%>
- </td>
- </tr>
- </table>
- </td>
- </tr>
- <tr height=10></tr>
- <tr>
- <td> </td>
- <td>
- <table width=100%>
- <tr>
- <th align=left>Invoice #</th>
- <th width=15%>Date</th>
- <th width=15%>Due</th>
- <th width=10%>Current</th>
- <th width=10%>30</th>
- <th width=10%>60</th>
- <th width=10%>90+</th>
- </tr>
-<%foreach invnumber%>
- <tr>
- <td><%invnumber%></td>
- <td><%invdate%></td>
- <td><%duedate%></td>
- <td align=right><%c0%></td>
- <td align=right><%c30%></td>
- <td align=right><%c60%></td>
- <td align=right><%c90%></td>
- </tr>
-<%end invnumber%>
- <tr>
- <td colspan=7><hr size=1></td>
- </tr>
- <tr>
- <td> </td>
- <td> </td>
- <td> </td>
- <th align=right><%c0total%></td>
- <th align=right><%c30total%></td>
- <th align=right><%c60total%></td>
- <th align=right><%c90total%></td>
- </tr>
- </table>
- </td>
- </tr>
- <tr height=10></tr>
- <tr>
- <td> </td>
- <td align=right>
- <table width=50%>
- <tr>
- <th>Total Outstanding</th>
- <th align=right><%total%></th>
- </tr>
- </table>
- </td>
- </tr>
- <tr>
- <td> </td>
- <td><hr noshade></td>
- </tr>
- <tr>
- <td> </td>
- <td>Please make check payable to <b><%company%></b>.
- </td>
- </tr>
- <tr height=20></tr>
-</table>
-
+++ /dev/null
-% ----------------------------------------------------------
-% translations.tex
-% Zentrale Uebersetzungsdatei f-tex
-%
-% Changelog: see gitlog
- \newcommand{\ftTranslationsVersion}{1.2-u (05.12.2012)}
-%
-% Lizenz
-% http://www.gnu.de/licenses/gpl-3.0.html
-%
-% Siehe ./README
-%
-% Autor: Wulf Coulmann scripts_at_gpl.coulmann.de
-%
-%
-% ----------------------------------------------------------
-
-
-\begingroup
- \makeatletter
- \@latex@warning@no@line{ #### this is translations.tex \ftTranslationsVersion #####}
-\endgroup
-
-
-%%%%% Anleitung zum zufuegen neuer Sprachen %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-% Am Beispiel Franzoesisch (fr) %
-% - Es wird empfohlen die Datei translations.tex im Vorlagenordner %
-% und _nicht_ in [lxo-home]/templates/f-tex zu aendern. %
-% - Kopiere den Block %
-% "\newcommand{\LoadDE}{" %
-% bis zur schliessenden Klammer %
-% "}" %
-% und fuege ihn am Ende der Datei bei %
-% "codeblock mit neuer Sprache hier einfuegen" %
-% an %
-% - uebersetze die deutschen Begriffe im neu eingefuegten Block in %
-% die neue Sprache %
-% - aendere den Kommandonamen entsprechend der Neuen Sprache %
-% "\newcommand{\LoadFR} %
-% - fuege am Ende der Datei eine neue Zeile mit dem neuen Sprachkuerzel %
-% und dem neuen Funktionsnamen an. %
-% "\IfEndWith{\docname}{_fr}{\loadFR}{} %
-% - pruefe, ob lxo bereits ueber eine Konfiguration zu der neuen Sprache %
-% verfuegt. Das Feld Vorlagenkuerzel muss den zur hier zugefuegten Sprache %
-% passenden Wert enthalten (in unserem Beispiel "fr") %
-% - rufe das script [lxo-home]/templates/f-tex/setup.sh erneut auf, um %
-% sicherzustellen, dass die benoetigten Symlinks vorhanden sind. %
-% - schicke die neue Version dieser Datei an %
-% scripts_at_gpl.coulmann.de %
-% damit in Zukunft die neue Sprache auch anderen Nutzern %
-% von lxo zur Verfuegung steht %
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-
-
-
-% ===== de ===========
-\newcommand{\loadDE}{
-
- \renewcommand{\TitleInv}{Rechnung}
- \newcommand{\TitleProforma}{Proformarechnung}
- \newcommand{\TitleCreditNote}{Gutschrift}
- \newcommand{\TitleSalesOrder}{Auftragsbestätigung}
- \newcommand{\TitleSalesQuotation}{Angebot}
- \newcommand{\TitleDelorder}{Lieferschein}
- \newcommand{\TitlePicklist}{Sammelliste}
- \newcommand{\TitlePurchaseOrder}{Bestellung}
- \newcommand{\DelorderNumber}{Lieferscheinnummer}
- \newcommand{\DeliveryAddress}{Lieferadresse}
- \newcommand{\InvNumber}{Rechnungsnummer}
- \newcommand{\CredNumber}{Gutschriftnummer}
- \newcommand{\OrderNumber}{Auftragsnummer}
- \newcommand{\RequestOrderNumber}{Bestellauftragsnummer}
- \newcommand{\QuotationNumber}{Angebotsnummer}
- \newcommand{\CustomerID}{Kundennummer}
- \newcommand{\VendorID}{Lieferantennummer}
- \newcommand{\DelDate}{Lieferdatum}
- \newcommand{\ReqByTitle}{Lieferung bis}
- \newcommand{\ValidUntil}{gültig bis}
- \newcommand{\Date}{Datum}
- \newcommand{\Pos}{Pos}
- \newcommand{\Number}{Best Nr.}
- \newcommand{\ItemNo}{Artikel}
- \newcommand{\Count}{Anz}
- \newcommand{\Unit}{Einh}
- \newcommand{\Storage}{Lagerplatz}
- \newcommand{\Take}{entnommen}
- \newcommand{\Fee}{Einzelp}
- \newcommand{\Total}{Total}
- \newcommand{\Sum}{Gesamtbetrag}
- \newcommand{\EbT}{Summe vor Steuern}
- \newcommand{\Left}{Restbetrag}
- \newcommand{\AlreadyPayed}{bereits gezahlt am}
- \newcommand{\TabSubTotal}{Zwischensumme}
- \newcommand{\TabCarry}{Übertrag}
- \newcommand{\Dis}{Rab}
- \newcommand{\TaxInc}{bereits enthalten: }
- \newcommand{\PriceInclTax}{Alle Preise incl. Mehrwertsteuer}
- \newcommand{\UstidTitle}{Ihre Umsatzsteueridentnummer:}
-
-
- % Zahlungshinweise
- \newcommand{\paymenthints}{
-
- \IfSubStr{\docname}{Angebot}{
- Das Angebot hat 4 Wochen Gültigkeit.\\
- }{}
- }
-
- \newcommand{\YourOrder}{
- \ifthenelse{\equal{\cusordnumber}{\leer}}
- {}
- {Ihre Bestellung {\bf\cusordnumber}}\\[0.5em]
- }
-
-}
-
-% ===== uk oder en ===========
-\newcommand{\loadUK}{
-
- \renewcommand{\TitleInv}{Invoice}
- \newcommand{\TitleProforma}{Pro Forma Invoice}
- \newcommand{\TitleCreditNote}{Credit Note}
- \newcommand{\TitleSalesOrder}{Sales Order}
- \newcommand{\TitleSalesQuotation}{Sales Quotation}
- \newcommand{\DelorderNumber}{delivery note no}
- \newcommand{\DeliveryAddress}{delivery address}
- \newcommand{\TitleDelorder}{Delivery Note}
- \newcommand{\TitlePicklist}{Pick List}
- \newcommand{\TitlePurchaseOrder}{Purchase Order}
- \newcommand{\InvNumber}{invoice number}
- \newcommand{\CredNumber}{credit number}
- \newcommand{\OrderNumber}{order number}
- \newcommand{\RequestOrderNumber}{purchase order no.}
- \newcommand{\QuotationNumber}{quotation no}
- \newcommand{\CustomerID}{customer id}
- \newcommand{\VendorID}{vendor id}
- \newcommand{\DelDate}{date of delivery}
- \newcommand{\ReqByTitle}{required by}
- \newcommand{\ValidUntil}{valid until}
- \newcommand{\Date}{date}
- \newcommand{\Pos}{pos}
- \newcommand{\Number}{item id}
- \newcommand{\ItemNo}{item}
- \newcommand{\Count}{count}
- \newcommand{\Unit}{unit}
- \newcommand{\Storage}{location}
- \newcommand{\Take}{taken}
- \newcommand{\Fee}{fee}
- \newcommand{\Total}{total}
- \newcommand{\Sum}{total amount}
- \newcommand{\EbT}{total without taxes}
- \newcommand{\Left}{residue}
- \newcommand{\AlreadyPayed}{already payed at}
- \newcommand{\TabSubTotal}{subtotal}
- \newcommand{\TabCarry}{carry}
- \newcommand{\Dis}{dis}
- \newcommand{\TaxInc}{already included: }
- \newcommand{\PriceInclTax}{Prices incl. tax}
- \newcommand{\UstidTitle}{Your VAT number:}
-
- % Zahlungshinweise Rechnung
- \newcommand{\paymenthints}{
- \IfSubStr{\docname}{Angebot}{
- The offer is valid for 4 weeks.\\
- }{}
- }
-
- \newcommand{\YourOrder}{
- \ifthenelse{\equal{\cusordnumber}{\leer}}
- {}
- {Your Order Number {\bf\cusordnumber}}\\[0.5em]
- }
-
-}
-
-% ====== neuen Sprache ================================
-
- % codeblock mit neuer Sprache hier einfuegen
-
-
-% ====== Ende Sprachblock =========
-\newcommand{\checkVal}{unknowen}
-\newcommand{\TitleInv}{\checkVal}
-
-
-\IfStrEq{\LangCode}{de}{\loadDE}{}
-\IfStrEq{\LangCode}{uk}{\loadUK}{}
-\IfStrEq{\LangCode}{en}{\loadUK}{}
-% neue Zeile mit dem neuen Sprachkuerzel und dem neuen Funktionsnamen hier anfuegen
-
-
-
-% ====== unterhalb dieser Zeile nichts aendern ==========================
-
-% defaultsprache
- \ifthenelse{\equal{\TitleInv}{\checkVal}}{\loadDE}{}
-
+++ /dev/null
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-% Makros zur Berechnung und Ausgabe einer Zwischensumme bei langen Tabellen
-% Der Hack der longtable Ausgabe ist von Heiko Oberdiek, das Paket zref auch.
-% ---<(kaimartin)>---(August, 2007)
-%
-% - Dezimaltrennzeichenn nur noch "." by scripts_at_gpl.coulmann.de 2010-12
-% (raw_numbers patch)
-% - \Wert -> default Wert 0, by scripts_at_gpl.coulmann.de 2009-08
-% wenn kein Wert uebergebenn wird, dies
-% ermoeglicht das Ausgeben von Tabellen ohne
-% Preise (z.b. Lieferscheine)
-% - keine Ausgabe der Zwischensumme, wenn 0
-% - neu: \brutto zur Ausgabe von Bruttopreisen by scripts_at_gpl.coulmann.de 2009-07
-% - Anpassungen fuer fancy LaTeX by scripts_at_gpl.coulmann.de 2009-03
-%
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-% Diese Datei steht unter der GPL-Lizenz, Version 3
-% siehe http://www.gnu.de/licenses/gpl-3.0.html
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-\usepackage{etex} % Damit Marken verwendet werden koennen
-\usepackage[savepos,user]{zref} % Um die jeweils aktuelle Position zu merken
-\usepackage{fltpoint} % Rechnen mit Komma-Zahlen
-\usepackage{numprint} % Zahlen formatiert ausgeben
-\usepackage{eurosym} % Das Euro-Zeichen
-\usepackage{calc} % Fuer das Makro \widthof{}
-
-% Vorlagen sind auf raw_num Patch ausgelegt daher nur noch . als Trennzeichen
-\newcommand{\DecimalSign}{.}
-\fpDecimalSign{\DecimalSign}
-
-% Globale Einstellungen fuer numprint
-\npstylegerman % Deutsche Zahlenformatierung, in der Ausgabe
-\nprounddigits{2} % Zwei Nachkommasstellen
-
-% \leer ist bereits in letter.tex definiert, wenn nicht muss es hier passieren
-% \newcommand{\leer}{}
-
-%%%%%%%%%%%%%%Befehle zur Berechnung der Zwischensumme%%%%%%%%%%%%%%%%%%%%%%%
-\newcommand*\laufsumme{0}
-\newcommand*\resetlaufsumme{\global\def\laufsumme{0}}
-\newcommand*\addlaufsumme[1]{\fpAdd{\laufsumme}{\laufsumme}{#1}%
- \global\let\laufsumme\laufsumme}
-\newcommand*\printwert[1]{
- \ifthenelse{\NoValue > 0}{
- }{
- \numprint{#1}
- }
-}
-
-
-%%%%%%%%Plaintex-Hack fuer Positionierung der Zwischensummen%%%%%%%%%%%%%%%%%%
-
-
-\makeatletter % Das at-Zeichen in Variablen zulassen
-
-% Variablen bereit stellen
- \newdimen\drx
- \newdimen\dry
-
- \newmarks\ltm@marks
- \def\ltm@setmarks#1{%
- \marks\ltm@marks{#1}%
- }
- \def\ltm@getmarks{%
- \botmarks\ltm@marks
- }
-
-
-% Den aktuellen Wert der Laufsumme berechnen und merken
-\newcommand*{\Wert}[1]{%
- \ifthenelse{\equal{#1}{\leer}}{
- \addlaufsumme{0}% Den uebergebenen Wert zur Laufsumme addieren
- \expandafter\ltm@setmarks\expandafter{\laufsumme}% Die Laufsumme merken
- }{
- \printwert{#1}% Ausgabe des Werts vor Ort
- \addlaufsumme{#1}% Den uebergebenen Wert zur Laufsumme addieren
- \expandafter\ltm@setmarks\expandafter{\laufsumme}% Die Laufsumme merken
- }
-}
-
-% Merken der aktuellen Position
-\newcommand*{\MarkZwsumPos}{%
- \leavevmode
- \zsavepos{zwsumpos\thepage}%
- \zrefused{zwsumpos\thepage}%
-}
-
-
-% Ausgabe der Zwischensumme
-\def\ltm@insertfoot#1{%
- \vbox to\z@{%
- \vss
- \hb@xt@\z@{%
- \color@begingroup
- \zsavepos{tabende\thepage}% % Die aktuelle Position merken
- \drx=0sp
- \dry=0sp
- % Die aktuelle Position abziehen und die gemerkte addieren
- \advance \drx by -\zposx{tabende\thepage}sp
- \advance \drx by \zposx{zwsumpos\thepage}sp
- \advance \dry by -\zposy{tabende\thepage}sp
- \advance \dry by \zposy{zwsumpos\thepage}sp
- \smash{\kern\drx\raise\dry%
- %\hbox{\makebox[\widthof{ \currency}][r]{\printwert{#1} \currency}}% % mit Waehrungszeichen
- \hbox{\printwert{#1} }% % ohne Waehrungszeichen
- }% end smash
- \color@endgroup
- }%
- }%
- \vspace{4mm}
-}
-
-% Ausgabe des Uebertrags
-% Wie die Ausgabe der Zwischensumme, nur ohne neu gemerkte Position
-\def\ltm@inserthead#1{%
- \vbox to\z@{%
- \vss
- \hb@xt@\z@{%
- \color@begingroup
- \drx=0sp
- \dry=0sp
- % Die Position des Tabellenendes abziehen und zur gemerkten gehen
- \advance \drx by -\zposx{tabende\thepage}sp
- \advance \drx by \zposx{zwsumpos\thepage}sp
- \advance \dry by -\zposy{tabende\thepage}sp
- \advance \dry by \zposy{zwsumpos\thepage}sp
- \smash{\kern\drx\raise\dry%
- % Die eigentliche Ausgabe.
- % Rechtsbuendig und um die Breite der Währung verschoben.
- %\hbox{\makebox[\widthof{ \currency}][r]{\printwert{#1} \currency}}%
- \hbox{\printwert{#1}}% % ohne Waehrungszeichen
- %\hbox{\makebox[\widthof{ \printwert{#1}}][r]{\printwert{#1}\rule{0mm}{10mm} }}% % ohne Waehrungszeichen
- }% end smash
- \color@endgroup
- }%
- }%
- \vspace{1mm}
-}
-
-
-\def\ltm@lastfoot{\ltm@insertfoot\ltm@getmarks}
-\def\ltm@foot{\ltm@insertfoot{\ltm@getmarks}}
-\def\ltm@head{\ltm@inserthead{\ltm@getmarks}}
-
-
-% Ueberschreiben der Output-Routine von longtable
-\def\LT@output{%
- \ifnum\outputpenalty <-\@Mi
- \ifnum\outputpenalty > -\LT@end@pen
- \LT@err{floats and marginpars %
- not allowed in a longtable}\@ehc
- \else
- \setbox\z@\vbox{\unvbox\@cclv}%
- \ifdim \ht\LT@lastfoot>\ht\LT@foot
- \dimen@\pagegoal
- \advance\dimen@-\ht\LT@lastfoot
- \ifdim\dimen@<\ht\z@
- \setbox\@cclv\vbox{%
- \unvbox\z@\copy\LT@foot\ltm@foot\vss
- }%
- \@makecol
- \@outputpage
- \setbox\z@\vbox{\box\LT@head}%
- \fi
- \fi
- \global\@colroom\@colht
- \global\vsize\@colht
- \vbox{%
- \unvbox\z@
- \box\ifvoid\LT@lastfoot
- \LT@foot\ltm@foot
- \else
- \LT@lastfoot\ltm@lastfoot
- \fi
- }%
- \fi
- \else
- \setbox\@cclv\vbox{%
- \unvbox\@cclv\copy\LT@foot\ltm@foot\vss
- }%
- \@makecol
- \@outputpage
- \global\vsize\@colroom
- \copy\LT@head\ltm@head
- \fi
-}
-
-\newcommand\BruttoSellPrice[2]{
- \fpAdd{\tax}{#2}{100}
- \fpDiv{\taxF}{\tax}{100}
- \fpMul{\result}{#1}{\taxF}
- \numprint{\result}
-}
-\newcommand\BruttoWert[2]{
- \fpAdd{\tax}{#2}{100}
- \fpDiv{\taxF}{\tax}{100}
- \fpMul{\rawresult}{#1}{\taxF}
- \Wert{\rawresult}
-}
-
-
-\newcommand\BruttoLineSum[4]{
- \fpAdd{\tax}{#4}{100}
- \fpDiv{\taxF}{\tax}{100}
- \fpMul{\result}{#1}{\taxF}
- \fpMul{\result}{#2}{\result}
- \fpSub{\rabatt}{100}{#3}
- \fpDiv{\rabatt}{\rabatt}{100}
- \fpMul{\result}{\result}{\rabatt}
- \Wert{\result}
-}
-
-% \ifthenelse{\equal{<%p_discount%>}{0}}{}{ -<%p_discount%>\%} &
-% %<%sellprice%>
-% \Wert{<%linetotal%>} % Zeilensumme
-
-% \fpMul{\result}{#1}{1.19}
-% \fpMul{\resultt}{#2}{\result}
-% \fpSub{\rabatt}{100}{#3}
-% \fpDiv{\rabattt}{\rabatt}{100}
-% \fpMul{\resulttt}{\resultt}{\rabattt}
-% %\fpRound{\roundresult}{\result}{3}
-% %\roundresult
-% \resulttt
-
-\makeatother % Das at-Zeichen in Variablen wieder verbieten
-%%%%%%%%%%%%%%%%%%%%Ende plaintex-Hack%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
--- /dev/null
+
+# Bemerkungen zum Vorlagensatz von
+### © 2020 by Marei Peischl (peiTeX TeXnical Solutions)
+
+## Aufbau:
+Die Grundstruktur besteht je Dokumententyp aus einer Basisdatei und verschiedenen Setup-Dateien.
+
+Die Basis wurde so überarbeitet, dass Dokumente nun generell auf der Dokumentenklasse *scrartcl.cls* basieren und das Paket *kiviletter.sty* benutzen.
+
+Mandantenspezifische Konfiguration findet sich in der Datei *insettings.tex* und dem Ordner eines spezifischen Mandanten (default=*firma/*).
+
+
+### Struktur der Basisdatei (je Dokumententyp eine)
+ 1. Dokumentenklasse
+ 2. *kiviletter.sty*
+ 3. Einstellungen, die über Variablen gesetzt werden: Mandant, Währung, Sprache
+ 4. `\input{insettings.tex}` Anteil der spezifischen Anpassungen, die von den Variablen unter 2. abhängig sind. Geladen werden darin die Dateien:
+ - Sprache: lädt die entsprechende Sprachdatei, falls DE -> *deutsch.tex*, falls EN *englisch.tex* und setzt die babel Optionen. Die Datei enthält Übersetzungen von Einzelbegriffen und Textbausteinen.
+ - Lädt die Konfigurationsdatei, ohne spezielle Mandanten ist der Suchpfad zur Konfiguration der Unterordner *firma/*
+ * Lädt die Datei *ident.tex*, sowie die Abbildung Briefkopf.
+
+Mandanten / Firma:
+ Um gleiche Vorlagen für verschiedene Firmen verwenden zu können, wird je
+ nach dem Wert der Kivitendo-Variablen <%kivicompany%> ein
+ Firmenverzeichnis ausgewählt (siehe 'insettings.tex'), in dem Briefkopf,
+ Identitäten und Währungs-/Kontoeinstellungen hinterlegt sind.
+ <%kivicompany%> enthält den Namen des verwendeten Mandantendaten.
+ Ist kein Firmenname eingetragen, so wird das
+ generische Unterverzeichnis 'firma' verwendet.
+
+Identitäten:
+ In jedem Firmen-Unterverzeichnis soll eine Datei 'ident.tex'
+ vorhanden sein, die mit \newcommand Werte für \telefon, \fax,
+ \firma, \strasse, \ort, \ustid, \email und \homepage definiert.
+
+Währungen / Konten:
+ Für jede Währung (siehe 'insettings.tex') soll eine Datei vorhanden
+ sein, die das Währungssymbol (\currency) und folgende Angaben für
+ ein Konto in dieser Währung enthält \kontonummer, \bank,
+ \bankleitzahl, \bic und \iban.
+ So kann in den Dokumenten je nach Währung ein anderes Konto
+ angegeben werden.
+ Nach demselben Schema können auch weitere, alternative Bankverbindungen
+ angelegt werden, die dann in insettings.tex als Variable im
+ unteren Abschnitt der Datei 'insettings.tex', Kommentar Fußzeile
+ (cfoot) eingefügt werden.
+ Briefbogen/Logos:
+ Eine Hintergrundgrafik oder ein Logo kann in Abhängigkeit vom
+ Medium (z.B. nur beim Verschicken mit E-Mail) eingebunden
+ werden. Dies ist im Moment auskommentiert.
+
+ Desweiteren sind (auskommentierte) Beispiele enthalten für eine
+ Grafik als Briefkopf, nur ein Logo, oder ein komplettes DinA4-PDF
+ als Briefpapier.
+
+ Fusszeile:
+ Die Tabelle im Fuß verwendet die Angaben aus firma/ident.tex und
+ firma/*_account.tex.
+
+## Tabellen:
+
+
+ Quickstart (wo kann was angepasst werden?):
+ insettings.tex : Pfad zu Angaben über Mandanten (default: firma)
+ Logo/Briefpapier
+ Layout der Kopf/Fußzeile
+ firma/* : Angaben über Mandanten
+ Es muß mindestens eine Sprache angelegt werden!
+ deutsch.tex : Textschnipsel für Deutsch
+ Dafür eine Sprache mit Vorlagenkürzel DE anlegen
+ english.tex : Textschnipsel für Englisch
+ Dafür eine Sprache mit Vorlagenkürzel EN anlegen
+
--- /dev/null
+<body bgcolor=ffffff>
+
+<table width=100%>
+ <tr>
+ <td width=10> </td>
+
+ <td>
+ <table width=100%>
+ <tr>
+ <td>
+ <h4>
+ <%company%>
+ <br><%address%>
+ </h4>
+ </td>
+
+ <th><img src=http://localhost/lx-erp/lx-office-erp.png border=0 width=64 height=58></th>
+
+ <th align=right>
+ <h4>
+ Tel: <%tel%>
+ <br>Fax: <%fax%>
+ </h4>
+ </td>
+ </tr>
+
+ <tr>
+ <th colspan=3>
+ <h4>L A G E R L I S T E</h4>
+ </th>
+ </tr>
+ </table>
+ </td>
+ </tr>
+
+ <tr>
+ <td> </td>
+
+ <td>
+ <table width=100% cellspacing=0 cellpadding=0>
+ <tr bgcolor=000000>
+ <th align=left width=50%><font color=ffffff>Absender</th>
+ <th align=left width=50%><font color=ffffff>Lieferanschrift</th>
+ </tr>
+
+ <tr valign=top>
+ <td><%name%>
+ <br><%street%>
+ <br><%zipcode%>
+ <br><%city%>
+ <br><%country%>
+ <br>
+
+ <%if contact%>
+ <br>Kontakt: <%contact%>
+ <%end contact%>
+
+ <%if vendorphone%>
+ <br>Tel: <%vendorphone%>
+ <%end vendorphone%>
+
+ <%if vendorfax%>
+ <br>Fax: <%vendorfax%>
+ <%end vendorfax%>
+
+ <%if email%>
+ <br><%email%>
+ <%end email%>
+
+ </td>
+
+ <td><%shiptoname%>
+ <br><%shiptostreet%>
+ <br><%shiptozipcode%>
+ <br><%shiptocity%>
+ <br><%shiptocountry%>
+
+ <br>
+ <%if shiptocontact%>
+ <br>Kontakt: <%shiptocontact%>
+ <%end shiptocontact%>
+
+ <%if shiptophone%>
+ <br>Tel: <%shiptophone%>
+ <%end shiptophone%>
+
+ <%if shiptofax%>
+ <br>Fax: <%shiptofax%>
+ <%end shiptofax%>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+
+ <tr height=5></tr>
+
+ <tr>
+ <td> </td>
+
+ <td>
+ <table width=100% border=1>
+ <tr>
+ <th width=17% align=left nowrap>BestellNr. #</th>
+ <th width=17% align=left nowrap>Datum</th>
+ <th width=17% align=left nowrap>Kontakt</th>
+ <%if warehouse%>
+ <th width=17% align=left nowrap>Lager</th>
+ <%end warehouse%>
+ <th width=17% align=left>Versandort</th>
+ <th width=15% align=left>Lieferung durch</th>
+ </tr>
+
+ <tr>
+ <td><%ordnumber%> </td>
+
+ <%if shippingdate%>
+ <td><%shippingdate%></td>
+ <%end shippingdate%>
+
+ <%if not shippingdate%>
+ <td><%orddate%></td>
+ <%end shippingdate%>
+
+ <td><%employee%> </td>
+
+ <%if warehouse%>
+ <td><%warehouse%></td>
+ <%end warehouse%>
+
+ <td><%shippingpoint%> </td>
+ <td><%shipvia%> </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+
+ <tr>
+ <td> </td>
+
+ <td>
+ <table width=100%>
+ <tr bgcolor=000000>
+ <th align=left><font color=ffffff>Pos</th>
+ <th align=left><font color=ffffff>ArtNr.</th>
+ <th align=left><font color=ffffff>Beschreibung</th>
+ <th><font color=ffffff>Seriennummer</th>
+ <th> </th>
+ <th><font color=ffffff>Menge</th>
+ <th><font color=ffffff>Erh</th>
+ <th> </th>
+ <th><font color=ffffff>Lagerplatz</th>
+ </tr>
+
+ <%foreach number%>
+ <tr valign=top>
+ <td><%runningnumber%></td>
+ <td><%number%></td>
+ <td><%description%></td>
+ <td><%serialnumber%></td>
+ <td><%deliverydate%></td>
+ <td align=right><%qty%></td>
+ <td align=right><%ship%></td>
+ <td><%unit%></td>
+ <td><%bin%></td>
+ </tr>
+ <%end number%>
+
+ </table>
+ </td>
+ </tr>
+
+ <tr>
+ <td> </td>
+
+ <td><hr noshade></td>
+ </tr>
+
+</table>
+
--- /dev/null
+\documentclass[twoside]{scrartcl}
+\usepackage[frame]{xy}
+\usepackage{tabularx}
+\usepackage[utf8]{inputenc}
+\usepackage{graphicx}
+\setlength{\voffset}{0.5cm}
+\setlength{\hoffset}{-2.0cm}
+\setlength{\topmargin}{0cm}
+\setlength{\headheight}{0.5cm}
+\setlength{\headsep}{1cm}
+\setlength{\topskip}{0pt}
+\setlength{\oddsidemargin}{1.0cm}
+\setlength{\evensidemargin}{1.0cm}
+\setlength{\textwidth}{17cm}
+\setlength{\textheight}{24.7cm}
+\setlength{\footskip}{1cm}
+\setlength{\parindent}{0pt}
+\renewcommand{\baselinestretch}{1}
+
+\begin{document}
+
+\pagestyle{myheadings}
+\thispagestyle{empty}
+
+\fontfamily{cmss}\fontsize{10pt}{12pt}\selectfont
+
+\vspace*{-1.3cm}
+
+\parbox{\textwidth}{
+ \parbox[b]{.42\textwidth}{%
+ <%company%>
+
+ <%address%>
+ }\hfill
+ \begin{tabular}[b]{rr@{}}
+ Tel & <%tel%>\\
+ Fax & <%fax%>
+ \end{tabular}
+
+ \rule[1.5ex]{\textwidth}{0.5pt}
+}
+
+\vspace*{0.5cm}
+
+\parbox[t]{1cm}{\hfill}
+\parbox[t]{.5\textwidth}{
+\textbf{Von}
+\vspace{0.7cm}
+
+<%name%> \\
+<%street%> \\
+<%zipcode%> \\
+<%city%> \\
+<%country%>
+}
+\parbox[t]{.4\textwidth}{
+\textbf{Lieferanschrift}
+\vspace{0.7cm}
+
+<%shiptoname%> \\
+<%shiptostreet%> \\
+<%shiptozipcode%> \\
+<%shiptocity%> \\
+<%shiptocountry%>
+}
+\hfill
+
+\vspace{1cm}
+
+\textbf{L A G E R L I S T E}
+\hfill
+
+\vspace{1cm}
+
+\begin{tabularx}{\textwidth}{*{6}{|X}|} \hline
+ \textbf{BestellNr. \#} & \textbf{Datum} & \textbf{Kontakt}
+ <%if warehouse%>
+ & \textbf{Lager}
+ <%end warehouse%>
+ & \textbf{Lagerplatz} & \textbf{Lieferung mit} \\ [0.5em]
+ \hline
+
+ <%ordnumber%>
+ <%if shippingdate%>
+ & <%shippingdate%>
+ <%end shippingdate%>
+ <%if not shippingdate%>
+ & <%orddate%>
+ <%end shippingdate%>
+ & <%employee%>
+ <%if warehouse%>
+ & <%warehouse%>
+ <%end warehouse%>
+ & <%shippingpoint%> & <%shipvia%> \\
+ \hline
+\end{tabularx}
+
+\vspace{1cm}
+
+\begin{tabularx}{\textwidth}{@{}rlXllrrll@{}}
+ \textbf{Pos} & \textbf{Nummer} & \textbf{Beschreibung} & \textbf{Seriennumner} & & \textbf{Menge} & \textbf{Erh} & & \textbf{Lagerplatz} \\
+
+<%foreach number%>
+ <%runningnumber%> & <%number%> & <%description%> & <%serialnumber%> &
+ <%deliverydate%> & <%qty%> & <%ship%> & <%unit%> & <%bin%> \\
+<%end number%>
+\end{tabularx}
+
+
+\rule{\textwidth}{2pt}
+
+\end{document}
+
--- /dev/null
+\documentclass[twoside]{scrartcl}
+\usepackage[frame]{xy}
+\usepackage{tabularx}
+\usepackage[utf8]{inputenc}
+\setlength{\voffset}{0.4cm}
+\setlength{\hoffset}{-2.0cm}
+\setlength{\topmargin}{0cm}
+\setlength{\headheight}{0.0cm}
+\setlength{\headsep}{1cm}
+\setlength{\topskip}{0pt}
+\setlength{\oddsidemargin}{1.0cm}
+\setlength{\evensidemargin}{1.0cm}
+\setlength{\textwidth}{17cm}
+\setlength{\textheight}{24.5cm}
+\setlength{\footskip}{1cm}
+\setlength{\parindent}{0pt}
+\renewcommand{\baselinestretch}{1}
+\begin{document}
+
+
+\fontfamily{cmss}\fontsize{9pt}{9pt}\selectfont
+
+\parbox[t]{12cm}{
+ <%company%>
+
+ <%address%>}
+\hfill
+\parbox[t]{6cm}{\hfill <%source%>}
+
+\vspace*{0.6cm}
+
+<%text_amount%> \dotfill <%decimal%>/100 \makebox[0.5cm]{\hfill}
+
+\vspace{0.5cm}
+
+\hfill <%datepaid%> \makebox[2cm]{\hfill} <%amount%>
+
+\vspace{0.5cm}
+
+<%name%>
+
+<%street%>
+
+<%zipcode%>
+
+<%city%>
+
+<%country%>
+
+\vspace{2.8cm}
+
+<%company%>
+
+\vspace{0.5cm}
+
+<%name%> \hfill <%datepaid%> \hfill <%source%>
+
+\vspace{0.5cm}
+\begin{tabularx}{\textwidth}{lXrr@{}}
+\textbf{Rechnung} & \textbf{Ausgestellt}
+ & \textbf{Fällig} & \textbf{Verrechnet} \\
+<%foreach invnumber%>
+<%invnumber%> & <%invdate%> \dotfill
+ & <%due%> & <%paid%> \\
+<%end invnumber%>
+\end{tabularx}
+
+\vfill
+
+\end{document}
+
--- /dev/null
+\documentclass[paper=a4,fontsize=10pt]{scrartcl}
+\usepackage{kiviletter}
+
+
+% Variablen, die in settings verwendet werden
+\newcommand{\lxlangcode} {<%template_meta.language.template_code%>}
+\newcommand{\lxmedia} {<%media%>}
+\newcommand{\lxcurrency} {<%currency%>}
+\newcommand{\kivicompany} {<%employee_company%>}
+
+% settings: Einstellungen, Logo, Briefpapier, Kopfzeile, Fusszeile
+\input{insettings.tex}
+
+
+% laufende Kopfzeile:
+\ourhead{\kundennummer}{<%customernumber%>}{\gutschrift}{<%invnumber%>}{<%invdate%>}
+
+
+\begin{document}
+
+
+<%if shiptoname%>
+\makeatletter
+\begin{lrbox}\shippingAddressBox
+ \parbox{\useplength{toaddrwidth}}{
+ \backaddr@format{\scriptsize\usekomafont{backaddress}%
+ \strut abweichende Lieferadresse
+ }
+ \par\smallskip
+ \setlength{\parskip}{\z@}
+ \par
+ \normalsize
+ <%shiptoname%>\par
+ <%if shiptocontact%> <%shiptocontact%><%end if%>\par
+ <%shiptodepartment_1%>\par
+ <%shiptodepartment_2%>\par
+ <%shiptostreet%>\par
+ <%shiptozipcode%> <%shiptocity%>
+ }
+\end{lrbox}
+\makeatother
+<%end if%>
+
+
+\setkomavar{title}{
+ \gutschrift~
+ \nr ~<%invnumber%>
+}
+
+<%if invnumber_for_credit_note%>
+\setkomavar*{myref}{\fuerRechnung}
+\setkomavar{myref}{<%invnumber_for_credit_note%>}
+<%end if%>
+
+\setkomavar*{date}{\datum}
+
+\setkomavar{date}{<%transdate%>}
+\setkomavar{customer}{<%customernumber%>}
+\setkomavar{fromname}{<%employee_name%>}
+\setkomavar{fromphone}{<%employee_tel%>}
+\setkomavar{fromemail}{<%employee_email%>}
+
+\begin{letter}{
+ <%name%>\strut\\
+ <%if department_1%><%department_1%>\\<%end if%>
+ <%if department_2%><%department_2%>\\<%end if%>
+ <%cp_givenname%> <%cp_name%>\strut\\
+ <%street%>\strut\\
+ <%zipcode%> <%city%>\strut\\
+ <%country%> \strut
+ }
+
+% Bei Kontaktperson Anrede nach Geschlecht unterscheiden.
+% Bei natürlichen Personen persönliche Anrede, sonst allgemeine Anrede.
+\opening{
+ \ifstr{<%cp_name%>}{}
+ {<%if natural_person%><%greeting%> <%name%>,<%else%>\anrede<%end if%>}
+ {
+ \ifstr{<%cp_gender%>}{f}
+ {\anredefrau}
+ {\anredeherr}
+ <%cp_title%> <%cp_name%>,
+ }
+ }
+\thispagestyle{kivitendo.letter.first}
+
+
+\gutschriftformel
+
+\begin{PricingTabular*}
+% eigentliche Tabelle
+\FakeTable{
+ <%foreach number%>%
+ <%runningnumber%> &%
+ <%number%> &%
+ \textbf{<%description%>}%
+ <%if longdescription%>\ExtraDescription{<%longdescription%>}<%end longdescription%>%
+ <%if serialnumber%>\ExtraDescription{\seriennummer: <%serialnumber%>}<%end serialnumber%>%
+ <%if ean%>\ExtraDescription{\ean: <%ean%>}<%end ean%>%
+ <%if projectnumber%>\ExtraDescription{\projektnummer: <%projectnumber%>}<%end projectnumber%>%
+ &%
+ <%qty%> <%unit%> &%
+ <%sellprice%>&%
+ \ifstr{<%p_discount%>}{0}{}{\sffamily\scriptsize{(-<%p_discount%>\,\%)}}%
+ <%linetotal%>\tabularnewline
+ <%end number%>
+ }
+ \begin{PricingTotal}
+ % Tabellenende letzte Seite
+ \nettobetrag & <%subtotal%>\\
+ <%foreach tax%>
+ <%taxdescription%> & <%tax%>\\
+ <%end tax%>
+ \bfseries\schlussbetrag & \bfseries <%ordtotal%>\\
+ \end{PricingTotal}
+\end{PricingTabular*}
+
+<%if notes%>
+<%notes%>
+\medskip
+<%end if%>
+
+\closing{\gruesse}
+
+\end{letter}
+
+\end{document}
--- /dev/null
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%standardphrasen und schnipsel in deutsch %
+%dient als vorlage für alle anderen sprachen %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+\newcommand{\anrede} {Sehr geehrte Damen und Herren,}
+\newcommand{\anredefrau} {Sehr geehrte Frau}
+\newcommand{\anredeherr} {Sehr geehrter Herr}
+
+
+\newcommand{\nr} {Nr.}
+\newcommand{\datum} {Datum}
+\newcommand{\kundennummer} {Kunden-Nr.}
+\newcommand{\ansprechpartner} {Ansprechpartner}
+\newcommand{\bearbeiter} {Bearbeiter}
+\newcommand{\gruesse} {Mit freundlichen Grüßen}
+\newcommand{\vom} {vom}
+\newcommand{\von} {von}
+\newcommand{\seite} {Seite}
+\newcommand{\uebertrag} {Übertrag}
+
+
+\newcommand{\position} {Pos.}
+\newcommand{\artikelnummer} {Art.-Nr.}
+\newcommand{\bild} {Bild}
+\newcommand{\keinbild} {kein Bild}
+\newcommand{\menge} {Menge}
+\newcommand{\bezeichnung} {Bezeichnung}
+\newcommand{\seriennummer}{Seriennummer}
+\newcommand{\ean}{EAN}
+\newcommand{\projektnummer}{Projektnummer}
+\newcommand{\charge}{Charge}
+\newcommand{\mhd}{MHD}
+\newcommand{\einzelpreis} {E-Preis}
+\newcommand{\gesamtpreis} {G-Preis}
+\newcommand{\nettobetrag} {Nettobetrag}
+\newcommand{\schlussbetrag} {Gesamtbetrag}
+
+\newcommand{\weiteraufnaechsterseite} {weiter auf der nächsten Seite \ldots}
+
+\newcommand{\zahlung} {Zahlungsbedingungen}
+\newcommand{\lieferung} {Lieferbedingungen}
+\newcommand{\textTelefon} {Tel.}
+\newcommand{\textEmail} {E-Mail}
+\newcommand{\textFax} {Fax}
+
+% angebot (sales_quotion)
+\newcommand{\angebot} {Angebot}
+\newcommand{\angebotsformel} {gerne unterbreiten wir Ihnen folgendes Angebot:}
+\newcommand{\angebotdanke} {Wir danken für Ihre Anfrage und hoffen, Ihnen hiermit ein interessantes Angebot gemacht zu haben.}
+\newcommand{\angebotgueltig} {Das Angebot ist freibleibend gültig bis zum} %Danach wird das Datum eingefügt, falls das grammatisch nicht funktionieren sollte müssen wir eine ausnahme für die sprache definieren
+\newcommand{\angebotfragen} {Sollten Sie noch Fragen oder Änderungswünsche haben, können Sie uns gerne jederzeit unter den unten genannten Telefonnummern oder E-Mail-Adressen kontaktieren.}
+\newcommand{\angebotagb} {Bei der Durchführung des Auftrags gelten unsere AGB, die wir Ihnen gerne zuschicken.}
+\newcommand{\auftragerteilt}{Auftrag erteilt:}
+\newcommand{\angebotortdatum}{Wir nehmen das vorstehende Angebot an.}
+\newcommand{\abweichendeLieferadresse}{abweichende Lieferadresse}
+
+% auftragbestätigung (sales_order)
+\newcommand{\auftragsbestaetigung} {Auftragsbestätigung}
+\newcommand{\auftragsnummer} {Auftrags-Nr.}
+\newcommand{\ihreBestellnummer} {Ihre Bestellnummer}
+\newcommand{\auftragsformel} {hiermit bestätigen wir Ihnen folgende Bestellpositionen:}
+\newcommand{\lieferungErfolgtAm} {Die Lieferung erfolgt am} %Danach wird das Datum eingefügt, falls das grammatisch nicht funktionieren sollte müssen wir eine ausnahme für die sprache definieren
+\newcommand{\auftragpruefen} {Bitte kontrollieren Sie alle Positionen auf Übereinstimmung mit Ihrer Bestellung! Teilen Sie Abweichungen innerhalb von 3 Tagen mit!}
+\newcommand{\proformarechnung} {Proforma Rechnung}
+\newcommand{\nurort} {Ort}
+\newcommand{\den} {den}
+\newcommand{\unterschrift} {Unterschrift}
+\newcommand{\stempel} {ggf. Stempel}
+
+% lieferschein (sales_delivery_order)
+\newcommand{\lieferschein} {Lieferschein}
+
+% rechnung (invoice)
+\newcommand{\rechnung} {Rechnung}
+\newcommand{\rechnungsdatum} {Rechnungsdatum}
+\newcommand{\ihrebestellung} {Ihre Bestellung}
+\newcommand{\lieferdatum} {Lieferdatum}
+\newcommand{\rechnungsformel} {für unsere Leistungen erlauben wir uns, folgende Positionen in Rechnung zu stellen:}
+\newcommand{\zwischensumme} {Zwischensumme}
+\newcommand{\leistungsdatumGleichRechnungsdatum} {Das Leistungsdatum entspricht, soweit nicht anders angegeben, dem Rechnungsdatum.}
+\newcommand{\unserebankverbindung} {Unsere Bankverbindung}
+\newcommand{\textKontonummer} {Kontonummer:}
+\newcommand{\textBank} {bei der}
+\newcommand{\textBankleitzahl} {BLZ:}
+\newcommand{\textBic} {BIC:}
+\newcommand{\textIban} {IBAN:}
+\newcommand{\unsereustid} {Unsere USt-Identifikationsnummer lautet}
+\newcommand{\ihreustid} {Ihre USt-Identifikationsnummer:}
+\newcommand{\steuerfreiEU} {Sonstige Leistungen Steuerschuldnerschaft des Leistungsempfängers. Reverse Charge}
+\newcommand{\steuerfreiAUS} {Steuerfreie Lieferung ins außereuropäische Ausland.}
+
+\newcommand{\textUstid} {UStId:}
+
+% gutschrift (credit_note)
+\newcommand{\gutschrift} {Gutschrift}
+\newcommand{\fuerRechnung} {für Rechnung}
+\newcommand{\gutschriftformel} {wir erlauben uns, Ihnen folgende Positionen gutzuschreiben:}
+
+% sammelrechnung (statement)
+\newcommand{\sammelrechnung} {Sammelrechnung}
+\newcommand{\sammelrechnungsformel} {bitte nehmen Sie zur Kenntnis, dass folgende Rechnungen unbeglichen sind:}
+\newcommand{\faellig} {Fälligkeit}
+\newcommand{\aktuell} {aktuell}
+\newcommand{\asDreissig} {30}
+\newcommand{\asSechzig} {60}
+\newcommand{\asNeunzig} {90+}
+
+% zahlungserinnerung (Mahnung)
+\newcommand{\mahnung} {Zahlungserinnerung}
+\newcommand{\mahnungsformel} {man kann seine Augen nicht überall haben - offensichtlich haben Sie übersehen, die folgenden Rechnungen zu begleichen:}
+\newcommand{\beruecksichtigtBis} {Zahlungseingänge sind nur berücksichtigt bis zum}
+\newcommand{\schonGezahlt} {Sollten Sie zwischenzeitlich bezahlt haben, betrachten Sie diese Zahlungserinnerung bitte als gegenstandslos.}
+
+% zahlungserinnerung_invoice (Rechnung zur Mahnung)
+\newcommand{\mahnungsrechnungsformel} {hiermit stellen wir Ihnen zu o.g. \mahnung \ folgende Posten in Rechnung:}
+\newcommand{\posten} {Posten}
+\newcommand{\betrag} {Betrag}
+\newcommand{\bitteZahlenBis} {Bitte begleichen Sie diese Forderung bis zum}
+
+% anfrage (request_quotion)
+\newcommand{\anfrage} {Anfrage}
+\newcommand{\anfrageformel} {bitte nennen Sie uns für folgende Artikel Preis und Liefertermin:}
+\newcommand{\anfrageBenoetigtBis} {Wir benötigen die Lieferung bis zum} %Danach wird das Datum eingefügt, falls das grammatisch nicht funktionieren sollte müssen wir eine ausnahme für die sprache definieren
+\newcommand{\anfragedanke} {Im Voraus besten Dank für Ihre Bemühungen.}
+
+% bestellung/auftrag (purchase_order)
+\newcommand{\bestellung} {Bestellung}
+\newcommand{\unsereBestellnummer} {Unsere Bestellnummer}
+\newcommand{\bestellformel} {hiermit bestellen wir verbindlich folgende Positionen:}
+
+% einkaufslieferschein (purchase_delivery_order)
+\newcommand{\einkaufslieferschein} {Eingangslieferschein}
+
+% Brief/letter
+\newcommand{\ihrzeichen}{Ihr Zeichen}
+\newcommand{\betreff}{Betreff}
--- /dev/null
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%standardphrasen und schnipsel in englisch %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+\newcommand{\anrede} {Dear Sirs,}
+\newcommand{\anredefrau} {Dear Ms.}
+\newcommand{\anredeherr} {Dear Mr.}
+
+
+\newcommand{\nr} {No.}
+\newcommand{\datum} {Date}
+\newcommand{\kundennummer} {Customer-No.}
+\newcommand{\ansprechpartner} {Contact person}
+\newcommand{\bearbeiter} {Employee}
+\newcommand{\gruesse} {Sincerely yours, }
+\newcommand{\vom} {from}
+\newcommand{\von} {from}
+\newcommand{\seite} {page}
+\newcommand{\uebertrag} {amount carried over}
+
+
+\newcommand{\position} {Pos.}
+\newcommand{\artikelnummer} {Part No.}
+\newcommand{\bild} {Picture}
+\newcommand{\keinbild} {n/a}
+\newcommand{\menge} {Qty}
+\newcommand{\bezeichnung} {Description}
+\newcommand{\seriennummer}{Serial No.}
+\newcommand{\ean}{EAN}
+\newcommand{\projektnummer}{Project No.}
+\newcommand{\charge}{Charge}
+\newcommand{\mhd}{Best before}
+\newcommand{\einzelpreis} {Price}
+\newcommand{\gesamtpreis} {Amount}
+\newcommand{\nettobetrag} {Net amount}
+\newcommand{\schlussbetrag} {Total}
+
+\newcommand{\weiteraufnaechsterseite} {to be continued on next page \ldots}
+
+\newcommand{\zahlung} {Payment terms:}
+\newcommand{\lieferung} {Delivery terms:}
+\newcommand{\textTelefon} {Tel.:}
+\newcommand{\textEmail} {Email:}
+\newcommand{\textFax} {Fax:}
+
+% angebot (sales_quotion)
+\newcommand{\angebot} {Quotation}
+\newcommand{\angebotsformel} {we are pleased to make the following offer:}
+\newcommand{\angebotdanke} {We thank you for your request and look forward to receiving your order.}
+\newcommand{\angebotgueltig} {This offer is valid until} %Danach wird das Datum eingefügt, falls das grammatisch nicht funktionieren sollte müssen wir eine ausnahme für die sprache definieren
+\newcommand{\angebotfragen} {If you have any questions do not hesitate to conatct us.}
+\newcommand{\angebotagb} {Our general terms and conditions (AGB) apply. We will send them to you on request.}
+\newcommand{\auftragerteilt}{Order confirmed:}
+\newcommand{\angebotortdatum}{We hereby accept this offer.}
+\newcommand{\abweichendeLieferadresse}{alternate delivery address}
+
+% auftragbestätigung (sales_order)
+\newcommand{\auftragsbestaetigung} {Order}
+\newcommand{\auftragsnummer} {Order No.}
+\newcommand{\ihreBestellnummer} {Your reference no.}
+\newcommand{\auftragsformel} {We hereby confirm your order for the following items:}
+\newcommand{\lieferungErfolgtAm} {Your items will be delivered on:} %Danach wird das Datum eingefügt, falls das grammatisch nicht funktionieren sollte müssen wir eine ausnahme für die sprache definieren
+\newcommand{\auftragpruefen} {Please check that all items correspond to your order. Please tell us of any deviations within 3 days.}
+\newcommand{\proformarechnung} {Proforma invoice}
+\newcommand{\nurort} {Place}
+\newcommand{\den} {Date}
+\newcommand{\unterschrift} {Signature}
+\newcommand{\stempel} {Company stamp}
+
+% lieferschein (sales_delivery_order)
+\newcommand{\lieferschein} {Delivery order}
+
+% rechnung (invoice)
+\newcommand{\rechnung} {Invoice}
+\newcommand{\rechnungsdatum} {Invoice date}
+\newcommand{\ihrebestellung} {Your order}
+\newcommand{\lieferdatum} {Delivery date}
+\newcommand{\rechnungsformel} {we invoice you for the following items:}
+\newcommand{\zwischensumme} {Subtotal}
+\newcommand{\leistungsdatumGleichRechnungsdatum} {The date of service corresponds to that of the invoice.}
+\newcommand{\unserebankverbindung} {Our bank details}
+\newcommand{\textKontonummer} {Account no.:}
+\newcommand{\textBank} {at}
+\newcommand{\textBankleitzahl} {Bank code:}
+\newcommand{\textBic} {BIC:}
+\newcommand{\textIban} {IBAN:}
+\newcommand{\unsereustid} {Our VAT number is}
+\newcommand{\ihreustid} {Your VAT number:}
+\newcommand{\steuerfreiEU} {VAT-exempt intra-community delivery. Reverse Charge.}
+\newcommand{\steuerfreiAUS} {VAT-exempt delivery for outside the EU.}
+
+\newcommand{\textUstid} {VAT number:}
+
+% gutschrift (credit_note)
+\newcommand{\gutschrift} {Credit note}
+\newcommand{\fuerRechnung} {for invoice}
+\newcommand{\gutschriftformel} {we credit you with the following items:}
+
+% sammelrechnung (statement)
+\newcommand{\sammelrechnung} {Statement}
+\newcommand{\sammelrechnungsformel} {please note that the following invoices are outstanding:}
+\newcommand{\faellig} {Due}
+\newcommand{\aktuell} {Current}
+\newcommand{\asDreissig} {30}
+\newcommand{\asSechzig} {60}
+\newcommand{\asNeunzig} {90+}
+
+% zahlungserinnerung (Mahnung)
+\newcommand{\mahnung} {Payment reminder}
+\newcommand{\mahnungsformel} {our records show that the following invoices are still outstanding:}
+\newcommand{\beruecksichtigtBis} {We have taken into account payments received up until}
+\newcommand{\schonGezahlt} {If you have already paid in the meantime, please ignore this payment reminder.}
+
+% zahlungserinnerung_invoice (Rechnung zur Mahnung)
+\newcommand{\mahnungsrechnungsformel} {for the above-mentioned payment reminder we charge you the following fees:}
+\newcommand{\posten} {Item}
+\newcommand{\betrag} {Amount}
+\newcommand{\bitteZahlenBis} {Please settle the outstanding balance by }
+
+% anfrage (request_quotion)
+\newcommand{\anfrage} {Quotation request}
+\newcommand{\anfrageformel} {please quote us prices and delivery dates for the following items:}
+\newcommand{\anfrageBenoetigtBis} {We need the delivery by} %Danach wird das Datum eingefügt, falls das grammatisch nicht funktionieren sollte müssen wir eine ausnahme für die sprache definieren
+\newcommand{\anfragedanke} {Thank you in advance.}
+
+% bestellung/auftrag (purchase_order)
+\newcommand{\bestellung} {Order}
+\newcommand{\unsereBestellnummer} {Our order number}
+\newcommand{\bestellformel} {we hereby order the following items:}
+
+% einkaufslieferschein (purchase_delivery_order)
+\newcommand{\einkaufslieferschein} {Purchase delivery order}
+
+% Brief/letter
+\newcommand{\ihrzeichen}{Your reference}
+\newcommand{\betreff}{Subject}
--- /dev/null
+\newcommand{\currency}{CHF}
+\newcommand{\kontonummer}{4004 283 800}
+\newcommand{\bank}{GLS Bank eG}
+\newcommand{\bankleitzahl}{430 609 67}
+\newcommand{\bic}{DE87430609674004283800}
+\newcommand{\iban}{GENODEM1GLS}
--- /dev/null
+\newcommand{\kontonummer}{4071953800}
+\newcommand{\bank}{GLS Bank eG}
+\newcommand{\bankleitzahl}{430 609 67}
+\newcommand{\bic}{DE50430609674071953800}
+\newcommand{\iban}{GENODEM1GLS}
+%Keine Definition von \currency!
--- /dev/null
+\newcommand{\currency}{€}
+\newcommand{\kontonummer}{4071953800}
+\newcommand{\bank}{GLS Bank eG}
+\newcommand{\bankleitzahl}{430 609 67}
+\newcommand{\bic}{DE50430609674071953800}
+\newcommand{\iban}{GENODEM1GLS}
--- /dev/null
+\newcommand{\telefon} {++49 228 92 98 2012}
+\newcommand{\fax} {}
+\newcommand{\firma} {kivitendo GmbH}
+\newcommand{\strasse} {Kölnstr. 311}
+\newcommand{\ort} {53117 Bonn}
+\newcommand{\ustid} {DE292363254}
+\newcommand{\finanzamt} {Finanzamt Bonn-Innenstadt}
+\newcommand{\email} {information@kivitendo-premium.de}
+\newcommand{\homepage} {http://www.kivitendo-premium.de}
--- /dev/null
+\newcommand{\currency}{\$}
+\newcommand{\kontonummer}{4004 283 800}
+\newcommand{\bank}{GLS Bank eG}
+\newcommand{\bankleitzahl}{430 609 67}
+\newcommand{\bic}{DE87430609674004283800}
+\newcommand{\iban}{GENODEM1GLS}
--- /dev/null
+\documentclass[paper=a4,fontsize=10pt]{scrartcl}
+\usepackage{kiviletter}
+
+% Variablen, die in settings verwendet werden
+\newcommand{\lxlangcode} {<%template_meta.language.template_code%>}
+\newcommand{\lxmedia} {<%media%>}
+\newcommand{\lxcurrency} {<%currency%>}
+\newcommand{\kivicompany} {<%employee_company%>}
+
+% settings: Einstellungen, Logo, Briefpapier, Kopfzeile, Fusszeile
+\input{insettings.tex}
+
+
+\usepackage{ulem}
+\usepackage{hyperref}
+\usepackage{xstring}
+\begin{document}
+
+\begin{Form}
+\large
+Bestätigung über das Gelangen des Gegenstands einer innergemeinschaftlichen Lieferung
+in einen anderen EU-Mitgliedstaat (Gelangensbestätigung)
+\vspace{0.4cm}
+
+{\color{purple} Bitte unterschreiben und faxen/mailen an:
+ \begin{center} <%employee_fax%> / <%employee_email%> \end{center}}
+\normalsize
+\vspace{0.4cm}
+<%name%>, <%street%>, <%zipcode%> <%city%>, <%country%>\hspace*{\fill}\\
+\TextField[name=department, bordercolor=gray, width=\linewidth]{}\\
+{\color{gray}(Name und Anschrift des Abnehmers der innergemeinschaftlichen Lieferung, ggf. E-Mail-Adresse)}
+
+\vspace{0.4cm}
+
+Hiermit bestätige ich als Abnehmer, dass ich folgenden Gegenstand / dass folgender Gegenstand \textsuperscript{1)} einer
+innergemeinschaftlichen Lieferung\\
+
+
+\TextField[name=qty, bordercolor=gray, width=\linewidth]{}\\
+{\color{gray}(Menge des Gegenstands der Lieferung)}\\
+
+\TextField[name=desc, bordercolor=gray, width=\linewidth]{}\\
+{\color{gray}(handelsübliche Bezeichnung, bei Fahrzeugen zusätzlich die Fahrzeug-Identifikationsnummer)}\\
+
+im\\
+
+\uline{ \StrGobbleLeft{<%reqdate%>}{3} \hspace*{\fill}}\\
+{\color{gray}(Monat und Jahr des Erhalts des Liefergegenstands im Mitgliedstaat, in den der Liefergegenstand gelangt ist, wenn der liefernde Unternehmer den Liefergegenstand befördert oder versendet hat oder wenn der Abnehmer den Liefergegenstand versendet hat)}\\
+
+
+\TextField[name=delivery, bordercolor=gray, width=\linewidth]{}\\
+{\color{gray}(Monat und Jahr des Endes der Beförderung, wenn der Abnehmer den Liefergegenstand selbst befördert hat)}\\
+
+in / nach \textsuperscript{1)}\\
+
+
+\uline{<%country%>\hspace*{\fill}}\\
+{\color{gray}(Mitgliedstaat und Ort, wohin der Liefergegenstand im Rahmen einer Beförderung oder Versendung gelangt ist)}\\
+
+erhalten habe / gelangt ist.
+
+\TextField[name=delivery, bordercolor=gray, width=\linewidth]{}\\
+{\color{gray}(Datum der Ausstellung der Bestätigung)}\\
+
+\uline{\hspace*{\fill}}
+
+{\color{gray}(Unterschrift des Abnehmers oder seines Vertretungsberechtigten sowie Name des Unterzeichnenden in Druckschrift)}\\
+
+\textbf{\textsuperscript{1)} Nichtzutreffendes bitte streichen.}
+\end{Form}
+\end{document}
+
--- /dev/null
+\documentclass[paper=a4,fontsize=10pt]{scrartcl}
+\usepackage{kiviletter}
+
+% Variablen, die in settings verwendet werden
+\newcommand{\lxlangcode} {<%template_meta.language.template_code%>}
+\newcommand{\lxmedia} {<%media%>}
+\newcommand{\lxcurrency} {<%currency%>}
+\newcommand{\kivicompany} {<%employee_company%>}
+
+% settings: Einstellungen, Logo, Briefpapier, Kopfzeile, Fusszeile
+\input{insettings.tex}
+
+
+\usepackage{ulem}
+\usepackage{hyperref}
+\usepackage{xstring}
+\begin{document}
+
+\begin{Form}
+\large
+Certification of the entry of the object of an intra-Community supply into another EU Member State (Entry Certificate)
+
+\vspace{0.4cm}
+
+{\color{purple} Please sign below and send back to fax-number/mail-address:
+ \begin{center} <%employee_fax%> / <%employee_email%> \end{center}}
+
+\normalsize
+\vspace{0.4cm}
+<%name%>, <%street%>, <%zipcode%> <%city%>, <%country%>\hspace*{\fill}\\
+\TextField[name=department, bordercolor=gray, width=\linewidth]{}\\
+{\color{gray}(Name and address of the customer of the intra-Community supply, e-mail address if applicable)}
+
+
+\vspace{0.4cm}
+I as the customer hereby certify my receipt / the entry \textsuperscript{1)} of the following object of an intra-Community supply\\
+
+\TextField[name=qty, bordercolor=gray, width=\linewidth]{}\\
+{\color{gray}(Quantity of the object of the supply)}\\
+
+\TextField[name=desc, bordercolor=gray, width=\linewidth]{}\\
+{\color{gray}(Standard commercial description – in the case of vehicles, including vehicle identification number)}\\
+
+in\\
+
+\uline{ \StrGobbleLeft{<%reqdate%>}{3} \hspace*{\fill}}\\
+{\color{gray}(Month and year the object of the supply was received in the Member State of entry if the supplying trader transported or dispatched the object of the supply or if the customer dispatched the object of the supply)}\\
+
+\TextField[name=delivery, bordercolor=gray, width=\linewidth]{}\\
+{\color{gray}(Month and year the transportation ended if the customer transported the object of the supply himself or herself)}\\
+
+in / at \textsuperscript{1)}\\
+
+\uline{<%country%>\hspace*{\fill}}\\
+{\color{gray}(Member State and place of entry as part of the transport or dispatch of the object)}\\
+
+
+% X\TextField[name=delivery, bordercolor=gray, width=\linewidth]{}\\
+\uline{X\hspace*{\fill}}\\
+{\color{gray}(Date of issue of the certificate)}\\
+
+\uline{X\hspace*{\fill}}\\
+{\color{gray}(Signature of the customer or of the authorised representative as well as the signatory’s name in capitals)}\\
+
+\textbf{\textsuperscript{1)} Delete as appropriate.}
+
+\end{Form}
+\end{document}
+
--- /dev/null
+%Dokumentenklasse für DIN-Briefe auf A4
+\documentclass[paper=a4,fontsize=10pt]{scrartcl}
+\usepackage{kiviletter}
+
+%TODO babel setup
+\endinput
--- /dev/null
+%% insettings.tex
+%% Copyright 2019 Marei Peischl
+\ProvidesFile{insettings.tex}[2019/12/22 Konfigurationsdatei kivitendo ERP]
+% Sprachüberprüfung
+\RequirePackage[english, ngerman]{babel}
+\ifstr{\lxlangcode}{EN}{
+ \makeatletter
+ \main@language{english}
+ \makeatother
+ \input{english.tex}}{
+ \ifstr{\lxlangcode}{DE}{
+ \makeatletter
+ \main@language{ngerman}
+ \makeatother
+ \input{deutsch.tex}}{\input{deutsch.tex}}
+} % Ende EN
+
+
+% Mandanten-/Firmenabhängigkeiten
+
+% Pfad zu firmenspez. Angaben
+% Hat man mehrere Mandanten muß man statt "Firma1" den Datenbanknamen seines
+% Mandanten eingeben.
+
+\ExplSyntaxOn
+\int_set:Nn \l_kivi_tmp_int {1}
+\bool_set_true:N \l_kivi_tmp_bool
+\bool_while_do:Nn \l_kivi_tmp_bool {
+ \file_if_exist:nTF {firma\int_use:N \l_kivi_tmp_int/ident.tex}
+ {
+ \str_if_in:NnTF \kivicompany {Firma\int_use:N \l_kivi_tmp_int}
+ {
+ \newcommand*{\identpath}{firma\int_use:N \l_kivi_tmpa_int}
+ \bool_set_false:N \l_kivi_tmp_bool
+ }
+ {\int_incr:N \l_kivi_tmp_int}
+ }
+ {
+ \bool_set_false:N \l_kivi_tmp_bool
+ \newcommand*{\identpath}{firma}
+ }
+}
+
+\ExplSyntaxOff
+
+
+% Identität
+\input{\identpath/ident.tex}
+
+\ExplSyntaxOn
+
+%Setze Briefkopf-logo falls vorhanden
+\setkomavar{fromlogo}{\includegraphics[width=.25\linewidth]{\identpath/briefkopf}}
+
+% Währungen/Konten
+\tl_new:N \g_kivi_currency_tl
+\str_if_in:NnT \lxcurrency {USD} {\tl_gset:Nn \g_kivi_currency_tl {usd}}
+\str_if_in:NnT \lxcurrency {CHF} {\tl_gset:Nn \g_kivi_currency_tl {chf}}
+\str_if_in:NnT \lxcurrency {EUR} {\tl_gset:Nn \g_kivi_currency_tl {euro}}
+\tl_if_empty:NT \g_kivi_currency_tl {
+ \tl_gset:Nn \g_kivi_currency_tl {default}
+ \edef \currency {\tl_to_str:N \lxcurrency}
+}
+
+\input{\identpath/\g_kivi_currency_tl _account.tex}
+
+\ExplSyntaxOff
+
+
+% keine Absätze nach rechts einrücken
+\setlength\parindent{0pt}
+
+
+
+% Befehl f. normale Schriftart und -größe
+\renewcommand*{\familydefault}{\sfdefault}
+\KOMAoptions{fontsize=10pt}
+
+% Einstellungen f. Kopf und Fuss
+\pagestyle{kivitendo.letter}
+% Befehl f. laufende Kopfzeile:
+% 1. Text f. Kunden- oder Lieferantennummer (oder leer, wenn diese nicht ausgegeben werden soll)
+% 2. Kunden- oder Lieferantennummer (oder leer)
+% 3. Belegname {oder leer}
+% 4. Belegnummer {oder leer}
+% 5. Belegdatum {oder leer}
+% Beispiel: \ourhead{\kundennummer}{<%customernumber%>}{\angebot}{<%quonumber%>}{<%quodate%>}
+\setkomafont{pagehead}{\scriptsize}
+\newcommand{\ourhead}[5] {
+\chead{
+ \ifnum\thepage=1
+ \else
+ \makebox[\textwidth]{
+ \ifstr{#1}{}{}{#1: #2 \hspace{0.7cm}}
+ #3
+ \ifstr{#4}{}{}{~\nr: #4}
+ \ifstr{#5}{}{}{\vom ~ #5}
+ \hspace{0.7cm} - \seite ~ \thepage/\letterlastpage ~-%
+ }
+ \fi
+}
+}
+
+%% % Firmenfuss
+\setkomafont{pagefoot}{\tiny}
+\cfoot{
+ {
+ \begin{tabular*}{\textwidth}{@{\extracolsep{\fill}}p{5cm}p{4.5cm}lr@{}}
+ \firma & \email & \textKontonummer & \kontonummer \\
+ \strasse & \homepage & \textBank & \bank \\
+ \ort & \textUstid\ \ustid & \textIban & \iban \\
+ \textTelefon~\telefon & \finanzamt & \textBic & \bic \\
+ \ifstr{\fax}{}{}{\textFax~\fax} & &\textBankleitzahl & \bankleitzahl \\
+ \end{tabular*}
+ }
+}
+
+\endinput
--- /dev/null
+
+<body bgcolor=ffffff>
+
+<table width=100%>
+<tr valign=bottom>
+ <td width=10> </td>
+ <td>
+
+ <table width=100%>
+ <tr>
+ <td>
+ <h4>
+ <%company%>
+ <br><%address%>
+ </h4>
+ </td>
+
+ <td align=right>
+ <h4>
+ Telefon <%tel%>
+ <br>Telefax <%fax%>
+ </h4>
+ </td>
+ </tr>
+
+ <tr>
+ <th colspan=3>
+ <h4>R E C H N U N G</h4>
+ </th>
+ </tr>
+
+ </table>
+
+
+ <table width=100% callspacing=0 cellpadding=0>
+
+ <tr>
+ <td align=right>
+ <table>
+ <tr>
+ <th align=right>Ausgestellt am</th><td width=10> </td><td><%invdate%></td>
+ </tr>
+
+ <tr>
+ <th align=right>Bezahlbar bis</th><td width=10> </td><td><%duedate%></td>
+ </tr>
+
+ <tr>
+ <th align=right>Nummer</th><td> </td><td><%invnumber%></td></tr>
+ </tr>
+
+ <tr>
+ <th align=right>Lieferdatum</th><td> </td><td><%deliverydate%></td></tr>
+ </tr>
+<!--
+ <tr>
+ <th align=right>Clerk:</th><td> </td><td><%username%></td>
+ </tr>
+-->
+
+ <tr>
+ <td> </td>
+ </tr>
+ </td>
+ </table>
+ </tr>
+
+ <tr>
+ <td>
+ <table width=100%>
+ <tr bgcolor=000000>
+ <th align=left><font color=ffffff>An:</th>
+ <th align=left><font color=ffffff>Lieferaddresse:</th>
+ </tr>
+
+<!--
+ other variables which can be use:
+ contact, shiptocontact, shiptophone, shiptofax
+-->
+
+ <tr>
+ <td><%name%>
+ <br><%street%>
+ <br><%zipcode%>
+ <br><%city%>
+ <br><%country%>
+ </td>
+
+ <td><%shiptoname%>
+ <br><%shiptostreet%>
+ <br><%shiptozipcode%>
+ <br><%shiptocity%>
+ <br><%shiptocountry%>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+
+ <tr>
+ <td> </td>
+ </tr>
+
+ <tr>
+ <td>
+ <table width=100%>
+ <tr bgcolor=000000>
+<!-- <th align=right><font color=ffffff>No.</th> -->
+ <th align=left><font color=ffffff>Nummer</th>
+ <th align=left><font color=ffffff>Beschreibung</th>
+ <th><font color=ffffff>Anz.</th>
+ <th> </th>
+ <th><font color=ffffff>Preis</th>
+ <th><font color=ffffff>Rab</th>
+ <th><font color=ffffff>Total</th>
+ </tr>
+
+<%foreach number%>
+ <tr valign=top>
+<!-- <td align=right><%runningnumber%>.</td>
+adjust the colspan if you include this to shift subtotal one to the right
+-->
+ <td><%number%></td>
+ <td><%description%></td>
+ <td align=right><%qty%></td>
+ <td><%unit%></td>
+ <td align=right><%sellprice%></td>
+ <td align=right><%discount%></td>
+ <td align=right><%linetotal%></td>
+ </tr>
+<%end number%>
+
+<!--
+you can also use netprice instead of sellprice if you
+don't want to show the discount
+netprice = linetotal/qty
+-->
+
+ <tr>
+ <td colspan=7><hr noshade></td>
+ </tr>
+
+<%if taxincluded%>
+ <tr>
+ <th colspan=5 align=right>Total</th>
+ <td colspan=2 align=right><%invtotal%></td>
+ </tr>
+<%end taxincluded%>
+<%if not taxincluded%>
+ <tr>
+ <th colspan=5 align=right>Zwischensumme</th>
+ <td colspan=2 align=right><%subtotal%></td>
+ </tr>
+<%end taxincluded%>
+
+<%foreach tax%>
+ <tr>
+ <th colspan=5 align=right><%taxdescription%> auf <%taxbase%></th>
+ <td colspan=2 align=right><%tax%></td>
+ </tr>
+<%end tax%>
+
+<%if rounding%>
+ <tr>
+ <th colspan=5 align=right>Rundung</th>
+ <td colspan=2 align=right><%rounding%></td>
+ </tr>
+<%end rounding%>
+
+<%if paid%>
+ <tr>
+ <th colspan=5 align=right>Bezahlt</th>
+ <td colspan=2 align=right>- <%paid%></td>
+ </tr>
+<%end paid%>
+
+ <tr>
+ <td colspan=3> </td>
+ <td colspan=4><hr noshade></td>
+ </tr>
+
+ <tr>
+ <td colspan=3>Bezahlbar innerhalb von <b><%terms%></b> Tagen</td>
+<%if total%>
+ <th colspan=2 align=right>Total</th>
+ <th colspan=2 align=right><%total%></th>
+<%end total%>
+ </tr>
+
+ <tr>
+ <td> </td>
+ </tr>
+
+ </table>
+ </td>
+ </tr>
+
+<tr>
+ <td>
+ <table width=100%>
+ <tr valign=top>
+<%if notes%>
+ <td>Bemerkungen:</td>
+ <td><%notes%></td>
+<%end notes%>
+ <td align=right>
+ Alle Preise in <b><%currency%></b>
+ <br><%shippingpoint%>
+ </td>
+ </tr>
+
+ </table>
+ </td>
+</tr>
+
+<tr><td> </td></tr>
+
+<tr>
+ <td>
+ <table width=100%>
+ <tr valign=top>
+ <td><font size=-3>
+ Rechnung ist bezahlbar innerhalb von <%terms%> Tagen.
+ Nach dem <%duedate%> werden Zinsen zu einem
+ monatlichen Satz von 1.5% verrechnet.
+ Waren bleiben im Besitz von <%company%> bis die Rechnung voll bezahlt ist.
+ Rückgaben werden mit 10% Lagergebühren belastet. Beschädigte Waren
+ und Waren ohne eine Rückgabenummer werden nicht entgegengenommen.
+ </font>
+ </td>
+ <td width=150>
+ X <hr noshade>
+ </td>
+ </tr>
+ </table>
+ </td>
+</tr>
+
+<%foreach tax%>
+ <tr>
+ <th colspan=7 align=left><font size=-2><%taxdescription%> Registration <%taxnumber%></th>
+ </tr>
+<%end tax%>
+
+<%if taxincluded%>
+ <tr>
+ <th colspan=7 align=left><font size=-2>Steuern sind im Preis inbegriffen.</th>
+ </tr>
+<%end taxincluded%>
+
+<!-- business number
+ <tr>
+ <th colspan=7 align=left><font size=-2>Business Number: <%businessnumber%></font></th>
+ </tr>
+-->
+
+ <tr>
+ <th colspan=7 align=left>
+ <hr>
+ <br>Bankverbindung
+ <br>Bank
+ <br>Bankleitzahl
+ <br>Konto No.
+ </td>
+ </tr>
+
+</table>
+
+</td>
+</tr>
+</table>
+
+</body>
+</html>
+
--- /dev/null
+\documentclass[paper=a4,fontsize=10pt]{scrartcl}
+\usepackage{kiviletter}
+
+
+% Variablen, die in settings verwendet werden
+\newcommand{\lxlangcode} {<%template_meta.language.template_code%>}
+\newcommand{\lxmedia} {<%media%>}
+\newcommand{\lxcurrency} {<%currency%>}
+\newcommand{\kivicompany} {<%employee_company%>}
+
+% settings: Einstellungen, Logo, Briefpapier, Kopfzeile, Fusszeile
+\input{insettings.tex}
+
+
+% laufende Kopfzeile:
+\ourhead{\kundennummer}{<%customernumber%>}{\rechnung}{<%invnumber%>}{<%invdate%>}
+
+
+\setkomavar*{date}{\rechnungsdatum}
+
+\setkomavar{date}{<%invdate%>}
+\setkomavar{customer}{<%customernumber%>}
+\setkomavar{fromname}{<%employee_name%>}
+\setkomavar{fromphone}{<%employee_tel%>}
+\setkomavar{fromemail}{<%employee_email%>}
+\setkomavar{title}{
+ \rechnung~ \nr ~<%invnumber%>
+}
+<%if ordnumber%>
+\setkomavar*{myref}{\auftragsnummer}
+\setkomavar{myref}{<%ordnumber%>}
+<%end if%>
+<%if cusordnumber%>
+\setkomavar*{yourref}{\ihreBestellnummer}
+\setkomavar{yourref}{<%cusordnumber%>}
+<%end if%>
+<%if donumber%>
+ \setkomavar{delivery}{<%donumber%>}
+<%end if%>
+<%if quonumber%>
+\setkomavar{quote}{<%quonumber%>}
+<%end if%>
+
+<%if shiptoname%>
+\makeatletter
+\begin{lrbox}\shippingAddressBox
+ \parbox{\useplength{toaddrwidth}}{
+ \backaddr@format{\scriptsize\usekomafont{backaddress}%
+ \strut abweichende Lieferadresse
+ }
+ \par\smallskip
+ \setlength{\parskip}{\z@}
+ \par
+ \normalsize
+ <%shiptoname%>\par
+ <%if shiptocontact%> <%shiptocontact%><%end if%>\par
+ <%shiptodepartment_1%>\par
+ <%shiptodepartment_2%>\par
+ <%shiptostreet%>\par
+ <%shiptozipcode%> <%shiptocity%>
+ }
+\end{lrbox}
+\makeatother
+<%end if%>
+
+\begin{document}
+
+\begin{letter}{
+ <%name%>\strut\\
+ <%if department_1%><%department_1%>\\<%end if%>
+ <%if department_2%><%department_2%>\\<%end if%>
+ <%cp_givenname%> <%cp_name%>\strut\\
+ <%street%>\strut\\
+ <%zipcode%> <%city%>\strut\\
+ <%country%> \strut
+ }
+
+% Bei Kontaktperson Anrede nach Geschlecht unterscheiden.
+% Bei natürlichen Personen persönliche Anrede, sonst allgemeine Anrede.
+\opening{
+ \ifstr{<%cp_name%>}{}
+ {<%if natural_person%><%greeting%> <%name%>,<%else%>\anrede<%end if%>}
+ {
+ \ifstr{<%cp_gender%>}{f}
+ {\anredefrau}
+ {\anredeherr}
+ <%cp_title%> <%cp_name%>,
+ }
+ }
+\thispagestyle{kivitendo.letter.first}
+
+<%if notes%>
+ <%notes%>
+ \vspace{0.5cm}
+<%end if%>
+
+
+
+\begin{PricingTabular*}
+% eigentliche Tabelle
+\FakeTable{
+<%foreach number%>%
+<%runningnumber%> &%
+<%number%> &%
+\textbf{<%description%>}%
+<%if longdescription%>\ExtraDescription{<%longdescription%>}<%end longdescription%>%
+<%if serialnumber%>\ExtraDescription{\seriennummer: <%serialnumber%>}<%end serialnumber%>%
+<%if ean%>\ExtraDescription{\ean: <%ean%>}<%end ean%>%
+<%if projectnumber%>\ExtraDescription{\projektnummer: <%projectnumber%>}<%end projectnumber%>%
+&%
+<%qty%> <%unit%> &%
+<%sellprice%>&%
+\ifstr{<%p_discount%>}{0}{}{\sffamily\scriptsize{(-<%p_discount%>\,\%)}}%
+<%linetotal%>\tabularnewline
+<%end number%>
+}
+\begin{PricingTotal}
+% Tabellenende letzte Seite
+\nettobetrag & <%subtotal%>\\
+<%foreach tax%>
+<%taxdescription%> & <%tax%>\\
+<%end tax%>
+\bfseries\schlussbetrag & \bfseries <%ordtotal%>\\
+\end{PricingTotal}
+\end{PricingTabular*}
+
+\vspace{0.2cm}
+
+\ifstr{<%deliverydate%>}{}{}{%
+ \leistungsdatumGleichRechnungsdatum%
+}{
+ \lieferungErfolgtAm ~<%deliverydate%>.
+}\\
+
+<%if payment_terms%>
+ \zahlung ~<%payment_terms%>\\
+<%end payment_terms%>
+
+<%if delivery_term%>
+ \lieferung ~<%delivery_term.description_long%>\\
+<%end delivery_term%>
+
+<%if ustid%>\ihreustid ~<%ustid%>.\\<%end if%>
+
+\ifnum<%taxzone_id%>=1
+ \steuerfreiEU\\ % EU mit USt-ID Nummer
+\else
+ \ifnum<%taxzone_id%>=3
+ \steuerfreiAUS\\ % Außerhalb EU
+ \fi
+\fi
+
+\closing{\gruesse}
+
+
+\end{letter}
+
+\end{document}
--- /dev/null
+\NeedsTeXFormat{LaTeX2e}
+\ProvidesPackage{kiviletter}[2020/04/24 Letter Layouts for Kivitendo]
+
+
+\newif\if@kivi@infobox
+\DeclareOption{reffields}{\@kivi@infoboxfalse}
+\DeclareOption{infobox}{\@kivi@infoboxtrue}
+\@kivi@infoboxtrue
+
+\ProcessOptions\relax
+
+
+\RequirePackage{expl3}
+\RequirePackage{iftex}
+\KOMAoptions{fontsize=12pt}
+% Schriftart, Eingabelayout der Tastatur
+\ifPDFTeX
+\RequirePackage[utf8]{inputenc}% Nur notwendig, wenn Basis älter als TL2018
+\RequirePackage[T1]{fontenc}
+\RequirePackage{lmodern}
+\else
+\RequirePackage{fontspec}
+\fi
+
+%\RequirePackage{xltabular}
+\RequirePackage{tabularx}
+\RequirePackage{longtable}
+\RequirePackage{booktabs}
+\PassOptionsToPackage{table}{xcolor}
+
+\RequirePackage{xcolor}
+\RequirePackage{graphicx}
+
+\ifPDFTeX
+\RequirePackage{eurosym}
+\DeclareUnicodeCharacter{20AC}{\euro}
+\fi
+
+\RequirePackage[fromlogo,fromalign=right,
+ firstfoot=false,%Für einheitliche Randeinstellungen
+ refline=nodate,
+ ]{scrletter}
+\LoadLetterOption{DIN}
+
+\newkomavar{transaction}
+\newkomavar[\lieferschein{}~\nr]{delivery}
+\newkomavar[\angebot{}~\nr]{quote}
+\newkomavar{orderID}
+\newkomavar{projectID}
+
+\usepackage{geometry}
+
+\ExplSyntaxOn
+\dim_new:N \g_kivi_margin_dim
+\dim_gset:Nn \g_kivi_margin_dim {\useplength{toaddrhpos}}
+\geometry{a4paper,margin=\g_kivi_margin_dim,heightrounded}
+\savegeometry{kivi.letter@default}
+%Scratch variables
+\int_new:N \l_kivi_tmp_int
+\bool_new:N \l_kivi_tmp_bool
+\bool_new:N \g_kivi_TableFoot_bool
+\dim_new:N \g_kivi_orig@textheight_dim
+\int_new:N \g_PricingTabular_firstpage_int
+\ExplSyntaxOff
+
+\newsavebox{\shippingAddressBox}
+
+
+\DeclareNewLayer[
+foreground,
+hoffset=\useplength{toaddrhpos},
+voffset=\dimexpr\useplength{toaddrvpos}+\useplength{toaddrheight}+4\baselineskip,
+contents={\usebox\shippingAddressBox}
+]{kivitendo.shippingaddress}
+
+
+\ExplSyntaxOn
+\DeclareNewLayer[
+foreground,
+mode=picture,
+hoffset=\g_kivi_margin_dim,
+voffset=\g_kivi_margin_dim,
+align=tl,
+height=\box_ht:N \g_kivi_LT@head_box,
+contents={\box_use:N \g_kivi_LT@head_box},
+]{kivitendo.TableHead}
+
+
+\DeclareNewLayer[
+foreground,
+textarea,
+mode=picture,
+voffset=\dim_eval:n {\paperheight-\g_kivi_margin_dim},
+height=\box_ht:N \g_kivi_LT@foot_box,
+contents=\bool_if:NT \g_kivi_TableFoot_bool {\box_use:N \g_kivi_LT@foot_box},
+align=bl,
+]{kivitendo.TableFoot}
+
+\AtBeginLetter{\dim_gset:Nn \g_kivi_orig@textheight_dim {\textheight}}
+\ExplSyntaxOff
+
+\newpairofpagestyles{kivitendo.letter}{}
+\renewcommand*{\letterpagestyle}{kivitendo.letter}
+
+\DeclareNewPageStyleByLayers{kivitendo.letter.PricingTable}{
+ kivitendo.TableHead,
+ kivitendo.TableFoot
+ kivitendo.letter.head.odd,kivitendo.letter.head.even,kivitendo.letter.head.oneside,%
+ kivitendo.letter.foot.odd,kivitendo.letter.foot.even,kivitendo.letter.foot.oneside,%
+}
+\DeclareNewPageStyleByLayers{kivitendo.letter.first}{
+ kivitendo.shippingaddress,
+ kivitendo.TableFoot,
+ kivitendo.letter.head.odd,kivitendo.letter.head.even,kivitendo.letter.head.oneside,%
+ kivitendo.letter.foot.odd,kivitendo.letter.foot.even,kivitendo.letter.foot.oneside,%
+}
+
+\setkomavar{backaddress}{\firma\ $\cdot$ \strasse\ $\cdot$ \ort}
+
+\setkomavar{firsthead}{
+ \if@logo
+ \rlap{\usekomavar{fromlogo}}%
+ \fi
+}
+
+\@setplength{locwidth}{6cm}
+
+\ExplSyntaxOn
+\dim_new:N \g_kivi_tab_pos_dim
+\dim_gset:Nn \g_kivi_tab_pos_dim {3.5ex}
+\dim_new:N \g_kivi_tab_id_dim
+\dim_gset:Nn \g_kivi_tab_id_dim {4em}
+\dim_new:N \g_kivi_tab_num_dim
+\dim_gset:Nn \g_kivi_tab_num_dim {5em}
+\dim_new:N \g_kivi_tab_price_dim
+\dim_gset:Nn \g_kivi_tab_price_dim {7em}
+\dim_new:N \g_kivi_tab_desc_dim
+
+\dim_new:N \g_kivi_tabcolsep_dim
+\dim_gset:Nn \g_kivi_tabcolsep_dim {.5\tabcolsep}
+\newcommand*{\CalcTabCols}{
+ \dim_gset:Nn \g_kivi_tab_desc_dim {\textwidth-\g_kivi_tab_pos_dim -\g_kivi_tab_id_dim-\g_kivi_tab_num_dim - 2\g_kivi_tab_price_dim - 10\g_kivi_tabcolsep_dim}
+}
+
+\newcolumntype{P}{>{\raggedleft\arraybackslash}p{\g_kivi_tab_price_dim}<{\,\currency}}
+
+%\if@kivi@faketable
+\RequirePackage{tcolorbox}
+\tcbuselibrary{breakable, skins}
+\seq_new:N \l_kivi_PricingTable_seq
+\seq_new:N \g_kivi_extraDescription_seq
+\newcommand{\FakeTable}[1]{
+ \par
+ \CalcTabCols
+ \seq_set_split:Nnn \l_kivi_PricingTable_seq {\tabularnewline} {#1}
+ \begingroup
+ \setlength{\tabcolsep}{\g_kivi_tabcolsep_dim}
+ \seq_map_inline:Nn \l_kivi_PricingTable_seq {
+ \seq_gclear:N \g_kivi_extraDescription_seq
+ \exp_args:NnV \use:n {\tabular[t]}\g_kivi_Pricing_colspec_tl
+ ##1
+ \endtabular
+ \seq_if_empty:NTF \g_kivi_extraDescription_seq
+ {\par\nointerlineskip}
+ {\par\nointerlineskip
+ \begin{tcolorbox}[
+ empty,
+ left=\dim_eval:n {\g_kivi_tab_pos_dim+ \g_kivi_tab_id_dim +4\g_kivi_tabcolsep_dim},
+ right=\dim_eval:n {\g_kivi_tab_num_dim+ 2\g_kivi_tab_price_dim +6\g_kivi_tabcolsep_dim},top=0pt,bottom=0pt,
+ boxsep=0pt,
+ breakable,
+ lines~before~break=1,
+ ]
+ \seq_use:Nn \g_kivi_extraDescription_seq {\\}
+ \end{tcolorbox}
+ \nointerlineskip
+ }
+ }
+ \endgroup
+}
+
+
+\tl_new:N \g_kivi_Pricing_colspec_tl
+\tl_gset:Nn \g_kivi_Pricing_colspec_tl {@{}p{\g_kivi_tab_pos_dim}p{\g_kivi_tab_id_dim}p{\g_kivi_tab_desc_dim}>{\raggedleft\arraybackslash}p{\g_kivi_tab_num_dim}*2{P}@{}}
+
+
+\clist_map_inline:nn {head, foot, firsthead, lastfoot} {%TODO reduce
+ \box_new:c {g_kivi_LT@#1_box}
+}
+
+\AtBeginDocument{
+ \csname kivi_setup_LT_boxes:\endcsname
+ \geometry{a4paper,
+ hmargin=\g_kivi_margin_dim,
+ top=\dim_eval:n {\g_kivi_margin_dim + \box_ht:N \g_kivi_LT@head_box},
+ bottom=\dim_eval:n {\g_kivi_margin_dim + \box_ht:N \g_kivi_LT@foot_box},
+ heightrounded}
+ \savegeometry{kivi.letter@table}
+ \loadgeometry{kivi.letter@default}
+}
+
+\cs_new:Nn \kivi_setup_LT_boxes: {
+ \CalcTabCols
+ \hbox_gset:Nn \g_kivi_LT@head_box {
+ \setlength{\tabcolsep}{\g_kivi_tabcolsep_dim}
+ \exp_args:NnV \use:n {\tabular[b]}\g_kivi_Pricing_colspec_tl
+ \toprule
+ \bfseries\position & \bfseries\artikelnummer & \bfseries\bezeichnung & \bfseries\menge &\multicolumn{1}{P}{\bfseries\einzelpreis}&\multicolumn{1}{P@{}}{\bfseries\gesamtpreis}\\
+ \midrule
+ \endtabular
+ }
+ \hbox_gset:Nn \g_kivi_LT@foot_box {
+ \raisebox{\depth}{
+ \begin{tabular*}{\textwidth}{@{\extracolsep{\fill}}r@{}}
+ \midrule
+ \strut\weiteraufnaechsterseite
+ \end{tabular*}
+ }
+ }
+}
+
+
+%Macht es sinn hier eine Variante zu machen, in der alle Spalten Belegbar sind?
+\newenvironment{PricingTotal}{
+ \tabular[t]{@{}p{\dim_eval:n {\linewidth-\g_kivi_tab_price_dim-2\tabcolsep}}P@{}}
+ \midrule
+}{
+ \bottomrule\endtabular
+}
+
+\newcommand{\ExtraDescription}[1]{\seq_gput_right:Nn \g_kivi_extraDescription_seq {#1}}
+%\else
+\newenvironment{PricingTabular}[1][]{
+ \begingroup
+ \setlength{\tabcolsep}{\g_kivi_tabcolsep_dim}
+ \CalcTabCols
+ \exp_args:NV \longtable \g_kivi_Pricing_colspec_tl
+ % Tabellenkopf
+ \toprule
+ \bfseries\position & \bfseries\artikelnummer & \bfseries\bezeichnung & \bfseries\menge &\multicolumn{1}{P}{\bfseries\einzelpreis}&\multicolumn{1}{P@{}}{\bfseries\gesamtpreis}\\
+ \midrule
+ \endhead
+ \midrule
+ \multicolumn{6}{@{}r@{}}{\weiteraufnaechsterseite}\\
+ \endfoot
+}{
+ \endlongtable
+ \endgroup
+}
+
+\RequirePackage{xltabular}
+
+
+\newenvironment{SimpleTabular}[1][\bfseries\position & \bfseries\menge & \bfseries\bezeichnung]
+{
+ \setlength{\tabcolsep}{\g_kivi_tabcolsep_dim}
+ \xltabular{\linewidth}{@{}rrX@{}}
+ \toprule
+ #1\\
+ \midrule\\\endhead
+ \midrule
+ \multicolumn{3}{@{}>{\raggedright}p{\linewidth}@{}}{\weiteraufnaechsterseite}\\
+ \endfoot
+ \bottomrule
+ \endlastfoot
+ \ignorespaces
+}{
+ \def\@currenvir{tabularx}
+ \endxltabular
+}
+
+
+\usepackage{afterpage}
+
+\cs_new:cpn {PricingTabular*}{
+ \bool_gset_true:N \g_kivi_inTable_bool
+ \endgroup
+ \@nameuse{Gm@restore@@kivi.letter@table}%
+ \Gm@changelayout
+ \begingroup
+ \def \@currenvir {PricingTabular*}\edef \@currenvline {\on@line }
+ \int_gset:Nn \g_PricingTabular_firstpage_int {\c@page}
+ \addtolength{\vsize}{-\box_ht:N \g_kivi_LT@foot_box}
+ \pagegoal\vsize
+ \widowpenalty0
+ \clubpenalty0
+ \bool_gset_true:N \g_kivi_TableFoot_bool
+ \pagestyle{kivitendo.letter.PricingTable}
+ \leavevmode\box_use:N \g_kivi_LT@head_box
+ \par\nointerlineskip\ignorespaces
+}
+
+\cs_new:cpn {endPricingTabular*} {
+ \int_compare:nNnF \g_PricingTabular_firstpage_int = \c@page {\thispagestyle{kivitendo.letter.PricingTable}}
+ \bool_gset_false:N \g_kivi_TableFoot_bool
+ \@nameuse{Gm@restore@@kivi.letter@default}
+ \Gm@changelayout
+ \bool_gset_true:N \g_kivi_restore_geometry_bool
+ \afterpage{
+ \kivi_conditional_restore_geometry:
+ }
+}
+
+\cs_new:Nn \kivi_conditional_restore_geometry: {
+ \bool_if:NT \g_kivi_restore_geometry_bool
+ {
+ \@nameuse{Gm@restore@@kivi.letter@default}
+ \Gm@changelayout
+ }
+ \bool_gset_false:N \g_kivi_restore_geometry_bool
+}
+
+
+
+\if@kivi@infobox
+\setkomavar{location}{
+ \ifkomavarempty{transaction}{}{
+ \bfseries
+ \usekomavar{transaction}
+ }
+ \par
+ \medskip
+ \begin{tabularx}{\useplength{locwidth}}{@{}l<{:}>{\raggedleft\arraybackslash}X@{}}
+ \usekomavar*{date}&\usekomavar{date}\\
+ \ifkomavarempty{myref}{}{
+ \usekomavar*{myref}&\usekomavar{myref}\\
+ }
+ \kundennummer&\usekomavar{customer}\\
+ \ifkomavarempty{yourref}{}{
+ \usekomavar*{yourref}&\usekomavar{yourref}\\
+ }
+ \ifkomavarempty{delivery}{}{
+ \usekomavar*{delivery}&\usekomavar{delivery}\\
+ }
+ \ifkomavarempty{quote}{}{
+ \usekomavar*{quote}&\usekomavar{quote}\\
+ }
+ \ifkomavarempty{orderID}{}{\auftragsnummer&\usekomavar{orderID}\\}
+ \ifkomavarempty{projectID}{}{\projektnummer&\usekomavar{projectID}\\}
+ \ansprechpartner&\usekomavar{fromname}
+ \ifkomavarempty{fromphone}{}{\\\textTelefon&\usekomavar{fromphone}}
+ \ifkomavarempty{fromemail}{}{\\\textEmail&\usekomavar{fromemail}}
+ \end{tabularx}
+}
+\removereffields
+\AtBeginLetter{
+ \ifdim\ht\shippingAddressBox>\z@
+ \addtoplength{refvpos}{\ht\shippingAddressBox}
+ \addtoplength{refvpos}{4\baselineskip}
+ \fi
+}
+\ExplSyntaxOff
+\fi
+
+
+
+\renewcommand*{\raggedsignature}{\raggedright}
+
+\endinput
--- /dev/null
+\ProvidesFile{kivitendo.sty}
+\usepackage{colortbl}
+\usepackage{eurosym}
+\usepackage{german}
+\usepackage{graphicx}
+\usepackage{ifthen}
+\usepackage[utf8]{inputenc}
+\usepackage{latexsym}
+\usepackage{longtable}
+\usepackage{textcomp}
+
+%% Paketoptionen
+\newboolean{defaultbg}\setboolean{defaultbg}{true}
+\newboolean{draftbg}
+\newboolean{reqspeclogo}
+\newboolean{secondpagelogo}
+\DeclareOption{nologo}{\setboolean{defaultbg}{false}}
+\DeclareOption{draftlogo}{\setboolean{defaultbg}{false}\setboolean{draftbg}{true}}
+\DeclareOption{reqspeclogo}{\setboolean{reqspeclogo}{true}}
+\DeclareOption{secondpagelogo}{\setboolean{defaultbg}{false}\setboolean{secondpagelogo}{true}}
+\ProcessOptions
+
+%% Seitenlayout
+\setlength{\voffset}{-1.5cm}
+\setlength{\hoffset}{-2.5cm}
+\setlength{\topmargin}{0cm}
+\setlength{\headheight}{0.5cm}
+\setlength{\headsep}{1cm}
+\setlength{\topskip}{0pt}
+\setlength{\oddsidemargin}{2cm}
+\setlength{\textwidth}{16.4cm}
+\setlength{\textheight}{25cm}
+\setlength{\footskip}{1cm}
+\setlength{\parindent}{0pt}
+\setlength{\tabcolsep}{0.2cm}
+
+\setlength{\unitlength}{1cm}
+
+\newcommand{\kivitendobgsettings}{%
+ \setlength{\headsep}{2.5cm}
+ \setlength{\textheight}{22.5cm}
+ \setlength{\footskip}{0.9cm}
+}
+
+%% Standardschrift
+\newcommand{\defaultfont}{\fontfamily{cmss}\fontsize{10pt}{12pt}\fontseries{m}\selectfont}
+\renewcommand{\familydefault}{cmss}
+
+%% Checkboxen
+\newsavebox{\checkedbox}
+\savebox{\checkedbox}(0.2,0.4){
+ \put(-0.15,-0.425){$\times$}
+ \put(-0.15,-0.45){$\Box$}
+}
+\newsavebox{\uncheckedbox}
+\savebox{\uncheckedbox}(0.2,0.4){
+ \put(-0.15,-0.45){$\Box$}
+}
+
+%% Farben
+\definecolor{kivitendoorange}{rgb}{1,0.4,0.2}
+\definecolor{kivitendodarkred}{rgb}{0.49,0,0}
+\definecolor{kivitendoyellow}{rgb}{1,1,0.4}
+\definecolor{kivitendobggray}{gray}{0.9}
+\definecolor{kivitendowhite}{gray}{1}
+
+%% Kopf- und Fußzeilen
+\newcommand{\kivitendofirsthead}{}
+\newcommand{\kivitendofirstfoot}{}
+\newcommand{\kivitendosecondhead}{}
+\newcommand{\kivitendosecondfoot}{\centerline{\defaultfont\small Seite \thepage}}
+
+\newcommand{\myhead}{%
+ \ifthenelse{\boolean{defaultbg}}{%
+ \begin{picture}(0,0)
+ \put(-2.025,-28.1){\includegraphics*[width=\paperwidth,keepaspectratio=true]{images/hintergrund_seite1.png}}
+ \end{picture}%
+ }{}%
+ \ifthenelse{\boolean{secondpagelogo}}{%
+ \begin{picture}(0,0)
+ \put(-2.025,-28.1){\includegraphics*[width=\paperwidth,keepaspectratio=true]{images/hintergrund_seite2.png}}
+ \end{picture}%
+ }{}%
+ \ifthenelse{\boolean{draftbg}}{%
+ \begin{picture}(0,0)
+ \put(-2.025,-26.9){\includegraphics*[width=\paperwidth,keepaspectratio=true]{images/draft.png}}
+ \end{picture}%
+ }{}%
+ \ifthenelse{\boolean{reqspeclogo}}{%
+ \begin{picture}(0,0)
+ \put(3,-22){\includegraphics*[width=13cm,keepaspectratio=true]{images/schachfiguren.jpg}}
+ \put(0.275,-4.1){\colorbox{kivitendoorange}{\begin{minipage}[t][4.5cm]{2.5cm}\hspace*{2.5cm}\end{minipage}}}
+ \put(0.275,-8.8){\colorbox{kivitendodarkred}{\begin{minipage}[t][4.5cm]{2.5cm}\hspace*{2.5cm}\end{minipage}}}
+ \put(0.275,-13.5){\colorbox{kivitendoyellow}{\begin{minipage}[t][4.5cm]{2.5cm}\hspace*{2.5cm}\end{minipage}}}
+ \end{picture}%
+ }{}%
+ \kivitendofirsthead
+}
+
+\newcommand{\mysecondhead}{%
+ \ifthenelse{\boolean{defaultbg} \or \boolean{secondpagelogo}}{%
+ \begin{picture}(0,0)
+ \put(-2.025,-28.1){\includegraphics*[width=\paperwidth,keepaspectratio=true]{images/hintergrund_seite2.png}}
+ \end{picture}%
+ }{}%
+ \ifthenelse{\boolean{draftbg}}{%
+ \begin{picture}(0,0)
+ \put(-2.025,-26.9){\includegraphics*[width=\paperwidth,keepaspectratio=true]{images/draft.png}}
+ \end{picture}%
+ }{}%
+ \kivitendosecondhead
+}
+
+\newcommand{\myfoot}{\kivitendofirstfoot}
+\newcommand{\mysecondfoot}{\kivitendosecondfoot}
+
+\renewcommand{\ps@headings}{%
+ \renewcommand{\@oddhead}{\myhead}
+ \renewcommand{\@evenhead}{\@oddhead}%
+ \renewcommand{\@oddfoot}{\myfoot}
+ \renewcommand{\@evenfoot}{\@oddfoot}%
+}
+
+\renewcommand{\ps@plain}{%
+ \renewcommand{\@oddhead}{\mysecondhead}
+ \renewcommand{\@evenhead}{\@oddhead}%
+ \renewcommand{\@oddfoot}{\mysecondfoot}
+ \renewcommand{\@evenfoot}{\@oddfoot}%
+}
+
+\pagestyle{plain}
+\thispagestyle{headings}
+
+% Abschnitte mit Kasten hinterlegt
+
+\newcommand{\reqspecsectionstyle}{%
+\renewcommand{\thesection}{\alph{section}}
+\makeatletter
+\def\section{\@ifstar\unnumberedsection\numberedsection}
+\makeatother
+}
+
+\makeatletter
+\def\numberedsection{\@ifnextchar[%]
+ \numberedsectionwithtwoarguments\numberedsectionwithoneargument}
+\def\unnumberedsection{\@ifnextchar[%]
+ \unnumberedsectionwithtwoarguments\unnumberedsectionwithoneargument}
+\def\numberedsectionwithoneargument#1{\numberedsectionwithtwoarguments[#1]{#1}}
+\def\unnumberedsectionwithoneargument#1{\unnumberedsectionwithtwoarguments[#1]{#1}}
+\def\numberedsectionwithtwoarguments[#1]#2{%
+ \ifhmode\par\fi
+ \removelastskip
+ \vskip 3ex\goodbreak
+ \refstepcounter{section}%
+ \noindent
+ \begingroup
+ \leavevmode\Large\bfseries\raggedright
+ \begin{picture}(0,0)
+ \put(0,0){\colorbox{kivitendoorange}{\parbox{0.7cm}{\hspace*{0.7cm}\\\vspace*{0.2cm}}}}
+ \end{picture}%
+ \hspace*{0.3cm}\textcolor{white}{\thesection{}.}%
+ \quad%
+ #2
+ \par
+ \endgroup
+ \vskip 2ex\nobreak
+ \addcontentsline{toc}{section}{\protect\numberline{\thesection{}.}#1}%
+ }
+\def\unnumberedsectionwithtwoarguments[#1]#2{%
+ \ifhmode\par\fi
+ \removelastskip
+ \vskip 3ex\goodbreak
+ \noindent
+ \begingroup
+ \leavevmode\Large\bfseries\raggedright
+ \leavevmode\Large\bfseries\raggedright
+ #2
+ \par
+ \endgroup
+ \vskip 2ex\nobreak%
+}
+\makeatother
--- /dev/null
+% config: use-template-toolkit=1
+% config: tag-style=$( )$
+$( USE KiviLatex )$
+$( USE P )$
+$( SET customer = letter.customer_vendor )$
+\documentclass[paper=a4,fontsize=10pt]{scrartcl}
+\usepackage{kiviletter}
+$( KiviLatex.required_packages_for_html )$
+
+% Variablen, die in settings verwendet werden
+\newcommand{\lxlangcode}{$(template_meta.language.template_code)$}
+\newcommand{\lxmedia}{$(template_meta.media)$}
+\newcommand{\lxcurrency}{}
+\newcommand{\kivicompany}{$(employee_company)$}
+
+% settings: Einstellungen, Logo, Briefpapier, Kopfzeile, Fusszeile
+\input{insettings.tex}
+
+% laufende Kopfzeile:
+%\ourhead{}{}{$( KiviLatex.filter(letter.subject) )$}{$( KiviLatex.filter(letter.letternumber) )$}{$( KiviLatex.filter(letter.date.to_kivitendo) )$}
+\ourhead{}{}{}{}{}
+
+\begin{document}
+
+\setkomavar{date}{$( KiviLatex.filter(letter.date.to_kivitendo) )$}
+
+$( IF letter.reference )$
+\setkomavar*{yourref}{\ihrzeichen}
+\setkomavar{yourref}{$( KiviLatex.filter(letter.reference) )$}
+$( END )$
+
+$( IF letter.subject )$
+\setkomavar{subject}{$( KiviLatex.filter(letter.subject) )$}
+$( END )$
+
+\begin{letter}{
+ $( KiviLatex.filter(customer.name) )$\strut\\
+ $( KiviLatex.filter(letter.contact.formal_greeting) )$\strut\\
+ $( KiviLatex.filter(customer.street) )$\strut\\
+ $( KiviLatex.filter(customer.zipcode) )$ $( KiviLatex.filter(customer.city) )$\strut\\
+ $( KiviLatex.filter(customer.country) )$
+ }
+
+\opening{$( KiviLatex.filter(letter.greeting) )$}
+
+
+$( KiviLatex.filter_html(letter.body) )$
+
+
+\closing{\gruesse}
+
+\end{letter}
+
+\end{document}
--- /dev/null
+
+<body bgcolor=ffffff>
+
+<table width=100%>
+ <tr>
+ <td width=10> </td>
+
+ <td>
+ <table width=100%>
+ <tr>
+ <td>
+ <h4>
+ <%company%>
+ <br><%address%>
+ </h4>
+ </td>
+
+ <th><img src=http://localhost/lx-erp/lx-office-erp.png border=0 width=64 height=58></th>
+
+ <td align=right>
+ <h4>
+ Tel: <%tel%>
+ <br>Fax: <%fax%>
+ </h4>
+ </td>
+ </tr>
+
+ <tr>
+ <th colspan=3>
+ <h4>S A M M E L L I S T E</h4>
+ </th>
+ </tr>
+ </table>
+ </td>
+ </tr>
+
+ <tr>
+ <td> </td>
+
+ <td>
+ <table width=100% callspacing=0 cellpadding=0>
+ <tr bgcolor=000000>
+ <th width=50% align=left><font color=ffffff>Lieferanschrift:</th>
+ <th width=50%> </th>
+ </tr>
+
+ <tr valign=top>
+ <td><%shiptoname%>
+ <br><%shiptostreet%>
+ <br><%shiptozipcode%>
+ <br><%shiptocity%>
+ <br><%shiptocountry%>
+ </td>
+
+ <td>
+ <%if shiptocontact%>
+ <br>Kontakt: <%shiptocontact%>
+ <%end shiptocontact%>
+
+ <%if shiptophone%>
+ <br>Tel: <%shiptophone%>
+ <%end shiptophone%>
+
+ <%if shiptofax%>
+ <br>Fax: <%shiptofax%>
+ <%end shiptofax%>
+
+ <%shiptoemail%>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+
+ <tr height=5></tr>
+
+ <tr>
+ <td> </td>
+
+ <td>
+ <table width=100% border=1>
+ <tr>
+ <th width=17% align=left>BestellNr. #</th>
+ <th width=17% align=left>Datum</th>
+ <th width=17% align=left nowrap>Kontakt</th>
+ <%if warehouse%>
+ <th width=17% align=left>Lager</th>
+ <%end warehouse%>
+ <th width=17% align=left>Versandort</th>
+ <th width=15% align=left>Transportmittel</th>
+ </tr>
+
+ <tr>
+ <td><%ordnumber%> </td>
+
+ <%if shippingdate%>
+ <td><%shippingdate%></td>
+ <%end shippingdate%>
+
+ <%if not shippingdate%>
+ <td><%orddate%></td>
+ <%end shippingdate%>
+
+ <td><%employee%> </td>
+
+ <%if warehouse%>
+ <td><%warehouse%> </td>
+ <%end warehouse%>
+
+ <td><%shippingpoint%> </td>
+ <td><%shipvia%> </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+
+ <tr>
+ <td> </td>
+
+ <td>
+ <table width=100%>
+ <tr bgcolor=000000>
+ <th align=left><font color=ffffff>Pos</th>
+ <th align=left><font color=ffffff>Nummer</th>
+ <th align=left><font color=ffffff>Beschreibung</th>
+ <th><font color=ffffff>Menge</th>
+ <th><font color=ffffff>geliefert</th>
+ <th> </th>
+ <th><font color=ffffff>Lagerplatz</th>
+ </tr>
+
+ <%foreach number%>
+ <tr valign=top>
+ <td><%runningnumber%>
+ <td><%number%></td>
+ <td><%description%></td>
+ <td align=right><%qty%></td>
+ <td align=right>[ ]</td>
+ <td><%unit%></td>
+ <td align=right><%bin%></td>
+ </tr>
+ <%end number%>
+ </table>
+ </td>
+ </tr>
+
+ <tr>
+ <td> </td>
+
+ <td><hr noshade></td>
+ </tr>
+
+</table>
+
--- /dev/null
+\documentclass[twoside]{scrartcl}
+\usepackage[frame]{xy}
+\usepackage{tabularx}
+\usepackage[utf8]{inputenc}
+\usepackage{graphicx}
+\setlength{\voffset}{0.5cm}
+\setlength{\hoffset}{-2.0cm}
+\setlength{\topmargin}{0cm}
+\setlength{\headheight}{0.5cm}
+\setlength{\headsep}{1cm}
+\setlength{\topskip}{0pt}
+\setlength{\oddsidemargin}{1.0cm}
+\setlength{\evensidemargin}{1.0cm}
+\setlength{\textwidth}{17cm}
+\setlength{\textheight}{24.7cm}
+\setlength{\footskip}{1cm}
+\setlength{\parindent}{0pt}
+\renewcommand{\baselinestretch}{1}
+
+\begin{document}
+
+\newlength{\descrwidth}\setlength{\descrwidth}{9cm}
+\fontfamily{cmss}\fontsize{10pt}{12pt}\selectfont
+
+\pagestyle{myheadings}
+\thispagestyle{empty}
+
+\vspace*{-1.3cm}
+
+\parbox{\textwidth}{
+ \parbox[b]{.42\textwidth}{
+ <%company%>
+
+ <%address%>
+ }\hfill
+ \begin{tabular}[b]{rr@{}}
+ Tel & <%tel%>\\
+ Fax & <%fax%>
+ \end{tabular}
+
+ \rule[1.5ex]{\textwidth}{0.5pt}
+}
+
+\vspace*{0.5cm}
+
+\parbox[t]{1cm}{\hfill}
+\parbox[t]{.5\textwidth}{
+ \textbf{Lieferanschrift}
+} \hfill
+
+\vspace{0.7cm}
+
+\parbox[t]{1cm}{\hfill}
+\parbox[t]{.5\textwidth}{
+
+<%shiptoname%> \\
+<%shiptostreet%> \\
+<%shiptozipcode%> \\
+<%shiptocity%> \\
+<%shiptocountry%>
+}
+\parbox[t]{.4\textwidth}{
+ <%shiptocontact%>
+
+ <%if shiptophone%>
+ Tel: <%shiptophone%>
+ <%end shiptophone%>
+
+ <%if shiptofax%>
+ Fax: <%shiptofax%>
+ <%end shiptofax%>
+
+ <%shiptoemail%>
+}
+\hfill
+
+\vspace{1cm}
+
+\textbf{S A M M E L L I S T E}
+\hfill
+
+\vspace{1cm}
+
+\begin{tabularx}{\textwidth}{*{6}{|X}|} \hline
+ \textbf{BestellNr. \#} & \textbf{Datum} & \textbf{Kontakt}
+ <%if warehouse%>
+ & \textbf{Lager}
+ <%end warehouse%>
+ & \textbf{Lagerplatz} & \textbf{Lieferung mit} \\ [0.5em]
+ \hline
+ <%ordnumber%>
+ <%if shippingdate%>
+ & <%shippingdate%>
+ <%end shippingdate%>
+ <%if not shippingdate%>
+ & <%orddate%>
+ <%end shippingdate%>
+ & <%employee%>
+ <%if warehouse%>
+ & <%warehouse%>
+ <%end warehouse%>
+ & <%shippingpoint%> & <%shipvia%> \\
+ \hline
+\end{tabularx}
+
+\vspace{1cm}
+
+\begin{tabular*}{\textwidth}{@{}rlp{\descrwidth}@{\extracolsep\fill}rcll@{}}
+ \textbf{Pos} & \textbf{Nummer} & \textbf{Beschreibung} &
+ \textbf{Menge} & \textbf{Lagerausgang} & & \textbf{Lagerplatz} \\
+<%foreach number%>
+ <%runningnumber%> & <%number%> & <%description%> &
+ <%qty%> & [\hspace{1cm}] & <%unit%> & <%bin%> \\
+<%end number%>
+\end{tabular*}
+
+
+\parbox{\textwidth}{
+\rule{\textwidth}{2pt}
+}
+
+\end{document}
+
--- /dev/null
+\documentclass[paper=a4,fontsize=10pt]{scrartcl}
+\usepackage{kiviletter}
+
+
+% Variablen, die in settings verwendet werden
+\newcommand{\lxlangcode} {<%template_meta.language.template_code%>}
+\newcommand{\lxmedia} {<%media%>}
+\newcommand{\lxcurrency} {<%currency%>}
+\newcommand{\kivicompany} {<%employee_company%>}
+
+% settings: Einstellungen, Logo, Briefpapier, Kopfzeile, Fusszeile
+\input{insettings.tex}
+
+
+% laufende Kopfzeile:
+\ourhead{\kundennummer}{<%customernumber%>}{\proformarechnung}{<%ordnumber%>}{<%invdate%>}
+
+
+\begin{document}
+
+\setkomavar{title}{
+ \proformarechnung~
+ \nr ~<%ordnumber%>
+}
+\setkomavar*{date}{\datum}
+
+\setkomavar{date}{<%orddate%>}
+\setkomavar{customer}{<%customernumber%>}
+<%if cusordnumber%>
+ \setkomavar*{yourref}{\ihreBestellnummer}
+ \setkomavar{yourref}{<%cusordnumber%>}
+<%end if%>
+<%if quonumber%>\setkomavar{quote}{<%quonumber%>}<%end if%>
+\setkomavar{fromname}{<%employee_name%>}
+\setkomavar{fromphone}{<%employee_tel%>}
+\setkomavar{fromemail}{<%employee_email%>}
+\setkomavar{transaction}{<%transaction_description%>}
+
+<%if shiptoname%>
+\makeatletter
+\begin{lrbox}\shippingAddressBox
+ \parbox{\useplength{toaddrwidth}}{
+ \backaddr@format{\scriptsize\usekomafont{backaddress}%
+ \strut abweichende Lieferadresse
+ }
+ \par\smallskip
+ \setlength{\parskip}{\z@}
+ \par
+ \normalsize
+ <%shiptoname%>\par
+ <%if shiptocontact%> <%shiptocontact%><%end if%>\par
+ <%shiptodepartment_1%>\par
+ <%shiptodepartment_2%>\par
+ <%shiptostreet%>\par
+ <%shiptozipcode%> <%shiptocity%>
+ }
+\end{lrbox}
+\makeatother
+<%end if%>
+
+\begin{letter}{
+ <%name%>\strut\\
+ <%if department_1%><%department_1%>\\<%end if%>
+ <%if department_2%><%department_2%>\\<%end if%>
+ <%cp_givenname%> <%cp_name%>\strut\\
+ <%street%>\strut\\
+ <%zipcode%> <%city%>\strut\\
+ <%country%> \strut
+ }
+
+% Bei Kontaktperson Anrede nach Geschlecht unterscheiden.
+% Bei natürlichen Personen persönliche Anrede, sonst allgemeine Anrede.
+\opening{
+ \ifstr{<%cp_name%>}{}
+ {<%if natural_person%><%greeting%> <%name%>,<%else%>\anrede<%end if%>}
+ {
+ \ifstr{<%cp_gender%>}{f}
+ {\anredefrau}
+ {\anredeherr}
+ <%cp_title%> <%cp_name%>,
+ }
+ }
+ \thispagestyle{kivitendo.letter.first}
+
+\auftragsformel
+
+\begin{PricingTabular*}
+\FakeTable{
+ <%foreach number%>%
+ <%runningnumber%> &%
+ <%number%> &%
+ \textbf{<%description%>}%
+ <%if longdescription%>\ExtraDescription{<%longdescription%>}<%end longdescription%>%
+ <%if reqdate%>\ExtraDescription{\lieferdatum: <%reqdate%>}<%end reqdate%>
+ <%if serialnumber%>\ExtraDescription{\seriennummer: <%serialnumber%>}<%end serialnumber%>%
+ <%if ean%>\ExtraDescription{\ean: <%ean%>}<%end ean%>%
+ <%if projectnumber%>\ExtraDescription{\projektnummer: <%projectnumber%>}<%end projectnumber%>%
+ &%
+ <%qty%> <%unit%> &%
+ <%sellprice%>&%
+ \ifstr{<%p_discount%>}{0}{}{\sffamily\scriptsize{(-<%p_discount%>\,\%)}}%
+ <%linetotal%>\tabularnewline
+ <%end number%>
+}
+% Tabellenende letzte Seite
+ \begin{PricingTotal}
+ % Tabellenende letzte Seite
+ \nettobetrag & <%subtotal%>\\
+ <%foreach tax%>
+ <%taxdescription%> & <%tax%>\\
+ <%end tax%>
+ \bfseries\schlussbetrag & \bfseries <%ordtotal%>\\
+\end{PricingTotal}
+\end{PricingTabular*}
+
+<%if notes%>
+<%notes%>
+\medskip
+<%end if%>
+
+<%if reqdate%>
+\lieferungErfolgtAm ~<%reqdate%>. \\
+<%end if%>
+
+\textit{\auftragpruefen}
+
+\closing{\gruesse}
+
+\end{letter}
+
+\end{document}
+
--- /dev/null
+\documentclass[paper=a4,fontsize=10pt]{scrartcl}
+\usepackage{kiviletter}
+
+
+% Variablen, die in settings verwendet werden
+\newcommand{\lxlangcode} {<%template_meta.language.template_code%>}
+\newcommand{\lxmedia} {<%media%>}
+\newcommand{\lxcurrency} {<%currency%>}
+\newcommand{\kivicompany} {<%employee_company%>}
+
+% settings: Einstellungen, Logo, Briefpapier, Kopfzeile, Fusszeile
+\input{insettings.tex}
+
+
+% laufende Kopfzeile:
+\ourhead{}{}{\einkaufslieferschein}{<%donumber%>}{<%dodate%>}
+
+
+\begin{document}
+
+\begin{minipage}[t]{8cm}
+ \scriptsize
+
+ {\color{gray}\underline{\firma\ $\cdot$ \strasse\ $\cdot$ \ort}}
+ \normalsize
+
+ \vspace*{0.3cm}
+
+ <%name%>
+
+ <%if department_1%><%department_1%><%end if%>
+
+ <%if department_2%><%department_2%><%end if%>
+
+ <%cp_givenname%> <%cp_name%>
+
+ <%street%>
+
+ ~
+
+ <%zipcode%> <%city%>
+
+ <%country%>
+\end{minipage}
+\hfill
+\begin{minipage}{6cm}
+ \rightline{\LARGE\textbf{\textit{\einkaufslieferschein}}} \vspace*{0.2cm}
+ \rightline{\large\textbf{\textit{\nr ~<%donumber%>
+ }}} \vspace*{0.2cm}
+
+ \datum:\hfill <%dodate%>
+
+ <%if cusordnumber%>\unsereBestellnummer:\hfill <%cusordnumber%><%end if%>
+
+ <%if ordnumber%>\auftragsnummer:\hfill <%ordnumber%><%end if%>
+
+ \ansprechpartner:\hfill <%employee_name%>
+
+ <%if globalprojectnumber%> \projektnummer:\hfill <%globalprojectnumber%> <%end globalprojectnumber%>
+\end{minipage}
+
+<%if shiptoname%>
+ \vspace{0.8cm}
+ \scriptsize \underline{\abweichendeLieferadresse:}\\
+ \normalsize <%shiptoname%>
+
+ <%if shiptocontact%> <%shiptocontact%><%end if%>
+
+ <%shiptodepartment_1%>
+
+ <%shiptodepartment_2%>
+
+ <%shiptostreet%>
+
+ <%shiptozipcode%> <%shiptocity%>
+<%end if%>
+\vspace*{1.5cm}
+
+
+%
+% - longtable kann innerhalb der Tabelle umbrechen
+% - da der Umbruch nicht von Lx-Office kontrolliert wird, kann man keinen
+% Übertrag machen
+% - Innerhalb des Langtextes <%longdescription%> wird nicht umgebrochen.
+% Falls das gewünscht ist, \\ mit \renewcommand umschreiben (siehe dazu:
+% http://www.lx-office.org/uploads/media/Lx-Office_Anwendertreffen_LaTeX-Druckvorlagen-31.01.2011_01.pdf)
+%
+\begin{SimpleTabular}
+% eigentliche Tabelle
+<%foreach number%>
+ <%runningnumber%> &
+ <%qty%> <%unit%> &
+ \textbf{<%description%>} \\* % kein Umbruch nach der ersten Zeile, damit Beschreibung und Langtext nicht getrennt werden
+
+ <%if longdescription%> && \scriptsize <%longdescription%>\\<%end longdescription%>
+ <%if projectnumber%> && \scriptsize \projektnummer: <%projectnumber%>\\<%end projectnumber%>
+ <%if reqdate%> && \scriptsize \lieferdatum: <%reqdate%>\\<%end reqdate%>
+ <%if serialnumber%> && \scriptsize \seriennummer: <%serialnumber%>\\<%end serialnumber%>
+ <%if ean%> && \scriptsize \ean: <%ean%>\\<%end ean%>
+ <%foreach si_number%><%if si_chargenumber%> && \scriptsize \charge: <%si_chargenumber%> <%if si_bestbefore%> \mhd: <%si_bestbefore%><%end if%> <%si_qty%>~<%si_unit%><%end si_chargenumber%>\\<%end si_number%>
+
+ \\[-0.8em]
+<%end number%>
+\end{SimpleTabular}
+
+\vspace{0.2cm}
+
+<%if notes%>
+ \vspace{5mm}
+ <%notes%>
+ \vspace{5mm}
+<%end if%>
+
+<%if delivery_term%>
+ \lieferung ~<%delivery_term.description_long%>\\
+<%end delivery_term%>
+
+\end{document}
--- /dev/null
+
+<body bgcolor=ffffff>
+
+<table width=100%>
+<tr valign=bottom>
+ <td width=10> </td>
+ <td>
+
+ <table width=100%>
+ <tr>
+ <td>
+ <h4>
+ <%company%>
+ <br><%address%>
+ </h4>
+ </td>
+
+ <td align=right>
+ <h4>
+ Telefon <%tel%>
+ <br>Telefax <%fax%>
+ </h4>
+ </td>
+ </tr>
+
+ <tr>
+ <th colspan=3>
+ <h4>B E S T E L L U N G</h4>
+ </th>
+ </tr>
+
+ </table>
+
+
+ <table width=100% callspacing=0 cellpadding=0>
+
+ <tr>
+ <td align=right>
+ <table>
+ <tr>
+ <th align=right>Bestellungsdatum</th><td width=10> </td><td><%orddate%></td>
+ </tr>
+
+ <tr>
+ <th align=right>Lieferbar bis</th><td width=10> </td><td><%reqdate%></td>
+ </tr>
+
+ <tr>
+ <th align=right>Bestellnummer</th><td> </td><td><%ordnumber%></td></tr>
+ </tr>
+
+ <tr>
+ <td> </td>
+ </tr>
+ </td>
+ </table>
+ </tr>
+
+ <tr>
+ <td>
+ <table width=100%>
+ <tr bgcolor=000000>
+ <th align=left><font color=ffffff>An:</th>
+ </tr>
+
+ <tr>
+ <td><%name%>
+ <br><%street%>
+ <br><%zipcode%>
+ <br><%city%>
+ <br><%country%>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+
+ <tr>
+ <td> </td>
+ </tr>
+
+ <tr>
+ <td>
+ <table width=100%>
+ <tr bgcolor=000000>
+<!-- <th align=right><font color=ffffff>No.</th> -->
+ <th align=left><font color=ffffff>Nummer</th>
+ <th align=left><font color=ffffff>Artikel</th>
+ <th><font color=ffffff>Anz</th>
+ <th> </th>
+ <th><font color=ffffff>Preis</th>
+ <th><font color=ffffff>Total</th>
+ </tr>
+
+<%foreach number%>
+ <tr valign=top>
+<!-- <td align=right><%runningnumber%>.</td>
+adjust the colspan if you include this to shift subtotal one to the right
+-->
+ <td><%number%></td>
+ <td><%description%></td>
+ <td align=right><%qty%></td>
+ <td><%unit%></td>
+ <td align=right><%sellprice%></td>
+ <td align=right><%linetotal%></td>
+ </tr>
+<%end number%>
+
+ <tr>
+ <td colspan=6><hr noshade></td>
+ </tr>
+
+ <tr>
+ <th colspan=4 align=right>Zwischensumme</th>
+ <td colspan=2 align=right><%subtotal%></td>
+ </tr>
+
+<%foreach tax%>
+ <tr>
+ <th colspan=4 align=right><%taxdescription%> @ <%taxrate%> %</th>
+ <td colspan=2 align=right><%tax%></td>
+ </tr>
+<%end tax%>
+
+ <tr>
+ <td colspan=2> </td>
+ <td colspan=4><hr noshade></td>
+ </tr>
+
+ <tr>
+ <td colspan=2>Netto <b><%terms%></b> Tage</td>
+ <th colspan=2 align=right>Total</th>
+ <th colspan=2 align=right><%total%></th>
+ </tr>
+
+ <tr>
+ <td> </td>
+ </tr>
+
+ </table>
+ </td>
+ </tr>
+
+<tr>
+ <td>
+ <table width=100%>
+ <tr valign=top>
+<%if notes%>
+ <td>Bemerkungen</td>
+ <td><%notes%></td>
+<%end notes%>
+ <td align=right>
+ Alle Preise in <b><%currency%></b>
+ <br><%shippingpoint%>
+ </td>
+ </tr>
+
+ </table>
+ </td>
+</tr>
+
+<tr><td> </td></tr>
+
+<tr>
+ <td>
+ <table width=100%>
+ <tr valign=top>
+ <td><font size=-3>
+
+ </font>
+ </td>
+ <td width=150>
+ X <hr noshade>
+ </td>
+ </tr>
+ </table>
+ </td>
+</tr>
+
+</table>
+
+</td>
+</tr>
+</table>
+
+</body>
+</html>
+
--- /dev/null
+\documentclass[paper=a4,fontsize=10pt]{scrartcl}
+\usepackage{kiviletter}
+
+
+% Variablen, die in settings verwendet werden
+\newcommand{\lxlangcode} {<%template_meta.language.template_code%>}
+\newcommand{\lxmedia} {<%media%>}
+\newcommand{\lxcurrency} {<%currency%>}
+\newcommand{\kivicompany} {<%employee_company%>}
+
+% settings: Einstellungen, Logo, Briefpapier, Kopfzeile, Fusszeile
+\input{insettings.tex}
+
+
+% laufende Kopfzeile:
+\ourhead{}{}{\bestellung}{<%ordnumber%>}{<%orddate%>}
+
+
+\begin{document}
+
+\setkomavar{title}{
+ \bestellung~
+ \nr~<%ordnumber%>
+}
+\setkomavar*{date}{\datum}
+\setkomavar{date}{<%orddate%>}
+<%if cusordnumber%>
+\setkomavar*{yourref}{\unsereBestellnummer}
+\setkomavar{yourref}{<%cusordnumber%>}
+<%end if%>
+<%if quonumber%>\setkomavar{quote}{<%quonumber%>}<%end if%>
+\setkomavar{fromname}{<%employee_name%>}
+\setkomavar{fromphone}{<%employee_tel%>}
+\setkomavar{fromemail}{<%employee_email%>}
+\setkomavar{transaction}{<%transaction_description%>}
+
+<%if shiptoname%>
+\makeatletter
+\begin{lrbox}\shippingAddressBox
+ \parbox{\useplength{toaddrwidth}}{
+ \backaddr@format{\scriptsize\usekomafont{backaddress}%
+ \strut abweichende Lieferadresse
+ }
+ \par\smallskip
+ \setlength{\parskip}{\z@}
+ \par
+ \normalsize
+ <%shiptoname%>\par
+ <%if shiptocontact%> <%shiptocontact%><%end if%>\par
+ <%shiptodepartment_1%>\par
+ <%shiptodepartment_2%>\par
+ <%shiptostreet%>\par
+ <%shiptozipcode%> <%shiptocity%>
+ }
+\end{lrbox}
+\makeatother
+<%end if%>
+
+
+\begin{letter}{
+ <%name%>\strut\\
+ <%if department_1%><%department_1%>\\<%end if%>
+ <%if department_2%><%department_2%>\\<%end if%>
+ <%cp_givenname%> <%cp_name%>\strut\\
+ <%street%>\strut\\
+ <%zipcode%> <%city%>\strut\\
+ <%country%> \strut
+ }
+
+% Bei Kontaktperson Anrede nach Geschlecht unterscheiden.
+% Bei natürlichen Personen persönliche Anrede, sonst allgemeine Anrede.
+\opening{
+ \ifstr{<%cp_name%>}{}
+ {<%if natural_person%><%greeting%> <%name%>,<%else%>\anrede<%end if%>}
+ {
+ \ifstr{<%cp_gender%>}{f}
+ {\anredefrau}
+ {\anredeherr}
+ <%cp_title%> <%cp_name%>,
+ }
+}
+\thispagestyle{kivitendo.letter.first}
+
+\bestellformel
+
+\begin{PricingTabular*}
+ % eigentliche Tabelle
+ \FakeTable{
+ <%foreach number%>%
+ <%runningnumber%> &%
+ <%number%> &%
+ \textbf{<%description%>}%
+ <%if longdescription%>\ExtraDescription{<%longdescription%>}<%end longdescription%>%
+ <%if reqdate%>\ExtraDescription{\lieferdatum: <%reqdate%>}<%end reqdate%>%
+ <%if serialnumber%>\ExtraDescription{\seriennummer: <%serialnumber%>}<%end serialnumber%>%
+ <%if ean%>\ExtraDescription{\ean: <%ean%>}<%end ean%>%
+ <%if projectnumber%>\ExtraDescription{\projektnummer: <%projectnumber%>}<%end projectnumber%>%
+ &%
+ <%qty%> <%unit%> &%
+ <%sellprice%>&%
+ \ifstr{<%p_discount%>}{0}{}{\sffamily\scriptsize{(-<%p_discount%>\,\%)}}%
+ <%linetotal%>\tabularnewline
+ <%end number%>
+ }
+ \begin{PricingTotal}
+ % Tabellenende letzte Seite
+ \nettobetrag & <%subtotal%>\\
+ <%foreach tax%>
+ <%taxdescription%> & <%tax%>\\
+ <%end tax%>
+ \bfseries\schlussbetrag & \bfseries <%ordtotal%>\\
+ \end{PricingTotal}
+\end{PricingTabular*}
+
+<%if notes%>
+<%notes%>
+\medskip
+<%end if%>
+
+<%if delivery_term%>
+ \lieferung ~<%delivery_term.description_long%>\\
+<%end delivery_term%>
+
+\closing{\gruesse}
+\end{letter}
+
+\end{document}
--- /dev/null
+\documentclass[twoside]{scrartcl}
+\usepackage[frame]{xy}
+\usepackage{tabularx}
+\usepackage[utf8]{inputenc}
+\setlength{\voffset}{0.4cm}
+\setlength{\hoffset}{-2.0cm}
+\setlength{\topmargin}{0cm}
+\setlength{\headheight}{0.0cm}
+\setlength{\headsep}{1cm}
+\setlength{\topskip}{0pt}
+\setlength{\oddsidemargin}{1.0cm}
+\setlength{\evensidemargin}{1.0cm}
+\setlength{\textwidth}{17cm}
+\setlength{\textheight}{24.5cm}
+\setlength{\footskip}{1cm}
+\setlength{\parindent}{0pt}
+\renewcommand{\baselinestretch}{1}
+\begin{document}
+
+
+\fontfamily{cmss}\fontsize{9pt}{9pt}\selectfont
+
+\parbox[t]{12cm}{
+ <%company%>
+
+ <%address%>}
+\hfill
+\parbox[t]{6cm}{\hfill <%source%>}
+
+\vspace*{0.6cm}
+
+<%text_amount%> \dotfill <%decimal%>/100 \makebox[0.5cm]{\hfill}
+
+\vspace{0.5cm}
+
+\hfill <%datepaid%> \makebox[2cm]{\hfill} <%amount%>
+
+\vspace{0.5cm}
+
+<%name%>
+
+<%street%>
+
+<%zipcode%>
+
+<%city%>
+
+<%country%>
+
+\vspace{2.8cm}
+
+<%company%>
+
+\vspace{0.5cm}
+
+<%name%> \hfill <%datepaid%> \hfill <%source%>
+
+\vspace{0.5cm}
+\begin{tabularx}{\textwidth}{lXrr@{}}
+\textbf{Rechnung} & \textbf{Ausgestellt}
+ & \textbf{Fällig} & \textbf{Verrechnet} \\
+<%foreach invnumber%>
+<%invnumber%> & <%invdate%> \dotfill
+ & <%due%> & <%paid%> \\
+<%end invnumber%>
+\end{tabularx}
+
+\vfill
+
+\end{document}
+
--- /dev/null
+
+<body bgcolor=ffffff>
+
+<table width=100%>
+<tr valign=bottom>
+ <td width=10> </td>
+ <td>
+
+ <table width=100%>
+ <tr>
+ <td>
+ <h4>
+ <%company%>
+ <br><%address%>
+ </h4>
+ </td>
+
+ <td><img src=http://localhost/lx-erp/lx-office-erp.png border=0 width=64 height=58>
+ </td>
+
+ <td align=right>
+ <h4>
+ Tel: <%tel%>
+ <br>Fax: <%fax%>
+ </h4>
+ </td>
+ </tr>
+
+ <tr>
+ <th colspan=3>
+ <h4>A N F R A G E</h4>
+ </th>
+ </tr>
+
+ </table>
+
+
+ <table width=100% callspacing=0 cellpadding=0>
+
+ <tr>
+ <td>
+ <table width=100%>
+ <tr bgcolor=000000>
+ <th align=left width=50%><font color=ffffff>Rechnungsanschrift:</th>
+ <th align=left width=50%><font color=ffffff>Lieferanschrift:</th>
+ </tr>
+
+ <tr valign=top>
+ <td><%name%>
+ <br><%street%>
+ <br><%zipcode%>
+ <br><%city%>
+ <br><%country%>
+<br>
+<%if contact%>
+<br>Kontakt: <%contact%>
+<%end contact%>
+<%if vendorphone%>
+<br>Tel: <%vendorphone%>
+<%end vendorphone%>
+<%if vendorfax%>
+<br>Fax: <%vendorfax%>
+<%end vendorfax%>
+ </td>
+
+ <td><%shiptoname%>
+ <br><%shiptostreet%>
+ <br><%shiptozipcode%>
+ <br><%shiptocity%>
+ <br><%shiptocountry%>
+<br>
+<%if shiptocontact%>
+<br>Kontakt: <%shiptocontact%>
+<%end shiptocontact%>
+<%if shiptophone%>
+<br>Tel: <%shiptophone%>
+<%end shiptophone%>
+<%if shiptofax%>
+<br>Fax: <%shiptofax%>
+<%end shiptofax%>
+ </tr>
+ </table>
+ </td>
+ </tr>
+
+ <tr><td> </td></tr>
+
+ <tr>
+ <td colspan=2>
+ <table width=100% border=1>
+ <tr>
+ <th width=17% align=left>AnfrageNr. #</th>
+ <th width=17% align=left>Datum</th>
+ <th width=17% align=left>Erforderlich am</th>
+ <th width=17% align=left>Kontakt</th>
+ <th width=17% align=left>Lagerplatz</th>
+ <th width=15% align=left>Versand mit:</th>
+ </tr>
+
+ <tr>
+ <td><%quonumber%></td>
+ <td><%quodate%></td>
+ <td><%reqdate%></td>
+ <td><%employee%></td>
+ <td><%shippingpoint%></td>
+ <td><%shipvia%></td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+
+ <tr height="10"></tr>
+
+ <tr>
+ <td>Bitte teilen Sie uns Preise und Lieferzeit für folgende Artikel mit:</td>
+ </tr>
+
+ <tr height="10"></tr>
+
+ <tr>
+ <td>
+ <table width=100%>
+ <tr>
+<!-- <th align=right>No.</th> -->
+ <th align=left>ArtNr.</th>
+ <th align=left>Beschreibung</th>
+ <th>Menge</th>
+ <th> </th>
+ <th>Lieferung</th>
+ <th>Stückpreis</th>
+ <th>Gesamtpreis</th>
+ </tr>
+
+<%foreach number%>
+ <tr valign=top>
+<!-- <td align=right><%runningnumber%>.</td>
+other per line item variables available <%reqdate%>
+adjust the colspan if you include this to shift subtotal one to the right
+-->
+ <td><%number%></td>
+ <td><%description%></td>
+ <td align=right><%qty%></td>
+ <td><%unit%></td>
+
+ </tr>
+<%end number%>
+
+ <tr>
+ <td colspan=7><hr noshade></td>
+ </tr>
+
+ </table>
+ </td>
+ </tr>
+
+<tr>
+ <td>
+ <table width=100%>
+<%if notes%>
+ <tr valign=top>
+ <td>Bemerkungen</td>
+ <td><%notes%></td>
+ </tr>
+<%end notes%>
+
+ </table>
+ </td>
+</tr>
+
+<tr><td> </td></tr>
+
+<tr>
+ <td>
+ <table width=100%>
+ <tr valign=top>
+ <td width=70%> </td>
+
+ <td width=30%>
+ X <hr noshade>
+ </td>
+ </tr>
+ </table>
+ </td>
+</tr>
+
+</table>
+
+</td>
+</tr>
+</table>
+
+</body>
+</html>
+
--- /dev/null
+\documentclass[paper=a4,fontsize=10pt]{scrartcl}
+\usepackage{kiviletter}
+
+
+% Variablen, die in settings verwendet werden
+\newcommand{\lxlangcode} {<%template_meta.language.template_code%>}
+\newcommand{\lxmedia} {<%media%>}
+\newcommand{\lxcurrency} {<%currency%>}
+\newcommand{\kivicompany} {<%employee_company%>}
+
+% settings: Einstellungen, Logo, Briefpapier, Kopfzeile, Fusszeile
+\input{insettings.tex}
+
+
+% laufende Kopfzeile:
+\ourhead{}{}{\anfrage}{<%quonumber%>}{<%transdate%>}
+
+
+\begin{document}
+\setkomavar*{date}{\datum}
+
+\setkomavar{date}{<%transdate%>}
+\setkomavar{customer}{<%customernumber%>}
+\setkomavar{fromname}{<%employee_name%>}
+\setkomavar{fromphone}{<%employee_tel%>}
+\setkomavar{fromemail}{<%employee_email%>}
+\setkomavar{title}{
+ \anfrage~
+ \nr ~<%quonumber%>
+}
+<%if globalprojectnumber%>
+ \setkomavar{projectID}{<%globalprojectnumber%>}
+<%end globalprojectnumber%>
+\setkomavar{transaction}{<%transaction_description%>}
+
+\begin{letter}{
+ <%name%>\strut\\
+ <%if department_1%><%department_1%>\\<%end if%>
+ <%if department_2%><%department_2%>\\<%end if%>
+ <%cp_givenname%> <%cp_name%>\strut\\
+ <%street%>\strut\\
+ <%zipcode%> <%city%>\strut\\
+ <%country%> \strut
+ }
+
+% Bei Kontaktperson Anrede nach Geschlecht unterscheiden.
+% Bei natürlichen Personen persönliche Anrede, sonst allgemeine Anrede.
+\opening{
+ \ifstr{<%cp_name%>}{}
+ {<%if natural_person%><%greeting%> <%name%>,<%else%>\anrede<%end if%>}
+ {
+ \ifstr{<%cp_gender%>}{f}
+ {\anredefrau}
+ {\anredeherr}
+ <%cp_title%> <%cp_name%>,
+ }
+}
+\thispagestyle{kivitendo.letter.first}
+
+\anfrageformel
+
+
+%
+% - longtable kann innerhalb der Tabelle umbrechen
+% - da der Umbruch nicht von Lx-Office kontrolliert wird, kann man keinen
+% Übertrag machen
+% - Innerhalb des Langtextes <%longdescription%> wird nicht umgebrochen.
+% Falls das gewünscht ist, \\ mit \renewcommand umschreiben (siehe dazu:
+% http://www.lx-office.org/uploads/media/Lx-Office_Anwendertreffen_LaTeX-Druckvorlagen-31.01.2011_01.pdf)
+%
+\setlength\LTleft\parindent % Tabelle beginnt am linken Textrand
+\setlength\LTright{0pt} % Tabelle endet am rechten Textrand
+\begin{longtable}{@{}rrp{14cm}@{\extracolsep{\fill}}@{}}
+% Tabellenkopf
+\hline
+\textbf{\position} & \textbf{\menge} & \textbf{\bezeichnung} \\
+\hline\\
+\endhead
+
+% Tabellenkopf erste Seite
+\hline
+\textbf{\position} & \textbf{\menge} & \textbf{\bezeichnung} \\
+\hline\\[-0.5em]
+\endfirsthead
+
+% Tabellenende
+\\
+\multicolumn{3}{@{}r@{}}{\weiteraufnaechsterseite}
+\endfoot
+
+% Tabellenende letzte Seite
+\hline\\
+\endlastfoot
+
+% eigentliche Tabelle
+<%foreach number%>
+ <%runningnumber%> &
+ <%qty%> <%unit%> &
+ \textbf{<%description%>} \\* % kein Umbruch nach der ersten Zeile, damit Beschreibung und Langtext nicht getrennt werden
+
+ <%if longdescription%> && \scriptsize <%longdescription%>\\<%end longdescription%>
+% <%if projectnumber%> && \scriptsize \projektnummer: <%projectnumber%>\\<%end projectnumber%>
+ <%if make%>
+ <%foreach make%>
+ \ifstr{<%make%>}{<%name%>}{&& \artikelnummer: <%model%>\\}{}
+ <%end foreach%>
+ <%end if%>
+
+ \\[-0.8em]
+<%end number%>
+
+\end{longtable}
+
+<%if notes%>
+<%notes%>
+\medskip
+<%end if%>
+
+
+<%if delivery_term%>
+ \lieferung ~<%delivery_term.description_long%>\\
+<%end delivery_term%>
+
+<%if reqdate%>
+\anfrageBenoetigtBis~<%reqdate%>.
+<%end if%>
+
+\anfragedanke
+
+\closing{\gruesse}
+
+\end{letter}
+
+\end{document}
--- /dev/null
+% config: use-template-toolkit=1
+% config: tag-style=$( )$
+$( USE KiviLatex )$
+$( USE P )$
+\documentclass{scrartcl}
+
+\usepackage[reqspeclogo,$( IF !rspec.version )$draftlogo$( ELSE )$secondpagelogo$( END )$]{kivitendo}
+$( KiviLatex.required_packages_for_html )$
+
+\kivitendobgsettings
+
+\setlength{\LTpre}{0pt}
+\setlength{\LTpost}{0pt}
+
+\renewcommand{\kivitendosecondfoot}{%
+ \parbox{12cm}{%
+ \defaultfont\scriptsize%
+ $( KiviLatex.filter(rspec.displayable_name) )$\\
+ $( !rspec.version ? "Arbeitskopie ohne Version" : "Version " _ rspec.version.version_number _ " vom " _ rspec.version.itime.to_kivitendo(precision='minute') )$
+
+ \vspace*{0.2cm}%
+ Seite \thepage%
+ }%
+}
+
+\reqspecsectionstyle
+
+\begin{document}
+
+%% Titelseite
+
+\setlongtables
+\defaultfont
+
+\begin{picture}(0,0)
+ \put(3.5,-5){%
+ \begin{minipage}[t][6cm]{12cm}
+ \Large
+ \textcolor{kivitendodarkred}{$( KiviLatex.filter(rspec.type.description) )$}
+
+ \huge
+ $( KiviLatex.filter(rspec.customer.name) )$
+
+ \vspace*{0.5cm}
+ \Large
+ $( KiviLatex.filter(rspec.title) )$
+ \normalsize
+%$( IF rspec.version )$
+
+ Version $( KiviLatex.filter(rspec.version.version_number) )$
+%$( END )$
+ \end{minipage}%
+ }
+\end{picture}
+
+%% Inhaltsverzeichnis
+
+%\newpage
+
+%\tableofcontents
+
+%% Versionen
+\newpage
+
+\section{Versionen}
+
+\vspace*{0.7cm}
+
+%$( SET working_copy = rspec.working_copy_id ? rspec.working_copy : rspec )$
+%$( SET versioned_copies = rspec.version ? working_copy.versioned_copies_sorted(max_version_number = rspec.version.version_number) : working_copy.versioned_copies_sorted )$
+%$( IF !versioned_copies.size )$
+ Bisher wurden noch keine Versionen angelegt.
+%$( ELSE )$
+\begin{longtable}{|p{2cm}|p{2cm}|p{12cm}|}
+ \hline
+ \multicolumn{1}{|r}{\small Version} &
+ \multicolumn{1}{|r|}{\small Datum} &
+ \small Beschreibung\\
+ \hline
+%$( FOREACH versioned_copy = versioned_copies )$
+ \multicolumn{1}{|r}{\small $( KiviLatex.filter(versioned_copy.version.version_number) )$} &
+ \multicolumn{1}{|r|}{\small $( KiviLatex.filter(versioned_copy.version.itime.to_kivitendo(precision='minute')) )$} &
+ \small $( KiviLatex.filter(versioned_copy.version.description) )$\\
+%$( END )$
+ \hline
+\end{longtable}
+%$( END )$
+
+%$( BLOCK picture_outputter )$
+% $( SET width_cm = (picture.picture_width / 150.0) * 2.54 )$
+% $( SET width_cm = width_cm < 16.4 ? width_cm : 16.4 )$
+\begin{figure}[h!]
+ \centering
+ \includegraphics[width=$( width_cm )$cm,keepaspectratio]{$( picture.print_file_name )$}
+
+\mbox{Abbildung $( picture.number )$: $( KiviLatex.filter(picture.description ? picture.description : picture.picture_file_name) )$}
+\end{figure}
+%$( END )$
+
+%$( BLOCK text_block_outputter )$
+% $( SET text_blocks = rspec.text_blocks_sorted(output_position=output_position) )$
+% $( IF text_blocks.size )$
+
+ \newpage
+
+ \section{$( heading )$}
+
+% $( FOREACH text_block = text_blocks )$
+
+ \subsection{$( KiviLatex.filter(text_block.title) )$}
+
+$( KiviLatex.filter_html(text_block.text_as_restricted_html) )$
+
+% $( FOREACH picture = text_block.pictures_sorted.as_list )$
+$( PROCESS picture_outputter picture=picture )$
+% $( END )$
+
+% $( END )$
+% $( END )$
+%$( END )$
+
+%% Textblöcke davor
+$( PROCESS text_block_outputter output_position=0 heading='Allgemeines' )$
+
+%% Abschnitte und Funktionsblöcke
+\newpage
+
+\section{Spezifikation}
+
+\setlength{\LTpre}{-0.3cm}
+
+
+%$( FOREACH top_item = rspec.sections_sorted )$
+
+ \subsection{Abschnitt $( KiviLatex.filter(top_item.fb_number) )$: $( KiviLatex.filter(top_item.title) )$}
+
+% $( IF top_item.description )$
+ $( KiviLatex.filter_html(top_item.description_as_restricted_html.replace('\r', '').replace('\n+\Z', '')) )$
+
+ \vspace{0.5cm}
+% $( END )$
+% $( FOREACH item = top_item.children_sorted )$
+\parbox[t]{1.0cm}{\textcolor{kivitendodarkred}{$>>>$}}%
+\parbox[t]{15.0cm}{%
+\begin{longtable}{p{2.8cm}p{11.7cm}}
+ Funktionsblock & $( KiviLatex.filter(item.fb_number) )$\\
+ Beschreibung & $( KiviLatex.filter_html(item.description_as_restricted_html) )$\\
+ Abhängigkeiten & $( KiviLatex.filter(item.presenter.requirement_spec_item_dependency_list) )$
+\end{longtable}}
+
+% $( FOREACH sub_item = item.children_sorted )$
+\hspace*{1.15cm}\rule{15.2cm}{0.2pt}\\
+\hspace*{1.0cm}%
+\parbox[t]{15.0cm}{%
+\begin{longtable}{p{2.8cm}p{11.7cm}}
+ Unterfunktionsblock & $( KiviLatex.filter(sub_item.fb_number) )$\\
+ Beschreibung & $( KiviLatex.filter_html(sub_item.description_as_restricted_html) )$\\
+ Abhängigkeiten & $( KiviLatex.filter(sub_item.presenter.requirement_spec_item_dependency_list) )$
+\end{longtable}}
+
+% $( END )$
+
+% $( UNLESS loop.last )$
+\vspace{0.2cm}
+\hrule
+\vspace{0.4cm}
+
+% $( END )$
+
+% $( END )$
+%
+%$( END )$
+
+%% Textblöcke dahinter
+$( PROCESS text_block_outputter output_position=1 heading='Weitere Punkte' )$
+
+
+\end{document}
--- /dev/null
+\documentclass[paper=a4,fontsize=10pt]{scrartcl}
+\usepackage{kiviletter}
+
+
+% Variablen, die in settings verwendet werden
+\newcommand{\lxlangcode} {<%template_meta.language.template_code%>}
+\newcommand{\lxmedia} {<%media%>}
+\newcommand{\lxcurrency} {<%currency%>}
+\newcommand{\kivicompany} {<%employee_company%>}
+
+% settings: Einstellungen, Logo, Briefpapier, Kopfzeile, Fusszeile
+\input{insettings.tex}
+
+
+% laufende Kopfzeile:
+\ourhead{\kundennummer}{<%customernumber%>}{\lieferschein}{<%donumber%>}{<%dodate%>}
+
+
+\begin{document}
+
+\setkomavar{title}{
+ \lieferschein~
+ \nr~<%donumber%>
+}
+\setkomavar*{date}{\datum}
+\setkomavar{date}{<%dodate%>}
+<%if cusordnumber%>
+ \setkomavar*{yourref}{\unsereBestellnummer}
+ \setkomavar{yourref}{<%cusordnumber%>}
+<%end if%>
+<%if ordnumber%>\setkomavar{orderID}{<%ordnumber%>}<%end if%>
+\setkomavar{fromname}{<%employee_name%>}
+\setkomavar{fromphone}{<%employee_tel%>}
+\setkomavar{fromemail}{<%employee_email%>}
+<%if globalprojectnumber%>
+ \setkomavar{projectID}{<%globalprojectnumber%>}
+<%end globalprojectnumber%>
+
+\setkomavar{transaction}{<%transaction_description%>}
+
+
+\begin{letter}{
+ \ifstr{<%shiptoname%>}{}{ % KEINE ABWEICHENDE LIEFERADRESSE
+ <%name%>\strut\\
+ <%if department_1%><%department_1%>\\<%end if%>
+ <%if department_2%><%department_2%>\\<%end if%>
+ <%cp_givenname%> <%cp_name%>\strut\\
+ <%street%>\strut\\
+ <%zipcode%> <%city%>\strut\\
+ <%country%> \strut
+ }{ % ABWEICHENDE LIEFERADRESSE (Aus Stammdaten oder Beleg)
+ <%shiptoname%>\strut\\
+ <%if shiptocontact%> <%shiptocontact%><%end if%>\strut\\
+ <%shiptodepartment_1%>\strut\\
+ <%shiptodepartment_2%>\strut\\
+ <%shiptostreet%>\strut\\
+ <%shiptozipcode%> <%shiptocity%>\strut
+ } % ende ifthenelse LIEFERADRESSE
+}
+
+\opening{}%muss existieren, damit seitenstil erzeugt wird.
+
+%
+% - longtable kann innerhalb der Tabelle umbrechen
+% - da der Umbruch nicht von Lx-Office kontrolliert wird, kann man keinen
+% Übertrag machen
+% - Innerhalb des Langtextes <%longdescription%> wird nicht umgebrochen.
+% Falls das gewünscht ist, \\ mit \renewcommand umschreiben (siehe dazu:
+% http://www.lx-office.org/uploads/media/Lx-Office_Anwendertreffen_LaTeX-Druckvorlagen-31.01.2011_01.pdf)
+%
+\setlength\LTleft\parindent % Tabelle beginnt am linken Textrand
+\setlength\LTright{0pt} % Tabelle endet am rechten Textrand
+\begin{longtable}{@{}rrp{10.7cm}@{\extracolsep{\fill}}r@{}}
+% Tabellenkopf
+\hline
+\textbf{\position} & \textbf{\artikelnummer} & \textbf{\bezeichnung} & \textbf{\menge} \\
+\hline\\
+\endhead
+
+% Tabellenkopf erste Seite
+\hline
+\textbf{\position} & \textbf{\artikelnummer} & \textbf{\bezeichnung} & \textbf{\menge} \\
+\hline\\[-0.5em]
+\endfirsthead
+
+% Tabellenende
+\\
+\multicolumn{4}{@{}r@{}}{\weiteraufnaechsterseite}
+\endfoot
+
+% Tabellenende letzte Seite
+\hline\\
+\endlastfoot
+
+% eigentliche Tabelle
+<%foreach number%>
+ <%runningnumber%> &
+ <%number%> &
+ \textbf{<%description%>}&
+ <%qty%> <%unit%> \\* % kein Umbruch nach der ersten Zeile, damit Beschreibung und Langtext nicht getrennt werden
+
+ <%if longdescription%> && \scriptsize <%longdescription%>\\<%end longdescription%>
+ <%if reqdate%> && \scriptsize \lieferdatum: <%reqdate%>\\<%end reqdate%>
+ <%if serialnumber%> && \scriptsize \seriennummer: <%serialnumber%>\\<%end serialnumber%>
+ <%if ean%> && \scriptsize \ean: <%ean%>\\<%end ean%>
+ <%if projectnumber%> && \scriptsize \projektnummer: <%projectnumber%>\\<%end projectnumber%>
+ <%foreach si_number%><%if si_chargenumber%> && \scriptsize \charge: <%si_chargenumber%> <%if si_bestbefore%> \mhd: <%si_bestbefore%><%end if%> <%si_qty%>~<%si_unit%><%end si_chargenumber%>\\<%end si_number%>
+
+ \\[-0.8em]
+<%end number%>
+
+\end{longtable}
+
+
+\vspace{0.2cm}
+
+<%if notes%>
+<%notes%>
+\medskip
+<%end if%>
+
+
+<%if delivery_term%>
+ \lieferung ~<%delivery_term.description_long%>\\
+<%end delivery_term%>
+
+\end{letter}
+
+\end{document}
--- /dev/null
+
+<body bgcolor=ffffff>
+
+<table width=100%>
+<tr valign=bottom>
+ <td width=10> </td>
+ <td>
+
+ <table width=100%>
+ <tr>
+ <td>
+ <h4>
+ <%company%>
+ <br><%address%>
+ </h4>
+ </td>
+
+ <td align=right>
+ <h4>
+ Telefon <%tel%>
+ <br>Telefax <%fax%>
+ </h4>
+ </td>
+ </tr>
+
+ <tr>
+ <th colspan=3>
+ <h4>B E S T E L L U N G</h4>
+ </th>
+ </tr>
+
+ </table>
+
+
+ <table width=100% callspacing=0 cellpadding=0>
+
+ <tr>
+ <td align=right>
+ <table>
+ <tr>
+ <th align=right>Bestelldatum</th><td width=10> </td><td><%orddate%></td>
+ </tr>
+
+ <tr>
+ <th align=right>Lieferbar bei</th><td width=10> </td><td><%reqdate%></td>
+ </tr>
+
+ <tr>
+ <th align=right>Bestellnummer</th><td> </td><td><%ordnumber%></td></tr>
+ </tr>
+
+ <tr>
+ <td> </td>
+ </tr>
+ </td>
+ </table>
+ </tr>
+
+ <tr>
+ <td>
+ <table width=100%>
+ <tr bgcolor=000000>
+ <th align=left><font color=ffffff>Verrechnet An:</th>
+ <th align=left><font color=ffffff>Lieferaddresse:</th>
+ </tr>
+
+ <tr>
+ <td><%name%>
+ <br><%street%>
+ <br><%zipcode%>
+ <br><%city%>
+ <br><%country%>
+ </td>
+
+ <td><%shiptoname%>
+ <br><%shiptostreet%>
+ <br><%shiptozipcode%>
+ <br><%shiptocity%>
+ <br><%shiptocountry%>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+
+ <tr>
+ <td> </td>
+ </tr>
+
+ <tr>
+ <td>
+ <table width=100%>
+ <tr bgcolor=000000>
+<!-- <th align=right><font color=ffffff>No.</th> -->
+ <th align=left><font color=ffffff>Nummer</th>
+ <th align=left><font color=ffffff>Artikel</th>
+ <th><font color=ffffff>Anz</th>
+ <th> </th>
+ <th><font color=ffffff>Preis</th>
+ <th><font color=ffffff>Rab</th>
+ <th><font color=ffffff>Total</th>
+ </tr>
+
+<%foreach number%>
+ <tr valign=top>
+<!-- <td align=right><%runningnumber%>.</td>
+adjust the colspan if you include this to shift subtotal one to the right
+-->
+ <td><%number%></td>
+ <td><%description%></td>
+ <td align=right><%qty%></td>
+ <td><%unit%></td>
+ <td align=right><%sellprice%></td>
+ <td align=right><%discount%></td>
+ <td align=right><%linetotal%></td>
+ </tr>
+<%end number%>
+
+ <tr>
+ <td colspan=7><hr noshade></td>
+ </tr>
+
+<%if taxincluded%>
+ <tr>
+ <th colspan=5 align=right>Total</th>
+ <td colspan=2 align=right><%ordtotal%></td>
+ </tr>
+<%end taxincluded%>
+
+<%if not taxincluded%>
+ <tr>
+ <th colspan=5 align=right>Zwischensumme</th>
+ <td colspan=2 align=right><%subtotal%></td>
+ </tr>
+<%end taxincluded%>
+
+<%foreach tax%>
+ <tr>
+ <th colspan=5 align=right><%taxdescription%> auf <%taxbase%> @ <%taxrate%> %</th>
+ <td colspan=2 align=right><%tax%></td>
+ </tr>
+<%end tax%>
+
+<%if rounding%>
+ <th colspan=5 align=right>Rundung</th>
+ <td colspan=2 align=right><%rounding%></td>
+<%end rounding%>
+
+ <tr>
+ <td colspan=2> </td>
+ <td colspan=5><hr noshade></td>
+ </tr>
+
+ <tr>
+ <td colspan=3>Netto <b><%terms%></b> Tage</td>
+ <th colspan=2 align=right>Total</th>
+ <th colspan=2 align=right><%ordtotal%></th>
+ </tr>
+<%if taxincluded%>
+ <tr>
+ <td colspan=3>Steuern sind im Preis inbegriffen</td>
+ </tr>
+<%end taxincluded%>
+
+ <tr>
+ <td> </td>
+ </tr>
+
+ </table>
+ </td>
+ </tr>
+
+<tr>
+ <td>
+ <table width=100%>
+ <tr valign=top>
+<%if notes%>
+ <td>Bemerkungen</td>
+ <td><%notes%></td>
+<%end notes%>
+ <td align=right>
+ Alle Preise in <b><%currency%></b>
+ <br><%shippingpoint%>
+ </td>
+ </tr>
+
+ </table>
+ </td>
+</tr>
+
+<tr><td> </td></tr>
+
+<tr>
+ <td>
+ <table width=100%>
+ <tr valign=top>
+ <td><font size=-3>
+ Spezialprodukte werden nicht zurückgenommen. Für alle anderen Waren
+ wird eine 10% Stornogebühr verrechnet.
+ </font>
+ </td>
+ <td width=150>
+ X <hr noshade>
+ </td>
+ </tr>
+ </table>
+ </td>
+</tr>
+
+</table>
+
+</td>
+</tr>
+</table>
+
+</body>
+</html>
+
--- /dev/null
+\documentclass[paper=a4,fontsize=10pt]{scrartcl}
+\usepackage{kiviletter}
+
+
+% Variablen, die in settings verwendet werden
+\newcommand{\lxlangcode} {<%template_meta.language.template_code%>}
+\newcommand{\lxmedia} {<%media%>}
+\newcommand{\lxcurrency} {<%currency%>}
+\newcommand{\kivicompany} {<%employee_company%>}
+
+% settings: Einstellungen, Logo, Briefpapier, Kopfzeile, Fusszeile
+\input{insettings.tex}
+
+
+% laufende Kopfzeile:
+\ourhead{\kundennummer}{<%customernumber%>}{\auftragsbestaetigung}{<%ordnumber%>}{<%orddate%>}
+
+
+\begin{document}
+\setkomavar{title}{
+ \auftragsbestaetigung~
+ \nr~<%ordnumber%>
+}
+\setkomavar*{date}{\datum}
+\setkomavar{date}{<%orddate%>}
+<%if cusordnumber%>
+\setkomavar*{yourref}{\ihreBestellnummer}
+\setkomavar{yourref}{<%cusordnumber%>}
+<%end if%>
+<%if quonumber%>\setkomavar{quote}{<%quonumber%>}<%end if%>
+\setkomavar{customer}{<%customernumber%>}
+\setkomavar{fromname}{<%employee_name%>}
+\setkomavar{fromphone}{<%employee_tel%>}
+\setkomavar{fromemail}{<%employee_email%>}
+\setkomavar{transaction}{<%transaction_description%>}
+
+
+
+<%if shiptoname%>
+\makeatletter
+\begin{lrbox}\shippingAddressBox
+ \parbox{\useplength{toaddrwidth}}{
+ \backaddr@format{\scriptsize\usekomafont{backaddress}%
+ \strut abweichende Lieferadresse
+ }
+ \par\smallskip
+ \setlength{\parskip}{\z@}
+ \par
+ \normalsize
+ <%shiptoname%>\par
+ <%if shiptocontact%> <%shiptocontact%><%end if%>\par
+ <%shiptodepartment_1%>\par
+ <%shiptodepartment_2%>\par
+ <%shiptostreet%>\par
+ <%shiptozipcode%> <%shiptocity%>
+ }
+\end{lrbox}
+\makeatother
+<%end if%>
+
+\begin{letter}{
+ <%name%>\strut\\
+ <%if department_1%><%department_1%>\\<%end if%>
+ <%if department_2%><%department_2%>\\<%end if%>
+ <%cp_givenname%> <%cp_name%>\strut\\
+ <%street%>\strut\\
+ <%zipcode%> <%city%>\strut\\
+ <%country%> \strut
+ }
+
+% Bei Kontaktperson Anrede nach Geschlecht unterscheiden.
+% Bei natürlichen Personen persönliche Anrede, sonst allgemeine Anrede.
+\opening{
+ \ifstr{<%cp_name%>}{}
+ {<%if natural_person%><%greeting%> <%name%>,<%else%>\anrede<%end if%>}
+ {
+ \ifstr{<%cp_gender%>}{f}
+ {\anredefrau}
+ {\anredeherr}
+ <%cp_title%> <%cp_name%>,
+ }
+}
+\thispagestyle{kivitendo.letter.first}
+
+\auftragsformel
+
+\begin{PricingTabular*}
+ % eigentliche Tabelle
+ \FakeTable{
+ <%foreach number%>%
+ <%runningnumber%> &%
+ <%number%> &%
+ \textbf{<%description%>}%
+ <%if longdescription%>\ExtraDescription{<%longdescription%>}<%end longdescription%>%
+ <%if reqdate%>\ExtraDescription{\lieferdatum: <%reqdate%>}<%end reqdate%>%
+ <%if serialnumber%>\ExtraDescription{\seriennummer: <%serialnumber%>}<%end serialnumber%>%
+ <%if ean%>\ExtraDescription{\ean: <%ean%>}<%end ean%>%
+ <%if projectnumber%>\ExtraDescription{\projektnummer: <%projectnumber%>}<%end projectnumber%>%
+ &%
+ <%qty%> <%unit%> &%
+ <%sellprice%>&%
+ \ifstr{<%p_discount%>}{0}{}{\sffamily\scriptsize{(-<%p_discount%>\,\%)}}%
+ <%linetotal%>\tabularnewline
+ <%end number%>
+ }
+ \begin{PricingTotal}
+ % Tabellenende letzte Seite
+ \nettobetrag & <%subtotal%>\\
+ <%foreach tax%>
+ <%taxdescription%> & <%tax%>\\
+ <%end tax%>
+ \bfseries\schlussbetrag & \bfseries <%ordtotal%>\\
+ \end{PricingTotal}
+\end{PricingTabular*}
+
+<%if notes%>
+<%notes%>
+\medskip
+<%end if%>
+
+<%if delivery_term%>
+ \lieferung ~<%delivery_term.description_long%>\\
+<%end delivery_term%>
+
+<%if reqdate%>
+\lieferungErfolgtAm ~<%reqdate%>.
+<%end if%>
+
+\textit{\auftragpruefen}
+
+\closing{\gruesse}
+
+\end{letter}
+
+\end{document}
+
--- /dev/null
+
+<body bgcolor=ffffff>
+
+<table width=100%>
+<tr valign=bottom>
+ <td width=10> </td>
+ <td>
+
+ <table width=100%>
+ <tr valign=top>
+ <td>
+ <h4>
+ <%company%>
+ <br><%address%>
+ </h4>
+ </td>
+
+ <th><img src=http://localhost/lx-erp/lx-office-erp.png border=0 width=64 height=58></th>
+
+ <td align=right>
+ <h4>
+ Tel: <%tel%>
+ <br>Fax: <%fax%>
+ </h4>
+ </td>
+ </tr>
+
+<tr><td colspan=3> </td></tr>
+
+ <tr>
+ <th colspan=3>
+ <h4>A N G E B O T</h4>
+ </th>
+ </tr>
+
+ </table>
+
+ <table width=100% callspacing=0 cellpadding=0>
+
+ <tr>
+ <td>
+ <table width=100%>
+
+ <tr valign=top>
+ <td><%name%>
+ <br><%street%>
+ <br><%zipcode%>
+ <br><%city%>
+ <br><%country%>
+<br>
+<%if contact%>
+<br>Kontakt: <%contact%>
+<%end contact%>
+
+<%if customerphone%>
+<br>Tel: <%customerphone%>
+<%end customerphone%>
+
+<%if customerfax%>
+<br>Fax: <%customerfax%>
+<%end customerfax%>
+
+<%if email%>
+<br><%email%>
+<%end email%>
+ </td>
+
+ </tr>
+ </table>
+ </td>
+ </tr>
+
+ <tr><td> </td></tr>
+
+ <tr>
+ <td colspan=2>
+ <table width=100% border=1>
+ <tr>
+ <th width=17% align=left nowrap>Nummer</th>
+ <th width=17% align=left>Datum</th>
+ <th width=17% align=left>Gültig bis</th>
+ <th width=17% align=left nowrap>Kontakt</th>
+ <th width=17% align=left nowrap>Lagerplatz</th>
+ <th width=15% align=left nowrap>Lieferung mit</th>
+ </tr>
+
+ <tr>
+ <td><%quonumber%></td>
+ <td><%quodate%></td>
+ <td><%reqdate%></td>
+ <td><%employee%></td>
+ <td><%shippingpoint%></td>
+ <td><%shipvia%></td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+
+ <tr>
+ <td> </td>
+ </tr>
+
+ <tr>
+ <td>
+ <table width=100%>
+ <tr bgcolor=000000>
+ <th align=right><font color=ffffff>Nr.</th>
+ <th align=left><font color=ffffff>Artikelnummer</th>
+ <th align=left><font color=ffffff>Beschreibung</th>
+ <th><font color=ffffff>Menge</th>
+ <th> </th>
+ <th><font color=ffffff>Preis</th>
+ <th><font color=ffffff>Rabatt</th>
+ <th><font color=ffffff>Gesamtpreis</th>
+ </tr>
+
+<%foreach number%>
+ <tr valign=top>
+ <td align=right><%runningnumber%></td>
+
+ <td><%number%></td>
+ <td><%description%></td>
+ <td align=right><%qty%></td>
+ <td><%unit%></td>
+ <td align=right><%sellprice%></td>
+ <td align=right><%discount%></td>
+ <td align=right><%linetotal%></td>
+ </tr>
+<%end number%>
+
+ <tr>
+ <td colspan=8><hr noshade></td>
+ </tr>
+
+ <tr>
+<%if taxincluded%>
+ <th colspan=6 align=right>Gesamtbetrag netto</th>
+ <td colspan=2 align=right><%invtotal%></td>
+<%end taxincluded%>
+
+<%if not taxincluded%>
+ <th colspan=6 align=right>Zwischensumme</th>
+ <td colspan=2 align=right><%subtotal%></td>
+<%end taxincluded%>
+ </tr>
+
+<%foreach tax%>
+ <tr>
+ <th colspan=6 align=right><%taxdescription%> von <%taxbase%> @ <%taxrate%> %</th>
+ <td colspan=2 align=right><%tax%></td>
+ </tr>
+<%end tax%>
+
+<%if rounding%>
+ <th colspan=6 align=right>Rundung</th>
+ <td colspan=2 align=right><%rounding%></td>
+<%end rounding%>
+
+ <tr>
+ <td colspan=4> </td>
+ <td colspan=4><hr noshade></td>
+ </tr>
+
+ <tr>
+ <td colspan=4>
+<%if terms%>
+ Zahlungsziel <b><%terms%></b> Tage
+<%end terms%>
+ </td>
+ <th colspan=2 align=right>Gesamtbetrag brutto</th>
+ <th colspan=2 align=right><%quototal%></th>
+ </tr>
+
+ <tr>
+ <td> </td>
+ </tr>
+
+ </table>
+ </td>
+ </tr>
+
+<tr>
+ <td>
+ <table width=100%>
+ <tr valign=top>
+<%if notes%>
+ <td>Bemerkungen</td>
+ <td><%notes%></td>
+<%end notes%>
+ <td align=right>
+ Alle Preise in <b><%currency%></b> Euro
+ </td>
+ </tr>
+
+ </table>
+ </td>
+</tr>
+
+<tr><td> </td></tr>
+
+<tr>
+ <td>
+ <table width=100%>
+ <tr valign=top>
+ <td width=60%><font size=-3>
+ Spezialanfertigungen können nicht zurückgenommen werden.
+ </font>
+ </td>
+ <td width=40%>
+ X <hr noshade>
+ </td>
+ </tr>
+ </table>
+ </td>
+</tr>
+
+</table>
+
+</td>
+</tr>
+</table>
+
+</body>
+</html>
+
+
--- /dev/null
+\documentclass[paper=a4,fontsize=10pt]{scrartcl}
+\usepackage{kiviletter}
+
+
+% Variablen, die in settings verwendet werden
+\newcommand{\lxlangcode} {<%template_meta.language.template_code%>}
+\newcommand{\lxmedia} {<%media%>}
+\newcommand{\lxcurrency} {<%currency%>}
+\newcommand{\kivicompany} {<%employee_company%>}
+
+% settings: Einstellungen, Logo, Briefpapier, Kopfzeile, Fusszeile
+\input{insettings.tex}
+
+
+% laufende Kopfzeile:
+\ourhead{\kundennummer}{<%customernumber%>}{\angebot}{<%quonumber%>}{<%transdate%>}
+
+
+\begin{document}
+
+\setkomavar{signature}{%
+<%employee_company%>%
+\ifhmode\\\fi
+<%salesman_name%>%
+}
+
+\setkomavar*{date}{\datum}
+
+\setkomavar{date}{<%transdate%>}
+\setkomavar{customer}{<%customernumber%>}
+\setkomavar{fromname}{<%employee_name%>}
+\setkomavar{fromphone}{<%employee_tel%>}
+\setkomavar{fromemail}{<%employee_email%>}
+\setkomavar{title}{
+ \angebot~
+ <%quonumber%>
+}
+
+\setkomavar{transaction}{<%transaction_description%>}
+
+<%if shiptoname%>
+\makeatletter
+ \begin{lrbox}\shippingAddressBox
+ \parbox{\useplength{toaddrwidth}}{
+ \backaddr@format{\scriptsize\usekomafont{backaddress}%
+ \strut abweichende Lieferadresse
+ }
+ \par\smallskip
+ \setlength{\parskip}{\z@}
+ \par
+ \normalsize
+ <%shiptoname%>\par
+ <%if shiptocontact%> <%shiptocontact%><%end if%>\par
+ <%shiptodepartment_1%>\par
+ <%shiptodepartment_2%>\par
+ <%shiptostreet%>\par
+ <%shiptozipcode%> <%shiptocity%>
+ }
+\end{lrbox}
+\makeatother
+<%end if%>
+
+
+\begin{letter}{
+ <%name%>\strut\\
+ <%if department_1%><%department_1%>\\<%end if%>
+ <%if department_2%><%department_2%>\\<%end if%>
+ <%cp_givenname%> <%cp_name%>\strut\\
+ <%street%>\strut\\
+ <%zipcode%> <%city%>\strut\\
+ <%country%> \strut
+}
+
+% Bei Kontaktperson Anrede nach Geschlecht unterscheiden.
+% Bei natürlichen Personen persönliche Anrede, sonst allgemeine Anrede.
+\opening{
+ \ifstr{<%cp_name%>}{}
+ {<%if natural_person%><%greeting%> <%name%>,<%else%>\anrede<%end if%>}
+ {
+ \ifstr{<%cp_gender%>}{f}
+ {\anredefrau}
+ {\anredeherr}
+ <%cp_title%> <%cp_name%>,
+ }
+}
+\thispagestyle{kivitendo.letter.first}
+
+\angebotsformel
+
+
+\begin{PricingTabular*}
+% eigentliche Tabelle
+\FakeTable{
+<%foreach number%>%
+<%runningnumber%> &%
+<%number%> &%
+\textbf{<%description%>}%
+ <%if longdescription%>\ExtraDescription{<%longdescription%>}<%end longdescription%>%
+ <%if reqdate%>\ExtraDescription{\lieferdatum: <%reqdate%>}<%end reqdate%>%
+ <%if serialnumber%>\ExtraDescription{\seriennummer: <%serialnumber%>}<%end serialnumber%>%
+ <%if ean%>\ExtraDescription{\ean: <%ean%>}<%end ean%>%
+ <%if projectnumber%>\ExtraDescription{\projektnummer: <%projectnumber%>}<%end projectnumber%>%
+ &%
+ <%qty%> <%unit%> &%
+ <%sellprice%>&%
+ \ifstr{<%p_discount%>}{0}{}{\sffamily\scriptsize{(-<%p_discount%>\,\%)}}%
+ <%linetotal%>\tabularnewline
+<%end number%>
+}
+ \begin{PricingTotal}
+ % Tabellenende letzte Seite
+ \nettobetrag & <%subtotal%>\\
+ <%foreach tax%>
+ <%taxdescription%> & <%tax%>\\
+ <%end tax%>
+ \bfseries\schlussbetrag & \bfseries <%ordtotal%>\\
+ \end{PricingTotal}
+\end{PricingTabular*}
+
+<%if notes%>
+ <%notes%>
+ \medskip
+<%end if%>
+
+<%if delivery_term%>
+ \lieferung ~<%delivery_term.description_long%>\\
+<%end delivery_term%>
+
+\angebotdanke\\
+<%if reqdate%>
+\angebotgueltig~<%reqdate%>.
+<%end if%>
+\angebotfragen
+
+
+\angebotagb
+
+\closing{\gruesse}
+
+\begin{minipage}{\textwidth}
+\rule{\linewidth}{.2pt}\par
+\auftragerteilt\par\bigskip
+\nurort:\rule[-.5ex]{8cm}{.2pt}\ ,\den\ \rule[-.5ex]{5cm}{.2pt}\par\bigskip
+
+\unterschrift/\stempel:\rule[-.5ex]{6cm}{.2pt}
+\end{minipage}
+
+
+\end{letter}
+\end{document}
--- /dev/null
+
+<body bgcolor=ffffff>
+
+<table width=100%>
+ <tr>
+ <td width=10> </td>
+ <td>
+ <table width=100%>
+ <tr>
+ <td>
+ <h4>
+ <%company%>
+ <br><%address%>
+ </h4>
+ </td>
+ <th></th>
+ <td align=right>
+ <h4>
+ Tel: <%tel%>
+ <br>Fax: <%fax%>
+ </h4>
+ </td>
+ </tr>
+ <tr>
+ <th colspan=3><h4>S T A T E M E N T</h4></th>
+ </tr>
+ <tr>
+ <td colspan=3 align=right><%statementdate%></td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ <tr>
+ <td> </td>
+ <td>
+ <table width=100%>
+ <tr valign=top>
+ <td><%name%>
+ <br><%street%>
+ <br><%zipcode%>
+ <br><%city%>
+ <br><%country%>
+ <br>
+<%if customerphone%>
+ <br>Tel: <%customerphone%>
+<%end customerphone%>
+<%if customerfax%>
+ <br>Fax: <%customerfax%>
+<%end customerfax%>
+<%if email%>
+ <br><%email%>
+<%end email%>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ <tr height=10></tr>
+ <tr>
+ <td> </td>
+ <td>
+ <table width=100%>
+ <tr>
+ <th align=left>Invoice #</th>
+ <th width=15%>Date</th>
+ <th width=15%>Due</th>
+ <th width=10%>Current</th>
+ <th width=10%>30</th>
+ <th width=10%>60</th>
+ <th width=10%>90+</th>
+ </tr>
+<%foreach invnumber%>
+ <tr>
+ <td><%invnumber%></td>
+ <td><%invdate%></td>
+ <td><%duedate%></td>
+ <td align=right><%c0%></td>
+ <td align=right><%c30%></td>
+ <td align=right><%c60%></td>
+ <td align=right><%c90%></td>
+ </tr>
+<%end invnumber%>
+ <tr>
+ <td colspan=7><hr size=1></td>
+ </tr>
+ <tr>
+ <td> </td>
+ <td> </td>
+ <td> </td>
+ <th align=right><%c0total%></td>
+ <th align=right><%c30total%></td>
+ <th align=right><%c60total%></td>
+ <th align=right><%c90total%></td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ <tr height=10></tr>
+ <tr>
+ <td> </td>
+ <td align=right>
+ <table width=50%>
+ <tr>
+ <th>Total Outstanding</th>
+ <th align=right><%total%></th>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ <tr>
+ <td> </td>
+ <td><hr noshade></td>
+ </tr>
+ <tr>
+ <td> </td>
+ <td>Please make check payable to <b><%company%></b>.
+ </td>
+ </tr>
+ <tr height=20></tr>
+</table>
+
--- /dev/null
+\documentclass[paper=a4,fontsize=10pt]{scrartcl}
+\usepackage{kiviletter}
+
+
+% Variablen, die in settings verwendet werden
+\newcommand{\lxlangcode} {<%template_meta.language.template_code%>}
+\newcommand{\lxmedia} {<%media%>}
+\newcommand{\lxcurrency} {<%currency%>}
+\newcommand{\kivicompany} {<%employee_company%>}
+
+% settings: Einstellungen, Logo, Briefpapier, Kopfzeile, Fusszeile
+\input{insettings.tex}
+
+
+% laufende Kopfzeile:
+\ourhead{}{}{\sammelrechnung}{}{}
+
+
+\begin{document}
+
+\setkomavar{title}{
+ \sammelrechnung~
+ \nr~<%quonumber%>
+}
+\setkomavar{transaction}{<%transaction_description%>}
+\setkomavar{customer}{<%customernumber%>}
+\setkomavar{fromname}{<%employee_name%>}
+\setkomavar{fromphone}{<%employee_tel%>}
+\setkomavar{fromemail}{<%employee_email%>}
+
+
+\begin{letter}{
+ <%name%>\strut\\
+ <%if department_1%><%department_1%>\\<%end if%>
+ <%if department_2%><%department_2%>\\<%end if%>
+ <%cp_givenname%> <%cp_name%>\strut\\
+ <%street%>\strut\\
+ <%zipcode%> <%city%>\strut\\
+ <%country%> \strut
+ }
+
+% Bei Kontaktperson Anrede nach Geschlecht unterscheiden.
+% Bei natürlichen Personen persönliche Anrede, sonst allgemeine Anrede.
+\opening{
+ \ifstr{<%cp_name%>}{}
+ {<%if natural_person%><%greeting%> <%name%>,<%else%>\anrede<%end if%>}
+ {
+ \ifstr{<%cp_gender%>}{f}
+ {\anredefrau}
+ {\anredeherr}
+ <%cp_title%> <%cp_name%>,
+ }
+}
+\thispagestyle{kivitendo.letter.first}
+
+\sammelrechnungsformel
+
+%
+% - longtable kann innerhalb der Tabelle umbrechen
+% - da der Umbruch nicht von Lx-Office kontrolliert wird, kann man keinen
+% Übertrag machen
+%
+\setlength\LTleft\parindent % Tabelle beginnt am linken Textrand
+\setlength\LTright{0pt} % Tabelle endet am rechten Textrand
+\begin{longtable}{@{\extracolsep{\fill}}rrrrrrr@{}}
+% Tabellenkopf
+\hline
+\textbf{\rechnung~\nr} & \textbf{\datum} & \textbf{\faellig} &
+\textbf{\aktuell} & \textbf{\asDreissig} & \textbf{\asSechzig} & \textbf{\asNeunzig}\\
+\hline\\
+\endhead
+
+% Tabellenkopf erste Seite
+\hline
+\textbf{\rechnung~\nr} & \textbf{\datum} & \textbf{\faellig} &
+\textbf{\aktuell} & \textbf{\asDreissig} & \textbf{\asSechzig} & \textbf{\asNeunzig}\\
+\hline\\[-0.5em]
+\endfirsthead
+
+% Tabellenende
+\\
+\multicolumn{7}{@{}r@{}}{\weiteraufnaechsterseite}
+\endfoot
+
+% Tabellenende letzte Seite
+\hline\\
+\multicolumn{3}{@{}l}{\textbf{\zwischensumme}} & \textbf{<%c0total%>} & \textbf{<%c30total%>} & \textbf{<%c60total%>} & \textbf{<%c90total%>}\\
+\hline\\
+\multicolumn{6}{@{}l}{\textbf{\schlussbetrag}} & \textbf{<%total%>} \\
+\hline\hline\\
+\endlastfoot
+
+% eigentliche Tabelle
+<%foreach invnumber%>
+ <%invnumber%> & <%invdate%> & <%duedate%> &
+ <%c0%> & <%c30%> & <%c60%> & <%c90%> \\
+<%end invnumber%>
+
+\end{longtable}
+
+
+\closing{\gruesse}
+
+\end{letter}
+
+\end{document}
+
--- /dev/null
+\documentclass[paper=a4,fontsize=10pt]{scrartcl}
+\usepackage{kiviletter}
+
+
+% Variablen, die in settings verwendet werden
+\newcommand{\lxlangcode} {<%template_meta.language.template_code%>}
+\newcommand{\lxmedia} {<%media%>}
+\newcommand{\lxcurrency} {<%currency%>}
+\newcommand{\kivicompany} {<%employee_company%>}
+
+% settings: Einstellungen, Logo, Briefpapier, Kopfzeile, Fusszeile
+\input{insettings.tex}
+
+
+% laufende Kopfzeile:
+\ourhead{\kundennummer}{<%customernumber%>}{\mahnung}{<%dunning_id%>}{<%dunning%>}
+
+
+\begin{document}
+
+\setkomavar*{date}{\datum}
+
+\setkomavar{date}{<%dunning_date%>}
+\setkomavar{customer}{<%customernumber%>}
+\setkomavar{fromname}{<%employee_name%>}
+\setkomavar{fromphone}{<%employee_tel%>}
+\setkomavar{fromemail}{<%employee_email%>}
+\setkomavar{title}{
+ \mahnung
+ <%if dunning_id%>~\nr~<%dunning_id%><%end if%>
+}
+\setkomavar{transaction}{<%transaction_description%>}
+
+\begin{letter}{
+ <%name%>\strut\\
+ <%if department_1%><%department_1%>\\<%end if%>
+ <%if department_2%><%department_2%>\\<%end if%>
+ <%cp_givenname%> <%cp_name%>\strut\\
+ <%street%>\strut\\
+ <%zipcode%> <%city%>\strut\\
+ <%country%> \strut
+ }
+
+% Bei Kontaktperson Anrede nach Geschlecht unterscheiden.
+% Bei natürlichen Personen persönliche Anrede, sonst allgemeine Anrede.
+\opening{
+ \ifstr{<%cp_name%>}{}
+ {<%if natural_person%><%greeting%> <%name%>,<%else%>\anrede<%end if%>}
+ {
+ \ifstr{<%cp_gender%>}{f}
+ {\anredefrau}
+ {\anredeherr}
+ <%cp_title%> <%cp_name%>,
+ }
+}
+\thispagestyle{kivitendo.letter.first}
+
+\mahnungsformel
+
+\begin{SimpleTabular}[\bfseries\rechnung~\nr&\bfseries\datum&\bfseries\betrag]
+% eigentliche Tabelle
+<%foreach dn_invnumber%>
+ <%dn_invnumber%> & <%dn_transdate%> & <%dn_amount%> \currency \\[0.1cm]
+<%end dn_invnumber%>
+\end{SimpleTabular}
+
+\vspace{0.2cm}
+
+\bitteZahlenBis~<%dunning_duedate%>.
+
+
+\beruecksichtigtBis~<%dunning_date%>.
+
+
+\schonGezahlt
+
+\closing{\gruesse}
+
+\end{letter}
+
+\end{document}
--- /dev/null
+\documentclass[paper=a4,fontsize=10pt]{scrartcl}
+\usepackage{kiviletter}
+
+
+% Variablen, die in settings verwendet werden
+\newcommand{\lxlangcode} {<%template_meta.language.template_code%>}
+\newcommand{\lxmedia} {<%media%>}
+\newcommand{\lxcurrency} {<%currency%>}
+\newcommand{\kivicompany} {<%employee_company%>}
+
+% settings: Einstellungen, Logo, Briefpapier, Kopfzeile, Fusszeile
+\input{insettings.tex}
+
+
+% laufende Kopfzeile:
+\ourhead{\kundennummer}{<%customernumber%>}{\rechnung}{<%invnumber%>}{<%invdate%>}
+
+
+\begin{document}
+
+\setkomavar{title}{
+ \rechnung~
+ \nr ~<%invnumber%>
+}
+\setkomavar*{date}{\rechnungsdatum}
+\setkomavar{date}{<%invdate%>}
+\setkomavar*{myref}{\mahnung~\nr}
+\setkomavar{myref}{<%dunning_id%>}
+\setkomavar{customer}{<%customernumber%>}
+\setkomavar{fromname}{<%employee_name%>}
+\setkomavar{fromphone}{<%employee_tel%>}
+\setkomavar{fromemail}{<%employee_email%>}
+<%if globalprojectnumber%>
+ \setkomavar{projectID}{<%globalprojectnumber%>}
+<%end globalprojectnumber%>
+\setkomavar{transaction}{<%transaction_description%>}
+
+\begin{letter}{
+ <%name%>\strut\\
+ <%if department_1%><%department_1%>\\<%end if%>
+ <%if department_2%><%department_2%>\\<%end if%>
+ <%cp_givenname%> <%cp_name%>\strut\\
+ <%street%>\strut\\
+ <%zipcode%> <%city%>\strut\\
+ <%country%> \strut
+ }
+
+% Bei Kontaktperson Anrede nach Geschlecht unterscheiden.
+% Bei natürlichen Personen persönliche Anrede, sonst allgemeine Anrede.
+\opening{
+ \ifstr{<%cp_name%>}{}
+ {<%if natural_person%><%greeting%> <%name%>,<%else%>\anrede<%end if%>}
+ {
+ \ifstr{<%cp_gender%>}{f}
+ {\anredefrau}
+ {\anredeherr}
+ <%cp_title%> <%cp_name%>,
+ }
+}
+\thispagestyle{kivitendo.letter.first}
+
+\mahnungsrechnungsformel
+
+
+
+\setlength\LTleft\parindent % Tabelle beginnt am linken Textrand
+\setlength\LTright{0pt} % Tabelle endet am rechten Textrand
+\begin{longtable}{@{}p{7cm}@{\extracolsep{\fill}}r@{}}
+% Tabellenkopf
+\hline
+\textbf{\posten} & \textbf{\betrag} \\
+\hline\\
+\endhead
+
+% Tabellenkopf erste Seite
+\hline
+\textbf{\posten} & \textbf{\betrag} \\
+\hline\\[-0.5em]
+\endfirsthead
+
+% Tabellenende
+\\
+\multicolumn{2}{@{}r@{}}{\weiteraufnaechsterseite}
+\endfoot
+
+% Tabellenende letzte Seite
+\hline\\
+\multicolumn{1}{@{}l}{\schlussbetrag} & <%invamount%> \currency\\
+\hline\hline\\
+\endlastfoot
+
+% eigentliche Tabelle
+Mahngebühren & <%fee%> \currency \\
+Zinsen & <%interest%> \currency \\
+\\[-0.8em]
+
+\end{longtable}
+
+
+\vspace{0.2cm}
+
+\bitteZahlenBis~<%duedate%>.
+
+\closing{\gruesse}
+
+\end{letter}
+
+\end{document}
[% L.input_tag('positions_scrollbar_height', positions_scrollbar_height, size = 5) %]
</td>
</tr>
+ <tr>
+ <th align="right">[% 'Search parts by vendor partnumber (model) in purchase order forms' | $T8 %]</th>
+ <td>
+ [% L.yes_no_tag('purchase_search_makemodel', purchase_search_makemodel) %]
+ [%- 'This also enables displaying a column with the vendor partnumber (model) (new order controller).' | $T8 %]
+ </td>
+ </tr>
+ <tr>
+ <th align="right">[% 'Search parts by customer partnumber in sales order forms' | $T8 %]</th>
+ <td>
+ [% L.yes_no_tag('sales_search_customer_partnumber', sales_search_customer_partnumber) %]
+ [%- 'This also enables displaying a column with the customer partnumber (new order controller).' | $T8 %]
+ </td>
+ </tr>
+ <tr>
+ <th align="right">[% 'Show update button for positions in order forms' | $T8 %]</th>
+ <td>
+ [% L.yes_no_tag('positions_show_update_button', positions_show_update_button) %]
+ </td>
+ </tr>
[%- END -%]
<tr>
</tr>
<tr class="coa_listrow[% loop.count % 2 %]">
- <td class="coa_detail_emph">[% IF row.taxkey %][% HTML.escape(row.taxkey).replace(',', '<br>') %][% ELSE %]-[% END %]</td>
- <td class="coa_detail_emph">[% IF row.taxaccount %][% HTML.escape(row.taxaccount).replace(',', '<br>') %][% ELSE %]-[% END %]</td>
- <td class="coa_detail_emph">[% IF row.taxdescription %][% HTML.escape(row.taxdescription).replace(',', '<br>') %][% ELSE %]-[% END %]</td>
- <td class="coa_detail_emph">[% IF row.tk_ustva %][% HTML.escape(row.tk_ustva).replace(',', '<br>') %][% ELSE %]-[% END %]</td>
- <td class="coa_detail_emph">[% IF row.startdate %][% HTML.escape(row.startdate).replace(',', '<br>') %][% ELSE %]-[% END %]</td>
+ <td class = "coa_detail_emph">[% IF row.taxkeys.size %][% FOR taxkey = row.taxkeys %][% HTML.escape(taxkey) %]<br>[% END %][% ELSE %]-[% END %]</td>
+ <td class = "coa_detail_emph">[% IF row.taxaccounts.size %][% FOR taxaccount = row.taxaccounts %][% HTML.escape(taxaccount) %]<br>[% END %][% ELSE %]-[% END %]</td>
+ <td class = "coa_detail_emph">[% IF row.taxdescriptions.size %][% FOR taxdescription = row.taxdescriptions %][% HTML.escape(taxdescription) %]<br>[% END %][% ELSE %]-[% END %]</td>
+ <td class = "coa_detail_emph">[% IF row.pos_ustvas.size %][% FOR pos_ustva = row.pos_ustvas %][% HTML.escape(pos_ustva) %]<br>[% END %][% ELSE %]-[% END %]</td>
+ <td class = "coa_detail_emph">[% IF row.startdates.size %][% FOR startdate = row.startdates %][% HTML.escape(startdate) %]<br>[% END %][% ELSE %]-[% END %]</td>
</tr>
<tr class="coa_listrow[% loop.count % 2 %]">
[%- USE T8 %]
[%- USE HTML %]
+[% INCLUDE "common/flash.html" %]
<h1>[% title %]</h1>
<table>
<input type="hidden" name="paidaccounts" value="[% paidaccounts | html %]">
+[%- P.hidden_tag('convert_from_oe_id', convert_from_oe_id) -%]
+
[% FOREACH i IN [1..paidaccounts] %]
[% temp = "acc_trans_id_"_ i %]
<input type="hidden" name="[% temp %]" value="[% $temp | html %]">
<th align="right" nowrap>[% 'Due Date' | $T8 %]</th>
<td>[% L.date_tag('duedate', duedate) %]</td>
</tr>
+ <tr>
+ <th align=right nowrap>[% 'Delivery Date' | $T8 %]</th>
+ <td>[% L.date_tag('deliverydate', deliverydate) %]</td>
+ </tr>
<tr>
<th align="right" nowrap>[% 'Project Number' | $T8 %]</th>
<td>
[% L.date_tag('transdateto') %]
</td>
</tr>
+ <tr>
+ <th align=right nowrap>[% 'Due Date' | $T8 %]</th>
+ <td>
+ [% L.date_tag('duedatefrom') %]
+ [% 'Bis' | $T8 %]
+ [% L.date_tag('duedateto') %]
+ </td>
+ </tr>
<input type=hidden name=sort value=transdate>
</table>
</td>
<td nowrap>[% 'Notes' | $T8 %]</td>
<td align=right><input name="l_employee" class=checkbox type=checkbox value=Y></td>
<td nowrap>[% 'Employee' | $T8 %]</td>
+ <td align=right><input name="l_department" class=checkbox type=checkbox value=Y></td>
+ <td nowrap>[% 'Department' | $T8 %]</td>
</tr>
<tr>
<td align=right><input name="l_subtotal" class=checkbox type=checkbox value=Y></td>
[% L.hidden_tag("action", "MassInvoiceCreatePrint/dispatch") %]
[% L.hidden_tag("printer_id") %]
+ [% L.hidden_tag("bothsided") %]
</form>
<form method="post" action="ar.pl" id="create_new_form">
[% IF ALL_PRINTERS.size %]
<div id="print_options" class="hidden">
+ <p>
+ [% LxERP.t8("Print both sided") %]:
+ [% L.checkbox_tag('', id="print_options_bothsided") %]
+ </p>
<p>
[% LxERP.t8("Print destination") %]:
[% SET printers = [ { description=LxERP.t8("Download PDF, do not print") } ] ;
[% L.hidden_tag('forex', forex) %]
[% IF show_exch %]
<th align=right>[% 'Exchangerate' | $T8 %]</th>
- <td>[%- IF forex %][% L.hidden_tag('exchangerate', LxERP.format_amount(exchangerate, 5)) %][% LxERP.format_amount(exchangerate, 5) %][%- ELSE %][% L.input_tag('exchangerate', LxERP.format_amount(exchangerate, 5), size=10) %][%- END %]</td>
+ <td>[%- IF forex %][% L.hidden_tag('exchangerate', LxERP.format_amount(exchangerate, 5, 1)) %][% LxERP.format_amount(exchangerate, 5, 1) %][%- ELSE %][% L.input_tag('exchangerate', LxERP.format_amount(exchangerate, 5, 1), size=10) %][%- END %]</td>
[% END %]
</tr>
[% IF ALL_DEPARTMENTS %]
<th align=right nowrap>[% 'Due Date' | $T8 %]</th>
<td>[% L.date_tag('duedate', duedate) %]</td>
</tr>
+ <tr>
+ <th align=right nowrap>[% 'Delivery Date' | $T8 %]</th>
+ <td>[% L.date_tag('deliverydate', deliverydate) %]</td>
+ </tr>
<tr>
<th align=right nowrap>[% 'Project Number' | $T8 %]</th>
<td>[% L.select_tag('globalproject_id', ALL_PROJECTS, title_key = 'projectnumber', default = globalproject_id, with_empty = 1, onChange = "document.getElementById('update_button').click();") %]</td>
[%- IF show_exch %]
<td align=center>
[%- IF row.forex || !row.changeable%]
- <input type=hidden name="exchangerate_[% loop.count %]" value='[% row.exchangerate | html %]'>[% row.exchangerate | html %]
+ <input type=hidden name="exchangerate_[% loop.count %]" value="[%- LxERP.format_amount(row.exchangerate, 5, 1) -%]">[%- LxERP.format_amount(row.exchangerate, 5, 1) -%]
[%- ELSE %]
- <input name="exchangerate_[% loop.count %]" size=10 value='[% row.exchangerate | html %]'>
+ <input name="exchangerate_[% loop.count %]" size=10 value="[%- LxERP.format_amount(row.exchangerate, 5, 1) -%]">
[%- END %]
<input type=hidden name="forex_[% loop.count %]" value='[% row.forex | html %]'>
</td>
<td>[% P.chart.picker('defaults.ar_chart_id', SELF.defaults.ar_chart_id, type='AR', choose=1, style=style) %]<td>
</tr>
- </table>
+ <tr>
+ <td align="right">[% LxERP.t8("Account for workflow from purchase order to ap transaction") %]</td>
+ <td>[% P.chart.picker('defaults.workflow_po_ap_chart_id', SELF.defaults.workflow_po_ap_chart_id, type='AP_amount', choose=1, style=style) %]<td>
+ </tr>
+
+ <tr>
+ <th align="right">[% LxERP.t8("Year-end closing") %]</th>
+ </tr>
+ <tr>
+ <td align="right">[% LxERP.t8("Carry over account for year-end closing") %]</td>
+ <td>[% P.chart.picker('defaults.carry_over_account_chart_id', SELF.defaults.carry_over_account_chart_id, category='A', choose=1, style=style) %]<td>
+ </tr>
+
+ <tr>
+ <td align="right">[% LxERP.t8("Profit carried forward account") %]</td>
+ <td>[% P.chart.picker('defaults.profit_carried_forward_chart_id', SELF.defaults.profit_carried_forward_chart_id, category='A', choose=1, style=style) %]<td>
+ </tr>
+
+ <tr>
+ <td align="right">[% LxERP.t8("Loss carried forward account") %]</td>
+ <td>[% P.chart.picker('defaults.loss_carried_forward_chart_id', SELF.defaults.loss_carried_forward_chart_id, category='A', choose=1, style=style) %]<td>
+ </tr>
+</table>
</div>
<td>[% L.yes_no_tag('defaults.vertreter', SELF.defaults.vertreter) %]</td>
<td>[% LxERP.t8('Representative for Customer') %]</td>
</tr>
- <tr>
+ <tr>
<td align="right">[% LxERP.t8('Normalize Customer / Vendor names') %]</td>
<td> [% L.yes_no_tag('defaults.normalize_vc_names', SELF.defaults.normalize_vc_names) %]</td>
<td>[% LxERP.t8('Automatic deletion of leading, trailing and excessive (repetitive) spaces in customer or vendor names') %]</td>
</tr>
+ <tr>
+ <td align="right">[% LxERP.t8('Use text field for greetings') %]</td>
+ <td> [% L.yes_no_tag('defaults.vc_greetings_use_textfield', SELF.defaults.vc_greetings_use_textfield) %]</td>
+ <td>[% LxERP.t8('Use a text field to enter (new) greetings if enabled. Otherwise, only a drop down box is offered.') %]</td>
+ </tr>
+ <tr>
+ <td align="right">[% LxERP.t8('Use text field for title of contacts') %]</td>
+ <td> [% L.yes_no_tag('defaults.contact_titles_use_textfield', SELF.defaults.contact_titles_use_textfield) %]</td>
+ <td>[% LxERP.t8('Use a text field to enter (new) contact titles if enabled. Otherwise, only a drop down box is offered.') %]</td>
+ </tr>
+ <tr>
+ <td align="right">[% LxERP.t8('Use text field for department of contacts') %]</td>
+ <td> [% L.yes_no_tag('defaults.contact_departments_use_textfield', SELF.defaults.contact_departments_use_textfield) %]</td>
+ <td>[% LxERP.t8('Use a text field to enter (new) contact departments if enabled. Otherwise, only a drop down box is offered.') %]</td>
+ </tr>
<tr>
<td align="right">[% LxERP.t8('Hourly Rate') %]</td>
<td>[% L.yes_no_tag("defaults.order_warn_no_deliverydate", SELF.defaults.order_warn_no_deliverydate) %]</td>
<td>[% LxERP.t8("If enabled a warning will be shown in sales and purchase orders if there the delivery date is empty.") %]</td>
</tr>
+ <tr>
+ <td align="right">[% LxERP.t8("Create sales invoices with ZUGFeRD data") %]</td>
+ <td>[% L.select_tag("defaults.create_zugferd_invoices", [ [ 0, LxERP.t8('Do not create ZUGFeRD invoices') ], [ 1, LxERP.t8('Create ZUGFeRD invoices') ], [ 2, LxERP.t8('Create ZUGFeRD invoices in test mode') ] ],
+ default=SELF.defaults.create_zugferd_invoices) %]</td>
+ <td>
+ [% LxERP.t8("If enabled ZUGFeRD-conformant sales invoice PDFs will be created.") %]
+ [% LxERP.t8("If the test mode is enabled, the ZUGFeRD invoices will be flagged so that they're only fit to be used for testing purposes.") %]
+ </td>
+ </tr>
<tr><td class="listheading" colspan="4">[% LxERP.t8("E-mail") %]</td></tr>
[% SET style="width: 400px" %]
<div id="miscellaneous">
<table>
- <tr><td class="listheading" colspan="4">[% LxERP.t8("Company settings") %]</td></tr>
+ <tr><td class="listheading" colspan="4">[% LxERP.t8("Company name and address") %]</td></tr>
<tr>
<td align="right">[% LxERP.t8("Company name") %]</td>
</tr>
<tr>
- <td align="right" valign="top">[% LxERP.t8("Address") %]</td>
- <td valign="top">[% L.textarea_tag('defaults.address', SELF.defaults.address, style=style, rows=4) %]</td>
+ <td align="right" valign="top">[% LxERP.t8("Street 1") %]</td>
+ <td>[% L.input_tag('defaults.address_street1', SELF.defaults.address_street1, style=style) %]</td>
+ </tr>
+
+ <tr>
+ <td align="right" valign="top">[% LxERP.t8("Street 2") %]</td>
+ <td>[% L.input_tag('defaults.address_street2', SELF.defaults.address_street2, style=style) %]</td>
</tr>
+ <tr>
+ <td align="right" valign="top">[% LxERP.t8("Zipcode and city") %]</td>
+ <td>
+ [% L.input_tag('defaults.address_zipcode', SELF.defaults.address_zipcode, size=8) %]
+ [% L.input_tag('defaults.address_city', SELF.defaults.address_city, size=30) %]
+ </td>
+ </tr>
+
+ <tr>
+ <td align="right" valign="top">[% LxERP.t8("Country") %]</td>
+ <td>[% L.input_tag('defaults.address_country', SELF.defaults.address_country, style=style) %]</td>
+ </tr>
+
+ <tr><td class="listheading" colspan="4">[% LxERP.t8("Company settings") %]</td></tr>
+
<tr>
<td align="right" valign="top">[% LxERP.t8("Signature") %]</td>
<td valign="top">[% L.textarea_tag('defaults.signature', SELF.defaults.signature, style=style, rows=4) %]</td>
[% LxERP.t8('Transfer out all items of a sales invoice when posting it. Items are transfered out acording to the settings above.') %]
</td>
</tr>
+ <tr>
+ <td align="right">[% LxERP.t8('Match Sales Invoice Serial numbers with inventory charge numbers?') %]</td>
+ <td>
+ [% L.yes_no_tag('defaults.sales_serial_eq_charge', SELF.defaults.sales_serial_eq_charge) %]
+ </td>
+ <td>
+ [% LxERP.t8('If one or more space separated serial numbers are assigned in a sales invoice, match the charge number of the inventory item. Assumes that Serial Number and Charge Number have 1:1 relation. Otherwise throw a error message for the default sales invoice transfer.') %]
+ </td>
+ </tr>
+
<tr>
<td align="right">[% LxERP.t8('Use default warehouse for assembly transfer') %]</td>
<td>
[% L.button_tag("kivi.SalesPurchase.copy_shipto_address()", LxERP.t8("Copy")) %]
</p>
+[% IF cs_obj ;
+ fields = ['shiptoname', 'shiptodepartment_1', 'shiptodepartment_2',
+ 'shiptostreet', 'shiptozipcode', 'shiptocity', 'shiptocountry',
+ 'shiptogln', 'shiptocontact', 'shiptocp_gender', 'shiptophone',
+ 'shiptofax', 'shiptoemail'] ;
+ FOREACH field = fields ;
+ $field = cs_obj.$field ;
+ END ;
+END ;
+'' %]
+
+
<table>
<tr class="listheading">
<th></th>
</table>
<p>
- [% L.button_tag("kivi.SalesPurchase.submit_custom_shipto()", LxERP.t8("Apply")) %]
+ [% L.button_tag("kivi.SalesPurchase.submit_custom_shipto('" _ id_selector _ "')", LxERP.t8("Apply")) %]
[% L.button_tag("kivi.SalesPurchase.reset_shipto_fields()", LxERP.t8("Reset")) %]
[% L.button_tag("kivi.SalesPurchase.clear_shipto_fields()", LxERP.t8("Clear fields")) %]
[% L.button_tag("\$('#shipto_dialog').dialog('close');", LxERP.t8("Abort")) %]
[% 'Element disabled' | $T8 %]
[%- END %]
[%- ELSIF ( var.config .type == 'bool' ) %]
- [% L.checkbox_tag(var_name, checked = var.value) %]
+ [% L.checkbox_tag(var_name, checked = var.value, for_submit = 1) %]
[%- ELSIF ( var.config .type == 'textfield' ) %]
[% L.textarea_tag(var_name, var.value, cols = var.config.processed_options.WIDTH, rows = var.config.processed_options.HEIGHT) %]
[%- ELSIF ( var.config.type == 'date' ) %]
--- /dev/null
+[% USE LxERP %]
+[% USE L %]
+<tr>
+ <th align="right">[%- LxERP.t8('Order/Item/Stock row name') %]:</th>
+ <td colspan="10">
+ [% L.input_tag('settings.order_column', SELF.profile.get('order_column'), size => "10") %]
+ [% L.input_tag('settings.item_column', SELF.profile.get('item_column'), size => "10") %]
+ [% L.input_tag('settings.stock_column', SELF.profile.get('stock_column'), size => "10") %]
+ </td>
+<tr>
+ <th align="right">[%- LxERP.t8('Error handling') %]:</th>
+ <td colspan="10">
+ [% L.checkbox_tag('settings.ignore_faulty_positions',
+ label => LxERP.t8('Ignore faulty positions'),
+ checked => SELF.profile.get('ignore_faulty_positions')) %]
+ </td>
+</tr>
[%- LxERP.t8('One of the columns "qty" or "target_qty" must be given. If "target_qty" is given, the quantity to transfer for each transfer will be calculate, so that the quantity for this part, warehouse and bin will result in the given "target_qty" after each transfer.') %]
</p>
-[%- ELSIF SELF.type == 'orders' OR SELF.type == 'ar_transactions' %]
+[%- ELSIF SELF.type == 'orders' OR SELF.type == 'delivery_orders' OR SELF.type == 'ar_transactions' %]
<p>
[1]:
[% LxERP.t8('The column "datatype" must be present and must be at the same position / column in each data set. The values must be the row names (see settings) for order and item data respectively.') %]
</p>
- <p>
- [2]:
- [%- LxERP.t8('Amount and net amount are calculated by kivitendo. "verify_amount" and "verify_netamount" can be used for sanity checks.') %]<br>
- [%- LxERP.t8('If amounts differ more than "Maximal amount difference" (see settings), this item is marked as invalid.') %]<br>
- </p>
+ [%- IF SELF.type == 'orders' OR SELF.type == 'ar_transactions' %]
+ <p>
+ [2]:
+ [%- LxERP.t8('Amount and net amount are calculated by kivitendo. "verify_amount" and "verify_netamount" can be used for sanity checks.') %]<br>
+ [%- LxERP.t8('If amounts differ more than "Maximal amount difference" (see settings), this item is marked as invalid.') %]<br>
+ </p>
+ [%- END %]
[%- END %][%# IF SELF.type == … %]
<p>
[%- INCLUDE 'csv_import/_form_inventories.html' %]
[%- ELSIF SELF.type == 'orders' %]
[%- INCLUDE 'csv_import/_form_orders.html' %]
+[%- ELSIF SELF.type == 'delivery_orders' %]
+ [%- INCLUDE 'csv_import/_form_delivery_orders.html' %]
[%- ELSIF SELF.type == 'ar_transactions' %]
[%- INCLUDE 'csv_import/_form_artransactions.html' %]
[%- ELSIF SELF.type == 'bank_transactions' %]
<tr>
<th align="right">[%- LxERP.t8('Preview Mode') %]:</th>
<td colspan="10">
- [% L.radio_button_tag('settings.full_preview', value=2, checked=SELF.profile.get('full_preview')==2, label=LxERP.t8('Full Preview')) %]
- [% L.radio_button_tag('settings.full_preview', value=1, checked=SELF.profile.get('full_preview')==1, label=LxERP.t8('Only Warnings and Errors')) %]
- [% L.radio_button_tag('settings.full_preview', value=0, checked=!SELF.profile.get('full_preview'), label=LxERP.t8('First 20 Lines')) %]
+ [% L.radio_button_tag('settings.full_preview', value=0, checked=!SELF.profile.get('full_preview'), label=LxERP.t8('Full Preview')) %]
+ [% L.radio_button_tag('settings.full_preview', value=1, checked=SELF.profile.get('full_preview')==1, label=LxERP.t8('Only Lines with Notes or Errors')) %]
+ [% L.radio_button_tag('settings.full_preview', value=2, checked=SELF.profile.get('full_preview')==2, label=LxERP.t8('First 20 Lines')) %]
</td>
</tr>
[%- ELSE %]
[%- LxERP.t8('Import result') %]
[%- END %]
+ [%- IF SELF.num_errors -%]
+ <font color="red">([%- SELF.num_errors -%] [%- LxERP.t8('Errors') -%])</font>
+ [%- END -%]
</h2>
[%- IF SELF.report.test_mode %]
[%- INCLUDE 'common/flash.html' %]
+ [%- SET show_deliveries = ( SELF.cv.id && ((SELF.is_customer && AUTH.assert('sales_all_edit', 1)) || (SELF.is_vendor && AUTH.assert('purchase_all_edit', 1))) ) -%]
<div class="tabwidget" id="customer_vendor_tabs">
<ul>
<li><a href="#billing">[% 'Billing Address' | $T8 %]</a></li>
<li><a href="#bank">[% 'Bank account' | $T8 %]</a></li>
<li><a href="#shipto">[% 'Shipping Address' | $T8 %]</a></li>
<li><a href="#contacts">[% 'Contacts' | $T8 %]</a></li>
- [% IF ( SELF.cv.id && AUTH.assert('sales_all_edit', 1) ) %]
+ [% IF show_deliveries %]
<li><a href="#deliveries">[% 'Supplies' | $T8 %]</a></li>
-[%- IF INSTANCE_CONF.get_doc_storage %]
- <li><a href="controller.pl?action=File/list&file_type=attachment&object_type=[% FORM.db == 'vendor' ? 'vendor' : 'customer' %]&object_id=[% SELF.cv.id %]">[% 'Attachments' | $T8 %]</a></li>
-[%- END %]
[% END %]
+ [%- IF INSTANCE_CONF.get_webdav %]
+ <li><a href="#ui-tabs-webdav">[% 'WebDAV' | $T8 %]</a></li>
+ [%- END %]
+ [%- IF INSTANCE_CONF.get_doc_storage %]
+ <li><a href="controller.pl?action=File/list&file_type=attachment&object_type=[% FORM.db == 'vendor' ? 'vendor' : 'customer' %]&object_id=[% SELF.cv.id %]">[% 'Attachments' | $T8 %]</a></li>
+ [%- END %]
<li><a href="#vcnotes">[% 'Notes' | $T8 %]</a></li>
[% IF ( cv_cvars.size ) %]
<li><a href="#price_rules">[% 'Price Rules' | $T8 %]</a></li>
[% END %]
+ [% IF ( SELF.cv.id && SELF.cv.pricegroup_id && AUTH.assert('part_service_assembly_details', 1) ) %]
+ <li><a href="#price_list">[% 'Price List' | $T8 %]</a></li>
+ [% END %]
+
[% IF SELF.cv.id %]
[% IF ( FORM.db == 'customer' && AUTH.assert('show_extra_record_tab_customer',1) ) %]
<li><a href="[% 'controller.pl?action=CustomerVendorTurnover/list_turnover&id=' _ SELF.cv.id _ '&db=' _ FORM.db %]">[% LxERP.t8('Records') %]
[% PROCESS "customer_vendor/tabs/bank.html" %]
[% PROCESS "customer_vendor/tabs/shipto.html" %]
[% PROCESS "customer_vendor/tabs/contacts.html" %]
- [% IF ( SELF.cv.id && AUTH.assert('sales_all_edit', 1) ) %]
+ [% IF show_deliveries %]
[% PROCESS "customer_vendor/tabs/deliveries.html" %]
[% END %]
+ [% PROCESS 'webdav/_list.html' %]
[% PROCESS "customer_vendor/tabs/vcnotes.html" %]
[% IF ( cv_cvars.size ) %]
[% PROCESS "customer_vendor/tabs/custom_variables.html" %]
[% IF SELF.cv.id %]
[% PROCESS "customer_vendor/tabs/price_rules.html" %]
[% END %]
+ [% IF ( SELF.cv.id && SELF.cv.pricegroup_id && AUTH.assert('part_service_assembly_details', 1) ) %]
+ [% PROCESS "customer_vendor/tabs/price_list.html" %]
+ [% END %]
</div>
</form>
<th align="right" nowrap>[% 'Greeting' | $T8 %]</th>
<td>
- [% L.input_tag('cv.greeting', SELF.cv.greeting) %]
- [% L.select_tag('cv_greeting_select', SELF.all_greetings, default = SELF.cv.greeting, with_empty = 1, onchange = '$("#cv_greeting").val(this.value);') %]
+ [%- IF INSTANCE_CONF.get_vc_greetings_use_textfield -%]
+ [% L.input_tag('cv.greeting', SELF.cv.greeting) %]
+ [% L.select_tag('cv_greeting_select', SELF.all_greetings, default = SELF.cv.greeting, value_key = 'description', title_key = 'description', with_empty = 1, onchange = '$("#cv_greeting").val(this.value);') %]
+ [%- ELSE -%]
+ [% L.select_tag('cv.greeting', SELF.all_greetings, default = SELF.cv.greeting, value_key = 'description', title_key = 'description', with_empty = 1) %]
+ [%- END -%]
</td>
</tr>
<td>
[% L.input_tag('cv.name', SELF.cv.name) %]
+ <label for="cv_natural_person">[% 'natural person' | $T8 %]</label>
+ [% L.checkbox_tag('cv.natural_person', checked = SELF.cv.natural_person, for_submit=1) %]
</td>
</tr>
<td>
[% L.checkbox_tag('cv.order_lock', checked = SELF.cv.order_lock, for_submit=1) %]
</td>
+ <th align="right">[% LxERP.t8("Create sales invoices with ZUGFeRD data") %]</th>
+ <td>[% L.select_tag("cv.create_zugferd_invoices",
+ [ [ -1, LxERP.t8('Use settings from client configuration') ],
+ [ 0, LxERP.t8('Do not create ZUGFeRD invoices') ],
+ [ 1, LxERP.t8('Create ZUGFeRD invoices') ],
+ [ 2, LxERP.t8('Create ZUGFeRD invoices in test mode') ] ],
+ default=SELF.cv.create_zugferd_invoices) %]</td>
</tr>
[% END %]
</table>
<th align="right" nowrap>[% 'Title' | $T8 %]</th>
<td>
- [% L.input_tag('contact.cp_title', SELF.contact.cp_title, size = 40) %]
- [% L.select_tag('contact_cp_title_select', SELF.all_titles, with_empty = 1, onchange = '$("#contact_cp_title").val(this.value);') %]
+ [%- IF INSTANCE_CONF.get_contact_titles_use_textfield -%]
+ [% L.input_tag('contact.cp_title', SELF.contact.cp_title, size = 40) %]
+ [% L.select_tag('contact_cp_title_select', SELF.all_contact_titles, default = SELF.contact.cp_title, value_key = 'description', title_key = 'description', with_empty = 1, onchange = '$("#contact_cp_title").val(this.value);') %]
+ [%- ELSE -%]
+ [% L.select_tag('contact.cp_title', SELF.all_contact_titles, default = SELF.contact.cp_title, value_key = 'description', title_key = 'description', with_empty = 1) %]
+ [%- END -%]
</td>
</tr>
<th align="right" nowrap>[% 'Department' | $T8 %]</th>
<td>
- [% L.input_tag('contact.cp_abteilung', SELF.contact.cp_abteilung, size = 40) %]
- [% L.select_tag('contact_cp_abteilung_select', SELF.all_departments, default = SELF.contact.cp_abteilung, with_empty = 1, onchange = '$("#contact_cp_abteilung").val(this.value);') %]
+ [%- IF INSTANCE_CONF.get_contact_departments_use_textfield -%]
+ [% L.input_tag('contact.cp_abteilung', SELF.contact.cp_abteilung, size = 40) %]
+ [% L.select_tag('contact_cp_abteilung_select', SELF.all_contact_departments, default = SELF.contact.cp_abteilung, value_key = 'description', title_key = 'description', with_empty = 1, onchange = '$("#contact_cp_abteilung").val(this.value);') %]
+ [%- ELSE -%]
+ [% L.select_tag('contact.cp_abteilung', SELF.all_contact_departments, default = SELF.contact.cp_abteilung, value_key = 'description', title_key = 'description', with_empty = 1) %]
+ [%- END -%]
</td>
</tr>
</table>
- [% L.button_tag('submitInputButton("delete_contact");', LxERP.t8('Delete Contact'), class = 'submit') %]
+ [% L.button_tag('submitInputButton("delete_contact");', LxERP.t8('Delete Contact'), id = 'action_delete_contact', class = 'submit') %]
[% IF ( !SELF.contact.cp_id ) %]
<script type="text/javascript">
$('#action_delete_contact').hide();
--- /dev/null
+[%- USE T8 %]
+[%- USE LxERP %]
+[%- USE HTML %]
+[%- USE L %]
+
+<div id="price_list">
+ [%- LxERP.t8("Loading...") %]
+</div>
</td>
<td>
- [% row.follow_up.created_for.safe_name | html %]
+ [% row.follow_up.created_for_employee.safe_name | html %]
</td>
<td>
<td>[% type | $T8 %]</td>
<td><a href="[% link %]?action=edit&id=[% row.id %]">[% row.invnumber | html %]</a></td>
<td>[% row.transdate.to_kivitendo | html %]</td>
- <td>[%- LxERP.format_amount(row.amount, 2) %]</td>
+ <td class="numeric">[%- LxERP.format_amount(row.amount, 2) %]</td>
<td>[% row.duedate.to_kivitendo | html %]</td>
- <td>[%- LxERP.format_amount(row.paid, 2) %]</td>
- <td>[%- LxERP.format_amount(row.amount - row.paid,2) %]
+ <td class="numeric">[%- LxERP.format_amount(row.paid, 2) %]</td>
+ <td class="numeric">[%- LxERP.format_amount(row.amount - row.paid,2) %]
[% IF FORM.db == 'customer' %]
<td>
[%- IF row.dunning_config_id != '' %]
[%- END -%]
[% END %]
<td>[% row.transdate.to_kivitendo | html %]</td>
- <td>[%- LxERP.format_amount(row.amount, 2) %]</td>
+ <td class="numeric">[%- LxERP.format_amount(row.amount, 2) %]</td>
<td>[% row.employee.name | html %]</td>
<td>[% row.transaction_description | html %]</td>
</tr>
<tr class="listrow[% loop.count % 2 %]">
<td>[% row.date_part | html %]</td>
<td>[% row.count | html %]</td>
- <td>[%- LxERP.format_amount(row.amount,2) %]</td>
- <td>[%- LxERP.format_amount(row.netamount,2) %]</td>
- <td>[%- LxERP.format_amount(row.paid,2) %]</td>
+ <td class="numeric">[%- LxERP.format_amount(row.amount,2) %]</td>
+ <td class="numeric">[%- LxERP.format_amount(row.netamount,2) %]</td>
+ <td class="numeric">[%- LxERP.format_amount(row.paid,2) %]</td>
</tr>
[% END %]
<td>[% type | $T8 %]</td>
<td><a href="[% link %]?action=edit&id=[% row.id %]">[% row.invnumber | html %]</a></td>
<td>[% row.transdate.to_kivitendo | html %]</td>
- <td>[%- LxERP.format_amount(row.amount, 2) %]</td>
+ <td class="numeric">[%- LxERP.format_amount(row.amount, 2) %]</td>
<td>[% row.duedate.to_kivitendo | html %]</td>
- <td>[%- LxERP.format_amount(row.paid, 2) %]</td>
- <td>[%- LxERP.format_amount(row.amount - row.paid, 2) %]
+ <td class="numeric">[%- LxERP.format_amount(row.paid, 2) %]</td>
+ <td class="numeric">[%- LxERP.format_amount(row.amount - row.paid, 2) %]
</tr>
[% END %]
</tbody>
[%- IF INSTANCE_CONF.get_feature_experimental_order -%]
<td>[% IF row.id %]<a href='controller.pl?action=Order/edit&type=[% IF FORM.db == "customer" %]sales_order[% ELSE %]purchase_order[% END %]&id=[% HTML.escape(row.id) %]'>[% END %][% HTML.escape(row.ordnumber) || ' ' %][% IF row.id %]</a>[% END %]</td>
[%- ELSE -%]
- <td>[% IF row.id %]<a href='oe.pl?action=edit&type=[% IF FORM.db == "customer" %]sales_order[% ELSE %]purchase_order[% END %]&vc=[% FORM.db %]&id=[% HTML.escape(row.oe_id) %]'>[% END %][% HTML.escape(row.ordnumber) || ' ' %][% IF row.id %]</a>[% END %]</td>
+ <td>[% IF row.id %]<a href='oe.pl?action=edit&type=[% IF FORM.db == "customer" %]sales_order[% ELSE %]purchase_order[% END %]&vc=[% FORM.db %]&id=[% HTML.escape(row.id) %]'>[% END %][% HTML.escape(row.ordnumber) || ' ' %][% IF row.id %]</a>[% END %]</td>
[%- END -%]
<td>[% row.transdate.to_kivitendo | html %]</td>
- <td>[%- LxERP.format_amount(row.amount, 2) %]</td>
+ <td class="numeric">[%- LxERP.format_amount(row.amount, 2) %]</td>
<td>[% row.reqdate.to_kivitendo | html %]</td>
<td>[% row.transaction_description %]</td>
</tr>
<td>[% IF row.id %]<a href='controller.pl?action=Order/edit&type=[% IF FORM.db == "customer" %]sales_quotation[% ELSE %]request_quotation[% END %]&id=[% HTML.escape(row.id) %]'>
[% END %][% HTML.escape(row.quonumber) || ' ' %][% IF row.id %]</a>[% END %]</td>
[%- ELSE -%]
- <td>[% IF row.id %]<a href='oe.pl?action=edit&type=[% IF FORM.db == "customer" %]sales_quotation[% ELSE %]request_quotation[% END %]&vc=[% FORM.db %]&id=[% HTML.escape(row.oe_id) %]'>
+ <td>[% IF row.id %]<a href='oe.pl?action=edit&type=[% IF FORM.db == "customer" %]sales_quotation[% ELSE %]request_quotation[% END %]&vc=[% FORM.db %]&id=[% HTML.escape(row.id) %]'>
[% END %][% HTML.escape(row.quonumber) || ' ' %][% IF row.id %]</a>[% END %]</td>
[%- END -%]
<td>[% row.transdate.to_kivitendo | html %]</td>
- <td>[%- LxERP.format_amount(row.amount, 2) %]</td>
+ <td class="numeric">[%- LxERP.format_amount(row.amount, 2) %]</td>
<td>[% row.reqdate.to_kivitendo | html %]</td>
<td>[% row.transaction_description %]</td>
</tr>
[%- IF !delivered %]
<div id="shipto_inputs" class="hidden">
- [%- PROCESS 'common/_ship_to_dialog.html' vc_obj=VC_OBJ %]
+ [%- PROCESS 'common/_ship_to_dialog.html' vc_obj=VC_OBJ cvars=shipto_cvars %]
</div>
[%- END %]
<input type="hidden" name="type" id="type" value="[% HTML.escape(type) %]">
<input type="hidden" name="vc" id="vc" value="[% HTML.escape(vc) %]">
<input type="hidden" name="lastmtime" id="lastmtime" value="[% HTML.escape(lastmtime) %]">
-[%- FOREACH row = HIDDENS %]
- [% L.hidden_tag(row.name, row.value) %]
-[%- END %]
<p>
<table width="100%">
-[%- USE T8 %]
+[%- USE T8 %][%- USE L %]
[% USE HTML %]<script type="text/javascript" src="js/common.js"></script>
<h1>[% title %]</h1>
</td>
</tr>
+ [% IF SHOW_DEPARTMENT_SELECTION %]
+ <tr>
+ <th align="right">[% 'Department' | $T8 %]</th>
+ <td colspan="3">
+ [% L.select_tag('department_id', ALL_DEPARTMENTS, title_key = 'description', with_empty = 1, style=style) %]
+ </td>
+ </tr>
+ [% END %]
+
[% IF SHOW_DUNNING_LEVEL_SELECTION %]
<tr>
<th align="right">[% 'Next Dunning Level' | $T8 %]</th>
<th align="right" nowrap><label for="l_include_direct_debit">[% 'Include invoices with direct debit' | $T8 %]</label></th>
<td><input type="checkbox" value="1" id="l_include_direct_debit" name="l_include_direct_debit"></td>
</tr>
+ <tr>
+ <th align="right" nowrap><label for="l_include_credit_notes">[% 'Add open Credit Notes' | $T8 %]</label></th>
+ <td><input type="checkbox" value="1" id="l_include_credit_notes" name="l_include_credit_notes"></td>
+ </tr>
</table>
</form>
<th class="listheading">[% 'eMail Send?' | $T8 %]</th>
<!-- <th class="listheading">[% 'Auto Send?' | $T8 %]</th> -->
<th class="listheading">[% 'Create invoice?' | $T8 %]</th>
+ <th class="listheading">[% 'Include original Invoices?' | $T8 %]</th>
<th class="listheading">[% 'Fristsetzung' | $T8 %]</th>
<th class="listheading">[% 'Duedate +Days' | $T8 %]</th>
<th class="listheading">[% 'Fee' | $T8 %]</th>
<!-- <td><input type="checkbox" name="auto_[% DUNNING_it.count %]" value="1" [% IF row.auto %]checked[% END %]></td> -->
<td><input type="checkbox" name="create_invoices_for_fees_[% DUNNING_it.count %]" value="1" [% IF row.create_invoices_for_fees %]checked[% END %]></td>
+ <td><input type="checkbox" name="print_original_invoice_[% DUNNING_it.count %]" value="1" [% IF row.print_original_invoice %]checked[% END %]></td>
<td><input name="payment_terms_[% DUNNING_it.count %]" size="3" value="[% HTML.escape(row.payment_terms) %]"></td>
<td><input name="terms_[% DUNNING_it.count %]" size="3" value="[% HTML.escape(row.terms) %]"></td>
<td><input name="fee_[% DUNNING_it.count %]" size="5" value="[% HTML.escape(row.fee) %]"></td>
<!-- <td><input type="checkbox" name="auto_[% rowcount %]" value="1" checked></td> -->
<td><input type="checkbox" name="create_invoices_for_fees_[% rowcount %]" value="1" checked></td>
+ <td><input type="checkbox" name="print_original_invoice_[% DUNNING_it.count %]" value="1" [% IF row.print_original_invoice %]checked[% END %]></td>
<td><input name="payment_terms_[% rowcount %]" size="3"></td>
<td><input name="terms_[% rowcount %]" size="3"></td>
<td><input name="fee_[% rowcount %]" size="5"></td>
[% SET all_active = 1 %][% FOREACH row = DUNNINGS %][% IF !row.active %][% SET all_active = 0 %][% LAST %][% END %][% END %]
[% SET all_email = 1 %][% FOREACH row = DUNNINGS %][% IF !row.email %][% SET all_email = 0 %][% LAST %][% END %][% END %]
+[% SET all_include_invoices = 1 %][% FOREACH row = DUNNINGS %][% IF !row.print_original_invoice %][% SET all_include_invoices = 0 %][% LAST %][% END %][% END %]
<form name="Form" method="post" action="dn.pl" id="form">
<h2>[% LxERP.t8("Print options") %]</h2>
[% L.checkbox_tag('selectall_email', checkall='INPUT[name*=email_]', checked=all_email) %]
<label for="selectall_email">[% 'eMail?' | $T8 %]</label>
</th>
+ <th class="listheading">
+ [% L.checkbox_tag('selectall_include_invoices', checkall='INPUT[name*=include_invoice_]', checked=all_include_invoices) %]
+ <label for="selectall_include_invoices">[% 'Include original Invoices?' | $T8 %]</label>
+ </th>
<th class="listheading">[% 'Customername' | $T8 %]</th>
+ <th class="listheading">[% 'Department' | $T8 %]</th>
<th class="listheading">[% 'Language' | $T8 %]</th>
<th class="listheading">[% 'Invno.' | $T8 %]</th>
<th class="listheading">[% 'Invdate' | $T8 %]</th>
</td>
<td>
+ [% IF row.credit_note %]
+ [% LxERP.t8("Add Credit Note for this dunning level:") %]
+ <input type="hidden" name="credit_note_[% loop.count %]" value="1">
+ [% END %]
<select name="next_dunning_config_id_[% loop.count %]">
[% FOREACH cfg_row = row.DUNNING_CONFIG %]<option value="[% HTML.escape(cfg_row.id) %]" [% IF cfg_row.SELECTED %]selected[% END %]>[% HTML.escape(cfg_row.dunning_description) %]</option>[% END %]
</select>
<td><input type="checkbox" name="active_[% loop.count %]" value="1" [% IF row.active %]checked[% END %]></td>
<td><input type="checkbox" name="email_[% loop.count %]" value="1" [% IF row.email %]checked[% END %]></td>
+ <td><input type="checkbox" name="include_invoice_[% loop.count %]" value="1" [% IF row.print_original_invoice %]checked[% END %]></td>
<td><input type="hidden" name="customername_[% loop.count %]" size="6" value="[% HTML.escape(row.customername) %]">[% HTML.escape(row.customername) %]</td>
+ <td><input type="hidden" name="department_[% loop.count %]" size="6" value="[% HTML.escape(row.departmentname) %]">[% HTML.escape(row.departmentname) %]</td>
<td><input type="hidden" name="language_id_[% loop.count %]" size="6" value="[% HTML.escape(row.language_id) %]">[% HTML.escape(row.language) %]</td>
<td>
<input type="hidden" name="invnumber_[% loop.count %]" size="6" value="[% HTML.escape(row.invnumber) %]">
<input name="rowcount" type="hidden" value="[% HTML.escape(rowcount) %]">
<input name="groupinvoices" type="hidden" value="[% HTML.escape(groupinvoices) %]">
-
+ <input name="l_include_credit_notes" type="hidden" value="[% HTML.escape(l_include_credit_notes) %]">
<input name="callback" type="hidden" value="[% HTML.escape(callback) %]">
</form>
<script type="text/javascript">
function calculate_qty() {
[%- FOREACH row = VARIABLES %]
- var [% row.name %] = parse_amount('[% MYCONFIG.numberformat %]', $('#calc_qty_form_id #[% row.name %]').val());
+ var [% row.name %] = kivi.parse_amount($('#calc_qty_form_id #[% row.name %]').val());
[%- END %]
var result = [% formel %];
- result = number_format(result, 2, '[% MYCONFIG.numberformat %]');
+ result = kivi.format_amount(result, 2);
if (document.CalcQtyForm.input_id.value) {
document.getElementById(document.CalcQtyForm.input_id.value).value = result;
} else {
$('#calc_qty_dialog').dialog('close');
}
- function parse_amount(numberformat, amount) {
- if (numberformat == '1.000,00' || numberformat == '1000,00')
- amount = amount.replace(/\./g, "").replace(/,/, ".");
- if (numberformat == "1'000.00")
- amount = amount.replace(/\'/g, '');
- return amount.replace(/,/g, '');
- }
-
- function number_format(number, precision, numberformat) {
- number = Math.round( number * Math.pow(10, precision) ) / Math.pow(10, precision);
- var nf = numberformat.replace(/\d/g, '').split('').reverse();
- var sep = nf[0];
- var th_sep = nf[1];
-
- str_number = number+"";
- arr_int = str_number.split(".");
- if(!arr_int[0]) arr_int[0] = "0";
- if(!arr_int[1]) arr_int[1] = "";
- if(arr_int[1].length < precision) {
- nachkomma = arr_int[1];
- for(i=arr_int[1].length+1; i <= precision; i++) {
- nachkomma += "0";
- }
- arr_int[1] = nachkomma;
- }
- if(th_sep != "" && arr_int[0].length > 3) {
- raw_arr_int = arr_int[0];
- arr_int[0] = "";
- for(j = 3; j < raw_arr_int.length ; j+=3) {
- arr_int[0] = th_sep + raw_arr_int.slice(raw_arr_int.length - j, raw_arr_int.length - j + 3) + arr_int[0] + "";
- }
- str_first = raw_arr_int.substr(0, (raw_arr_int.length % 3 == 0) ? 3 : (raw_arr_int.length % 3));
- arr_int[0] = str_first + arr_int[0];
- }
- return arr_int[0] + sep + arr_int[1];
- }
</script>
--- /dev/null
+[%- USE T8 %]
+[%- USE HTML %]
+<h1>[% HTML.escape(title) %]</h1>
+
+ [%- IF message %]
+ <p>
+ [% HTML.escape(message) %]
+ </p>
+ [%- END %]
+
+ <form method="post" action="generictranslations.pl" id="form">
+
+ <table>
+
+ <tr>
+ <th class="listheading"> </th>
+ <th class="listheading">[% 'ZUGFeRD notes for each invoice' | $T8 %]</th>
+ </tr>
+
+ [%- FOREACH language = LANGUAGES %]
+ <tr>
+ <td>
+ [%- IF language.id == 'default' %]
+ [% 'Default (no language selected)' | $T8 %]
+ [%- ELSE %]
+ [%- HTML.escape(language.description) %]
+ [%- END %]
+ </td>
+ <td><input name="translation__[% language.id %]" size="40" value="[% HTML.escape(language.translation) %]"></td>
+ </tr>
+ [%- END %]
+ </table>
+ </form>
<table>
<tr>
<th align=right width=50% nowrap>[% 'Transdate' | $T8 %]</th>
- <td>[% L.date_tag('transdate', transdate, readonly=readonly) %]</td>
+ <td align=left>[% L.date_tag('transdate', transdate, readonly=readonly) %]</td>
</tr>
</table>
</td>
<table>
<tr>
<th align=right width=50%>[% 'Gldate' | $T8 %]</th>
- <td align=left>[% L.date_tag('gldate', gldate, readonly=1) %]</td>
+ [%-# hidden img to keep alignment -%]
+ <td align=left>[% L.date_tag('gldate', gldate, readonly=1) %]<img class="ui-datepicker-trigger" src="image/calendar.png" alt="..." title="..." style='visibility:hidden'></td>
</tr>
</table>
</td>
</tr>
[%- END %]
-[%- IF ALL_DEPARTMENTS %]
+ [% SET departments_style = "";
+ SET departments_style = "style='visibility:hidden'" IF ALL_DEPARTMENTS.size == 0 %]
<tr>
- <th align=right nowrap>[% 'Department' | $T8 %]</th>
- <td>[% L.select_tag('department_id', ALL_DEPARTMENTS, default = department_id, title_key = 'description', with_empty = 1) %]</td>
+ <th [%- departments_style -%]align=right nowrap>[% 'Department' | $T8 %]</th>
+ <td [%- departments_style -%]>[% L.select_tag('department_id', ALL_DEPARTMENTS, default = department_id, title_key = 'description', with_empty = 1) %]</td>
+ <td align=left>
+ <table>
+ <tr>
+ <th align=right width=50% nowrap>[% 'Delivery Date' | $T8 %]</th>
+ <td align=left>[% L.date_tag('deliverydate', deliverydate) %]</td>
+ </tr>
+ </table>
</tr>
-[%- END %]
<tr>
<th align=right width=1%>[% 'Description' | $T8 %]</th>
[% USE L %][%- USE LxERP -%]
[% FOR row = TAX_ACCOUNTS %]
-<option value='[% row.id %]--[% row.rate %]'[% IF row.is_default %] selected[% END %]>[% row.taxdescription %] [% LxERP.round_amount(row.rate * 100) %] %</option>
+<option value='[% row.id %]--[% row.rate %]'[% IF row.is_default %] selected[% END %]>[% row.taxkey _ " - " _ row.taxdescription %] [% LxERP.round_amount(row.rate * 100) %] %</option>
[% END %]
+++ /dev/null
-[%- USE L %]
-[%- USE LxERP %]
- [%- L.hidden_tag("cb_date",SELF.cb_date.to_kivitendo) %]
- [%- L.hidden_tag("cb_startdate",SELF.cb_startdate.to_kivitendo) %]
- [%- L.hidden_tag("cb_reference",SELF.cb_reference) %]
- [%- L.hidden_tag("cb_description",SELF.cb_description) %]
- [%- L.hidden_tag("ob_date",SELF.ob_date.to_kivitendo) %]
- [%- L.hidden_tag("ob_reference",SELF.ob_reference) %]
- [%- L.hidden_tag("ob_description",SELF.ob_description) %]
- [%- L.hidden_tag("cbob_chart",SELF.cbob_chart) %]
- [%- L.hidden_tag("rowcount",SELF.row_count) %]
-</form>
+++ /dev/null
-[%- USE HTML %]
-[%- USE T8 %]
-[%- USE L %]
-[%- USE LxERP %]
-
-<h1>[% title | html %]</h1>
-
-[%- INCLUDE 'common/flash.html' %]
-
-<form id='filter_form'>
-
-<table>
- <tr>
- <td width="20px"></td>
- <td align="left" colspan="5">[% 'Attention: Here will be generated a lot of CB/OB transactions.' | $T8 %]</td>
- </tr>
- <tr>
- <td><p></p></td>
- </tr>
- <tr>
- <th></th>
- <th width="400px" colspan="2" align="center">[% 'CB Transactions' | $T8 %]</th>
- <th width="400px"colspan="2" align="center">[% 'OB Transactions' | $T8 %]</th>
- <th> </th>
- </tr>
- <tr>
- <td></td>
- <td align="right">[% 'Date' | $T8 %]</td>
- <td>[% L.date_tag('cb_date', SELF.cb_date) %]</td>
- <td align="right">[% 'Date' | $T8 %]</td>
- <td>[% L.date_tag('ob_date', SELF.ob_date) %]</td>
- <td></td>
- </tr>
- <tr>
- <td></td>
- <td align="right">[% 'Reference' | $T8 %]</td>
- <td>[% L.input_tag('cb_reference', SELF.cb_reference) %]</td>
- <td align="right">[% 'Reference' | $T8 %]</td>
- <td>[% L.input_tag('ob_reference', SELF.ob_reference) %]</td>
- <td></td>
- </tr>
- <tr>
- <td></td>
- <td align="right">[% 'Description' | $T8 %]</td>
- <td>[% L.input_tag('cb_description', SELF.cb_description) %]</td>
- <td align="right">[% 'Description' | $T8 %]</td>
- <td>[% L.input_tag('ob_description', SELF.ob_description) %]</td>
- <td></td>
- </tr>
- <tr>
- <td><p></p></td>
- </tr>
- <tr>
- <th colspan="2"></th>
- <th align=right>[% 'close chart' | $T8 %]</th>
- <td colspan="3">[% L.select_tag('cbob_chart', SELF.charts9000, title_sub=\make_title_of_chart, default=SELF.cbob_chart, style="width: 400px") %]</td>
- </tr>
-</table>
-</form>
+++ /dev/null
-[%- USE LxERP %]
-<form method="post" action="controller.pl" id="form">
-<table>
- <tr>
- <td width="20px"></td>
- <td colspan="6" align="left">
- [%- LxERP.t8('Select charts for which the CB/OB transactions want to be posted.') %]<br>
- [%- LxERP.t8('There will be two transactions done:') %]<br>
- - [%- LxERP.t8('One SB-transaction') %] ( [% SELF.cb_date.to_kivitendo %], [% SELF.cb_reference %], [% SELF.cb_description %], [% SELF.cbob_chartaccno %] )<br>
- - [%- LxERP.t8('One OB-transaction') %] ( [% SELF.ob_date.to_kivitendo %], [% SELF.ob_reference %], [% SELF.ob_description %], [% SELF.cbob_chartaccno %] )<br>
- [%- LxERP.t8('No revert available.') %]
- </td>
- </tr>
- <tr>
- <td><p></p></td>
- </tr>
- <tr>
- <th></th>
- <th align="right">[% LxERP.t8('close chart') %]</th>
- <td>[% SELF.cbob_chartaccno %]</td>
- </tr>
-</table>
<td colspan="3">
[% L.radio_button_tag('bom', id='bom_0', value=0, checked=1, label=LxERP.t8('Top Level Designation only')) %]
[% L.radio_button_tag('bom', id='bom_1', value=1, label=LxERP.t8('Individual Items')) %]
+ [% L.radio_button_tag('bom', id='bom_2', value=2, label=LxERP.t8('Search for Items used in Assemblies')) %]
</td>
</tr>
[% SET forex = 'forex_' _ i %]
[% SET exchangerate = 'exchangerate_' _ i %]
[% IF $forex %]
- <input type="hidden" name="exchangerate_[% i %]" value="[% LxERP.format_amount($exchangerate, 2) %]">
- [% LxERP.format_amount($forex, 2) %]
+ <input type="hidden" name="exchangerate_[% i %]" value="[% LxERP.format_amount($exchangerate, 5) %]">
+ [% LxERP.format_amount($forex, 5) %]
[% ELSE %]
[% IF $changeable %]
- <input name="exchangerate_[% i %]" size="10" value="[% LxERP.format_amount($exchangerate, 2, 1) %]">
+ <input name="exchangerate_[% i %]" size="10" value="[% LxERP.format_amount($exchangerate, 5, 1) %]">
[% ELSE %]
- <input type="hidden" name="exchangerate_[% i %]" value="[% LxERP.format_amount($exchangerate, 2, 1) %]">
- [% LxERP.format_amount($exchangerate, 2, 1) %]
+ <input type="hidden" name="exchangerate_[% i %]" value="[% LxERP.format_amount($exchangerate, 5, 1) %]">
+ [% LxERP.format_amount($exchangerate, 5, 1) %]
[% END %]
[% END %]
<input type="hidden" name="forex_[% i %]" value="[% $forex %]">
</td>
<td>
<table>
+ <tr>
+ <th align="right">[% 'Payment Terms' | $T8 %]</th>
+ <td>[% L.select_tag('payment_id', payment_terms, default = payment_id, title_key = 'description', with_empty = 1, style="width: 250px") %]
+ <script type='text/javascript'>$('#payment_id').change(function(){ kivi.SalesPurchase.set_duedate_on_reference_date_change("invdate"); })</script>
+ </td>
+ </tr>
<tr>
<th align="right">[% 'Delivery Terms' | $T8 %] </th>
<td>
<th align="right">[% 'Exchangerate' | $T8 %]</th>
<td>
[%- IF forex %]
- [% LxERP.format_amount(exchangerate, 2) %]
+ [% LxERP.format_amount(exchangerate, 5) %]
[%- ELSE %]
<input name="exchangerate" size="10" value="[% HTML.escape(LxERP.format_amount(exchangerate)) %]">
[%- END %]
<span id="duedate_fixed"[% IF !payment_terms_obj.auto_calculation %] style="display:none"[% END %]>[% HTML.escape(duedate) %]</span>
</td>
</tr>
+ <tr>
+ <th align="right">[% 'Delivery Date' | $T8 %]</th>
+ <td>[% L.date_tag('deliverydate', deliverydate) %]</td>
+ </tr>
<tr>
<th align="right" nowrap>[% 'Order Number' | $T8 %]</th>
<td colspan="3"><input size='11' name="ordnumber" value="[% HTML.escape(ordnumber) %]"></td>
[% SET forex = 'forex_' _ i %]
[% SET exchangerate = 'exchangerate_' _ i %]
[% IF $forex %]
- <input type="hidden" name="exchangerate_[% i %]" value="[% LxERP.format_amount($exchangerate, 2) %]">
- [% LxERP.format_amount($forex, 2) %]
+ <input type="hidden" name="exchangerate_[% i %]" value="[% LxERP.format_amount($exchangerate, 5) %]">
+ [% LxERP.format_amount($forex, 5) %]
[% ELSE %]
[% IF $changeable %]
- <input name="exchangerate_[% i %]" size="10" value="[% LxERP.format_amount($exchangerate, 2, 1) %]">
+ <input name="exchangerate_[% i %]" size="10" value="[% LxERP.format_amount($exchangerate, 5, 1) %]">
[% ELSE %]
- <input type="hidden" name="exchangerate_[% i %]" value="[% LxERP.format_amount($exchangerate, 2, 1) %]">
- [% LxERP.format_amount($exchangerate, 2, 1) %]
+ <input type="hidden" name="exchangerate_[% i %]" value="[% LxERP.format_amount($exchangerate, 5, 1) %]">
+ [% LxERP.format_amount($exchangerate, 5, 1) %]
[% END %]
[% END %]
<input type="hidden" name="forex_[% i %]" value="[% $forex %]">
<th align="right">[% 'Exchangerate' | $T8 %]</th>
<td>
[%- IF forex %]
- [% LxERP.format_amount(exchangerate, 2) %]
+ [% LxERP.format_amount(exchangerate, 5) %]
[%- ELSE %]
<input name="exchangerate" size="10" value="[% HTML.escape(LxERP.format_amount(exchangerate)) %]">
[%- END %]
<form method="post" name="loginscreen" action="controller.pl" target="_top">
<input type="hidden" name="show_dbupdate_warning" value="1">
+ [% L.hidden_tag("callback", callback) %]
<table width="100%">
<tr>
<th align="right">[% LxERP.t8('Customer') %]</th>
<td>[% L.input_tag('filter.customer.name:substr::ilike', filter.customer.name_substr__ilike, size = 20) %]</td>
</tr>
+ <tr>
+ <th align="right">[% LxERP.t8('Customer type') %]</th>
+ <td>
+ [% L.select_tag('filter.customer.business_id', SELF.all_businesses,
+ default => filter.customer.business_id
+ title_key => 'description',
+ value_key => 'id',
+ with_empty => 1,
+ style => 'width: 200px') %]
+ </td>
+ </tr>
+ <tr>
<th align="right">[% LxERP.t8('Delivery Order Date') %] [% LxERP.t8('From Date') %]</th>
<td>[% L.date_tag('filter.transdate:date::ge', filter.transdate_date__ge) %]</td>
</tr>
[% L.hidden_tag("action", "MassInvoiceCreatePrint/dispatch") %]
[% L.hidden_tag("printer_id") %]
+ [% L.hidden_tag("bothsided") %]
</form>
[% IF SELF.printers.size %]
<div id="print_options" class="hidden">
+ <p>
+ [% LxERP.t8("Print both sided") %]:
+ [% L.checkbox_tag('', id="print_options_bothsided") %]
+ </p>
<p>
[% LxERP.t8("Print destination") %]:
[% SET printers = [ { description=LxERP.t8("Download PDF, do not print") } ] ;
<th align="right">[% 'Exchangerate' | $T8 %]</th>
<td>
[%- IF forex %]
- [% LxERP.format_amount(exchangerate, 2) %]
+ [% LxERP.format_amount(exchangerate, 5) %]
[%- ELSE %]
<input name="exchangerate" size="10" value="[% HTML.escape(LxERP.format_amount(exchangerate)) %]">
[%- END %]
<tr>
<th align="right">[% 'Transaction description' | $T8 %]</th>
<td>[% L.input_tag("transaction_description", "", style=style) %]</td>
-
<th align="right">[% 'Part Description' | $T8 %]</th>
<td>[% L.input_tag("parts_description", "", style=style) %]</td>
</tr>
<tr>
<th align="right">[% 'Project' | $T8 %]</th>
<td>[% P.project.picker("project_id", '', style=style) %]</td>
-
<th align="right">[% 'Part Number' | $T8 %]</th>
<td>[% L.input_tag("parts_partnumber", "", style=style) %]</td>
</tr>
<input name="l_transaction_description" id="l_transaction_description" class="checkbox" type="checkbox" value="Y"[% IF INSTANCE_CONF.get_require_transaction_description_ps %] checked[% END %]>
<label for="l_transaction_description">[% 'Transaction description' | $T8 %]</label>
</td>
+ <td>
+ [%- L.checkbox_tag('l_department', label => LxERP.t8('Department')) %]</td>
+ </td>
+
</tr>
<tr>
<td>
</form>
</div>
+<div id="shipto_dialog" class="hidden"></div>
+
<form method="post" action="controller.pl" id="order_form">
[% L.hidden_tag('callback', FORM.callback) %]
[% L.hidden_tag('type', FORM.type) %]
<div id="ui-tabs-1">
[%- LxERP.t8("Loading...") %]
</div>
+
+ <div id="shipto_inputs" class="hidden">
+ [%- PROCESS 'common/_ship_to_dialog.html'
+ vc_obj=SELF.order.customervendor
+ cs_obj=SELF.order.custom_shipto
+ cvars=SELF.order.custom_shipto.cvars_by_config
+ id_selector='#order_shipto_id' %]
+ </div>
+
</div>
+
</form>
<table id="input_row_table_id">
<thead>
<tr class="listheading">
+ <th class="listheading" nowrap >[%- 'position' | $T8 %] </th>
<th class="listheading" nowrap >[%- 'Part' | $T8 %] </th>
<th class="listheading" nowrap >[%- 'Description' | $T8 %] </th>
<th class="listheading" nowrap width="5" >[%- 'Qty' | $T8 %] </th>
</thead>
<tbody>
<tr valign="top" class="listrow">
- <td>[% P.part.picker('add_item.parts_id', '', fat_set_item=1, style='width: 300px', class="add_item_input") %]</td>
+ <td>[% L.input_tag('add_item.position', '', size = 5, class="add_item_input numeric") %]</td>
+ <td>
+ [%- SET PARAM_KEY = SELF.cv == "customer" ? 'with_customer_partnumber' : 'with_makemodel' -%]
+ [%- SET PARAM_VAL = SELF.search_cvpartnumber -%]
+ [% P.part.picker('add_item.parts_id', '', fat_set_item=1, style='width: 300px', class="add_item_input", $PARAM_KEY=PARAM_VAL) %]</td>
<td>[% L.input_tag('add_item.description', '', class="add_item_input") %]</td>
<td>
[% L.input_tag('add_item.qty_as_number', '', size = 5, class="add_item_input numeric") %]
<div id='multi_items_result'></div>
<hr>
+[% 'At position' | $T8 %]
+[% L.input_tag('multi_items.position', '', size = 5, class="numeric") %]</td>
[% L.button_tag('kivi.Order.add_multi_items()', LxERP.t8('Continue'), id='multi_items_dialog_continue_button') %]
<a href="#" onclick="kivi.Order.close_multi_items_dialog();">[%- LxERP.t8("Cancel") %]</a>
[%- USE LxERP %]
[% SET best_price = price_source.best_price %]
[% SET best_discount = price_source.best_discount %]
-[% SET price_editable = AUTH.assert('edit_prices', 1) %]
+[% SET price_editable = 0 %]
+[% IF (FORM.type == "sales_order" || FORM.type == "sales_quotation") %]
+ [% SET price_editable = AUTH.assert('sales_edit_prices', 1) %]
+[% END %]
+[% IF (FORM.type == "purchase_order" || FORM.type == "request_quotation") %]
+ [% SET price_editable = AUTH.assert('purchase_edit_prices', 1) %]
+[% END %]
+[% SET exfactor = price_source.record.exchangerate ? 1 / price_source.record.exchangerate : 1 %]
+[% SET exnoshow = price_source.record.currency_id==INSTANCE_CONF.get_currency_id %]
+[% SET places = exnoshow ? -2 : 5 %]
<h2>[% 'Prices' | $T8 %]</h2>
<table>
<th></th>
<th>[% 'Price Source' | $T8 %]</th>
<th>[% 'Price' | $T8 %]</th>
+ <th [%- IF exnoshow -%]style='display:none'[%- END %]>
+ [% 'Price' | $T8 -%]/[%- price_source.record.currency.name %]
+ </th>
<th>[% 'Best Price' | $T8 %]</th>
<th>[% 'Details' | $T8 %]</th>
</tr>
[%- END %]
<td>[% 'None (PriceSource)' | $T8 %]</td>
<td>-</td>
+ <td [%- IF exnoshow -%]style='display:none'[%- END %]>-</td>
<td></td>
<td></td>
</tr>
[%- FOREACH price IN price_source.available_prices %]
<tr class='listrow'>
[%- IF price_source.record_item.active_price_source != price.source %]
- <td>[% L.button_tag('kivi.Order.update_price_source(\'' _ FORM.item_id _ '\', \'' _ price.source _ '\', \'' _ price.source_description _ '\', \'' _ LxERP.format_amount(price.price, -2) _ '\', ' _ price_editable _ ')', LxERP.t8('Select')) %]</td>
+ <td>[% L.button_tag('kivi.Order.update_price_source(\'' _ FORM.item_id _ '\', \'' _ price.source _ '\', \'' _ price.source_description _ '\', \'' _ LxERP.format_amount(price.price * exfactor, places) _ '\', ' _ price_editable _ ')', LxERP.t8('Select')) %]</td>
[%- ELSIF price_source.record_item.sellprice * 1 != price.price * 1 %]
- <td>[% L.button_tag('kivi.Order.update_price_source(\'' _ FORM.item_id _ '\', \'' _ price.source _ '\', \'' _ price.source_description _ '\', \'' _ LxERP.format_amount(price.price, -2) _ '\', ' _ price_editable _ ')', LxERP.t8('Update Price')) %]</td>
+ <td>[% L.button_tag('kivi.Order.update_price_source(\'' _ FORM.item_id _ '\', \'' _ price.source _ '\', \'' _ price.source_description _ '\', \'' _ LxERP.format_amount(price.price * exfactor, places) _ '\', ' _ price_editable _ ')', LxERP.t8('Update Price')) %]</td>
[%- ELSE %]
<td><b>[% 'Selected' | $T8 %]</b></td>
[% END %]
<td>[% price.source_description | html %]</td>
<td>[% price.price_as_number %]</td>
+ <td [%- IF exnoshow -%]style='display:none'[%- END %]>
+ [% LxERP.format_amount(price.price * exfactor, places) %]
+ </td>
[% IF price.source == best_price.source %]
<td align='center'>•</td>
[% ELSE %]
[%- USE L %]
[%- USE P %]
-<tbody class="row_entry listrow"[%- IF MYCONFIG.show_form_details -%] data-expanded="1"[%- END -%]>
+<tbody class="row_entry listrow" data-position="[%- HTML.escape(ITEM.position) -%]"[%- IF MYCONFIG.show_form_details -%] data-expanded="1"[%- END -%]>
<tr>
<td align="center">
[%- IF MYCONFIG.show_form_details %]
LxERP.t8("X"),
confirm=LxERP.t8("Are you sure?")) %]
</td>
+ [%- IF SELF.show_update_button -%]
+ <td align="center">
+ [%- L.img_tag(src="image/rotate_cw.svg",
+ alt=LxERP.t8('Update from master data'),
+ title= LxERP.t8('Update from master data'),
+ onclick="if (!confirm('" _ LxERP.t8("Are you sure to update this position from master data?") _ "')) return false; kivi.Order.update_row_from_master_data(this);",
+ id='update_from_master') %]
+ </td>
+ [%- END -%]
<td>
- <div name="partnumber">[% HTML.escape(ITEM.part.partnumber) %]</div>
+ <div name="partnumber">
+ [%- P.link_tag(SELF.url_for(controller='Part', action='edit', 'part.id'=ITEM.part.id), ITEM.part.partnumber, target="_blank", title=LxERP.t8('Open in new window')) -%]
+ </div>
</td>
+ [%- IF SELF.search_cvpartnumber -%]
+ <td>
+ <div name="cvpartnumber">[% HTML.escape(ITEM.cvpartnumber) %]</div>
+ </td>
+ [%- END -%]
<td>
<div name="partclassification">[% ITEM.part.presenter.typeclass_abbreviation %]</div>
</td>
[%- END -%]
[%- L.button_tag("kivi.Order.show_longdescription_dialog(this)", LxERP.t8("L")) %]
</td>
- [%- IF (TYPE == "sales_order" || TYPE == "purchase_order") -%]
+ [%- IF (SELF.type == "sales_order" || SELF.type == "purchase_order") -%]
<td nowrap>
[%- L.div_tag(LxERP.format_amount(ITEM.shipped_qty, 2, 0) _ ' ' _ ITEM.unit, name="shipped_qty", class="numeric") %]
</td>
</td>
<td>
[%- L.select_tag("order.orderitems[].price_factor_id",
- ALL_PRICE_FACTORS,
+ SELF.all_price_factors,
default = ITEM.price_factor_id,
title_key = 'description',
with_empty = 1,
ITEM.active_price_source.source_description _ ' | ' _ ITEM.active_discount_source.source_description,
name = "price_chooser_button") %]
</td>
+ [% SET RIGHT_TO_EDIT_PRICES = 0 %]
+ [% IF (SELF.type == "sales_order" || SELF.type == "sales_quotation") %]
+ [% SET RIGHT_TO_EDIT_PRICES = AUTH.assert('sales_edit_prices', 1) %]
+ [% END %]
+ [% IF (SELF.type == "purchase_order" || SELF.type == "request_quotation") %]
+ [% SET RIGHT_TO_EDIT_PRICES = AUTH.assert('purchase_edit_prices', 1) %]
+ [% END %]
<td>
[%- L.hidden_tag("order.orderitems[].active_price_source", ITEM.active_price_source.source) %]
- [%- SET EDIT_PRICE = (AUTH.assert('edit_prices', 1) && ITEM.active_price_source.source == '') %]
+ [%- SET EDIT_PRICE = (RIGHT_TO_EDIT_PRICES && ITEM.active_price_source.source == '') %]
<div name="editable_price" [%- IF !EDIT_PRICE %]style="display:none"[%- END %] class="numeric">
[%- L.input_tag("order.orderitems[].sellprice_as_number",
ITEM.sellprice_as_number,
</td>
<td>
[%- L.hidden_tag("order.orderitems[].active_discount_source", ITEM.active_discount_source.source) %]
- [%- SET EDIT_DISCOUNT = (AUTH.assert('edit_prices', 1) && ITEM.active_discount_source.source == '') %]
+ [%- SET EDIT_DISCOUNT = (RIGHT_TO_EDIT_PRICES && ITEM.active_discount_source.source == '') %]
<div name="editable_discount" [%- IF !EDIT_DISCOUNT %]style="display:none"[%- END %] class="numeric">
[%- L.input_tag("order.orderitems[].discount_as_percent",
ITEM.discount_as_percent,
<td colspan="100%">
[%- IF MYCONFIG.show_form_details || ITEM.render_second_row %]
<div name="second_row" data-loaded="1">
- [%- PROCESS order/tabs/_second_row.html ITEM=ITEM TYPE=TYPE %]
+ [%- PROCESS order/tabs/_second_row.html ITEM=ITEM TYPE=SELF.type %]
</div>
[%- ELSE %]
<div name="second_row" id="second_row_[% ID %]">
style='width: 300px') %]</td>
</tr>
- <tr id='shipto_row' [%- IF !SELF.order.${SELF.cv}.shipto.size %]style='display:none'[%- END %]>
+ <tr>
<th align="right">[% 'Shipping Address' | $T8 %]</th>
- <td>[% L.select_tag('order.shipto_id',
- SELF.order.${SELF.cv}.shipto,
- default=SELF.order.shipto_id,
- title_key='displayable_id',
- value_key='shipto_id',
- with_empty=1,
- style='width: 300px') %]</td>
+ <td>
+ <span id='shipto_selection' [%- IF !SELF.order.${SELF.cv}.shipto.size %]style='display:none'[%- END %]>
+ [% shiptos = [ { shipto_id => "", displayable_id => LxERP.t8("No/individual shipping address") } ] ;
+ FOREACH s = SELF.order.${SELF.cv}.shipto ;
+ shiptos.push(s) ;
+ END ;
+ L.select_tag('order.shipto_id',
+ shiptos,
+ default=SELF.order.shipto_id,
+ title_key='displayable_id',
+ value_key='shipto_id',
+ with_empty=0,
+ style='width: 300px') %]
+ </span>
+ [% L.button_tag("kivi.Order.edit_custom_shipto()", LxERP.t8("Custom shipto")) %]
+ </td>
</tr>
[%- PROCESS order/tabs/_business_info_row.html SELF=SELF %]
<td>[% L.select_tag('order.taxzone_id', SELF.all_taxzones, default=SELF.order.taxzone_id, title_key='description', style='width: 300px', class='recalc') %]</td>
</tr>
+ [% SET currency_id = SELF.order.currency_id || INSTANCE_CONF.get_currency_id # use default currency for new order %]
+ <tr id="currency_settings">
+ <th align="right">[% 'Currency' | $T8 %]</th>
+ <td>[% L.select_tag('order.currency_id', SELF.all_currencies, default=currency_id, value_key='id', title_key='name') %]</td>
+ </tr>
+ <tr id="exchangerate_settings" [%- IF SELF.order.currency_id==INSTANCE_CONF.get_currency_id %]style='display:none'[%- END %]>
+ <th align="right">[% 'Exchangerate' | $T8 %]</th>
+ <td> 1 <span id="currency_name">[% SELF.order.currency.name %]</span> =
+ [% L.input_tag('order.exchangerate_as_null_number', SELF.order.exchangerate_as_null_number, size="15", class="reformat_number_as_null_number numeric") %]
+ [% INSTANCE_CONF.default_currency %]
+ [% L.hidden_tag('old_currency_id', currency_id) %]
+ [% L.hidden_tag('old_exchangerate', SELF.order.exchangerate_as_null_number) %]
+ </td>
+ </tr>
+
[%- IF SELF.all_departments.size %]
<tr>
<th align="right">[% 'Department' | $T8 %]</th>
<tr>
<th align="right">[% 'Transaction description' | $T8 %]</th>
- <td>[% L.input_tag('order.transaction_description', SELF.order.transaction_description, style='width: 300px') %]</td>
+ <td>[% L.input_tag('order.transaction_description', SELF.order.transaction_description, 'data-validate'=INSTANCE_CONF.get_require_transaction_description_ps ? 'required' : '', style='width: 300px') %]</td>
</tr>
<tr>
</tr>
</table>
- [%- PROCESS order/tabs/_item_input.html %]
+ [%- PROCESS order/tabs/_item_input.html SELF=SELF %]
[% L.button_tag('kivi.Order.show_multi_items_dialog()', LxERP.t8('Add multiple items')) %]
<th class="listheading" nowrap width="3" >[%- 'position' | $T8 %] </th>
<th class="listheading" style='text-align:center' nowrap width="1"><img src="image/updown.png" alt="[%- LxERP.t8('reorder item') %]"></th>
<th class="listheading" style='text-align:center' nowrap width="1"><img src="image/close.png" alt="[%- LxERP.t8('delete item') %]"></th>
- <th id="partnumber_header_id" class="listheading" nowrap width="15"><a href='#' onClick='javascript:kivi.Order.reorder_items("partnumber")'> [%- 'Partnumber' | $T8 %]</a></th>
- <th id="partclass_header_id" class="listheading" nowrap width="2">[%- 'Type' | $T8 %]</th>
- <th id="description_header_id" class="listheading" nowrap ><a href='#' onClick='javascript:kivi.Order.reorder_items("description")'>[%- 'Description' | $T8 %]</a></th>
+ [%- IF SELF.show_update_button -%]
+ <th class="listheading" style='text-align:center' nowrap width="1">
+ [%- L.img_tag(src="image/rotate_cw.svg",
+ alt=LxERP.t8('Update from master data'),
+ title= LxERP.t8('Update from master data'),
+ onclick="if (!confirm('" _ LxERP.t8("Are you sure to update all positions from master data?") _ "')) return false; kivi.Order.update_all_rows_from_master_data();",
+ id='update_from_master') %]
+ </th>
+ [%- END %]
+ <th id="partnumber_header_id" class="listheading" nowrap width="15"><a href='#' onClick='javascript:kivi.Order.reorder_items("partnumber")'> [%- 'Partnumber' | $T8 %]</a></th>
+ [%- IF SELF.search_cvpartnumber -%]
+ <th id="cvpartnumber_header_id" class="listheading" nowrap width="15"><a href='#' onClick='javascript:kivi.Order.reorder_items("cvpartnumber")' > [%- SELF.cv == "customer" ? LxERP.t8('Customer Part Number') : LxERP.t8('Model') %]</a></th>
+ [%- END -%]
+ <th id="partclass_header_id" class="listheading" nowrap width="2">[%- 'Type' | $T8 %]</th>
+ <th id="description_header_id" class="listheading" nowrap ><a href='#' onClick='javascript:kivi.Order.reorder_items("description")'>[%- 'Description' | $T8 %]</a></th>
[%- IF (SELF.type == "sales_order" || SELF.type == "purchase_order") -%]
- <th id="shipped_qty_header_id" class="listheading" nowrap width="5" ><a href='#' onClick='javascript:kivi.Order.reorder_items("shipped_qty")'>[%- 'Delivered' | $T8 %]</a></th>
+ <th id="shipped_qty_header_id" class="listheading" nowrap width="5" ><a href='#' onClick='javascript:kivi.Order.reorder_items("shipped_qty")'>[%- 'Delivered' | $T8 %]</a></th>
[%- END -%]
- <th id="qty_header_id" class="listheading" nowrap width="5" ><a href='#' onClick='javascript:kivi.Order.reorder_items("qty")'> [%- 'Qty' | $T8 %]</a></th>
+ <th id="qty_header_id" class="listheading" nowrap width="5" ><a href='#' onClick='javascript:kivi.Order.reorder_items("qty")'> [%- 'Qty' | $T8 %]</a></th>
<th class="listheading" nowrap width="5" >[%- 'Price Factor' | $T8 %] </th>
<th class="listheading" nowrap width="5" >[%- 'Unit' | $T8 %] </th>
<th class="listheading" nowrap width="5" >[%- 'Price Source' | $T8 %] </th>
</thead>
[%- FOREACH item = SELF.order.items_sorted %]
- [%- PROCESS order/tabs/_row.html ITEM=item ID=(item.id||item.new_fake_id) TYPE=SELF.type ALL_PRICE_FACTORS=SELF.all_price_factors %]
+ [%- PROCESS order/tabs/_row.html ITEM=item ID=(item.id||item.new_fake_id) -%]
[%- END %]
</table>
<tr>
<th align="right">[% 'Part Description' | $T8 %]</th>
<td>
- [% L.areainput_tag("part.description", SELF.part.description, size=40) %]</td>
+ [% L.areainput_tag("part.description", SELF.part.description, size=40) %]
</td>
</tr>
<tr>
<tr>
<th align="right">[% 'Price Factor' | $T8 %]</th>
<td>
- [%- L.select_tag('part.price_factor_id', SELF.all_price_factors, default=SELF.part.price_factor_id, title_key='description', value_key='id', with_empty=1) %]</td>
+ [%- L.select_tag('part.price_factor_id', SELF.all_price_factors, default=SELF.part.price_factor_id, title_key='description', value_key='id', with_empty=1) %]
</td>
</tr>
[%- END %]
<th align="right" nowrap="true">[% 'Unit' | $T8 %]</th>
<td>
[%- IF !SELF.part.id or SELF.part.orphaned # same logic as unit_changable %]
- [%- L.select_tag('part.unit', SELF.all_units, default=SELF.part.unit, title_key='name', value_key='name') %]</td>
+ [%- L.select_tag('part.unit', SELF.all_units, default=SELF.part.unit, title_key='name', value_key='name') %]
[%- ELSE %]
[% L.hidden_tag('part.unit', SELF.part.unit) %] [% HTML.escape(SELF.part.unit) %]
[%- END %]
</td>
</tr>
-<div id="pricegroups">
[% PROCESS 'part/_pricegroup_prices.html' %]
-</div>
-
-<div id="customerprices">
[% PROCESS 'part/_customerprices.html' %]
-</div>
[%- UNLESS SELF.part.is_assembly %]
-<div id="makemodel">
[% PROCESS 'part/_makemodel.html' %]
-</div>
[% END %]
<tr>
<td>
<table id="customerprice_table">
<thead>
+ <tr>
<th class="listheading" style='text-align:center' nowrap width="1"><img src="image/close.png" alt="[%- LxERP.t8('delete item') %]"></th>
<th class="listheading">[% 'position' | $T8 %]</th>
<th class="listheading" style='text-align:center' nowrap width="1"><img src="image/updown.png" alt="[%- LxERP.t8('reorder item') %]"></th>
<th class="listheading">[% 'Customer Part Number' | $T8 %]</th>
<th class="listheading">[% 'Customer Price' | $T8 %]</th>
<th class="listheading">[% 'Updated' | $T8 %]</th>
+ </tr>
</thead>
<tbody id="customerprice_rows">
[% SET listrow = 0 %]
- [%- FOREACH customerprice = SELF.part.customerprices %]
+ [%- FOREACH customerprice = SELF.part.customerprices_sorted %]
[% listrow = listrow + 1 %]
[% PROCESS 'part/_customerprice_row.html' customerprice=customerprice listrow=listrow %]
[%- END %]
[%- FOREACH language = SELF.all_languages %]
[% SET language_id = language.id
translation = translations_map.$language_id %]
- [% L.hidden_tag('translations[+].language_id', language.id) %]
<tr class="listrow" valign="top">
- <td>[% HTML.escape(language.description) %]</td>
+ <td>
+ [% L.hidden_tag('translations[+].language_id', language.id) %]
+ [% HTML.escape(language.description) %]
+ </td>
<td>[% L.input_tag("translations[].translation", translation.translation) %]</td>
<td>[% L.textarea_tag("translations[].longdescription", P.restricted_html(translation.longdescription), id="translations_longdescription_" _ language_id, class="texteditor", style="width: 500px; height: 100px") %]</td>
</tr>
--- /dev/null
+[%- USE HTML %][%- USE L -%][%- USE P -%][%- USE LxERP -%][%- USE T8 -%]
+
+[%- IF AUTH.assert('warehouse_management', 1) -%]
+<p>
+[% 'Actions' | $T8 %]:
+ <span><a href="controller.pl?action=Inventory/stock_in&part_id=[% HTML.escape(SELF.part.id)%]&select_default_bin=1">[% 'Stock' | $T8 %]</a></span>
+ <span><a href="wh.pl?trans_type=transfer&action=transfer_warehouse_selection&parts_id=[% HTML.escape(SELF.part.id) %]">[% 'Transfer' | $T8 %]</a></span>
+ <span><a href="wh.pl?action=transfer_warehouse_selection&trans_type=removal&parts_id=[% HTML.escape(SELF.part.id) %]">[% 'Removal' | $T8 %]</a></span>
+</p>
+[%- END -%]
+
+<div id="inventory_data">
+</div>
+
+<script type='text/javascript'>
+$(function() {
+ $('.tabwidget').on('tabsbeforeactivate', function(event, ui){
+ if (ui.newPanel.attr('id') == 'inventory') {
+ $.ajax({
+ url: 'controller.pl?action=Part/inventory&id=[% SELF.part.id %]',
+ success: function (html) {
+ $("#inventory_data").html(html);
+ },
+ });
+ }
+ return 1;
+ });
+});
+</script>
--- /dev/null
+[%- USE HTML %][%- USE L -%][%- USE P -%][%- USE LxERP -%][%- USE T8 -%]
+
+[%- SET dec = 2 %]
+[%- SET show_warehouse_subtotals = 1 %]
+
+<div id="stock_levels">
+
+<h3>[% 'Stock levels' | $T8 %]</h3>
+
+[%- IF SELF.stock_amounts.size %]
+<a href="wh.pl?action=report&partnumber=[% HTML.escape(SELF.part.partnumber) %]">[% 'Stock levels' | $T8 %]</a>:
+<table>
+ <thead>
+ <tr class='listheading'>
+ <th>[% 'Warehouse' | $T8 %]</th>
+ <th>[% 'Bin' | $T8 %]</th>
+ <th>[% 'Qty' | $T8 %]</th>
+ <th>[% 'Unit' | $T8 %]</th>
+ <th>[% 'Stock value' | $T8 %]</th>
+ </tr>
+ </thead>
+ <tbody>
+ [% FOREACH stock = SELF.stock_amounts %]
+ <tr class='listrow'>
+ <td >[% HTML.escape(stock.warehouse_description) %]</td>
+ <td >[% IF stock.order_link %]<a target="_blank" href="[% stock.order_link %]">[% END %]
+ [% HTML.escape(stock.bin_description) %]
+ [% IF stock.order_link %]</a>[% END %]
+ </td>
+ <td class='numeric'>[% LxERP.format_amount(stock.qty, dec) %]</td>
+ <td >[% HTML.escape(stock.unit) %]</td>
+ <td class='numeric'>[% LxERP.format_amount(stock.stock_value, 2) %]</td>
+ </tr>
+ [% IF show_warehouse_subtotals AND stock.wh_lead != stock.warehouse_description %]
+ <tr class='listheading'>
+ <th >[% HTML.escape(stock.warehouse_description) %]</th>
+ <td></td>
+ <td class='numeric bold'>[% LxERP.format_amount(stock.wh_run_qty, dec) %]</td>
+ <td></td>
+ <td class='numeric bold'>[% LxERP.format_amount(stock.wh_run_stock_value, dec) %]</td>
+ </tr>
+ [% END %]
+ [% IF loop.last %]
+ <tr class='listheading'>
+ <th>[% 'Total' | $T8 %]</th>
+ <td></td>
+ <td class='numeric bold'>[% LxERP.format_amount(stock.run_qty, dec) %]</td>
+ <td></td>
+ <td class='numeric bold'>[% LxERP.format_amount(stock.run_stock_value, dec) %]</td>
+ </tr>
+ [% END %]
+ [% END %]
+ </tbody>
+</table>
+[% ELSE %]
+ <p>[% 'No transactions yet.' | $T8 %]</p>
+[% END %]
+</div>
+
+[% IF AUTH.assert('warehouse_management', 1) %]
+<div>
+<h3>[% 'Journal of Last 10 Transfers' | $T8 %]</h3>
+<a href="wh.pl?action=journal&partnumber=[% HTML.escape(SELF.part.partnumber) %]">[% 'WHJournal' | $T8 %]</a>:
+[%- IF SELF.journal.size %]
+<table>
+ <tr class='listheading'>
+ <th>[% 'Date' | $T8 %]</th>
+ <th>[% 'Trans Type' | $T8 %]</th>
+ <th>[% 'Warehouse From' | $T8 %]</th>
+ <th>[% 'Qty' | $T8 %]</th>
+ <th>[% 'Unit' | $T8 %]</th>
+ <th>[% 'Warehouse To' | $T8 %]</th>
+ <th>[% 'Charge Number' | $T8 %]</th>
+ <th>[% 'Comment' | $T8 %]</th>
+ </tr>
+[% FOREACH row = SELF.journal %]
+ <tr class='listrow'>
+ <td>[% row.base.itime_as_date %]</td>
+ <td>[% row.base.trans_type.description | $T8 %]</td>
+ <td>[% row.out ? row.out.bin.full_description : '-' | html %]</td>
+ <td class='numeric'>[% row.in ? row.in.qty_as_number : LxERP.format_amount(-1 * row.out.qty, 2) %]</td>
+ <td>[% row.base.part.unit | html %]</td>
+ <td>[% row.in ? row.in.bin.full_description : '-' | html %]</td>
+ <td>[% row.base.chargenumber | html %]</td>
+ <td>[% row.base.comment | html %]</td>
+ </tr>
+[% END %]
+</table>
+[%- ELSE %]
+<p>[% 'No transactions yet.' | $T8 %]</p>
+[%- END %]
+</div>
+[% END # assert warehouse_management %]
<td>
<table id="makemodel_table">
<thead>
+ <tr>
<th class="listheading" style='text-align:center' nowrap width="1"><img src="image/close.png" alt="[%- LxERP.t8('delete item') %]"></th>
<th class="listheading">[% 'position' | $T8 %]</th>
<th class="listheading" style='text-align:center' nowrap width="1"><img src="image/updown.png" alt="[%- LxERP.t8('reorder item') %]"></th>
<th class="listheading">[% 'Model' | $T8 %]</th>
<th class="listheading">[% 'Last Cost' | $T8 %]</th>
<th class="listheading">[% 'Updated' | $T8 %]</th>
+ </tr>
</thead>
<tbody id="makemodel_rows">
[% SET listrow = 0 %]
<table id='multi_items_filter_table'>
<tr>
<th>[%- LxERP.t8("Description") %]/[%- LxERP.t8("Partnumber") %]:</th>
- <td>[%- L.input_tag('multi_items.filter.all:substr:multi::ilike', SELF.multi_items_models.filtered.laundered.all_substr_multi__ilike) %]</td>
+ <td>[%- L.input_tag('multi_items_filter', search_term) %]</td>
<th>[%- LxERP.t8("Partsgroup") %]</th>
<td>[%- L.select_tag('multi_items.filter.partsgroup_id', all_partsgroups, title_key='partsgroup', value_key='id', with_empty=1) %]</td>
<tr>
[%- IF SELF.part.id %]
<li><a href="#price_rules">[% 'Price Rules' | $T8 %]</a></li>
[% END %]
+ [%- IF (AUTH.assert('warehouse_contents', 1) AND SELF.part.id AND NOT SELF.part.is_service) %]
+ <li><a href="#inventory">[% 'Inventories' | $T8 %]</a></li>
+ [%- END %]
[%- IF CUSTOM_VARIABLES.size %]
<li><a href="#custom_variables">[% 'Custom Variables' | $T8 %]</a></li>
[%- END %]
[% PROCESS 'part/_shop.html' %]
</div>
[%- END %]
+
+ [%- IF AUTH.assert('warehouse_contents', 1) AND SELF.part.id AND NOT SELF.part.is_service %]
+ <div id="inventory">
+ [% PROCESS 'part/_inventory.html' %]
+ </div>
+ [%- END %]
+
[%- END %]
[%- IF CUSTOM_VARIABLES.size %]
<div style='overflow:hidden'>
-[% LxERP.t8("Filter") %]: [% L.input_tag('part_picker_filter', SELF.models.filtered.laundered.all_substr_multi__ilike, class='part_picker_filter') %]
+[% LxERP.t8("Filter") %]: [% L.input_tag('part_picker_filter', search_term, class='part_picker_filter') %]
<div class='float-right'>
[% L.checkbox_tag('no_paginate', checked=FORM.no_paginate, id='no_paginate', for_submit=1, label=LxERP.t8('All as list')) %]
[% P.part.picker('part_id17', undef, status="obsolete") %]<br>
Artikel-Status: Alle<br>
[% P.part.picker('part_id18', undef, status="all") %]<br>
-
+<br>
Pre-filled:<br>
[% P.part.picker('part_id6', pre_filled_part) %]<br>
Convertible unit 'Std': (only select parts with unit Tag/Std/Min)<br>
[% P.part.picker('part_id7', undef, convertible_unit='Std') %]<br>
-
+<br>
With multi select popup<br>
[% P.part.picker('part_id8', undef, multiple=1) %]<br>
+With multi select popup (only obsolete)<br>
+[% P.part.picker('part_id8', undef, multiple=1, status='obsolete') %]<br>
+<br>
+All parts including make models of all vendors: <br>
+[% P.part.picker('part_id', undef, with_makemodel=1) %]<br>
+All parts including make models of all vendors with multi select popup: <br>
+[% P.part.picker('part_id', undef, with_makemodel=1, multiple=1) %]<br>
+All parts including customer partnumbers of all customers: <br>
+[% P.part.picker('part_id', undef, with_customer_partnumber=1) %]<br>
<h2>Styling</h2>
<th align="right">[% LxERP.t8('Chart') %]</th>
<td>[% P.chart.picker('object.chart_id', SELF.object.chart_id, type='AR_paid,AP_paid', category='A,L,Q', choose=1, style=style, "data-validate"="required", "data-title"=LxERP.t8("Chart")) %]</td>
</tr>
+ <tr>
+ <th align="right">[% LxERP.t8('Use for ZUGFeRD') %]</th>
+ <td>[% L.checkbox_tag('object.use_for_zugferd', checked = SELF.object.use_for_zugferd, for_submit=1) %]</td>
+ </tr>
<tr>
<th align="right">[% LxERP.t8('Obsolete') %]</th>
<td>[% L.checkbox_tag('object.obsolete', checked = SELF.object.obsolete, for_submit=1) %]</td>
+++ /dev/null
-[%- USE T8 %]
-[%- USE HTML %]
-
-<h1>[% 'Generic Tax Report' | $T8 %]</h1>
-<p>[% 'Taxnumber' | $T8 %]: [% taxnumber %]</p>
-<p>[% 'Year' | $T8 %]: [% year %]</p>
-<p>[% 'Period' | $T8 %]: [% period %]</p>
-<br />
-<table width="33%">
- <tr>
- <th>[% 'Tax Position' | $T8 %]</th>
- <th>[% 'Amount' | $T8 %]</th>
- </tr>
-[% FOREACH row = USTVA %]
- <tr class="listrow[% loop.count % 2 %]">
-
- <td align="left">[% HTML.escape(row.id) %]</td>
- <td align="right">[% HTML.escape(row.amount) %]</td>
- </tr>
-[% END %]
-
-</table>
-
<td class="spalte"><span class="nodis">(Spalte 86 rechts)</span></td>
<td class="betrag">[%pos_ustva_861%]</td>
</tr>
+[% IF pos_ustva_81b_kivi || pos_ustva_86b_kivi %]
+ <tr>
+ <td class="text2">zum Steuersatz von 16 v.H.(2020 Konjunktur)</td>
+ <td class="spalte ausfuellen"><span class="nodis">(Spalte </span>81b-kivi<span class="nodis">)</span></td>
+ <td class="betrag ausfuellen" width="70">[%pos_ustva_81b_kivi%]<br></td>
+ <td class="spalte"><span class="nodis">(Informativ)</span></td>
+ <td class="betrag">[%pos_ustva_811b_kivi%]</td>
+ </tr>
+ <tr>
+ <td class="text2">zum Steuersatz von 5 v.H.(2020 Konjunktur)</td>
+ <td class="spalte ausfuellen"><span class="nodis">(Spalte </span>86b-kivi<span class="nodis">)</span></td>
+ <td class="betrag ausfuellen" width="70">[%pos_ustva_86b_kivi%]<br></td>
+ <td class="spalte"><span class="nodis">(Informativ)</span></td>
+ <td class="betrag">[%pos_ustva_861b_kivi%]</td>
+ </tr>
+[%END%]
<tr>
<td class="text2">andere Steuersätze</td>
<td class="spalte ausfuellen"><span class="nodis"></span>35 <span class="nodis"></span></td>
<option value="business">[% 'Customer type' | $T8 %]</option>
<option value="salesman" selected="selected">[% 'Salesman' | $T8 %]</option>
<option value="month">[% 'Month' | $T8 %]</option>
+ <option value="shipvia">[% 'Ship via' | $T8 %]</option>
</select>
</td>
<td align=left><input name="l_headers_mainsort" class=checkbox type=checkbox value=Y checked> [% 'Heading' | $T8 %]</td>
<td align=left><input name="l_country" class=checkbox type=checkbox value=Y>[% 'Country' | $T8 %]</td>
<td align=left><input name="l_business" class=checkbox type=checkbox value=Y>[% 'Customer type' | $T8 %]</td>
</tr>
-
+ <tr>
+ <td align=left><input name="l_shipvia" class=checkbox type=checkbox value=Y>[% 'Ship via' | $T8 %]</td>
+ </tr>
<tr>
<th colspan="4" align="left">
[% 'Customer variables' | $T8 %] ([% 'Only shown in item mode' | $T8 %])
</tr>
<tr>
<th align="right" nowrap>[% 'Part Number' | $T8 %]:</th>
- <td><input name="partnumber" id="partnumber" size=20></td>
+ <td><input name="partnumber" id="partnumber" size=20 value="[% partnumber %]"></td>
</tr>
<tr>
<th align="right" nowrap>[% 'Parts Classification' | $T8 %]:</th>
[%- USE T8 %]
[%- USE L %]
[%- USE P %]
+[%- USE LxERP %]
[%- USE HTML %][%- USE JavaScript %]
<h1>[% 'Report about warehouse contents' | $T8 %]</h1>
</tr>
<tr>
<th align="right" nowrap>[% 'Part Number' | $T8 %]:</th>
- <td><input name="partnumber" size=20></td>
+ <td><input name="partnumber" size=20 value="[% partnumber %]"></td>
</tr>
<tr>
<th align="right" nowrap>[% 'Parts Classification' | $T8 %]:</th>
<th align="right" nowrap>[% 'Stock Qty for Date' | $T8 %]:</th>
<td>[% L.date_tag('date') %]</td>
</tr>
+ <tr>
+ <th align="right">
+ [% "basis for stock value" | $T8 %]:
+ </th>
+ <td align="left">
+ [% L.radio_button_tag("stock_value_basis", value='purchase_price', checked=1, label=LxERP.t8('Purchase price')) %]
+ [% L.radio_button_tag("stock_value_basis", value='list_price', checked=0, label=LxERP.t8('List Price')) %]
+ </td>
+ </tr>
+ <th align="right">
+ [% "List all rows" | $T8 %]:
+ </th>
+ <td align="left">
+ [% L.yes_no_tag("allrows", 1) %]
+ </td>
+ </tr>
+
</table>
</td>
</tr>
[% END %]
</tr>
- <tr><td colspan="4"><hr noshade height="1"></td></tr>
+ <tr><td colspan="6"><hr noshade height="1"></td></tr>
<tr>
<td align="right"><input name="subtotal" id="subtotal" class="checkbox" type="checkbox" value="Y"></td>
<td nowrap><label for="l_stock_value">[% 'Stock value' | $T8 %]</label></td>
<td align="right"><input name="l_purchase_price" id="l_purchase_price" class="checkbox" type="checkbox" value="Y"></td>
<td nowrap><label for="l_purchase_price">[% 'Purchase price' | $T8 %]</label></td>
+ <td align="right"><input name="l_list_price" id="l_list_price" class="checkbox" type="checkbox" value="Y"></td>
+ <td nowrap><label for="l_list_price">[% 'List Price' | $T8 %]</label></td>
</tr>
-
</table>
</td>
</tr>
$(function() {
warehouse_selected(0, 0);
- document.Form.partnumber.focus();
+ document.Form.part_id_name.focus();
});
-->
</script>
<tr>
<th align="right" nowrap>[% 'Part' | $T8 %]</th>
<td>
- [% P.part.picker("part_id", '', size="30", part_type="part,assembly") %]
+ [% P.part.picker("part_id", parts_id, size="30", part_type="part,assembly") %]
</td>
</tr>
--- /dev/null
+[%- USE LxERP -%]
+[%- USE T8 -%]
+[%- USE L -%]
+[%- USE HTML -%]
+[%- USE P -%]
+
+
+[%- SET dec = 2 %]
+
+<h2>[% 'Balance accounts' | $T8 %]</h2>
+<table cellpadding="3px">
+ <tr class="listheading">
+ <th >[%- 'Account' | $T8 %]</th>
+ <th >[%- 'Description' | $T8 %]</th>
+ <th colspan="2">[%- 'Starting Balance' | $T8 %]</th>
+ <th colspan="2">[%- 'Balance with CB' | $T8 %]</th>
+ <th colspan="2">[%- 'Closing Balance' | $T8 %]</th>
+ </tr>
+ <tr class="listheading">
+ <th></th>
+ <th></th>
+ <th>[%- 'Debit' | $T8 %]</th>
+ <th>[%- 'Credit' | $T8 %]</th>
+ <th>[%- 'Debit' | $T8 %]</th>
+ <th>[%- 'Credit' | $T8 %]</th>
+ <th>[%- 'Debit' | $T8 %]</th>
+ <th>[%- 'Credit' | $T8 %]</th>
+ </tr>
+ [% FOREACH chart = charts %]
+ [%- NEXT UNLESS chart.account_type == 'asset_account' -%]
+ <tr id="tr_[% loop.count %]" class="listrow[% loop.count % 2 %]">
+ <td> [% chart.accno | html %]</td>
+ <td> [% chart.description | html %]</td>
+ <td class="numeric"> [% IF chart.ob_amount < 0 %] [% LxERP.format_amount(chart.ob_amount * -1, dec) %] [% END %]</td>
+ <td class="numeric"> [% IF chart.ob_amount > 0 %] [% LxERP.format_amount(chart.ob_amount, dec) %] [% END %]</td>
+ <td class="numeric"> [% IF chart.amount_with_cb < 0 %] [% LxERP.format_amount(chart.amount_with_cb * -1, dec) %] [% END %]</td>
+ <td class="numeric"> [% IF chart.amount_with_cb > 0 %] [% LxERP.format_amount(chart.amount_with_cb, dec) %] [% END %]</td>
+ [% # cb amounts: >/< are switched and cb_amounts are multiplied with -1. The closing balance as calculated by cb_amount negates the actual balance, but when displaying it as the closing balance we want to display it in the same form as the actual balance %]
+ <td class="numeric"> [% IF chart.cb_amount > 0 %] [% LxERP.format_amount(chart.cb_amount * 1, dec) %] [% END %]</td>
+ <td class="numeric"> [% IF chart.cb_amount < 0 %] [% LxERP.format_amount(chart.cb_amount * -1, dec) %] [% END %]</td>
+ </tr>
+ [% END %]
+</table>
+
+<h2>[% 'Profit and loss accounts' | $T8 %]</h2>
+
+<p>
+[% IF profit_loss_sum < 0 %] [% THEN %][% 'Loss' | $T8 %] [% ELSE %] [% 'Profit' | $T8 %] [% END %]:
+[% LxERP.format_amount(profit_loss_sum, dec) %]
+</p>
+
+<table cellpadding="3px">
+ <tr class="listheading">
+ <th >[%- 'Account' | $T8 %]</th>
+ <th >[%- 'Description' | $T8 %]</th>
+ <th colspan=2>[%- 'Balance with CB' | $T8 %]</th>
+ <th colspan=2>[%- 'Closing Balance' | $T8 %]</th>
+ </tr>
+ <tr class="listheading">
+ <th></th>
+ <th></th>
+ <th>[%- 'Debit' | $T8 %]</th>
+ <th>[%- 'Credit' | $T8 %]</th>
+ <th>[%- 'Debit' | $T8 %]</th>
+ <th>[%- 'Credit' | $T8 %]</th>
+ </tr>
+ [% FOREACH chart = charts %]
+ [%- NEXT UNLESS chart.account_type == 'profit_loss_account' -%]
+ <tr id="tr_[% loop.count %]" class="listrow[% loop.count % 2 %]">
+ <td >[% chart.accno | html %]</td>
+ <td >[% chart.description | html %]</td>
+ <td class="numeric">[% IF chart.amount_with_cb < 0 %] [% LxERP.format_amount(chart.amount_with_cb * -1, dec) %] [% END %]</td>
+ <td class="numeric">[% IF chart.amount_with_cb > 0 %] [% LxERP.format_amount(chart.amount_with_cb, dec) %] [% END %]</td>
+ <td class="numeric">[% IF chart.cb_amount > 0 %] [% LxERP.format_amount(chart.cb_amount * 1, dec) %] [% END %]</td>
+ <td class="numeric">[% IF chart.cb_amount < 0 %] [% LxERP.format_amount(chart.cb_amount * -1, dec) %] [% END %]</td>
+ </tr>
+ [% END %]
+</table>
+[% # L.dump(charts) %]
--- /dev/null
+[%- USE HTML %]
+[%- USE T8 %]
+[%- USE L %]
+[%- USE LxERP %]
+
+<h1>[% title | html %]</h1>
+
+[%- INCLUDE 'common/flash.html' %]
+
+[% IF carry_over_chart AND profit_chart AND loss_chart %] [% THEN %]
+<form id="filter" name="filter" method="post" action="controller.pl">
+<table>
+ <tr>
+ <td align="right">[% 'Year-end date' | $T8 %]</td>
+ <td>[% L.date_tag('cb_date', SELF.cb_date) %]</td>
+ </tr>
+ <tr class="startdate">
+ <td align="right">[% 'Startdate method' | $T8 %]</td>
+ <td>[% L.select_tag('balance_startdate_method', balance_startdate_method_options, value_key = 'value', title_key = 'title') %]</td>
+ </tr>
+ <tr class="startdate">
+ <td align="right">[% 'Start date' | $T8 %]</td>
+ <td>[% L.date_tag('cb_startdate', '', readonly=1) %]</td>
+ </tr>
+ <tr>
+ <td align="right">[% 'Carry over account for year-end closing' | $T8 %]</td>
+ <td>[% carry_over_chart.displayable_name | html %]</td>
+ </tr>
+ <tr>
+ <td align="right">[% 'Profit carried forward account' | $T8 %]</td>
+ <td>[% profit_chart.displayable_name | html %]</td>
+ </tr>
+ <tr>
+ <td align="right">[% 'Loss carried forward account' | $T8 %]</td>
+ <td>[% loss_chart.displayable_name | html %]</td>
+ </tr>
+</table>
+</form>
+[% ELSE %]
+ [% 'Please configure the carry over and profit and loss accounts for year-end closing in the client configuration!' | $T8 %]
+[% END %]
+
+[% # L.button_tag("refresh_charts();", LxERP.t8("Preview")) %]
+[% L.button_tag("year_end_bookings();", LxERP.t8("Apply year-end bookings"), id='apply_year_end_bookings_button', confirm=LxERP.t8("Are you sure?")) %]
+
+<div id="charts" style="padding-top: 20px">
+</div>
+
+<script type="text/javascript">
+
+ function get_startdate() {
+ $.get("controller.pl", {
+ action: 'YearEndTransactions/get_start_date',
+ cb_date: $('#cb_date').val(),
+ balance_startdate_method: $('#balance_startdate_method').val()
+ }, kivi.eval_json_result)
+ }
+
+ function year_end_bookings() {
+ $.post("controller.pl", {
+ action: 'YearEndTransactions/year_end_bookings',
+ cb_date: $('#cb_date').val(),
+ }, kivi.eval_json_result)
+ }
+
+ function refresh_charts() {
+ var filterdata = $('#filter').serialize()
+ var url = './controller.pl?action=YearEndTransactions/update_charts&' + filterdata;
+ $.ajax({
+ url : url,
+ type: 'GET',
+ success: function(data){
+ $('#charts').html(data);
+ }
+ })
+ };
+
+$(function(){
+
+ $('#apply_year_end_bookings_button').hide();
+ $('.startdate').hide();
+
+ $('#balance_startdate_method').change(function(){
+ get_startdate();
+ setTimeout(function() {
+ refresh_charts();
+ }, 200);
+ });
+
+ $('#cb_date').change(function(){
+ get_startdate();
+ setTimeout(function() {
+ refresh_charts();
+ }, 200);
+ });
+})
+
+</script>
--- /dev/null
+[%- USE HTML %]
+[%- USE LxERP %]
+[%- USE L %]
+[%- USE T8 %]
+[%- INCLUDE 'common/flash.html' %]
+ <div class="listtop">[% FORM.title %]</div>
+
+ <p>
+ [% "Import a ZUGFeRD file:" | $T8 %]
+ </p>
+
+ <form method="post" action="controller.pl" enctype="multipart/form-data" id="form">
+ [% L.input_tag('file', '', type => 'file', accept => '.pdf') %]
+ </form>
+
--- /dev/null
+%% !!NOTE NOTE NOTE!!
+%%
+%% This is a modified version of `embedfile.sty' generated from a
+%% modified `embedfile.dtx' incorporating the following pull request:
+%% https://github.com/ho-tex/oberdiek/pull/72
+%%
+%% This PR adds support for creating PDF/A-compliant attachments. See
+%% also the following issue:
+%% https://github.com/ho-tex/oberdiek/issues/37
+%%
+%% !!END OF NOTE NOTE NOTE!!
+%%
+%%
+%% This is file `embedfile.sty',
+%% generated with the docstrip utility.
+%%
+%% The original source files were:
+%%
+%% embedfile.dtx (with options: `package')
+%%
+%% This is a generated file.
+%%
+%% Project: embedfile
+%% Version: 2018/11/01 v2.8
+%%
+%% Copyright (C) 2006-2011 by
+%% Heiko Oberdiek <heiko.oberdiek at googlemail.com>
+%%
+%% This work may be distributed and/or modified under the
+%% conditions of the LaTeX Project Public License, either
+%% version 1.3c of this license or (at your option) any later
+%% version. This version of this license is in
+%% http://www.latex-project.org/lppl/lppl-1-3c.txt
+%% and the latest version of this license is in
+%% http://www.latex-project.org/lppl.txt
+%% and version 1.3 or later is part of all distributions of
+%% LaTeX version 2005/12/01 or later.
+%%
+%% This work has the LPPL maintenance status "maintained".
+%%
+%% This Current Maintainer of this work is Heiko Oberdiek.
+%%
+%% The Base Interpreter refers to any `TeX-Format',
+%% because some files are installed in TDS:tex/generic//.
+%%
+%% This work consists of the main source file embedfile.dtx
+%% and the derived files
+%% embedfile.sty, embedfile.pdf, embedfile.ins, embedfile.drv,
+%% dtx-attach.sty, embedfile-example-plain.tex,
+%% embedfile-example-collection.tex, embedfile-test1.tex,
+%% embedfile-test2.tex, embedfile-test3.tex,
+%% embedfile-test4.tex.
+%%
+\begingroup\catcode61\catcode48\catcode32=10\relax%
+ \catcode13=5 % ^^M
+ \endlinechar=13 %
+ \catcode35=6 % #
+ \catcode39=12 % '
+ \catcode44=12 % ,
+ \catcode45=12 % -
+ \catcode46=12 % .
+ \catcode58=12 % :
+ \catcode64=11 % @
+ \catcode123=1 % {
+ \catcode125=2 % }
+ \expandafter\let\expandafter\x\csname ver@embedfile.sty\endcsname
+ \ifx\x\relax % plain-TeX, first loading
+ \else
+ \def\empty{}%
+ \ifx\x\empty % LaTeX, first loading,
+ % variable is initialized, but \ProvidesPackage not yet seen
+ \else
+ \expandafter\ifx\csname PackageInfo\endcsname\relax
+ \def\x#1#2{%
+ \immediate\write-1{Package #1 Info: #2.}%
+ }%
+ \else
+ \def\x#1#2{\PackageInfo{#1}{#2, stopped}}%
+ \fi
+ \x{embedfile}{The package is already loaded}%
+ \aftergroup\endinput
+ \fi
+ \fi
+\endgroup%
+\begingroup\catcode61\catcode48\catcode32=10\relax%
+ \catcode13=5 % ^^M
+ \endlinechar=13 %
+ \catcode35=6 % #
+ \catcode39=12 % '
+ \catcode40=12 % (
+ \catcode41=12 % )
+ \catcode44=12 % ,
+ \catcode45=12 % -
+ \catcode46=12 % .
+ \catcode47=12 % /
+ \catcode58=12 % :
+ \catcode64=11 % @
+ \catcode91=12 % [
+ \catcode93=12 % ]
+ \catcode123=1 % {
+ \catcode125=2 % }
+ \expandafter\ifx\csname ProvidesPackage\endcsname\relax
+ \def\x#1#2#3[#4]{\endgroup
+ \immediate\write-1{Package: #3 #4}%
+ \xdef#1{#4}%
+ }%
+ \else
+ \def\x#1#2[#3]{\endgroup
+ #2[{#3}]%
+ \ifx#1\@undefined
+ \xdef#1{#3}%
+ \fi
+ \ifx#1\relax
+ \xdef#1{#3}%
+ \fi
+ }%
+ \fi
+\expandafter\x\csname ver@embedfile.sty\endcsname
+\ProvidesPackage{embedfile}%
+ [2018/11/01 v2.8 Embed files into PDF (HO)]%
+\begingroup\catcode61\catcode48\catcode32=10\relax%
+ \catcode13=5 % ^^M
+ \endlinechar=13 %
+ \catcode123=1 % {
+ \catcode125=2 % }
+ \catcode64=11 % @
+ \def\x{\endgroup
+ \expandafter\edef\csname EmFi@AtEnd\endcsname{%
+ \endlinechar=\the\endlinechar\relax
+ \catcode13=\the\catcode13\relax
+ \catcode32=\the\catcode32\relax
+ \catcode35=\the\catcode35\relax
+ \catcode61=\the\catcode61\relax
+ \catcode64=\the\catcode64\relax
+ \catcode123=\the\catcode123\relax
+ \catcode125=\the\catcode125\relax
+ }%
+ }%
+\x\catcode61\catcode48\catcode32=10\relax%
+\catcode13=5 % ^^M
+\endlinechar=13 %
+\catcode35=6 % #
+\catcode64=11 % @
+\catcode123=1 % {
+\catcode125=2 % }
+\def\TMP@EnsureCode#1#2{%
+ \edef\EmFi@AtEnd{%
+ \EmFi@AtEnd
+ \catcode#1=\the\catcode#1\relax
+ }%
+ \catcode#1=#2\relax
+}
+\TMP@EnsureCode{39}{12}% '
+\TMP@EnsureCode{40}{12}% (
+\TMP@EnsureCode{41}{12}% )
+\TMP@EnsureCode{44}{12}% ,
+\TMP@EnsureCode{46}{12}% .
+\TMP@EnsureCode{47}{12}% /
+\TMP@EnsureCode{58}{12}% :
+\TMP@EnsureCode{60}{12}% <
+\TMP@EnsureCode{62}{12}% >
+\TMP@EnsureCode{91}{12}% [
+\TMP@EnsureCode{93}{12}% ]
+\TMP@EnsureCode{96}{12}% `
+\edef\EmFi@AtEnd{\EmFi@AtEnd\noexpand\endinput}
+\begingroup\expandafter\expandafter\expandafter\endgroup
+\expandafter\ifx\csname RequirePackage\endcsname\relax
+ \def\EmFi@RequirePackage#1[#2]{%
+ \input #1.sty\relax
+ }%
+\else
+ \let\EmFi@RequirePackage\RequirePackage
+\fi
+\EmFi@RequirePackage{infwarerr}[2007/09/09]%
+\def\EmFi@Error{%
+ \@PackageError{embedfile}%
+}
+\ifx\pdfextension\@undefined\else
+ \protected\def\pdflastobj {\numexpr\pdffeedback lastobj\relax}
+ \protected\def\pdfnames {\pdfextension names }
+ \protected\def\pdfobj {\pdfextension obj }
+ \let\pdfoutput \outputmode
+\fi
+\EmFi@RequirePackage{ifpdf}[2007/09/09]
+\ifpdf
+\else
+ \EmFi@Error{%
+ Missing pdfTeX in PDF mode%
+ }{%
+ Currently other drivers are not supported. %
+ Package loading is aborted.%
+ }%
+ \expandafter\EmFi@AtEnd
+\fi%
+\EmFi@RequirePackage{pdftexcmds}[2007/11/11]
+\EmFi@RequirePackage{ltxcmds}[2010/03/01]
+\EmFi@RequirePackage{kvsetkeys}[2010/03/01]
+\EmFi@RequirePackage{kvdefinekeys}[2010/03/01]
+\begingroup\expandafter\expandafter\expandafter\endgroup
+\expandafter\ifx\csname pdf@filesize\endcsname\relax
+ \EmFi@Error{%
+ Unsupported pdfTeX version%
+ }{%
+ At least version 1.30 is necessary. Package loading is aborted.%
+ }%
+ \expandafter\EmFi@AtEnd
+\fi%
+\EmFi@RequirePackage{pdfescape}[2007/11/11]
+\def\EmFi@temp#1{%
+ \expandafter\EdefSanitize\csname EmFi@S@#1\endcsname{#1}%
+}
+\EmFi@temp{details}%
+\EmFi@temp{tile}%
+\EmFi@temp{hidden}%
+\EmFi@temp{text}
+\EmFi@temp{date}
+\EmFi@temp{number}
+\EmFi@temp{file}
+\EmFi@temp{desc}
+\EmFi@temp{afrelationship}
+\EmFi@temp{moddate}
+\EmFi@temp{creationdate}
+\EmFi@temp{size}
+\EmFi@temp{ascending}
+\EmFi@temp{descending}
+\EmFi@temp{true}
+\EmFi@temp{false}
+\ltx@newif\ifEmFi@collection
+\ltx@newif\ifEmFi@sort
+\ltx@newif\ifEmFi@visible
+\ltx@newif\ifEmFi@edit
+\ltx@newif\ifEmFi@item
+\ltx@newif\ifEmFi@finished
+\ltx@newif\ifEmFi@id
+\def\EmFi@GlobalKey#1#2{%
+ \global\expandafter\let\csname KV@#1@#2\expandafter\endcsname
+ \csname KV@#1@#2\endcsname
+}
+\def\EmFi@GlobalDefaultKey#1#2{%
+ \EmFi@GlobalKey{#1}{#2}%
+ \global\expandafter\let
+ \csname KV@#1@#2@default\expandafter\endcsname
+ \csname KV@#1@#2@default\endcsname
+}
+\def\EmFi@DefineKey#1#2{%
+ \kv@define@key{EmFi}{#1}{%
+ \expandafter\def\csname EmFi@#1\endcsname{##1}%
+ }%
+ \expandafter\def\csname EmFi@#1\endcsname{#2}%
+}
+\EmFi@DefineKey{mimetype}{}
+\EmFi@DefineKey{filespec}{\EmFi@file}
+\EmFi@DefineKey{ucfilespec}{}
+\EmFi@DefineKey{filesystem}{}
+\EmFi@DefineKey{desc}{}
+\EmFi@DefineKey{afrelationship}{}
+\EmFi@DefineKey{stringmethod}{%
+ \ifx\pdfstringdef\@undefined
+ escape%
+ \else
+ \ifx\pdfstringdef\relax
+ escape%
+ \else
+ psd%
+ \fi
+ \fi
+}
+\kv@define@key{EmFi}{id}{%
+ \def\EmFi@id{#1}%
+ \EmFi@idtrue
+}
+\def\EmFi@defobj#1{%
+ \ifEmFi@id
+ \expandafter\xdef\csname EmFi@#1@\EmFi@id\endcsname{%
+ \the\pdflastobj\ltx@space 0 R%
+ }%
+ \fi
+}
+\def\embedfileifobjectexists#1#2{%
+ \expandafter\ifx\csname EmFi@#2@#1\endcsname\relax
+ \expandafter\ltx@secondoftwo
+ \else
+ \expandafter\ltx@firstoftwo
+ \fi
+}
+\def\embedfilegetobject#1#2{%
+ \embedfileifobjectexists{#1}{#2}{%
+ \csname EmFi@#2@#1\endcsname
+ }{%
+ 0 0 R%
+ }%
+}
+\kv@define@key{EmFi}{view}[]{%
+ \EdefSanitize\EmFi@temp{#1}%
+ \def\EmFi@next{%
+ \global\EmFi@collectiontrue
+ }%
+ \ifx\EmFi@temp\ltx@empty
+ \let\EmFi@view\EmFi@S@details
+ \else\ifx\EmFi@temp\EmFi@S@details
+ \let\EmFi@view\EmFi@S@details
+ \else\ifx\EmFi@temp\EmFi@S@tile
+ \let\EmFi@view\EmFi@S@tile
+ \else\ifx\EmFi@temp\EmFi@S@hidden
+ \let\EmFi@view\EmFi@S@hidden
+ \else
+ \let\EmFi@next\relax
+ \EmFi@Error{%
+ Unknown value `\EmFi@temp' for key `view'.\MessageBreak
+ Supported values: `details', `tile', `hidden'.%
+ }\@ehc
+ \fi\fi\fi\fi
+ \EmFi@next
+}
+\EmFi@DefineKey{initialfile}{}
+\def\embedfilesetup{%
+ \ifEmFi@finished
+ \def\EmFi@next##1{}%
+ \EmFi@Error{%
+ \string\embedfilefield\ltx@space after \string\embedfilefinish
+ }{%
+ The list of embedded files is already written.%
+ }%
+ \else
+ \def\EmFi@next{%
+ \kvsetkeys{EmFi}%
+ }%
+ \fi
+ \EmFi@next
+}
+\def\EmFi@schema{}
+\gdef\EmFi@order{0}
+\let\EmFi@@order\relax
+\def\EmFi@fieldlist{}
+\def\EmFi@sortcase{0}%
+\def\embedfilefield#1#2{%
+ \ifEmFi@finished
+ \EmFi@Error{%
+ \string\embedfilefield\ltx@space after \string\embedfilefinish
+ }{%
+ The list of embedded files is already written.%
+ }%
+ \else
+ \global\EmFi@collectiontrue
+ \EdefSanitize\EmFi@key{#1}%
+ \expandafter\ifx\csname KV@EmFi@\EmFi@key.prefix\endcsname\relax
+ \begingroup
+ \count@=\EmFi@order
+ \advance\count@ 1 %
+ \xdef\EmFi@order{\the\count@}%
+ \let\EmFi@title\EmFi@key
+ \let\EmFi@type\EmFi@S@text
+ \EmFi@visibletrue
+ \EmFi@editfalse
+ \kvsetkeys{EmFiFi}{#2}%
+ \EmFi@convert\EmFi@title\EmFi@title
+ \xdef\EmFi@schema{%
+ \EmFi@schema
+ /\pdf@escapename{\EmFi@key}<<%
+ /Subtype/%
+ \ifx\EmFi@type\EmFi@S@date D%
+ \else\ifx\EmFi@type\EmFi@S@number N%
+ \else\ifx\EmFi@type\EmFi@S@file F%
+ \else\ifx\EmFi@type\EmFi@S@desc Desc%
+ \else\ifx\EmFi@type\EmFi@S@afrelationship AFRelationship%
+ \else\ifx\EmFi@type\EmFi@S@moddate ModDate%
+ \else\ifx\EmFi@type\EmFi@S@creationdate CreationDate%
+ \else\ifx\EmFi@type\EmFi@S@size Size%
+ \else S%
+ \fi\fi\fi\fi\fi\fi\fi
+ /N(\EmFi@title)%
+ \EmFi@@order{\EmFi@order}%
+ \ifEmFi@visible
+ \else
+ /V false%
+ \fi
+ \ifEmFi@edit
+ /E true%
+ \fi
+ >>%
+ }%
+ \let\do\relax
+ \xdef\EmFi@fieldlist{%
+ \EmFi@fieldlist
+ \do{\EmFi@key}%
+ }%
+ \ifx\EmFi@type\EmFi@S@text
+ \kv@define@key{EmFi}{\EmFi@key.value}{%
+ \EmFi@itemtrue
+ \def\EmFi@temp{##1}%
+ \EmFi@convert\EmFi@temp\EmFi@temp
+ \expandafter\def\csname EmFi@V@#1%
+ \expandafter\endcsname\expandafter{%
+ \expandafter(\EmFi@temp)%
+ }%
+ }%
+ \EmFi@GlobalKey{EmFi}{\EmFi@key.value}%
+ \else\ifx\EmFi@type\EmFi@S@date
+ \kv@define@key{EmFi}{\EmFi@key.value}{%
+ \EmFi@itemtrue
+ \def\EmFi@temp{##1}%
+ \EmFi@convert\EmFi@temp\EmFi@temp
+ \expandafter\def\csname EmFi@V@#1%
+ \expandafter\endcsname\expandafter{%
+ \expandafter(\EmFi@temp)%
+ }%
+ }%
+ \EmFi@GlobalKey{EmFi}{\EmFi@key.value}%
+ \else\ifx\EmFi@type\EmFi@S@number
+ \kv@define@key{EmFi}{\EmFi@key.value}{%
+ \EmFi@itemtrue
+ \expandafter\EdefSanitize\csname EmFi@V@#1\endcsname{ ##1}%
+ }%
+ \EmFi@GlobalKey{EmFi}{\EmFi@key.value}%
+ \fi\fi\fi
+ \kv@define@key{EmFi}{\EmFi@key.prefix}{%
+ \EmFi@itemtrue
+ \expandafter\def\csname EmFi@P@#1\endcsname{##1}%
+ }%
+ \EmFi@GlobalKey{EmFi}{\EmFi@key.prefix}%
+ \kv@define@key{EmFiSo}{\EmFi@key}[ascending]{%
+ \EdefSanitize\EmFi@temp{##1}%
+ \ifx\EmFi@temp\EmFi@S@ascending
+ \def\EmFi@temp{true}%
+ \else\ifx\EmFi@temp\EmFi@S@descending
+ \def\EmFi@temp{false}%
+ \else
+ \def\EmFi@temp{}%
+ \EmFi@Error{%
+ Unknown sort order `\EmFi@temp'.\MessageBreak
+ Supported values: `\EmFi@S@ascending', %
+ `\EmFi@S@descending
+ }\@ehc
+ \fi\fi
+ \ifx\EmFi@temp\ltx@empty
+ \else
+ \xdef\EmFi@sortkeys{%
+ \EmFi@sortkeys
+ /\pdf@escapename{#1}%
+ }%
+ \ifx\EmFi@sortorders\ltx@empty
+ \global\let\EmFi@sortorders\EmFi@temp
+ \gdef\EmFi@sortcase{1}%
+ \else
+ \xdef\EmFi@sortorders{%
+ \EmFi@sortorders
+ \ltx@space
+ \EmFi@temp
+ }%
+ \xdef\EmFi@sortcase{2}%
+ \fi
+ \fi
+ }%
+ \EmFi@GlobalDefaultKey{EmFiSo}\EmFi@key
+ \endgroup
+ \else
+ \EmFi@Error{%
+ Field `\EmFi@key' is already defined%
+ }\@ehc
+ \fi
+ \fi
+}
+\kv@define@key{EmFiFi}{type}{%
+ \EdefSanitize\EmFi@temp{#1}%
+ \ifx\EmFi@temp\EmFi@S@text
+ \let\EmFi@type\EmFi@temp
+ \else\ifx\EmFi@temp\EmFi@S@date
+ \let\EmFi@type\EmFi@temp
+ \else\ifx\EmFi@temp\EmFi@S@number
+ \let\EmFi@type\EmFi@temp
+ \else\ifx\EmFi@temp\EmFi@S@file
+ \let\EmFi@type\EmFi@temp
+ \else\ifx\EmFi@temp\EmFi@S@desc
+ \let\EmFi@type\EmFi@temp
+ \else\ifx\EmFi@temp\EmFi@S@afrelationship
+ \let\EmFi@type\EmFi@temp
+ \else\ifx\EmFi@temp\EmFi@S@moddate
+ \let\EmFi@type\EmFi@temp
+ \else\ifx\EmFi@temp\EmFi@S@creationdate
+ \let\EmFi@type\EmFi@temp
+ \else\ifx\EmFi@temp\EmFi@S@size
+ \let\EmFi@type\EmFi@temp
+ \else
+ \EmFi@Error{%
+ Unknown type `\EmFi@temp'.\MessageBreak
+ Supported types: `text', `date', `number', `file',\MessageBreak
+ `desc', `afrelationship', `moddate', `creationdate', `size'%
+ }%
+ \fi\fi\fi\fi\fi\fi\fi\fi\fi
+}
+\kv@define@key{EmFiFi}{title}{%
+ \def\EmFi@title{#1}%
+}
+\def\EmFi@setboolean#1#2{%
+ \EdefSanitize\EmFi@temp{#2}%
+ \ifx\EmFi@temp\EmFi@S@true
+ \csname EmFi@#1true\endcsname
+ \else
+ \ifx\EmFi@temp\EmFi@S@false
+ \csname EmFi@#1false\endcsname
+ \else
+ \EmFi@Error{%
+ Unknown value `\EmFi@temp' for key `#1'.\MessageBreak
+ Supported values: `true', `false'%
+ }\@ehc
+ \fi
+ \fi
+}
+\kv@define@key{EmFiFi}{visible}[true]{%
+ \EmFi@setboolean{visible}{#1}%
+}
+\kv@define@key{EmFiFi}{edit}[true]{%
+ \EmFi@setboolean{edit}{#1}%
+}
+\def\EmFi@sortkeys{}
+\def\EmFi@sortorders{}
+\def\embedfilesort{%
+ \kvsetkeys{EmFiSo}%
+}
+\def\embedfile{%
+ \ltx@ifnextchar[\EmFi@embedfile{\EmFi@embedfile[]}%
+}
+\def\EmFi@embedfile[#1]#2{%
+ \ifEmFi@finished
+ \EmFi@Error{%
+ \string\embedfile\ltx@space after \string\embedfilefinish
+ }{%
+ The list of embedded files is already written.%
+ }%
+ \else
+ \begingroup
+ \def\EmFi@file{#2}%
+ \kvsetkeys{EmFi}{#1}%
+ \expandafter\expandafter\expandafter
+ \ifx\expandafter\expandafter\expandafter
+ \\\pdf@filesize{\EmFi@file}\\%
+ \EmFi@Error{%
+ File `\EmFi@file' not found%
+ }{%
+ The unknown file is not embedded.%
+ }%
+ \else
+ \edef\EmFi@@filespec{%
+ \pdf@escapestring{\EmFi@filespec}%
+ }%
+ \ifx\EmFi@ucfilespec\ltx@empty
+ \let\EmFi@@ucfilespec\ltx@empty
+ \else
+ \EmFi@convert\EmFi@ucfilespec\EmFi@@ucfilespec
+ \fi
+ \ifx\EmFi@desc\ltx@empty
+ \let\EmFi@@desc\ltx@empty
+ \else
+ \EmFi@convert\EmFi@desc\EmFi@@desc
+ \fi
+ \ifx\EmFi@afrelationship\ltx@empty
+ \let\EmFi@@afrelationship\ltx@empty
+ \else
+ \EmFi@convert\EmFi@afrelationship\EmFi@@afrelationship
+ \fi
+ \ifEmFi@item
+ \let\do\EmFi@do
+ \immediate\pdfobj{%
+ <<%
+ \EmFi@fieldlist
+ >>%
+ }%
+ \edef\EmFi@ci{\the\pdflastobj}%
+ \fi
+ \immediate\pdfobj stream attr{%
+ /Type/EmbeddedFile%
+ \ifx\EmFi@mimetype\ltx@empty
+ \else
+ /Subtype/\pdf@escapename{\EmFi@mimetype}%
+ \fi
+ /Params<<%
+ /ModDate(\pdf@filemoddate{\EmFi@file})%
+ /Size \pdf@filesize{\EmFi@file}%
+ /CheckSum<\pdf@filemdfivesum{\EmFi@file}>%
+ >>%
+ }file{\EmFi@file}\relax
+ \EmFi@defobj{EmbeddedFile}%
+ \immediate\pdfobj{%
+ <<%
+ /Type/Filespec%
+ \ifx\EmFi@filesystem\ltx@empty
+ \else
+ /FS/\pdf@escapename{\EmFi@filesystem}%
+ \fi
+ /F(\EmFi@@filespec)%
+ \ifx\EmFi@@ucfilespec\ltx@empty
+ \else
+ /UF(\EmFi@@ucfilespec)%
+ \fi
+ \ifx\EmFi@@desc\ltx@empty
+ \else
+ /Desc(\EmFi@@desc)%
+ \fi
+ \ifx\EmFi@@afrelationship\ltx@empty
+ \else
+ /AFRelationship\EmFi@@afrelationship%
+ \fi
+ /EF<<%
+ /F \the\pdflastobj\ltx@space 0 R%
+ >>%
+ \ifEmFi@item
+ /CI \EmFi@ci\ltx@space 0 R%
+ \fi
+ >>%
+ }%
+ \EmFi@defobj{Filespec}%
+ \EmFi@add{%
+ \EmFi@@filespec
+ }{\the\pdflastobj\ltx@space 0 R}%
+ \fi
+ \endgroup
+ \fi
+}
+\def\EmFi@do#1{%
+ \expandafter\ifx\csname EmFi@P@#1\endcsname\relax
+ \expandafter\ifx\csname EmFi@V@#1\endcsname\relax
+ \else
+ /\pdf@escapename{#1}\csname EmFi@V@#1\endcsname
+ \fi
+ \else
+ /\pdf@escapename{#1}<<%
+ \expandafter\ifx\csname EmFi@V@#1\endcsname\relax
+ \else
+ /D\csname EmFi@V@#1\endcsname
+ \fi
+ /P(\csname EmFi@P@#1\endcsname)%
+ >>%
+ \fi
+}
+\def\EmFi@convert#1#2{%
+ \ifnum\pdf@strcmp{\EmFi@stringmethod}{psd}=0 %
+ \pdfstringdef\EmFi@temp{#1}%
+ \let#2\EmFi@temp
+ \else
+ \edef#2{\pdf@escapestring{#1}}%
+ \fi
+}
+\global\let\EmFi@list\ltx@empty
+\def\EmFi@add#1#2{%
+ \begingroup
+ \ifx\EmFi@list\ltx@empty
+ \xdef\EmFi@list{\noexpand\do{#1}{#2}}%
+ \else
+ \def\do##1##2{%
+ \ifnum\pdf@strcmp{##1}{#1}>0 %
+ \edef\x{%
+ \toks@{%
+ \the\toks@%
+ \noexpand\do{#1}{#2}%
+ \noexpand\do{##1}{##2}%
+ }%
+ }%
+ \x
+ \def\do####1####2{%
+ \toks@\expandafter{\the\toks@\do{####1}{####2}}%
+ }%
+ \def\stop{%
+ \xdef\EmFi@list{\the\toks@}%
+ }%
+ \else
+ \toks@\expandafter{\the\toks@\do{##1}{##2}}%
+ \fi
+ }%
+ \def\stop{%
+ \xdef\EmFi@list{\the\toks@\noexpand\do{#1}{#2}}%
+ }%
+ \toks@{}%
+ \EmFi@list\stop
+ \fi
+ \endgroup
+}
+\def\embedfilefinish{%
+ \ifEmFi@finished
+ \EmFi@Error{%
+ Too many invocations of \string\embedfilefinish
+ }{%
+ The list of embedded files is already written.%
+ }%
+ \else
+ \ifx\EmFi@list\ltx@empty
+ \else
+ \global\EmFi@finishedtrue
+ \begingroup
+ \def\do##1##2{%
+ (##1)##2%
+ }%
+ \immediate\pdfobj{%
+ <<%
+ /Names[\EmFi@list]%
+ >>%
+ }%
+ \pdfnames{%
+ /EmbeddedFiles \the\pdflastobj\ltx@space 0 R%
+ }%
+ \endgroup
+ \begingroup
+ \def\do##1##2{%
+ \ltx@space##2%
+ }%
+ \immediate\pdfobj{%
+ [\EmFi@list]%
+ }%
+ \pdfcatalog{%
+ /AF \the\pdflastobj\ltx@space 0 R%
+ }%
+ \endgroup
+ \ifx\EmFi@initialfile\ltx@empty
+ \else
+ \EmFi@collectiontrue
+ \fi
+ \ifEmFi@collection
+ \ifx\EmFi@initialfile\ltx@empty
+ \let\EmFi@@initialfile\ltx@empty
+ \else
+ \edef\EmFi@@initialfile{%
+ \pdf@escapestring{\EmFi@initialfile}%
+ }%
+ \fi
+ \begingroup
+ \let\f=N%
+ \def\do##1##2{%
+ \def\x{##1}%
+ \ifx\x\EmFi@@initialfile
+ \let\f=Y%
+ \let\do\ltx@gobbletwo
+ \fi
+ }%
+ \EmFi@list
+ \expandafter\endgroup
+ \ifx\f Y%
+ \else
+ \@PackageWarningNoLine{embedfile}{%
+ Missing initial file `\EmFi@initialfile'\MessageBreak
+ among the embedded files%
+ }%
+ \let\EmFi@initialfile\ltx@empty
+ \let\EmFi@@initialfile\ltx@empty
+ \fi
+ \ifcase\EmFi@sortcase
+ \def\EmFi@temp{}%
+ \or
+ \def\EmFi@temp{%
+ /S\EmFi@sortkeys
+ /A \EmFi@sortorders
+ }%
+ \else
+ \def\EmFi@temp{%
+ /S[\EmFi@sortkeys]%
+ /A[\EmFi@sortorders]%
+ }%
+ \fi
+ \def\EmFi@@order##1{%
+ \ifnum\EmFi@order>1 %
+ /O ##1%
+ \fi
+ }%
+ \immediate\pdfobj{%
+ <<%
+ \ifx\EmFi@schema\ltx@empty
+ \else
+ /Schema<<\EmFi@schema>>%
+ \fi
+ \ifx\EmFi@@initialfile\ltx@empty
+ \else
+ /D(\EmFi@@initialfile)%
+ \fi
+ \ifx\EmFi@view\EmFi@S@tile
+ /View/T%
+ \else\ifx\EmFi@view\EmFi@S@hidden
+ /View/H%
+ \fi\fi
+ \ifx\EmFi@temp\ltx@empty
+ \EmFi@temp
+ \else
+ /Sort<<\EmFi@temp>>%
+ \fi
+ >>%
+ }%
+ \pdfcatalog{%
+ /Collection \the\pdflastobj\ltx@space0 R%
+ }%
+ \fi
+ \fi
+ \fi
+}
+\begingroup\expandafter\expandafter\expandafter\endgroup
+\expandafter\ifx\csname AtEndDocument\endcsname\relax
+\else
+ \AtEndDocument{\embedfilefinish}%
+\fi
+\EmFi@AtEnd%
+\endinput
+%%
+%% End of file `embedfile.sty'.