Projekt

Allgemein

Profil

Herunterladen (9,91 KB) Statistiken
| Zweig: | Markierung: | Revision:
82515b2d Sven Schöling
package SL::DB::Part;

use strict;

use Carp;
a6bf2bd6 Moritz Bunkus
use List::MoreUtils qw(any);
57faab8f Sven Schöling
use Rose::DB::Object::Helpers qw(as_tree);
a6bf2bd6 Moritz Bunkus
82515b2d Sven Schöling
use SL::DBUtils;
use SL::DB::MetaSetup::Part;
use SL::DB::Manager::Part;
7d26ae8a Moritz Bunkus
use SL::DB::Chart;
52131da1 Moritz Bunkus
use SL::DB::Helper::AttrHTML;
8d87ea57 Moritz Bunkus
use SL::DB::Helper::TransNumberGenerator;
e1bf173b Sven Schöling
use SL::DB::Helper::CustomVariables (
module => 'IC',
cvars_alias => 1,
);
82515b2d Sven Schöling
__PACKAGE__->meta->add_relationships(
assemblies => {
type => 'one to many',
class => 'SL::DB::Assembly',
column_map => { id => 'id' },
},
7ade1b95 Moritz Bunkus
prices => {
type => 'one to many',
class => 'SL::DB::Price',
column_map => { id => 'parts_id' },
},
39e13b0f Moritz Bunkus
makemodels => {
type => 'one to many',
class => 'SL::DB::MakeModel',
column_map => { id => 'parts_id' },
},
translations => {
type => 'one to many',
class => 'SL::DB::Translation',
column_map => { id => 'parts_id' },
},
82515b2d Sven Schöling
);

__PACKAGE__->meta->initialize;

52131da1 Moritz Bunkus
__PACKAGE__->attr_html('notes');

8d87ea57 Moritz Bunkus
__PACKAGE__->before_save('_before_save_set_partnumber');

sub _before_save_set_partnumber {
my ($self) = @_;

846c2f9e Sven Schöling
$self->create_trans_number if !$self->partnumber;
8d87ea57 Moritz Bunkus
return 1;
}

82515b2d Sven Schöling
sub is_type {
my $self = shift;
my $type = lc(shift || '');
adecbacd Sven Schöling
die 'invalid type' unless $type =~ /^(?:part|service|assembly)$/;
82515b2d Sven Schöling
adecbacd Sven Schöling
return $self->type eq $type ? 1 : 0;
}
82515b2d Sven Schöling
adecbacd Sven Schöling
sub is_part { $_[0]->is_type('part') }
sub is_assembly { $_[0]->is_type('assembly') }
sub is_service { $_[0]->is_type('service') }
82515b2d Sven Schöling
adecbacd Sven Schöling
sub type {
my ($self, $type) = @_;
if (@_ > 1) {
die 'invalid type' unless $type =~ /^(?:part|service|assembly)$/;
$self->assembly( $type eq 'assembly' ? 1 : 0);
$self->inventory_accno_id($type ne 'service' ? 1 : undef);
82515b2d Sven Schöling
}

adecbacd Sven Schöling
return 'assembly' if $self->assembly;
return 'part' if $self->inventory_accno_id;
return 'service';
}

sub new_part {
my ($class, %params) = @_;
$class->new(%params, type => 'part');
}

sub new_assembly {
my ($class, %params) = @_;
$class->new(%params, type => 'assembly');
}

sub new_service {
my ($class, %params) = @_;
$class->new(%params, type => 'service');
}

sub orphaned {
my ($self) = @_;
die 'not an accessor' if @_ > 1;

my @relations = qw(
SL::DB::InvoiceItem
SL::DB::OrderItem
SL::DB::Inventory
);

for my $class (@relations) {
eval "require $class";
return 0 if $class->_get_manager_class->get_all_count(query => [ parts_id => $self->id ]);
}
return 1;
82515b2d Sven Schöling
}

sub get_sellprice_info {
my $self = shift;
my %params = @_;

confess "Missing part id" unless $self->id;

my $object = $self->load;

return { sellprice => $object->sellprice,
price_factor_id => $object->price_factor_id };
}

sub get_ordered_qty {
my $self = shift;
my %result = SL::DB::Manager::Part->get_ordered_qty($self->id);

return $result{ $self->id };
}

sub available_units {
shift->unit_obj->convertible_units;
}

997c9f23 Sven Schöling
# autogenerated accessor is slightly off...
sub buchungsgruppe {
shift->buchungsgruppen(@_);
}

7d26ae8a Moritz Bunkus
sub get_taxkey {
my ($self, %params) = @_;

my $date = $params{date} || DateTime->today_local;
my $is_sales = !!$params{is_sales};
my $taxzone = $params{ defined($params{taxzone}) ? 'taxzone' : 'taxzone_id' } * 1;
0a8ac1f3 Moritz Bunkus
my $tk_info = $::request->cache('get_taxkey');
7d26ae8a Moritz Bunkus
0682dd7e Moritz Bunkus
$tk_info->{$self->id} //= {};
$tk_info->{$self->id}->{$taxzone} //= { };
my $cache = $tk_info->{$self->id}->{$taxzone}->{$is_sales} //= { };
7d26ae8a Moritz Bunkus
0682dd7e Moritz Bunkus
if (!exists $cache->{$date}) {
$cache->{$date} =
a6bf2bd6 Moritz Bunkus
$self->get_chart(type => $is_sales ? 'income' : 'expense', taxzone => $taxzone)
->get_active_taxkey($date);
}
7d26ae8a Moritz Bunkus
0682dd7e Moritz Bunkus
return $cache->{$date};
a6bf2bd6 Moritz Bunkus
}
7d26ae8a Moritz Bunkus
a6bf2bd6 Moritz Bunkus
sub get_chart {
my ($self, %params) = @_;

my $type = (any { $_ eq $params{type} } qw(income expense inventory)) ? $params{type} : croak("Invalid 'type' parameter '$params{type}'");
my $taxzone = $params{ defined($params{taxzone}) ? 'taxzone' : 'taxzone_id' } * 1;

0682dd7e Moritz Bunkus
my $charts = $::request->cache('get_chart_id/by_part_id_and_taxzone')->{$self->id} //= {};
0a8ac1f3 Moritz Bunkus
my $all_charts = $::request->cache('get_chart_id/by_id');
a6bf2bd6 Moritz Bunkus
$charts->{$taxzone} ||= { };
7d26ae8a Moritz Bunkus
a6bf2bd6 Moritz Bunkus
if (!exists $charts->{$taxzone}->{$type}) {
529e6feb Moritz Bunkus
require SL::DB::Buchungsgruppe;
my $bugru = SL::DB::Buchungsgruppe->load_cached($self->buchungsgruppen_id);
a6bf2bd6 Moritz Bunkus
my $chart_id = ($type eq 'inventory') ? ($self->inventory_accno_id ? $bugru->inventory_accno_id : undef)
b989d7cf Geoffrey Richardson
: $bugru->call_sub("${type}_accno_id", $taxzone);
7d26ae8a Moritz Bunkus
0a8ac1f3 Moritz Bunkus
if ($chart_id) {
529e6feb Moritz Bunkus
my $chart = $all_charts->{$chart_id} // SL::DB::Chart->load_cached($chart_id)->load;
0a8ac1f3 Moritz Bunkus
$all_charts->{$chart_id} = $chart;
$charts->{$taxzone}->{$type} = $chart;
}
a6bf2bd6 Moritz Bunkus
}

return $charts->{$taxzone}->{$type};
7d26ae8a Moritz Bunkus
}

666d4cad Sven Schöling
# this is designed to ignore chargenumbers, expiration dates and just give a list of how much <-> where
sub get_simple_stock {
my ($self, %params) = @_;

return [] unless $self->id;

my $query = <<'';
SELECT sum(qty), warehouse_id, bin_id FROM inventory WHERE parts_id = ?
GROUP BY warehouse_id, bin_id

my $stock_info = selectall_hashref_query($::form, $::form->get_standard_dbh, $query, $self->id);
[ map { bless $_, 'SL::DB::Part::SimpleStock'} @$stock_info ];
}
# helper class to have bin/warehouse accessors in stock result
{ package SL::DB::Part::SimpleStock;
sub warehouse { require SL::DB::Warehouse; SL::DB::Manager::Warehouse->find_by_or_create(id => $_[0]->{warehouse_id}) }
sub bin { require SL::DB::Bin; SL::DB::Manager::Bin ->find_by_or_create(id => $_[0]->{bin_id}) }
}

b720e272 Geoffrey Richardson
sub displayable_name {
7a646681 Sven Schöling
join ' ', grep $_, map $_[0]->$_, qw(partnumber description);
}

82515b2d Sven Schöling
1;

__END__

=pod

47a963f4 Moritz Bunkus
=encoding utf-8

82515b2d Sven Schöling
=head1 NAME

SL::DB::Part: Model for the 'parts' table

=head1 SYNOPSIS

This is a standard Rose::DB::Object based model and can be used as one.

adecbacd Sven Schöling
=head1 TYPES

Although the base class is called C<Part> we usually talk about C<Articles> if
we mean instances of this class. This is because articles come in three
flavours called:
82515b2d Sven Schöling
=over 4

adecbacd Sven Schöling
=item Part - a single part

=item Service - a part without onhand, and without inventory accounting

=item Assembly - a collection of both parts and services

=back

These types are sadly represented by data inside the class and cannot be
migrated into a flag. To work around this, each C<Part> object knows what type
734b356d Geoffrey Richardson
it currently is. Since the type is data driven, there ist no explicit setting
adecbacd Sven Schöling
method for it, but you can construct them explicitly with C<new_part>,
C<new_service>, and C<new_assembly>. A Buchungsgruppe should be supplied in this
case, but it will use the default Buchungsgruppe if you don't.

47a963f4 Moritz Bunkus
Matching these there are assorted helper methods dealing with types,
e.g. L</new_part>, L</new_service>, L</new_assembly>, L</type>,
L</is_type> and others.

=head1 FUNCTIONS

=over 4
adecbacd Sven Schöling
47a963f4 Moritz Bunkus
=item C<new_part %PARAMS>
adecbacd Sven Schöling
47a963f4 Moritz Bunkus
=item C<new_service %PARAMS>
adecbacd Sven Schöling
47a963f4 Moritz Bunkus
=item C<new_assembly %PARAMS>
adecbacd Sven Schöling
Will set the appropriate data fields so that the resulting instance will be of
734b356d Geoffrey Richardson
the requested type. Since accounting targets are part of the distinction,
adecbacd Sven Schöling
providing a C<Buchungsgruppe> is recommended. If none is given the constructor
will load a default one and set the accounting targets from it.

47a963f4 Moritz Bunkus
=item C<type>
adecbacd Sven Schöling
Returns the type as a string. Can be one of C<part>, C<service>, C<assembly>.

47a963f4 Moritz Bunkus
=item C<is_type $TYPE>
82515b2d Sven Schöling
Tests if the current object is a part, a service or an
assembly. C<$type> must be one of the words 'part', 'service' or
'assembly' (their plurals are ok, too).

Returns 1 if the requested type matches, 0 if it doesn't and
C<confess>es if an unknown C<$type> parameter is encountered.

47a963f4 Moritz Bunkus
=item C<is_part>
adecbacd Sven Schöling
47a963f4 Moritz Bunkus
=item C<is_service>
adecbacd Sven Schöling
47a963f4 Moritz Bunkus
=item C<is_assembly>
adecbacd Sven Schöling
47a963f4 Moritz Bunkus
Shorthand for C<is_type('part')> etc.
adecbacd Sven Schöling
47a963f4 Moritz Bunkus
=item C<get_sellprice_info %params>
82515b2d Sven Schöling
Retrieves the C<sellprice> and C<price_factor_id> for a part under
different conditions and returns a hash reference with those two keys.

If C<%params> contains a key C<project_id> then a project price list
will be consulted if one exists for that project. In this case the
parameter C<country_id> is evaluated as well: if a price list entry
has been created for this country then it will be used. Otherwise an
entry without a country set will be used.

If none of the above conditions is met then the information from
C<$self> is used.

47a963f4 Moritz Bunkus
=item C<get_ordered_qty %params>
82515b2d Sven Schöling
Retrieves the quantity that has been ordered from a vendor but that
has not been delivered yet. Only open purchase orders are considered.

7d26ae8a Moritz Bunkus
=item C<get_taxkey %params>

Retrieves and returns a taxkey object valid for the given date
C<$params{date}> and tax zone C<$params{taxzone}>
(C<$params{taxzone_id}> is also recognized). The date defaults to the
current date if undefined.

This function looks up the income (for trueish values of
C<$params{is_sales}>) or expense (for falsish values of
C<$params{is_sales}>) account for the current part. It uses the part's
associated buchungsgruppe and uses the fields belonging to the tax
1c62d23e Geoffrey Richardson
zone given by C<$params{taxzone}>.
7d26ae8a Moritz Bunkus
The information retrieved by the function is cached.

a6bf2bd6 Moritz Bunkus
=item C<get_chart %params>

Retrieves and returns a chart object valid for the given type
C<$params{type}> and tax zone C<$params{taxzone}>
(C<$params{taxzone_id}> is also recognized). The type must be one of
the three key words C<income>, C<expense> and C<inventory>.

This function uses the part's associated buchungsgruppe and uses the
1c62d23e Geoffrey Richardson
fields belonging to the tax zone given by C<$params{taxzone}>.
a6bf2bd6 Moritz Bunkus
The information retrieved by the function is cached.

47a963f4 Moritz Bunkus
=item C<orphaned>
82515b2d Sven Schöling
734b356d Geoffrey Richardson
Checks if this article is used in orders, invoices, delivery orders or
adecbacd Sven Schöling
assemblies.
82515b2d Sven Schöling
47a963f4 Moritz Bunkus
=item C<buchungsgruppe BUCHUNGSGRUPPE>
adecbacd Sven Schöling
734b356d Geoffrey Richardson
Used to set the accounting information from a L<SL:DB::Buchungsgruppe> object.
adecbacd Sven Schöling
Please note, that this is a write only accessor, the original Buchungsgruppe can
not be retrieved from an article once set.
82515b2d Sven Schöling
47a963f4 Moritz Bunkus
=back

=head1 AUTHORS
82515b2d Sven Schöling
47a963f4 Moritz Bunkus
Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>,
Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>
82515b2d Sven Schöling
=cut