Whitespace in Kontennamen filtern
[kivitendo-erp.git] / SL / AM.pm
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) 2001
10 #
11 #  Author: Dieter Simader
12 #   Email: dsimader@sql-ledger.org
13 #     Web: http://www.sql-ledger.org
14 #
15 #  Contributors:
16 #
17 # This program is free software; you can redistribute it and/or modify
18 # it under the terms of the GNU General Public License as published by
19 # the Free Software Foundation; either version 2 of the License, or
20 # (at your option) any later version.
21 #
22 # This program is distributed in the hope that it will be useful,
23 # but WITHOUT ANY WARRANTY; without even the implied warranty of
24 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25 # GNU General Public License for more details.
26 # You should have received a copy of the GNU General Public License
27 # along with this program; if not, write to the Free Software
28 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
29 #======================================================================
30 #
31 # Administration module
32 #    Chart of Accounts
33 #    template routines
34 #    preferences
35 #
36 #======================================================================
37
38 package AM;
39
40 use Carp;
41 use Data::Dumper;
42 use Encode;
43 use List::MoreUtils qw(any);
44 use SL::DBUtils;
45 use SL::DB::AuthUser;
46 use SL::DB::Default;
47 use SL::DB::Employee;
48 use SL::DB::Chart;
49 use SL::GenericTranslations;
50
51 use strict;
52
53 sub get_account {
54   $main::lxdebug->enter_sub();
55
56   # fetch chart-related data and set form fields
57   # get_account is called by add_account in am.pl
58   # always sets $form->{TAXKEY} and default_accounts
59   # loads chart data when $form->{id} is passed
60
61   my ($self, $myconfig, $form) = @_;
62
63   # get default accounts
64   map { $form->{$_} = $::instance_conf->{$_} } qw(inventory_accno_id income_accno_id expense_accno_id);
65
66   require SL::DB::Tax;
67   my $taxes = SL::DB::Manager::Tax->get_all( with_objects => ['chart'] , sort_by => 'taxkey' );
68   $form->{TAXKEY} = [];
69   foreach my $tk ( @{$taxes} ) {
70     push @{ $form->{TAXKEY} },  { id          => $tk->id,
71                                   chart_accno => $tk->chart_id ? $tk->chart->accno : undef,
72                                   taxkey      => $tk->taxkey,
73                                   tax         => $tk->id . '--' . $tk->taxkey,
74                                   rate        => $tk->rate
75                                 };
76   };
77
78   if ($form->{id}) {
79
80     my $chart_obj = SL::DB::Manager::Chart->find_by(id => $form->{id}) || die "Can't open chart";
81
82     my @chart_fields = qw(accno description charttype category link pos_bilanz
83                           pos_eur pos_er new_chart_id valid_from pos_bwa datevautomatik);
84     foreach my $cf ( @chart_fields ) {
85       $form->{"$cf"} = $chart_obj->$cf;
86     }
87
88     my $active_taxkey = $chart_obj->get_active_taxkey;
89     $form->{$_}  = $active_taxkey->$_ foreach qw(taxkey_id pos_ustva tax_id startdate);
90     $form->{tax} = $active_taxkey->tax_id . '--' . $active_taxkey->taxkey_id;
91
92     # check if there are any transactions for this chart
93     $form->{orphaned} = $chart_obj->has_transaction ? 0 : 1;
94
95     # check if new account is active
96     # The old sql query was broken since at least 2006 and always returned 0
97     $form->{new_chart_valid} = $chart_obj->new_chart_valid;
98
99     # get the taxkeys of the account
100     $form->{ACCOUNT_TAXKEYS} = [];
101     foreach my $taxkey ( @{ $chart_obj->taxkeys } ) {
102       push @{ $form->{ACCOUNT_TAXKEYS} }, { id             => $taxkey->id,
103                                             chart_id       => $taxkey->chart_id,
104                                             tax_id         => $taxkey->tax_id,
105                                             taxkey_id      => $taxkey->taxkey_id,
106                                             pos_ustva      => $taxkey->pos_ustva,
107                                             startdate      => $taxkey->startdate->to_kivitendo,
108                                             taxdescription => $taxkey->tax->taxdescription,
109                                             rate           => $taxkey->tax->rate,
110                                             accno          => defined $taxkey->tax->chart_id ? $taxkey->tax->chart->accno : undef,
111                                           };
112     }
113
114     # get new accounts (Folgekonto). Find all charts with the same link
115     $form->{NEWACCOUNT} = $chart_obj->db->dbh->selectall_arrayref('select id, accno,description from chart where link = ? order by accno', {Slice => {}}, $chart_obj->link);
116
117   } else { # set to orphaned for new charts, so chart_type can be changed (needed by $AccountIsPosted)
118     $form->{orphaned} = 1;
119   };
120
121   $main::lxdebug->leave_sub();
122 }
123
124 sub save_account {
125   $main::lxdebug->enter_sub();
126
127   # TODO: it should be forbidden to change an account to a heading if there
128   # have been bookings to this account in the past
129
130   my ($self, $myconfig, $form) = @_;
131
132   # connect to database, turn off AutoCommit
133   my $dbh = $form->dbconnect_noauto($myconfig);
134
135   for (qw(AR_include_in_dropdown AP_include_in_dropdown summary_account)) {
136     $form->{$form->{$_}} = $form->{$_} if $form->{$_};
137   }
138
139   # sanity check, can't have AR with AR_...
140   if ($form->{AR} || $form->{AP} || $form->{IC}) {
141     if (any { $form->{$_} } qw(AR_amount AR_tax AR_paid AP_amount AP_tax AP_paid IC_sale IC_cogs IC_taxpart IC_income IC_expense IC_taxservice)) {
142       $form->error($::locale->text('It is not allowed that a summary account occurs in a drop-down menu!'));
143     }
144   }
145
146   my @link_order = qw(AR AR_amount AR_tax AR_paid AP AP_amount AP_tax AP_paid IC IC_sale IC_cogs IC_taxpart IC_income IC_expense IC_taxservice);
147   $form->{link} = join ':', grep $_, map $form->{$_}, @link_order;
148
149   # strip blanks from accno
150   map { $form->{$_} =~ s/ //g; } qw(accno);
151
152   # collapse multiple (horizontal) whitespace in chart description (Ticket 148)
153   map { $form->{$_} =~ s/\h+/ /g } qw(description);
154
155   my ($query, $sth);
156
157   if ($form->{id} eq "NULL") {
158     $form->{id} = "";
159   }
160
161   $query = '
162     SELECT accno
163     FROM chart
164     WHERE accno = ?';
165
166   my @values = ($form->{accno});
167
168   if ( $form->{id} ) {
169     $query .= ' AND NOT id = ?';
170     push(@values, $form->{id});
171   }
172
173   my ($accno) = selectrow_query($form, $dbh, $query, @values);
174
175   if ($accno) {
176     $form->error($::locale->text('Account number not unique!'));
177   }
178
179
180   if (!$form->{id} || $form->{id} eq "") {
181     $query = qq|SELECT nextval('id')|;
182     ($form->{"id"}) = selectrow_query($form, $dbh, $query);
183     $query = qq|INSERT INTO chart (id, accno, link) VALUES (?, ?, ?)|;
184     do_query($form, $dbh, $query, $form->{"id"}, $form->{"accno"}, '');
185   }
186
187   @values = ();
188
189
190   if ($form->{id}) {
191
192     # if charttype is heading make sure certain values are empty
193     # specifically, if charttype is changed from an existing account, empty the
194     # fields unnecessary for headings, so that e.g. heading doesn't appear in
195     # drop-down menues due to still having a valid "link" entry
196
197     if ( $form->{charttype} eq 'H' ) {
198       $form->{link} = '';
199       $form->{pos_bwa} = '';
200       $form->{pos_bilanz} = '';
201       $form->{pos_eur} = '';
202       $form->{new_chart_id} = '';
203       $form->{valid_from} = '';
204     };
205
206     $query = qq|UPDATE chart SET
207                   accno = ?,
208                   description = ?,
209                   charttype = ?,
210                   category = ?,
211                   link = ?,
212                   pos_bwa   = ?,
213                   pos_bilanz = ?,
214                   pos_eur = ?,
215                   pos_er = ?,
216                   new_chart_id = ?,
217                   valid_from = ?,
218                   datevautomatik = ?
219                 WHERE id = ?|;
220
221     @values = (
222                   $form->{accno},
223                   $form->{description},
224                   $form->{charttype},
225                   $form->{category},
226                   $form->{link},
227                   conv_i($form->{pos_bwa}),
228                   conv_i($form->{pos_bilanz}),
229                   conv_i($form->{pos_eur}),
230                   conv_i($form->{pos_er}),
231                   conv_i($form->{new_chart_id}),
232                   conv_date($form->{valid_from}),
233                   ($form->{datevautomatik} eq 'T') ? 'true':'false',
234                 $form->{id},
235     );
236
237
238   }
239
240   do_query($form, $dbh, $query, @values);
241
242   #Save Taxkeys
243
244   my @taxkeys = ();
245
246   my $MAX_TRIES = 10; # Maximum count of taxkeys in form
247   my $tk_count;
248
249   READTAXKEYS:
250   for $tk_count (0 .. $MAX_TRIES) {
251
252     # Loop control
253
254     # Check if the account already exists, else cancel
255
256     print(STDERR "Keine Taxkeys weil ID =: $form->{id}\n");
257
258     last READTAXKEYS if ( $form->{'id'} == 0);
259
260     # check if there is a startdate
261     if ( $form->{"taxkey_startdate_$tk_count"} eq '' ) {
262       $tk_count++;
263       next READTAXKEYS;
264     }
265
266     # Add valid taxkeys into the array
267     push @taxkeys ,
268       {
269         id        => ($form->{"taxkey_id_$tk_count"} eq 'NEW') ? conv_i('') : conv_i($form->{"taxkey_id_$tk_count"}),
270         tax_id    => conv_i($form->{"taxkey_tax_$tk_count"}),
271         startdate => conv_date($form->{"taxkey_startdate_$tk_count"}),
272         chart_id  => conv_i($form->{"id"}),
273         pos_ustva => conv_i($form->{"taxkey_pos_ustva_$tk_count"}),
274         delete    => ( $form->{"taxkey_del_$tk_count"} eq 'delete' ) ? '1' : '',
275       };
276
277     $tk_count++;
278   }
279
280   TAXKEY:
281   for my $j (0 .. $#taxkeys){
282     if ( defined $taxkeys[$j]{'id'} ){
283       # delete Taxkey?
284
285       if ($taxkeys[$j]{'delete'}){
286         $query = qq{
287           DELETE FROM taxkeys WHERE id = ?
288         };
289
290         @values = ($taxkeys[$j]{'id'});
291
292         do_query($form, $dbh, $query, @values);
293
294         next TAXKEY;
295       }
296
297       # UPDATE Taxkey
298
299       $query = qq{
300         UPDATE taxkeys
301         SET taxkey_id = (SELECT taxkey FROM tax WHERE tax.id = ?),
302             chart_id  = ?,
303             tax_id    = ?,
304             pos_ustva = ?,
305             startdate = ?
306         WHERE id = ?
307       };
308       @values = (
309         $taxkeys[$j]{'tax_id'},
310         $taxkeys[$j]{'chart_id'},
311         $taxkeys[$j]{'tax_id'},
312         $taxkeys[$j]{'pos_ustva'},
313         $taxkeys[$j]{'startdate'},
314         $taxkeys[$j]{'id'},
315       );
316       do_query($form, $dbh, $query, @values);
317     }
318     else {
319       # INSERT Taxkey
320
321       $query = qq{
322         INSERT INTO taxkeys (
323           taxkey_id,
324           chart_id,
325           tax_id,
326           pos_ustva,
327           startdate
328         )
329         VALUES ((SELECT taxkey FROM tax WHERE tax.id = ?), ?, ?, ?, ?)
330       };
331       @values = (
332         $taxkeys[$j]{'tax_id'},
333         $taxkeys[$j]{'chart_id'},
334         $taxkeys[$j]{'tax_id'},
335         $taxkeys[$j]{'pos_ustva'},
336         $taxkeys[$j]{'startdate'},
337       );
338
339       do_query($form, $dbh, $query, @values);
340     }
341
342   }
343
344   # Update chart.taxkey_id to the latest from taxkeys for this chart.
345   $query = <<SQL;
346     UPDATE chart
347     SET taxkey_id = (
348       SELECT taxkey_id
349       FROM taxkeys
350       WHERE taxkeys.chart_id = chart.id
351       ORDER BY startdate DESC
352       LIMIT 1
353     )
354     WHERE id = ?
355 SQL
356
357   do_query($form, $dbh, $query, $form->{id});
358
359   # commit
360   my $rc = $dbh->commit;
361   $dbh->disconnect;
362
363   $main::lxdebug->leave_sub();
364
365   return $rc;
366 }
367
368 sub delete_account {
369   $main::lxdebug->enter_sub();
370
371   my ($self, $myconfig, $form) = @_;
372
373   # connect to database, turn off AutoCommit
374   my $dbh = $form->dbconnect_noauto($myconfig);
375
376   my $query = qq|SELECT count(*) FROM acc_trans a
377                  WHERE a.chart_id = ?|;
378   my ($count) = selectrow_query($form, $dbh, $query, $form->{id});
379
380   if ($count) {
381     $dbh->disconnect;
382     $main::lxdebug->leave_sub();
383     return;
384   }
385
386   # set inventory_accno_id, income_accno_id, expense_accno_id to defaults
387   foreach my $type (qw(inventory income expense)) {
388     $query =
389       qq|UPDATE parts | .
390       qq|SET ${type}_accno_id = (SELECT ${type}_accno_id FROM defaults) | .
391       qq|WHERE ${type}_accno_id = ?|;
392     do_query($form, $dbh, $query, $form->{id});
393   }
394
395   $query = qq|DELETE FROM tax
396               WHERE chart_id = ?|;
397   do_query($form, $dbh, $query, $form->{id});
398
399   # delete account taxkeys
400   $query = qq|DELETE FROM taxkeys
401               WHERE chart_id = ?|;
402   do_query($form, $dbh, $query, $form->{id});
403
404   # delete chart of account record
405   # last step delete chart, because we have a constraint
406   # to taxkeys
407   $query = qq|DELETE FROM chart
408               WHERE id = ?|;
409   do_query($form, $dbh, $query, $form->{id});
410
411   # commit and redirect
412   my $rc = $dbh->commit;
413   $dbh->disconnect;
414
415   $main::lxdebug->leave_sub();
416
417   return $rc;
418 }
419
420 sub lead {
421   $main::lxdebug->enter_sub();
422
423   my ($self, $myconfig, $form) = @_;
424
425   # connect to database
426   my $dbh = $form->dbconnect($myconfig);
427
428   my $query = qq|SELECT id, lead
429                  FROM leads
430                  ORDER BY 2|;
431
432   my $sth = $dbh->prepare($query);
433   $sth->execute || $form->dberror($query);
434
435   while (my $ref = $sth->fetchrow_hashref("NAME_lc")) {
436     push @{ $form->{ALL} }, $ref;
437   }
438
439   $sth->finish;
440   $dbh->disconnect;
441
442   $main::lxdebug->leave_sub();
443 }
444
445 sub get_lead {
446   $main::lxdebug->enter_sub();
447
448   my ($self, $myconfig, $form) = @_;
449
450   # connect to database
451   my $dbh = $form->dbconnect($myconfig);
452
453   my $query =
454     qq|SELECT l.id, l.lead | .
455     qq|FROM leads l | .
456     qq|WHERE l.id = ?|;
457   my $sth = $dbh->prepare($query);
458   $sth->execute($form->{id}) || $form->dberror($query . " ($form->{id})");
459
460   my $ref = $sth->fetchrow_hashref("NAME_lc");
461
462   map { $form->{$_} = $ref->{$_} } keys %$ref;
463
464   $sth->finish;
465
466   $dbh->disconnect;
467
468   $main::lxdebug->leave_sub();
469 }
470
471 sub save_lead {
472   $main::lxdebug->enter_sub();
473
474   my ($self, $myconfig, $form) = @_;
475   my ($query);
476
477   # connect to database
478   my $dbh = $form->dbconnect($myconfig);
479
480   my @values = ($form->{description});
481   # id is the old record
482   if ($form->{id}) {
483     $query = qq|UPDATE leads SET
484                 lead = ?
485                 WHERE id = ?|;
486     push(@values, $form->{id});
487   } else {
488     $query = qq|INSERT INTO leads
489                 (lead)
490                 VALUES (?)|;
491   }
492   do_query($form, $dbh, $query, @values);
493
494   $dbh->disconnect;
495
496   $main::lxdebug->leave_sub();
497 }
498
499 sub delete_lead {
500   $main::lxdebug->enter_sub();
501
502   my ($self, $myconfig, $form) = @_;
503   my ($query);
504
505   # connect to database
506   my $dbh = $form->dbconnect($myconfig);
507
508   $query = qq|DELETE FROM leads
509               WHERE id = ?|;
510   do_query($form, $dbh, $query, $form->{id});
511
512   $dbh->disconnect;
513
514   $main::lxdebug->leave_sub();
515 }
516
517 sub language {
518   $main::lxdebug->enter_sub();
519
520   my ($self, $myconfig, $form, $return_list) = @_;
521
522   # connect to database
523   my $dbh = $form->dbconnect($myconfig);
524
525   my $query =
526     "SELECT id, description, template_code, article_code, " .
527     "  output_numberformat, output_dateformat, output_longdates " .
528     "FROM language ORDER BY description";
529
530   my $sth = $dbh->prepare($query);
531   $sth->execute || $form->dberror($query);
532
533   my $ary = [];
534
535   while (my $ref = $sth->fetchrow_hashref("NAME_lc")) {
536     push(@{ $ary }, $ref);
537   }
538
539   $sth->finish;
540   $dbh->disconnect;
541
542   $main::lxdebug->leave_sub();
543
544   if ($return_list) {
545     return @{$ary};
546   } else {
547     $form->{ALL} = $ary;
548   }
549 }
550
551 sub get_language {
552   $main::lxdebug->enter_sub();
553
554   my ($self, $myconfig, $form) = @_;
555
556   # connect to database
557   my $dbh = $form->dbconnect($myconfig);
558
559   my $query =
560     "SELECT description, template_code, article_code, " .
561     "  output_numberformat, output_dateformat, output_longdates " .
562     "FROM language WHERE id = ?";
563   my $sth = $dbh->prepare($query);
564   $sth->execute($form->{"id"}) || $form->dberror($query . " ($form->{id})");
565
566   my $ref = $sth->fetchrow_hashref("NAME_lc");
567
568   map { $form->{$_} = $ref->{$_} } keys %$ref;
569
570   $sth->finish;
571
572   $dbh->disconnect;
573
574   $main::lxdebug->leave_sub();
575 }
576
577 sub get_language_details {
578   $main::lxdebug->enter_sub();
579
580   my ($self, $myconfig, $form, $id) = @_;
581
582   # connect to database
583   my $dbh = $form->dbconnect($myconfig);
584
585   my $query =
586     "SELECT template_code, " .
587     "  output_numberformat, output_dateformat, output_longdates " .
588     "FROM language WHERE id = ?";
589   my @res = selectrow_query($form, $dbh, $query, $id);
590   $dbh->disconnect;
591
592   $main::lxdebug->leave_sub();
593
594   return @res;
595 }
596
597 sub save_language {
598   $main::lxdebug->enter_sub();
599
600   my ($self, $myconfig, $form) = @_;
601
602   # connect to database
603   my $dbh = $form->dbconnect($myconfig);
604   my (@values, $query);
605
606   map({ push(@values, $form->{$_}); }
607       qw(description template_code article_code
608          output_numberformat output_dateformat output_longdates));
609
610   # id is the old record
611   if ($form->{id}) {
612     $query =
613       "UPDATE language SET " .
614       "  description = ?, template_code = ?, article_code = ?, " .
615       "  output_numberformat = ?, output_dateformat = ?, " .
616       "  output_longdates = ? " .
617       "WHERE id = ?";
618     push(@values, $form->{id});
619   } else {
620     $query =
621       "INSERT INTO language (" .
622       "  description, template_code, article_code, " .
623       "  output_numberformat, output_dateformat, output_longdates" .
624       ") VALUES (?, ?, ?, ?, ?, ?)";
625   }
626   do_query($form, $dbh, $query, @values);
627
628   $dbh->disconnect;
629
630   $main::lxdebug->leave_sub();
631 }
632
633 sub delete_language {
634   $main::lxdebug->enter_sub();
635
636   my ($self, $myconfig, $form) = @_;
637   my $query;
638
639   # connect to database
640   my $dbh = $form->dbconnect_noauto($myconfig);
641
642   foreach my $table (qw(generic_translations units_language)) {
643     $query = qq|DELETE FROM $table WHERE language_id = ?|;
644     do_query($form, $dbh, $query, $form->{"id"});
645   }
646
647   $query = "DELETE FROM language WHERE id = ?";
648   do_query($form, $dbh, $query, $form->{"id"});
649
650   $dbh->commit();
651   $dbh->disconnect;
652
653   $main::lxdebug->leave_sub();
654 }
655
656 sub prepare_template_filename {
657   $main::lxdebug->enter_sub();
658
659   my ($self, $myconfig, $form) = @_;
660
661   my ($filename, $display_filename);
662
663   if ($form->{type} eq "stylesheet") {
664     $filename = "css/$myconfig->{stylesheet}";
665     $display_filename = $myconfig->{stylesheet};
666
667   } else {
668     $filename = $form->{formname};
669
670     if ($form->{language}) {
671       my ($id, $template_code) = split(/--/, $form->{language});
672       $filename .= "_${template_code}";
673     }
674
675     if ($form->{printer}) {
676       my ($id, $template_code) = split(/--/, $form->{printer});
677       $filename .= "_${template_code}";
678     }
679
680     $filename .= "." . ($form->{format} eq "html" ? "html" : "tex");
681     if ($form->{"formname"} =~ m|\.\.| || $form->{"formname"} =~ m|^/|) {
682       $filename =~ s|.*/||;
683     }
684     $display_filename = $filename;
685     $filename = SL::DB::Default->get->templates . "/$filename";
686   }
687
688   $main::lxdebug->leave_sub();
689
690   return ($filename, $display_filename);
691 }
692
693
694 sub load_template {
695   $main::lxdebug->enter_sub();
696
697   my ($self, $filename) = @_;
698
699   my ($content, $lines) = ("", 0);
700
701   local *TEMPLATE;
702
703   if (open(TEMPLATE, $filename)) {
704     while (<TEMPLATE>) {
705       $content .= $_;
706       $lines++;
707     }
708     close(TEMPLATE);
709   }
710
711   $content = Encode::decode('utf-8-strict', $content);
712
713   $main::lxdebug->leave_sub();
714
715   return ($content, $lines);
716 }
717
718 sub save_template {
719   $main::lxdebug->enter_sub();
720
721   my ($self, $filename, $content) = @_;
722
723   local *TEMPLATE;
724
725   my $error = "";
726
727   if (open(TEMPLATE, ">", $filename)) {
728     $content = Encode::encode('utf-8-strict', $content);
729     $content =~ s/\r\n/\n/g;
730     print(TEMPLATE $content);
731     close(TEMPLATE);
732   } else {
733     $error = $!;
734   }
735
736   $main::lxdebug->leave_sub();
737
738   return $error;
739 }
740
741 sub save_preferences {
742   $main::lxdebug->enter_sub();
743
744   my ($self, $form) = @_;
745
746   my $employee = SL::DB::Manager::Employee->find_by(login => $::myconfig{login});
747   $employee->update_attributes(name => $form->{name});
748
749   my $user = SL::DB::Manager::AuthUser->find_by(login => $::myconfig{login});
750   $user->update_attributes(
751     config_values => {
752       %{ $user->config_values },
753       map { ($_ => $form->{$_}) } SL::DB::AuthUser::CONFIG_VARS(),
754     });
755
756   $main::lxdebug->leave_sub();
757
758   return 1;
759 }
760
761 sub get_defaults {
762   $main::lxdebug->enter_sub();
763
764   my $self     = shift;
765   my %params   = @_;
766
767   my $myconfig = \%main::myconfig;
768   my $form     = $main::form;
769
770   my $dbh      = $params{dbh} || $form->get_standard_dbh($myconfig);
771
772   my $defaults = selectfirst_hashref_query($form, $dbh, qq|SELECT * FROM defaults|) || {};
773
774   $defaults->{weightunit} ||= 'kg';
775
776   $main::lxdebug->leave_sub();
777
778   return $defaults;
779 }
780
781 sub closedto {
782   $main::lxdebug->enter_sub();
783
784   my ($self, $myconfig, $form) = @_;
785
786   my $dbh = $form->dbconnect($myconfig);
787
788   my $query = qq|SELECT closedto, max_future_booking_interval, revtrans FROM defaults|;
789   my $sth   = $dbh->prepare($query);
790   $sth->execute || $form->dberror($query);
791
792   ($form->{closedto}, $form->{max_future_booking_interval}, $form->{revtrans}) = $sth->fetchrow_array;
793
794   $sth->finish;
795
796   $dbh->disconnect;
797
798   $main::lxdebug->leave_sub();
799 }
800
801 sub closebooks {
802   $main::lxdebug->enter_sub();
803
804   my ($self, $myconfig, $form) = @_;
805
806   my $dbh = $form->dbconnect($myconfig);
807
808   my ($query, @values);
809
810   # is currently NEVER trueish (no more hidden revtrans in $form)
811   # if ($form->{revtrans}) {
812   #   $query = qq|UPDATE defaults SET closedto = NULL, revtrans = '1'|;
813   # -> therefore you can only set this to false (which is already the default)
814   # and this flag is currently only checked in gl.pl. TOOD Can probably be removed
815
816     $query = qq|UPDATE defaults SET closedto = ?, max_future_booking_interval = ?, revtrans = '0'|;
817     @values = (conv_date($form->{closedto}), conv_i($form->{max_future_booking_interval}));
818
819   # set close in defaults
820   do_query($form, $dbh, $query, @values);
821
822   $dbh->disconnect;
823
824   $main::lxdebug->leave_sub();
825 }
826
827 sub get_base_unit {
828   my ($self, $units, $unit_name, $factor) = @_;
829
830   $factor = 1 unless ($factor);
831
832   my $unit = $units->{$unit_name};
833
834   if (!defined($unit) || !$unit->{"base_unit"} ||
835       ($unit_name eq $unit->{"base_unit"})) {
836     return ($unit_name, $factor);
837   }
838
839   return AM->get_base_unit($units, $unit->{"base_unit"}, $factor * $unit->{"factor"});
840 }
841
842 sub retrieve_units {
843   $main::lxdebug->enter_sub();
844
845   my ($self, $myconfig, $form, $prefix) = @_;
846   $prefix ||= '';
847
848   my $dbh = $form->get_standard_dbh;
849
850   my $query = "SELECT *, base_unit AS original_base_unit FROM units";
851
852   my $sth = prepare_execute_query($form, $dbh, $query);
853
854   my $units = {};
855   while (my $ref = $sth->fetchrow_hashref()) {
856     $units->{$ref->{"name"}} = $ref;
857   }
858   $sth->finish();
859
860   my $query_lang = "SELECT id, template_code FROM language ORDER BY description";
861   $sth = $dbh->prepare($query_lang);
862   $sth->execute() || $form->dberror($query_lang);
863   my @languages;
864   while (my $ref = $sth->fetchrow_hashref()) {
865     push(@languages, $ref);
866   }
867   $sth->finish();
868
869   $query_lang = "SELECT ul.localized, ul.localized_plural, l.id, l.template_code " .
870     "FROM units_language ul " .
871     "LEFT JOIN language l ON ul.language_id = l.id " .
872     "WHERE ul.unit = ?";
873   $sth = $dbh->prepare($query_lang);
874
875   foreach my $unit (values(%{$units})) {
876     ($unit->{"${prefix}base_unit"}, $unit->{"${prefix}factor"}) = AM->get_base_unit($units, $unit->{"name"});
877
878     $unit->{"LANGUAGES"} = {};
879     foreach my $lang (@languages) {
880       $unit->{"LANGUAGES"}->{$lang->{"template_code"}} = { "template_code" => $lang->{"template_code"} };
881     }
882
883     $sth->execute($unit->{"name"}) || $form->dberror($query_lang . " (" . $unit->{"name"} . ")");
884     while (my $ref = $sth->fetchrow_hashref()) {
885       map({ $unit->{"LANGUAGES"}->{$ref->{"template_code"}}->{$_} = $ref->{$_} } keys(%{$ref}));
886     }
887   }
888   $sth->finish;
889
890   $main::lxdebug->leave_sub();
891
892   return $units;
893 }
894
895 sub retrieve_all_units {
896   $main::lxdebug->enter_sub();
897
898   my $self = shift;
899
900   if (!$::request->{cache}{all_units}) {
901     $::request->{cache}{all_units} = $self->retrieve_units(\%main::myconfig, $main::form);
902   }
903
904   $main::lxdebug->leave_sub();
905
906   return $::request->{cache}{all_units};
907 }
908
909
910 sub translate_units {
911   $main::lxdebug->enter_sub();
912
913   my ($self, $form, $template_code, $unit, $amount) = @_;
914
915   my $units = $self->retrieve_units(\%main::myconfig, $form);
916
917   my $h = $units->{$unit}->{"LANGUAGES"}->{$template_code};
918   my $new_unit = $unit;
919   if ($h) {
920     if (($amount != 1) && $h->{"localized_plural"}) {
921       $new_unit = $h->{"localized_plural"};
922     } elsif ($h->{"localized"}) {
923       $new_unit = $h->{"localized"};
924     }
925   }
926
927   $main::lxdebug->leave_sub();
928
929   return $new_unit;
930 }
931
932 sub units_in_use {
933   $main::lxdebug->enter_sub();
934
935   my ($self, $myconfig, $form, $units) = @_;
936
937   my $dbh = $form->dbconnect($myconfig);
938
939   map({ $_->{"in_use"} = 0; } values(%{$units}));
940
941   foreach my $unit (values(%{$units})) {
942     my $base_unit = $unit->{"original_base_unit"};
943     while ($base_unit) {
944       $units->{$base_unit}->{"in_use"} = 1;
945       $units->{$base_unit}->{"DEPENDING_UNITS"} = [] unless ($units->{$base_unit}->{"DEPENDING_UNITS"});
946       push(@{$units->{$base_unit}->{"DEPENDING_UNITS"}}, $unit->{"name"});
947       $base_unit = $units->{$base_unit}->{"original_base_unit"};
948     }
949   }
950
951   foreach my $unit (values(%{$units})) {
952     map({ $_ = $dbh->quote($_); } @{$unit->{"DEPENDING_UNITS"}});
953
954     foreach my $table (qw(parts invoice orderitems)) {
955       my $query = "SELECT COUNT(*) FROM $table WHERE unit ";
956
957       if (0 == scalar(@{$unit->{"DEPENDING_UNITS"}})) {
958         $query .= "= " . $dbh->quote($unit->{"name"});
959       } else {
960         $query .= "IN (" . $dbh->quote($unit->{"name"}) . "," .
961           join(",", map({ $dbh->quote($_) } @{$unit->{"DEPENDING_UNITS"}})) . ")";
962       }
963
964       my ($count) = $dbh->selectrow_array($query);
965       $form->dberror($query) if ($dbh->err);
966
967       if ($count) {
968         $unit->{"in_use"} = 1;
969         last;
970       }
971     }
972   }
973
974   $dbh->disconnect();
975
976   $main::lxdebug->leave_sub();
977 }
978
979 sub convertible_units {
980   $main::lxdebug->enter_sub();
981
982   my $self        = shift;
983   my $units       = shift;
984   my $filter_unit = shift;
985   my $not_smaller = shift;
986
987   my $conv_units = [];
988
989   $filter_unit = $units->{$filter_unit};
990
991   foreach my $name (sort { lc $a cmp lc $b } keys %{ $units }) {
992     my $unit = $units->{$name};
993
994     if (($unit->{base_unit} eq $filter_unit->{base_unit}) &&
995         (!$not_smaller || ($unit->{factor} >= $filter_unit->{factor}))) {
996       push @{$conv_units}, $unit;
997     }
998   }
999
1000   my @sorted = sort { $b->{factor} <=> $a->{factor} } @{ $conv_units };
1001
1002   $main::lxdebug->leave_sub();
1003
1004   return \@sorted;
1005 }
1006
1007 # if $a is translatable to $b, return the factor between them.
1008 # else return 1
1009 sub convert_unit {
1010   $main::lxdebug->enter_sub(2);
1011   my ($this, $a, $b, $all_units) = @_;
1012
1013   if (!$all_units) {
1014     $all_units = $this->retrieve_all_units;
1015   }
1016
1017   $main::lxdebug->leave_sub(2) and return 0 unless $a && $b;
1018   $main::lxdebug->leave_sub(2) and return 0 unless $all_units->{$a} && $all_units->{$b};
1019   $main::lxdebug->leave_sub(2) and return 0 unless $all_units->{$a}{base_unit} eq $all_units->{$b}{base_unit};
1020   $main::lxdebug->leave_sub(2) and return $all_units->{$a}{factor} / $all_units->{$b}{factor};
1021 }
1022
1023 sub unit_select_data {
1024   $main::lxdebug->enter_sub();
1025
1026   my ($self, $units, $selected, $empty_entry, $convertible_into) = @_;
1027
1028   my $select = [];
1029
1030   if ($empty_entry) {
1031     push(@{$select}, { "name" => "", "base_unit" => "", "factor" => "", "selected" => "" });
1032   }
1033
1034   foreach my $unit (sort({ $units->{$a}->{"sortkey"} <=> $units->{$b}->{"sortkey"} } keys(%{$units}))) {
1035     if (!$convertible_into ||
1036         ($units->{$convertible_into} &&
1037          ($units->{$convertible_into}->{base_unit} eq $units->{$unit}->{base_unit}))) {
1038       push @{$select}, { "name"      => $unit,
1039                          "base_unit" => $units->{$unit}->{"base_unit"},
1040                          "factor"    => $units->{$unit}->{"factor"},
1041                          "selected"  => ($unit eq $selected) ? "selected" : "" };
1042     }
1043   }
1044
1045   $main::lxdebug->leave_sub();
1046
1047   return $select;
1048 }
1049
1050 sub unit_select_html {
1051   $main::lxdebug->enter_sub();
1052
1053   my ($self, $units, $name, $selected, $convertible_into) = @_;
1054
1055   my $select = "<select name=${name}>";
1056
1057   foreach my $unit (sort({ $units->{$a}->{"sortkey"} <=> $units->{$b}->{"sortkey"} } keys(%{$units}))) {
1058     if (!$convertible_into ||
1059         ($units->{$convertible_into} &&
1060          ($units->{$convertible_into}->{"base_unit"} eq $units->{$unit}->{"base_unit"}))) {
1061       $select .= "<option" . (($unit eq $selected) ? " selected" : "") . ">${unit}</option>";
1062     }
1063   }
1064   $select .= "</select>";
1065
1066   $main::lxdebug->leave_sub();
1067
1068   return $select;
1069 }
1070
1071 sub sum_with_unit {
1072   $main::lxdebug->enter_sub();
1073
1074   my $self  = shift;
1075
1076   my $units = $self->retrieve_all_units();
1077
1078   my $sum   = 0;
1079   my $base_unit;
1080
1081   while (2 <= scalar(@_)) {
1082     my $qty  = shift(@_);
1083     my $unit = $units->{shift(@_)};
1084
1085     croak "No unit defined with name $unit" if (!defined $unit);
1086
1087     if (!$base_unit) {
1088       $base_unit = $unit->{base_unit};
1089     } elsif ($base_unit ne $unit->{base_unit}) {
1090       croak "Adding values with incompatible base units $base_unit/$unit->{base_unit}";
1091     }
1092
1093     $sum += $qty * $unit->{factor};
1094   }
1095
1096   $main::lxdebug->leave_sub();
1097
1098   return $sum;
1099 }
1100
1101 sub add_unit {
1102   $main::lxdebug->enter_sub();
1103
1104   my ($self, $myconfig, $form, $name, $base_unit, $factor, $languages) = @_;
1105
1106   my $dbh = $form->dbconnect_noauto($myconfig);
1107
1108   my $query = qq|SELECT COALESCE(MAX(sortkey), 0) + 1 FROM units|;
1109   my ($sortkey) = selectrow_query($form, $dbh, $query);
1110
1111   $query = "INSERT INTO units (name, base_unit, factor, sortkey) " .
1112     "VALUES (?, ?, ?, ?)";
1113   do_query($form, $dbh, $query, $name, $base_unit, $factor, $sortkey);
1114
1115   if ($languages) {
1116     $query = "INSERT INTO units_language (unit, language_id, localized, localized_plural) VALUES (?, ?, ?, ?)";
1117     my $sth = $dbh->prepare($query);
1118     foreach my $lang (@{$languages}) {
1119       my @values = ($name, $lang->{"id"}, $lang->{"localized"}, $lang->{"localized_plural"});
1120       $sth->execute(@values) || $form->dberror($query . " (" . join(", ", @values) . ")");
1121     }
1122     $sth->finish();
1123   }
1124
1125   $dbh->commit();
1126   $dbh->disconnect();
1127
1128   $main::lxdebug->leave_sub();
1129 }
1130
1131 sub save_units {
1132   $main::lxdebug->enter_sub();
1133
1134   my ($self, $myconfig, $form, $units, $delete_units) = @_;
1135
1136   my $dbh = $form->dbconnect_noauto($myconfig);
1137
1138   my ($base_unit, $unit, $sth, $query);
1139
1140   $query = "DELETE FROM units_language";
1141   $dbh->do($query) || $form->dberror($query);
1142
1143   if ($delete_units && (0 != scalar(@{$delete_units}))) {
1144     $query = "DELETE FROM units WHERE name IN (";
1145     map({ $query .= "?," } @{$delete_units});
1146     substr($query, -1, 1) = ")";
1147     $dbh->do($query, undef, @{$delete_units}) ||
1148       $form->dberror($query . " (" . join(", ", @{$delete_units}) . ")");
1149   }
1150
1151   $query = "UPDATE units SET name = ?, base_unit = ?, factor = ? WHERE name = ?";
1152   $sth = $dbh->prepare($query);
1153
1154   my $query_lang = "INSERT INTO units_language (unit, language_id, localized, localized_plural) VALUES (?, ?, ?, ?)";
1155   my $sth_lang = $dbh->prepare($query_lang);
1156
1157   foreach $unit (values(%{$units})) {
1158     $unit->{"depth"} = 0;
1159     my $base_unit = $unit;
1160     while ($base_unit->{"base_unit"}) {
1161       $unit->{"depth"}++;
1162       $base_unit = $units->{$base_unit->{"base_unit"}};
1163     }
1164   }
1165
1166   foreach $unit (sort({ $a->{"depth"} <=> $b->{"depth"} } values(%{$units}))) {
1167     if ($unit->{"LANGUAGES"}) {
1168       foreach my $lang (@{$unit->{"LANGUAGES"}}) {
1169         next unless ($lang->{"id"} && $lang->{"localized"});
1170         my @values = ($unit->{"name"}, $lang->{"id"}, $lang->{"localized"}, $lang->{"localized_plural"});
1171         $sth_lang->execute(@values) || $form->dberror($query_lang . " (" . join(", ", @values) . ")");
1172       }
1173     }
1174
1175     next if ($unit->{"unchanged_unit"});
1176
1177     my @values = ($unit->{"name"}, $unit->{"base_unit"}, $unit->{"factor"}, $unit->{"old_name"});
1178     $sth->execute(@values) || $form->dberror($query . " (" . join(", ", @values) . ")");
1179   }
1180
1181   $sth->finish();
1182   $sth_lang->finish();
1183   $dbh->commit();
1184   $dbh->disconnect();
1185
1186   $main::lxdebug->leave_sub();
1187 }
1188
1189 sub taxes {
1190   $main::lxdebug->enter_sub();
1191
1192   my ($self, $myconfig, $form) = @_;
1193
1194   # connect to database
1195   my $dbh = $form->dbconnect($myconfig);
1196
1197   my $query = qq|SELECT
1198                    t.id,
1199                    t.taxkey,
1200                    t.taxdescription,
1201                    round(t.rate * 100, 2) AS rate,
1202                    (SELECT accno FROM chart WHERE id = chart_id) AS taxnumber,
1203                    (SELECT description FROM chart WHERE id = chart_id) AS account_description,
1204                    (SELECT accno FROM chart WHERE id = skonto_sales_chart_id) AS skonto_chart_accno,
1205                    (SELECT description FROM chart WHERE id = skonto_sales_chart_id) AS skonto_chart_description,
1206                    (SELECT accno FROM chart WHERE id = skonto_purchase_chart_id) AS skonto_chart_purchase_accno,
1207                    (SELECT description FROM chart WHERE id = skonto_purchase_chart_id) AS skonto_chart_purchase_description
1208                  FROM tax t
1209                  ORDER BY taxkey, rate|;
1210
1211   my $sth = $dbh->prepare($query);
1212   $sth->execute || $form->dberror($query);
1213
1214   $form->{TAX} = [];
1215   while (my $ref = $sth->fetchrow_hashref("NAME_lc")) {
1216     push @{ $form->{TAX} }, $ref;
1217   }
1218
1219   $sth->finish;
1220   $dbh->disconnect;
1221
1222   $main::lxdebug->leave_sub();
1223 }
1224
1225 sub get_tax_accounts {
1226   $main::lxdebug->enter_sub();
1227
1228   my ($self, $myconfig, $form) = @_;
1229
1230   my $dbh = $form->dbconnect($myconfig);
1231
1232   # get Accounts from chart
1233   my $query = qq{ SELECT
1234                  id,
1235                  accno || ' - ' || description AS taxaccount
1236                FROM chart
1237                WHERE link LIKE '%_tax%'
1238                ORDER BY accno
1239              };
1240
1241   my $sth = $dbh->prepare($query);
1242   $sth->execute || $form->dberror($query);
1243
1244   $form->{ACCOUNTS} = [];
1245   while (my $ref = $sth->fetchrow_hashref("NAME_lc")) {
1246     push @{ $form->{ACCOUNTS} }, $ref;
1247   }
1248
1249   $form->{AR_PAID} = SL::DB::Manager::Chart->get_all(where => [ link => { like => '%AR_paid%' } ], sort_by => 'accno ASC');
1250   $form->{AP_PAID} = SL::DB::Manager::Chart->get_all(where => [ link => { like => '%AP_paid%' } ], sort_by => 'accno ASC');
1251
1252   $form->{skontochart_value_title_sub} = sub {
1253     my $item = shift;
1254     return [
1255       $item->{id},
1256       $item->{accno} .' '. $item->{description},
1257     ];
1258   };
1259
1260   $sth->finish;
1261
1262   $dbh->disconnect;
1263
1264   $main::lxdebug->leave_sub();
1265 }
1266
1267 sub get_tax {
1268   $main::lxdebug->enter_sub();
1269
1270   my ($self, $myconfig, $form) = @_;
1271
1272   # connect to database
1273   my $dbh = $form->dbconnect($myconfig);
1274
1275   my $query = qq|SELECT
1276                    taxkey,
1277                    taxdescription,
1278                    round(rate * 100, 2) AS rate,
1279                    chart_id,
1280                    chart_categories,
1281                    (id IN (SELECT tax_id
1282                            FROM acc_trans)) AS tax_already_used,
1283                    skonto_sales_chart_id,
1284                    skonto_purchase_chart_id
1285                  FROM tax
1286                  WHERE id = ? |;
1287
1288   my $sth = $dbh->prepare($query);
1289   $sth->execute($form->{id}) || $form->dberror($query . " ($form->{id})");
1290
1291   my $ref = $sth->fetchrow_hashref("NAME_lc");
1292
1293   map { $form->{$_} = $ref->{$_} } keys %$ref;
1294
1295   $sth->finish;
1296
1297   # see if it is used by a taxkey
1298   $query = qq|SELECT count(*) FROM taxkeys
1299               WHERE tax_id = ? AND chart_id >0|;
1300
1301   ($form->{orphaned}) = selectrow_query($form, $dbh, $query, $form->{id});
1302
1303   $form->{orphaned} = !$form->{orphaned};
1304   $sth->finish;
1305
1306   if (!$form->{orphaned} ) {
1307     $query = qq|SELECT DISTINCT c.id, c.accno
1308                 FROM taxkeys tk
1309                 JOIN   tax t ON (t.id = tk.tax_id)
1310                 JOIN chart c ON (c.id = tk.chart_id)
1311                 WHERE tk.tax_id = ?|;
1312
1313     $sth = $dbh->prepare($query);
1314     $sth->execute($form->{id}) || $form->dberror($query . " ($form->{id})");
1315
1316     $form->{TAXINUSE} = [];
1317     while (my $ref = $sth->fetchrow_hashref("NAME_lc")) {
1318       push @{ $form->{TAXINUSE} }, $ref;
1319     }
1320
1321     $sth->finish;
1322   }
1323
1324   $dbh->disconnect;
1325
1326   $main::lxdebug->leave_sub();
1327 }
1328
1329 sub save_tax {
1330   $main::lxdebug->enter_sub();
1331
1332   my ($self, $myconfig, $form) = @_;
1333   my $query;
1334
1335   # connect to database
1336   my $dbh = $form->get_standard_dbh($myconfig);
1337
1338   $form->{rate} = $form->{rate} / 100;
1339
1340   my $chart_categories = '';
1341   $chart_categories .= 'A' if $form->{asset};
1342   $chart_categories .= 'L' if $form->{liability};
1343   $chart_categories .= 'Q' if $form->{equity};
1344   $chart_categories .= 'I' if $form->{revenue};
1345   $chart_categories .= 'E' if $form->{expense};
1346   $chart_categories .= 'C' if $form->{costs};
1347
1348   my @values = ($form->{taxkey}, $form->{taxdescription}, $form->{rate}, conv_i($form->{chart_id}), conv_i($form->{chart_id}), conv_i($form->{skonto_sales_chart_id}), conv_i($form->{skonto_purchase_chart_id}), $chart_categories);
1349   if ($form->{id} ne "") {
1350     $query = qq|UPDATE tax SET
1351                   taxkey                   = ?,
1352                   taxdescription           = ?,
1353                   rate                     = ?,
1354                   chart_id                 = ?,
1355                   taxnumber                = (SELECT accno FROM chart WHERE id = ? ),
1356                   skonto_sales_chart_id    = ?,
1357                   skonto_purchase_chart_id = ?,
1358                   chart_categories         = ?
1359                 WHERE id = ?|;
1360
1361   } else {
1362     #ok
1363     ($form->{id}) = selectfirst_array_query($form, $dbh, qq|SELECT nextval('id')|);
1364     $query = qq|INSERT INTO tax (
1365                   taxkey,
1366                   taxdescription,
1367                   rate,
1368                   chart_id,
1369                   taxnumber,
1370                   skonto_sales_chart_id,
1371                   skonto_purchase_chart_id,
1372                   chart_categories,
1373                   id
1374                 )
1375                 VALUES (?, ?, ?, ?, (SELECT accno FROM chart WHERE id = ?), ?, ?,  ?, ?)|;
1376   }
1377   push(@values, $form->{id});
1378   do_query($form, $dbh, $query, @values);
1379
1380   foreach my $language_id (keys %{ $form->{translations} }) {
1381     GenericTranslations->save('dbh'              => $dbh,
1382                               'translation_type' => 'SL::DB::Tax/taxdescription',
1383                               'translation_id'   => $form->{id},
1384                               'language_id'      => $language_id,
1385                               'translation'      => $form->{translations}->{$language_id});
1386   }
1387
1388   $dbh->commit();
1389
1390   $main::lxdebug->leave_sub();
1391 }
1392
1393 sub delete_tax {
1394   $main::lxdebug->enter_sub();
1395
1396   my ($self, $myconfig, $form) = @_;
1397   my $query;
1398
1399   # connect to database
1400   my $dbh = $form->get_standard_dbh($myconfig);
1401
1402   $query = qq|DELETE FROM tax
1403               WHERE id = ?|;
1404   do_query($form, $dbh, $query, $form->{id});
1405
1406   $dbh->commit();
1407
1408   $main::lxdebug->leave_sub();
1409 }
1410
1411 sub save_price_factor {
1412   $main::lxdebug->enter_sub();
1413
1414   my ($self, $myconfig, $form) = @_;
1415
1416   # connect to database
1417   my $dbh = $form->get_standard_dbh($myconfig);
1418
1419   my $query;
1420   my @values = ($form->{description}, conv_i($form->{factor}));
1421
1422   if ($form->{id}) {
1423     $query = qq|UPDATE price_factors SET description = ?, factor = ? WHERE id = ?|;
1424     push @values, conv_i($form->{id});
1425
1426   } else {
1427     $query = qq|INSERT INTO price_factors (description, factor, sortkey) VALUES (?, ?, (SELECT COALESCE(MAX(sortkey), 0) + 1 FROM price_factors))|;
1428   }
1429
1430   do_query($form, $dbh, $query, @values);
1431
1432   $dbh->commit();
1433
1434   $main::lxdebug->leave_sub();
1435 }
1436
1437 sub get_all_price_factors {
1438   $main::lxdebug->enter_sub();
1439
1440   my ($self, $myconfig, $form) = @_;
1441
1442   # connect to database
1443   my $dbh = $form->get_standard_dbh($myconfig);
1444
1445   $form->{PRICE_FACTORS} = selectall_hashref_query($form, $dbh, qq|SELECT * FROM price_factors ORDER BY sortkey|);
1446
1447   $main::lxdebug->leave_sub();
1448 }
1449
1450 sub get_price_factor {
1451   $main::lxdebug->enter_sub();
1452
1453   my ($self, $myconfig, $form) = @_;
1454
1455   # connect to database
1456   my $dbh = $form->get_standard_dbh($myconfig);
1457
1458   my $query = qq|SELECT description, factor,
1459                    ((SELECT COUNT(*) FROM parts      WHERE price_factor_id = ?) +
1460                     (SELECT COUNT(*) FROM invoice    WHERE price_factor_id = ?) +
1461                     (SELECT COUNT(*) FROM orderitems WHERE price_factor_id = ?)) = 0 AS orphaned
1462                  FROM price_factors WHERE id = ?|;
1463
1464   ($form->{description}, $form->{factor}, $form->{orphaned}) = selectrow_query($form, $dbh, $query, (conv_i($form->{id})) x 4);
1465
1466   $main::lxdebug->leave_sub();
1467 }
1468
1469 sub delete_price_factor {
1470   $main::lxdebug->enter_sub();
1471
1472   my ($self, $myconfig, $form) = @_;
1473
1474   # connect to database
1475   my $dbh = $form->get_standard_dbh($myconfig);
1476
1477   do_query($form, $dbh, qq|DELETE FROM price_factors WHERE id = ?|, conv_i($form->{id}));
1478   $dbh->commit();
1479
1480   $main::lxdebug->leave_sub();
1481 }
1482
1483 sub save_warehouse {
1484   $main::lxdebug->enter_sub();
1485
1486   my ($self, $myconfig, $form) = @_;
1487
1488   # connect to database
1489   my $dbh = $form->get_standard_dbh($myconfig);
1490
1491   my ($query, @values, $sth);
1492
1493   if (!$form->{id}) {
1494     $query        = qq|SELECT nextval('id')|;
1495     ($form->{id}) = selectrow_query($form, $dbh, $query);
1496
1497     $query        = qq|INSERT INTO warehouse (id, sortkey) VALUES (?, (SELECT COALESCE(MAX(sortkey), 0) + 1 FROM warehouse))|;
1498     do_query($form, $dbh, $query, $form->{id});
1499   }
1500
1501   do_query($form, $dbh, qq|UPDATE warehouse SET description = ?, invalid = ? WHERE id = ?|,
1502            $form->{description}, $form->{invalid} ? 't' : 'f', conv_i($form->{id}));
1503
1504   if (0 < $form->{number_of_new_bins}) {
1505     my ($num_existing_bins) = selectfirst_array_query($form, $dbh, qq|SELECT COUNT(*) FROM bin WHERE warehouse_id = ?|, $form->{id});
1506     $query = qq|INSERT INTO bin (warehouse_id, description) VALUES (?, ?)|;
1507     $sth   = prepare_query($form, $dbh, $query);
1508
1509     foreach my $i (1..$form->{number_of_new_bins}) {
1510       do_statement($form, $sth, $query, conv_i($form->{id}), "$form->{prefix}" . ($i + $num_existing_bins));
1511     }
1512
1513     $sth->finish();
1514   }
1515
1516   $dbh->commit();
1517
1518   $main::lxdebug->leave_sub();
1519 }
1520
1521 sub save_bins {
1522   $main::lxdebug->enter_sub();
1523
1524   my ($self, $myconfig, $form) = @_;
1525
1526   # connect to database
1527   my $dbh = $form->get_standard_dbh($myconfig);
1528
1529   my ($query, @values, $commit_necessary, $sth);
1530
1531   @values = map { $form->{"id_${_}"} } grep { $form->{"delete_${_}"} } (1..$form->{rowcount});
1532
1533   if (@values) {
1534     $query = qq|DELETE FROM bin WHERE id IN (| . join(', ', ('?') x scalar(@values)) . qq|)|;
1535     do_query($form, $dbh, $query, @values);
1536
1537     $commit_necessary = 1;
1538   }
1539
1540   $query = qq|UPDATE bin SET description = ? WHERE id = ?|;
1541   $sth   = prepare_query($form, $dbh, $query);
1542
1543   foreach my $row (1..$form->{rowcount}) {
1544     next if ($form->{"delete_${row}"});
1545
1546     do_statement($form, $sth, $query, $form->{"description_${row}"}, conv_i($form->{"id_${row}"}));
1547
1548     $commit_necessary = 1;
1549   }
1550
1551   $sth->finish();
1552
1553   $dbh->commit() if ($commit_necessary);
1554
1555   $main::lxdebug->leave_sub();
1556 }
1557
1558 sub delete_warehouse {
1559   $main::lxdebug->enter_sub();
1560
1561   my ($self, $myconfig, $form) = @_;
1562
1563   # connect to database
1564   my $dbh = $form->get_standard_dbh($myconfig);
1565
1566   my $id      = conv_i($form->{id});
1567   my $query   = qq|SELECT i.bin_id FROM inventory i WHERE i.bin_id IN (SELECT b.id FROM bin b WHERE b.warehouse_id = ?) LIMIT 1|;
1568   my ($count) = selectrow_query($form, $dbh, $query, $id);
1569
1570   if ($count) {
1571     $main::lxdebug->leave_sub();
1572     return 0;
1573   }
1574
1575   do_query($form, $dbh, qq|DELETE FROM bin       WHERE warehouse_id = ?|, conv_i($form->{id}));
1576   do_query($form, $dbh, qq|DELETE FROM warehouse WHERE id           = ?|, conv_i($form->{id}));
1577
1578   $dbh->commit();
1579
1580   $main::lxdebug->leave_sub();
1581
1582   return 1;
1583 }
1584
1585 sub get_all_warehouses {
1586   $main::lxdebug->enter_sub();
1587
1588   my ($self, $myconfig, $form) = @_;
1589
1590   # connect to database
1591   my $dbh = $form->get_standard_dbh($myconfig);
1592
1593   my $query = qq|SELECT w.id, w.description, w.invalid,
1594                    (SELECT COUNT(b.description) FROM bin b WHERE b.warehouse_id = w.id) AS number_of_bins
1595                  FROM warehouse w
1596                  ORDER BY w.sortkey|;
1597
1598   $form->{WAREHOUSES} = selectall_hashref_query($form, $dbh, $query);
1599
1600   $main::lxdebug->leave_sub();
1601 }
1602
1603 sub get_warehouse {
1604   $main::lxdebug->enter_sub();
1605
1606   my ($self, $myconfig, $form) = @_;
1607
1608   # connect to database
1609   my $dbh = $form->get_standard_dbh($myconfig);
1610
1611   my $id    = conv_i($form->{id});
1612   my $query = qq|SELECT w.description, w.invalid
1613                  FROM warehouse w
1614                  WHERE w.id = ?|;
1615
1616   my $ref   = selectfirst_hashref_query($form, $dbh, $query, $id);
1617
1618   map { $form->{$_} = $ref->{$_} } keys %{ $ref };
1619
1620   $query = <<SQL;
1621     SELECT b.*,
1622       (   EXISTS(SELECT i.bin_id FROM inventory i WHERE i.bin_id = b.id LIMIT 1)
1623        OR EXISTS(SELECT p.bin_id FROM parts     p WHERE p.bin_id = b.id LIMIT 1))
1624       AS in_use
1625     FROM bin b
1626     WHERE b.warehouse_id = ?
1627 SQL
1628
1629   $form->{BINS} = selectall_hashref_query($form, $dbh, $query, conv_i($form->{id}));
1630
1631   $main::lxdebug->leave_sub();
1632 }
1633
1634 1;