Wiederkehrende Rechnungen: Auftragswerts-Periodizität setzen können
[kivitendo-erp.git] / SL / DB / Part.pm
index ce9738f..2a74b74 100644 (file)
@@ -3,30 +3,56 @@ package SL::DB::Part;
 use strict;
 
 use Carp;
 use strict;
 
 use Carp;
+use List::MoreUtils qw(any);
+use Rose::DB::Object::Helpers qw(as_tree);
+
 use SL::DBUtils;
 use SL::DB::MetaSetup::Part;
 use SL::DB::Manager::Part;
 use SL::DBUtils;
 use SL::DB::MetaSetup::Part;
 use SL::DB::Manager::Part;
+use SL::DB::Chart;
+use SL::DB::Helper::AttrHTML;
+use SL::DB::Helper::TransNumberGenerator;
+use SL::DB::Helper::CustomVariables (
+  module      => 'IC',
+  cvars_alias => 1,
+);
 
 __PACKAGE__->meta->add_relationships(
 
 __PACKAGE__->meta->add_relationships(
-  unit_obj                     => {
-    type         => 'one to one',
-    class        => 'SL::DB::Unit',
-    column_map   => { unit => 'name' },
-  },
   assemblies                     => {
     type         => 'one to many',
     class        => 'SL::DB::Assembly',
     column_map   => { id => 'id' },
   },
   assemblies                     => {
     type         => 'one to many',
     class        => 'SL::DB::Assembly',
     column_map   => { id => 'id' },
   },
-  partsgroup                     => {
-    type         => 'one to one',
-    class        => 'SL::DB::PartsGroup',
-    column_map   => { partsgroup_id => 'id' },
+  prices         => {
+    type         => 'one to many',
+    class        => 'SL::DB::Price',
+    column_map   => { id => 'parts_id' },
+  },
+  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' },
   },
 );
 
 __PACKAGE__->meta->initialize;
 
   },
 );
 
 __PACKAGE__->meta->initialize;
 
+__PACKAGE__->attr_html('notes');
+
+__PACKAGE__->before_save('_before_save_set_partnumber');
+
+sub _before_save_set_partnumber {
+  my ($self) = @_;
+
+  $self->create_trans_number if !$self->partnumber;
+  return 1;
+}
+
 sub is_type {
   my $self = shift;
   my $type  = lc(shift || '');
 sub is_type {
   my $self = shift;
   my $type  = lc(shift || '');
@@ -75,7 +101,6 @@ sub orphaned {
     SL::DB::InvoiceItem
     SL::DB::OrderItem
     SL::DB::Inventory
     SL::DB::InvoiceItem
     SL::DB::OrderItem
     SL::DB::Inventory
-    SL::DB::RMAItem
   );
 
   for my $class (@relations) {
   );
 
   for my $class (@relations) {
@@ -113,12 +138,85 @@ sub buchungsgruppe {
   shift->buchungsgruppen(@_);
 }
 
   shift->buchungsgruppen(@_);
 }
 
+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;
+  my $tk_info  = $::request->cache('get_taxkey');
+
+  $tk_info->{$self->id}                                      //= {};
+  $tk_info->{$self->id}->{$taxzone}                          //= { };
+  my $cache = $tk_info->{$self->id}->{$taxzone}->{$is_sales} //= { };
+
+  if (!exists $cache->{$date}) {
+    $cache->{$date} =
+      $self->get_chart(type => $is_sales ? 'income' : 'expense', taxzone => $taxzone)
+      ->get_active_taxkey($date);
+  }
+
+  return $cache->{$date};
+}
+
+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;
+
+  my $charts     = $::request->cache('get_chart_id/by_part_id_and_taxzone')->{$self->id} //= {};
+  my $all_charts = $::request->cache('get_chart_id/by_id');
+
+  $charts->{$taxzone} ||= { };
+
+  if (!exists $charts->{$taxzone}->{$type}) {
+    require SL::DB::Buchungsgruppe;
+    my $bugru    = SL::DB::Buchungsgruppe->load_cached($self->buchungsgruppen_id);
+    my $chart_id = ($type eq 'inventory') ? ($self->inventory_accno_id ? $bugru->inventory_accno_id : undef)
+                 :                          $bugru->call_sub("${type}_accno_id", $taxzone);
+
+    if ($chart_id) {
+      my $chart                    = $all_charts->{$chart_id} // SL::DB::Chart->load_cached($chart_id)->load;
+      $all_charts->{$chart_id}     = $chart;
+      $charts->{$taxzone}->{$type} = $chart;
+    }
+  }
+
+  return $charts->{$taxzone}->{$type};
+}
+
+# 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}) }
+}
+
+sub displayable_name {
+  join ' ', grep $_, map $_[0]->$_, qw(partnumber description);
+}
+
 1;
 
 __END__
 
 =pod
 
 1;
 
 __END__
 
 =pod
 
+=encoding utf-8
+
 =head1 NAME
 
 SL::DB::Part: Model for the 'parts' table
 =head1 NAME
 
 SL::DB::Part: Model for the 'parts' table
@@ -150,24 +248,30 @@ 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.
 
 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.
 
-Matching these there are assorted helper methods dealing with type:
+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
 
 
-=head2 new_part PARAMS
+=item C<new_part %PARAMS>
 
 
-=head2 new_service PARAMS
+=item C<new_service %PARAMS>
 
 
-=head2 new_assembly PARAMS
+=item C<new_assembly %PARAMS>
 
 Will set the appropriate data fields so that the resulting instance will be of
 tthe requested type. Since part of the distinction are accounting targets,
 providing a C<Buchungsgruppe> is recommended. If none is given the constructor
 will load a default one and set the accounting targets from it.
 
 
 Will set the appropriate data fields so that the resulting instance will be of
 tthe requested type. Since part of the distinction are accounting targets,
 providing a C<Buchungsgruppe> is recommended. If none is given the constructor
 will load a default one and set the accounting targets from it.
 
-=head2 type
+=item C<type>
 
 Returns the type as a string. Can be one of C<part>, C<service>, C<assembly>.
 
 
 Returns the type as a string. Can be one of C<part>, C<service>, C<assembly>.
 
-=head2 is_type TYPE
+=item C<is_type $TYPE>
 
 Tests if the current object is a part, a service or an
 assembly. C<$type> must be one of the words 'part', 'service' or
 
 Tests if the current object is a part, a service or an
 assembly. C<$type> must be one of the words 'part', 'service' or
@@ -176,17 +280,15 @@ assembly. C<$type> must be one of the words 'part', 'service' or
 Returns 1 if the requested type matches, 0 if it doesn't and
 C<confess>es if an unknown C<$type> parameter is encountered.
 
 Returns 1 if the requested type matches, 0 if it doesn't and
 C<confess>es if an unknown C<$type> parameter is encountered.
 
-=head2 is_part
+=item C<is_part>
 
 
-=head2 is_service
+=item C<is_service>
 
 
-=head2 is_assembly
+=item C<is_assembly>
 
 
-Shorthand for is_type('part') etc.
-
-=head1 FUNCTIONS
+Shorthand for C<is_type('part')> etc.
 
 
-=head2 get_sellprice_info %params
+=item C<get_sellprice_info %params>
 
 Retrieves the C<sellprice> and C<price_factor_id> for a part under
 different conditions and returns a hash reference with those two keys.
 
 Retrieves the C<sellprice> and C<price_factor_id> for a part under
 different conditions and returns a hash reference with those two keys.
@@ -200,24 +302,54 @@ entry without a country set will be used.
 If none of the above conditions is met then the information from
 C<$self> is used.
 
 If none of the above conditions is met then the information from
 C<$self> is used.
 
-=head2 get_ordered_qty %params
+=item C<get_ordered_qty %params>
 
 Retrieves the quantity that has been ordered from a vendor but that
 has not been delivered yet. Only open purchase orders are considered.
 
 
 Retrieves the quantity that has been ordered from a vendor but that
 has not been delivered yet. Only open purchase orders are considered.
 
-=head2 orphaned
+=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
+zone given by C<$params{taxzone}>.
+
+The information retrieved by the function is cached.
+
+=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
+fields belonging to the tax zone given by C<$params{taxzone}>.
+
+The information retrieved by the function is cached.
+
+=item C<orphaned>
 
 Checks if this articke is used in orders, invoices, delivery orders or
 assemblies.
 
 
 Checks if this articke is used in orders, invoices, delivery orders or
 assemblies.
 
-=head2 buchungsgruppe BUCHUNGSGRUPPE
+=item C<buchungsgruppe BUCHUNGSGRUPPE>
 
 Used to set the accounting informations from a L<SL:DB::Buchungsgruppe> object.
 Please note, that this is a write only accessor, the original Buchungsgruppe can
 not be retrieved from an article once set.
 
 
 Used to set the accounting informations from a L<SL:DB::Buchungsgruppe> object.
 Please note, that this is a write only accessor, the original Buchungsgruppe can
 not be retrieved from an article once set.
 
-=head1 AUTHOR
+=back
+
+=head1 AUTHORS
 
 
-Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
+Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>,
+Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>
 
 =cut
 
 =cut