Revision 662df9d7
Von Bernd Bleßmann vor mehr als 3 Jahren hinzugefügt
SL/BackgroundJob/ConvertTimeRecordings.pm | ||
---|---|---|
85 | 85 |
|
86 | 86 |
# valid parameters with default values |
87 | 87 |
my %valid_params = ( |
88 |
from_date => DateTime->new( day => 1, month => DateTime->today_local->month, year => DateTime->today_local->year)->subtract(months => 1)->to_kivitendo, |
|
89 |
to_date => DateTime->last_day_of_month(month => DateTime->today_local->month, year => DateTime->today_local->year)->subtract(months => 1)->to_kivitendo, |
|
90 |
customernumbers => [], |
|
91 |
part_id => undef, |
|
92 |
project_id => undef, |
|
93 |
rounding => 1, |
|
94 |
link_order => 0, |
|
88 |
from_date => DateTime->new( day => 1, month => DateTime->today_local->month, year => DateTime->today_local->year)->subtract(months => 1)->to_kivitendo, |
|
89 |
to_date => DateTime->last_day_of_month(month => DateTime->today_local->month, year => DateTime->today_local->year)->subtract(months => 1)->to_kivitendo, |
|
90 |
customernumbers => [], |
|
91 |
override_part_id => undef, |
|
92 |
default_part_id => undef, |
|
93 |
override_project_id => undef, |
|
94 |
default_project_id => undef, |
|
95 |
rounding => 1, |
|
96 |
link_order => 0, |
|
95 | 97 |
); |
96 | 98 |
|
97 | 99 |
|
... | ... | |
136 | 138 |
|
137 | 139 |
|
138 | 140 |
# check part |
139 |
if ($self->params->{part_id} && !SL::DB::Manager::Part->find_by(id => $self->params->{part_id}, |
|
140 |
or => [obsolete => undef, obsolete => 0])) { |
|
141 |
die 'No valid part found by given part id'; |
|
141 |
if ($self->params->{override_part_id} && !SL::DB::Manager::Part->find_by(id => $self->params->{override_part_id}, |
|
142 |
or => [obsolete => undef, obsolete => 0])) { |
|
143 |
die 'No valid part found by given override part id'; |
|
144 |
} |
|
145 |
if ($self->params->{default_part_id} && !SL::DB::Manager::Part->find_by(id => $self->params->{default_part_id}, |
|
146 |
or => [obsolete => undef, obsolete => 0])) { |
|
147 |
die 'No valid part found by given default part id'; |
|
142 | 148 |
} |
143 | 149 |
|
144 | 150 |
|
145 | 151 |
# check project |
146 |
if ($self->params->{project_id} && !SL::DB::Manager::Project->find_by(id => $self->params->{project_id}, |
|
147 |
active => 1, valid => 1)) { |
|
148 |
die 'No valid project found by given project id'; |
|
152 |
if ($self->params->{override_project_id} && !SL::DB::Manager::Project->find_by(id => $self->params->{override_project_id}, |
|
153 |
active => 1, valid => 1)) { |
|
154 |
die 'No valid project found by given override project id'; |
|
155 |
} |
|
156 |
if ($self->params->{default_project_id} && !SL::DB::Manager::Project->find_by(id => $self->params->{default_project_id}, |
|
157 |
active => 1, valid => 1)) { |
|
158 |
die 'No valid project found by given default project id'; |
|
149 | 159 |
} |
150 | 160 |
|
151 | 161 |
return $self->params; |
... | ... | |
158 | 168 |
push @{ $time_recordings_by_customer_id{$_->customer_id} }, $_ for @$time_recordings; |
159 | 169 |
|
160 | 170 |
my %convert_params = ( |
161 |
rounding => $self->params->{rounding}, |
|
162 |
default_part_id => $self->params->{part_id}, |
|
171 |
rounding => $self->params->{rounding}, |
|
172 |
override_part_id => $self->params->{override_part_id}, |
|
173 |
default_part_id => $self->params->{default_part_id}, |
|
163 | 174 |
); |
164 | 175 |
|
165 | 176 |
my @donumbers; |
... | ... | |
193 | 204 |
|
194 | 205 |
my %convert_params = ( |
195 | 206 |
rounding => $self->params->{rounding}, |
196 |
default_part_id => $self->params->{part_id}, |
|
207 |
override_part_id => $self->params->{override_part_id}, |
|
208 |
default_part_id => $self->params->{default_part_id}, |
|
197 | 209 |
); |
198 | 210 |
|
199 | 211 |
my @donumbers; |
... | ... | |
257 | 269 |
if (!$tr->order_id) { |
258 | 270 |
# check project |
259 | 271 |
my $project_id; |
260 |
#$project_id = $self->override_project_id; |
|
261 |
$project_id = $self->params->{project_id}; |
|
272 |
$project_id = $self->params->{override_project_id}; |
|
262 | 273 |
$project_id ||= $tr->project_id; |
263 |
#$project_id ||= $self->default_project_id;
|
|
274 |
$project_id ||= $self->params->{default_project_id};
|
|
264 | 275 |
|
265 | 276 |
if (!$project_id) { |
266 | 277 |
$self->log_error('searching related order failed for time recording id ' . $tr->id . ' : no project id'); |
... | ... | |
300 | 311 |
|
301 | 312 |
# check part |
302 | 313 |
my $part_id; |
303 |
#$part_id = $self->override_part_id;
|
|
314 |
$part_id = $self->params->{override_part_id};
|
|
304 | 315 |
$part_id ||= $tr->part_id; |
305 |
#$part_id ||= $self->default_part_id; |
|
306 |
$part_id ||= $self->params->{part_id}; |
|
316 |
$part_id ||= $self->params->{default_part_id}; |
|
307 | 317 |
|
308 | 318 |
if (!$part_id) { |
309 | 319 |
$self->log_error('searching related order failed for time recording id ' . $tr->id . ' : no part id'); |
... | ... | |
339 | 349 |
return; |
340 | 350 |
} |
341 | 351 |
|
342 |
if ($tr->project_id && $tr->project_id != ($matching_order->globalproject_id || 0)) { |
|
352 |
if ($tr->project_id && !$self->params->{override_project_id} && $tr->project_id != ($matching_order->globalproject_id || 0)) {
|
|
343 | 353 |
$self->log_error('searching related order failed for time recording id ' . $tr->id . ' : project of order does not match project of time recording'); |
344 | 354 |
return; |
345 | 355 |
} |
... | ... | |
415 | 425 |
|
416 | 426 |
customernumbers: [c1,22332,334343] |
417 | 427 |
|
418 |
=item C<part_id> |
|
428 |
=item C<override_part_id> |
|
429 |
|
|
430 |
The part id of a time based service which should be used to |
|
431 |
book the times instead of the parts which are set in the time |
|
432 |
recordings. |
|
433 |
|
|
434 |
=item C<default_part_id> |
|
419 | 435 |
|
420 | 436 |
The part id of a time based service which should be used to |
421 | 437 |
book the times if no part is set in the time recording entry. |
... | ... | |
452 | 468 |
Hint: take a look or extend the job CloseProjectsBelongingToClosedSalesOrder for |
453 | 469 |
further automatisation of your organisational needs. |
454 | 470 |
|
455 |
=item C<project_id> |
|
471 |
=item C<override_project_id>
|
|
456 | 472 |
|
457 |
Use this project_id instead of the project_id in the time recordings. |
|
473 |
Use this project id instead of the project id in the time recordings to find |
|
474 |
a related order. This is only used if C<link_order> is true. |
|
475 |
|
|
476 |
=item C<default_project_id> |
|
477 |
|
|
478 |
Use this project id if no project id is set in the time recording |
|
479 |
entry. This is only used if C<link_order> is true. |
|
458 | 480 |
|
459 | 481 |
=back |
460 | 482 |
|
... | ... | |
468 | 490 |
numbers. E.g. (default_/override_)part_number, |
469 | 491 |
(default_/override_)project_number. |
470 | 492 |
|
471 |
=item * part and project parameters override and default |
|
472 |
|
|
473 |
In the moment, the part id given as parameter is used as the default value. |
|
474 |
This means, it will be used if there is no part in the time recvording entry. |
|
475 |
|
|
476 |
The project id given is used as override parameter. It overrides the project |
|
477 |
given in the time recording entry. |
|
478 |
|
|
479 |
To solve this, there should be parameters named override_part_id, |
|
480 |
default_part_id, override_project_id and default_project_id. |
|
481 |
|
|
482 | 493 |
|
483 | 494 |
=back |
484 | 495 |
|
SL/DB/DeliveryOrder.pm | ||
---|---|---|
194 | 194 |
# - merge same descriptions |
195 | 195 |
# |
196 | 196 |
|
197 |
my $default_part_id = $params{default_part_id} ? $params{default_part_id} |
|
198 |
: $params{default_partnumber} ? SL::DB::Manager::Part->find_by(partnumber => $params{default_partnumber})->id |
|
199 |
: undef; |
|
197 |
my $default_part_id = $params{default_part_id} ? $params{default_part_id} |
|
198 |
: $params{default_partnumber} ? SL::DB::Manager::Part->find_by(partnumber => $params{default_partnumber})->id |
|
199 |
: undef; |
|
200 |
my $override_part_id = $params{override_part_id} ? $params{override_part_id} |
|
201 |
: $params{override_partnumber} ? SL::DB::Manager::Part->find_by(partnumber => $params{override_partnumber})->id |
|
202 |
: undef; |
|
200 | 203 |
|
201 | 204 |
# check parts and collect entries |
202 | 205 |
my %part_by_part_id; |
... | ... | |
204 | 207 |
foreach my $source (@$sources) { |
205 | 208 |
next if !$source->duration; |
206 | 209 |
|
207 |
my $part_id = $source->part_id ? $source->part_id
|
|
208 |
: $default_part_id ? $default_part_id
|
|
209 |
: undef;
|
|
210 |
my $part_id = $override_part_id;
|
|
211 |
$part_id ||= $source->part_id;
|
|
212 |
$part_id ||= $default_part_id;
|
|
210 | 213 |
|
211 | 214 |
die 'article not found for entry "' . $source->displayable_times . '"' if !$part_id; |
212 | 215 |
|
... | ... | |
434 | 437 |
given as C<$sources>. All time recording entries must belong to the same |
435 | 438 |
customer. Time recordings are sorted by article and date. For each article |
436 | 439 |
a new delivery order item is created. If no article is associated with an |
437 |
entry, a default article will be used. |
|
440 |
entry, a default article will be used. The article given in the time |
|
441 |
recording entry can be overriden. |
|
438 | 442 |
Entries of the same date (for each article) are summed together and form a |
439 | 443 |
list entry in the long description of the item. |
440 | 444 |
|
... | ... | |
451 | 455 |
An optional hash reference. If it exists then it is used to set |
452 | 456 |
attributes of the newly created delivery order object. |
453 | 457 |
|
458 |
=item C<default_part_id> |
|
459 |
|
|
460 |
An optional part id which is used as default value if no part is set |
|
461 |
in the time recording entry. |
|
462 |
|
|
463 |
=item C<default_partnumber> |
|
464 |
|
|
465 |
Like C<default_part_id> but given as partnumber, not as id. |
|
466 |
|
|
467 |
=item C<override_part_id> |
|
468 |
|
|
469 |
An optional part id which is used instead of a value set in the time |
|
470 |
recording entry. |
|
471 |
|
|
472 |
=item C<override_partnumber> |
|
473 |
|
|
474 |
Like C<overrride_part_id> but given as partnumber, not as id. |
|
475 |
|
|
454 | 476 |
=item C<related_order> |
455 | 477 |
|
456 | 478 |
An optional C<SL::DB::Order> object. If it exists then it is used to |
t/background_job/convert_time_recordings.t | ||
---|---|---|
1 |
use Test::More tests => 40;
|
|
1 |
use Test::More tests => 52;
|
|
2 | 2 |
|
3 | 3 |
use strict; |
4 | 4 |
|
... | ... | |
37 | 37 |
clear_up(); |
38 | 38 |
|
39 | 39 |
######################################## |
40 |
# two time recordings, one order linked with project_id |
|
40 |
# two time recordings, one order linked with project_id in time recording entry
|
|
41 | 41 |
######################################## |
42 | 42 |
my $part = new_service(partnumber => 'Serv1', unit => 'Std')->save; |
43 | 43 |
my $project = create_project(projectnumber => 'p1', description => 'Project 1'); |
... | ... | |
70 | 70 |
|
71 | 71 |
my %data = ( |
72 | 72 |
link_order => 1, |
73 |
project_id => $project->id, |
|
74 | 73 |
from_date => '01.01.2021', |
75 | 74 |
to_date => '30.04.2021', |
76 | 75 |
); |
... | ... | |
102 | 101 |
|
103 | 102 |
|
104 | 103 |
######################################## |
105 |
# two time recordings, one order linked with project_id |
|
104 |
# two time recordings, one order linked with project_id in time recording entry
|
|
106 | 105 |
# unit in order is 'min', but part is 'Std' |
107 | 106 |
######################################## |
108 | 107 |
$part = new_service(partnumber => 'Serv1', unit => 'Std')->save; |
... | ... | |
133 | 132 |
part => $part, |
134 | 133 |
)->save; |
135 | 134 |
|
136 |
# two time recordings, one order linked with project_id |
|
137 | 135 |
%data = ( |
138 | 136 |
link_order => 1, |
139 |
project_id => $project->id, |
|
140 | 137 |
from_date => '01.04.2021', |
141 | 138 |
to_date => '30.04.2021', |
142 | 139 |
); |
... | ... | |
161 | 158 |
|
162 | 159 |
|
163 | 160 |
######################################## |
164 |
# two time recordings, one order linked with project_id |
|
161 |
# two time recordings, one order linked with project_id in time recording entry
|
|
165 | 162 |
# unit in order is 'Std', but part is 'min' |
166 | 163 |
######################################## |
167 | 164 |
$part = new_service(partnumber => 'Serv1', unit => 'min')->save; |
... | ... | |
192 | 189 |
part => $part, |
193 | 190 |
)->save; |
194 | 191 |
|
195 |
# two time recordings, one order linked with project_id |
|
196 | 192 |
%data = ( |
197 | 193 |
link_order => 1, |
198 |
project_id => $project->id, |
|
199 | 194 |
from_date => '01.04.2021', |
200 | 195 |
to_date => '30.04.2021', |
201 | 196 |
); |
... | ... | |
313 | 308 |
clear_up(); |
314 | 309 |
|
315 | 310 |
|
311 |
######################################## |
|
312 |
# override project and part |
|
313 |
######################################## |
|
314 |
$part = new_service(partnumber => 'Serv1', unit => 'Std')->save; |
|
315 |
my $part2 = new_service(partnumber => 'Serv2', unit => 'min')->save; |
|
316 |
$project = create_project(projectnumber => 'p1', description => 'Project 1'); |
|
317 |
my $project2 = create_project(projectnumber => 'p2', description => 'Project 2'); |
|
318 |
$customer = new_customer()->save; |
|
319 |
|
|
320 |
$sales_order = create_sales_order( |
|
321 |
save => 1, |
|
322 |
customer => $customer, |
|
323 |
globalproject => $project, |
|
324 |
taxincluded => 0, |
|
325 |
orderitems => [ create_order_item(part => $part, qty => 180, unit => 'min', sellprice => 70), ] |
|
326 |
); |
|
327 |
my $sales_order2 = create_sales_order( |
|
328 |
save => 1, |
|
329 |
customer => $customer, |
|
330 |
globalproject => $project, |
|
331 |
taxincluded => 0, |
|
332 |
orderitems => [ create_order_item(part => $part2, qty => 180, unit => 'min', sellprice => 70), ] |
|
333 |
); |
|
334 |
|
|
335 |
new_time_recording( |
|
336 |
start_time => DateTime->new(year => 2021, month => 4, day => 19, hour => 10, minute => 10), |
|
337 |
end_time => DateTime->new(year => 2021, month => 4, day => 19, hour => 11, minute => 10), |
|
338 |
customer => $customer, |
|
339 |
project => $project, |
|
340 |
part => $part, |
|
341 |
)->save; |
|
342 |
new_time_recording( |
|
343 |
start_time => DateTime->new(year => 2021, month => 4, day => 19, hour => 11, minute => 10), |
|
344 |
end_time => DateTime->new(year => 2021, month => 4, day => 19, hour => 12, minute => 10), |
|
345 |
customer => $customer, |
|
346 |
project => $project2, |
|
347 |
part => $part2, |
|
348 |
)->save; |
|
349 |
new_time_recording( |
|
350 |
start_time => DateTime->new(year => 2021, month => 4, day => 19, hour => 12, minute => 10), |
|
351 |
end_time => DateTime->new(year => 2021, month => 4, day => 19, hour => 13, minute => 10), |
|
352 |
customer => $customer, |
|
353 |
)->save; |
|
354 |
|
|
355 |
%data = ( |
|
356 |
link_order => 1, |
|
357 |
from_date => '01.04.2021', |
|
358 |
to_date => '30.04.2021', |
|
359 |
override_part_id => $part->id, |
|
360 |
override_project_id => $project->id, |
|
361 |
); |
|
362 |
$db_obj = SL::DB::BackgroundJob->new(); |
|
363 |
$db_obj->set_data(%data); |
|
364 |
$job = SL::BackgroundJob::ConvertTimeRecordings->new; |
|
365 |
$ret = $job->run($db_obj); |
|
366 |
|
|
367 |
$linked_dos = $sales_order->linked_records(to => 'DeliveryOrder'); |
|
368 |
is($linked_dos->[0]->globalproject_id, $project->id, 'overriden part and project: project in delivery order'); |
|
369 |
|
|
370 |
$linked_items = $sales_order->items->[0]->linked_records(to => 'DeliveryOrderItem'); |
|
371 |
is($linked_items->[0]->qty*1, 3, 'overriden part and project: qty in delivery order'); |
|
372 |
is($linked_items->[0]->base_qty*1, 3, 'overriden part and project: base_qty in delivery order'); |
|
373 |
is($linked_items->[0]->parts_id, $part->id, 'overriden part and project: part id'); |
|
374 |
|
|
375 |
my $linked_dos2 = $sales_order2->linked_records(to => 'DeliveryOrder'); |
|
376 |
is(scalar @$linked_dos2, 0, 'overriden part and project: no delivery order for unused order'); |
|
377 |
|
|
378 |
# reload order and orderitems to get changes to deliverd and ship |
|
379 |
Rose::DB::Object::Helpers::forget_related($sales_order, 'orderitems'); |
|
380 |
$sales_order->load; |
|
381 |
Rose::DB::Object::Helpers::forget_related($sales_order2, 'orderitems'); |
|
382 |
$sales_order2->load; |
|
383 |
|
|
384 |
is($sales_order ->items->[0]->ship||0, 180, 'overriden part and project: ship in related order'); |
|
385 |
is($sales_order2->items->[0]->ship||0, 0, 'overriden part and project: ship in not related order'); |
|
386 |
|
|
387 |
clear_up(); |
|
388 |
|
|
389 |
|
|
390 |
######################################## |
|
391 |
# default project and part |
|
392 |
######################################## |
|
393 |
$part = new_service(partnumber => 'Serv1', unit => 'Std')->save; |
|
394 |
$project = create_project(projectnumber => 'p1', description => 'Project 1'); |
|
395 |
$customer = new_customer()->save; |
|
396 |
|
|
397 |
$sales_order = create_sales_order( |
|
398 |
save => 1, |
|
399 |
customer => $customer, |
|
400 |
globalproject => $project, |
|
401 |
taxincluded => 0, |
|
402 |
orderitems => [ create_order_item(part => $part, qty => 180, unit => 'min', sellprice => 70), ] |
|
403 |
); |
|
404 |
|
|
405 |
new_time_recording( |
|
406 |
start_time => DateTime->new(year => 2021, month => 4, day => 19, hour => 10, minute => 10), |
|
407 |
end_time => DateTime->new(year => 2021, month => 4, day => 19, hour => 11, minute => 40), |
|
408 |
customer => $customer, |
|
409 |
)->save; |
|
410 |
|
|
411 |
%data = ( |
|
412 |
link_order => 1, |
|
413 |
from_date => '01.04.2021', |
|
414 |
to_date => '30.04.2021', |
|
415 |
default_part_id => $part->id, |
|
416 |
default_project_id => $project->id, |
|
417 |
); |
|
418 |
$db_obj = SL::DB::BackgroundJob->new(); |
|
419 |
$db_obj->set_data(%data); |
|
420 |
$job = SL::BackgroundJob::ConvertTimeRecordings->new; |
|
421 |
$ret = $job->run($db_obj); |
|
422 |
|
|
423 |
$linked_dos = $sales_order->linked_records(to => 'DeliveryOrder'); |
|
424 |
is($linked_dos->[0]->globalproject_id, $project->id, 'default and project: project in delivery order'); |
|
425 |
|
|
426 |
$linked_items = $sales_order->items->[0]->linked_records(to => 'DeliveryOrderItem'); |
|
427 |
is($linked_items->[0]->qty*1, 1.5, 'default part and project: qty in delivery order'); |
|
428 |
is($linked_items->[0]->base_qty*1, 1.5, 'default part and project: base_qty in delivery order'); |
|
429 |
is($linked_items->[0]->parts_id, $part->id, 'default part and project: part id'); |
|
430 |
|
|
431 |
# reload order and orderitems to get changes to deliverd and ship |
|
432 |
Rose::DB::Object::Helpers::forget_related($sales_order, 'orderitems'); |
|
433 |
$sales_order->load; |
|
434 |
|
|
435 |
is($sales_order->items->[0]->ship*1, 90, 'default part and project: ship in related order'); |
|
436 |
|
|
437 |
clear_up(); |
|
438 |
|
|
439 |
|
|
316 | 440 |
######################################## |
317 | 441 |
# check rounding |
318 | 442 |
######################################## |
... | ... | |
453 | 577 |
##### |
454 | 578 |
|
455 | 579 |
%data = ( |
456 |
part_id => '123', |
|
580 |
override_part_id => '123',
|
|
457 | 581 |
); |
458 | 582 |
|
459 | 583 |
$db_obj = SL::DB::BackgroundJob->new(); |
... | ... | |
462 | 586 |
|
463 | 587 |
$err_msg = ''; |
464 | 588 |
eval { $ret = $job->run($db_obj); 1; } or do {$err_msg = $@}; |
465 |
ok($err_msg =~ '^No valid part found by given part id', 'invalid part id detected'); |
|
589 |
ok($err_msg =~ '^No valid part found by given override part id', 'invalid part id detected');
|
|
466 | 590 |
|
467 | 591 |
##### |
468 | 592 |
|
469 | 593 |
$part = new_service(partnumber => 'Serv1', unit => 'Std', obsolete => 1)->save; |
470 | 594 |
%data = ( |
471 |
part_id => $part->id, |
|
595 |
override_part_id => $part->id,
|
|
472 | 596 |
); |
473 | 597 |
|
474 | 598 |
$db_obj = SL::DB::BackgroundJob->new(); |
... | ... | |
477 | 601 |
|
478 | 602 |
$err_msg = ''; |
479 | 603 |
eval { $ret = $job->run($db_obj); 1; } or do {$err_msg = $@}; |
480 |
ok($err_msg =~ '^No valid part found by given part id', 'obsolete part detected'); |
|
604 |
ok($err_msg =~ '^No valid part found by given override part id', 'obsolete part detected');
|
|
481 | 605 |
|
482 | 606 |
##### |
483 | 607 |
|
484 | 608 |
%data = ( |
485 |
project_id => 123, |
|
609 |
override_project_id => 123,
|
|
486 | 610 |
); |
487 | 611 |
|
488 | 612 |
$db_obj = SL::DB::BackgroundJob->new(); |
... | ... | |
491 | 615 |
|
492 | 616 |
$err_msg = ''; |
493 | 617 |
eval { $ret = $job->run($db_obj); 1; } or do {$err_msg = $@}; |
494 |
ok($err_msg =~ '^No valid project found by given project id', 'invalid project id detected'); |
|
618 |
ok($err_msg =~ '^No valid project found by given override project id', 'invalid project id detected');
|
|
495 | 619 |
|
496 | 620 |
##### |
497 | 621 |
|
498 | 622 |
$project = create_project(projectnumber => 'p1', description => 'Project 1', valid => 0)->save; |
499 | 623 |
%data = ( |
500 |
project_id => $project->id, |
|
624 |
override_project_id => $project->id,
|
|
501 | 625 |
); |
502 | 626 |
|
503 | 627 |
$db_obj = SL::DB::BackgroundJob->new(); |
... | ... | |
506 | 630 |
|
507 | 631 |
$err_msg = ''; |
508 | 632 |
eval { $ret = $job->run($db_obj); 1; } or do {$err_msg = $@}; |
509 |
ok($err_msg =~ '^No valid project found by given project id', 'invalid project detected'); |
|
633 |
ok($err_msg =~ '^No valid project found by given override project id', 'invalid project detected');
|
|
510 | 634 |
|
511 | 635 |
##### |
512 | 636 |
|
Auch abrufbar als: Unified diff
Zeiterfassung: Konvertierung: Artikel/Projekt: override- und default-Parameter