Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision 783b0bd2

Von Kivitendo Admin vor mehr als 1 Jahr hinzugefügt

  • ID 783b0bd2740eafb1ab227a30f47bef0c812ae842
  • Vorgänger 2fb77d82
  • Nachfolger 34d34bef

Disposition Manager - ein paar Änderungen 2 neu

Folgende Sachen habe ich umbenannt:

DispositionsManager > DispositionManager
dispositionsmanager -> disposition_manager
purchase_bucket -> purchase_bucket_item
form arrayrefs: id -> ids, vendor_id -> vendor_ids
SL::DB::MetaSetup
>PurchaseBucketItem: rose foreign key Relationship für parts: parts -> part

Dadurch, daß sich die Dateinamen geändert haben, ist leider die
git-Änderungshistorie ausgehebelt...

Unterschiede anzeigen:

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

  
3
use strict;
4

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

  
7
use SL::DB::Part;
8
use SL::DB::PurchaseBasketItem;
9
use SL::PriceSource;
10
use SL::Locale::String qw(t8);
11
use Data::Dumper;
12

  
13
sub action_list_parts {
14
  my ( $self ) = @_;
15

  
16
  my $parts = $self->_get_parts;
17

  
18
  $self->_setup_list_action_bar;
19
  $self->render('disposition_manager/list_parts', title => t8('Parts short onhand'), PARTS => $parts);
20
}
21

  
22
sub action_add_to_purchase_basket{
23
  my ( $self ) = @_;
24

  
25
  my $parts_to_add = delete($::form->{ids}) || [];
26
  foreach my $id (@{ $parts_to_add }) {
27
    my $part = SL::DB::Manager::Part->find_by(id => $id) or die "Can't find part with id: $id\n";
28
    my $basket_part = SL::DB::PurchaseBasketItem->new(
29
      parts_id    => $part->id,
30
      qty         => $part->min_order_qty, # was ist wenn min_order_qty < (rop-onhand) ist? sollte dann nicht (rop-onhand) genommen werden?
31
      description => $part->description,  # Warum wird description zusätzlich gespeichert, parts_id sollte doch reichen? Falls die sich in den Stammdaten in der Zwischenzeit verändert? zumal du in action_transfer_to_purchase_order explizit $part->description verwendest
32
    )->save;
33
 }
34
 $self->action_show_basket;
35

  
36
}
37

  
38
sub action_show_basket {
39
  my ( $self ) = @_;
40

  
41
  $::request->{layout}->add_javascripts('kivi.DispositionManager.js');
42
  my $basket_items = SL::DB::Manager::PurchaseBasketItem->get_all( query => [ cleared => 'F' ],  with_objects => [ 'part', 'part.makemodels' ]);
43
  $self->_setup_show_basket_action_bar;
44
  $self->render('disposition_manager/show_purchase_basket', BASKET_ITEMS => $basket_items, title => "Purchase basket" );
45
}
46

  
47
sub action_transfer_to_purchase_order {
48

  
49
  my ( $self ) = @_;
50
  require SL::DB::Order;
51
  require SL::DB::OrderItem;
52
  require SL::DB::Part;
53
  require SL::DB::Vendor;
54
  my @error_report;
55

  
56
  my $v_id =  $::form->{vendor_ids}->[0] ;
57

  
58
  my ($vendor, $employee);
59
  $vendor   = SL::DB::Manager::Vendor->find_by(id => $v_id) or die "Can't find vendor";
60
  $employee = SL::DB::Manager::Employee->current            or die "Can't find employee";
61

  
62
  my $basket_items = SL::DB::Manager::PurchaseBasketItem->get_all( query => [ id => \@{ $::form->{ids} } ] );
63

  
64
  # create order first so we have a record for PriceSource
65
  my $order = SL::DB::Order->new(
66
    vendor_id               => $vendor->id,
67
    employee_id             => $employee->id,
68
    intnotes                => $vendor->notes,
69
    salesman_id             => $employee->id,
70
    payment_id              => $vendor->payment_id,
71
    taxzone_id              => $vendor->taxzone_id,
72
    currency_id             => $vendor->currency_id,
73
    transdate               => DateTime->today_local
74
  );
75

  
76
  my $i = 0;
77
  my @items;
78

  
79
  foreach my $basket_item ( @{ $basket_items } ) {
80
    $i++;
81
    my $mm = SL::DB::Manager::MakeModel->get_first( query => [ make => $vendor->id, parts_id => $basket_item->parts_id] );
82

  
83
    my $current_order_item = SL::DB::OrderItem->new(
84
      part                => $basket_item->part,
85
      description         => $basket_item->part->description,
86
      qty                 => $basket_item->qty || 1,
87
      unit                => $basket_item->part->unit,
88
      position            => $i,
89
    );
90

  
91
    my $price_source  = SL::PriceSource->new(record_item => $current_order_item, record => $order);
92
    $current_order_item->sellprice($price_source->best_price->price);
93
    $current_order_item->active_price_source($price_source->best_price->source);
94
    push(@items, $current_order_item);
95
  }
96

  
97
  $order->orderitems( [ @items ] );
98
  $order->db->with_transaction( sub {
99
    $order->calculate_prices_and_taxes;
100
    $order->save;
101

  
102
    my $snumbers = "ordernumber_" . $order->ordnumber;
103
    SL::DB::History->new(
104
                      trans_id    => $order->id,
105
                      snumbers    => $snumbers,
106
                      employee_id => SL::DB::Manager::Employee->current->id,
107
                      addition    => 'SAVED',
108
                      what_done   => 'PurchaseBasket->Order',
109
                    )->save();
110
    foreach my $item(@{ $order->orderitems }){
111
      $item->parse_custom_variable_values->save;
112
      $item->{custom_variables} = \@{ $item->cvars_by_config };
113
      $item->save;
114
    }
115
    SL::DB::Manager::PurchaseBasketItem->delete_all( where => [ id => \@{ $::form->{ids} }]);
116
    return 1;
117
  }) || die "error: " . $order->db->error;
118
  $self->redirect_to(controller => "oe.pl", action => 'edit', type => 'purchase_order', vc => 'vendor', id => $order->id);
119
}
120

  
121
sub _get_parts {
122
  my ($self) = @_;
123

  
124
  my $query = <<SQL;
125
SELECT *
126
  FROM parts
127
 WHERE onhand <= rop
128
   AND rop != 0
129
   AND id NOT IN( SELECT parts_id FROM purchase_basket_items )
130
   AND NOT obsolete
131
SQL
132

  
133
  return SL::DB::Manager::Part->get_objects_from_sql( sql => $query );
134
};
135

  
136

  
137
sub _setup_list_action_bar {
138
  my ($self) = @_;
139
  for my $bar ($::request->layout->get('actionbar')) {
140
    $bar->add(
141
      action => [
142
        t8('Action'),
143
        submit   => [ '#form', { action => "DispositionManager/add_to_purchase_basket" } ],
144
        tooltip  => t8('Add to purchase basket'),
145
      ],
146
    );
147
  }
148
}
149

  
150
sub _setup_show_basket_action_bar {
151
  my ($self) = @_;
152
  for my $bar ($::request->layout->get('actionbar')) {
153
    $bar->add(
154
      action => [
155
        t8('Reload'),
156
        submit   => [ '#purchasebasket', { action => "DispositionManager/show_basket" } ],
157
      ],
158
      action => [
159
        t8('Action'),
160
        call   => [ 'kivi.DispositionManager.create_order' ],
161
        tooltip  => t8('Create purchase order'),
162
      ],
163
    );
164
  }
165
}
166
1;
167

  
168
__END__
169

  
170
=encoding utf-8
171

  
172
=head1 NAME
173

  
174
SL::Controller::DispositionManager Controller to manage purchase orders for parts
175

  
176
=head1 DESCRIPTION
177

  
178
This controller shows a list of parts using the filter minimum stock (rop).
179
From this list it is possible to put parts in a purchase basket to order.
180
It's also possible to put parts from the parts edit form in the purchase basket.
181

  
182
From the purchase basket you can create a purchase order by using the filter vendor.
183
The quantity to order will be prefilled by the value min_qty_to_order from parts or
184
makemodel(vendor_parts) or default to qty 1.
185

  
186
Tables:
187

  
188
=over 2
189

  
190
=item purchase_basket
191

  
192
=back
193

  
194
Dependencies:
195

  
196
=over 2
197

  
198
=item parts
199

  
200
=item makemodels
201

  
202

  
203
=back
204

  
205
=head1 URL ACTIONS
206

  
207
=over 4
208

  
209
=item C<action_list_parts>
210

  
211
List the parts by the filter min stock (rop) and not in an open purchase order.
212

  
213
=item C<action_add_to_purchase_basket>
214

  
215
Adds one or more parts to the purchase basket.
216

  
217
=item C<action_show_basket>
218

  
219
Shows a list with parts which are in the basket.
220
This list can be filtered by vendor. Then you can create a purchase order.
221

  
222
=item C<action_transfer_to_purchase_order>
223

  
224
Transfers the marked and by vendor filtered parts to a purchase order.
225
Deletes the entry in the purchase basket.
226

  
227
=back
228

  
229
=head1 BUGS
230

  
231
None yet. :)
232

  
233
=head1 AUTHOR
234

  
235
W. Hahn E<lt>wh@futureworldsearch.netE<gt>
236

  
237
=cut
SL/Controller/DispositionsManager.pm
1
package SL::Controller::DispositionsManager;
2

  
3
use strict;
4

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

  
7
use SL::DB::Part;
8
use SL::DB::PurchaseBasket;
9
use SL::Locale::String qw(t8);
10

  
11
sub action_list_parts {
12
  my ( $self ) = @_;
13

  
14
  my $parts = $self->_get_parts;
15

  
16
  $self->_setup_list_action_bar;
17
  $self->render('dispositionsmanager/list_parts', title => t8('Parts short onhand'), PARTS => $parts);
18
}
19

  
20
sub action_add_to_purchase_basket{
21
  my ( $self ) = @_;
22

  
23
  my $parts_to_add = delete($::form->{id}) || [];
24
  foreach my $id (@{ $parts_to_add }) {
25
    my $part = SL::DB::Manager::Part->get_first( query => [ id => $id ] );
26
    my $basket_part = SL::DB::PurchaseBasket->new(
27
      parts_id    => $part->id,
28
      qty         => $part->min_order_qty,
29
      description => $part->description,
30
    );
31
   $basket_part->save;
32
 }
33
 $self->action_show_basket;
34

  
35
}
36

  
37
sub action_show_basket {
38
  my ( $self ) = @_;
39

  
40
  $::request->{layout}->add_javascripts('kivi.DispositionsManager.js');
41
  my $basket = SL::DB::Manager::PurchaseBasket->get_all( query => [ cleared => 'F' ],  with_objects => [ 'parts', 'parts.makemodels' ]);
42
  $self->_setup_show_basket_action_bar;
43
  $self->render('dispositionsmanager/show_purchase_basket', PARTS => $basket, title => "Purchase basket" );
44
}
45

  
46
sub action_transfer_to_purchase_order {
47
  my ( $self ) = @_;
48
  require SL::DB::Order;
49
  require SL::DB::OrderItem;
50
  require SL::DB::Part;
51
  require SL::DB::Vendor;
52
  my @error_report;
53

  
54
  my $v_id =  $::form->{vendor_id}->[0] ;
55
  my $vendor = SL::DB::Manager::Vendor->find_by(id => $v_id);
56
  die "Can't find vendor" unless $vendor;
57
  my $employee = SL::DB::Manager::Employee->current;
58
  die "Can't find employee" unless $employee;
59
  my $basket_items= SL::DB::Manager::PurchaseBasket->get_all( query => [ id => \@{ $::form->{id} } ] );
60

  
61
  my @items = map{
62
    my $part = SL::DB::Manager::Part->get_first( query => [ id => $_->parts_id,]);
63
    my $mm = SL::DB::Manager::MakeModel->get_first( query => [ make => $vendor->id, parts_id => $_->parts_id] );
64

  
65
    unless($part){
66
      push @error_report, t8('Part with id: #1 not found', $_->parts_id);
67
    }else{
68
      my $current_order_item = SL::DB::OrderItem->new(
69
        parts_id            => $_->parts_id,
70
        description         => $part->description,
71
        qty                 => $_->qty,
72
        sellprice           => $mm->lastcost,
73
        unit                => $part->unit,
74
        position            => $_->id,
75
        active_price_source => "makemodel/" . $mm->id,
76
      );
77
    }
78
  }@{$basket_items};
79

  
80
  my $order = SL::DB::Order->new(
81
    vendor_id               => $vendor->id,
82
    orderitems              => [ @items ],
83
    employee_id             => $employee->id,
84
    intnotes                => $vendor->notes,
85
    salesman_id             => $employee->id,
86
    payment_id              => $vendor->payment_id,
87
    taxzone_id              => $vendor->taxzone_id,
88
    currency_id             => $vendor->currency_id,
89
    transdate               => DateTime->today_local
90
  );
91

  
92
  $order->db->with_transaction( sub {
93
    $order->calculate_prices_and_taxes;
94
    $order->save;
95

  
96
    my $snumbers = "ordernumber_" . $order->ordnumber;
97
    SL::DB::History->new(
98
                      trans_id    => $order->id,
99
                      snumbers    => $snumbers,
100
                      employee_id => SL::DB::Manager::Employee->current->id,
101
                      addition    => 'SAVED',
102
                      what_done   => 'PurchaseBaket->Order',
103
                    )->save();
104
    foreach my $item(@{ $order->orderitems }){
105
      $item->parse_custom_variable_values->save;
106
      $item->{custom_variables} = \@{ $item->cvars_by_config };
107
      $item->save;
108
    }
109
    SL::DB::Manager::PurchaseBasket->delete_all( where => [ id => \@{ $::form->{id} }]);
110
  }) || die $order->db->error;
111
  $self->redirect_to(controller => "oe.pl", action => 'edit', type => 'purchase_order', vc => 'vendor', id => $order->id);
112

  
113
}
114

  
115
sub _get_parts {
116
  my ($self) = @_;
117

  
118
  my $query = <<SQL;
119
SELECT *
120
  FROM parts
121
 WHERE onhand <= rop
122
   AND rop != 0
123
   AND id NOT IN( SELECT parts_id FROM purchase_basket ) AND NOT obsolete
124
SQL
125

  
126
  return SL::DB::Manager::Part->get_objects_from_sql( sql => $query );
127
};
128

  
129

  
130
sub _setup_list_action_bar {
131
  my ($self) = @_;
132
  for my $bar ($::request->layout->get('actionbar')) {
133
    $bar->add(
134
      action => [
135
        t8('Action'),
136
        submit   => [ '#form', { action => "DispositionsManager/add_to_purchase_basket" } ],
137
        tooltip  => t8('Add to purchase basket'),
138
      ],
139
    );
140
  }
141
}
142

  
143
sub _setup_show_basket_action_bar {
144
  my ($self) = @_;
145
  for my $bar ($::request->layout->get('actionbar')) {
146
    $bar->add(
147
      action => [
148
        t8('Reload'),
149
        submit   => [ '#purchasebasket', { action => "DispositionsManager/show_basket" } ],
150
      ],
151
      action => [
152
        t8('Action'),
153
        call   => [ 'kivi.DispositionsManager.create_order' ],
154
        tooltip  => t8('Create purchase order'),
155
      ],
156
    );
157
  }
158
}
159
1;
160

  
161
__END__
162

  
163
=encoding utf-8
164

  
165
=head1 NAME
166

  
167
SL::Controller::DispositionsManager Controller to manage purchase orders for parts
168

  
169
=head1 DESCRIPTION
170

  
171
This controller shows a list of parts using the filter minimum stock (rop).
172
From this list it is possible to put parts in a purchase basket to order.
173
It's also possible to put parts from the parts edit form in the purchase basket.
174

  
175
From the purchase basket you can create a purchase order by using the filter vendor.
176
The quantity to order will be prefilled by the value min_qty_to_order from parts or
177
makemodel(vendor_parts) or default to qty 1.
178

  
179
Tables:
180

  
181
=over 2
182

  
183
=item purchase_basket
184

  
185
=back
186

  
187
Dependencies:
188

  
189
=over 2
190

  
191
=item parts
192

  
193
=item makemodels
194

  
195

  
196
=back
197

  
198
=head1 URL ACTIONS
199

  
200
=over 4
201

  
202
=item C<action_list_parts>
203

  
204
List the parts by the filter min stock (rop) and not in an open purchase order.
205

  
206
=item C<action_add_to_purchase_basket>
207

  
208
Adds one or more parts to the purchase basket.
209

  
210
=item C<action_show_basket>
211

  
212
Shows a list with parts which are in the basket.
213
This list can be filtered by vendor. Then you can create a purchase order.
214

  
215
=item C<action_transfer_to_purchase_order>
216

  
217
Transfers the marked and by vendor filtered parts to a purchase order.
218
Deletes the entry in the purchase basket.
219

  
220
=back
221

  
222
=head1 BUGS
223

  
224
None yet. :)
225

  
226
=head1 AUTHOR
227

  
228
W. Hahn E<lt>wh@futureworldsearch.netE<gt>
229

  
230
=cut
SL/DB/Helper/ALL.pm
107 107
use SL::DB::ProjectRole;
108 108
use SL::DB::ProjectStatus;
109 109
use SL::DB::ProjectType;
110
use SL::DB::PurchaseBasketItem;
110 111
use SL::DB::PurchaseInvoice;
111 112
use SL::DB::Reclamation;
112 113
use SL::DB::ReclamationItem;
SL/DB/Helper/Mappings.pm
187 187
  project_statuses               => 'project_status',
188 188
  project_types                  => 'project_type',
189 189
  purchase_basket                => 'purchase_basket',
190
  purchase_basket_items          => 'purchase_basket_item',
190 191
  reclamations                   => 'Reclamation',
191 192
  reclamation_items              => 'ReclamationItem',
192 193
  reclamation_reasons            => 'ReclamationReason',
SL/DB/Manager/PurchaseBasket.pm
1
# This file has been auto-generated only because it didn't exist.
2
# Feel free to modify it at will; it will not be overwritten automatically.
3

  
4
package SL::DB::Manager::PurchaseBasket;
5

  
6
use strict;
7

  
8
use parent qw(SL::DB::Helper::Manager);
9

  
10
sub object_class { 'SL::DB::PurchaseBasket' }
11

  
12
__PACKAGE__->make_manager_methods;
13

  
14
1;
SL/DB/Manager/PurchaseBasketItem.pm
1
# This file has been auto-generated only because it didn't exist.
2
# Feel free to modify it at will; it will not be overwritten automatically.
3

  
4
package SL::DB::Manager::PurchaseBasketItem;
5

  
6
use strict;
7

  
8
use parent qw(SL::DB::Helper::Manager);
9

  
10
sub object_class { 'SL::DB::PurchaseBasketItem' }
11

  
12
__PACKAGE__->make_manager_methods;
13

  
14
1;
SL/DB/MetaSetup/PurchaseBasket.pm
1
# This file has been auto-generated. Do not modify it; it will be overwritten
2
# by rose_auto_create_model.pl automatically.
3
package SL::DB::PurchaseBasket;
4

  
5
use strict;
6

  
7
use parent qw(SL::DB::Object);
8

  
9
__PACKAGE__->meta->table('purchase_basket');
10

  
11
__PACKAGE__->meta->columns(
12
  cleared     => { type => 'boolean', default => 'false', not_null => 1 },
13
  description => { type => 'text' },
14
  id          => { type => 'serial', not_null => 1 },
15
  parts_id    => { type => 'integer' },
16
  qty         => { type => 'numeric', precision => 15, scale => 5 },
17
);
18

  
19
__PACKAGE__->meta->primary_key_columns([ 'id' ]);
20

  
21
__PACKAGE__->meta->foreign_keys(
22
  parts => {
23
    class       => 'SL::DB::Part',
24
    key_columns => { parts_id => 'id' },
25
  },
26
);
27

  
28
1;
29
;
SL/DB/MetaSetup/PurchaseBasketItem.pm
1
# This file has been auto-generated. Do not modify it; it will be overwritten
2
# by rose_auto_create_model.pl automatically.
3
package SL::DB::PurchaseBasketItem;
4

  
5
use strict;
6

  
7
use parent qw(SL::DB::Object);
8

  
9
__PACKAGE__->meta->table('purchase_basket_items');
10

  
11
__PACKAGE__->meta->columns(
12
  cleared     => { type => 'boolean', default => 'false', not_null => 1 },
13
  description => { type => 'text' },
14
  id          => { type => 'serial', not_null => 1 },
15
  parts_id    => { type => 'integer' },
16
  qty         => { type => 'numeric', precision => 15, scale => 5 },
17
);
18

  
19
__PACKAGE__->meta->primary_key_columns([ 'id' ]);
20

  
21
__PACKAGE__->meta->foreign_keys(
22
  part => {
23
    class       => 'SL::DB::Part',
24
    key_columns => { parts_id => 'id' },
25
  },
26
);
27

  
28
1;
29
;
SL/DB/PurchaseBasket.pm
1
# This file has been auto-generated only because it didn't exist.
2
# Feel free to modify it at will; it will not be overwritten automatically.
3

  
4
package SL::DB::PurchaseBasket;
5

  
6
use strict;
7

  
8
use SL::DB::MetaSetup::PurchaseBasket;
9
use SL::DB::Manager::PurchaseBasket;
10

  
11
__PACKAGE__->meta->initialize;
12

  
13

  
14
1;
SL/DB/PurchaseBasketItem.pm
1
# This file has been auto-generated only because it didn't exist.
2
# Feel free to modify it at will; it will not be overwritten automatically.
3

  
4
package SL::DB::PurchaseBasketItem;
5

  
6
use strict;
7

  
8
use SL::DB::MetaSetup::PurchaseBasketItem;
9
use SL::DB::Manager::PurchaseBasketItem;
10

  
11
__PACKAGE__->meta->initialize;
12

  
13
1;
js/kivi.DispositionManager.js
1
namespace('kivi.DispositionManager', function(ns) {
2
  ns.sort_vendors = function() {
3
    $("table tr").each(function(index) {
4
      if ( index !== 0 ) {
5
        $row = $(this);
6
        //alert( $row.find("select[name='vendor_id[]']").val() + '!=' + $('#cv_id').val());
7
        if( $row.find("select[name='vendor_id[]']").val() != $('#cv_id').val()) {
8
          $row.remove();
9
        }
10
      }
11
    });
12
  }
13
  ns.create_order = function() {
14
    var data = $('#purchasebasket').serializeArray();
15
    data.push({ name: 'action', value: 'DispositionManager/transfer_to_purchase_order' });
16

  
17
    $.post("controller.pl", data, kivi.eval_json_result);
18
  }
19
});
js/kivi.DispositionsManager.js
1
namespace('kivi.DispositionsManager', function(ns) {
2
  ns.sort_vendors = function() {
3
    $("table tr").each(function(index) {
4
      if ( index !== 0 ) {
5
        $row = $(this);
6
        //alert( $row.find("select[name='vendor_id[]']").val() + '!=' + $('#cv_id').val());
7
        if( $row.find("select[name='vendor_id[]']").val() != $('#cv_id').val()) {
8
          $row.remove();
9
        }
10
      }
11
    });
12
  }
13
  ns.create_order = function() {
14
    var data = $('#purchasebasket').serializeArray();
15
    data.push({ name: 'action', value: 'DispositionsManager/transfer_to_purchase_order' });
16

  
17
    $.post("controller.pl", data, kivi.eval_json_result);
18
  }
19
});
locale/de/all
1212 1212
  'Display in basic data tab'   => 'Im Reiter Basisdaten anzeigen',
1213 1213
  'Display options'             => 'Anzeigeoptionen',
1214 1214
  'Displayable Name Preferences' => 'Einstellungen für Anzeigenamen',
1215
  'Disposition manager'         => 'Dispositionsmanager',
1215 1216
  'Do not change the tax rate of taxkey 0.' => 'Ändern Sie nicht den Steuersatz vom Steuerschlüssel 0.',
1216 1217
  'Do not check for duplicates' => 'Nicht nach Dubletten suchen',
1217 1218
  'Do not create Factur-X/ZUGFeRD invoices' => 'Keine Factur-X-/ZUGFeRD-Rechnungen erzeugen',
menus/user/10-disposition-manager.yaml
1
---
2
- parent: ap
3
  id: ap_disp_manager
4
  name: Disposition manager
5
  icon: rfq_add
6
  order: 150
7
  access: request_quotation_edit
8
- parent: ap_disp_manager
9
  id: ap_disp_manager_onhand
10
  name: List short onhand
11
  order: 160
12
  params:
13
    action: DispositionManager/list_parts
14
- parent: ap_disp_manager
15
  id: ap_disp_manager_basket
16
  name: Show purchase basket
17
  order: 170
18
  params:
19
    action: DispositionManager/show_basket
menus/user/10-dispositionsmanager.yaml
1
---
2
- parent: ap
3
  id: ap_disp_manager
4
  name: Dispositionsmanager
5
  icon: rfq_add
6
  order: 150
7
  access: request_quotation_edit
8
- parent: ap_disp_manager
9
  id: ap_disp_manager_onhand
10
  name: List short onhand
11
  order: 160
12
  params:
13
    action: DispositionsManager/list_parts
14
- parent: ap_disp_manager
15
  id: ap_disp_manager_basket
16
  name: Show purchase basket
17
  order: 170
18
  params:
19
    action: DispositionsManager/show_basket
scripts/rose_auto_create_model.pl
83 83

  
84 84
    assembly                  => { parts_id => 'part', id => 'assembly_part' },
85 85
    assortment_items          => { parts_id => 'part' },
86
    purchase_basket_items     => { parts_id => 'part' },
86 87

  
87 88
    dunning                   => { trans_id => 'invoice', fee_interest_ar_id => 'fee_interest_invoice' },
88 89
  },
sql/Pg-upgrade2/purchase_basket.sql
1 1
-- @tag: purchase_basket
2
-- @description: Tabelle für den Dispostionsmanager
2
-- @description: Tabelle für den Dispositionsmanager
3 3
-- @depends: release_3_5_1
4 4
-- @ignore: 0
5 5

  
6
CREATE TABLE purchase_basket (
6
CREATE TABLE purchase_basket_items (
7 7
  id SERIAL PRIMARY KEY,
8 8
  parts_id INTEGER REFERENCES parts(id),
9 9
  qty NUMERIC(15,5),
t/controllers/disposition_manager/disposition_manager.t
1 1
use strict;
2
use Test::More;
2
use Test::More tests => 9;
3 3

  
4 4
use lib 't';
5 5
use Support::TestSetup;
......
7 7
use SL::DB::Part;
8 8
use SL::DB::Inventory;
9 9
use SL::DB::MakeModel;
10
use SL::DB::PurchaseBasket;
11
use SL::Controller::DispositionsManager;
10
use SL::DB::PurchaseBasketItem;
11
use SL::Controller::DispositionManager;
12 12
use DateTime;
13 13
use Data::Dumper;
14 14
use SL::Dev::Part qw(new_part);
......
25 25
my $vendor = new_vendor()->save;
26 26

  
27 27
my $part1 = new_part(
28
  partnumber   => 'Testpart 1 rop no stock',
28
  partnumber   => 'TP 1',
29
  description  => 'Testpart 1 rop no stock',
30
  sellprice    => 5,
31
  lastcost     => 3,
29 32
  rop          => 20,
30 33
  warehouse_id => $wh->id,
31 34
  bin_id       => $bin->id,
......
33 36
)->save;
34 37

  
35 38
my $part2 = new_part(
36
  partnumber   => 'Testpart 2 rop with stock',
39
  partnumber   => 'TP 2',
40
  description  => 'Testpart 2 norop',
37 41
  rop          => 60,
38 42
)->save;
39
set_stock(part => $part2, bin_id => $bin->id, qty => 10);
43
set_stock(part => $part2, bin_id => $bin->id, qty => 80);
44

  
45
for my $i (1 .. 10) {
46
  my $part = new_part(
47
    partnumber   => "TPO $i",
48
    description  => "Testpart onhand $i",
49
    rop          => 50,
50
    sellprice    => 5,
51
    lastcost     => 3,
52
    warehouse_id => $wh->id,
53
    bin_id       => $bin->id,
54
    makemodels   => [ _create_makemodel_for_vendor(vendor => $vendor) ],
55
  )->save;
56
  set_stock(part => $part, bin_id => $bin->id, qty => ($i * 10));
57
}
40 58

  
41
my $part3 = new_part(
42
  partnumber   => 'Testpart 3 norop',
43
  rop          => 60,
44
)->save;
45
set_stock(part => $part3, bin_id => $bin->id, qty => 80);
59
my $controller = SL::Controller::DispositionManager->new();
60
my $reorder_parts = $controller->_get_parts;
61
is(scalar @{$reorder_parts}, 6, "found 6 parts where onhand <= rop");
62

  
63
# die; # die here if you want to test making basket manually
64

  
65
note('creating purchase basket items');
66
$::form = Support::TestSetup->create_new_form;
67
$::form->{ids} = [ map { $_->id } @{$reorder_parts} ];
68

  
69
# call action_add_to_purchase_basket while redirecting rendered HTML output
70
my $output;
71
open(my $outputFH, '>', \$output) or die;
72
my $oldFH = select $outputFH;
73
$controller->action_add_to_purchase_basket;
74
select $oldFH;
75
close $outputFH;
76

  
77
is(SL::DB::Manager::PurchaseBasketItem->get_all_count(), 6, "6 items in purchase basket ok");
78

  
79
# die; # die here if you want to test creating purchase orders manually
80

  
81
note('making purchase order from purchase basket items');
82
my $purchase_basket_items = SL::DB::Manager::PurchaseBasketItem->get_all;
83
$::form = Support::TestSetup->create_new_form;
84
$::form->{ids}        = [ map { $_->id       } @{ $purchase_basket_items } ];
85
$::form->{vendor_ids} = [ map { $vendor->id  } @{ $purchase_basket_items } ];
86

  
87
$controller->action_transfer_to_purchase_order;
88

  
89
is(SL::DB::Manager::Order->get_all_count( where => [ SL::DB::Manager::Order->type_filter('purchase_order') ] ), 1, "1 purchase order created ok");
90
is(SL::DB::Manager::PurchaseBasketItem->get_all_count(), 0, "purchase basket empty after purchase order was created");
46 91

  
47
my $controller = SL::Controller::DispositionsManager->new();
48
my $parts = $controller->_get_parts;
92
my $purchase_order = SL::DB::Manager::Order->get_first();
49 93

  
50
is(scalar @{$parts}, 2, "found 2 parts that are below rop");
94
is( scalar @{$purchase_order->items}, 6, "Purchase order has 6 item ok");
95
my $first_item = $purchase_order->items_sorted->[0];
96
is( $first_item->parts_id, $part1->id, "Purchase order: first item is part1");
97
is( $first_item->qty, 1, "Purchase order: first item has qty 1");
98
cmp_ok( $purchase_order->netamount, '==', 12, "Purchase order: netamount ok");
99
is( $first_item->active_price_source, 'makemodel/' . $first_item->part->makemodels->[0]->id, "Purchase order: first item has correct active_price_source");
51 100

  
52 101
done_testing();
53 102
# clear_up();
......
56 105
  my %params = @_;
57 106
  SL::DB::Manager::Inventory->delete_all(all => 1);
58 107
  SL::DB::Manager::Order->delete_all(all => 1);
59
  SL::DB::Manager::PurchaseBasket->delete_all(all => 1);
108
  SL::DB::Manager::PurchaseBasketItem->delete_all(all => 1);
60 109
  SL::DB::Manager::MakeModel->delete_all(all => 1);
61 110
  SL::DB::Manager::Part->delete_all(all => 1);
62 111
  SL::DB::Manager::Vendor->delete_all(all => 1);
......
73 122
  my $mm = SL::DB::MakeModel->new(make          => $vendor->id,
74 123
                                  model         => '',
75 124
                                  min_order_qty => '1',
76
                                  lastcost      => 0,
125
                                  lastcost      => 2,
77 126
                                  sortorder     => 1,
78 127
                                 );
79 128
  $mm->assign_attributes( %params );
templates/webpages/disposition_manager/list_parts.html
1
[%- USE HTML -%][%- USE LxERP -%][%- USE L -%][%- USE T8 -%]
2
[% USE Dumper %]
3
[%- INCLUDE 'common/flash.html' %]
4
<h1>[% title %]</h1>
5
<hr>
6
<h2>[% 'Short onhand' | $T8 %]</h2>
7
<form id="form">
8
  <table>
9
    <thead>
10
     <tr class="listheading">
11
       <th>[% L.checkbox_tag('check_all') %][% 'Purchase basket' | $T8 %] </th>
12
       <th>[% 'Partnumber'                                      | $T8 %] </th>
13
       <th>[% 'Description'                                     | $T8 %] </th>
14
       <th>[% 'Onhand'                                          | $T8 %] </th>
15
       <th>[% 'Rop'                                             | $T8 %] </th>
16
       <th>[% 'Minimum order quantity'                          | $T8 %] </th>
17
     </tr>
18
    </thead>
19
    [% FOREACH part = PARTS %]
20
      [% IF !part.get_ordered_qty(part.id) %]
21
      <tr class="listrow">
22
        <td>[% IF part.makemodels.size %][% L.checkbox_tag('ids[+]', checked = '1', value=part.id) %][% ELSE %][% 'No Vendor' | $T8 %][% END %]</td>
23
        <td>[% HTML.escape(part.partnumber) %] </td>
24
        <td>[% HTML.escape(part.description) %]</td>
25
        <td class="numeric">[% part.onhand_as_number %]</td>
26
        <td class="numeric">[% part.rop_as_number %]           </td>
27
        <td class="numeric">[% part.min_order_qty_as_number %] </td>
28
      </tr>
29
      [% END %]
30
    [% END %]
31
  </table>
32
</form>
33
<hr>
34
<h2>[% 'Short onhand Ordered' | $T8 %]</h2>
35
<table>
36
  <thead>
37
   <tr class="listheading">
38
     <th>[% 'Partnumber'       | $T8 %] </th>
39
     <th>[% 'Description'      | $T8 %] </th>
40
     <th>[% 'onhand'           | $T8 %] </th>
41
     <th>[% 'rop'              | $T8 %] </th>
42
     <th>[% 'Ordered purchase' | $T8 %] </th>
43
   </tr>
44
  </thead>
45
  [% FOREACH part = PARTS %]
46
    [% IF part.get_ordered_qty(part.id) %]
47
    <tr class="listrow">
48
      <td>[% HTML.escape(part.partnumber) %]  </td>
49
      <td>[% HTML.escape(part.description) %] </td>
50
      <td class="numeric">[% part.onhand_as_number %]         </td>
51
      <td class="numeric">[% part.rop_as_number %]            </td>
52
      <td class="numeric">[% part.get_ordered_qty(part.id) %] </td>
53
    </tr>
54
    [% END %]
55
  [% END %]
56
</table>
57
<script type="text/javascript">
58
<!--
59

  
60
  kivi.DispositionManager.sort_vendors();
61
$(function() {
62
  alert('hallo9');
63
  $('#check_all').checkall('INPUT[name^="id"]');
64
  kivi.DispositionManager.sort_vendors();
65
});
66
-->
67
</script>
templates/webpages/disposition_manager/show_purchase_basket.html
1
[%- USE HTML -%][%- USE LxERP -%][%- USE L -%][%- USE T8 -%][% USE P %]
2
[% USE Dumper %]
3
[%- INCLUDE 'common/flash.html' %]
4
<h1>[% title %]</h1>
5
[% # Dumper.dump_html(BASKET_ITEMS) %]
6
<form id="purchasebasket" style="margin:1em;">
7
[% P.customer_vendor.picker('vendor_id2', '', type='vendor', fat_set_item=1) %]<br>
8
<div>id  from change: <span id='change3'></span></div>
9
[% L.hidden_tag('cv_id', '') %]
10
  <table id="baskettable" width="100%">
11
    <thead>
12
     <tr class="listheading">
13
       <th>[% L.checkbox_tag('check_all') %][% 'Purchase basket' | $T8 %] </th>
14
       <th>[% 'Partnumber'                                      | $T8 %] </th>
15
       <th>[% 'Description'                                     | $T8 %] </th>
16
       <th>[% 'Onhand'                                          | $T8 %] </th>
17
       <th>[% 'Rop'                                             | $T8 %] </th>
18
       <th>[% 'Order quantity'                                  | $T8 %] </th>
19
       <th>[% 'Vendor'                                          | $T8 %] </th>
20
     </tr>
21
    </thead>
22
    <tbody>
23
    [% FOREACH basket_item = BASKET_ITEMS %]
24
    [% SET select_size = basket_item.part.vendor_dropdown.size %]
25
      [% IF !basket_item.part.get_ordered_qty(part.id) %]
26
      <tr class="listrow">
27
        <td>[% L.checkbox_tag('ids[+]', checked = '1',  value=basket_item.id) %]</td>
28
        <td>[% HTML.escape(basket_item.part.partnumber) %] </td>
29
        <td>[% HTML.escape(basket_item.part.description) %]</td>
30
        <td class="numeric">[% basket_item.part.onhand_as_number %]        </td>
31
        <td class="numeric">[% basket_item.part.rop_as_number %]           </td>
32
        <td class="numeric">[% basket_item.qty_as_number %] </td>
33
        <td>[% L.select_tag('vendor_ids[]', basket_item.part.vendor_dropdown, value_key = 'value', title_key = 'title', default = basket_item.part.makemodels.item(0).make, size = select_size, style='width: 350px;' ) %]</td>
34
      </tr>
35
      [% END %]
36
    [% END %]
37
    </tbody>
38
  </table>
39
</form>
40
<hr>
41
<script type="text/javascript">
42
<!--
43

  
44
 $('#vendor_id2').change(function() { $('#change3').html($('#vendor_id2').val()) })
45
 $('#vendor_id2').on('set_item:CustomerVendorPicker', function(e,o) {
46
   $('#cv_id').val(o.id)
47
   kivi.DispositionManager.sort_vendors();
48
 })
49
$(function() {
50
  $('#check_all').checkall('INPUT[name^="id"]');
51
});
52
-->
53
</script>
templates/webpages/dispositionsmanager/list_parts.html
1
[%- USE HTML -%][%- USE LxERP -%][%- USE L -%][%- USE T8 -%]
2
[% USE Dumper %]
3
[%- INCLUDE 'common/flash.html' %]
4
<h1>[% title %]</h1>
5
<hr>
6
<h2>[% 'Short onhand' | $T8 %]</h2>
7
<form id="form">
8
  <table>
9
    <thead>
10
     <tr class="listheading">
11
       <th>[% L.checkbox_tag('check_all') %][% 'Purchasebasket' | $T8 %] </th>
12
       <th>[% 'Partnumber'                                      | $T8 %] </th>
13
       <th>[% 'Description'                                     | $T8 %] </th>
14
       <th>[% 'Onhand'                                          | $T8 %] </th>
15
       <th>[% 'Rop'                                             | $T8 %] </th>
16
       <th>[% 'Minimum order quantity'                          | $T8 %] </th>
17
     </tr>
18
    </thead>
19
    [% FOREACH part = PARTS %]
20
      [% IF !part.get_ordered_qty(part.id) %]
21
      <tr class="listrow">
22
        <td>[% IF part.makemodels.size %][% L.checkbox_tag('id[]', checked = '1',  value=part.id) %][% ELSE %][% 'No Vendor' | $T8 %][% END %]</td>
23
        <td>[% HTML.escape(part.partnumber) %] </td>
24
        <td>[% HTML.escape(part.description) %]</td>
25
        <td class="numeric">[% part.onhand_as_number %]</td>
26
        <td class="numeric">[% part.rop_as_number %]           </td>
27
        <td class="numeric">[% part.min_order_qty_as_number %] </td>
28
      </tr>
29
      [% END %]
30
    [% END %]
31
  </table>
32
</form>
33
<hr>
34
<h2>[% 'Short onhand Ordered' | $T8 %]</h2>
35
<table>
36
  <thead>
37
   <tr class="listheading">
38
     <th>[% 'Partnumber'       | $T8 %] </th>
39
     <th>[% 'Description'      | $T8 %] </th>
40
     <th>[% 'onhand'           | $T8 %] </th>
41
     <th>[% 'rop'              | $T8 %] </th>
42
     <th>[% 'Ordered purchase' | $T8 %] </th>
43
   </tr>
44
  </thead>
45
  [% FOREACH part = PARTS %]
46
    [% IF part.get_ordered_qty(part.id) %]
47
    <tr class="listrow">
48
      <td>[% HTML.escape(part.partnumber) %]  </td>
49
      <td>[% HTML.escape(part.description) %] </td>
50
      <td>[% part.onhand_as_number %]         </td>
51
      <td>[% part.rop_as_number %]            </td>
52
      <td>[% part.get_ordered_qty(part.id) %] </td>
53
    </tr>
54
    [% END %]
55
  [% END %]
56
</table>
57
<script type="text/javascript">
58
<!--
59

  
60
  kivi.DispositionsManager.sort_vendors();
61
$(function() {
62
  alert('hallo9');
63
  $('#check_all').checkall('INPUT[name^="id"]');
64
  kivi.DispositionsManager.sort_vendors();
65
});
66
-->
67
</script>
templates/webpages/dispositionsmanager/show_purchase_basket.html
1
[%- USE HTML -%][%- USE LxERP -%][%- USE L -%][%- USE T8 -%][% USE P %]
2
[% USE Dumper %]
3
[%- INCLUDE 'common/flash.html' %]
4
<h1>[% title %]</h1>
5
[% # Dumper.dump_html(PARTS) %]
6
<form id="purchasebasket" style="margin:1em;">
7
[% P.customer_vendor.picker('vendor_id2', '', type='vendor', fat_set_item=1) %]<br>
8
<div>id  from change: <span id='change3'></span></div>
9
[% L.hidden_tag('cv_id', '') %]
10
  <table id="baskettable" width="100%">
11
    <thead>
12
     <tr class="listheading">
13
       <th>[% L.checkbox_tag('check_all') %][% 'Purchasebasket' | $T8 %] </th>
14
       <th>[% 'Partnumber'                                      | $T8 %] </th>
15
       <th>[% 'Description'                                     | $T8 %] </th>
16
       <th>[% 'Onhand'                                          | $T8 %] </th>
17
       <th>[% 'Rop'                                             | $T8 %] </th>
18
       <th>[% 'Order quantity'                                  | $T8 %] </th>
19
       <th>[% 'Vendor'                                          | $T8 %] </th>
20
     </tr>
21
    </thead>
22
    <tbody>
23
    [% FOREACH part = PARTS %]
24
    [% SET select_size = part.parts.vendor_dropdown.size %]
25
      [% IF !part.part.get_ordered_qty(part.id) %]
26
      <tr class="listrow">
27
        <td>[% L.checkbox_tag('id[]', checked = '1',  value=part.id) %]</td>
28
        <td>[% HTML.escape(part.parts.partnumber) %] </td>
29
        <td>[% HTML.escape(part.parts.description) %]</td>
30
        <td class="numeric">[% part.parts.onhand_as_number %]        </td>
31
        <td class="numeric">[% part.parts.rop_as_number %]           </td>
32
        <td class="numeric">[% part.qty_as_number %] </td>
33
        <td>[% L.select_tag('vendor_id[]', part.parts.vendor_dropdown, value_key = 'value', title_key = 'title', default = part.parts.makemodels.item(0).make, size = select_size, style='width: 350px;' ) %]</td>
34
      </tr>
35
      [% END %]
36
    [% END %]
37
    </tbody>
38
  </table>
39
</form>
40
<hr>
41
<script type="text/javascript">
42
<!--
43

  
44
 $('#vendor_id2').change(function() { $('#change3').html($('#vendor_id2').val()) })
45
 $('#vendor_id2').on('set_item:CustomerVendorPicker', function(e,o) {
46
   $('#cv_id').val(o.id)
47
   kivi.DispositionsManager.sort_vendors();
48
 })
49
$(function() {
50
  $('#check_all').checkall('INPUT[name^="id"]');
51
});
52
-->
53
</script>

Auch abrufbar als: Unified diff