Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision b213d89c

Von Sven Schöling vor mehr als 12 Jahren hinzugefügt

  • ID b213d89c9bb929cbebda13388284bd442f6f8aa2
  • Vorgänger eb518737
  • Nachfolger 45f68536

DeliveryPlan

Erste Version squashed (überspringt 10 Commits, in denen das Query unbrauchbar langsam ist)

Unterschiede anzeigen:

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::ParseFilter;
9
use SL::Controller::Helper::ReportGenerator;
10

  
11
__PACKAGE__->run_before(sub { $::auth->assert('sales_order_edit'); });
12

  
13
sub action_list {
14
  my ($self) = @_;
15
  my %list_params = (
16
    sort_by  => $::form->{sort_by} || 'reqdate',
17
    sort_dir => $::form->{sort_dir},
18
    filter   => $::form->{filter},
19
    page     => $::form->{page},
20
  );
21

  
22
  my $db_args = $self->setup_for_list(%list_params);
23
  $self->{pages} = SL::DB::Manager::OrderItem->paginate(%list_params, args => $db_args);
24
  $self->{flat_filter} = { map { $_->{key} => $_->{value} } $::form->flatten_variables('filter') };
25

  
26
  my $top    = $::form->parse_html_template('delivery_plan/report_top', { FORM => $::form, SELF => $self });
27
  my $bottom = $::form->parse_html_template('delivery_plan/report_bottom', { SELF => $self });
28

  
29
  $self->prepare_report(
30
    report_generator_options => {
31
      raw_top_info_text    => $top,
32
      raw_bottom_info_text => $bottom,
33
      controller_class     => 'DeliveryPlan',
34
    },
35
    report_generator_export_options => [
36
      'list', qw(filter sort_by sort_dir),
37
    ],
38
    db_args => $db_args,
39
  );
40

  
41
  $self->{orderitems} = SL::DB::Manager::OrderItem->get_all(%$db_args);
42

  
43
  $self->list_objects;
44
}
45

  
46
# private functions
47

  
48
sub setup_for_list {
49
  my ($self, %params) = @_;
50
  $self->{filter} = {};
51
  my %args = (
52
    parse_filter(
53
      $self->_pre_parse_filter($::form->{filter}, $self->{filter}),
54
      with_objects => [ 'order', 'order.customer', 'part' ],
55
      launder_to => $self->{filter},
56
    ),
57
    sort_by => $self->set_sort_params(%params),
58
    page    => $params{page},
59
  );
60

  
61
  $args{query} = [ @{ $args{query} || [] },
62
    (
63
      'order.customer_id' => { gt => 0 },
64
      'order.closed' => 0,
65
      or => [ 'order.quotation' => 0, 'order.quotation' => undef ],
66

  
67
      # filter by shipped_qty < qty, read from innermost to outermost
68
      'id' => [ \"
69
        -- 3. resolve the desired information about those
70
        SELECT oi.id FROM (
71
          -- 2. slice only part, orderitem and both quantities from it
72
          SELECT parts_id, trans_id, qty, SUM(doi_qty) AS doi_qty FROM (
73
            -- 1. join orderitems and deliverorder items via record_links.
74
            --    also add customer data to filter for sales_orders
75
            SELECT oi.parts_id, oi.trans_id, oi.id, oi.qty, doi.qty AS doi_qty
76
            FROM orderitems oi, oe, record_links rl, delivery_order_items doi
77
            WHERE
78
              oe.id = oi.trans_id AND
79
              oe.customer_id IS NOT NULL AND
80
              (oe.quotation = 'f' OR oe.quotation IS NULL) AND
81
              NOT oe.closed AND
82
              rl.from_id = oe.id AND
83
              rl.from_id = oi.trans_id AND
84
              oe.id = oi.trans_id AND
85
              rl.from_table = 'oe' AND
86
              rl.to_table = 'delivery_orders' AND
87
              rl.to_id = doi.delivery_order_id AND
88
              oi.parts_id = doi.parts_id
89
          ) tuples GROUP BY parts_id, trans_id, qty
90
        ) partials
91
        LEFT JOIN orderitems oi ON partials.parts_id = oi.parts_id AND partials.trans_id = oi.trans_id
92
        WHERE oi.qty > doi_qty
93

  
94
        UNION ALL
95

  
96
        -- 4. since the join over record_links fails for sales_orders wihtout any delivery order
97
        --    retrieve those without record_links at all
98
        SELECT oi.id FROM orderitems oi, oe
99
        WHERE
100
          oe.id = oi.trans_id AND
101
          oe.customer_id IS NOT NULL AND
102
          (oe.quotation = 'f' OR oe.quotation IS NULL) AND
103
          NOT oe.closed AND
104
          oi.trans_id NOT IN (
105
            SELECT from_id
106
            FROM record_links rl
107
            WHERE
108
              rl.from_table ='oe' AND
109
              rl.to_table = 'delivery_orders'
110
          )
111
      " ],
112
    )
113
  ];
114

  
115
  return \%args;
116
}
117

  
118
sub set_sort_params {
119
  my ($self, %params) = @_;
120
  my $sort_str;
121
  ($self->{sort_by}, $self->{sort_dir}, $sort_str) =
122
    SL::DB::Manager::OrderItem->make_sort_string(%params);
123
  return $sort_str;
124
}
125

  
126
sub prepare_report {
127
  my ($self, %params) = @_;
128

  
129
  my $objects  = $params{objects} || [];
130
  my $report = SL::ReportGenerator->new(\%::myconfig, $::form);
131
  $self->{report} = $report;
132

  
133
  my @columns  = qw(reqdate customer ordnumber partnumber description qty shipped_qty);
134
  my @visible  = qw(reqdate partnumber description qty shipped_qty ordnumber customer);
135
  my @sortable = qw(reqdate partnumber description                 ordnumber customer);
136

  
137
  my %column_defs = (
138
    reqdate                 => { text => $::locale->text('Reqdate'),
139
                                  sub => sub { $_[0]->reqdate_as_date || $_[0]->order->reqdate_as_date }},
140
    description             => { text => $::locale->text('Description'),
141
                                  sub => sub { $_[0]->description },
142
                             obj_link => sub { $self->link_to($_[0]->part) }},
143
    partnumber              => { text => $::locale->text('Part Number'),
144
                                  sub => sub { $_[0]->part->partnumber },
145
                             obj_link => sub { $self->link_to($_[0]->part) }},
146
    qty                     => { text => $::locale->text('Qty'),
147
                                  sub => sub { $_[0]->qty_as_number . ' ' . $_[0]->unit }},
148
    shipped_qty             => { text => $::locale->text('shipped'),
149
                                  sub => sub { $::form->format_amount(\%::myconfig, $_[0]->shipped_qty, 2) . ' ' . $_[0]->unit }},
150
    ordnumber               => { text => $::locale->text('Order'),
151
                                  sub => sub { $_[0]->order->ordnumber },
152
                             obj_link => sub { $self->link_to($_[0]->order) }},
153
    customer                => { text => $::locale->text('Customer'),
154
                                  sub => sub { $_[0]->order->customer->name },
155
                             obj_link => sub { $self->link_to($_[0]->order->customer) }},
156
  );
157

  
158

  
159
  for my $col (@sortable) {
160
    $column_defs{$col}{link} = $self->url_for(
161
      action   => 'list',
162
      sort_by  => $col,
163
      sort_dir => ($self->{sort_by} eq $col ? 1 - $self->{sort_dir} : $self->{sort_dir}),
164
      page     => $self->{pages}{cur},
165
      %{ $self->{flat_filter} },
166
    );
167
  }
168

  
169
  map { $column_defs{$_}->{visible} = 1 } @visible;
170

  
171
  $report->set_columns(%column_defs);
172
  $report->set_column_order(@columns);
173
  $report->set_options(allow_pdf_export => 1, allow_csv_export => 1);
174
  $report->set_sort_indicator(%params);
175
  $report->set_export_options(@{ $params{report_generator_export_options} || [] });
176
  $report->set_options(
177
    %{ $params{report_generator_options} || {} },
178
    output_format        => 'HTML',
179
    top_info_text        => $::locale->text('Delivery Plan for currently outstanding sales orders'),
180
    title                => $::locale->text('Delivery Plan'),
181
  );
182
  $report->set_options_from_form;
183

  
184
  SL::DB::Manager::OrderItem->disable_paginating(args => $params{db_args}) if $report->{options}{output_format} =~ /^(pdf|csv)$/i;
185

  
186
  $self->{report_data} = {
187
    column_defs => \%column_defs,
188
    columns     => \@columns,
189
    visible     => \@visible,
190
    sortable    => \@sortable,
191
  };
192
}
193

  
194
sub list_objects {
195
  my ($self) = @_;
196
  my $column_defs = $self->{report_data}{column_defs};
197
  for my $obj (@{ $self->{orderitems} || [] }) {
198
    $self->{report}->add_data({
199
      map {
200
        $_ => {
201
          data => $column_defs->{$_}{sub} ? $column_defs->{$_}{sub}->($obj)
202
                : $obj->can($_)           ? $obj->$_
203
                :                           $obj->{$_},
204
          link => $column_defs->{$_}{obj_link} ? $column_defs->{$_}{obj_link}->($obj) : '',
205
        },
206
      } @{ $self->{report_data}{columns} || {} }
207
    });
208
  }
209

  
210
  return $self->{report}->generate_with_headers;
211
}
212

  
213
sub link_to {
214
  my ($self, $object, %params) = @_;
215

  
216
  return unless $object;
217
  my $action = $params{action} || 'edit';
218

  
219
  if ($object->isa('SL::DB::Order')) {
220
    my $type   = $object->type;
221
    my $vc     = $object->is_sales ? 'customer' : 'vendor';
222
    my $id     = $object->id;
223

  
224
    return "oe.pl?action=$action&type=$type&vc=$vc&id=$id";
225
  }
226
  if ($object->isa('SL::DB::Part')) {
227
    my $id     = $object->id;
228
    return "ic.pl?action=$action&id=$id";
229
  }
230
  if ($object->isa('SL::DB::Customer')) {
231
    my $id     = $object->id;
232
    return "ct.pl?action=$action&id=$id&db=customer";
233
  }
234
}
235

  
236
# unfortunately ParseFilter can't handle compount filters.
237
# so we clone the original filter (still need that for serializing)
238
# rip out the options we know an replace them with the compound options.
239
# ParseFilter will take care of the prefixing then.
240
sub _pre_parse_filter {
241
  my ($self, $orig_filter, $launder_to) = @_;
242

  
243
  return undef unless $orig_filter;
244

  
245
  my $filter = clone($orig_filter);
246
  if ($filter->{part} && $filter->{part}{type}) {
247
    $launder_to->{part}{type} = delete $filter->{part}{type};
248
    my @part_filters = grep $_, map {
249
      $launder_to->{part}{type}{$_} ? SL::DB::Manager::Part->type_filter($_) : ()
250
    } qw(part service assembly);
251

  
252
    push @{ $filter->{and} }, or => [ @part_filters ] if @part_filters;
253
  }
254

  
255
  if ($filter->{'reqdate:date::le'}) {
256
    $launder_to->{'reqdate_date__le'} = delete $filter->{'reqdate:date::le'};
257
    my $parsed_date = DateTime->from_lxoffice($launder_to->{'reqdate_date__le'});
258
    push @{ $filter->{and} }, or => [
259
      'reqdate' => { le => $parsed_date },
260
      and => [
261
        'reqdate' => undef,
262
        'order.reqdate' => { le => $parsed_date },
263
      ]
264
    ] if $parsed_date;
265
  }
266

  
267
  return $filter;
268
}
269

  
270
1;
SL/DB/OrderItem.pm
68 68
                        qty           => [ 'qty'                  ],
69 69
                        ordnumber     => [ 'order.ordnumber'      ],
70 70
                        customer      => [ 'lower(customer.name)', ],
71
                        position      => [ 'trans_id' ],
72
                        reqdate       => [ 'COALESCE(orderitems.reqdate, order.transdate)' ],
71
                        position      => [ 'trans_id', 'runningnumber' ],
72
                        reqdate       => [ 'COALESCE(orderitems.reqdate, order.reqdate)' ],
73 73
                        orddate       => [ 'order.orddate' ],
74 74
                        sellprice     => [ 'sellprice' ],
75 75
                        discount      => [ 'discount' ],
76
                        transdate     => [ 'transdate::date', 'order.reqdate' ],
76 77
                      },
77 78
           default => [ 'position', 1 ],
78 79
           nulls   => { }
SL/InstallationCheck.pm
11 11
@required_modules = (
12 12
  { name => "parent",                              url => "http://search.cpan.org/~corion/",    debian => 'libparent-perl' },
13 13
  { name => "Archive::Zip",    version => '1.16',  url => "http://search.cpan.org/~adamk/",     debian => 'libarchive-zip-perl' },
14
  { name => "Clone",                               url => "http://search.cpan.org/~rdf/",       debian => 'libclone-perl' },
14 15
  { name => "Config::Std",                         url => "http://search.cpan.org/~dconway/",   debian => 'libconfig-std-perl' },
15 16
  { name => "DateTime",                            url => "http://search.cpan.org/~drolsky/",   debian => 'libdatetime-perl' },
16 17
  { name => "DBI",             version => '1.50',  url => "http://search.cpan.org/~timb/",      debian => 'libdbi-perl' },
locale/de/all
208 208
  'Article type (see below)'    => 'Artikeltyp (siehe unten)',
209 209
  'As a result, the saved onhand values of the present goods can be stored into a warehouse designated by you, or will be reset for a proper warehouse tracking' => 'Als Konsequenz k&ouml;nnen die gespeicherten Mengen entweder in ein Lager &uuml;berf&uuml;hrt werden, oder f&uuml;r eine frische Lagerverwaltung resettet werden.',
210 210
  'Assemblies'                  => 'Erzeugnisse',
211
  'Assembly'                    => 'Erzeugnis',
211 212
  'Assembly Description'        => 'Erzeugnis-Beschreibung',
212 213
  'Assembly Number'             => 'Erzeugnis-Nummer',
213 214
  'Assembly Number missing!'    => 'Erzeugnisnummer fehlt!',
......
592 593
  'Delivery Order created'      => 'Lieferschein erstellt',
593 594
  'Delivery Order deleted!'     => 'Lieferschein gel&ouml;scht!',
594 595
  'Delivery Orders'             => 'Lieferscheine',
596
  'Delivery Orders for this document' => 'Lieferscheine für dieses Dokument',
597
  'Delivery Plan'               => 'Lieferplan',
598
  'Delivery Plan for currently outstanding sales orders' => 'Lieferplan für offene Verkaufsaufträge',
599
  'Delivery information deleted.' => 'Lieferinformation gelöscht.',
600
  'Delivery information saved.' => 'Lieferinformation gespeichert.',
595 601
  'Department'                  => 'Abteilung',
596 602
  'Department 1'                => 'Abteilung (1)',
597 603
  'Department 2'                => 'Abteilung (2)',
......
903 909
  'Help Template Variables'     => 'Hilfe zu Dokumenten-Variablen',
904 910
  'Help on column names'        => 'Hilfe zu Spaltennamen',
905 911
  'Here\'s an example command line:' => 'Hier ist eine Kommandozeile, die als Beispiel dient:',
912
  'Hide Filter'                 => 'Filter verbergen',
906 913
  'Hide by default'             => 'Standardm&auml;&szlig;ig verstecken',
907 914
  'Hide help text'              => 'Hilfetext verbergen',
908 915
  'History'                     => 'Historie',
......
1518 1525
  'Requested execution date from' => 'Gewünschtes Ausführungsdatum von',
1519 1526
  'Requested execution date to' => 'Gewünschtes Ausführungsdatum bis',
1520 1527
  'Required by'                 => 'Lieferdatum',
1528
  'Reset'                       => 'Zurücksetzen',
1521 1529
  'Restore Dataset'             => 'Datenbank wiederherstellen',
1522 1530
  'Revenue'                     => 'Erlöskonto',
1523 1531
  'Revenue Account'             => 'Erlöskonto',
......
1635 1643
  'Shopartikel'                 => 'Shopartikel',
1636 1644
  'Short'                       => 'Knapp',
1637 1645
  'Show'                        => 'Zeigen',
1646
  'Show Filter'                 => 'Filter zeigen',
1638 1647
  'Show Salesman'               => 'Verkäufer anzeigen',
1639 1648
  'Show TODO list'              => 'Aufgabenliste anzeigen',
1640 1649
  'Show by default'             => 'Standardm&auml;&szlig;ig anzeigen',
menu.ini
166 166
module=dn.pl
167 167
action=search
168 168

  
169
[AR--Reports--Delivery Plan]
170
ACCESS=sales_order_edit
171
module=controller.pl
172
action=DeliveryPlan/list
169 173

  
170 174
[AP]
171 175

  
templates/webpages/delivery_plan/_filter.html
1
[%- USE T8 %]
2
[%- USE L %]
3
[%- USE LxERP %]
4
[%- USE HTML %]
5
<form action='controller.pl' method='post'>
6
<div class='filter_toggle'>
7
<a href='#' onClick='javascript:$(".filter_toggle").toggle()'>[% 'Show Filter' | $T8 %]</a>
8
  [% SELF.filter_summary %]
9
</div>
10
<div class='filter_toggle' style='display:none'>
11
<a href='#' onClick='javascript:$(".filter_toggle").toggle()'>[% 'Hide Filter' | $T8 %]</a>
12
 <table id='filter_table'>
13
  <tr>
14
   <th align="right">[% 'Number' | $T8 %]</th>
15
   <td>[% L.input_tag('filter.order.ordnumber:substr::ilike', filter.order.ordnumber_substr__ilike, size = 20) %]</td>
16
  </tr>
17
  <tr>
18
   <th align="right">[% 'Part Number' | $T8 %]</th>
19
   <td>[% L.input_tag('filter.part.partnumber:substr::ilike', filter.part.partnumber_substr__ilike, size = 20) %]</td>
20
  </tr>
21
  <tr>
22
   <th align="right">[% 'Part Description' | $T8 %]</th>
23
   <td>[% L.input_tag('filter.description:substr::ilike', filter.description_substr__ilike, size = 20) %]</td>
24
  </tr>
25
  <tr>
26
   <th align="right">[% 'Delivery Date' | $T8 %]</th>
27
   <td>[% L.date_tag('filter.reqdate:date::le', filter.reqdate_date__le, cal_align = 'BR') %]</td>
28
  </tr>
29
  <tr>
30
   <th align="right">[% 'Quantity' | $T8 %]</th>
31
   <td>[% L.input_tag('filter.qty:number', filter.qty_number, size = 20) %]</td>
32
  </tr>
33
  <tr>
34
   <th align="right">[% 'Customer' | $T8 %]</th>
35
   <td>[% L.input_tag('filter.order.customer.name:substr::ilike', filter.order.customer.name_substr__ilike, size = 20) %]</td>
36
  </tr>
37
  <tr>
38
   <th align="right">[% 'Type' | $T8 %]</th>
39
   <td>
40
     [% L.checkbox_tag('filter.part.type.part',     checked=filter.part.type.part,     label=LxERP.t8('Part')) %]
41
     [% L.checkbox_tag('filter.part.type.service',  checked=filter.part.type.service,  label=LxERP.t8('Service')) %]
42
     [% L.checkbox_tag('filter.part.type.assembly', checked=filter.part.type.assembly, label=LxERP.t8('Assembly')) %]
43
   </td>
44
  </tr>
45
 </table>
46

  
47
[% L.hidden_tag('action', 'DeliveryPlan/dispatch') %]
48
[% L.hidden_tag('sort_by', FORM.sort_by) %]
49
[% L.hidden_tag('sort_dir', FORM.sort_dir) %]
50
[% L.hidden_tag('page', FORM.page) %]
51
[% L.input_tag('action_list', LxERP.t8('Continue'), type = 'submit', class='submit')%]
52

  
53

  
54
<a href='#' onClick='javascript:$("#filter_table input").attr("value","");$("#filter_table option").attr("selected","")'>[% 'Reset' | $T8 %]</a>
55

  
56
</div>
57

  
58
</form>
templates/webpages/delivery_plan/_list.html
1
[% USE HTML %][% USE T8 %][% USE L %][% USE LxERP %]
2

  
3
[% BLOCK header %]
4
 [% SET new_sort_dir = SELF.sort_by == sort_by ? 1 - SELF.sort_dir : SELF.sort_dir %]
5
 <th width="[% size %]%">
6
  <a href="[% SELF.url_for(action => 'list') %]&sort_by=[% sort_by %]&sort_dir=[% new_sort_dir %]&page=[% FORM.page %]">
7
   [%- title %]
8
   [%- IF SELF.sort_by == sort_by %]
9
    <img src="image/[% IF SELF.sort_dir %]down[% ELSE %]up[% END %].png" border="0">
10
   [%- END %]
11
  </a>
12
 </th>
13
[% END %]
14

  
15
<div id="orders">
16
[%- IF !SELF.orderitems.size %]
17
 <p>[%- 'There are no outstanding deliveries at the moment.' | $T8 %]</p>
18
[%- ELSE %]
19

  
20
 <table width=100%>
21
  <tr class="listheading">
22
   [% PROCESS header   title=LxERP.t8('Date')        sort_by='transdate',     size=15 %]
23
   [% PROCESS header   title=LxERP.t8('Description') sort_by='description',   size=15 %]
24
   [% PROCESS header   title=LxERP.t8('Part Number') sort_by='partnumber',    size=15 %]
25
   [% PROCESS header   title=LxERP.t8('Qty')         sort_by='qty',           size=10 %]
26
   [% PROCESS header   title=LxERP.t8('Order')       sort_by='ordnumber',     size=10 %]
27
   [% PROCESS header   title=LxERP.t8('Customer')    sort_by='customer',      size=10 %]
28
  </tr>
29

  
30
  [%- FOREACH row = SELF.orderitems %]
31
  <tr class="listrow[% loop.count % 2 %]">
32
   <td>[% row.transdate ? row.transdate : row.order.reqdate_as_date %]</td>
33
   <td>[% row.part.partnumber | html %]</td>
34
   <td>[% row.description | html %]</td>
35
   <td class='numeric'>[% LxERP.format_amount(row.qty, 2) | html %]</td>
36
   <td>[% row.order.ordnumber | html %]</td>
37
   <td>[% row.order.customer.name | html %]</td>
38
  </tr>
39
  [%- END %]
40
 </table>
41
 <p align=right>[% PROCESS 'common/paginate.html' pages=SELF.pages, base_url=SELF.url_for(action='list', sort_dir=SELF.sort_dir, sort_by=SELF.sort_by) %]</p>
42

  
43
[%- END %]
44
</div>
templates/webpages/delivery_plan/list.html
1
[%- USE T8 %]
2

  
3
<h1>[% 'Delivery Plan' | $T8 %]</h1>
4

  
5
[%- PROCESS 'delivery_plan/_filter.html' filter=FORM.filter %]
6
 <hr>
7
[%- PROCESS 'delivery_plan/_list.html' %]
templates/webpages/delivery_plan/report_bottom.html
1
[% SET report_bottom_url_args = {} %]
2
[%     report_bottom_url_args.import(SELF.flat_filter) %]
3
[%     report_bottom_url_args.import({action='list', sort_dir=SELF.sort_dir, sort_by=SELF.sort_by}) %]
4
<p align=right>[% PROCESS 'common/paginate.html' pages=SELF.pages, base_url=SELF.url_for(report_bottom_url_args) %]</p>
templates/webpages/delivery_plan/report_top.html
1
[%- USE L %]
2
[%- PROCESS 'delivery_plan/_filter.html' filter=SELF.filter %]
3
 <hr>

Auch abrufbar als: Unified diff