1 package SL::Presenter::Record;
 
   5 use parent qw(Exporter);
 
   7 use Exporter qw(import);
 
   8 our @EXPORT = qw(grouped_record_list empty_record_list record_list record);
 
  13 use List::Util qw(first);
 
  17   return []     if !defined $array;
 
  18   return $array if ref $array;
 
  23   my ($self, $record, %params) = @_;
 
  25   my %grouped = _group_records( [ $record ] ); # pass $record as arrayref
 
  26   my $type    = (keys %grouped)[0];
 
  28   return $self->sales_invoice(   $record, %params) if $type eq 'sales_invoices';
 
  29   return $self->purchase_invoice($record, %params) if $type eq 'purchase_invoices';
 
  30   return $self->ar_transaction(  $record, %params) if $type eq 'ar_transactions';
 
  31   return $self->ap_transaction(  $record, %params) if $type eq 'ap_transactions';
 
  32   return $self->gl_transaction(  $record, %params) if $type eq 'gl_transactions';
 
  37 sub grouped_record_list {
 
  38   my ($self, $list, %params) = @_;
 
  40   %params    = map { exists $params{$_} ? ($_ => $params{$_}) : () } qw(edit_record_links with_columns object_id object_model);
 
  42   my %groups = _sort_grouped_lists(_group_records($list));
 
  45   $output .= _requirement_spec_list(       $self, $groups{requirement_specs},        %params) if $groups{requirement_specs};
 
  46   $output .= _sales_quotation_list(        $self, $groups{sales_quotations},         %params) if $groups{sales_quotations};
 
  47   $output .= _sales_order_list(            $self, $groups{sales_orders},             %params) if $groups{sales_orders};
 
  48   $output .= _sales_delivery_order_list(   $self, $groups{sales_delivery_orders},    %params) if $groups{sales_delivery_orders};
 
  49   $output .= _sales_invoice_list(          $self, $groups{sales_invoices},           %params) if $groups{sales_invoices};
 
  50   $output .= _ar_transaction_list(         $self, $groups{ar_transactions},          %params) if $groups{ar_transactions};
 
  52   $output .= _request_quotation_list(      $self, $groups{purchase_quotations},      %params) if $groups{purchase_quotations};
 
  53   $output .= _purchase_order_list(         $self, $groups{purchase_orders},          %params) if $groups{purchase_orders};
 
  54   $output .= _purchase_delivery_order_list($self, $groups{purchase_delivery_orders}, %params) if $groups{purchase_delivery_orders};
 
  55   $output .= _purchase_invoice_list(       $self, $groups{purchase_invoices},        %params) if $groups{purchase_invoices};
 
  56   $output .= _ap_transaction_list(         $self, $groups{ap_transactions},          %params) if $groups{ap_transactions};
 
  58   $output .= _bank_transactions(           $self, $groups{bank_transactions},        %params) if $groups{bank_transactions};
 
  60   $output .= _sepa_collection_list(        $self, $groups{sepa_collections},         %params) if $groups{sepa_collections};
 
  61   $output .= _sepa_transfer_list(          $self, $groups{sepa_transfers},           %params) if $groups{sepa_transfers};
 
  63   $output .= _letter_list(                 $self, $groups{letters},                  %params) if $groups{letters};
 
  65   $output  = $self->render('presenter/record/grouped_record_list', %params, output => $output);
 
  70 sub empty_record_list {
 
  71   my ($self, %params) = @_;
 
  72   return $self->grouped_record_list([], %params);
 
  76   my ($self, $list, %params) = @_;
 
  80   if (ref($params{columns}) eq 'ARRAY') {
 
  82       if (ref($_) eq 'ARRAY') {
 
  83         { title => $_->[0], data => $_->[1], link => $_->[2] }
 
  87     } @{ delete $params{columns} };
 
  90     croak "Wrong type for 'columns' argument: not an array reference";
 
  93   my %with_columns = map { ($_ => 1) } @{ _arrayify($params{with_columns}) };
 
  94   if ($with_columns{record_link_direction}) {
 
  96       title => $::locale->text('Link direction'),
 
  98           $_[0]->{_record_link_depth} > 1
 
  99         ? $::locale->text('Row was linked to another record')
 
 100         : $_[0]->{_record_link_direction} eq 'from'
 
 101         ? $::locale->text('Row was source for current record')
 
 102         : $::locale->text('Row was created from current record') },
 
 106   my %column_meta   = map { $_->name => $_ } @{ $list->[0]->meta->columns       };
 
 107   my %relationships = map { $_->name => $_ } @{ $list->[0]->meta->relationships };
 
 110     my ($obj, $method, @args) = @_;
 
 111     $obj->$method(@args);
 
 115   foreach my $obj (@{ $list }) {
 
 118     foreach my $spec (@columns) {
 
 121       my $method       =  $spec->{column} || $spec->{data};
 
 122       my $meta         =  $column_meta{ $spec->{data} };
 
 123       my $type         =  ref $meta;
 
 124       my $relationship =  $relationships{ $spec->{data} };
 
 125       my $rel_type     =  !$relationship ? '' : $relationship->class;
 
 126       $rel_type        =~ s/^SL::DB:://;
 
 127       $rel_type        =  SL::Util::snakify($rel_type);
 
 129       if (ref($spec->{data}) eq 'CODE') {
 
 130         $cell{value} = $spec->{data}->($obj);
 
 133         $cell{value} = $rel_type && $self->can($rel_type)                                       ? $self->$rel_type($obj->$method, display => 'table-cell')
 
 134                      : $type eq 'Rose::DB::Object::Metadata::Column::Date'                      ? $call->($obj, $method . '_as_date')
 
 135                      : $type =~ m/^Rose::DB::Object::Metadata::Column::(?:Float|Numeric|Real)$/ ? $::form->format_amount(\%::myconfig, $call->($obj, $method), 2)
 
 136                      : $type eq 'Rose::DB::Object::Metadata::Column::Boolean'                   ? $call->($obj, $method . '_as_bool_yn')
 
 137                      : $type =~ m/^Rose::DB::Object::Metadata::Column::(?:Integer|Serial)$/     ? $spec->{data} * 1
 
 138                      :                                                                            $call->($obj, $method);
 
 141       $cell{alignment} = 'right' if $type =~ m/int|serial|float|real|numeric/;
 
 146     push @data, { columns => \@row, record_link => $obj->{_record_link} };
 
 150     map +{ value     => $columns[$_]->{title},
 
 151            alignment => $data[0]->{columns}->[$_]->{alignment},
 
 152          }, (0..scalar(@columns) - 1);
 
 154   return $self->render(
 
 155     'presenter/record/record_list',
 
 157     TABLE_HEADER => \@header,
 
 158     TABLE_ROWS   => \@data,
 
 170     requirement_specs        => sub { (ref($_[0]) eq 'SL::DB::RequirementSpec')                                         },
 
 171     sales_quotations         => sub { (ref($_[0]) eq 'SL::DB::Order')           &&  $_[0]->is_type('sales_quotation')   },
 
 172     sales_orders             => sub { (ref($_[0]) eq 'SL::DB::Order')           &&  $_[0]->is_type('sales_order')       },
 
 173     sales_delivery_orders    => sub { (ref($_[0]) eq 'SL::DB::DeliveryOrder')   &&  $_[0]->is_sales                     },
 
 174     sales_invoices           => sub { (ref($_[0]) eq 'SL::DB::Invoice')         &&  $_[0]->invoice                      },
 
 175     ar_transactions          => sub { (ref($_[0]) eq 'SL::DB::Invoice')         && !$_[0]->invoice                      },
 
 176     purchase_quotations      => sub { (ref($_[0]) eq 'SL::DB::Order')           &&  $_[0]->is_type('request_quotation') },
 
 177     purchase_orders          => sub { (ref($_[0]) eq 'SL::DB::Order')           &&  $_[0]->is_type('purchase_order')    },
 
 178     purchase_delivery_orders => sub { (ref($_[0]) eq 'SL::DB::DeliveryOrder')   && !$_[0]->is_sales                     },
 
 179     purchase_invoices        => sub { (ref($_[0]) eq 'SL::DB::PurchaseInvoice') &&  $_[0]->invoice                      },
 
 180     ap_transactions          => sub { (ref($_[0]) eq 'SL::DB::PurchaseInvoice') && !$_[0]->invoice                      },
 
 181     sepa_collections         => sub { (ref($_[0]) eq 'SL::DB::SepaExportItem')  &&  $_[0]->ar_id                        },
 
 182     sepa_transfers           => sub { (ref($_[0]) eq 'SL::DB::SepaExportItem')  &&  $_[0]->ap_id                        },
 
 183     gl_transactions          => sub { (ref($_[0]) eq 'SL::DB::GLTransaction')                                           },
 
 184     bank_transactions        => sub { (ref($_[0]) eq 'SL::DB::BankTransaction') &&  $_[0]->id                           },
 
 185     letters                  => sub { (ref($_[0]) eq 'SL::DB::Letter')          &&  $_[0]->id                           },
 
 190   foreach my $record (@{ $list || [] }) {
 
 191     my $type         = (first { $matchers{$_}->($record) } keys %matchers) || 'other';
 
 192     $groups{$type} ||= [];
 
 193     push @{ $groups{$type} }, $record;
 
 199 sub _sort_grouped_lists {
 
 202   foreach my $group (keys %groups) {
 
 203     next unless @{ $groups{$group} };
 
 204     if ($groups{$group}->[0]->can('compare_to')) {
 
 205       $groups{$group} = [ sort { $a->compare_to($b)    } @{ $groups{$group} } ];
 
 207       $groups{$group} = [ sort { $a->date <=> $b->date } @{ $groups{$group} } ];
 
 214 sub _requirement_spec_list {
 
 215   my ($self, $list, %params) = @_;
 
 217   return $self->record_list(
 
 219     title   => $::locale->text('Requirement specs'),
 
 220     type    => 'requirement_spec',
 
 222       [ $::locale->text('Requirement spec number'), sub { $self->requirement_spec($_[0], display => 'table-cell') } ],
 
 223       [ $::locale->text('Customer'),                'customer'                                                      ],
 
 224       [ $::locale->text('Title'),                   'title'                                                         ],
 
 225       [ $::locale->text('Project'),                 'project',                                                      ],
 
 226       [ $::locale->text('Status'),                  sub { $_[0]->status->description }                              ],
 
 232 sub _sales_quotation_list {
 
 233   my ($self, $list, %params) = @_;
 
 235   return $self->record_list(
 
 237     title   => $::locale->text('Sales Quotations'),
 
 238     type    => 'sales_quotation',
 
 240       [ $::locale->text('Quotation Date'),          'transdate'                                                                ],
 
 241       [ $::locale->text('Quotation Number'),        sub { $self->sales_quotation($_[0], display => 'table-cell') }   ],
 
 242       [ $::locale->text('Customer'),                'customer'                                                                 ],
 
 243       [ $::locale->text('Net amount'),              'netamount'                                                                ],
 
 244       [ $::locale->text('Transaction description'), 'transaction_description'                                                  ],
 
 245       [ $::locale->text('Project'),                 'globalproject', ],
 
 246       [ $::locale->text('Closed'),                  'closed'                                                                   ],
 
 252 sub _request_quotation_list {
 
 253   my ($self, $list, %params) = @_;
 
 255   return $self->record_list(
 
 257     title   => $::locale->text('Request Quotations'),
 
 258     type    => 'request_quotation',
 
 260       [ $::locale->text('Quotation Date'),          'transdate'                                                                ],
 
 261       [ $::locale->text('Quotation Number'),        sub { $self->request_quotation($_[0], display => 'table-cell') }   ],
 
 262       [ $::locale->text('Vendor'),                  'vendor'                                                                   ],
 
 263       [ $::locale->text('Net amount'),              'netamount'                                                                ],
 
 264       [ $::locale->text('Transaction description'), 'transaction_description'                                                  ],
 
 265       [ $::locale->text('Project'),                 'globalproject', ],
 
 266       [ $::locale->text('Closed'),                  'closed'                                                                   ],
 
 272 sub _sales_order_list {
 
 273   my ($self, $list, %params) = @_;
 
 275   return $self->record_list(
 
 277     title   => $::locale->text('Sales Orders'),
 
 278     type    => 'sales_order',
 
 280       [ $::locale->text('Order Date'),              'transdate'                                                                ],
 
 281       [ $::locale->text('Order Number'),            sub { $self->sales_order($_[0], display => 'table-cell') }   ],
 
 282       [ $::locale->text('Quotation'),               'quonumber' ],
 
 283       [ $::locale->text('Customer'),                'customer'                                                                 ],
 
 284       [ $::locale->text('Net amount'),              'netamount'                                                                ],
 
 285       [ $::locale->text('Transaction description'), 'transaction_description'                                                  ],
 
 286       [ $::locale->text('Project'),                 'globalproject', ],
 
 287       [ $::locale->text('Closed'),                  'closed'                                                                   ],
 
 293 sub _purchase_order_list {
 
 294   my ($self, $list, %params) = @_;
 
 296   return $self->record_list(
 
 298     title   => $::locale->text('Purchase Orders'),
 
 299     type    => 'purchase_order',
 
 301       [ $::locale->text('Order Date'),              'transdate'                                                                ],
 
 302       [ $::locale->text('Order Number'),            sub { $self->purchase_order($_[0], display => 'table-cell') }   ],
 
 303       [ $::locale->text('Request for Quotation'),   'quonumber' ],
 
 304       [ $::locale->text('Vendor'),                  'vendor'                                                                 ],
 
 305       [ $::locale->text('Net amount'),              'netamount'                                                                ],
 
 306       [ $::locale->text('Transaction description'), 'transaction_description'                                                  ],
 
 307       [ $::locale->text('Project'),                 'globalproject', ],
 
 308       [ $::locale->text('Closed'),                  'closed'                                                                   ],
 
 314 sub _sales_delivery_order_list {
 
 315   my ($self, $list, %params) = @_;
 
 317   return $self->record_list(
 
 319     title   => $::locale->text('Sales Delivery Orders'),
 
 320     type    => 'sales_delivery_order',
 
 322       [ $::locale->text('Delivery Order Date'),     'transdate'                                                                ],
 
 323       [ $::locale->text('Delivery Order Number'),   sub { $self->sales_delivery_order($_[0], display => 'table-cell') } ],
 
 324       [ $::locale->text('Order Number'),            'ordnumber' ],
 
 325       [ $::locale->text('Customer'),                'customer'                                                                 ],
 
 326       [ $::locale->text('Transaction description'), 'transaction_description'                                                  ],
 
 327       [ $::locale->text('Project'),                 'globalproject', ],
 
 328       [ $::locale->text('Delivered'),               'delivered'                                                                ],
 
 329       [ $::locale->text('Closed'),                  'closed'                                                                   ],
 
 335 sub _purchase_delivery_order_list {
 
 336   my ($self, $list, %params) = @_;
 
 338   return $self->record_list(
 
 340     title   => $::locale->text('Purchase Delivery Orders'),
 
 341     type    => 'purchase_delivery_order',
 
 343       [ $::locale->text('Delivery Order Date'),     'transdate'                                                                ],
 
 344       [ $::locale->text('Delivery Order Number'),   sub { $self->purchase_delivery_order($_[0], display => 'table-cell') } ],
 
 345       [ $::locale->text('Order Number'),            'ordnumber' ],
 
 346       [ $::locale->text('Vendor'),                  'vendor'                                                                 ],
 
 347       [ $::locale->text('Transaction description'), 'transaction_description'                                                  ],
 
 348       [ $::locale->text('Project'),                 'globalproject', ],
 
 349       [ $::locale->text('Delivered'),               'delivered'                                                                ],
 
 350       [ $::locale->text('Closed'),                  'closed'                                                                   ],
 
 356 sub _sales_invoice_list {
 
 357   my ($self, $list, %params) = @_;
 
 359   return $self->record_list(
 
 361     title   => $::locale->text('Sales Invoices'),
 
 362     type    => 'sales_invoice',
 
 364       [ $::locale->text('Invoice Date'),            'transdate'               ],
 
 365       [ $::locale->text('Type'),                    sub { $_[0]->displayable_type } ],
 
 366       [ $::locale->text('Invoice Number'),          sub { $self->sales_invoice($_[0], display => 'table-cell') } ],
 
 367       [ $::locale->text('Quotation Number'),        'quonumber' ],
 
 368       [ $::locale->text('Order Number'),            'ordnumber' ],
 
 369       [ $::locale->text('Customer'),                'customer'                ],
 
 370       [ $::locale->text('Net amount'),              'netamount'               ],
 
 371       [ $::locale->text('Paid'),                    'paid'                    ],
 
 372       [ $::locale->text('Transaction description'), 'transaction_description' ],
 
 378 sub _purchase_invoice_list {
 
 379   my ($self, $list, %params) = @_;
 
 381   return $self->record_list(
 
 383     title   => $::locale->text('Purchase Invoices'),
 
 384     type    => 'purchase_invoice',
 
 386       [ $::locale->text('Invoice Date'),                 'transdate'               ],
 
 387       [ $::locale->text('Invoice Number'),               sub { $self->purchase_invoice($_[0], display => 'table-cell') } ],
 
 388       [ $::locale->text('Request for Quotation Number'), 'quonumber' ],
 
 389       [ $::locale->text('Order Number'),                 'ordnumber' ],
 
 390       [ $::locale->text('Vendor'),                       'vendor'                 ],
 
 391       [ $::locale->text('Net amount'),                   'netamount'               ],
 
 392       [ $::locale->text('Paid'),                         'paid'                    ],
 
 393       [ $::locale->text('Transaction description'),      'transaction_description' ],
 
 399 sub _ar_transaction_list {
 
 400   my ($self, $list, %params) = @_;
 
 402   return $self->record_list(
 
 404     title   => $::locale->text('AR Transactions'),
 
 405     type    => 'ar_transaction',
 
 407       [ $::locale->text('Invoice Date'),            'transdate'               ],
 
 408       [ $::locale->text('Type'),                    sub { $_[0]->displayable_type } ],
 
 409       [ $::locale->text('Invoice Number'),          sub { $self->ar_transaction($_[0], display => 'table-cell') } ],
 
 410       [ $::locale->text('Customer'),                'customer'                ],
 
 411       [ $::locale->text('Net amount'),              'netamount'               ],
 
 412       [ $::locale->text('Paid'),                    'paid'                    ],
 
 413       [ $::locale->text('Transaction description'), 'transaction_description' ],
 
 419 sub _ap_transaction_list {
 
 420   my ($self, $list, %params) = @_;
 
 422   return $self->record_list(
 
 424     title   => $::locale->text('AP Transactions'),
 
 425     type    => 'ap_transaction',
 
 427       [ $::locale->text('Invoice Date'),            'transdate'                      ],
 
 428       [ $::locale->text('Invoice Number'),          sub { $self->ap_transaction($_[0 ], display => 'table-cell') } ],
 
 429       [ $::locale->text('Vendor'),                  'vendor'                         ],
 
 430       [ $::locale->text('Net amount'),              'netamount'                      ],
 
 431       [ $::locale->text('Paid'),                    'paid'                           ],
 
 432       [ $::locale->text('Transaction description'), 'transaction_description'        ],
 
 438 sub _bank_transactions {
 
 439   my ($self, $list, %params) = @_;
 
 441   return $self->record_list(
 
 443     title   => $::locale->text('Bank transactions'),
 
 444     type    => 'bank_transactions',
 
 446       [ $::locale->text('Transdate'),            'transdate'                      ],
 
 447       [ $::locale->text('Local Bank Code'),      sub { $self->bank_code($_[0]->local_bank_account) }  ],
 
 448       [ $::locale->text('Local account number'), sub { $self->account_number($_[0]->local_bank_account) }  ],
 
 449       [ $::locale->text('Remote Bank Code'),     'remote_bank_code' ],
 
 450       [ $::locale->text('Remote account number'),'remote_account_number' ],
 
 451       [ $::locale->text('Valutadate'),           'valutadate' ],
 
 452       [ $::locale->text('Amount'),               'amount' ],
 
 453       [ $::locale->text('Currency'),             sub { $_[0]->currency->name } ],
 
 454       [ $::locale->text('Remote name'),          'remote_name' ],
 
 455       [ $::locale->text('Purpose'),              'purpose' ],
 
 461 sub _sepa_export_list {
 
 462   my ($self, $list, %params) = @_;
 
 464   my ($source, $destination) = $params{type} eq 'sepa_transfer' ? qw(our vc)                                 : qw(vc our);
 
 465   $params{title}             = $params{type} eq 'sepa_transfer' ? $::locale->text('Bank transfers via SEPA') : $::locale->text('Bank collections via SEPA');
 
 466   $params{with_columns}      = [ grep { $_ ne 'record_link_direction' } @{ $params{with_columns} || [] } ];
 
 468   delete $params{edit_record_links};
 
 470   return $self->record_list(
 
 473       [ $::locale->text('Export Number'),    'sepa_export',                                  ],
 
 474       [ $::locale->text('Execution date'),   'execution_date'                                ],
 
 475       [ $::locale->text('Export date'),      sub { $_[0]->sepa_export->itime->to_kivitendo } ],
 
 476       [ $::locale->text('Source BIC'),       "${source}_bic"                                 ],
 
 477       [ $::locale->text('Source IBAN'),      "${source}_iban"                                ],
 
 478       [ $::locale->text('Destination BIC'),  "${destination}_bic"                            ],
 
 479       [ $::locale->text('Destination IBAN'), "${destination}_iban"                           ],
 
 480       [ $::locale->text('Amount'),           'amount'                                        ],
 
 486 sub _sepa_transfer_list {
 
 487   my ($self, $list, %params) = @_;
 
 488   _sepa_export_list($self, $list, %params, type => 'sepa_transfer');
 
 491 sub _sepa_collection_list {
 
 492   my ($self, $list, %params) = @_;
 
 493   _sepa_export_list($self, $list, %params, type => 'sepa_collection');
 
 497   my ($self, $list, %params) = @_;
 
 499   return $self->record_list(
 
 501     title   => $::locale->text('Letters'),
 
 504       [ $::locale->text('Date'),         'date'                                                ],
 
 505       [ $::locale->text('Letternumber'), sub { $self->letter($_[0], display => 'table-cell') } ],
 
 506       [ $::locale->text('Customer'),     'customer'                                            ],
 
 507       [ $::locale->text('Reference'),    'reference'                                           ],
 
 508       [ $::locale->text('Subject'),      'subject'                                             ],
 
 524 SL::Presenter::Record - Presenter module for lists of
 
 525 sales/purchase/general ledger record Rose::DB objects
 
 529   # Retrieve a number of documents from somewhere, e.g.
 
 530   my $order   = SL::DB::Manager::Order->get_first(where => [ SL::DB::Manager::Order->type_filter('sales_order') ]);
 
 531   my $records = $order->linked_records(destination => 'to');
 
 533   # Give HTML representation:
 
 534   my $html = SL::Presenter->get->grouped_record_list($records);
 
 546 Returns a rendered version (actually an instance of
 
 547 L<SL::Presenter::EscapedText>) of a single ar, ap or gl object.
 
 550   # fetch the record from a random acc_trans object and print its link (could be ar, ap or gl)
 
 551   my $record = SL::DB::Manager::AccTransaction->get_first()->record;
 
 552   my $html   = SL::Presenter->get->record($record, display => 'inline');
 
 554 =item C<grouped_record_list $list, %params>
 
 556 =item C<empty_record_list>
 
 558 Returns a rendered version (actually an instance of
 
 559 L<SL::Presenter::EscapedText>) of an empty list of records. Is usually
 
 560 only called by L<grouped_record_list> if its list is empty.
 
 562 =item C<grouped_record_list $list, %params>
 
 564 Given a number of Rose::DB objects in the array reference C<$list>
 
 565 this function first groups them by type. Then it calls L<record_list>
 
 566 with each non-empty type-specific sub-list and the appropriate
 
 567 parameters for outputting a list of those records.
 
 569 Returns a rendered version (actually an instance of
 
 570 L<SL::Presenter::EscapedText>) of all the lists.
 
 572 The order in which the records are grouped is:
 
 576 =item * sales quotations
 
 580 =item * sales delivery orders
 
 582 =item * sales invoices
 
 584 =item * AR transactions
 
 586 =item * requests for quotations
 
 588 =item * purchase orders
 
 590 =item * purchase delivery orders
 
 592 =item * purchase invoices
 
 594 =item * AP transactions
 
 596 =item * SEPA collections
 
 598 =item * SEPA transfers
 
 602 Objects of unknown types are skipped.
 
 604 Parameters are passed to C<record_list> include C<with_objects> and
 
 605 C<edit_record_links>.
 
 607 =item C<record_list $list, %params>
 
 609 Returns a rendered version (actually an instance of
 
 610 L<SL::Presenter::EscapedText>) of a list of records. This list
 
 611 consists of a heading and a tabular representation of the list.
 
 613 The parameters include:
 
 619 Mandatory. The title to use in the heading. Must already be
 
 624 Mandatory. An array reference of column specs to output. Each column
 
 625 spec can be either an array reference or a hash reference.
 
 627 If a column spec is an array reference then the first element is the
 
 628 column's name shown in the table header. It must already be translated.
 
 630 The second element can be either a string or a code reference. A
 
 631 string is taken as the name of a function to call on the Rose::DB
 
 632 object for the current row. Its return value is formatted depending on
 
 633 the column's type (e.g. dates are output as the user expects them,
 
 634 floating point numbers are rounded to two decimal places and
 
 635 right-aligned etc). If it is a code reference then that code is called
 
 636 with the object as the first argument. Its return value should be an
 
 637 instance of L<SL::Presenter::EscapedText> and contain the rendered
 
 638 representation of the content to output.
 
 640 The third element, if present, can be a link to which the column will
 
 643 If the column spec is a hash reference then the same arguments are
 
 644 expected. The corresponding hash keys are C<title>, C<data> and
 
 647 =item C<with_columns>
 
 649 Can be set by the caller to indicate additional columns to
 
 650 be listed. Currently supported:
 
 654 =item C<record_link_destination>
 
 656 The record link destination. Requires that the records to be listed have
 
 657 been retrieved via the L<SL::DB::Helper::LinkedRecords> helper.
 
 661 =item C<edit_record_links>
 
 663 If trueish additional controls will be rendered that allow the user to
 
 664 remove and add record links. Requires that the records to be listed have
 
 665 been retrieved via the L<SL::DB::Helper::LinkedRecords> helper.
 
 677 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>