Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision 4f152ed2

Von Kivitendo Admin vor etwa 10 Jahren hinzugefügt

  • ID 4f152ed21fb137de6d6f4d48ec28d67055bcf965
  • Vorgänger 4249ea44
  • Nachfolger ea1f1390

Neues Feature: Chartpicker

Praktisch komplett analog zum Partpicker, danke Sven für die
Pionierleistung!

Es gibt natürlich ein paar Unterschiede bei den Filteroptionen, z.B.
kann man im Popup nur bebuchte Konten anzeigen lassen, oder die
Ergebnisliste nach der Kontenart einschränken. Es wird auch immer nur
eine Konto pro Spalte angezeigt, auch im Block Modus.

Hat der Benutzer FiBu-Rechte wird auch der aktuelle Kontensaldo
angezeigt. Hierfür wurden ein paar neue Methoden für Chart hinzugefügt.

Unterschiede anzeigen:

SL/Controller/Chart.pm
1
package SL::Controller::Chart;
2

  
3
use strict;
4
use parent qw(SL::Controller::Base);
5

  
6
use Clone qw(clone);
7
use SL::DB::Chart;
8
use SL::Controller::Helper::GetModels;
9
use SL::DB::Helper::Paginated;
10
use SL::Locale::String qw(t8);
11
use SL::JSON;
12

  
13
use Rose::Object::MakeMethods::Generic (
14
  'scalar --get_set_init' => [ qw(charts models chart) ],
15
);
16

  
17
sub action_ajax_autocomplete {
18
  my ($self, %params) = @_;
19

  
20
  my $value = $::form->{column} || 'description';
21

  
22
  # if someone types something, and hits enter, assume he entered the full name.
23
  # if something matches, treat that as sole match
24
  # unfortunately get_models can't do more than one per package atm, so we do it
25
  # the oldfashioned way.
26
  if ($::form->{prefer_exact}) {
27
    my $exact_matches;
28
    # we still need the type filter so that we can't choose an illegal chart
29
    # via exact_match if we have preset a link type, e.g. AR_paid
30
    if (1 == scalar @{ $exact_matches = SL::DB::Manager::Chart->get_all(
31
      query => [
32
        SL::DB::Manager::Chart->type_filter($::form->{filter}{type}),
33
        or => [
34
          description => { ilike => $::form->{filter}{'all:substr:multi::ilike'} },
35
          accno       => { ilike => $::form->{filter}{'all:substr:multi::ilike'} },
36
        ]
37
      ],
38
      limit => 2,
39
    ) }) {
40
      $self->charts($exact_matches);
41
    }
42
  }
43

  
44
  my @hashes = map {
45
   +{
46
     value       => $_->displayable_name,
47
     label       => $_->displayable_name,
48
     id          => $_->id,
49
     accno       => $_->accno,
50
     description => $_->description,
51
    }
52
  } @{ $self->charts }; # neato: if exact match triggers we don't even need the init_parts
53

  
54
  $self->render(\ SL::JSON::to_json(\@hashes), { layout => 0, type => 'json', process => 0 });
55
}
56

  
57
sub action_test_page {
58
  $_[0]->render('chart/test_page');
59
}
60

  
61
sub action_chart_picker_search {
62
  $_[0]->render('chart/chart_picker_search', { layout => 0 }, charts => $_[0]->charts);
63
}
64

  
65
sub action_chart_picker_result {
66
  $_[0]->render('chart/_chart_picker_result', { layout => 0 });
67
}
68

  
69
sub action_show {
70
  my ($self) = @_;
71

  
72
  if ($::request->type eq 'json') {
73
    my $chart_hash;
74
    if (!$self->chart) {
75
      # TODO error
76
    } else {
77
      require Rose::DB::Object::Helpers;
78
        $chart_hash                     = $self->chart->as_tree;
79
        $chart_hash->{displayable_name} = $self->chart->displayable_name;
80
    }
81

  
82
    $self->render(\ SL::JSON::to_json($chart_hash), { layout => 0, type => 'json', process => 0 });
83
  }
84
}
85

  
86
sub init_charts {
87

  
88
  # disable pagination when hiding chart details = paginate when showing chart details
89
  if ($::form->{hide_chart_details}) {
90
    $_[0]->models->disable_plugin('paginated');
91
  }
92

  
93
  $_[0]->models->get;
94
}
95

  
96
sub init_chart {
97
  SL::DB::Chart->new(id => $::form->{id} || $::form->{chart}{id})->load;
98
}
99

  
100
sub init_models {
101
  my ($self) = @_;
102

  
103
  SL::Controller::Helper::GetModels->new(
104
    controller => $self,
105
    sorted => {
106
      _default  => {
107
        by  => 'accno',
108
        dir => 1,
109
      },
110
      accno       => t8('Account number'),
111
      description => t8('Description'),
112
    },
113
  );
114
}
115

  
116
1;
SL/DB/Chart.pm
4 4

  
5 5
use SL::DB::MetaSetup::Chart;
6 6
use SL::DB::Manager::Chart;
7
use SL::DBUtils;
8
use Rose::DB::Object::Helpers qw(as_tree);
9
use SL::DB::Helper::AccountingPeriod qw(get_balance_starting_date);
10
use SL::Locale::String qw(t8);
7 11

  
8 12
__PACKAGE__->meta->add_relationships(taxkeys => { type         => 'one to many',
9 13
                                                  class        => 'SL::DB::TaxKey',
......
27 31
    sort_by => "startdate DESC")->[0];
28 32
}
29 33

  
34
sub get_active_taxrate {
35
  my ($self, $date) = @_;
36
  $date ||= DateTime->today_local;
37
  require SL::DB::Tax;
38
  my $tax = SL::DB::Manager::Tax->find_by( id => $self->get_active_taxkey->tax_id );
39
  return $tax->rate;
40
}
41

  
42

  
43
sub get_balance {
44
  my ($self, %params) = @_;
45

  
46
  return undef unless $self->id;
47

  
48
  # return empty string if user doesn't have rights
49
  return "" unless ($main::auth->assert('general_ledger', 1));
50

  
51
  my $query = qq|SELECT SUM(amount) AS sum FROM acc_trans WHERE chart_id = ? AND transdate >= ? and transdate <= ?|;
52

  
53
  my $startdate = $self->get_balance_starting_date;
54
  my $today     = DateTime->today_local;
55

  
56
  my ($balance)  = selectfirst_array_query($::form, $self->db->dbh, $query, $self->id, $startdate, $today);
57

  
58
  return $balance;
59
};
60

  
61
sub formatted_balance_dc {
62
  my ($self, %params) = @_;
63

  
64
  # return empty string if user doesn't have rights
65
  return "" unless ($main::auth->assert('general_ledger', 1));
66

  
67
  # return empty string if chart has never been booked
68
  return "" unless $self->has_transaction;
69

  
70
  # return abs of current balance with the abbreviation for debit or credit behind it
71
  my $balance = $self->get_balance || 0;
72
  my $dc_abbreviation = $balance > 0 ? t8("Credit (one letter abbreviation)") : t8("Debit (one letter abbreviation)");
73
  my $amount = $::form->format_amount(\%::myconfig, abs($balance), 2);
74

  
75
  return "$amount $dc_abbreviation";
76
};
77

  
78
sub number_of_transactions {
79
  my ($self) = @_;
80

  
81
  my ($acc_trans) = $self->db->dbh->selectrow_array('select count(acc_trans_id) from acc_trans where chart_id = ?', {}, $self->id);
82

  
83
  return $acc_trans;
84
};
85

  
86
sub has_transaction {
87
  my ($self) = @_;
88

  
89
  my ($id) = $self->db->dbh->selectrow_array('select acc_trans_id from acc_trans where chart_id = ? limit 1', {}, $self->id) || 0;
90

  
91
  $id ? return 1 : return 0;
92

  
93
};
94

  
95
sub displayable_name {
96
  my ($self) = @_;
97

  
98
  return join ' ', grep $_, $self->accno, $self->description;
99
}
100

  
101
sub displayable_category {
102
  my ($self) = @_;
103

  
104
  return t8("Account Category E") if $self->category eq "E";
105
  return t8("Account Category I") if $self->category eq "I";
106
  return t8("Account Category A") if $self->category eq "A";
107
  return t8("Account Category L") if $self->category eq "L";
108
  return t8("Account Category Q") if $self->category eq "Q";
109
  return t8("Account Category C") if $self->category eq "C";
110
  return '';
111
}
112

  
113
sub date_of_last_transaction {
114
  my ($self) = @_;
115

  
116
  die unless $self->id;
117

  
118
  return '' unless $self->has_transaction;
119

  
120
  my ($transdate) = $self->db->dbh->selectrow_array('select max(transdate) from acc_trans where chart_id = ?', {}, $self->id);
121
  return DateTime->from_lxoffice($transdate);
122
}
123

  
30 124
1;
31 125

  
32 126
__END__
......
48 142
Returns the active tax key object for a given date. C<$date> defaults
49 143
to the current date if undefined.
50 144

  
145
=item C<get_active_taxrate $date>
146

  
147
Returns the tax rate of the active tax key as determined by
148
C<get_active_taxkey>.
149

  
150
=item C<get_balance>
151

  
152
Returns the current balance of the chart (sum of amount in acc_trans, positive
153
or negative). The transactions are filtered by transdate, the maximum date is
154
the current day, the minimum date is determined by get_balance_starting_date.
155

  
156
The balance should be same as that in the balance report for that chart, with
157
the asofdate as the current day, and the accounting_method "accrual".
158

  
159
=item C<formatted_balance_dc>
160

  
161
Returns a formatted version of C<get_balance>, taking the absolute value and
162
adding the translated abbreviation for debit or credit after the number.
163

  
164
=item C<has_transaction>
165

  
166
Returns 1 or 0, depending whether the chart has a transaction in the database
167
or not.
168

  
169
=item C<date_of_last_transaction>
170

  
171
Returns the date of the last transaction of the chart in the database, which
172
may lie in the future.
173

  
51 174
=back
52 175

  
53 176
=head1 BUGS
......
58 181

  
59 182
Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
60 183

  
184
G. Richardson E<lt>information@kivitendo-premium.deE<gt>
185

  
61 186
=cut
SL/DB/Manager/Chart.pm
6 6
use base qw(SL::DB::Helper::Manager);
7 7

  
8 8
use SL::DB::Helper::Sorted;
9
use SL::DB::Helper::Paginated;
10
use SL::DB::Helper::Filtered;
11
use SL::MoreCommon qw(listify);
9 12
use DateTime;
10 13
use SL::DBUtils;
14
use Data::Dumper;
11 15

  
12 16
sub object_class { 'SL::DB::Chart' }
13 17

  
14 18
__PACKAGE__->make_manager_methods;
15 19

  
20
__PACKAGE__->add_filter_specs(
21
  type => sub {
22
    my ($key, $value) = @_;
23
    return __PACKAGE__->type_filter($value);
24
  },
25
  category => sub {
26
    my ($key, $value) = @_;
27
    return __PACKAGE__->category_filter($value);
28
  },
29
  selected_category => sub {
30
    my ($key, $value) = @_;
31
    return __PACKAGE__->selected_category_filter($value);
32
  },
33
  all => sub {
34
    my ($key, $value) = @_;
35
    return or => [ map { $_ => $value } qw(accno description) ]
36
  },
37
  booked => sub {
38
    my ($key, $value) = @_;
39
    return __PACKAGE__->booked_filter($value);
40
  },
41
);
42

  
43
sub booked_filter {
44
  my ($class, $booked) = @_;
45

  
46
  $booked //= 0;
47
  my @filter;
48

  
49
  if ( $booked ) {
50
     push @filter, ( id => [ \"SELECT distinct chart_id FROM acc_trans" ] );
51
  };
52

  
53
  return @filter;
54
}
55

  
56
sub selected_category_filter {
57
  my ($class, $selected_categories) = @_;
58

  
59
  my @selected_categories = @$selected_categories;
60

  
61
  # if no category is selected, there is no filter and thus all charts of all
62
  # categories are displayed, which is what we want.
63

  
64
  return (category => \@$selected_categories);
65
}
66

  
67
sub type_filter {
68
  my ($class, $type) = @_;
69

  
70
  # filter by link or several defined custom types
71
  # special types:
72
  # bank, guv, balance
73

  
74
  return () unless $type;
75

  
76
  if ('HASH' eq ref $type) {
77
    # this is to make selection like type => { AR_paid => 1, AP_paid => 1 } work
78
    $type = [ grep { $type->{$_} } keys %$type ];
79
  }
80

  
81
  my @types = grep { $_ } listify($type);
82
  my @filter;
83

  
84
  for my $type (@types) {
85
    if ( $type eq 'bank' ) {
86
     push @filter, ( id => [ \"SELECT chart_id FROM bank_accounts" ] );
87
    } elsif ( $type eq 'guv' ) {
88
     push @filter, ( category => [ 'I', 'E' ] );
89
    } elsif ( $type eq 'balance' ) {
90
     push @filter, ( category => [ 'A', 'Q', 'L' ] );
91
    } else {
92
      push @filter, $class->link_filter($type);
93
    };
94
  };
95

  
96
  return @filter > 2 ? (or => \@filter) : @filter;
97
}
98

  
99
sub category_filter {
100
  my ($class, $category) = @_;
101

  
102
  return () unless $category;
103

  
104
  # filter for chart_picker if a category filter was passed via params
105

  
106
  if ( ref $category eq 'HASH' ) {
107
    # this is to make a selection like category => { I => 1, E => 1 } work
108
    $category = [ grep { $category->{$_} } keys %$category ];
109
  }
110

  
111
  my @categories = grep { $_ } listify($category);
112

  
113
  return (category => \@categories);
114
}
115

  
16 116
sub link_filter {
17 117
  my ($class, $link) = @_;
18 118

  
......
43 143
  }
44 144
}
45 145

  
146
sub _sort_spec {
147
  (
148
    default  => [ 'accno', 1 ],
149
    # columns  => {
150
    #   SIMPLE => 'ALL',
151
    # },
152
    nulls    => {},
153
  );
154
}
155

  
46 156
1;
47 157

  
48 158
__END__
......
77 187

  
78 188
Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
79 189

  
190
G. Richardson E<lt>information@kivitendo-premium.deE<gt>
191

  
80 192
=cut
SL/Presenter.pm
7 7
use Carp;
8 8
use Template;
9 9

  
10
use SL::Presenter::Chart;
10 11
use SL::Presenter::CustomerVendor;
11 12
use SL::Presenter::DeliveryOrder;
12 13
use SL::Presenter::EscapedText;
SL/Presenter/Chart.pm
1
package SL::Presenter::Chart;
2

  
3
use strict;
4

  
5
use SL::DB::Chart;
6

  
7
use Exporter qw(import);
8
use Data::Dumper;
9
our @EXPORT = qw(chart_picker chart);
10

  
11
use Carp;
12

  
13
sub chart {
14
  my ($self, $chart, %params) = @_;
15

  
16
  $params{display} ||= 'inline';
17

  
18
  croak "Unknown display type '$params{display}'" unless $params{display} =~ m/^(?:inline|table-cell)$/;
19

  
20
  my $text = join '', (
21
    $params{no_link} ? '' : '<a href="am.pl?action=edit_account&id=' . $self->escape($chart->id) . '">',
22
    $self->escape($chart->accno),
23
    $params{no_link} ? '' : '</a>',
24
  );
25
  return $self->escaped_text($text);
26
}
27

  
28
sub chart_picker {
29
  my ($self, $name, $value, %params) = @_;
30

  
31
  $value = SL::DB::Manager::Chart->find_by(id => $value) if $value && !ref $value;
32
  my $id = delete($params{id}) || $self->name_to_id($name);
33
  my $fat_set_item = delete $params{fat_set_item};
34

  
35
  my @classes = $params{class} ? ($params{class}) : ();
36
  push @classes, 'chart_autocomplete';
37
  push @classes, 'chartpicker_fat_set_item' if $fat_set_item;
38

  
39
  my $ret =
40
    $self->input_tag($name, (ref $value && $value->can('id') ? $value->id : ''), class => "@classes", type => 'hidden', id => $id) .
41
    join('', map { $params{$_} ? $self->input_tag("", delete $params{$_}, id => "${id}_${_}", type => 'hidden') : '' } qw(type category choose booked)) .
42
    $self->input_tag("", (ref $value && $value->can('displayable_name')) ? $value->displayable_name : '', id => "${id}_name", %params);
43

  
44
  $::request->layout->add_javascripts('autocomplete_chart.js');
45
  $::request->presenter->need_reinit_widgets($id);
46

  
47
  $self->html_tag('span', $ret, class => 'chart_picker');
48
}
49

  
50
1;
51

  
52
__END__
53

  
54
=encoding utf-8
55

  
56
=head1 NAME
57

  
58
SL::Presenter::Chart - Chart related presenter stuff
59

  
60
=head1 SYNOPSIS
61

  
62
  # Create an html link for editing/opening a chart
63
  my $object = SL::DB::Manager::Chart->get_first;
64
  my $html   = SL::Presenter->get->chart($object, display => 'inline');
65

  
66
see also L<SL::Presenter>
67

  
68
=head1 DESCRIPTION
69

  
70
see L<SL::Presenter>
71

  
72
=head1 FUNCTIONS
73

  
74
=over 2
75

  
76
=item C<chart, $object, %params>
77

  
78
Returns a rendered version (actually an instance of
79
L<SL::Presenter::EscapedText>) of the chart object C<$object>
80

  
81
C<%params> can include:
82

  
83
=over 4
84

  
85
=item * display
86

  
87
Either C<inline> (the default) or C<table-cell>. At the moment both
88
representations are identical and produce the chart's name linked
89
to the corresponding 'edit' action.
90

  
91
=back
92

  
93
=back
94

  
95
=over 2
96

  
97
=item C<chart_picker $name, $value, %params>
98

  
99
All-in-one picker widget for charts. The code was originally copied and adapted
100
from the part picker. The name will be both id and name of the resulting hidden
101
C<id> input field (but the ID can be overwritten with C<$params{id}>).
102

  
103
An additional dummy input will be generated which is used to find
104
chart. For a detailed description of its behaviour, see section
105
C<CHART PICKER SPECIFICATION>.
106

  
107
For some examples of usage see the test page at controller.pl?action=Chart/test_page
108

  
109
C<$value> can be a chart id or a C<Rose::DB:Object> instance.
110

  
111
C<%params> can include:
112

  
113
=over 4
114

  
115
=item * category
116

  
117
If C<%params> contains C<category> only charts of this category will be
118
available for selection (in the autocompletion and in the popup).
119

  
120
You may comma separate multiple categories, e.g C<A,Q,L>.
121

  
122
In SL::DB::Manager::Chart there is also a filter called C<selected_category>,
123
which filters the possible charts according to the category checkboxes the user
124
selects in the popup window. This filter may further restrict the results of
125
the filter category, but the user is not able to "break out of" the limits
126
defined by C<category>. In fact if the categories are restricted by C<category>
127
the popup template should only show checkboxes for those categories.
128

  
129
=item * type
130

  
131
If C<%params> contains C<type> only charts of this type will be used for
132
autocompletion, i.e. the selection is filtered. You may comma separate multiple
133
types.
134

  
135
Type is usually a filter for link: C<AR,AR_paid>
136

  
137
Type can also be a specially defined type: C<guv>, C<balance>, C<bank>
138

  
139
See the type filter in SL::DB::Manager::Chart.
140

  
141
=item * choose
142

  
143
If C<%params> is passed with choose=1 the input of the filter field in the
144
popup window is cleared. This is useful if a chart was already selected and we
145
want to choose a new chart and immediately see all options.
146

  
147
=item * fat_set_item
148

  
149
If C<%params> is passed with fat_set_item=1 the contents of the selected chart
150
object (the elements of the database chart table) are passed back via JSON into
151
the item object. There is an example on the test page.
152

  
153
Without fat_set_item only the variables id and name (displayable name) are
154
available.
155

  
156
=back
157

  
158
C<chart_picker> will register its javascript for inclusion in the next header
159
rendering. If you write a standard controller that only calls C<render> once, it
160
will just work.  In case the header is generated in a different render call
161
(multiple blocks, ajax, old C<bin/mozilla> style controllers) you need to
162
include C<js/autocomplete_part.js> yourself.
163

  
164
=back
165

  
166
=head1 POPUP LAYER
167

  
168
For users that don't regularly do bookkeeping and haven't memorised all the
169
account numbers and names there are some filter options inside the popup window
170
to quickly narrow down the possible matches. You can filter by
171

  
172
=over 4
173

  
174
=item * chart accno or description, inside the input field
175

  
176
=item * accounts that have already been booked
177

  
178
=item * by category (AIELQC)
179

  
180
By selecting category checkboxes the list of available charts can be
181
restricted. If all checkboxes are unchecked all possible charts are shown.
182

  
183
=back
184

  
185
There are two views in the list of accounts. By default all possible accounts are shown as a long list.
186

  
187
But you can also show more information, in this case the resulting list is automatically paginated:
188

  
189
=over 4
190

  
191
=item * the balance of the account (as determined by SL::DB::Chart get_balance, which checks for user rights)
192

  
193
=item * the category
194

  
195
=item * the invoice date of the last transaction (may lie in the future)
196

  
197
=back
198

  
199
The partpicker also has two views, but whereas the compact block view of the
200
partpicker allows part boxes to be aligned in two columns, the chartpicker
201
block view still only shows one chart per row, because there is more
202
information and the account names can be quite long. This behaviour is
203
determined by css, however, and could be changed (div.cpc_block).  The downside
204
of this is that you have to scroll the window to reach the pagination controls.
205

  
206
The default view of the display logic is to use block view, so we don't have to
207
pass any parameters in the pagination GET. However, the default view for the
208
user is the list view, so the popup window is opened with the "Hide additional
209
information" select box already ticked.
210

  
211
=head1 CHART PICKER SPECIFICATION
212

  
213
The following list of design goals were applied:
214

  
215
=over 4
216

  
217
=item *
218

  
219
Charts should not be perceived by the user as distinct inputs of chart number and
220
description but as a single object
221

  
222
=item *
223

  
224
Easy to use without documentation for novice users
225

  
226
=item *
227

  
228
Fast to use with keyboard for experienced users
229

  
230
=item *
231

  
232
Possible to use without any keyboard interaction for mouse (or touchscreen)
233
users
234

  
235
=item *
236

  
237
Must not leave the current page in event of ambiguity (cf. current select_item
238
mechanism)
239

  
240
=item *
241

  
242
Should not require a feedback/check loop in the common case
243

  
244
=item *
245

  
246
Should not be constrained to exact matches
247

  
248
=back
249

  
250
The implementation consists of the following parts which will be referenced later:
251

  
252
=over 4
253

  
254
=item 1
255

  
256
A hidden input (id input), used to hold the id of the selected part. The only
257
input that gets submitted
258

  
259
=item 2
260

  
261
An input (dummy input) containing a description of the currently selected chart,
262
also used by the user to search for charts
263

  
264
=item 3
265

  
266
A jquery.autocomplete mechanism attached to the dummy field
267

  
268
=item 4
269

  
270
A popup layer for both feedback and input of additional data in case of
271
ambiguity.
272

  
273
=item 5
274

  
275
An internal status of the chart picker, indicating whether id input and dummy
276
input are consistent. After leaving the dummy input the chart picker must
277
place itself in a consistent status.
278

  
279
=item 6
280

  
281
A clickable icon (popup trigger) attached to the dummy input, which triggers the popup layer.
282

  
283
=back
284

  
285
=head1 BUGS
286

  
287
None atm :)
288

  
289
=head1 AUTHOR
290

  
291
Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>
292

  
293
G. Richardson E<lt>information@kivitendo-premium.deE<gt>
294

  
295
=cut
SL/Template/Plugin/L.pm
67 67
sub truncate      { return _call_presenter('truncate',      @_); }
68 68
sub simple_format { return _call_presenter('simple_format', @_); }
69 69
sub part_picker   { return _call_presenter('part_picker',   @_); }
70
sub chart_picker  { return _call_presenter('chart_picker',  @_); }
70 71
sub customer_vendor_picker   { return _call_presenter('customer_vendor_picker',   @_); }
71 72

  
72 73
sub _set_id_attribute {
css/kivitendo/main.css
360 360
.part_picker {
361 361
  padding-right: 16px;
362 362
}
363
.chart_picker {
364
  padding-right: 16px;
365
}
363 366
.customer-vendor-picker-undefined,
367
.chartpicker-undefined,
364 368
.partpicker-undefined {
365 369
  color: red;
366 370
  font-style: italic;
367 371
}
368 372

  
369
div.part_picker_part {
373
div.part_picker_part,
374
div.chart_picker_chart {
370 375
  padding: 5px;
371 376
  margin: 5px;
372 377
  border: 1px;
......
378 383
  background-color: white;
379 384
  cursor: pointer;
380 385
}
381
div.part_picker_part:hover {
386
div.part_picker_part:hover,
387
div.chart_picker_chart:hover {
382 388
  background-color: #CCCCCC;
383 389
  color: #FE5F14;
384 390
  border-color: gray;
385 391
}
392

  
386 393
div.ppp_block {
387 394
  overflow:hidden;
388 395
  float:left;
389 396
  width: 350px;
390 397
}
391
div.ppp_block span.ppp_block_number {
398
/* div.cpc_block { */
399
/*   overflow:hidden; */
400
/*   float:left; */
401
/*   width: 350px; */
402
/* } */
403
div.ppp_block span.ppp_block_number,
404
div.cpc_block span.cpc_block_number
405
{
392 406
  float:left;
393 407
}
394 408
div.ppp_block span.ppp_block_description {
395 409
  float:right;
410
  margin-left:1em;
396 411
  font-weight:bold;
397 412
}
398
div.ppp_line span.ppp_block_description {
413
div.cpc_block span.cpc_block_description {
414
  float:left;
399 415
  margin-left:1em;
400 416
  font-weight:bold;
401 417
}
402
div.ppp_line span.ppp_block_sellprice {
418
div.ppp_line span.ppp_block_description,
419
div.cpc_line span.cpc_block_description
420
{
421
  margin-left:1em;
422
  font-weight:bold;
423
}
424
div.cpc_block span.cpc_block_balance {
425
  float:right;
426
}
427
div.cpc_block span.cpc_line_balance {
403 428
  display:none;
404 429
}
430
div.cpc_line span.cpc_block_second_row {
431
  display:none;
432
}
433
div.cpc_block span.cpc_block_second_row {
434
  font-size:80%;
435
}
405 436
span.toggle_selected {
406 437
  font-weight: bold;
407 438
}
css/lx-office-erp/main.css
406 406
.part_picker {
407 407
  padding-right: 16px;
408 408
}
409
.chart_picker {
410
  padding-right: 16px;
411
}
409 412
.customer-vendor-picker-undefined,
413
.chartpicker-undefined,
410 414
.partpicker-undefined {
411 415
  color: red;
412 416
  font-style: italic;
413 417
}
414
div.part_picker_part {
418

  
419
div.part_picker_part,
420
div.chart_picker_chart {
415 421
  padding: 5px;
416 422
  margin: 5px;
417 423
  border: 1px;
418 424
  border-color: darkgray;
419 425
  border-style: solid;
420
  -webkit-border-radius: 2px;
421
  -moz-border-radius: 2px;
422
  border-radius: 2px;
423
  background-color: whitesmoke;
426
  -webkit-border-radius: 4px;
427
  -moz-border-radius: 4px;
428
  border-radius: 4px;
429
  background-color: white;
424 430
  cursor: pointer;
425 431
}
426
div.part_picker_part:hover {
427
  background-color: lightgray;
432
div.part_picker_part:hover,
433
div.chart_picker_chart:hover {
434
  background-color: #CCCCCC;
435
  color: #FE5F14;
428 436
  border-color: gray;
429 437
}
438

  
430 439
div.ppp_block {
431 440
  overflow:hidden;
432 441
  float:left;
433
  width: 350px;
442
  width: 350;
434 443
}
435
div.ppp_block span.ppp_block_number {
444
div.ppp_block span.ppp_block_number,
445
div.cpc_block span.cpc_block_number
446
{
436 447
  float:left;
437 448
}
438 449
div.ppp_block span.ppp_block_description {
439 450
  float:right;
451
  margin-left:1em;
452
  font-weight:bold;
453
}
454
div.cpc_block span.cpc_block_description {
455
  float:left;
456
  margin-left:1em;
440 457
  font-weight:bold;
441 458
}
442
div.ppp_line span.ppp_block_description {
459
div.ppp_line span.ppp_block_description,
460
div.cpc_line span.cpc_block_description
461
{
443 462
  margin-left:1em;
444 463
  font-weight:bold;
445 464
}
446
div.ppp_line span.ppp_block_sellprice {
465
div.cpc_block span.cpc_block_balance {
466
  float:right;
467
}
468
div.cpc_block span.cpc_line_balance {
447 469
  display:none;
448 470
}
471
div.cpc_line span.cpc_block_second_row {
472
  display:none;
473
}
474
div.cpc_block span.cpc_block_second_row {
475
  font-size:80%;
476
}
449 477
span.toggle_selected {
450 478
  font-weight: bold;
451 479
}
doc/changelog
112 112
  jetzt eindeutig anhand der Variablen zu unterscheiden.
113 113
  Ggf. müssen bestehende Druckvorlagen angepasst werden (s.a. http://blog.kivitendo-premium.de/?p=351).
114 114

  
115
- Chartpicker
116
  Analog zum Partpicker steht nun ein intelligenter Picker für Konten zur
117
  Verfügung.
118

  
115 119
2014-02-28 - Release 3.1.0
116 120

  
117 121
Größere neue Features:
js/autocomplete_chart.js
1
namespace('kivi', function(k){
2
  k.ChartPicker = function($real, options) {
3
    // short circuit in case someone double inits us
4
    if ($real.data("chart_picker"))
5
      return $real.data("chart_picker");
6

  
7
    var KEY = {
8
      ESCAPE: 27,
9
      ENTER:  13,
10
      TAB:    9,
11
      LEFT:   37,
12
      RIGHT:  39,
13
      PAGE_UP: 33,
14
      PAGE_DOWN: 34,
15
    };
16
    var CLASSES = {
17
      PICKED:       'chartpicker-picked',
18
      UNDEFINED:    'chartpicker-undefined',
19
      FAT_SET_ITEM: 'chartpicker_fat_set_item',
20
    }
21
    var o = $.extend({
22
      limit: 20,
23
      delay: 50,
24
      fat_set_item: $real.hasClass(CLASSES.FAT_SET_ITEM),
25
    }, options);
26
    var STATES = {
27
      PICKED:    CLASSES.PICKED,
28
      UNDEFINED: CLASSES.UNDEFINED
29
    }
30
    var real_id = $real.attr('id');  // id of selected chart_picker, e.g. bank_id
31
    var $dummy  = $('#' + real_id + '_name');  // the input_field of the chart_picker
32
    var $type   = $('#' + real_id + '_type');  // hidden input_field of the chart_picker, added in Presenter
33
    var $category = $('#' + real_id + '_category');  // hidden input_field of the chart_picker, added in Presenter, never changes
34
    var $choose = $('#' + real_id + '_choose');
35
    var $booked = $('#' + real_id + '_booked');
36
    var state   = STATES.PICKED;
37
    var last_real = $real.val();
38
    var last_dummy = $dummy.val();
39
    var timer;
40

  
41
    function open_dialog () {
42
      // console.log('open_dialog');
43
      // console.log($type);
44
      // console.log(real_id);
45
      k.popup_dialog({
46
        url: 'controller.pl?action=Chart/chart_picker_search',
47
        // data that can be accessed in template chart_picker_search via FORM.boss
48
        data: $.extend({  // add id of part to the rest of the data in ajax_data, e.g. no_paginate, booked, ...
49
          real_id: real_id,
50
          hide_chart_details: 1,  // gets overwritten by ajax_data
51
          select: 1,
52
        }, ajax_data($dummy.val())),
53
        id: 'chart_selection',
54
        dialog: {
55
          title: k.t8('Chart picker'),
56
          width: 600,
57
          height: 800,
58
        }
59
      });
60
      window.clearTimeout(timer);
61
      return true;
62
    }
63

  
64
    function ajax_data(term) {
65
      var categories = $("#category input:checkbox:checked").map(function(){ return $(this).val(); }).get();
66
      var data = {
67
        'filter.all:substr:multi::ilike': term,
68
        'filter.selected_category': categories,
69
        'filter.booked': $('#booked').prop('checked') ? 1 : 0,
70
        hide_chart_details: $('#hide_chart_details').prop('checked') ? 1 : 0,
71
        booked:        $booked && $booked.val() ? $booked.val() : '',
72
        choose:        $choose && $choose.val() ? $choose.val() : '',
73
        current:       $real.val(),
74
      };
75

  
76
      if ($type && $type.val())
77
        data['filter.type'] = $type.val().split(',');
78

  
79
      if ($category && $category.val())
80
        data['filter.category'] = $category.val().split(',');
81

  
82
      return data;
83
    }
84

  
85
    function set_item (item) {
86
      if (item.id) {
87
        $real.val(item.id);
88
        // autocomplete ui has name, ajax items have description
89
        $dummy.val(item.name ? item.name : item.value);
90
      } else {
91
        $real.val('');
92
        $dummy.val('');
93
      }
94
      state = STATES.PICKED;
95
      last_real = $real.val();
96
      last_dummy = $dummy.val();
97
      last_unverified_dummy = $dummy.val();
98
      $real.trigger('change');
99

  
100
      if (o.fat_set_item && item.id) {
101
        $.ajax({
102
          url: 'controller.pl?action=Chart/show.json',
103
          data: { id: item.id },
104
          success: function(rsp) {
105
            $real.trigger('set_item:ChartPicker', rsp);
106
          },
107
        });
108
      } else {
109
        $real.trigger('set_item:ChartPicker', item);
110
      }
111
      annotate_state();
112
    }
113

  
114
    function make_defined_state () {
115
      if (state == STATES.PICKED) {
116
        annotate_state();
117
        return true
118
      } else if (state == STATES.UNDEFINED && $dummy.val() == '')
119
        set_item({})
120
      else {
121
        last_unverified_dummy = $dummy.val();
122
        set_item({ id: last_real, name: last_dummy })
123
      }
124
      annotate_state();
125
    }
126

  
127
    function annotate_state () {
128
      if (state == STATES.PICKED)
129
        $dummy.removeClass(STATES.UNDEFINED).addClass(STATES.PICKED);
130
      else if (state == STATES.UNDEFINED && $dummy.val() == '')
131
        $dummy.removeClass(STATES.UNDEFINED).addClass(STATES.PICKED);
132
      else {
133
        last_unverified_dummy = $dummy.val();
134
        $dummy.addClass(STATES.UNDEFINED).removeClass(STATES.PICKED);
135
      }
136
    }
137

  
138
    function update_results () {
139
      $.ajax({
140
        url: 'controller.pl?action=Chart/chart_picker_result',
141
        data: $.extend({
142
            'real_id': $real.val(),
143
        }, ajax_data(function(){ var val = $('#chart_picker_filter').val(); return val === undefined ? '' : val })),
144
        success: function(data){ $('#chart_picker_result').html(data) }
145
      });
146
    };
147

  
148
    function result_timer (event) {
149
      if (!$('hide_chart_details').prop('checked')) {
150
        if (event.keyCode == KEY.PAGE_UP) {
151
          $('#chart_picker_result a.paginate-prev').click();
152
          return;
153
        }
154
        if (event.keyCode == KEY.PAGE_DOWN) {
155
          $('#chart_picker_result a.paginate-next').click();
156
          return;
157
        }
158
      }
159
      window.clearTimeout(timer);
160
      timer = window.setTimeout(update_results, 100);
161
    }
162

  
163
    function close_popup() {
164
      $('#chart_selection').dialog('close');
165
    };
166

  
167
    $dummy.autocomplete({
168
      source: function(req, rsp) {
169
        $.ajax($.extend(o, {
170
          url:      'controller.pl?action=Chart/ajax_autocomplete',
171
          dataType: "json",
172
          // autoFocus: true,
173
          data:     ajax_data(req.term),
174
          success:  function (data){ rsp(data) }
175
        }));
176
      },
177
      select: function(event, ui) {
178
        set_item(ui.item);
179
      },
180
    });
181
    /*  In case users are impatient and want to skip ahead:
182
     *  Capture <enter> key events and check if it's a unique hit.
183
     *  If it is, go ahead and assume it was selected. If it wasn't don't do
184
     *  anything so that autocompletion kicks in.  For <tab> don't prevent
185
     *  propagation. It would be nice to catch it, but javascript is too stupid
186
     *  to fire a tab event later on, so we'd have to reimplement the "find
187
     *  next active element in tabindex order and focus it".
188
     */
189
    /* note:
190
     *  event.which does not contain tab events in keypressed in firefox but will report 0
191
     *  chrome does not fire keypressed at all on tab or escape
192
     */
193
    $dummy.keydown(function(event){
194
      if (event.which == KEY.ENTER || event.which == KEY.TAB) {
195
        // if string is empty assume they want to delete
196
        if ($dummy.val() == '') {
197
          set_item({});
198
          return true;
199
        } else if (state == STATES.PICKED) {
200
          return true;
201
        }
202
        if (event.which == KEY.TAB) event.preventDefault();
203
        $.ajax({
204
          url: 'controller.pl?action=Chart/ajax_autocomplete',
205
          dataType: "json",
206
          data: $.extend( ajax_data($dummy.val()), { prefer_exact: 1 } ),
207
          success: function (data) {
208
            if (data.length == 1) {
209
              set_item(data[0]);
210
              if (event.which == KEY.ENTER)
211
                $('#update_button').click();
212
            } else if (data.length > 1) {
213
             if (event.which == KEY.ENTER)
214
                open_dialog();
215
            } else {
216
            }
217
            annotate_state();
218
          }
219
        });
220
        if (event.which == KEY.ENTER)
221
          return false;
222
      } else {
223
        state = STATES.UNDEFINED;
224
      }
225
    });
226

  
227
    $dummy.blur(function(){
228
      window.clearTimeout(timer);
229
      timer = window.setTimeout(annotate_state, 100);
230
    });
231

  
232
    // now add a picker div after the original input
233
    var pcont  = $('<span>').addClass('position-absolute');
234
    var picker = $('<div>');
235
    $dummy.after(pcont);
236
    pcont.append(picker);
237
    picker.addClass('icon16 crm--search').click(open_dialog);
238

  
239
    var cp = {
240
      real:           function() { return $real },
241
      dummy:          function() { return $dummy },
242
      type:           function() { return $type },
243
      category:       function() { return $category },
244
      update_results: update_results,
245
      result_timer:   result_timer,
246
      set_item:       set_item,
247
      reset:          make_defined_state,
248
      is_defined_state: function() { return state == STATES.PICKED },
249
      init_results:    function () {
250
        $('div.chart_picker_chart').each(function(){
251
          $(this).click(function(){
252
            set_item({
253
              id:   $(this).children('input.chart_picker_id').val(),
254
              name: $(this).children('input.chart_picker_description').val(),  // hidden field
255
            });
256
            close_popup();
257
            $dummy.focus();
258
            return true;
259
          });
260
        });
261
        $('#chart_selection').keydown(function(e){
262
           if (e.which == KEY.ESCAPE) {
263
             close_popup();
264
             $dummy.focus();
265
           }
266
        });
267
      }
268
    }
269
    $real.data('chart_picker', cp);
270
    return cp;
271
  }
272
});
273

  
274
$(function(){
275
  $('input.chart_autocomplete').each(function(i,real){
276
    kivi.ChartPicker($(real));
277
  })
278
});
js/kivi.js
132 132
        kivi.CustomerVendorPicker($(elt));
133 133
      });
134 134

  
135
    if (ns.ChartPicker)
136
      ns.run_once_for('input.chart_autocomplete', 'chart_picker', function(elt) {
137
        kivi.ChartPicker($(elt));
138
      });
139

  
140

  
135 141
    var func = kivi.get_function_by_name('local_reinit_widgets');
136 142
    if (func)
137 143
      func();
js/locale/de.js
11 11
"Are you sure?":"Sind Sie sicher?",
12 12
"Basic settings actions":"Aktionen zu Grundeinstellungen",
13 13
"Cancel":"Abbrechen",
14
"Chart picker":"Kontenauswahl",
14 15
"Copy":"Kopieren",
15 16
"Copy requirement spec":"Pflichtenheft kopieren",
16 17
"Copy template":"Vorlage kopieren",
locale/de/all
486 486
  'Chart Type'                  => 'Kontentyp',
487 487
  'Chart balance'               => 'Kontensaldo',
488 488
  'Chart of Accounts'           => 'Kontenübersicht',
489
  'Chart picker'                => 'Kontenauswahl',
489 490
  'Chartaccounts connected to this Tax:' => 'Konten, die mit dieser Steuer verknüpft sind:',
490 491
  'Check'                       => 'Scheck',
491 492
  'Check Details'               => 'Bitte Angaben überprüfen',
......
1224 1225
  'Here\'s an example command line:' => 'Hier ist eine Kommandozeile, die als Beispiel dient:',
1225 1226
  'Hide Filter'                 => 'Filter verbergen',
1226 1227
  'Hide by default'             => 'Standardm&auml;&szlig;ig verstecken',
1228
  'Hide chart details'          => 'Konteninformation verstecken',
1227 1229
  'Hide help text'              => 'Hilfetext verbergen',
1228 1230
  'Hide settings'               => 'Einstellungen verbergen',
1229 1231
  'Hints'                       => 'Hinweise',
......
1413 1415
  'Last row, description'       => 'Letzte Zeile, Artikelbeschreibung',
1414 1416
  'Last row, partnumber'        => 'Letzte Zeile, Nummer',
1415 1417
  'Last run at'                 => 'Letzte Ausführung um',
1418
  'Last transaction'            => 'Letzte Buchung',
1416 1419
  'Lastcost'                    => 'Einkaufspreis',
1417 1420
  'Lastcost (with X being a number)' => 'Einkaufspreis (X ist eine fortlaufende Zahl)',
1418 1421
  'Lead'                        => 'Kundenquelle',
......
1676 1679
  'One or more Perl modules missing' => 'Ein oder mehr Perl-Module fehlen',
1677 1680
  'Onhand only sets the quantity in master data, not in inventory. This is only a legacy info field and will be overwritten as soon as a inventory transfer happens.' => 'Das Import-Feld Auf Lager setzt nur die Menge in den Stammdaten, nicht im Lagerbereich. Dies ist historisch gewachsen nur ein Informationsfeld was mit dem tatsächlichen Wert überschrieben wird, sobald eine wirkliche Lagerbewegung stattfindet (DB-Trigger).',
1678 1681
  'Only Warnings and Errors'    => 'Nur Warnungen und Fehler',
1682
  'Only booked accounts'        => 'Nur bebuchte Konten',
1679 1683
  'Only due follow-ups'         => 'Nur f&auml;llige Wiedervorlagen',
1680 1684
  'Only groups that have been configured for the client the user logs in to will be considered.' => 'Allerdings werden nur diejenigen Gruppen herangezogen, die für den Mandanten konfiguriert sind.',
1681 1685
  'Only list customer\'s projects in sales records' => 'Nur Projekte des Kunden in Verkaufsbelegen anzeigen',
templates/webpages/chart/_chart_picker_result.html
1
[%- USE T8 %]
2
[%- USE HTML %]
3
[%- USE L %]
4
[%- USE LxERP %]
5

  
6
[% FOREACH chart = SELF.charts %]
7
<div class='chart_picker_chart [% FORM.hide_chart_details ? 'cpc_line' : 'cpc_block' %]'>
8
  <input type='hidden' class='chart_picker_id' value='[% chart.id %]'>
9
  <input type='hidden' class='chart_picker_description' value='[% chart.displayable_name %]'>
10
  <span class='cpc_block_number'>[% chart.accno | html %]</span>
11
  <span class='cpc_block_description'>[% chart.description | html %]</span>
12
  [% UNLESS FORM.hide_chart_details %]
13
    <span class='cpc_block_balance'>[% chart.formatted_balance_dc | html %]</span>
14
    <div style='clear:both;'></div>
15
    <span class='cpc_block_second_row'>[% LxERP.t8("Chart Type") %]: [% chart.displayable_category %] &nbsp;&nbsp;&nbsp; [% IF chart.has_transaction %][% LxERP.t8("Last transaction")%]: [% chart.date_of_last_transaction.to_kivitendo %][% END %]</span>
16
  [% END %]
17
</div>
18
[%- END %]
19

  
20
<div style='clear:both'></div>
21

  
22
[% L.paginate_controls(target='#chart_picker_result', selector='#chart_picker_result', models=SELF.models) %]
23
<script type='text/javascript'>
24
  kivi.ChartPicker($('#'+$('#chart_picker_real_id').val())).init_results()
25
</script>
templates/webpages/chart/chart_picker_search.html
1
[%- USE HTML %]
2
[%- USE L %]
3
[%- USE LxERP %]
4
[%- USE T8 %]
5

  
6
<div style='overflow:hidden'>
7

  
8
[% LxERP.t8("Filter") %]: [% L.input_tag('chart_picker_filter', SELF.filter.all_substr_multi__ilike, class='chart_picker_filter',  style="width: 400px") %]
9
[% L.hidden_tag('chart_picker_real_id', FORM.real_id) %] <a href="javascript:void(0);" onclick="$('#chart_picker_filter').val('').focus();cp.update_results();">x</a>
10

  
11
<div style="padding: 5px 0em 5px 0em">
12
  <span>
13
    [% L.checkbox_tag('booked', checked=FORM.booked, for_submit=1, label=LxERP.t8('Only booked accounts')) %]
14
  </span>
15
  <span class='float-right'>
16
    [% L.checkbox_tag('hide_chart_details', checked=1, id='hide_chart_details', for_submit=1, label=LxERP.t8('Hide chart details')) %]
17
  </span>
18
</div>
19

  
20
<div id="category">
21
  [% LxERP.t8("Account Type") %]:
22

  
23
  [% IF FORM.filter.category %]
24
    [% FOREACH var IN FORM.filter.category %]
25
      [% IF var == 'A' %][% L.checkbox_tag('SELF.filter.selected_category_A', checked=1, value ='A', label=LxERP.t8('Assets')) %]   [% END %]
26
      [% IF var == 'L' %][% L.checkbox_tag('SELF.filter.selected_category_L', checked=1, value ='L', label=LxERP.t8('Liability')) %][% END %]
27
      [% IF var == 'Q' %][% L.checkbox_tag('SELF.filter.selected_category_Q', checked=1, value ='Q', label=LxERP.t8('Equity')) %]   [% END %]
28
      [% IF var == 'I' %][% L.checkbox_tag('SELF.filter.selected_category_I', checked=1, value ='I', label=LxERP.t8('Revenue')) %]  [% END %]
29
      [% IF var == 'E' %][% L.checkbox_tag('SELF.filter.selected_category_E', checked=1, value ='E', label=LxERP.t8('Expense')) %]  [% END %]
30
    [% END %]
31
  [% ELSE %]
32
    [% L.checkbox_tag('SELF.filter.selected_category_A', checked=0, value ='A', label=LxERP.t8('Assets')) %]
33
    [% L.checkbox_tag('SELF.filter.selected_category_L', checked=0, value ='L', label=LxERP.t8('Liability')) %]
34
    [% L.checkbox_tag('SELF.filter.selected_category_Q', checked=0, value ='Q', label=LxERP.t8('Equity')) %]
35
    [% L.checkbox_tag('SELF.filter.selected_category_I', checked=0, value ='I', label=LxERP.t8('Revenue')) %]
36
    [% L.checkbox_tag('SELF.filter.selected_category_E', checked=0, value ='E', label=LxERP.t8('Expense')) %]
37
  [% END %]
38
</div>
39

  
40
<div style='clear:both'></div>
41
<div id='chart_picker_result'></div>
42
</div>
43

  
44
<script type='text/javascript'>
45
  var cp = kivi.ChartPicker($('#[% FORM.real_id %]'));
46
  $(function(){
47
    $('#chart_picker_filter').focus();
48
    // empty input field upon opening if we just want to pick a field
49
    [% IF FORM.choose %] $('#chart_picker_filter').val(''); [% END %]
50

  
51
    cp.update_results();  // function from js/autocomplete_chart_js
52
  });
53
  $('#chart_picker_filter').keyup(cp.result_timer);
54
  // use keyup instead of keypress to get backspace to work
55
  // this messes up numblock though!
56
  $("input[type='checkbox']").change(cp.update_results);
57
</script>
templates/webpages/chart/test_page.html
1
[% USE L %]
2

  
3
<h1>Chart Picker Testpage</h1>
4

  
5
<div>
6

  
7
<p>
8
All charts: [% L.chart_picker('chart_id', '', style="width: 400px") %]text after icon<br>
9
Only booked charts: [% L.chart_picker('chart_id_booked', '', booked=1, style="width: 400px") %]<br>
10
All charts choose: [% L.chart_picker('chart_id_choose', '', choose=1, style="width: 400px") %]<br>
11
</p>
12

  
13
<p>
14
Filter by link:<br>
15
AR_paid: [% L.chart_picker('chart_id_ar_paid', undef, type='AR_paid', category='I,A' style="width: 400px") %]<br>
16
AR: [% L.chart_picker('chart_id_ar', undef, type='AR', style="width: 400px") %]<br>
17
AP: [% L.chart_picker('chart_id_ap', undef, type='AP', style="width: 400px") %]<br>
18
AR or AP: [% L.chart_picker('chart_id_arap', undef, type='AR,AP', style="width: 400px") %]<br>
19
IC_income,IC_sale: [% L.chart_picker('chart_id_icis', undef, type='IC_income,IC_sale', style="width: 400px") %]<br>
20
IC_expense,IC_cogs: [% L.chart_picker('chart_id_icco', undef, type='IC_expense,IC_cogs', style="width: 400px") %]<br>
21
</p>
22

  
23
<p>
24
Filter by category:<br>
25
I: [% L.chart_picker('chart_id_i', undef, category='I', style="width: 400px") %]<br>
26
IE: [% L.chart_picker('chart_id_ie', undef, category='I,E', style="width: 400px") %]<br>
27
AQL: [% L.chart_picker('chart_id_aql', undef, category='A,Q,L', style="width: 400px") %]<br>
28
</p>
29

  
30
<p>
31
Filter by special type:<br>
32
GuV: [% L.chart_picker('chart_id_guv', undef, type='guv', style="width: 400px") %]<br>
33
</p>
34

  
35
<p>bank (fat): [% L.chart_picker('bank_id', '', type='bank', fat_set_item=1, choose=1, style="width: 400px") %]
36
</p>
37
<p id="banktext"></p>
38

  
39

  
40
<p>
41
[% FOREACH i IN [ 1 2 3 4 5 6 ] %]
42
S [% i %]: [% L.chart_picker('credit_' _ i) %] - &nbsp;&nbsp;  H [% i %]: [% L.chart_picker('debit' _ i) %] <br>
43
[% END %]
44
</p>
45

  
46
</div>
47

  
48
<script type='text/javascript'>
49
  $(function(){
50
      // do something when a chartpicker with fat_set_item and a certain id changes
51
      $('#bank_id').on('set_item:ChartPicker', function (e, item) {
52
        console.log(item)
53
        $('#banktext').html('Selected fat item with category ' + item.category);
54
      })
55
  });
56

  
57
</script>
58

  

Auch abrufbar als: Unified diff