Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision 6d14c91b

Von Kivitendo Admin vor 27 Tagen hinzugefügt

  • ID 6d14c91b432b9a3690168cdd26b353363c6ac2d2
  • Vorgänger d7077fec
  • Nachfolger e8ce945f

Warengruppen hierarchisch - DB

Unterschiede anzeigen:

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

  
3
use strict;
4

  
5
use parent qw(SL::Controller::Base);
6

  
7
use SL::DB::PartsGroup;
8
use SL::Helper::Flash;
9
use SL::Locale::String;
10
use List::MoreUtils qw(any);
11

  
12
use Rose::Object::MakeMethods::Generic (
13
  scalar                  => [ qw(partsgroup) ],
14
  # 'scalar --get_set_init' => [ qw(defaults) ],
15
);
16

  
17
__PACKAGE__->run_before('check_auth');
18
__PACKAGE__->run_before('load_partsgroup', only => [ qw(edit update delete) ]);
19

  
20
sub check_auth {
21
  $::auth->assert('config');
22
}
23

  
24
#
25
# actions
26
#
27

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

  
31
  $self->setup_list_action_bar;
32
  $self->render('partsgroup/list',
33
                 title => $::locale->text('Partsgroups'),
34
                 PARTSGROUPS => SL::DB::Manager::PartsGroup->get_hierarchy,
35
               );
36
}
37

  
38
sub action_sort_roots {
39
  my ($self) = @_;
40

  
41
  # Only for sorting the root partsgroup, and adding new ones.
42
  # The simple arrows don't work on the main hierarchy view, if all subgroups
43
  # are also shown. You would need a proper drag&drop Module for this.
44

  
45
  my @root_partsgroups = grep { $_->{level} == 0 } @{ SL::DB::Manager::PartsGroup->get_hierarchy };
46

  
47
  $self->setup_show_sort_action_bar;
48
  $self->render(
49
    'partsgroup/sort_roots',
50
    title       => t8('Edit partsgroups'),
51
    PARTSGROUPS => \@root_partsgroups,
52
  );
53
}
54

  
55
sub action_new {
56
  my ($self) = @_;
57

  
58
  $self->partsgroup(SL::DB::PartsGroup->new());
59
  $self->show_form(title => t8('Add partsgroup'));
60
}
61

  
62
sub action_edit {
63
  my ($self) = @_;
64

  
65
  $self->show_form(title       => t8('Edit partsgroup'),
66
                   PARTSGROUPS => SL::DB::Manager::PartsGroup->get_hierarchy, # for dropsdown to move parts
67
                   PARTS       => $self->partsgroup->parts,
68
                  );
69
}
70

  
71
sub action_create {
72
  my ($self) = @_;
73

  
74
  $self->partsgroup(SL::DB::PartsGroup->new());
75
  $self->create_or_update;
76
}
77

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

  
81
  $self->create_or_update;
82
}
83

  
84
sub action_delete {
85
  my ($self) = @_;
86

  
87
  $self->partsgroup->db->with_transaction(sub {
88
    $self->partsgroup->delete();
89
    flash_later('info',  $::locale->text('The partsgroup has been deleted.'));
90

  
91
    1;
92
  }) || flash_later('error', $::locale->text('The partsgroup is in use and cannot be deleted.'));
93

  
94
  $self->redirect_to(action => 'list');
95
}
96

  
97
#
98
# ajax actions
99
#
100

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

  
104
  SL::DB::PartsGroup->reorder_list(@{ $::form->{partsgroup_id} || [] });
105

  
106
  $self->render(\'', { type => 'json' });
107
}
108

  
109
sub action_add_partsgroup {
110
  my ($self) = @_;
111

  
112
  unless ( $::form->{partsgroup_name} ) {
113
    return $self->js->flash('error', t8("The name must not be empty."))
114
                    ->render;
115
  };
116

  
117
  # check that name doesn't already exist in this grouping, catch before db constraint
118
  if ( SL::DB::Manager::PartsGroup->get_all_count(
119
    where => [ parent_id  => $::form->{parent_id} // undef,
120
               partsgroup => $::form->{partsgroup_name},
121
             ]) ) {
122
    return $self->js->flash('error', t8("A partsgroup with this name already exists."))
123
                    ->focus('#new_partsgroup')
124
                    ->render;
125
  };
126

  
127
  my %partsgroup_params = (
128
    partsgroup => $::form->{partsgroup_name},
129
  );
130

  
131
  $partsgroup_params{parent_id} = $::form->{parent_id} if $::form->{parent_id};
132

  
133
  my $new_partsgroup = SL::DB::PartsGroup->new(%partsgroup_params);
134
  $new_partsgroup->add_to_list(position => 'last');
135

  
136
  $self->_render_subgroups_table_body;
137
  return $self->js->val('#new_partsgroup', '')
138
                  ->flash('info', t8("Added partsgroup."))
139
                  ->focus('#new_partsgroup')
140
                  ->render;
141
}
142

  
143

  
144
sub action_add_part {
145
  my ($self) = @_;
146

  
147
  $main::lxdebug->dump(0, "add_part form", $::form );
148

  
149
  return $self->js->flash('error', t8("No part was selected."))->render
150
    unless $::form->{part_id};
151

  
152
  my $number_of_updated_parts = SL::DB::Manager::Part->update_all (
153
    set   => { partsgroup_id => $::form->{partsgroup_id} },
154
    where => [ id => $::form->{part_id},
155
               '!partsgroup_id' => $::form->{partsgroup_id}, # ignore updating to same partsgroup_id
156
             ]
157
  );
158

  
159
  if ( $number_of_updated_parts == 1 ) {
160
    $self->_render_parts_table_body; # needs $::form->{partsgroup_id}
161
    return $self->js->val('#add_part_id', undef)
162
                    ->val('#add_part_id_name', '')
163
                    ->flash('info', t8("Added part to partsgroup."))
164
                    ->render;
165
  } else {
166
    return $self->js->flash('error', t8("Part wasn't added to partsgroup!"))->render;
167
  }
168
}
169

  
170
sub action_update_partsgroup_for_parts{
171
  my ($self) = @_;
172

  
173
  $main::lxdebug->dump(0, "update_partsgroup", $::form );
174

  
175
  # change type and design of existing parts to an existing part_variant
176
  # updates part_variant_map entries and lemper_part.type_id and lemper_part.design
177

  
178
  # the id of the partsgroup we are moving parts from is $::form->{current_partsgroup_id}
179

  
180
  return $self->js->flash('error', t8("No parts selected."))->render      unless $::form->{part_ids};
181
  return $self->js->flash('error', t8("No partsgroup selected."))->render unless $::form->{selected_partsgroup_id};
182

  
183
  # don't delete partsgroup ids from form, needed by _render_parts_table_body
184
  # TODO: better error handling than die, use flash?
185
  my $partsgroup = SL::DB::Manager::PartsGroup->find_by(         id => $::form->{selected_partsgroup_id} ) // die 'selected partsgroup id not valid';
186
  my $current_partsgroup = SL::DB::Manager::PartsGroup->find_by( id => $::form->{current_partsgroup_id} )  // die 'not a valid partsgroup id';
187

  
188
  my $part_ids = $::form->{part_ids} // undef;
189
  if ( scalar @{ $part_ids } ) {
190
    my $parts_updated_count = 0;
191
    $current_partsgroup->db->with_transaction(sub {
192
      $parts_updated_count = SL::DB::Manager::Part->update_all (
193
        set   => { partsgroup_id => $partsgroup->id },
194
        where => [ id => $part_ids,
195
                   # partsgroup_id => $current_partsgroup->id
196
                 ], # what if one of them has changed in the meantime due to concurrent edits? should it fail? Currently
197
      );
198
      1;
199
    }) or return $self->js->error(t8('The parts couldn\'t be updated!') . ' ' . $current_partsgroup->db->error )->render;
200
    if ( $parts_updated_count == 1 ) {
201
      $self->js->flash('info', t8("Moved #1 part.", $parts_updated_count));
202
    } else {
203
      $self->js->flash('info', t8("Moved #1 parts.", $parts_updated_count));
204
    }
205
  } else {
206
    $self->js->flash('error', t8("No parts selected"));
207
  }
208

  
209
  $self->_render_parts_table_body; # needs $::form->{current_partsgroup_id}
210
  return $self->js->render;
211
}
212

  
213
#
214
# action bars
215
#
216

  
217
sub setup_show_form_action_bar {
218
  my ($self) = @_;
219

  
220
  my $is_new = !$self->partsgroup->id;
221

  
222
  for my $bar ($::request->layout->get('actionbar')) {
223
    $bar->add(
224
      action => [
225
        t8('Save'),
226
        submit    => [ '#form', { action => 'PartsGroup/' . ($is_new ? 'create' : 'update') } ],
227
        checks    => [ 'kivi.validate_form' ],
228
        accesskey => 'enter',
229
      ],
230

  
231
      action => [
232
        t8('Delete'),
233
        submit   => [ '#form', { action => 'PartsGroup/delete' } ],
234
        confirm  => t8('Do you really want to delete this partsgroup?'),
235
        disabled => $is_new                      ? t8('This partsgroup has not been saved yet.')
236
                  : !$self->partsgroup->orphaned ? t8('The partsgroup is in use and cannot be deleted.')
237
                  :                            undef,
238
      ],
239

  
240
      link => [
241
        t8('Abort'),
242
        link => $self->url_for(action => 'list'),
243
      ],
244
    );
245
  }
246
  $::request->layout->add_javascripts('kivi.Validator.js');
247
}
248

  
249
sub setup_list_action_bar {
250
  my ($self) = @_;
251

  
252
  for my $bar ($::request->layout->get('actionbar')) {
253
    $bar->add(
254
      link => [
255
        t8('Add'),
256
        link => $self->url_for(action => 'new'),
257
      ],
258
      link => [
259
        t8('Sort'),
260
        link => $self->url_for(action => 'sort_roots'),
261
      ],
262
    );
263
  }
264
}
265

  
266
sub setup_show_sort_action_bar {
267
  my ($self) = @_;
268

  
269
  for my $bar ($::request->layout->get('actionbar')) {
270
    $bar->add(
271
      link => [
272
        t8('Partsgroups'),
273
        link => $self->url_for(action => 'list'),
274
      ],
275
    );
276
  }
277
}
278
#
279
# helpers
280
#
281

  
282
sub _render_subgroups_table_body {
283
  my ($self) = @_;
284

  
285
  my ($partsgroup, $partsgroups);
286
  if ( $::form->{parent_id} ) {
287
    $partsgroup  = SL::DB::PartsGroup->new(id => $::form->{parent_id})->load;
288
    $partsgroups = $partsgroup->children_sorted;
289
  } else {
290
    $partsgroups = SL::DB::Manager::PartsGroup->get_all(where => [ parent_id => undef ], sort_by => ('sortkey'));
291
    $main::lxdebug->message(0, "found " . scalar @{ $partsgroups } . " roots");
292
  }
293

  
294
  my $html      = $self->render('partsgroup/_subgroups_table_body', { output => 0 }, CHILDREN => $partsgroups);
295

  
296
  $self->js->html('#subgroups_table_body', $html);
297
}
298

  
299

  
300
sub _render_parts_table_body {
301
  my ($self) = @_;
302

  
303
  # May be called when items are added to the current partsgroup
304
  #   (action_add_part with $::form->{partsgroup_id}
305
  # or after items are moved away to other partsgroups
306
  #   (action_update_partsgroup_for_parts with $::form->{current_partsgroup_id})
307
  my $parts = SL::DB::Manager::Part->get_all(
308
    where => [ partsgroup_id =>    $::form->{current_partsgroup_id}
309
                                // $::form->{partsgroup_id}
310
             ]
311
  );
312
  my $html      = $self->render('partsgroup/_parts_table_body', { output => 0 }, PARTS => $parts);
313
  $self->js->html('#parts_table_body', $html);
314
}
315

  
316
sub create_or_update {
317
  my ($self) = @_;
318
  my $is_new = !$self->partsgroup->id;
319

  
320
  my $params = delete($::form->{partsgroup}) || { };
321

  
322
  delete $params->{id};
323

  
324
  # parent_id needs additional checks
325
  # If the parent_id was changed the new parent_id mustn't have the current
326
  # parent_id as its ancestor, otherwise this would introdouce cycles in the
327
  # tree.
328
  # run this to prevent $params->{parent_id} to be used for assign_attributes
329

  
330
  my $old_parent_id = $self->partsgroup->parent_id; # may be undef
331
  my $new_parent_id = delete $params->{parent_id} || undef; # empty string/select will become undef
332

  
333
  my @errors;
334

  
335
  my $db = $self->partsgroup->db;
336
  if (!$db->with_transaction(sub {
337

  
338
    # assign attributes and validate
339
    $self->partsgroup->assign_attributes( %{$params} ) ;
340
    push(@errors, $self->partsgroup->validate); # check for description
341

  
342
    if (@errors) {
343
      die @errors . "\n";
344
    };
345

  
346
    if (    ( $old_parent_id == $new_parent_id )
347
         or ( !defined $old_parent_id && ! defined $new_parent_id )
348
       ) {
349
      # parent_id didn't change
350
      $self->partsgroup->save;
351

  
352
    } elsif (    ( $old_parent_id != $new_parent_id )
353
              or ( not defined $old_parent_id && $new_parent_id )
354
              or ( $old_parent_id && not defined $new_parent_id) # setting parent to undef is always allowed!
355
            ) {
356
      # parent_id has changed, check for cycles
357
      my $ancestor_ids = SL::DB::PartsGroup->new(id => $new_parent_id)->ancestor_ids;
358

  
359
      if ( any { $self->partsgroup->id == $_ } @{$ancestor_ids} ) {
360
        die "Error: This would introduce a cycle, new parent must not be a subparent\n";
361
      };
362
      $self->partsgroup->remove_from_list;
363
      $self->partsgroup->parent_id($new_parent_id);
364
      $self->partsgroup->add_to_list(position => 'last');
365
    }
366

  
367
    1;
368
  })) {
369
    die @errors ? join("\n", @errors) . "\n" : $db->error . "\n";
370
  }
371

  
372
  flash_later('info', $is_new ? t8('The partsgroup has been created.') : t8('The partsgroup has been saved.'));
373
  $self->redirect_to(action => 'list');
374
}
375

  
376
sub show_form {
377
  my ($self, %params) = @_;
378

  
379
  $self->setup_show_form_action_bar;
380
  $self->render('partsgroup/form', %params,
381
               );
382
}
383

  
384
sub load_partsgroup {
385
  my ($self) = @_;
386

  
387
  $self->partsgroup(SL::DB::PartsGroup->new(id => $::form->{id})->load);
388
}
389

  
390
1;
SL/Controller/SimpleSystemSetting.pm
134 134
    ],
135 135
  },
136 136

  
137
  parts_group => {
138
    # Make locales.pl happy: $self->render("simple_system_setting/_parts_group_form")
139
    class  => 'PartsGroup',
140
    titles => {
141
      list => t8('Partsgroups'),
142
      add  => t8('Add partsgroup'),
143
      edit => t8('Edit partsgroup'),
144
    },
145
    list_attributes => [
146
      { method => 'partsgroup', title => t8('Description') },
147
      { method => 'obsolete',   title => t8('Obsolete'), formatter => sub { $_[0]->obsolete ? t8('yes') : t8('no') } },
148
    ],
149
  },
150

  
151 137
  price_factor => {
152 138
    # Make locales.pl happy: $self->render("simple_system_setting/_price_factor_form")
153 139
    class  => 'PriceFactor',
SL/DB/Manager/PartsGroup.pm
16 16
           columns => { SIMPLE => 'ALL' });
17 17
}
18 18

  
19
sub get_hierarchy {
20
  my (%params) = @_;
21
  # print params is only used for debugging in console
22

  
23
  my @list;
24

  
25
  foreach my $root_pg ( @{ SL::DB::Manager::PartsGroup->get_all( where => [ parent_id => undef ],
26
                                                                 sort_by => ('sortkey'),
27
                                                               ) } ) {
28
    $root_pg->{partscount} = $root_pg->parts_count;
29
    $root_pg->{level} = 0;
30
    push(@list, $root_pg);
31
    $root_pg->printable if $params{print}; # only for debugging
32
    next unless scalar @{ $root_pg->children };
33
    my $iterator = $root_pg->partsgroup_iterator_dfs;
34
    while ( my $pg = $iterator->() ) {
35
      push(@list, $pg);
36
      $pg->{level} = $pg->get_level;
37
      $pg->{partscount} = $pg->parts_count // 0; # probably better to call this separately. Also it doesn't need to be calculated each time for dropdown
38
      # $pg->{padded_partsgroup} = '  ' x $pg->{level} . $pg->partsgroup; # this is probably redundant now, using css
39
			$pg->printable if $params{print};
40
      # $pg->print_report_charts if $params{charts};
41
      # code $pg->printable if $params{tail};
42
    };
43
    # $root_pg->printable if $params{tail};
44
  };
45
  return \@list;
46
}
47

  
19 48
1;
SL/DB/MetaSetup/PartsGroup.pm
14 14
  itime       => { type => 'timestamp', default => 'now()' },
15 15
  mtime       => { type => 'timestamp' },
16 16
  obsolete    => { type => 'boolean', default => 'false' },
17
  parent_id   => { type => 'integer' },
17 18
  partsgroup  => { type => 'text' },
18 19
  sortkey     => { type => 'integer', not_null => 1 },
19 20
);
......
22 23

  
23 24
__PACKAGE__->meta->allow_inline_column_values(1);
24 25

  
26
__PACKAGE__->meta->foreign_keys(
27
  parent => {
28
    class       => 'SL::DB::PartsGroup',
29
    key_columns => { parent_id => 'id' },
30
  },
31
);
32

  
25 33
1;
26 34
;
SL/DB/PartsGroup.pm
8 8
use SL::DB::MetaSetup::PartsGroup;
9 9
use SL::DB::Manager::PartsGroup;
10 10
use SL::DB::Helper::ActsAsList;
11
use SL::DB::Helper::AttrSorted;
12
use SL::DBUtils qw(selectall_array_query);
13

  
14
__PACKAGE__->configure_acts_as_list(group_by => [qw(parent_id)]);
15
__PACKAGE__->attr_sorted({ unsorted => 'children', position => 'sortkey' });
11 16

  
12 17
__PACKAGE__->meta->add_relationship(
13 18
  custom_variable_configs => {
......
18 23
    type         => 'one to many',
19 24
    class        => 'SL::DB::Part',
20 25
    column_map   => { id => 'partsgroup_id' },
26
    add_methods  => ['count'],
21 27
  },
28
 children  => {
29
   type         => 'one to many',
30
   class        => 'SL::DB::PartsGroup',
31
   column_map   => { id => 'parent_id' },
32
   add_methods  => ['count'],
33
 }
22 34
);
23 35

  
24 36
__PACKAGE__->meta->initialize;
......
26 38
sub displayable_name {
27 39
  my $self = shift;
28 40

  
29
  return join ' ', grep $_, $self->id, $self->partsgroup;
41
  return $self->partsgroup;
42
}
43

  
44
sub indented_name {
45
  my $self = shift;
46
  # used for label in select_tag
47

  
48
  return '  -  ' x $self->get_level . $self->partsgroup;
30 49
}
31 50

  
32 51
sub validate {
......
59 78
  eval "require SL::DB::PriceRuleItem";
60 79
  return 0 if SL::DB::Manager::PriceRuleItem->get_all_count(query => [ type => 'partsgroup', value_int => $self->id ]);
61 80

  
81
  return 0 if SL::DB::Manager::PartsGroup->get_all_count(query => [ parent_id => $self->id ]);
82

  
62 83
  return 1;
63 84
}
64 85

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

  
89
  my @ancestors = ();
90
  my $pg = $self;
91

  
92
  return \@ancestors unless defined $self->parent;
93

  
94
  while ( $pg->parent_id ) {
95
    $pg = $pg->parent;
96
    unshift(@ancestors, $pg);
97
  };
98

  
99
  return \@ancestors;
100
}
101

  
102
sub ancestor_ids {
103
  my ($self) = @_;
104

  
105
  my $query = <<SQL;
106
WITH RECURSIVE rec (id) as
107
(
108
  SELECT partsgroup.id, partsgroup.parent_id from partsgroup where partsgroup.id = ?
109
  UNION ALL
110
  SELECT partsgroup.id, partsgroup.parent_id from rec, partsgroup where partsgroup.id = rec.parent_id
111
)
112
SELECT id as ancestors
113
  FROM rec
114
SQL
115
  my @ids = selectall_array_query($::form, $self->dbh, $query, $self->id);
116
  return \@ids;
117
}
118

  
119
sub partsgroup_iterator_dfs {
120
  # partsgroup iterator that starts with a partsgroup, using depth first search
121
  # to iterate over partsgroups you have to first find the roots (level 0) and
122
  # then use this method to recursively dig down and find all the children
123
  my ($self) = @_;
124
  my @queue ;
125

  
126
  @queue = @{ $self->children_sorted } if $self->children_count;
127

  
128
  return sub {
129
    while ( @queue ) {
130
      my $pg = shift @queue;
131

  
132
      if ( scalar @{ $pg->children_sorted } ) {
133
        unshift @queue, @{ $pg->children_sorted };
134
      };
135
      return $pg;
136
    };
137
    return;
138
  };
139
}
140

  
141
sub get_level {
142
  my ($self) = @_;
143
  # iterate through parents to calculate the level
144

  
145
  return 0 unless defined $self->parent;
146
  return $self->{cached_level} if exists $self->{cached_level};
147
  my $level = 1;
148
  my $parent = $self->parent;
149
  while ( $parent->parent ) {
150
    $level++;
151
    $parent = $parent->parent;
152
  };
153
  return $self->{cached_level} = $level;
154
}
155

  
65 156
1;
doc/changelog
983 983
 - komplette Überarbeitung der Standard-LaTeX-Druckvorlagen von PeiTeX
984 984
   S.a.: templates/print/marei/Readme.md
985 985

  
986
<<<<<<< HEAD
986 987
 - Erstellung von ZUGFeRD 2.0 fähigen PDFs
987 988
 - Verarbeitung von ZUGFeRD 2.0 kompatiblen Eingangsrechnungen über
988 989
   Kreditorenbuchungsvorlagen
......
1023 1024

  
1024 1025

  
1025 1026
2019-12-11 - Release 3.5.5
1027
 - Warengruppen können nun verschachtelt angelegt werden, d.h. Warengruppen
1028
   können Untergruppen enthalten
1026 1029

  
1027 1030
Mittelgroße neue Features:
1028 1031

  
locale/de/all
78 78
  'A directory with the name for the new print templates exists already.' => 'Ein Verzeichnis mit dem selben Namen wie die neuen Druckvorlagen existiert bereits.',
79 79
  'A lot of the usability of kivitendo has been enhanced with javascript. Although it is currently possible to use every aspect of kivitendo without javascript, we strongly recommend it. In a future version this may change and javascript may be necessary to access advanced features.' => 'Die Bedienung von kivitendo wurde an vielen Stellen mit Javascript verbessert. Obwohl es derzeit möglich ist, jeden Aspekt von kivitendo auch ohne Javascript zu benutzen, empfehlen wir es. In einer zukünftigen Version wird Javascript eventuell notwendig sein um weitergehende Features zu benutzen.',
80 80
  'A lower-case character is required.' => 'Ein Kleinbuchstabe ist vorgeschrieben.',
81
  'A partsgroup with this name already exists.' => 'Eine Warengruppe mit diesem Namen existiert bereits.',
81 82
  'A payment can only be posted for multiple invoices if the amount to post is equal to or bigger than the sum of the open amounts of the affected invoices.' => 'Eine Zahlung kann nur dann für mehrere Rechnungen verbucht werden, wenn die Zahlung gleich oder größer als die Summe der offenen Beträge der betroffenen Rechnungen ist.',
82 83
  'A special character is required (valid characters: #1).' => 'Ein Sonderzeichen ist vorgeschrieben (gültige Zeichen: #1).',
83 84
  'A target quantitiy has to be given' => 'Es muss eine Zielmenge angegeben werden',
......
293 294
  'Add to basket'               => 'Einkaufen',
294 295
  'Add to purchase basket'      => 'Markierte in den Warenkorb legen',
295 296
  'Add unit'                    => 'Einheit hinzufügen',
297
  'Added part to partsgroup.'   => 'Der Artikel wurde der Warengruppe hinzugefügt.',
298
  'Added partsgroup.'           => 'Die Warengruppe wurde hinzugefügt.',
296 299
  'Added sections and function blocks: #1' => 'Hinzugefügte Abschnitte und Funktionsblöcke: #1',
297 300
  'Added text blocks: #1'       => 'Hinzugefügte Textblöcke: #1',
298 301
  'Addition'                    => 'Zusatz',
......
1303 1306
  'Do you really want to delete this draft?' => 'Möchten Sie diesen Entwurf wirklich löschen?',
1304 1307
  'Do you really want to delete this object?' => 'Möchten Sie dieses Objekt wirklich löschen?',
1305 1308
  'Do you really want to delete this reclamation reason?' => 'Möchten Sie diesen Reklamationsgrund wirklich löschen?',
1309
  'Do you really want to delete this partsgroup?' => 'Möchten Sie diese Warengruppe wirklich löschen?',
1306 1310
  'Do you really want to delete this record template?' => 'Möchten Sie diese Belegvorlage wirklich löschen?',
1307 1311
  'Do you really want to mark the selected entries as booked?' => 'Möchten Sie die ausgewählten Einträge wirklich als gebucht markieren?',
1308 1312
  'Do you really want to print?' => 'Wollen Sie wirklich drucken?',
......
1478 1482
  'Edit note'                   => 'Notiz bearbeiten',
1479 1483
  'Edit part classification'    => 'Artikel-Klassifizierung bearbeiten',
1480 1484
  'Edit partsgroup'             => 'Warengruppe bearbeiten',
1485
  'Edit partsgroups'            => 'Warengruppen bearbeiten',
1481 1486
  'Edit payment term'           => 'Zahlungsbedingungen bearbeiten',
1482 1487
  'Edit picture'                => 'Bild bearbeiten',
1483 1488
  'Edit pre-defined text'       => 'Vordefinierten Textblock bearbeiten',
......
2436 2441
  'Monthly'                     => 'monatlich',
2437 2442
  'More than one control file with the tag \'%s\' exist.' => 'Es gibt mehr als eine Kontrolldatei mit dem Tag \'%s\'.',
2438 2443
  'More than one file selected, please set only one checkbox!' => 'Mehr als ein Element selektiert, bitte nur eine Box anklicken',
2444
  'Move selected parts to partsgroup' => 'Ausgewählte Artikel nach Warengruppe verschieben',
2445
  'Moved #1 part.'              => '#1 Artikel wurde verschoben.',
2446
  'Moved #1 parts.'             => '#1 Artikel wurden verschoben',
2439 2447
  'Multi mode not supported.'   => 'Multimodus wird nicht unterstützt.',
2440 2448
  'Multiple addresses can be entered separated by commas.' => 'Mehrere Adressen können durch Kommata getrennt angegeben werden.',
2441 2449
  'MwSt. inkl.'                 => 'MwSt. inkl.',
......
2559 2567
  'No internal phone extensions have been configured yet.' => 'Es wurden noch keine internen Durchwahlen konfiguriert.',
2560 2568
  'No invoice email found.'     => 'Keine Rechnungsmailadresse gefunden.',
2561 2569
  'No invoices have been selected.' => 'Es wurden keine Rechnungen ausgewählt.',
2562
  'No part was selected.'       => 'Es wurde kein Artikel ausgewählt',
2570
  'No part was selected.'       => 'Es wurde kein Artikel ausgewählt.',
2571
  'No parts selected.'          => 'Es wurden keine Artikel ausgewählt.',
2572
  'No partsgroup selected.'     => 'Es wurde keine Warengruppen ausgewählt.',
2563 2573
  'No payment term has been created yet.' => 'Es wurden noch keine Zahlungsbedingungen angelegt.',
2564 2574
  'No picture has been uploaded' => 'Es wurde kein Bild hochgeladen',
2565 2575
  'No picture uploaded yet'     => 'Noch kein Bild hochgeladen',
......
2664 2674
  'Number of months'            => 'Anzahl Monate',
2665 2675
  'Number of new bins'          => 'Anzahl neuer Lagerplätze',
2666 2676
  'Number of orders created:'   => 'Anzahl Aufträge erstellt',
2677
  'Number of parts'             => 'Anzahl Artikel',
2667 2678
  'Number pages'                => 'Seiten nummerieren',
2668 2679
  'Number variables: \'PRECISION=n\' forces numbers to be shown with exactly n decimal places.' => 'Zahlenvariablen: Mit \'PRECISION=n\' erzwingt man, dass Zahlen mit n Nachkommastellen formatiert werden.',
2669 2680
  'Numbers'                     => 'Nummern',
......
2800 2811
  'Parameter module must be given.' => 'Der Parameter "module" miss angegeben werden.',
2801 2812
  'Parsing the XML data failed: #1' => 'Parsen der XML-Daten fehlgeschlagen: #1',
2802 2813
  'Parsing the XMP metadata failed.' => 'Parsen der XMP-Metadaten schlug fehl.',
2814
  'Parents'                     => 'Obergruppen',
2803 2815
  'Part'                        => 'Ware',
2804 2816
  'Part "#1" has chargenumber or best before date set. So it cannot be transfered automatically.' => 'Bei Artikel "#1" ist eine Chargenummer oder ein Mindesthaltbarkeitsdatum vergeben. Deshalb kann dieser Artikel nicht automatisch ausgelagert werden.',
2805 2817
  'Part #1 exists in warehouse #2, but not in warehouse #3 ' => 'Artikel #1 existiert im Lager #2, aber nicht im Lager #3',
......
2824 2836
  'Part picker'                 => 'Artikelauswahl',
2825 2837
  'Part picker search dialog (magnifying glass): "all as list" defaults to on' => 'Artikel-Such-Dialog (Lupe): "alle als Liste" voreinstellen',
2826 2838
  'Part successful counted'     => 'Der Artikel wurde gezählt',
2839
  'Part wasn\'t added to partsgroup!' => 'Der Artikel wurde nicht der Warengruppe hinzugefügt!',
2827 2840
  'Part with partnumber: #1 not found' => 'Artikel mit Artikelnummer #1 wurde nicht gefunden',
2828 2841
  'PartClassAbbreviation'       => 'Abkürzung der Artikel-Klassifizierung',
2829 2842
  'Part_br_Description'         => 'Beschreibung',
......
3772 3785
  'Solution'                    => 'Lösung',
3773 3786
  'Sorry, I am too stupid to figure out the default warehouse/bin and the sold qty. I drop the default warehouse/bin option.' => 'Entschuldigung, ich bin nicht in der Lage Standard-Lager und die Menge in gewählten Belegen gleichzeitig anzuzeigen. Ich lass die Standard-Lager weg.',
3774 3787
  'Sort'                        => 'Sortieren',
3788
  'Sort & Save'                 => '',
3775 3789
  'Sort By'                     => 'Sortiert nach',
3776 3790
  'Sort by'                     => 'Sortieren nach',
3777 3791
  'Sort order'                  => 'Sortierfolge',
......
3857 3871
  'Style the picture with the following CSS code' => 'Bildeigenschaft mit folgendem CSS-Style versehen',
3858 3872
  'Stylesheet'                  => 'Stilvorlage',
3859 3873
  'Sub function blocks'         => 'Unterfunktionsblöcke',
3874
  'Subgroups'                   => 'Untergruppen',
3860 3875
  'Subject'                     => 'Betreff',
3861 3876
  'Subject:'                    => 'Betreff:',
3862 3877
  'Subtotal'                    => 'Zwischensumme',
......
4213 4228
  'The parts for this order have already been transferred' => 'Die Artikel in diesem Lieferschein wurden schon umgelagert',
4214 4229
  'The parts have been removed.' => 'Die Waren wurden aus dem Lager entnommen.',
4215 4230
  'The parts have been transferred.' => 'Die Waren wurden umgelagert.',
4231
  'The partsgroup has been created.' => 'Die Warengruppe wurde erstellt.',
4232
  'The partsgroup has been deleted.' => 'Die Warengruppe wurde gelöscht.',
4233
  'The partsgroup has been saved.' => 'Die Warengruppe wurde gespeichert.',
4234
  'The partsgroup is in use and cannot be deleted.' => 'Die Warengruppe wird verwendet und kann nicht gelöscht werden.',
4216 4235
  'The partsgroup is missing.'  => 'Die Warengruppe fehlt.',
4217 4236
  'The password is too long (maximum length: #1).' => 'Das Passwort ist zu lang (maximale Länge: #1).',
4218 4237
  'The password is too short (minimum length: #1).' => 'Das Password ist zu kurz (minimale Länge: #1).',
......
4474 4493
  'This part has already been added.' => 'Dieser Artikel wurde schon hinzugefügt',
4475 4494
  'This part should not be ordered any more.' => 'Dieser Artikel kann nicht mehr bestellt werden.',
4476 4495
  'This part was already counted for this bin:' => 'Dieser Artikel wurde für diesen Lagerplatz bereits erfasst:',
4496
  'This partsgroup has not been saved yet.' => 'Die Warengruppe wurde noch nicht gespeichert.',
4477 4497
  'This price has since gone down' => 'Dieser Preis ist mittlerweile niedriger',
4478 4498
  'This price has since gone up' => 'Dieser Preis ist mittlerweile höher',
4479 4499
  'This record contains not orderable items at position #1' => 'Dieser Beleg enthält nicht bestellbare Artikel an Position #1',
locale/en/all
78 78
  'A directory with the name for the new print templates exists already.' => '',
79 79
  'A lot of the usability of kivitendo has been enhanced with javascript. Although it is currently possible to use every aspect of kivitendo without javascript, we strongly recommend it. In a future version this may change and javascript may be necessary to access advanced features.' => '',
80 80
  'A lower-case character is required.' => '',
81
  'A partsgroup with this name already exists.' => '',
81 82
  'A payment can only be posted for multiple invoices if the amount to post is equal to or bigger than the sum of the open amounts of the affected invoices.' => '',
82 83
  'A special character is required (valid characters: #1).' => '',
83 84
  'A target quantitiy has to be given' => '',
......
293 294
  'Add to basket'               => '',
294 295
  'Add to purchase basket'      => '',
295 296
  'Add unit'                    => '',
297
  'Added part to partsgroup.'   => '',
298
  'Added partsgroup.'           => '',
296 299
  'Added sections and function blocks: #1' => '',
297 300
  'Added text blocks: #1'       => '',
298 301
  'Addition'                    => '',
......
1303 1306
  'Do you really want to delete this draft?' => '',
1304 1307
  'Do you really want to delete this object?' => '',
1305 1308
  'Do you really want to delete this reclamation reason?' => '',
1309
  'Do you really want to delete this partsgroup?' => '',
1306 1310
  'Do you really want to delete this record template?' => '',
1307 1311
  'Do you really want to mark the selected entries as booked?' => '',
1308 1312
  'Do you really want to print?' => '',
......
1478 1482
  'Edit note'                   => '',
1479 1483
  'Edit part classification'    => '',
1480 1484
  'Edit partsgroup'             => '',
1485
  'Edit partsgroups'            => '',
1481 1486
  'Edit payment term'           => '',
1482 1487
  'Edit picture'                => '',
1483 1488
  'Edit pre-defined text'       => '',
......
2435 2440
  'Monthly'                     => '',
2436 2441
  'More than one control file with the tag \'%s\' exist.' => '',
2437 2442
  'More than one file selected, please set only one checkbox!' => '',
2443
  'Move selected parts to partsgroup' => '',
2444
  'Moved #1 part.'              => '',
2445
  'Moved #1 parts.'             => '',
2438 2446
  'Multi mode not supported.'   => '',
2439 2447
  'Multiple addresses can be entered separated by commas.' => '',
2440 2448
  'MwSt. inkl.'                 => '',
......
2559 2567
  'No invoice email found.'     => '',
2560 2568
  'No invoices have been selected.' => '',
2561 2569
  'No part was selected.'       => '',
2570
  'No partsgroup selected.'     => '',
2562 2571
  'No payment term has been created yet.' => '',
2563 2572
  'No picture has been uploaded' => '',
2564 2573
  'No picture uploaded yet'     => '',
......
2663 2672
  'Number of months'            => '',
2664 2673
  'Number of new bins'          => '',
2665 2674
  'Number of orders created:'   => '',
2675
  'Number of parts'             => '',
2666 2676
  'Number pages'                => '',
2667 2677
  'Number variables: \'PRECISION=n\' forces numbers to be shown with exactly n decimal places.' => '',
2668 2678
  'Numbers'                     => '',
......
2799 2809
  'Parameter module must be given.' => '',
2800 2810
  'Parsing the XML data failed: #1' => '',
2801 2811
  'Parsing the XMP metadata failed.' => '',
2812
  'Parents'                     => '',
2802 2813
  'Part'                        => '',
2803 2814
  'Part "#1" has chargenumber or best before date set. So it cannot be transfered automatically.' => '',
2804 2815
  'Part #1 exists in warehouse #2, but not in warehouse #3 ' => '',
......
3856 3867
  'Style the picture with the following CSS code' => '',
3857 3868
  'Stylesheet'                  => '',
3858 3869
  'Sub function blocks'         => '',
3870
  'Subgroups'                   => '',
3859 3871
  'Subject'                     => '',
3860 3872
  'Subject:'                    => '',
3861 3873
  'Subtotal'                    => '',
......
4211 4223
  'The parts for this order have already been transferred' => '',
4212 4224
  'The parts have been removed.' => '',
4213 4225
  'The parts have been transferred.' => '',
4226
  'The partsgroup has been created.' => '',
4227
  'The partsgroup has been deleted.' => '',
4228
  'The partsgroup has been saved.' => '',
4229
  'The partsgroup is in use and cannot be deleted.' => '',
4214 4230
  'The partsgroup is missing.'  => '',
4215 4231
  'The password is too long (maximum length: #1).' => '',
4216 4232
  'The password is too short (minimum length: #1).' => '',
......
4472 4488
  'This part has already been added.' => '',
4473 4489
  'This part should not be ordered any more.' => '',
4474 4490
  'This part was already counted for this bin:' => '',
4491
  'This partsgroup has not been saved yet.' => '',
4475 4492
  'This price has since gone down' => '',
4476 4493
  'This price has since gone up' => '',
4477 4494
  'This record contains not orderable items at position #1' => '',
menus/user/00-erp.yaml
1193 1193
  name: Partsgroups
1194 1194
  order: 900
1195 1195
  params:
1196
    action: SimpleSystemSetting/list
1197
    type: parts_group
1196
    action: PartsGroup/list
1198 1197
- parent: system
1199 1198
  id: system_part_classification
1200 1199
  name: Parts Classification
sql/Pg-upgrade2/partsgroup_adjacency.sql
1
-- @tag: partsgroup_adjacency
2
-- @description: Warengruppe um parent_id erweitern
3
-- @depends: release_3_5_5 partsgroup_description
4

  
5
-- There is no specific code for upgrading from older versions, all existing
6
-- partsgroups start with parent_id NULL, which makes them top level
7
-- partsgroups
8

  
9
ALTER TABLE partsgroup ADD COLUMN parent_id INT REFERENCES partsgroup(id);
10

  
11
ALTER TABLE partsgroup ADD CONSTRAINT partsgroup_zero_cycle_check CHECK (id <> parent_id);
12

  
13
-- need to check during upgrade if they are unique, otherwise allow user to edit them (like upgrade for parts)
14
ALTER TABLE partsgroup ADD CONSTRAINT partsgroup_unique UNIQUE (partsgroup, parent_id);
15

  
16
-- this doesn't work for parent_id is null, allows all top level partsgroups to have the same sortkey
17
-- also doesn't seem to work for certain add_to_list / remove_from_list method
18
-- ALTER TABLE partsgroup ADD CONSTRAINT partsgroup_sortkey_unique UNIQUE (sortkey, parent_id);
templates/webpages/partsgroup/_parts_table_body.html
1
[%- USE HTML -%][%- USE LxERP -%][%- USE L -%][%- USE T8 -%][% USE P %]
2
[% FOREACH part = PARTS %]
3
  <tr>
4
   <td>[% L.checkbox_tag('part_ids[]', value=part.id, checked=part.selected, "data-checkall"=1) %]</td>
5
   <td><a href="[% SELF.url_for(controller = 'Part', action = 'edit', 'part.id' = part.id) %]">[% part.displayable_name | html %]</a></td>
6
  </tr>
7
[% END %]
8

  
9
[% # L.dump(PARTS) %]
templates/webpages/partsgroup/_subgroups_table_body.html
1
[%- USE HTML -%][%- USE LxERP -%][%- USE L -%][%- USE T8 -%][% USE P %]
2
[% FOREACH child = CHILDREN %]
3
    <tr class="listrow" id="partsgroup_id_[% child.id %]">
4
     <td align="center" class="dragdrop"><img src="image/updown.png" alt="[%- LxERP.t8('reorder item') %]"></td>
5
     <td><a href="[% SELF.url_for(action='edit', id=child.id) %]">[% HTML.escape(child.partsgroup) %]</a></td>
6
     <td>[% IF child.obsolete %][% 'Yes' | $T8 %][% ELSE %][% 'No' | $T8 %][% END %]</td>
7
  </tr>
8
[% END %]
templates/webpages/partsgroup/form.html
1
[%- USE HTML -%][%- USE LxERP -%][%- USE L -%][%- USE P -%][%- USE T8 -%]
2
[% INCLUDE 'common/flash.html' %]
3

  
4
<h1>[% HTML.escape(title) %]</h1>
5
[% SET style="width: 400px" %]
6

  
7
<form action="controller.pl" method="post" id="form">
8
[%- L.hidden_tag("id", SELF.partsgroup.id) %]
9

  
10
<table>
11
  <tr>
12
    <th align="right">[% 'Partsgroup' | $T8 %]</th>
13
    <td>[%- L.input_tag("partsgroup.partsgroup", SELF.partsgroup.partsgroup, "data-validate"="required", "data-title"=LxERP.t8("Description")) %]</td>
14
  </tr>
15
  <tr>
16
    <th align="right">[% 'Description' | $T8 %]</th>
17
    <td>[%- L.textarea_tag("partsgroup.description", SELF.partsgroup.description, cols = 50 rows = 2, "data-title"=LxERP.t8("Description")) %]</td>
18
  </tr>
19
  <tr>
20
    <th align="right">[% 'Obsolete' | $T8 %]</th>
21
    <td>[% L.checkbox_tag('partsgroup.obsolete', checked = SELF.partsgroup.obsolete, for_submit=1) %]</td>
22
  </tr>
23

  
24
  [% IF SELF.partsgroup.id %]
25
  <tr>
26
    <th align="right">[% 'Parents' | $T8 %]</th>
27
    <td>
28
    [% FOREACH ancestor = SELF.partsgroup.ancestors %]
29
      [% IF loop.last %]
30
      [%- L.select_tag('partsgroup.parent_id', PARTSGROUPS, default=SELF.partsgroup.parent_id, title_key='indented_name', value_key='id', with_empty=1 style='width: 200px') %]
31
      [% ELSE %]
32
    <a href="[% SELF.url_for(action='edit', id=ancestor.id) %]">[% HTML.escape(ancestor.partsgroup) %]</a> ->
33
      [% END %]
34
    [% END %]
35
    [% IF SELF.partsgroup.ancestors.size == 0 %]
36
      [%- L.select_tag('partsgroup.parent_id', PARTSGROUPS, title_key='indented_name', value_key='id', with_empty=1 style='width: 200px') %]
37
    [% END %]
38
    </td>
39
  </tr>
40
  [% END %]
41
 </table>
42

  
43
</form>
44

  
45
  [% IF SELF.partsgroup.id %]
46

  
47
<h2>[% 'Subgroups' | $T8 %]</h2>
48
<table id="subgroups">
49
 <thead>
50
  <tr>
51
    <th align="center" width="1%"><img src="image/updown.png" alt="[%- LxERP.t8('reorder item') %]"></th>
52
    <th align="left">[% 'Subgroups' | $T8 %]</th>
53
    <th align="left">[% 'Obsolete'  | $T8 %]</th>
54
  </tr>
55
 </thead>
56

  
57
 <tbody id="subgroups_table_body">
58
  [% PROCESS 'partsgroup/_subgroups_table_body.html', CHILDREN = SELF.partsgroup.children_sorted %]
59
  </tbody>
60
</table>
61

  
62
[% L.sortable_element('#subgroups tbody', url=SELF.url_for(action='reorder'), with='partsgroup_id') %]
63

  
64
[%- L.input_tag("new_partsgroup", '', "data-title"=LxERP.t8("Partsgroup")) %] [% L.button_tag("add_partsgroup()", LxERP.t8('Insert new')) %]
65

  
66
<h2>[% 'Parts' | $T8 %]</h2>
67

  
68
<form id="parts_form" name="parts_form">
69
<table id="parts_in_partsgroup">
70
 <table>
71
  <thead>
72
   <tr class="listheading">
73
    <th>[% L.checkbox_tag("", id="check_all", checkall="[data-checkall=1]") %]</th>
74
    <th>[% LxERP.t8("Part Number") %]</th>
75
   </tr>
76
  </thead>
77
  <tbody id="parts_table_body">
78
  [% PROCESS 'partsgroup/_parts_table_body.html', PARTS = SELF.partsgroup.parts %]
79
  </tbody>
80
</table>
81
</form>
82

  
83
<table>
84
<body>
85
<tr>
86
 <th align="right">[% 'Partsgroup' | $T8 %]</th>
87
 <td>[%- L.select_tag('selected_partsgroup', PARTSGROUPS, title_key='indented_name', value_key='id', with_empty=1 style='width: 200px') %]</td>
88
 <td>[% L.button_tag('move_parts_to_partsgroup()', LxERP.t8('Move selected parts to partsgroup')) %]</td>
89
</tr>
90
</body>
91
</table>
92

  
93
<table>
94
<tr>
95
 <th align="right">[% 'Part' | $T8 %]</th>
96
 <td>[% P.part.picker('add_part_id', '', style='width: 300px') %]</td>
97
 <td>[% L.button_tag("add_part()", LxERP.t8('Insert new')) %]</td>
98
</tr>
99
</table>
100

  
101
[% END %]
102

  
103
<script type='text/javascript'>
104
  function move_parts_to_partsgroup() {
105
    var data = $('#parts_form').serializeArray();
106
    data.push({ name: 'current_partsgroup_id',  value: '[% SELF.partsgroup.id %]' });
107
    data.push({ name: 'selected_partsgroup_id', value: $("#selected_partsgroup").val() });
108
    data.push({ name: 'action',                   value: 'PartsGroup/update_partsgroup_for_parts' });
109
    $.post("controller.pl", data, kivi.eval_json_result);
110
  }
111

  
112
  function add_part() {
113
    var data = {
114
      action:        'PartsGroup/add_part',
115
      part_id:       $('#add_part_id').val(),
116
      partsgroup_id: $('#id').val()
117
    };
118
    $.post("controller.pl", data, kivi.eval_json_result);
119
  }
120

  
121
  function add_partsgroup() {
122
    var data = {
123
      action:          'PartsGroup/add_partsgroup',
124
      parent_id:       $('#id').val(),
125
      partsgroup_name: $('#new_partsgroup').val()
126
    };
127
    $.post("controller.pl", data, kivi.eval_json_result);
128
  }
129
</script>
templates/webpages/partsgroup/list.html
1
[%- USE HTML -%][%- USE LxERP -%][%- USE L -%][%- USE T8 -%]
2
[%- INCLUDE 'common/flash.html' %]
3

  
4
<h1>[% title %]</h1>
5

  
6
<p>
7
 <table id="partsgroup_list">
8
  <thead>
9
   <tr class="listheading">
10
    <th>[% 'Partsgroup'      | $T8 %]</th>
11
    <th>[% 'Number of parts' | $T8 %]</th>
12
    <th>[% 'Obsolete'        | $T8 %]</th>
13
   </tr>
14
  </thead>
15

  
16
  <tbody>
17
   [%- FOREACH pg = PARTSGROUPS %]
18
    <tr class="listrow" id="pg_id_[% pg.id %]">
19
     <td style="padding-left:[% (pg.level) * 2 %]em"><a href="[% SELF.url_for(action='edit', id=pg.id) %]">[% HTML.escape(pg.partsgroup) %]
20
[% # pg.level %]</a></td>
21
    <td>[% pg.partscount | html %]</td>
22
    <td>[% IF pg.obsolete %][% 'Yes' | $T8 %][% ELSE %][% 'No' | $T8 %][% END %]</td>
23
   [%- END %]
24
  </tbody>
25
 </table>
26
</p>
templates/webpages/partsgroup/sort_roots.html
1
[%- USE HTML -%][%- USE LxERP -%][%- USE L -%][%- USE P -%][%- USE T8 -%]
2
[% INCLUDE 'common/flash.html' %]
3

  
4
<h1>[% HTML.escape(title) %]</h1>
5
[% SET style="width: 400px" %]
6

  
7
<table id="subgroups">
8
 <thead>
9
  <tr>
10
    <th align="center"><img src="image/updown.png" alt="[%- LxERP.t8('reorder item') %]"></th>
11
    <th align="left">[% 'Partsgroups' | $T8 %]</th>
12
    <th align="left">[% 'Obsolete'    | $T8 %]</th>
13
  </tr>
14
 </thead>
15

  
16
 <tbody id="subgroups_table_body">
17
  [% PROCESS 'partsgroup/_subgroups_table_body.html', CHILDREN = PARTSGROUPS %]
18
  </tbody>
19
</table>
20
</div>
21

  
22
[% L.sortable_element('#subgroups tbody', url=SELF.url_for(action='reorder'), with='partsgroup_id') %]
23

  
24
[%- L.input_tag("new_partsgroup", '', "data-title"=LxERP.t8("Partsgroup")) %] [% L.button_tag("add_partsgroup()", LxERP.t8('Add')) %]
25

  
26
<script type='text/javascript'>
27
  function add_partsgroup() {
28
    console.log('called add_part');
29
    var data = {
30
      action:          'PartsGroup/add_partsgroup',
31
      // parent_id:       $('#id').val(),
32
      partsgroup_name: $('#new_partsgroup').val()
33
    };
34
    $.post("controller.pl", data, kivi.eval_json_result);
35
  }
36
</script>
templates/webpages/simple_system_setting/_parts_group_form.html
1
[%- USE LxERP -%][%- USE L -%]
2
<table>
3
 <tr>
4
  <th align="right">[% LxERP.t8("Description") %]</th>
5
  <td>
6
   [%- L.input_tag("object.partsgroup", SELF.object.partsgroup, "data-validate"="required", "data-title"=LxERP.t8("Description")) %]
7
  </td>
8
 </tr>
9
 <tr>
10
  <th align="right">[% LxERP.t8("Obsolete") %]</th>
11
  <td>[% L.checkbox_tag("object.obsolete", checked=SELF.object.obsolete, for_submit=1) %]</td>
12
 </tr>
13
</table>

Auch abrufbar als: Unified diff