#!/usr/bin/perl -w

use utf8;
use strict;
use warnings;
use utf8;

BEGIN {
    unshift @INC, ( $ENV{'POPHOME'} || '@POPHOME@' ) . '/lib'
      unless $ENV{'KUNDE_NO_PERLPATH'};
}

use Dbase::Getopt qw(
  :DEFAULT
  :kunden
  getopt_abt_like
  getopt_date
  getopt_person
);
use Dbase::Globals qw(if_defined kpersinfo name_kunde time4ticket);
use Dbase::Help qw(:readonly DoSelect DoTrans in_list isotime unixtime);
use List::Util qw(min);
use noris::CSV;
use noris::Ticket::API qw(get_pooled_connection in_out_list link_list);
use Scalar::Util qw(reftype);
use Umlaut qw(textmodus);

use constant OHNE_MIT => map "Arbeitszeit $_ Faktoren (s)", qw(ohne mit);

sub empty_unless_defined {
    my $value = shift;
    defined $value && $value;
}

sub flatten_list {
    my $value = shift;
    defined $value && join ' ', sort { lc $a cmp lc $b } @$value;
}

sub seconds_since_incident_start {
    my ( $value, $ticketdaten ) = @_;
    $value && $ticketdaten->{incident_start}
      and unixtime($value) - unixtime( $ticketdaten->{incident_start} );
}

use constant TICKET_ATTRIBUTES => (
    {
        attribute => 'est_effort',
        display   => 'geplante Arbeitszeit',
        filter    => sub { my $value = shift; defined $value && $value * 3600 },
        group     => 'zeit',
    },
    { attribute => 'owner',    display => 'Bearbeiter', group => 'standard', },
    { attribute => 'priority', display => 'Priorität', group => 'standard', },
    { attribute => 'kunde',    display => 'Kunde',      group => 'standard', },
    { attribute => 'status',   display => 'Status',     group => 'standard', },
    { attribute => 'queue',    display => 'Queue',      group => 'standard' },
    {
        attribute => 'type',
        display   => 'Typ',
        filter    => \&empty_unless_defined,
        group     => 'standard'
    },
    { attribute => 'title', display => 'Titel', group => 'standard', },
    { attribute => 'info',  display => 'Info',  group => 'standard', },
    {
        attribute => 'created',
        display   => 'Entstehungszeitpunkt',
        group     => 'standard',
    },
    {
        attribute => 'confitems',
        display   => 'Conf.Items',
        filter    => \&flatten_list,
        group     => 'links',
    },
    {
        attribute => 'leitungen',
        display   => 'Leitungen',
        filter    => \&flatten_list,
        group     => 'links',
    },
    {
        attribute => 'incident_responsible',
        display   => 'verantwortlich',
        group     => 'incidents',
    },
    {
        attribute => 'incident_response',
        display   => 'Dauer bis Beginn der Störungsanalyse (s)',
        filter    => \&seconds_since_incident_start,
        group     => 'incidents',
    },
    {
        attribute => 'incident_resolved',
        display   => 'Wiederherstellungszeit (s)',
        filter    => \&seconds_since_incident_start,
        group     => 'incidents',
    },
    {
        attribute => 'incident_type',
        display   => 'Störungsart',
        filter    => \&empty_unless_defined,
        group     => 'incidents'
    },

    # Der Spaltenname "Störungsflags" ist historisch bedingt:
    # Im RT waren mal beliebig viele Flags zu Störungen vorgesehen.
    # Faktisch haben wir aber immer nur "sla-relevant" genutzt.
    # => Sollte man gelegentlich vielleicht mal umbenennen,
    # aber eher erst, wenn das RT abgeschafft ist.
    {
        attribute => 'sla_relevant',
        display   => 'SLA-Relevanz',
        filter    => sub { shift() ? 'ja' : '-' },
        group     => 'incidents',
    },

    # Wird selbst nicht ausgegeben, sondern nur zum Errechnen der "Dauer bis
    # Beginn der Störungsanalyse" und der "Wiederherstellungszeit" benötigt:
    { attribute => 'incident_start', group => 'incidents' },
);

use constant DISPLAY =>
  { map +( $_->{attribute} => $_->{display} ), TICKET_ATTRIBUTES };

# Bastel eine Referenz auf einen Hash, der für jedes Attribut eine Referenz
# auf eine Liste der zugehörigen Filter enthält.
use constant FILTER4ATTR => {
    map +(
        $_->{attribute} => [
              defined $_->{filter}
            ? reftype( $_->{filter} ) eq 'ARRAY'
                  ? @{ $_->{filter} }
                  : $_->{filter}
            : ()
        ]
    ),
    TICKET_ATTRIBUTES
};

use constant FOLLOW_BY => qw/sub split_target/;

die <<_ unless @ARGV;
Nein, ich werde jetzt _nicht_ die Arbeitszeit _aller_ Tickets ermitteln.
_

sub getopt_not_supported {
    die <<_;
Die Option -$_[0] wird nicht mehr unterstützt, nachdem sie länger nicht
verwendet wurde und ihre Umstellung auf die Ticket-API unverhältnismäßig
aufwändig gewesen wäre, vgl. Ticket 465235.
_
}

my (
    @Bearbeiter,  $CustomerVisibility, %Gruppe, @InfoLike,
    $Locked,      @OhneTypen,          %Spalte, $TicketsBis,
    $TicketsSeit, @TitleLike,          @Typen,  $Verbundene,
    $ZeitSeit,    $ZeitVor,
);
my $CheckKunde = 1;

sub set_lock($) {
    my ($lock_wanted) = @_;
    die "Was nun: nur freie oder nur gesperrte Tickets?\n"
      if defined $Locked && $Locked ne $lock_wanted;
    $Locked = $lock_wanted;
}

GetOptions(
    'abteilung-like=s' =>
      sub { push @Bearbeiter, map kpersinfo($_), &getopt_abt_like },
    'addiere-verbundene' => sub { $Verbundene = '' },
    'alle-gruppen'       => sub { $Gruppe{''} = 1 },
    'antwort=s%'         => \&getopt_not_supported,
    'area|ohne-area:s'   => sub {
        my ($option) = @_;
        ( my $neuer_name = $option ) =~ s/\barea\b/typ/;
        die <<_;
Die Option -$option heißt jetzt -$neuer_name.
Und evtl. musst Du Deine Logik anpassen.
_
    },
    'aufrunden=i' => \my $Aufrunden,
    'bearbeiter=s' =>
      sub { push @Bearbeiter, map kpersinfo($_), &getopt_person },
    'conf-item=s'    => \my @ConfItems,
    'csv-option=s'   => \my %CSV_Options,
    'debug+'         => \my $Debug,
    'gruppe=s'       => sub { $Gruppe{ $_[1] } = 1 },
    'info-like=s'    => \@InfoLike,
    'leitung=s'      => \my @Leitungen,
    'max-priority=i' => \my $MaxPriority,
    'min-priority=i' => \my $MinPriority,
    'min-sollzeit=f' => \&getopt_not_supported,
    'nur-freie-tickets'     => sub { set_lock('unlock') },
    'nur-gesperrte-tickets' => sub { set_lock('lock') },
    'nur-sichtbare'         => sub { $CustomerVisibility = 'visible' },
    'ohne-bearbeiter=s' => sub {
        die <<_;
Die Option -$_[0] wird nicht mehr unterstützt, vgl. Ticket #10074640.
_
    },
    'ohne-conf-item=s' => \my @OhneConfItems,
    'ohne-leitung=s'   => \my @OhneLeitungen,
    'ohne-queue=s'     => \my @OhneQueues,
    'ohne-sollzeit'    => \&getopt_not_supported,
    'ohne-spalte=s'    => sub { $Spalte{ $_[1] } = '' },
    'ohne-status=s'    => \my @OhneStatus,
    'ohne-summenzeile' => sub {
        my ($option) = @_;
        die "-$option ist jetzt Default. Lass sie weg!\n";
    },
    'ohne-ticket=i'  => \my @OhneTicketIDs,
    'ohne-typ=s'     => \@OhneTypen,
    'queue=s'        => \my @Queues,
    'spalte=s'       => sub { $Spalte{ $_[1] } = 1 },
    'status=s'       => \my @Status,
    'subject-like:s' => sub {
        my ($option) = @_;
        die "Die Option -$option heißt jetzt -titel-like.\n";
    },
    'titel-like|title-like=s' => \@TitleLike,
    'summenzeile'             => \my $Summenzeile,
    'ticket=i'                => \my @TicketIDs,
    'tickets-seit=s' => sub { $TicketsSeit = isotime( &getopt_date, 1 | 2 ) },
    'tickets-vor=s' => sub { $TicketsBis = isotime( &getopt_date - 1, 1 | 2 ) },
    'typ=s'                               => \@Typen,
    'verantwortlich=s'                    => \my @Verantwortlich,
    'verbindungsart=s'                    => \my @Verbindungsarten,
    'zeige-verbundene'                    => sub { $Verbundene = 1 },
    'zeit-fuer-ausgewaehlte-kunden'       => sub { $CheckKunde = 1 },
    'zeit-fuer-alle-kunden|nocheck-kunde' => sub { $CheckKunde = '' },
    'zeit-seit=s'                         => sub { $ZeitSeit = &getopt_date },
    'zeit-vor=s'                          => sub { $ZeitVor = &getopt_date },
);
$Gruppe{standard} = 1 unless grep $_, values %Spalte;

sub default_or_set(\%$) {
    my ( $hash_ref, $key ) = @_;
    !defined $hash_ref->{$key} || $hash_ref->{$key};
}

# Welche Spalten brauchen wir von der Ticket-API?
my @attributes = map $_->{attribute}, grep
  # Erstmal alle, die explizit einzeln selektiert wurden ...
  $Spalte{ $_->{attribute} }     # ... über ihren Attribut_Namen ...
  || defined $_->{display}
  && $Spalte{ $_->{display} }    # ... oder über ihren Anzeigenamen.

  # Ansonsten nach Gruppen:
  || (
    $Gruppe{''}                  # -alle-gruppen
    || $Gruppe{ $_->{group} }    # speziell diese Gruppe ausgewählt ...
  )

  # ... und diese Spalte nicht explizit ausgeschlossen ...
  && default_or_set( %Spalte,
    $_->{attribute} )            # ... über ihren Attribut-Namen ...
  && ( !defined $_->{display}
    || default_or_set( %Spalte, $_->{display} )
  )    # ... oder ihren Display-Namen, sofern vorhanden
  , TICKET_ATTRIBUTES;

my @OhneMit =
  grep +( $Spalte{$_} || $Gruppe{zeit} and default_or_set( %Spalte, $_ ) ),
  OHNE_MIT;

die <<_ if $Summenzeile && !@OhneMit;
Eine -summenzeile ergibt nur Sinn, wenn Du auch Zeitwerte anzeigen lässt.
_

$_ eq '' and $_ = undef for @Typen, @OhneTypen;
if ($Aufrunden) {
    require POSIX and POSIX->import('ceil');
    $Aufrunden *= 60;
}

push @OhneStatus, 'merged' unless grep $_ eq 'merged', @Status, @OhneStatus;

@Verbindungsarten = FOLLOW_BY if defined $Verbundene && !@Verbindungsarten;

sub effective_tickets {
    return unless @_;
    my %effective_id;
    @effective_id{@_} = ();
    get_pooled_connection()->select_tickets(
        attributes => [qw/ticket_number merge_root/],
        query      => {
            status        => 'merged',
            ticket_number => [ in => @_ ],
        },
      )->foreach_row(
        sub {
            my ( $ticket_number, $merge_root ) = @_;
            delete $effective_id{$ticket_number};
            $effective_id{$merge_root} = undef;
        }
      );
    keys %effective_id;
}

sub zeitwerte($) {
    my ($ticket) = @_;
    map sprintf( '%.f', $_ ), @{$ticket}{@OhneMit};
}

textmodus( \*STDOUT );
my $csv = noris::CSV->new( \%CSV_Options );
print $csv->as_decoded_string( defined $Verbundene ? 'Ticketgruppe' : (),
    'Ticket', @OhneMit, grep defined, map DISPLAY->{$_}, @attributes );

sub arbeitszeit4ticket($) {
    my ($ticket_number) = @_;
    time4ticket( $ticket_number, $ZeitSeit, $ZeitVor,
        $CheckKunde ? ( \@Kunden, \@OhneKunden ) : () );
}

DoTrans {

    # Nicht unnötig Tickets ohne einschlägige Zeitbuchung auswählen.
    # Wenn eh schon nur nach speziellen Ticket-IDs gefragt wird,
    # sparen wir uns diese Optimierung aber.
    # Und wenn -*-verbundene gefragt sind, können wir sie nicht machen, weil es
    # sein könnte, dass zwar auf ein einschlägiges Ticket selbst im fraglichen
    # Zeitraum keine Arbeitszeit gebucht wurde, aber auf ein damit verbundenes.
    if ( !@TicketIDs && !defined $Verbundene
        and defined $ZeitSeit || defined $ZeitVor )
    {
        my @where = 'stunden.ticket IS NOT NULL';
        push @where, "stunden.beginn >= $ZeitSeit" if defined $ZeitSeit;
        push @where, "stunden.beginn + stunden.dauer < $ZeitVor"
          if defined $ZeitVor;
        push @where, in_list( 'stunden.kunde', '', @Kunden ),
          in_list( 'stunden.kunde', NOT => @OhneKunden )
          if $CheckKunde;

        DoSelect { push @TicketIDs, shift }
        'SELECT DISTINCT ticket FROM stunden WHERE ' . join ' AND ', @where;
    }

    my %query = (
        link_list( confitems => [ \@ConfItems ], [ \@OhneConfItems ] ),
        created => [ range => $TicketsSeit, $TicketsBis ],
        if_defined( customer_visibility => $CustomerVisibility ),
        in_out_list( incident_responsible => \@Verantwortlich, [] ),
        in_out_list(
            kunde => [ map name_kunde($_), @Kunden ],
            [ map name_kunde($_), @OhneKunden ]
        ),
        link_list( leitungen => \@Leitungen, \@OhneLeitungen ),
        if_defined( locked => $Locked ),
        in_out_list( owner => \@Bearbeiter, [] ),
        priority => [ range => $MinPriority, $MaxPriority ],
        in_out_list( queue  => \@Queues, \@OhneQueues ),
        in_out_list( status => \@Status, \@OhneStatus ),
        in_out_list(
            ticket_number => [ effective_tickets(@TicketIDs) ],
            [ effective_tickets(@OhneTicketIDs) ]
        ),
        @Typen && !@OhneTypen
        ? ( type => [ like => @Typen ] )
        : in_out_list( type => \@Typen, \@OhneTypen ),
    );
    $query{info}  = [ like => @InfoLike ]  if @InfoLike;
    $query{title} = [ like => @TitleLike ] if @TitleLike;

    my $ticket_api = get_pooled_connection();
    my $resultset =
      $Verbundene
      ? $ticket_api->select_tickets( query => \%query )->follow_tickets(
        attributes => [ 'ticket_number', @attributes, 'origin' ],
        follow_by  => \@Verbindungsarten,
      )
      : $ticket_api->select_tickets(
        attributes => [ 'ticket_number', @attributes ],
        query      => \%query,
      );

    # Einlesen der primären Ticket-Datensätze, noch unsortiert:
    my %ticket;
    $resultset->foreach_row(
        sub {
            my %ticketdaten;
            my ($ticket_number) =
              ( @ticketdaten{ 'ticket_number', @attributes, 'origin' } ) = @_;
            die "Doppeltes Ticket #$ticket_number!?\n"
              if exists $ticket{$ticket_number};

            # Nachbearbeitung der Daten:
            for (@attributes) {
                my $value = $ticketdaten{$_};

                # vorsorglich generell: Zeilenumbrüche entfernen:
                if ( defined $value ) {
                    s#$/# #g for ref $value ? @$value : $value;
                }

                $value = $_->( $value, \%ticketdaten )
                  for @{ FILTER4ATTR->{$_} };
                $ticketdaten{$_} = $value;
            }

            @ticketdaten{ (OHNE_MIT) } = arbeitszeit4ticket($ticket_number)
              if @OhneMit;

            $ticket{$ticket_number} = \%ticketdaten;
        }
    );

    # -addiere-verbundene:
    $resultset->follow_tickets(
        attributes => [qw/ticket_number origin/],
        follow_by  => \@Verbindungsarten,
      )->foreach_row(
        sub {
            my ( $ticket_number, $origin ) = @_;

           # Dieses ResultSet enthält auch die ursprünglichen Tickets nochmal;
           # deren Arbeitszeit ist aber schon berücksichtigt, nur der origin
           # noch nicht gesetzt.
            if ( exists $ticket{$ticket_number} ) {
                $ticket{$ticket_number}{origin} = $origin;
            }
            else {
                my %time4ticket;
                @time4ticket{ (OHNE_MIT) } = arbeitszeit4ticket($ticket_number)
                  if @OhneMit;
                $_ = $_ / @$origin for values %time4ticket;

                print "Zeitverteilung: $ticket_number => "
                  . join( '/', sort @$origin ) . ' ('
                  . @$origin . ")\n"
                  if $Debug;

                for my $base_ticket_number (@$origin) {
                    die <<_ unless exists $ticket{$base_ticket_number};
Ticket #$base_ticket_number im Basis-Ergebnis nicht gefunden!
_
                    $ticket{$base_ticket_number}{$_} += $time4ticket{$_}
                      for @OhneMit;
                }
            }
        }
      ) if @OhneMit && defined $Verbundene && !$Verbundene;

    if ($Aufrunden) {
        for my $ticket ( values %ticket ) {
            $_ = $Aufrunden * ceil( $_ / $Aufrunden ) for @{$ticket}{@OhneMit};
        }
    }

    # Traditionell gibt dieses Tool für Tickets, die zu mehreren anderen
    # verlinkt sind, nur die jeweils kleinste Nummer aus:
    $_->{origin} = defined $Verbundene ? min( @{ $_->{origin} } ) : 0
      for values %ticket;

    my %time4tickets = map +( $_ => 0 ), @OhneMit;
    for my $ticket_number (
        sort { $ticket{$a}{origin} <=> $ticket{$b}{origin} || $a <=> $b }
        keys %ticket
      )
    {

        my $ticket = $ticket{$ticket_number};

        $time4tickets{$_} += $ticket->{$_} for @OhneMit;

        print $csv->as_decoded_string(
            defined $Verbundene ? $ticket->{origin} : (),
            $ticket_number, zeitwerte($ticket), @{$ticket}{@attributes} )
          if !@OhneMit
              or !defined $ZeitSeit && !defined $ZeitVor
              || grep $ticket->{$_} != 0,
            @OhneMit;
    }

    print $csv->as_decoded_string( ('') x ( defined $Verbundene ? 2 : 1 ),
        zeitwerte( \%time4tickets ) )
      if $Summenzeile;
};

__END__

=head1 NAME

arbeitszeit4tickets - werte auf Tickets gebuchte Arbeitszeit aus

=head1 BESCHREIBUNG

Gibt eine CSV-Liste aller Tickets aus, auf die (ggf. im gewünschten Zeitraum)
Arbeitszeit gebucht wurde.

=head1 OPTIONEN

=head2 Welche Spalten/Attribute sollen angezeigt werden?

Die Ermittlung der Werte für einige Spalten ist (zeit)aufwändig.
Per Default werden daher nur diejenigen Attribute ausgegeben, die meistens
benötigt werden bzw. einfach zu ermitteln sind.

=over 4

=item -gruppe standard

die "normalen" Meta-Daten zu Tickets, z. B. Titel, Queue etc.

Sofern die Option L<-spalte|/-spalte Spaltenname> I<nicht> genutzt wird,
wird diese Gruppe automatisch mit ausgewählt.

=item -gruppe links

bei Tickets nur verlinkte Objekte, z. B. Configuration Items und Leitungen

=item -gruppe incidents

Attribute, die nur bei Incident-Tickets relevant sind

=item -gruppe zeit

Inhalte aus der Arbeitszeiterfassung (gebuchte Arbeitszeit mit und ohne Faktor)

=item -alle-gruppen

um alle Gruppen/Spalten vorauszuwählen; Ausnahmen sind dann noch mit
L</-ohne-spalte Spaltenname> möglich.

=item -spalte Spaltenname

Zum gezielten Auswählen einzelner Spalten.
Als I<Spaltenname> kann sowohl der angegeben werden, der in der Ausgabe als
Spaltenüberschrift verwendet wird, als auch der interne Name des Attributs aus
der L<noris::Ticket::API>.

=item -ohne-spalte Spaltenname

Um einzelne Spalten, die beispielsweise durch eine L<Gruppe|/-gruppe standard>
ausgewählt wurden, auszulassen.

=back

=head2 Weitere Optionen

=over 4

=item -ticket ID

Selektion nach Ticket-ID(s); bitte die Option mehrfach verwenden, um mehrere
Tickets auszuwählen!
Funktioniert auch für Tickets, die mit anderen Tickets zusammengefasst wurden.

=item -ohne-ticket ID

Selektion nach Ticket-ID(s); bitte die Option mehrfach verwenden, um mehrere
Tickets auszuschließen!
Funktioniert auch für Tickets, die mit anderen Tickets zusammengefasst wurden.

=item -tickets-seit Datum

nur Tickets beachten, die seit dem angegebenen Datum entstanden sind.
Das Datum wird mit L<Time::ParseDate> analyisiert und kann (also) auch relativ
angegeben werden.

=item -tickets-vor Datum

nur Tickets beachten, die vor dem angegebenen Datum entstanden sind.
Das Datum wird mit L<Time::ParseDate> analyisiert und kann (also) auch relativ
angegeben werden.

=item -zeit-seit Datum

nur Zeiterfassungseinträge ab dem angegebenen Datum beachten.
Bei Verwendung dieser Option werden nur Tickets selektiert, auf die im
fraglichen Zeitraum mindestens ein Zeiterfassungseintrag gebucht wurde.
Das Datum wird mit L<Time::ParseDate> analyisiert und kann (also) auch relativ
angegeben werden.

=item -zeit-vor Datum

nur Zeiterfassungseinträge vor dem angegebenen Datum beachten.
Bei Verwendung dieser Option werden nur Tickets selektiert, auf die im
fraglichen Zeitraum mindestens ein Zeiterfassungseintrag gebucht wurde.
Das Datum wird mit L<Time::ParseDate> analyisiert und kann (also) auch relativ
angegeben werden.

=item -zeit-fuer-ausgewaehlte-kunden

nur Zeiterfassungseinträge beachten, die auf einen der mit L</-kunde> und/oder
L</-ohne-kunde> ausgewählten Kunden gebucht wurden (Default).

=item -zeit-fuer-alle-kunden

Zeiterfassungseinträge unabhängig vom Kunden, auf den sie gebucht wurden,
beachten

=item -bearbeiter Person

Selektion nach Ticket-Bearbeiter(n); bitte die Option mehrfach verwenden, um
Tickets unterschiedlicher Bearbeiter auszuwählen!

=item -abteilung-like SQL-Wildcard

Selektion nach Abteilung/Team des Ticket-Bearbeiters, also z. B.

 	-abteilung-like 'Technik%'
oder
	-abteilung-like 'Technik, Team Entwicklung'

Bei mehrfacher Verwendung der Option werden alle Tickets selektiert, bei denen
die Abteilung des Bearbeiters zu mindestens einer der angegebenen SQL-Wildcards
passt.

=item -nur-freie-tickets

=item -nur-gesperrte-tickets

um nur freie bzw. nur gesperrte Tickets auszuwählen

Die beiden Optionen schließen sich gegenseitig aus.

=item -ap-technik Person

=item -ap-vertrieb Person

=item -kunde Kunde

=item -kunde-und-unterkunden Kunde

=item -ohne-ap-technik Person

=item -ohne-ap-vertrieb Person

=item -ohne-kunde Kunde

=item -ohne-kunde-und-unterkunden Kunde

Per Default werden Tickets aller Kunden ausgewertet.
Dies lässt sich vermittels dieser Optionen modifizieren.
Details dazu siehe L<Dbase::Getopt/:kunden>.

=item -queue Queue

Selektion nach Queue(s); bitte die Option mehrfach verwenden,
um mehrere Queues zu selektieren!

Um eine Queue inklusive aller Sub-Queues auszuwählen, muss man an den
Queue-Namen einen Doppeldoppelpunkt anhängen, z. B.:

	-queue mse::

=item -ohne-queue Queue

Ausschluss von Queue(s); bitte die Option mehrfach verwenden,
um mehrere Queues auszuschließen!

Um eine Queue inklusive aller Sub-Queues auszuschließen, muss man an den
Queue-Namen einen Doppeldoppelpunkt anhängen, z. B.:

	-ohne-queue ito::

=item -typ Tickettyp

Selektion nach Ticket-Typ; bitte die Option
mehrfach verwenden, um mehrere Typen auszuwählen!

Sofern I<nicht> gleichzeitig die Option L</-ohne-typ|-ohne-typ Tickettyp>
verwendet wird, sind auch Wildcards möglich, z. B.:

    -typ CCM -typ 'CCM::*'

=item -ohne-typ Tickettyp

Ausschluss von Ticket-Typen; bitte die Option mehrfach
verwenden, um mehrere Typen auszuschließen!

=item -info-like Wildcard

Selektion nach Ticket-Infotext.
Bei mehrfacher Verwendung der Option werden alle Tickets selektiert, deren
Infotext zu mindestens einer der angegebenen Wildcards passt.

=item -titel-like Wildcard

Selektion nach Ticket-Titel.
Bei mehrfacher Verwendung der Option werden alle Tickets selektiert, deren
Titel zu mindestens einer der angegebenen Wildcards passt.

=item -status open

=item -status stalled

=item -status resolved

=item -status dead

nur Tickets beachten, die aktuell einen entsprechenden Status haben.
Kann mehrfach verwendet werden, um Tickets mit unterschiedlichen Status zu
beachten.

=item -ohne-status open

=item -ohne-status stalled

=item -ohne-status resolved

=item -ohne-status dead

Tickets, die aktuell einen entsprechenden Status haben, ausschließen.
Kann mehrfach verwendet werden, um Tickets mit unterschiedlichen Status
auszuschließen.

=item -min-priority <Priorität>

Selektion auf Tickets ab einer bestimmten Mindest-Priorität einschränken.

=item -max-priority <Priorität>

Selektion auf Tickets bis zu einer bestimmten Maximal-Priorität einschränken.

=item -verantwortlich noris

=item -verantwortlich Kunde

=item -verantwortlich fremd

=item -verantwortlich unklar

Um nur (Incident-)Tickets mit bestimmter Verantwortlichkeit auszuwählen.
Kann mehrfach verwendet werden, um Tickets mit unterschiedlicher
Verantwortlichkeit auszuwählen.

=item -nur-sichtbare

Nur Tickets auswählen, die für den Kunden (auf service.noris.net) freigegeben
sind.

=item -conf-item Conf.Item

Selektion nach den Tickets zugeordneten Configuration Items, also nur Tickets
auswählen, denen mindestens eines der angegebenen Configuration Items zugeordnet
ist

=item -ohne-conf-item Conf.Item

Ausschluss von Tickets, denen mindestens eines der angegebenen Configuration
Items zugeordnet ist

=item -leitung Leitungsname

Selektion nach den Tickets zugeordneten Leitungen, also nur Tickets auswählen,
denen mindestens eine der angegebenen Leitnugen zugeordnet ist

=item -ohne-leitung Leitungsname

Ausschluss von Tickets, denen mindestens eine der angegebenen Leitungen
zugeordnet ist

=item -zeige-verbundene

mit den ausgewählten Tickets verbundene Tickets ebenfalls anzeigen.
Es wird außerdem eine zusätzliche Spalte C<Ticketgruppe> angezeigt,
die für alle miteinander verbundenen Tickets dieselbe Kennnummer enthält.

Schließt L</-addiere-verbundene> aus.

=item -addiere-verbundene

auf mit ausgewählten Tickets verbundene Tickets gebuchte Arbeitszeit zu den
ausgewählten Tickets addieren.
Arbeitszeit von Tickets, die mit mehreren ausgewählten Tickets verbunden sind,
wird dabei gleichmäßig auf diese Tickets verteilt; die Summe wird anschließend
auf ganze Sekunden gerundet.

Schließt L</-zeige-verbundene> aus.

=item -verbindungsart Richtung

Diese Option ist (nur) in Zusammenhang mit L</-addiere-verbundene> oder
L</-zeige-verbundene> sinnvoll, um nur bestimmte Ticket-Verlinkungsarten zu
verfolgen, vgl. L<noris::Ticket::API/follow_by>.

Beispiel (um auch "rückwärts" zu suchen):

    -verbindungsart sub -verbindungsart split_target \
    -verbindungsart main -verbindungsart split_source \

Default ist: C<sub> und C<split_target>

=item -aufrunden Minuten

Zeiteintragungen pro Ticket(gruppe) in den angegebenen Minutenschritten
aufrunden

=item -summenzeile

um eine abschließende Summenzeile der Arbeitszeiten zu bekommen (nur, falls
mindestens eine der Spalten aus der L</-gruppe zeit> ausgegeben werden)

=item -csv-option Name=Wert

Optionen für L<Text::CSV_XS>, s. Dokumentation dieses Moduls

=item -debug

Zu Debugging-Zwecken zusätzliche Informationen 
(über die Verteilung von Arbeitszeit bei L</-addiere-verbundene>) ausgeben.

=item -help

=item -?

um (nur) diese Dokumentation anzeigen zu lassen

=back
