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->{item};
83 $form->{title} = $locale->text($title);
84 $form->{callback} = "$form->{script}?action=add&item=$form->{item}" 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 my %is_xyz = map { +"is_$_" => ($form->{searchitems} eq $_) } qw(part service assembly);
105 $form->{title} = (ucfirst $form->{searchitems}) . "s";
106 $form->{title} = $locale->text($form->{title});
107 $form->{title} = $locale->text('Assemblies') if ($is_xyz{is_assembly});
109 $form->{CUSTOM_VARIABLES} = CVar->get_configs('module' => 'IC');
110 ($form->{CUSTOM_VARIABLES_FILTER_CODE},
111 $form->{CUSTOM_VARIABLES_INCLUSION_CODE}) = CVar->render_search_options('variables' => $form->{CUSTOM_VARIABLES},
112 'include_prefix' => 'l_',
113 'include_value' => 'Y');
117 $form->get_lists('partsgroup' => 'ALL_PARTSGROUPS');
118 print $form->parse_html_template('ic/search', { %is_xyz, });
120 $lxdebug->leave_sub();
123 sub search_update_prices {
124 $lxdebug->enter_sub();
126 $auth->assert('part_service_assembly_edit');
128 my $pricegroups = IC->get_pricegroups(\%myconfig, \%$form);
130 $form->{title} = $locale->text('Update Prices');
134 print $form->parse_html_template('ic/search_update_prices', { PRICE_ROWS => $pricegroups });
136 $lxdebug->leave_sub();
139 sub confirm_price_update {
140 $lxdebug->enter_sub();
142 $auth->assert('part_service_assembly_edit');
145 my $value_found = undef;
147 foreach my $idx (qw(sellprice listprice), (1..$form->{price_rows})) {
148 my $name = $idx =~ m/\d/ ? $form->{"pricegroup_${idx}"} : $idx eq 'sellprice' ? $locale->text('Sell Price') : $locale->text('List Price');
149 my $type = $idx =~ m/\d/ ? $form->{"pricegroup_type_${idx}"} : $form->{"${idx}_type"};
150 my $value_idx = $idx =~ m/\d/ ? "price_${idx}" : $idx;
151 my $value = $form->parse_amount(\%myconfig, $form->{$value_idx});
153 if ((0 > $value) && ($type eq 'percent')) {
154 push @errors, $locale->text('You cannot adjust the price for pricegroup "#1" by a negative percentage.', $name);
156 } elsif (!$value && ($form->{$value_idx} ne '')) {
157 push @errors, $locale->text('No valid number entered for pricegroup "#1".', $name);
159 } elsif (0 < $value) {
164 push @errors, $locale->text('No prices will be updated because no prices have been entered.') if (!$value_found);
166 my $num_matches = IC->get_num_matches_for_priceupdate();
171 $form->show_generic_error(join('<br>', @errors), 'back_button' => 1);
174 $form->{nextsub} = "update_prices";
176 map { delete $form->{$_} } qw(action header);
178 print $form->parse_html_template('ic/confirm_price_update', { HIDDENS => [ map { name => $_, value => $form->{$_} }, keys %$form ],
179 num_matches => $num_matches });
181 $lxdebug->leave_sub();
185 $lxdebug->enter_sub();
187 $auth->assert('part_service_assembly_edit');
189 my $num_updated = IC->update_prices(\%myconfig, \%$form);
191 if (-1 != $num_updated) {
192 $form->redirect($locale->text('#1 prices were updated.', $num_updated));
194 $form->error($locale->text('Could not update prices!'));
197 $lxdebug->leave_sub();
201 $::lxdebug->enter_sub();
203 $::auth->assert('part_service_assembly_edit');
205 $::form->{l_soldtotal} = "Y";
206 $::form->{sort} = "soldtotal";
207 $::form->{lastsort} = "soldtotal";
209 $::form->{l_qty} = undef;
210 $::form->{l_linetotal} = undef;
211 $::form->{l_number} = "Y";
212 $::form->{number} = "position";
214 unless ( $::form->{bought}
217 || $::form->{quoted}) {
218 $::form->{bought} = $::form->{sold} = 1;
223 $lxdebug->leave_sub();
228 # Warning, deep magic ahead.
229 # This function parses the requested details, sanity checks them, and converts them into a format thats usable for IC->all_parts
231 # flags coming from the form:
233 # searchitems=part revers=0 lastsort=''
236 # partnumber ean description partsgroup serialnumber make model drawing microfiche
237 # transdatefrom transdateto
240 # itemstatus = active | onhand | short | obsolete | orphaned
241 # action = continue | top100
244 # bought sold onorder ordered rfq quoted
245 # l_partnumber l_description l_serialnumber l_unit l_listprice l_sellprice l_lastcost
246 # l_linetotal l_priceupdate l_bin l_rop l_weight l_image l_drawing l_microfiche
247 # l_partsgroup l_subtotal l_soldtotal l_deliverydate l_pricegroups
250 # nextsub revers lastsort sort ndxs_counter
252 sub generate_report {
253 $lxdebug->enter_sub();
255 $auth->assert('part_service_assembly_details');
257 my ($revers, $lastsort, $description);
259 my $cvar_configs = CVar->get_configs('module' => 'IC');
261 $form->{title} = (ucfirst $form->{searchitems}) . "s";
262 $form->{title} =~ s/ys$/ies/;
263 $form->{title} = $locale->text($form->{title});
266 'bin' => { 'text' => $locale->text('Bin'), },
267 'deliverydate' => { 'text' => $locale->text('deliverydate'), },
268 'description' => { 'text' => $locale->text('Part Description'), },
269 'notes' => { 'text' => $locale->text('Notes'), },
270 'drawing' => { 'text' => $locale->text('Drawing'), },
271 'ean' => { 'text' => $locale->text('EAN'), },
272 'image' => { 'text' => $locale->text('Image'), },
273 'insertdate' => { 'text' => $locale->text('Insert Date'), },
274 'invnumber' => { 'text' => $locale->text('Invoice Number'), },
275 'lastcost' => { 'text' => $locale->text('Last Cost'), },
276 'linetotallastcost' => { 'text' => $locale->text('Extended'), },
277 'linetotallistprice' => { 'text' => $locale->text('Extended'), },
278 'linetotalsellprice' => { 'text' => $locale->text('Extended'), },
279 'listprice' => { 'text' => $locale->text('List Price'), },
280 'microfiche' => { 'text' => $locale->text('Microfiche'), },
281 'name' => { 'text' => $locale->text('Name'), },
282 'onhand' => { 'text' => $locale->text('Stocked Qty'), },
283 'ordnumber' => { 'text' => $locale->text('Order Number'), },
284 'partnumber' => { 'text' => $locale->text('Part Number'), },
285 'partsgroup' => { 'text' => $locale->text('Group'), },
286 'priceupdate' => { 'text' => $locale->text('Updated'), },
287 'quonumber' => { 'text' => $locale->text('Quotation'), },
288 'rop' => { 'text' => $locale->text('ROP'), },
289 'sellprice' => { 'text' => $locale->text('Sell Price'), },
290 'serialnumber' => { 'text' => $locale->text('Serial Number'), },
291 'soldtotal' => { 'text' => $locale->text('Qty in Selected Records'), },
292 'name' => { 'text' => $locale->text('Name in Selected Records'), },
293 'transdate' => { 'text' => $locale->text('Transdate'), },
294 'unit' => { 'text' => $locale->text('Unit'), },
295 'weight' => { 'text' => $locale->text('Weight'), },
296 'shop' => { 'text' => $locale->text('Shop article'), },
297 'projectnumber' => { 'text' => $locale->text('Project Number'), },
298 'projectdescription' => { 'text' => $locale->text('Project Description'), },
301 $revers = $form->{revers};
302 $lastsort = $form->{lastsort};
304 # sorting and direction of sorting
305 # ToDO: change this to the simpler field+direction method
306 if (($form->{lastsort} eq "") && ($form->{sort} eq undef)) {
308 $form->{lastsort} = "partnumber";
309 $form->{sort} = "partnumber";
311 if ($form->{lastsort} eq $form->{sort}) {
312 $form->{revers} = 1 - $form->{revers};
315 $form->{lastsort} = $form->{sort};
319 # special case if we have a serialnumber limit search
320 # serialnumbers are only given in invoices and orders,
321 # so they can only pop up in bought, sold, rfq, and quoted stuff
322 $form->{no_sn_joins} = 'Y' if ( !$form->{bought} && !$form->{sold}
323 && !$form->{rfq} && !$form->{quoted}
324 && ($form->{l_serialnumber} || $form->{serialnumber}));
326 # special case for any checkbox of bought | sold | onorder | ordered | rfq | quoted.
327 # if any of these are ticked the behavior changes slightly for lastcost
328 # since all those are aggregation checks for the legder tables this is an internal switch
329 # refered to as ledgerchecks
330 $form->{ledgerchecks} = 'Y' if ( $form->{bought} || $form->{sold} || $form->{onorder}
331 || $form->{ordered} || $form->{rfq} || $form->{quoted});
333 # if something should be activated if something else is active, enter it here
335 onhand => [ qw(l_onhand) ],
336 short => [ qw(l_onhand) ],
337 onorder => [ qw(l_ordnumber) ],
338 ordered => [ qw(l_ordnumber) ],
339 rfq => [ qw(l_quonumber) ],
340 quoted => [ qw(l_quonumber) ],
341 bought => [ qw(l_invnumber) ],
342 sold => [ qw(l_invnumber) ],
343 ledgerchecks => [ qw(l_name) ],
344 serialnumber => [ qw(l_serialnumber) ],
345 no_sn_joins => [ qw(bought sold) ],
348 # get name of partsgroup if id is given
350 if ($form->{partsgroup_id}) {
351 my $pg = SL::DB::PartsGroup->new(id => $form->{partsgroup_id})->load;
352 $pg_name = $pg->{'partsgroup'};
355 # these strings get displayed at the top of the results to indicate the user which switches were used
357 active => $locale->text('Active'),
358 obsolete => $locale->text('Obsolete'),
359 orphaned => $locale->text('Orphaned'),
360 onhand => $locale->text('On Hand'),
361 short => $locale->text('Short'),
362 onorder => $locale->text('On Order'),
363 ordered => $locale->text('Ordered'),
364 rfq => $locale->text('RFQ'),
365 quoted => $locale->text('Quoted'),
366 bought => $locale->text('Bought'),
367 sold => $locale->text('Sold'),
368 transdatefrom => $locale->text('From') . " " . $locale->date(\%myconfig, $form->{transdatefrom}, 1),
369 transdateto => $locale->text('To (time)') . " " . $locale->date(\%myconfig, $form->{transdateto}, 1),
370 partnumber => $locale->text('Part Number') . ": '$form->{partnumber}'",
371 partsgroup => $locale->text('Group') . ": '$form->{partsgroup}'",
372 partsgroup_id => $locale->text('Group') . ": '$pg_name'",
373 serialnumber => $locale->text('Serial Number') . ": '$form->{serialnumber}'",
374 description => $locale->text('Part Description') . ": '$form->{description}'",
375 make => $locale->text('Make') . ": '$form->{make}'",
376 model => $locale->text('Model') . ": '$form->{model}'",
377 drawing => $locale->text('Drawing') . ": '$form->{drawing}'",
378 microfiche => $locale->text('Microfiche') . ": '$form->{microfiche}'",
379 l_soldtotal => $locale->text('Qty in Selected Records'),
380 ean => $locale->text('EAN') . ": '$form->{ean}'",
381 insertdatefrom => $locale->text('Insert Date') . ": " . $locale->text('From') . " " . $locale->date(\%myconfig, $form->{insertdatefrom}, 1),
382 insertdateto => $locale->text('Insert Date') . ": " . $locale->text('To (time)') . " " . $locale->date(\%myconfig, $form->{insertdateto}, 1),
385 my @itemstatus_keys = qw(active obsolete orphaned onhand short);
386 my @callback_keys = qw(onorder ordered rfq quoted bought sold partnumber partsgroup partsgroup_id serialnumber description make model
387 drawing microfiche l_soldtotal l_deliverydate transdatefrom transdateto insertdatefrom insertdateto ean shop all);
389 # calculate dependencies
390 for (@itemstatus_keys, @callback_keys) {
391 next if ($form->{itemstatus} ne $_ && !$form->{$_});
392 map { $form->{$_} = 'Y' } @{ $dependencies{$_} } if $dependencies{$_};
395 # generate callback and optionstrings
397 for my $key (@itemstatus_keys, @callback_keys) {
398 next if ($form->{itemstatus} ne $key && !$form->{$key});
399 push @options, $optiontexts{$key};
402 # special case for lastcost
403 if ($form->{ledgerchecks}){
404 # ledgerchecks don't know about sellprice or lastcost. they just return a
405 # price. so rename sellprice to price, and drop lastcost.
406 $column_defs{sellprice}{text} = $locale->text('Price');
407 $form->{l_lastcost} = ""
410 if ($form->{description}) {
411 $description = $form->{description};
412 $description =~ s/\n/<br>/g;
415 if ($form->{l_linetotal}) {
416 $form->{l_qty} = "Y";
417 $form->{l_linetotalsellprice} = "Y" if $form->{l_sellprice};
418 $form->{l_linetotallastcost} = $form->{searchitems} eq 'assembly' && !$form->{bom} ? "" : 'Y' if $form->{l_lastcost};
419 $form->{l_linetotallistprice} = "Y" if $form->{l_listprice};
422 if ($form->{searchitems} eq 'service') {
424 # remove bin, weight and rop from list
425 map { $form->{"l_$_"} = "" } qw(bin weight rop);
427 $form->{l_onhand} = "";
429 # qty is irrelevant unless bought or sold
435 || $form->{quoted}) {
436 # $form->{l_onhand} = "Y";
438 $form->{l_linetotalsellprice} = "";
439 $form->{l_linetotallastcost} = "";
443 # soldtotal doesn't make sense with more than one bsooqr option.
444 # so reset it to sold (the most common option), and issue a warning
446 # also it doesn't make sense without bsooqr. disable and issue a warning too
447 my @bsooqr = qw(sold bought onorder ordered rfq quoted);
448 my $bsooqr_mode = grep { $form->{$_} } @bsooqr;
449 if ($form->{l_subtotal} && 1 < $bsooqr_mode) {
450 my $enabled = first { $form->{$_} } @bsooqr;
451 $form->{$_} = '' for @bsooqr;
452 $form->{$enabled} = 'Y';
454 push @options, $::locale->text('Subtotal cannot distinguish betweens record types. Only one of the selected record types will be displayed: #1', $optiontexts{$enabled});
456 if ($form->{l_soldtotal} && !$bsooqr_mode) {
457 delete $form->{l_soldtotal};
459 flash('warning', $::locale->text('Soldtotal does not make sense without any bsooqr options'));
461 if ($form->{l_name} && !$bsooqr_mode) {
462 delete $form->{l_name};
464 flash('warning', $::locale->text('Name does not make sense without any bsooqr options'));
466 IC->all_parts(\%myconfig, \%$form);
469 partnumber description notes partsgroup bin onhand rop soldtotal unit listprice
470 linetotallistprice sellprice linetotalsellprice lastcost linetotallastcost
471 priceupdate weight image drawing microfiche invnumber ordnumber quonumber
472 transdate name serialnumber deliverydate ean projectnumber projectdescription
476 my $pricegroups = SL::DB::Manager::Pricegroup->get_all(sort => 'id');
477 my @pricegroup_columns;
478 my %column_defs_pricegroups;
479 if ($form->{l_pricegroups}) {
480 @pricegroup_columns = map { "pricegroup_" . $_->id } @{ $pricegroups };
481 %column_defs_pricegroups = map {
482 "pricegroup_" . $_->id => {
483 text => $::locale->text('Pricegroup') . ' ' . $_->pricegroup,
488 push @columns, @pricegroup_columns;
490 my @includeable_custom_variables = grep { $_->{includeable} } @{ $cvar_configs };
491 my @searchable_custom_variables = grep { $_->{searchable} } @{ $cvar_configs };
492 my %column_defs_cvars = map { +"cvar_$_->{name}" => { 'text' => $_->{description} } } @includeable_custom_variables;
494 push @columns, map { "cvar_$_->{name}" } @includeable_custom_variables;
496 %column_defs = (%column_defs, %column_defs_cvars, %column_defs_pricegroups);
497 map { $column_defs{$_}->{visible} ||= $form->{"l_$_"} ? 1 : 0 } @columns;
498 map { $column_defs{$_}->{align} = 'right' } qw(onhand sellprice listprice lastcost linetotalsellprice linetotallastcost linetotallistprice rop weight soldtotal shop), @pricegroup_columns;
500 my @hidden_variables = (
501 qw(l_subtotal l_linetotal searchitems itemstatus bom l_pricegroups insertdatefrom insertdateto),
504 map({ "cvar_$_->{name}" } @searchable_custom_variables),
505 map({'cvar_'. $_->{name} .'_qtyop'} grep({$_->{type} eq 'number'} @searchable_custom_variables)),
506 map({ "l_$_" } @columns),
509 my $callback = build_std_url('action=generate_report', grep { $form->{$_} } @hidden_variables);
511 my @sort_full = qw(partnumber description onhand soldtotal deliverydate insertdate shop);
512 my @sort_no_revers = qw(partsgroup bin priceupdate invnumber ordnumber quonumber name image drawing serialnumber);
514 foreach my $col (@sort_full) {
515 $column_defs{$col}->{link} = join '&', $callback, "sort=$col", map { "$_=" . E($form->{$_}) } qw(revers lastsort);
517 map { $column_defs{$_}->{link} = "${callback}&sort=$_" } @sort_no_revers;
519 # add order to callback
520 $form->{callback} = join '&', ($callback, map { "${_}=" . E($form->{$_}) } qw(sort revers));
522 my $report = SL::ReportGenerator->new(\%myconfig, $form);
524 my %attachment_basenames = (
525 'part' => $locale->text('part_list'),
526 'service' => $locale->text('service_list'),
527 'assembly' => $locale->text('assembly_list'),
530 $report->set_options('raw_top_info_text' => $form->parse_html_template('ic/generate_report_top', { options => \@options }),
531 'raw_bottom_info_text' => $form->parse_html_template('ic/generate_report_bottom'),
532 'output_format' => 'HTML',
533 'title' => $form->{title},
534 'attachment_basename' => $attachment_basenames{$form->{searchitems}} . strftime('_%Y%m%d', localtime time),
536 $report->set_options_from_form();
537 $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
539 $report->set_columns(%column_defs);
540 $report->set_column_order(@columns);
542 $report->set_export_options('generate_report', @hidden_variables, qw(sort revers));
544 $report->set_sort_indicator($form->{sort}, $form->{revers} ? 0 : 1);
546 CVar->add_custom_variables_to_report('module' => 'IC',
547 'trans_id_field' => 'id',
548 'configs' => $cvar_configs,
549 'column_defs' => \%column_defs,
550 'data' => $form->{parts});
552 CVar->add_custom_variables_to_report('module' => 'IC',
553 'sub_module' => sub { $_[0]->{ioi} },
554 'trans_id_field' => 'ioi_id',
555 'configs' => $cvar_configs,
556 'column_defs' => \%column_defs,
557 'data' => $form->{parts});
559 my @subtotal_columns = qw(sellprice listprice lastcost);
560 my %subtotals = map { $_ => 0 } ('onhand', @subtotal_columns);
561 my %totals = map { $_ => 0 } @subtotal_columns;
563 my $same_item = @{ $form->{parts} } ? $form->{parts}[0]{ $form->{sort} } : undef;
565 my $defaults = AM->get_defaults();
568 foreach my $ref (@{ $form->{parts} }) {
570 # fresh row, for inserting later
571 my $row = { map { $_ => { 'data' => $ref->{$_} } } @columns };
573 $ref->{exchangerate} ||= 1;
574 $ref->{price_factor} ||= 1;
575 $ref->{sellprice} *= $ref->{exchangerate} / $ref->{price_factor};
576 $ref->{listprice} *= $ref->{exchangerate} / $ref->{price_factor};
577 $ref->{lastcost} *= $ref->{exchangerate} / $ref->{price_factor};
579 # use this for assemblies
580 my $soldtotal = $bsooqr_mode ? $ref->{soldtotal} : $ref->{onhand};
582 if ($ref->{assemblyitem}) {
583 $row->{partnumber}{align} = 'right';
584 $row->{soldtotal}{data} = 0;
585 $soldtotal = 0 if ($form->{sold});
588 my $edit_link = build_std_url('action=edit', 'id=' . E($ref->{id}), 'callback');
589 $row->{partnumber}->{link} = $edit_link;
590 $row->{description}->{link} = $edit_link;
592 foreach (qw(sellprice listprice lastcost)) {
593 $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{$_}, 2);
594 $row->{"linetotal$_"}{data} = $form->format_amount(\%myconfig, $ref->{onhand} * $ref->{$_}, 2);
596 foreach ( @pricegroup_columns ) {
597 $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{"$_"}, 2);
601 map { $row->{$_}{data} = $form->format_amount(\%myconfig, $ref->{$_}); } qw(onhand rop weight soldtotal);
603 $row->{weight}->{data} .= ' ' . $defaults->{weightunit};
605 # 'yes' and 'no' for boolean value shop
606 if ($form->{l_shop}) {
607 $row->{shop}{data} = $row->{shop}{data}? $::locale->text('yes') : $::locale->text('no');
610 if (!$ref->{assemblyitem}) {
611 foreach my $col (@subtotal_columns) {
612 $totals{$col} += $soldtotal * $ref->{$col};
613 $subtotals{$col} += $soldtotal * $ref->{$col};
616 $subtotals{soldtotal} += $soldtotal;
620 if ($ref->{module} eq 'oe') {
621 # für oe gibt es vier fälle, jeweils nach kunde oder lieferant unterschiedlich:
623 # | ist bestellt | Von Kunden bestellt | -> edit_oe_ord_link
624 # | Anfrage | Angebot | -> edit_oe_quo_link
626 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');
627 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');
629 $row->{ordnumber}{link} = $edit_oe_ord_link;
630 $row->{quonumber}{link} = $edit_oe_quo_link if (!$ref->{ordnumber});
633 $row->{invnumber}{link} = build_std_url("script=$ref->{module}.pl", 'action=edit', 'type=invoice', 'id=' . E($ref->{trans_id}), 'callback') if ($ref->{invnumber});
636 # set properties of images
637 if ($ref->{image} && (lc $report->{options}->{output_format} eq 'html')) {
638 $row->{image}{data} = '';
639 $row->{image}{raw_data} = '<a href="' . H($ref->{image}) . '"><img src="' . H($ref->{image}) . '" height="32" border="0"></a>';
641 map { $row->{$_}{link} = $ref->{$_} } qw(drawing microfiche);
643 $row->{notes}{data} = SL::HTML::Util->strip($ref->{notes});
645 $report->add_data($row);
647 my $next_ref = $form->{parts}[$idx + 1];
649 # insert subtotal rows
650 if (($form->{l_subtotal} eq 'Y') &&
652 (!$next_ref->{assemblyitem} && ($same_item ne $next_ref->{ $form->{sort} })))) {
653 my $row = { map { $_ => { 'class' => 'listsubtotal', } } @columns };
655 if (($form->{searchitems} ne 'assembly') || !$form->{bom}) {
656 $row->{soldtotal}->{data} = $form->format_amount(\%myconfig, $subtotals{soldtotal});
659 map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $subtotals{$_}, 2) } @subtotal_columns;
660 map { $subtotals{$_} = 0 } ('soldtotal', @subtotal_columns);
662 $report->add_data($row);
664 $same_item = $next_ref->{ $form->{sort} };
670 if ($form->{"l_linetotal"} && !$form->{report_generator_csv_options_for_import}) {
671 my $row = { map { $_ => { 'class' => 'listtotal', } } @columns };
673 map { $row->{"linetotal$_"}->{data} = $form->format_amount(\%myconfig, $totals{$_}, 2) } @subtotal_columns;
675 $report->add_separator();
676 $report->add_data($row);
679 $report->generate_with_headers();
681 $lxdebug->leave_sub();
682 } #end generate_report
685 $lxdebug->enter_sub();
687 $auth->assert('part_service_assembly_edit');
690 my ($column_index, $subtotalonhand, $subtotalsellprice, $subtotallastcost, $subtotallistprice) = @_;
692 map { $column_data{$_} = "<td> </td>" } @{ $column_index };
693 $$subtotalonhand = 0 if ($form->{searchitems} eq 'assembly' && $form->{bom});
695 $column_data{onhand} =
696 "<th class=listsubtotal align=right>"
697 . $form->format_amount(\%myconfig, $$subtotalonhand)
700 $column_data{linetotalsellprice} =
701 "<th class=listsubtotal align=right>"
702 . $form->format_amount(\%myconfig, $$subtotalsellprice, 2)
704 $column_data{linetotallistprice} =
705 "<th class=listsubtotal align=right>"
706 . $form->format_amount(\%myconfig, $$subtotallistprice, 2)
708 $column_data{linetotallastcost} =
709 "<th class=listsubtotal align=right>"
710 . $form->format_amount(\%myconfig, $$subtotallastcost, 2)
713 $$subtotalonhand = 0;
714 $$subtotalsellprice = 0;
715 $$subtotallistprice = 0;
716 $$subtotallastcost = 0;
718 print "<tr class=listsubtotal>";
720 map { print "\n$column_data{$_}" } @{ $column_index };
726 $lxdebug->leave_sub();
730 $lxdebug->enter_sub();
732 $auth->assert('part_service_assembly_details');
734 # show history button
735 $form->{javascript} = qq|<script type="text/javascript" src="js/show_history.js"></script>|;
736 #/show hhistory button
737 IC->get_part(\%myconfig, \%$form);
739 $form->{"original_partnumber"} = $form->{"partnumber"};
741 my $title = 'Edit ' . ucfirst $form->{item};
742 $form->{title} = $locale->text($title);
747 $lxdebug->leave_sub();
751 $lxdebug->enter_sub();
753 $auth->assert('part_service_assembly_details');
755 IC->create_links("IC", \%myconfig, \%$form);
758 map({ $form->{selectcurrency} .= "<option>$_\n" } $::form->get_all_currencies());
760 # parts and assemblies have the same links
761 my $item = $form->{item};
762 if ($form->{item} eq 'assembly') {
766 # build the popup menus
767 $form->{taxaccounts} = "";
768 foreach my $key (keys %{ $form->{IC_links} }) {
769 foreach my $ref (@{ $form->{IC_links}{$key} }) {
771 # if this is a tax field
772 if ($key =~ /IC_tax/) {
773 if ($key =~ /\Q$item\E/) {
774 $form->{taxaccounts} .= "$ref->{accno} ";
775 $form->{"IC_tax_$ref->{accno}_description"} =
776 "$ref->{accno}--$ref->{description}";
779 if ($form->{amount}{ $ref->{accno} }) {
780 $form->{"IC_tax_$ref->{accno}"} = "checked";
783 $form->{"IC_tax_$ref->{accno}"} = "checked";
788 $form->{"select$key"} .=
789 "<option $ref->{selected}>$ref->{accno}--$ref->{description}\n";
790 if ($form->{amount}{$key} eq $ref->{accno}) {
791 $form->{$key} = "$ref->{accno}--$ref->{description}";
797 chop $form->{taxaccounts};
799 if (($form->{item} eq "part") || ($form->{item} eq "assembly")) {
800 $form->{selectIC_income} = $form->{selectIC_sale};
801 $form->{selectIC_expense} = $form->{selectIC_cogs};
802 $form->{IC_income} = $form->{IC_sale};
803 $form->{IC_expense} = $form->{IC_cogs};
806 delete $form->{IC_links};
807 delete $form->{amount};
809 $form->get_partsgroup(\%myconfig, { all => 1 });
811 $form->{partsgroup} = "$form->{partsgroup}--$form->{partsgroup_id}";
813 if (@{ $form->{all_partsgroup} }) {
814 $form->{selectpartsgroup} = qq|<option>\n|;
815 map { $form->{selectpartsgroup} .= qq|<option value="$_->{partsgroup}--$_->{id}">$_->{partsgroup}\n| } @{ $form->{all_partsgroup} };
818 if ($form->{item} eq 'assembly') {
820 foreach my $i (1 .. $form->{assembly_rows}) {
821 if ($form->{"partsgroup_id_$i"}) {
822 $form->{"partsgroup_$i"} =
823 qq|$form->{"partsgroup_$i"}--$form->{"partsgroup_id_$i"}|;
826 $form->get_partsgroup(\%myconfig);
828 if (@{ $form->{all_partsgroup} }) {
829 $form->{selectassemblypartsgroup} = qq|<option>\n|;
832 $form->{selectassemblypartsgroup} .=
833 qq|<option value="$_->{partsgroup}--$_->{id}">$_->{partsgroup}\n|
834 } @{ $form->{all_partsgroup} };
837 $lxdebug->leave_sub();
841 $lxdebug->enter_sub();
843 $auth->assert('part_service_assembly_details');
845 $form->{pg_keys} = sub { "$_[0]->{partsgroup}--$_[0]->{id}" };
846 $form->{description_area} = ($form->{rows} = $form->numtextrows($form->{description}, 40)) > 1;
847 $form->{notes_rows} = max 4, $form->numtextrows($form->{notes}, 40), $form->numtextrows($form->{formel}, 40);
849 map { $form->{"is_$_"} = ($form->{item} eq $_) } qw(part service assembly);
850 map { $form->{$_} =~ s/"/"/g; } qw(unit);
852 $form->get_lists('price_factors' => 'ALL_PRICE_FACTORS',
853 'partsgroup' => 'all_partsgroup',
854 'vendors' => 'ALL_VENDORS',
855 'warehouses' => { 'key' => 'WAREHOUSES',
856 'bins' => 'BINS', });
857 # leerer wert für Lager und Lagerplatz korrekt einstellt
858 # ID 0 sollte in Ordnung sein, da der Zähler sowieso höher ist
859 my $no_default_bin_entry = { 'id' => '0', description => '--', 'BINS' => [ { id => '0', description => ''} ] };
860 push @ { $form->{WAREHOUSES} }, $no_default_bin_entry;
861 if (my $max = scalar @{ $form->{WAREHOUSES} }) {
862 my ($default_warehouse_id, $default_bin_id);
863 if ($form->{action} eq 'add') { # default only for new entries
864 $default_warehouse_id = $::instance_conf->get_warehouse_id;
865 $default_bin_id = $::instance_conf->get_bin_id;
867 $form->{warehouse_id} ||= $default_warehouse_id || $form->{WAREHOUSES}->[$max -1]->{id};
868 $form->{bin_id} ||= $default_bin_id || $form->{WAREHOUSES}->[$max -1]->{BINS}->[0]->{id};
871 $form->{LANGUAGES} = SL::DB::Manager::Language->get_all_sorted;
872 $form->{translations_map} = { map { ($_->{language_id} => $_) } @{ $form->{translations} || [] } };
874 IC->retrieve_buchungsgruppen(\%myconfig, $form);
875 @{ $form->{BUCHUNGSGRUPPEN} } = grep { $_->{id} eq $form->{buchungsgruppen_id} || ($form->{id} && $form->{orphaned}) || !$form->{id} } @{ $form->{BUCHUNGSGRUPPEN} };
877 if (($form->{partnumber} ne '') && !SL::TransNumber->new(number => $form->{partnumber}, type => $form->{item}, id => $form->{id})->is_unique) {
878 flash('info', $::locale->text('This partnumber is not unique. You should change it.'));
881 my $units = AM->retrieve_units(\%myconfig, $form);
882 $form->{ALL_UNITS} = [ map +{ name => $_ }, sort { $units->{$a}{sortkey} <=> $units->{$b}{sortkey} } keys %$units ];
884 $form->{defaults} = AM->get_defaults();
886 $form->{CUSTOM_VARIABLES} = CVar->get_custom_variables('module' => 'IC', 'trans_id' => $form->{id});
888 my ($null, $partsgroup_id) = split /--/, $form->{partsgroup};
890 CVar->render_inputs('variables' => $form->{CUSTOM_VARIABLES}, show_disabled_message => 1, partsgroup_id => $partsgroup_id)
891 if (scalar @{ $form->{CUSTOM_VARIABLES} });
893 $::request->layout->use_javascript("${_}.js") for qw(ckeditor/ckeditor ckeditor/adapters/jquery kivi.PriceRule);
894 $::request->layout->add_javascripts_inline("\$(function(){kivi.PriceRule.load_price_rules_for_part(@{[ $::form->{id} * 1 ]})});") if $::form->{id};
896 #print $form->parse_html_template('ic/form_header', { ALL_PRICE_FACTORS => $form->{ALL_PRICE_FACTORS},
897 # ALL_UNITS => $form->{ALL_UNITS},
898 # BUCHUNGSGRUPPEN => $form->{BUCHUNGSGRUPPEN},
899 # payment_terms => $form->{payment_terms},
900 # all_partsgroup => $form->{all_partsgroup}});
902 $form->{show_edit_buttons} = $main::auth->check_right($::myconfig{login}, 'part_service_assembly_edit');
904 print $form->parse_html_template('ic/form_header');
905 $lxdebug->leave_sub();
909 $lxdebug->enter_sub();
911 $auth->assert('part_service_assembly_details');
913 print $form->parse_html_template('ic/form_footer');
915 $lxdebug->leave_sub();
919 $lxdebug->enter_sub();
922 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;
923 delete @{$form}{grep { m/^make_\d+/ || m/^model_\d+/ } keys %{ $form }};
924 print $form->parse_html_template('ic/makemodel', { MM_DATA => [ @mm_data, {} ], mm_rows => scalar @mm_data + 1 });
926 $lxdebug->leave_sub();
930 $lxdebug->enter_sub();
933 my ($nochange, $callback, $previousform, $linetotal, $line_purchase_price, $href);
935 @column_index = qw(runningnumber qty unit bom partnumber description partsgroup lastcost total);
937 if ($form->{previousform}) {
939 @column_index = qw(qty unit bom partnumber description partsgroup total);
943 $form->{old_callback} = $form->{callback};
944 $callback = $form->{callback};
945 $form->{callback} = "$form->{script}?action=display_form";
948 map { delete $form->{$_} } qw(action header);
950 # save form variables in a previousform variable
951 my %form_to_save = map { ($_ => m/^ (?: listprice | sellprice | lastcost ) $/x ? $form->format_amount(\%myconfig, $form->{$_}) : $form->{$_}) }
953 $previousform = $::auth->save_form_in_session(form => \%form_to_save);
955 $form->{callback} = $callback;
956 $form->{assemblytotal} = 0;
957 $form->{assembly_purchase_price_total} = 0;
962 runningnumber => { text => $locale->text('No.'), nowrap => 1, width => '5%', align => 'left',},
963 qty => { text => $locale->text('Qty'), nowrap => 1, width => '10%', align => 'left',},
964 unit => { text => $locale->text('Unit'), nowrap => 1, width => '5%', align => 'left',},
965 partnumber => { text => $locale->text('Part Number'), nowrap => 1, width => '20%', align => 'left',},
966 description => { text => $locale->text('Part Description'), nowrap => 1, width => '50%', align => 'left',},
967 lastcost => { text => $locale->text('Purchase Prices'), nowrap => 1, width => '50%', align => 'right',},
968 total => { text => $locale->text('Sale Prices'), nowrap => 1, align => 'right',},
969 bom => { text => $locale->text('BOM'), align => 'center',},
970 partsgroup => { text => $locale->text('Group'), align => 'left',},
975 for my $i (1 .. $numrows) {
976 my (%row, @row_hiddens);
978 $form->{"partnumber_$i"} =~ s/\"/"/g;
980 $linetotal = $form->round_amount($form->{"sellprice_$i"} * $form->{"qty_$i"} / ($form->{"price_factor_$i"} || 1), 4);
981 $line_purchase_price = $form->round_amount($form->{"lastcost_$i"} * $form->{"qty_$i"} / ($form->{"price_factor_$i"} || 1), 4);
982 $form->{assemblytotal} += $linetotal;
983 $form->{assembly_purchase_price_total} += $line_purchase_price;
984 $form->{"qty_$i"} = $form->format_amount(\%myconfig, $form->{"qty_$i"});
985 $linetotal = $form->format_amount(\%myconfig, $linetotal, 2);
986 $line_purchase_price = $form->format_amount(\%myconfig, $line_purchase_price, 2);
987 $href = build_std_url("action=edit", qq|id=$form->{"id_$i"}|, "rowcount=$numrows", "currow=$i", "previousform=$previousform");
988 map { $row{$_}{data} = "" } qw(qty unit partnumber description bom partsgroup runningnumber);
991 if (($i >= 1) && ($i == $numrows)) {
992 if (!$form->{previousform}) {
993 $row{partnumber}{data} = qq|<input name="partnumber_$i" size=15 value="$form->{"partnumber_$i"}">|;
994 $row{qty}{data} = qq|<input name="qty_$i" size=5 value="$form->{"qty_$i"}">|;
995 $row{description}{data} = qq|<input name="description_$i" size=40 value="$form->{"description_$i"}">|;
996 $row{partsgroup}{data} = qq|<input name="partsgroup_$i" size=10 value="$form->{"partsgroup_$i"}">|;
1000 if ($form->{previousform}) {
1001 push @row_hiddens, qw(qty bom);
1002 $row{partnumber}{data} = $form->{"partnumber_$i"};
1003 $row{qty}{data} = $form->{"qty_$i"};
1004 $row{bom}{data} = $form->{"bom_$i"} ? "x" : " ";
1005 $row{qty}{align} = 'right';
1007 $row{partnumber}{data} = qq|$form->{"partnumber_$i"}|;
1008 $row{partnumber}{link} = $href;
1009 $row{qty}{data} = qq|<input name="qty_$i" size=5 value="$form->{"qty_$i"}">|;
1010 $row{runningnumber}{data} = qq|<input name="runningnumber_$i" size=3 value="$i">|;
1011 $row{bom}{data} = sprintf qq|<input name="bom_$i" type=checkbox class=checkbox value=1 %s>|,
1012 $form->{"bom_$i"} ? 'checked' : '';
1014 push @row_hiddens, qw(unit description partnumber partsgroup);
1015 $row{unit}{data} = $form->{"unit_$i"};
1016 #Bei der Artikelbeschreibung und Warengruppe können Sonderzeichen verwendet
1017 #werden, die den HTML Code stören. Daher sollen diese im Template escaped werden
1018 #dies geschieht, wenn die Variable escape gesetzt ist
1019 $row{description}{data} = $form->{"description_$i"};
1020 $row{description}{escape} = 1;
1021 $row{partsgroup}{data} = $form->{"partsgroup_$i"};
1022 $row{partsgroup}{escape} = 1;
1023 $row{bom}{align} = 'center';
1026 $row{lastcost}{data} = $line_purchase_price;
1027 $row{total}{data} = $linetotal;
1028 $row{lastcost}{align} = 'right';
1029 $row{total}{align} = 'right';
1030 $row{deliverydate}{align} = 'right';
1032 push @row_hiddens, qw(id sellprice lastcost weight price_factor_id price_factor);
1033 $row{hiddens} = [ map +{ name => "${_}_$i", value => $form->{"${_}_$i"} }, @row_hiddens ];
1038 print $form->parse_html_template('ic/assembly_row', { COLUMNS => \@column_index, ROWS => \@ROWS, HEADER => \%header });
1040 $lxdebug->leave_sub();
1044 $lxdebug->enter_sub();
1046 $auth->assert('part_service_assembly_edit');
1048 # update checks whether pricegroups, makemodels or assembly items have been changed/added
1049 # new items might have been added (and the original form might have been stored and restored)
1050 # so at the end the ic form is run through check_form in io.pl
1051 # The various combination of events can lead to problems with the order of parse_amount and format_amount
1052 # Currently check_form parses some variables in assembly mode, but not in article or service mode
1053 # This will only ever really be sanely resolved with a rewrite...
1055 # parse pricegroups. and no, don't rely on check_form for this...
1056 map { $form->{"price_$_"} = $form->parse_amount(\%myconfig, $form->{"price_$_"}) } 1 .. $form->{price_rows};
1058 unless ($form->{item} eq 'assembly') {
1059 # for assemblies check_form will parse sellprice and listprice, but not for parts or services
1060 $form->{$_} = $form->parse_amount(\%myconfig, $form->{$_}) for qw(sellprice listprice ve gv);
1063 if ($form->{item} eq 'part') {
1064 $form->{$_} = $form->parse_amount(\%myconfig, $form->{$_}) for qw(weight rop);
1067 # same for makemodel lastcosts
1068 # but parse_amount not necessary for assembly component lastcosts
1069 unless ($form->{item} eq "assembly") {
1070 map { $form->{"lastcost_$_"} = $form->parse_amount(\%myconfig, $form->{"lastcost_$_"}) } 1 .. $form->{"makemodel_rows"};
1071 $form->{lastcost} = $form->parse_amount(\%myconfig, $form->{lastcost});
1074 if ($form->{item} eq "assembly") {
1075 my $i = $form->{assembly_rows};
1077 # if last row is empty check the form otherwise retrieve item
1078 if ( ($form->{"partnumber_$i"} eq "")
1079 && ($form->{"description_$i"} eq "")
1080 && ($form->{"partsgroup_$i"} eq "")) {
1081 # no new assembly item was added
1086 # search db for newly added assemblyitems, via partnumber or description
1087 IC->assembly_item(\%myconfig, \%$form);
1089 # form->{item_list} contains the possible matches, next check whether the
1090 # match is unique or we need to call the page to select the item
1091 my $rows = scalar @{ $form->{item_list} };
1094 $form->{"qty_$i"} = 1 unless ($form->{"qty_$i"});
1097 $form->{makemodel_rows}--;
1098 select_item(mode => 'IC', pre_entered_qty => $form->parse_amount(\%myconfig, $form->{"qty_$i"}));
1099 $::dispatcher->end_request;
1101 map { $form->{item_list}[$i]{$_} =~ s/\"/"/g }
1102 qw(partnumber description unit partsgroup);
1103 map { $form->{"${_}_$i"} = $form->{item_list}[0]{$_} }
1104 keys %{ $form->{item_list}[0] };
1105 $form->{"runningnumber_$i"} = $form->{assembly_rows};
1106 $form->{assembly_rows}++;
1114 $form->{rowcount} = $i;
1115 $form->{assembly_rows}++;
1122 } elsif (($form->{item} eq 'part') || ($form->{item} eq 'service')) {
1126 $lxdebug->leave_sub();
1130 $lxdebug->enter_sub();
1132 $auth->assert('part_service_assembly_edit');
1133 $::form->mtime_ischanged('parts');
1134 my ($parts_id, %newform, $amount, $callback);
1136 # check if there is a part number - commented out, cause there is an automatic allocation of numbers
1137 # $form->isblank("partnumber", $locale->text(ucfirst $form->{item}." Part Number missing!"));
1139 # check if there is a description
1140 $form->isblank("description", $locale->text("Part Description missing!"));
1142 $form->error($locale->text("Inventory quantity must be zero before you can set this $form->{item} obsolete!"))
1143 if $form->{obsolete} && $form->{onhand} * 1 && $form->{item} ne 'service';
1145 if (!$form->{buchungsgruppen_id}) {
1146 $form->error($locale->text("Parts must have an entry type.") . " " .
1147 $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.")
1151 $form->error($locale->text('Description must not be empty!')) unless $form->{description};
1152 $form->error($locale->text('Partnumber must not be set to empty!')) if $form->{id} && !$form->{partnumber};
1154 # undef warehouse_id if the empty value is selected
1155 if ( ($form->{warehouse_id} == 0) && ($form->{bin_id} == 0) ) {
1156 undef $form->{warehouse_id};
1157 undef $form->{bin_id};
1160 if (IC->save(\%myconfig, \%$form) == 3) {
1161 $form->error($locale->text('Partnumber not unique!'));
1163 # saving the history
1164 if(!exists $form->{addition}) {
1165 $form->{snumbers} = qq|partnumber_| . $form->{partnumber};
1166 $form->{what_done} = "part";
1167 $form->{addition} = "SAVED";
1168 $form->save_history;
1170 # /saving the history
1171 $parts_id = $form->{id};
1174 # load previous variables
1175 if ($form->{previousform}) {
1177 # save the new form variables before splitting previousform
1178 map { $newform{$_} = $form->{$_} } keys %$form;
1180 # don't trample on previous variables
1181 map { delete $form->{$_} } keys %newform;
1183 my $ic_cvar_configs = CVar->get_configs(module => 'IC');
1184 my @ic_cvar_fields = map { "cvar_$_->{name}" } @{ $ic_cvar_configs };
1186 # restore original values
1187 $::auth->restore_form_from_session($newform{previousform}, form => $form);
1188 $form->{taxaccounts} = $newform{taxaccount2};
1190 if ($form->{item} eq 'assembly') {
1192 # undo number formatting
1193 map { $form->{$_} = $form->parse_amount(\%myconfig, $form->{$_}) }
1194 qw(weight listprice sellprice rop);
1196 $form->{assembly_rows}--;
1197 if ($newform{currow}) {
1198 $i = $newform{currow};
1200 $i = $form->{assembly_rows};
1202 $form->{"qty_$i"} = 1 unless ($form->{"qty_$i"} > 0);
1204 $form->{sellprice} -= $form->{"sellprice_$i"} * $form->{"qty_$i"};
1205 $form->{weight} -= $form->{"weight_$i"} * $form->{"qty_$i"};
1207 # change/add values for assembly item
1208 map { $form->{"${_}_$i"} = $newform{$_} } qw(partnumber description bin unit weight listprice sellprice inventory_accno income_accno expense_accno price_factor_id);
1209 map { $form->{"ic_${_}_$i"} = $newform{$_} } @ic_cvar_fields;
1211 # das ist __voll__ bekloppt, dass so auszurechnen jb 22.5.09
1212 #$form->{sellprice} += $form->{"sellprice_$i"} * $form->{"qty_$i"};
1213 $form->{weight} += $form->{"weight_$i"} * $form->{"qty_$i"};
1217 # set values for last invoice/order item
1218 $i = $form->{rowcount};
1219 $form->{"qty_$i"} = 1 unless ($form->{"qty_$i"} > 0);
1221 map { $form->{"${_}_$i"} = $newform{$_} } qw(partnumber description bin unit listprice inventory_accno income_accno expense_accno sellprice lastcost price_factor_id);
1222 map { $form->{"ic_${_}_$i"} = $newform{$_} } @ic_cvar_fields;
1224 $form->{"longdescription_$i"} = $newform{notes};
1226 $form->{"sellprice_$i"} = $newform{lastcost} if ($form->{vendor_id});
1228 if ($form->{exchangerate} != 0) {
1229 $form->{"sellprice_$i"} /= $form->{exchangerate};
1232 map { $form->{"taxaccounts_$i"} .= "$_ " } split / /, $newform{taxaccount};
1233 chop $form->{"taxaccounts_$i"};
1234 foreach my $item (qw(description rate taxnumber)) {
1235 my $index = $form->{"taxaccounts_$i"} . "_$item";
1236 $form->{$index} = $newform{$index};
1239 # credit remaining calculation
1240 $amount = $form->{"sellprice_$i"} * (1 - $form->{"discount_$i"} / 100) * $form->{"qty_$i"};
1242 map { $form->{"${_}_base"} += $amount } (split / /, $form->{"taxaccounts_$i"});
1243 map { $amount += ($form->{"${_}_base"} * $form->{"${_}_rate"}) } split / /, $form->{"taxaccounts_$i"} if !$form->{taxincluded};
1245 $form->{creditremaining} -= $amount;
1247 # redo number formatting, because invoice parse them!
1248 map { $form->{"${_}_$i"} = $form->format_amount(\%myconfig, $form->{"${_}_$i"}) } qw(weight listprice sellprice lastcost rop);
1251 $form->{"id_$i"} = $parts_id;
1253 # Get the actual price factor (not just the ID) for the marge calculation.
1254 $form->get_lists('price_factors' => 'ALL_PRICE_FACTORS');
1255 foreach my $pfac (@{ $form->{ALL_PRICE_FACTORS} }) {
1256 next if ($pfac->{id} != $newform{price_factor_id});
1257 $form->{"marge_price_factor_$i"} = $pfac->{factor};
1260 delete $form->{ALL_PRICE_FACTORS};
1262 delete $form->{action};
1264 # restore original callback
1265 $callback = $form->unescape($form->{callback});
1266 $form->{callback} = $form->unescape($form->{old_callback});
1267 delete $form->{old_callback};
1269 $form->{makemodel_rows}--;
1271 # put callback together
1272 foreach my $key (keys %$form) {
1274 # do single escape for Apache 2.0
1275 my $value = $form->escape($form->{$key}, 1);
1276 $callback .= qq|&$key=$value|;
1278 $form->{callback} = $callback;
1284 $lxdebug->leave_sub();
1288 $lxdebug->enter_sub();
1290 $auth->assert('part_service_assembly_edit');
1292 # saving the history
1293 if(!exists $form->{addition}) {
1294 $form->{snumbers} = qq|partnumber_| . $form->{partnumber};
1295 $form->{addition} = "SAVED AS NEW";
1296 $form->{what_done} = "part";
1297 $form->save_history;
1299 # /saving the history
1301 if ($form->{"original_partnumber"} &&
1302 ($form->{"partnumber"} eq $form->{"original_partnumber"})) {
1303 $form->{partnumber} = "";
1306 $lxdebug->leave_sub();
1310 $lxdebug->enter_sub();
1312 $auth->assert('part_service_assembly_edit');
1314 # saving the history
1315 if(!exists $form->{addition}) {
1316 $form->{snumbers} = qq|partnumber_| . $form->{partnumber};
1317 $form->{addition} = "DELETED";
1318 $form->{what_done} = "part";
1319 $form->save_history;
1321 # /saving the history
1322 my $rc = IC->delete(\%myconfig, \%$form);
1325 $form->redirect($locale->text('Item deleted!')) if ($rc > 0);
1326 $form->error($locale->text('Cannot delete item!'));
1328 $lxdebug->leave_sub();
1332 $lxdebug->enter_sub();
1334 $auth->assert('part_service_assembly_details');
1339 pricegroup => $form->{"pricegroup_$_"},
1340 pricegroup_id => $form->{"pricegroup_id_$_"},
1341 price => $form->{"price_$_"},
1344 print $form->parse_html_template('ic/price_row', { PRICES => \@PRICES });
1346 $lxdebug->leave_sub();
1349 sub ajax_autocomplete {
1350 $main::lxdebug->enter_sub();
1352 my $form = $main::form;
1353 my %myconfig = %main::myconfig;
1355 $form->{column} = 'description' unless $form->{column} =~ /^partnumber|description$/;
1356 $form->{$form->{column}} = $form->{q} || '';
1357 $form->{limit} = ($form->{limit} * 1) || 10;
1358 $form->{searchitems} ||= '';
1360 my @results = IC->all_parts(\%myconfig, $form);
1362 print $form->ajax_response_header(),
1363 $form->parse_html_template('ic/ajax_autocomplete');
1365 $main::lxdebug->leave_sub();
1369 $::lxdebug->enter_sub;
1371 $auth->assert('part_service_assembly_edit');
1375 $::form->language_payment(\%::myconfig);
1377 Common::webdav_folder($::form);
1380 price_row($::form->{price_rows});
1381 makemodel_row(++$::form->{makemodel_rows}) if $::form->{item} =~ /^(part|service)$/;
1382 assembly_row(++$::form->{assembly_rows}) if $::form->{item} eq 'assembly';
1386 $::lxdebug->leave_sub;
1389 sub back_to_record {
1393 delete @{$::form}{qw(action action_add action_back_to_record back_sub description item notes partnumber sellprice taxaccount2 unit vc)};
1395 $::auth->restore_form_from_session($::form->{previousform}, clobber => 1);
1396 $::form->{rowcount}--;
1397 $::form->{action} = 'display_form';
1398 $::form->{callback} = $::form->{script} . '?' . join('&', map { $::form->escape($_) . '=' . $::form->escape($::form->{$_}) } sort keys %{ $::form });
1402 sub continue { call_sub($form->{"nextsub"}); }
1405 my $action = first { $::form->{"action_${_}"} } qw(add back_to_record);
1406 $::form->error($::locale->text('No action defined.')) unless $action;
1408 $::form->{dispatched_action} = $action;