epic-ts
[kivitendo-erp.git] / SL / ARAP.pm
1 package ARAP;
2
3 use SL::AM;
4 use SL::Common;
5 use SL::DBUtils;
6 use SL::MoreCommon;
7 use SL::DB;
8 use Data::Dumper;
9
10 use strict;
11
12 sub close_orders_if_billed {
13   $main::lxdebug->enter_sub();
14
15   my $self   = shift;
16   my %params = @_;
17
18   Common::check_params(\%params, qw(arap_id table));
19
20   my $myconfig  = \%main::myconfig;
21   my $form      = $main::form;
22
23   my $dbh       = $params{dbh} || SL::DB->client->dbh;
24
25   # First, find all order IDs from which this invoice has been
26   # created. Either directly by a conversion from an order to this invoice
27   # or indirectly from an order to one or more delivery orders and
28   # from those to this invoice.
29
30   # Direct conversion "order -> invoice":
31   my @links     = RecordLinks->get_links('dbh'        => $dbh,
32                                          'from_table' => 'oe',
33                                          'to_table'   => $params{table},
34                                          'to_id'      => $params{arap_id});
35
36   my %oe_id_map = map { $_->{from_id} => 1 } @links;
37
38   # Indirect conversion "order -> delivery orders -> invoice":
39   my @do_links  = RecordLinks->get_links('dbh'        => $dbh,
40                                          'from_table' => 'delivery_orders',
41                                          'to_table'   => $params{table},
42                                          'to_id'      => $params{arap_id});
43
44   foreach my $do_link (@do_links) {
45     @links      = RecordLinks->get_links('dbh'        => $dbh,
46                                          'from_table' => 'oe',
47                                          'to_table'   => 'delivery_orders',
48                                          'to_id'      => $do_link->{from_id});
49
50     map { $oe_id_map{$_->{from_id}} = 1 } @links;
51   }
52
53   my @oe_ids = keys %oe_id_map;
54
55   # No orders found? Nothing to do then, so let's return.
56   return $main::lxdebug->leave_sub unless @oe_ids;
57
58   my $all_units = AM->retrieve_all_units;
59
60   my $qtyfactor = $params{table} eq 'ap' ? '* -1' : '';
61   my $q_billed  = qq|SELECT i.parts_id, i.qty ${qtyfactor} AS qty, i.unit, p.unit AS partunit
62                      FROM invoice i
63                      LEFT JOIN parts p ON (i.parts_id = p.id)
64                      WHERE i.trans_id = ? AND i.assemblyitem is false|;
65   my $h_billed  = prepare_query($form, $dbh, $q_billed);
66
67   my $q_ordered = qq|SELECT oi.parts_id, oi.qty, oi.unit, p.unit AS partunit
68                       FROM orderitems oi
69                       LEFT JOIN parts p ON (oi.parts_id = p.id)
70                       WHERE oi.trans_id = ?
71                       AND not oi.optional|;
72   my $h_ordered = prepare_query($form, $dbh, $q_ordered);
73
74   my @close_oe_ids;
75
76   # Interate over each order and look up all invoices created for
77   # said order. Again consider both direct conversions and indirect
78   # conversions via delivery orders.
79   foreach my $oe_id (@oe_ids) {
80
81     # Dont close orders with periodic invoice
82     next if SL::DB::Manager::PeriodicInvoicesConfig->find_by(oe_id => $oe_id);
83
84     # Direct conversions "order -> invoice":
85     @links          = RecordLinks->get_links('dbh'        => $dbh,
86                                              'from_table' => 'oe',
87                                              'from_id'    => $oe_id,
88                                              'to_table'   => $params{table},);
89
90     my %arap_id_map = map { $_->{to_id} => 1 } @links;
91
92     # Indirect conversions "order -> delivery orders -> invoice":
93     @do_links       = RecordLinks->get_links('dbh'        => $dbh,
94                                              'from_table' => 'oe',
95                                              'from_id'    => $oe_id,
96                                              'to_table'   => 'delivery_orders',);
97     foreach my $do_link (@do_links) {
98       @links        = RecordLinks->get_links('dbh'        => $dbh,
99                                              'from_table' => 'delivery_orders',
100                                              'from_id'    => $do_link->{to_id},
101                                              'to_table'   => $params{table},);
102
103       map { $arap_id_map{$_->{to_id}} = 1 } @links;
104     }
105
106     my @arap_ids = keys %arap_id_map;
107
108     next if (!scalar @arap_ids);
109
110     # Retrieve all positions for this order. Calculate the ordered quantity for each position.
111     my %ordered = ();
112
113     do_statement($form, $h_ordered, $q_ordered, $oe_id);
114
115     while (my $ref = $h_ordered->fetchrow_hashref()) {
116       $ref->{baseqty} = $ref->{qty} * AM->convert_unit($ref->{unit}, $ref->{partunit}, $all_units);
117
118       if ($ordered{$ref->{parts_id}}) {
119         $ordered{$ref->{parts_id}}->{baseqty} += $ref->{baseqty};
120       } else {
121         $ordered{$ref->{parts_id}}             = $ref;
122       }
123     }
124
125     # Retrieve all positions for all invoices that have been created from this order.
126     my %billed  = ();
127
128     foreach my $arap_id (@arap_ids) {
129       do_statement($form, $h_billed, $q_billed, $arap_id);
130
131       while (my $ref = $h_billed->fetchrow_hashref()) {
132         $ref->{baseqty} = $ref->{qty} * AM->convert_unit($ref->{unit}, $ref->{partunit}, $all_units);
133
134         if ($billed{$ref->{parts_id}}) {
135           $billed{$ref->{parts_id}}->{baseqty} += $ref->{baseqty};
136         } else {
137           $billed{$ref->{parts_id}}             = $ref;
138         }
139       }
140     }
141
142     # Check all ordered positions. If all positions have been billed completely then this order can be closed.
143     my $all_billed = 1;
144     foreach my $part (values %ordered) {
145       if (!$billed{$part->{parts_id}} || ($billed{$part->{parts_id}}->{baseqty} < $part->{baseqty})) {
146         $all_billed = 0;
147         last;
148       }
149     }
150
151     push @close_oe_ids, $oe_id if ($all_billed);
152   }
153
154   $h_billed->finish;
155   $h_ordered->finish;
156
157   # Close orders that have been billed fully.
158   if (scalar @close_oe_ids) {
159     SL::DB->client->with_transaction(sub {
160       my $query = qq|UPDATE oe SET closed = TRUE WHERE id IN (| . join(', ', ('?') x scalar @close_oe_ids) . qq|)|;
161       do_query($form, $dbh, $query, @close_oe_ids);
162       1;
163     }) or do { die SL::DB->client->error };
164   }
165
166   $main::lxdebug->leave_sub();
167 }
168
169 1;