Sammelcommit Bankerweiterung und Skonto
[kivitendo-erp.git] / SL / DB / DeliveryOrder.pm
1 package SL::DB::DeliveryOrder;
2
3 use strict;
4
5 use Carp;
6
7 use Rose::DB::Object::Helpers ();
8
9 use SL::DB::MetaSetup::DeliveryOrder;
10 use SL::DB::Manager::DeliveryOrder;
11 use SL::DB::Helper::AttrHTML;
12 use SL::DB::Helper::FlattenToForm;
13 use SL::DB::Helper::LinkedRecords;
14 use SL::DB::Helper::TransNumberGenerator;
15
16 use List::Util qw(first);
17
18 __PACKAGE__->meta->add_relationship(orderitems => { type         => 'one to many',
19                                                     class        => 'SL::DB::DeliveryOrderItem',
20                                                     column_map   => { id => 'delivery_order_id' },
21                                                     manager_args => { with_objects => [ 'part' ] }
22                                                   },
23                                     custom_shipto => {
24                                       type        => 'one to one',
25                                       class       => 'SL::DB::Shipto',
26                                       column_map  => { id => 'trans_id' },
27                                       query_args  => [ module => 'DO' ],
28                                     },
29                                    );
30
31 __PACKAGE__->meta->initialize;
32
33 __PACKAGE__->attr_html('notes');
34
35 __PACKAGE__->before_save('_before_save_set_donumber');
36
37 # hooks
38
39 sub _before_save_set_donumber {
40   my ($self) = @_;
41
42   $self->create_trans_number if !$self->donumber;
43
44   return 1;
45 }
46
47 # methods
48
49 sub items { goto &orderitems; }
50 sub add_items { goto &add_orderitems; }
51
52 sub items_sorted {
53   my ($self) = @_;
54
55   return [ sort {$a->position <=> $b->position } @{ $self->items } ];
56 }
57
58 sub sales_order {
59   my $self   = shift;
60   my %params = @_;
61
62
63   require SL::DB::Order;
64   my $orders = SL::DB::Manager::Order->get_all(
65     query => [
66       ordnumber => $self->ordnumber,
67       @{ $params{query} || [] },
68     ],
69   );
70
71   return first { $_->is_type('sales_order') } @{ $orders };
72 }
73
74 sub type {
75   return shift->customer_id ? 'sales_delivery_order' : 'purchase_delivery_order';
76 }
77
78 sub displayable_state {
79   my ($self) = @_;
80
81   return join '; ',
82     ($self->closed    ? $::locale->text('closed')    : $::locale->text('open')),
83     ($self->delivered ? $::locale->text('delivered') : $::locale->text('not delivered'));
84 }
85
86 sub date {
87   goto &transdate;
88 }
89
90 sub _clone_orderitem_cvar {
91   my ($cvar) = @_;
92
93   my $cloned = Rose::DB::Object::Helpers::clone_and_reset($_);
94   $cloned->sub_module('delivery_order_items');
95
96   return $cloned;
97 }
98
99 sub new_from {
100   my ($class, $source, %params) = @_;
101
102   croak("Unsupported source object type '" . ref($source) . "'") unless ref($source) eq 'SL::DB::Order';
103
104   my ($item_parent_id_column, $item_parent_column);
105
106   if (ref($source) eq 'SL::DB::Order') {
107     $item_parent_id_column = 'trans_id';
108     $item_parent_column    = 'order';
109   }
110
111   my $terms = $source->can('payment_id') && $source->payment_id ? $source->payment_terms->terms_netto : 0;
112
113   my %args = ( map({ ( $_ => $source->$_ ) } qw(cp_id currency_id customer_id cusordnumber department_id employee_id globalproject_id intnotes language_id notes
114                                                 ordnumber reqdate salesman_id shippingpoint shipvia taxincluded taxzone_id transaction_description vendor_id
115                                              )),
116                closed    => 0,
117                is_sales  => !!$source->customer_id,
118                delivered => 0,
119                terms     => $terms,
120                transdate => DateTime->today_local,
121             );
122
123   # Custom shipto addresses (the ones specific to the sales/purchase
124   # record and not to the customer/vendor) are only linked from
125   # shipto -> delivery_orders. Meaning delivery_orders.shipto_id
126   # will not be filled in that case. Therefore we have to return the
127   # new shipto object as a separate object so that the caller can
128   # save it, too.
129   my $custom_shipto;
130   if (!$source->shipto_id && $source->id) {
131     my $old = $source->custom_shipto;
132     if ($old) {
133       $custom_shipto = SL::DB::Shipto->new(
134         map  { +($_ => $old->$_) }
135         grep { !m{^ (?: itime | mtime | shipto_id | trans_id ) $}x }
136         map  { $_->name }
137         @{ $old->meta->columns }
138       );
139       $custom_shipto->module('DO');
140     }
141
142   } else {
143     $args{shipto_id} = $source->shipto_id;
144   }
145
146   my $delivery_order = $class->new(%args);
147   $delivery_order->assign_attributes(%{ $params{attributes} }) if $params{attributes};
148   my $items          = delete($params{items}) || $source->items_sorted;
149   my %item_parents;
150
151   my @items = map {
152     my $source_item      = $_;
153     my $source_item_id   = $_->$item_parent_id_column;
154     my @custom_variables = map { _clone_orderitem_cvar($_) } @{ $source_item->custom_variables };
155
156     $item_parents{$source_item_id} ||= $source_item->$item_parent_column;
157     my $item_parent                  = $item_parents{$source_item_id};
158
159     SL::DB::DeliveryOrderItem->new(map({ ( $_ => $source_item->$_ ) }
160                                          qw(base_qty cusordnumber description discount lastcost longdescription marge_price_factor parts_id price_factor price_factor_id
161                                             project_id qty reqdate sellprice serialnumber transdate unit active_discount_source active_price_source
162                                          )),
163                                    custom_variables => \@custom_variables,
164                                    ordnumber        => ref($item_parent) eq 'SL::DB::Order' ? $item_parent->ordnumber : $source_item->ordnumber,
165                                  );
166
167   } @{ $items };
168
169   @items = grep { $_->qty * 1 } @items if $params{skip_items_zero_qty};
170   @items = grep { $_->qty >=0 } @items if $params{skip_items_negative_qty};
171
172   $delivery_order->items(\@items);
173
174   return ($delivery_order, $custom_shipto);
175 }
176
177 sub customervendor {
178   $_[0]->is_sales ? $_[0]->customer : $_[0]->vendor;
179 }
180
181 1;
182 __END__
183
184 =pod
185
186 =encoding utf8
187
188 =head1 NAME
189
190 SL::DB::DeliveryOrder - Rose model for delivery orders (table
191 "delivery_orders")
192
193 =head1 FUNCTIONS
194
195 =over 4
196
197 =item C<date>
198
199 An alias for C<transdate> for compatibility with other sales/purchase models.
200
201 =item C<displayable_state>
202
203 Returns a human-readable description of the state regarding being
204 closed and delivered.
205
206 =item C<items>
207
208 An alias for C<deliver_orer_items> for compatibility with other
209 sales/purchase models.
210
211 =item C<items_sorted>
212
213 Returns the delivery order items sorted by their ID (same order they
214 appear in the frontend delivery order masks).
215
216 =item C<new_from $source, %params>
217
218 Creates a new C<SL::DB::DeliveryOrder> instance and copies as much
219 information from C<$source> as possible. At the moment only instances
220 of C<SL::DB::Order> (sales quotations, sales orders, requests for
221 quotations and purchase orders) are supported as sources.
222
223 The conversion copies order items into delivery order items. Dates are copied
224 as appropriate, e.g. the C<transdate> field will be set to the current date.
225
226 Returns one or two objects depending on the context. In list context
227 the new delivery order instance and a shipto instance will be
228 returned. In scalar instance only the delivery order instance is
229 returned.
230
231 Custom shipto addresses (the ones specific to the sales/purchase
232 record and not to the customer/vendor) are only linked from C<shipto>
233 to C<delivery_orders>. Meaning C<delivery_orders.shipto_id> will not
234 be filled in that case. That's why a separate shipto object is created
235 and returned.
236
237 The objects returned are not saved.
238
239 C<%params> can include the following options:
240
241 =over 2
242
243 =item C<items>
244
245 An optional array reference of RDBO instances for the items to use. If
246 missing then the method C<items_sorted> will be called on
247 C<$source>. This option can be used to override the sorting, to
248 exclude certain positions or to add additional ones.
249
250 =item C<skip_items_negative_qty>
251
252 If trueish then items with a negative quantity are skipped. Items with
253 a quantity of 0 are not affected by this option.
254
255 =item C<skip_items_zero_qty>
256
257 If trueish then items with a quantity of 0 are skipped.
258
259 =item C<attributes>
260
261 An optional hash reference. If it exists then it is passed to C<new>
262 allowing the caller to set certain attributes for the new delivery
263 order.
264
265 =back
266
267 =item C<sales_order>
268
269 TODO: Describe sales_order
270
271 =item C<type>
272
273 Returns a stringdescribing this record's type: either
274 C<sales_delivery_order> or C<purchase_delivery_order>.
275
276 =back
277
278 =head1 BUGS
279
280 Nothing here yet.
281
282 =head1 AUTHOR
283
284 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
285
286 =cut