4c3f4633a25cca4d1b581c7ec3d9eca9b6ef0de3
[kivitendo-erp.git] / SL / DB / Part.pm
1 package SL::DB::Part;
2
3 use strict;
4
5 use Carp;
6 use SL::DBUtils;
7 use SL::DB::MetaSetup::Part;
8 use SL::DB::Manager::Part;
9
10 __PACKAGE__->meta->add_relationships(
11   unit_obj                     => {
12     type         => 'one to one',
13     class        => 'SL::DB::Unit',
14     column_map   => { unit => 'name' },
15   },
16   assemblies                     => {
17     type         => 'one to many',
18     class        => 'SL::DB::Assembly',
19     column_map   => { id => 'id' },
20   },
21   partsgroup                     => {
22     type         => 'one to one',
23     class        => 'SL::DB::PartsGroup',
24     column_map   => { partsgroup_id => 'id' },
25   },
26   price_factor   => {
27     type         => 'one to one',
28     class        => 'SL::DB::PriceFactor',
29     column_map   => { price_factor_id => 'id' },
30   },
31 );
32
33 __PACKAGE__->meta->initialize;
34
35 sub is_type {
36   my $self = shift;
37   my $type  = lc(shift || '');
38   die 'invalid type' unless $type =~ /^(?:part|service|assembly)$/;
39
40   return $self->type eq $type ? 1 : 0;
41 }
42
43 sub is_part     { $_[0]->is_type('part') }
44 sub is_assembly { $_[0]->is_type('assembly') }
45 sub is_service  { $_[0]->is_type('service') }
46
47 sub type {
48   my ($self, $type) = @_;
49   if (@_ > 1) {
50     die 'invalid type' unless $type =~ /^(?:part|service|assembly)$/;
51     $self->assembly(          $type eq 'assembly' ? 1 : 0);
52     $self->inventory_accno_id($type ne 'service'  ? 1 : undef);
53   }
54
55   return 'assembly' if $self->assembly;
56   return 'part'     if $self->inventory_accno_id;
57   return 'service';
58 }
59
60 sub new_part {
61   my ($class, %params) = @_;
62   $class->new(%params, type => 'part');
63 }
64
65 sub new_assembly {
66   my ($class, %params) = @_;
67   $class->new(%params, type => 'assembly');
68 }
69
70 sub new_service {
71   my ($class, %params) = @_;
72   $class->new(%params, type => 'service');
73 }
74
75 sub orphaned {
76   my ($self) = @_;
77   die 'not an accessor' if @_ > 1;
78
79   my @relations = qw(
80     SL::DB::InvoiceItem
81     SL::DB::OrderItem
82     SL::DB::Inventory
83     SL::DB::RMAItem
84   );
85
86   for my $class (@relations) {
87     eval "require $class";
88     return 0 if $class->_get_manager_class->get_all_count(query => [ parts_id => $self->id ]);
89   }
90   return 1;
91 }
92
93 sub get_sellprice_info {
94   my $self   = shift;
95   my %params = @_;
96
97   confess "Missing part id" unless $self->id;
98
99   my $object = $self->load;
100
101   return { sellprice       => $object->sellprice,
102            price_factor_id => $object->price_factor_id };
103 }
104
105 sub get_ordered_qty {
106   my $self   = shift;
107   my %result = SL::DB::Manager::Part->get_ordered_qty($self->id);
108
109   return $result{ $self->id };
110 }
111
112 sub available_units {
113   shift->unit_obj->convertible_units;
114 }
115
116 # autogenerated accessor is slightly off...
117 sub buchungsgruppe {
118   shift->buchungsgruppen(@_);
119 }
120
121 1;
122
123 __END__
124
125 =pod
126
127 =head1 NAME
128
129 SL::DB::Part: Model for the 'parts' table
130
131 =head1 SYNOPSIS
132
133 This is a standard Rose::DB::Object based model and can be used as one.
134
135 =head1 TYPES
136
137 Although the base class is called C<Part> we usually talk about C<Articles> if
138 we mean instances of this class. This is because articles come in three
139 flavours called:
140
141 =over 4
142
143 =item Part     - a single part
144
145 =item Service  - a part without onhand, and without inventory accounting
146
147 =item Assembly - a collection of both parts and services
148
149 =back
150
151 These types are sadly represented by data inside the class and cannot be
152 migrated into a flag. To work around this, each C<Part> object knows what type
153 it currently is. Since the type ist data driven, there ist no explicit setting
154 method for it, but you can construct them explicitly with C<new_part>,
155 C<new_service>, and C<new_assembly>. A Buchungsgruppe should be supplied in this
156 case, but it will use the default Buchungsgruppe if you don't.
157
158 Matching these there are assorted helper methods dealing with type:
159
160 =head2 new_part PARAMS
161
162 =head2 new_service PARAMS
163
164 =head2 new_assembly PARAMS
165
166 Will set the appropriate data fields so that the resulting instance will be of
167 tthe requested type. Since part of the distinction are accounting targets,
168 providing a C<Buchungsgruppe> is recommended. If none is given the constructor
169 will load a default one and set the accounting targets from it.
170
171 =head2 type
172
173 Returns the type as a string. Can be one of C<part>, C<service>, C<assembly>.
174
175 =head2 is_type TYPE
176
177 Tests if the current object is a part, a service or an
178 assembly. C<$type> must be one of the words 'part', 'service' or
179 'assembly' (their plurals are ok, too).
180
181 Returns 1 if the requested type matches, 0 if it doesn't and
182 C<confess>es if an unknown C<$type> parameter is encountered.
183
184 =head2 is_part
185
186 =head2 is_service
187
188 =head2 is_assembly
189
190 Shorthand for is_type('part') etc.
191
192 =head1 FUNCTIONS
193
194 =head2 get_sellprice_info %params
195
196 Retrieves the C<sellprice> and C<price_factor_id> for a part under
197 different conditions and returns a hash reference with those two keys.
198
199 If C<%params> contains a key C<project_id> then a project price list
200 will be consulted if one exists for that project. In this case the
201 parameter C<country_id> is evaluated as well: if a price list entry
202 has been created for this country then it will be used. Otherwise an
203 entry without a country set will be used.
204
205 If none of the above conditions is met then the information from
206 C<$self> is used.
207
208 =head2 get_ordered_qty %params
209
210 Retrieves the quantity that has been ordered from a vendor but that
211 has not been delivered yet. Only open purchase orders are considered.
212
213 =head2 orphaned
214
215 Checks if this articke is used in orders, invoices, delivery orders or
216 assemblies.
217
218 =head2 buchungsgruppe BUCHUNGSGRUPPE
219
220 Used to set the accounting informations from a L<SL:DB::Buchungsgruppe> object.
221 Please note, that this is a write only accessor, the original Buchungsgruppe can
222 not be retrieved from an article once set.
223
224 =head1 AUTHOR
225
226 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
227
228 =cut