Class: ShipmentReceiptsController

Inherits:
CrmController show all
Includes:
Controllers::Showable
Defined in:
app/controllers/shipment_receipts_controller.rb

Overview

== Schema Information

Table name: shipment_receipts

id :integer not null, primary key
purchase_order_shipment_id :integer
effective_date :date
creator_id :integer
updater_id :integer
created_at :datetime
updated_at :datetime
landed_cost :decimal(8, 2)
currency :string(255)
estimated_landed_cost :decimal(8, 2)
location :string(255)
state :string(255)
serial_number_state :string

Constant Summary

Constants included from Controllers::ReferenceFindable

Controllers::ReferenceFindable::ID_EMBEDDED_PATTERNS

Constants included from Controllers::AnalyticsEvents

Controllers::AnalyticsEvents::MAX_QUEUED_EVENTS, Controllers::AnalyticsEvents::SESSION_KEY

Constants included from Controllers::ErrorRendering

Controllers::ErrorRendering::NON_CONTENT_PATH_PREFIXES

Constants included from Www::SeoHelper

Www::SeoHelper::AWARDS, Www::SeoHelper::CA_ADDRESS, Www::SeoHelper::CA_BUSINESS_HOURS, Www::SeoHelper::CA_CONTACT_POINT, Www::SeoHelper::CA_CURRENCIES, Www::SeoHelper::CA_DESCRIPTION, Www::SeoHelper::CA_FOUNDING_DATE, Www::SeoHelper::CA_GLOBAL_LOCATION_NUMBER, Www::SeoHelper::CA_LEGAL_NAME, Www::SeoHelper::CA_LOCAL_BUSINESS, Www::SeoHelper::CA_ONLINE_STORE, Www::SeoHelper::CA_RETURN_POLICY, Www::SeoHelper::CA_SALES_DEPARTMENT, Www::SeoHelper::CA_SERVICE_AREA, Www::SeoHelper::CA_URL, Www::SeoHelper::CA_VAT_ID, Www::SeoHelper::CA_WAREHOUSE_DEPARTMENT, Www::SeoHelper::CA_WAREHOUSE_HOURS, Www::SeoHelper::COMPANY_EMAIL, Www::SeoHelper::COMPANY_LOGO, Www::SeoHelper::COMPANY_NAME, Www::SeoHelper::COMPANY_SLOGAN, Www::SeoHelper::EXPERTISE, Www::SeoHelper::FAX_NUMBER, Www::SeoHelper::GS1_COMPANY_PREFIX, Www::SeoHelper::ISO6523_CODE, Www::SeoHelper::PAYMENT_METHODS, Www::SeoHelper::PHONE_NUMBER, Www::SeoHelper::PRIMARY_NAICS, Www::SeoHelper::REFUND_TYPE, Www::SeoHelper::RETURN_FEES, Www::SeoHelper::RETURN_METHOD, Www::SeoHelper::RETURN_POLICY_CATEGORY, Www::SeoHelper::SECONDARY_NAICS, Www::SeoHelper::SOCIAL_PROFILES, Www::SeoHelper::US_ADDRESS, Www::SeoHelper::US_BUSINESS_HOURS, Www::SeoHelper::US_CONTACT_POINT, Www::SeoHelper::US_CURRENCIES, Www::SeoHelper::US_DESCRIPTION, Www::SeoHelper::US_FOUNDING_DATE, Www::SeoHelper::US_GLOBAL_LOCATION_NUMBER, Www::SeoHelper::US_IMAGE, Www::SeoHelper::US_LEGAL_NAME, Www::SeoHelper::US_LOCAL_BUSINESS, Www::SeoHelper::US_ONLINE_STORE, Www::SeoHelper::US_RETURN_POLICY, Www::SeoHelper::US_SALES_DEPARTMENT, Www::SeoHelper::US_SERVICE_AREA, Www::SeoHelper::US_TAX_ID, Www::SeoHelper::US_URL, Www::SeoHelper::US_WAREHOUSE_DEPARTMENT, Www::SeoHelper::US_WAREHOUSE_HOURS

Constants included from IconHelper

IconHelper::CUSTOM_ICON_MAP, IconHelper::CUSTOM_SVG_DIR, IconHelper::DEFAULT_FAMILY

Instance Method Summary collapse

Methods included from Controllers::Showable

#perform_show, #show

Methods inherited from CrmController

#access_denied, #context_id, #context_object, #crm_home_path, #current_ability, #default_url_options, #download_temp, #get_tempfile_path_for_download, #init_status_job_collector, #initialize_crm_lazy_chunks, #persist_enqueued_status_jobs, #record_not_found, #redirect_to_job_or_fallback, #render_edit_action, #set_context, #set_download_path, #stash_file_for_temp_download, #sync_admin_presence_cookie

Methods inherited from ApplicationController

#account_impersonated?, #add_to_flash, #after_sign_in_path_for, #bypass_forgery_protection?, #chat_enabled?, #cloudflare_cleared?, #default_catalog, #default_url_options, #enable_turbo_frames, #find_publication, #fix_invalid_accept_header, #init_js_utils, #is_globals_call?, #layout_by_resource, #locale_store, #redirect_to, #require_employee_for_crm, #set_base_host, #set_real_ip, #set_report_errors_for, #should_render_layout?, #stamp_impersonation_context, #warmlyyours_canada_ip?, #warmlyyours_ip?, #y

Methods included from Controllers::ReturnPathHandling

#check_for_return_path, #redirect_to_return_path_or_default

Methods included from Controllers::AnalyticsEvents

#consume_queued_analytics_events, #track_event

Methods included from Controllers::DeviceDetection

#device_detector, #is_ie?

Methods included from Controllers::SubdomainDetection

#is_crm_request?, #is_www_request?, #json_request?

Methods included from Controllers::TurboSafeRedirect

#redirect_to

Methods included from Controllers::TrackingDetection

#bot_request?, #gdpr_country?, #gdpr_country_data, #prevent_bots, #set_tracking_cookie, #track_visitor?

Methods included from Controllers::AcceleratedFileSending

#send_file_accelerated, #send_upload_accelerated

Methods included from Controllers::ErrorRendering

#excp_string, #mail_to_for_error_reporting, #render_400, #render_404, #render_406, #render_410, #render_500, #render_invalid_authenticity_token, #render_ip_spoof_error, #render_unpermitted_parameters, #safe_referer_or_fallback

Methods included from Controllers::TurnstileVerification

#load_turnstile_script_tag, #turnstile_lazy_widget, #turnstile_script_tag, #turnstile_widget, #validate_turnstile!

Methods included from Controllers::CloudflareCaching

edge_cached, #edge_cached_action?, #reset_cloudflare_cache, #set_cloudflare_cache, #skip_edge_cache!, #skip_session

Methods included from Controllers::Webpackable

#preload_webpack_fonts, #webpack_css_include, #webpack_css_url, #webpack_js_include, #wpd_is_running?

Methods included from Controllers::Localizable

#cloudflare_country_locale, #determine_request_locale, #geocoder_locale, #guest_user_locale_check, #locale_optional_www_auth_path?, #param_locale, #set_locale, #set_request_locale, #skip_localization?, #warmlyyours_ip_locale

Methods included from Controllers::Authenticable

#access_denied, #authenticate_account, #authenticate_account!, #authenticate_account_from_login_token!, #check_is_a_manager, #check_is_a_sales_manager, #check_is_an_admin, #check_is_an_employee, #check_party, #clear_mismatched_guest_user, #create_guest_user, #credentials?, #current_or_guest_user, #current_or_guest_user_id_read_only, #current_user, #devise_mapping, #fully_logged_in?, #generate_bot_id, #guest_user, #identifiable?, #init_current_user, #initialize_guest, #load_context_user, #logging_in, #resource, #resource_name, #restrict_access_for_non_employees, #scrubbed_request_path, #user_object, #warn_on_session_guest_id_leak

Methods included from ApplicationHelper

#better_number_to_currency, #check_force_logout, #check_or_cross, #check_or_times, #embedded_tab_frame_id, #error_messages, #general_disclaimer_on_product_installation_and_local_codes, #gridjs_from_html_table, #gridjs_table, #is_wy_ip, #line_break, #parent_layout, #pass_or_fail, #render_error_messages_list, #render_video_card, #resolved_auth_form_turbo_frame, #return_path_or, #safe_css_color, #set_return_path_if_present, #set_section_if_present, #tab_frame_id, #to_underscore, #track_page?, #turbo_section_wrapper, #turbo_tabs_request?, #url_on_same_domain_as_request, #widget_index_daily_focus_index_path, #working_hours?, #yes_or_no, #yes_or_no_highlighted, #yes_or_no_with_check_or_cross, #youtube_video

Methods included from UppyUploaderHelper

#file_uploader, #image_uploader, #large_file_uploader_s3, #lead_sketch_uploader, #rma_image_uploader, #rma_image_uploader_s3, #uppy_uploader, #video_uploader

Methods included from Www::ImagesHelper

#image_asset_tag, #image_asset_url

Methods included from Www::SeoHelper

#add_page_schema, #add_webpage_schema, #canada?, #company_social_links, #ensure_context_json, #json_ld_script_tag, #local_business_schema, #online_store_id, #online_store_schema, #page_main_entity, #page_main_entity_json, #render_auto_collection_page_schema, #render_collection_page_schema, #render_local_business_schema, #render_online_store_schema, #render_page_schemas, #render_page_video_schemas, #render_webpage_schema, #render_webpage_schema_with_collections, #usa?

Methods included from UrlsHelper

#catalog_breadcrumb_links, #catalog_link, #catalog_link_for_product_line, #catalog_link_for_sku, #cms_link, #delocalized_path, #path_to_sales_product_sku, #path_to_sales_product_sku_for_product_line, #path_to_sales_product_sku_for_product_line_slug, #product_line_from_catalog_link, #protocol_neutral_url, #sanitize_external_url, #valid_external_url?

Methods included from IconHelper

#account_nav_icon, #fa_icon, #star_rating_html

Instance Method Details

#assign_serial_numbersObject

GET /shipment_receipts/:id/assign_serial_numbers — render the
serial-number entry form for items that require reservation
(heating mats / cables ship with manufacturer SNs we have to
record on receipt). Builds blank SN rows for any item that
hasn't been fully entered yet.



185
186
187
188
189
# File 'app/controllers/shipment_receipts_controller.rb', line 185

def assign_serial_numbers
  @shipment_receipt = ShipmentReceipt.find(params[:id])
  @shipment_receipt_items = @shipment_receipt.shipment_receipt_items.select { |sri| sri.item.require_reservation? }
  @shipment_receipt_items.each { |sri| sri.build_serial_numbers unless sri.serial_numbers.to_a.sum(&:qty) == sri.quantity }
end

#confirm_landed_costsObject

GET /shipment_receipts/:id/confirm_landed_costs — final
confirmation step before posting. Empty action; just renders
the template.



163
# File 'app/controllers/shipment_receipts_controller.rb', line 163

def confirm_landed_costs; end

#createObject

POST /shipment_receipts — receive inbound supplier shipment.
Synchronous DB work was moved into ShipmentReceiptsWorker (see
the commented-out direct path below) because the receipts form
can submit hundreds of items at once and the warehouse couldn't
tolerate the request timeout. Detects an already-enqueued
duplicate via BackgroundJobStatus so a double-submit shows the
existing job rather than creating two.



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'app/controllers/shipment_receipts_controller.rb', line 42

def create
  @purchase_order_shipment = PurchaseOrderShipment.find(params[:shipment_receipt][:purchase_order_shipment_id])
  @shipment_receipt = ShipmentReceipt.new(params[:shipment_receipt])
  authorize! :create, @shipment_receipt
  # If shipment_items is empty then the shipment receipt will be invalid and won't save
  # ShipmentReceipt.with_advisory_lock("receiving_items_pos_#{@purchase_order_shipment.id}", timeout_seconds: 0) do
  #   @shipment_receipt.enter_receipts(params[:shipment_items])
  #   if @purchase_order_shipment.estimated_landed_cost.present?
  #     @shipment_receipt.estimated_landed_cost = @shipment_receipt.prorate_estimated_landed_cost
  #     @shipment_receipt.currency = @purchase_order_shipment.currency
  #   end
  #   @shipment_receipt.save
  # end
  # if @shipment_receipt.persisted?
  #   if @shipment_receipt.serial_number_state == "awaiting_assignment"
  #     redirect_to assign_serial_numbers_shipment_receipt_path(@shipment_receipt)
  #   else
  #     redirect_to purchase_order_path(@shipment_receipt.shipment_receipt_items.first.purchase_order)
  #   end
  # else
  #   render :new
  # end
  job_options = { shipment_receipt: params[:shipment_receipt].to_hash, purchase_order_shipment_id: @purchase_order_shipment.id, shipment_items: params[:shipment_items].to_hash }.stringify_keys
  job_id = ShipmentReceiptsWorker.perform_async(job_options)

  if job_id.blank?
    if (job_id = BackgroundJobStatus.search(worker_klass: 'ShipmentReceiptsWorker', args: job_options).first&.dig(:job_id))
      flash[:warning] = "A similar job was already enqueued, watching status of that job now"
    end
  end
  if job_id.present?
    redirect_to job_path(job_id)
  else
    flash[:warning] = "There was a problem queuing the job"
    redirect_to_return_path_or_default purchase_order_path(@shipment_receipt.shipment_receipt_items.first.purchase_order)
  end
end

#do_assign_serial_numbersObject

POST /shipment_receipts/:id/do_assign_serial_numbers — persist
the entered serial numbers and route the user back to the
purchase order. Re-renders the form when assignment is
incomplete (every reservable item must have its full quantity
of SNs).



196
197
198
199
200
201
202
203
204
205
206
207
# File 'app/controllers/shipment_receipts_controller.rb', line 196

def do_assign_serial_numbers
  @shipment_receipt = ShipmentReceipt.find(params[:id])
  @shipment_receipt.assign_attributes(params[:shipment_receipt])
  if @shipment_receipt.complete_serial_number_assignment
    flash[:info] = "New serial numbers (#{@shipment_receipt.serial_number_numbers}) created."
    redirect_to purchase_order_path(@shipment_receipt.shipment_receipt_items.first.purchase_order)
  else
    flash.now[:error] = "Unable to save, please provide all serial numbers."
    @shipment_receipt_items = @shipment_receipt.shipment_receipt_items.select { |sri| sri.item.require_reservation? }
    render :assign_serial_numbers, status: :unprocessable_content
  end
end

#edit_landed_costsObject

GET /shipment_receipts/:id/edit_landed_costs — re-open the
landed-costs form when a receipt already has costs entered. The
form pre-fills with the existing values so the user can adjust
without re-deriving them.



93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'app/controllers/shipment_receipts_controller.rb', line 93

def edit_landed_costs
  @shipment_receipt = ShipmentReceipt.find(params[:id])
  @landed_cost = OpenStruct.new(params[:landed_cost])
  if @shipment_receipt.landed_cost.present?
    landed_cost = {}
    landed_cost[:landed_cost] = @shipment_receipt.landed_cost
    landed_cost[:carrier_id] = @shipment_receipt.landed_costs.actual.map(&:carrier_id).uniq.first
    landed_cost[:estimated] = true
    @landed_cost = OpenStruct.new(landed_cost)
  else
    @landed_cost = OpenStruct.new(params[:landed_cost])
  end
end

#enter_landed_costsObject

GET /shipment_receipts/:id/enter_landed_costs — render the
landed-costs form for first-time entry (carrier + total cost).
The cost is later prorated across the receipt's items by
ShipmentReceipt#enter_landed_costs.



84
85
86
87
# File 'app/controllers/shipment_receipts_controller.rb', line 84

def enter_landed_costs
  @shipment_receipt = ShipmentReceipt.find(params[:id])
  @landed_cost = OpenStruct.new(params[:landed_cost])
end

#newObject

GET /purchase_order_shipments/:purchase_order_shipment_id/shipment_receipts/new —
render the receiving form. Pre-populates effective_date to today
so receivers don't have to fill it on every receipt.



29
30
31
32
33
# File 'app/controllers/shipment_receipts_controller.rb', line 29

def new
  @purchase_order_shipment = PurchaseOrderShipment.find(params[:purchase_order_shipment_id])
  @shipment_receipt = ShipmentReceipt.new(purchase_order_shipment_id: @purchase_order_shipment.id, effective_date: Date.current)
  authorize! :create, @shipment_receipt
end

#post_landed_costsObject

POST /shipment_receipts/:id/post_landed_costs — apply the
entered landed cost. If the receipt already had costs they're
voided first so we don't double-post. Subtracts the
estimated-landed-cost figure (already booked at receive time) so
only the delta hits the GL. Updates the parent shipment's
rolled-up landed-cost so the report stays accurate.



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'app/controllers/shipment_receipts_controller.rb', line 113

def post_landed_costs
  @shipment_receipt = ShipmentReceipt.find(params[:id])
  @shipment_receipt.void_landed_costs if @shipment_receipt.landed_cost.present?
  estimated_landed_cost = @shipment_receipt.estimated_landed_cost.nil? ? 0 : @shipment_receipt.estimated_landed_cost
  @landed_cost = OpenStruct.new(params[:landed_cost])
  if @landed_cost.landed_cost && @landed_cost.landed_cost.to_i >= 0 && @landed_cost.carrier_id
    landed_cost_calc = @landed_cost.landed_cost.to_f - estimated_landed_cost
    @shipment_receipt.enter_landed_costs(landed_cost_calc.to_s, @landed_cost.carrier_id)
    pos = @shipment_receipt.purchase_order_shipment
    if pos.shipment_receipts.map(&:landed_cost).any?
      landed_cost = pos.shipment_receipts.filter_map(&:landed_cost).sum
      estimated_landed_cost = pos.shipment_receipts.filter_map { |sr| sr.landed_cost.present? ? sr.estimated_landed_cost : nil }.sum
      pos.update(landed_cost: landed_cost + estimated_landed_cost)
    else
      pos.update(landed_cost: nil)
    end
    redirect_to purchase_order_path(@shipment_receipt.shipment_receipt_items.first.purchase_order)
  else
    flash.now[:error] = "You must specify a carrier and a landed cost of 0 or more"
    render :enter_landed_costs, status: :unprocessable_content
  end
end

#preview_landed_costsObject

GET /shipment_receipts/:id/preview_landed_costs — render the
landed-cost preview screen (shows the prorated per-item bump
before the user posts).



158
# File 'app/controllers/shipment_receipts_controller.rb', line 158

def preview_landed_costs; end

#voidObject

POST /shipment_receipts/:id/void — void the entire shipment
receipt (returns inventory commits, reverses the GL postings).
The model returns a {success:, message:} hash so the flash
surfaces gateway-side reasons for refusal.



169
170
171
172
173
174
175
176
177
178
# File 'app/controllers/shipment_receipts_controller.rb', line 169

def void
  @shipment_receipt = ShipmentReceipt.find(params[:id])
  res = @shipment_receipt.void_shipment_receipt
  if res["success"] == false
    flash[:error] = res["message"]
  else
    flash[:info] = res["message"]
  end
  redirect_to shipment_receipt_path(@shipment_receipt)
end

#void_landed_costsObject

POST /shipment_receipts/:id/void_landed_costs — reverse the
landed-cost entries for this receipt and recalculate the
parent shipment's rolled-up landed cost. Used when accounting
needs to back out a wrongly-attributed freight bill.



140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'app/controllers/shipment_receipts_controller.rb', line 140

def void_landed_costs
  @shipment_receipt = ShipmentReceipt.find(params[:id])
  @shipment_receipt.void_landed_costs
  pos = @shipment_receipt.purchase_order_shipment
  if pos.shipment_receipts.map(&:landed_cost).any?
    landed_cost = pos.shipment_receipts.filter_map(&:landed_cost).sum
    estimated_landed_cost = pos.shipment_receipts.filter_map { |sr| sr.landed_cost.present? ? sr.estimated_landed_cost : nil }.sum
    pos.update(landed_cost: landed_cost + estimated_landed_cost)
  else
    pos.update(landed_cost: nil)
  end
  flash[:info] = "Landed costs have been voided for this shipment receipt"
  redirect_to @shipment_receipt
end