Project

General

Profile

Download (65.1 KB) Statistics
| Branch: | Tag: | Revision:

kivitendo / bin / mozilla / do.pl @ 25117316

1
#=====================================================================
2
# LX-Office ERP
3
# Copyright (C) 2004
4
# Based on SQL-Ledger Version 2.1.9
5
# Web http://www.lx-office.org
6
#
7
#=====================================================================
8
# SQL-Ledger, Accounting
9
# Copyright (c) 1998-2003
10
#
11
#  Author: Dieter Simader
12
#   Email: dsimader@sql-ledger.org
13
#     Web: http://www.sql-ledger.org
14
#
15
#
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.
20
#
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
#======================================================================
29
#
30
# Delivery orders
31
#======================================================================
32

    
33
use Carp;
34
use List::MoreUtils qw(uniq);
35
use List::Util qw(max sum);
36
use POSIX qw(strftime);
37
use YAML;
38

    
39
use SL::DB::DeliveryOrder;
40
use SL::DO;
41
use SL::IR;
42
use SL::IS;
43
use SL::MoreCommon qw(ary_diff restore_form save_form);
44
use SL::ReportGenerator;
45
use SL::WH;
46
use Sort::Naturally ();
47
require "bin/mozilla/arap.pl";
48
require "bin/mozilla/common.pl";
49
require "bin/mozilla/io.pl";
50
require "bin/mozilla/reportgenerator.pl";
51

    
52
use strict;
53

    
54
1;
55

    
56
# end of main
57

    
58
sub check_do_access {
59
  $main::auth->assert($main::form->{type} . '_edit');
60
}
61

    
62
sub set_headings {
63
  $main::lxdebug->enter_sub();
64

    
65
  check_do_access();
66

    
67
  my ($action) = @_;
68

    
69
  my $form     = $main::form;
70
  my $locale   = $main::locale;
71

    
72
  if ($form->{type} eq 'purchase_delivery_order') {
73
    $form->{vc}    = 'vendor';
74
    $form->{title} = $action eq "edit" ? $locale->text('Edit Purchase Delivery Order') : $locale->text('Add Purchase Delivery Order');
75
  } else {
76
    $form->{vc}    = 'customer';
77
    $form->{title} = $action eq "edit" ? $locale->text('Edit Sales Delivery Order') : $locale->text('Add Sales Delivery Order');
78
  }
79

    
80
  $form->{heading} = $locale->text('Delivery Order');
81

    
82
  $main::lxdebug->leave_sub();
83
}
84

    
85
sub add {
86
  $main::lxdebug->enter_sub();
87

    
88
  check_do_access();
89

    
90
  if (($::form->{type} =~ /purchase/) && !$::instance_conf->get_allow_new_purchase_invoice) {
91
    $::form->show_generic_error($::locale->text("You do not have the permissions to access this function."));
92
  }
93

    
94
  my $form     = $main::form;
95

    
96
  set_headings("add");
97

    
98
  $form->{show_details} = $::myconfig{show_form_details};
99
  $form->{callback} = build_std_url('action=add', 'type', 'vc') unless ($form->{callback});
100

    
101
  order_links();
102
  prepare_order();
103
  display_form();
104

    
105
  $main::lxdebug->leave_sub();
106
}
107

    
108
sub edit {
109
  $main::lxdebug->enter_sub();
110

    
111
  check_do_access();
112

    
113
  my $form     = $main::form;
114

    
115
  $form->{show_details} = $::myconfig{show_form_details};
116

    
117
  # show history button
118
  $form->{javascript} = qq|<script type="text/javascript" src="js/show_history.js"></script>|;
119
  #/show hhistory button
120

    
121
  $form->{simple_save} = 0;
122

    
123
  set_headings("edit");
124

    
125
  # editing without stuff to edit? try adding it first
126
  if ($form->{rowcount} && !$form->{print_and_save}) {
127
#     map { $id++ if $form->{"multi_id_$_"} } (1 .. $form->{rowcount});
128
#     if (!$id) {
129

    
130
      # reset rowcount
131
      undef $form->{rowcount};
132
      add();
133
      $main::lxdebug->leave_sub();
134
      return;
135
#     }
136
  } elsif (!$form->{id}) {
137
    add();
138
    $main::lxdebug->leave_sub();
139
    return;
140
  }
141

    
142
  my ($language_id, $printer_id);
143
  if ($form->{print_and_save}) {
144
    $form->{action}   = "dispatcher";
145
    $form->{action_print} = "1";
146
    $form->{resubmit} = 1;
147
    $language_id      = $form->{language_id};
148
    $printer_id       = $form->{printer_id};
149
  }
150

    
151
  set_headings("edit");
152

    
153
  order_links();
154
  prepare_order();
155

    
156
  if ($form->{print_and_save}) {
157
    $form->{language_id} = $language_id;
158
    $form->{printer_id}  = $printer_id;
159
  }
160

    
161
  display_form();
162

    
163
  $main::lxdebug->leave_sub();
164
}
165

    
166
sub order_links {
167
  $main::lxdebug->enter_sub();
168

    
169
  check_do_access();
170

    
171
  my $form     = $main::form;
172
  my %myconfig = %main::myconfig;
173

    
174
  # get customer/vendor
175
  $form->all_vc(\%myconfig, $form->{vc}, ($form->{vc} eq 'customer') ? "AR" : "AP");
176

    
177
  # retrieve order/quotation
178
  my $editing = $form->{id};
179

    
180
  DO->retrieve('vc'  => $form->{vc},
181
               'ids' => $form->{id});
182

    
183
  $form->backup_vars(qw(payment_id language_id taxzone_id salesman_id taxincluded cp_id intnotes delivery_term_id currency));
184

    
185
  # get customer / vendor
186
  if ($form->{vc} eq 'vendor') {
187
    IR->get_vendor(\%myconfig, \%$form);
188
    $form->{discount} = $form->{vendor_discount};
189
  } else {
190
    IS->get_customer(\%myconfig, \%$form);
191
    $form->{discount} = $form->{customer_discount};
192
  }
193

    
194
  $form->restore_vars(qw(payment_id language_id taxzone_id intnotes cp_id delivery_term_id));
195
  $form->restore_vars(qw(currency)) if ($form->{id} || $form->{convert_from_oe_ids});
196
  $form->restore_vars(qw(taxincluded)) if $form->{id};
197
  $form->restore_vars(qw(salesman_id)) if $editing;
198

    
199
  if ($form->{"all_$form->{vc}"}) {
200
    unless ($form->{"$form->{vc}_id"}) {
201
      $form->{"$form->{vc}_id"} = $form->{"all_$form->{vc}"}->[0]->{id};
202
    }
203
  }
204

    
205
  ($form->{ $form->{vc} })  = split /--/, $form->{ $form->{vc} };
206
  $form->{"old$form->{vc}"} = qq|$form->{$form->{vc}}--$form->{"$form->{vc}_id"}|;
207

    
208
  $form->{employee} = "$form->{employee}--$form->{employee_id}";
209

    
210
  $main::lxdebug->leave_sub();
211
}
212

    
213
sub prepare_order {
214
  $main::lxdebug->enter_sub();
215

    
216
  check_do_access();
217

    
218
  my $form     = $main::form;
219
  my %myconfig = %main::myconfig;
220

    
221
  $form->{formname} = $form->{type} unless $form->{formname};
222

    
223
  my $i = 0;
224
  foreach my $ref (@{ $form->{form_details} }) {
225
    $form->{rowcount} = ++$i;
226

    
227
    map { $form->{"${_}_$i"} = $ref->{$_} } keys %{$ref};
228
  }
229
  for my $i (1 .. $form->{rowcount}) {
230
    if ($form->{id}) {
231
      $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"} * 100);
232
    } else {
233
      $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"});
234
    }
235
    my ($dec) = ($form->{"sellprice_$i"} =~ /\.(\d+)/);
236
    $dec           = length $dec;
237
    my $decimalplaces = ($dec > 2) ? $dec : 2;
238

    
239
    # copy reqdate from deliverydate for invoice -> order conversion
240
    $form->{"reqdate_$i"} = $form->{"deliverydate_$i"} unless $form->{"reqdate_$i"};
241

    
242
    $form->{"sellprice_$i"} = $form->format_amount(\%myconfig, $form->{"sellprice_$i"}, $decimalplaces);
243
    $form->{"lastcost_$i"} = $form->format_amount(\%myconfig, $form->{"lastcost_$i"}, $decimalplaces);
244

    
245
    (my $dec_qty) = ($form->{"qty_$i"} =~ /\.(\d+)/);
246
    $dec_qty = length $dec_qty;
247
    $form->{"qty_$i"} = $form->format_amount(\%myconfig, $form->{"qty_$i"}, $dec_qty);
248
  }
249

    
250
  $main::lxdebug->leave_sub();
251
}
252

    
253
sub form_header {
254
  $main::lxdebug->enter_sub();
255

    
256
  check_do_access();
257

    
258
  my $form     = $main::form;
259
  my %myconfig = %main::myconfig;
260

    
261
  $form->{employee_id} = $form->{old_employee_id} if $form->{old_employee_id};
262
  $form->{salesman_id} = $form->{old_salesman_id} if $form->{old_salesman_id};
263

    
264
  my $vc = $form->{vc} eq "customer" ? "customers" : "vendors";
265
  $form->get_lists($vc              => "ALL_VC",
266
                   "price_factors"  => "ALL_PRICE_FACTORS",
267
                   "departments"    => "ALL_DEPARTMENTS",
268
                   "business_types" => "ALL_BUSINESS_TYPES",
269
    );
270

    
271
  # Projects
272
  my @old_project_ids = uniq grep { $_ } map { $_ * 1 } ($form->{"globalproject_id"}, map { $form->{"project_id_$_"} } 1..$form->{"rowcount"});
273
  my @old_ids_cond    = @old_project_ids ? (id => \@old_project_ids) : ();
274
  my @customer_cond;
275
  if (($vc eq 'customers') && $::instance_conf->get_customer_projects_only_in_sales) {
276
    @customer_cond = (
277
      or => [
278
        customer_id          => $::form->{customer_id},
279
        billable_customer_id => $::form->{customer_id},
280
      ]);
281
  }
282
  my @conditions = (
283
    or => [
284
      and => [ active => 1, @customer_cond ],
285
      @old_ids_cond,
286
    ]);
287

    
288
  $::form->{ALL_PROJECTS}          = SL::DB::Manager::Project->get_all_sorted(query => \@conditions);
289
  $::form->{ALL_EMPLOYEES}         = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{employee_id},  deleted => 0 ] ]);
290
  $::form->{ALL_SALESMEN}          = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{salesman_id},  deleted => 0 ] ]);
291
  $::form->{ALL_SHIPTO}            = SL::DB::Manager::Shipto->get_all_sorted(query => [
292
    or => [ trans_id  => $::form->{"$::form->{vc}_id"} * 1, and => [ shipto_id => $::form->{shipto_id} * 1, trans_id => undef ] ]
293
  ]);
294
  $::form->{ALL_CONTACTS}          = SL::DB::Manager::Contact->get_all_sorted(query => [
295
    or => [
296
      cp_cv_id => $::form->{"$::form->{vc}_id"} * 1,
297
      and      => [
298
        cp_cv_id => undef,
299
        cp_id    => $::form->{cp_id} * 1
300
      ]
301
    ]
302
  ]);
303

    
304
  map { $_->{value} = "$_->{description}--$_->{id}" } @{ $form->{ALL_DEPARTMENTS} };
305
  map { $_->{value} = "$_->{name}--$_->{id}"        } @{ $form->{ALL_VC} };
306

    
307
  $form->{SHOW_VC_DROP_DOWN} =  $myconfig{vclimit} > scalar @{ $form->{ALL_VC} };
308

    
309
  $form->{oldvcname}         =  $form->{"old$form->{vc}"};
310
  $form->{oldvcname}         =~ s/--.*//;
311

    
312
  my $dispatch_to_popup = '';
313
  if ($form->{resubmit} && ($form->{format} eq "html")) {
314
    $dispatch_to_popup  = "window.open('about:blank','Beleg'); document.do.target = 'Beleg';";
315
    $dispatch_to_popup .= "document.do.submit();";
316
  } elsif ($form->{resubmit}) {
317
    # emulate click for resubmitting actions
318
    $dispatch_to_popup  = "document.do.${_}.click(); " for grep { /^action_/ } keys %$form;
319
  }
320
  $::request->{layout}->add_javascripts_inline("\$(function(){$dispatch_to_popup});");
321

    
322

    
323
  my $follow_up_vc                =  $form->{ $form->{vc} eq 'customer' ? 'customer' : 'vendor' };
324
  $follow_up_vc                   =~ s/--\d*\s*$//;
325

    
326
  $form->{follow_up_trans_info} = $form->{donumber} .'('. $follow_up_vc .')';
327

    
328
  $::request->{layout}->use_javascript(map { "${_}.js" } qw(kivi.SalesPurchase ckeditor/ckeditor ckeditor/adapters/jquery kivi.io autocomplete_customer autocomplete_part));
329

    
330
  my @custom_hidden;
331
  push @custom_hidden, map { "shiptocvar_" . $_->name } @{ SL::DB::Manager::CustomVariableConfig->get_all(where => [ module => 'ShipTo' ]) };
332

    
333
  $::form->{HIDDENS} = [ map { +{ name => $_, value => $::form->{$_} } } (@custom_hidden) ];
334

    
335
  $form->header();
336
  # Fix für Bug 1082 Erwartet wird: 'abteilungsNAME--abteilungsID'
337
  # und Erweiterung für Bug 1760:
338
  # Das war leider nur ein Teil-Fix, da das Verhalten den 'Erneuern'-Knopf
339
  # nicht überlebt. Konsequent jetzt auf L umgestellt
340
  #   $ perldoc SL::Template::Plugin::L
341
  # Daher entsprechend nur die Anpassung in form_header
342
  # und in DO.pm gemacht. 4 Testfälle:
343
  # department_id speichern                 | i.O.
344
  # department_id lesen                     | i.O.
345
  # department leer überlebt erneuern       | i.O.
346
  # department nicht leer überlebt erneuern | i.O.
347
  # $main::lxdebug->message(0, 'ABTEILUNGS ID in form?' . $form->{department_id});
348
  print $form->parse_html_template('do/form_header');
349

    
350
  $main::lxdebug->leave_sub();
351
}
352

    
353
sub form_footer {
354
  $main::lxdebug->enter_sub();
355

    
356
  check_do_access();
357

    
358
  my $form     = $main::form;
359

    
360
  $form->{PRINT_OPTIONS} = print_options('inline' => 1);
361
  $form->{ALL_DELIVERY_TERMS} = SL::DB::Manager::DeliveryTerm->get_all_sorted();
362

    
363
  print $form->parse_html_template('do/form_footer',
364
    {transfer_default         => ($::instance_conf->get_transfer_default)});
365

    
366
  $main::lxdebug->leave_sub();
367
}
368

    
369
sub update_delivery_order {
370
  $main::lxdebug->enter_sub();
371

    
372
  check_do_access();
373

    
374
  my $form     = $main::form;
375
  my %myconfig = %main::myconfig;
376

    
377
  set_headings($form->{"id"} ? "edit" : "add");
378

    
379
  $form->{insertdate} = SL::DB::DeliveryOrder->new(id => $form->{id})->load->itime_as_date if $form->{id};
380

    
381
  $form->{update} = 1;
382

    
383
  my $payment_id;
384
  $payment_id = $form->{payment_id} if $form->{payment_id};
385

    
386
  check_name($form->{vc});
387
  $form->{discount} =  $form->{"$form->{vc}_discount"} if defined $form->{"$form->{vc}_discount"};
388
  # Problem: Wenn man ohne Erneuern einen Kunden/Lieferanten
389
  # wechselt, wird der entsprechende Kunden/ Lieferantenrabatt
390
  # nicht übernommen. Grundproblem: In Commit 82574e78
391
  # hab ich aus discount customer_discount und vendor_discount
392
  # gemacht und entsprechend an den Oberflächen richtig hin-
393
  # geschoben. Die damals bessere Lösung wäre gewesen:
394
  # In den Templates nur die hidden für form-discount wieder ein-
395
  # setzen dann wäre die Verrenkung jetzt nicht notwendig.
396
  # TODO: Ggf. Bugfix 1284, 1575 und 817 wieder zusammenführen
397
  # Testfälle: Kunden mit Rabatt 0 -> Rabatt 20 i.O.
398
  #            Kunde mit Rabatt 20 -> Rabatt 0  i.O.
399
  #            Kunde mit Rabatt 20 -> Rabatt 5,5 i.O.
400
  $form->{payment_id} = $payment_id if $form->{payment_id} eq "";
401

    
402
  my $i = $form->{rowcount};
403

    
404
  if (   ($form->{"partnumber_$i"} eq "")
405
      && ($form->{"description_$i"} eq "")
406
      && ($form->{"partsgroup_$i"}  eq "")) {
407

    
408
    check_form();
409

    
410
  } else {
411

    
412
    my $mode;
413
    if ($form->{type} eq 'purchase_delivery_order') {
414
      IR->retrieve_item(\%myconfig, $form);
415
      $mode = 'IR';
416
    } else {
417
      IS->retrieve_item(\%myconfig, $form);
418
      $mode = 'IS';
419
    }
420

    
421
    my $rows = scalar @{ $form->{item_list} };
422

    
423
    if ($rows) {
424
      $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
425
      if( !$form->{"qty_$i"} ) {
426
        $form->{"qty_$i"} = 1;
427
      }
428

    
429
      if ($rows > 1) {
430

    
431
        select_item(mode => $mode, pre_entered_qty => $form->{"qty_$i"});
432
        $::dispatcher->end_request;
433

    
434
      } else {
435

    
436
        my $sellprice = $form->parse_amount(\%myconfig, $form->{"sellprice_$i"});
437

    
438
        map { $form->{"${_}_$i"} = $form->{item_list}[0]{$_} } keys %{ $form->{item_list}[0] };
439

    
440
        $form->{"marge_price_factor_$i"} = $form->{item_list}->[0]->{price_factor};
441

    
442
        if ($sellprice) {
443
          $form->{"sellprice_$i"} = $sellprice;
444
        } else {
445
          my $record        = _make_record();
446
          my $price_source  = SL::PriceSource->new(record_item => $record->items->[$i-1], record => $record);
447
          my $best_price    = $price_source->best_price;
448
          my $best_discount = $price_source->best_discount;
449

    
450
          if ($best_price) {
451
            $::form->{"sellprice_$i"}           = $best_price->price;
452
            $::form->{"active_price_source_$i"} = $best_price->source;
453
          }
454
          if ($best_discount) {
455
            $::form->{"discount_$i"}               = $best_discount->discount;
456
            $::form->{"active_discount_source_$i"} = $best_discount->source;
457
          }
458
        }
459

    
460
        $form->{"sellprice_$i"}          = $form->format_amount(\%myconfig, $form->{"sellprice_$i"});
461
        $form->{"lastcost_$i"}           = $form->format_amount(\%myconfig, $form->{"lastcost_$i"});
462
        $form->{"qty_$i"}                = $form->format_amount(\%myconfig, $form->{"qty_$i"});
463
        $form->{"discount_$i"}           = $form->format_amount(\%myconfig, $form->{"discount_$i"} * 100.0);
464
      }
465

    
466
      display_form();
467

    
468
    } else {
469

    
470
      # ok, so this is a new part
471
      # ask if it is a part or service item
472

    
473
      if (   $form->{"partsgroup_$i"}
474
          && ($form->{"partsnumber_$i"} eq "")
475
          && ($form->{"description_$i"} eq "")) {
476
        $form->{rowcount}--;
477
        $form->{"discount_$i"} = "";
478
        $form->{"not_discountable_$i"} = "";
479
        display_form();
480

    
481
      } else {
482
        $form->{"id_$i"}   = 0;
483
        new_item();
484
      }
485
    }
486
  }
487

    
488
  $main::lxdebug->leave_sub();
489
}
490

    
491
sub search {
492
  $main::lxdebug->enter_sub();
493

    
494
  check_do_access();
495

    
496
  my $form     = $main::form;
497
  my %myconfig = %main::myconfig;
498
  my $locale   = $main::locale;
499

    
500
  $form->{vc} = $form->{type} eq 'purchase_delivery_order' ? 'vendor' : 'customer';
501

    
502
  $form->get_lists("projects"       => { "key" => "ALL_PROJECTS",
503
                                         "all" => 1 },
504
                   "departments"    => "ALL_DEPARTMENTS",
505
                   "$form->{vc}s"   => "ALL_VC",
506
                   "business_types" => "ALL_BUSINESS_TYPES");
507
  $form->{ALL_EMPLOYEES} = SL::DB::Manager::Employee->get_all_sorted(query => [ deleted => 0 ]);
508

    
509
  $form->{SHOW_VC_DROP_DOWN} =  $myconfig{vclimit} > scalar @{ $form->{ALL_VC} };
510
  $form->{title}             = $locale->text('Delivery Orders');
511

    
512
  $form->header();
513

    
514
  print $form->parse_html_template('do/search');
515

    
516
  $main::lxdebug->leave_sub();
517
}
518

    
519
sub orders {
520
  $main::lxdebug->enter_sub();
521

    
522
  check_do_access();
523

    
524
  my $form     = $main::form;
525
  my %myconfig = %main::myconfig;
526
  my $locale   = $main::locale;
527
  my $cgi      = $::request->{cgi};
528

    
529
  $form->{department_id} = (split /--/, $form->{department})[-1];
530
  ($form->{ $form->{vc} }, $form->{"$form->{vc}_id"}) = split(/--/, $form->{ $form->{vc} });
531

    
532
  report_generator_set_default_sort('transdate', 1);
533

    
534
  DO->transactions();
535

    
536
  $form->{rowcount} = scalar @{ $form->{DO} };
537

    
538
  my @columns = qw(
539
    ids                     transdate               reqdate
540
    id                      donumber
541
    ordnumber               customernumber          cusordnumber
542
    name                    employee  salesman
543
    shipvia                 globalprojectnumber
544
    transaction_description department
545
    open                    delivered
546
    insertdate
547
  );
548

    
549
  $form->{l_open}      = $form->{l_closed} = "Y" if ($form->{open}      && $form->{closed});
550
  $form->{l_delivered} = "Y"                     if ($form->{delivered} && $form->{notdelivered});
551

    
552
  $form->{title}       = $locale->text('Delivery Orders');
553

    
554
  my $attachment_basename = $form->{vc} eq 'vendor' ? $locale->text('purchase_delivery_order_list') : $locale->text('sales_delivery_order_list');
555

    
556
  my $report = SL::ReportGenerator->new(\%myconfig, $form);
557

    
558
  my @hidden_variables = map { "l_${_}" } @columns;
559
  push @hidden_variables, $form->{vc}, qw(l_closed l_notdelivered open closed delivered notdelivered donumber ordnumber serialnumber cusordnumber
560
                                          transaction_description transdatefrom transdateto reqdatefrom reqdateto
561
                                          type vc employee_id salesman_id project_id parts_partnumber parts_description
562
                                          insertdatefrom insertdateto business_id);
563

    
564
  my $href = build_std_url('action=orders', grep { $form->{$_} } @hidden_variables);
565

    
566
  my %column_defs = (
567
    'ids'                     => { 'text' => '', },
568
    'transdate'               => { 'text' => $locale->text('Delivery Order Date'), },
569
    'reqdate'                 => { 'text' => $locale->text('Reqdate'), },
570
    'id'                      => { 'text' => $locale->text('ID'), },
571
    'donumber'                => { 'text' => $locale->text('Delivery Order'), },
572
    'ordnumber'               => { 'text' => $locale->text('Order'), },
573
    'customernumber'          => { 'text' => $locale->text('Customer Number'), },
574
    'cusordnumber'            => { 'text' => $locale->text('Customer Order Number'), },
575
    'name'                    => { 'text' => $form->{vc} eq 'customer' ? $locale->text('Customer') : $locale->text('Vendor'), },
576
    'employee'                => { 'text' => $locale->text('Employee'), },
577
    'salesman'                => { 'text' => $locale->text('Salesman'), },
578
    'shipvia'                 => { 'text' => $locale->text('Ship via'), },
579
    'globalprojectnumber'     => { 'text' => $locale->text('Project Number'), },
580
    'transaction_description' => { 'text' => $locale->text('Transaction description'), },
581
    'open'                    => { 'text' => $locale->text('Open'), },
582
    'delivered'               => { 'text' => $locale->text('Delivered'), },
583
    'department'              => { 'text' => $locale->text('Department'), },
584
    'insertdate'              => { 'text' => $locale->text('Insert Date'), },
585
  );
586

    
587
  foreach my $name (qw(id transdate reqdate donumber ordnumber name employee salesman shipvia transaction_description department insertdate)) {
588
    my $sortdir                 = $form->{sort} eq $name ? 1 - $form->{sortdir} : $form->{sortdir};
589
    $column_defs{$name}->{link} = $href . "&sort=$name&sortdir=$sortdir";
590
  }
591

    
592
  $form->{"l_type"} = "Y";
593
  map { $column_defs{$_}->{visible} = $form->{"l_${_}"} ? 1 : 0 } @columns;
594

    
595
  $column_defs{ids}->{visible} = 'HTML';
596

    
597
  $report->set_columns(%column_defs);
598
  $report->set_column_order(@columns);
599

    
600
  $report->set_export_options('orders', @hidden_variables, qw(sort sortdir));
601

    
602
  $report->set_sort_indicator($form->{sort}, $form->{sortdir});
603

    
604
  my @options;
605
  if ($form->{customer}) {
606
    push @options, $locale->text('Customer') . " : $form->{customer}";
607
  }
608
  if ($form->{vendor}) {
609
    push @options, $locale->text('Vendor') . " : $form->{vendor}";
610
  }
611
  if ($form->{cp_name}) {
612
    push @options, $locale->text('Contact Person') . " : $form->{cp_name}";
613
  }
614
  if ($form->{department}) {
615
    my ($department) = split /--/, $form->{department};
616
    push @options, $locale->text('Department') . " : $department";
617
  }
618
  if ($form->{donumber}) {
619
    push @options, $locale->text('Delivery Order Number') . " : $form->{donumber}";
620
  }
621
  if ($form->{ordnumber}) {
622
    push @options, $locale->text('Order Number') . " : $form->{ordnumber}";
623
  }
624
  push @options, $locale->text('Serial Number') . " : $form->{serialnumber}" if $form->{serialnumber};
625
  if ($form->{business_id}) {
626
    my $vc_type_label = $form->{vc} eq 'customer' ? $locale->text('Customer type') : $locale->text('Vendor type');
627
    push @options, $vc_type_label . " : " . SL::DB::Business->new(id => $form->{business_id})->load->description;
628
  }
629
  if ($form->{transaction_description}) {
630
    push @options, $locale->text('Transaction description') . " : $form->{transaction_description}";
631
  }
632
  if ($form->{parts_description}) {
633
    push @options, $locale->text('Part Description') . " : $form->{parts_description}";
634
  }
635
  if ($form->{parts_partnumber}) {
636
    push @options, $locale->text('Part Number') . " : $form->{parts_partnumber}";
637
  }
638
  if ( $form->{transdatefrom} or $form->{transdateto} ) {
639
    push @options, $locale->text('Delivery Order Date');
640
    push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{transdatefrom}, 1)     if $form->{transdatefrom};
641
    push @options, $locale->text('Bis')  . " " . $locale->date(\%myconfig, $form->{transdateto},   1)     if $form->{transdateto};
642
  };
643
  if ( $form->{reqdatefrom} or $form->{reqdateto} ) {
644
    push @options, $locale->text('Reqdate');
645
    push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{reqdatefrom}, 1)       if $form->{reqdatefrom};
646
    push @options, $locale->text('Bis')  . " " . $locale->date(\%myconfig, $form->{reqdateto},   1)       if $form->{reqdateto};
647
  };
648
  if ( $form->{insertdatefrom} or $form->{insertdateto} ) {
649
    push @options, $locale->text('Insert Date');
650
    push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{insertdatefrom}, 1)    if $form->{insertdatefrom};
651
    push @options, $locale->text('Bis')  . " " . $locale->date(\%myconfig, $form->{insertdateto},   1)    if $form->{insertdateto};
652
  };
653
  if ($form->{open}) {
654
    push @options, $locale->text('Open');
655
  }
656
  if ($form->{closed}) {
657
    push @options, $locale->text('Closed');
658
  }
659
  if ($form->{delivered}) {
660
    push @options, $locale->text('Delivered');
661
  }
662
  if ($form->{notdelivered}) {
663
    push @options, $locale->text('Not delivered');
664
  }
665

    
666
  $report->set_options('top_info_text'        => join("\n", @options),
667
                       'raw_top_info_text'    => $form->parse_html_template('do/orders_top'),
668
                       'raw_bottom_info_text' => $form->parse_html_template('do/orders_bottom'),
669
                       'output_format'        => 'HTML',
670
                       'title'                => $form->{title},
671
                       'attachment_basename'  => $attachment_basename . strftime('_%Y%m%d', localtime time),
672
    );
673
  $report->set_options_from_form();
674
  $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
675

    
676
  # add sort and escape callback, this one we use for the add sub
677
  $form->{callback} = $href .= "&sort=$form->{sort}";
678

    
679
  # escape callback for href
680
  my $callback = $form->escape($href);
681

    
682
  my $edit_url       = build_std_url('action=edit', 'type', 'vc');
683
  my $edit_order_url = build_std_url('script=oe.pl', 'type=' . ($form->{type} eq 'sales_delivery_order' ? 'sales_order' : 'purchase_order'), 'action=edit');
684

    
685
  my $idx            = 1;
686

    
687
  foreach my $dord (@{ $form->{DO} }) {
688
    $dord->{open}      = $dord->{closed}    ? $locale->text('No')  : $locale->text('Yes');
689
    $dord->{delivered} = $dord->{delivered} ? $locale->text('Yes') : $locale->text('No');
690

    
691
    my $row = { map { $_ => { 'data' => $dord->{$_} } } @columns };
692

    
693
    $row->{ids}  = {
694
      'raw_data' =>   $cgi->hidden('-name' => "trans_id_${idx}", '-value' => $dord->{id})
695
                    . $cgi->checkbox('-name' => "multi_id_${idx}", '-value' => 1, '-label' => ''),
696
      'valign'   => 'center',
697
      'align'    => 'center',
698
    };
699

    
700
    $row->{donumber}->{link}  = $edit_url       . "&id=" . E($dord->{id})      . "&callback=${callback}";
701
    $row->{ordnumber}->{link} = $edit_order_url . "&id=" . E($dord->{oe_id})   . "&callback=${callback}" if $dord->{oe_id};
702
    $report->add_data($row);
703

    
704
    $idx++;
705
  }
706

    
707
  $report->generate_with_headers();
708

    
709
  $main::lxdebug->leave_sub();
710
}
711

    
712
sub save {
713
  $main::lxdebug->enter_sub();
714

    
715
  my (%params) = @_;
716

    
717
  check_do_access();
718

    
719
  my $form     = $main::form;
720
  my %myconfig = %main::myconfig;
721
  my $locale   = $main::locale;
722

    
723
  $form->mtime_ischanged('delivery_orders');
724

    
725
  $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
726

    
727
  $form->isblank("transdate", $locale->text('Delivery Order Date missing!'));
728

    
729
  $form->{donumber} =~ s/^\s*//g;
730
  $form->{donumber} =~ s/\s*$//g;
731

    
732
  my $msg = ucfirst $form->{vc};
733
  $form->isblank($form->{vc}, $locale->text($msg . " missing!"));
734

    
735
  # $locale->text('Customer missing!');
736
  # $locale->text('Vendor missing!');
737

    
738
  remove_emptied_rows();
739
  validate_items();
740

    
741
  # if the name changed get new values
742
  if (check_name($form->{vc})) {
743
    update();
744
    $::dispatcher->end_request;
745
  }
746

    
747
  $form->{id} = 0 if $form->{saveasnew};
748

    
749
  DO->save();
750
  # saving the history
751
  if(!exists $form->{addition}) {
752
    $form->{snumbers} = qq|donumber_| . $form->{donumber};
753
    $form->{addition} = "SAVED";
754
    $form->save_history;
755
  }
756
  # /saving the history
757

    
758
  $form->{simple_save} = 1;
759
  if (!$params{no_redirect} && !$form->{print_and_save}) {
760
    delete @{$form}{ary_diff([keys %{ $form }], [qw(login id script type cursor_fokus)])};
761
    edit();
762
    $::dispatcher->end_request;
763
  }
764
  $main::lxdebug->leave_sub();
765
}
766

    
767
sub delete {
768
  $main::lxdebug->enter_sub();
769

    
770
  check_do_access();
771

    
772
  my $form     = $main::form;
773
  my %myconfig = %main::myconfig;
774
  my $locale   = $main::locale;
775

    
776
  if (DO->delete()) {
777
    # saving the history
778
    if(!exists $form->{addition}) {
779
      $form->{snumbers} = qq|donumber_| . $form->{donumber};
780
      $form->{addition} = "DELETED";
781
      $form->save_history;
782
    }
783
    # /saving the history
784

    
785
    $form->info($locale->text('Delivery Order deleted!'));
786
    $::dispatcher->end_request;
787
  }
788

    
789
  $form->error($locale->text('Cannot delete delivery order!'));
790

    
791
  $main::lxdebug->leave_sub();
792
}
793

    
794
sub invoice {
795
  $main::lxdebug->enter_sub();
796

    
797
  my $form     = $main::form;
798
  my %myconfig = %main::myconfig;
799
  my $locale   = $main::locale;
800

    
801
  check_do_access();
802
  $form->mtime_ischanged('delivery_orders');
803

    
804
  $main::auth->assert($form->{type} eq 'purchase_delivery_order' ? 'vendor_invoice_edit' : 'invoice_edit');
805

    
806
  $form->{convert_from_do_ids} = $form->{id};
807
  $form->{deliverydate}        = $form->{transdate};
808
  $form->{transdate}           = $form->{invdate} = $form->current_date(\%myconfig);
809
  $form->{duedate}             = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
810
  $form->{defaultcurrency}     = $form->get_default_currency(\%myconfig);
811

    
812
  $form->{rowcount}--;
813

    
814
  delete @{$form}{qw(id closed delivered)};
815

    
816
  my ($script, $buysell);
817
  if ($form->{type} eq 'purchase_delivery_order') {
818
    $form->{title}  = $locale->text('Add Vendor Invoice');
819
    $form->{script} = 'ir.pl';
820
    $script         = "ir";
821
    $buysell        = 'sell';
822

    
823
  } else {
824
    $form->{title}  = $locale->text('Add Sales Invoice');
825
    $form->{script} = 'is.pl';
826
    $script         = "is";
827
    $buysell        = 'buy';
828
  }
829

    
830
  for my $i (1 .. $form->{rowcount}) {
831
    map { $form->{"${_}_${i}"} = $form->parse_amount(\%myconfig, $form->{"${_}_${i}"}) if $form->{"${_}_${i}"} } qw(ship qty sellprice lastcost basefactor discount);
832
    # für bug 1284
833
    # adds a customer/vendor discount, unless we have a workflow case
834
    # CAVEAT: has to be done, after the above parse_amount
835
    unless ($form->{"ordnumber"}) {
836
      if ($form->{discount}) { # Falls wir einen Lieferanten-/Kundenrabatt haben
837
        # und rabattfähig sind, dann
838
        unless ($form->{"not_discountable_$i"}) {
839
          $form->{"discount_$i"} = $form->{discount}*100; # ... nehmen wir diesen Rabatt
840
        }
841
      }
842
    }
843
    $form->{"donumber_$i"} = $form->{donumber};
844
    $form->{"converted_from_delivery_order_items_id_$i"} = delete $form->{"delivery_order_items_id_$i"};
845
  }
846

    
847
  $form->{type} = "invoice";
848

    
849
  # locale messages
850
  $main::locale = Locale->new("$myconfig{countrycode}", "$script");
851
  $locale = $main::locale;
852

    
853
  require "bin/mozilla/$form->{script}";
854

    
855
  my $currency = $form->{currency};
856
  invoice_links();
857

    
858
  if ($form->{ordnumber}) {
859
    require SL::DB::Order;
860
    if (my $order = SL::DB::Manager::Order->find_by(ordnumber => $form->{ordnumber})) {
861
      $order->load;
862
      $form->{orddate} = $order->transdate_as_date;
863
      $form->{$_}      = $order->$_ for qw(payment_id salesman_id taxzone_id quonumber);
864
    }
865
  }
866

    
867
  $form->{currency}     = $currency;
868
  $form->{exchangerate} = "";
869
  $form->{forex}        = $form->check_exchangerate(\%myconfig, $form->{currency}, $form->{invdate}, $buysell);
870
  $form->{exchangerate} = $form->{forex} if ($form->{forex});
871

    
872
  prepare_invoice();
873

    
874
  # format amounts
875
  for my $i (1 .. $form->{rowcount}) {
876
    $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"});
877

    
878
    my ($dec) = ($form->{"sellprice_$i"} =~ /\.(\d+)/);
879
    $dec           = length $dec;
880
    my $decimalplaces = ($dec > 2) ? $dec : 2;
881

    
882
    # copy delivery date from reqdate for order -> invoice conversion
883
    $form->{"deliverydate_$i"} = $form->{"reqdate_$i"}
884
      unless $form->{"deliverydate_$i"};
885

    
886

    
887
    $form->{"sellprice_$i"} =
888
      $form->format_amount(\%myconfig, $form->{"sellprice_$i"},
889
                           $decimalplaces);
890

    
891
    $form->{"lastcost_$i"} =
892
      $form->format_amount(\%myconfig, $form->{"lastcost_$i"},
893
                           $decimalplaces);
894

    
895
    (my $dec_qty) = ($form->{"qty_$i"} =~ /\.(\d+)/);
896
    $dec_qty = length $dec_qty;
897
    $form->{"qty_$i"} =
898
      $form->format_amount(\%myconfig, $form->{"qty_$i"}, $dec_qty);
899

    
900
  }
901

    
902
  display_form();
903

    
904
  $main::lxdebug->leave_sub();
905
}
906

    
907
sub invoice_multi {
908
  $main::lxdebug->enter_sub();
909

    
910
  my $form     = $main::form;
911
  my %myconfig = %main::myconfig;
912
  my $locale   = $main::locale;
913

    
914
  check_do_access();
915
  $main::auth->assert($form->{type} eq 'sales_delivery_order' ? 'invoice_edit' : 'vendor_invoice_edit');
916

    
917
  my @do_ids = map { $form->{"trans_id_$_"} } grep { $form->{"multi_id_$_"} } (1..$form->{rowcount});
918

    
919
  if (!scalar @do_ids) {
920
    $form->show_generic_error($locale->text('You have not selected any delivery order.'), 'back_button' => 1);
921
  }
922

    
923
  map { delete $form->{$_} } grep { m/^(?:trans|multi)_id_\d+/ } keys %{ $form };
924

    
925
  if (!DO->retrieve('vc' => $form->{vc}, 'ids' => \@do_ids)) {
926
    $form->show_generic_error($form->{vc} eq 'customer' ?
927
                              $locale->text('You cannot create an invoice for delivery orders for different customers.') :
928
                              $locale->text('You cannot create an invoice for delivery orders from different vendors.'),
929
                              'back_button' => 1);
930
  }
931

    
932
  my $source_type              = $form->{type};
933
  $form->{convert_from_do_ids} = join ' ', @do_ids;
934
  # bei der auswahl von mehreren Lieferscheinen fuer eine Rechnung, die einfach in donumber_array
935
  # zwischenspeichern (DO.pm) und als ' '-separierte Liste wieder zurueckschreiben
936
  # Hinweis: delete gibt den wert zurueck und loescht danach das element (nett und einfach)
937
  # $shell: perldoc perlunc; /delete EXPR
938
  $form->{donumber}            = delete $form->{donumber_array};
939
  $form->{ordnumber}           = delete $form->{ordnumber_array};
940
  $form->{cusordnumber}        = delete $form->{cusordnumber_array};
941
  $form->{deliverydate}        = $form->{transdate};
942
  $form->{transdate}           = $form->current_date(\%myconfig);
943
  $form->{duedate}             = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
944
  $form->{type}                = "invoice";
945
  $form->{closed}              = 0;
946
  $form->{defaultcurrency}     = $form->get_default_currency(\%myconfig);
947

    
948
  my ($script, $buysell);
949
  if ($source_type eq 'purchase_delivery_order') {
950
    $form->{title}  = $locale->text('Add Vendor Invoice');
951
    $form->{script} = 'ir.pl';
952
    $script         = "ir";
953
    $buysell        = 'sell';
954

    
955
  } else {
956
    $form->{title}  = $locale->text('Add Sales Invoice');
957
    $form->{script} = 'is.pl';
958
    $script         = "is";
959
    $buysell        = 'buy';
960
  }
961

    
962
  map { delete $form->{$_} } qw(id subject message cc bcc printed emailed queued);
963

    
964
  # get vendor or customer discount
965
  my $vc_discount;
966
  my $saved_form = save_form();
967
  if ($form->{vc} eq 'vendor') {
968
    IR->get_vendor(\%myconfig, \%$form);
969
    $vc_discount = $form->{vendor_discount};
970
  } else {
971
    IS->get_customer(\%myconfig, \%$form);
972
    $vc_discount = $form->{customer_discount};
973
  }
974
  # use payment terms from customer or vendor
975
  restore_form($saved_form,0,qw(payment_id));
976

    
977
  $form->{rowcount} = 0;
978
  foreach my $ref (@{ $form->{form_details} }) {
979
    $form->{rowcount}++;
980
    $ref->{reqdate} ||= $ref->{dord_transdate}; # copy transdates into each invoice row
981
    map { $form->{"${_}_$form->{rowcount}"} = $ref->{$_} } keys %{ $ref };
982
    map { $form->{"${_}_$form->{rowcount}"} = $form->format_amount(\%myconfig, $ref->{$_}) } qw(qty sellprice lastcost);
983
    $form->{"converted_from_delivery_order_items_id_$form->{rowcount}"} = delete $form->{"delivery_order_items_id_$form->{rowcount}"};
984

    
985
    if ($vc_discount){ # falls wir einen Lieferanten/Kundenrabatt haben
986
      # und keinen anderen discount wert an $i ...
987
      $form->{"discount_$form->{rowcount}"} ||= $vc_discount; # ... nehmen wir diesen Rabatt
988
    }
989

    
990
    $form->{"discount_$form->{rowcount}"}   = $form->{"discount_$form->{rowcount}"}  * 100; #s.a. Bug 1151
991
    # Anm.: Eine Änderung des discounts in der SL/DO.pm->retrieve (select (doi.discount * 100) as discount) ergibt in psql einen
992
    # Wert von 10.0000001490116. Ferner ist der Rabatt in der Rechnung dann bei 1.0 (?). Deswegen lasse ich das hier. jb 10.10.09
993

    
994
    $form->{"discount_$form->{rowcount}"} = $form->format_amount(\%myconfig, $form->{"discount_$form->{rowcount}"});
995
  }
996
  delete $form->{form_details};
997

    
998
  $locale = Locale->new("$myconfig{countrycode}", "$script");
999

    
1000
  require "bin/mozilla/$form->{script}";
1001

    
1002
  invoice_links();
1003
  prepare_invoice();
1004

    
1005
  display_form();
1006

    
1007
  $main::lxdebug->leave_sub();
1008
}
1009

    
1010
sub save_as_new {
1011
  $main::lxdebug->enter_sub();
1012

    
1013
  check_do_access();
1014

    
1015
  my $form     = $main::form;
1016

    
1017
  $form->{saveasnew} = 1;
1018
  $form->{closed}    = 0;
1019
  $form->{delivered} = 0;
1020
  map { delete $form->{$_} } qw(printed emailed queued);
1021
  delete @{ $form }{ grep { m/^stock_(?:in|out)_\d+/ } keys %{ $form } };
1022
  $form->{"converted_from_delivery_order_items_id_$_"} = delete $form->{"delivery_order_items_id_$_"} for 1 .. $form->{"rowcount"};
1023
  # Let kivitendo assign a new order number if the user hasn't changed the
1024
  # previous one. If it has been changed manually then use it as-is.
1025
  $form->{donumber} =~ s/^\s*//g;
1026
  $form->{donumber} =~ s/\s*$//g;
1027
  if ($form->{saved_donumber} && ($form->{saved_donumber} eq $form->{donumber})) {
1028
    delete($form->{donumber});
1029
  }
1030

    
1031
  save();
1032

    
1033
  $main::lxdebug->leave_sub();
1034
}
1035

    
1036
sub e_mail {
1037
  $main::lxdebug->enter_sub();
1038

    
1039
  check_do_access();
1040

    
1041
  $::form->mtime_ischanged('delivery_orders','mail');
1042

    
1043
  $::form->{print_and_save} = 1;
1044

    
1045
  my $saved_form = save_form();
1046

    
1047
  save();
1048

    
1049
  restore_form($saved_form, 0, qw(id ordnumber quonumber));
1050

    
1051
  edit_e_mail();
1052

    
1053
  $main::lxdebug->leave_sub();
1054
}
1055

    
1056
sub calculate_stock_in_out {
1057
  $main::lxdebug->enter_sub();
1058

    
1059
  my $form     = $main::form;
1060

    
1061
  my $i = shift;
1062

    
1063
  if (!$form->{"id_${i}"}) {
1064
    $main::lxdebug->leave_sub();
1065
    return '';
1066
  }
1067

    
1068
  my $all_units = AM->retrieve_all_units();
1069

    
1070
  my $in_out   = $form->{type} =~ /^sales/ ? 'out' : 'in';
1071
  my $sinfo    = DO->unpack_stock_information('packed' => $form->{"stock_${in_out}_${i}"});
1072

    
1073
  my $do_qty   = AM->sum_with_unit($::form->{"qty_$i"}, $::form->{"unit_$i"});
1074
  my $sum      = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $sinfo });
1075
  my $matches  = $do_qty == $sum;
1076

    
1077
  my $content  = $form->format_amount_units('amount'      => $sum * 1,
1078
                                            'part_unit'   => $form->{"partunit_$i"},
1079
                                            'amount_unit' => $all_units->{$form->{"partunit_$i"}}->{base_unit},
1080
                                            'conv_units'  => 'convertible_not_smaller',
1081
                                            'max_places'  => 2);
1082
  $content     = qq|<span id="stock_in_out_qty_display_${i}">${content}</span><input type=hidden id='stock_in_out_qty_matches_$i' value='$matches'> <input type="button" onclick="open_stock_in_out_window('${in_out}', $i);" value="?">|;
1083

    
1084
  $main::lxdebug->leave_sub();
1085

    
1086
  return $content;
1087
}
1088

    
1089
sub get_basic_bin_wh_info {
1090
  $main::lxdebug->enter_sub();
1091

    
1092
  my $stock_info = shift;
1093

    
1094
  my $form     = $main::form;
1095

    
1096
  foreach my $sinfo (@{ $stock_info }) {
1097
    next unless ($sinfo->{bin_id});
1098

    
1099
    my $bin_info = WH->get_basic_bin_info('id' => $sinfo->{bin_id});
1100
    map { $sinfo->{"${_}_description"} = $sinfo->{"${_}description"} = $bin_info->{"${_}_description"} } qw(bin warehouse);
1101
  }
1102

    
1103
  $main::lxdebug->leave_sub();
1104
}
1105

    
1106
sub stock_in_out_form {
1107
  $main::lxdebug->enter_sub();
1108

    
1109
  my $form     = $main::form;
1110

    
1111
  if ($form->{in_out} eq 'out') {
1112
    stock_out_form();
1113
  } else {
1114
    stock_in_form();
1115
  }
1116

    
1117
  $main::lxdebug->leave_sub();
1118
}
1119

    
1120
sub redo_stock_info {
1121
  $main::lxdebug->enter_sub();
1122

    
1123
  my %params    = @_;
1124

    
1125
  my $form     = $main::form;
1126

    
1127
  my @non_empty = grep { $_->{qty} } @{ $params{stock_info} };
1128

    
1129
  if ($params{add_empty_row}) {
1130
    push @non_empty, {
1131
      'warehouse_id' => scalar(@non_empty) ? $non_empty[-1]->{warehouse_id} : undef,
1132
      'bin_id'       => scalar(@non_empty) ? $non_empty[-1]->{bin_id}       : undef,
1133
    };
1134
  }
1135

    
1136
  @{ $params{stock_info} } = @non_empty;
1137

    
1138
  $main::lxdebug->leave_sub();
1139
}
1140

    
1141
sub update_stock_in {
1142
  $main::lxdebug->enter_sub();
1143

    
1144
  my $form     = $main::form;
1145
  my %myconfig = %main::myconfig;
1146

    
1147
  my $stock_info = [];
1148

    
1149
  foreach my $i (1..$form->{rowcount}) {
1150
    $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1151
    push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(warehouse_id bin_id chargenumber
1152
                                                                   bestbefore qty unit delivery_order_items_stock_id) };
1153
  }
1154

    
1155
  display_stock_in_form($stock_info);
1156

    
1157
  $main::lxdebug->leave_sub();
1158
}
1159

    
1160
sub stock_in_form {
1161
  $main::lxdebug->enter_sub();
1162

    
1163
  my $form     = $main::form;
1164

    
1165
  my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
1166

    
1167
  display_stock_in_form($stock_info);
1168

    
1169
  $main::lxdebug->leave_sub();
1170
}
1171

    
1172
sub display_stock_in_form {
1173
  $main::lxdebug->enter_sub();
1174

    
1175
  my $stock_info = shift;
1176

    
1177
  my $form     = $main::form;
1178
  my %myconfig = %main::myconfig;
1179
  my $locale   = $main::locale;
1180

    
1181
  $form->{title} = $locale->text('Stock');
1182

    
1183
  my $part_info  = IC->get_basic_part_info('id' => $form->{parts_id});
1184

    
1185
  # Standardlagerplatz für Standard-Auslagern verwenden, falls keiner für die Ware explizit definiert wurde
1186
  if ($::instance_conf->get_transfer_default_use_master_default_bin) {
1187
    $part_info->{warehouse_id} ||= $::instance_conf->get_warehouse_id;
1188
    $part_info->{bin_id}       ||= $::instance_conf->get_bin_id;
1189
  }
1190

    
1191
  my $units      = AM->retrieve_units(\%myconfig, $form);
1192
  # der zweite Parameter von unit_select_data gibt den default-Namen (selected) vor
1193
  my $units_data = AM->unit_select_data($units, $form->{do_unit}, undef, $part_info->{unit});
1194

    
1195
  $form->get_lists('warehouses' => { 'key'    => 'WAREHOUSES',
1196
                                     'bins'   => 'BINS' });
1197

    
1198
  redo_stock_info('stock_info' => $stock_info, 'add_empty_row' => !$form->{delivered});
1199

    
1200
  get_basic_bin_wh_info($stock_info);
1201

    
1202
  $form->header(no_layout => 1);
1203
  print $form->parse_html_template('do/stock_in_form', { 'UNITS'      => $units_data,
1204
                                                         'STOCK_INFO' => $stock_info,
1205
                                                         'PART_INFO'  => $part_info, });
1206

    
1207
  $main::lxdebug->leave_sub();
1208
}
1209

    
1210
sub _stock_in_out_set_qty_display {
1211
  my $stock_info       = shift;
1212
  my $form             = $::form;
1213
  my $all_units        = AM->retrieve_all_units();
1214
  my $sum              = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1215
  $form->{qty_display} = $form->format_amount_units(amount      => $sum * 1,
1216
                                                    part_unit   => $form->{partunit},
1217
                                                    amount_unit => $all_units->{ $form->{partunit} }->{base_unit},
1218
                                                    conv_units  => 'convertible_not_smaller',
1219
                                                    max_places  => 2);
1220
}
1221

    
1222
sub set_stock_in {
1223
  $main::lxdebug->enter_sub();
1224

    
1225
  my $form     = $main::form;
1226
  my %myconfig = %main::myconfig;
1227

    
1228
  my $stock_info = [];
1229

    
1230
  foreach my $i (1..$form->{rowcount}) {
1231
    $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1232

    
1233
    next if ($form->{"qty_$i"} <= 0);
1234

    
1235
    push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(delivery_order_items_stock_id warehouse_id bin_id chargenumber bestbefore qty unit) };
1236
  }
1237

    
1238
  $form->{stock} = YAML::Dump($stock_info);
1239

    
1240
  _stock_in_out_set_qty_display($stock_info);
1241

    
1242
  my $do_qty       = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
1243
  my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1244

    
1245
  $form->header();
1246
  print $form->parse_html_template('do/set_stock_in_out', {
1247
    qty_matches => $do_qty == $transfer_qty,
1248
  });
1249

    
1250
  $main::lxdebug->leave_sub();
1251
}
1252

    
1253
sub stock_out_form {
1254
  $main::lxdebug->enter_sub();
1255

    
1256
  my $form     = $main::form;
1257
  my %myconfig = %main::myconfig;
1258
  my $locale   = $main::locale;
1259

    
1260
  $form->{title} = $locale->text('Release From Stock');
1261

    
1262
  my $part_info  = IC->get_basic_part_info('id' => $form->{parts_id});
1263

    
1264
  my $units      = AM->retrieve_units(\%myconfig, $form);
1265
  my $units_data = AM->unit_select_data($units, undef, undef, $part_info->{unit});
1266

    
1267
  my @contents   = DO->get_item_availability('parts_id' => $form->{parts_id});
1268

    
1269
  my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
1270

    
1271
  if (!$form->{delivered}) {
1272
    foreach my $row (@contents) {
1273
      $row->{available_qty} = $form->format_amount_units('amount'      => $row->{qty} * 1,
1274
                                                         'part_unit'   => $part_info->{unit},
1275
                                                         'conv_units'  => 'convertible_not_smaller',
1276
                                                         'max_places'  => 2);
1277

    
1278
      foreach my $sinfo (@{ $stock_info }) {
1279
        next if (($row->{bin_id}       != $sinfo->{bin_id}) ||
1280
                 ($row->{warehouse_id} != $sinfo->{warehouse_id}) ||
1281
                 ($row->{chargenumber} ne $sinfo->{chargenumber}) ||
1282
                 ($row->{bestbefore}   ne $sinfo->{bestbefore}));
1283

    
1284
        map { $row->{"stock_$_"} = $sinfo->{$_} } qw(qty unit error delivery_order_items_stock_id);
1285
      }
1286
    }
1287

    
1288
  } else {
1289
    get_basic_bin_wh_info($stock_info);
1290

    
1291
    foreach my $sinfo (@{ $stock_info }) {
1292
      map { $sinfo->{"stock_$_"} = $sinfo->{$_} } qw(qty unit);
1293
    }
1294
  }
1295

    
1296
  $form->header(no_layout => 1);
1297
  print $form->parse_html_template('do/stock_out_form', { 'UNITS'      => $units_data,
1298
                                                          'WHCONTENTS' => $form->{delivered} ? $stock_info : \@contents,
1299
                                                          'PART_INFO'  => $part_info, });
1300

    
1301
  $main::lxdebug->leave_sub();
1302
}
1303

    
1304
sub set_stock_out {
1305
  $main::lxdebug->enter_sub();
1306

    
1307
  my $form     = $main::form;
1308
  my %myconfig = %main::myconfig;
1309
  my $locale   = $main::locale;
1310

    
1311
  my $stock_info = [];
1312

    
1313
  foreach my $i (1 .. $form->{rowcount}) {
1314
    $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1315

    
1316
    next if ($form->{"qty_$i"} <= 0);
1317

    
1318
    push @{ $stock_info }, {
1319
      'warehouse_id' => $form->{"warehouse_id_$i"},
1320
      'bin_id'       => $form->{"bin_id_$i"},
1321
      'chargenumber' => $form->{"chargenumber_$i"},
1322
      'bestbefore'   => $form->{"bestbefore_$i"},
1323
      'qty'          => $form->{"qty_$i"},
1324
      'unit'         => $form->{"unit_$i"},
1325
      'row'          => $i,
1326
      'delivery_order_items_stock_id'  => $form->{"delivery_order_items_stock_id_$i"},
1327
    };
1328
  }
1329

    
1330
  my @errors     = DO->check_stock_availability('requests' => $stock_info,
1331
                                                'parts_id' => $form->{parts_id});
1332

    
1333
  $form->{stock} = YAML::Dump($stock_info);
1334

    
1335
  if (@errors) {
1336
    $form->{ERRORS} = [];
1337
    map { push @{ $form->{ERRORS} }, $locale->text('Error in row #1: The quantity you entered is bigger than the stocked quantity.', $_->{row}); } @errors;
1338
    stock_in_out_form();
1339

    
1340
  } else {
1341
    _stock_in_out_set_qty_display($stock_info);
1342

    
1343
    my $do_qty       = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
1344
    my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1345

    
1346
    $form->header();
1347
    print $form->parse_html_template('do/set_stock_in_out', {
1348
      qty_matches => $do_qty == $transfer_qty,
1349
    });
1350
  }
1351

    
1352
  $main::lxdebug->leave_sub();
1353
}
1354

    
1355
sub transfer_in {
1356
  $main::lxdebug->enter_sub();
1357

    
1358
  my $form     = $main::form;
1359
  my %myconfig = %main::myconfig;
1360
  my $locale   = $main::locale;
1361

    
1362
  if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
1363
    $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred in.'), 'back_button' => 1);
1364
  }
1365

    
1366
  save(no_redirect => 1);
1367

    
1368
  my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_in_${_}"} } (1 .. $form->{rowcount});
1369
  my @all_requests;
1370

    
1371
  if (@part_ids) {
1372
    my $units         = AM->retrieve_units(\%myconfig, $form);
1373
    my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1374
    my %request_map;
1375

    
1376
    $form->{ERRORS}   = [];
1377

    
1378
    foreach my $i (1 .. $form->{rowcount}) {
1379
      next unless ($form->{"id_$i"} && $form->{"stock_in_$i"});
1380

    
1381
      my $row_sum_base_qty = 0;
1382
      my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1383

    
1384
      foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_in_$i"}) }) {
1385
        $request->{parts_id}  = $form->{"id_$i"};
1386
        $row_sum_base_qty    += $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
1387

    
1388
        $request->{project_id} = $form->{"project_id_$i"} || $form->{globalproject_id};
1389

    
1390
        push @all_requests, $request;
1391
      }
1392

    
1393
      next if (0 == $row_sum_base_qty);
1394

    
1395
      my $do_base_qty = $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1396

    
1397
#      if ($do_base_qty != $row_sum_base_qty) {
1398
#        push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no stock at all or the full quantity of #2 #3.',
1399
#                                                 $i, $form->{"qty_$i"}, $form->{"unit_$i"});
1400
#      }
1401
    }
1402

    
1403
    if (@{ $form->{ERRORS} }) {
1404
      push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
1405

    
1406
      set_headings('edit');
1407
      update();
1408
      $main::lxdebug->leave_sub();
1409

    
1410
      $::dispatcher->end_request;
1411
    }
1412
  }
1413

    
1414
  DO->transfer_in_out('direction' => 'in',
1415
                      'requests'  => \@all_requests);
1416

    
1417
  SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1418

    
1419
  $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id});
1420
  $form->redirect;
1421

    
1422
  $main::lxdebug->leave_sub();
1423
}
1424

    
1425
sub transfer_out {
1426
  $main::lxdebug->enter_sub();
1427

    
1428
  my $form     = $main::form;
1429
  my %myconfig = %main::myconfig;
1430
  my $locale   = $main::locale;
1431

    
1432
  if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
1433
    $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred out.'), 'back_button' => 1);
1434
  }
1435

    
1436
  save(no_redirect => 1);
1437

    
1438
  my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_out_${_}"} } (1 .. $form->{rowcount});
1439
  my @all_requests;
1440

    
1441
  if (@part_ids) {
1442
    my $units         = AM->retrieve_units(\%myconfig, $form);
1443
    my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1444
    my %request_map;
1445

    
1446
    $form->{ERRORS}   = [];
1447

    
1448
    foreach my $i (1 .. $form->{rowcount}) {
1449
      next unless ($form->{"id_$i"} && $form->{"stock_out_$i"});
1450

    
1451
      my $row_sum_base_qty = 0;
1452
      my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1453

    
1454
      foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_out_$i"}) }) {
1455
        $request->{parts_id} = $form->{"id_$i"};
1456
        $request->{base_qty} = $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
1457
        $request->{project_id} = $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id};
1458

    
1459
        my $map_key          = join '--', ($form->{"id_$i"}, @{$request}{qw(warehouse_id bin_id chargenumber bestbefore)});
1460

    
1461
        $request_map{$map_key}                 ||= $request;
1462
        $request_map{$map_key}->{sum_base_qty} ||= 0;
1463
        $request_map{$map_key}->{sum_base_qty}  += $request->{base_qty};
1464
        $row_sum_base_qty                       += $request->{base_qty};
1465

    
1466
        push @all_requests, $request;
1467
      }
1468

    
1469
      next if (0 == $row_sum_base_qty);
1470

    
1471
      my $do_base_qty = $form->{"qty_$i"} * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1472

    
1473
#      if ($do_base_qty != $row_sum_base_qty) {
1474
#        push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no transfer at all or the full quantity of #2 #3.',
1475
#                                                 $i, $form->{"qty_$i"}, $form->{"unit_$i"});
1476
#      }
1477
    }
1478

    
1479
    if (%request_map) {
1480
      my @bin_ids      = map { $_->{bin_id} } values %request_map;
1481
      my %bin_info_map = WH->get_basic_bin_info('id' => \@bin_ids);
1482
      my @contents     = DO->get_item_availability('parts_id' => \@part_ids);
1483

    
1484
      foreach my $inv (@contents) {
1485
        my $map_key = join '--', @{$inv}{qw(parts_id warehouse_id bin_id chargenumber bestbefore)};
1486

    
1487
        next unless ($request_map{$map_key});
1488

    
1489
        my $request    = $request_map{$map_key};
1490
        $request->{ok} = $request->{sum_base_qty} <= $inv->{qty};
1491
      }
1492

    
1493
      foreach my $request (values %request_map) {
1494
        next if ($request->{ok});
1495

    
1496
        my $pinfo = $part_info_map{$request->{parts_id}};
1497
        my $binfo = $bin_info_map{$request->{bin_id}};
1498

    
1499
        if ($::instance_conf->get_show_bestbefore) {
1500
            push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, #5, for the transfer of #6.",
1501
                                                     $pinfo->{description},
1502
                                                     $binfo->{warehouse_description},
1503
                                                     $binfo->{bin_description},
1504
                                                     $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
1505
                                                     $request->{bestbefore} ? $locale->text('bestbefore #1', $request->{bestbefore}) : $locale->text('no bestbefore'),
1506
                                                     $form->format_amount_units('amount'      => $request->{sum_base_qty},
1507
                                                                                'part_unit'   => $pinfo->{unit},
1508
                                                                                'conv_units'  => 'convertible_not_smaller'));
1509
        } else {
1510
            push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, for the transfer of #5.",
1511
                                                     $pinfo->{description},
1512
                                                     $binfo->{warehouse_description},
1513
                                                     $binfo->{bin_description},
1514
                                                     $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
1515
                                                     $form->format_amount_units('amount'      => $request->{sum_base_qty},
1516
                                                                                'part_unit'   => $pinfo->{unit},
1517
                                                                                'conv_units'  => 'convertible_not_smaller'));
1518
        }
1519
      }
1520
    }
1521

    
1522
    if (@{ $form->{ERRORS} }) {
1523
      push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
1524

    
1525
      set_headings('edit');
1526
      update();
1527
      $main::lxdebug->leave_sub();
1528

    
1529
      $::dispatcher->end_request;
1530
    }
1531
  }
1532
  DO->transfer_in_out('direction' => 'out',
1533
                      'requests'  => \@all_requests);
1534

    
1535
  SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1536

    
1537
  $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id});
1538
  $form->redirect;
1539

    
1540
  $main::lxdebug->leave_sub();
1541
}
1542

    
1543
sub mark_closed {
1544
  $main::lxdebug->enter_sub();
1545

    
1546
  my $form     = $main::form;
1547

    
1548
  DO->close_orders('ids' => [ $form->{id} ]);
1549

    
1550
  $form->{closed} = 1;
1551

    
1552
  update();
1553

    
1554
  $main::lxdebug->leave_sub();
1555
}
1556

    
1557
sub display_form {
1558
  $::lxdebug->enter_sub;
1559

    
1560
  $::auth->assert('purchase_delivery_order_edit | sales_delivery_order_edit');
1561

    
1562
  relink_accounts();
1563
  retrieve_partunits();
1564

    
1565
  my $new_rowcount = $::form->{"rowcount"} * 1 + 1;
1566
  $::form->{"project_id_${new_rowcount}"} = $::form->{"globalproject_id"};
1567

    
1568
  $::form->language_payment(\%::myconfig);
1569

    
1570
  Common::webdav_folder($::form);
1571

    
1572
  form_header();
1573
  display_row(++$::form->{rowcount});
1574
  form_footer();
1575

    
1576
  $::lxdebug->leave_sub;
1577
}
1578

    
1579
sub yes {
1580
  call_sub($main::form->{yes_nextsub});
1581
}
1582

    
1583
sub no {
1584
  call_sub($main::form->{no_nextsub});
1585
}
1586

    
1587
sub update {
1588
  call_sub($main::form->{update_nextsub} || $main::form->{nextsub} || 'update_delivery_order');
1589
}
1590

    
1591
sub dispatcher {
1592
  my $form     = $main::form;
1593
  my $locale   = $main::locale;
1594

    
1595
  foreach my $action (qw(update ship_to print e_mail save transfer_out transfer_out_default sort
1596
                         transfer_in transfer_in_default mark_closed save_as_new invoice delete)) {
1597
    if ($form->{"action_${action}"}) {
1598
      call_sub($action);
1599
      return;
1600
    }
1601
  }
1602

    
1603
  $form->error($locale->text('No action defined.'));
1604
}
1605

    
1606
sub transfer_out_default {
1607
  $main::lxdebug->enter_sub();
1608

    
1609
  my $form     = $main::form;
1610

    
1611
  transfer_in_out_default('direction' => 'out');
1612

    
1613
  $main::lxdebug->leave_sub();
1614
}
1615

    
1616
sub transfer_in_default {
1617
  $main::lxdebug->enter_sub();
1618

    
1619
  my $form     = $main::form;
1620

    
1621
  transfer_in_out_default('direction' => 'in');
1622

    
1623
  $main::lxdebug->leave_sub();
1624
}
1625

    
1626
# Falls das Standardlagerverfahren aktiv ist, wird
1627
# geprüft, ob alle Standardlagerplätze für die Auslager-
1628
# artikel vorhanden sind UND ob die Warenmenge ausreicht zum
1629
# Auslagern. Falls nicht wird entsprechend eine Fehlermeldung
1630
# generiert. Offen Chargennummer / bestbefore wird nicht berücksichtigt
1631
sub transfer_in_out_default {
1632
  $main::lxdebug->enter_sub();
1633

    
1634
  my $form     = $main::form;
1635
  my %myconfig = %main::myconfig;
1636
  my $locale   = $main::locale;
1637
  my %params   = @_;
1638

    
1639
  my (%missing_default_bins, %qty_parts, @all_requests, %part_info_map, $default_warehouse_id, $default_bin_id);
1640

    
1641
  Common::check_params(\%params, qw(direction));
1642

    
1643
  # entsprechende defaults holen, falls standardlagerplatz verwendet werden soll
1644
  if ($::instance_conf->get_transfer_default_use_master_default_bin) {
1645
    $default_warehouse_id = $::instance_conf->get_warehouse_id;
1646
    $default_bin_id       = $::instance_conf->get_bin_id;
1647
  }
1648

    
1649

    
1650
  my @part_ids = map { $form->{"id_${_}"} } (1 .. $form->{rowcount});
1651
  if (@part_ids) {
1652
    my $units         = AM->retrieve_units(\%myconfig, $form);
1653
    %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1654
    foreach my $i (1 .. $form->{rowcount}) {
1655
      next unless ($form->{"id_$i"});
1656
      my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1657
      my $qty =   $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1658

    
1659
      $form->show_generic_error($locale->text("Cannot transfer negative entries." ), 'back_button' => 1) if ($qty < 0);
1660
      # if we do not want to transfer services and this part is a service, set qty to zero
1661
      # ... and do not create a hash entry in %qty_parts below (will skip check for bins for the transfer == out case)
1662
      # ... and push only a empty (undef) element to @all_requests (will skip check for bin_id and warehouse_id and will not alter the row)
1663

    
1664
      $qty = 0 if (!$::instance_conf->get_transfer_default_services && !defined($part_info_map{$form->{"id_$i"}}->{inventory_accno_id}) && !$part_info_map{$form->{"id_$i"}}->{assembly});
1665
      $qty_parts{$form->{"id_$i"}} += $qty;
1666
      if ($qty == 0) {
1667
        delete $qty_parts{$form->{"id_$i"}} unless $qty_parts{$form->{"id_$i"}};
1668
        undef $form->{"stock_in_$i"};
1669
      }
1670

    
1671
      $part_info_map{$form->{"id_$i"}}{bin_id}       ||= $default_bin_id;
1672
      $part_info_map{$form->{"id_$i"}}{warehouse_id} ||= $default_warehouse_id;
1673

    
1674
      push @all_requests, ($qty == 0) ? { } : {
1675
                        'chargenumber' => '',  #?? die müsste entsprechend geholt werden
1676
                        #'bestbefore' => undef, # TODO wird nicht berücksichtigt
1677
                        'bin_id' => $part_info_map{$form->{"id_$i"}}{bin_id},
1678
                        'qty' => $qty,
1679
                        'parts_id' => $form->{"id_$i"},
1680
                        'comment' => $locale->text("Default transfer delivery order"),
1681
                        'unit' => $part_info_map{$form->{"id_$i"}}{unit},
1682
                        'warehouse_id' => $part_info_map{$form->{"id_$i"}}{warehouse_id},
1683
                        'oe_id' => $form->{id},
1684
                        'project_id' => $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id}
1685
                      };
1686
    }
1687

    
1688
    # jetzt wird erst überprüft, ob die Stückzahl entsprechend stimmt.
1689
    # check if bin (transfer in and transfer out and qty (transfer out) is correct
1690
    foreach my $key (keys %qty_parts) {
1691

    
1692
      $missing_default_bins{$key}{missing_bin} = 1 unless ($part_info_map{$key}{bin_id});
1693
      next unless ($part_info_map{$key}{bin_id}); # abbruch
1694

    
1695
      if ($params{direction} eq 'out') {  # wird nur für ausgehende Mengen benötigt
1696
        my ($max_qty, $error) = WH->get_max_qty_parts_bin(parts_id => $key, bin_id => $part_info_map{$key}{bin_id});
1697
        if ($error == 1) {
1698
          # wir können nicht entscheiden, welche charge oder mhd (bestbefore) ausgewählt sein soll
1699
          # deshalb rückmeldung nach oben geben, manuell auszulagern
1700
          # TODO Bei nur einem Treffer mit Charge oder bestbefore wäre das noch möglich
1701
          $missing_default_bins{$key}{chargenumber} = 1;
1702
        }
1703
        if ($max_qty < $qty_parts{$key}){
1704
          $missing_default_bins{$key}{missing_qty} = $max_qty - $qty_parts{$key};
1705
        }
1706
      }
1707
    }
1708
  } # if @parts_id
1709

    
1710
  # Abfrage für Fehlerbehandlung (nur bei direction == out)
1711
  if (scalar (keys %missing_default_bins)) {
1712
    my $fehlertext;
1713
    foreach my $fehler (keys %missing_default_bins) {
1714

    
1715
      my $ware = WH->get_part_description(parts_id => $fehler);
1716
      if ($missing_default_bins{$fehler}{missing_bin}){
1717
        $fehlertext .= "Kein Standardlagerplatz definiert bei $ware <br>";
1718
      }
1719
      if ($missing_default_bins{$fehler}{missing_qty}) {  # missing_qty
1720
        $fehlertext .= "Es fehlen " . $missing_default_bins{$fehler}{missing_qty}*-1 .
1721
                       " von $ware auf dem Standard-Lagerplatz " . $part_info_map{$fehler}{bin} .   " zum Auslagern<br>";
1722
      }
1723
      if ($missing_default_bins{$fehler}{chargenumber}){
1724
        $fehlertext .= "Die Ware hat eine Chargennummer oder eine Mindesthaltbarkeit definiert.
1725
                        Hier kann man nicht automatisch entscheiden.
1726
                        Bitte diesen Lieferschein manuell auslagern.
1727
                        Bei: $ware";
1728
      }
1729
      # auslagern soll immer gehen, auch wenn nicht genügend auf lager ist.
1730
      # der lagerplatz ist hier extra konfigurierbar, bspw. Lager-Korrektur mit
1731
      # Lagerplatz Lagerplatz-Korrektur
1732
      my $default_warehouse_id_ignore_onhand = $::instance_conf->get_warehouse_id_ignore_onhand;
1733
      my $default_bin_id_ignore_onhand       = $::instance_conf->get_bin_id_ignore_onhand;
1734
      if ($::instance_conf->get_transfer_default_ignore_onhand && $default_bin_id_ignore_onhand) {
1735
        # entsprechende defaults holen
1736
        # falls chargenumber, bestbefore oder anzahl nicht stimmt, auf automatischen
1737
        # lagerplatz wegbuchen!
1738
        foreach (@all_requests) {
1739
          if ($_->{parts_id} eq $fehler){
1740
          $_->{bin_id}        = $default_bin_id_ignore_onhand;
1741
          $_->{warehouse_id}  = $default_warehouse_id_ignore_onhand;
1742
          }
1743
        }
1744
      } else {
1745
        #$main::lxdebug->message(0, 'Fehlertext: ' . $fehlertext);
1746
        $form->show_generic_error($locale->text("Cannot transfer. <br> Reason:<br>#1", $fehlertext ), 'back_button' => 1);
1747
      }
1748
    }
1749
  }
1750

    
1751

    
1752
  # hier der eigentliche fallunterschied für in oder out
1753
  my $prefix   = $params{direction} eq 'in' ? 'in' : 'out';
1754

    
1755
  # dieser array_ref ist für DO->save da:
1756
  # einmal die all_requests in YAML verwandeln, damit delivery_order_items_stock
1757
  # gefüllt werden kann.
1758
  # could be dumped to the form in the first loop,
1759
  # but maybe bin_id and warehouse_id has changed to the "korrekturlager" with
1760
  # allowed negative qty ($::instance_conf->get_warehouse_id_ignore_onhand) ...
1761
  my $i = 0;
1762
  foreach (@all_requests){
1763
    $i++;
1764
    next unless scalar(%{ $_ });
1765
    $form->{"stock_${prefix}_$i"} = YAML::Dump([$_]);
1766
  }
1767

    
1768
  save(no_redirect => 1); # Wir können auslagern, deshalb beleg speichern
1769
                          # und in delivery_order_items_stock speichern
1770

    
1771
  # ... and fill back the persistent dois_id for inventory fk
1772
  undef (@all_requests);
1773
  foreach my $i (1 .. $form->{rowcount}) {
1774
    next unless ($form->{"id_$i"} && $form->{"stock_${prefix}_$i"});
1775
    push @all_requests, @{ DO->unpack_stock_information('packed' => $form->{"stock_${prefix}_$i"}) };
1776
  }
1777
  DO->transfer_in_out('direction' => $prefix,
1778
                      'requests'  => \@all_requests);
1779

    
1780
  SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1781

    
1782
  $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'out';
1783
  $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'in';
1784
  $form->redirect;
1785

    
1786
}
1787

    
1788
sub sort {
1789
  $main::lxdebug->enter_sub();
1790

    
1791
  check_do_access();
1792

    
1793
  my $form     = $main::form;
1794
  my %temp_hash;
1795

    
1796
  save(no_redirect => 1); # has to be done, at least for newly added positions
1797

    
1798
  # hashify partnumbers, positions. key is delivery_order_items_id
1799
  for my $i (1 .. ($form->{rowcount}) ) {
1800
    $temp_hash{$form->{"delivery_order_items_id_$i"}} = { runningnumber => $form->{"runningnumber_$i"}, partnumber => $form->{"partnumber_$i"} };
1801
    if ($form->{id} && $form->{"discount_$i"}) {
1802
      # prepare_order assumes a db value if there is a form->id and multiplies *100
1803
      # We hope for new controller code (no more format_amount/parse_amount distinction)
1804
      $form->{"discount_$i"} /=100;
1805
    }
1806
  }
1807
  # naturally sort partnumbers and get a sorted array of doi_ids
1808
  my @sorted_doi_ids =  sort { Sort::Naturally::ncmp($temp_hash{$a}->{"partnumber"}, $temp_hash{$b}->{"partnumber"}) }  keys %temp_hash;
1809

    
1810

    
1811
  my $new_number = 1;
1812

    
1813
  for (@sorted_doi_ids) {
1814
    $form->{"runningnumber_$temp_hash{$_}->{runningnumber}"} = $new_number;
1815
    $new_number++;
1816
  }
1817
  # all parse_amounts changes are in form (i.e. , to .) therefore we need
1818
  # another format_amount to change it back, for the next save ;-(
1819
  # works great except for row discounts (see above comment)
1820
  prepare_order();
1821

    
1822

    
1823
    $main::lxdebug->leave_sub();
1824
    save();
1825
}
1826

    
1827
__END__
1828

    
1829
=pod
1830

    
1831
=encoding utf8
1832

    
1833
=head1 NAME
1834

    
1835
do.pl - Script for all calls to delivery order
1836

    
1837

    
1838
=head1 FUNCTIONS
1839

    
1840
=over 2
1841

    
1842
=item C<sort>
1843

    
1844
Sorts all position with Natural Sort. Can be activated in form_footer.html like this
1845
C<E<lt>input class="submit" type="submit" name="action_sort" id="sort_button" value="[% 'Sort and Save' | $T8 %]"E<gt>>
1846

    
1847
=back
1848

    
1849
=head1 TODO
1850

    
1851
Sort and Save can be implemented as an optional button if configuration ca be set by client config.
1852
Example coding for database scripts and templates in (git show af2f24b8), check also
1853
autogeneration for rose (scripts/rose_auto_create_model.pl --h)