caruby-tissue 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. data/History.txt +4 -0
  2. data/LEGAL +5 -0
  3. data/LICENSE +22 -0
  4. data/README.md +44 -0
  5. data/bin/crtdump +31 -0
  6. data/bin/crtexample +18 -0
  7. data/bin/crtextract +47 -0
  8. data/bin/crtmigrate +17 -0
  9. data/bin/crtsmoke +27 -0
  10. data/examples/galena/README.md +53 -0
  11. data/examples/galena/bin/migrate.rb +42 -0
  12. data/examples/galena/bin/seed.rb +43 -0
  13. data/examples/galena/conf/extract/simple_fields.yaml +4 -0
  14. data/examples/galena/conf/migration/filter_fields.yaml +7 -0
  15. data/examples/galena/conf/migration/filter_migration.yaml +9 -0
  16. data/examples/galena/conf/migration/frozen_fields.yaml +11 -0
  17. data/examples/galena/conf/migration/frozen_migration.yaml +9 -0
  18. data/examples/galena/conf/migration/general_fields.yaml +42 -0
  19. data/examples/galena/conf/migration/general_migration.yaml +9 -0
  20. data/examples/galena/conf/migration/simple_fields.yaml +30 -0
  21. data/examples/galena/conf/migration/simple_migration.yaml +7 -0
  22. data/examples/galena/conf/migration/small_fields.yaml +24 -0
  23. data/examples/galena/conf/migration/small_migration.yaml +9 -0
  24. data/examples/galena/data/filter.csv +1 -0
  25. data/examples/galena/data/frozen.csv +1 -0
  26. data/examples/galena/data/general.csv +1 -0
  27. data/examples/galena/data/minimal.csv +1 -0
  28. data/examples/galena/data/simple.csv +1 -0
  29. data/examples/galena/data/small.csv +1 -0
  30. data/examples/galena/doc/CaTissue.html +93 -0
  31. data/examples/galena/doc/CaTissue/CollectionProtocolRegistration.html +181 -0
  32. data/examples/galena/doc/CaTissue/Participant.html +241 -0
  33. data/examples/galena/doc/CaTissue/SpecimenCollectionGroup.html +190 -0
  34. data/examples/galena/doc/CaTissue/StorageContainer.html +179 -0
  35. data/examples/galena/doc/CaTissue/TissueSpecimen.html +320 -0
  36. data/examples/galena/doc/Galena.html +290 -0
  37. data/examples/galena/doc/Galena/Seed.html +203 -0
  38. data/examples/galena/doc/Galena/Seed/Defaults.html +646 -0
  39. data/examples/galena/doc/_index.html +188 -0
  40. data/examples/galena/doc/class_list.html +36 -0
  41. data/examples/galena/doc/css/common.css +1 -0
  42. data/examples/galena/doc/css/full_list.css +53 -0
  43. data/examples/galena/doc/css/style.css +307 -0
  44. data/examples/galena/doc/file.README.html +108 -0
  45. data/examples/galena/doc/file_list.html +38 -0
  46. data/examples/galena/doc/frames.html +13 -0
  47. data/examples/galena/doc/index.html +108 -0
  48. data/examples/galena/doc/js/app.js +202 -0
  49. data/examples/galena/doc/js/full_list.js +149 -0
  50. data/examples/galena/doc/js/jquery.js +154 -0
  51. data/examples/galena/doc/method_list.html +179 -0
  52. data/examples/galena/doc/top-level-namespace.html +112 -0
  53. data/examples/galena/lib/README.html +33 -0
  54. data/examples/galena/lib/galena.rb +8 -0
  55. data/examples/galena/lib/galena/cli/seed.rb +43 -0
  56. data/examples/galena/lib/galena/migration/filter_shims.rb +43 -0
  57. data/examples/galena/lib/galena/migration/frozen_shims.rb +54 -0
  58. data/examples/galena/lib/galena/seed/defaults.rb +97 -0
  59. data/lib/catissue.rb +26 -0
  60. data/lib/catissue/cli/command.rb +51 -0
  61. data/lib/catissue/cli/example.rb +31 -0
  62. data/lib/catissue/cli/migrate.rb +60 -0
  63. data/lib/catissue/cli/smoke.rb +45 -0
  64. data/lib/catissue/database.rb +451 -0
  65. data/lib/catissue/database/annotation/annotatable_service.rb +25 -0
  66. data/lib/catissue/database/annotation/annotation_service.rb +79 -0
  67. data/lib/catissue/database/annotation/annotator.rb +84 -0
  68. data/lib/catissue/database/annotation/entity_manager.rb +10 -0
  69. data/lib/catissue/database/annotation/integration_service.rb +87 -0
  70. data/lib/catissue/database/controlled_value_finder.rb +43 -0
  71. data/lib/catissue/database/controlled_values.rb +162 -0
  72. data/lib/catissue/domain/abstract_domain_object.rb +8 -0
  73. data/lib/catissue/domain/abstract_position.rb +22 -0
  74. data/lib/catissue/domain/abstract_specimen.rb +288 -0
  75. data/lib/catissue/domain/abstract_specimen_collection_group.rb +25 -0
  76. data/lib/catissue/domain/address.rb +13 -0
  77. data/lib/catissue/domain/cancer_research_group.rb +11 -0
  78. data/lib/catissue/domain/capacity.rb +34 -0
  79. data/lib/catissue/domain/check_in_check_out_event_parameter.rb +19 -0
  80. data/lib/catissue/domain/collection_event_parameters.rb +13 -0
  81. data/lib/catissue/domain/collection_protocol.rb +177 -0
  82. data/lib/catissue/domain/collection_protocol_event.rb +108 -0
  83. data/lib/catissue/domain/collection_protocol_registration.rb +108 -0
  84. data/lib/catissue/domain/consent_tier_response.rb +13 -0
  85. data/lib/catissue/domain/consent_tier_status.rb +29 -0
  86. data/lib/catissue/domain/container.rb +234 -0
  87. data/lib/catissue/domain/container_position.rb +21 -0
  88. data/lib/catissue/domain/container_type.rb +131 -0
  89. data/lib/catissue/domain/department.rb +13 -0
  90. data/lib/catissue/domain/disposal_event_parameters.rb +13 -0
  91. data/lib/catissue/domain/embedded_event_parameters.rb +10 -0
  92. data/lib/catissue/domain/external_identifier.rb +22 -0
  93. data/lib/catissue/domain/frozen_event_parameters.rb +10 -0
  94. data/lib/catissue/domain/institution.rb +13 -0
  95. data/lib/catissue/domain/new_specimen_array_order_item.rb +35 -0
  96. data/lib/catissue/domain/order_details.rb +25 -0
  97. data/lib/catissue/domain/participant.rb +138 -0
  98. data/lib/catissue/domain/participant_medical_identifier.rb +38 -0
  99. data/lib/catissue/domain/password.rb +11 -0
  100. data/lib/catissue/domain/race.rb +11 -0
  101. data/lib/catissue/domain/received_event_parameters.rb +25 -0
  102. data/lib/catissue/domain/scg_event_parameters.rb +11 -0
  103. data/lib/catissue/domain/site.rb +30 -0
  104. data/lib/catissue/domain/specimen.rb +456 -0
  105. data/lib/catissue/domain/specimen_array.rb +47 -0
  106. data/lib/catissue/domain/specimen_array_content.rb +19 -0
  107. data/lib/catissue/domain/specimen_array_type.rb +20 -0
  108. data/lib/catissue/domain/specimen_characteristics.rb +20 -0
  109. data/lib/catissue/domain/specimen_collection_group.rb +412 -0
  110. data/lib/catissue/domain/specimen_event_parameters.rb +111 -0
  111. data/lib/catissue/domain/specimen_position.rb +38 -0
  112. data/lib/catissue/domain/specimen_protocol.rb +34 -0
  113. data/lib/catissue/domain/specimen_requirement.rb +143 -0
  114. data/lib/catissue/domain/storage_container.rb +204 -0
  115. data/lib/catissue/domain/storage_type.rb +82 -0
  116. data/lib/catissue/domain/transfer_event_parameters.rb +53 -0
  117. data/lib/catissue/domain/user.rb +100 -0
  118. data/lib/catissue/extract/command.rb +31 -0
  119. data/lib/catissue/extract/delta.rb +62 -0
  120. data/lib/catissue/extract/extractor.rb +99 -0
  121. data/lib/catissue/migration/migrator.rb +101 -0
  122. data/lib/catissue/migration/shims.rb +108 -0
  123. data/lib/catissue/migration/uniquify.rb +111 -0
  124. data/lib/catissue/resource.rb +84 -0
  125. data/lib/catissue/util/controlled_value.rb +29 -0
  126. data/lib/catissue/util/location.rb +116 -0
  127. data/lib/catissue/util/log.rb +30 -0
  128. data/lib/catissue/util/person.rb +31 -0
  129. data/lib/catissue/util/position.rb +54 -0
  130. data/lib/catissue/util/storable.rb +34 -0
  131. data/lib/catissue/util/storage_type_holder.rb +30 -0
  132. data/lib/catissue/version.rb +7 -0
  133. metadata +212 -0
@@ -0,0 +1,47 @@
1
+
2
+
3
+ module CaTissue
4
+ # import the Java class
5
+ java_import('edu.wustl.catissuecore.domain.SpecimenArray')
6
+
7
+ class SpecimenArray
8
+ include Resource
9
+
10
+ # caTissue alert - the superclass Container occupied_positions does not apply to SpecimenArray.
11
+ remove_attribute(:occupied_positions)
12
+
13
+ add_attribute_aliases(:container_type => :specimen_array_type, :contents => :specimen_array_contents)
14
+
15
+ set_attribute_type(:new_specimen_array_order_items, CaTissue::SpecimenArrayOrderItem)
16
+
17
+ add_dependent_attribute(:new_specimen_array_order_items)
18
+
19
+ add_dependent_attribute(:specimen_array_contents)
20
+
21
+ # Raises NotImplementedError since caTissue SpecimenArray is broken in important ways
22
+ # described below.
23
+ #
24
+ # caTissue alert - SpecimenArray is a subclass of Container but is a mismatch for Container
25
+ # in two critical ways:
26
+ # * SpecimenArray does not hold subcontainers in Container occupied_positions
27
+ # * the Specimen position cannot be a SpecimenArrayContent
28
+ # * SpecimenArrayContent is not an AbstractPosition, although it should be
29
+ #
30
+ # Thus, SpecimenArray extends Container although it shouldn't and SpecimenArrayContent
31
+ # doesn't extend AbstractPosition although it should.
32
+ def add(storable, coordinate=nil, attribute=nil)
33
+ raise NotImplementedError.new("Adding specimens to a SpecimenArray is not yet supported")
34
+ end
35
+
36
+ alias :<< :add
37
+
38
+ alias :all_occupied_positions :specimen_array_contents
39
+
40
+ protected
41
+
42
+ # Returns the contents if storable is a Specimen, nil otherwise.
43
+ def content_collection_for(storable)
44
+ contents if CaTissue::Specimen === storable
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,19 @@
1
+ require 'catissue/util/position'
2
+
3
+ module CaTissue
4
+ # import the Java class
5
+ java_import('edu.wustl.catissuecore.domain.SpecimenArrayContent')
6
+
7
+ # caTissue alert - #{CaTissue::SpecimenArrayContent} should be derived from
8
+ # {CaTissue::AbstractPosition} but isn't (cf. {CaTissue::ContainerType}).
9
+ # Partially rectify this by including the {Position} mix-in in common with
10
+ # {CaTissue::AbstractPosition}.
11
+ class SpecimenArrayContent
12
+ include Position, Resource
13
+
14
+ add_attribute_aliases(:holder => :specimen_array, :occupant => :specimen)
15
+
16
+ # Each SpecimenArrayContent has a specimen and there is only one such slot per specimen.
17
+ set_secondary_key_attributes(:specimen)
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ module CaTissue
2
+ # import the Java class
3
+ java_import('edu.wustl.catissuecore.domain.SpecimenArrayType')
4
+
5
+ class SpecimenArrayType
6
+ include Resource
7
+
8
+ add_mandatory_attributes(:specimen_class, :specimen_types)
9
+
10
+ # Returns SpecimenArray.
11
+ def container_class
12
+ CaTissue::SpecimenArray
13
+ end
14
+
15
+ # Returns true if Storable is a Specimen and supported by this SpecimenArrayType.
16
+ def can_hold_child?(storable)
17
+ Specimen === storable and storable.specimen_class == specimen_class and specimen_types.include?(storable.specimen_type)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+
2
+
3
+ module CaTissue
4
+ # import the Java class
5
+ java_import('edu.wustl.catissuecore.domain.SpecimenCharacteristics')
6
+
7
+ class SpecimenCharacteristics
8
+ include Resource
9
+
10
+ add_attribute_defaults(:tissue_side => 'Not Specified', :tissue_site => 'Not Specified')
11
+
12
+ add_mandatory_attributes(:tissue_site, :tissue_side)
13
+
14
+ # The tissue_side constants.
15
+ class TissueSide
16
+ LEFT = 'Left'
17
+ RIGHT = 'Right'
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,412 @@
1
+ require 'caruby/util/transitive_closure'
2
+
3
+ module CaTissue
4
+ # import the Java class
5
+ java_import('edu.wustl.catissuecore.domain.SpecimenCollectionGroup')
6
+
7
+ # The SpecimenCollectionGroup domain class.
8
+ #
9
+ # _Note_: the SpecimenCollectionGroup name attribute is auto-generated on create in caTissue 1.1 API and should not
10
+ # be set by API clients when creating a new SpecimenCollectionGroup in the database.
11
+ class SpecimenCollectionGroup
12
+ include Resource
13
+
14
+ # caTissue alert - Bug #64: Some domain collection properties not initialized.
15
+ # Initialize consent_tier_statuses if necessary.
16
+ #
17
+ # @return [Java::JavaUtil::Set] the statuses
18
+ def consent_tier_statuses
19
+ getConsentTierStatusCollection or (self.consent_tier_statuses = Java::JavaUtil::LinkedHashSet.new)
20
+ end
21
+
22
+ # Sets the collection status for this SCG.
23
+ # If the SCG status is set to +Complete+, then the status of each of the SCG Specimens with
24
+ # status +Pending+ is reset to +Collected+.
25
+ #
26
+ # @param [String] value a permissible SCG status
27
+ def collection_status=(value)
28
+ if value == 'Complete' then
29
+ specimens.each { |spc| spc.collection_status = 'Collected' if spc.pending? }
30
+ end
31
+ setCollectionStatus(value)
32
+ end
33
+
34
+ # Converts an Integer SPN value to a String.
35
+ #
36
+ # @param [Numeric, String] value the SPN value
37
+ def surgical_pathology_number=(value)
38
+ value = value.to_s if Numeric === value
39
+ setSurgicalPathologyNumber(value)
40
+ end
41
+
42
+ add_attribute_aliases(:collection_event => :collection_protocol_event,
43
+ :event_parameters => :specimen_event_parameters,
44
+ :events => :specimen_event_parameters,
45
+ :registration => :collection_protocol_registration)
46
+
47
+ add_mandatory_attributes(:specimen_event_parameters, :specimen_collection_site, :clinical_diagnosis, :collection_status)
48
+
49
+ set_secondary_key_attributes(:name)
50
+
51
+ set_alternate_key_attributes(:surgical_pathology_number, :collection_protocol_registration)
52
+
53
+ # caTissue alert - An auto-generated SCG in turn auto-generates a ConsentTierStatus
54
+ # for each ConsentTierResponse defined in the SCG owner CPR.
55
+ #
56
+ # caTissue alert - SCG consent_tier_statuses is cascaded but not fetched.
57
+ add_dependent_attribute(:consent_tier_statuses, :autogenerated, :unfetched)
58
+
59
+ # SCG event parameters are disjoint, since they are owned by either a SCG or a Specimen.
60
+ # An auto-generated SCG also auto-generates the Collection and Received SEPs.
61
+ # A SCG SEP is only created as a dependent when the SCG is created. A SCG SEP
62
+ # cannot be created for an existing SCG. By contrast, a Specimen SEP can only be
63
+ # created, not updated.
64
+ #
65
+ # caTissue alert - SpecimenCollectionGroup auto-generated update ignores the referenced
66
+ # specimen_event_parameters and instead creates new parameters. This occurs only on the
67
+ # first update, and an SEP cannot be added to an existing, updated SCG. Work-around is
68
+ # to update the SCG template without the parameters, then update the parameters separately.
69
+ add_dependent_attribute(:specimen_event_parameters, :autogenerated, :disjoint)
70
+
71
+ # SCG Specimens are auto-generated from SpecimenRequirement templates when the SCG is created.
72
+ # The Specimens are not cascaded.
73
+ add_dependent_attribute(:specimens, :logical, :autogenerated)
74
+
75
+ # The CPE-SCG association is bi-directional.
76
+ set_attribute_inverse(:collection_protocol_event, :specimen_collection_groups)
77
+
78
+ # CPE is fetched but not cascaded.
79
+ qualify_attribute(:collection_protocol_event, :fetched)
80
+
81
+ # caTissue alert - Augment the cascaded reference attributes to work around a CaTissue bug that requires that a
82
+ # SpecimenCollectionGroup update object references a CollectionProtocolRegistration with a Participant reference.
83
+ # The CPR identifier should be sufficient for a SCG update, but caTissue bizlogic requires an extraneous
84
+ # CPR -> Participant reference.
85
+ # TODO - file a bug and reference it here
86
+ qualify_attribute(:collection_protocol_registration, :cascaded)
87
+
88
+ # caTissue alert - Bug #65: Although SCG name uniquely identifies a SCG, the SCG name is auto-generated on create
89
+ # and cannnot be set by the client. Therefore, name is marked as update_only.
90
+ qualify_attribute(:name, :autogenerated, :update_only)
91
+
92
+ # caTissue alert - Bug #64: Initialize the SCG consent_tier_statuses to an empty set.
93
+ def initialize(params=nil)
94
+ super
95
+ # work around caTissue Bug #64
96
+ self.consent_tier_statuses ||= Java::JavaUtil::LinkedHashSet.new
97
+ end
98
+
99
+ # @return [CollectionProtocol] the SCG CPE CP
100
+ def collection_protocol
101
+ collection_protocol_event.collection_protocol if collection_protocol_event
102
+ end
103
+
104
+ # @return [Double, nil] the SCG CPE event point
105
+ def event_point
106
+ collection_protocol_event and collection_protocol_event.study_calendar_event_point
107
+ end
108
+
109
+ # @return [<SpecimenRequirement>] the SCG CPE requirements
110
+ def requirements
111
+ collection_protocol_event.nil? ? Array::EMPTY_ARRAY : collection_protocol_event.requirements
112
+ end
113
+
114
+ # @return [Boolean] whether this SCG collection status is one of the +Pending+ statuses.
115
+ def pending?
116
+ collection_status =~ /^Pending/
117
+ end
118
+
119
+ # Returns the number of specimens in this SpecimenCollectionGroup.
120
+ def size
121
+ specimens.size
122
+ end
123
+
124
+ # Removes associations to this registration.
125
+ def delete
126
+ registration.specimen_collection_groups.delete(self) if registration
127
+ end
128
+
129
+ # Merges the other object into this SpecimenCollectionGroup. This method overrides the
130
+ # standard {CaRuby::Resource#merge_attributes} method as follows:
131
+ # * Builds the SpecimenEventParameter objects from atomic parameters, e.g.:
132
+ # SpecimenCollectionGroup.create(:name = > name, ..., :collector => collector, :receiver => receiver)
133
+ # The supported event parameters are described in {#collect}.
134
+ # * Adds the transitive closure of each non-derived Specimen in other.
135
+ def merge_attributes(other, attributes=nil)
136
+ if Hash === other then
137
+ # extract the event parameters
138
+ other[:specimen_event_parameters] = extract_event_params(other)
139
+ # take the transitive closure of the specimens
140
+ spcs = other.delete(:specimens)
141
+ if spcs then
142
+ spcs = [spcs] if CaTissue::Specimen === spcs
143
+ # take the transitive closure of the root specimens in the hierarchy
144
+ other[:specimens] = spcs.select { |spc| spc.parent.nil? }.transitive_closure(:children)
145
+ end
146
+ end
147
+ # delegate to super for standard attribute value merge
148
+ super
149
+ end
150
+
151
+ # Collects and receives this SCG with the given params option => value hash.
152
+ # The SCG collection status is set to 'Completed'.
153
+ #
154
+ # Supported parameters are as follows:
155
+ # * receiver - the required User who received the specimens
156
+ # * received_date - the optional receival date (default is now)
157
+ # * collector - the optional User who collected the specimens (default is receiver)
158
+ # * collection_date - the optional collection date (default is received_date)
159
+ #
160
+ # If there is at least a receiver, then no additional event parameters are added.
161
+ #
162
+ # Raises ValidationError if this SCG has already been received.
163
+ def collect(params)
164
+ raise ValidationError.new("SCG is already collected") if received?
165
+ specimen_event_parameters.merge!(extract_event_params(params))
166
+ end
167
+
168
+ def protocol
169
+ collection_event.protocol if collection_event
170
+ end
171
+
172
+ # Returns whether this SCG has a collected event.
173
+ def collected?
174
+ collection_event_parameters
175
+ end
176
+
177
+ # Returns the User who collected this SCG.
178
+ def collector
179
+ ep = collection_event_parameters
180
+ ep.user if ep
181
+ end
182
+
183
+ # @return [Date] the date this SCG was donated by the participant.
184
+ def collection_timestamp
185
+ ep = collection_event_parameters
186
+ ep.timestamp if ep
187
+ end
188
+
189
+ # Returns the CollectionEventParameters for this specimen group.
190
+ def collection_event_parameters
191
+ event_parameters.detect { |ep| CaTissue::CollectionEventParameters === ep }
192
+ end
193
+
194
+ # Returns whether this SCG has a received event.
195
+ def received?
196
+ received_event_parameters
197
+ end
198
+
199
+ # Returns the User who received this specimen group.
200
+ def receiver
201
+ ep = received_event_parameters
202
+ ep.user if ep
203
+ end
204
+
205
+ # Returns the ReceivedEventParameters for this specimen group.
206
+ def received_event_parameters
207
+ event_parameters.detect { |ep| CaTissue::ReceivedEventParameters === ep }
208
+ end
209
+
210
+ # Returns whether this SCG is the same as the other SCG in the scope of an existing parent CPR.
211
+ # This method returns whether the other SCG status is Pending and the event point is the
212
+ # same as the other event point.
213
+ def minimal_match?(other)
214
+ super and event_point == other.event_point
215
+ end
216
+
217
+ # Overrides {CaRuby::Resource#direct_dependents} in the case of the _specimens_ attribute to select
218
+ # only top-level Specimens not derived from another Specimen.
219
+ def direct_dependents(attribute)
220
+ if attribute == :specimens then
221
+ super.reject { |spc| spc.parent }
222
+ else
223
+ super
224
+ end
225
+ end
226
+
227
+ def validate
228
+ super
229
+ validate_consent
230
+ validate_event_parameters
231
+ end
232
+
233
+ # Restricts the {CaRuby::Persistable#fetch_saved?} condition for a SCG as follows:
234
+ #
235
+ # If the SCG status was created or was updated from +Pending+ to +Collected+,
236
+ # then fetch the saved SCG. Otherwise, forego fetching the saved SCG.
237
+ #
238
+ # @return (see CaRuby::Persistable#fetch_saved)
239
+ def fetch_saved?
240
+ super and status_changed_to_complete?
241
+ end
242
+
243
+ private
244
+
245
+ SAVED_FETCH_MSG = "Saved #{qp} %s be fetched from the database to reflect the current database state, since it was %s."
246
+
247
+ # @see #fetch_saved
248
+ def status_changed_to_complete?
249
+ if identifier.nil? then
250
+ logger.debug { format_saved_fetch_message(true, "created") }
251
+ true
252
+ elsif collected? and (snapshot.nil? or snapshot[:collection_status] == 'Pending') then
253
+ logger.debug { format_saved_fetch_message(true, "updated to status Complete") }
254
+ true
255
+ else
256
+ logger.debug { format_saved_fetch_message(false, "not updated from pending to complete") }
257
+ false
258
+ end
259
+ end
260
+
261
+ def format_saved_fetch_message(fetch, reason)
262
+ args = fetch ? [qp, 'must', reason] : [qp, 'does not need to', reason]
263
+ SAVED_FETCH_MSG % args
264
+ end
265
+
266
+ def each_defaults_dependent
267
+ # visit ReceivedEventParameters first
268
+ rep = received_event_parameters
269
+ yield rep if rep
270
+ # add other dependent defaults
271
+ each_dependent { |dep| yield dep unless ReceivedEventParameters === dep }
272
+ end
273
+
274
+ # The default In Transit collection site.
275
+ DEF_SITE = Site.new(:name => 'In Transit')
276
+
277
+ # Adds defaults as follows:
278
+ # * the default collection event is the first event in the protocol registered with this SCG.
279
+ # * the default collection status is 'Complete' if there is a received event, 'Pending' otherwise.
280
+ # * the default collection site is the CP site, if this SCG is {#received?} and there is only CP one,
281
+ # otherwise the 'In Transit' site.
282
+ # * the default conset tier status is 'Complete' if there is a received event, 'Pending' otherwise.
283
+ # * a default ReceivedEventParameters is added to this SCG if the collection status is
284
+ # 'Complete' and there is no other ReceivedEventParameters. The receiver is an arbitrary
285
+ # protocol coordinator.
286
+ #
287
+ # Raises ValidationError if the default ReceivedEventParameters could not be created because
288
+ # there is no protocol or protocol coordinator.
289
+ #
290
+ # @see CollectionProtocol#first_event
291
+ def add_defaults_local
292
+ super
293
+ # the default event
294
+ self.collection_protocol_event ||= default_collection_event
295
+
296
+ # the default collection status and received parameters
297
+ if received? then
298
+ self.collection_status ||= 'Complete'
299
+ elsif collection_status == 'Complete' then
300
+ create_default_received_event_parameters
301
+ else
302
+ self.collection_status ||= 'Pending'
303
+ end
304
+ # the default collection event
305
+ if received? and not collected? then
306
+ create_default_collection_event_parameters
307
+ end
308
+
309
+ # the default site
310
+ self.collection_site ||= default_site
311
+
312
+ # the default CT statuses
313
+ make_default_consent_tier_statuses
314
+ end
315
+
316
+ # Makes a consent status for each registration consent.
317
+ #
318
+ # caTissue alert - Bug #156: SCG without consent status displays error.
319
+ # A SCG consent tier status is required for each consent tier in the SCG registration.
320
+ def make_default_consent_tier_statuses
321
+ return if registration.nil? or registration.consent_tier_responses.empty?
322
+
323
+ # the consent tiers
324
+ ctses = consent_tier_statuses.map { |cts| cts.consent_tier }
325
+ # ensure that there is a CT status for each consent tier
326
+ registration.consent_tier_responses.each do |ctr|
327
+ ct = ctr.consent_tier
328
+ next if ctses.include?(ct)
329
+ cts = CaTissue::ConsentTierStatus.new(:consent_tier => ct).add_defaults
330
+ consent_tier_statuses << cts
331
+ logger.debug { "Made default #{qp} #{cts.qp} for consent tier #{ct.qp}." }
332
+ end
333
+ end
334
+
335
+ def default_site
336
+ cp = collection_event.protocol if collection_event
337
+ cp && cp.sites.size == 1 ? cp.sites.first : DEF_SITE
338
+ end
339
+
340
+ # Returns the first event in the protocol registered with this SCG.
341
+ def default_collection_event
342
+ return if registration.nil?
343
+ pcl = registration.protocol || return
344
+ # if no protocol event, then add the default event
345
+ pcl.add_defaults if pcl.events.empty?
346
+ pcl.first_event
347
+ end
348
+
349
+ def create_default_received_event_parameters
350
+ cp = collection_protocol
351
+ if cp.nil? then
352
+ raise ValidationError.new("SCG with status Complete default CollectionEventParameters could not be created since there is no collection protocol: #{self}")
353
+ end
354
+ rcvr = cp.coordinators.first
355
+ if rcvr.nil? then
356
+ raise ValidationError.new("SCG with status Complete default CollectionEventParameters could not be created since there is no collection protocol coordinator: #{self}")
357
+ end
358
+ # make the REP
359
+ CaTissue::SpecimenEventParameters.create_parameters(:received, self, :user => rcvr)
360
+ end
361
+
362
+ def create_default_collection_event_parameters
363
+ rep = received_event_parameters || return
364
+ # make the CEP from the REP
365
+ CaTissue::SpecimenEventParameters.create_parameters(:collection, self, :user => rep.user, :timestamp => rep.timestamp)
366
+ end
367
+
368
+ # Verifies that each registration consent tier response has a corresponding SCG consent tier status.
369
+ def validate_consent
370
+ return unless registration
371
+ # the default consent statuses
372
+ ctses = consent_tier_statuses.map { |cts| cts.consent_tier }
373
+ registration.consent_tier_responses.each do |ctr|
374
+ ct = ctr.consent_tier
375
+ unless ctses.include?(ct) then
376
+ raise ValidationError.new("#{self} is missing a ConsentTierStatus for consent statement #{ct.statement}")
377
+ end
378
+ end
379
+ end
380
+
381
+ # Verifies that a SCG create with collection status 'Pending' does not have event parameters.
382
+ def validate_event_parameters
383
+ if identifier.nil? and collection_status == 'Pending' and not event_parameters.empty? then
384
+ raise ValidationError.new("#{self} create with status Pending cannot include event parameters #{event_parameters.to_series}")
385
+ end
386
+ end
387
+
388
+ # Returns SpecimenEventParameters built from the params associations.
389
+ # The params used to build the SpecimenEventParameters are removed, since they are
390
+ # redundant.
391
+ #
392
+ # Raises ValidationError if the required collector or receiver features are missing.
393
+ def extract_event_params(params)
394
+ # Check if there is an attribute association
395
+ event_params = params.delete(:specimen_event_parameters) || []
396
+ # collect additional parameter associations
397
+ scg_receiver = params.delete(:receiver)
398
+ scg_collector = params.delete(:collector)
399
+ scg_collector ||= scg_receiver
400
+ # if there is not at least a collector, then don't continue parsing
401
+ return event_params if scg_collector.nil?
402
+ received_date = params.delete(:received_date)
403
+ received_date ||= DateTime.now
404
+ event_params << CaTissue::SpecimenEventParameters.create_parameters(:received, self, :user => scg_receiver, :timestamp => received_date)
405
+ collection_date = params.delete(:collection_date)
406
+ collection_date ||= received_date
407
+ event_params << CaTissue::SpecimenEventParameters.create_parameters(:collection, self, :user => scg_collector, :timestamp => collection_date)
408
+ logger.debug { "SCG #{self} event parameters: #{event_params.pp_s(:single_line)}" }
409
+ event_params
410
+ end
411
+ end
412
+ end