Projekt

Allgemein

Profil

Herunterladen (19,7 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
9e24ed15 Bernd Bleßmann
use SL::Locale::String qw(t8);
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;
dcc967a6 Sven Schöling
use SL::DB::Helper::AttrSorted;
8d87ea57 Moritz Bunkus
use SL::DB::Helper::TransNumberGenerator;
e1bf173b Sven Schöling
use SL::DB::Helper::CustomVariables (
module => 'IC',
cvars_alias => 1,
);
9e24ed15 Bernd Bleßmann
use SL::DB::Helper::DisplayableNamePreferences (
title => t8('Article'),
options => [ {name => 'partnumber', title => t8('Part Number') },
{name => 'description', title => t8('Description') },
{name => 'notes', title => t8('Notes')},
{name => 'ean', title => t8('EAN') }, ],
);

0d5564ee Geoffrey Richardson
use List::Util qw(sum);
82515b2d Sven Schöling
__PACKAGE__->meta->add_relationships(
assemblies => {
type => 'one to many',
class => 'SL::DB::Assembly',
ea8bf973 Geoffrey Richardson
manager_args => { sort_by => 'position, oid' },
82515b2d Sven Schöling
column_map => { id => 'id' },
},
7ade1b95 Moritz Bunkus
prices => {
type => 'one to many',
class => 'SL::DB::Price',
column_map => { id => 'parts_id' },
266b3889 Werner Hahn
manager_args => { with_objects => [ 'pricegroup' ] }
7ade1b95 Moritz Bunkus
},
39e13b0f Moritz Bunkus
makemodels => {
type => 'one to many',
class => 'SL::DB::MakeModel',
1663057f Geoffrey Richardson
manager_args => { sort_by => 'sortorder' },
39e13b0f Moritz Bunkus
column_map => { id => 'parts_id' },
},
62f21410 Martin Helmling
customerprices => {
type => 'one to many',
class => 'SL::DB::PartCustomerPrice',
column_map => { id => 'parts_id' },
},
39e13b0f Moritz Bunkus
translations => {
type => 'one to many',
class => 'SL::DB::Translation',
column_map => { id => 'parts_id' },
},
f2b44f13 Geoffrey Richardson
assortment_items => {
type => 'one to many',
class => 'SL::DB::AssortmentItem',
column_map => { id => 'assortment_id' },
},
2fd17923 Geoffrey Richardson
history_entries => {
type => 'one to many',
class => 'SL::DB::History',
column_map => { id => 'trans_id' },
query_args => [ what_done => 'part' ],
manager_args => { sort_by => 'itime' },
},
b2bedb6b Werner Hahn
shop_parts => {
type => 'one to many',
class => 'SL::DB::ShopPart',
column_map => { id => 'part_id' },
manager_args => { with_objects => [ 'shop' ] },
},
82515b2d Sven Schöling
);

__PACKAGE__->meta->initialize;

52131da1 Moritz Bunkus
__PACKAGE__->attr_html('notes');
dcc967a6 Sven Schöling
__PACKAGE__->attr_sorted({ unsorted => 'makemodels', position => 'sortorder' });
52131da1 Moritz Bunkus
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;
}

06e79bf0 Geoffrey Richardson
sub items {
my ($self) = @_;

if ( $self->part_type eq 'assembly' ) {
return $self->assemblies;
} elsif ( $self->part_type eq 'assortment' ) {
return $self->assortment_items;
} else {
return undef;
}
}

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

# for detecting if the items of an (orphaned) assembly or assortment have
# changed when saving

return join(' ', sort map { $_->part->id } @{$self->items});
};

53d15b75 Geoffrey Richardson
sub validate {
my ($self) = @_;

my @errors;
f2779405 Geoffrey Richardson
push @errors, $::locale->text('The partnumber is missing.') if $self->id and !$self->partnumber;
53d15b75 Geoffrey Richardson
push @errors, $::locale->text('The unit is missing.') unless $self->unit;
push @errors, $::locale->text('The buchungsgruppe is missing.') unless $self->buchungsgruppen_id or $self->buchungsgruppe;

unless ( $self->id ) {
push @errors, $::locale->text('The partnumber already exists.') if SL::DB::Manager::Part->get_all_count(where => [ partnumber => $self->partnumber ]);
};

06e79bf0 Geoffrey Richardson
if ($self->is_assortment && $self->orphaned && scalar @{$self->assortment_items} == 0) {
# when assortment isn't orphaned form doesn't contain any items
53d15b75 Geoffrey Richardson
push @errors, $::locale->text('The assortment doesn\'t have any items.');
}

if ($self->is_assembly && scalar @{$self->assemblies} == 0) {
push @errors, $::locale->text('The assembly doesn\'t have any items.');
}

return @errors;
}

82515b2d Sven Schöling
sub is_type {
my $self = shift;
my $type = lc(shift || '');
65de6f61 Geoffrey Richardson
die 'invalid type' unless $type =~ /^(?:part|service|assembly|assortment)$/;
82515b2d Sven Schöling
adecbacd Sven Schöling
return $self->type eq $type ? 1 : 0;
}
82515b2d Sven Schöling
65de6f61 Geoffrey Richardson
sub is_part { $_[0]->part_type eq 'part' }
sub is_assembly { $_[0]->part_type eq 'assembly' }
sub is_service { $_[0]->part_type eq 'service' }
sub is_assortment { $_[0]->part_type eq 'assortment' }
82515b2d Sven Schöling
adecbacd Sven Schöling
sub type {
98b64fe1 Geoffrey Richardson
return $_[0]->part_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);
# }

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

sub new_part {
my ($class, %params) = @_;
98b64fe1 Geoffrey Richardson
$class->new(%params, part_type => 'part');
adecbacd Sven Schöling
}

sub new_assembly {
my ($class, %params) = @_;
98b64fe1 Geoffrey Richardson
$class->new(%params, part_type => 'assembly');
adecbacd Sven Schöling
}

sub new_service {
my ($class, %params) = @_;
98b64fe1 Geoffrey Richardson
$class->new(%params, part_type => 'service');
adecbacd Sven Schöling
}

65de6f61 Geoffrey Richardson
sub new_assortment {
my ($class, %params) = @_;
$class->new(%params, part_type => 'assortment');
}

3c28eab6 Geoffrey Richardson
sub last_modification {
my ($self) = @_;
67ad549c Sven Schöling
return $self->mtime // $self->itime;
3c28eab6 Geoffrey Richardson
};

bf78e242 Geoffrey Richardson
sub used_in_record {
my ($self) = @_;
die 'not an accessor' if @_ > 1;

return 1 unless $self->id;

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

for my $class (@relations) {
eval "require $class";
return 1 if $class->_get_manager_class->get_all_count(query => [ parts_id => $self->id ]);
}
return 0;
}
adecbacd Sven Schöling
sub orphaned {
my ($self) = @_;
die 'not an accessor' if @_ > 1;

06e79bf0 Geoffrey Richardson
return 1 unless $self->id;

adecbacd Sven Schöling
my @relations = qw(
SL::DB::InvoiceItem
SL::DB::OrderItem
06e79bf0 Geoffrey Richardson
SL::DB::DeliveryOrderItem
adecbacd Sven Schöling
SL::DB::Inventory
65de6f61 Geoffrey Richardson
SL::DB::AssortmentItem
adecbacd Sven Schöling
);

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);
fb5b97ff Geoffrey Richardson
my $chart_id = ($type eq 'inventory') ? ($self->is_part ? $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
}

179e3c4c Geoffrey Richardson
sub get_stock {
my ($self, %params) = @_;

return undef unless $self->id;

my $query = 'SELECT SUM(qty) FROM inventory WHERE parts_id = ?';
my @values = ($self->id);

if ( $params{bin_id} ) {
$query .= ' AND bin_id = ?';
push(@values, $params{bin_id});
}

if ( $params{warehouse_id} ) {
$query .= ' AND warehouse_id = ?';
push(@values, $params{warehouse_id});
}

if ( $params{shippingdate} ) {
die unless ref($params{shippingdate}) eq 'DateTime';
$query .= ' AND shippingdate <= ?';
push(@values, $params{shippingdate});
}

my ($stock) = selectrow_query($::form, $self->db->dbh, $query, @values);

return $stock || 0; # never return undef
};


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}) }
}

fdfa4918 Geoffrey Richardson
sub get_simple_stock_sql {
my ($self, %params) = @_;

return [] unless $self->id;

my $query = <<SQL;
SELECT w.description AS warehouse_description,
b.description AS bin_description,
SUM(i.qty) AS qty,
SUM(i.qty * p.lastcost) AS stock_value,
p.unit AS unit,
LEAD(w.description) OVER pt AS wh_lead, -- to detect warehouse changes for subtotals
SUM( SUM(i.qty) ) OVER pt AS run_qty, -- running total of total qty
SUM( SUM(i.qty) ) OVER wh AS wh_run_qty, -- running total of warehouse qty
SUM( SUM(i.qty * p.lastcost)) OVER pt AS run_stock_value, -- running total of total stock_value
SUM( SUM(i.qty * p.lastcost)) OVER wh AS wh_run_stock_value -- running total of warehouse stock_value
FROM inventory i
LEFT JOIN parts p ON (p.id = i.parts_id)
LEFT JOIN warehouse w ON (i.warehouse_id = w.id)
LEFT JOIN bin b ON (i.bin_id = b.id)
WHERE parts_id = ?
GROUP BY w.description, b.description, p.unit, i.parts_id
HAVING SUM(qty) != 0
WINDOW pt AS (PARTITION BY i.parts_id ORDER BY w.description, b.description, p.unit),
wh AS (PARTITION by w.description ORDER BY w.description, b.description, p.unit)
ORDER BY w.description, b.description
SQL

my $stock_info = selectall_hashref_query($::form, $self->db->dbh, $query, $self->id);
return $stock_info;
}

ae83d528 Geoffrey Richardson
sub clone_and_reset_deep {
my ($self) = @_;

my $clone = $self->clone_and_reset; # resets id and partnumber (primary key and unique constraint)
06e79bf0 Geoffrey Richardson
$clone->makemodels( map { $_->clone_and_reset } @{$self->makemodels} ) if @{$self->makemodels};
$clone->translations( map { $_->clone_and_reset } @{$self->translations} ) if @{$self->translations};
ae83d528 Geoffrey Richardson
if ( $self->is_assortment ) {
06e79bf0 Geoffrey Richardson
# use clone rather than reset_and_clone because the unique constraint would also remove parts_id
ae83d528 Geoffrey Richardson
$clone->assortment_items( map { $_->clone } @{$self->assortment_items} );
06e79bf0 Geoffrey Richardson
$_->assortment_id(undef) foreach @{ $clone->assortment_items }
ae83d528 Geoffrey Richardson
};

if ( $self->is_assembly ) {
$clone->assemblies( map { $_->clone_and_reset } @{$self->assemblies});
};

if ( $self->prices ) {
$clone->prices( map { $_->clone } @{$self->prices}); # pricegroup_id gets reset here because it is part of a unique contraint
if ( $clone->prices ) {
foreach my $price ( @{$clone->prices} ) {
$price->id(undef);
$price->parts_id(undef);
};
};
};

return $clone;
}

06e79bf0 Geoffrey Richardson
sub item_diffs {
my ($self, $comparison_part) = @_;
0d5564ee Geoffrey Richardson
06e79bf0 Geoffrey Richardson
die "item_diffs needs a part object" unless ref($comparison_part) eq 'SL::DB::Part';
die "part and comparison_part need to be of the same part_type" unless
( $self->part_type eq 'assembly' or $self->part_type eq 'assortment' )
and ( $comparison_part->part_type eq 'assembly' or $comparison_part->part_type eq 'assortment' )
and $self->part_type eq $comparison_part->part_type;
0d5564ee Geoffrey Richardson
06e79bf0 Geoffrey Richardson
# return [], [] if $self->items_checksum eq $comparison_part->items_checksum;
my @self_part_ids = map { $_->parts_id } $self->items;
my @comparison_part_ids = map { $_->parts_id } $comparison_part->items;
0d5564ee Geoffrey Richardson
06e79bf0 Geoffrey Richardson
my %orig = map{ $_ => 1 } @self_part_ids;
my %comparison = map{ $_ => 1 } @comparison_part_ids;
my (@additions, @removals);
@additions = grep { !exists( $orig{$_} ) } @comparison_part_ids if @comparison_part_ids;
@removals = grep { !exists( $comparison{$_} ) } @self_part_ids if @self_part_ids;

return \@additions, \@removals;
0d5564ee Geoffrey Richardson
};

06e79bf0 Geoffrey Richardson
sub items_sellprice_sum {
my ($self, %params) = @_;

return unless $self->is_assortment or $self->is_assembly;
return unless $self->items;

if ($self->is_assembly) {
return sum map { $_->linetotal_sellprice } @{$self->items};
} else {
return sum map { $_->linetotal_sellprice(%params) } grep { $_->charge } @{$self->items};
}
}

sub items_lastcost_sum {
0d5564ee Geoffrey Richardson
my ($self) = @_;

06e79bf0 Geoffrey Richardson
return unless $self->is_assortment or $self->is_assembly;
return unless $self->items;
sum map { $_->linetotal_lastcost } @{$self->items};
0d5564ee Geoffrey Richardson
};

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

a6774cf0 Geoffrey Richardson
=item Assortment - a collection of items (parts or assemblies)
65de6f61 Geoffrey Richardson
adecbacd Sven Schöling
=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>,
65de6f61 Geoffrey Richardson
C<new_service>, C<new_assembly> and C<new_assortment>. A Buchungsgruppe should be supplied in this
adecbacd Sven Schöling
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.

bf78e242 Geoffrey Richardson
=item C<used_in_record>

Checks if this article has been used in orders, invoices or delivery orders.

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
fdfa4918 Geoffrey Richardson
=item C<get_simple_stock_sql>

Fetches the qty and the stock value for the current part for each bin and
warehouse where the part is in stock (or rather different from 0, might be
negative).

Runs some additional window functions to add the running totals (total running
total and total per warehouse) for qty and stock value to each line.

Using the LEAD(w.description) the template can check if the warehouse
description is about to change, i.e. the next line will contain numbers from a
different warehouse, so that a subtotal line can be added.

The last line will contain the qty total and the total stock value over all
warehouses/bins and can be used to add a line for the grand totals.

78d813b9 Geoffrey Richardson
=item C<items_lastcost_sum>
0d5564ee Geoffrey Richardson
78d813b9 Geoffrey Richardson
Non-recursive lastcost sum of all the items in an assembly or assortment.
0d5564ee Geoffrey Richardson
179e3c4c Geoffrey Richardson
=item C<get_stock %params>

Fetches stock qty in the default unit for a part.

bin_id and warehouse_id may be passed as params. If only a bin_id is passed,
the stock qty for that bin is returned. If only a warehouse_id is passed, the
stock qty for all bins in that warehouse is returned. If a shippingdate is
passed the stock qty for that date is returned.

Examples:
my $qty = $part->get_stock(bin_id => 52);

$part->get_stock(shippingdate => DateTime->today->add(days => -5));

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