Projekt

Allgemein

Profil

Herunterladen (13,1 KB) Statistiken
| Zweig: | Markierung: | Revision:
82515b2d Sven Schöling
package SL::DB::Order;

05c6840d Moritz Bunkus
use utf8;
82515b2d Sven Schöling
use strict;

42ea98b4 Moritz Bunkus
use Carp;
use DateTime;
use List::Util qw(max);
82515b2d Sven Schöling
use SL::DB::MetaSetup::Order;
use SL::DB::Manager::Order;
a34c05f3 Moritz Bunkus
use SL::DB::Helper::AttrHTML;
f63af42d Moritz Bunkus
use SL::DB::Helper::AttrSorted;
c692dae1 Moritz Bunkus
use SL::DB::Helper::FlattenToForm;
e9fb6244 Moritz Bunkus
use SL::DB::Helper::LinkedRecords;
use SL::DB::Helper::PriceTaxCalculator;
2209370e Moritz Bunkus
use SL::DB::Helper::PriceUpdater;
b9dbc9e3 Moritz Bunkus
use SL::DB::Helper::TransNumberGenerator;
42ea98b4 Moritz Bunkus
use SL::RecordLinks;
0409db7c Sven Schöling
use Rose::DB::Object::Helpers qw(as_tree);
82515b2d Sven Schöling
__PACKAGE__->meta->add_relationship(
orderitems => {
type => 'one to many',
class => 'SL::DB::OrderItem',
column_map => { id => 'trans_id' },
manager_args => {
0845c4b7 Moritz Bunkus
with_objects => [ 'part' ]
82515b2d Sven Schöling
}
250fa402 Moritz Bunkus
},
periodic_invoices_config => {
27bb493f Moritz Bunkus
type => 'one to one',
250fa402 Moritz Bunkus
class => 'SL::DB::PeriodicInvoicesConfig',
column_map => { id => 'oe_id' },
},
325c539c Moritz Bunkus
custom_shipto => {
type => 'one to one',
class => 'SL::DB::Shipto',
column_map => { id => 'trans_id' },
query_args => [ module => 'OE' ],
},
82515b2d Sven Schöling
);

__PACKAGE__->meta->initialize;

a34c05f3 Moritz Bunkus
__PACKAGE__->attr_html('notes');
f63af42d Moritz Bunkus
__PACKAGE__->attr_sorted('items');
a34c05f3 Moritz Bunkus
1bcb4ec6 Moritz Bunkus
__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;
}

82515b2d Sven Schöling
# methods

0845c4b7 Moritz Bunkus
sub items { goto &orderitems; }
ae906113 Moritz Bunkus
sub add_items { goto &add_orderitems; }
4f7e0fa9 Geoffrey Richardson
sub record_number { goto &number; }
4ac74078 Moritz Bunkus
82515b2d Sven Schöling
sub type {
my $self = shift;

return 'sales_order' if $self->customer_id && ! $self->quotation;
return 'purchase_order' if $self->vendor_id && ! $self->quotation;
return 'sales_quotation' if $self->customer_id && $self->quotation;
return 'request_quotation' if $self->vendor_id && $self->quotation;

return;
}

sub is_type {
return shift->type eq shift;
}

2746ccd0 Sven Schöling
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';
}

a7114646 Geoffrey Richardson
sub displayable_name {
join ' ', grep $_, map $_[0]->$_, qw(displayable_type record_number);
};
2746ccd0 Sven Schöling
211de9e3 Sven Schöling
sub is_sales {
croak 'not an accessor' if @_ > 1;
61bd6288 Moritz Bunkus
return !!shift->customer_id;
211de9e3 Sven Schöling
}

82515b2d Sven Schöling
sub invoices {
my $self = shift;
my %params = @_;

if ($self->quotation) {
return [];
} else {
71e6f43b Sven Schöling
require SL::DB::Invoice;
82515b2d Sven Schöling
return SL::DB::Manager::Invoice->get_all(
query => [
ordnumber => $self->ordnumber,
@{ $params{query} || [] },
]
);
}
}

2746ccd0 Sven Schöling
sub displayable_state {
my ($self) = @_;

return $self->closed ? $::locale->text('closed') : $::locale->text('open');
}

82515b2d Sven Schöling
sub abschlag_invoices {
return shift()->invoices(query => [ abschlag => 1 ]);
}

sub end_invoice {
return shift()->invoices(query => [ abschlag => 0 ]);
}

42ea98b4 Moritz Bunkus
sub convert_to_invoice {
my ($self, %params) = @_;

f4dca613 Moritz Bunkus
croak("Conversion to invoices is only supported for sales records") unless $self->customer_id;

42ea98b4 Moritz Bunkus
my $invoice;
0fdcea4d Moritz Bunkus
if (!$self->db->with_transaction(sub {
c50f0950 Moritz Bunkus
require SL::DB::Invoice;
42ea98b4 Moritz Bunkus
$invoice = SL::DB::Invoice->new_from($self)->post(%params) || die;
$self->link_to_record($invoice);
$self->update_attributes(closed => 1);
0fdcea4d Moritz Bunkus
1;
42ea98b4 Moritz Bunkus
})) {
return undef;
}

return $invoice;
}

7664f50f Moritz Bunkus
sub convert_to_delivery_order {
c37da034 Moritz Bunkus
my ($self, @args) = @_;
7664f50f Moritz Bunkus
492c85c2 Moritz Bunkus
my $delivery_order;
0fdcea4d Moritz Bunkus
if (!$self->db->with_transaction(sub {
7664f50f Moritz Bunkus
require SL::DB::DeliveryOrder;
492c85c2 Moritz Bunkus
$delivery_order = SL::DB::DeliveryOrder->new_from($self, @args);
7664f50f Moritz Bunkus
$delivery_order->save;
$self->link_to_record($delivery_order);
fc890e10 Jan Büren
# 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};
5c257935 Bernd Bleßmann
RecordLinks->create_links('dbh' => $self->db->dbh,
'mode' => 'ids',
fc890e10 Jan Büren
'from_table' => $_,
'from_ids' => $item->{"converted_from_${_}_id"},
'to_table' => 'delivery_order_items',
'to_id' => $item->{id},
) || die;
delete $item->{"converted_from_${_}_id"};
}
}
}

1c9c1ad9 Moritz Bunkus
$self->update_attributes(delivered => 1);
0fdcea4d Moritz Bunkus
1;
7664f50f Moritz Bunkus
})) {
492c85c2 Moritz Bunkus
return undef;
7664f50f Moritz Bunkus
}

492c85c2 Moritz Bunkus
return $delivery_order;
7664f50f Moritz Bunkus
}

b14755d0 Bernd Bleßmann
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';
5af5ceee Bernd Bleßmann
croak("A destination type must be given as parameter") unless $params{destination_type};
b14755d0 Bernd Bleßmann
my $destination_type = delete $params{destination_type};
951ebae4 Bernd Bleßmann
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);
b14755d0 Bernd Bleßmann
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
)),
951ebae4 Bernd Bleßmann
quotation => !!($destination_type =~ m{quotation$}),
b14755d0 Bernd Bleßmann
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
951ebae4 Bernd Bleßmann
# shipto → order. Meaning order.shipto_id
b14755d0 Bernd Bleßmann
# 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;
}

3a4ddae0 Sven Schöling
sub number {
my $self = shift;

052a7bb6 Bernd Bleßmann
return if !$self->type;

3a4ddae0 Sven Schöling
my %number_method = (
sales_order => 'ordnumber',
sales_quotation => 'quonumber',
71180454 Sven Schöling
purchase_order => 'ordnumber',
3a4ddae0 Sven Schöling
request_quotation => 'quonumber',
);

return $self->${ \ $number_method{$self->type} }(@_);
}

9589ecd7 Sven Schöling
sub customervendor {
$_[0]->is_sales ? $_[0]->customer : $_[0]->vendor;
}

16c6be41 Moritz Bunkus
sub date {
goto &transdate;
}

23c5a950 Sven Schöling
sub digest {
my ($self) = @_;

sprintf "%s %s %s (%s)",
$self->number,
$self->customervendor->name,
$self->amount_as_number,
$self->date->to_kivitendo;
}

82515b2d Sven Schöling
1;

__END__

5af4094b Moritz Bunkus
=pod

=encoding utf8

82515b2d Sven Schöling
=head1 NAME

SL::DB::Order - Order Datenbank Objekt.

=head1 FUNCTIONS

d80ffb81 Sven Schöling
=head2 C<type>
82515b2d Sven Schöling
Returns one of the following string types:

=over 4

d80ffb81 Sven Schöling
=item sales_order
82515b2d Sven Schöling
=item purchase_order

=item sales_quotation

=item request_quotation

=back

d80ffb81 Sven Schöling
=head2 C<is_type TYPE>
82515b2d Sven Schöling
d80ffb81 Sven Schöling
Returns true if the order is of the given type.
82515b2d Sven Schöling
7664f50f Moritz Bunkus
=head2 C<convert_to_delivery_order %params>

Creates a new delivery order with C<$self> as the basis by calling
L<SL::DB::DeliveryOrder::new_from>. That delivery order is saved, and
1c9c1ad9 Moritz Bunkus
C<$self> is linked to the new invoice via
L<SL::DB::RecordLink>. C<$self>'s C<delivered> attribute is set to
C<true>, and C<$self> is saved.
7664f50f Moritz Bunkus
The arguments in C<%params> are passed to
L<SL::DB::DeliveryOrder::new_from>.

492c85c2 Moritz Bunkus
Returns C<undef> on failure. Otherwise the new delivery order will be
returned.
7664f50f Moritz Bunkus
d80ffb81 Sven Schöling
=head2 C<convert_to_invoice %params>
62bb3c51 Moritz Bunkus
Creates a new invoice with C<$self> as the basis by calling
L<SL::DB::Invoice::new_from>. That invoice is posted, and C<$self> is
linked to the new invoice via L<SL::DB::RecordLink>. C<$self>'s
C<closed> attribute is set to C<true>, and C<$self> is saved.

ed3be965 Moritz Bunkus
The arguments in C<%params> are passed to L<SL::DB::Invoice::post>.
62bb3c51 Moritz Bunkus
78600d89 Moritz Bunkus
Returns the new invoice instance on success and C<undef> on
failure. The whole process is run inside a transaction. On failure
nothing is created or changed in the database.
62bb3c51 Moritz Bunkus
At the moment only sales quotations and sales orders can be converted.

b14755d0 Bernd Bleßmann
=head2 C<new_from $source, %params>

Creates a new C<SL::DB::Order> instance and copies as much
951ebae4 Bernd Bleßmann
information from C<$source> as possible. At the moment only records with the
same destination type as the source type and sales orders from
b14755d0 Bernd Bleßmann
sales quotations and purchase orders from requests for quotations can be
created.

The C<transdate> 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<destination_type> is mandatory):

=over 4

=item C<destination_type>

(mandatory)
951ebae4 Bernd Bleßmann
The type of the newly created object. Can be C<sales_quotation>,
C<sales_order>, C<purchase_quotation> or C<purchase_order> for now.
b14755d0 Bernd Bleßmann
=item C<items>

An optional array reference of RDBO instances for the items to use. If
missing then the method C<items_sorted> 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<skip_items_negative_qty>

If trueish then items with a negative quantity are skipped. Items with
a quantity of 0 are not affected by this option.

=item C<skip_items_zero_qty>

If trueish then items with a quantity of 0 are skipped.

=item C<item_filter>

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<attributes>

An optional hash reference. If it exists then it is passed to C<new>
allowing the caller to set certain attributes for the new delivery
order.

=back

d80ffb81 Sven Schöling
=head2 C<create_sales_process>
78600d89 Moritz Bunkus
Creates and saves a new sales process. Can only be called for sales
orders.

The newly created process will be linked bidirectionally to both
C<$self> and to all sales quotations that are linked to C<$self>.

Returns the newly created process instance.

82515b2d Sven Schöling
=head1 BUGS

Nothing here yet.

=head1 AUTHOR

05c6840d Moritz Bunkus
Sven Schöling <s.schoeling@linet-services.de>
82515b2d Sven Schöling
=cut