X-Git-Url: http://wagnertech.de/git?a=blobdiff_plain;f=SL%2FDB%2FOrder.pm;h=6baa0e20dc7905f0fa70e493dbb86115a23758ee;hb=862dfb20115843050efddb16828fd455fe3c9456;hp=d74f9414dc8958283ad3f8069156c0714919c37d;hpb=2209370ef80da58725187ec1c3d68719898f79c4;p=kivitendo-erp.git diff --git a/SL/DB/Order.pm b/SL/DB/Order.pm index d74f9414d..6baa0e20d 100644 --- a/SL/DB/Order.pm +++ b/SL/DB/Order.pm @@ -9,12 +9,15 @@ use List::Util qw(max); use SL::DB::MetaSetup::Order; use SL::DB::Manager::Order; -use SL::DB::Invoice; +use SL::DB::Helper::AttrHTML; +use SL::DB::Helper::AttrSorted; +use SL::DB::Helper::FlattenToForm; use SL::DB::Helper::LinkedRecords; use SL::DB::Helper::PriceTaxCalculator; use SL::DB::Helper::PriceUpdater; use SL::DB::Helper::TransNumberGenerator; use SL::RecordLinks; +use Rose::DB::Object::Helpers qw(as_tree); __PACKAGE__->meta->add_relationship( orderitems => { @@ -30,23 +33,41 @@ __PACKAGE__->meta->add_relationship( class => 'SL::DB::PeriodicInvoicesConfig', column_map => { id => 'oe_id' }, }, - periodic_invoices => { - type => 'one to many', - class => 'SL::DB::PeriodicInvoice', - column_map => { id => 'oe_id' }, - }, - payment_term => { - type => 'one to one', - class => 'SL::DB::PaymentTerm', - column_map => { payment_id => 'id' }, + custom_shipto => { + type => 'one to one', + class => 'SL::DB::Shipto', + column_map => { id => 'trans_id' }, + query_args => [ module => 'OE' ], }, ); __PACKAGE__->meta->initialize; +__PACKAGE__->attr_html('notes'); +__PACKAGE__->attr_sorted('items'); + +__PACKAGE__->before_save('_before_save_set_ord_quo_number'); + +# hooks + +sub _before_save_set_ord_quo_number { + my ($self) = @_; + + # ordnumber is 'NOT NULL'. Therefore make sure it's always set to at + # least an empty string, even if we're saving a quotation. + $self->ordnumber('') if !$self->ordnumber; + + my $field = $self->quotation ? 'quonumber' : 'ordnumber'; + $self->create_trans_number if !$self->$field; + + return 1; +} + # methods sub items { goto &orderitems; } +sub add_items { goto &add_orderitems; } +sub record_number { goto &number; } sub type { my $self = shift; @@ -63,6 +84,26 @@ sub is_type { return shift->type eq shift; } +sub displayable_type { + my $type = shift->type; + + return $::locale->text('Sales quotation') if $type eq 'sales_quotation'; + return $::locale->text('Request quotation') if $type eq 'request_quotation'; + return $::locale->text('Sales Order') if $type eq 'sales_order'; + return $::locale->text('Purchase Order') if $type eq 'purchase_order'; + + die 'invalid type'; +} + +sub displayable_name { + join ' ', grep $_, map $_[0]->$_, qw(displayable_type record_number); +}; + +sub is_sales { + croak 'not an accessor' if @_ > 1; + return !!shift->customer_id; +} + sub invoices { my $self = shift; my %params = @_; @@ -70,6 +111,7 @@ sub invoices { if ($self->quotation) { return []; } else { + require SL::DB::Invoice; return SL::DB::Manager::Invoice->get_all( query => [ ordnumber => $self->ordnumber, @@ -79,6 +121,12 @@ sub invoices { } } +sub displayable_state { + my ($self) = @_; + + return $self->closed ? $::locale->text('closed') : $::locale->text('open'); +} + sub abschlag_invoices { return shift()->invoices(query => [ abschlag => 1 ]); } @@ -93,11 +141,12 @@ sub convert_to_invoice { croak("Conversion to invoices is only supported for sales records") unless $self->customer_id; my $invoice; - if (!$self->db->do_transaction(sub { + if (!$self->db->with_transaction(sub { + require SL::DB::Invoice; $invoice = SL::DB::Invoice->new_from($self)->post(%params) || die; $self->link_to_record($invoice); $self->update_attributes(closed => 1); - # die; + 1; })) { return undef; } @@ -105,23 +154,183 @@ sub convert_to_invoice { return $invoice; } +sub convert_to_delivery_order { + my ($self, @args) = @_; + + my $delivery_order; + if (!$self->db->with_transaction(sub { + require SL::DB::DeliveryOrder; + $delivery_order = SL::DB::DeliveryOrder->new_from($self, @args); + $delivery_order->save; + $self->link_to_record($delivery_order); + # TODO extend link_to_record for items, otherwise long-term no d.r.y. + foreach my $item (@{ $delivery_order->items }) { + foreach (qw(orderitems)) { # expand if needed (delivery_order_items) + if ($item->{"converted_from_${_}_id"}) { + die unless $item->{id}; + RecordLinks->create_links('dbh' => $self->db->dbh, + 'mode' => 'ids', + 'from_table' => $_, + 'from_ids' => $item->{"converted_from_${_}_id"}, + 'to_table' => 'delivery_order_items', + 'to_id' => $item->{id}, + ) || die; + delete $item->{"converted_from_${_}_id"}; + } + } + } + + $self->update_attributes(delivered => 1); + 1; + })) { + return undef; + } + + return $delivery_order; +} + +sub _clone_orderitem_cvar { + my ($cvar) = @_; + + my $cloned = $_->clone_and_reset; + $cloned->sub_module('orderitems'); + + return $cloned; +} + +sub new_from { + my ($class, $source, %params) = @_; + + croak("Unsupported source object type '" . ref($source) . "'") unless ref($source) eq 'SL::DB::Order'; + croak("A destination type must be given parameter") unless $params{destination_type}; + + my $destination_type = delete $params{destination_type}; + my $src_dst_allowed = ('sales_quotation' eq $source->type && 'sales_order' eq $destination_type) + || ('request_quotation' eq $source->type && 'purchase_order' eq $destination_type) + || ('sales_quotation' eq $source->type && 'sales_quotation' eq $destination_type) + || ('sales_order' eq $source->type && 'sales_order' eq $destination_type) + || ('request_quotation' eq $source->type && 'request_quotation' eq $destination_type) + || ('purchase_order' eq $source->type && 'purchase_order' eq $destination_type); + croak("Cannot convert from '" . $source->type . "' to '" . $destination_type . "'") unless $src_dst_allowed; + + my ($item_parent_id_column, $item_parent_column); + + if (ref($source) eq 'SL::DB::Order') { + $item_parent_id_column = 'trans_id'; + $item_parent_column = 'order'; + } + + my %args = ( map({ ( $_ => $source->$_ ) } qw(amount cp_id currency_id cusordnumber customer_id delivery_customer_id delivery_term_id delivery_vendor_id + department_id employee_id globalproject_id intnotes marge_percent marge_total language_id netamount notes + ordnumber payment_id quonumber reqdate salesman_id shippingpoint shipvia taxincluded taxzone_id + transaction_description vendor_id + )), + quotation => !!($destination_type =~ m{quotation$}), + closed => 0, + delivered => 0, + transdate => DateTime->today_local, + ); + + # Custom shipto addresses (the ones specific to the sales/purchase + # record and not to the customer/vendor) are only linked from + # shipto → order. Meaning order.shipto_id + # will not be filled in that case. + if (!$source->shipto_id && $source->id) { + $args{custom_shipto} = $source->custom_shipto->clone($class) if $source->can('custom_shipto') && $source->custom_shipto; + + } else { + $args{shipto_id} = $source->shipto_id; + } + + my $order = $class->new(%args); + $order->assign_attributes(%{ $params{attributes} }) if $params{attributes}; + my $items = delete($params{items}) || $source->items_sorted; + my %item_parents; + + my @items = map { + my $source_item = $_; + my $source_item_id = $_->$item_parent_id_column; + my @custom_variables = map { _clone_orderitem_cvar($_) } @{ $source_item->custom_variables }; + + $item_parents{$source_item_id} ||= $source_item->$item_parent_column; + my $item_parent = $item_parents{$source_item_id}; + + my $current_oe_item = SL::DB::OrderItem->new(map({ ( $_ => $source_item->$_ ) } + qw(active_discount_source active_price_source base_qty cusordnumber + description discount lastcost longdescription + marge_percent marge_price_factor marge_total + ordnumber parts_id price_factor price_factor_id pricegroup_id + project_id qty reqdate sellprice serialnumber ship subtotal transdate unit + )), + custom_variables => \@custom_variables, + ); + $current_oe_item->{"converted_from_orderitems_id"} = $_->{id} if ref($item_parent) eq 'SL::DB::Order'; + $current_oe_item; + } @{ $items }; + + @items = grep { $params{item_filter}->($_) } @items if $params{item_filter}; + @items = grep { $_->qty * 1 } @items if $params{skip_items_zero_qty}; + @items = grep { $_->qty >=0 } @items if $params{skip_items_negative_qty}; + + $order->items(\@items); + + return $order; +} + +sub number { + my $self = shift; + + return if !$self->type; + + my %number_method = ( + sales_order => 'ordnumber', + sales_quotation => 'quonumber', + purchase_order => 'ordnumber', + request_quotation => 'quonumber', + ); + + return $self->${ \ $number_method{$self->type} }(@_); +} + +sub customervendor { + $_[0]->is_sales ? $_[0]->customer : $_[0]->vendor; +} + +sub date { + goto &transdate; +} + +sub digest { + my ($self) = @_; + + sprintf "%s %s %s (%s)", + $self->number, + $self->customervendor->name, + $self->amount_as_number, + $self->date->to_kivitendo; +} + 1; __END__ +=pod + +=encoding utf8 + =head1 NAME SL::DB::Order - Order Datenbank Objekt. =head1 FUNCTIONS -=head2 type +=head2 C Returns one of the following string types: =over 4 -=item saes_order +=item sales_order =item purchase_order @@ -131,11 +340,25 @@ Returns one of the following string types: =back -=head2 is_type TYPE +=head2 C -Rreturns true if the order is of the given type. +Returns true if the order is of the given type. -=item C +=head2 C + +Creates a new delivery order with C<$self> as the basis by calling +L. That delivery order is saved, and +C<$self> is linked to the new invoice via +L. C<$self>'s C attribute is set to +C, and C<$self> is saved. + +The arguments in C<%params> are passed to +L. + +Returns C on failure. Otherwise the new delivery order will be +returned. + +=head2 C Creates a new invoice with C<$self> as the basis by calling L. That invoice is posted, and C<$self> is @@ -150,7 +373,63 @@ nothing is created or changed in the database. At the moment only sales quotations and sales orders can be converted. -=item C +=head2 C + +Creates a new C instance and copies as much +information from C<$source> as possible. At the moment only records with the +same destination type as the source type and sales orders from +sales quotations and purchase orders from requests for quotations can be +created. + +The C field will be set to the current date. + +The conversion copies the order items as well. + +Returns the new order instance. The object returned is not +saved. + +C<%params> can include the following options +(C is mandatory): + +=over 4 + +=item C + +(mandatory) +The type of the newly created object. Can be C, +C, C or C for now. + +=item C + +An optional array reference of RDBO instances for the items to use. If +missing then the method C will be called on +C<$source>. This option can be used to override the sorting, to +exclude certain positions or to add additional ones. + +=item C + +If trueish then items with a negative quantity are skipped. Items with +a quantity of 0 are not affected by this option. + +=item C + +If trueish then items with a quantity of 0 are skipped. + +=item C + +An optional code reference that is called for each item with the item +as its sole parameter. Items for which the code reference returns a +falsish value will be skipped. + +=item C + +An optional hash reference. If it exists then it is passed to C +allowing the caller to set certain attributes for the new delivery +order. + +=back + +=head2 C Creates and saves a new sales process. Can only be called for sales orders. @@ -160,8 +439,6 @@ C<$self> and to all sales quotations that are linked to C<$self>. Returns the newly created process instance. -=back - =head1 BUGS Nothing here yet.