6 use List::MoreUtils qw(any);
9 use SL::DB::MetaSetup::Part;
10 use SL::DB::Manager::Part;
12 use SL::DB::Helper::TransNumberGenerator;
14 __PACKAGE__->meta->add_relationships(
17 class => 'SL::DB::Unit',
18 column_map => { unit => 'name' },
21 type => 'one to many',
22 class => 'SL::DB::Assembly',
23 column_map => { id => 'id' },
27 class => 'SL::DB::PartsGroup',
28 column_map => { partsgroup_id => 'id' },
32 class => 'SL::DB::PriceFactor',
33 column_map => { price_factor_id => 'id' },
36 type => 'one to many',
37 class => 'SL::DB::Price',
38 column_map => { id => 'parts_id' },
41 type => 'one to many',
42 class => 'SL::DB::MakeModel',
43 column_map => { id => 'parts_id' },
46 type => 'one to many',
47 class => 'SL::DB::Translation',
48 column_map => { id => 'parts_id' },
51 type => 'one to many',
52 class => 'SL::DB::CustomVariable',
53 column_map => { id => 'trans_id' },
54 query_args => [ config_id => [ \"(SELECT custom_variable_configs.id FROM custom_variable_configs WHERE custom_variable_configs.module = 'IC')" ] ],
58 __PACKAGE__->meta->initialize;
60 __PACKAGE__->before_save('_before_save_set_partnumber');
62 sub _before_save_set_partnumber {
65 $self->create_trans_number if $self->partnumber eq '';
71 my $type = lc(shift || '');
72 die 'invalid type' unless $type =~ /^(?:part|service|assembly)$/;
74 return $self->type eq $type ? 1 : 0;
77 sub is_part { $_[0]->is_type('part') }
78 sub is_assembly { $_[0]->is_type('assembly') }
79 sub is_service { $_[0]->is_type('service') }
82 my ($self, $type) = @_;
84 die 'invalid type' unless $type =~ /^(?:part|service|assembly)$/;
85 $self->assembly( $type eq 'assembly' ? 1 : 0);
86 $self->inventory_accno_id($type ne 'service' ? 1 : undef);
89 return 'assembly' if $self->assembly;
90 return 'part' if $self->inventory_accno_id;
95 my ($class, %params) = @_;
96 $class->new(%params, type => 'part');
100 my ($class, %params) = @_;
101 $class->new(%params, type => 'assembly');
105 my ($class, %params) = @_;
106 $class->new(%params, type => 'service');
111 die 'not an accessor' if @_ > 1;
120 for my $class (@relations) {
121 eval "require $class";
122 return 0 if $class->_get_manager_class->get_all_count(query => [ parts_id => $self->id ]);
127 sub get_sellprice_info {
131 confess "Missing part id" unless $self->id;
133 my $object = $self->load;
135 return { sellprice => $object->sellprice,
136 price_factor_id => $object->price_factor_id };
139 sub get_ordered_qty {
141 my %result = SL::DB::Manager::Part->get_ordered_qty($self->id);
143 return $result{ $self->id };
146 sub available_units {
147 shift->unit_obj->convertible_units;
150 # autogenerated accessor is slightly off...
152 shift->buchungsgruppen(@_);
156 my ($self, %params) = @_;
158 my $date = $params{date} || DateTime->today_local;
159 my $is_sales = !!$params{is_sales};
160 my $taxzone = $params{ defined($params{taxzone}) ? 'taxzone' : 'taxzone_id' } * 1;
162 $self->{__partpriv_taxkey_information} ||= { };
163 my $tk_info = $self->{__partpriv_taxkey_information};
165 $tk_info->{$taxzone} ||= { };
166 $tk_info->{$taxzone}->{$is_sales} ||= { };
168 if (!exists $tk_info->{$taxzone}->{$is_sales}->{$date}) {
169 $tk_info->{$taxzone}->{$is_sales}->{$date} =
170 $self->get_chart(type => $is_sales ? 'income' : 'expense', taxzone => $taxzone)
172 ->get_active_taxkey($date);
175 return $tk_info->{$taxzone}->{$is_sales}->{$date};
179 my ($self, %params) = @_;
181 my $type = (any { $_ eq $params{type} } qw(income expense inventory)) ? $params{type} : croak("Invalid 'type' parameter '$params{type}'");
182 my $taxzone = $params{ defined($params{taxzone}) ? 'taxzone' : 'taxzone_id' } * 1;
184 $self->{__partpriv_get_chart_id} ||= { };
185 my $charts = $self->{__partpriv_get_chart_id};
187 $charts->{$taxzone} ||= { };
189 if (!exists $charts->{$taxzone}->{$type}) {
190 my $bugru = $self->buchungsgruppe;
191 my $chart_id = ($type eq 'inventory') ? ($self->inventory_accno_id ? $bugru->inventory_accno_id : undef)
192 : $bugru->call_sub("${type}_accno_id_${taxzone}");
194 $charts->{$taxzone}->{$type} = $chart_id ? SL::DB::Chart->new(id => $chart_id)->load : undef;
197 return $charts->{$taxzone}->{$type};
210 SL::DB::Part: Model for the 'parts' table
214 This is a standard Rose::DB::Object based model and can be used as one.
218 Although the base class is called C<Part> we usually talk about C<Articles> if
219 we mean instances of this class. This is because articles come in three
224 =item Part - a single part
226 =item Service - a part without onhand, and without inventory accounting
228 =item Assembly - a collection of both parts and services
232 These types are sadly represented by data inside the class and cannot be
233 migrated into a flag. To work around this, each C<Part> object knows what type
234 it currently is. Since the type ist data driven, there ist no explicit setting
235 method for it, but you can construct them explicitly with C<new_part>,
236 C<new_service>, and C<new_assembly>. A Buchungsgruppe should be supplied in this
237 case, but it will use the default Buchungsgruppe if you don't.
239 Matching these there are assorted helper methods dealing with types,
240 e.g. L</new_part>, L</new_service>, L</new_assembly>, L</type>,
241 L</is_type> and others.
247 =item C<new_part %PARAMS>
249 =item C<new_service %PARAMS>
251 =item C<new_assembly %PARAMS>
253 Will set the appropriate data fields so that the resulting instance will be of
254 tthe requested type. Since part of the distinction are accounting targets,
255 providing a C<Buchungsgruppe> is recommended. If none is given the constructor
256 will load a default one and set the accounting targets from it.
260 Returns the type as a string. Can be one of C<part>, C<service>, C<assembly>.
262 =item C<is_type $TYPE>
264 Tests if the current object is a part, a service or an
265 assembly. C<$type> must be one of the words 'part', 'service' or
266 'assembly' (their plurals are ok, too).
268 Returns 1 if the requested type matches, 0 if it doesn't and
269 C<confess>es if an unknown C<$type> parameter is encountered.
277 Shorthand for C<is_type('part')> etc.
279 =item C<get_sellprice_info %params>
281 Retrieves the C<sellprice> and C<price_factor_id> for a part under
282 different conditions and returns a hash reference with those two keys.
284 If C<%params> contains a key C<project_id> then a project price list
285 will be consulted if one exists for that project. In this case the
286 parameter C<country_id> is evaluated as well: if a price list entry
287 has been created for this country then it will be used. Otherwise an
288 entry without a country set will be used.
290 If none of the above conditions is met then the information from
293 =item C<get_ordered_qty %params>
295 Retrieves the quantity that has been ordered from a vendor but that
296 has not been delivered yet. Only open purchase orders are considered.
298 =item C<get_taxkey %params>
300 Retrieves and returns a taxkey object valid for the given date
301 C<$params{date}> and tax zone C<$params{taxzone}>
302 (C<$params{taxzone_id}> is also recognized). The date defaults to the
303 current date if undefined.
305 This function looks up the income (for trueish values of
306 C<$params{is_sales}>) or expense (for falsish values of
307 C<$params{is_sales}>) account for the current part. It uses the part's
308 associated buchungsgruppe and uses the fields belonging to the tax
309 zone given by C<$params{taxzone}> (range 0..3).
311 The information retrieved by the function is cached.
313 =item C<get_chart %params>
315 Retrieves and returns a chart object valid for the given type
316 C<$params{type}> and tax zone C<$params{taxzone}>
317 (C<$params{taxzone_id}> is also recognized). The type must be one of
318 the three key words C<income>, C<expense> and C<inventory>.
320 This function uses the part's associated buchungsgruppe and uses the
321 fields belonging to the tax zone given by C<$params{taxzone}> (range
324 The information retrieved by the function is cached.
328 Checks if this articke is used in orders, invoices, delivery orders or
331 =item C<buchungsgruppe BUCHUNGSGRUPPE>
333 Used to set the accounting informations from a L<SL:DB::Buchungsgruppe> object.
334 Please note, that this is a write only accessor, the original Buchungsgruppe can
335 not be retrieved from an article once set.
341 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>,
342 Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>