use Carp;
-use Rose::DB::Object::Helpers ();
+use Rose::DB::Object::Helpers qw(as_tree strip);
use SL::DB::MetaSetup::DeliveryOrder;
use SL::DB::Manager::DeliveryOrder;
use SL::DB::Helper::AttrSorted;
use SL::DB::Helper::FlattenToForm;
use SL::DB::Helper::LinkedRecords;
+use SL::DB::Helper::TypeDataProxy;
use SL::DB::Helper::TransNumberGenerator;
-
-use SL::DB::Part;
-use SL::DB::Unit;
+use SL::DB::Helper::RecordLink qw(RECORD_ID RECORD_TYPE_REF);
use SL::DB::DeliveryOrder::TypeData qw(:types);
+use SL::DB::Order::TypeData qw(:types);
+use SL::DB::Reclamation::TypeData qw(:types);
use SL::Helper::Number qw(_format_total _round_total);
+use SL::Helper::ShippedQty;
use List::Util qw(first);
use List::MoreUtils qw(any pairwise);
__PACKAGE__->attr_sorted('items');
__PACKAGE__->before_save('_before_save_set_donumber');
+__PACKAGE__->after_save('_after_save_link_records');
+__PACKAGE__->after_save('_mark_orders_if_delivered');
# hooks
return 1;
}
+sub _after_save_link_records {
+ my ($self) = @_;
+
+ my @allowed_record_sources = qw(SL::DB::Reclamation SL::DB::Order);
+ my @allowed_item_sources = qw(SL::DB::ReclamationItem SL::DB::OrderItem);
+
+ SL::DB::Helper::RecordLink::link_records(
+ $self,
+ \@allowed_record_sources,
+ \@allowed_item_sources,
+ );
+}
+
+sub _mark_orders_if_delivered {
+ my ($self) = @_;
+ my $orders = $self->linked_records(from => 'Order');
+ SL::Helper::ShippedQty->new->calculate($orders)->write_to_objects;
+ foreach my $order (@$orders) {
+ next if $order->is_sales != $self->is_sales;
+ $order->update_attributes(delivered => $order->{delivered});
+ }
+ return 1;
+}
+
# methods
sub items { goto &orderitems; }
],
);
- return first { $_->is_type('sales_order') } @{ $orders };
+ return first { $_->is_type(SALES_ORDER_TYPE()) } @{ $orders };
}
sub type {
- goto &order_type;
+ goto &record_type;
}
sub is_type {
}
sub displayable_type {
- my $type = shift->type;
-
- return $::locale->text('Sales Delivery Order') if $type eq 'sales_delivery_order';
- return $::locale->text('Purchase Delivery Order') if $type eq 'purchase_delivery_order';
-
- die 'invalid type';
+ my ($self) = @_;
+ return $self->type_data->text('type');
}
sub displayable_name {
goto &donumber;
}
+sub preceding_purchase_order_confirmations {
+ my ($self) = @_;
+
+ my @lrs = ();
+ if ($self->id) {
+ @lrs = grep { $_->record_type eq PURCHASE_ORDER_CONFIRMATION_TYPE() } @{$self->linked_records(from => 'SL::DB::Order')};
+ } else {
+ if ('SL::DB::Order' eq $self->{RECORD_TYPE_REF()}) {
+ my $order = SL::DB::Order->load_cached($self->{RECORD_ID()});
+ push @lrs, $order if $order->record_type eq PURCHASE_ORDER_CONFIRMATION_TYPE();
+ }
+ }
+
+ return \@lrs;
+}
+
sub _clone_orderitem_cvar {
my ($cvar) = @_;
return $cloned;
}
+sub convert_to_reclamation {
+ my ($self, %params) = @_;
+
+ $params{destination_type} = $self->is_sales ? SALES_RECLAMATION_TYPE()
+ : PURCHASE_RECLAMATION_TYPE();
+
+ my $reclamation = SL::DB::Reclamation->new_from($self, %params);
+
+ return $reclamation;
+}
+
sub new_from {
my ($class, $source, %params) = @_;
- croak("Unsupported source object type '" . ref($source) . "'") unless ref($source) eq 'SL::DB::Order';
+ my %allowed_sources = map { $_ => 1 } qw(
+ SL::DB::Reclamation
+ SL::DB::Order
+ SL::DB::DeliveryOrder
+ );
+ unless( $allowed_sources{ref $source} ) {
+ croak("Unsupported source object type '" . ref($source) . "'");
+ }
- my ($item_parent_id_column, $item_parent_column);
+ my %record_args = (
+ donumber => undef,
+ employee => SL::DB::Manager::Employee->current,
+ closed => 0,
+ delivered => 0,
+ record_type => $params{destination_type},
+ transdate => DateTime->today_local,
+ );
- if (ref($source) eq 'SL::DB::Order') {
- $item_parent_id_column = 'trans_id';
- $item_parent_column = 'order';
+ if ( ref($source) eq 'SL::DB::Order' ) {
+ map{ ( $record_args{$_} = $source->$_ ) } # {{{ for vim folds
+ qw(
+ billing_address_id
+ cp_id
+ currency_id
+ cusordnumber
+ customer_id
+ delivery_term_id
+ department_id
+ globalproject_id
+ intnotes
+ language_id
+ notes
+ payment_id
+ reqdate
+ salesman_id
+ shippingpoint
+ shipvia
+ taxincluded
+ taxzone_id
+ transaction_description
+ vendor_confirmation_number
+ vendor_id
+ );
+ if ($source->record_type eq PURCHASE_ORDER_CONFIRMATION_TYPE()) {
+ $record_args{ordnumber} = join ' ', map { $_->ordnumber } @{$source->preceding_purchase_orders()};
+ } else {
+ $record_args{ordnumber} = $source->ordnumber;
+ }
+ # }}} for vim folds
+ } elsif ( ref($source) eq 'SL::DB::Reclamation' ) {
+ map{ ( $record_args{$_} = $source->$_ ) } # {{{ for vim folds
+ qw(
+ billing_address_id
+ currency_id
+ customer_id
+ delivery_term_id
+ department_id
+ globalproject_id
+ intnotes
+ language_id
+ notes
+ payment_id
+ reqdate
+ salesman_id
+ shippingpoint
+ shipvia
+ taxincluded
+ taxzone_id
+ transaction_description
+ vendor_id
+ );
+ $record_args{cp_id} = $source->contact_id;
+ $record_args{cusordnumber} = $source->cv_record_number;
+ $record_args{is_sales} = $source->is_sales;
+ # }}} for vim folds
+ } elsif ( ref($source) eq 'SL::DB::DeliveryOrder' ) {
+ map{ ( $record_args{$_} = $source->$_ ) } # {{{ for vim folds
+ qw(
+ billing_address_id
+ cp_id
+ currency_id
+ cusordnumber
+ customer_id
+ delivery_term_id
+ department_id
+ donumber
+ globalproject_id
+ intnotes
+ language_id
+ notes
+ ordnumber
+ oreqnumber
+ payment_id
+ reqdate
+ salesman_id
+ shippingpoint
+ shipto_id
+ shipvia
+ taxincluded
+ taxzone_id
+ transdate
+ transaction_description
+ vendor_confirmation_number
+ vendor_id
+ );
+ # }}} for vim folds
}
- my %args = ( map({ ( $_ => $source->$_ ) } qw(cp_id currency_id customer_id cusordnumber delivery_term_id department_id employee_id globalproject_id intnotes language_id notes
- ordnumber payment_id reqdate salesman_id shippingpoint shipvia taxincluded taxzone_id transaction_description vendor_id billing_address_id
- )),
- closed => 0,
- delivered => 0,
- order_type => $params{type},
- 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 → delivery_orders. Meaning delivery_orders.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;
-
+ $record_args{custom_shipto} = $source->custom_shipto->clone($class) if $source->can('custom_shipto') && $source->custom_shipto;
} else {
- $args{shipto_id} = $source->shipto_id;
+ $record_args{shipto_id} = $source->shipto_id;
}
# infer type from legacy fields if not given
- $args{order_type} //= $source->customer_id ? 'sales_delivery_order'
- : $source->vendor_id ? 'purchase_delivery_order'
- : $source->is_sales ? 'sales_delivery_order'
- : croak "need some way to set delivery order type from source";
+ $record_args{record_type} //= $source->customer_id ? SALES_DELIVERY_ORDER_TYPE()
+ : $source->vendor_id ? PURCHASE_DELIVERY_ORDER_TYPE()
+ : $source->is_sales ? SALES_DELIVERY_ORDER_TYPE()
+ : croak "need some way to set delivery order type from source";
- my $delivery_order = $class->new(%args);
+ my $delivery_order = $class->new(%record_args);
$delivery_order->assign_attributes(%{ $params{attributes} }) if $params{attributes};
- my $items = delete($params{items}) || $source->items_sorted;
- my %item_parents;
-
- # do not copy items when converting to supplier delivery order
- my @items = $delivery_order->is_type(SUPPLIER_DELIVERY_ORDER_TYPE) ? () : 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_do_item = SL::DB::DeliveryOrderItem->new(map({ ( $_ => $source_item->$_ ) }
- qw(base_qty cusordnumber description discount lastcost longdescription marge_price_factor parts_id price_factor price_factor_id
- project_id qty reqdate sellprice serialnumber transdate unit active_discount_source active_price_source
- )),
- custom_variables => \@custom_variables,
- ordnumber => ref($item_parent) eq 'SL::DB::Order' ? $item_parent->ordnumber : $source_item->ordnumber,
- );
- $current_do_item->{"converted_from_orderitems_id"} = $_->{id} if ref($item_parent) eq 'SL::DB::Order';
- $current_do_item;
- } @{ $items };
+
+ my $items = delete($params{items}) || $source->items_sorted;
+ my @items = ( $delivery_order->is_type(SUPPLIER_DELIVERY_ORDER_TYPE()) && ref($source) ne 'SL::DB::Reclamation' ) ?
+ ()
+ : map { SL::DB::DeliveryOrderItem->new_from($_, %params) } @{ $items };
@items = grep { $params{item_filter}->($_) } @items if $params{item_filter};
@items = grep { $_->qty * 1 } @items if $params{skip_items_zero_qty};
$delivery_order->items(\@items);
+ unless ($params{no_linked_records}) {
+ $delivery_order->{ RECORD_ID() } = $source->id;
+ $delivery_order->{ RECORD_TYPE_REF() } = ref $source;
+ }
+
return $delivery_order;
}
sub new_from_time_recordings {
my ($class, $sources, %params) = @_;
+ require SL::DB::Part;
+ require SL::DB::Unit;
croak("Unsupported object type in sources") if any { ref($_) ne 'SL::DB::TimeRecording' } @$sources;
croak("Cannot create delivery order from source records of different customers") if any { $_->customer_id != $sources->[0]->customer_id } @$sources;
} else {
my %args = (
- is_sales => 1,
- order_type => 'sales_delivery_order',
+ record_type => SALES_DELIVERY_ORDER_TYPE,
delivered => 0,
customer_id => $sources->[0]->customer_id,
taxzone_id => $sources->[0]->customer->taxzone_id,
# legacy for compatibility
# use type_data cusomtervendor and transfer direction instead
sub is_sales {
- if ($_[0]->order_type) {
- return SL::DB::DeliveryOrder::TypeData::get3($_[0]->order_type, "properties", "is_customer");
+ if ($_[0]->record_type) {
+ return SL::DB::DeliveryOrder::TypeData::get3($_[0]->record_type, "properties", "is_customer");
}
return $_[0]{is_sales};
}
sub customervendor {
- SL::DB::DeliveryOrder::TypeData::get3($_[0]->order_type, "properties", "is_customer") ? $_[0]->customer : $_[0]->vendor;
+ SL::DB::DeliveryOrder::TypeData::get3($_[0]->record_type, "properties", "is_customer") ? $_[0]->customer : $_[0]->vendor;
}
sub convert_to_invoice {
if (!$self->db->with_transaction(sub {
require SL::DB::Invoice;
$invoice = SL::DB::Invoice->new_from($self, %params)->post || die;
- $self->link_to_record($invoice);
- # TODO extend link_to_record for items, otherwise long-term no d.r.y.
- foreach my $item (@{ $invoice->items }) {
- foreach (qw(delivery_order_items)) { # expand if needed (orderitems)
- if ($item->{"converted_from_${_}_id"}) {
- die unless $item->{id};
- RecordLinks->create_links('mode' => 'ids',
- 'from_table' => $_,
- 'from_ids' => $item->{"converted_from_${_}_id"},
- 'to_table' => 'invoice',
- 'to_id' => $item->{id},
- ) || die;
- delete $item->{"converted_from_${_}_id"};
- }
- }
- }
$self->update_attributes(closed => 1);
1;
})) {
$self->date->to_kivitendo;
}
+sub type_data {
+ SL::DB::Helper::TypeDataProxy->new(ref $_[0], $_[0]->type);
+}
+
1;
__END__