让您的客户订阅使用Spree Commerce的股票产品 - 重新分配 -
#javascript #ruby #rails #spreecommerce

Alt Text

这是使用spree_frontend的原始文章here的重构版本。为了能够跟随,请花2-3分钟阅读。我称之为V1,使用了过时的Spree Commerce版本。 V2(此帖子)现在使用Spree v4.5.x使用spree_backend Gem。

我已经更新了StockItemsControllerDecorator

# https://github.com/spree/spree_backend/blob/main/app/controllers/spree/admin/stock_items_controller.rb
module Spree
  module Admin
    module StockItemsControllerDecorator
      # renamed from NotifyCustomersHelper
      include NotifyCustomers

      def self.prepended(base)
        base.before_action :process_notifiees_on_stock_item, only: :update

        # We have not taken into account should stock_movement.save fails.
        # see https://github.com/spree/spree_backend/blob/main/app/controllers/spree/admin/stock_items_controller.rb#L13
        base.before_action :process_notifiees_on_stock_movement, only: :create

        # We don't need to track when deleted as the "track Inventory" can be checked when deleted.
        # For this, look at Spree::Admin::VariantsIncludingMasterController#update
        # base.before_action :notify_notifiees, only: :destroy
      end 
    end
  end
end
::Spree::Admin::StockItemsController.prepend Spree::Admin::StockItemsControllerDecorator if ::Spree::Admin::StockItemsController.included_modules.exclude?(Spree::Admin::StockItemsControllerDecorator)

variantsincludingmasterControllerDecorator也进行了重构,因为我们只在不再跟踪库存时才发送电子邮件:

module Spree
  module Admin
    module VariantsIncludingMasterControllerDecorator
      include NotifyCustomers

      def self.prepended(base)
        # Defined in NotifyCustomers. Continue reading...
        base.before_action :notify_notifiees, only: :update
      end
    end
  end
end
::Spree::Admin::VariantsIncludingMasterController.prepend Spree::Admin::VariantsIncludingMasterControllerDecorator if ::Spree::Admin::VariantsIncludingMasterController.included_modules.exclude?(Spree::Admin::VariantsIncludingMasterControllerDecorator)

我还重构了NotifyCustomers,而是寻找变体而不是产品:

module NotifyCustomers
  private
    # We've made the executive decision by not keeping stocks.
    # Alert all customers that the product is available to purchase.
    def notify_notifiees 
      variant_id = params[:id]
      not_tracking = !ActiveRecord::Type::Boolean.new.cast(
        params[:variant][:track_inventory]
      )

      not_tracking && email_all_notifiees(variant_id)
    end

    def process_notifiees_on_stock_movement
      quantity = params[:stock_movement][:quantity].to_i
      variant_id = params[:variant_id]

      unless quantity.zero?
        email_all_notifiees(variant_id)
      end

    end

    def email_all_notifiees(variant_id)
      notifiees = lookup_notifiees_by(variant_id)

      send_notification_email(notifiees)

      # We said we'd delete their email address
      notifiees.destroy_all
    end

    def process_notifiees_on_stock_item
      # Backorderable: boolean
      # stock_item.backorderable

      # Number of items in stock: integer
      # stock_item.count_on_hand

      unless stock_item.count_on_hand.zero?
        variant_id = stock_item.variant.id
        email_all_notifiees(variant_id)
      end
    end

    def lookup_notifiees_by(variant_id)
        Spree::VariantNotification.where(variant_id: variant_id)
    end

    def send_notification_email(notifiees)
      if notifiees.present?
        emails_to_send = notifiees.pluck(:email)
        puts "---- SENDING EMAIL TO #{emails_to_send.length} email addresses (plural for now)"

        # Now send the email
        # You'll need to implement a mailer for this
      end
    end
end

迁移文件也已更新(我正在一个新项目中进行此操作):

class CreateSpreeVariantNotifications < ActiveRecord::Migration[7.0]
  def change
    create_table :spree_variant_notifications do |t|
      t.references  :user
      t.references  :variant, null: false
      t.string      :email, null: false

      t.timestamps
    end
  end
end

更新的模型:

module Spree
  class VariantNotification < ApplicationRecord
    validates   :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }

    # Optional as a user doesnt have to be signed in
    belongs_to  :user, class_name: 'Spree::User', optional: true

    belongs_to  :variant, class_name: 'Spree::Variant'
  end
end

module Spree
  module VariantDecorator
    def self.prepended(base)
      base.has_many :notifications, class_name: 'Spree::VariantNotification', foreign_key: 'variant_id', dependent: :destroy
    end
  end
end

::Spree::Variant.prepend Spree::VariantDecorator if ::Spree::Variant.included_modules.exclude?(Spree::VariantDecorator)

也将电子邮件处理到我们的数据库中的地方:

module Spree
  # Though belongs to a variant, we'll keep this for the product
  module Products
    class NotifyController < Spree::StoreController
      before_action :set_variant
      include ActionView::Helpers::TextHelper

      # Does not uses a layout
      layout false

      def notify_me
        email = strip_tags(notify_params[:email])

        @notif = @variant.notifications.find_or_create_by(email: email) do |perm|
                    # user_id can be null
                    perm.user_id = spree_current_user&.id || notify_params[:user_id]
                  end

          respond_to do |format|

            if @notif.save
              format.turbo_stream
            else
              format.html { redirect_to spree.product_path(@variant.product), alert: "Something went wrong", status: 422 }
            end
          end
      end


      private
        def notify_params
          params.fetch(:product_notification, {}).permit(:email)
        end

        def set_variant
          @variant = Spree::Variant.find_by(id: params[:variant_id])
          unless @variant.present?
            redirect_to root_path
          end
        end
    end
  end
end

# Route updated to:
post '/products/:variant_id/notify', to: 'products/notify#notify_me', as: 'variant_notify'

_notify_me_when_available.html,在_cart_form内部,但也更新了订单表格外:

<%= form_with(
    scope: :product_notification, 
    url: spree.variant_notify_path(variant),
    data: {
      controller: "reset-form",
      action: "turbo:submit-end->reset-form#reset"
    },
    id: dom_id(variant, :notify)) do |form| %>
  <%= form.label :email, "Notify me when in stock" %>
  <%= form.email_field :email, class: 'spree-flat-input', placeholder: "Enter your email address", required: true %>
  <%= form.submit "Notify me", class: "btn btn-primary w-100 text-uppercase font-weight-bold mt-2" %>
  <p class="mt-2 text-sm">After this notification, your email address will be deleted and not used again.</p>
<% end %>

我在这个新项目中使用了涡轮增压器。提交表单时,我清除了输入字段,并且在成功时,我会用部分(spree/shared/_variant_notify_success)更新DOM:

<div class="p-3 text-center bg-green-600 text-white sm:text-base text-sm">
  <p>Great! We'll send you a one-time email when item becomes available.</p>
</div>

使用..

<%= turbo_stream.replace dom_id(@variant, :notify) do %>
  <%= render "spree/shared/variant_notify_success" %>
<% end %>

...外订单表格:

<%# Outside order form %>
<% unless is_product_available_in_currency && @product.can_supply? %>
  <%= render 'spree/shared/notify_me_when_available', variant: default_variant %>
<% end %>

如果变体缺货,则需要其他工作来显示/隐藏通知表格。我的示例只会在产品无法提供时显示通知表格。

Spree Frontend在SEED here上为.product-variants-variant-values-radio提供了一个活动lister。您可以将逻辑放在那里显示/隐藏通知表格。这是逻辑:

在您的单击事件中,OPTION_VALUE_SELECTOR(在您自己的js文件中或使用刺激):

使用选定的variant_id,将data-variants的数据从购物车中解析。例如,在data-variants的数组中,使用variants.find(o.id == variant_id).purchasable检查该变体是否为purchasable。基于True或fals,显示/隐藏通知表格。我从here获得purchasable

现在应该全部。 At the time of this post, I have not implement show/hide when an option is selected. Live demo here