6 use List::MoreUtils qw(any);
 
   9 use SL::DB::MetaSetup::Part;
 
  10 use SL::DB::Manager::Part;
 
  12 use SL::DB::Helper::TransNumberGenerator;
 
  13 use SL::DB::Helper::CustomVariables (
 
  18 __PACKAGE__->meta->add_relationships(
 
  20     type         => 'one to many',
 
  21     class        => 'SL::DB::Assembly',
 
  22     column_map   => { id => 'id' },
 
  26     class        => 'SL::DB::PartsGroup',
 
  27     column_map   => { partsgroup_id => 'id' },
 
  31     class        => 'SL::DB::PriceFactor',
 
  32     column_map   => { price_factor_id => 'id' },
 
  35     type         => 'one to many',
 
  36     class        => 'SL::DB::Price',
 
  37     column_map   => { id => 'parts_id' },
 
  40     type         => 'one to many',
 
  41     class        => 'SL::DB::MakeModel',
 
  42     column_map   => { id => 'parts_id' },
 
  45     type         => 'one to many',
 
  46     class        => 'SL::DB::Translation',
 
  47     column_map   => { id => 'parts_id' },
 
  51 __PACKAGE__->meta->initialize;
 
  53 __PACKAGE__->before_save('_before_save_set_partnumber');
 
  55 sub _before_save_set_partnumber {
 
  58   $self->create_trans_number if !$self->partnumber;
 
  64   my $type  = lc(shift || '');
 
  65   die 'invalid type' unless $type =~ /^(?:part|service|assembly)$/;
 
  67   return $self->type eq $type ? 1 : 0;
 
  70 sub is_part     { $_[0]->is_type('part') }
 
  71 sub is_assembly { $_[0]->is_type('assembly') }
 
  72 sub is_service  { $_[0]->is_type('service') }
 
  75   my ($self, $type) = @_;
 
  77     die 'invalid type' unless $type =~ /^(?:part|service|assembly)$/;
 
  78     $self->assembly(          $type eq 'assembly' ? 1 : 0);
 
  79     $self->inventory_accno_id($type ne 'service'  ? 1 : undef);
 
  82   return 'assembly' if $self->assembly;
 
  83   return 'part'     if $self->inventory_accno_id;
 
  88   my ($class, %params) = @_;
 
  89   $class->new(%params, type => 'part');
 
  93   my ($class, %params) = @_;
 
  94   $class->new(%params, type => 'assembly');
 
  98   my ($class, %params) = @_;
 
  99   $class->new(%params, type => 'service');
 
 104   die 'not an accessor' if @_ > 1;
 
 113   for my $class (@relations) {
 
 114     eval "require $class";
 
 115     return 0 if $class->_get_manager_class->get_all_count(query => [ parts_id => $self->id ]);
 
 120 sub get_sellprice_info {
 
 124   confess "Missing part id" unless $self->id;
 
 126   my $object = $self->load;
 
 128   return { sellprice       => $object->sellprice,
 
 129            price_factor_id => $object->price_factor_id };
 
 132 sub get_ordered_qty {
 
 134   my %result = SL::DB::Manager::Part->get_ordered_qty($self->id);
 
 136   return $result{ $self->id };
 
 139 sub available_units {
 
 140   shift->unit_obj->convertible_units;
 
 143 # autogenerated accessor is slightly off...
 
 145   shift->buchungsgruppen(@_);
 
 149   my ($self, %params) = @_;
 
 151   my $date     = $params{date} || DateTime->today_local;
 
 152   my $is_sales = !!$params{is_sales};
 
 153   my $taxzone  = $params{ defined($params{taxzone}) ? 'taxzone' : 'taxzone_id' } * 1;
 
 155   $self->{__partpriv_taxkey_information} ||= { };
 
 156   my $tk_info = $self->{__partpriv_taxkey_information};
 
 158   $tk_info->{$taxzone}              ||= { };
 
 159   $tk_info->{$taxzone}->{$is_sales} ||= { };
 
 161   if (!exists $tk_info->{$taxzone}->{$is_sales}->{$date}) {
 
 162     $tk_info->{$taxzone}->{$is_sales}->{$date} =
 
 163       $self->get_chart(type => $is_sales ? 'income' : 'expense', taxzone => $taxzone)
 
 165       ->get_active_taxkey($date);
 
 168   return $tk_info->{$taxzone}->{$is_sales}->{$date};
 
 172   my ($self, %params) = @_;
 
 174   my $type    = (any { $_ eq $params{type} } qw(income expense inventory)) ? $params{type} : croak("Invalid 'type' parameter '$params{type}'");
 
 175   my $taxzone = $params{ defined($params{taxzone}) ? 'taxzone' : 'taxzone_id' } * 1;
 
 177   $self->{__partpriv_get_chart_id} ||= { };
 
 178   my $charts = $self->{__partpriv_get_chart_id};
 
 180   $charts->{$taxzone} ||= { };
 
 182   if (!exists $charts->{$taxzone}->{$type}) {
 
 183     my $bugru    = $self->buchungsgruppe;
 
 184     my $chart_id = ($type eq 'inventory') ? ($self->inventory_accno_id ? $bugru->inventory_accno_id : undef)
 
 185                  :                          $bugru->call_sub("${type}_accno_id_${taxzone}");
 
 187     $charts->{$taxzone}->{$type} = $chart_id ? SL::DB::Chart->new(id => $chart_id)->load : undef;
 
 190   return $charts->{$taxzone}->{$type};
 
 203 SL::DB::Part: Model for the 'parts' table
 
 207 This is a standard Rose::DB::Object based model and can be used as one.
 
 211 Although the base class is called C<Part> we usually talk about C<Articles> if
 
 212 we mean instances of this class. This is because articles come in three
 
 217 =item Part     - a single part
 
 219 =item Service  - a part without onhand, and without inventory accounting
 
 221 =item Assembly - a collection of both parts and services
 
 225 These types are sadly represented by data inside the class and cannot be
 
 226 migrated into a flag. To work around this, each C<Part> object knows what type
 
 227 it currently is. Since the type ist data driven, there ist no explicit setting
 
 228 method for it, but you can construct them explicitly with C<new_part>,
 
 229 C<new_service>, and C<new_assembly>. A Buchungsgruppe should be supplied in this
 
 230 case, but it will use the default Buchungsgruppe if you don't.
 
 232 Matching these there are assorted helper methods dealing with types,
 
 233 e.g.  L</new_part>, L</new_service>, L</new_assembly>, L</type>,
 
 234 L</is_type> and others.
 
 240 =item C<new_part %PARAMS>
 
 242 =item C<new_service %PARAMS>
 
 244 =item C<new_assembly %PARAMS>
 
 246 Will set the appropriate data fields so that the resulting instance will be of
 
 247 tthe requested type. Since part of the distinction are accounting targets,
 
 248 providing a C<Buchungsgruppe> is recommended. If none is given the constructor
 
 249 will load a default one and set the accounting targets from it.
 
 253 Returns the type as a string. Can be one of C<part>, C<service>, C<assembly>.
 
 255 =item C<is_type $TYPE>
 
 257 Tests if the current object is a part, a service or an
 
 258 assembly. C<$type> must be one of the words 'part', 'service' or
 
 259 'assembly' (their plurals are ok, too).
 
 261 Returns 1 if the requested type matches, 0 if it doesn't and
 
 262 C<confess>es if an unknown C<$type> parameter is encountered.
 
 270 Shorthand for C<is_type('part')> etc.
 
 272 =item C<get_sellprice_info %params>
 
 274 Retrieves the C<sellprice> and C<price_factor_id> for a part under
 
 275 different conditions and returns a hash reference with those two keys.
 
 277 If C<%params> contains a key C<project_id> then a project price list
 
 278 will be consulted if one exists for that project. In this case the
 
 279 parameter C<country_id> is evaluated as well: if a price list entry
 
 280 has been created for this country then it will be used. Otherwise an
 
 281 entry without a country set will be used.
 
 283 If none of the above conditions is met then the information from
 
 286 =item C<get_ordered_qty %params>
 
 288 Retrieves the quantity that has been ordered from a vendor but that
 
 289 has not been delivered yet. Only open purchase orders are considered.
 
 291 =item C<get_taxkey %params>
 
 293 Retrieves and returns a taxkey object valid for the given date
 
 294 C<$params{date}> and tax zone C<$params{taxzone}>
 
 295 (C<$params{taxzone_id}> is also recognized). The date defaults to the
 
 296 current date if undefined.
 
 298 This function looks up the income (for trueish values of
 
 299 C<$params{is_sales}>) or expense (for falsish values of
 
 300 C<$params{is_sales}>) account for the current part. It uses the part's
 
 301 associated buchungsgruppe and uses the fields belonging to the tax
 
 302 zone given by C<$params{taxzone}> (range 0..3).
 
 304 The information retrieved by the function is cached.
 
 306 =item C<get_chart %params>
 
 308 Retrieves and returns a chart object valid for the given type
 
 309 C<$params{type}> and tax zone C<$params{taxzone}>
 
 310 (C<$params{taxzone_id}> is also recognized). The type must be one of
 
 311 the three key words C<income>, C<expense> and C<inventory>.
 
 313 This function uses the part's associated buchungsgruppe and uses the
 
 314 fields belonging to the tax zone given by C<$params{taxzone}> (range
 
 317 The information retrieved by the function is cached.
 
 321 Checks if this articke is used in orders, invoices, delivery orders or
 
 324 =item C<buchungsgruppe BUCHUNGSGRUPPE>
 
 326 Used to set the accounting informations from a L<SL:DB::Buchungsgruppe> object.
 
 327 Please note, that this is a write only accessor, the original Buchungsgruppe can
 
 328 not be retrieved from an article once set.
 
 334 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>,
 
 335 Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>