SpeciesFileGroup/taxonworks

View on GitHub
app/helpers/collection_objects_helper.rb

Summary

Maintainability
A
0 mins
Test Coverage
module CollectionObjectsHelper

  def table_example(collection_objects)
    cols = %i{
      class
      order
      family
      genus
      scientificName
      sex
    }

    tag.table do
      tag.tr { cols.collect{|h| tag.th(h.to_s) }.join.html_safe } +

      collection_objects.collect{|co|
        tag.tr do
          (tag.td( co.dwc_class) +
          tag.td( co.dwc_order) +
          tag.td( co.dwc_family) +
          tag.td( co.dwc_genus) +
          tag.td( co.dwc_scientific_name) +
          tag.td( co.dwc_sex)).html_safe
        end
      }.join.html_safe
    end.html_safe
  end

  # Return [String, nil]
  #   a descriptor including the identifier and determination
  def collection_object_tag(collection_object)
    return nil if collection_object.nil?
    a = [
      collection_object_loan_tag(collection_object),
      collection_object_deaccession_tag(collection_object),
      collection_object_identifier_tag(collection_object),
      taxon_determination_tag(collection_object.taxon_determinations.order(:position).first)
    ].compact

    if a.empty?
      a << [
        collection_object.buffered_collecting_event,
        collection_object.buffered_determinations,
        collection_object.buffered_other_labels
      ].compact
    end

    a << "[#{collection_object.type[(0..2)].capitalize}]"

    a.join('&nbsp;').html_safe
  end

  def collection_object_link(collection_object)
    return nil if collection_object.nil?
    link_to(collection_object_tag(collection_object).html_safe, collection_object.metamorphosize)
  end

  def radial_quick_forms_tag(object)
    content_tag(:span, '', data: { "global-id": object.to_global_id.to_s, 'radial-quick-forms': 'true'})
  end

  def label_for_collection_object(collection_object)
    return nil if collection_object.nil?
    [ 'CollectionObject ' + collection_object.id.to_s,
      identifier_list_labels(collection_object)
    ].compact.join('; ')
  end

  def collection_object_autocomplete_tag(collection_object)
    return nil if collection_object.nil?
    [
      collection_object_loan_tag(collection_object),
      collection_object_deaccession_tag(collection_object),
      collection_object_identifier_tag(collection_object),
      collection_object_taxon_determination_tag(collection_object)
    ].join(' ').html_safe
  end

  # Text only, taxon name cached or OTU name for the
  # most recent determination
  def collection_object_scientific_name(collection_object)
    return nil if collection_object.nil?
    if a = collection_object.taxon_determinations.order(:position)&.first
      if a.otu.taxon_name
        a.otu.taxon_name.cached
      else
        a.otu.name
      end
    else
      nil
    end
  end

  def collection_objects_search_form
    render('/collection_objects/quick_search_form')
  end

  def verify_accessions_task_link(collection_object)
    priority = [collection_object.container, collection_object.identifiers.first, collection_object ].compact.first
    link_to('Verify', verify_accessions_task_path(by: priority.metamorphosize.class.name.tableize.singularize.to_sym, id: priority.to_param))
  end

  def collection_object_identifier_tag(collection_object)
    return nil if collection_object.nil?
    t, i = collection_object_visualized_identifier(collection_object)

    return content_tag(:span, i, class: [
      :feedback,
      'feedback-thin',
      (t == :collection_object ? 'feedback-primary' : 'feedback-warning')
    ]) if i
    content_tag(:span, 'no identifier assigned', class: [:feedback, 'feedback-thin', 'feedback-warning'])
  end

  def collection_object_deaccession_tag(collection_object)
    return nil if collection_object.nil? || (collection_object.deaccession_reason.blank? && collection_object.deaccessioned_at.nil?)
    msg = ['DEACCESSIONED"', collection_object.deaccession_reason, collection_object.deaccessioned_at&.year].compact.join(' - ')
    content_tag(:span, msg, class: [
      :feedback,
      'feedback-thin',
      'feedback-danger'
    ]).html_safe
  end

  def collection_object_loan_tag(collection_object)
    return nil if collection_object.nil? || !collection_object.on_loan?
    msg = collection_object.loan_return_date ? 'On Loan until ' + collection_object.loan_return_date.to_s : 'Gifted'
    content_tag(:span, msg, class: [
      :feedback,
      'feedback-thin',
      'feedback-warning'
    ]).html_safe
  end


  # @return [Array [Identifier, String (type)], nil]
  #    also checks virtual container for identifier by proxy
  def collection_object_visualized_identifier(collection_object)
    return nil if collection_object.nil?
    # Get the Identifier::Local::Catalog number on collection_object, or immediate containing Container
    i = collection_object.preferred_catalog_number # see delegation in collection_object.rb

    # Get some other identifier on collection_object
    i ||= collection_object.identifiers.order(:position)&.first
    return  [:collection_object, identifier_tag(i)] if i

    # Get some other identifier on container
    j = collection_object&.container&.identifiers&.first
    return [:container, identifier_tag(j)] if j
    nil
  end

  def collection_object_taxon_determination_tag(collection_object)
    return nil if collection_object.nil?
    i = taxon_determination_tag(collection_object.taxon_determinations.order(:position).first)
    return content_tag(:span, i, class: [:feedback, 'feedback-thin', 'feedback-secondary']) if i
    nil
  end

  # TODO: Isolate into own helper
  # TODO: syncronize with class methods
  def dwc_occurrence_table_header_tag
    content_tag(:tr, CollectionObject::DwcExtensions::DWC_OCCURRENCE_MAP.keys.collect{|k| content_tag(:th, k)}.join.html_safe, class: [:error])
  end

  def dwc_occurrence_table_body_tag(collection_objects)
    collection_objects.collect do |c|
      dwc_occurrence_table_row_stub(c).html_safe
    end.join.html_safe
  end

  def dwc_table(collection_objects)
    content_tag(:table) do
      dwc_occurrence_table_header_tag +
        dwc_occurrence_table_body_tag(collection_objects)
    end
  end

  def dwc_occurrence_table_row_stub(collection_object)
    r = collection_object.dwc_occurrence
    if r
      dwc_occurrence_table_row_tag(r)
    else
      id = collection_object.to_param
      content_tag(:tr, nil, id: "dwc_row_stub_#{id}", data: {'collection-object-id': id}, class: 'dwc_row_stub' )
    end
  end

  def dwc_occurrence_table_row_tag(dwc_occurrence)
    o = metamorphosize_if(dwc_occurrence.dwc_occurrence_object)
    content_tag(:tr, class: :contextMenuCells) do
      [CollectionObject::DwcExtensions::DWC_OCCURRENCE_MAP.keys.collect{|k| content_tag(:td, dwc_occurrence.send(k))}.join,
       fancy_show_tag(o),
       fancy_edit_tag(o)
      ].join.html_safe
    end
  end

  # @return [link_to]
  #    this may not work for all identifier types, i.e. those with identifiers like `123.34` or `3434.33X` may not increment correctly
  def collection_object_browse_previous_by_identifier(collection_object)
    return nil if collection_object.nil?
    o = collection_object.previous_by_identifier
    return content_tag(:div, 'None', 'class' => 'navigation-item disable') if o.nil?
    link_text = content_tag(:span, 'Previous by id', 'class' => 'small-icon icon-left', 'data-icon' => 'arrow-left')
    link_to(link_text, browse_collection_objects_task_path(collection_object_id: o.id), data: {
      arrow: :previous,
      'no-turbolinks' => 'true',
      help: 'Sorts by identifier type, namespace, then an conversion of identifier into integer.  Will not work for all identifier types.'},
      class: 'navigation-item')
  end

  # @return [link_to]
  #   this may not work for all identifier types, i.e. those with identifiers like `123.34` or `3434.33X` may not increment correctly
  def collection_object_browse_next_by_identifier(collection_object)
    return nil if collection_object.nil?
    o = collection_object.next_by_identifier
    return content_tag(:div, 'None', 'class' => 'navigation-item disable') if o.nil?
    link_text = content_tag(:span, 'Next by id', 'class' => 'small-icon icon-right', 'data-icon' => 'arrow-right')
    link_to(link_text, browse_collection_objects_task_path(collection_object_id: o.id),
            data: {arrow: :next,
                   'no-turbolinks' => 'false',
                   help: 'Sorts by identifier type, namespace, then an conversion of identifier into integer.  Will not work for all identifier types.'}, class:'navigation-item')
  end

  def collection_object_metadata_badge(collection_object)
    return nil if collection_object.nil?
    o = collection_object

    layout = Waxy::Geometry::Layout.new(
      Waxy::Geometry::Orientation::LAYOUT_POINTY,
      Waxy::Geometry::Point.new(14,14), # size
      Waxy::Geometry::Point.new(14,14), # start
      0 # padding
    )

    s = [
      (o.identifiers.any? ? 1 : 0),
      (o.taxon_determinations.any? ? 1 : 0),
      (o.collecting_event&.map_center.nil? ? 0 : 1),
      (o.collecting_event_id ? 1 : 0),
      (o.buffered_determinations.blank? ? 0 : 1),
      (o.buffered_collecting_event.blank? ? 0 : 1),
    ]

    a = Waxy::Meta.new
    a.size = s
    a.stroke = 'grey'
    a.link_title = "#{o.id} created #{time_ago_in_words(o.created_at)} ago by #{user_tag(o.creator)}"

    c = Waxy::Render::Svg::Canvas.new(28, 28)
    c.body << Waxy::Render::Svg.rectangle(layout, [a], 0)
    c.to_svg
  end

  # @return [GeoJSON feature, nil]
  # @param base [Boolean]
  #   wehther to annotate the feature properties with TW 'base' attributes
  def collection_object_to_geo_json_feature(collection_object, base = true)
    return nil if collection_object.nil?
    if a = collecting_event_to_geo_json_feature(collection_object.collecting_event)
      l = label_for_collection_object(collection_object)
      a['properties']['target'] = {
        'type' => 'CollectionObject',
        'id' => collection_object.id,
        'label' => l
      }
      if base
        a['properties']['base'] =  {
          'type' => 'CollectionObject',
          'id' => collection_object.id,
          'label' => l}
      end
      a
    else
      nil
    end
  end

  # Perhaps a /lib/catalog method
  # @return Hash
  def collection_object_count_by_classification(scope = nil)

    return [] if scope.nil?
    specimen_data = {}
    lot_data = {}

    total_index = {}

    scope.each do |n|
      a = ::Queries::CollectionObject::Filter.new(project_id: sessions_current_project_id, taxon_name_id: n.id, descendants: true,  collection_object_type: 'Specimen').all
      b = ::Queries::CollectionObject::Filter.new(project_id: sessions_current_project_id, taxon_name_id: n.id, descendants: true, collection_object_type: 'Lot').all

      ts = CollectionObject.where(id: a).calculate(:sum, :total)
      tl = CollectionObject.where(id: b).calculate(:sum, :total)

      if (ts > 0) || (tl > 0)
        lot_data[n.cached] = tl
        specimen_data[n.cached] = ts
        total_index[n.cached] = ts + tl
      end

    end

    # We only need to sort 1 pile!
    specimen_data = specimen_data.sort{|a,b| total_index[b[0]] <=> total_index[a[0]] }.to_h

    return {
      total_index:,
      data: [
        { name: 'Specimen', data: specimen_data},
        { name: 'Lot', data: lot_data}
      ]
    }
  end

  # Perhaps a /lib/catalog method
  # @return Hash
  def collection_object_preparation_by_classification(scope = nil)
    return [] if scope.nil?
    data = {}
    no_data = {}

    preparations = ::PreparationType.joins(:collection_objects).where(collection_objects: {project_id: sessions_current_project_id}).distinct

    i = 0
    scope.each do |n|

      j = []

      a = ::Queries::CollectionObject::Filter.new(
        project_id: sessions_current_project_id,
        taxon_name_id:  n.id,
        descendants: true
      )

      # Yes a custom query could do this much faster
      preparations.each do |p|
        a.preparation_type_id = p.id
        t = CollectionObject.where(id: a.all).calculate(:sum, :total)
        if t > 0
          j.push [p.name, t]
          i += 1
        end
      end

      if j.empty?
        # There are no data at all, don't query for Missing
        no_data[n.id] = n.cached
      else
        a.preparation_type_id = nil
        a.preparation_type = false
        w = CollectionObject.where(id: a.all).calculate(:sum, :total)
        if w > 0
          j.push ['Missing', w]
        end

        data[n.cached] = j
      end
    end

    return {
      labels: preparations.collect{|p| p.name} + ['Missing'],
      data:,
      no_data:
    }

  end

  def table_example(collection_objects)
    cols = %i{
      class
      b
      c
    }

    tag.table do
      tag.tr { cols.collect{|h| tag.td(h.to_s) }.join.html_safe } +

      collection_objects.collect{|co|
        tag.tr +
          tag.td( co.dwc_class) +
          tag.td( co.dwc_order) +
          tag.td( co.dwc_family) +
          tag.td( co.dwc_sex)

      }.join.html_safe
    end.html_safe
  end



end