File Coverage

blib/lib/OpenERP/OOM/Class/Base.pm
Criterion Covered Total %
statement 35 227 15.4
branch 0 82 0.0
condition 0 21 0.0
subroutine 14 42 33.3
pod 14 14 100.0
total 63 386 16.3


line stmt bran cond sub pod time code
1              
2             use 5.010;
3 2     2   141418 use Carp;
  2         16  
4 2     2   10 use Moose;
  2         3  
  2         108  
5 2     2   450 use RPC::XML;
  2         435759  
  2         11  
6 2     2   13503 use DateTime;
  2         1681071  
  2         97  
7 2     2   17 use DateTime::Format::Strptime;
  2         4  
  2         38  
8 2     2   12 use MooseX::NotRequired;
  2         21  
  2         19  
9 2     2   1043 use Try::Tiny;
  2         704  
  2         59  
10 2     2   14 use Try::Tiny::Retry;
  2         3  
  2         107  
11 2     2   780 use Time::HiRes qw/usleep/;
  2         2381  
  2         117  
12 2     2   13  
  2         6  
  2         14  
13             extends 'Moose::Object';
14             with 'OpenERP::OOM::DynamicUtils';
15              
16             =head1 NAME
17              
18             OpenERP::OOM::Class::Base
19              
20             =head1 SYNOPSYS
21              
22             my $obj = $schema->class('Name')->create(\%args);
23            
24             foreach my $obj ($schema->class('Name')->search(@query)) {
25             ...
26             }
27              
28             =head1 DESCRIPTION
29              
30             Provides a base set of methods for OpenERP::OOM classes (search, create, etc).
31              
32             =cut
33              
34             has 'schema' => (
35             is => 'ro',
36             );
37              
38             has 'object_class' => (
39             is => 'ro',
40             lazy => 1,
41             builder => '_build_object_class',
42             );
43              
44             my $self = shift;
45            
46 0     0   0 # if you get this blow up it probably means the class doesn't compile for some
47             # reason. Run the t/00-load.t tests. If they pass check you have a use_ok
48             # statement for all your modules.
49             die 'Your code doesn\'t compile llamma' if !$self->can('object');
50             $self->ensure_class_loaded($self->object);
51 0 0       0
52 0         0 $self->object->meta->add_method('class' => sub{return $self});
53            
54 0     0   0 return $self->object->new;
  0         0  
55             }
56 0         0  
57             #-------------------------------------------------------------------------------
58              
59             =head2 search
60              
61             Searches OpenERP and returns a list of objects matching a given query.
62              
63             my @list = $schema->class('Name')->search(
64             ['name', 'ilike', 'OpusVL'],
65             ['active', '=', 1],
66             );
67              
68             The query is formatted as a list of array references, each specifying a
69             column name, operator, and value. The objects returned will be those where
70             all of these sub-queries match.
71              
72             Searches can be performed against OpenERP fields, linked objects (e.g. DBIx::Class
73             relationships), or a combination of both.
74              
75             my @list = $schema->class('Name')->search(
76             ['active', '=', 1],
77             ['details', {status => 'value'}, {}],
78             )
79              
80             In this example, 'details' is a linked DBIx::Class object with a column called
81             'status'.
82              
83             An optional 'search context' can also be provided at the end of the query list, e.g.
84              
85             my @list = $schema->class('Location')->search(
86             ['usage' => '=' => 'internal'],
87             ['active' => '=' => 1],
88             {
89             active_id => $self->id,
90             active_ids => [$self->id],
91             active_model => 'product.product',
92             full => 1,
93             product_id => $self->id,
94             search_default_in_location => 1,
95             section_id => undef,
96             tz => undef,
97             }
98             );
99              
100             Supplying a context further restricts the search, for example to narrow down a
101             'stock by location' query to 'stock of a specific product by location'.
102              
103             Following the search context, an arrayref of options can be given to return a
104             paged set of results:
105              
106             {
107             limit => 10, # Return max 10 results
108             offset => 20, # Start at result 20
109             }
110              
111             =head2 raw_search
112              
113             This is the same as search but it doesn't turn the results into objects. This
114             is useful if your search is likely to have returned fields that aren't part of
115             the object. Queries like those used by the Stock By Location report are likely
116             to return stock levels as well as the location details for example.
117              
118             =cut
119              
120             my $self = shift;
121             return $self->_raw_search(0, @_);
122             }
123 0     0 1 0  
124 0         0 =head2 search_limited_fields
125              
126             This is an alternative version of search that only fills in the required fields
127             of the object.
128              
129             # avoid pulling the whole attachement down for a search
130             my @a = $attachments->search_limited_fields([
131             qw/res_model res_name type url create_uid create_date
132             datas_fname description name res_id/
133             ], [
134             res_model => '=' => 'product.template',
135             res_id => '=' => 1,
136             ]);
137              
138             This allows you to avoid pulling down problem fields. The most obvious example
139             is get a list of attachments for an object, without pulling down all the data
140             for the attachement.
141              
142             =cut
143              
144             my $self = shift;
145             return $self->_search_limited_fields(1, @_);
146             }
147              
148 0     0 1 0 my $self = shift;
149 0         0 my $objects = shift;
150             my $fields = shift;
151              
152             my $ids = $self->_raw_search(1, @_);
153 0     0   0 return wantarray ? () : undef unless ( defined $ids && ref $ids eq 'ARRAY' && scalar @$ids >= 1 );
154 0         0 my ($context) = grep { ref $_ eq 'HASH' } @_;
155 0         0 return $self->_retrieve_list($objects, $ids, $context, $fields);
156             }
157 0         0  
158 0 0 0     0 my ($self, $ids_only, @args) = @_;
    0 0        
159 0         0 ### Initial search args: @args
  0         0  
160 0         0 my @search;
161             while (@args && ref $args[0] ne 'HASH') {push @search, shift @args}
162            
163             # Loop through each search criteria, and if it is a linked object
164 0     0   0 # search, replace it with a translated OpenERP search parameter.
165             foreach my $criteria (@search) {
166 0         0 if(ref $criteria eq 'ARRAY') {
167 0   0     0 my $search_field = $criteria->[0];
  0         0  
168              
169             if (my $link = $self->object_class->meta->link->{$search_field}) {
170             if ($self->schema->link($link->{class})->can('search')) {
171 0         0 my @results = $self->schema->link($link->{class})->search($link->{args}, @$criteria[1 .. @$criteria-1]);
172 0 0       0  
173 0         0 if (@results) {
174             ### Adding to OpenERP search:
175 0 0       0 ### $link->{key}
176 0 0       0 ### IN
177 0         0 ### join(', ', @results)
178             $criteria = [$link->{key}, 'in', \@results];
179 0 0       0 } else {
180             return; # No results found, so no point searching in OpenERP
181             }
182             } else {
183             carp "Cannot search for link type " . $link->{class};
184 0         0 }
185             }
186 0         0 }
187             }
188             my $context = $self->_get_context(shift @args);
189 0         0 my $options = shift @args;
190             $options = {} unless $options;
191             ### Search: @search
192             ### Search context: $context
193             ### Search options: $options
194 0         0 if($ids_only)
195 0         0 {
196 0 0       0 return $self->schema->client->search($self->object_class->model,[@search], $context, $options->{offset}, $options->{limit}, $options->{order});
197             }
198              
199             my $objects = $self->schema->client->search_detail($self->object_class->model,[@search], $context, $options->{offset}, $options->{limit}, $options->{order});
200 0 0       0  
201             if ($objects) {
202 0         0 foreach my $attribute ($self->object_class->meta->get_all_attributes) {
203             if($attribute->type_constraint && $attribute->type_constraint =~ /DateTime/)
204             {
205 0         0 map { $_->{$attribute->name} = $self->_parse_datetime($_->{$attribute->name}) } @$objects;
206             }
207 0 0       0 }
208 0         0 return $objects;
209 0 0 0     0 } else {
210             return undef;
211 0         0 }
  0         0  
212             }
213              
214 0         0 {
215             my $self = shift;
216 0         0 my $objects = $self->raw_search(@_);
217             if($objects) {
218             return map {$self->object_class->new($_)} @$objects;
219             } else {
220             return wantarray ? () : undef;
221             }
222 0     0 1 0 }
223 0         0  
224 0 0       0 =head2 is_not_null
225 0         0  
  0         0  
226             Returns search criteria for a not null search. i.e. equivalend to $field is not null in SQL.
227 0 0       0  
228             $self->search($self->is_not_null('x_department'), [ 'other_field', '=', 3 ]);
229              
230             =cut
231              
232             {
233             my $self = shift;
234             my $field = shift;
235             return [ $field, '!=', RPC::XML::boolean->new(0) ];
236             }
237              
238             =head2 null
239              
240             Returns a 'null' for use in OpenERP calls and objects. (Actually this is a False value).
241 0     0 1 0  
242 0         0 =cut
243 0         0  
244              
245             =head2 is_null
246              
247             Returns search criteria for an is null search. i.e. equivalend to $field is null in SQL.
248              
249             $self->search($self->is_null('x_department'), [ 'other_field', '=', 3 ]);
250              
251             =cut
252 0     0 1 0  
253             {
254             my $self = shift;
255             my $field = shift;
256             return [ $field, '=', RPC::XML::boolean->new(0) ];
257             }
258              
259             #-------------------------------------------------------------------------------
260              
261             =head2 find
262              
263             Returns the first object matching a given query.
264 0     0 1 0  
265 0         0 my $obj = $schema->class('Name')->find(['id', '=', 32]);
266 0         0  
267             Will return C<undef> if no objects matching the query are found.
268              
269             =cut
270              
271             my $self = shift;
272            
273             #my $ids = $self->schema->client->search($self->object_class->model,[@_]);
274             my $ids = $self->raw_search(@_);
275            
276             if ($ids->[0]) {
277             #return $self->retrieve($ids->[0]);
278             return $self->object_class->new($ids->[0]);
279             }
280             }
281              
282 0     0 1 0  
283             =head2 get_options
284              
285 0         0 This returns the options for available for a selection field. It will croak if you
286             try to give it a field that isn't an option.
287 0 0       0  
288             =cut
289 0         0  
290             {
291             my $self = shift;
292             my $field = shift;
293              
294             my $model_info = $self->schema->client->model_fields($self->object_class->model);
295             my $field_info = $model_info->{$field};
296             croak 'Can only get options for selection objects' unless $field_info->{type} eq 'selection';
297             my $options = $field_info->{selection};
298             return $options;
299             }
300              
301             #-------------------------------------------------------------------------------
302              
303 0     0 1 0 =head2 retrieve
304 0         0  
305             Returns an object by ID.
306 0         0  
307 0         0 my $obj = $schema->class('Name')->retrieve(32);
308 0 0       0  
309 0         0 =cut
310 0         0  
311             my ($self, $id, @args) = @_;
312            
313             # FIXME - This should probably be in a try/catch block
314             my $context = $self->_get_context(shift @args);
315             $self->_ensure_object_fields(\@args);
316             if (my $object = $self->schema->client->read_single($self->object_class->model, $id, $context, @args))
317             {
318             return $self->_inflate_object($self->object, $object);
319             }
320             }
321              
322             {
323             my $self = shift;
324 0     0 1 0 my $args = shift;
325              
326             unless(@$args)
327 0         0 {
328 0         0 my @fields;
329 0 0       0 foreach my $attribute ($self->object_class->meta->get_all_attributes)
330             {
331 0         0 my $name = $attribute->name;
332             push @fields, $name unless $name =~ /^_/;
333             }
334             push @$args, \@fields;
335             }
336             }
337 0     0   0  
338 0         0 {
339             my $self = shift;
340 0 0       0 my $context = shift;
341              
342 0         0 my %translation = ( lang => $self->schema->lang );
343 0         0 if($context)
344             {
345 0         0 # merge the context with our language for translation.
346 0 0       0 @translation{keys %$context} = values %$context;
347             }
348 0         0 $context = \%translation;
349             return $context;
350             }
351              
352             {
353             my $self = shift;
354 0     0   0 my $object_class = shift;
355 0         0 my $object = shift;
356              
357 0         0 foreach my $attribute ($self->object_class->meta->get_all_attributes) {
358 0 0       0 if($attribute->type_constraint && $attribute->type_constraint =~ /DateTime/)
359             {
360             $object->{$attribute->name} = $self->_parse_datetime($object->{$attribute->name});
361 0         0 }
362             }
363 0         0 return $object_class->new($object);
364 0         0 }
365              
366             my ($self, $string, $format) = @_;
367             return unless $string;
368             my $parser = DateTime::Format::Strptime->new(pattern => $format, time_zone => 'UTC');
369 0     0   0 return $parser->parse_datetime($string);
370 0         0 }
371 0         0  
372             my ($self, $string) = @_;
373 0         0 return $self->_do_strptime($string, '%Y-%m-%d %H:%M:%S') // $self->_do_strptime($string, '%Y-%m-%d');
374 0 0 0     0 }
375              
376 0         0 =head2 default_values
377              
378             Returns an instance of the object filled in with the default values suggested by OpenERP.
379 0         0  
380             =cut
381             {
382             my $self = shift;
383 0     0   0 my $context = shift;
384 0 0       0 # do a default_get
385 0         0  
386 0         0 my @fields = map { $_->name } $self->object_class->meta->get_all_attributes;
387             my $object = $self->schema->client->get_defaults($self->object_class->model, \@fields, $context);
388             my $class = MooseX::NotRequired::make_optional_subclass($self->object);
389             return $self->_inflate_object($class, $object);
390 0     0   0 }
391 0   0     0  
392             =head2 create_related_object_for_DBIC
393              
394             Creates a related DBIC object for an object of this class (before the object
395             is created).
396              
397             It returns a transaction guard alongside the id so that if the corresponding
398             object fails to create it can be aborted.
399              
400             This can make the link up smoother as you know the id of the object to refer
401 0     0 1 0 to in OpenERP before creating the OpenERP object. It also allows for failures
402 0         0 to be dealt with more reliably.
403              
404             my ($id, $guard) = $self->create_related_object_for_DBIC('details', $details);
405 0         0 # Create the object
  0         0  
406 0         0 $object->{x_dbic_link_id} = $id;
407 0         0 $object->{default_code} = sprintf("OBJ%06d", $id);
408 0         0  
409             my $prod = $self->$orig($object);
410             $guard->commit;
411              
412             =cut
413              
414             {
415             my ($self, $relation_name, $data) = @_;
416             my $object = $self->object_class;
417             my $relation = $object->meta->link->{$relation_name};
418             if($relation)
419             {
420             die 'Wrong type of relation' unless $relation->{class} eq 'DBIC';
421             my $link = $self->schema->link($relation->{class});
422             my $guard = $link->dbic_schema->storage->txn_scope_guard;
423             my $id = $link->create($relation->{args}, $data);
424             return ($id, $guard);
425             }
426             else
427             {
428             die 'Unable to find relation';
429             }
430             }
431             #-------------------------------------------------------------------------------
432              
433             =head2 retrieve_list
434              
435 0     0 1 0 Takes a reference to a list of object IDs and returns a list of objects.
436 0         0  
437 0         0 my @list = $schema->class('Name')->retrieve_list([32, 15, 60]);
438 0 0       0  
439             =cut
440 0 0       0  
441 0         0 my $self = shift;
442 0         0 return $self->_retrieve_list(1, @_);
443 0         0 }
444 0         0  
445             my ($self, $inflate_objects, $ids, @args) = @_;
446            
447             my $context = $self->_get_context(shift @args);
448 0         0 $self->_ensure_object_fields(\@args);
449             if (my $objects = $self->schema->client->read($self->object_class->model, $ids, $context, @args)) {
450             foreach my $attribute ($self->object_class->meta->get_all_attributes) {
451             if($attribute->type_constraint && $attribute->type_constraint =~ /DateTime/)
452             {
453             map { $_->{$attribute->name} = $self->_parse_datetime($_->{$attribute->name}) } @$objects;
454             }
455             }
456             my %id_map = map { $_->{id} => $_ } @$objects;
457             my @sorted = map { $id_map{$_} } @$ids;
458             return map {$self->object_class->new($_)} @sorted if $inflate_objects;
459             return @sorted;
460             }
461             }
462 0     0 1 0  
463 0         0  
464             #-------------------------------------------------------------------------------
465              
466             {
467 0     0   0 my ($self, $object_data) = @_;
468              
469 0         0 my $relationships = $self->object_class->meta->relationship;
470 0         0 while (my ($name, $rel) = each %$relationships) {
471 0 0       0 if ($rel->{type} eq 'one2many') {
472 0         0 if ($object_data->{$name}) {
473 0 0 0     0 $object_data->{$rel->{key}} = $self->_id($rel, $object_data->{$name});
474             delete $object_data->{$name} if $name ne $rel->{key};
475 0         0 }
  0         0  
476             }
477            
478 0         0 if ($rel->{type} eq 'many2one') {
  0         0  
479 0         0 if ($object_data->{$name}) {
  0         0  
480 0 0       0 $object_data->{$rel->{key}} = $self->_id($rel, $object_data->{$name});
  0         0  
481 0         0 }
482             if ($name ne $rel->{key}) {
483             delete $object_data->{$name};
484             }
485             }
486             if ($rel->{type} eq 'many2many') {
487             if ($object_data->{$name}) {
488             my $val = $object_data->{$name};
489             my @ids;
490 0     0   0 if(ref $val eq 'ARRAY')
491             {
492 0         0 # they passed in an arrayref.
493 0         0 my $objects = $val;
494 0 0       0 @ids = map { $self->_id($rel, $_) } @$objects;
495 0 0       0 }
496 0         0 else
497 0 0       0 {
498             # assume it's a single object.
499             push @ids, $self->_id($rel, $val);
500             }
501 0 0       0 $object_data->{$rel->{key}} = [[ 6, 0, \@ids ]];
502 0 0       0 delete $object_data->{$name} if $name ne $rel->{key};
503 0         0 }
504             }
505 0 0       0 }
506 0         0 # Force Str parameters to be object type RPC::XML::string
507             foreach my $attribute ($self->object_class->meta->get_all_attributes) {
508             if (exists $object_data->{$attribute->name}) {
509 0 0       0 $object_data->{$attribute->name} = $self->prepare_attribute_for_send($attribute->type_constraint, $object_data->{$attribute->name});
510 0 0       0 }
511 0         0 }
512 0         0 return $object_data;
513 0 0       0 }
514              
515             {
516 0         0 my $self = shift;
517 0         0 my $rel = shift;
  0         0  
518             my $val = shift;
519             my $ref = ref $val;
520             if($ref)
521             {
522 0         0 # FIXME: this is close to what I want but I need to be doing it with the class
523             # that corresponds to the relation we're delving into.
524 0         0 if($ref eq 'HASH')
525 0 0       0 {
526             my $class = $self->schema->class($rel->{class});
527             return [[ 0, 0, $class->_collapse_data_to_ids($val) ]];
528             }
529             elsif($ref eq 'ARRAY')
530 0         0 {
531 0 0       0 # this should allow us to do child objects too.
532 0         0 my $class = $self->schema->class($rel->{class});
533             my @expanded = map { [ 0, 0, $class->_collapse_data_to_ids($_) ] } @$val;
534             return \@expanded;
535 0         0 }
536             else
537             {
538             return $val->id;
539             }
540 0     0   0 }
541 0         0 return $val;
542 0         0 }
543 0         0  
544 0 0       0 =head2 create
545              
546             Creates a new instance of an object in OpenERP.
547              
548 0 0       0 my $obj = $schema->class('Name')->create({
    0          
549             name => 'OpusVL',
550 0         0 active => 1,
551 0         0 });
552              
553             Takes a hashref of object parameters.
554              
555             Returns the new object or C<undef> if it could not be created.
556 0         0  
557 0         0 =cut
  0         0  
558 0         0  
559             my ($self, $object_data, @args) = @_;
560              
561             ### Create called with initial object data:
562 0         0 ### $object_data;
563            
564             $object_data = $self->_collapse_data_to_ids($object_data);
565 0         0  
566             ### To
567             ### $object_data;
568             my $id;
569             $self->_with_retries(sub {
570             $id = $self->schema->client->create($self->object_class->model, $object_data, @args);
571             });
572             if ($id)
573             {
574             return $self->retrieve($id);
575             }
576             }
577              
578             {
579             my $self = shift;
580             my $call = shift;
581             retry
582             {
583             $call->();
584 0     0 1 0 }
585             retry_if {/current transaction is aborted, commands ignored until end of transaction block/}
586             catch
587             {
588             die $_; # rethrow the unhandled exception
589 0         0 };
590             }
591              
592              
593 0         0 #-------------------------------------------------------------------------------
594              
595 0     0   0 =head2 execute
596 0         0  
597 0 0       0 Performs an execute in OpenERP on the class level.
598              
599 0         0 $c->model('OpenERP')->class('Invoice')->execute('build_invoice', $args);
600              
601             Please look at L<OpenERP::OOM::Object::Base> for more information on C<execute>
602              
603             =cut
604              
605 4     4   11132 my $self = shift;
606 4         7 my $action = shift;
607             my @params = @_;
608             my @args = ($action, $self->object_class->model, @params);
609 17     17   11947603 my $retval;
610             $self->_with_retries(sub {
611 16     16   3379 $retval = $self->schema->client->object_execute(@args);
612             });
613             return $retval;
614 3     3   113 }
615 4         27  
616             #-------------------------------------------------------------------------------
617              
618              
619             1;