• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pulibrary / orangelight / d973573e-9a44-478e-9952-901d9a0e6df1

10 Nov 2025 09:45PM UTC coverage: 95.247% (-0.2%) from 95.41%
d973573e-9a44-478e-9952-901d9a0e6df1

push

circleci

web-flow
Annex requests with no items should email forranx@princeton.edu (#5339)

* Add new confirmation and email templates when
there are no annex items - add new RequestMailer methods to
support the templates
email this case to forranx@princeton.edu

Update request_mailer_preview.rb
 * remove stale and unused methods
 * parsing of location label
Update mock data in request_mailer_preview.rb spec

related to [#4183]

* Update the hidden_service_options_fill_in with a new check for annex_no_items service
Update the submittable services to include annex_no_items
Include annex_no_items where needed by the code
Add new annex abstract method and subclasses for digitize , noitems and pickup
Pass patron as an argument in the new annex services
Update items validator with annex_no_items

Add a new condition in categorize_by_delivery_and_location method
that checks for an item type of annex_no_items && edd?
to set the item type to annex_edd (Illiad request)

related to [#4183]

* when annex no item service is used the patron should
receive a confirmation email
with the annex confirmation subject

related to [#4183]

50 of 63 new or added lines in 8 files covered. (79.37%)

1 existing line in 1 file now uncovered.

6232 of 6543 relevant lines covered (95.25%)

1448.52 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.27
/app/models/requests/submission.rb
1
# frozen_string_literal: true
2
require 'email_validator'
3✔
3

4
module Requests
3✔
5
  class Submission
3✔
6
    include ActiveModel::Validations
3✔
7

8
    validates :email, presence: true, email: true, length: { minimum: 5, maximum: 50 }
3✔
9
    validates :user_name, presence: true, length: { minimum: 1, maximum: 50 }
3✔
10
    validates :user_barcode, allow_blank: true, presence: true, length: { minimum: 5, maximum: 14 },
3✔
11
                             format: { with: /(^\d{14}$)/i, message: "Please supply a valid library barcode" }
12
    validate :item_validations
3✔
13

14
    def initialize(params, patron)
3✔
15
      @patron = patron
265✔
16
      @items = selected_items(params[:requestable])
265✔
17
      @bib = params[:bib]
265✔
18
      @services = []
265✔
19
      @success_messages = []
265✔
20
    end
21

22
    # :reek:DuplicateMethodCall
23
    def self.new_from_hash(original_hash)
3✔
24
      requestable = original_hash['items']&.map(&:with_indifferent_access) || []
122✔
25
      patron = Patron.new(user: User.find_by(uid: original_hash['patron']['netid']), patron_hash: original_hash['patron'].with_indifferent_access)
122✔
26
      new({ requestable:, bib: original_hash['bib'] }, patron)
122✔
27
    end
28

29
    attr_reader :patron, :success_messages, :items, :bib
3✔
30

31
    def email
3✔
32
      @patron.active_email
532✔
33
    end
34

35
    delegate :source, to: :@patron
3✔
36

37
    def user_name
3✔
38
      @patron.netid
389✔
39
    end
40

41
    def filter_items_by_service(service)
3✔
42
      @items.select { |item| item["type"] == service }
99✔
43
    end
44

45
    def selected_items(requestable_list)
3✔
46
      items = requestable_list.select { |r| r unless r[:selected] == 'false' || !r.key?('selected') }
782✔
47
      items.map { |item| categorize_by_delivery_and_location(item) }
562✔
48
    end
49

50
    def item_validations
3✔
51
      validates_with Requests::SelectedItemsValidator
83✔
52
    end
53

54
    def user_barcode
3✔
55
      @patron.barcode
518✔
56
    end
57

58
    def id
3✔
59
      @bib[:id]
1✔
60
    end
61

62
    def partner_item?(item)
3✔
63
      Requests.config[:recap_partner_locations].keys.include? item["location_code"]
15✔
64
    end
65

66
    def service_types
3✔
67
      @types ||= @items.map { |item| item['type'] }.uniq
87✔
68
      @types
44✔
69
    end
70

71
    def process_submission
3✔
72
      @services = service_types.map do |type|
42✔
73
        service_by_type(type)
42✔
74
      end
75
      @services.each(&:handle)
42✔
76

77
      @success_messages = generate_success_messages(@success_messages)
42✔
78

79
      @services
42✔
80
    end
81

82
    def service_errors
3✔
83
      return [] if @services.blank?
41✔
84
      @services.map(&:errors).flatten
41✔
85
    end
86

87
    def pick_up_location
3✔
88
      Requests::BibdataService.delivery_locations.dig(items.first&.dig('pick_up'), "library") || {}
96✔
89
    end
90

91
    def marquand?
3✔
92
      items.first["holding_library"] == 'marquand'
1✔
93
    end
94

95
    def edd?(item)
3✔
96
      delivery_mode = delivery_mode(item)
301✔
97
      delivery_mode.present? && delivery_mode == "edd"
301✔
98
    end
99

100
    # Convert the submission to a hash that is compatible that
101
    # Sidekiq can safely serialize to JSON for redis.  This means:
102
    #   * No application-specific objects (unless they are activerecord db objects)
103
    #   * No ActiveSupport::HashWithIndifferentAccess objects, we can only have Hash objects
104
    #   * The keys of the hash must all be strings, not symbols
105
    # See https://github.com/sidekiq/sidekiq/wiki/Best-Practices#1-make-your-job-parameters-small-and-simple
106
    def to_h
3✔
107
      {
108
        bib: bib.to_hash.stringify_keys,
81✔
109
        email: email,
110
        errors: errors.messages.stringify_keys,
111
        items: items.map { |hash| hash.to_hash.stringify_keys },
80✔
112
        patron: patron.to_h.to_hash.stringify_keys,
113
        pick_up_location: pick_up_location.to_hash.stringify_keys,
114
        user_barcode:,
115
        user_name:
116
      }.stringify_keys
117
    end
118

119
    private
3✔
120

121
      def service_by_type(type)
3✔
122
        case type
42✔
123
        when 'on_shelf', 'marquand_in_library', 'annex', 'annex_in_library'
124
          Requests::Submissions::HoldItem.new(self, service_type: type)
16✔
125
        when 'recap', 'recap_edd', 'recap_in_library', 'recap_marquand_in_library', 'recap_marquand_edd'
126
          Requests::Submissions::Recap.new(self, service_type: type)
9✔
127
        when 'digitize', 'annex_edd', 'marquand_edd'
128
          Requests::Submissions::DigitizeItem.new(self, service_type: type)
5✔
129
        when *inter_library_services
130
          Requests::Submissions::Illiad.new(self, service_type: type)
3✔
131
        else
132
          Requests::Submissions::Generic.new(self, service_type: type)
9✔
133
        end
134
      end
135

136
      # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
137
      # Annex records with no barcoded items have an annex_edd type if edd is selected - This is an Illiad request
138
      # Recap records with no barcoded items have a recap_no_item type - In this case we email
139
      # the recapproblems@princeton.edu - The recap system will not accept items with no barcode.
140
      def categorize_by_delivery_and_location(item)
3✔
141
        library_code = item["library_code"]
297✔
142
        if recap_no_items?(item)
297✔
143
          item["type"] = "recap_no_items"
×
144
        elsif annex_no_items?(item) && edd?(item)
297✔
NEW
145
          item["type"] = "annex_edd"
×
146
        elsif annex_no_items?(item)
297✔
NEW
147
          item["type"] = "annex_no_items"
×
148
        elsif print?(item) && library_code == 'annex'
297✔
149
          item["type"] = "annex"
6✔
150
        elsif off_site?(library_code)
291✔
151
          raise Exception, "#{self.class}: Item does not have a delivery mode" unless delivery_mode(item)
49✔
152
          item["type"] = library_code
49✔
153
          item["type"] += "_edd" if edd?(item)
49✔
154
          item["type"] += "_in_library" if in_library?(item)
49✔
155
        elsif item["type"] == "paging"
242✔
156
          item["type"] = "digitize" if edd?(item)
10✔
157
        elsif edd?(item) && library_code.present?
232✔
158
          item["type"] = "digitize"
3✔
159
        elsif print?(item) && library_code.present?
229✔
160
          item["type"] = "on_shelf"
13✔
161
        end
162
        item
297✔
163
      end
164
      # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
165

166
      def in_library?(item)
3✔
167
        delivery_mode = delivery_mode(item)
49✔
168
        delivery_mode.present? && delivery_mode == "in_library"
49✔
169
      end
170

171
      def recap_no_items?(item)
3✔
172
        item["library_code"] == 'recap' && (item["type"] == "digitize_fill_in" || item["type"] == "recap_no_items")
297✔
173
      end
174

175
      # :reek:UtilityFunction
176
      def annex_no_items?(item)
3✔
177
        item["library_code"] == 'annex' && item["type"] == "annex_no_items"
594✔
178
      end
179

180
      def off_site?(library_code)
3✔
181
        library_code == 'recap' || library_code == 'marquand' || library_code == 'recap_marquand' || library_code == 'annex'
291✔
182
      end
183

184
      def print?(item)
3✔
185
        delivery_mode = delivery_mode(item)
526✔
186
        delivery_mode.present? && delivery_mode == "print"
526✔
187
      end
188

189
      def delivery_mode(item)
3✔
190
        item["delivery_mode_#{item['item_id']}"]
925✔
191
      end
192

193
      def inter_library_services
3✔
194
        ['ill']
12✔
195
      end
196

197
      def generate_success_messages(success_messages)
3✔
198
        @services.each do |service|
42✔
199
          success_messages << service.success_message
42✔
200
          service.send_mail
42✔
201
        end
202
        success_messages
42✔
203
      end
204
  end
205
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc