diff --git a/app/classes/api2/project_api.rb b/app/classes/api2/project_api.rb
index 12dcad5cf4..7a0438f232 100644
--- a/app/classes/api2/project_api.rb
+++ b/app/classes/api2/project_api.rb
@@ -43,6 +43,7 @@ def query_params
has_summary: parse(:boolean, :has_summary),
title_has: parse(:string, :title_has, help: 1),
summary_has: parse(:string, :summary_has, help: 1),
+ field_slip_prefix_has: parse(:string, :field_slip_prefix_has, help: 1),
comments_has: parse(:string, :comments_has, help: 1)
}
end
@@ -52,6 +53,7 @@ def create_params
{
title: parse(:string, :title, limit: 100),
summary: parse(:string, :summary, default: ""),
+ field_slip_prefix: parse(:string, :field_slip_prefix, default: ""),
user: @user
}
end
@@ -60,7 +62,8 @@ def update_params
parse_update_params
{
title: parse(:string, :set_title, limit: 100, not_blank: true),
- summary: parse(:string, :set_summary)
+ summary: parse(:string, :set_summary),
+ field_slip_prefix: parse(:string, :set_field_slip_prefix)
}
end
diff --git a/app/classes/query/project_base.rb b/app/classes/query/project_base.rb
index 0a8a73eb4e..294eb87a05 100644
--- a/app/classes/query/project_base.rb
+++ b/app/classes/query/project_base.rb
@@ -17,6 +17,7 @@ def parameter_declarations
has_summary?: :boolean,
title_has?: :string,
summary_has?: :string,
+ field_slip_prefix_has?: :string,
comments_has?: :string,
member?: User
)
@@ -59,6 +60,10 @@ def initialize_search_parameters
"projects.summary",
params[:summary_has]
)
+ add_search_condition(
+ "projects.field_slip_prefix",
+ params[:field_slip_prefix_has]
+ )
add_search_condition(
"CONCAT(comments.summary,COALESCE(comments.comment,''))",
params[:comments_has],
diff --git a/app/classes/query/project_pattern_search.rb b/app/classes/query/project_pattern_search.rb
index aff4d7ce11..b951128798 100644
--- a/app/classes/query/project_pattern_search.rb
+++ b/app/classes/query/project_pattern_search.rb
@@ -15,7 +15,8 @@ def initialize_flavor
def search_fields
"CONCAT(" \
"projects.title," \
- "COALESCE(projects.summary,'')" \
+ "COALESCE(projects.summary,'')," \
+ "COALESCE(projects.field_slip_prefix,'')" \
")"
end
end
diff --git a/app/controllers/field_slips_controller.rb b/app/controllers/field_slips_controller.rb
new file mode 100644
index 0000000000..1c241a9605
--- /dev/null
+++ b/app/controllers/field_slips_controller.rb
@@ -0,0 +1,144 @@
+# frozen_string_literal: true
+
+class FieldSlipsController < ApplicationController
+ before_action :set_field_slip, only: [:edit, :update, :destroy]
+ before_action :login_required, except: [:show]
+
+ # GET /field_slips or /field_slips.json
+ def index
+ @field_slips = FieldSlip.includes(
+ [{ observation: [:location, :name, :namings, :rss_log, :user] },
+ :project, :user]
+ )
+ end
+
+ # GET /field_slips/1 or /field_slips/1.json
+ def show
+ obs = nil
+ if params[:id].match?(/^\d+$/)
+ set_field_slip
+ else
+ @field_slip = FieldSlip.find_by(code: params[:id].upcase)
+ obs = @field_slip&.observation
+ end
+ if @field_slip
+ redirect_to(observation_url(id: obs.id)) if obs
+ else
+ redirect_to(new_field_slip_url(code: params[:id].upcase))
+ end
+ end
+
+ # GET /field_slips/new
+ def new
+ @field_slip = FieldSlip.new
+ @field_slip.code = params[:code].upcase if params.include?(:code)
+ end
+
+ # GET /field_slips/1/edit
+ def edit
+ return unless @field_slip.user != User.current
+
+ redirect_to(field_slip_url(id: @field_slip.id))
+ end
+
+ # POST /field_slips or /field_slips.json
+ def create
+ @field_slip = FieldSlip.new(field_slip_params)
+
+ respond_to do |format|
+ check_project_membership
+ check_for_last_obs
+ if params[:commit] == :field_slip_last_obs.t
+ @field_slip.observation = ObservationView.last(User.current)
+ end
+ if @field_slip.save
+ format.html do
+ if params[:commit] == :field_slip_create_obs.t
+ redirect_to(new_observation_url(field_code: @field_slip.code))
+ else
+ redirect_to(field_slip_url(@field_slip),
+ notice: :field_slip_created.t)
+ end
+ end
+ format.json { render(:show, status: :created, location: @field_slip) }
+ else
+ format.html { render(:new, status: :unprocessable_entity) }
+ format.json do
+ render(json: @field_slip.errors, status: :unprocessable_entity)
+ end
+ end
+ end
+ end
+
+ # PATCH/PUT /field_slips/1 or /field_slips/1.json
+ def update
+ respond_to do |format|
+ check_for_last_obs
+ if @field_slip.update(field_slip_params)
+ format.html do
+ if params[:commit] == :field_slip_create_obs.t
+ redirect_to(new_observation_url(field_code: @field_slip.code))
+ else
+ redirect_to(field_slip_url(@field_slip),
+ notice: :field_slip_updated.t)
+ end
+ end
+ format.json { render(:show, status: :ok, location: @field_slip) }
+ else
+ format.html { render(:edit, status: :unprocessable_entity) }
+ format.json do
+ render(json: @field_slip.errors, status: :unprocessable_entity)
+ end
+ end
+ end
+ end
+
+ # DELETE /field_slips/1 or /field_slips/1.json
+ def destroy
+ if @field_slip.user != User.current
+ redirect_to(field_slip_url(id: @field_slip.id))
+ return
+ end
+
+ @field_slip.destroy!
+
+ respond_to do |format|
+ format.html do
+ redirect_to(field_slips_url,
+ notice: :field_slip_destroyed.t)
+ end
+ format.json { head(:no_content) }
+ end
+ end
+
+ private
+
+ # Use callbacks to share common setup or constraints between actions.
+ def set_field_slip
+ @field_slip = FieldSlip.find(params[:id])
+ end
+
+ # Only allow a list of trusted parameters through.
+ def field_slip_params
+ params.require(:field_slip).permit(:observation_id, :project_id, :code)
+ end
+
+ def check_project_membership
+ project = @field_slip&.project
+ return unless project&.can_join?(User.current)
+
+ project.user_group.users << User.current
+ flash_notice(:field_slip_welcome.t(title: project.title))
+ end
+
+ def check_for_last_obs
+ return unless params[:commit] == :field_slip_last_obs.t
+
+ obs = ObservationView.last(User.current)
+ @field_slip.observation = obs
+ project = @field_slip.project
+ return unless obs && project&.user_can_add_observation?(obs, User.current)
+
+ project.add_observation(obs)
+ end
+end
diff --git a/app/controllers/herbarium_records_controller.rb b/app/controllers/herbarium_records_controller.rb
index 3c48599eac..53026ec630 100644
--- a/app/controllers/herbarium_records_controller.rb
+++ b/app/controllers/herbarium_records_controller.rb
@@ -194,7 +194,9 @@ def default_herbarium_record
end
def default_accession_number
- if @observation.collection_numbers.length == 1
+ if @observation.field_slips.length == 1
+ @observation.field_slips.first.code
+ elsif @observation.collection_numbers.length == 1
@observation.collection_numbers.first.format_name
else
"MO #{@observation.id}"
diff --git a/app/controllers/observations_controller/new_and_create.rb b/app/controllers/observations_controller/new_and_create.rb
index 2bbd133d9e..cb0594b75e 100644
--- a/app/controllers/observations_controller/new_and_create.rb
+++ b/app/controllers/observations_controller/new_and_create.rb
@@ -46,10 +46,12 @@ def new
@reasons = @naming.init_reasons
@images = []
@good_images = []
+ @field_code = params[:field_code]
init_specimen_vars
init_project_vars_for_create
init_list_vars
defaults_from_last_observation_created
+ add_field_slip_project(@field_code)
end
##############################################################################
@@ -83,6 +85,14 @@ def defaults_from_last_observation_created
end
end
+ def add_field_slip_project(code)
+ project = FieldSlip.find_by(code: code)&.project
+ return unless project
+ return unless project&.member?(User.current)
+
+ @project_checks[project.id] = true
+ end
+
##############################################################################
public
@@ -102,20 +112,14 @@ def create
success = false if @name && !@vote.value.nil? && !validate_object(@vote)
success = false if @bad_images != []
success = false if success && !save_observation(@observation)
-
- # Once observation is saved we can save everything else.
- if success
- @observation.log(:log_observation_created)
- # should always succeed
- save_everything_else(params.dig(:naming, :reasons))
- strip_images! if @observation.gps_hidden
- flash_notice(:runtime_observation_success.t(id: @observation.id))
- redirect_to_next_page
-
- # If anything failed reload the form.
- else
- reload_new_form(params.dig(:naming, :reasons))
- end
+ return reload_new_form(params.dig(:naming, :reasons)) unless success
+
+ @observation.log(:log_observation_created)
+ save_everything_else(params.dig(:naming, :reasons))
+ strip_images! if @observation.gps_hidden
+ update_field_slip(@observation, params[:field_code])
+ flash_notice(:runtime_observation_success.t(id: @observation.id))
+ redirect_to_next_page
end
##############################################################################
@@ -309,10 +313,19 @@ def reload_new_form(reasons)
@reasons = @naming.init_reasons(reasons)
@images = @bad_images
@new_image.when = @observation.when
+ @field_code = params[:field_code]
init_specimen_vars_for_reload
init_project_vars_for_create
init_project_vars_for_reload(@observation)
init_list_vars_for_reload(@observation)
render(action: :new, location: new_observation_path(q: get_query_param))
end
+
+ def update_field_slip(observation, field_code)
+ field_slip = FieldSlip.find_by(code: field_code)
+ return unless field_slip
+
+ field_slip.observation = observation
+ field_slip.save
+ end
end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index cf46e90f85..71363e21e1 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -101,6 +101,7 @@ def update
upload_image_if_present
@summary = params[:project][:summary]
+ @field_slip_prefix = params[:project][:field_slip_prefix]
if valid_title && valid_where && valid_dates
if @project.update(project_create_params)
override_fixed_dates
@@ -242,7 +243,7 @@ def show_selected_projects(query, args = {})
def project_create_params
params.require(:project).
- permit(:title, :summary, :open_membership,
+ permit(:title, :summary, :open_membership, :field_slip_prefix,
"start_date(1i)", "start_date(2i)", "start_date(3i)",
"end_date(1i)", "end_date(2i)", "end_date(3i)")
end
diff --git a/app/helpers/field_slips_helper.rb b/app/helpers/field_slips_helper.rb
new file mode 100644
index 0000000000..2663a6b811
--- /dev/null
+++ b/app/helpers/field_slips_helper.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module FieldSlipsHelper
+ def last_observation
+ return unless User.current
+
+ ObservationView.last(User.current)
+ end
+end
diff --git a/app/models/field_slip.rb b/app/models/field_slip.rb
new file mode 100644
index 0000000000..85e56a96c9
--- /dev/null
+++ b/app/models/field_slip.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+class FieldSlip < AbstractModel
+ belongs_to :observation
+ belongs_to :project
+ belongs_to :user
+
+ validates :code, uniqueness: true
+ validates :code, presence: true
+ validate do |field_slip|
+ unless field_slip.code.match?(/[^\d.-]/)
+ errors.add(:code, :format, message: :field_slip_code_format_error.t)
+ end
+ end
+
+ def code=(val)
+ self[:code] = val.upcase
+ return if project
+
+ prefix_match = code.match(/(^.+)[ -]\d+$/)
+ return unless prefix_match
+
+ self.project = Project.find_by(field_slip_prefix: prefix_match[1])
+ end
+
+ def title
+ code
+ end
+
+ def projects
+ @projects ||= find_projects
+ end
+
+ def find_projects
+ result = Project.includes(:project_members).where(
+ project_members: { user: User.current }
+ ).order(:title).pluck(:title, :id)
+ return result unless project && result.exclude?([project.title, project.id])
+
+ result.unshift([project.title, project.id])
+ end
+end
diff --git a/app/models/observation.rb b/app/models/observation.rb
index 4ea81e7c84..d2c2d68c30 100644
--- a/app/models/observation.rb
+++ b/app/models/observation.rb
@@ -199,6 +199,7 @@ class Observation < AbstractModel # rubocop:disable Metrics/ClassLength
has_many :observation_collection_numbers, dependent: :destroy
has_many :collection_numbers, through: :observation_collection_numbers
+ has_many :field_slips, dependent: :destroy
has_many :observation_herbarium_records, dependent: :destroy
has_many :herbarium_records, through: :observation_herbarium_records
@@ -503,6 +504,7 @@ class Observation < AbstractModel # rubocop:disable Metrics/ClassLength
scope :show_includes, lambda {
strict_loading.includes(
:collection_numbers,
+ :field_slips,
{ comments: :user },
{ external_links: { external_site: { project: :user_group } } },
{ herbarium_records: [{ herbarium: :curators }, :user] },
@@ -1076,8 +1078,9 @@ def add_image(img)
def remove_image(img)
if images.include?(img) || thumb_image_id == img.id
images.delete(img)
- update(thumb_image: images.empty? ? nil : images.first) \
- if thumb_image_id == img.id
+ if thumb_image_id == img.id
+ update(thumb_image: images.empty? ? nil : images.first)
+ end
notify_users(:removed_image)
end
img
@@ -1105,6 +1108,7 @@ def turn_off_specimen_if_no_more_records
return unless collection_numbers.empty?
return unless herbarium_records.empty?
return unless sequences.empty?
+ return unless field_slips.empty?
update(specimen: false)
end
diff --git a/app/models/observation_view.rb b/app/models/observation_view.rb
index 0d78793dec..d8ffaba26f 100644
--- a/app/models/observation_view.rb
+++ b/app/models/observation_view.rb
@@ -30,4 +30,9 @@ def self.update_view_stats(obs_id, user_id, reviewed = nil)
create!(args)
end
end
+
+ def self.last(user)
+ view = ObservationView.where(user:).order(last_view: :desc).first
+ view&.observation
+ end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 83645515cd..3afb9c550b 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -10,14 +10,15 @@
#
# == Attributes
#
-# id:: Locally unique numerical id, starting at 1.
-# created_at:: Date/time it was first created.
-# updated_at:: Date/time it was last updated.
-# user:: User that created it.
-# admin_group:: UserGroup of admins.
-# user_group:: UserGroup of members.
-# title:: Title string.
-# summary:: Summary of purpose.
+# id:: Locally unique numerical id, starting at 1.
+# created_at:: Date/time it was first created.
+# updated_at:: Date/time it was last updated.
+# user:: User that created it.
+# admin_group:: UserGroup of admins.
+# user_group:: UserGroup of members.
+# title:: Title string.
+# summary:: Summary of purpose.
+# field_slip_prefix:: Prefix for associated field slip codes
# open_membership Enable users to add themselves, disable shared editing
# location::
# image::
@@ -79,6 +80,7 @@ class Project < AbstractModel # rubocop:disable Metrics/ClassLength
has_many :species_lists, through: :project_species_lists
before_destroy :orphan_drafts
+ validates :field_slip_prefix, uniqueness: true, allow_blank: true
scope :show_includes, lambda {
strict_loading.includes(
@@ -96,6 +98,16 @@ def text_name
title.to_s
end
+ # Ensure that field_slip_prefix is uppercase and at most 60
+ # characters so in the worst case the prefix plus 5 single byte
+ # characters is under 255 bytes (limit in SQL assuming all prefix
+ # characters are 4-byte unicode).
+ def field_slip_prefix=(val)
+ self[:field_slip_prefix] = if val && val.strip != ""
+ val.strip.upcase[0, 60]
+ end
+ end
+
# Same as +text_name+ but with id tacked on to make unique.
def unique_text_name
"#{text_name} (#{id || "?"})"
diff --git a/app/views/controllers/api2/_project.json.jbuilder b/app/views/controllers/api2/_project.json.jbuilder
index aea0882d8c..9206c80476 100644
--- a/app/views/controllers/api2/_project.json.jbuilder
+++ b/app/views/controllers/api2/_project.json.jbuilder
@@ -4,6 +4,7 @@ json.id(object.id)
json.type("project")
json.title(object.title.to_s)
json.summary(object.summary.to_s.tpl_nodiv) if object.summary.present?
+json.title(object.field_slip_prefix.to_s) if object.field_slip_prefix.present?
json.created_at(object.created_at.try(&:utc))
json.updated_at(object.updated_at.try(&:utc))
if detail
diff --git a/app/views/controllers/api2/_project.xml.builder b/app/views/controllers/api2/_project.xml.builder
index 8aa73f69e3..b05f0c623c 100644
--- a/app/views/controllers/api2/_project.xml.builder
+++ b/app/views/controllers/api2/_project.xml.builder
@@ -8,6 +8,7 @@ xml.tag!(
) do
xml_string(xml, :title, object.title)
xml_html_string(xml, :summary, object.summary.to_s.tpl_nodiv)
+ xml_string(xml, :field_slip_prefix, object.field_slip_prefix)
xml_datetime(xml, :created_at, object.created_at)
xml_datetime(xml, :updated_at, object.updated_at)
if detail
diff --git a/app/views/controllers/field_slips/_field_slip.html.erb b/app/views/controllers/field_slips/_field_slip.html.erb
new file mode 100644
index 0000000000..1b38068074
--- /dev/null
+++ b/app/views/controllers/field_slips/_field_slip.html.erb
@@ -0,0 +1,26 @@
+
+
+ <%= :PROJECT.t %>:
+ <% if field_slip.project %>
+ <%= link_to_object(field_slip.project) %>
+ <% else %>
+ <%= :field_slip_no_project.t %>
+ <% end %>
+
+ <% if field_slip.user %>
+ <%= :field_slip_creator.t %>:
+ <%= link_to(field_slip.user.legal_name, user_path(field_slip.user_id)) %>
+ <% end %>
+ <%= :OBSERVATION.t %>:
+ <% if field_slip.observation %>
+ <%= link_to(field_slip.observation.unique_format_name.t, observation_path(field_slip.observation)) %>
+
+ <%= render(partial: "shared/matrix_box",
+ locals: { object: field_slip.observation,
+ columns: "col-xs-12" }) %>
+
+ <% else %>
+ <%= :field_slip_no_observation.t %>
+ <% end %>
+
+
diff --git a/app/views/controllers/field_slips/_field_slip.json.jbuilder b/app/views/controllers/field_slips/_field_slip.json.jbuilder
new file mode 100644
index 0000000000..37c5521152
--- /dev/null
+++ b/app/views/controllers/field_slips/_field_slip.json.jbuilder
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+json.extract!(field_slip, :id, :observation_id, :project_id, :code,
+ :created_at, :updated_at)
+json.url(field_slip_url(field_slip, format: :json))
diff --git a/app/views/controllers/field_slips/_form.html.erb b/app/views/controllers/field_slips/_form.html.erb
new file mode 100644
index 0000000000..59bc645288
--- /dev/null
+++ b/app/views/controllers/field_slips/_form.html.erb
@@ -0,0 +1,48 @@
+<%
+action = controller.action_name
+%>
+
+<%= form_with(model: field_slip) do |form| %>
+ <% if field_slip.errors.any? %>
+
+ <%= "#{pluralize(field_slip.errors.count,
+ :error.t, plural: :errors.t)} #{:field_slip_errors.t}" %>:
+
+ <% field_slip.errors.each do |error| %>
+ - <%= error.full_message %>
+ <% end %>
+
+
+ <% end %>
+
+ <%= text_field_with_label(form: form, field: :code) %>
+
+ <% if @field_slip.projects %>
+ <%= select_with_label(form: form, field: :project_id,
+ options: @field_slip.projects) %>
+ <% end %>
+
+ <%= submit_button(form: form, button: :field_slip_create_obs.t,
+ class: "mt-5") if action == "new" %>
+
+
+ <% if field_slip.observation %>
+
+ <%= render(partial: "field_slips/obs_thumbnail",
+ locals: { obs: field_slip.observation, form: form,
+ button: :field_slip_keep_obs.t }) %>
+
+ <% end %>
+ <% if last_observation %>
+
+ <%= render(partial: "field_slips/obs_thumbnail",
+ locals: { obs: last_observation, form: form,
+ button: :field_slip_last_obs.t }) %>
+
+ <% end %>
+
+
+ <%= submit_button(form: form, button: :field_slip_create_obs.t,
+ class: "my-5") if action == "edit" %>
+
+<% end %>
diff --git a/app/views/controllers/field_slips/_obs_thumbnail.erb b/app/views/controllers/field_slips/_obs_thumbnail.erb
new file mode 100644
index 0000000000..92683315e8
--- /dev/null
+++ b/app/views/controllers/field_slips/_obs_thumbnail.erb
@@ -0,0 +1,12 @@
+ <%= tag.div(class: "panel panel-default") do
+ concat(panel_block_heading(
+ heading: submit_button(form: form, button: button)
+ ))
+ concat(tag.div(class: "thumbnail-container") do
+ interactive_image(obs.thumb_image_id, votes: false,
+ image_link: observation_path(obs))
+ end) if obs.thumb_image_id.present?
+ concat(tag.div(class: "panel-body") do
+ link_to(obs.unique_format_name.t, observation_path(obs))
+ end)
+ end %>
diff --git a/app/views/controllers/field_slips/edit.html.erb b/app/views/controllers/field_slips/edit.html.erb
new file mode 100644
index 0000000000..06277d96bc
--- /dev/null
+++ b/app/views/controllers/field_slips/edit.html.erb
@@ -0,0 +1,10 @@
+<% add_page_title(:field_slip_editing.t) %>
+
+<%= render "form", field_slip: @field_slip %>
+
+
+
+
+ <%= link_to :field_slip_show.t, @field_slip %> |
+ <%= link_to :field_slip_index.t, field_slips_path %>
+
diff --git a/app/views/controllers/field_slips/index.html.erb b/app/views/controllers/field_slips/index.html.erb
new file mode 100644
index 0000000000..7d5d005e47
--- /dev/null
+++ b/app/views/controllers/field_slips/index.html.erb
@@ -0,0 +1,17 @@
+<% add_page_title(:FIELD_SLIPS.t) %>
+
+<% if notice %>
+ <%= notice %>
+<% end %>
+
+
+ <% @field_slips.each do |field_slip| %>
+ <%= :field_slip_code.t %>:
+ <%= field_slip.code %>
+ <%= render field_slip %>
+ <%= link_to :field_slip_show.t, field_slip %>
+
+ <% end %>
+
+
+<%= link_to :field_slip_new.t, new_field_slip_path %>
diff --git a/app/views/controllers/field_slips/index.json.jbuilder b/app/views/controllers/field_slips/index.json.jbuilder
new file mode 100644
index 0000000000..374336fa00
--- /dev/null
+++ b/app/views/controllers/field_slips/index.json.jbuilder
@@ -0,0 +1,3 @@
+# frozen_string_literal: true
+
+json.array!(@field_slips, partial: "field_slips/field_slip", as: :field_slip)
diff --git a/app/views/controllers/field_slips/new.html.erb b/app/views/controllers/field_slips/new.html.erb
new file mode 100644
index 0000000000..8bffb043e0
--- /dev/null
+++ b/app/views/controllers/field_slips/new.html.erb
@@ -0,0 +1,9 @@
+<% add_page_title(:field_slip_new.t) %>
+
+<%= render "form", field_slip: @field_slip %>
+
+
+
+
+ <%= link_to :field_slip_index.t, field_slips_path %>
+
diff --git a/app/views/controllers/field_slips/show.html.erb b/app/views/controllers/field_slips/show.html.erb
new file mode 100644
index 0000000000..217f358a5e
--- /dev/null
+++ b/app/views/controllers/field_slips/show.html.erb
@@ -0,0 +1,17 @@
+<% add_page_title(@field_slip.code) %>
+
+<% if notice %>
+ <%= notice %>
+<% end %>
+
+<%= "#{:FIELD_SLIP.t}: #{@field_slip.code}" %>
+
+<%= render @field_slip %>
+
+
+ <%= link_to :field_slip_index.t, field_slips_path %>
+ <% if @field_slip.user == User.current %>
+ | <%= link_to :field_slip_edit.t, edit_field_slip_path(@field_slip) %>
+ | <%= button_to :field_slip_destroy.t, @field_slip, method: :delete %>
+ <% end %>
+
diff --git a/app/views/controllers/field_slips/show.json.jbuilder b/app/views/controllers/field_slips/show.json.jbuilder
new file mode 100644
index 0000000000..1fb4d693be
--- /dev/null
+++ b/app/views/controllers/field_slips/show.json.jbuilder
@@ -0,0 +1,3 @@
+# frozen_string_literal: true
+
+json.partial!("field_slips/field_slip", field_slip: @field_slip)
diff --git a/app/views/controllers/observations/_form.html.erb b/app/views/controllers/observations/_form.html.erb
index 9c6586f20e..10654dc267 100644
--- a/app/views/controllers/observations/_form.html.erb
+++ b/app/views/controllers/observations/_form.html.erb
@@ -31,6 +31,8 @@ image_upload_localization = {
}
) do |f| %>
+ <%= hidden_field_tag(:field_code, @field_code) %>
+
<%= submit_button(form: f, button: button_name, center: true) %>
<%= render(partial: "observations/form/when", locals: { f: f }) %>
diff --git a/app/views/controllers/observations/show/_observation_details.erb b/app/views/controllers/observations/show/_observation_details.erb
index e78db2ae9b..b2ebdf8d39 100644
--- a/app/views/controllers/observations/show/_observation_details.erb
+++ b/app/views/controllers/observations/show/_observation_details.erb
@@ -16,6 +16,20 @@
end
end %>
+ <% if obs.field_slips.present? %>
+ <%= tag.div(class: "obs-field-slips", id: "observation_field_slips") do
+ if obs.field_slips.count == 1
+ concat(tag.span("#{:FIELD_SLIP.t}: "))
+ concat(link_to_object(obs.field_slips[0]))
+ else
+ concat([tag.span("#{:FIELD_SLIPS.t}:"), tag.br].safe_join)
+ obs.field_slips.each do |field_slip|
+ concat(tag.div(link_to_object(field_slip), class: "indent"))
+ end
+ end
+ end %>
+ <% end %>
+
<%= tag.p(class: "obs-specimen", id: "observation_specimen_available") do
if obs.specimen
:show_observation_specimen_available.t
diff --git a/app/views/controllers/projects/_form.html.erb b/app/views/controllers/projects/_form.html.erb
index 11e86bd301..f881c2fef4 100644
--- a/app/views/controllers/projects/_form.html.erb
+++ b/app/views/controllers/projects/_form.html.erb
@@ -12,6 +12,9 @@
<%= text_area_with_label(form: f, field: :summary, rows: 5,
label: :SUMMARY.t + ":") %>
+ <%= text_field_with_label(form: f, field: :field_slip_prefix,
+ label: :FIELD_SLIP_PREFIX.t + ":") %>
+
<%= render(partial: "shared/textilize_help") %>
<%= autocompleter_field(form: f, field: :place_name, label: :WHERE.t + ":",
diff --git a/app/views/controllers/projects/show.html.erb b/app/views/controllers/projects/show.html.erb
index 0ec5e0457f..239e5ac510 100644
--- a/app/views/controllers/projects/show.html.erb
+++ b/app/views/controllers/projects/show.html.erb
@@ -36,6 +36,7 @@ violations_button_class =
<% end %>
<%= :show_project_created_at.t %>: <%= @project.created_at.web_date %>
+ <%= :show_project_field_slip_prefix.t %>: <%= @project.field_slip_prefix %>
<% if @project.observations.any? %>
<%= link_to("#{@project.observations.length} #{:OBSERVATIONS.l}",
diff --git a/app/views/controllers/shared/_matrix_box.erb b/app/views/controllers/shared/_matrix_box.erb
index ba3b65c23e..2559af3fdb 100644
--- a/app/views/controllers/shared/_matrix_box.erb
+++ b/app/views/controllers/shared/_matrix_box.erb
@@ -18,19 +18,28 @@ if presenter
else
image_args = {}
end
+ header_components = passed_args[:header].presence || []
+ footer_components =
+ if passed_args[:footer] == false
+ []
+ else
+ passed_args[:footer].presence ||
+ [matrix_box_log_footer(presenter),
+ matrix_box_identify_footer(identify, presenter.id)]
+ end
%>
<%= matrix_box(columns: columns, id: object_id) do
tag.div(class: "panel panel-default") do
[
+ *header_components,
tag.div(class: "panel-sizing") do
[
matrix_box_image(image, **image_args),
matrix_box_details(presenter, object_id, identify),
].safe_join
end,
- matrix_box_log_footer(presenter),
- matrix_box_identify_footer(identify, presenter.id)
+ *footer_components
].safe_join
end
end %>
diff --git a/app/views/controllers/visual_models/_form.html.erb b/app/views/controllers/visual_models/_form.html.erb
index c3d0884057..5caf2e75f2 100644
--- a/app/views/controllers/visual_models/_form.html.erb
+++ b/app/views/controllers/visual_models/_form.html.erb
@@ -1,8 +1,8 @@
<%= form_with(model: visual_model) do |f| %>
<% if visual_model.errors.any? %>
-
<%= pluralize(visual_model.errors.count, "error") %> prohibited this visual_model from being saved:
-
+ <%= "#{pluralize(visual_model.errors.count,
+ :error.t, plural: :errors.t)} #{:visual_model_errors.t}" %>:
<% visual_model.errors.each do |error| %>
- <%= error.full_message %>
diff --git a/config/locales/en.txt b/config/locales/en.txt
index b8208fea50..6756b855bd 100644
--- a/config/locales/en.txt
+++ b/config/locales/en.txt
@@ -255,6 +255,8 @@
draft: draft
DRAFTS: Drafts
drafts: drafts
+ error: error
+ errors: errors
EXTERNAL_LINK: External Link
external_link: external link
EXTERNAL_LINKS: External Links
@@ -263,6 +265,10 @@
external_site: external site
EXTERNAL_SITES: External Sites
external_sites: external sites
+ FIELD_SLIP: Field Slip
+ field_slip: field slip
+ FIELD_SLIPS: Field Slips
+ field_slips: field slips
IMAGE: Image
image: image
IMAGES: Images
@@ -381,6 +387,7 @@
species_list: species list
SPECIES_LISTS: Species Lists
species_lists: species lists
+ SpeciesList: Species List
SPECIMEN: Specimen
specimen: specimen
SPECIMENS: Specimens
@@ -494,6 +501,8 @@
email_address: email address
EMAIL_ADDRESSS: Email Addresses
email_addresss: email addresses
+ FIELD_SLIP_PREFIX: Field Slip Prefix
+ field_slip_prefix: field slip prefix
FULL_NAME: Full Name
full_name: full name
FULL_NAMES: Full Names
@@ -2745,9 +2754,33 @@
show_visual_model_edit: "[:EDIT] [:VISUAL_MODEL]"
show_visual_model_index: "[:VISUAL_MODEL] [:INDEX]"
+ # Visual Models
+ visual_model_errors: prohibited this visual_model from being saved
+
# visual_models/destroy
destroy_visual_model_success: "[:VISUAL_MODEL] [:destroyed]"
+ # Field Slips
+ field_slip_code: Code
+ field_slip_code_format_error: code must have non-numeric characters other than period (.) and dash (-)
+ field_slip_created: Field slip was successfully created.
+ field_slip_create_obs: Create New Observation
+ field_slip_creator: Creator
+ field_slip_destroy: Destroy this field slip
+ field_slip_destroyed: Field slip was successfully destroyed.
+ field_slip_edit: Edit this field slip
+ field_slip_editing: Editing Field Slip
+ field_slip_errors: prohibited this field_slip from being saved
+ field_slip_index: Back to field slips
+ field_slip_keep_obs: Keep Current
+ field_slip_last_obs: Use Last Observation
+ field_slip_new: Record Field Slip
+ field_slip_no_observation: Observation not found
+ field_slip_no_project: Project not found
+ field_slip_show: Show this field slip
+ field_slip_updated: Field slip was successfully updated.
+ field_slip_welcome: "Welcome to the [TITLE] project!"
+
##############################################################################
# LESS POPULAR PAGES
@@ -3244,6 +3277,7 @@
show_project_destroy: Destroy Project
show_project_drafts: Drafts
show_project_edit: Edit Project
+ show_project_field_slip_prefix: Field Slip Prefix
show_project_join: Join Project
show_project_location: Location
show_project_leave: Leave Project
@@ -4113,6 +4147,7 @@
api_help_create_key: if you pass in your app name here it will create an api key for the user for your app to use
api_help_creator: creator
api_help_east: max longitude
+ api_help_field_slip_prefix_has: search within field slip prefix
api_help_first_user: creator / first to use
api_help_has_notes_field: is given observation notes template field filled in?
api_help_has_obs_notes: observation has notes?
diff --git a/config/routes.rb b/config/routes.rb
index 38083615eb..951fc8a060 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -23,6 +23,7 @@
comments: {},
external_links: {},
external_sites: {},
+ field_slips: {},
herbaria: {},
herbarium_records: {},
images: {},
@@ -40,6 +41,7 @@
comments: {},
external_links: {},
external_sites: {},
+ field_slips: {},
herbaria: {},
herbarium_records: {},
images: {},
@@ -381,6 +383,10 @@ def route_actions_hash
get("glossary_terms/:id/versions", to: "glossary_terms/versions#show",
as: "glossary_term_versions")
+ # ----- Field Slip Records: standard actions --------------------------------
+ resources :field_slips
+ get("qr/:id", to: "field_slips#show", id: /.*[^\d.-].*/)
+
# ----- Herbaria: standard actions -------------------------------------------
namespace :herbaria do
resources :curator_requests, only: [:new, :create]
diff --git a/db/migrate/20240310132813_create_field_slips.rb b/db/migrate/20240310132813_create_field_slips.rb
new file mode 100644
index 0000000000..3ddfa757f0
--- /dev/null
+++ b/db/migrate/20240310132813_create_field_slips.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+class CreateFieldSlips < ActiveRecord::Migration[7.1]
+ def change
+ create_table(:field_slips) do |t|
+ t.integer(:observation_id)
+ t.integer(:project_id)
+ t.string(:code, null: false)
+
+ t.timestamps
+ end
+ add_index(:field_slips, :code, unique: true)
+ end
+end
diff --git a/db/migrate/20240312231729_add_field_slip_prefix_to_projects.rb b/db/migrate/20240312231729_add_field_slip_prefix_to_projects.rb
new file mode 100644
index 0000000000..d82c3d1a65
--- /dev/null
+++ b/db/migrate/20240312231729_add_field_slip_prefix_to_projects.rb
@@ -0,0 +1,6 @@
+class AddFieldSlipPrefixToProjects < ActiveRecord::Migration[7.1]
+ def change
+ add_column :projects, :field_slip_prefix, :string
+ add_index(:projects, :field_slip_prefix, unique: true)
+ end
+end
diff --git a/db/migrate/20240322232703_add_user_id_to_field_slips.rb b/db/migrate/20240322232703_add_user_id_to_field_slips.rb
new file mode 100644
index 0000000000..37aa2209b4
--- /dev/null
+++ b/db/migrate/20240322232703_add_user_id_to_field_slips.rb
@@ -0,0 +1,5 @@
+class AddUserIdToFieldSlips < ActiveRecord::Migration[7.1]
+ def change
+ add_column :field_slips, :user_id, :integer
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 9968dd4dfe..1417ac8df2 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.1].define(version: 2024_03_09_072017) do
+ActiveRecord::Schema[7.1].define(version: 2024_03_22_232703) do
create_table "api_keys", id: :integer, charset: "utf8mb3", force: :cascade do |t|
t.datetime "created_at", precision: nil
t.datetime "last_used", precision: nil
@@ -85,6 +85,16 @@
t.integer "project_id"
end
+ create_table "field_slips", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+ t.integer "observation_id"
+ t.integer "project_id"
+ t.string "code", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.integer "user_id"
+ t.index ["code"], name: "index_field_slips_on_code", unique: true
+ end
+
create_table "glossary_term_images", charset: "utf8mb3", force: :cascade do |t|
t.integer "image_id"
t.integer "glossary_term_id"
@@ -528,6 +538,8 @@
t.integer "image_id"
t.date "start_date"
t.date "end_date"
+ t.string "field_slip_prefix"
+ t.index ["field_slip_prefix"], name: "index_projects_on_field_slip_prefix", unique: true
end
create_table "publications", id: :integer, charset: "utf8mb3", force: :cascade do |t|
diff --git a/test/controllers/field_slips_controller_test.rb b/test/controllers/field_slips_controller_test.rb
new file mode 100644
index 0000000000..bbf93ed9c6
--- /dev/null
+++ b/test/controllers/field_slips_controller_test.rb
@@ -0,0 +1,234 @@
+# frozen_string_literal: true
+
+require "test_helper"
+
+class FieldSlipsControllerTest < FunctionalTestCase
+ setup do
+ @field_slip = field_slips(:field_slip_one)
+ end
+
+ test "should get index" do
+ requires_login(:index)
+ assert_response :success
+ end
+
+ test "should get new" do
+ requires_login(:new)
+ assert_response :success
+ end
+
+ test "should get new with unknown code" do
+ login
+ project = projects(:bolete_project)
+ code = "#{project.field_slip_prefix}-1234"
+ get(:new, params: { code: code })
+ assert_response :success
+ assert(response.body.include?(project.title))
+ end
+
+ test "should create field_slip with last viewed obs" do
+ login(@field_slip.user.login)
+ ObservationView.update_view_stats(@field_slip.observation_id,
+ @field_slip.user_id)
+ assert_difference("FieldSlip.count") do
+ post(:create,
+ params: {
+ commit: :field_slip_last_obs.t,
+ field_slip: {
+ code: "Y#{@field_slip.code}",
+ project: projects(:eol_project)
+ }
+ })
+ end
+
+ assert_redirected_to field_slip_url(FieldSlip.last)
+ assert_equal(FieldSlip.last.observation, ObservationView.last(User.current))
+ end
+
+ test "should create field_slip and join project" do
+ user = @field_slip.user
+ login(user.login)
+ project = projects(:open_membership_project)
+ assert_not(project.member?(user))
+ ObservationView.update_view_stats(@field_slip.observation_id,
+ @field_slip.user_id)
+ assert_difference("FieldSlip.count") do
+ post(:create,
+ params: {
+ commit: :field_slip_last_obs.t,
+ field_slip: {
+ code: "#{project.field_slip_prefix}-0001",
+ project: project
+ }
+ })
+ end
+
+ assert_redirected_to field_slip_url(FieldSlip.last)
+ assert(project.member?(user))
+ end
+
+ test "should create field_slip and redirect to create obs" do
+ login(@field_slip.user.login)
+ assert_difference("FieldSlip.count") do
+ post(:create,
+ params: {
+ commit: :field_slip_create_obs.t,
+ field_slip: {
+ code: "Z#{@field_slip.code}",
+ project: projects(:eol_project)
+ }
+ })
+ end
+ assert_redirected_to new_observation_url(field_code: FieldSlip.last.code)
+ end
+
+ test "should create field_slip in project from code" do
+ login
+ project = projects(:eol_project)
+ code = "#{project.field_slip_prefix}-1234"
+ assert_difference("FieldSlip.count") do
+ post(:create,
+ params: {
+ field_slip: {
+ code: code
+ }
+ })
+ end
+ field_slip = FieldSlip.find_by(code: code)
+ assert_equal(field_slip.project, project)
+ end
+
+ test "should fail to create field_slip" do
+ login
+ post(:create,
+ params: {
+ field_slip: {
+ code: @field_slip.code.to_s,
+ observation: observations(:coprinus_comatus_obs),
+ project: projects(:eol_project)
+ }
+ })
+ assert_response 422
+ end
+
+ test "json should fail to create field_slip" do
+ login
+ post(:create,
+ format: :json,
+ params: {
+ field_slip: {
+ code: @field_slip.code.to_s,
+ observation: observations(:coprinus_comatus_obs),
+ project: projects(:eol_project)
+ }
+ })
+ assert_response 422
+ end
+
+ test "should show field_slip" do
+ get(:show, params: { id: @field_slip.id })
+ assert_response :success
+ end
+
+ test "should show field_slip by code" do
+ get(:show, params: { id: @field_slip.code })
+ assert_redirected_to observation_url(@field_slip.observation)
+ end
+
+ test "should redirect to get new" do
+ login
+ project = projects(:bolete_project)
+ code = "#{project.field_slip_prefix}-1235"
+ get(:show, params: { id: code })
+ assert_redirected_to new_field_slip_url(code: code)
+ end
+
+ test "should get edit" do
+ login(@field_slip.user.login)
+ get(:edit, params: { id: @field_slip.id })
+ assert_response :success
+ end
+
+ test "should get not edit" do
+ login
+ get(:edit, params: { id: @field_slip.id })
+ assert_redirected_to field_slip_url(@field_slip)
+ end
+
+ test "should update field_slip" do
+ login
+ initial = @field_slip.observation_id
+ patch(:update,
+ params: { id: @field_slip.id,
+ commit: :field_slip_keep_obs.t,
+ field_slip: { code: @field_slip.code,
+ observation_id: @field_slip.observation_id,
+ project_id: @field_slip.project_id } })
+ assert_redirected_to field_slip_url(@field_slip)
+ assert_equal(@field_slip.observation_id, initial)
+ end
+
+ test "should update field_slip with last viewed obs" do
+ login(@field_slip.user.login)
+ obs = observations(:minimal_unknown_obs)
+ ObservationView.update_view_stats(obs.id, @field_slip.user_id)
+ patch(:update,
+ params: { id: @field_slip.id,
+ commit: :field_slip_last_obs.t,
+ field_slip: { code: @field_slip.code,
+ project_id: @field_slip.project_id } })
+ assert_redirected_to field_slip_url(@field_slip)
+ assert_equal(@field_slip.observation, ObservationView.last(User.current))
+ assert(@field_slip.project.observations.include?(obs))
+ end
+
+ test "should update field_slip and redirect to create obs" do
+ login
+ patch(:update,
+ params: { id: @field_slip.id,
+ commit: :field_slip_create_obs.t,
+ field_slip: { code: @field_slip.code,
+ observation_id: @field_slip.observation_id,
+ project_id: @field_slip.project_id } })
+ assert_redirected_to new_observation_url(field_code: @field_slip.code)
+ end
+
+ test "should fail to update field_slip" do
+ login
+ patch(:update,
+ params: { id: @field_slip.id,
+ field_slip: { code: "-3.14",
+ observation_id: @field_slip.observation_id,
+ project_id: @field_slip.project_id } })
+ assert_response 422
+ end
+
+ test "json should fail to update field_slip" do
+ login
+ patch(:update,
+ format: :json,
+ params: { id: @field_slip.id,
+ field_slip: { code: "-3.14",
+ observation_id: @field_slip.observation_id,
+ project_id: @field_slip.project_id } })
+ assert_response 422
+ end
+
+ test "should destroy field_slip" do
+ login(@field_slip.user.login)
+ assert_difference("FieldSlip.count", -1) do
+ delete(:destroy, params: { id: @field_slip.id })
+ end
+
+ assert_redirected_to field_slips_url
+ end
+
+ test "should not destroy field_slip" do
+ login
+ assert_difference("FieldSlip.count", 0) do
+ delete(:destroy, params: { id: @field_slip.id })
+ end
+
+ assert_redirected_to field_slip_url(@field_slip)
+ end
+end
diff --git a/test/controllers/herbarium_records_controller_test.rb b/test/controllers/herbarium_records_controller_test.rb
index 6b48ba37bb..581af3f8ff 100644
--- a/test/controllers/herbarium_records_controller_test.rb
+++ b/test/controllers/herbarium_records_controller_test.rb
@@ -164,7 +164,7 @@ def test_next_and_prev_herbarium_record
end
def test_new_herbarium_record
- obs_id = observations(:coprinus_comatus_obs).id
+ obs_id = observations(:unknown_with_no_naming).id
get(:new, params: { observation_id: obs_id })
assert_response(:redirect)
@@ -172,7 +172,35 @@ def test_new_herbarium_record
get(:new, params: { observation_id: obs_id })
assert_template("new")
assert_template("shared/_matrix_box")
+ assert_equal(assigns(:herbarium_record).accession_number, "MO #{obs_id}")
+ end
+
+ def test_new_herbarium_record_with_collection_number
+ obs = observations(:coprinus_comatus_obs)
+ get(:new, params: { observation_id: obs.id })
+ assert_response(:redirect)
+
+ login("rolf")
+ get(:new, params: { observation_id: obs.id })
+ assert_template("new")
+ assert_template("shared/_matrix_box")
+ assert(assigns(:herbarium_record))
+ assert_equal(assigns(:herbarium_record).accession_number,
+ obs.collection_numbers.first.format_name)
+ end
+
+ def test_new_herbarium_record_with_field_slip
+ obs = observations(:owner_accepts_general_questions)
+ get(:new, params: { observation_id: obs.id })
+ assert_response(:redirect)
+
+ login("rolf")
+ get(:new, params: { observation_id: obs.id })
+ assert_template("new")
+ assert_template("shared/_matrix_box")
assert(assigns(:herbarium_record))
+ assert_equal(assigns(:herbarium_record).accession_number,
+ obs.field_slips.first.code)
end
def test_create_herbarium_record
diff --git a/test/controllers/observations_controller_test.rb b/test/controllers/observations_controller_test.rb
index 10eaf72d3f..be85f2d1e5 100644
--- a/test/controllers/observations_controller_test.rb
+++ b/test/controllers/observations_controller_test.rb
@@ -1478,6 +1478,18 @@ def test_construct_observation_approved_place_name
assert_equal("mo_website", obs.source)
end
+ def test_create_observation_with_field_slip
+ generic_construct_observation(
+ { observation: { specimen: "1" },
+ field_code: field_slips(:field_slip_no_obs).code,
+ naming: { name: "Coprinus comatus" } },
+ 1, 1, 0
+ )
+ obs = assigns(:observation)
+ assert(obs.specimen)
+ assert(obs.field_slips.count == 1)
+ end
+
def test_create_observation_with_collection_number
generic_construct_observation(
{ observation: { specimen: "1" },
@@ -2929,6 +2941,14 @@ def test_inital_project_checkboxes
)
end
+ def test_field_slip_project_checkbox
+ login("katrina")
+ slip = field_slips(:field_slip_no_obs)
+ get(:new, params: { field_code: slip.code })
+
+ assert_project_checks(slip.project.id => :checked)
+ end
+
def test_project_checkboxes_in_create_observation
init_for_project_checkbox_tests
diff --git a/test/controllers/projects_controller_test.rb b/test/controllers/projects_controller_test.rb
index 5ecca3237a..4babb1ae94 100644
--- a/test/controllers/projects_controller_test.rb
+++ b/test/controllers/projects_controller_test.rb
@@ -11,6 +11,7 @@ def build_params(
project: {
title: title,
summary: summary,
+ field_slip_prefix: "",
place_name: "",
open_membership: false,
"start_date(1i)" => start_date&.year,
diff --git a/test/fixtures/field_slips.yml b/test/fixtures/field_slips.yml
new file mode 100644
index 0000000000..ad8d396b19
--- /dev/null
+++ b/test/fixtures/field_slips.yml
@@ -0,0 +1,23 @@
+# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
+
+field_slip_one:
+ observation: minimal_unknown_obs
+ project: eol_project
+ code: EOL-0001
+ user: mary
+
+field_slip_two:
+ observation: owner_accepts_general_questions
+ project: eol_project
+ code: EOL-0002
+ user: mary
+
+field_slip_no_obs:
+ project: eol_project
+ code: EOL-0003
+ user: mary
+
+field_slip_falmouth_one:
+ project: falmouth_2023_09_project
+ code: FAL-0001
+ user: rolf
diff --git a/test/fixtures/projects.yml b/test/fixtures/projects.yml
index ea7bb0ab94..d30617d7a6 100644
--- a/test/fixtures/projects.yml
+++ b/test/fixtures/projects.yml
@@ -21,6 +21,7 @@ eol_project:
admin_group: eol_admins
title: EOL Project
summary: Project to make names for EOL
+ field_slip_prefix: EOL
created_at: 2008-09-24 21:33:30
updated_at: 2008-09-24 21:33:30
open_membership: false
@@ -39,6 +40,7 @@ bolete_project:
admin_group: bolete_admins
title: Bolete Project
summary: Project about Boletes
+ field_slip_prefix: BLT
created_at: 2008-09-25 07:00:50
updated_at: 2008-09-25 07:00:55
images: in_situ_image, turned_over_image
@@ -73,6 +75,7 @@ open_membership_project:
admin_group: burbank_admins
title: Burbank Project
summary: Project about Fungi of Burbank
+ field_slip_prefix: BURB
created_at: 2008-09-25 07:00:50
updated_at: 2008-09-25 07:00:55
observations: owner_accepts_general_questions, unknown_with_lat_long, trusted_hidden, untrusted_hidden, reused_observation
@@ -229,6 +232,7 @@ falmouth_2023_09_project:
user: dick
admin_group: dick_only
user_group: falmouth_2023_09_users
+ field_slip_prefix: FAL
start_date: 2023-09-01
end_date: 2023-09-30
location: falmouth
diff --git a/test/integration/capybara/field_slips_integration_test.rb b/test/integration/capybara/field_slips_integration_test.rb
new file mode 100644
index 0000000000..62932c2690
--- /dev/null
+++ b/test/integration/capybara/field_slips_integration_test.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require("test_helper")
+
+# Test relating to projects
+class FieldSlipsIntegrationTest < CapybaraIntegrationTestCase
+ def test_new_observation_violates_project_constraints
+ project = projects(:falmouth_2023_09_project)
+ user = users(:roy)
+ assert(project.member?(user),
+ "Test needs user who is member of #{project.title} Project")
+
+ login(user)
+ visit("/qr/NFAL-0001")
+ click_on(:field_slip_create_obs.l)
+
+ fill_in(:WHERE.l, with: locations(:albion).name)
+ assert_no_difference(
+ "Observation.count",
+ "Observation shouldn't be created before confirming constraint violation"
+ ) do
+ first(:button, "Create").click
+ end
+ end
+end
diff --git a/test/models/field_slip_test.rb b/test/models/field_slip_test.rb
new file mode 100644
index 0000000000..2e931cbf29
--- /dev/null
+++ b/test/models/field_slip_test.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require "test_helper"
+
+class FieldSlipTest < ActiveSupport::TestCase
+ # test "the truth" do
+ # assert true
+ # end
+end
diff --git a/test/system/field_slips_test.rb b/test/system/field_slips_test.rb
new file mode 100644
index 0000000000..c5dcb78c8a
--- /dev/null
+++ b/test/system/field_slips_test.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require "application_system_test_case"
+
+class FieldSlipsTest < ApplicationSystemTestCase
+ setup do
+ @field_slip = field_slips(:field_slip_one)
+ end
+
+ test "visiting the index" do
+ login!(mary)
+ visit field_slips_url
+ assert_selector "h1", text: :FIELD_SLIPS.t
+ end
+
+ test "navigate to should field slip" do
+ login!(mary)
+ visit field_slips_url
+ find(:xpath, '//*[@id="field_slips"]/a[1]').click
+ assert_text :field_slip_index.t
+ end
+
+ test "should update field slip" do
+ login!(mary)
+ visit field_slip_url(@field_slip)
+ click_on :field_slip_edit.t, match: :first
+
+ fill_in :field_slip_code.t, with: @field_slip.code
+ select(@field_slip.project.title, from: :PROJECT.t)
+ click_on :field_slip_keep_obs.t
+
+ assert_text :field_slip_updated.t
+ end
+
+ test "should destroy field slip" do
+ login!(mary)
+ visit field_slip_url(@field_slip)
+ click_on :field_slip_destroy.t, match: :first
+
+ assert_text :field_slip_destroyed.t
+ end
+end