Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision 049e49fe

Von Sven Schöling vor etwa 10 Jahren hinzugefügt

  • ID 049e49fea3e4a3c7c78d7aebf055936b3cab40c5
  • Vorgänger ed83bf47
  • Nachfolger b776c6ba

PriceSource: Preisselektion auf Popup umgestellt.

- Logik für geänderte Preise implementiert
- Visualisierung verbessert
- fix für emptied rows
- nachricht wenn invalid und missing
- benachrichtigung für höher/niedriger
- js ausgelagert
- best price benachrichtigung

noch offene bugs:
- preise mit mehr als 2 stellen werden abgeschnitten
- interaktive preise noch nicht möglich
- symbol für "besser preis" ist nicht schön
- beide make_record_item implementierungen sind leicht unterschiedlich
- pricesource controller grösstenteils ungetestet
- performance ist im moment mies

Unterschiede anzeigen:

SL/Controller/PriceSource.pm
1
package SL::Controller::PriceSource;
2

  
3
use strict;
4

  
5
use parent qw(SL::Controller::Base);
6

  
7
use List::MoreUtils qw(any uniq apply);
8
use SL::ClientJS;
9
use SL::Locale::String qw(t8);
10
use SL::PriceSource;
11

  
12
use Rose::Object::MakeMethods::Generic
13
(
14
 scalar => [ qw(record_item) ],
15
 'scalar --get_set_init' => [ qw(js record) ],
16
);
17

  
18
__PACKAGE__->run_before('check_auth');
19

  
20
#
21
# actions
22
#
23

  
24
sub action_price_popup {
25
  my ($self) = @_;
26

  
27
  my $record_item = _make_record_item($::form->{row});
28

  
29
  $self->render_price_dialog($record_item);
30
}
31

  
32
sub render_price_dialog {
33
  my ($self, $record_item) = @_;
34

  
35
  my $price_source = SL::PriceSource->new(record_item => $record_item, record => $self->record);
36

  
37
  $self->js
38
    ->run(
39
      'kivi.io.price_chooser_dialog',
40
      t8('Available Prices'),
41
      $self->render('oe/price_sources_dialog', { output => 0 }, price_source => $price_source)
42
    )
43
    ->reinit_widgets;
44

  
45
#   if (@errors) {
46
#     $self->js->text('#dialog_flash_error_content', join ' ', @errors);
47
#     $self->js->show('#dialog_flash_error');
48
#   }
49

  
50
  $self->js->render($self);
51
}
52

  
53

  
54
#
55
# internal stuff
56
#
57

  
58
sub check_auth {
59
  $::auth->assert('edit_prices');
60
}
61

  
62
sub init_js {
63
  SL::ClientJS->new
64
}
65

  
66
sub init_record {
67
  _make_record();
68
}
69

  
70
sub _make_record_item {
71
  my ($row) = @_;
72

  
73
  my $class = {
74
    sales_order             => 'OrderItem',
75
    purchase_oder           => 'OrderItem',
76
    sales_quotation         => 'OrderItem',
77
    request_quotation       => 'OrderItem',
78
    invoice                 => 'InvoiceItem',
79
    purchase_invoice        => 'InvoiceItem',
80
    purchase_delivery_order => 'DeliveryOrderItem',
81
    sales_delivery_order    => 'DeliveryOrderItem',
82
  }->{$::form->{type}};
83

  
84
  return unless $class;
85

  
86
  $class = 'SL::DB::' . $class;
87

  
88
  eval "require $class";
89

  
90
  my $obj = $::form->{"orderitems_id_$row"}
91
          ? $class->meta->convention_manager->auto_manager_class_name->find_by(id => $::form->{"orderitems_id_$row"})
92
          : $class->new;
93

  
94
  for my $method (apply { s/_$row$// } grep { /_$row$/ } keys %$::form) {
95
    next unless $obj->meta->column($method);
96
    if ($obj->meta->column($method)->isa('Rose::DB::Object::Metadata::Column::Date')) {
97
      $obj->${\"$method\_as_date"}($::form->{"$method\_$row"});
98
    } elsif ((ref $obj->meta->column($method)) =~ /^Rose::DB::Object::Metadata::Column::(?:Numeric|Float|DoublePrecsion)$/) {
99
      $obj->${\"$method\_as_number"}($::form->{"$method\_$row"});
100
    } else {
101
      $obj->$method($::form->{"$method\_$row"});
102
    }
103
  }
104

  
105
  if ($::form->{"id_$row"}) {
106
    $obj->part(SL::DB::Part->load_cached($::form->{"id_$row"}));
107
  }
108

  
109
  return $obj;
110
}
111

  
112
sub _make_record {
113
  my ($with_items) = @_;
114

  
115
  my $class = {
116
    sales_order             => 'Order',
117
    purchase_oder           => 'Order',
118
    sales_quotation         => 'Order',
119
    request_quotation       => 'Order',
120
    purchase_delivery_order => 'DeliveryOrder',
121
    sales_delivery_order    => 'DeliveryOrder',
122
  }->{$::form->{type}};
123

  
124
  if ($::form->{type} eq 'invoice') {
125
    $class = $::form->{vc} eq 'customer' ? 'Invoice'
126
           : $::form->{vc} eq 'vendor'   ? 'PurchaseInvoice'
127
           : do { die 'unknown invoice type' };
128
  }
129

  
130
  return unless $class;
131

  
132
  $class = 'SL::DB::' . $class;
133

  
134
  eval "require $class";
135

  
136
  my $obj = $::form->{id}
137
          ? $class->meta->convention_manager->auto_manager_class_name->find_by(id => $::form->{id})
138
          : $class->new;
139

  
140
  for my $method (keys %$::form) {
141
    next unless $obj->can($method);
142
    next unless $obj->meta->column($method);
143

  
144
    if ($obj->meta->column($method)->isa('Rose::DB::Object::Metadata::Column::Date')) {
145
      $obj->${\"$method\_as_date"}($::form->{$method});
146
    } elsif ((ref $obj->meta->column($method)) =~ /^Rose::DB::Object::Metadata::Column::(?:Numeric|Float|DoublePrecsion)$/) {
147
      $obj->${\"$method\_as\_number"}($::form->{$method});
148
    } else {
149
      $obj->$method($::form->{$method});
150
    }
151
  }
152

  
153
  if ($with_items) {
154
    my @items;
155
    for my $i (1 .. $::form->{rowcount}) {
156
      next unless $::form->{"id_$i"};
157
      push @items, _make_record_item($i)
158
    }
159

  
160
    $obj->items(@items) if @items;
161
  }
162

  
163
  return $obj;
164
}
165

  
166
1;
167

  
SL/PriceSource.pm
59 59
PriceSource is an interface that allows generic algorithms to be plugged
60 60
together to calculate available prices for a position in a record.
61 61

  
62
Each algorithm can access details of the record to realize dependancies on
62
Each algorithm can access details of the record to realize dependencies on
63 63
part, customer, vendor, date, quantity etc, which was previously not possible.
64 64

  
65 65
=head1 BACKGROUND AND PHILOSOPY
......
111 111
calculation can be repeated so that invalid prices can be caught (because for
112 112
example the special offer is no longer valid), and so that sales personnel have
113 113
information about rising or falling prices. The fourth point ensures that
114
insular calculation processes can be developed independant of the core code.
114
insular calculation processes can be developed independent of the core code.
115 115

  
116 116
=head1 INTERFACE METHODS
117 117

  
SL/PriceSource/Base.pm
183 183

  
184 184
C<source> and C<spec> are tainted. If you store data directly in C<spec>, sanitize.
185 185

  
186
=back
187

  
186 188
=head1 SEE ALSO
187 189

  
188 190
L<SL::PriceSource>,
bin/mozilla/do.pl
323 323

  
324 324
  $form->{follow_up_trans_info} = $form->{donumber} .'('. $follow_up_vc .')';
325 325

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

  
328 328
  $form->header();
329 329
  # Fix für Bug 1082 Erwartet wird: 'abteilungsNAME--abteilungsID'
bin/mozilla/io.pl
44 44
use SL::CVar;
45 45
use SL::Common;
46 46
use SL::CT;
47
use SL::Locale::String qw(t8);
47 48
use SL::IC;
48 49
use SL::IO;
49 50
use SL::PriceSource;
......
325 326
    if ($form->{"id_${i}"}) {
326 327
      my $price_source = SL::PriceSource->new(record_item => $record_item, record => $record);
327 328
      my $price = $price_source->price_from_source($::form->{"active_price_source_$i"});
328
      $::form->{price_sources}[$i] = $price_source;
329
      $column_data{price_source} .= $cgi->button(-value => $price->full_description, -onClick => "toggle_price_source($i)");
329
      $column_data{price_source} .= $cgi->button(-value => $price->full_description, -onClick => "kivi.io.price_chooser($i)");
330
      if ($price->source) {
331
        $column_data{price_source} .= ' ' . $cgi->img({src => 'image/flag-red.png', alt => $price->invalid, title => $price->invalid }) if $price->invalid;
332
        $column_data{price_source} .= ' ' . $cgi->img({src => 'image/flag-red.png', alt => $price->missing, title => $price->missing }) if $price->missing;
333
        $column_data{price_source} .= ' ' . $cgi->img({src => 'image/up.png',   alt => t8('This price has since gone up'),      title => t8('This price has since gone up' )     }) if $price->price > $record_item->sellprice;
334
        $column_data{price_source} .= ' ' . $cgi->img({src => 'image/down.png', alt => t8('This price has since gone down'),    title => t8('This price has since gone down')    }) if $price->price < $record_item->sellprice;
335
        $column_data{price_source} .= ' ' . $cgi->img({src => 'image/ok.png',   alt => t8('There is a better price available'), title => t8('There is a better price available') }) if $price->source ne $price_source->best_price->source;
336
      }
330 337
    }
331 338

  
332 339
    if ($is_delivery_order) {
......
702 709
                price_old price_new unit_old ordnumber donumber
703 710
                transdate longdescription basefactor marge_total marge_percent
704 711
                marge_price_factor lastcost price_factor_id partnotes
705
                stock_out stock_in has_sernumber reqdate orderitems_id);
712
                stock_out stock_in has_sernumber reqdate orderitems_id
713
                active_price_source);
706 714

  
707 715
  my $ic_cvar_configs = CVar->get_configs(module => 'IC');
708 716
  push @flds, map { "ic_cvar_$_->{name}" } @{ $ic_cvar_configs };
......
1931 1939
    next unless $obj->meta->column($method);
1932 1940
    if ($obj->meta->column($method)->isa('Rose::DB::Object::Metadata::Column::Date')) {
1933 1941
      $obj->${\"$method\_as_date"}($::form->{"$method\_$row"});
1934
    } elsif ((ref $obj->meta->column($method)) =~ /^Rose::DB::Object::Metadata::Column::(?:Numeric|Float|DoublePrecsion)$/) {
1935
      $obj->${\"$method\_as\_number"}($::form->{$method});
1936 1942
    } else {
1937 1943
      $obj->$method($::form->{"$method\_$row"});
1938 1944
    }
......
1978 1984
    if ($obj->meta->column($method)->isa('Rose::DB::Object::Metadata::Column::Date')) {
1979 1985
      $obj->${\"$method\_as_date"}($::form->{$method});
1980 1986
    } elsif ((ref $obj->meta->column($method)) =~ /^Rose::DB::Object::Metadata::Column::(?:Numeric|Float|DoublePrecsion)$/) {
1981
      $obj->${\"$method\_as\_number"}($::form->{$method});
1987
      $obj->${\"$method\_as_number"}($::form->{$method});
1982 1988
    } else {
1983 1989
      $obj->$method($::form->{$method});
1984 1990
    }
bin/mozilla/ir.pl
335 335
  ), @custom_hiddens,
336 336
  map { $_.'_rate', $_.'_description', $_.'_taxnumber' } split / /, $form->{taxaccounts}];
337 337

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

  
340 340
  $form->header();
341 341

  
bin/mozilla/is.pl
385 385
  ), @custom_hiddens,
386 386
  map { $_.'_rate', $_.'_description', $_.'_taxnumber' } split / /, $form->{taxaccounts}];
387 387

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

  
390 390
  $form->header();
391 391

  
bin/mozilla/oe.pl
465 465
    }
466 466
  }
467 467

  
468
  $::request->{layout}->use_javascript(map { "${_}.js" } qw(kivi.SalesPurchase show_form_details show_history show_vc_details ckeditor/ckeditor ckeditor/adapters/jquery));
468
  $::request->{layout}->use_javascript(map { "${_}.js" } qw(kivi.SalesPurchase show_form_details show_history show_vc_details ckeditor/ckeditor ckeditor/adapters/jquery kivi.io));
469 469

  
470 470
  $form->header;
471 471
  if ($form->{CFDD_shipto} && $form->{CFDD_shipto_id} ) {
js/kivi.io.js
1
namespace('kivi.io', function(ns) {
2
  var $dialog;
3

  
4
  ns.price_chooser_dialog = function(title, html) {
5
    var id            = 'jqueryui_popup_dialog';
6
    var dialog_params = {
7
      id:     id,
8
      width:  800,
9
      height: 500,
10
      modal:  true,
11
      close: function(event, ui) { $dialog.remove(); },
12
    };
13

  
14
    $('#' + id).remove();
15

  
16
    $dialog = $('<div style="display:none" id="' + id + '"></div>').appendTo('body');
17
    $dialog.attr('title', title);
18
    $dialog.html(html);
19
    $dialog.dialog(dialog_params);
20

  
21
    $('.cancel').click(ns.close_dialog);
22

  
23
    return true;
24
  };
25

  
26
  ns.close_dialog = function() {
27
    $dialog.dialog("close");
28
  }
29

  
30
  ns.price_chooser = function(i) {
31
    var form = $('form').serializeArray();
32
    form.push( { name: 'action', value: 'PriceSource/price_popup' }
33
             , { name: 'row',    value: i }
34
    );
35

  
36
    $.post('controller.pl', form, function(data) {
37
      kivi.eval_json_result(data);
38
    });
39
  }
40

  
41
  ns.update_price_source = function(row, source, price_str) {
42
    $('#active_price_source_' + row).val(source);
43
    if (price_str) $('#sellprice_' + row).val(price_str);
44
    $('#update_button').click();
45
  }
46
});
locale/de/all
291 291
  'Automatic deletion of leading, trailing and excessive (repetitive) spaces in part description and part notes. Affects the CSV import as well.' => 'Automatisches Löschen von voran-/nachgestellten und aufeinanderfolgenden Leerzeichen in Artikelbeschreibungen und -bemerkungen. Betrifft auch den CSV-Import.',
292 292
  'Automatically created invoice for fee and interest for dunning %s' => 'Automatisch erzeugte Rechnung für Gebühren und Zinsen zu Mahnung %s',
293 293
  'Available'                   => 'Verfügbar',
294
  'Available Prices'            => 'Mögliche Preise',
294 295
  'Available qty'               => 'Lagerbestand',
295 296
  'BALANCE SHEET'               => 'BILANZ',
296 297
  'BIC'                         => 'BIC',
......
340 341
  'Beratername'                 => 'Beratername',
341 342
  'Beraternummer'               => 'Beraternummer',
342 343
  'Best Before'                 => 'Mindesthaltbarkeit',
344
  'Best Price'                  => 'Bester Preis',
343 345
  'Bilanz'                      => 'Bilanz',
344 346
  'Billable amount'             => 'Abrechenbarer Betrag',
345 347
  'Billed amount'               => 'Abgerechneter Betrag',
......
2628 2630
  'There are still transfers not matching the qty of the delivery order. Stock operations can not be changed later. Do you really want to proceed?' => 'Einige der Lagerbewegungen sind nicht vollständig und Lagerbewegungen können nachträglich nicht mehr verändert werden. Wollen Sie wirklich fortfahren?',
2629 2631
  'There are undefined currencies in your system.' => 'In Ihrer Datenbank wurden Währungen benutzt, die nicht ordnungsgemäß in den Währungen eingetragen wurden.',
2630 2632
  'There are usually three ways to install Perl modules.' => 'Es gibt normalerweise drei Arten, ein Perlmodul zu installieren.',
2633
  'There is a better price available' => 'Es ist ein besserer Preis verfügbar',
2631 2634
  'There is already a taxkey 0 with tax rate not 0.' => 'Es existiert bereits ein Steuerschlüssel mit Steuersatz ungleich 0%.',
2632 2635
  'There is an inconsistancy in your database.' => 'In Ihrer Datenbank sind Unstimmigkeiten vorhanden.',
2633 2636
  'There is at least one sales or purchase invoice for which kivitendo recorded an inventory transaction with taxkeys even though no tax was recorded.' => 'Es gibt mindestens eine Verkaufs- oder Einkaufsrechnung, für die kivitendo eine Warenbestandsbuchung ohne dazugehörige Steuerbuchung durchgeführt hat.',
......
2663 2666
  'This option controls the method used for profit determination.' => 'Dieser Parameter legt die Berechnungsmethode für die Gewinnermittlung fest.',
2664 2667
  'This option controls the posting and calculation behavior for the accounting method.' => 'Dieser Parameter steuert die Buchungs- und Berechnungsmethoden für die Versteuerungsart.',
2665 2668
  'This partnumber is not unique. You should change it.' => 'Diese Artikelnummer ist nicht eindeutig. Bitte wählen Sie eine andere.',
2669
  'This price has since gone down' => 'Dieser Preis ist mittlerweile niedriger',
2670
  'This price has since gone up' => 'Dieser Preis ist mittlerweile höher',
2666 2671
  'This requirement spec is currently linked to the following project:' => 'Dieses Pflichtenheft ist mit dem folgenden Projekt verknüpft:',
2667 2672
  'This requirement spec is currently not linked to a project.' => 'Dieses Pflichtenheft ist noch nicht mit einem Projekt verknüpft.',
2668 2673
  'This requires you to manually correct entries for which an automatic conversion failed and to check those for which it succeeded.' => 'Dies erfordert, dass Sie diejenigen Einträge manuell korrigieren, für die die automatische Umstellung fehlschlug, sowie dass Sie diejenigen überprüfen, für die die Umstellung erfolgreich war.',
......
2770 2775
  'Unsupported image type (supported types: #1)' => 'Nicht unterstützter Bildtyp (unterstützte Typen: #1)',
2771 2776
  'Until'                       => 'Bis',
2772 2777
  'Update'                      => 'Erneuern',
2778
  'Update Price'                => 'Preis übernehmen',
2773 2779
  'Update Prices'               => 'Preise aktualisieren',
2774 2780
  'Update SKR04: new tax account 3804 (19%)' => 'Update SKR04: neues Steuerkonto 3804 (19%) für innergemeinschaftlichen Erwerb',
2775 2781
  'Update prices'               => 'Preise aktualisieren',
templates/webpages/oe/_price_sources_row.html
1
[%- USE T8 %]
2
[%- USE HTML %]
3
[%- USE L %]
4
[%- USE LxERP %]
5
<tr class="listrow[% i % 2 %]" id="row[% i %]_3" style='display:none'>
6
 <td colspan="[% row.colspan %]">
7
   <span class="[% IF !row.obj.active_price_source %]bold[% END %]">
8
   [% L.radio_button_tag('active_price_source_' _ i, label=LxERP.t8('None (PriceSource)'), checked=!row.obj.active_price_source, value='', onChange='update_price_source(' _ i _ ', \'\')') %]
9
   </span>
10
   [%- FOREACH price IN price_sources.$i.available_prices %]
11
     <div class="[% IF price.source == row.obj.active_price_source %]bold[% END %]">
12
     [% L.radio_button_tag('active_price_source_' _ i, value=price.source, checked=price.source == row.obj.active_price_source, label=LxERP.format_amount(price.price, 2) _ ' (' _ price.full_description _ ')', onChange='update_price_source(' _ i _ ', \'' _ price.source _ '\', \'' _ LxERP.format_amount(price.price, -2) _ '\')' ) %]
13
     </div>
14
   [%- END %]
15
 </td>
16
</tr>
templates/webpages/oe/price_sources_dialog.html
1
[%- USE T8 %]
2
[%- USE HTML %]
3
[%- USE L %]
4
[%- USE LxERP %]
5
[% SET best_price = price_source.best_price %]
6
  <table>
7
   <tr class='listheading'>
8
    <th></th>
9
    <th>[% 'Price Source' | $T8 %]</th>
10
    <th>[% 'Price' | $T8 %]</th>
11
    <th>[% 'Best Price' | $T8 %]</th>
12
   </tr>
13
   <tr class='listrow'>
14
[%- IF price_source.record_item.active_price_source %]
15
    <td>[% L.button_tag('kivi.io.update_price_source(' _ FORM.row _ ', \'\')', LxERP.t8('Select')) %]</td>
16
[%- ELSE %]
17
    <td><b>[% 'Selected' | $T8 %]</b></td>
18
[%- END %]
19
    <td>[% 'None (PriceSource)' | $T8 %]</td>
20
    <td>-</td>
21
    <td></td>
22
   </tr>
23
   [%- FOREACH price IN price_source.available_prices %]
24
    <tr class='listrow'>
25
[%- IF price_source.record_item.active_price_source != price.source %]
26
     <td>[% L.button_tag('kivi.io.update_price_source(' _ FORM.row _ ', \'' _ price.source _ '\', \'' _ LxERP.format_amount(price.price, -2) _ '\')', LxERP.t8('Select')) %]</td>
27
[%- ELSIF price_source.record_item.sellprice_as_number != price.price_as_number %]
28
     <td>[% L.button_tag('kivi.io.update_price_source(' _ FORM.row _ ', \'' _ price.source _ '\', \'' _ LxERP.format_amount(price.price, -2) _ '\')', LxERP.t8('Update Price')) %]</td>
29
[%- ELSE %]
30
    <td><b>[% 'Selected' | $T8 %]</b></td>
31
[% END %]
32
     <td>[% price.full_description | html %]</td>
33
     <td>[% price.price_as_number %]</td>
34
[% IF price.source == best_price.source %]
35
     <td align='center'>&#x2022;</td>
36
[% ELSE %]
37
     <td></td>
38
[% END %]
39
    </tr>
40
   [%- END %]
41
  </table>
templates/webpages/oe/sales_order.html
75 75

  
76 76
      </td>
77 77
     </tr>
78
 [% PROCESS 'oe/_price_sources_row.html' i = loop.count %]
79 78
[%- END %]
80 79

  
81 80
  </table>
......
95 94
        [% END %]
96 95
      }, 1);
97 96
    });
98
    function toggle_price_source(row) {
99
      $('#row' + row + '_3').toggle();
100
    }
101
    function update_price_source(row, source, price_str){
102
      $('#active_price_source_' + row).val(source);
103
      if (price_str) $('#sellprice_' + row).val(price_str);
104
      $('#update_button').click();
105
    }
106 97
  </script>
107 98

  
108 99
 </td>

Auch abrufbar als: Unified diff