1 #=====================================================================
4 # Based on SQL-Ledger Version 2.1.9
5 # Web http://www.lx-office.org
7 #=====================================================================
8 # SQL-Ledger, Accounting
11 # Author: Dieter Simader
12 # Email: dsimader@sql-ledger.org
13 # Web: http://www.sql-ledger.org
16 # This program is free software; you can redistribute it and/or modify
17 # it under the terms of the GNU General Public License as published by
18 # the Free Software Foundation; either version 2 of the License, or
19 # (at your option) any later version.
21 # This program is distributed in the hope that it will be useful,
22 # but WITHOUT ANY WARRANTY; without even the implied warranty of
23 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 # GNU General Public License for more details.
25 # You should have received a copy of the GNU General Public License
26 # along with this program; if not, write to the Free Software
27 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
28 #======================================================================
30 # Inventory Control module
32 #======================================================================
34 use POSIX qw(strftime);
35 use List::Util qw(first max);
36 use List::MoreUtils qw(any);
41 use SL::Helper::Flash qw(flash);
43 use SL::ReportGenerator;
51 our ($form, $locale, %myconfig, $lxdebug, $auth);
53 require "bin/mozilla/io.pl";
54 require "bin/mozilla/common.pl";
55 require "bin/mozilla/reportgenerator.pl";
60 # type=submit $locale->text('Add Part')
61 # type=submit $locale->text('Add Service')
62 # type=submit $locale->text('Add Assembly')
63 # type=submit $locale->text('Edit Part')
64 # type=submit $locale->text('Edit Service')
65 # type=submit $locale->text('Edit Assembly')
66 # $locale->text('Parts')
67 # $locale->text('Services')
68 # $locale->text('Inventory quantity must be zero before you can set this part obsolete!')
69 # $locale->text('Inventory quantity must be zero before you can set this assembly obsolete!')
70 # $locale->text('Part Number missing!')
71 # $locale->text('Service Number missing!')
72 # $locale->text('Assembly Number missing!')
73 # $locale->text('ea');
78 $lxdebug->enter_sub();
80 $auth->assert('part_service_assembly_edit');
82 my $title = 'Add ' . ucfirst $form->{part_type};
83 $form->{title} = $locale->text($title);
84 $form->{callback} = "$form->{script}?action=add&part_type=$form->{part_type}" unless $form->{callback};
85 $form->{unit_changeable} = 1;
87 IC->get_pricegroups(\%myconfig, \%$form);
91 $lxdebug->leave_sub();
95 $lxdebug->enter_sub();
97 $auth->assert('part_service_assembly_details');
99 $form->{revers} = 0; # switch for backward sorting
100 $form->{lastsort} = ""; # memory for which table was sort at last time
101 $form->{ndxs_counter} = 0; # counter for added entries to top100
103 # for seach all possibibilities, is_service only used as UNLESS so == 0
104 my %is_xyz = ("is_part" => 1, "is_service" => 0, "is_assembly" =>1 );
106 $form->{title} = (ucfirst $form->{searchitems}) . "s";
107 $form->{title} =~ s/ys$/ies/;
108 $form->{title} = $locale->text($form->{title});
110 $form->{CUSTOM_VARIABLES} = CVar->get_configs('module' => 'IC');
111 ($form->{CUSTOM_VARIABLES_FILTER_CODE},
112 $form->{CUSTOM_VARIABLES_INCLUSION_CODE}) = CVar->render_search_options('variables' => $form->{CUSTOM_VARIABLES},
113 'include_prefix' => 'l_',
114 'include_value' => 'Y');
118 $form->get_lists('partsgroup' => 'ALL_PARTSGROUPS');
119 print $form->parse_html_template('ic/search', { %is_xyz, });
121 $lxdebug->leave_sub();
124 sub search_update_prices {
125 $lxdebug->enter_sub();
127 $auth->assert('part_service_assembly_edit');
129 my $pricegroups = IC->get_pricegroups(\%myconfig, \%$form);
131 $form->{title} = $locale->text('Update Prices');
135 print $form->parse_html_template('ic/search_update_prices', { PRICE_ROWS => $pricegroups });
137 $lxdebug->leave_sub();
140 sub confirm_price_update {
141 $lxdebug->enter_sub();
143 $auth->assert('part_service_assembly_edit');
146 my $value_found = undef;
148 foreach my $idx (qw(sellprice listprice), (1..$form->{price_rows})) {
149 my $name = $idx =~ m/\d/ ? $form->{"pricegroup_${idx}"} : $idx eq 'sellprice' ? $locale->text('Sell Price') : $locale->text('List Price');
150 my $type = $idx =~ m/\d/ ? $form->{"pricegroup_type_${idx}"} : $form->{"${idx}_type"};
151 my $value_idx = $idx =~ m/\d/ ? "price_${idx}" : $idx;
152 my $value = $form->parse_amount(\%myconfig, $form->{$value_idx});
154 if ((0 > $value) && ($type eq 'percent')) {
155 push @errors, $locale->text('You cannot adjust the price for pricegroup "#1" by a negative percentage.', $name);
157 } elsif (!$value && ($form->{$value_idx} ne '')) {
158 push @errors, $locale->text('No valid number entered for pricegroup "#1".', $name);
160 } elsif (0 < $value) {
165 push @errors, $locale->text('No prices will be updated because no prices have been entered.') if (!$value_found);
167 my $num_matches = IC->get_num_matches_for_priceupdate();
172 $form->show_generic_error(join('<br>', @errors), 'back_button' => 1);
175 $form->{nextsub} = "update_prices";
177 map { delete $form->{$_} } qw(action header);
179 print $form->parse_html_template('ic/confirm_price_update', { HIDDENS => [ map { name => $_, value => $form->{$_} }, keys %$form ],
180 num_matches => $num_matches });
182 $lxdebug->leave_sub();
186 $lxdebug->enter_sub();
188 $auth->assert('part_service_assembly_edit');
190 my $num_updated = IC->update_prices(\%myconfig, \%$form);
192 if (-1 != $num_updated) {
193 $form->redirect($locale->text('#1 prices were updated.', $num_updated));
195 $form->error($locale->text('Could not update prices!'));
198 $lxdebug->leave_sub();
202 $::lxdebug->enter_sub();
204 $::auth->assert('part_service_assembly_edit');
206 $::form->{l_soldtotal} = "Y";
207 $::form->{sort} = "soldtotal";
208 $::form->{lastsort} = "soldtotal";
210 $::form->{l_qty} = undef;
211 $::form->{l_linetotal} = undef;
212 $::form->{l_number} = "Y";
213 $::form->{number} = "position";
215 unless ( $::form->{bought}
218 || $::form->{quoted}) {
219 $::form->{bought} = $::form->{sold} = 1;
224 $lxdebug->leave_sub();
229 # Warning, deep magic ahead.
230 # This function parses the requested details, sanity checks them, and converts them into a format thats usable for IC->all_parts
232 # flags coming from the form:
234 # searchitems=part revers=0 lastsort=''
237 # partnumber ean description partsgroup classification serialnumber make model drawing microfiche
238 # transdatefrom transdateto
241 # itemstatus = active | onhand | short | obsolete | orphaned
242 # action = continue | top100
245 # bought sold onorder ordered rfq quoted
246 # l_partnumber l_description l_serialnumber l_unit l_listprice l_sellprice l_lastcost
247 # l_linetotal l_priceupdate l_bin l_rop l_weight l_image l_drawing l_microfiche
248 # l_partsgroup l_subtotal l_soldtotal l_deliverydate l_pricegroups
251 # nextsub revers lastsort sort ndxs_counter
253 sub generate_report {
254 $lxdebug->enter_sub();
256 $auth->assert('part_service_assembly_details');
258 my ($revers, $lastsort, $description);
260 my $cvar_configs = CVar->get_configs('module' => 'IC');
263 '' => $locale->text('Articles'),
264 part => $locale->text('Parts'),
265 service => $locale->text('Services'),
266 assembly => $locale->text('Assemblies'),
267 assortment => $locale->text('Assortments'),
270 $form->{title} = $titles{$form->{searchitems}};
273 'bin' => { 'text' => $locale->text('Bin'), },
274 'deliverydate' => { 'text' => $locale->text('deliverydate'), },
275 'description' => { 'text' => $locale->text('Part Description'), },
276 'notes' => { 'text' => $locale->text('Notes'), },
277 'drawing' => { 'text' => $locale->text('Drawing'), },
278 'ean' => { 'text' => $locale->text('EAN'), },
279 'image' => { 'text' => $locale->text('Image'), },
280 'insertdate' => { 'text' => $locale->text('Insert Date'), },
281 'invnumber' => { 'text' => $locale->text('Invoice Number'), },
282 'lastcost' => { 'text' => $locale->text('Last Cost'), },
283 'linetotallastcost' => { 'text' => $locale->text('Extended'), },
284 'linetotallistprice' => { 'text' => $locale->text('Extended'), },
285 'linetotalsellprice' => { 'text' => $locale->text('Extended'), },
286 'listprice' => { 'text' => $locale->text('List Price'), },
287 'microfiche' => { 'text' => $locale->text('Microfiche'), },
288 'name' => { 'text' => $locale->text('Name'), },
289 'onhand' => { 'text' => $locale->text('Stocked Qty'), },
290 'ordnumber' => { 'text' => $locale->text('Order Number'), },
291 'partnumber' => { 'text' => $locale->text('Part Number'), },
292 'partsgroup' => { 'text' => $locale->text('Group'), },
293 'priceupdate' => { 'text' => $locale->text('Updated'), },
294 'quonumber' => { 'text' => $locale->text('Quotation'), },
295 'rop' => { 'text' => $locale->text('ROP'), },
296 'sellprice' => { 'text' => $locale->text('Sell Price'), },
297 'serialnumber' => { 'text' => $locale->text('Serial Number'), },
298 'soldtotal' => { 'text' => $locale->text('Qty in Selected Records'), },
299 'name' => { 'text' => $locale->text('Name in Selected Records'), },
300 'transdate' => { 'text' => $locale->text('Transdate'), },
301 'unit' => { 'text' => $locale->text('Unit'), },
302 'weight' => { 'text' => $locale->text('Weight'), },
303 'shop' => { 'text' => $locale->text('Shop article'), },
304 'type_and_classific' => { 'text' => $locale->text('Type'), },
305 'projectnumber' => { 'text' => $locale->text('Project Number'), },
306 'projectdescription' => { 'text' => $locale->text('Project Description'), },
309 $revers = $form->{revers};
310 $lastsort = $form->{lastsort};
312 # sorting and direction of sorting
313 # ToDO: change this to the simpler field+direction method
314 if (($form->{lastsort} eq "") && ($form->{sort} eq undef)) {
316 $form->{lastsort} = "partnumber";
317 $form->{sort} = "partnumber";
319 if ($form->{lastsort} eq $form->{sort}) {
320 $form->{revers} = 1 - $form->{revers};
323 $form->{lastsort} = $form->{sort};
327 # special case if we have a serialnumber limit search
328 # serialnumbers are only given in invoices and orders,
329 # so they can only pop up in bought, sold, rfq, and quoted stuff
330 $form->{no_sn_joins} = 'Y' if ( !$form->{bought} && !$form->{sold}
331 && !$form->{rfq} && !$form->{quoted}
332 && ($form->{l_serialnumber} || $form->{serialnumber}));
334 # special case for any checkbox of bought | sold | onorder | ordered | rfq | quoted.
335 # if any of these are ticked the behavior changes slightly for lastcost
336 # since all those are aggregation checks for the legder tables this is an internal switch
337 # refered to as ledgerchecks
338 $form->{ledgerchecks} = 'Y' if ( $form->{bought} || $form->{sold} || $form->{onorder}
339 || $form->{ordered} || $form->{rfq} || $form->{quoted});
341 # if something should be activated if something else is active, enter it here
343 onhand => [ qw(l_onhand) ],
344 short => [ qw(l_onhand) ],
345 onorder => [ qw(l_ordnumber) ],
346 ordered => [ qw(l_ordnumber) ],
347 rfq => [ qw(l_quonumber) ],
348 quoted => [ qw(l_quonumber) ],
349 bought => [ qw(l_invnumber) ],
350 sold => [ qw(l_invnumber) ],
351 ledgerchecks => [ qw(l_name) ],
352 serialnumber => [ qw(l_serialnumber) ],
353 no_sn_joins => [ qw(bought sold) ],
356 # get name of partsgroup if id is given
358 if ($form->{partsgroup_id}) {
359 my $pg = SL::DB::PartsGroup->new(id => $form->{partsgroup_id})->load;
360 $pg_name = $pg->{'partsgroup'};
363 # these strings get displayed at the top of the results to indicate the user which switches were used
365 active => $locale->text('Active'),
366 obsolete => $locale->text('Obsolete'),
367 orphaned => $locale->text('Orphaned'),
368 onhand => $locale->text('On Hand'),
369 short => $locale->text('Short'),
370 onorder => $locale->text('On Order'),
371 ordered => $locale->text('Ordered'),
372 rfq => $locale->text('RFQ'),
373 quoted => $locale->text('Quoted'),
374 bought => $locale->text('Bought'),
375 sold => $locale->text('Sold'),
376 transdatefrom => $locale->text('From') . " " . $locale->date(\%myconfig, $form->{transdatefrom}, 1),
377 transdateto => $locale->text('To (time)') . " " . $locale->date(\%myconfig, $form->{transdateto}, 1),
378 partnumber => $locale->text('Part Number') . ": '$form->{partnumber}'",
379 partsgroup => $locale->text('Group') . ": '$form->{partsgroup}'",
380 partsgroup_id => $locale->text('Group') . ": '$pg_name'",
381 serialnumber => $locale->text('Serial Number') . ": '$form->{serialnumber}'",
382 description => $locale->text('Part Description') . ": '$form->{description}'",
383 make => $locale->text('Make') . ": '$form->{make}'",
384 model => $locale->text('Model') . ": '$form->{model}'",
385 drawing => $locale->text('Drawing') . ": '$form->{drawing}'",
386 microfiche => $locale->text('Microfiche') . ": '$form->{microfiche}'",
387 l_soldtotal => $locale->text('Qty in Selected Records'),
388 ean => $locale->text('EAN') . ": '$form->{ean}'",
389 insertdatefrom => $locale->text('Insert Date') . ": " . $locale->text('From') . " " . $locale->date(\%myconfig, $form->{insertdatefrom}, 1),
390 insertdateto => $locale->text('Insert Date') . ": " . $locale->text('To (time)') . " " . $locale->date(\%myconfig, $form->{insertdateto}, 1),
393 my @itemstatus_keys = qw(active obsolete orphaned onhand short);
394 my @callback_keys = qw(onorder ordered rfq quoted bought sold partnumber partsgroup partsgroup_id serialnumber description make model
395 drawing microfiche l_soldtotal l_deliverydate transdatefrom transdateto insertdatefrom insertdateto ean shop all);
397 # calculate dependencies
398 for (@itemstatus_keys, @callback_keys) {
399 next if ($form->{itemstatus} ne $_ && !$form->{$_});
400 map { $form->{$_} = 'Y' } @{ $dependencies{$_} } if $dependencies{$_};
403 # generate callback and optionstrings
405 for my $key (@itemstatus_keys, @callback_keys) {
406 next if ($form->{itemstatus} ne $key && !$form->{$key});
407 push @options, $optiontexts{$key};
410 # special case for lastcost
411 if ($form->{ledgerchecks}){
412 # ledgerchecks don't know about sellprice or lastcost. they just return a
413 # price. so rename sellprice to price, and drop lastcost.
414 $column_defs{sellprice}{text} = $locale->text('Price');
415 $form->{l_lastcost} = ""
418 if ($form->{description}) {
419 $description = $form->{description};
420 $description =~ s/\n/<br>/g;
423 if ($form->{l_linetotal}) {
424 $form->{l_qty} = "Y";
425 $form->{l_linetotalsellprice} = "Y" if $form->{l_sellprice};
426 $form->{l_linetotallastcost} = $form->{searchitems} eq 'assembly' && !$form->{bom} ? "" : 'Y' if $form->{l_lastcost};
427 $form->{l_linetotallistprice} = "Y" if $form->{l_listprice};
429 $form->{"l_type_and_classific"} = "Y";
431 if ($form->{l_service} && !$form->{l_assembly} && !$form->{l_part}) {
433 # remove bin, weight and rop from list
434 map { $form->{"l_$_"} = "" } qw(bin weight rop);
436 $form->{l_onhand} = "";
438 # qty is irrelevant unless bought or sold
444 || $form->{quoted}) {
445 # $form->{l_onhand} = "Y";
447 $form->{l_linetotalsellprice} = "";
448 $form->{l_linetotallastcost} = "";
452 # soldtotal doesn't make sense with more than one bsooqr option.
453 # so reset it to sold (the most common option), and issue a warning
455 # also it doesn't make sense without bsooqr. disable and issue a warning too
456 my @bsooqr = qw(sold bought onorder ordered rfq quoted);
457 my $bsooqr_mode = grep { $form->{$_} } @bsooqr;
458 if ($form->{l_subtotal} && 1 < $bsooqr_mode) {
459 my $enabled = first { $form->{$_} } @bsooqr;
460 $form->{$_} = '' for @bsooqr;
461 $form->{$enabled} = 'Y';
463 push @options, $::locale->text('Subtotal cannot distinguish betweens record types. Only one of the selected record types will be displayed: #1', $optiontexts{$enabled});
465 if ($form->{l_soldtotal} && !$bsooqr_mode) {
466 delete $form->{l_soldtotal};
468 flash('warning', $::locale->text('Soldtotal does not make sense without any bsooqr options'));
470 if ($form->{l_name} && !$bsooqr_mode) {
471 delete $form->{l_name};
473 flash('warning', $::locale->text('Name does not make sense without any bsooqr options'));
475 IC->all_parts(\%myconfig, \%$form);
478 partnumber type_and_classific description notes partsgroup bin onhand rop soldtotal unit listprice
479 linetotallistprice sellprice linetotalsellprice lastcost linetotallastcost
480 priceupdate weight image drawing microfiche invnumber ordnumber quonumber
481 transdate name serialnumber deliverydate ean projectnumber projectdescription
485 my $pricegroups = SL::DB::Manager::Pricegroup->get_all_sorted;
486 my @pricegroup_columns;
487 my %column_defs_pricegroups;
488 if ($form->{l_pricegroups}) {
489 @pricegroup_columns = map { "pricegroup_" . $_->id } @{ $pricegroups };
490 %column_defs_pricegroups = map {
491 "pricegroup_" . $_->id => {
492 text => $::locale->text('Pricegroup') . ' ' . $_->pricegroup,
497 push @columns, @pricegroup_columns;
499 my @includeable_custom_variables = grep { $_->{includeable} } @{ $cvar_configs };
500 my @searchable_custom_variables = grep { $_->{searchable} } @{ $cvar_configs };
501 my %column_defs_cvars = map { +"cvar_$_->{name}" => { 'text' => $_->{description} } } @includeable_custom_variables;
503 push @columns, map { "cvar_$_->{name}" } @includeable_custom_variables;
505 %column_defs = (%column_defs, %column_defs_cvars, %column_defs_pricegroups);
506 map { $column_defs{$_}->{visible} ||= $form->{"l_$_"} ? 1 : 0 } @columns;
507 map { $column_defs{$_}->{align} = 'right' } qw(onhand sellprice listprice lastcost linetotalsellprice linetotallastcost linetotallistprice rop weight soldtotal shop), @pricegroup_columns;
509 my @hidden_variables = (
510 qw(l_subtotal l_linetotal searchitems itemstatus bom l_pricegroups insertdatefrom insertdateto),
511 qw(l_type_and_classific classification_id),
514 map({ "cvar_$_->{name}" } @searchable_custom_variables),
515 map({'cvar_'. $_->{name} .'_qtyop'} grep({$_->{type} eq 'number'} @searchable_custom_variables)),
516 map({ "l_$_" } @columns),
519 my $callback = build_std_url('action=generate_report', grep { $form->{$_} } @hidden_variables);
521 my @sort_full = qw(partnumber description onhand soldtotal deliverydate insertdate shop);
522 my @sort_no_revers = qw(partsgroup bin priceupdate invnumber ordnumber quonumber name image drawing serialnumber);
524 foreach my $col (@sort_full) {
525 $column_defs{$col}->{link} = join '&', $callback, "sort=$col", map { "$_=" . E($form->{$_}) } qw(revers lastsort);
527 map { $column_defs{$_}->{link} = "${callback}&sort=$_" } @sort_no_revers;
529 # add order to callback
530 $form->{callback} = join '&', ($callback, map { "${_}=" . E($form->{$_}) } qw(sort revers));
532 my $report = SL::ReportGenerator->new(\%myconfig, $form);
534 my %attachment_basenames = (
535 'part' => $locale->text('part_list'),
536 'service' => $locale->text('service_list'),
537 'assembly' => $locale->text('assembly_list'),
538 'article' => $locale->text('article_list'),
541 $report->set_options('raw_top_info_text' => $form->parse_html_template('ic/generate_report_top', { options => \@options }),
542 'raw_bottom_info_text' => $form->parse_html_template('ic/generate_report_bottom'),
543 'output_format' => 'HTML',
544 'title' => $form->{title},
545 'attachment_basename' => 'article_list' . strftime('_%Y%m%d', localtime time),
547 $report->set_options_from_form();
548 $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
550 $report->set_columns(%column_defs);
551 $report->set_column_order(@columns);
553 $report->set_export_options('generate_report', @hidden_variables, qw(sort revers));
555 $report->set_sort_indicator($form->{sort}, $form->{revers} ? 0 : 1);
557 CVar->add_custom_variables_to_report('module' => 'IC',
558 'trans_id_field' => 'id',
559 'configs' => $cvar_configs,
560 'column_defs' => \%column_defs,
561 'data' => $form->{parts});
563 CVar->add_custom_variables_to_report('module' => 'IC',
564 'sub_module' => sub { $_[0]->{ioi} },
565 'trans_id_field' => 'ioi_id',
566 'configs' => $cvar_configs,
567 'column_defs' => \%column_defs,
568 'data' => $form->{parts});
570 my @subtotal_columns = qw(sellprice listprice lastcost);
571 my %subtotals = map { $_ => 0 } ('onhand', @subtotal_columns);
572 my %totals = map { $_ => 0 } @subtotal_columns;
574 my $same_item = @{ $form->{parts} } ? $form->{parts}[0]{ $form->{sort} } : undef;
576 my $defaults = AM->get_defaults();
579 foreach my $ref (@{ $form->{parts} }) {
581 # fresh row, for inserting later
582 my $row = { map { $_ => { 'data' => $ref->{$_} } } @columns };
584 $ref->{exchangerate} ||= 1;
585 $ref->{price_factor} ||= 1;
586 $ref->{sellprice} *= $ref->{exchangerate} / $ref->{price_factor};
587 $ref->{listprice} *= $ref->{exchangerate} / $ref->{price_factor};
588 $ref->{lastcost} *= $ref->{exchangerate} / $ref->{price_factor};
590 # use this for assemblies
591 my $soldtotal = $bsooqr_mode ? $ref->{soldtotal} : $ref->{onhand};
593 if ($ref->{assemblyitem}) {
594 $row->{partnumber}{align} = 'right';
595 $row->{soldtotal}{data} = 0;
596 $soldtotal = 0 if ($form->{sold});
599 my $edit_link = build_std_url('script=controller.pl', 'action=Part/edit', 'part.id=' . E($ref->{id}), 'callback');
600 $row->{partnumber}->{link} = $edit_link;
601 $row->{description}->{link} = $edit_link;
603 foreach (qw(sellprice listprice lastcost)) {
604 $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{$_}, 2);
605 $row->{"linetotal$_"}{data} = $form->format_amount(\%myconfig, $ref->{onhand} * $ref->{$_}, 2);
607 foreach ( @pricegroup_columns ) {
608 $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{"$_"}, 2);
612 map { $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{$_}); } qw(onhand rop weight soldtotal);
614 $row->{weight}->{data} .= ' ' . $defaults->{weightunit};
616 # 'yes' and 'no' for boolean value shop
617 if ($form->{l_shop}) {
618 $row->{shop}{data} = $row->{shop}{data}? $::locale->text('yes') : $::locale->text('no');
621 if (!$ref->{assemblyitem}) {
622 foreach my $col (@subtotal_columns) {
623 $totals{$col} += $soldtotal * $ref->{$col};
624 $subtotals{$col} += $soldtotal * $ref->{$col};
627 $subtotals{soldtotal} += $soldtotal;
631 if ($ref->{module} eq 'oe') {
632 # für oe gibt es vier fälle, jeweils nach kunde oder lieferant unterschiedlich:
634 # | ist bestellt | Von Kunden bestellt | -> edit_oe_ord_link
635 # | Anfrage | Angebot | -> edit_oe_quo_link
637 my $edit_oe_ord_link = build_std_url("script=oe.pl", 'action=edit', 'type=' . E($ref->{cv} eq 'vendor' ? 'purchase_order' : 'sales_order'), 'id=' . E($ref->{trans_id}), 'callback');
638 my $edit_oe_quo_link = build_std_url("script=oe.pl", 'action=edit', 'type=' . E($ref->{cv} eq 'vendor' ? 'request_quotation' : 'sales_quotation'), 'id=' . E($ref->{trans_id}), 'callback');
640 $row->{ordnumber}{link} = $edit_oe_ord_link;
641 $row->{quonumber}{link} = $edit_oe_quo_link if (!$ref->{ordnumber});
644 $row->{invnumber}{link} = build_std_url("script=$ref->{module}.pl", 'action=edit', 'type=invoice', 'id=' . E($ref->{trans_id}), 'callback') if ($ref->{invnumber});
647 # set properties of images
648 if ($ref->{image} && (lc $report->{options}->{output_format} eq 'html')) {
649 $row->{image}{data} = '';
650 $row->{image}{raw_data} = '<a href="' . H($ref->{image}) . '"><img src="' . H($ref->{image}) . '" height="32" border="0"></a>';
652 map { $row->{$_}{link} = $ref->{$_} } qw(drawing microfiche);
654 $row->{notes}{data} = SL::HTML::Util->strip($ref->{notes});
655 $row->{type_and_classific}{data} = $::request->presenter->type_abbreviation($ref->{part_type}).
656 $::request->presenter->classification_abbreviation($ref->{classification_id});
658 $report->add_data($row);
660 my $next_ref = $form->{parts}[$idx + 1];
662 # insert subtotal rows
663 if (($form->{l_subtotal} eq 'Y') &&
665 (!$next_ref->{assemblyitem} && ($same_item ne $next_ref->{ $form->{sort} })))) {
666 my $row = { map { $_ => { 'class' => 'listsubtotal', } } @columns };
668 if ( !$form->{l_assembly} || !$form->{bom}) {
669 $row->{soldtotal}->{data} = $form->format_amount(\%myconfig, $subtotals{soldtotal});
672 map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $subtotals{$_}, 2) } @subtotal_columns;
673 map { $subtotals{$_} = 0 } ('soldtotal', @subtotal_columns);
675 $report->add_data($row);
677 $same_item = $next_ref->{ $form->{sort} };
683 if ($form->{"l_linetotal"} && !$form->{report_generator_csv_options_for_import}) {
684 my $row = { map { $_ => { 'class' => 'listtotal', } } @columns };
686 map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $totals{$_}, 2) } @subtotal_columns;
688 $report->add_separator();
689 $report->add_data($row);
692 $report->generate_with_headers();
694 $lxdebug->leave_sub();
695 } #end generate_report
698 $lxdebug->enter_sub();
700 $auth->assert('part_service_assembly_edit');
703 my ($column_index, $subtotalonhand, $subtotalsellprice, $subtotallastcost, $subtotallistprice) = @_;
705 map { $column_data{$_} = "<td> </td>" } @{ $column_index };
706 $$subtotalonhand = 0 if ($form->{l_assembly} && $form->{bom});
708 $column_data{onhand} =
709 "<th class=listsubtotal align=right>"
710 . $form->format_amount(\%myconfig, $$subtotalonhand)
713 $column_data{linetotalsellprice} =
714 "<th class=listsubtotal align=right>"
715 . $form->format_amount(\%myconfig, $$subtotalsellprice, 2)
717 $column_data{linetotallistprice} =
718 "<th class=listsubtotal align=right>"
719 . $form->format_amount(\%myconfig, $$subtotallistprice, 2)
721 $column_data{linetotallastcost} =
722 "<th class=listsubtotal align=right>"
723 . $form->format_amount(\%myconfig, $$subtotallastcost, 2)
726 $$subtotalonhand = 0;
727 $$subtotalsellprice = 0;
728 $$subtotallistprice = 0;
729 $$subtotallastcost = 0;
731 print "<tr class=listsubtotal>";
733 map { print "\n$column_data{$_}" } @{ $column_index };
739 $lxdebug->leave_sub();
743 $lxdebug->enter_sub();
745 $auth->assert('part_service_assembly_details');
747 # show history button
748 $form->{javascript} = qq|<script type="text/javascript" src="js/show_history.js"></script>|;
749 #/show hhistory button
750 IC->get_part(\%myconfig, \%$form);
752 $form->{"original_partnumber"} = $form->{"partnumber"};
754 my $title = 'Edit ' . ucfirst $form->{part_type};
755 $form->{title} = $locale->text($title);
760 $lxdebug->leave_sub();
764 $lxdebug->enter_sub();
766 $auth->assert('part_service_assembly_details');
768 IC->create_links("IC", \%myconfig, \%$form);
771 map({ $form->{selectcurrency} .= "<option>$_\n" } $::form->get_all_currencies());
773 # parts and assemblies have the same links
774 my $item = $form->{part_type};
775 if ($form->{part_type} eq 'assembly') {
779 # build the popup menus
780 $form->{taxaccounts} = "";
781 foreach my $key (keys %{ $form->{IC_links} }) {
782 foreach my $ref (@{ $form->{IC_links}{$key} }) {
784 # if this is a tax field
785 if ($key =~ /IC_tax/) {
786 if ($key =~ /\Q$item\E/) {
787 $form->{taxaccounts} .= "$ref->{accno} ";
788 $form->{"IC_tax_$ref->{accno}_description"} =
789 "$ref->{accno}--$ref->{description}";
792 if ($form->{amount}{ $ref->{accno} }) {
793 $form->{"IC_tax_$ref->{accno}"} = "checked";
796 $form->{"IC_tax_$ref->{accno}"} = "checked";
801 $form->{"select$key"} .=
802 "<option $ref->{selected}>$ref->{accno}--$ref->{description}\n";
803 if ($form->{amount}{$key} eq $ref->{accno}) {
804 $form->{$key} = "$ref->{accno}--$ref->{description}";
810 chop $form->{taxaccounts};
812 if (($form->{part_type} eq "part") || ($form->{part_type} eq "assembly")) {
813 $form->{selectIC_income} = $form->{selectIC_sale};
814 $form->{selectIC_expense} = $form->{selectIC_cogs};
815 $form->{IC_income} = $form->{IC_sale};
816 $form->{IC_expense} = $form->{IC_cogs};
819 delete $form->{IC_links};
820 delete $form->{amount};
822 $form->get_partsgroup(\%myconfig, { all => 1 });
824 $form->{partsgroup} = "$form->{partsgroup}--$form->{partsgroup_id}";
826 if (@{ $form->{all_partsgroup} }) {
827 $form->{selectpartsgroup} = qq|<option>\n|;
828 map { $form->{selectpartsgroup} .= qq|<option value="$_->{partsgroup}--$_->{id}">$_->{partsgroup}\n| } @{ $form->{all_partsgroup} };
831 if ($form->{part_type} eq 'assembly') {
833 foreach my $i (1 .. $form->{assembly_rows}) {
834 if ($form->{"partsgroup_id_$i"}) {
835 $form->{"partsgroup_$i"} =
836 qq|$form->{"partsgroup_$i"}--$form->{"partsgroup_id_$i"}|;
839 $form->get_partsgroup(\%myconfig);
841 if (@{ $form->{all_partsgroup} }) {
842 $form->{selectassemblypartsgroup} = qq|<option>\n|;
845 $form->{selectassemblypartsgroup} .=
846 qq|<option value="$_->{partsgroup}--$_->{id}">$_->{partsgroup}\n|
847 } @{ $form->{all_partsgroup} };
850 $lxdebug->leave_sub();
854 $lxdebug->enter_sub();
856 $auth->assert('part_service_assembly_details');
858 $form->{pg_keys} = sub { "$_[0]->{partsgroup}--$_[0]->{id}" };
859 $form->{description_area} = ($form->{rows} = $form->numtextrows($form->{description}, 40)) > 1;
860 $form->{notes_rows} = max 4, $form->numtextrows($form->{notes}, 40), $form->numtextrows($form->{formel}, 40);
862 map { $form->{"is_$_"} = ($form->{part_type} eq $_) } qw(part service assembly);
863 map { $form->{$_} =~ s/"/"/g; } qw(unit);
865 $form->get_lists('price_factors' => 'ALL_PRICE_FACTORS',
866 'partsgroup' => 'all_partsgroup',
867 'vendors' => 'ALL_VENDORS',
868 'warehouses' => { 'key' => 'WAREHOUSES',
869 'bins' => 'BINS', });
870 # leerer wert für Lager und Lagerplatz korrekt einstellt
871 # ID 0 sollte in Ordnung sein, da der Zähler sowieso höher ist
872 my $no_default_bin_entry = { 'id' => '0', description => '--', 'BINS' => [ { id => '0', description => ''} ] };
873 push @ { $form->{WAREHOUSES} }, $no_default_bin_entry;
874 if (my $max = scalar @{ $form->{WAREHOUSES} }) {
875 my ($default_warehouse_id, $default_bin_id);
876 if ($form->{action} eq 'add') { # default only for new entries
877 $default_warehouse_id = $::instance_conf->get_warehouse_id;
878 $default_bin_id = $::instance_conf->get_bin_id;
880 $form->{warehouse_id} ||= $default_warehouse_id || $form->{WAREHOUSES}->[$max -1]->{id};
881 $form->{bin_id} ||= $default_bin_id || $form->{WAREHOUSES}->[$max -1]->{BINS}->[0]->{id};
884 $form->{LANGUAGES} = SL::DB::Manager::Language->get_all_sorted;
885 $form->{translations_map} = { map { ($_->{language_id} => $_) } @{ $form->{translations} || [] } };
887 IC->retrieve_buchungsgruppen(\%myconfig, $form);
888 @{ $form->{BUCHUNGSGRUPPEN} } = grep { $_->{id} eq $form->{buchungsgruppen_id} || ($form->{id} && $form->{orphaned}) || !$form->{id} } @{ $form->{BUCHUNGSGRUPPEN} };
890 if (($form->{partnumber} ne '') && !SL::TransNumber->new(number => $form->{partnumber}, type => $form->{part_type}, id => $form->{id})->is_unique) {
891 flash('info', $::locale->text('This partnumber is not unique. You should change it.'));
894 my $units = AM->retrieve_units(\%myconfig, $form);
895 $form->{ALL_UNITS} = [ map +{ name => $_ }, sort { $units->{$a}{sortkey} <=> $units->{$b}{sortkey} } keys %$units ];
897 $form->{defaults} = AM->get_defaults();
899 $form->{CUSTOM_VARIABLES} = CVar->get_custom_variables('module' => 'IC', 'trans_id' => $form->{id});
901 my ($null, $partsgroup_id) = split /--/, $form->{partsgroup};
903 CVar->render_inputs('variables' => $form->{CUSTOM_VARIABLES}, show_disabled_message => 1, partsgroup_id => $partsgroup_id)
904 if (scalar @{ $form->{CUSTOM_VARIABLES} });
906 $::request->layout->use_javascript("${_}.js") for qw(ckeditor/ckeditor ckeditor/adapters/jquery kivi.PriceRule);
907 $::request->layout->add_javascripts_inline("\$(function(){kivi.PriceRule.load_price_rules_for_part(@{[ $::form->{id} * 1 ]})});") if $::form->{id};
909 #print $form->parse_html_template('ic/form_header', { ALL_PRICE_FACTORS => $form->{ALL_PRICE_FACTORS},
910 # ALL_UNITS => $form->{ALL_UNITS},
911 # BUCHUNGSGRUPPEN => $form->{BUCHUNGSGRUPPEN},
912 # payment_terms => $form->{payment_terms},
913 # all_partsgroup => $form->{all_partsgroup}});
915 $form->{show_edit_buttons} = $main::auth->check_right($::myconfig{login}, 'part_service_assembly_edit');
917 print $form->parse_html_template('ic/form_header');
918 $lxdebug->leave_sub();
922 $lxdebug->enter_sub();
924 $auth->assert('part_service_assembly_details');
926 print $form->parse_html_template('ic/form_footer');
928 $lxdebug->leave_sub();
932 $lxdebug->enter_sub();
935 my @mm_data = grep { any { $_ ne '' } @$_{qw(make model)} } map +{ make => $form->{"make_$_"}, model => $form->{"model_$_"}, lastcost => $form->{"lastcost_$_"}, lastupdate => $form->{"lastupdate_$_"}, sortorder => $form->{"sortorder_$_"} }, 1 .. $numrows;
936 delete @{$form}{grep { m/^make_\d+/ || m/^model_\d+/ } keys %{ $form }};
937 print $form->parse_html_template('ic/makemodel', { MM_DATA => [ @mm_data, {} ], mm_rows => scalar @mm_data + 1 });
939 $lxdebug->leave_sub();
943 $lxdebug->enter_sub();
946 my ($nochange, $callback, $previousform, $linetotal, $line_purchase_price, $href);
948 @column_index = qw(runningnumber qty unit bom partnumber type_and_classific description partsgroup lastcost total);
950 if ($form->{previousform}) {
952 @column_index = qw(qty unit bom partnumber type_and_classific description partsgroup total);
956 $form->{old_callback} = $form->{callback};
957 $callback = $form->{callback};
958 $form->{callback} = "$form->{script}?action=display_form";
961 map { delete $form->{$_} } qw(action header);
963 # save form variables in a previousform variable
964 my %form_to_save = map { ($_ => m/^ (?: listprice | sellprice | lastcost ) $/x ? $form->format_amount(\%myconfig, $form->{$_}) : $form->{$_}) }
966 $previousform = $::auth->save_form_in_session(form => \%form_to_save);
968 $form->{callback} = $callback;
969 $form->{assemblytotal} = 0;
970 $form->{assembly_purchase_price_total} = 0;
975 runningnumber => { text => $locale->text('No.'), nowrap => 1, width => '5%', align => 'left',},
976 qty => { text => $locale->text('Qty'), nowrap => 1, width => '10%', align => 'left',},
977 unit => { text => $locale->text('Unit'), nowrap => 1, width => '5%', align => 'left',},
978 partnumber => { text => $locale->text('Part Number'), nowrap => 1, width => '20%', align => 'left',},
979 type_and_classific => { text => $locale->text('Typ'), nowrap => 1, width => '5%' , align => 'left',},
980 description => { text => $locale->text('Part Description'), nowrap => 1, width => '50%', align => 'left',},
981 lastcost => { text => $locale->text('Purchase Prices'), nowrap => 1, width => '45%', align => 'right',},
982 total => { text => $locale->text('Sale Prices'), nowrap => 1, align => 'right',},
983 bom => { text => $locale->text('BOM'), align => 'center',},
984 partsgroup => { text => $locale->text('Group'), align => 'left',},
989 for my $i (1 .. $numrows) {
990 my (%row, @row_hiddens);
992 $form->{"partnumber_$i"} =~ s/\"/"/g;
994 $linetotal = $form->round_amount($form->{"sellprice_$i"} * $form->{"qty_$i"} / ($form->{"price_factor_$i"} || 1), 4);
995 $line_purchase_price = $form->round_amount($form->{"lastcost_$i"} * $form->{"qty_$i"} / ($form->{"price_factor_$i"} || 1), 4);
996 $form->{assemblytotal} += $linetotal;
997 $form->{assembly_purchase_price_total} += $line_purchase_price;
998 $form->{"qty_$i"} = $form->format_amount(\%myconfig, $form->{"qty_$i"});
999 $linetotal = $form->format_amount(\%myconfig, $linetotal, 2);
1000 $line_purchase_price = $form->format_amount(\%myconfig, $line_purchase_price, 2);
1001 $href = build_std_url("action=edit", qq|id=$form->{"id_$i"}|, "rowcount=$numrows", "currow=$i", "previousform=$previousform");
1002 map { $row{$_}{data} = "" } qw(qty unit partnumber typ_and_class description bom partsgroup runningnumber);
1005 if (($i >= 1) && ($i == $numrows)) {
1006 if (!$form->{previousform}) {
1007 $row{partnumber}{data} = qq|<input name="partnumber_$i" size=15 value="$form->{"partnumber_$i"}">|;
1008 $row{qty}{data} = qq|<input name="qty_$i" size=5 value="$form->{"qty_$i"}">|;
1009 $row{description}{data} = qq|<input name="description_$i" size=40 value="$form->{"description_$i"}">|;
1010 $row{partsgroup}{data} = qq|<input name="partsgroup_$i" size=10 value="$form->{"partsgroup_$i"}">|;
1014 if ($form->{previousform}) {
1015 push @row_hiddens, qw(qty bom);
1016 $row{partnumber}{data} = $form->{"partnumber_$i"};
1017 $row{qty}{data} = $form->{"qty_$i"};
1018 $row{bom}{data} = $form->{"bom_$i"} ? "x" : " ";
1019 $row{qty}{align} = 'right';
1021 $row{partnumber}{data} = qq|$form->{"partnumber_$i"}|;
1022 $row{partnumber}{link} = $href;
1023 $row{qty}{data} = qq|<input name="qty_$i" size=5 value="$form->{"qty_$i"}">|;
1024 $row{runningnumber}{data} = qq|<input name="runningnumber_$i" size=3 value="$i">|;
1025 $row{bom}{data} = sprintf qq|<input name="bom_$i" type=checkbox class=checkbox value=1 %s>|,
1026 $form->{"bom_$i"} ? 'checked' : '';
1028 # type impl $row{type_and_classific}{data} = $::request->presenter->type_abbreviation($form->{"type_$i"}).
1029 $row{type_and_classific}{data} = $::request->presenter->type_abbreviation($form->{"assembly_$i"},$form->{"inventory_accno_id_$i"}).
1030 $::request->presenter->classification_abbreviation($form->{"classification_id_$i"});
1031 push @row_hiddens, qw(unit description partnumber partsgroup);
1032 $row{unit}{data} = $form->{"unit_$i"};
1033 #Bei der Artikelbeschreibung und Warengruppe können Sonderzeichen verwendet
1034 #werden, die den HTML Code stören. Daher sollen diese im Template escaped werden
1035 #dies geschieht, wenn die Variable escape gesetzt ist
1036 $row{description}{data} = $form->{"description_$i"};
1037 $row{description}{escape} = 1;
1038 $row{partsgroup}{data} = $form->{"partsgroup_$i"};
1039 $row{partsgroup}{escape} = 1;
1040 $row{bom}{align} = 'center';
1043 $row{lastcost}{data} = $line_purchase_price;
1044 $row{total}{data} = $linetotal;
1045 $row{lastcost}{align} = 'right';
1046 $row{total}{align} = 'right';
1047 $row{deliverydate}{align} = 'right';
1049 push @row_hiddens, qw(id sellprice lastcost weight price_factor_id price_factor);
1050 $row{hiddens} = [ map +{ name => "${_}_$i", value => $form->{"${_}_$i"} }, @row_hiddens ];
1055 print $form->parse_html_template('ic/assembly_row', { COLUMNS => \@column_index, ROWS => \@ROWS, HEADER => \%header });
1057 $lxdebug->leave_sub();
1061 $lxdebug->enter_sub();
1063 $auth->assert('part_service_assembly_edit');
1065 # update checks whether pricegroups, makemodels or assembly items have been changed/added
1066 # new items might have been added (and the original form might have been stored and restored)
1067 # so at the end the ic form is run through check_form in io.pl
1068 # The various combination of events can lead to problems with the order of parse_amount and format_amount
1069 # Currently check_form parses some variables in assembly mode, but not in article or service mode
1070 # This will only ever really be sanely resolved with a rewrite...
1072 # parse pricegroups. and no, don't rely on check_form for this...
1073 map { $form->{"price_$_"} = $form->parse_amount(\%myconfig, $form->{"price_$_"}) } 1 .. $form->{price_rows};
1075 unless ($form->{part_type} eq 'assembly') {
1076 # for assemblies check_form will parse sellprice and listprice, but not for parts or services
1077 $form->{$_} = $form->parse_amount(\%myconfig, $form->{$_}) for qw(sellprice listprice ve gv);
1080 if ($form->{part_type} eq 'part') {
1081 $form->{$_} = $form->parse_amount(\%myconfig, $form->{$_}) for qw(weight rop);
1084 # same for makemodel lastcosts
1085 # but parse_amount not necessary for assembly component lastcosts
1086 unless ($form->{part_type} eq "assembly") {
1087 map { $form->{"lastcost_$_"} = $form->parse_amount(\%myconfig, $form->{"lastcost_$_"}) } 1 .. $form->{"makemodel_rows"};
1088 $form->{lastcost} = $form->parse_amount(\%myconfig, $form->{lastcost});
1091 if ($form->{part_type} eq "assembly") {
1092 my $i = $form->{assembly_rows};
1094 # if last row is empty check the form otherwise retrieve item
1095 if ( ($form->{"partnumber_$i"} eq "")
1096 && ($form->{"description_$i"} eq "")
1097 && ($form->{"partsgroup_$i"} eq "")) {
1098 # no new assembly item was added
1103 # search db for newly added assemblyitems, via partnumber or description
1104 IC->assembly_item(\%myconfig, \%$form);
1106 # form->{item_list} contains the possible matches, next check whether the
1107 # match is unique or we need to call the page to select the item
1108 my $rows = scalar @{ $form->{item_list} };
1111 $form->{"qty_$i"} = 1 unless ($form->{"qty_$i"});
1114 $form->{makemodel_rows}--;
1115 select_item(mode => 'IC', pre_entered_qty => $form->parse_amount(\%myconfig, $form->{"qty_$i"}));
1116 $::dispatcher->end_request;
1118 map { $form->{item_list}[$i]{$_} =~ s/\"/"/g }
1119 qw(partnumber description unit partsgroup);
1120 map { $form->{"${_}_$i"} = $form->{item_list}[0]{$_} }
1121 keys %{ $form->{item_list}[0] };
1122 $form->{"runningnumber_$i"} = $form->{assembly_rows};
1123 $form->{assembly_rows}++;
1131 $form->{rowcount} = $i;
1132 $form->{assembly_rows}++;
1139 } elsif (($form->{part_type} eq 'part') || ($form->{part_type} eq 'service')) {
1143 $lxdebug->leave_sub();
1147 $lxdebug->enter_sub();
1149 $auth->assert('part_service_assembly_edit');
1150 $::form->mtime_ischanged('parts');
1151 my ($parts_id, %newform, $amount, $callback);
1153 # check if there is a part number - commented out, cause there is an automatic allocation of numbers
1154 # $form->isblank("partnumber", $locale->text(ucfirst $form->{part_type}." Part Number missing!"));
1156 # check if there is a description
1157 $form->isblank("description", $locale->text("Part Description missing!"));
1159 $form->error($locale->text("Inventory quantity must be zero before you can set this $form->{part_type} obsolete!"))
1160 if $form->{obsolete} && $form->{onhand} * 1 && $form->{part_type} ne 'service';
1162 if (!$form->{buchungsgruppen_id}) {
1163 $form->error($locale->text("Parts must have an entry type.") . " " .
1164 $locale->text("If you see this message, you most likely just setup your LX-Office and haven't added any entry types. If this is the case, the option is accessible for administrators in the System menu.")
1168 $form->error($locale->text('Description must not be empty!')) unless $form->{description};
1169 $form->error($locale->text('Partnumber must not be set to empty!')) if $form->{id} && !$form->{partnumber};
1171 # undef warehouse_id if the empty value is selected
1172 if ( ($form->{warehouse_id} == 0) && ($form->{bin_id} == 0) ) {
1173 undef $form->{warehouse_id};
1174 undef $form->{bin_id};
1177 if (IC->save(\%myconfig, \%$form) == 3) {
1178 $form->error($locale->text('Partnumber not unique!'));
1180 # saving the history
1181 if(!exists $form->{addition}) {
1182 $form->{snumbers} = qq|partnumber_| . $form->{partnumber};
1183 $form->{what_done} = "part";
1184 $form->{addition} = "SAVED";
1185 $form->save_history;
1187 # /saving the history
1188 $parts_id = $form->{id};
1191 # load previous variables
1192 if ($form->{previousform}) {
1194 # save the new form variables before splitting previousform
1195 map { $newform{$_} = $form->{$_} } keys %$form;
1197 # don't trample on previous variables
1198 map { delete $form->{$_} } keys %newform;
1200 my $ic_cvar_configs = CVar->get_configs(module => 'IC');
1201 my @ic_cvar_fields = map { "cvar_$_->{name}" } @{ $ic_cvar_configs };
1203 # restore original values
1204 $::auth->restore_form_from_session($newform{previousform}, form => $form);
1205 $form->{taxaccounts} = $newform{taxaccount2};
1207 if ($form->{part_type} eq 'assembly') {
1209 # undo number formatting
1210 map { $form->{$_} = $form->parse_amount(\%myconfig, $form->{$_}) }
1211 qw(weight listprice sellprice rop);
1213 $form->{assembly_rows}--;
1214 if ($newform{currow}) {
1215 $i = $newform{currow};
1217 $i = $form->{assembly_rows};
1219 $form->{"qty_$i"} = 1 unless ($form->{"qty_$i"} > 0);
1221 $form->{sellprice} -= $form->{"sellprice_$i"} * $form->{"qty_$i"};
1222 $form->{weight} -= $form->{"weight_$i"} * $form->{"qty_$i"};
1224 # change/add values for assembly item
1225 map { $form->{"${_}_$i"} = $newform{$_} } qw(partnumber description bin unit weight listprice sellprice inventory_accno income_accno expense_accno price_factor_id);
1226 map { $form->{"ic_${_}_$i"} = $newform{$_} } @ic_cvar_fields;
1228 # das ist __voll__ bekloppt, dass so auszurechnen jb 22.5.09
1229 #$form->{sellprice} += $form->{"sellprice_$i"} * $form->{"qty_$i"};
1230 $form->{weight} += $form->{"weight_$i"} * $form->{"qty_$i"};
1234 # set values for last invoice/order item
1235 $i = $form->{rowcount};
1236 $form->{"qty_$i"} = 1 unless ($form->{"qty_$i"} > 0);
1238 map { $form->{"${_}_$i"} = $newform{$_} } qw(partnumber description bin unit listprice inventory_accno income_accno expense_accno sellprice lastcost price_factor_id);
1239 map { $form->{"ic_${_}_$i"} = $newform{$_} } @ic_cvar_fields;
1241 $form->{"longdescription_$i"} = $newform{notes};
1243 $form->{"sellprice_$i"} = $newform{lastcost} if ($form->{vendor_id});
1245 if ($form->{exchangerate} != 0) {
1246 $form->{"sellprice_$i"} /= $form->{exchangerate};
1249 map { $form->{"taxaccounts_$i"} .= "$_ " } split / /, $newform{taxaccount};
1250 chop $form->{"taxaccounts_$i"};
1251 foreach my $item (qw(description rate taxnumber)) {
1252 my $index = $form->{"taxaccounts_$i"} . "_$item";
1253 $form->{$index} = $newform{$index};
1256 # credit remaining calculation
1257 $amount = $form->{"sellprice_$i"} * (1 - $form->{"discount_$i"} / 100) * $form->{"qty_$i"};
1259 map { $form->{"${_}_base"} += $amount } (split / /, $form->{"taxaccounts_$i"});
1260 map { $amount += ($form->{"${_}_base"} * $form->{"${_}_rate"}) } split / /, $form->{"taxaccounts_$i"} if !$form->{taxincluded};
1262 $form->{creditremaining} -= $amount;
1264 # redo number formatting, because invoice parse them!
1265 map { $form->{"${_}_$i"} = $form->format_amount(\%myconfig, $form->{"${_}_$i"}) } qw(weight listprice sellprice lastcost rop);
1268 $form->{"id_$i"} = $parts_id;
1270 # Get the actual price factor (not just the ID) for the marge calculation.
1271 $form->get_lists('price_factors' => 'ALL_PRICE_FACTORS');
1272 foreach my $pfac (@{ $form->{ALL_PRICE_FACTORS} }) {
1273 next if ($pfac->{id} != $newform{price_factor_id});
1274 $form->{"marge_price_factor_$i"} = $pfac->{factor};
1277 delete $form->{ALL_PRICE_FACTORS};
1279 delete $form->{action};
1281 # restore original callback
1282 $callback = $form->unescape($form->{callback});
1283 $form->{callback} = $form->unescape($form->{old_callback});
1284 delete $form->{old_callback};
1286 $form->{makemodel_rows}--;
1288 # put callback together
1289 foreach my $key (keys %$form) {
1291 # do single escape for Apache 2.0
1292 my $value = $form->escape($form->{$key}, 1);
1293 $callback .= qq|&$key=$value|;
1295 $form->{callback} = $callback;
1301 $lxdebug->leave_sub();
1305 $lxdebug->enter_sub();
1307 $auth->assert('part_service_assembly_edit');
1309 # saving the history
1310 if(!exists $form->{addition}) {
1311 $form->{snumbers} = qq|partnumber_| . $form->{partnumber};
1312 $form->{addition} = "SAVED AS NEW";
1313 $form->{what_done} = "part";
1314 $form->save_history;
1316 # /saving the history
1318 # deleting addition to get the history saved for the new part, too.
1319 delete $form->{addition};
1322 if ($form->{"original_partnumber"} &&
1323 ($form->{"partnumber"} eq $form->{"original_partnumber"})) {
1324 $form->{partnumber} = "";
1327 $lxdebug->leave_sub();
1331 $lxdebug->enter_sub();
1333 $auth->assert('part_service_assembly_edit');
1335 # saving the history
1336 if(!exists $form->{addition}) {
1337 $form->{snumbers} = qq|partnumber_| . $form->{partnumber};
1338 $form->{addition} = "DELETED";
1339 $form->{what_done} = "part";
1340 $form->save_history;
1342 # /saving the history
1343 my $rc = IC->delete(\%myconfig, \%$form);
1346 $form->redirect($locale->text('Item deleted!')) if ($rc > 0);
1347 $form->error($locale->text('Cannot delete item!'));
1349 $lxdebug->leave_sub();
1353 $lxdebug->enter_sub();
1355 $auth->assert('part_service_assembly_details');
1360 pricegroup => $form->{"pricegroup_$_"},
1361 pricegroup_id => $form->{"pricegroup_id_$_"},
1362 price => $form->{"price_$_"},
1365 print $form->parse_html_template('ic/price_row', { PRICES => \@PRICES });
1367 $lxdebug->leave_sub();
1370 sub ajax_autocomplete {
1371 $main::lxdebug->enter_sub();
1373 my $form = $main::form;
1374 my %myconfig = %main::myconfig;
1376 $form->{column} = 'description' unless $form->{column} =~ /^partnumber|description$/;
1377 $form->{$form->{column}} = $form->{q} || '';
1378 $form->{limit} = ($form->{limit} * 1) || 10;
1379 $form->{searchitems} ||= '';
1381 my @results = IC->all_parts(\%myconfig, $form);
1383 print $form->ajax_response_header(),
1384 $form->parse_html_template('ic/ajax_autocomplete');
1386 $main::lxdebug->leave_sub();
1390 $::lxdebug->enter_sub;
1392 $auth->assert('part_service_assembly_edit');
1396 $::form->language_payment(\%::myconfig);
1398 Common::webdav_folder($::form);
1401 price_row($::form->{price_rows});
1402 makemodel_row(++$::form->{makemodel_rows}) if $::form->{part_type} =~ /^(part|service)$/;
1403 assembly_row(++$::form->{assembly_rows}) if $::form->{part_type} eq 'assembly';
1407 $::lxdebug->leave_sub;
1410 sub back_to_record {
1414 delete @{$::form}{qw(action action_add action_back_to_record back_sub description item notes partnumber sellprice taxaccount2 unit vc)};
1416 $::auth->restore_form_from_session($::form->{previousform}, clobber => 1);
1417 $::form->{rowcount}--;
1418 $::form->{action} = 'display_form';
1419 $::form->{callback} = $::form->{script} . '?' . join('&', map { $::form->escape($_) . '=' . $::form->escape($::form->{$_}) } sort keys %{ $::form });
1423 sub continue { call_sub($form->{"nextsub"}); }
1426 my $action = first { $::form->{"action_${_}"} } qw(add back_to_record);
1427 $::form->error($::locale->text('No action defined.')) unless $action;
1429 $::form->{dispatched_action} = $action;