From b9b0aa6a904c80a80952961162b0b5190a95773f Mon Sep 17 00:00:00 2001 From: Hongtien2910 Date: Fri, 1 Nov 2024 00:43:56 +0700 Subject: [PATCH] api_order --- app/controllers/admin/orders_controller.rb | 2 +- .../api/v1/admin/orders_controller.rb | 136 ++++++++++++++++++ app/controllers/api/v1/orders_controller.rb | 106 ++++++++++++++ app/models/order.rb | 2 +- app/serializers/order_serializer.rb | 25 ++++ config/routes.rb | 12 ++ 6 files changed, 281 insertions(+), 2 deletions(-) create mode 100644 app/controllers/api/v1/admin/orders_controller.rb create mode 100644 app/controllers/api/v1/orders_controller.rb create mode 100644 app/serializers/order_serializer.rb diff --git a/app/controllers/admin/orders_controller.rb b/app/controllers/admin/orders_controller.rb index 70e4f5d..067e7f0 100644 --- a/app/controllers/admin/orders_controller.rb +++ b/app/controllers/admin/orders_controller.rb @@ -46,7 +46,7 @@ def batch_update private def update_orders order_ids, status - orders = Order.where(id: order_ids) + orders = Order.by_ids(order_ids) orders.each do |order| if order.cancelled? diff --git a/app/controllers/api/v1/admin/orders_controller.rb b/app/controllers/api/v1/admin/orders_controller.rb new file mode 100644 index 0000000..322015e --- /dev/null +++ b/app/controllers/api/v1/admin/orders_controller.rb @@ -0,0 +1,136 @@ +class Api::V1::Admin::OrdersController < Api::V1::ApplicationController + before_action :find_order, only: %i(show update) + + def index + @q = Order.ransack(params[:q]) + @orders = filtered_orders(@q.result.order(id: :desc)) + render json: @orders, each_serializer: OrderSerializer, status: :ok + end + + def show + render json: @order, serializer: OrderSerializer, status: :ok + end + + def update + ActiveRecord::Base.transaction do + handle_cancel_order if cancel_reason_present? + @order.update!(order_params) + + if params[:order][:address].present? && @order.address.present? + @order.address.update!(address_params) + end + + OrderEmailJob.perform_later(@order, @order.user, :update) + render json: {message: I18n.t("admin.orders_admin.update.success")}, + status: :ok + rescue ActiveRecord::RecordInvalid => e + handle_update_error(e) + end + end + + def batch_update + order_ids = order_ids_param + status = status_param + + ActiveRecord::Base.transaction do + update_orders(order_ids, status) + end + + render json: {message: I18n.t("admin.orders_admin.batch_update.success")}, + status: :ok + rescue ActiveRecord::RecordInvalid => e + handle_batch_update_error(e) + end + + private + + def update_orders order_ids, status + orders = Order.by_ids(order_ids) + + orders.each do |order| + if order.cancelled? + add_cancelled_order_alert(order) + else + order.update!(status:) + OrderEmailJob.perform_later(order, order.user, :update) + end + end + end + + def add_cancelled_order_alert order + flash[:alert] ||= "" + flash[:alert] += "#{t('admin.orders_admin.batch_update.cancelled_order', + order_id: order.id)} " + end + + def handle_batch_update_error exception + render json: { + error: t("admin.orders_admin.batch_update.error", + errors: exception.record.errors.full_messages.join(", ")) + }, status: :unprocessable_entity + end + + def order_ids_param + params[:order_ids] || [] + end + + def status_param + params[:status] + end + + def find_order + @order = Order.find_by(id: params[:id]) + return if @order + + render json: {error: I18n.t("admin.orders_admin.not_found")}, + status: :not_found + end + + def order_params + params.require(:order).permit(Order::UPDATE_ORDER_ADMIN) + end + + def address_params + params.require(:order).require(:address) + .permit(Address::ADDRESS_REQUIRE_ATTRIBUTES) + end + + def cancel_reason_present? + params[:order][:cancel_reason].present? + end + + def handle_cancel_order + @order.cancel_reason = params[:order][:cancel_reason] + @order.cancelled! + restore_product_stock + end + + def restore_product_stock + @order.order_items.each do |order_item| + order_item.product.increment!(:stock, order_item.quantity) + end + end + + def handle_update_error exception + render json: { + error: if exception.record == @order + t("admin.orders_admin.update.error_with_order", + errors: order_errors_message) + else + t("admin.orders_admin.update.error_with_address") + end + }, status: :unprocessable_entity + end + + def order_errors_message + @order.errors.full_messages.join(", ") + end + + def filtered_orders orders + if params[:status].present? + orders.by_status(params[:status]) + else + orders + end + end +end diff --git a/app/controllers/api/v1/orders_controller.rb b/app/controllers/api/v1/orders_controller.rb new file mode 100644 index 0000000..0b3fe60 --- /dev/null +++ b/app/controllers/api/v1/orders_controller.rb @@ -0,0 +1,106 @@ +class Api::V1::OrdersController < Api::V1::ApplicationController + before_action :authenticate_user! + before_action :load_cart_items, only: [:create] + before_action :find_order, only: [:show, :cancel] + before_action :load_user, :correct_user, only: [:index, :cancel] + + def index + @orders = @user.orders.with_status(params[:status]).order(id: :asc) + render json: @orders, each_serializer: OrderSerializer, status: :ok + end + + def show + render json: @order, status: :ok + end + + def create + @order = Order.new(order_params) + @order.user = current_user + @order.total = calculate_cart_total + + if @order.save + save_order_items(@order) + clear_cart + render json: @order, status: :created + else + Rails.logger.debug("Order errors: #{@order.errors.full_messages}") + render json: {error: I18n.t("orders.create_failed")}, + status: :unprocessable_entity + end + end + + def cancel + cancel_reason = params.dig(:order, :cancel_reason) + + if @order.pending? && cancel_reason.present? + @order.update(cancel_reason:, status: "cancelled") + update_cart_items(@order) + render json: @order, status: :ok + else + render json: {error: I18n.t("orders.cancel_failed")}, + status: :unprocessable_entity + end + end + + private + + def load_cart_items + @cart_items = current_cart_items + end + + def current_cart_items + current_user.cart.cart_items.includes(product: :category) + end + + def find_order + @order = Order.find_by(id: params[:id]) + return if @order + + render json: {error: I18n.t("orders.not_found")}, status: :not_found + end + + def order_params + params.require(:order).permit(:address_id, :payment_method) + end + + def calculate_cart_total + @cart_items.sum{|item| item.product.price * item.quantity} + end + + def save_order_items order + @cart_items.each do |item| + order.order_items.create( + product_id: item.product_id, + price: item.product.price, + quantity: item.quantity + ) + item.product.decrement!(:stock, item.quantity) + end + end + + def clear_cart + current_user.cart.cart_items.destroy_all + end + + def update_cart_items order + cart = current_user.cart || current_user.create_cart + order.order_items.each do |order_item| + cart_item = cart.cart_items.find_by(product_id: order_item.product_id) + + if cart_item + increment_cart_item_quantity(cart_item, order_item) + else + create_new_cart_item(cart, order_item) + end + Product.find(order_item.product_id).increment!(:stock, + order_item.quantity) + end + end + + def load_user + @user = User.find(params[:user_id]) + return if @user + + render json: {error: I18n.t("users.not_found")}, status: :not_found + end +end diff --git a/app/models/order.rb b/app/models/order.rb index a3b8f2a..0038e90 100644 --- a/app/models/order.rb +++ b/app/models/order.rb @@ -24,7 +24,7 @@ class Order < ApplicationRecord after_create :send_order_notification scope :ordered_by_updated_at, ->{order(updated_at: :desc)} - + scope :by_ids, ->(ids){where(id: ids) if ids.present?} scope :search, lambda {|query| if query.present? joins(:user, :address) diff --git a/app/serializers/order_serializer.rb b/app/serializers/order_serializer.rb new file mode 100644 index 0000000..625a813 --- /dev/null +++ b/app/serializers/order_serializer.rb @@ -0,0 +1,25 @@ +class OrderSerializer < ActiveModel::Serializer + attributes :id, :status, :total, :cancel_reason, :created_at, :updated_at + + has_one :user + has_many :order_items + + def user + { + id: object.user.id, + name: object.user.user_name, + email: object.user.email + } + end + + def order_items + object.order_items.map do |order_item| + { + product_id: order_item.product.id, + product_name: order_item.product.name, + quantity: order_item.quantity, + price: order_item.price + } + end + end +end diff --git a/config/routes.rb b/config/routes.rb index 442fb38..4a84e75 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -81,6 +81,11 @@ namespace :v1 do namespace :admin do resources :products, only: %i(index show create update destroy) + resources :orders, only: %i(index show update) do + collection do + patch :batch_update + end + end end post "login", to: "auths#login" resources :carts, only: :show do @@ -91,6 +96,13 @@ delete "remove_item" end end + resources :users do + resources :orders, only: [:index, :show, :create, :update] do + member do + patch :cancel + end + end + end end end end