Revision 7f9b8f76
Von Tamino Steinert vor mehr als 2 Jahren hinzugefügt
SL/DB/Helper/ALL.pm | ||
---|---|---|
use SL::DB::ProjectStatus;
|
||
use SL::DB::ProjectType;
|
||
use SL::DB::PurchaseInvoice;
|
||
use SL::DB::Reclamation;
|
||
use SL::DB::ReclamationItem;
|
||
use SL::DB::ReclamationReason;
|
||
use SL::DB::ReconciliationLink;
|
||
use SL::DB::RecordLink;
|
||
use SL::DB::RecordTemplate;
|
SL/DB/Helper/Mappings.pm | ||
---|---|---|
project_roles => 'project_role',
|
||
project_statuses => 'project_status',
|
||
project_types => 'project_type',
|
||
reclamations => 'Reclamation',
|
||
reclamation_items => 'ReclamationItem',
|
||
reclamation_reasons => 'ReclamationReason',
|
||
reconciliation_links => 'reconciliation_link',
|
||
record_links => 'record_link',
|
||
record_templates => 'record_template',
|
SL/DB/Manager/Reclamation.pm | ||
---|---|---|
package SL::DB::Manager::Reclamation;
|
||
|
||
use strict;
|
||
|
||
use parent qw(SL::DB::Helper::Manager);
|
||
|
||
use SL::DB::Helper::Paginated;
|
||
use SL::DB::Helper::Sorted;
|
||
use SL::DB::Helper::Filtered;
|
||
|
||
sub object_class { 'SL::DB::Reclamation' }
|
||
|
||
__PACKAGE__->make_manager_methods;
|
||
|
||
__PACKAGE__->add_filter_specs(
|
||
type => sub {
|
||
my ($key, $value, $prefix) = @_;
|
||
return __PACKAGE__->type_filter($value, $prefix);
|
||
},
|
||
# todo when is this used?
|
||
#all => sub {
|
||
# my ($key, $value, $prefix) = @_;
|
||
# return or => [ map { $prefix . $_ => $value } qw(record_number customer.name vendor.name transaction_description) ]
|
||
#}
|
||
);
|
||
|
||
sub type_filter {
|
||
my $class = shift;
|
||
my $type = lc(shift || '');
|
||
my $prefix = shift || '';
|
||
|
||
return (and => [ "!customer_id" => undef ]) if $type eq 'sales_reclamation';
|
||
return (and => [ "!vendor_id" => undef ]) if $type eq 'purchase_reclamation';
|
||
|
||
die "Unknown type $type";
|
||
}
|
||
|
||
sub _sort_spec {
|
||
return (
|
||
default => [ 'transdate', 1 ],
|
||
nulls => {
|
||
transaction_description => 'FIRST',
|
||
default => 'LAST',
|
||
},
|
||
columns => {
|
||
SIMPLE => 'ALL',
|
||
customer => 'lower(customer.name)',
|
||
vendor => 'lower(vendor.name)',
|
||
employee => 'lower(employee.name)',
|
||
globalprojectnumber => 'lower(globalproject.projectnumber)',
|
||
|
||
# Bug in Rose::DB::Object: the next should be
|
||
# "globalproject.project_type.description". This workaround will
|
||
# only work if no other table with "project_type" is visible in
|
||
# the current query
|
||
globalproject_type => 'lower(project_type.description)',
|
||
|
||
map { ( $_ => "lower(reclamations.$_)" ) }
|
||
qw(record_number cv_record_number shippingpoint shipvia notes intnotes
|
||
transaction_description
|
||
),
|
||
});
|
||
}
|
||
|
||
1;
|
SL/DB/Manager/ReclamationItem.pm | ||
---|---|---|
# This file has been auto-generated only because it didn't exist.
|
||
# Feel free to modify it at will; it will not be overwritten automatically.
|
||
|
||
package SL::DB::Manager::ReclamationItem;
|
||
|
||
use strict;
|
||
|
||
use parent qw(SL::DB::Helper::Manager);
|
||
|
||
sub object_class { 'SL::DB::ReclamationItem' }
|
||
|
||
__PACKAGE__->make_manager_methods;
|
||
|
||
1;
|
SL/DB/Manager/ReclamationReason.pm | ||
---|---|---|
package SL::DB::Manager::ReclamationReason;
|
||
|
||
use strict;
|
||
|
||
use parent qw(SL::DB::Helper::Manager);
|
||
use SL::DB::Helper::Paginated;
|
||
use SL::DB::Helper::Filtered;
|
||
|
||
use SL::DB::Helper::Sorted;
|
||
|
||
sub object_class { 'SL::DB::ReclamationReason' }
|
||
|
||
__PACKAGE__->make_manager_methods;
|
||
__PACKAGE__->add_filter_specs(
|
||
all => sub {
|
||
my ($key, $value, $prefix) = @_;
|
||
return or => [ map { $prefix . $_ => $value } qw(reclamation_reason) ]
|
||
},
|
||
);
|
||
|
||
sub _sort_spec {
|
||
return ( default => [ 'position', 1 ],
|
||
columns => { SIMPLE => 'ALL' });
|
||
}
|
||
1;
|
SL/DB/MetaSetup/Reclamation.pm | ||
---|---|---|
# This file has been auto-generated. Do not modify it; it will be overwritten
|
||
# by rose_auto_create_model.pl automatically.
|
||
package SL::DB::Reclamation;
|
||
|
||
use strict;
|
||
|
||
use parent qw(SL::DB::Object);
|
||
|
||
__PACKAGE__->meta->table('reclamations');
|
||
|
||
__PACKAGE__->meta->columns(
|
||
amount => { type => 'numeric', precision => 15, scale => 5 },
|
||
closed => { type => 'boolean', default => 'false', not_null => 1 },
|
||
contact_id => { type => 'integer' },
|
||
currency_id => { type => 'integer', not_null => 1 },
|
||
customer_id => { type => 'integer' },
|
||
cv_record_number => { type => 'text' },
|
||
delivered => { type => 'boolean', default => 'false', not_null => 1 },
|
||
delivery_term_id => { type => 'integer' },
|
||
department_id => { type => 'integer' },
|
||
employee_id => { type => 'integer', not_null => 1 },
|
||
exchangerate => { type => 'numeric', precision => 15, scale => 5 },
|
||
globalproject_id => { type => 'integer' },
|
||
id => { type => 'integer', not_null => 1, sequence => 'id' },
|
||
intnotes => { type => 'text' },
|
||
itime => { type => 'timestamp', default => 'now()' },
|
||
language_id => { type => 'integer' },
|
||
mtime => { type => 'timestamp' },
|
||
netamount => { type => 'numeric', precision => 15, scale => 5 },
|
||
notes => { type => 'text' },
|
||
payment_id => { type => 'integer' },
|
||
record_number => { type => 'text', not_null => 1 },
|
||
reqdate => { type => 'date' },
|
||
salesman_id => { type => 'integer' },
|
||
shippingpoint => { type => 'text' },
|
||
shipto_id => { type => 'integer' },
|
||
shipvia => { type => 'text' },
|
||
tax_point => { type => 'date' },
|
||
taxincluded => { type => 'boolean', not_null => 1 },
|
||
taxzone_id => { type => 'integer', not_null => 1 },
|
||
transaction_description => { type => 'text' },
|
||
transdate => { type => 'date', default => 'now()' },
|
||
vendor_id => { type => 'integer' },
|
||
);
|
||
|
||
__PACKAGE__->meta->primary_key_columns([ 'id' ]);
|
||
|
||
__PACKAGE__->meta->allow_inline_column_values(1);
|
||
|
||
__PACKAGE__->meta->foreign_keys(
|
||
contact => {
|
||
class => 'SL::DB::Contact',
|
||
key_columns => { contact_id => 'cp_id' },
|
||
},
|
||
|
||
currency => {
|
||
class => 'SL::DB::Currency',
|
||
key_columns => { currency_id => 'id' },
|
||
},
|
||
|
||
customer => {
|
||
class => 'SL::DB::Customer',
|
||
key_columns => { customer_id => 'id' },
|
||
},
|
||
|
||
delivery_term => {
|
||
class => 'SL::DB::DeliveryTerm',
|
||
key_columns => { delivery_term_id => 'id' },
|
||
},
|
||
|
||
department => {
|
||
class => 'SL::DB::Department',
|
||
key_columns => { department_id => 'id' },
|
||
},
|
||
|
||
employee => {
|
||
class => 'SL::DB::Employee',
|
||
key_columns => { employee_id => 'id' },
|
||
},
|
||
|
||
globalproject => {
|
||
class => 'SL::DB::Project',
|
||
key_columns => { globalproject_id => 'id' },
|
||
},
|
||
|
||
language => {
|
||
class => 'SL::DB::Language',
|
||
key_columns => { language_id => 'id' },
|
||
},
|
||
|
||
payment => {
|
||
class => 'SL::DB::PaymentTerm',
|
||
key_columns => { payment_id => 'id' },
|
||
},
|
||
|
||
salesman => {
|
||
class => 'SL::DB::Employee',
|
||
key_columns => { salesman_id => 'id' },
|
||
},
|
||
|
||
shipto => {
|
||
class => 'SL::DB::Shipto',
|
||
key_columns => { shipto_id => 'shipto_id' },
|
||
},
|
||
|
||
taxzone => {
|
||
class => 'SL::DB::TaxZone',
|
||
key_columns => { taxzone_id => 'id' },
|
||
},
|
||
|
||
vendor => {
|
||
class => 'SL::DB::Vendor',
|
||
key_columns => { vendor_id => 'id' },
|
||
},
|
||
);
|
||
|
||
1;
|
||
;
|
SL/DB/MetaSetup/ReclamationItem.pm | ||
---|---|---|
# This file has been auto-generated. Do not modify it; it will be overwritten
|
||
# by rose_auto_create_model.pl automatically.
|
||
package SL::DB::ReclamationItem;
|
||
|
||
use strict;
|
||
|
||
use parent qw(SL::DB::Object);
|
||
|
||
__PACKAGE__->meta->table('reclamation_items');
|
||
|
||
__PACKAGE__->meta->columns(
|
||
active_discount_source => { type => 'text', default => '', not_null => 1 },
|
||
active_price_source => { type => 'text', default => '', not_null => 1 },
|
||
base_qty => { type => 'float', precision => 4, scale => 4 },
|
||
description => { type => 'text' },
|
||
discount => { type => 'float', precision => 4, scale => 4 },
|
||
id => { type => 'serial', not_null => 1 },
|
||
itime => { type => 'timestamp', default => 'now()' },
|
||
lastcost => { type => 'numeric', precision => 15, scale => 5 },
|
||
longdescription => { type => 'text' },
|
||
mtime => { type => 'timestamp' },
|
||
parts_id => { type => 'integer', not_null => 1 },
|
||
position => { type => 'integer', not_null => 1 },
|
||
price_factor => { type => 'numeric', default => 1, precision => 15, scale => 5 },
|
||
price_factor_id => { type => 'integer' },
|
||
pricegroup_id => { type => 'integer' },
|
||
project_id => { type => 'integer' },
|
||
qty => { type => 'float', precision => 4, scale => 4 },
|
||
reason_description_ext => { type => 'text' },
|
||
reason_description_int => { type => 'text' },
|
||
reason_id => { type => 'integer', not_null => 1 },
|
||
reclamation_id => { type => 'integer', not_null => 1 },
|
||
reqdate => { type => 'date' },
|
||
sellprice => { type => 'numeric', precision => 15, scale => 5 },
|
||
serialnumber => { type => 'text' },
|
||
unit => { type => 'varchar', length => 20 },
|
||
);
|
||
|
||
__PACKAGE__->meta->primary_key_columns([ 'id' ]);
|
||
|
||
__PACKAGE__->meta->allow_inline_column_values(1);
|
||
|
||
__PACKAGE__->meta->foreign_keys(
|
||
part => {
|
||
class => 'SL::DB::Part',
|
||
key_columns => { parts_id => 'id' },
|
||
},
|
||
|
||
price_factor_obj => {
|
||
class => 'SL::DB::PriceFactor',
|
||
key_columns => { price_factor_id => 'id' },
|
||
},
|
||
|
||
pricegroup => {
|
||
class => 'SL::DB::Pricegroup',
|
||
key_columns => { pricegroup_id => 'id' },
|
||
},
|
||
|
||
project => {
|
||
class => 'SL::DB::Project',
|
||
key_columns => { project_id => 'id' },
|
||
},
|
||
|
||
reason => {
|
||
class => 'SL::DB::ReclamationReason',
|
||
key_columns => { reason_id => 'id' },
|
||
},
|
||
|
||
reclamation => {
|
||
class => 'SL::DB::Reclamation',
|
||
key_columns => { reclamation_id => 'id' },
|
||
},
|
||
|
||
unit_obj => {
|
||
class => 'SL::DB::Unit',
|
||
key_columns => { unit => 'name' },
|
||
},
|
||
);
|
||
|
||
1;
|
||
;
|
SL/DB/MetaSetup/ReclamationReason.pm | ||
---|---|---|
# This file has been auto-generated. Do not modify it; it will be overwritten
|
||
# by rose_auto_create_model.pl automatically.
|
||
package SL::DB::ReclamationReason;
|
||
|
||
use strict;
|
||
|
||
use parent qw(SL::DB::Object);
|
||
|
||
__PACKAGE__->meta->table('reclamation_reasons');
|
||
|
||
__PACKAGE__->meta->columns(
|
||
description => { type => 'text', not_null => 1 },
|
||
id => { type => 'serial', not_null => 1 },
|
||
itime => { type => 'timestamp', default => 'now()' },
|
||
mtime => { type => 'timestamp' },
|
||
name => { type => 'text', not_null => 1 },
|
||
position => { type => 'integer', not_null => 1 },
|
||
valid_for_purchase => { type => 'boolean', default => 'false', not_null => 1 },
|
||
valid_for_sales => { type => 'boolean', default => 'false', not_null => 1 },
|
||
);
|
||
|
||
__PACKAGE__->meta->primary_key_columns([ 'id' ]);
|
||
|
||
__PACKAGE__->meta->allow_inline_column_values(1);
|
||
|
||
1;
|
||
;
|
SL/DB/Reclamation.pm | ||
---|---|---|
package SL::DB::Reclamation;
|
||
|
||
use utf8;
|
||
use strict;
|
||
|
||
use Carp;
|
||
use DateTime;
|
||
use List::Util qw(max sum0);
|
||
use List::MoreUtils qw(any);
|
||
|
||
use SL::DB::MetaSetup::Reclamation;
|
||
use SL::DB::Manager::Reclamation;
|
||
use SL::DB::Helper::Attr;
|
||
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::Locale::String qw(t8);
|
||
use SL::RecordLinks;
|
||
use Rose::DB::Object::Helpers qw(as_tree);
|
||
|
||
__PACKAGE__->meta->add_relationship(
|
||
|
||
reclamation_items => {
|
||
type => 'one to many',
|
||
class => 'SL::DB::ReclamationItem',
|
||
column_map => { id => 'reclamation_id' },
|
||
manager_args => {
|
||
with_objects => [ 'part', 'reason' ]
|
||
}
|
||
},
|
||
custom_shipto => {
|
||
type => 'one to one',
|
||
class => 'SL::DB::Shipto',
|
||
column_map => { id => 'trans_id' },
|
||
query_args => [ module => 'Reclamation' ],
|
||
},
|
||
exchangerate_obj => {
|
||
type => 'one to one',
|
||
class => 'SL::DB::Exchangerate',
|
||
column_map => { currency_id => 'currency_id', transdate => 'transdate' },
|
||
},
|
||
);
|
||
|
||
SL::DB::Helper::Attr::make(__PACKAGE__, daily_exchangerate => 'numeric');
|
||
|
||
__PACKAGE__->meta->initialize;
|
||
|
||
__PACKAGE__->attr_html('notes');
|
||
__PACKAGE__->attr_sorted('items');
|
||
|
||
__PACKAGE__->before_save('_before_save_set_record_number');
|
||
__PACKAGE__->before_save('_before_save_remove_empty_custom_shipto');
|
||
__PACKAGE__->before_save('_before_save_set_custom_shipto_module');
|
||
|
||
# hooks
|
||
|
||
sub _before_save_set_record_number {
|
||
my ($self) = @_;
|
||
|
||
$self->create_trans_number if !$self->record_number;
|
||
|
||
return 1;
|
||
}
|
||
|
||
sub _before_save_remove_empty_custom_shipto {
|
||
my ($self) = @_;
|
||
|
||
$self->custom_shipto(undef) if $self->custom_shipto && $self->custom_shipto->is_empty;
|
||
|
||
return 1;
|
||
}
|
||
|
||
sub _before_save_set_custom_shipto_module {
|
||
my ($self) = @_;
|
||
|
||
$self->custom_shipto->module('Reclamation') if $self->custom_shipto;
|
||
|
||
return 1;
|
||
}
|
||
|
||
# methods
|
||
|
||
sub items { goto &reclamation_items; }
|
||
sub add_items { goto &add_reclamation_items; }
|
||
sub record_items { goto &reclamation_items; }
|
||
|
||
sub type {
|
||
my ($self) = @_;
|
||
|
||
return 'sales_reclamation' if $self->customer_id;
|
||
return 'purchase_reclamation' if $self->vendor_id;
|
||
|
||
return;
|
||
}
|
||
|
||
sub is_type {
|
||
my ($self, $type) = @_;
|
||
return $self->type eq $type;
|
||
}
|
||
|
||
sub effective_tax_point {
|
||
my ($self) = @_;
|
||
|
||
return $self->tax_point || $self->reqdate || $self->transdate;
|
||
}
|
||
|
||
sub displayable_type {
|
||
my $type = shift->type;
|
||
|
||
return $::locale->text('Sales Reclamation') if $type eq 'sales_reclamation';
|
||
return $::locale->text('Purchase Reclamation') if $type eq 'purchase_reclamation';
|
||
|
||
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 daily_exchangerate {
|
||
my ($self, $val) = @_;
|
||
|
||
return 1 if $self->currency_id == $::instance_conf->get_currency_id;
|
||
|
||
my $rate = (any { $self->is_type($_) } qw(sales_reclamation)) ? 'buy'
|
||
: (any { $self->is_type($_) } qw(purchase_reclamation)) ? 'sell'
|
||
: undef;
|
||
return if !$rate;
|
||
|
||
if (defined $val) {
|
||
croak t8('exchange rate has to be positive') if $val <= 0;
|
||
if (!$self->exchangerate_obj) {
|
||
$self->exchangerate_obj(SL::DB::Exchangerate->new(
|
||
currency_id => $self->currency_id,
|
||
transdate => $self->transdate,
|
||
$rate => $val,
|
||
));
|
||
} elsif (!defined $self->exchangerate_obj->$rate) {
|
||
$self->exchangerate_obj->$rate($val);
|
||
} else {
|
||
croak t8('exchange rate already exists, no update allowed');
|
||
}
|
||
}
|
||
return $self->exchangerate_obj->$rate if $self->exchangerate_obj;
|
||
}
|
||
|
||
sub taxes {
|
||
my ($self) = @_;
|
||
# add taxes to recalmation
|
||
my %pat = $self->calculate_prices_and_taxes();
|
||
my @taxes;
|
||
foreach my $tax_id (keys %{ $pat{taxes_by_tax_id} }) {
|
||
my $netamount = sum0 map { $pat{amounts}->{$_}->{amount} } grep { $pat{amounts}->{$_}->{tax_id} == $tax_id } keys %{ $pat{amounts} };
|
||
push(@taxes, { amount => $pat{taxes_by_tax_id}->{$tax_id},
|
||
netamount => $netamount,
|
||
tax => SL::DB::Tax->new(id => $tax_id)->load });
|
||
}
|
||
return \@taxes;
|
||
}
|
||
|
||
sub displayable_state {
|
||
my ($self) = @_;
|
||
|
||
return $self->closed ? $::locale->text('closed') : $::locale->text('open');
|
||
}
|
||
|
||
sub valid_reclamation_reasons {
|
||
my ($self) = @_;
|
||
|
||
my $valid_for_type = ($self->type =~ m{sales} ? 'valid_for_sales' : 'valid_for_purchase');
|
||
return SL::DB::Manager::ReclamationReason->get_all_sorted(
|
||
where => [ $valid_for_type => 1 ]);
|
||
}
|
||
|
||
#TODO(Werner): überprüfen ob alle Felder richtig gestetzt werden
|
||
sub new_from {
|
||
my ($class, $source, %params) = @_;
|
||
my %allowed_sources = map { $_ => 1 } qw(
|
||
SL::DB::Reclamation
|
||
);
|
||
unless( $allowed_sources{ref $source} ) {
|
||
croak("Unsupported source object type '" . ref($source) . "'");
|
||
}
|
||
croak("A destination type must be given as parameter") unless $params{destination_type};
|
||
|
||
my $destination_type = delete $params{destination_type};
|
||
|
||
my @from_tos = (
|
||
#Reclamation
|
||
{ from => 'sales_reclamation', to => 'sales_reclamation', abbr => 'srsr', },
|
||
{ from => 'purchase_reclamation', to => 'purchase_reclamation', abbr => 'prpr', },
|
||
{ from => 'sales_reclamation', to => 'purchase_reclamation', abbr => 'srpr', },
|
||
{ from => 'purchase_reclamation', to => 'sales_reclamation', abbr => 'prsr', },
|
||
);
|
||
my $from_to = (grep { $_->{from} eq $source->type && $_->{to} eq $destination_type} @from_tos)[0];
|
||
if (!$from_to) {
|
||
croak("Cannot convert from '" . $source->type . "' to '" . $destination_type . "'");
|
||
}
|
||
|
||
my $is_abbr_any = sub {
|
||
any { $from_to->{abbr} eq $_ } @_;
|
||
};
|
||
|
||
my %record_args = (
|
||
record_number => undef,
|
||
employee => SL::DB::Manager::Employee->current,
|
||
closed => 0,
|
||
delivered => 0,
|
||
transdate => DateTime->today_local,
|
||
reqdate => DateTime->today_local->next_workday(),
|
||
);
|
||
if ( $is_abbr_any->(qw(srsr prpr srpr prsr)) ) { #Reclamation
|
||
map { $record_args{$_} = $source->$_ } # {{{ for vim folds
|
||
qw(
|
||
amount
|
||
contact_id
|
||
currency_id
|
||
customer_id
|
||
cv_record_number
|
||
delivery_term_id
|
||
department_id
|
||
exchangerate
|
||
globalproject_id
|
||
intnotes
|
||
language_id
|
||
netamount
|
||
notes
|
||
payment_id
|
||
salesman_id
|
||
shippingpoint
|
||
shipvia
|
||
tax_point
|
||
taxincluded
|
||
taxzone_id
|
||
transaction_description
|
||
vendor_id
|
||
); # }}} for vim folds
|
||
}
|
||
|
||
if ( ($from_to->{from} =~ m{sales}) && ($from_to->{to} =~ m{purchase}) ) {
|
||
$record_args{customer_id} = undef;
|
||
$record_args{salesman_id} = undef;
|
||
$record_args{payment_id} = undef;
|
||
$record_args{delivery_term_id} = undef;
|
||
}
|
||
if ( ($from_to->{from} =~ m{purchase}) && ($from_to->{to} =~ m{sales}) ) {
|
||
$record_args{vendor_id} = undef;
|
||
$record_args{salesman_id} = undef;
|
||
$record_args{payment_id} = undef;
|
||
}
|
||
|
||
|
||
if ($source->can('shipto_id')) {
|
||
# Custom shipto addresses (the ones specific to the sales/purchase record and
|
||
# not to the customer/vendor) are only linked from shipto → record.
|
||
# Meaning record.shipto_id will not be filled in that case.
|
||
if (!$source->shipto_id && $source->id) {
|
||
$record_args{custom_shipto} = $source->custom_shipto->clone($class) if $source->can('custom_shipto') && $source->custom_shipto;
|
||
} elsif ($source->shipto_id) {
|
||
$record_args{shipto_id} = $source->shipto_id;
|
||
}
|
||
}
|
||
|
||
my $reclamation = $class->new(%record_args);
|
||
$reclamation->assign_attributes(%{ $params{attributes} }) if $params{attributes};
|
||
|
||
unless ($params{no_linked_records}) {
|
||
$reclamation->{"converted_from_record_type_ref"} = ref($source);
|
||
$reclamation->{"converted_from_record_id"} = $source->id;
|
||
};
|
||
|
||
my $items = delete($params{items}) || $source->items;
|
||
|
||
my @items = map { SL::DB::ReclamationItem->new_from($_, $from_to->{to}, no_linked_records => $params{no_linked_records}); } @{ $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};
|
||
|
||
$reclamation->items(\@items);
|
||
return $reclamation;
|
||
}
|
||
|
||
sub customervendor {
|
||
my ($reclamation) = @_;
|
||
return $reclamation->is_sales ? $reclamation->customer : $reclamation->vendor;
|
||
}
|
||
|
||
sub date {
|
||
goto &transdate;
|
||
}
|
||
|
||
sub digest {
|
||
my ($self) = @_;
|
||
|
||
sprintf "%s %s %s (%s)",
|
||
$self->record_number,
|
||
$self->customervendor->name,
|
||
$self->amount_as_number,
|
||
$self->date->to_kivitendo;
|
||
}
|
||
|
||
1;
|
||
|
||
__END__
|
||
|
||
=pod
|
||
|
||
=encoding utf8
|
||
|
||
=head1 NAME
|
||
|
||
SL::DB::Reclamation - reclamation Datenbank Objekt.
|
||
|
||
=head1 FUNCTIONS
|
||
|
||
=head2 C<type>
|
||
|
||
Returns one of the following string types:
|
||
|
||
=over 4
|
||
|
||
=item sales_reclamation
|
||
|
||
=item purchase_reclamation
|
||
|
||
=item sales_quotation
|
||
|
||
=item request_quotation
|
||
|
||
=back
|
||
|
||
=head2 C<is_type TYPE>
|
||
|
||
Returns true if the reclamation is of the given type.
|
||
|
||
=head2 C<daily_exchangerate $val>
|
||
|
||
Gets or sets the exchangerate object's value. This is the value from the
|
||
table C<exchangerate> depending on the reclamation's currency, the transdate and
|
||
if it is a sales or purchase reclamation.
|
||
|
||
The reclamation object (respectively the table C<oe>) has an own column
|
||
C<exchangerate> which can be get or set with the accessor C<exchangerate>.
|
||
|
||
The idea is to drop the legacy table C<exchangerate> in the future and to
|
||
give all relevant tables it's own C<exchangerate> column.
|
||
|
||
So, this method is here if you need to access the "legacy" exchangerate via
|
||
an reclamation object.
|
||
|
||
=over 4
|
||
|
||
=item C<$val>
|
||
|
||
(optional) If given, the exchangerate in the "legacy" table is set to this
|
||
value, depending on currency, transdate and sales or purchase.
|
||
|
||
=back
|
||
|
||
=head2 C<convert_to_delivery_order %params>
|
||
|
||
Creates a new delivery reclamation with C<$self> as the basis by calling
|
||
L<SL::DB::DeliveryReclamation::new_from>. That delivery reclamation is saved, and
|
||
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.
|
||
|
||
The arguments in C<%params> are passed to
|
||
L<SL::DB::DeliveryReclamation::new_from>.
|
||
|
||
Returns C<undef> on failure. Otherwise the new delivery reclamation will be
|
||
returned.
|
||
|
||
=head2 C<convert_to_invoice %params>
|
||
|
||
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.
|
||
|
||
The arguments in C<%params> are passed to L<SL::DB::Invoice::post>.
|
||
|
||
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.
|
||
|
||
At the moment only sales quotations and sales reclamations can be converted.
|
||
|
||
=head2 C<new_from $source, %params>
|
||
|
||
Creates a new C<SL::DB::Reclamation> 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 reclamations from
|
||
sales quotations and purchase reclamations from requests for quotations can be
|
||
created.
|
||
|
||
The C<transdate> field will be set to the current date.
|
||
|
||
The conversion copies the reclamation items as well.
|
||
|
||
Returns the new reclamation 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)
|
||
The type of the newly created object. Can be C<sales_quotation>,
|
||
C<sales_reclamation>, C<purchase_quotation> or C<purchase_reclamation> for now.
|
||
|
||
=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
|
||
reclamation.
|
||
|
||
=back
|
||
|
||
=head1 BUGS
|
||
|
||
Nothing here yet.
|
||
|
||
=head1 AUTHOR
|
||
|
||
Sven Schöling <s.schoeling@linet-services.de>
|
||
|
||
=cut
|
SL/DB/ReclamationItem.pm | ||
---|---|---|
package SL::DB::ReclamationItem;
|
||
|
||
use utf8;
|
||
use strict;
|
||
|
||
use List::MoreUtils qw(any);
|
||
|
||
use SL::DB::MetaSetup::ReclamationItem;
|
||
use SL::DB::Manager::ReclamationItem;
|
||
use SL::DB::ReclamationReason;
|
||
use SL::DB::Helper::ActsAsList;
|
||
use SL::DB::Helper::LinkedRecords;
|
||
use SL::DB::Helper::RecordItem;
|
||
use SL::DB::Helper::CustomVariables (
|
||
sub_module => 'reclamation_items',
|
||
cvars_alias => 1,
|
||
overloads => {
|
||
parts_id => {
|
||
class => 'SL::DB::Part',
|
||
module => 'IC',
|
||
}
|
||
},
|
||
);
|
||
use SL::Helper::ShippedQty;
|
||
|
||
__PACKAGE__->meta->initialize;
|
||
|
||
__PACKAGE__->configure_acts_as_list(group_by => [qw(reclamation_id)]);
|
||
|
||
sub shipped_qty {
|
||
my ($self, %params) = @_;
|
||
|
||
my $force = delete $params{force};
|
||
|
||
SL::Helper::ShippedQty->new(%params)->calculate($self)->write_to_objects if $force || !defined $self->{shipped_qty};
|
||
|
||
$self->{shipped_qty};
|
||
}
|
||
|
||
sub is_linked_to_record {
|
||
my ($self) = @_;
|
||
|
||
if(scalar(@{$self->linked_records}) || $self->{converted_from_record_item_type_ref}) {
|
||
return 1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
#TODO(Werner): überprüfen ob alle Felder richtig gestetzt werden
|
||
sub new_from {
|
||
my ($class, $source, $parent_type, %params) = @_;
|
||
unless (any {ref($source) eq $_}
|
||
qw(
|
||
SL::DB::ReclamationItem
|
||
)
|
||
) {
|
||
croak("Unsupported source object type '" . ref($source) . "'");
|
||
}
|
||
my @custom_variables = map { _clone_cvar_for_reclamation_item($_) } @{ $source->custom_variables };
|
||
|
||
|
||
my %item_args;
|
||
if (ref($source) eq 'SL::DB::ReclamationItem') {
|
||
map { $item_args{$_} = $source->$_ } qw(
|
||
active_discount_source active_price_source base_qty description discount
|
||
lastcost longdescription parts_id position price_factor price_factor_id
|
||
pricegroup_id project_id qty reason_description_ext reason_description_int
|
||
reason_id reqdate sellprice serialnumber
|
||
unit
|
||
);
|
||
$item_args{custom_variables} = \@custom_variables;
|
||
}
|
||
|
||
my $item = $class->new(%item_args);
|
||
|
||
if ( $source->record->is_sales() && ($parent_type =~ m{sales}) ) {
|
||
$item->sellprice($source->lastcost);
|
||
$item->discount(0);
|
||
}
|
||
if ( !$source->record->is_sales() && ($parent_type =~ m{purchase}) ) {
|
||
$item->lastcost($source->sellprice);
|
||
}
|
||
|
||
$item->assign_attributes(%{ $params{attributes} }) if $params{attributes};
|
||
|
||
unless ($params{no_linked_records}) {
|
||
$item->{"converted_from_record_item_type_ref"} = ref($source);
|
||
$item->{"converted_from_record_item_id"} = $source->id;
|
||
}
|
||
|
||
return $item;
|
||
}
|
||
|
||
sub _clone_cvar_for_reclamation_item {
|
||
my ($cvar) = @_;
|
||
|
||
my $cloned = $_->clone_and_reset;
|
||
$cloned->sub_module('reclamation_items');
|
||
|
||
return $cloned;
|
||
}
|
||
|
||
sub customervendor {
|
||
my ($self) = @_;
|
||
|
||
return $self->reclamation->customervendor;
|
||
}
|
||
|
||
sub delivered_qty { goto &shipped_qty }
|
||
sub record { goto &reclamation }
|
||
sub record_id { goto &reclamation_id }
|
||
sub trans_id { goto &reclamation_id }
|
||
sub date { goto &reqdate }
|
||
|
||
1;
|
||
|
||
__END__
|
||
|
||
=pod
|
||
|
||
=head1 NAME
|
||
|
||
SL::DB::ReclamationItems: Rose model for reclamationitems
|
||
|
||
=head1 FUNCTIONS
|
||
|
||
=over 4
|
||
|
||
=item C<shipped_qty PARAMS>
|
||
|
||
Calculates the shipped qty for this reclamationitem (measured in the current unit)
|
||
and returns it.
|
||
|
||
Note that the shipped qty is expected not to change within the request and is
|
||
cached in C<shipped_qty> once calculated. If C<< force => 1 >> is passed, the
|
||
existibng cache is ignored.
|
||
|
||
Given parameters will be passed to L<SL::Helper::ShippedQty>, so you can force
|
||
the shipped/delivered distinction like this:
|
||
|
||
$_->shipped_qty(require_stock_out => 0);
|
||
|
||
Note however that calculating shipped_qty on individual Reclamationitems is generally
|
||
a bad idea. See L<SL::Helper::ShippedQty> for way to compute these all at once.
|
||
|
||
=item C<delivered_qty>
|
||
|
||
Alias for L</shipped_qty>.
|
||
|
||
=back
|
||
|
||
=head1 AUTHORS
|
||
|
||
G. Richardson E<lt>grichardson@kivitendo-premium.deE<gt>
|
||
|
||
=cut
|
SL/DB/ReclamationReason.pm | ||
---|---|---|
package SL::DB::ReclamationReason;
|
||
|
||
use strict;
|
||
|
||
use SL::DB::MetaSetup::ReclamationReason;
|
||
use SL::DB::Manager::ReclamationReason;
|
||
use SL::DB::Helper::ActsAsList
|
||
|
||
__PACKAGE__->meta->initialize;
|
||
|
||
sub validate {
|
||
my ($self) = @_;
|
||
|
||
my @errors;
|
||
if (!$self->name) {
|
||
push @errors, $::locale->text('The name is missing.');
|
||
}
|
||
if (!$self->description) {
|
||
push @errors, $::locale->text('The description is missing.');
|
||
}
|
||
return @errors;
|
||
}
|
||
|
||
1;
|
scripts/rose_auto_create_model.pl | ||
---|---|---|
ap => { payment_id => 'payment_terms', },
|
||
|
||
orderitems => { parts_id => 'part', trans_id => 'order', },
|
||
reclamation_items => { parts_id => 'part' },
|
||
delivery_order_items => { parts_id => 'part' },
|
||
invoice => { parts_id => 'part' },
|
||
follow_ups => { created_for_user => 'created_for_employee', created_by => 'created_by_employee', },
|
sql/Pg-upgrade2/clean_up_record_links_before_delete_trigger.sql | ||
---|---|---|
-- @tag: clean_up_record_links_before_delete_trigger
|
||
-- @description: delete trigger for record_links clean up
|
||
-- @depends: release_3_5_7
|
||
|
||
CREATE OR REPLACE FUNCTION clean_up_record_links_before_delete() RETURNS trigger AS $$
|
||
BEGIN
|
||
DELETE FROM record_links
|
||
WHERE (from_table = TG_TABLE_NAME AND from_id = OLD.id)
|
||
OR (to_table = TG_TABLE_NAME AND to_id = OLD.id);
|
||
RETURN OLD;
|
||
END;
|
||
$$ LANGUAGE plpgsql;
|
sql/Pg-upgrade2/delete_cvars_on_trans_deletion_add_reclamation_items.sql | ||
---|---|---|
-- @tag: delete_cvars_on_trans_deletion_add_reclamation_items
|
||
-- @description: Add reclamation_items to trigger
|
||
-- @depends: delete_cvars_on_trans_deletion_add_shipto reclamations
|
||
|
||
CREATE OR REPLACE FUNCTION delete_custom_variables_trigger()
|
||
RETURNS TRIGGER AS $$
|
||
BEGIN
|
||
IF (TG_TABLE_NAME IN ('orderitems', 'delivery_order_items', 'invoice', 'reclamation_items')) THEN
|
||
PERFORM delete_custom_variables_with_sub_module('IC', TG_TABLE_NAME, old.id);
|
||
END IF;
|
||
|
||
IF (TG_TABLE_NAME = 'parts') THEN
|
||
PERFORM delete_custom_variables_with_sub_module('IC', '', old.id);
|
||
END IF;
|
||
|
||
IF (TG_TABLE_NAME IN ('customer', 'vendor')) THEN
|
||
PERFORM delete_custom_variables_with_sub_module('CT', '', old.id);
|
||
END IF;
|
||
|
||
IF (TG_TABLE_NAME = 'contacts') THEN
|
||
PERFORM delete_custom_variables_with_sub_module('Contacts', '', old.cp_id);
|
||
END IF;
|
||
|
||
IF (TG_TABLE_NAME = 'project') THEN
|
||
PERFORM delete_custom_variables_with_sub_module('Projects', '', old.id);
|
||
END IF;
|
||
|
||
IF (TG_TABLE_NAME = 'shipto') THEN
|
||
PERFORM delete_custom_variables_with_sub_module('ShipTo', '', old.shipto_id);
|
||
END IF;
|
||
|
||
RETURN old;
|
||
END;
|
||
$$ LANGUAGE plpgsql;
|
||
|
||
CREATE TRIGGER reclamation_items_delete_custom_variables_after_deletion
|
||
AFTER DELETE ON reclamation_items
|
||
FOR EACH ROW EXECUTE PROCEDURE delete_custom_variables_trigger();
|
sql/Pg-upgrade2/reclamations.sql | ||
---|---|---|
-- @tag: reclamations
|
||
-- @description: Add reclamations, reclamation_items and reclamation_reasons
|
||
-- @depends: release_3_5_7
|
||
-- @ignore: 0
|
||
|
||
CREATE TABLE reclamation_reasons (
|
||
id SERIAL PRIMARY KEY,
|
||
name TEXT NOT NULL,
|
||
description TEXT NOT NULL,
|
||
position INTEGER NOT NULL,
|
||
itime TIMESTAMP without time zone DEFAULT now(),
|
||
mtime TIMESTAMP without time zone,
|
||
valid_for_sales BOOLEAN NOT NULL DEFAULT false,
|
||
valid_for_purchase BOOLEAN NOT NULL DEFAULT false
|
||
);
|
||
CREATE TRIGGER mtime_reclamation_reasons
|
||
BEFORE UPDATE ON reclamation_reasons
|
||
FOR EACH ROW EXECUTE PROCEDURE set_mtime();
|
||
|
||
CREATE TABLE reclamations (
|
||
--basic
|
||
id INTEGER NOT NULL DEFAULT nextval('id'),
|
||
record_number TEXT NOT NULL,
|
||
transdate DATE DEFAULT now(),
|
||
itime TIMESTAMP without time zone DEFAULT now(),
|
||
mtime TIMESTAMP without time zone,
|
||
delivered BOOLEAN NOT NULL DEFAULT false,
|
||
closed BOOLEAN NOT NULL DEFAULT false,
|
||
--header
|
||
employee_id INTEGER NOT NULL REFERENCES employee(id),
|
||
globalproject_id INTEGER REFERENCES project(id),
|
||
delivery_term_id INTEGER REFERENCES delivery_terms(id),
|
||
shipto_id INTEGER REFERENCES shipto(shipto_id),
|
||
department_id INTEGER REFERENCES department(id),
|
||
contact_id INTEGER REFERENCES contacts(cp_id),
|
||
shipvia TEXT,
|
||
transaction_description TEXT,
|
||
shippingpoint TEXT,
|
||
cv_record_number TEXT,
|
||
reqdate DATE,
|
||
--money/summery
|
||
amount NUMERIC(15,5),
|
||
netamount NUMERIC(15,5),
|
||
payment_id INTEGER REFERENCES payment_terms(id),
|
||
currency_id INTEGER NOT NULL REFERENCES currencies(id),
|
||
taxincluded BOOLEAN NOT NULL,
|
||
tax_point DATE,
|
||
exchangerate NUMERIC(15,5),
|
||
taxzone_id INTEGER NOT NULL REFERENCES tax_zones(id),
|
||
--other
|
||
notes TEXT,
|
||
intnotes TEXT,
|
||
language_id INTEGER REFERENCES language(id),
|
||
|
||
salesman_id INTEGER REFERENCES employee(id),
|
||
customer_id INTEGER REFERENCES customer(id),
|
||
|
||
vendor_id INTEGER REFERENCES vendor(id),
|
||
|
||
CONSTRAINT reclamations_customervendor_check CHECK (
|
||
(customer_id IS NOT NULL AND vendor_id IS NULL)
|
||
OR (vendor_id IS NOT NULL AND customer_id IS NULL)
|
||
),
|
||
|
||
PRIMARY KEY (id)
|
||
);
|
||
CREATE TRIGGER mtime_reclamations BEFORE UPDATE ON reclamations FOR EACH ROW EXECUTE PROCEDURE set_mtime();
|
||
|
||
ALTER TABLE defaults ADD COLUMN p_reclamation_record_number TEXT NOT NULL DEFAULT 0;
|
||
ALTER TABLE defaults ADD COLUMN s_reclamation_record_number TEXT NOT NULL DEFAULT 0;
|
||
|
||
CREATE TABLE reclamation_items (
|
||
--base
|
||
id SERIAL PRIMARY KEY,
|
||
reclamation_id INTEGER NOT NULL REFERENCES reclamations(id) ON DELETE CASCADE,
|
||
reason_id INTEGER NOT NULL REFERENCES reclamation_reasons(id),
|
||
reason_description_ext TEXT,
|
||
reason_description_int TEXT,
|
||
position INTEGER NOT NULL CHECK(position > 0),
|
||
itime TIMESTAMP without time zone DEFAULT now(),
|
||
mtime TIMESTAMP without time zone,
|
||
--header
|
||
project_id INTEGER REFERENCES project(id) ON DELETE SET NULL,
|
||
--part description
|
||
parts_id INTEGER NOT NULL REFERENCES parts(id),
|
||
description TEXT,
|
||
longdescription TEXT,
|
||
serialnumber TEXT,
|
||
base_qty REAL,
|
||
qty REAL,
|
||
unit character varying(20) REFERENCES units(name),
|
||
--money
|
||
sellprice NUMERIC(15,5),
|
||
lastcost NUMERIC(15,5),
|
||
discount REAL,
|
||
pricegroup_id INTEGER REFERENCES pricegroup(id),
|
||
price_factor_id INTEGER REFERENCES price_factors(id),
|
||
price_factor NUMERIC(15,5) DEFAULT 1,
|
||
active_price_source TEXT NOT NULL DEFAULT ''::text,
|
||
active_discount_source TEXT NOT NULL DEFAULT ''::text,
|
||
--other
|
||
reqdate DATE
|
||
);
|
||
CREATE TRIGGER mtime_reclamation_items BEFORE UPDATE ON reclamation_items FOR EACH ROW EXECUTE PROCEDURE set_mtime();
|
sql/Pg-upgrade2/record_links_delete_triggers__reclamations__reclamation_items.sql | ||
---|---|---|
-- @tag: record_links_delete_triggers__reclamations__reclamation_items
|
||
-- @description: delete corresponding record_links if reclamation or reclamation_item is deleted
|
||
-- @depends: clean_up_record_links_before_delete_trigger reclamations
|
||
|
||
CREATE TRIGGER before_delete_reclamation_items_clean_up_record_linkes_trigger
|
||
BEFORE DELETE ON reclamation_items FOR EACH ROW EXECUTE
|
||
PROCEDURE clean_up_record_links_before_delete();
|
||
|
||
CREATE TRIGGER before_delete_reclamations_clean_up_record_linkes_trigger
|
||
BEFORE DELETE ON reclamations FOR EACH ROW EXECUTE
|
||
PROCEDURE clean_up_record_links_before_delete();
|
Auch abrufbar als: Unified diff
Reclamation: sql-script for data tables and rose objects added