Erste Version GetModels rewrite
[kivitendo-erp.git] / SL / Controller / DeliveryPlan.pm
1 package SL::Controller::DeliveryPlan;
2
3 use strict;
4 use parent qw(SL::Controller::Base);
5
6 use Clone qw(clone);
7 use SL::DB::OrderItem;
8 use SL::Controller::Helper::GetModels;
9 use SL::Controller::Helper::ReportGenerator;
10 use SL::Locale::String;
11
12 use Rose::Object::MakeMethods::Generic (
13   scalar => [ qw(db_args flat_filter) ],
14   'scalar --get_set_init' => [ qw(models) ],
15 );
16
17 __PACKAGE__->run_before(sub { $::auth->assert('sales_order_edit'); });
18
19 #__PACKAGE__->make_filtered(
20 #  MODEL             => 'OrderItem',
21 #  LAUNDER_TO        => 'filter'
22 #);
23 #__PACKAGE__->make_paginated(
24 #  MODEL         => 'OrderItem',
25 #  ONLY          => [ qw(list) ],
26 #);
27 #
28 #__PACKAGE__->make_sorted(
29 #  MODEL             => 'OrderItem',
30 #  ONLY              => [ qw(list) ],
31 #
32 #  DEFAULT_BY        => 'reqdate',
33 #  DEFAULT_DIR       => 1,
34 #
35 #  reqdate           => t8('Reqdate'),
36 #  description       => t8('Description'),
37 #  partnumber        => t8('Part Number'),
38 #  qty               => t8('Qty'),
39 #  shipped_qty       => t8('shipped'),
40 #  not_shipped_qty   => t8('not shipped'),
41 #  ordnumber         => t8('Order'),
42 #  customer          => t8('Customer'),
43 #);
44
45 my %sort_columns = (
46   reqdate           => t8('Reqdate'),
47   description       => t8('Description'),
48   partnumber        => t8('Part Number'),
49   qty               => t8('Qty'),
50   shipped_qty       => t8('shipped'),
51   not_shipped_qty   => t8('not shipped'),
52   ordnumber         => t8('Order'),
53   customer          => t8('Customer'),
54 );
55
56 my $delivery_plan_query = [
57   'order.customer_id' => { gt => 0 },
58   'order.closed' => 0,
59   or => [ 'order.quotation' => 0, 'order.quotation' => undef ],
60
61   # filter by shipped_qty < qty, read from innermost to outermost
62   'id' => [ \"
63     -- 3. resolve the desired information about those
64     SELECT oi.id FROM (
65       -- 2. slice only part, orderitem and both quantities from it
66       SELECT parts_id, trans_id, qty, SUM(doi_qty) AS doi_qty FROM (
67         -- 1. join orderitems and deliverorder items via record_links.
68         --    also add customer data to filter for sales_orders
69         SELECT oi.parts_id, oi.trans_id, oi.id, oi.qty, doi.qty AS doi_qty
70         FROM orderitems oi, oe, record_links rl, delivery_order_items doi
71         WHERE
72           oe.id = oi.trans_id AND
73           oe.customer_id IS NOT NULL AND
74           (oe.quotation = 'f' OR oe.quotation IS NULL) AND
75           NOT oe.closed AND
76           rl.from_id = oe.id AND
77           rl.from_id = oi.trans_id AND
78           oe.id = oi.trans_id AND
79           rl.from_table = 'oe' AND
80           rl.to_table = 'delivery_orders' AND
81           rl.to_id = doi.delivery_order_id AND
82           oi.parts_id = doi.parts_id
83       ) tuples GROUP BY parts_id, trans_id, qty
84     ) partials
85     LEFT JOIN orderitems oi ON partials.parts_id = oi.parts_id AND partials.trans_id = oi.trans_id
86     WHERE oi.qty > doi_qty
87
88     UNION ALL
89
90     -- 4. since the join over record_links fails for sales_orders wihtout any delivery order
91     --    retrieve those without record_links at all
92     SELECT oi.id FROM orderitems oi, oe
93     WHERE
94       oe.id = oi.trans_id AND
95       oe.customer_id IS NOT NULL AND
96       (oe.quotation = 'f' OR oe.quotation IS NULL) AND
97       NOT oe.closed AND
98       oi.trans_id NOT IN (
99         SELECT from_id
100         FROM record_links rl
101         WHERE
102           rl.from_table ='oe' AND
103           rl.to_table = 'delivery_orders'
104       )
105
106     UNION ALL
107
108     -- 5. In case someone deleted a line of the delivery_order there will be a record_link (4 fails)
109     --    but there won't be a delivery_order_items to find (3 fails too). Search for orphaned orderitems this way
110     SELECT oi.id FROM orderitems AS oi, oe, record_links AS rl
111     WHERE
112       rl.from_table = 'oe' AND
113       rl.to_table = 'delivery_orders' AND
114
115       oi.trans_id = rl.from_id AND
116       oi.parts_id NOT IN (
117         SELECT doi.parts_id FROM delivery_order_items AS doi WHERE doi.delivery_order_id = rl.to_id
118       ) AND
119
120       oe.id = oi.trans_id AND
121
122       oe.customer_id IS NOT NULL AND
123       (oe.quotation = 'f' OR oe.quotation IS NULL) AND
124       NOT oe.closed
125   " ],
126 ];
127
128 sub action_list {
129   my ($self) = @_;
130
131   $self->make_filter_summary;
132
133   my $orderitems = $self->models->get;
134
135   $self->prepare_report;
136   $self->report_generator_list_objects(report => $self->{report}, objects => $orderitems);
137 }
138
139 # private functions
140 #
141 sub prepare_report {
142   my ($self)      = @_;
143
144   my $report      = SL::ReportGenerator->new(\%::myconfig, $::form);
145   $self->{report} = $report;
146
147   my @columns     = qw(reqdate customer ordnumber partnumber description qty shipped_qty not_shipped_qty);
148   my @sortable    = qw(reqdate customer ordnumber partnumber description);
149
150   my %column_defs = (
151     reqdate           => {      sub => sub { $_[0]->reqdate_as_date || $_[0]->order->reqdate_as_date                         } },
152     description       => {      sub => sub { $_[0]->description                                                              },
153                            obj_link => sub { $self->link_to($_[0]->part)                                                     } },
154     partnumber        => {      sub => sub { $_[0]->part->partnumber                                                         },
155                            obj_link => sub { $self->link_to($_[0]->part)                                                     } },
156     qty               => {      sub => sub { $_[0]->qty_as_number . ' ' . $_[0]->unit                                        } },
157     shipped_qty       => {      sub => sub { $::form->format_amount(\%::myconfig, $_[0]->shipped_qty, 2) . ' ' . $_[0]->unit } },
158     not_shipped_qty   => {      sub => sub { $::form->format_amount(\%::myconfig, $_[0]->qty - $_[0]->shipped_qty, 2) . ' ' . $_[0]->unit } },
159     ordnumber         => {      sub => sub { $_[0]->order->ordnumber                                                         },
160                            obj_link => sub { $self->link_to($_[0]->order)                                                    } },
161     customer          => {      sub => sub { return ''; $_[0]->order->customer->name                                                    },
162                            obj_link => sub { $self->link_to($_[0]->order->customer)                                          } },
163   );
164
165   $column_defs{$_}->{text} = $sort_columns{$_} for keys %column_defs;
166
167   $report->set_options(
168     std_column_visibility => 1,
169     controller_class      => 'DeliveryPlan',
170     output_format         => 'HTML',
171     top_info_text         => $::locale->text('Delivery Plan for currently outstanding sales orders'),
172     raw_top_info_text     => $self->render('delivery_plan/report_top',    { output => 0 }),
173     raw_bottom_info_text  => $self->render('delivery_plan/report_bottom', { output => 0 }, models => $self->models),
174     title                 => $::locale->text('Delivery Plan'),
175     allow_pdf_export      => 1,
176     allow_csv_export      => 1,
177   );
178   $report->set_columns(%column_defs);
179   $report->set_column_order(@columns);
180   $report->set_export_options(qw(list filter));
181   $report->set_options_from_form;
182   $self->models->sorted->set_report_generator_sort_options(report => $report, sortable_columns => \@sortable);
183
184   $self->models->paginated->disable_pagination if $report->{options}{output_format} =~ /^(pdf|csv)$/i;
185 }
186
187 sub make_filter_summary {
188   my ($self) = @_;
189
190   my $filter = $::form->{filter} || {};
191   my @filter_strings;
192
193   my @filters = (
194     [ $filter->{order}{"ordnumber:substr::ilike"},                $::locale->text('Number')                                             ],
195     [ $filter->{part}{"partnumber:substr::ilike"},                $::locale->text('Part Number')                                        ],
196     [ $filter->{"description:substr::ilike"},                     $::locale->text('Part Description')                                   ],
197     [ $filter->{"reqdate:date::ge"},                              $::locale->text('Delivery Date') . " " . $::locale->text('From Date') ],
198     [ $filter->{"reqdate:date::le"},                              $::locale->text('Delivery Date') . " " . $::locale->text('To Date')   ],
199     [ $filter->{"qty:number"},                                    $::locale->text('Quantity')                                           ],
200     [ $filter->{order}{customer}{"name:substr::ilike"},           $::locale->text('Customer')                                           ],
201     [ $filter->{order}{customer}{"customernumber:substr::ilike"}, $::locale->text('Customer Number')                                    ],
202   );
203
204   my %flags = (
205     part     => $::locale->text('Parts'),
206     service  => $::locale->text('Services'),
207     assembly => $::locale->text('Assemblies'),
208   );
209   my @flags = map { $flags{$_} } @{ $filter->{part}{type} || [] };
210
211   for (@flags) {
212     push @filter_strings, $_ if $_;
213   }
214   for (@filters) {
215     push @filter_strings, "$_->[1]: $_->[0]" if $_->[0];
216   }
217
218   $self->{filter_summary} = join ', ', @filter_strings;
219 }
220
221 sub init_models {
222   my ($self) = @_;
223
224   SL::Controller::Helper::GetModels->new(
225     controller => $self,
226     model  => 'OrderItem', # defaults to controller
227     filtered => {
228       launder_to => 'filter',
229     },
230     sorted => {
231       _default => {
232         by        => 'reqdate',
233         dir       => 1,
234       },
235       %sort_columns,
236     },
237     query => $delivery_plan_query,
238     with_objects => [ 'order', 'order.customer', 'part' ],
239   );
240 }
241
242 sub link_to {
243   my ($self, $object, %params) = @_;
244
245   return unless $object;
246   my $action = $params{action} || 'edit';
247
248   if ($object->isa('SL::DB::Order')) {
249     my $type   = $object->type;
250     my $vc     = $object->is_sales ? 'customer' : 'vendor';
251     my $id     = $object->id;
252
253     return "oe.pl?action=$action&type=$type&vc=$vc&id=$id";
254   }
255   if ($object->isa('SL::DB::Part')) {
256     my $id     = $object->id;
257     return "ic.pl?action=$action&id=$id";
258   }
259   if ($object->isa('SL::DB::Customer')) {
260     my $id     = $object->id;
261     return "controller.pl?action=CustomerVendor/$action&id=$id&db=customer";
262   }
263 }
264
265 1;