Revision a45417f3
Von Kivitendo Admin vor 9 Monaten hinzugefügt
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 | ||
---|---|---|
762 | 762 |
- komplette Überarbeitung der Standard-LaTeX-Druckvorlagen von PeiTeX |
763 | 763 |
S.a.: templates/print/marei/Readme.md |
764 | 764 |
|
765 |
<<<<<<< HEAD |
|
765 | 766 |
- Erstellung von ZUGFeRD 2.0 fähigen PDFs |
766 | 767 |
- Verarbeitung von ZUGFeRD 2.0 kompatiblen Eingangsrechnungen über |
767 | 768 |
Kreditorenbuchungsvorlagen |
... | ... | |
802 | 803 |
|
803 | 804 |
|
804 | 805 |
2019-12-11 - Release 3.5.5 |
806 |
- Warengruppen können nun verschachtelt angelegt werden, d.h. Warengruppen |
|
807 |
können Untergruppen enthalten |
|
805 | 808 |
|
806 | 809 |
Mittelgroße neue Features: |
807 | 810 |
|
locale/de/all | ||
---|---|---|
77 | 77 |
'A directory with the name for the new print templates exists already.' => 'Ein Verzeichnis mit dem selben Namen wie die neuen Druckvorlagen existiert bereits.', |
78 | 78 |
'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.', |
79 | 79 |
'A lower-case character is required.' => 'Ein Kleinbuchstabe ist vorgeschrieben.', |
80 |
'A partsgroup with this name already exists.' => 'Eine Warengruppe mit diesem Namen existiert bereits.', |
|
80 | 81 |
'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.', |
81 | 82 |
'A special character is required (valid characters: #1).' => 'Ein Sonderzeichen ist vorgeschrieben (gültige Zeichen: #1).', |
82 | 83 |
'A target quantitiy has to be given' => 'Es muss eine Zielmenge angegeben werden', |
... | ... | |
295 | 296 |
'Add to basket' => 'Einkaufen', |
296 | 297 |
'Add to purchase basket' => 'Markierte in den Warenkorb legen', |
297 | 298 |
'Add unit' => 'Einheit hinzufügen', |
299 |
'Added part to partsgroup.' => 'Der Artikel wurde der Warengruppe hinzugefügt.', |
|
300 |
'Added partsgroup.' => 'Die Warengruppe wurde hinzugefügt.', |
|
298 | 301 |
'Added sections and function blocks: #1' => 'Hinzugefügte Abschnitte und Funktionsblöcke: #1', |
299 | 302 |
'Added text blocks: #1' => 'Hinzugefügte Textblöcke: #1', |
300 | 303 |
'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', |
... | ... | |
2424 | 2429 |
'Monthly' => 'monatlich', |
2425 | 2430 |
'More than one control file with the tag \'%s\' exist.' => 'Es gibt mehr als eine Kontrolldatei mit dem Tag \'%s\'.', |
2426 | 2431 |
'More than one file selected, please set only one checkbox!' => 'Mehr als ein Element selektiert, bitte nur eine Box anklicken', |
2432 |
'Move selected parts to partsgroup' => 'Ausgewählte Artikel nach Warengruppe verschieben', |
|
2433 |
'Moved #1 part.' => '#1 Artikel wurde verschoben.', |
|
2434 |
'Moved #1 parts.' => '#1 Artikel wurden verschoben', |
|
2427 | 2435 |
'Multi mode not supported.' => 'Multimodus wird nicht unterstützt.', |
2428 | 2436 |
'Multiple addresses can be entered separated by commas.' => 'Mehrere Adressen können durch Kommata getrennt angegeben werden.', |
2429 | 2437 |
'MwSt. inkl.' => 'MwSt. inkl.', |
... | ... | |
2546 | 2554 |
'No internal phone extensions have been configured yet.' => 'Es wurden noch keine internen Durchwahlen konfiguriert.', |
2547 | 2555 |
'No invoice email found.' => 'Keine Rechnungsmailadresse gefunden.', |
2548 | 2556 |
'No invoices have been selected.' => 'Es wurden keine Rechnungen ausgewählt.', |
2549 |
'No part was selected.' => 'Es wurde kein Artikel ausgewählt', |
|
2557 |
'No part was selected.' => 'Es wurde kein Artikel ausgewählt.', |
|
2558 |
'No parts selected.' => 'Es wurden keine Artikel ausgewählt.', |
|
2559 |
'No partsgroup selected.' => 'Es wurde keine Warengruppen ausgewählt.', |
|
2550 | 2560 |
'No payment term has been created yet.' => 'Es wurden noch keine Zahlungsbedingungen angelegt.', |
2551 | 2561 |
'No picture has been uploaded' => 'Es wurde kein Bild hochgeladen', |
2552 | 2562 |
'No picture uploaded yet' => 'Noch kein Bild hochgeladen', |
... | ... | |
2652 | 2662 |
'Number of months' => 'Anzahl Monate', |
2653 | 2663 |
'Number of new bins' => 'Anzahl neuer Lagerplätze', |
2654 | 2664 |
'Number of orders created:' => 'Anzahl Aufträge erstellt', |
2665 |
'Number of parts' => 'Anzahl Artikel', |
|
2655 | 2666 |
'Number pages' => 'Seiten nummerieren', |
2656 | 2667 |
'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.', |
2657 | 2668 |
'Numbers' => 'Nummern', |
... | ... | |
2789 | 2800 |
'Parent Variant' => 'Stammartikel', |
2790 | 2801 |
'Parsing the XML data failed: #1' => 'Parsen der XML-Daten fehlgeschlagen: #1', |
2791 | 2802 |
'Parsing the XMP metadata failed.' => 'Parsen der XMP-Metadaten schlug fehl.', |
2803 |
'Parents' => 'Obergruppen', |
|
2792 | 2804 |
'Part' => 'Ware', |
2793 | 2805 |
'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.', |
2794 | 2806 |
'Part #1 exists in warehouse #2, but not in warehouse #3 ' => 'Artikel #1 existiert im Lager #2, aber nicht im Lager #3', |
... | ... | |
2812 | 2824 |
'Part marked as "Shop part"' => 'Markiert als Shopartikel', |
2813 | 2825 |
'Part picker' => 'Artikelauswahl', |
2814 | 2826 |
'Part successful counted' => 'Der Artikel wurde gezählt', |
2827 |
'Part wasn\'t added to partsgroup!' => 'Der Artikel wurde nicht der Warengruppe hinzugefügt!', |
|
2815 | 2828 |
'Part with partnumber: #1 not found' => 'Artikel mit Artikelnummer #1 wurde nicht gefunden', |
2816 | 2829 |
'PartClassAbbreviation' => 'Abkürzung der Artikel-Klassifizierung', |
2817 | 2830 |
'Part_br_Description' => 'Beschreibung', |
... | ... | |
3742 | 3755 |
'Solution' => 'Lösung', |
3743 | 3756 |
'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.', |
3744 | 3757 |
'Sort' => 'Sortieren', |
3758 |
'Sort & Save' => '', |
|
3745 | 3759 |
'Sort By' => 'Sortiert nach', |
3746 | 3760 |
'Sort by' => 'Sortieren nach', |
3747 | 3761 |
'Sort order' => 'Sortierfolge', |
... | ... | |
3827 | 3841 |
'Style the picture with the following CSS code' => 'Bildeigenschaft mit folgendem CSS-Style versehen', |
3828 | 3842 |
'Stylesheet' => 'Stilvorlage', |
3829 | 3843 |
'Sub function blocks' => 'Unterfunktionsblöcke', |
3844 |
'Subgroups' => 'Untergruppen', |
|
3830 | 3845 |
'Subject' => 'Betreff', |
3831 | 3846 |
'Subject:' => 'Betreff:', |
3832 | 3847 |
'Subtotal' => 'Zwischensumme', |
... | ... | |
4182 | 4197 |
'The parts for this order have already been transferred' => 'Die Artikel in diesem Lieferschein wurden schon umgelagert', |
4183 | 4198 |
'The parts have been removed.' => 'Die Waren wurden aus dem Lager entnommen.', |
4184 | 4199 |
'The parts have been transferred.' => 'Die Waren wurden umgelagert.', |
4200 |
'The partsgroup has been created.' => 'Die Warengruppe wurde erstellt.', |
|
4201 |
'The partsgroup has been deleted.' => 'Die Warengruppe wurde gelöscht.', |
|
4202 |
'The partsgroup has been saved.' => 'Die Warengruppe wurde gespeichert.', |
|
4203 |
'The partsgroup is in use and cannot be deleted.' => 'Die Warengruppe wird verwendet und kann nicht gelöscht werden.', |
|
4185 | 4204 |
'The partsgroup is missing.' => 'Die Warengruppe fehlt.', |
4186 | 4205 |
'The password is too long (maximum length: #1).' => 'Das Passwort ist zu lang (maximale Länge: #1).', |
4187 | 4206 |
'The password is too short (minimum length: #1).' => 'Das Password ist zu kurz (minimale Länge: #1).', |
... | ... | |
4441 | 4460 |
'This part has already been added.' => 'Dieser Artikel wurde schon hinzugefügt', |
4442 | 4461 |
'This part should not be ordered any more.' => 'Dieser Artikel kann nicht mehr bestellt werden.', |
4443 | 4462 |
'This part was already counted for this bin:' => 'Dieser Artikel wurde für diesen Lagerplatz bereits erfasst:', |
4463 |
'This partsgroup has not been saved yet.' => 'Die Warengruppe wurde noch nicht gespeichert.', |
|
4444 | 4464 |
'This price has since gone down' => 'Dieser Preis ist mittlerweile niedriger', |
4445 | 4465 |
'This price has since gone up' => 'Dieser Preis ist mittlerweile höher', |
4446 | 4466 |
'This record contains not orderable items at position #1' => 'Dieser Beleg enthält nicht bestellbare Artikel an Position #1', |
locale/en/all | ||
---|---|---|
77 | 77 |
'A directory with the name for the new print templates exists already.' => '', |
78 | 78 |
'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.' => '', |
79 | 79 |
'A lower-case character is required.' => '', |
80 |
'A partsgroup with this name already exists.' => '', |
|
80 | 81 |
'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.' => '', |
81 | 82 |
'A special character is required (valid characters: #1).' => '', |
82 | 83 |
'A target quantitiy has to be given' => '', |
... | ... | |
295 | 296 |
'Add to basket' => '', |
296 | 297 |
'Add to purchase basket' => '', |
297 | 298 |
'Add unit' => '', |
299 |
'Added part to partsgroup.' => '', |
|
300 |
'Added partsgroup.' => '', |
|
298 | 301 |
'Added sections and function blocks: #1' => '', |
299 | 302 |
'Added text blocks: #1' => '', |
300 | 303 |
'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' => '', |
... | ... | |
2423 | 2428 |
'Monthly' => '', |
2424 | 2429 |
'More than one control file with the tag \'%s\' exist.' => '', |
2425 | 2430 |
'More than one file selected, please set only one checkbox!' => '', |
2431 |
'Move selected parts to partsgroup' => '', |
|
2432 |
'Moved #1 part.' => '', |
|
2433 |
'Moved #1 parts.' => '', |
|
2426 | 2434 |
'Multi mode not supported.' => '', |
2427 | 2435 |
'Multiple addresses can be entered separated by commas.' => '', |
2428 | 2436 |
'MwSt. inkl.' => '', |
... | ... | |
2546 | 2554 |
'No invoice email found.' => '', |
2547 | 2555 |
'No invoices have been selected.' => '', |
2548 | 2556 |
'No part was selected.' => '', |
2557 |
'No partsgroup selected.' => '', |
|
2549 | 2558 |
'No payment term has been created yet.' => '', |
2550 | 2559 |
'No picture has been uploaded' => '', |
2551 | 2560 |
'No picture uploaded yet' => '', |
... | ... | |
2651 | 2660 |
'Number of months' => '', |
2652 | 2661 |
'Number of new bins' => '', |
2653 | 2662 |
'Number of orders created:' => '', |
2663 |
'Number of parts' => '', |
|
2654 | 2664 |
'Number pages' => '', |
2655 | 2665 |
'Number variables: \'PRECISION=n\' forces numbers to be shown with exactly n decimal places.' => '', |
2656 | 2666 |
'Numbers' => '', |
... | ... | |
2788 | 2798 |
'Parent Variant' => '', |
2789 | 2799 |
'Parsing the XML data failed: #1' => '', |
2790 | 2800 |
'Parsing the XMP metadata failed.' => '', |
2801 |
'Parents' => '', |
|
2791 | 2802 |
'Part' => '', |
2792 | 2803 |
'Part "#1" has chargenumber or best before date set. So it cannot be transfered automatically.' => '', |
2793 | 2804 |
'Part #1 exists in warehouse #2, but not in warehouse #3 ' => '', |
... | ... | |
3826 | 3837 |
'Style the picture with the following CSS code' => '', |
3827 | 3838 |
'Stylesheet' => '', |
3828 | 3839 |
'Sub function blocks' => '', |
3840 |
'Subgroups' => '', |
|
3829 | 3841 |
'Subject' => '', |
3830 | 3842 |
'Subject:' => '', |
3831 | 3843 |
'Subtotal' => '', |
... | ... | |
4180 | 4192 |
'The parts for this order have already been transferred' => '', |
4181 | 4193 |
'The parts have been removed.' => '', |
4182 | 4194 |
'The parts have been transferred.' => '', |
4195 |
'The partsgroup has been created.' => '', |
|
4196 |
'The partsgroup has been deleted.' => '', |
|
4197 |
'The partsgroup has been saved.' => '', |
|
4198 |
'The partsgroup is in use and cannot be deleted.' => '', |
|
4183 | 4199 |
'The partsgroup is missing.' => '', |
4184 | 4200 |
'The password is too long (maximum length: #1).' => '', |
4185 | 4201 |
'The password is too short (minimum length: #1).' => '', |
... | ... | |
4439 | 4455 |
'This part has already been added.' => '', |
4440 | 4456 |
'This part should not be ordered any more.' => '', |
4441 | 4457 |
'This part was already counted for this bin:' => '', |
4458 |
'This partsgroup has not been saved yet.' => '', |
|
4442 | 4459 |
'This price has since gone down' => '', |
4443 | 4460 |
'This price has since gone up' => '', |
4444 | 4461 |
'This record contains not orderable items at position #1' => '', |
menus/user/00-erp.yaml | ||
---|---|---|
1214 | 1214 |
name: Partsgroups |
1215 | 1215 |
order: 900 |
1216 | 1216 |
params: |
1217 |
action: SimpleSystemSetting/list |
|
1218 |
type: parts_group |
|
1217 |
action: PartsGroup/list |
|
1219 | 1218 |
- parent: system |
1220 | 1219 |
id: system_part_classification |
1221 | 1220 |
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
Warengruppen hierarchisch - DB