Merge pull request #5205 from solgenomics/topic/generic_trial_upload
[sgn.git] / lib / CXGN / Stock / Seedlot.pm
blob4a6cf9a7662c12f367a3a700f9ff7451b6ee4cab
2 =head1 NAME
4 CXGN::Stock::Seedlot - a class to represent seedlots in the database
6 =head1 DESCRIPTION
8 CXGN::Stock::Seedlot inherits from CXGN::Stock.
10 To create a new seedlot do:
11 #Seedlot can either be from an accession or a cross, therefore, supply an accession_stock_id OR a cross_stock_id here
13 my $sl = CXGN::Stock::Seedlot->new(schema => $schema);
14 $sl->uniquename($seedlot_uniquename);
15 $sl->location_code($location_code);
16 $sl->accession_stock_id($accession_id);
17 $sl->cross_stock_id($cross_id);
18 $sl->organization_name($organization);
19 $sl->population_name($population_name);
20 $sl->breeding_program_id($breeding_program_id);
21 my $return = $sl->store();
22 my $seedlot_id = $return->{seedlot_id};
24 #The first transaction is between the accession_stock_id OR cross_stock_id that you specified above and the new seedlot created.
25 my $transaction = CXGN::Stock::Seedlot::Transaction->new(schema => $schema);
26 $transaction->factor(1);
27 $transaction->from_stock([$from_stock_id, $from_stock_uniquename]);
28 $transaction->to_stock([$seedlot_id, $seedlot_uniquename]);
29 $transaction->amount($amount);
30 $transaction->timestamp($timestamp);
31 $transaction->description($description);
32 $transaction->operator($operator);
33 $transaction->store();
35 $sl->set_current_count_property();
37 $phenome_schema->resultset("StockOwner")->find_or_create({
38 stock_id => $seedlot_id,
39 sp_person_id => $user_id,
40 });
42 -------------------------------------------------------------------------------
44 To Update or Edit a seedlot do:
46 my $seedlot = CXGN::Stock::Seedlot->new(
47 schema => $schema,
48 seedlot_id => $seedlot_id,
50 $seedlot->name($seedlot_name);
51 $seedlot->uniquename($seedlot_name);
52 $seedlot->breeding_program_id($breeding_program_id);
53 $seedlot->organization_name($organization);
54 $seedlot->location_code($location);
55 $seedlot->accession_stock_id($accession_id);
56 $seedlot->cross_stock_id($cross_id);
57 $seedlot->population_name($population);
58 my $return = $seedlot->store();
60 ------------------------------------------------------------------------------
62 To Search Across Seedlots do:
63 # This is different from CXGN::Stock::Search in that is retrieves information pertinent to seedlots like location and current count
65 my ($list, $records_total) = CXGN::Stock::Seedlot->list_seedlots(
66 $c->dbic_schema("Bio::Chado::Schema"),
67 $offset,
68 $limit,
69 $seedlot_name,
70 $description,
71 $breeding_program,
72 $location,
73 $minimum_count,
74 $contents_accession,
75 $contents_cross,
76 $exact_match_uniquenames,
77 $minimum_weight
80 ------------------------------------------------------------------------------
82 To Retrieve a single seedlot do:
84 my $seedlot = CXGN::Stock::Seedlot->new(
85 schema => $schema,
86 seedlot_id => $seedlot_id,
88 # You can access all seedlot accessors from here such as (you can also access all CXGN::Stock accessors):
89 my $uniquename => $seedlot->uniquename(),
90 my $seedlot_id => $seedlot->seedlot_id(),
91 my $current_count => $seedlot->current_count(),
92 my $location_code => $seedlot->location_code(),
93 my $breeding_program => $seedlot->breeding_program_name(),
94 my $organization_name => $seedlot->organization_name(),
95 my $population_name => $seedlot->population_name(),
96 my $accession => $seedlot->accession(),
97 my $cross => $seedlot->cross(),
99 ------------------------------------------------------------------------------
101 Seed transactions can be added using CXGN::Stock::Seedlot::Transaction.
103 ------------------------------------------------------------------------------
105 Seed Maintenance Events can be stored and retrieved using the helper functions
106 in this Seedlot class.
108 To add a Maintenance Event:
110 my $seedlot = CXGN::Stock::Seedlot->new( schema => $schema, seedlot_id => $seedlot_id );
111 my @events = (
113 cvterm_id => $cvterm_id,
114 value => $value,
115 notes => $notes,
116 operator => $operator,
117 timestamp => $timestamp
120 my $stored_events = $seedlot->store_events(\@events);
123 =head1 AUTHOR
125 Lukas Mueller <lam87@cornell.edu>
126 Nick Morales <nm529@cornell.edu>
128 =head1 ACCESSORS & METHODS
130 =cut
132 package CXGN::Stock::Seedlot;
134 use Moose;
135 use DateTime;
137 extends 'CXGN::Stock';
139 use Data::Dumper;
140 use CXGN::Stock::Seedlot::Transaction;
141 use CXGN::BreedersToolbox::Projects;
142 use SGN::Model::Cvterm;
143 use CXGN::List::Validate;
144 use Try::Tiny;
145 use CXGN::Stock::StockLookup;
146 use CXGN::Stock::Search;
147 use JSON::Any;
149 =head2 Accessor seedlot_id()
151 the database id of the seedlot. Is equivalent to stock_id.
153 =cut
155 has 'seedlot_id' => (
156 isa => 'Maybe[Int]',
157 is => 'rw',
160 =head2 Accessor location_code()
162 A string specifiying where the seedlot is stored. On the backend,
163 this is stored the nd_geolocation description field.
165 =cut
167 has 'location_code' => (
168 isa => 'Str',
169 is => 'rw',
170 lazy => 1,
171 builder => '_retrieve_location',
174 has 'nd_geolocation_id' => (
175 isa => 'Int',
176 is => 'rw',
177 lazy => 1,
178 builder => '_retrieve_location_id',
181 =head2 Accessor box_name()
183 A string specifiying box where the seedlot is stored. On the backend,
184 this is stored as a stockprop.
186 =cut
188 has 'box_name' => (
189 isa => 'Str|Undef',
190 is => 'rw',
191 lazy => 1,
192 builder => '_retrieve_box_name',
195 =head2 Accessor cross()
197 The crosses this seedlot is a "collection_of". Returns an arrayref of [$cross_stock_id, $cross_uniquename]
198 # for setter, use cross_stock_id
200 =cut
202 has 'cross' => (
203 isa => 'ArrayRef|Undef',
204 is => 'rw',
205 lazy => 1,
206 builder => '_retrieve_cross',
209 has 'cross_stock_id' => (
210 isa => 'Int|Undef',
211 is => 'rw',
214 =head2 Accessor quality()
216 Allows to store a string describing the quality of this seedlot. A seedlot with no quality issues has no data stored here. Requested initially by AC.
218 =cut
220 has 'quality' => (
221 isa => 'Maybe[Str]',
222 is => 'rw',
223 lazy => 1,
224 builder => '_retrieve_quality',
228 =head2 Accessor source()
230 The source of this seedlot. This can be the plant, plot, or accession it was sourced from, in a seed multiplication experiment, for example, in the absence of a cross experiment. Requested initially by AC.
232 =cut
234 has 'source' => (
235 isa => 'Str',
236 is => 'rw',
237 lazy => 1,
238 builder => '_retrieve_source',
242 =head2 Accessor accessions()
244 The accessions this seedlot is a "collection_of". Returns an arrayref of [$accession_stock_id, $accession_uniquename]
245 # for setter, use accession_stock_id
247 =cut
249 has 'accession' => (
250 isa => 'ArrayRef|Undef',
251 is => 'rw',
252 lazy => 1,
253 builder => '_retrieve_accession',
256 has 'accession_stock_id' => (
257 isa => 'Int|Undef',
258 is => 'rw',
261 =head2 Accessor transactions()
263 a ArrayRef of CXGN::Stock::Seedlot::Transaction objects
265 =cut
267 has 'transactions' => (
268 isa => 'ArrayRef',
269 is => 'rw',
270 lazy => 1,
271 builder => '_build_transactions',
274 =head2 Accessor breeding_program
276 The breeding program this seedlot is from. Useful for tracking movement of seedlots across breeding programs
277 Use breeding_program_id as setter (to save and update seedlots).
279 =cut
281 has 'breeding_program_name' => (
282 isa => 'Str',
283 is => 'rw',
284 lazy => 1,
285 builder => '_retrieve_breeding_program',
288 has 'breeding_program_id' => (
289 isa => 'Int',
290 is => 'rw',
291 lazy => 1,
292 builder => '_retrieve_breeding_program_id',
296 after 'stock_id' => sub {
297 my $self = shift;
298 my $id = shift;
299 return $self->seedlot_id($id);
302 # class method
303 =head2 Class method: list_seedlots()
305 Usage: my $seedlots = CXGN::Stock::Seedlot->list_seedlots($schema);
306 Desc: Class method that returns information on all seedlots
307 available in the system
308 Ret: ArrayRef of [ seedlot_id, seedlot name, location_code]
309 Args: $schema - Bio::Chado::Schema object
310 Side Effects: accesses the database
312 =cut
314 sub list_seedlots {
315 my $class = shift;
316 my $schema = shift;
317 my $people_schema = shift;
318 my $phenome_schema = shift;
319 my $offset = shift;
320 my $limit = shift;
321 my $seedlot_name = shift;
322 my $description = shift;
323 my $breeding_program = shift;
324 my $location = shift;
325 my $minimum_count = shift;
326 my $contents_accession = shift; #arrayref of uniquenames
327 my $contents_cross = shift; #arrayref of uniquenames
328 my $exact_match_uniquenames = shift;
329 my $minimum_weight = shift;
330 my $seedlot_id = shift; #added for BrAPI
331 my $accession_id = shift; #added for BrAPI
332 my $quality = shift;
333 my $only_good_quality = shift;
334 my $box_name = shift;
335 my $contents_cross_db_id = shift;
336 my $trial_name = shift; # name of trial used in a transaction (must also specify trial_usage)
337 my $trial_usage = shift; # transaction type (either 'source', 'sink', or 'source|sink')
338 # where the trial is the source, sink, or either of the matching seedlot's seed
340 select(STDERR);
341 $| = 1;
342 $schema->storage->debug(1);
344 my %unique_seedlots;
346 my $type_id = SGN::Model::Cvterm->get_cvterm_row($schema, "seedlot", "stock_type")->cvterm_id();
347 my $cross_type_id = SGN::Model::Cvterm->get_cvterm_row($schema, "cross", "stock_type")->cvterm_id();
348 my $accession_type_id = SGN::Model::Cvterm->get_cvterm_row($schema, "accession", "stock_type")->cvterm_id();
349 my $collection_of_cvterm_id = SGN::Model::Cvterm->get_cvterm_row($schema, "collection_of", "stock_relationship")->cvterm_id();
350 my $current_count_cvterm_id = SGN::Model::Cvterm->get_cvterm_row($schema, "current_count", "stock_property")->cvterm_id();
351 my $current_weight_cvterm_id = SGN::Model::Cvterm->get_cvterm_row($schema, "current_weight_gram", "stock_property")->cvterm_id();
352 my $experiment_cvterm_id = SGN::Model::Cvterm->get_cvterm_row($schema, "seedlot_experiment", "experiment_type")->cvterm_id();
353 my $seedlot_quality_cvterm_id = SGN::Model::Cvterm->get_cvterm_row($schema, "seedlot_quality", "stock_property")->cvterm_id();
354 my $location_code_cvterm_id = SGN::Model::Cvterm->get_cvterm_row($schema, "location_code", "stock_property")->cvterm_id();
356 my %search_criteria;
357 $search_criteria{'me.type_id'} = $type_id;
358 $search_criteria{'stock_relationship_objects.type_id'} = $collection_of_cvterm_id;
359 if ($seedlot_name) {
360 # print STDERR "Adding seedlot name ($seedlot_name) to query...\n";
361 $search_criteria{'me.uniquename'} = { 'ilike' => '%'.$seedlot_name.'%' };
363 if ($seedlot_id) {
364 # print STDERR "Adding seedlot_id ($seedlot_id) to query...\n";
365 $search_criteria{'me.stock_id'} = { -in => $seedlot_id };
367 if ($breeding_program) {
368 # print STDERR "Adding breeding_program $breeding_program to query...\n";
369 $search_criteria{'project.name'} = { 'ilike' => '%'.$breeding_program.'%' };
371 if ($location) {
372 # print STDERR "Adding location $location to query...\n";
373 $search_criteria{'nd_geolocation.description'} = { 'ilike' => '%'.$location.'%' };
375 if ($contents_accession && scalar(@$contents_accession)>0) {
376 # print STDERR "Adding contents accession: $contents_accession ...\n";
377 $search_criteria{'subject.type_id'} = $accession_type_id;
378 if ($exact_match_uniquenames){
379 $search_criteria{'subject.uniquename'} = { -in => $contents_accession };
380 } else {
381 foreach (@$contents_accession){
382 push @{$search_criteria{'subject.uniquename'}}, { 'ilike' => '%'.$_.'%' };
386 if ($accession_id && ref($accession_id) && scalar(@$accession_id)>0) {
387 print STDERR "Accession ID is ";
388 print Dumper $accession_id;
389 $search_criteria{'subject.type_id'} = $accession_type_id;
390 $search_criteria{'subject.stock_id'} = { -in => $accession_id };
392 if ($contents_cross && scalar(@$contents_cross)>0) {
393 $search_criteria{'subject.type_id'} = $cross_type_id;
394 if ($exact_match_uniquenames){
395 $search_criteria{'subject.uniquename'} = { -in => $contents_cross };
396 } else {
397 foreach (@$contents_cross){
398 push @{$search_criteria{'subject.uniquename'}}, { 'ilike' => '%'.$_.'%' };
403 if ($contents_cross_db_id && scalar(@$contents_cross_db_id)>0) {
404 $search_criteria{'subject.type_id'} = $cross_type_id;
405 $search_criteria{'subject.stock_id'} = { -in => $contents_cross_db_id };
408 my @seedlot_search_joins = (
409 {'nd_experiment_stocks' => {'nd_experiment' => [ {'nd_experiment_projects' => 'project' }, 'nd_geolocation' ] }},
410 {'stock_relationship_objects' => 'subject'}
413 if ($minimum_count || $minimum_weight || $quality || $only_good_quality || $box_name) {
414 if ($minimum_count) {
415 print STDERR "Minimum count $minimum_count\n";
416 $search_criteria{'stockprops.value'} = { '<>' => 'NA' };
417 $search_criteria{'stockprops.value::numeric'} = { '>=' => $minimum_count };
418 $search_criteria{'stockprops.type_id'} = $current_count_cvterm_id;
419 } elsif ($minimum_weight) {
420 print STDERR "Minimum weight $minimum_weight\n";
421 $search_criteria{'stockprops.value'} = { '<>' => 'NA' };
422 $search_criteria{'stockprops.value::numeric'} = { '>=' => $minimum_weight };
423 $search_criteria{'stockprops.type_id'} = $current_weight_cvterm_id;
425 if ($quality) {
426 print STDERR "Quality $quality\n";
427 $search_criteria{'stockprops.value' } = { '=' => $quality };
428 $search_criteria{'stockprops.type_id' } = $seedlot_quality_cvterm_id;
430 if ($box_name) {
431 print STDERR "Box Name $box_name\n";
432 $search_criteria{'stockprops.value'} = { 'ilike' => '%'.$box_name.'%' };
433 $search_criteria{'stockprops.type_id'} = $location_code_cvterm_id;
435 push @seedlot_search_joins, 'stockprops';
438 if ($trial_name && $trial_usage) {
440 # Build query to get stocks that match the requested transactions
441 my @phs;
442 my $q = "SELECT subject_id, object_id";
443 $q .= " FROM public.stock_relationship";
444 $q .= " WHERE type_id = (SELECT cvterm_id FROM public.cvterm WHERE name = 'seed transaction')";
446 # Subquery to get stocks (plots, etc) in requested trial
447 my $sq = "SELECT DISTINCT(stock_id) FROM public.materialized_phenoview WHERE trial_id = (SELECT project_id FROM public.project WHERE name = ?)";
448 my @filters;
450 # Add source transaction (plot --> seedlot)
451 if ( $trial_usage =~ m/source/ ) {
452 push @filters, "object_id IN ($sq)";
453 push @phs, $trial_name;
456 # Add sink transaction (seedlot --> plot)
457 if ( $trial_usage =~ m/sink/ ) {
458 push @filters, "subject_id IN ($sq)";
459 push @phs, $trial_name;
462 # Add filters to main query
463 $q .= " AND (" . join(" OR ", @filters) . ")";
465 # Execute query
466 my @seedlot_ids;
467 my $h = $schema->storage->dbh()->prepare($q);
468 $h->execute(@phs);
469 while ( my ($subject_id, $object_id) = $h->fetchrow_array() ) {
470 push @seedlot_ids, $subject_id;
471 push @seedlot_ids, $object_id;
474 # Add Seedlot IDs as filter to overall seedlot query
475 $search_criteria{'me.stock_id'} = { -in => \@seedlot_ids };
478 my $rs = $schema->resultset("Stock::Stock")->search(
479 \%search_criteria,
481 join => \@seedlot_search_joins,
482 '+select'=>['project.name', 'project.project_id', 'subject.stock_id', 'subject.uniquename', 'subject.type_id', 'nd_geolocation.description', 'nd_geolocation.nd_geolocation_id'],
483 '+as'=>['breeding_program_name', 'breeding_program_id', 'source_stock_id', 'source_uniquename', 'source_type_id', 'location', 'location_id'],
484 order_by => {-asc=>'project.name'},
485 #distinct => 1
489 my %source_types_hash = ( $type_id => 'seedlot', $accession_type_id => 'accession', $cross_type_id => 'cross' );
490 my $records_total = $rs->count();
491 if (defined($limit) && defined($offset)){
492 $rs = $rs->slice($offset, $limit);
494 my %seen_seedlot_ids;
495 while (my $row = $rs->next()) {
496 $seen_seedlot_ids{$row->stock_id}++;
498 $unique_seedlots{$row->uniquename}->{seedlot_stock_id} = $row->stock_id;
499 $unique_seedlots{$row->uniquename}->{seedlot_stock_uniquename} = $row->uniquename;
500 $unique_seedlots{$row->uniquename}->{seedlot_stock_description} = $row->description;
501 $unique_seedlots{$row->uniquename}->{breeding_program_name} = $row->get_column('breeding_program_name');
502 $unique_seedlots{$row->uniquename}->{breeding_program_id} = $row->get_column('breeding_program_id');
503 $unique_seedlots{$row->uniquename}->{location} = $row->get_column('location');
504 $unique_seedlots{$row->uniquename}->{location_id} = $row->get_column('location_id');
506 push @{$unique_seedlots{$row->uniquename}->{source_stocks}}, [$row->get_column('source_stock_id'), $row->get_column('source_uniquename'), $source_types_hash{$row->get_column('source_type_id')}];
509 my @seen_seedlot_ids = keys %seen_seedlot_ids;
510 my $stock_lookup = CXGN::Stock::StockLookup->new({ schema => $schema} );
511 my $owners_hash = $stock_lookup->get_owner_hash_lookup(\@seen_seedlot_ids);
513 my $stock_search = CXGN::Stock::Search->new({
514 bcs_schema=>$schema,
515 people_schema=>$people_schema,
516 phenome_schema=>$phenome_schema,
517 stock_id_list=>\@seen_seedlot_ids,
518 stock_type_id=>$type_id,
519 stockprop_columns_view=>{'current_count'=>1, 'current_weight_gram'=>1, 'organization'=>1, 'location_code'=>1, 'seedlot_quality'=>1},
520 minimal_info=>1, #for only returning stock_id and uniquenames
521 display_pedigree=>0 #to calculate and display pedigree
523 my ($stocksearch_result, $records_stock_total) = $stock_search->search();
525 my %stockprop_hash;
526 foreach (@$stocksearch_result){
527 $stockprop_hash{$_->{stock_id}} = $_;
530 my @seedlots;
531 foreach (sort keys %unique_seedlots){
532 my $owners = $owners_hash->{$unique_seedlots{$_}->{seedlot_stock_id}};
533 my @owners_html;
534 foreach (@$owners){
535 push @owners_html ,'<a href="/solpeople/personal-info.pl?sp_person_id='.$_->[0].'">'.$_->[2].' '.$_->[3].'</a>';
537 my $owners_string = join ', ', @owners_html;
538 $unique_seedlots{$_}->{owners_string} = $owners_string;
539 $unique_seedlots{$_}->{organization} = $stockprop_hash{$unique_seedlots{$_}->{seedlot_stock_id}}->{organization} ? $stockprop_hash{$unique_seedlots{$_}->{seedlot_stock_id}}->{organization} : 'NA';
540 $unique_seedlots{$_}->{box} = $stockprop_hash{$unique_seedlots{$_}->{seedlot_stock_id}}->{location_code} ? $stockprop_hash{$unique_seedlots{$_}->{seedlot_stock_id}}->{location_code} : 'NA';
541 $unique_seedlots{$_}->{seedlot_quality} = $stockprop_hash{$unique_seedlots{$_}->{seedlot_stock_id}}->{seedlot_quality} ? $stockprop_hash{$unique_seedlots{$_}->{seedlot_stock_id}}->{seedlot_quality} : '';
542 $unique_seedlots{$_}->{current_count} = $stockprop_hash{$unique_seedlots{$_}->{seedlot_stock_id}}->{current_count} ? $stockprop_hash{$unique_seedlots{$_}->{seedlot_stock_id}}->{current_count} : 'NA';
543 $unique_seedlots{$_}->{current_weight_gram} = $stockprop_hash{$unique_seedlots{$_}->{seedlot_stock_id}}->{current_weight_gram} ? $stockprop_hash{$unique_seedlots{$_}->{seedlot_stock_id}}->{current_weight_gram} : 'NA';
545 push @seedlots, $unique_seedlots{$_};
549 return (\@seedlots, $records_total);
552 # class method
553 =head2 Class method: verify_seedlot_stock_lists()
555 Usage: my $seedlots = CXGN::Stock::Seedlot->verify_seedlot_stock_lists($schema, $people_schema, $phenome_schema, \@stock_names, \@seedlot_names);
556 Desc: Class method that verifies if a given list of seedlots is valid for a given list of accessions
557 Ret: success or error
558 Args: $schema, $stock_names, $seedlot_names
559 Side Effects: accesses the database
561 =cut
563 sub verify_seedlot_stock_lists {
564 my $class = shift;
565 my $schema = shift;
566 my $people_schema = shift;
567 my $phenome_schema = shift;
568 my $stock_names = shift;
569 my $seedlot_names = shift;
570 my $error = '';
571 my %return;
573 if (!$stock_names) {
574 $error .= "No accession list selected!";
576 if (!$seedlot_names) {
577 $error .= "No seedlot list supplied!";
579 if ($error){
580 $return{error} = $error;
581 return \%return;
584 my @stock_names = @$stock_names;
585 my @seedlot_names = @$seedlot_names;
586 if (scalar(@stock_names)<1){
587 $error .= "Your accession list is empty!";
589 if (scalar(@seedlot_names)<1){
590 $error .= "Your seedlot list is empty!";
592 if ($error){
593 $return{error} = $error;
594 return \%return;
597 my $lv = CXGN::List::Validate->new();
598 my @accessions_missing = @{$lv->validate($schema,'accessions',\@stock_names)->{'missing'}};
599 my $lv_seedlots = CXGN::List::Validate->new();
600 my @seedlots_missing = @{$lv_seedlots->validate($schema,'seedlots',\@seedlot_names)->{'missing'}};
602 if (scalar(@accessions_missing) > 0){
603 $error .= 'The following accessions are not valid in the database, so you must add them first: '.join ',', @accessions_missing;
605 if (scalar(@seedlots_missing) > 0){
606 $error .= 'The following seedlots are not valid in the database, so you must add them first: '.join ',', @seedlots_missing;
608 if ($error){
609 $return{error} = $error;
610 return \%return;
613 my %selected_seedlots = map {$_=>1} @seedlot_names;
614 my %selected_accessions = map {$_=>1} @stock_names;
615 my %seedlot_hash;
617 my $ac = CXGN::BreedersToolbox::Accessions->new({schema=>$schema, people_schema=>$people_schema, phenome_schema=>$phenome_schema});
618 my $possible_seedlots = $ac->get_possible_seedlots(\@stock_names);
619 my %allowed_seedlots;
620 while (my($key,$val) = each %$possible_seedlots){
621 foreach my $seedlot (@$val){
622 my $seedlot_name = $seedlot->{seedlot}->[0];
623 if (exists($selected_accessions{$key}) && exists($selected_seedlots{$seedlot_name})){
624 push @{$seedlot_hash{$key}}, $seedlot_name;
628 #if(scalar(keys %seedlot_hash) != scalar(@stock_names)){
629 # $error .= "Error: The seedlot list you select must include seedlots for all the accessions you have selected. ";
631 if ($error){
632 $return{error} = $error;
633 } else {
634 $return{success} = 1;
635 $return{seedlot_hash} = \%seedlot_hash;
637 return \%return;
640 # class method
641 =head2 Class method: verify_seedlot_plot_compatibility()
643 Usage: my $seedlots = CXGN::Stock::Seedlot->verify_seedlot_plot_compatibility($schema, [[$seedlot_name, $plot_name]]);
644 Desc: Class method that verifies if a given list of pairs of seedlot_name and plot_name have the same underlying accession.
645 Ret: success or error
646 Args: $schema, $stock_names, $seedlot_names
647 Side Effects: accesses the database
649 =cut
651 sub verify_seedlot_plot_compatibility {
652 my $class = shift;
653 my $schema = shift;
654 my $pairs = shift; #arrayref of [ [seedlot_name, plot_name] ]
655 my $error = '';
656 my %return;
658 if (!$pairs){
659 $error .= "No pair array passed!";
661 if ($error){
662 $return{error} = $error;
663 return \%return;
666 my @pairs = @$pairs;
667 if (scalar(@pairs)<1){
668 $error .= "Your pairs list is empty!";
670 if ($error){
671 $return{error} = $error;
672 return \%return;
675 my $plot_cvterm_id = SGN::Model::Cvterm->get_cvterm_row($schema, "plot", "stock_type")->cvterm_id();
676 my $seedlot_cvterm_id = SGN::Model::Cvterm->get_cvterm_row($schema, "seedlot", "stock_type")->cvterm_id();
677 my $plot_of_cvterm_id = SGN::Model::Cvterm->get_cvterm_row($schema, "plot_of", "stock_relationship")->cvterm_id();
678 my $collection_of_cvterm_id = SGN::Model::Cvterm->get_cvterm_row($schema, "collection_of", "stock_relationship")->cvterm_id();
679 foreach (@pairs){
680 my $seedlot_name = $_->[0];
681 my $plot_name = $_->[1];
683 #The plot is linked to one accession via 'plot_of'. That accession is then linked to many seedlots via 'collection_of'. Here we can check if the provided seedlot is one of the seedlots linked to the plot's accession.
684 my $seedlot_rs = $schema->resultset("Stock::Stock")->search({'me.uniquename'=>$plot_name, 'me.type_id'=>$plot_cvterm_id})->search_related('stock_relationship_subjects', {'stock_relationship_subjects.type_id'=>$plot_of_cvterm_id})->search_related('object')->search_related('stock_relationship_subjects', {'stock_relationship_subjects_2.type_id'=>$collection_of_cvterm_id})->search_related('object', {'object_2.uniquename'=>$seedlot_name, 'object_2.type_id'=>$seedlot_cvterm_id});
685 if (!$seedlot_rs->first){
686 $error .= "The seedlot: $seedlot_name is not linked to the same accession as the plot: $plot_name . ";
689 if ($error){
690 $return{error} = $error;
691 } else {
692 $return{success} = 1;
694 return \%return;
697 # class method
698 =head2 Class method: verify_seedlot_accessions_crosses()
700 Usage: my $seedlots = CXGN::Stock::Seedlot->verify_seedlot_accessions_crosses($schema, [[$seedlot_name, $accession_name]]);
701 Desc: Class method that verifies if a given list of pairs of seedlot_name and accession_name or seedlot_name and cross unique id have the same underlying accession/cross_unique_id.
702 Ret: success or error
703 Args: $schema, $stock_names, $seedlot_names
704 Side Effects: accesses the database
706 =cut
708 sub verify_seedlot_accessions_crosses {
709 my $class = shift;
710 my $schema = shift;
711 my $pairs = shift; #arrayref of [ [seedlot_name, accession_name] ] #note: the variable accession_name can be either accession or cross stock type
712 my $error = '';
713 my %return;
715 if (!$pairs){
716 $error .= "No pair array passed!";
718 if ($error){
719 $return{error} = $error;
720 return \%return;
723 my @pairs = @$pairs;
724 if (scalar(@pairs)<1){
725 $error .= "Your pairs list is empty!";
727 if ($error){
728 $return{error} = $error;
729 return \%return;
732 my %seen_accession_names;
733 foreach (@pairs){
734 $seen_accession_names{$_->[1]}++;
736 my $accession_cvterm_id = SGN::Model::Cvterm->get_cvterm_row($schema, 'accession', 'stock_type')->cvterm_id();
737 my $cross_cvterm_id = SGN::Model::Cvterm->get_cvterm_row($schema, 'cross', 'stock_type')->cvterm_id();
738 my $synonym_cvterm_id = SGN::Model::Cvterm->get_cvterm_row($schema, 'stock_synonym', 'stock_property')->cvterm_id();
739 my $seedlot_cvterm_id = SGN::Model::Cvterm->get_cvterm_row($schema, "seedlot", "stock_type")->cvterm_id();
740 my $collection_of_cvterm_id = SGN::Model::Cvterm->get_cvterm_row($schema, "collection_of", "stock_relationship")->cvterm_id();
742 my @accessions = keys %seen_accession_names;
743 my $acc_synonym_rs = $schema->resultset("Stock::Stock")->search({
744 'me.is_obsolete' => { '!=' => 't' },
745 'stockprops.value' => { -in => \@accessions},
746 'me.type_id' => $accession_cvterm_id,
747 'stockprops.type_id' => $synonym_cvterm_id
748 },{join => 'stockprops', '+select'=>['stockprops.value'], '+as'=>['synonym']});
749 my %acc_synonyms_lookup;
750 while (my $r=$acc_synonym_rs->next){
751 $acc_synonyms_lookup{$r->get_column('synonym')}->{$r->uniquename} = $r->stock_id;
754 foreach (@pairs){
755 my $seedlot_name = $_->[0];
756 my $accession_name = $_->[1];
758 if ($acc_synonyms_lookup{$accession_name}){
759 my @accession_names = keys %{$acc_synonyms_lookup{$accession_name}};
760 if (scalar(@accession_names)>1){
761 print STDERR "There is more than one uniquename for this synonym $accession_name. this should not happen!\n";
763 $accession_name = $accession_names[0];
766 my $seedlot_rs = $schema->resultset("Stock::Stock")->search({'me.uniquename'=>$seedlot_name, 'me.type_id'=>$seedlot_cvterm_id})->search_related('stock_relationship_objects', {'stock_relationship_objects.type_id'=>$collection_of_cvterm_id})->search_related('subject', {'subject.uniquename'=>$accession_name, 'subject.type_id'=>[$accession_cvterm_id, $cross_cvterm_id]});
767 if (!$seedlot_rs->first){
768 $error .= "The seedlot: $seedlot_name is not linked to the accession/cross_unique_id: $accession_name.";
771 if ($error){
772 $return{error} = $error;
773 } else {
774 $return{success} = 1;
776 return \%return;
780 =head2 Class method: verify_seedlot_seedlot_compatibility()
782 Usage: my $seedlots = CXGN::Stock::Seedlot->verify_seedlot_seedlot_compatibility($schema, [[$seedlot_name_1, $seedlot_name_2]]);
783 Desc: Class method that verifies if a given list of pairs of seedlot_names have the same content.
784 Ret: success or error
785 Args: $schema, $seedlot_name_1, $seedlot_name_2
786 Side Effects: accesses the database
788 =cut
790 sub verify_seedlot_seedlot_compatibility {
791 my $class = shift;
792 my $schema = shift;
793 my $pairs = shift;
794 my $error = '';
795 my %return;
797 if (!$pairs){
798 $error .= "No pair array passed!";
800 if ($error){
801 $return{error} = $error;
802 return \%return;
805 my @pairs = @$pairs;
806 if (scalar(@pairs)<1){
807 $error .= "Your pairs list is empty!";
809 if ($error){
810 $return{error} = $error;
811 return \%return;
814 my $seedlot_cvterm_id = SGN::Model::Cvterm->get_cvterm_row($schema, "seedlot", "stock_type")->cvterm_id();
815 my $collection_of_cvterm_id = SGN::Model::Cvterm->get_cvterm_row($schema, "collection_of", "stock_relationship")->cvterm_id();
816 foreach (@pairs){
817 my $seedlot_name_1 = $_->[0];
818 my $seedlot_name_2 = $_->[1];
820 my $seedlot_rs_1 = $schema->resultset("Stock::Stock")->find({'uniquename' => $seedlot_name_1,'type_id' => $seedlot_cvterm_id});
821 my $seedlot_id_1 = $seedlot_rs_1->stock_id();
822 my $seedlot_1_content = $schema->resultset("Stock::StockRelationship")->find({ object_id => $seedlot_id_1, type_id => $collection_of_cvterm_id});
823 my $content_1_id = $seedlot_1_content->subject_id();
825 my $seedlot_rs_2 = $schema->resultset("Stock::Stock")->find({'uniquename' => $seedlot_name_2,'type_id' => $seedlot_cvterm_id});
826 my $seedlot_id_2 = $seedlot_rs_2->stock_id();
827 my $seedlot_2_content = $schema->resultset("Stock::StockRelationship")->find({ object_id => $seedlot_id_2, type_id => $collection_of_cvterm_id});
828 my $content_2_id = $seedlot_2_content->subject_id();
830 if ($content_1_id ne $content_2_id){
831 $error .= "The seedlots: $seedlot_name_1 and $seedlot_name_2 have different contents.";
836 if ($error){
837 $return{error} = $error;
838 } else {
839 $return{success} = 1;
841 return \%return;
845 =head2 Class method: verify_all_seedlots_compatibility()
847 Usage: my $seedlots = CXGN::Stock::Seedlot->verify_all_seedlots_compatibility($schema, [$new_seedlot_name, \%seedlot_names]);
848 Desc: Class method that verifies if a new seedlot name is associated with only one content.
849 Ret: success or error
850 Args: $schema, \@new_seedlot_and_associated_seedlots
851 Side Effects: accesses the database
853 =cut
855 sub verify_all_seedlots_compatibility {
856 my $class = shift;
857 my $schema = shift;
858 my $new_seedlot_and_associated_seedlots = shift;
859 my $error = '';
860 my %return;
862 if (!$new_seedlot_and_associated_seedlots){
863 $error .= "No seedlot names passed!";
865 if ($error){
866 $return{error} = $error;
867 return \%return;
870 my $new_seedlot_name = $new_seedlot_and_associated_seedlots->[0];
871 my $associated_seedlots = $new_seedlot_and_associated_seedlots->[1];
872 my @seedlot_names = keys %{$associated_seedlots};
874 if (scalar(@seedlot_names)<1){
875 $error .= "No associated seedlot!";
877 if ($error){
878 $return{error} = $error;
879 return \%return;
882 my $seedlot_cvterm_id = SGN::Model::Cvterm->get_cvterm_row($schema, "seedlot", "stock_type")->cvterm_id();
883 my $collection_of_cvterm_id = SGN::Model::Cvterm->get_cvterm_row($schema, "collection_of", "stock_relationship")->cvterm_id();
884 my %seen_content;
885 foreach my $each_seedlot(@seedlot_names){
886 my $seedlot_rs = $schema->resultset("Stock::Stock")->find({'uniquename' => $each_seedlot,'type_id' => $seedlot_cvterm_id});
887 my $seedlot_id = $seedlot_rs->stock_id();
888 my $seedlot_content = $schema->resultset("Stock::StockRelationship")->find({ object_id => $seedlot_id, type_id => $collection_of_cvterm_id});
889 my $content_id = $seedlot_content->subject_id();
890 $seen_content{$content_id}++;
893 my $content_count = keys %seen_content;
894 if ($content_count > 1) {
895 $error = "You assigned more than one content to this new seedlot name: $new_seedlot_name "
898 if ($error){
899 $return{error} = $error;
900 } else {
901 $return{success} = 1;
903 return \%return;
907 =head2 Class method: verify_accession_content_source_compatibility()
909 Usage: my $seedlots = CXGN::Stock::Seedlot->verify_accession_content_source_compatibility($schema, [[$accession_name, $source_name]]);
910 Desc: Class method that verifies if accession of a seedlot source is the same as accession content.
911 Ret: success or error
912 Args: $schema, $accession_name, $source_name
913 Side Effects: accesses the database
915 =cut
917 sub verify_accession_content_source_compatibility {
918 my $class = shift;
919 my $schema = shift;
920 my $pairs = shift;
921 my $error = '';
922 my %return;
924 if (!$pairs){
925 $error .= "No pair array passed!";
927 if ($error){
928 $return{error} = $error;
929 return \%return;
932 my @pairs = @$pairs;
933 if (scalar(@pairs)<1){
934 $error .= "Your pairs list is empty!";
936 if ($error){
937 $return{error} = $error;
938 return \%return;
941 my $accession_cvterm_id = SGN::Model::Cvterm->get_cvterm_row($schema, "accession", "stock_type")->cvterm_id();
942 my $plot_cvterm_id = SGN::Model::Cvterm->get_cvterm_row($schema, "plot", "stock_type")->cvterm_id();
943 my $subplot_cvterm_id = SGN::Model::Cvterm->get_cvterm_row($schema, "subplot", "stock_type")->cvterm_id();
944 my $plant_cvterm_id = SGN::Model::Cvterm->get_cvterm_row($schema, "plant", "stock_type")->cvterm_id();
946 my $plot_of_cvterm_id = SGN::Model::Cvterm->get_cvterm_row($schema, "plot_of", "stock_relationship")->cvterm_id();
947 my $subplot_of_cvterm_id = SGN::Model::Cvterm->get_cvterm_row($schema, "subplot_of", "stock_relationship")->cvterm_id();
948 my $plant_of_cvterm_id = SGN::Model::Cvterm->get_cvterm_row($schema, "plant_of", "stock_relationship")->cvterm_id();
950 foreach my $each_pair (@pairs){
951 my $accession_id;
952 my $source_id;
953 my $source_type_id;
954 my $accession_source_relationship_type;
955 my $source_accession_id;
956 my $accession_name = $each_pair->[0];
957 my $source_name = $each_pair->[1];
959 my $accession_rs = $schema->resultset("Stock::Stock")->find({'uniquename' => $accession_name,'type_id' => $accession_cvterm_id});
960 if ($accession_rs) {
961 $accession_id = $accession_rs->stock_id();
964 my $source_rs = $schema->resultset("Stock::Stock")->find({'uniquename' => $source_name});
965 if ($source_rs) {
966 $source_id = $source_rs->stock_id();
967 $source_type_id = $source_rs->type_id();
969 if ($source_type_id eq $plot_cvterm_id) {
970 $accession_source_relationship_type = $plot_of_cvterm_id;
971 } elsif ($source_type_id eq $subplot_cvterm_id) {
972 $accession_source_relationship_type = $subplot_of_cvterm_id;
973 } elsif ($source_type_id eq $plant_cvterm_id) {
974 $accession_source_relationship_type = $plant_of_cvterm_id;
975 } else {
976 $error .= "The source name: $source_name is not a plot, subplot or plant stock type.";
980 if ($accession_id && $source_id && $accession_source_relationship_type) {
981 my $accession_source_relationship_rs = $schema->resultset("Stock::StockRelationship")->find({ subject_id => $source_id, type_id => $accession_source_relationship_type});
982 $source_accession_id = $accession_source_relationship_rs->object_id();
984 if ($accession_id ne $source_accession_id){
985 $error .= "The source name: $source_name is not linked to the same accession as the access content: $accession_name"."<br>";
990 if ($error){
991 $return{error} = $error;
992 } else {
993 $return{success} = 1;
995 return \%return;
999 =head2 Class method: get_content_id()
1001 =cut
1003 sub get_content_id {
1004 my $class = shift;
1005 my $schema = shift;
1006 my $seedlot_id = shift;
1007 my $accession_stock_id;
1008 my $cross_stock_id;
1009 my @return_content_id = ();
1011 my $accession_cvterm_id = SGN::Model::Cvterm->get_cvterm_row($schema, 'accession', 'stock_type')->cvterm_id();
1012 my $cross_cvterm_id = SGN::Model::Cvterm->get_cvterm_row($schema, 'cross', 'stock_type')->cvterm_id();
1013 my $seedlot_cvterm_id = SGN::Model::Cvterm->get_cvterm_row($schema, "seedlot", "stock_type")->cvterm_id();
1014 my $collection_of_cvterm_id = SGN::Model::Cvterm->get_cvterm_row($schema, "collection_of", "stock_relationship")->cvterm_id();
1016 my $seedlot_content = $schema->resultset("Stock::StockRelationship")->find({ object_id => $seedlot_id, type_id => $collection_of_cvterm_id});
1017 my $content_id = $seedlot_content->subject_id();
1019 my $check_content_type = $schema->resultset("Stock::Stock")->find({'stock_id' => $content_id});
1020 my $type_id = $check_content_type->type_id();
1021 if ($type_id eq $accession_cvterm_id) {
1022 $accession_stock_id = $content_id;
1023 } elsif ($type_id eq $cross_cvterm_id) {
1024 $cross_stock_id = $content_id;
1026 @return_content_id = ($accession_stock_id, $cross_stock_id);
1028 return \@return_content_id;
1032 sub BUILDARGS {
1033 my $orig = shift;
1034 my %args = @_;
1035 $args{stock_id} = $args{seedlot_id};
1036 return \%args;
1039 sub BUILD {
1040 my $self = shift;
1041 if ($self->stock_id()) {
1042 $self->seedlot_id($self->stock_id);
1043 $self->name($self->uniquename());
1044 $self->seedlot_id($self->stock_id());
1048 sub _build_transactions {
1049 my $self = shift;
1050 my $transactions = CXGN::Stock::Seedlot::Transaction->get_transactions_by_seedlot_id($self->schema(), $self->seedlot_id());
1051 $self->transactions($transactions);
1054 sub _store_seedlot_location {
1055 my $self = shift;
1056 my $nd_geolocation = $self->schema()->resultset("NaturalDiversity::NdGeolocation")->find_or_create({
1057 description => $self->location_code
1059 $self->nd_geolocation_id($nd_geolocation->nd_geolocation_id);
1062 sub _retrieve_location {
1063 my $self = shift;
1064 my $experiment_type_id = SGN::Model::Cvterm->get_cvterm_row($self->schema(), "seedlot_experiment", "experiment_type")->cvterm_id();
1065 my $nd_geolocation_rs = $self->schema()->resultset('Stock::Stock')->search({'me.stock_id'=>$self->seedlot_id})->search_related('nd_experiment_stocks')->search_related('nd_experiment', {'nd_experiment.type_id'=>$experiment_type_id})->search_related('nd_geolocation');
1066 if ($nd_geolocation_rs->count != 1){
1067 die "Seedlot does not have 1 nd_geolocation associated!\n";
1069 my $nd_geolocation_id = $nd_geolocation_rs->first()->nd_geolocation_id();
1070 my $location_code = $nd_geolocation_rs->first()->description();
1071 $self->nd_geolocation_id($nd_geolocation_id);
1072 $self->location_code($location_code);
1075 sub _retrieve_location_id {
1076 my $self = shift;
1077 my $experiment_type_id = SGN::Model::Cvterm->get_cvterm_row($self->schema(), "seedlot_experiment", "experiment_type")->cvterm_id();
1078 my $nd_geolocation_rs = $self->schema()->resultset('Stock::Stock')->search({'me.stock_id'=>$self->seedlot_id})->search_related('nd_experiment_stocks')->search_related('nd_experiment', {'nd_experiment.type_id'=>$experiment_type_id})->search_related('nd_geolocation');
1079 if ($nd_geolocation_rs->count != 1){
1080 die "Seedlot does not have 1 nd_geolocation associated!\n";
1082 my $nd_geolocation_id = $nd_geolocation_rs->first()->nd_geolocation_id();
1083 my $location_code = $nd_geolocation_rs->first()->description();
1084 $self->nd_geolocation_id($nd_geolocation_id);
1087 sub _retrieve_box_name {
1088 my $self = shift;
1089 $self->box_name($self->_retrieve_stockprop('location_code'));
1092 sub _retrieve_breeding_program {
1093 my $self = shift;
1094 my $experiment_type_id = SGN::Model::Cvterm->get_cvterm_row($self->schema(), "seedlot_experiment", "experiment_type")->cvterm_id();
1095 my $project_rs = $self->schema()->resultset('Stock::Stock')->search({'me.stock_id'=>$self->seedlot_id})->search_related('nd_experiment_stocks')->search_related('nd_experiment', {'nd_experiment.type_id'=>$experiment_type_id})->search_related('nd_experiment_projects')->search_related('project');
1096 if ($project_rs->count != 1){
1097 die "Seedlot does not have 1 breeding program project (".$project_rs->count.") associated!\n";
1099 my $breeding_program_id = $project_rs->first()->project_id();
1100 my $breeding_program_name = $project_rs->first()->name();
1101 $self->breeding_program_id($breeding_program_id);
1102 $self->breeding_program_name($breeding_program_name);
1105 sub _retrieve_breeding_program_id {
1106 my $self = shift;
1107 my $experiment_type_id = SGN::Model::Cvterm->get_cvterm_row($self->schema(), "seedlot_experiment", "experiment_type")->cvterm_id();
1108 my $project_rs = $self->schema()->resultset('Stock::Stock')->search({'me.stock_id'=>$self->seedlot_id})->search_related('nd_experiment_stocks')->search_related('nd_experiment', {'nd_experiment.type_id'=>$experiment_type_id})->search_related('nd_experiment_projects')->search_related('project');
1109 if ($project_rs->count != 1){
1110 die "Seedlot does not have 1 breeding program project (".$project_rs->count.") associated!\n";
1112 my $breeding_program_id = $project_rs->first()->project_id();
1113 my $breeding_program_name = $project_rs->first()->name();
1114 $self->breeding_program_id($breeding_program_id);
1117 sub _store_seedlot_relationships {
1118 my $self = shift;
1119 my $error;
1121 eval {
1122 if ($self->accession_stock_id){
1123 $error = $self->_store_seedlot_accession();
1125 if ($self->cross_stock_id){
1126 $error = $self->_store_seedlot_cross();
1128 if (!$error){
1129 my $experiment_type_id = SGN::Model::Cvterm->get_cvterm_row($self->schema(), "seedlot_experiment", "experiment_type")->cvterm_id();
1130 my $experiment = $self->schema->resultset('NaturalDiversity::NdExperiment')->create({
1131 nd_geolocation_id => $self->nd_geolocation_id,
1132 type_id => $experiment_type_id
1134 $experiment->create_related('nd_experiment_stocks', { stock_id => $self->seedlot_id(), type_id => $experiment_type_id });
1135 $experiment->create_related('nd_experiment_projects', { project_id => $self->breeding_program_id });
1139 if ($@) {
1140 $error = $@;
1142 return $error;
1145 sub _update_seedlot_breeding_program {
1146 my $self = shift;
1147 my $stock = $self->stock;
1148 my $seedlot_experiment_cvterm_id = SGN::Model::Cvterm->get_cvterm_row($self->schema, 'seedlot_experiment', 'experiment_type')->cvterm_id();
1149 my $nd_exp_project = $stock->search_related('nd_experiment_stocks')->search_related('nd_experiment', {'nd_experiment.type_id'=>$seedlot_experiment_cvterm_id})->search_related('nd_experiment_projects');
1150 if($nd_exp_project->count != 1){
1151 die "There should be exactly one nd_experiment_project for any single seedlot!";
1153 my $nd_exp_proj = $nd_exp_project->first();
1154 $nd_exp_proj->update({project_id=>$self->breeding_program_id});
1157 sub _update_seedlot_location {
1158 my $self = shift;
1159 my $stock = $self->stock;
1160 my $seedlot_experiment_cvterm_id = SGN::Model::Cvterm->get_cvterm_row($self->schema, 'seedlot_experiment', 'experiment_type')->cvterm_id();
1161 my $nd_exp = $stock->search_related('nd_experiment_stocks')->search_related('nd_experiment', {'nd_experiment.type_id'=>$seedlot_experiment_cvterm_id});
1162 if($nd_exp->count != 1){
1163 die "There should be exactly one nd_experiment for any single seedlot!";
1165 my $nd = $nd_exp->first();
1166 $nd->update({nd_geolocation_id=>$self->nd_geolocation_id});
1169 sub _store_seedlot_accession {
1170 my $self = shift;
1171 my $accession_stock_id = $self->accession_stock_id;
1173 my $organism_id = $self->schema->resultset('Stock::Stock')->find({stock_id => $accession_stock_id})->organism_id();
1174 if ($self->organism_id){
1175 if ($self->organism_id != $organism_id){
1176 return "Accessions must all be the same organism, so that a population can group the seed lots.\n";
1179 $self->organism_id($organism_id);
1181 my $type_id = SGN::Model::Cvterm->get_cvterm_row($self->schema(), "collection_of", "stock_relationship")->cvterm_id();
1182 my $already_exists = $self->schema()->resultset("Stock::StockRelationship")->find({ object_id => $self->seedlot_id(), type_id => $type_id, subject_id=>$accession_stock_id });
1184 if ($already_exists) {
1185 print STDERR "Accession with id $accession_stock_id is already associated with seedlot id ".$self->seedlot_id()."\n";
1186 return "Accession with id $accession_stock_id is already associated with seedlot id ".$self->seedlot_id();
1188 my $row = $self->schema()->resultset("Stock::StockRelationship")->create({
1189 object_id => $self->seedlot_id(),
1190 subject_id => $accession_stock_id,
1191 type_id => $type_id,
1193 return;
1196 sub _update_content_stock_id {
1197 my $self = shift;
1198 my $type_id = SGN::Model::Cvterm->get_cvterm_row($self->schema(), "collection_of", "stock_relationship")->cvterm_id();
1199 my $accession_type_id = SGN::Model::Cvterm->get_cvterm_row($self->schema(), "accession", "stock_type")->cvterm_id();
1200 my $cross_type_id = SGN::Model::Cvterm->get_cvterm_row($self->schema(), "cross", "stock_type")->cvterm_id();
1202 my $acc_rs = $self->stock->search_related('stock_relationship_objects', {'me.type_id'=>$type_id, 'subject.type_id'=>[$accession_type_id,$cross_type_id]}, {'join'=>'subject'});
1204 while (my $r=$acc_rs->next){
1205 $r->delete();
1207 my $error;
1208 if ($self->accession_stock_id){
1209 $error = $self->_store_seedlot_accession();
1211 if ($self->cross_stock_id){
1212 $error = $self->_store_seedlot_cross();
1214 return $error;
1217 sub _retrieve_accession {
1218 my $self = shift;
1219 my $type_id = SGN::Model::Cvterm->get_cvterm_row($self->schema(), "collection_of", "stock_relationship")->cvterm_id();
1220 my $accession_type_id = SGN::Model::Cvterm->get_cvterm_row($self->schema(), "accession", "stock_type")->cvterm_id();
1221 my $rs = $self->schema()->resultset("Stock::StockRelationship")->search({ 'me.type_id' => $type_id, 'me.object_id' => $self->seedlot_id(), 'subject.type_id'=>$accession_type_id }, {'join'=>'subject'});
1223 my $accession_id;
1224 if ($rs->count == 1){
1225 $accession_id = $rs->first->subject_id;
1228 if ($accession_id){
1229 $self->accession_stock_id($accession_id);
1231 my $accession_rs = $self->schema()->resultset("Stock::Stock")->find({ stock_id => $accession_id });
1232 $self->accession([$accession_rs->stock_id(), $accession_rs->uniquename()]);
1236 sub _remove_accession {
1237 my $self = shift;
1241 sub _store_seedlot_cross {
1242 my $self = shift;
1243 my $cross_stock_id = $self->cross_stock_id;
1244 my $organism_id = $self->schema->resultset('Stock::Stock')->find({stock_id => $cross_stock_id})->organism_id();
1245 if ($self->organism_id){
1246 if ($self->organism_id != $organism_id){
1247 return "Crosses must all be the same organism to be in a seed lot.\n";
1250 $self->organism_id($organism_id);
1252 my $type_id = SGN::Model::Cvterm->get_cvterm_row($self->schema(), "collection_of", "stock_relationship")->cvterm_id();
1253 my $already_exists = $self->schema()->resultset("Stock::StockRelationship")->find({ object_id => $self->seedlot_id(), type_id => $type_id, subject_id=>$cross_stock_id });
1255 if ($already_exists) {
1256 print STDERR "Cross with id $cross_stock_id is already associated with seedlot id ".$self->seedlot_id()."\n";
1257 return "Cross with id $cross_stock_id is already associated with seedlot id ".$self->seedlot_id();
1259 my $row = $self->schema()->resultset("Stock::StockRelationship")->create({
1260 object_id => $self->seedlot_id(),
1261 subject_id => $cross_stock_id,
1262 type_id => $type_id,
1264 return;
1267 sub _retrieve_cross {
1268 my $self = shift;
1269 my $type_id = SGN::Model::Cvterm->get_cvterm_row($self->schema(), "collection_of", "stock_relationship")->cvterm_id();
1270 my $cross_type_id = SGN::Model::Cvterm->get_cvterm_row($self->schema(), "cross", "stock_type")->cvterm_id();
1271 my $rs = $self->schema()->resultset("Stock::StockRelationship")->search({ 'me.type_id' => $type_id, 'me.object_id' => $self->seedlot_id(), 'subject.type_id'=>$cross_type_id }, {'join'=>'subject'});
1273 my $cross_id;
1274 if ($rs->count == 1){
1275 $cross_id = $rs->first->subject_id;
1278 if ($cross_id){
1279 $self->cross_stock_id($cross_id);
1281 my $cross_rs = $self->schema()->resultset("Stock::Stock")->find({ stock_id => $cross_id });
1282 $self->cross([$cross_rs->stock_id(), $cross_rs->uniquename()]);
1287 =head2 Method current_count()
1289 Usage: my $current_count = $sl->current_count();
1290 Desc: returns the current balance of seeds in the seedlot
1291 Ret: a number
1292 Args: none
1293 Side Effects: retrieves transactions from db and calculates count
1294 Example:
1296 =cut
1298 sub current_count {
1299 my $self = shift;
1300 my $transactions = $self->transactions();
1302 my $count = 0;
1303 foreach my $t (@$transactions) {
1304 if ($t->amount() ne 'NA'){
1305 $count += $t->amount() * $t->factor();
1308 if ($count == 0 && scalar(@$transactions)>0){
1309 $count = 'NA';
1311 return $count;
1314 # It is convenient and also much faster to retrieve a single value for the current_count, rather than calculating it from the transactions.
1315 sub set_current_count_property {
1316 my $self = shift;
1317 my $current_count = $self->current_count();
1318 my $current_count_cvterm = SGN::Model::Cvterm->get_cvterm_row($self->schema, 'current_count', 'stock_property');
1319 my $stock = $self->stock();
1320 my $recorded_current_count = $stock->find_related('stockprops', {'me.type_id'=>$current_count_cvterm->cvterm_id});
1321 if($recorded_current_count){
1322 $recorded_current_count->update({'value'=>$current_count});
1323 } else {
1324 $stock->create_stockprops({$current_count_cvterm->name() => $current_count});
1326 return $current_count;
1329 # It is convenient and also much faster to retrieve a single value for the current_count, rather than calculating it from the transactions.
1330 sub get_current_count_property {
1331 my $self = shift;
1332 my $current_count_cvterm = SGN::Model::Cvterm->get_cvterm_row($self->schema, 'current_count', 'stock_property');
1333 my $recorded_current_count = $self->stock()->find_related('stockprops', {'me.type_id'=>$current_count_cvterm->cvterm_id});
1334 return $recorded_current_count ? $recorded_current_count->value() : '';
1337 =head2 _retrieve_quality
1339 Usage:
1340 Desc:
1341 Ret:
1342 Args:
1343 Side Effects:
1344 Example:
1346 =cut
1348 sub _retrieve_quality {
1349 my $self = shift;
1350 $self->quality($self->_retrieve_stockprop('seedlot_quality'));
1355 =head2 Method current_weight()
1357 Usage: my $current_weight = $sl->current_weight();
1358 Desc: returns the current weight of seeds in the seedlot
1359 Ret: a number
1360 Args: none
1361 Side Effects: retrieves transactions from db and calculates weight
1362 Example:
1364 =cut
1366 sub current_weight {
1367 my $self = shift;
1368 my $transactions = $self->transactions();
1370 my $weight = 0;
1371 foreach my $t (@$transactions) {
1372 if ($t->weight_gram() ne 'NA'){
1373 $weight += $t->weight_gram() * $t->factor();
1376 if ($weight == 0 && scalar(@$transactions)>0){
1377 $weight = 'NA';
1379 return $weight;
1382 # It is convenient and also much faster to retrieve a single value for the current_weight, rather than calculating it from the transactions.
1383 sub set_current_weight_property {
1384 my $self = shift;
1385 my $current_weight = $self->current_weight();
1386 my $current_weight_cvterm = SGN::Model::Cvterm->get_cvterm_row($self->schema, 'current_weight_gram', 'stock_property');
1387 my $stock = $self->stock();
1388 my $recorded_current_weight = $stock->find_related('stockprops', {'me.type_id'=>$current_weight_cvterm->cvterm_id});
1389 if ($recorded_current_weight){
1390 $recorded_current_weight->update({'value'=>$current_weight});
1391 } else {
1392 $stock->create_stockprops({$current_weight_cvterm->name() => $current_weight});
1394 return $current_weight;
1397 # It is convenient and also much faster to retrieve a single value for the current_weight, rather than calculating it from the transactions.
1398 sub get_current_weight_property {
1399 my $self = shift;
1400 my $current_count_cvterm = SGN::Model::Cvterm->get_cvterm_row($self->schema, 'current_weight_gram', 'stock_property');
1401 my $recorded_current_count = $self->stock()->find_related('stockprops', {'me.type_id'=>$current_count_cvterm->cvterm_id});
1402 return $recorded_current_count ? $recorded_current_count->value() : '';
1406 sub _add_transaction {
1407 my $self = shift;
1408 my $transaction = shift;
1410 my $transactions = $self->transactions();
1411 push @$transactions, $transaction;
1413 $self->transactions($transactions);
1416 =head2 store()
1418 Usage: my $seedlot_id = $sl->store();
1419 Desc: stores the current state of the object to the db. uses CXGN::Stock store as well.
1420 Ret: the seedlot id.
1421 Args: none
1422 Side Effects: accesses the db. Creates a new seedlot ID if not
1423 already existing.
1424 Example:
1426 =cut
1428 sub store {
1429 my $self = shift;
1430 my $error;
1432 my $coderef = sub {
1433 #Creating new seedlot
1434 if(!$self->stock){
1435 $self->name($self->uniquename());
1436 $self->type('seedlot');
1437 my $id = $self->SUPER::store();
1438 print STDERR "Saving seedlot returned ID $id.".localtime."\n";
1439 $self->seedlot_id($id);
1440 $self->_store_seedlot_location();
1441 $error = $self->_store_seedlot_relationships();
1442 if ($error){
1443 die $error;
1445 if ($self->box_name){
1446 $self->_store_stockprop('location_code', $self->box_name);
1448 if ($self->quality()) {
1449 $self->_store_stockprop('seedlot_quality', $self->quality());
1452 } else { #Updating seedlot
1454 #Attempting to update seedlot's accession. Will not proceed if seedlot has already been used in transactions.
1455 if($self->accession_stock_id){
1456 my $input_accession_id = $self->accession_stock_id;
1457 my $transactions = $self->transactions();
1458 my $stored_accession_id = $self->accession ? $self->accession->[0] : 0;
1459 $self->accession_stock_id($input_accession_id);
1460 my $accessions_have_changed = $input_accession_id == $stored_accession_id ? 0 : 1;
1461 if ($accessions_have_changed && scalar(@$transactions)>1){
1462 $error = "This seedlot ".$self->uniquename." has been used in transactions, so the contents (accessions) cannot be changed now!";
1463 } elsif ($accessions_have_changed && scalar(@$transactions) <= 1) {
1464 $error = $self->_update_content_stock_id();
1465 my $update_first_transaction_id = $transactions->[0]->update_transaction_object_id($self->accession_stock_id);
1467 if ($error){
1468 die $error;
1472 #Attempting to update seedlot's cross. Will not proceed if seedlot has already been used in transactions.
1473 if($self->cross_stock_id){
1474 my $input_cross_id = $self->cross_stock_id;
1475 my $transactions = $self->transactions();
1476 my $stored_cross_id = $self->cross ? $self->cross->[0] : 0;
1477 $self->cross_stock_id($input_cross_id);
1478 my $crosses_have_changed = $input_cross_id == $stored_cross_id ? 0 : 1;
1479 if ($crosses_have_changed && scalar(@$transactions)>1){
1480 $error = "This seedlot ".$self->uniquename." has been used in transactions, so the contents (crosses) cannot be changed now!";
1481 } elsif ($crosses_have_changed && scalar(@$transactions) <= 1) {
1482 $error = $self->_update_content_stock_id();
1483 my $update_first_transaction_id = $transactions->[0]->update_transaction_object_id($self->cross_stock_id);
1485 if ($error){
1486 die $error;
1490 my $id = $self->SUPER::store();
1491 print STDERR "Updating seedlot returned ID $id.".localtime."\n";
1492 $self->seedlot_id($id);
1493 if($self->breeding_program_id){
1494 $self->_update_seedlot_breeding_program();
1496 if($self->location_code){
1497 $self->_store_seedlot_location();
1498 $self->_update_seedlot_location();
1500 if($self->box_name){
1501 $self->_update_stockprop('location_code', $self->box_name);
1503 if($self->quality) {
1504 $self->_update_stockprop('seedlot_quality', $self->quality());
1509 my $transaction_error;
1510 try {
1511 $self->schema->txn_do($coderef);
1512 } catch {
1513 print STDERR "Transaction Error: $_\n";
1514 $transaction_error = $_;
1516 if ($transaction_error){
1517 return { error=>$transaction_error };
1518 } else {
1519 return { success=>1, seedlot_id=>$self->stock_id() };
1523 =head2 delete()
1525 Usage: my $error_message = $sl->delete();
1526 Desc: delete the seedlot from the database. only possible to delete a seedlot that has not been used in any transactions other than the transaction that initiated it.
1527 Ret: any error message. undef if no errors
1528 Args: none
1529 Side Effects: accesses the db. Deletes seedlot
1530 Example:
1532 =cut
1534 sub delete {
1535 my $self = shift;
1536 my $error = '';
1537 my $transactions = $self->transactions();
1538 my $name = $self->name();
1539 if (scalar(@$transactions)>1){
1540 $error = "Seedlot '$name' has been used in transactions and so cannot be deleted!";
1541 } else {
1542 my $stock = $self->stock();
1543 my $experiment_type_id = SGN::Model::Cvterm->get_cvterm_row($self->schema(), "seedlot_experiment", "experiment_type")->cvterm_id();
1544 my $nd_experiment_rs = $self->schema()->resultset('Stock::Stock')->search({'me.stock_id'=>$self->seedlot_id})->search_related('nd_experiment_stocks')->search_related('nd_experiment', {'nd_experiment.type_id'=>$experiment_type_id});
1545 if ($nd_experiment_rs->count != 1){
1546 $error = "Seedlot '$name' should have only one associated nd_experiment!";
1547 } else {
1548 my $nd_experiment = $nd_experiment_rs->first();
1549 $nd_experiment->delete();
1550 my $stock_owner_rs = $self->phenome_schema->resultset("StockOwner")->find({stock_id=>$self->stock_id});
1551 if ($stock_owner_rs){
1552 $stock_owner_rs->delete();
1554 $stock->delete();
1558 return $error;
1562 ### CLASS FUNCTION DELETE_USING_LIST
1564 sub delete_verify_using_list {
1565 my $class = shift;
1566 my $schema = shift;
1567 my $phenome_schema = shift;
1568 my $list_id = shift;
1570 my $list = CXGN::List->new( { dbh => $schema->storage->dbh(), list_id => $list_id } );
1571 my $type_row = SGN::Model::Cvterm->get_cvterm_row($schema, "seedlot", 'stock_type');
1573 my $type_id;
1574 if ($type_row) {
1575 $type_id = $type_row->cvterm_id();
1578 print STDERR "TYPE ID = $type_id\n";
1580 my $elements = $list->elements();
1582 my @errors;
1583 my @ok;
1585 print STDERR "ELEMENTS ".join(",", @$elements);
1586 my $delete_count = 0;
1587 foreach my $ele (@$elements) {
1588 print STDERR "start deletion for seedlot ".Dumper($ele)."...\n";
1589 my $rs = $schema->resultset("Stock::Stock")->search( { uniquename => $ele, type_id => $type_id });
1590 if ($rs->count() == 0) {
1591 print STDERR "No such seedlot $ele\n";
1592 push @errors, [ $ele, "No seedlot named '$ele' could be found in the database" ];
1594 else {
1595 my $experiment_type_id = SGN::Model::Cvterm->get_cvterm_row($schema, "seedlot_experiment", "experiment_type")->cvterm_id();
1596 my $seedlot_id = $rs->next()->stock_id();
1597 print STDERR "SEEDLOT ID: $seedlot_id\n";
1598 my $nd_experiment_rs = $schema->resultset('Stock::Stock')->search({'me.stock_id'=> $seedlot_id})->search_related('nd_experiment_stocks')->search_related('nd_experiment', {'nd_experiment.type_id'=>$experiment_type_id});
1599 if ($nd_experiment_rs->count != 1){
1600 my $error = "Seedlot '$ele' should have only one associated nd_experiment!";
1601 push @errors, [ $ele, $error];
1603 else {
1604 push @ok, $ele;
1609 return ( \@ok, \@errors );
1614 sub delete_using_list {
1615 my $class = shift;
1616 my $schema = shift;
1617 my $phenome_schema = shift;
1618 my $list_id = shift;
1620 my $list = CXGN::List->new( { dbh => $schema->storage->dbh(), list_id => $list_id } );
1621 my $type_row = SGN::Model::Cvterm->get_cvterm_row($schema, "seedlot", 'stock_type');
1623 my $type_id;
1624 if ($type_row) {
1625 $type_id = $type_row->cvterm_id();
1628 print STDERR "TYPE ID = $type_id\n";
1630 my $elements = $list->elements();
1632 my @errors;
1633 my $delete_count = 0;
1634 foreach my $ele (@$elements) {
1635 print STDERR "start deletion for seedlot ".Dumper($ele)."...\n";
1636 my $rs = $schema->resultset("Stock::Stock")->search( { uniquename => $ele, type_id => $type_id });
1637 if ($rs->count() == 0) {
1638 print STDERR "No such seedlot $ele\n";
1639 push @errors, "No seedlot named '$ele' could be found in the database";
1641 else {
1642 my $seedlot = CXGN::Stock::Seedlot->new( schema => $schema, phenome_schema => $phenome_schema, seedlot_id => $rs->next()->stock_id());
1643 my $error = $seedlot->delete();
1644 if ($error) {
1645 print STDERR "Error during seedlot deletion: $error\n";
1646 push @errors, $error;
1648 else {
1649 $delete_count++;
1653 return ( scalar(@$elements), $delete_count, \@errors );
1658 # SEEDLOT MAINTENANCE EVENT FUNCTIONS
1661 =head2 get_events()
1663 Usage: my @events = $sl->get_events();
1664 Desc: get all of seedlot maintenance events associated with the seedlot
1665 Args: page = (optional) the page number of results to return
1666 pageSize = (optional) the number of results per page to return
1667 Ret: a hash with the results metadata and the matching seedlot events:
1668 - page: current page number
1669 - maxPage: the number of the last page
1670 - pageSize: (max) number of results per page
1671 - total: total number of results
1672 - results: an arrayref of hases of the seedlot's stored events, with the following keys:
1673 - stock_id: the unique id of the seedlot
1674 - uniquename: the unique name of the seedlot
1675 - stockprop_id: the unique id of the maintenance event
1676 - cvterm_id: id of seedlot maintenance event ontology term
1677 - cvterm_name: name of seedlot maintenance event ontology term
1678 - value: value of the seedlot maintenance event
1679 - notes: additional notes/comments about the event
1680 - operator: username of the person creating the event
1681 - timestamp: timestamp string of when the event was created ('YYYY-MM-DD HH:MM:SS' format)
1683 =cut
1685 sub get_events {
1686 my $self = shift;
1687 my $page = shift;
1688 my $pageSize = shift;
1689 my $schema = $self->schema();
1690 my $seedlot_name = $self->uniquename();
1691 my $m = CXGN::Stock::Seedlot::Maintenance->new({ bcs_schema => $schema });
1693 return $m->filter_events({ names => [$seedlot_name] }, $page, $pageSize);
1697 =head2 get_event()
1699 Usage: my $event = $sl->get_event($id);
1700 Desc: get the specified seedlot maintenance event associated with the seedlot
1701 Args: id = stockprop_id of maintenance event
1702 Ret: a hashref of the seedlot maintenance event, with the following keys:
1703 - stock_id: the unique id of the seedlot
1704 - uniquename: the unique name of the seedlot
1705 - stockprop_id: the unique id of the maintenance event
1706 - cvterm_id: id of seedlot maintenance event ontology term
1707 - cvterm_name: name of seedlot maintenance event ontology term
1708 - value: value of the seedlot maintenance event
1709 - notes: additional notes/comments about the event
1710 - operator: username of the person creating the event
1711 - timestamp: timestamp string of when the event was created ('YYYY-MM-DD HH:MM:SS' format)
1713 =cut
1715 sub get_event {
1716 my $self = shift;
1717 my $event_id = shift;
1718 my $schema = $self->schema();
1719 my $seedlot_name = $self->uniquename();
1720 my $m = CXGN::Stock::Seedlot::Maintenance->new({ bcs_schema => $schema });
1722 my $events = $m->filter_events({ names => [$seedlot_name], events => [$event_id] });
1723 return $events->{'results'}->[0];
1727 =head2 store_events()
1729 Usage: my @events = ({ cvterm_id => $cvterm_id, value => $value, notes => $notes, operator => $operator, timestamp => $timestamp }, ... );
1730 my $stored_events = $sl->store_events(\@events);
1731 Desc: store one or more seedlot maintenance events in the database as a JSON stockprop associated with the seedlot's stock entry.
1732 this function uses the CXGN::Stock::Seedlot::Maintenance class to store the JSON stockprop
1733 Args: $events = arrayref of hashes of the event properties, with the following keys:
1734 - cvterm_id: id of seedlot maintenance event ontology term
1735 - value: value of the seedlot maintenance event
1736 - notes: (optional) additional notes/comments about the event
1737 - operator: username of the person creating the event
1738 - timestamp: timestamp string of when the event was created ('YYYY-MM-DD HH:MM:SS' format)
1739 Ret: an arrayref of hashes of the processed/stored events (includes stockprop_id), with the following keys:
1740 - stockprop_id: the unique id of the maintenance event
1741 - stock_id: the unique id of the seedlot
1742 - cvterm_id: id of seedlot maintenance event ontology term
1743 - cvterm_name: name of seedlot maintenance event ontology term
1744 - value: value of the seedlot maintenance event
1745 - notes: additional notes/comments about the event
1746 - operator: username of the person creating the event
1747 - timestamp: timestamp string of when the event was created ('YYYY-MM-DD HH:MM:SS' format)
1748 the function will die on a caught error
1750 =cut
1752 sub store_events {
1753 my $self = shift;
1754 my $events = shift;
1755 my $schema = $self->schema();
1756 my $seedlot_id = $self->seedlot_id();
1758 # Process the passed events
1759 my @processed_events = ();
1760 foreach my $event (@$events) {
1761 my $cvterm_id = $event->{cvterm_id};
1762 my $value = $event->{value};
1763 my $notes = $event->{notes};
1764 my $operator = $event->{operator};
1765 my $timestamp = $event->{timestamp};
1767 # Check for required parameters
1768 if ( !defined $cvterm_id || $cvterm_id eq '' ) {
1769 die "cvterm_id is required!";
1771 if ( !defined $value || $value eq '' ) {
1772 die "value is required!";
1774 if ( !defined $operator || $operator eq '' ) {
1775 die "operator is required!";
1777 if ( !defined $timestamp || $timestamp eq '' ) {
1778 die "timestamp is required!";
1780 if ( $timestamp !~ /^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$/ ) {
1781 die "timestamp not valid format [YYYY-MM-DD HH:MM:SS]!";
1784 # Find matching cvterm by id
1785 my $cvterm_rs = $schema->resultset("Cv::Cvterm")->search({ cvterm_id => $cvterm_id })->first();
1786 if ( !defined $cvterm_rs ) {
1787 die "cvterm_id $cvterm_id not found!";
1789 my $cvterm_name = $cvterm_rs->name();
1791 # Save processed event
1792 my %processed_event = (
1793 cvterm_id => $cvterm_id,
1794 cvterm_name => $cvterm_name,
1795 value => $value,
1796 notes => $notes,
1797 operator => $operator,
1798 timestamp => $timestamp
1800 push(@processed_events, \%processed_event);
1803 # Store the processed events
1804 foreach my $processed_event (@processed_events) {
1805 my $event_obj = CXGN::Stock::Seedlot::Maintenance->new({ bcs_schema => $schema, parent_id => $seedlot_id });
1806 $event_obj->cvterm_id($processed_event->{cvterm_id});
1807 $event_obj->cvterm_name($processed_event->{cvterm_name});
1808 $event_obj->value($processed_event->{value});
1809 $event_obj->notes($processed_event->{notes});
1810 $event_obj->operator($processed_event->{operator});
1811 $event_obj->timestamp($processed_event->{timestamp});
1812 my $stockprop_id = $event_obj->store_by_rank();
1813 $processed_event->{stockprop_id} = $stockprop_id;
1814 $processed_event->{stock_id} = $seedlot_id;
1817 # Return the processed events
1818 return(\@processed_events);
1822 =head2 remove_event()
1824 Usage: $sl->remove_event($id)
1825 Desc: delete the specified seedlot maintenance event from the database
1826 Args: $id = stockprop_id of the seedlot maintenance event
1827 Ret:
1829 =cut
1831 sub remove_event {
1832 my $self = shift;
1833 my $event_id = shift;
1834 my $seedlot_id = $self->seedlot_id();
1835 my $schema = $self->schema();
1836 my $m = CXGN::Stock::Seedlot::Maintenance->new({ bcs_schema => $schema, parent_id => $seedlot_id, prop_id => $event_id });
1838 $m->delete();
1842 =head2 get_seedlot_species()
1844 Usage: $seedlot->get_seedlot_species($id)
1845 Desc: retrieve species of seedlot content
1846 Ret:
1848 =cut
1850 sub get_seedlot_species {
1851 my $self = shift;
1852 my $schema = $self->schema();
1853 my $seedlot_id = $self->seedlot_id();
1855 my $collection_of_type_id = SGN::Model::Cvterm->get_cvterm_row($schema, 'collection_of', 'stock_relationship')->cvterm_id();
1857 my $q = "SELECT organism.species FROM stock_relationship
1858 JOIN stock ON (stock_relationship.subject_id = stock.stock_id) AND stock_relationship.type_id = ?
1859 JOIN organism ON (stock.organism_id = organism.organism_id)
1860 WHERE stock_relationship.object_id = ?";
1862 my $h = $schema->storage->dbh()->prepare($q);
1863 $h->execute($collection_of_type_id, $seedlot_id);
1865 my @data = ();
1866 while(my($species) = $h->fetchrow_array()){
1867 push @data, [$species];
1870 my $species_info = $data[0][0];
1872 return $species_info
1879 no Moose;
1880 __PACKAGE__->meta->make_immutable;