use utf8;
use strict;
use warnings; no warnings "redefine";
no warnings 'redefine';

sub acct_rechnung {
	return Rechnung->new(@_);
}

package Rechnung;

use strict;
use warnings; no warnings "redefine";
no warnings 'redefine';
use Dbase::Help qw(unixtime unixdate isodate DoFn DoSelect isotime Do
	sisodate DoPing date_add_ymd DoTime qquote);
use Dbase::Globals qw(svdaterange find_descr def_or_minus
	is_dienst sdaterange in_euro bignum oberkunde puny_decode
	flag_names test_flag name_kunde oberkunde content rund
	name_dienst find_dienst list_descr);
use Cf qw($MWST $EURO);
use Loader qw($base acct_text_kopf acct_text_page acct_text_fuss
	acct_text_zahlung acct_betrag get_rechstep get_recheinheit
	get_free_rnr);
use Fehler qw(fehler problem hat_problem);
use Date::Calc qw(Add_Delta_YMD);
use Math::BigFloat;

BEGIN {
	do "$base/acct/text_vars";
}
my $long = !bignum();

=head1

Rechnungserstellung

=head2 Rechnung

Das Objekt hat, bzw. C<new()> akzeptiert, folgende Parameter:

=over 4

=item kunde

Kundennummer. Pflichtfeld.

=item intervall

Rechnungszeitraum. Default: der Parameter des Kunden, aus der Datenbank.

=item rechnungsdatum

Erstelldatum der Rechnung. Default: Letzter des Vormonats (normale
Berechnung des Kunden) oder Erster des aktuellen Monats
(Vorausberechnung).

=item rech_beginn / rech_ende

Liste: Start- und Enddatum für den Rechnungskopf. Default: Start ist das
in kunde.zuletzt hinterlegte Datum (bei Vorausberechnung: plus ein
Monat), Ende der Schluß des (Voraus)berechnungszeitraums.

=item acct_beginn / acct_ende

Start- und Enddatum für die zu berücksichtigenden Accountingdaten.
Default: Start aus kunde.zuletzt, Ende ist der Monatsletzte des
Vormonats.

=item tarif_ende

Enddatum für die Berechnung von fest abgerechneten Kundentarifen (der
Beginn ist in den Tarifen verzeichnet). Default: Schluß des
(Voraus)berechnungszeitraums.

=item rnr

Rechnungsnummer.

=item repro

Reproduziere diese alte Rechnung.

B<Nur> für Testrechnungen; das Testflag muß gesetzt sein.

=item buchung

Hash für die Buchungseinträge.

=item nextrech

Hash für Einträge in der nextrech-Tabelle. Key ist die Kundentarifs-ID,
Wert C<[Beginn,Ende]>.

=item mwst

Mehrwertsteuersatz, Promille.

Default: C<$MWST> wenn das no_mwst-Flag im Datensatz des Kunden nicht
gesetzt ist, ansonsten I<undef>.

=item flags

=over 4

Zusätzliche Parameter.

=item test

Die generierte Rechnung ist eine Testrechnung.

=item next

Verwende den B<nächsten> Zeitraum für Rechnungen, nicht den gerade
abgelaufenen solchen.

B<Nur> für Testrechnungen; das Testflag muß gesetzt sein.

=item debug

Die Rechnung wird mit zusätzlichen Text-Features versehen.

B<Nur> für Testrechnungen; das Testflag muß gesetzt sein.

=item trace

Bei der Rechnungsgenerierung wird ziemlich viel Text ausgespuckt.

=back

=item text

Der generierte Rechnungstext.

=item betrag

Der generierte Rechnungsbetrag.

=back

=head2 Methoden

=over 4

=item new

Neu anlegen. Parameter: Liste/Hash der Parameter, siehe oben.

=item prep

Noch nicht eingetragene Parameter aus der Datenbank extrahieren.

=item run

Rechnung erstellen.

=item db

Generierte Rechnung in die Datenbank eintragen.

=back

=cut

sub new {
	my $pack = shift;
	my $h;
	if(@_ == 1) {
		$h = { %{$_[0]} };
	} else {
		$h = { @_ };
	}
	$h->{'flags'} = {} unless $h->{'flags'};
	bless($h,$pack);
}


my($voraus_flag, $mwst_flag);

sub dump($) {
	my($r) = @_;
	my $res = "R:";
	my $x;
	$res .= $r->{'betrag'}." "
		if defined $r->{'betrag'};
	$res .= $r->{'rnr'}." "
		if defined $r->{'rnr'};
	$res .= "K:".$r->{'kunde'}." "
		if defined $r->{'kunde'};
	if($r->{'rechnungsdatum'}) {
		$res .= isotime $r->{'rechnungsdatum'};
	} else {
		$res .= "?";
	}
	$x = sdaterange($r->{'acct_beginn'}, $r->{'acct_ende'},1);
	$res .= " A:$x" if $x ne "";
	$x = sdaterange($r->{'rech_beginn'}, $r->{'rech_ende'},1);
	$res .= " R:$x" if $x ne "";
	$res .= " T:--".sisodate($r->{'tarif_ende'},2) if $r->{'tarif_ende'};
	my @fl = grep { $r->{'flags'}{$_} } keys %{$r->{'flags'}};
	$res .= " Fl:".join(",",@fl) if @fl;
	$res;
}

sub prep {
	my $r = shift;
	my($fusstext) = @_;
	return if hat_problem();

	$voraus_flag = bignum(1)<<find_descr("kunde","vorausberechnen")
		unless defined $voraus_flag;
	$mwst_flag = bignum(1)<<find_descr("kunde","keine_ust")
		unless defined $mwst_flag;

	my $knr = $r->{'kunde'};
	if(not $knr and $r->{'repro'}) { # reproduziere eine Rechnung
		$knr = $r->{'kunde'} = DoFn("select kunde from rechnungen where rnr=".$r->{'repro'});
	}
	fehler "kein Kunde bekannt" unless defined $knr;

	my($ybe,$yen,$kflags,$zuletzt,$int) = DoFn("select beginn,ende,flags,zuletzt,berechne from kunde where id=$knr");
	$kflags = bignum($kflags);

	unless($zuletzt) {
		return problem "Kein Anfangsdatum bekannt",$knr unless $ybe;
		my($y,$m,$d) = isodate $ybe;
		$zuletzt = 100*$y+$m;
	}
	unless($ENV{'TESTING2'}) {
		my($yn,$mn,$dn) = isodate DoTime;
		my $zd = 100*($yn-3)+1;
		if($zuletzt < $zd) {
			print STDERR "\rStart $zuletzt => $zd              \n";
			$zuletzt = $zd;
		}
	}

	$r->{'fusstext'} = $fusstext if defined $fusstext;
	unless($r->{'flags'}{'test'}) {
		return problem "Nur Testrechnungen dürfen debuggen"
			if $r->{'flags'}{'debug'};
		return problem "Nur Testrechnungen dürfen in der Zukunft liegen"
			if $r->{'flags'}{'next'};
	}

	if(defined $r->{'intervall'}) {
		$int = $r->{'intervall'};
	} else {
		$r->{'intervall'} = $int;
	}
	my($jp,$mp) = (0,0); # Schrittweiten für Intervalle
	if($int eq "y") {
		$jp=1;
	} elsif($int eq "q") {
		$mp=3;
	} elsif($int eq "m") {
		$mp=1;
	} else {
		if ($r->{'flags'}{'test'}) {
			$mp = 1;
		} else {
			return problem "kein Berechnungsintervall bekannt",$int;
		}
	}
	my($rdatum,$rbe,$ren,$abe,$aen,$ten);

	if($r->{'repro'}) { # reproduziere eine Rechnung
		my $rnr = $r->{'repro'};
		my($rid,$mwst);

		($rid,$abe,$aen,$rbe,$ren,$mwst) = DoFn("select id,beginn,ende,rbeginn,rende,mwst from knextrech where rnr=$rnr and kunde=".oberkunde($knr,1));
		return problem "Die Rechnung konnte nicht gefunden werden",$knr,$rnr
			unless $rid;

		$rdatum = DoFn("select datum from rechnungen where rnr=$rnr");

		$r->{'mwst'} = $mwst
			unless exists $r->{'mwst'};
		$r->{'_repro'} = $rid;
		undef $r->{'tarif_ende'};

	} else { # generiere eine neue Rechnung
		$r->{'now'} = DoTime unless defined $r->{'now'};

		unless(exists $r->{'mwst'}) {
			if($kflags & $mwst_flag) {
				$r->{'mwst'} = undef;
			} else {
				$r->{'mwst'} = rund($MWST*1000);
			}
		}

		$rdatum = $r->{'rechnungsdatum'};
		$rbe = $r->{'rech_beginn'};
		$ren = $r->{'rech_ende'};
		$abe = $r->{'acct_beginn'};
		$aen = $r->{'acct_ende'};
		$ten = $r->{'tarif_ende'};
		my $next_set;

		if(not $rdatum) {
			my($j,$m,$d) = isodate($r->{'now'}); $d=1;
			$rdatum = unixdate($j,$m,$d);
		}
		if(not $abe) {
			# Accounting-Start, aus kunde.zuletzt
			$abe = unixdate(int($zuletzt/100), $zuletzt%100, 1);
		}

		$next_set=1 if $r->{'flags'}{'next'};
		if ($abe >= $rdatum) {
			return problem "Rechnungsanfang in der Zukunft", (scalar isotime $abe), (scalar isotime $rdatum)
				if not $r->{'flags'}{'test'};
			$r->{'flags'}{'next'} = 1;
		}
		if(not $rbe) {
			# Datum aus kunde.zuletzt; bei Vorausberechnung +1 Monat
			$rbe = unixdate(int($zuletzt/100), $zuletzt%100, 1);
			$rbe = date_add_ymd($rbe, 0,1,0) if $kflags & $voraus_flag;
		}
		if(not $ten) {
			# Schluss des Tarifierungszeitraums: intervalle
			$ten = $rbe;
			$ten = date_add_ymd($ten, $jp,$mp) while $ten <= $rdatum;
			$ten = date_add_ymd($ten, -$jp,-$mp);
		}

		if(not $aen) {
			# Accounting-Ende, kann nicht in die Zukunft liegen.
			# Ausnahme: wir testen, und der Accountingzeitraum ist leer:
			#           erstelle eine Vorausrechnung
			$aen = $abe;
			$aen = date_add_ymd($aen, $jp,$mp) while $aen <= $rdatum;
			$aen = date_add_ymd($aen, -$jp,-$mp) unless $r->{'flags'}{'next'};
		}

		if(not $r->{'tarif_ende'}) {
			$ten = date_add_ymd($ten, $jp,$mp) if $kflags & $voraus_flag;
			$ten = date_add_ymd($ten, $jp,$mp) if $r->{'flags'}{'next'};
		}

		if(not $r->{'rechnungsdatum'}) {
			$rdatum = date_add_ymd($rdatum, 0,1,0) if $r->{'flags'}{'next'};
			$rdatum = date_add_ymd($rdatum, 0,0,-1) unless $kflags & $voraus_flag;
		}
		
		if(not $r->{'rech_beginn'}) {
			$rbe = date_add_ymd($rbe, 0,1,0) if $next_set;
		}

		if(not $a->{'acct_beginn'}) {
			$abe = date_add_ymd($abe, 0,1,0) if $next_set;
		}

		if(not $ren) {
			# Ende des Rechnungszeitraums im RechKopf: Default ist tarif.ende
			$ren = $ten;
			# $ren = date_add_ymd($ren, 0,1,0) if $r->{'flags'}{'test'};
		}

		if($abe >= $aen and $rbe >= $ren) {
			if($abe >= $aen) {
				problem "#$knr: Kein Accounting (Start >= Ende)",scalar isodate $abe,scalar isodate $aen, $knr;
			} else {
				problem "#$knr: Keine Rechnung (Start >= Ende)",scalar isodate $rbe,scalar isodate $ren, $knr;
			}
		}
		$r->{'tarif_ende'} = $ten
			unless exists $r->{'tarif_ende'};
	}
	$r->{'rechnungsdatum'} = $rdatum
		unless defined $r->{'rechnungsdatum'};
	$r->{'rech_beginn'} = $rbe
		unless defined $r->{'rech_beginn'};
	$r->{'rech_ende'} = $ren
		unless defined $r->{'rech_ende'};
	$r->{'acct_beginn'} = $abe
		unless defined $r->{'acct_beginn'};
	$r->{'acct_ende'} = $aen
		unless defined $r->{'acct_ende'};

	$r->{'nextrech'} = {} unless $r->{'nextrech'};
	$r->{'buchungen'} = {} unless $r->{'buchungen'};
	$r->{'tarifkunden'} = {} unless $r->{'tarifkunden'};
}

sub db {
	my $r = shift;
	return if $r->{'flags'}{'test'};
	return if $r->{'repro'};
	my $mwst = $r->{'mwst'};
	$mwst="NULL" if not defined $mwst;
	my $knr = Do "insert into knextrech set rnr=$r->{'rnr'}, kunde=$r->{'kunde'}, beginn=$r->{'acct_beginn'}, ende=$r->{'acct_ende'}, rbeginn=$r->{'rech_beginn'}, rende=$r->{'rech_ende'}, mwst=$mwst";

	while(my($id,$ar)=each %{$r->{'nextrech'}}) {
		my($b,$e,$n) = @$ar;
		$n = rund($n*1000,1);
		Do "insert into nextrech set knextrech=$knr,tarifkunde=$id, beginn=$b,ende=$e,anzahl=$n";
	}
}

my $eigene_re;
sub run {
	my $r = shift;

	$r->prep();
	return if hat_problem();
	# print STDERR "R:",$r->dump,"\n" if $r->{'flags'}{'test'};

	my $rdate = $r->{'rechnungsdatum'};
	fehler "kein Rechnungsdatum bekannt" unless defined $rdate;
	my $knr = $r->{'kunde'};
	fehler "kein Kunde bekannt" unless defined $knr;

	my($oberkunde,$kflags) = DoFn("select kunde,flags from kunde where id=$knr");
	$kflags = bignum($kflags);

	$eigene_re = bignum(1)<<find_descr("kunde","eigene_re")
		if not $eigene_re;
	$oberkunde=undef if $kflags & $eigene_re;

	# Für den Umbau werden ein paar alte Parameter reproduziert.
	# $jjmm: Datums-Bereich für Accounting
	my $jjmm;
	{
		my ($aj,$am,$ad) = isodate $r->{'acct_beginn'};
		my ($bj,$bm,$bd) = isodate $r->{'acct_ende'};
		if($aj==$bj and $am==$am) {
			$jjmm = sprintf("%04d%02d",$aj,$am);
		} else  {
			$jjmm = sprintf("%04d%02d=%04d%02d",$aj,$am,$bj,$bm);
		}
	}
	# $rnr: Ziel-Rechnungsnummer
	# $flag: siehe unten
	# $outtxt: Zusatztext
	my $outtxt=$r->{'zusatztext'};

	my $flag=0;
	## $flag: 1: Originalrechnung; Rechnungsdatensätze updaten
	##           sonst: wiederholte Rechnung; Daten aus den alten
	##                  Datensätzen verwenden (Tabelle 'nextrech')
	$flag|=1 unless $r->{'flags'}{'test'};
	##        2: kein Blafasel
	$flag|=2 if $r->{'flags'}{'quiet'};
	##        4: Blafasel (an stdout) beim Erstellen ausführlich
	$flag|=4 if $r->{'flags'}{'trace'};
	##        8: Debug-Texte in die Rechnung kritzeln
	$flag|=8 if $r->{'flags'}{'debug'};
	##       16: vergleiche mit existierender Rechnung
	$flag|=16 if $r->{'repro'};
	##       32: -frei- war Testrechnung
	##       64: keine USt
	$flag|=64 if not $r->{'mwst'};
	##      128: -frei-
	##      256: hänge Datei an (in acct_kunde)

	my $oldf = $|; $|=1;
	my($cmd,$cmd1,$cmd7,$cmd7a,$pr1,$pr2,$pr20);
	my($ratarif,$rkonto,$rtt,$rjjmm,$rquelle,$rart,$rtid,$rdienst,$rziel,$rtarif,$rppreis,$rxpreis,$rbpreis,$rinfo,$rfaktor,$rmodus,$rskala,$rdfrei,$rfrei,$rtxt,$rtari,$rrabatt,$rnds,$rrdienst);
	my($iatarif,$ikonto,$itt,$ijjmm,$iquelle,$iart,$itid,$idienst,$iziel,$itarif,$ippreis,$ixpreis,$ibpreis,$iinfo,$ifaktor,$imodus,$iskala,$idfrei,$ifrei,$itxt,$itari,$irabatt,$iitid);
	my($ibytes,$ipack);
	my(%ttarif,$j1,$j2);
	my($bytes,$pakete,$nds);
	my($gkosten,$firstacct,$arr,$rpreis,$sumkosten,$bkosten,$kosten,$ofakt,$rabatt);
	my($sumnr,$sumblock); # Berechnungsgrundlage bei Volumentarifen
	my(@subkunde,$outdata, $line);
	my($kfaktor,$ibkosten,$ikosten,$ipreis,$hastarif,$ihastarif,$res);
	my($prseq);
	my $subpapier = "";
	my(%quelle, %dienst, %ziel);
	my $nskiptar = 0;
	my $skiptar = "nix"; my $skiprabatt = "nix"; my $skipmonat = "nix";
	my %fups;
	my %dkunde;
	my %jmh;
	my @xline;
	my @yline;
	my %out; my %rtarif; my %outv; my %outx; my %outmap; my %outlf;
	my %ttlink;
	my %didprf;
	my %didpri;
	my $klassen = 0;
	my %steps;
	my $abe = $r->{'acct_beginn'};
	my $aen = $r->{'acct_ende'};
	my $rbe = $r->{'rech_beginn'};
	my $ren = $r->{'rech_ende'};
	my $ten = $r->{'tarif_ende'};
	my %dienst_warn;

	my $xbe = $abe||$rbe||0;
	my $xen = $aen||$ren||$ten||0;
	$xbe=$rbe if $rbe and $xbe>$rbe;
	$xen=$ren if $ren and $xen<$ren;
	$xen=$ten if $ten and $xen<$ten;

	# Schritte einlesen.
	my $step = sub($) {
		my($dienst) = @_;
		if(not exists $steps{$dienst}) {
			my $id = get_rechstep($dienst,$knr);
			fehler "Accounting: Dienst nicht berechenbar",$knr,name_dienst $dienst unless defined $id;
			my($step,$flags) = DoFn("select step,flags from rechstep where id=$id");
			$steps{$dienst}{'id'} = $id;
			$steps{$dienst}{'step'} = $step;
			$steps{$dienst}{'flag'} = $flags;
			foreach my $fl(flag_names($flags,"acctflag")) {
				$steps{$dienst}{'flags'}{$fl}++;
			}
		}
		return $steps{$dienst};
	};

	# Ausgabe eines Prozentsatzes
	sub acct_rechnung_rabatt($) {
		my($num) = @_;
		if($num > 100) { ## Aufschlag?!
			return sprintf("-%2d:%%",$num-100);
#		} elsif(int($num+0.99) != int($num)) {
#			return sprintf("%2d.%1d%%",100-$num,((100-$num)*10)%10);
		} else {
			return sprintf(" %3d:%%",int(100-$num));
		}
	}
	
	# Ausgabe einer Zahl (bignum => ist eigentlich Altlast)
	sub pra($) {
		my($num) = @_;
		$num =~ s/^\+//;
		$num."   "; ## komma null null
	}

	$bkosten = 0; $kosten = 0; $gkosten = 0; $sumkosten = bignum(0);
	%fups = ();

	print STDERR "\r  Accounting für $knr                 \r" unless $flag & 6;
	print STDERR "Accounting für $knr\n" if ($flag & 6)==2;

	# Unterkunden vorher accounten
	#unless($flag & (4|8))
	{ # doch Unterkunden wenn Debugging
		$res = DoSelect {
			my($uk) = @_;

			my $ru = Rechnung->new(%$r, rnr => "-KEINE-", kunde => $uk);

			$ru->prep();
			$ru->run();

			my($knr,$preis,$papier,$subacct);
			push(@subkunde,$uk,$ru->{'betrag'},$ru->{'text'}) if defined $ru->{'text'} and $ru->{'text'} ne "";
		} "select id from kunde where kunde=$knr and (flags & $eigene_re)=0 order by kunde.name";
	}

	# jetzt den Hauptkunden
	print STDERR "\r  Accounting für $knr/".name_kunde($knr).", 1 Unterkunde ...\r" if @subkunde == 1 and not ($flag & 6);
	print STDERR "\r  Accounting für $knr/".name_kunde($knr).", ".(0+@subkunde)." Unterkunden...\r" if @subkunde > 1 and not ($flag & 6);

	# Das Bauen dieses SELECT-Strings ist historisch gewachsen und daher
	# möglicherweise etwas unübersichtlich. :-/

	my $sel_acct = "";
	my $sel_acct_ = "where";

	my $sel_tarif = "";
	my $sel_tarif_ = "and";

	my $sel_tarifkunde = "tarifkunde.anzahl > 0";
	my $sel_tarifkunde_ = "and";

	my $sel_tarifacct = "";
	my $sel_tarifacct_ = "where";

	my $s_global = "*";
	my $s_konflikt = "konflikt";
	my $s_unbekannt = "unbekannt";
	my $d_general = find_dienst "general";

	$sel_acct = " where KUNDEN "; $sel_acct_ = "and";
	$sel_tarifkunde .= " $sel_tarifkunde_ kunde = $knr"; $sel_tarifkunde_ = "and";

	# selektiere Datensätze im angegebenen Bereich
	if($abe) {
		my ($jb,$mb,$db) = isodate $abe;
		my $b = 100*$jb+$mb;
		$sel_acct .= " $sel_acct_ jjmm >= $b"; $sel_acct_ = "and";
	}
	if($aen) {
		my ($je,$me,$de) = isodate $aen;
		my $e = 100*$je+$me;
		$sel_acct .= " $sel_acct_ jjmm < $e"; $sel_acct_ = "and";
	}

	# *De*selektiere Datensätze die den interessanten Bereich nicht
	# überschneiden (also entweder komplett vorher oder nachher)
	if($xen) {
		$sel_tarifkunde .= " $sel_tarifkunde_ beginn <= $xen"; $sel_tarifkunde_ = "and";
		$sel_tarifacct .= " $sel_tarifacct_ beginn <= $xen"; $sel_tarifacct_ = "and";
	}
	if($ten) {
		$sel_tarif .= " $sel_tarif_ beginn <= $ten"; $sel_tarif_ = "and";
	}
	if($xbe) {
		$sel_tarifkunde .= " $sel_tarifkunde_ ( ende > $xbe or ende is NULL)"; $sel_tarifkunde_ = "and";
		$sel_tarifacct .= " $sel_tarifacct_ ( ende > $xbe or ende is NULL)"; $sel_tarifacct_ = "and";
	}
	
	my %d_name;
	my %d_info;
	DoSelect {
		my($num,$bla,$bbla) = @_;
		$d_name{$num} = $bla;
		$d_info{$num} = $bbla;
	} "SELECT id, name, rechnungstext FROM dienst";

	my %art_name;
	my %art_info;
	list_descr ("acctinfo", 0,"", sub {
		my($name,$num,$bla,$bbla) = @_;
		$art_name{$num} = $bla;
		$art_info{$num} = $bbla;
	});

%ttarif = ();
	my $lbeginn=0;
	my $tana = sub {
		my($tid,$aklassen,$aname,$adienst,$abeginn,$afrei,$afestpreis,$apreis,$askala,$aunitpreis,$aintval,$ainfo,$afinfo,$avinfo,$adnull,$afnull,$anonum,$amini,$aunitmini,$afkonto,$avkonto,$pfrei) = @_;
		# wenn ein Tarif den alten ersetzt:
		#  Enddatum des alten Tarifs einstellen
		if($lbeginn != $abeginn) {
			my $t = $ttarif{$aname}{$adienst};
			while(ref $t and $t->{'ende'} == 0) {
				$t->{'ende'} = $lbeginn;
			} continue {
				$t = $t->{'NEXT'};
			}
		}
		$lbeginn=$abeginn;
		$ttarif{$aname}{$adienst}{'ende'} = $abeginn if ref $ttarif{$aname}{$adienst} and $ttarif{$aname}{$adienst}{'beginn'} < $abeginn;
		if(in_euro($rdate) and not in_euro($abeginn)) {
			$aunitpreis=rund($aunitpreis/$EURO);
			$afestpreis=rund($afestpreis/$EURO);
			$apreis=rund($apreis/$EURO);
			$afrei=rund($afrei/$EURO);
		}
		$ttarif{$aname}{$adienst} = {
			"NEXT"      => $ttarif{$aname}{$adienst},
			"beginn"    => $abeginn,
			"ende"      => 0,
			"klassen"   => $aklassen,
			"frei"      => $afrei,
			"pfrei"     => $pfrei,
			"preis"     => $apreis,
			"skala"     => bignum($askala),
			"mini"      => $amini,
			"unitmini"  => $aunitmini,
			"unitpreis" => $aunitpreis,
			"festpreis" => $afestpreis,
			"intval"    => $aintval,
			"info"      => $ainfo,
			"finfo"     => $afinfo,
			"vinfo"     => $avinfo,
			"fkonto"    => $afkonto,
			"vkonto"    => $avkonto,
			"dnull"     => $adnull,
			"fnull"     => $afnull,
			"nonum"     => $anonum,
			"NAME"      => $aname, ## Debugging
			"DIENST"    => name_dienst($adienst), ## dito
			"SEEN"      => 0,
			"ID"        => $tid,
		};
		# $ttarif{$aname}{$adienst}{'NEXT'}{'PREV'} = $ttarif{$aname}{$adienst} if ref $ttarif{$aname}{$adienst}{'NEXT'};
		my $nxt;
		if(defined($nxt = $ttarif{$aname}{$adienst}{'NEXT'})) {
			$$nxt{'maxi'} = $ttarif{$aname}{$adienst}{'mini'};
			$$nxt{'unitmaxi'} = $ttarif{$aname}{$adienst}{'unitmini'};
		}
	};
	$res = DoSelect {
		&$tana(@_);
	} "select distinct tarif.id,tarifklasse.klassen,tarifname.name,tarif.dienst,tarif.beginn,tarif.frei,tarif.festpreis,tarif.preis,tarif.skala,tarif.unitpreis,tarif.intval,tarif.infotext,tarif.festinfo,tarif.varinfo,tarif.dnull,tarif.fnull,tarif.nonum,tarif.mini,tarif.unitmini,tarif.fkonto,tarif.vkonto,tarif.freiinfo from tarif,tarifklasse,tarifname where tarif.klasse = tarifklasse.id and tarifklasse.tarifname=tarifname.id $sel_tarif and tarifklasse.kunde is NULL order by tarifname.name,tarif.dienst,tarif.beginn,tarif.mini,tarif.unitmini";

	# Spezialtarife pro Kunde lesen.
	my($ot,$od);
	$res = DoSelect {
		my($tid,$aklassen,$aname,$adienst,$abeginn,$afrei,$afestpreis,$apreis,$askala,$aunitpreis,$aintval,$ainfo,$afinfo,$avinfo,$adnull,$afnull,$anonum,$amini,$aunitmini,$afkonto,$avkonto,$pfrei) = @_;
		if(not defined $ot or $ot ne $aname or $od ne $adienst) {
			$ot = $aname;
			$od = $adienst;
			# print STDERR "\r".(" "x70)."\rSpezialtarif: $aname\n" unless $flag & 6;
			$ttarif{$aname}{$adienst} = $ttarif{$aname}{$adienst}{'NEXT'}
				while ref $ttarif{$aname}{$adienst} and
					$ttarif{$aname}{$adienst}{'beginn'} > $xbe;
			# delete $ttarif{$aname}{$adienst};
		}
		&$tana($tid,$aklassen,$aname,$adienst,$abeginn,$afrei,$afestpreis,$apreis,$askala,$aunitpreis,$aintval,$ainfo,$afinfo,$avinfo,$adnull,$afnull,$anonum,$amini,$aunitmini,$afkonto,$avkonto,$pfrei);
	} "select distinct tarif.id,tarifklasse.klassen,tarifname.name,tarif.dienst,tarif.beginn,tarif.frei,tarif.festpreis,tarif.preis,tarif.skala,tarif.unitpreis,tarif.intval,tarif.infotext,tarif.festinfo,tarif.varinfo,tarif.dnull,tarif.fnull,tarif.nonum,tarif.mini,tarif.unitmini,tarif.fkonto,tarif.vkonto,tarif.freiinfo from tarif,tarifklasse,tarifname where tarif.klasse = tarifklasse.id and tarifklasse.tarifname=tarifname.id $sel_tarif and tarifklasse.kunde = $knr order by tarifname.name,tarif.dienst,tarif.beginn,tarif.mini,tarif.unitmini";
	# print STDERR "tarif: $res\n" unless $res > 0;

	my(%eqneu,%eqfakt,%folgenr,%rstop,$maxfolgenr);
	my %rechstep;
	$res = DoSelect {
		my($dienst,$berechne,$faktor,$rstop) = @_;
		if(defined $berechne) {
			$eqneu{$dienst} = $berechne;
			$eqfakt{$dienst} = $faktor;
			$rstop{$dienst} = $rstop ne "j";
		}
	} "select dienst,berechne,faktor,isrec from tarifeq";
	print STDERR "tarifeq: $res\n" unless $res > 0;

	$maxfolgenr = DoFn("select max(step) from rechstep");

	# "sdienst" ist gesetzt, wenn es einen Deskriptor mit dem
	# Dienstnamen gibt. TODO: Verbessern, der Wert sollte eigentlich
	# der resultierende Dienst laut Übersetzertabelle sein..?
	my %sdienst;
	$res = DoSelect {
		my($dienst,$dname) = @_;
		return if $dname eq "nic";
		$sdienst{$dienst} = $dname;
	} "select id,name from dienst";
	print STDERR "dienst: $res\n" unless $res > 0;

	my($hastar) = 0; # compiler warning...

### $minfixed: Länge einer Zeile mit Kosten
### $minfixxed: Länge einer Zeile mit Abrechnung etc.

	use Text::Wrapper;
	my $xwrap = new Text::Wrapper(columns => $dienstlen);

# PRSEQ 0:Volumen, 1:Einheiten, 2: Plattenplatz, 3: Arbeitszeit
#       Sonderkram: fällt flach und weg, funktionierte noch nie
	my $isdienst = sub($) {
		my($dienst) = @_;
		return $step->($dienst){'step'}-1;
	};



	#################################################################
	# Variable Preise ("Accounting")
	#################################################################

	my $pr = sub {
		#my($warum)=@_;
		my $warum="";
		my($hastar);
		my($dosum, $rinfo);
		my $prunit=0;
		my $prbyte=0;
		my $prfrei=0;
		my $prstaf=0;

		my($ppakete,$pbytes);
		my $out = "";
		my($stafp,$stafu);
		my $iline = $rtarif;

		$rtid=0 unless defined $rtid;
		push(@{$outmap{"$iline|\177"}},$rtid);
		push(@{$outmap{"$iline|\1771"}},$rtid);

		my $st = &$step($rrdienst);
		if($st->{'flags'}{'tausch_bytes_pakete'}) {
			$ppakete = $bytes;
			$pbytes = $pakete;
		} else {
			$ppakete = $pakete;
			$pbytes = $bytes;
		}
		$st = &$step($rdienst);
		if($hastarif == 0) { $hastar = "-"; }
		if($hastarif == 1) { $hastar = "+"; }
		if($hastarif == 2) { $hastar = "="; $bkosten = ""; $kosten = ""; }
		if($hastarif == 3) { $hastar = "*"; } ## sollte nicht passieren
		if($hastarif >= 4) { $hastar = "?"; } ## mehrere Daten wurden zusammengeworfen
		print "p$rmodus $pbytes/$ppakete/$rnds $rdienst/$rziel $rquelle.$rart ${rjjmm}_$rtt :: $hastarif:$hastar $kosten/$bkosten/$sumkosten $rfaktor $rbpreis/$rppreis T:$rtid\n" if $flag & 4;
		#p 35801/35/1 41/n 1. 200801_16 :: 1:+ 0/0/0 1 1000000/333000 T:60920

		if((($rppreis or $rbpreis) and $rpreis) or (($rmodus ne "a" and $rmodus ne "m" and $rmodus ne "d") and ($kosten or $bkosten)) or $ijjmm eq "last" or $$rtari{'dnull'} ne "n") {
			if($rmodus eq "m" or $rmodus eq "a" or $rmodus eq "d") {
				$itid=0 unless $itid;
				$rtid=0 unless $rtid;
				if($rmodus eq "d") {
					$pbytes = Math::BigFloat->new($pbytes)
						unless ref $pbytes and $pbytes->isa("Math::BigFloat");
					$ppakete = Math::BigFloat->new($ppakete)
						unless ref $ppakete and $ppakete->isa("Math::BigFloat");
					$pbytes /= $rnds;
					$ppakete /= $rnds;
					print "p$rmodus $pbytes/$ppakete\n" if $flag & 4;
				}
				if($ijjmm eq "last" or $ijjmm != $rjjmm or $itarif ne $rtarif or $rtid != $itid) { # neuer Tarif
					if($rbpreis or $rppreis or $$rtari{'dnull'} ne "n") {
						$dosum = 1;
						$bkosten = $sumkosten - 1; # wird unten wieder draufgerechnet
						print "pS0 $bkosten\n" if $flag & 4;
						if($hastar eq "+") {
							## my $ppbytes = $pbytes * int($rfaktor*1000);
							## $bkosten += $ppbytes/1000;

							# Das ist wegen bigfloat so herum
							$bkosten = $pbytes + $bkosten;
							print "pS1 $bkosten\n" if $flag & 4;
						}
						else {
							print "pS2 $bkosten $hastar\n" if $flag & 4;
						}

						if(ref $rtari) {
							my $pskala;
							rtloop:
							while(ref $rtari) {
								$pskala = $$rtari{'skala'};
								$pskala = 1 if $pskala < 1;
								$stafu=$$rtari{'unitmini'},$$rtari{'SKIP'}++,$prunit=1, next if $$rtari{'unitmini'} and $ppakete < $$rtari{'unitmini'};
								$stafp=$$rtari{'mini'},$$rtari{'SKIP'}++,$prbyte=1, next if $$rtari{'mini'} and int($bkosten/$pskala) < $$rtari{'mini'};
								last rtloop;
							} continue {
								$rtari = $$rtari{'NEXT'};
							}
							if(ref $rtari) {
								$rskala = $pskala;
								$rbpreis = $$rtari{'preis'};
								$rppreis = $$rtari{'unitpreis'};
								$rxpreis = $$rtari{'festpreis'};
								$rdfrei = $$rtari{'pfrei'};

								## für Kunden sichtbar
								$rfrei = rund($$rtari{'frei'}*$rfaktor,-1);
								$rfrei *= $$ratarif{'anzahl'} if ref $ratarif and $$ratarif{'anzahl'};
								$rinfo = $$rtari{'info'};
								$rkonto = ($$rtari{'vkonto'} or $$rtari{'fkonto'} or $rkonto or "");
								$$rtari{'SEEN'} = 1;
							}
						}

						if(($bkosten - int($rskala/100)) =~ /^[\+0-9]/) { ## mehr als 1 Prozent?

							### für Kunden sichtbar: Preis pro XXX, in zPf
							my $rbp = rund($rbpreis*$rfaktor,-1);
							print "K $bkosten $rskala $rbpreis*$rfaktor=$rbp -- $kosten/$sumkosten\n" if $flag & 4;

							use integer;
							$bkosten /= $rskala if $rskala > 1;
							if(ref $bkosten and $bkosten->isa("Math::BigFloat")) {
								$bkosten->bfround(0) if ref $bkosten and $bkosten->isa("Math::BigFloat");
							} else {
								$bkosten += 1; # wieder drauf (Skalierung)
							}
							# Berechnungsgrundlage (weiter unten: drucken)
							$sumnr = $bkosten; $sumblock = $rskala;
							$bkosten *= $rbp;
							print "KK $sumnr*$rbp=$bkosten\n" if $flag & 4;
						} else { # ... ansonsten berechne nix.
							$bkosten = 0;
						}
						$prbyte |= ($rbpreis ? 1 : 2);
						$prbyte |= 4 if ($$rtari{'dnull'}||"") eq 'j';

						$prunit = 1 if $rppreis and $ppakete;
						### für Kunden sichtbar: Preis pro Paket, in zPf
						$bkosten += rund($rppreis*$rfaktor) * $ppakete;
						if($bkosten > 0 and $rfrei > 0) {
							print "Kf $bkosten-$rfrei=" if $flag & 4;
							$bkosten -= $rfrei;
							print "$bkosten\n" if $flag & 4;
							$prfrei = $$rtari{'ID'};
							$bkosten = 0 if $bkosten < 0;
						}
					} else {
						$bkosten = 0;
					}
					$sumkosten = bignum(0);
				} elsif($hastar eq "+") { # alter Tarif =: Pluszeichen
					## my $rf = int($rfaktor*1000);
					use integer;
					## my $ppbytes = $pbytes * $rf;
					## $sumkosten += $ppbytes/1000;
					$sumkosten += $pbytes;
					$bkosten = $hastar;
					$prbyte|=1;
					$rinfo = "";
				} else {
					$bkosten = $hastar;
				}
			} elsif($rmodus eq "x") {
				$bkosten = 0;
			} else {
				$prunit = 1 if $rppreis and $ppakete;
				$prbyte = 1 if $rbpreis and $pbytes;
			}
			if($kosten ne "+" and $kosten != 0) {
				$kosten += $bkosten;
			} else { # um das "+" von oben zu retten...
				$kosten = $bkosten;
			}
			# $rrabatt = 100 if $rrabatt == 0;
			if($kosten ne "+" and $kosten != 0) {
				$kosten=0 if $kosten eq "+";
				#$kosten =~ s/^\+//; # wenn BigInt

				### für Kunden sichtbar: (sichtbar) rabattierter Preis
				$kosten = rund($kosten*$rrabatt,3);
				$gkosten += $kosten;
			}
			$rtxt = puny_decode($rtxt)
				if $rtxt =~ /xn--/;;
			if($rtxt ne "" and $rtxt ne $iline) {
				push(@{$outmap{"$iline|$rtxt"}},$rtid);
				push(@{$outmap{"$iline|$rtxt\1772"}},$rtid);
			}

			#$iline .= "|".(content($rtarif) ? $rtarif : $iline);
			# push(@{$outmap{$iline}},$rtid);
			if(content($rtarif) and $rtarif ne $iline) {
				push(@{$outmap{"$rtarif|$iline"}},$rtid);
				push(@{$outmap{"$rtarif|$iline\1773"}},$rtid);
			} else {
				push(@{$outmap{"$iline|\177"}},$rtid);
				push(@{$outmap{"$iline|\1774"}},$rtid);
			}

			$rinfo = "" if not content $rinfo;
#			if($rtarif eq "*") {
#				if ($rtxt and $rtxt ne $rtarif) {
#					$rtarif = $rtxt; $rtxt = "";
#				} elsif ($rinfo and $rinfo ne $rtarif) {
#					$rtarif = $rinfo; $rinfo = "";
#				}
#			}
			my $pkosten = $kosten;
			my $xkosten;
			if($dosum) {
				$xkosten = $kosten;
				$kosten = $hastar;
				$rabatt = "";
			} else {
				if($kosten ne "" and $kosten ne $hastar) {
					$rabatt = acct_rechnung_rabatt($rrabatt);
				} else {
					$rabatt = "";
				}
			}

			my($pme,$pta,$pst);
			if($prseq != 3) {
				$pta = sprintf "%04d-%02d: ",int($rjjmm/100),$rjjmm%100; ### if $jjmm eq "*";
			} else {
				$pta = sprintf "%04d-%02d-%02d: ",int($rjjmm/100),$rjjmm%100,$rtt;
			}
			# $pta .= "$rtarif/$d_name{$rdienst}";
			if(defined $rart) { # Art gefunden: ausgeben
				if(defined $art_info{$rart}) {
					$pta .= ":41:$rart:" if $flag & 8;
					$pta .= $art_info{$rart};
				} else {
					$pta .= ":42:$rart:" if $flag & 8;
					$pta .= $art_name{$rart};
				}
			} else { # Dienst ausgeben
				if(defined $d_info{$rdienst}) {
					$pta .= ":43:$rdienst:" if $flag & 8;
					$pta .= $d_info{$rdienst};
				} else {
					$pta .= ":44:$rdienst:" if $flag & 8;
					$pta .= $d_name{$rdienst};
				}
			}
			## war:  $pta .= "$d_name{$rdienst}";

			$pta = ":4:$warum $pta" if $flag & 8;
			push(@yline,split(/\s*\n/,$xwrap->wrap($pta)));
			if($dosum) {
				print "px $dosum $rbpreis/$rppreis $kosten/$bkosten/$sumkosten/$xkosten\n" if $flag & 4;
				$kosten = $xkosten;
			}


			if($rinfo ne "" and (not content($rtarif) or $rinfo ne $rtarif)) {
				my $is = $rinfo;
				$is = ":5:$is" if $flag & 8;
				push(@yline,split(/\s*\n/,$xwrap->wrap($is)));
			}
			if($rtxt ne "" and (not content($rtarif) or $rtxt ne $rtarif) and $rinfo ne $rtxt and not $didpri{$rtid}{$rtxt}) {
				$didpri{$rtid}{$rtxt} = 1;
				my $is = $rtxt;
				$is = ":6:$is" if $flag & 8;
				foreach my $li(split(/\|/,$is)) {
					push(@yline,split(/\s*\n/,$xwrap->wrap($li)));
				}
			}
			if($sumnr and $sumblock > 999) {
				# Tarif mit 'ausreichender' Blockgröße. Mitdrucken.
				my($pb,$pm) = get_recheinheit($st->{'id'},'b',$st->{'flag'}|(1<<find_descr("acctflag","via_faktor")),$sumblock);
				$pb =~ s/\,0+$//; # erzwinge: ohne leere Kommastellen
				if($pb eq "1")  {
					$pb = "";
				} else {
					$pb = "* $pb ";
				}
				push(@yline,"Gesamtsumme entspr. $sumnr $pb$pm");
				$sumnr=0;
			}
			if($dosum) {
				if($$rtari{'vinfo'}) {
					my $is = $$rtari{'vinfo'};
					$is = ":7:$is" if $flag & 8;
					foreach my $li(split(/\|/,$is)) {
						push(@yline,split(/\s*\n/,$xwrap->wrap($li)));
					}
				}
			}
			if($prunit or ($prbyte&5 and $st->{'flags'}{'drucke_pakete'}) ) {
				my($pme,$pst) = get_recheinheit($st->{'id'},'p',$st->{'flag'},$ppakete);
				$pta=shift @yline;
				$pta = ":10:$pta" if $flag & 8;

				push(@xline,[$pme,$pst,$pta,"#RPP#"]);
			}
			if($prbyte&5 and not $st->{'flags'}{'drucke_pakete'}) {
				my($pme,$pst) = get_recheinheit($st->{'id'},'b',$st->{'flag'},$pbytes);
				$pta=shift @yline;
				$pta = ":9:$pta" if $flag & 8;

				push(@xline,[$pme,$pst,$pta,($prbyte & 1) ? "#RPB#" : ""]);
			}
			if($prstaf) {
				if($stafu) {
					my $stafx = 0+$$rtari{'unitmini'};
					push(@xline,["","","Staffel: $stafx bis $stafu $pst",acct_betrag(rund($rxpreis,1))]);
				} elsif($stafp) {
					my($pb,$pm,$pf) = get_recheinheit($st->{'id'},'b',$st->{'flag'},$$rtari{'skala'});
					$pb =~ s/\,0+$//; # ohne leere Kommastellen
					if($pb eq "1")  {
						$pb = "";
					} else {
						$pb = "* $pb ";
					}
					my $v = (0+$$rtari{'mini'})*$sumblock*1000/$pf;
					if($$rtari{'maxi'}) {
						$v = "$v bis ".((0+$$rtari{'maxi'})*$sumblock*1000/$pf);
					} else {
						$v = "ab $v";
					}

					# push(@xline,["","","Staffel: $stafx bis $stafp $pst",acct_betrag(rund($rxpreis,1))]);
					push(@xline,["","","Staffel: $v $pb$pm",acct_betrag(rund($rxpreis,1))])
						if $rxpreis;
				} else {
					push(@xline,["","","Staffelpreis",acct_betrag(rund($rxpreis,1))]);
				}
			}
			my $yt = "";
			foreach my $yl(@yline) {
				if($yt =~ /\S/ and length($yt)+length($yl) > $dienstlen-2) {
					$yt = ":12:$yt" if $flag & 8;
					push(@xline,["","",$yt,""]);
					$yt = $yl;
				} else {
					if($yt =~ /\S/) {
						$yt .= "; ";
					} else {
						$yt = "";
					}
					$yt .= $yl;
				}
			}
			if($yt =~ /\S/) {
				$yt = ":8:$yt" if $flag & 8;
				push(@xline,["","",$yt,""]);
			}
			@yline=();

			if($prfrei and not $didprf{$rtid.$rjjmm}) {
				if(content $rdfrei) {
					my $pan = ""; # Anzahl mit andrucken
					$pan = $$ratarif{'anzahl'}." * " if ref $ratarif and $$ratarif{'anzahl'} and $$ratarif{'anzahl'} != 1;
					my $is = $rdfrei;
					$is = ":3:$rtid:|$is" if $flag & 8;
					foreach my $l(split(/\|/,$is)) {
						push(@xline,["","",$pan.$l,""]);
						$pan="";
					}
				} else {
					push(@xline,["","",(($flag & 8)?":$prfrei:":"")."Freikontingent","- ".acct_betrag($rfrei/10)]);
				}
				$didprf{$rtid.$rjjmm}=1;
			}

			if($kosten ne "+") {
				### für Kunden sichtbar: Ausgabe (sichtbar) rabattierter Preis

				my($rpb,$rpp);

				if($st->{'flags'}{'ohne_einzelpreis'}) {
					$rpp = "";
					$rpb = "";
				} elsif($st->{'flags'}{'basis_epreis'}) {
					my($pb,$pm,$pf) = get_recheinheit($st->{'id'},'b',$st->{'flag'},$rskala);
					
					$rpp = acct_betrag(rund($rppreis*$rfaktor*$pf/$rskala,4));
					$rpb = acct_betrag(rund($rbpreis*$rfaktor*$pf/$rskala,4));
				} else {
					$rpp = acct_betrag(rund($rppreis*$rfaktor,1));
					$rpb = acct_betrag(rund($rbpreis*$rfaktor,1));
				}
				print "pk $pbytes/$ppakete $rdienst/$rziel $rquelle.$rart ${rjjmm}_$rtt :: $hastarif:$hastar $kosten/$bkosten/$sumkosten $rfaktor $rbpreis/$rppreis =$rpb/$rpp\n" if $flag & 4;

				map {
					if($_->[3] eq "#RPP#") {
						$_->[3] = $rpp; $rpp = "";
					} elsif($_->[3] eq "#RPB#") {
						$_->[3] = $rpb; $rpb = "";
					}
				} @xline;

				my $yl = shift @xline;
				$out .= sprintf "%7s %-6s%-${dienstlen}.${dienstlen}s %10s%6s %11s\n",
					@{$yl},acct_rechnung_rabatt($rrabatt),acct_betrag($kosten);
				foreach $yl(@xline) {
					$out .= sprintf "%7s %-6s%-${dienstlen}.${dienstlen}s %10s\n", @{$yl};
				}
				@xline = ();
			}

			if($pkosten ne "+" and $pkosten != 0) {
				my $rrinfo = $rinfo;
				$rrinfo = "xxx" if $rrinfo eq "";

				my $dr = "$knr~$rkonto~".sdaterange($abe,$aen); # ."~".$rrinfo;
				my $kto = $r->{'buchungen'}{$dr};

				unless(ref $kto) {
					$kto = ['~', 0, '~'];
					$r->{'buchungen'}{$dr} = $kto;
				}
				my $trt = $rtarif;
				if(content($trt) and $trt eq "*") {
					$trt = "*/".name_dienst($rdienst);
				}
				my $pat = "~${trt}~";
				$kto->[0] .= "${trt}~" unless $kto->[0] =~ /$pat/;
				$kto->[1] += $pkosten;
			}
		} else {
			### Duplikat von oben
			if(content($rtarif) and defined $outmap{"$rtarif|$iline"}) {
				push(@{$outmap{"$iline|\177"}},$rtid);
				push(@{$outmap{"$iline|\1775"}},$rtid);
			} elsif(defined $ttlink{$iline}) {
				if($iline ne $ttlink{$iline}) {
					push(@{$outmap{$iline."|".$ttlink{$iline}}},$rtid);
					push(@{$outmap{$iline."|".$ttlink{$iline}."\1776"}},$rtid);
				} else {
					push(@{$outmap{"$iline|\177"}},$rtid);
					push(@{$outmap{"$iline|\1777"}},$rtid);
				}
			}
		}
		$rtarif{$rtid} = $rtarif;

		$outv{$rtid} = do {my $x; \$x} unless ref $outv{$rtid};
		${$outv{$rtid}} .= $out;
		$rpreis = 0;
		$kosten = 0;
		$bkosten = 0;
		$rinfo = "";
		$rtarif = "";
	};

	$sel_acct = "SELECT readonly acct.hash, acct.seq, acct.bytes, acct.pakete, acct.jjmm, acct.tt, acct.quelle, acct.dienst, acct.dest, acct.kunde, acctassoc.info, acctassoc.acctinfo, acctassoc.tarifkunde FROM acct LEFT JOIN acctassoc ON acctassoc.hash = acct.hash AND acctassoc.seq = acct.seq $sel_acct ORDER BY acct.jjmm, acct.dienst, acct.quelle, acct.dest, acct.tt";

	my @tarif_acct;
	my($tline) = 0;

	$sel_tarifacct .= " $sel_tarifacct_ ( kunde = $knr or kunde is null or dkunde = $knr )"; $sel_tarifacct_ = "and";
	
	$res = DoSelect {
		my($aid,$akunde,$atarif,$adienst,$aziel,$aquelle,$afaktor,$abeginn,$aende,$adirekt,$addienst,$adkunde) = @_;
		$atarif = undef if defined $atarif and $atarif eq $s_global;
		push(@tarif_acct,{
			NAME   => (defined $atarif) ? $atarif : "*",
			id     => $aid,
			kunde  => $akunde,
			tarif  => $atarif,
			dienst => $adienst,
			ziel   => $aziel || "",
			quelle => $aquelle,
			faktor => $afaktor,
			beginn => $abeginn,
			ende   => $aende,
			direkt => $adirekt,
			ddienst=> $addienst,
			dkunde => $adkunde
		});
		$dkunde{$akunde}++ if $akunde;
		$tline++; print STDERR "  $tline\r" unless $flag & 6;

	} "select tarifacct.id,tarifacct.kunde,tarifname.name,tarifacct.dienst,tarifacct.ziel,tarifacct.quelle,tarifacct.faktor,tarifacct.beginn,tarifacct.ende,tarifacct.direkt,tarifacct.ddienst,tarifacct.dkunde from tarifacct left join tarifname on tarifacct.tarifname=tarifname.id $sel_tarifacct order by tarifacct.id,tarifacct.kunde";
	print STDERR "tarifacct: $res\n" unless $res > 0;
	$dkunde{$knr}++;

	# Tarife des Kunden: Hash {Kundennummer}{Dienst}
	my %tarif_kunde;
	my %tarif_id;
	$res = DoSelect {
		my($aid,$akunde,$atarif,$adienst,$anum,$abeginn,$aende,$ainfo,$anextrech) = @_;

		return if $akunde != $knr;
		return if $atarif eq $s_unbekannt;
		return if $atarif eq $s_konflikt;

		# return if $anum == 0;
		$tarif_kunde{$akunde}{$adienst} = {
			"NEXT"   => $tarif_kunde{$akunde}{$adienst},
			"id"     => $aid,
			"DIENST" => name_dienst($adienst), ## Debugging
			"tarif"  => $atarif,
			"beginn" => $abeginn,
			"ende"   => $aende,
			"anzahl" => $anum,
			"info"   => $ainfo,
			"next"   => $anextrech,
			"SEEN"   => 0};
		$tarif_id{$aid}=$tarif_kunde{$akunde}{$adienst};
	} "select tarifkunde.id,tarifkunde.kunde,tarifname.name,tarifkunde.dienst,tarifkunde.anzahl,tarifkunde.beginn,tarifkunde.ende,tarifkunde.infotext,tarifkunde.nextrech from tarifkunde,tarifname where tarifname.id=tarifkunde.tarifname and $sel_tarifkunde order by beginn";
	if($res == 0) {
		if($res eq "empty" or $res =~ /^0/) {
			print STDERR "$knr/".name_kunde($knr).": Kein Tarif!\n" unless $flag&2;
		} else {
			print STDERR "tarifkunde: $res\n" unless $flag&2;
		}
	}

	$line = 0; print STDERR "\r        Accountingdaten $knr, ".svdaterange($abe,$aen)."\r" unless $flag & 6;
	my(@rrf);
	{
		my %xplatz; my %xby; my %xmby;
		print "MByte.Byte/Pkt Dienst/Ziel Quelle jjmm_tt\n" if $flag & 4;

		my $acctread = sub {
			my (
				$ihash, $iseq,    $ibytes,  $ipack, $ijjmm,
				$itt,   $iquelle, $idienst, $iziel, $iknd,
				$itxt,  $iart,    $itid
			) = @_;
			$itxt="" unless content $itxt;

			$line++; print STDERR "  $line\r" if !($line % 20) and not $flag & 6;
			print "r $ihash-$iseq $ibytes/$ipack $idienst/$iziel $iquelle ${ijjmm}_$itt ${\def_or_minus $iart}/${\def_or_minus $itid}\n" if $flag & 4;

			$iziel=defined $iart ? $iart : "" if is_dienst($idienst,"nic");

			my $atm;
			if($itt == 0 or $itt > 31) {
				$atm = unixtime "$ijjmm-01";
			} else {
				$atm = unixtime "$ijjmm-$itt";
			}

			suche_tarifacct:
			foreach my $tl(@tarif_acct) {
				next suche_tarifacct if $$tl{'direkt'} ne 'p';

				next suche_tarifacct if $$tl{'beginn'} > $atm;
				next suche_tarifacct if $$tl{'ende'} and $$tl{'ende'} <= $atm;
				next suche_tarifacct if defined $$tl{'quelle'} and $$tl{'quelle'} != $iquelle;

				if (defined $$tl{'dienst'} and $$tl{'dienst'} != $idienst) {
					my($mdienst) = $idienst;
					ffx: {
						do {
							last ffx if $$tl{'dienst'} == $mdienst;
							next suche_tarifacct if $rstop{$mdienst};
							$mdienst = $eqneu{$mdienst};
						} while(defined $mdienst);
						next suche_tarifacct;
					}
				}
				if (($sdienst{$$tl{'dienst'}}||"") eq ($sdienst{$idienst}||"")) {
					next suche_tarifacct if $$tl{'ziel'} ne $iziel and $$tl{'ziel'} ne "";
				} else {
					next suche_tarifacct if $$tl{'ziel'} ne "";
				}

				if($iknd == $knr) { ## Das ist ein Datensatz des "richtigen" Kunden
					next suche_tarifacct if $$tl{'kunde'} and $$tl{'kunde'} != $knr; # .. betrifft nicht diesen  Eintrag
					return if $$tl{'dkunde'} and $$tl{'dkunde'} != $knr; # dieser Eintrag routet um => raus
					### TODO: trotzdem anzeigen!
				} else { ## Datensatz für einen anderen Kunden
					next suche_tarifacct if $$tl{'kunde'} != $iknd and $$tl{'kunde'} != 0; # ... betrifft nicht diesen  Eintrag
					if($$tl{'dkunde'} != $knr) { # ... routet aber nicht auf unseren Kunden um => raus
						print "r- kunde $$tl{'dkunde'} $knr\n" if $flag & 4;
						return;
					}
					$iknd = $knr;
				}

				$idienst = $$tl{'ddienst'} if defined $$tl{'ddienst'}; # Zieldienst gesetzt => umklemmen

				last suche_tarifacct if $$tl{'faktor'} == 100; ## nix umzurechnen
				use integer;

				$ibytes = Math::BigFloat->new($ibytes)
					unless ref $ibytes and $ibytes->isa("Math::BigFloat");
				$ipack = Math::BigFloat->new($ipack)
					unless ref $ipack and $ipack->isa("Math::BigFloat");
				if($ibytes > 0) {
					$ibytes--;
					$ibytes *= $$tl{faktor};
					$ibytes /= 100;
					$ibytes++;
				}

				if($ipack > 0) {
					$ipack--;
					$ipack *= $$tl{faktor};
					$ipack /= 100;
					$ipack++;
				}
				last suche_tarifacct;
			}
			if($iknd != $knr) {
				print "r- iknd $iknd $knr\n" if $flag & 4;
				return;
			}

			$jmh{$ijjmm}++;
			my $vec = [ $ihash,$iseq, $ibytes,$ipack,$ijjmm,$itt,$iquelle,$idienst,$iziel,$itxt,$iart,$itid ];

			if(&$isdienst($idienst) == 2) {
				my $xplatz;
				if(defined($xplatz = $xplatz{$ijjmm}{$itxt}{$iziel})) {
					 $rrf[$xplatz] = $vec if $rrf[$xplatz]->[1] < $ibytes;
				} else {
					$xplatz{$ijjmm}{$itxt}{$iziel} = 0+@rrf;
					print "rx @$vec\n" if $flag & 4;
					push(@rrf, $vec);
				}
			} else {
				print "r+ ".join(" ",map { def_or_minus($_) } @$vec)."\n" if $flag & 4;
				push(@rrf, $vec);
			}

			$firstacct = $atm if not $firstacct or $firstacct > $atm;
		};

		my $ks = "kunde=".join(" or kunde=", keys %dkunde);
		$sel_acct =~ s/ KUNDEN / ( $ks ) /;
		DoSelect {
			&$acctread(@_);
		} "$sel_acct";
	}

	my $donow = sub { # Test ob Wiederholung oder was auch immer
		my($tarif,$rabatt,$monat) = @_;
		$tarif = \%fups unless ref $tarif;

		if($$tarif{"DONE.$rabatt.$monat"}) {
			return 0 if $skiptar eq "nix";
			return 0 if $skiptar != $tarif or $skiprabatt != $rabatt or $skipmonat != $monat;
			return 1;
		} else {
			if($skiptar eq "nix") {
				$$tarif{"DONE.$rabatt.$monat"} = 1;
				$skiptar = $tarif; $skiprabatt = $rabatt; $skipmonat = $monat;
				return 1;
			}
			$nskiptar++;
			return 0;
		}
	};
	if(@rrf) {
		print "a* 0..$maxfolgenr\n" if $flag & 4;
		foreach my $jm (sort {$a <=> $b} keys %jmh) {
			$jm=0+"$jm"; # Perl-Bug !?!
			$prseq = 0;
			my $acnt;
			while($prseq <= $maxfolgenr) {
				$acnt=0;
				$line = 0; print STDERR "\r        Analyzing accounting data for $knr, $jm, seq $prseq     \r" unless $flag & 6;
				$rjjmm = "start"; 
				$rtt = "start"; $rquelle = "start"; $rziel = "start";
				$rdienst = "start"; 
				$rkonto = "start"; $rinfo = "start"; $rtarif = "start";
				$rfaktor = "start"; $rpreis = 0;
				$rtxt = "start"; $rrabatt = "start"; $rskala = "start";
				$rmodus = "start"; $rbpreis = "start"; $rfrei = 0;
				$rdfrei = "start";
				$rppreis = "start"; $rxpreis = "start";

				foreach $arr(@rrf) {
					my($ihash,$iseq);
					($ihash,$iseq, $ibytes,$ipack,$ijjmm,$itt,$iquelle,$idienst,$iziel,$itxt,$iart,$itid) = @$arr;
					$iitid=undef;
					next if $ijjmm != $jm;
					$line++; print STDERR "  $line\r" if !($line % 100) and not $flag & 6;
					next if $prseq != &$isdienst($idienst);
					print "s $ihash-$iseq $ibytes/$ipack $idienst/$iziel $iquelle ${ijjmm}_$itt ${\def_or_minus $iart}/${\def_or_minus $itid}\n" if $flag & 4;
					$ofakt = 100;

					my $tm = unixtime "$ijjmm-$itt";
					#
					$ikosten = 0;
					$ibkosten = 0;
					$ipreis = 0;
					$ihastarif = 0;
					$iinfo = "";
					$imodus = "";
					$iskala = 1;
					$ifrei = 0;
					$idfrei = "";
					$irabatt = 100;

					$kfaktor = 1;
					my $kfskip = 0;
					my $kdienst;

					# Wir haben: Zeit, Dienst, Accounting-Datensatz
					# Wir suchen: zu Dienst und Zeit passenden Tarif, 
					#             zugehöriger Berechnungsfaktor.
					# Suche in tarifkunde/atarif den Tarif.
					# Suche in tarifacct/aFELDNAME den Faktor.
					# Suche in tarif den Preis.
					# Wenn nicht gefunden, wiederhole mit tarifeq/eqfakt+eqneu.
					# Wenn nicht gefunden, wiederhole mit tarif=*.
					# 
					## Falls es einen Eintrag in der acctassoc-Tabelle
					## gibt, dann kommt die große Keule der
					## Spezialverarbeitung:
					## $itid: Kundentarif-ID/
					## $iart: Art des Accountingdatensatzes
					##        (für die Ausgabe auf der Rechnung)

					suche_tarife:
					foreach my $mit_globalem_tarif ("",$s_global) {
						my $jdienst = $idienst;
						my $oofaktor = 1;

						suche_ktarif:
						while(1) {
							$kdienst = $jdienst;
							my $ktarif;
							#my $fprabatt = 100;

							if($itid) {
								$ktarif = $tarif_id{$itid};
							} elsif(not $mit_globalem_tarif) {
								$ktarif = $tarif_kunde{$knr}{$kdienst};
								while(not ref $ktarif) {
									next suche_ktarif if $rstop{$kdienst} or not defined($kdienst = $eqneu{$kdienst});
									$ktarif = $tarif_kunde{$knr}{$kdienst};
								}
							}

							suche_tarif:
							while(ref $ktarif or $mit_globalem_tarif) {
								$iitid = $$ktarif{'id'} if ref $ktarif;
								unless($itid or $mit_globalem_tarif) {
									next suche_tarif if $$ktarif{'beginn'} >= $tm+86400;
									next suche_tarif if $$ktarif{'ende'} and $$ktarif{'ende'} <= $tm;
									next suche_tarif if $itxt and defined $ttarif{$itxt} and $$ktarif{'tarif'} ne $itxt;

								}
								my $ttari;
								suche_tarifacct:
								foreach my $tl(@tarif_acct) {
									next suche_tarifacct if $$tl{'direkt'} eq 'p';
									next suche_tarifacct if $$tl{'direkt'} eq 'f';
									next suche_tarifacct if $$tl{'direkt'} eq '=';
									next suche_tarifacct if $$tl{'direkt'} eq 'F';
									next suche_tarifacct if $$tl{'direkt'} eq 'V';
									next suche_tarifacct if $$tl{'beginn'} > $tm;
									next suche_tarifacct if $$tl{'ende'} and $$tl{'ende'} <= $tm;
									next suche_tarifacct if $$tl{'kunde'} and $$tl{'kunde'} != $knr;
									next suche_tarifacct if defined $$tl{'tarif'} and ($mit_globalem_tarif or $$tl{'tarif'} ne $$ktarif{'tarif'});
									next suche_tarifacct if defined $$tl{'quelle'} and $$tl{'quelle'} != $iquelle;

									if (defined $$tl{'dienst'} and $$tl{'dienst'} != $idienst) {
										# Pruefe ob der angegebene Dienst passt.
										my($mdienst) = $idienst;
										ffx: {
											while($mdienst) {
												last ffx if $$tl{'dienst'} == $mdienst;
												next suche_tarifacct if (!$mit_globalem_tarif and $rstop{$mdienst});
												$mdienst = $eqneu{$mdienst};
											}
											next suche_tarifacct;
										}
									}
									# 
									# Wenn von diesen Diensten einer
									# speziell ist und einer nicht, dann
									# haben die Ziele nix miteinander zu
									# tun.
									if (defined $$tl{'dienst'} and ($sdienst{$$tl{'dienst'}}||"") eq ($sdienst{$kdienst}||"")) {
										next suche_tarifacct if ($$tl{'ziel'}||"") ne ($iziel||"") and ($$tl{'ziel'}||"") ne "";
									} else {
										next suche_tarifacct if ($$tl{'ziel'}||"") ne "";
									}

									print "s-$ihash-$iseq $$tl{'id'} =$$tl{'faktor'}\n" if $flag & 4;
									my $ofaktor = $kfaktor;
									#if($$tl{'direkt'} eq 'f') {
									#	$fprabatt = $$tl{'faktor'};
									#	next suche_tarifacct;
									#}
									if($$tl{'direkt'} eq "v") {
										$irabatt = $$tl{'faktor'};
									} elsif($$tl{'direkt'} eq "k") {
										if($ibytes > 0) {
											# Die nächste Zeile
											# implementiert "immer aufrunden".
											$ibytes -= 1; $ibytes *= $$tl{'faktor'}; $ibytes /= 100; $ibytes += 1;
										}

										if($ipack > 0) {
											if($ipack > 10) { # nur runden wenn ausreichend gross
												$ipack -= 1; $ipack *= $$tl{'faktor'}; $ipack /= 100; $ipack += 1;
											} else {
												$ipack *= $$tl{'faktor'}; $ipack /= 100;
											}
										}
									} else {
										$ofaktor = ($ofaktor * $$tl{'faktor'})/100;
									}
									my $ltarif = $mit_globalem_tarif || $$ktarif{'tarif'};
									my $ooofaktor = $oofaktor;

									# Umrechnungsfaktor global, vom gesuchten
									# Dienst auf den gefundenen Tarif
									suche_faktor:
									while(1) {
										$kdienst = $idienst; ## WAR: jdienst
										$ttari = $ttarif{$ltarif}{$kdienst};
										while(!ref $ttari) {
											$kfskip = 0 if $kdienst == $jdienst;
											next suche_faktor if (!$mit_globalem_tarif and $rstop{$kdienst}) or not defined $eqneu{$kdienst};
											$ooofaktor *= $eqfakt{$kdienst}/100 unless $kfskip;
											$kdienst = $eqneu{$kdienst};

											$ttari = $ttarif{$ltarif}{$kdienst};
										}

										suche_staffel: # ... und Beginn
										while(ref $ttari) {
											next suche_staffel if $$ttari{'beginn'} > $tm;

											# ab hier gilt: $ttari: 
											# "korrekter Tarif gefunden".

											$iinfo = $$ttari{'info'} if $kdienst eq $idienst;
											$imodus = $$ttari{'intval'};
											$itarif = $ltarif;
											$iskala = $$ttari{'skala'};
											$ifrei = rund($$ttari{'frei'},-1); # *$fprabatt/100;
											$idfrei = $$ttari{'pfrei'};
											$ibpreis = $$ttari{'preis'};
											$ixpreis = $$ttari{'festpreis'};
											$ippreis = $$ttari{'unitpreis'};
											$itari = $ttari;
											$ifaktor = $ofaktor;
											$ibytes *= int($ooofaktor*1000);
											$ibytes /= 1000;

											if($ofaktor == 0) {
												print "...ignoriert F=0\n" if $flag & 4;
												last suche_tarife;
											}
											if($imodus eq "x") {
												print "...ignoriert M=x\n" if $flag & 4;
												last suche_tarife;
											}
											# last suche_tarife if $$ttari{'preis'} == 0 and $$ttari{'unitpreis'} == 0;
											$ipreis = 1;

											if($imodus ne "m" and $imodus ne "a" and $imodus ne "d") {
												if($$ttari{'mini'}) {
													my $tval = $ibytes;
													$tval /= $$ttari{'skala'} if $$ttari{'skala'} > 0;
													$$ttari{'SKIP'}++, next suche_staffel if $tval < $$ttari{'mini'};
												}
												if($$ttari{'unitmini'}) {
													$$ttari{'SKIP'}++, next suche_staffel if $ipack < $$ttari{'unitmini'};
												}

												my $jbkosten = $ibkosten;
												$ibkosten = 0 if $imodus eq "i";
												if($ibytes) {
													my $pbytes   = $ibytes * $$ttari{'preis'};
													$pbytes  /= $$ttari{'skala'} if $$ttari{'skala'} > 0;
													$ibkosten += ($pbytes * int($ofaktor*100))/100;
												}
												$ibkosten = $jbkosten if $ibkosten < $jbkosten and $imodus eq "i";
												if($ipack) {
													$ikosten += $ipack * $$ttari{'unitpreis'} * $ofaktor;
												}
												print ".K $ikosten, .KB $ibkosten\n" if $flag & 4;
												$$ttari{'SEEN'} = 1;
											}
											$$ktarif{'SEEN'} = 1 unless $mit_globalem_tarif;
											$iatarif = $ktarif;

											# $ihastarif |= 1 if $ofaktor;
											$ihastarif |= 1 if $ikosten;
											$ihastarif |= 1 if $ibkosten;
											$ihastarif |= 1 if $ibytes and $ibpreis;
											$ihastarif |= 1 if $ipack and $ippreis;
											$ihastarif |= 1 if $$ttari{'dnull'} ne "n";
											$ikonto = ($$ttari{'vkonto'} or $$ttari{'fkonto'});


											# Gefunden und alles gesetzt!
											last suche_tarife;
										} continue {
											$ttari = $$ttari{'NEXT'};
										}
									} continue {
										if(not ref $ttari and $s_global and $ltarif ne $s_global) {
											$ltarif = $s_global;
										} else {
											last suche_faktor;
										}
									}
									last suche_tarifacct; # wenn es keinen passenden Tarif gibt, nicht weitersuchen
								}
							} continue {
								last if $mit_globalem_tarif;
								$ktarif = $$ktarif{'NEXT'};
							}
						} continue {
							last if $itid;
							last if $jdienst == 0;
							# $kfaktor *= $eqfakt{$jdienst}/100;
							$oofaktor *= ($eqfakt{$jdienst}||0)/100;
							$kfskip = 1;
							next suche_tarife if ($mit_globalem_tarif and $rstop{$jdienst});
							my $kdienst = $jdienst;
							if(not defined $eqneu{$jdienst}) {
								print STDERR "\r* $knr: Dienst $jdienst: ohne Tarif      \n" if $mit_globalem_tarif and not $dienst_warn{$jdienst}++ and not $flag&2;
								next suche_tarife;
							}

							$jdienst = $eqneu{$jdienst};
							next suche_tarife if $jdienst and not $kfaktor;
						}
						$ihastarif |= 2 if $mit_globalem_tarif;
					} continue {
						$kfaktor = 1;
						$kfskip = 1;
					}

					# If writing invoices, totally ignore ignored data.
					next unless $ihastarif & 1;
					next unless &$donow($itari,$irabatt,$jm);

					# Re-init if start, or if a managed field changes.
					my $prc = "";
					my $idi = sub {
						my($v) = @_;
						return if $prc =~ /start$/;
						my $rv = eval "\$r$v";
						my $iv = eval "\$i$v";
						if(defined $rv and $rv eq "start") { $prc = ($v||"").":start"; }
						elsif(($rv||"") ne ($iv||"")) { $prc .= ($v||"").",".($rv||"").",".($iv||"").";"; }
					};
					my $sdi = sub {
						my($v) = @_;
						return if $prc =~ /start$/;
						my $rv = eval "\$r$v";
						my $iv = eval "\$i$v";
						if($rv eq "start") { $prc = "$v:start"; }
						elsif($rv ne $iv) { $prc .= "$v,$rv,$iv;"; }
					};
					my $mdi = sub {
						my($v) = @_;
						return if $prc =~ /start$/;
						my $rv = eval "\$r$v";
						my $iv = eval "\$i$v";
						my $vv = eval "\$$v";
						if($rv eq "start") { $prc = "$v:start"; }
						elsif($rv ne $iv and $vv eq "*") { $prc .= "$v,$rv,$iv;"; }
					};
					$itid=$iitid unless $itid;

					&$idi("faktor");
					&$idi("tarif");
					&$idi("info");
					&$idi("txt");
					&$idi("tid");
					&$sdi("modus");
					&$idi("bpreis");
					&$idi("xpreis");
					&$idi("ppreis");
					&$idi("frei");
					&$idi("dfrei");
					&$idi("konto");
					&$idi("skala");
					&$idi("rabatt");
					&$mdi("rabatt");
					&$idi("dienst");

					if($prc ne "") {
						$prc =~ s/,$//;
						&$pr($prc) if $rtxt ne "start";
						$bytes = 0; $pakete = 0; $nds = 0;
						$hastarif = $ihastarif;
					}
#					elsif(($pakete > 0 or $bytes > 0) and (
#		($rfaktor ne $ifaktor                   ) or
#		($rtarif  ne $itarif                    ) or
#		($rinfo   ne $iinfo                     ) or
#		($rtxt    ne $itxt                      ) or
#		($rbpreis != $ibpreis                   ) or
#		($rxpreis != $ixpreis                   ) or
#		($rppreis != $ippreis                   ) or
#		($rmodus  ne $imodus                    ) or
#		($rskala  != $iskala                    ) or
#		($rrabatt != $irabatt                   ) or
#		($rkonto  != $ikonto                    ) or
#		($rfrei   != $ifrei                     ) or
#		($rdfrei  ne $idfrei                    ) or
#		($rjjmm   ne $ijjmm   and $jjmm   eq "*") or
#		($rdienst ne $idienst                   ))) {
#						$hastarif |= 4;
#					}

					$bytes   += $ibytes;
					$pakete  += $ipack;
					$nds     += 1;
					$rjjmm    = $ijjmm;
					$rtt      = $itt;
					$rart     = $iart;
					$rquelle  = $iquelle;
					$rkonto   = $ikonto;
					$rdienst  = $idienst;
					$rrdienst = $kdienst;
					$rziel    = $iziel;
					$rtxt     = $itxt;
					$rfaktor  = $ifaktor;
					$rtarif   = $itarif;
					$rtid     = $itid;
					$rinfo    = $iinfo;
					$rbpreis  = $ibpreis;
					$rxpreis  = $ixpreis;
					$rppreis  = $ippreis;
					$ratarif  = $iatarif;
					$rtari    = $itari;
					$rmodus   = $imodus;
					$rskala   = $iskala;
					$rnds     = $nds;
					$rrabatt  = $irabatt;
					$rfrei    = $ifrei;
					$rdfrei   = $idfrei;
					$rpreis  |= $ipreis;
					$kosten  += $ikosten;
					$bkosten += $ibkosten;
					$hastarif|= $ihastarif;
				} continue {
					DoPing();
				}
			} continue {
				# $prunit = 0;
				$ijjmm = "last"; ## Marker für Summenausgabe
				&$pr("last") if $rtxt ne "start";

				$bytes  = 0; $pakete = 0; $nds = 0;
				$hastarif = $ihastarif;

				if($nskiptar) {
					$skiptar = "nix";
					$nskiptar = 0;
				} else {
					$prseq++;
				}
			}
		}
		$skiptar = "nix";
		$nskiptar = 0;
	} else {
		print STDERR "$knr/".name_kunde($knr).": Keine Verkehrsdaten gefunden                 \n" unless $flag&2;
	}
	@rrf = (); # nicht mehr benötigt



	#################################################################
	# Festpreise ("Kundentarife")
	#################################################################

	my $rnr = $r->{'rnr'};
	unless(defined $rnr) {
		my($j,$m,$t) = isodate $r->{'rechnungsdatum'};
		$rnr = $r->{'rnr'} = $r->{'repro'} || get_free_rnr(1|2|4,"Rechnungslauf",undef,$j)
	}
	$outdata .= acct_text_kopf($rnr,$knr,$rdate, undef,$rbe,$ren, $oberkunde?2:0);

	my($dienst,$subd);
	my $atar = $tarif_kunde{$knr};

	if(ref $atar) { # hat der Kunde überhaupt Tarife?
		my $subatar;
		while(($dienst,$subatar) = each %$atar) { # über alle Tarife
			ktarife:
			while(ref $subatar) { # über alle Zeiträume
				next unless $$subatar{'anzahl'};
				my $tstart = $$subatar{'beginn'};
				my $ende;

				## Wurde schon berechnet?
				my($rnday,$tende);
				{
					if (not $r->{'repro'}) {
						my $nrx = $$subatar{'next'};
						$tstart = $nrx if defined $nrx and $nrx > 10 and $tstart < $nrx;
						# 'nextrech' im Kundentarif
						$tende = $$subatar{'ende'};
						$rnday = undef;
						print ":T ".$$subatar{'id'}." ${\scalar isotime $tstart} ${\($tende ? scalar isotime $tende : '')}\n" if $flag & 4;
					} else {
						($tstart,$tende,$rnday) = DoFn("select beginn,ende,anzahl from nextrech where tarifkunde=".$$subatar{'id'}." and knextrech=$r->{'_repro'}");
						unless($tende) {
							print ":Tn ".$$subatar{'id'}."\n" if $flag & 4;
							next ktarife;
						}
						if($rnday) { ## Kompatibilität mit Altdaten
							$rnday /= 100;
							print ":Ta ".$$subatar{'id'}." $rnday ${\scalar isotime $tstart} ${\scalar isotime $tende}\n" if $flag & 4;
						} else {
							$rnday = undef;
							print ":Tb ".$$subatar{'id'}." ${\scalar isotime $tstart} ${\scalar isotime $tende}\n" if $flag & 4;
						}
					}
					# $nrx = $r->{'tarifkunden'}{$$subatar{'id'}} if $nrx < $r->{'tarifkunden'}{$$subatar{'id'}};

					# $start = $nrx if (($flag & 1) ? ($nrx > 10) : ($start < $nrx));
					# Wenn normale Rechnung, nicht vor den Startzeitpunkt setzen
				}
				next if $$subatar{'ende'} and $tstart >= $$subatar{'ende'};
				next if $ten and $tstart >= $ten;

				my($jdienst) = $dienst;
				my $xtstart = $tstart;
				my $ttar = $ttarif{$$subatar{'tarif'}}{$dienst};
				while(!ref $ttar) {
					last if $rstop{$jdienst};
					last unless defined($jdienst = $eqneu{$jdienst});
					$ttar = $ttarif{$$subatar{'tarif'}}{$jdienst};
				}
				my $rabt = 0;
				my $begpr = 0;
				my @work;
				{
					my $lastbeginn = undef;
					while(ref $ttar) {
						next if $$ttar{'SKIP'};
						next if ($$ttar{'unitmini'} or $$ttar{'mini'}) and not $$ttar{'SEEN'};
						next if $lastbeginn and $lastbeginn == $$ttar{'beginn'};
						push(@work,$ttar);
						$lastbeginn = $$ttar{'beginn'};
					} continue {
						$ttar = $$ttar{'NEXT'};
					}
				}

				$ttar = pop @work;
				my $rende=undef; # reales Ende, nicht "vorverlegt"
				while(ref $ttar) {
					my $rabatt = 100;
					my $hiderabatt;
					my $start = $tstart;
					$ende = $tende;
					$rende=undef;

					my $beginn = $start;

					## Limitiere den Zeitraum
					if($start < $$ttar{'beginn'}) { $start = $$ttar{'beginn'}; }
					$ende = $$ttar{'ende'} if $$ttar{'ende'} and (not $ende or $ende > $$ttar{'ende'});
					next if $ende and $tstart >= $ende;

					if($ten and (not $ende or $ende > $ten)) { $ende = $ten; }

					#
					# Fallback...
					if($start == 0) { $start = $firstacct; }
					if($ende == 0) { $ende = $r->{'tarif_ende'}; }
					if($beginn == 0) { $beginn = $start; }
					next if $start == 0 or $start >= $ende and $$ttar{'dnull'} ne "j";
					### für den Kunden sichtbar
					my $epr = rund($$ttar{'festpreis'});
					my $outp;
					
					my $rtid = $$subatar{'id'};
					if ($epr != 0 or $$ttar{'fnull'} eq "j") {
						$out{$rtid} = do {my $x=""; \$x} unless ref $out{$rtid};
						$outp = $out{$rtid};
					} else {
						$outx{$rtid} = do {my $x=""; \$x} unless ref $outx{$rtid};
						$outp = $outx{$rtid};
					}


					## Nun finde Rabatte
					suche_tarifacct: foreach my $tl(@tarif_acct) {
						next suche_tarifacct if $$tl{'direkt'} ne 'f' and $$tl{'direkt'} ne '=';
						next suche_tarifacct if defined $$tl{'tarif'} and $$tl{'tarif'} ne $$subatar{'tarif'};
						if (defined $$tl{'dienst'} and $$tl{'dienst'} != $dienst) {
							my($mdienst) = $dienst;
							ffx: {
								do {
									last ffx if $$tl{'dienst'} == $mdienst;
									next suche_tarifacct if $rstop{$mdienst};
									$mdienst = $eqneu{$mdienst};
								} while(defined $mdienst);
								next suche_tarifacct;
							}
						}

						next suche_tarifacct if $$tl{'beginn'} >= $ende;
						next suche_tarifacct if $$tl{'ende'} and $$tl{'ende'} <= $start;
						next suche_tarifacct if  $$tl{'kunde'} and $$tl{'kunde'} != $knr;

						if($rabt == 0) { # Fall 1: Zeitraum davor
							if($$tl{'beginn'} > $start) {
								# begrenze auf Zeit vor dem Rabatt
								$rabt = 1;
								$ende = $$tl{'beginn'};
							} else {
								$rabt = 2;
							}
						}
						if($rabt == 2) {
							# begrenze auf Zeit während des Rabatts
							$rabatt = $$tl{'faktor'};
							$hiderabatt = ($$tl{'direkt'} eq "=");
							$ende = $$tl{'ende'} if $$tl{'ende'} and $ende > $$tl{'ende'};
							$rende = $ende;
						}
						
						last suche_tarifacct;
					}

					my($byear,$bmon,$bday,$bhour,$bmin,$bsec)=isotime $beginn;
					my($syear,$smon,$sday,$shour,$smin,$ssec)=isotime $start;
					my($eyear,$emon,$eday,$ehour,$emin,$esec)=isotime $ende;

					my $nday;
					my $iv = $$ttar{'intval'};

					if($start >= $ende) {
						next if $iv eq "x";
						$nday = 0;
					}
					elsif($iv eq "x") { next; }
					elsif($iv eq "m") {
						## Berechne den ersten angefangenen Monat nicht
						if($sday > 15) {
							if($smon++ == 12) {
								$syear++;
								$smon = 1;
							}
						}
						$sday = 1;
						$start = unixdate($syear,$smon,$sday);
						if($start >= $ende) {
							$nday = 0;
						} else {
							$nday += ($eyear-$syear)*12+$emon-$smon;
							# Berechne den letzten (angeschnittenen) Monat?
							if($sday+15 < $eday) {
								$nday++;
							} else {
								$ende = unixdate($eyear,$emon,$sday);
							}
						}
					} elsif($iv eq "a" or $iv eq "d") {
						# $nday = ($ende-$start)/24/3600*12/365;
						## Wieviele ganze Monate?
						# $start1 ist der Startzeitpunkt des nächsten
						# Monats

						## Anteil des ersten Monats:
						my $sm = $smon; my $sy = $syear;
						if($sm++ == 12) { $sm=1;$sy++;}
						my $start1 = unixdate($sy,$sm,1);
						$nday = ($start1-$start)/($start1-unixdate($syear,$smon,1));

						## Anteil des letzten Monats:
						my $em = $emon; my $ey = $eyear;
						if($em++ == 12) { $em=1; $ey++;}

						$nday += ($ende-unixdate($eyear,$emon,1))/(unixdate($ey,$em,1)-unixdate($eyear,$emon,1));

						# Jetzt noch die Monate dazwischen...
						$nday += $eyear*12+$emon - $sy*12-$sm;
						print "tA $nday\n" if $flag & 4;
						$nday = rund($nday*100)/100;
						print "tA.$nday\n" if $flag & 4;
					} elsif($iv eq "y") {
						$sday = 1;
						$start = unixdate($syear,$smon,$sday);
						if($start >= $ende) {
							$nday = 0;
						} else {
							$nday = $eyear-$syear;
							if($emon > $smon or $emon == $smon and $eday > $sday) {
									$nday++; $eyear++;
							}
	
							# Wenn der Rest dieses Jahr kleiner ist als
							# der Bereich, den wir am Anfang
							# zugeschlagen haben, weil die Domain am
							# Ersten anfängt, dann berechne das
							# "zusätzliche" Jahr nicht.
							if($nday >= 1) {
								$ende = unixdate($eyear,$smon,$sday);
								my($oby,$obm,$obd) = isodate($$subatar{'beginn'});
								if($$subatar{'ende'} and unixdate($syear,$smon,$obd) > $$subatar{'ende'}) {
									$nday--;
								}
							}
						}

					} elsif($iv eq "s") {
						my $beg = $$subatar{'beginn'};
						next if $beg < $start;
						next if $beg >= $ende;
						$nday = 1;
					} else {
						$nday = 1;
					}
					if ($nday <= 0) {
						next if $$ttar{'dnull'} ne "j";
						$nday = 0;
					}

					$r->{'nextrech'}{$$subatar{'id'}} = [$xtstart,$ende,$nday];

					if(defined $rnday) { # Altdaten (Rechnungs-Neuerstellung)
						$nday = $rnday;
					} else { # runde auf Hundertstel
						$nday = int(($nday*10000+1)/100)/100;
					}

					my $gpr = $epr * $nday * $$subatar{'anzahl'};
					if($gpr == 0 and $$ttar{'fnull'} ne 'j') {
						$r->{'tarifkunden'}{$$subatar{'id'}} = $ende;
						next;
					}
					
					### für den Kunden sichtbar
					$gpr = rund($gpr * $rabatt, 3);
					$gkosten += $gpr;

					my($anzstr);
					if($nday == 0) {
						$anzstr = "-   ";
					} elsif($$subatar{'anzahl'} == 1 or $$ttar{'nonum'} eq "j") {
						if($nday == int($nday)) {
							$anzstr = sprintf("%d   ",$nday);
						} else {
							$anzstr = sprintf("%.2f",$nday);
							$anzstr =~ s/\./,/;
						}
					} elsif($nday == 1) {
						$anzstr = sprintf("%d   ",$$subatar{'anzahl'});
					} else {
						if($nday == int($nday)) {
							$anzstr = sprintf("%d*%-2d",$$subatar{'anzahl'},$nday);
						} else {
							$nday *= $$subatar{'anzahl'};
							$anzstr = sprintf("%.2f",$nday);
							$anzstr =~ s/\./,/;
						}
					}
					### immer noch korrekt gerundet
					$epr *= $$subatar{'anzahl'} if $$ttar{'nonum'} eq "j";
					### Rabatt verstecken.
					$epr = rund($epr*$rabatt,2) if $hiderabatt;

					$rabatt = acct_rechnung_rabatt($hiderabatt ? 100 : $rabatt);
					#$outdata .= sprintf "%1s %-20s %-10s %7s %s%8s %7s %10s\n",$kx,$$subatar{'tarif'},name_dienst($dienst), $anzstr,$xspc,acct_betrag($epr/10),$hiderabatt ? 100 : $rabatt,acct_betrag($gpr);
					my $tkey = $$subatar{'tarif'};
					my $tari = $tkey;
                    my $istr = "";

                    $istr = $$subatar{'info'} if content $$subatar{'info'};
					$istr = puny_decode($istr) if $istr =~ /xn--/;;
					my $instr = $istr;
					$instr =~ s/\s*PBNr\b.*//i;

					if($istr =~ /\S/ and $tkey ne $istr and $dienst != $d_general) {
						push(@{$outmap{"$tkey|$istr"}},$rtid);
						push(@{$outmap{"$tkey|$istr\1778"}},$rtid);
					}

					$didpri{$rtid}{$instr} = 1;
					if($instr ne "" and $tkey ne $instr and $dienst != $d_general) {
						push(@{$outmap{"$tkey|$instr"}},$rtid);
						push(@{$outmap{"$tkey|$instr\1779"}},$rtid);
					}

					push(@{$outmap{"$tkey|\177"}},$rtid);
					push(@{$outmap{"$tkey|\17710"}},$rtid);

					$istr = ":1:$istr" if $flag & 8 and $istr =~ /\S/;

					my $dt = "";
					if(not $step->($dienst){'flags'}{'ohne_name'}) {
						if(defined $d_info{$dienst}) {
							$dt = $d_info{$dienst};
						} else {
							$dt = $d_name{$dienst};
						}
						if($dt eq "--" or defined $outv{$rtid}) {
							$dt = "";
						} else {
							$dt = "/$dt";
						}
					}
					# $$outp .= "<$rtid>\n" if $flag & 8;
					$outlf{$rtid} = "\n" if $$ttar{'klassen'} & (bignum(1)<<find_descr("tarifklasse","drucke_ml"));
					if($epr or not ($$ttar{'klassen'} & bignum(1)<<find_descr("tarifklasse","ohne_null"))) {
						$$outp .= sprintf "%7s %-6s%-${dienstlen}.${dienstlen}s %10s%6s %11s\n",$anzstr, "", $tari.$dt, acct_betrag($epr/10),$rabatt,acct_betrag($gpr);
					} else {
						$$outp .= sprintf "%7s %-6s%-${dienstlen}.${dienstlen}s %10s%6s %11s\n",($nday==1)?"":$anzstr, "", $tari.$dt, "","","";
					}
# Menge Art  Bezeichnung                  $xspc E-Preis  Rabatt     Betrag


					if(index($istr,'|') > -1) { # mehrzeilig?
						# => immer neue Zeile anfangen
						foreach my $l(split(/\|/,$istr)) {
							$$outp .= "$fspc$l\n";
						}
						$istr = "";
					} elsif(length($istr) > $dienstlen) {
						foreach my $l (split(/\s*\n/,$xwrap->wrap($istr))) {
							$$outp .= "$fspc$l\n" if $l =~ /\S/;
						}
						$istr = "";
					}
					my $iistr = $istr;
					if(content $$ttar{'finfo'}) { # Festpreistext
						my $is = $$ttar{'finfo'};
						$is = ":2:$is" if $flag & 8;
						if(index($is,'|') > -1) { # mehrzeilig?
							# => immer neue Zeile anfangen
							$$outp .= "$fspc$istr\n" if $istr =~ /\S/;
							foreach my $l(split(/\|/,$is)) {
								$$outp .= "$fspc$l\n";
							}
							$istr = "";
							
						} elsif(length($istr)+length($is) > $dienstlen-2) {
							# passt nicht mehr in die Zeile => wrappen
							foreach my $l (split(/\s*\n/,$xwrap->wrap($is))) {
								$$outp .= "$fspc$istr\n" if $istr =~ /\S/;
								$istr = $l;
							}
						} else { # passt noch
							$istr .= "; " if $istr ne "";
							$istr .= $is;
						}
					}

					my $begstr  = svdaterange($$subatar{'beginn'},$$subatar{'ende'},1|4);
					my($begstrx) = svdaterange($start,$ende,0);
					if($begstr ne "" and not $begpr++) {
						$begstr = "Nutzung $begstr";
						if(length($istr)+length($begstr) > $dienstlen-2) {
							$$outp .= "$fspc$istr\n" if $istr =~ /\S/;
							$istr = $begstr;
						} else {
							$istr .= "; " if $istr ne "";
							$istr .= $begstr;
						}
					}
					if($begstrx ne "" and $ende != 1) {
						if($nday == 0) {
							$begstr = "bezahlt bis ".(scalar isodate $start);
						} else {
							$begstr = "Berechnung $begstrx";
						}
						if(length($istr)+length($begstr) > $dienstlen-2) {
							$$outp .= "$fspc$istr\n" if $istr =~ /\S/;
							$istr = $begstr;
						} else {
							$istr .= "; " if $istr ne "";
							$istr .= $begstr;
						}
					}
					$$outp .= "$fspc$istr\n" if $istr =~ /\S/;

					if($gpr != 0) {
						$iistr = "x" if $iistr eq "";
						my $rkonto = ($$ttar{'fkonto'} or $$ttar{'vkonto'} or '');
						my $dr = "$knr~$rkonto~".sdaterange($start,$ende); # ."~".$iistr;
						my $kto = $r->{'buchungen'}{$dr};

						unless(ref $kto) {
							$kto = ['~', 0, '~'];
							$r->{'buchungen'}{$dr} = $kto;
						}
						my $rtarif = $$subatar{'tarif'};
						my $pat = "~${rtarif}~";
						$kto->[0] .= "${rtarif}~" unless $kto->[0] =~ /$pat/;
						$kto->[1] += $gpr;
					}
					$r->{'tarifkunden'}{$$subatar{'id'}} = $ende;

					## Merke den Startzeitpunkt, damit beim nächsten
					## Mal der Scan anhält
					$$ttar{'SEEN'}=1;# if $gpr != 0;
				} continue {
					last if $ende < 10;
					$tstart = $ende if $tstart < $ende;
					$tstart = $rende if defined $rende and $tstart < $rende;
					# dies passiert, wenn ein rabattierter Monatstarif vor der
					# Monatsmitte endet
					if($rabt == 2) {
						$rabt = 0;
					} elsif($rabt) {
						$rabt++;
					} else {
						$ttar = pop @work;
					}
					last if $ten and $ende >= $ten;
					last if not $ttar or $$ttar{'SKIP'};
				}
			} continue {
				$subatar = $$subatar{'NEXT'};
			} # über alle Zeiträume
		} # über alle Tarife
	} # wenn der Kunde Tarife hat

# Mappe die fixen Tarife
#	foreach my $iline (keys %out) {
#		next if ${$out{$iline}} eq "";
#		my $il = $outmap{$iline};
#		if($il) {
#			if(not $out{$il}) {
#				$out{$il} = $out{$iline};
#			} elsif($out{$il} != $out{$iline}) {
#				${$out{$il}} .= ${$out{$iline}};
#				${$out{$iline}} = "";
#			} else { ## dasselbe
#				## tue garnix
#			}
#			
#		}
#	}
#
## Füge die variablen Tarife hinzu
#	foreach my $iline (keys %outv) {
#		next if ${$outv{$iline}} eq "";
#		my $il = $outmap{$iline} || $iline;
#		unless (ref $out{$il}) {
#			$out{$il} = do {my $x; \$x};
#			${$out{$il}} .= "<$il;$iline>\n" if $flag & 8;
#
#			my $rtl = $rtarif{$il};
#			$rtl = $rtarif{$iline} unless defined $rtl;
#			$rtl = $1 if not defined $rtl and $il =~ /^(.+)\|/;
#			$rtl = $il unless defined $rtl;
#
#			${$out{$il}} .= "${fspc}Tarif: $rtl\n";
#		}
#		${$out{$il}} .= ${$outv{$iline}};
#		${$outv{$iline}} = "";
#	}
	{
		# sortiere 'gleiche' Tarife nach (Sub)dienst.
		sub subdsort($$) {
			my($x,$y)=@_;
			my $dx=$x?DoFn("select dienst from tarifkunde where id=$x"):0;
			my $dy=$y?DoFn("select dienst from tarifkunde where id=$y"):0;
			return $x-$y if $dx==$dy;
			return 1 if is_dienst($dx,$dy);
			return -1 if is_dienst($dy,$dx);
			return ($dx-$dy) || ($x-$y);
		}

		my $lf = ""; my $lfi = "";
		foreach my $tar(sort { lc($a) cmp lc($b) } keys %outmap) {
			my $dpr=0;
			foreach my $odat(sort subdsort @{$outmap{$tar}}) {
				my $txt = $out{$odat};
				if(not defined $txt or $$txt eq "") {
					$txt = $outx{$odat};
					next if not defined $txt or $$txt eq "";
				}

				$outdata .= $lf; $lf=""; $dpr++;
				if($flag & 8) {
					$outdata .= "<$tar>1:$odat";
					dl:
					foreach my $ta(sort { lc($a) cmp lc($b) } keys %outmap) {
						next dl if $tar eq $ta;
						foreach my $oda(@{$outmap{$ta}}) {
							if($oda == $odat) {
								$outdata .= " $ta";
								next dl;
							}
						}
					}
					$outdata .= "\n";
				}
				$outdata .= $lfi.$$txt; $$txt = "";
				$lfi=$outlf{$odat} if defined $outlf{$odat};
			}
			foreach my $odat(sort subdsort @{$outmap{$tar}}) {
				my $txt = $outv{$odat};
				next if not defined $txt or $$txt eq "";

				$outdata .= $lf; $lf=""; $dpr++;
				if($flag & 8) {
					$outdata .= "<$tar>2:$odat";
					dl:
					foreach my $ta(sort { lc($a) cmp lc($b) } keys %outmap) {
						next dl if $tar eq $ta;
						foreach my $oda(@{$outmap{$ta}}) {
							if($oda == $odat) {
								$outdata .= " $ta";
								next dl;
							}
						}
					}
					$outdata .= "\n" if $flag & 8;
				}
				$outdata .= $$txt; $$txt = "";
			}
			$lf="\n" if $dpr and not $flag & 8;
		}
		foreach my $k(keys %out) {
			die "Problem Rechnung: OUT $k" if ${$out{$k}} ne "";
		}
		foreach my $k(keys %outv) {
			die "Problem Rechnung: OUTV $k" if ${$outv{$k}} ne "";
		}
	}
	if(@subkunde) {
		my($knr,$preis,$papier);
		$outdata .= <<END;

Unterkunden (Übertrag) $zzspc------
  Kunde         $zspc        Summe
$zzspc-----------------------------
END
		while($knr = shift @subkunde) {
			$preis = shift @subkunde;
			$papier = shift @subkunde;
			$gkosten += $preis;
			$subpapier .= "\f" if $subpapier ne "";
			$subpapier .= $papier;
			$outdata .= sprintf <<END, name_kunde($knr), acct_betrag($preis);
  %-20s     $zospc%12s
END
		}
	}

	my $oq = "";

	if($rnr !~ /aaaaa/) {
		my($mwst) = ($flag&64) ? undef : rund("$gkosten" * $MWST);
		$outdata .= acct_text_fuss($knr,$gkosten,$mwst,!in_euro($rdate));
		$outdata .= $r->{'fusstext'} if defined $r->{'fusstext'}
			and not $oberkunde;
		$outdata .= "\n";
	}

	if($rnr =~ /aaaaa/ or $rnr =~ /bbbbb/) {
		#
	} else {
		if($gkosten == 0) {
			$outdata = ""; # alles wegwerfen, da kein Betrag
		} else {
			$outdata .= "\n".acct_text_zahlung($knr,$rdate,$gkosten);
		}
	}

	if(test_flag("tarifklasse","drucke_pf",$klassen)) {
		my $pfs = "";
		my $pfi = bignum(1)<<find_descr("pwdomain","mail");
		DoSelect {
			my($pers) = @_;
			$pfi .= "$pers ";
		} "select LOWER(user) as u from person where kunde = $knr and (pwuse & $pfi) != 0 order by u";
		$pfi =~ s/\s+$//;
		if($pfi ne "") {
			my $xwrap = new Text::Wrapper(columns => $linelen);
			$outdata.= <<END . $xwrap->wrap($pfi);

*** aktuelle Liste der Postfächer ***
END
		}
	}

	if($rnr =~ /aaaaa/) {
		$oq = $outdata;
	} elsif($outdata ne "") {
		# $oq: Ausgabedaten (vorherige Seiten)
		my $fq = 0; # Fortsetzung-mit-^f
		my $cur = 1; # aktuelle Zeilennummer
		my $oqp = ""; # Ausgabe dieser Seite

		foreach my $od2(split(/\f/,$outdata.($outtxt||""))) {
			my $page = 1; # aktuelle Seitennummer
			my @ol = (); # aktueller Paragraf

			$oqp .= "\f" if $fq;
			my @oq = split(/\n/,$od2); 
			$cur = 1;

			my $do_par = sub { # Paragraf anhängen, evtl. umbrechen
				my($umb) = @_;

				# Die Idee hinter dem folgenden Code ist:
				# Wenn wir Mehrfach-Leerzeilen haben, der nächste Absatz
				# aber draufpassen würde wenn wir ein paar Leerzeilen
				# wegwerfen, dann tun wir das, beginnend von unten.
				# Die ersten 15 Zeilen lassen wir dabei in Ruhe, denn
				# da ist die Adresse drin. (Auf den Folgeseiten landen
				# die Füllzeilen mit in $op, siehe unten, aber das macht
				# nichts.)
				if(not $umb and $cur + @ol >= $maxll) {
					my $otmp = $oqp;
					my $multi = 0;
					$multi++ while $otmp =~ s/\A((?:.*\n){15,})\n\s*\n/$1\n/ and ($cur + @ol >= $maxll + $multi);
					if($cur + @ol < $maxll + $multi) {
						$oqp = $otmp;
						$cur -= $multi;
					}
				}

				# Wenn immer noch kein Platz (oder Umbruch erzwungen) ist,
				# dann breche um.
				if($umb or $cur + @ol >= $maxll) {
					my $pg;
					($pg,$cur) = acct_text_page(++$page);
					$oq .= $oqp.$pg;
					$oqp = "";
					# Leerzeilen am Anfag des neuen Output => raus
					while(@ol and $ol[0] =~ /^\s*$/) {
						shift @ol;
					}
				}
				$oqp .= join("\n",@ol)."\n" if @ol;
				$cur += @ol;
				@ol = ();
			};

			print STDERR "\r  Splitting output, ".(0+@oq)." lines...\r" unless $flag & 6;
			nemi: while(@oq) {
				# Sammle einen Absatz in @ol
				my $line = shift @oq;

				if($line =~ s/^\002// or $line =~ /^\s*$/) {
					&$do_par();
				} elsif($line =~ s/^\001//) {
					&$do_par(1);
				}
				push(@ol,$line);
			}
			&$do_par();
		} continue {
			$oq .= $oqp;
			$fq = 1;
		}
		if ($kflags & (bignum(1) << find_descr kunde => 'steuernr')) {
			my $steuernr = DoFn("select steuernr from kunde where id=$knr");
			$steuernr = DoFn("select steuernr from kunde where id=".oberkunde($knr))
				unless $steuernr;
			$oq .= "\n" x ($maxll-$cur) . "\nIhre Steuernummer: $steuernr\n";
		}
	}

	print STDERR "\r" . (" " x 75) . "\r" unless $flag & 6;
	$oq =~ s/[\001\002\005]//g;

	$oq .= "\f" if defined $oq and $oq ne "" and defined $subpapier and $subpapier ne "";
	$oq .= $subpapier if defined $subpapier;
	$|=$oldf;
	$r->{'betrag'} = $gkosten;
	$r->{'text'} = $oq;

	print STDERR "# ",$r->dump(),"\n" unless $flag&2;
	return;
}

1;
