diff --git a/app/controllers/spree/api/v2/storefront/queue_cart/line_items_controller.rb b/app/controllers/spree/api/v2/storefront/queue_cart/line_items_controller.rb new file mode 100644 index 000000000..2509a62d2 --- /dev/null +++ b/app/controllers/spree/api/v2/storefront/queue_cart/line_items_controller.rb @@ -0,0 +1,56 @@ +module Spree + module Api + module V2 + module Storefront + module QueueCart + class LineItemsController < CartController + before_action :ensure_order, only: :create + before_action :load_variant, only: :create + before_action :ensure_cart_exist, only: :create + + # override + def create + spree_authorize! :update, spree_current_order, order_token + spree_authorize! :show, @variant + + job = SpreeCmCommissioner::EnqueueCart::AddItemJob.perform_later( + spree_current_order.id, + @variant.id, + add_item_params[:quantity], + add_item_params[:public_metadata], + add_item_params[:private_metadata], + add_item_params[:options] + ) + + result = SpreeCmCommissioner::EnqueueCart::AddItemStatusMarker.call( + order_number: spree_current_order.number, + job_id: job.job_id, + status: 'processing', + queued_at: Time.current, + variant_id: @variant.id, + quantity: add_item_params[:quantity] + ) + + line_item_queue = SpreeCmCommissioner::QueueItem.new( + id: job.job_id, + status: result.firestore_status, + queued_at: result.firestore_queued_at, + collection_reference: result.firestore_collection_reference, + document_reference: result.firestore_document_reference + ) + + render_serialized_payload do + serialize_resource(line_item_queue) + end + end + + # override + def resource_serializer + Spree::V2::Storefront::FirestoreQueueSerializer + end + end + end + end + end + end +end diff --git a/app/interactors/spree_cm_commissioner/enqueue_cart/add_item.rb b/app/interactors/spree_cm_commissioner/enqueue_cart/add_item.rb new file mode 100644 index 000000000..17e82355c --- /dev/null +++ b/app/interactors/spree_cm_commissioner/enqueue_cart/add_item.rb @@ -0,0 +1,43 @@ +module SpreeCmCommissioner + module EnqueueCart + class AddItem < BaseInteractor + delegate :order_id, :variant_id, :job_id, :quantity, :public_metadata, :private_metadata, :options, to: :context + + def call + result = add_item_to_cart + if result.success? + update_status('completed') + else + update_status('failed') + end + end + + def add_item_to_cart + Spree::Cart::AddItem.call( + order: order, + variant: variant, + quantity: quantity, + public_metadata: public_metadata, + private_metadata: private_metadata, + options: options + ) + end + + def update_status(status) + SpreeCmCommissioner::EnqueueCart::AddItemStatusMarker.call( + order_number: order.number, + job_id: job_id, + status: status + ) + end + + def order + @order ||= Spree::Order.find(order_id) + end + + def variant + @variant ||= Spree::Variant.find(variant_id) + end + end + end +end diff --git a/app/interactors/spree_cm_commissioner/enqueue_cart/add_item_status_marker.rb b/app/interactors/spree_cm_commissioner/enqueue_cart/add_item_status_marker.rb new file mode 100644 index 000000000..70dc1bdb9 --- /dev/null +++ b/app/interactors/spree_cm_commissioner/enqueue_cart/add_item_status_marker.rb @@ -0,0 +1,75 @@ +require 'google/cloud/firestore' + +module SpreeCmCommissioner + module EnqueueCart + class AddItemStatusMarker < BaseInteractor + delegate :order_number, :job_id, :status, :queued_at, :variant_id, :quantity, to: :context + + def call + if order_number.nil? || job_id.nil? || status.nil? + context.fail!(message: 'Missing required fields') + return + end + update_cart_firestore_status + + assign_firestore_context_attributes + end + + def firestore + @firestore ||= Google::Cloud::Firestore.new(project_id: service_account[:project_id], credentials: service_account) + end + + def service_account + @service_account ||= Rails.application.credentials.cloud_firestore_service_account + end + + def update_cart_firestore_status + data_to_set = build_data_to_set + + firestore.col('queues') + .doc('cart') + .col(order_number) + .doc(job_id) + .set(data_to_set, merge: true) + end + + def build_data_to_set + { + status: status, + queued_at: queued_at.presence, + variant_id: variant_id.presence, + quantity: quantity.presence + }.compact + end + + def firestore_object + firestore.col('queues').doc('cart').col(order_number).doc(job_id).get + end + + def firestore_status + firestore_object[:status] + end + + def firestore_queued_at + firestore_object[:queued_at] + end + + def firestore_collection_reference + firestore_object.ref.path + end + + def firestore_document_reference + firestore_collection_reference.split('/documents').last + end + + def assign_firestore_context_attributes + document = firestore_object + + context.firestore_status = document[:status] + context.firestore_queued_at = document[:queued_at] + context.firestore_collection_reference = document.ref.path + context.firestore_document_reference = document.ref.path.split('/documents').last + end + end + end +end diff --git a/app/jobs/spree_cm_commissioner/enqueue_cart/add_item_job.rb b/app/jobs/spree_cm_commissioner/enqueue_cart/add_item_job.rb new file mode 100644 index 000000000..b13db8a6e --- /dev/null +++ b/app/jobs/spree_cm_commissioner/enqueue_cart/add_item_job.rb @@ -0,0 +1,17 @@ +module SpreeCmCommissioner + module EnqueueCart + class AddItemJob < ApplicationJob + def perform(order_id, variant_id, quantity, public_metadata, private_metadata, options) # rubocop:disable Metrics/ParameterLists + SpreeCmCommissioner::EnqueueCart::AddItem.call( + order_id: order_id, + variant_id: variant_id, + quantity: quantity, + public_metadata: public_metadata, + private_metadata: private_metadata, + options: options, + job_id: job_id + ) + end + end + end +end diff --git a/app/serializables/spree_cm_commissioner/queue_item.rb b/app/serializables/spree_cm_commissioner/queue_item.rb new file mode 100644 index 000000000..c801f6d02 --- /dev/null +++ b/app/serializables/spree_cm_commissioner/queue_item.rb @@ -0,0 +1,13 @@ +module SpreeCmCommissioner + class QueueItem + attr_accessor :id, :status, :queued_at, :collection_reference, :document_reference + + def initialize(id:, status:, queued_at:, collection_reference:, document_reference:) + @id = id + @status = status + @queued_at = queued_at + @collection_reference = collection_reference + @document_reference = document_reference + end + end +end diff --git a/app/serializers/spree/v2/storefront/firestore_queue_serializer.rb b/app/serializers/spree/v2/storefront/firestore_queue_serializer.rb new file mode 100644 index 000000000..9df5b170f --- /dev/null +++ b/app/serializers/spree/v2/storefront/firestore_queue_serializer.rb @@ -0,0 +1,9 @@ +module Spree + module V2 + module Storefront + class FirestoreQueueSerializer < BaseSerializer + attributes :status, :queued_at, :collection_reference, :document_reference + end + end + end +end diff --git a/config/routes.rb b/config/routes.rb index 1c05fb28b..d4d008b83 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -391,6 +391,10 @@ patch :restart_checkout_flow end + namespace :queue_cart do + resources :line_items, only: [:create] + end + resources :wished_items resources :user_promotion resources :order_promotions diff --git a/spec/interactors/spree_cm_commissioner/enqueue_cart/add_item_status_marker_spec.rb b/spec/interactors/spree_cm_commissioner/enqueue_cart/add_item_status_marker_spec.rb new file mode 100644 index 000000000..3941a4ef3 --- /dev/null +++ b/spec/interactors/spree_cm_commissioner/enqueue_cart/add_item_status_marker_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper' + +RSpec.describe SpreeCmCommissioner::EnqueueCart::AddItemStatusMarker do + let(:order_number) { 'R12345678' } + let(:job_id) { 'job_456' } + let(:status) { 'processing' } + let(:queued_at) { Time.now } + + subject { described_class.new(order_number: order_number, job_id: job_id, status: status, queued_at: queued_at) } + + describe '#call' do + it 'fails when order_number is missing' do + allow(subject).to receive(:order_number).and_return(nil) + expect { subject.call }.to raise_error(Interactor::Failure) + end + + it 'fails when job_id is missing' do + allow(subject).to receive(:job_id).and_return(nil) + expect { subject.call }.to raise_error(Interactor::Failure) + end + + it 'fails when status is missing' do + allow(subject).to receive(:status).and_return(nil) + expect { subject.call }.to raise_error(Interactor::Failure) + end + end +end diff --git a/spec/jobs/spree_cm_commissioner/add_item_job_spec.rb b/spec/jobs/spree_cm_commissioner/add_item_job_spec.rb new file mode 100644 index 000000000..6b8f8fa70 --- /dev/null +++ b/spec/jobs/spree_cm_commissioner/add_item_job_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +RSpec.describe SpreeCmCommissioner::EnqueueCart::AddItemJob, type: :job do + describe '#perform' do + let(:order_id) { 1 } + let(:variant_id) { 2 } + let(:quantity) { 2 } + let(:public_metadata) { { data: 'data' } } + let(:private_metadata) { { data: 'info' } } + let(:options) { { key: 'value' } } + + it 'calls SpreeCmCommissioner::EnqueueCart::AddItem with correct arguments' do + add_item = class_double("SpreeCmCommissioner::EnqueueCart::AddItem") + allow(add_item).to receive(:call).and_return(true) + + stub_const("SpreeCmCommissioner::EnqueueCart::AddItem", add_item) + + job = described_class.new + allow(job).to receive(:job_id).and_return('test-job-id') + + job.perform(order_id, variant_id, quantity, public_metadata, private_metadata, options) + + expect(add_item).to have_received(:call).with( + order_id: order_id, + variant_id: variant_id, + quantity: quantity, + public_metadata: public_metadata, + private_metadata: private_metadata, + options: options, + job_id: 'test-job-id' + ) + end + end +end