[RUBY] Create an EC site with Rails 5 ⑩ ~ Create an order function ~

Introduction

This is a continuation of the series "Creating an EC site with Rails 5 ⑨](https://qiita.com/GreenFingers_tk/items/3fd347dee907328d20e0), which creates an EC site where you can shop at a fictitious bakery. We will implement the Order model (order function), which is the main function and the most difficult part of this site.

The user's movement is as follows.

Put the product in the cart and press the "Proceed to order screen" button
↓
Enter order information(orders/new)screen
·Payment Method
·Addressee
And press the "Proceed to confirmation screen" button
↓
Confirm order information(orders/confirm)screen
Check the contents and press the "Confirm order" button
↓
orders/Show thanks screen (static page)

You can check the order history on the list and details screen.

The point is that you can select the delivery address to enter on the orders / new screen from "your address", "registered address", and "new address". When you select a new address and fill in the form on the new screen, it will be saved as Order data as well as new data in the Address model. Nesting forms and going through confirmation screens before saving that data are factors that make it difficult to implement this feature.

Source code

https://github.com/Sn16799/bakeryFUMIZUKI

Model association

fumizuki_ER.jpg

Controller

app/controllers/orders_controller.rb


class OrdersController < ApplicationController
  before_action :authenticate_customer!
  before_action :set_customer

  def index
    @orders = @customer.orders
  end

  def create
    if current_customer.cart_items.exists?
      @order = Order.new(order_params)
      @order.customer_id = current_customer.id

      #Adjust arguments according to address radio button selection
      @add = params[:order][:add].to_i
      case @add
        when 1
          @order.post_code = @customer.post_code
          @order.send_to_address = @customer.address
          @order.addressee = full_name(@customer)
        when 2
          @order.post_code = params[:order][:post_code]
          @order.send_to_address = params[:order][:send_to_address]
          @order.addressee = params[:order][:addressee]
        when 3
          @order.post_code = params[:order][:post_code]
          @order.send_to_address = params[:order][:send_to_address]
          @order.addressee = params[:order][:addressee]
      end
      @order.save

      # send_to_Address model search by address, create new if there is no corresponding data
      if Address.find_by(address: @order.send_to_address).nil?
        @address = Address.new
        @address.post_code = @order.post_code
        @address.address = @order.send_to_address
        @address.addressee = @order.addressee
        @address.customer_id = current_customer.id
        @address.save
      end

      # cart_Order the contents of items_Newly registered in items
      current_customer.cart_items.each do |cart_item|
        order_item = @order.order_items.build
        order_item.order_id = @order.id
        order_item.product_id = cart_item.product_id
        order_item.quantity = cart_item.quantity
        order_item.order_price = cart_item.product.price
        order_item.save
        cart_item.destroy #order_After moving the information to item, cart_item is erased
      end
      render :thanks
    else
      redirect_to customer_top_path
   flash[:danger] = 'The cart is empty.'
    end
  end

  def show
    @order = Order.find(params[:id])
    if @order.customer_id != current_customer.id
      redirect_back(fallback_location: root_path)
      flash[:alert] = "Access failed."
    end
  end

  def new
    @order = Order.new
  end

  def confirm
    @order = Order.new
    @cart_items = current_customer.cart_items
    @order.how_to_pay = params[:order][:how_to_pay]
    #Adjust arguments according to address radio button selection
    @add = params[:order][:add].to_i
    case @add
      when 1
        @order.post_code = @customer.post_code
        @order.send_to_address = @customer.address
        @order.addressee = @customer.family_name + @customer.first_name
      when 2
        @sta = params[:order][:send_to_address].to_i
        @send_to_address = Address.find(@sta)
        @order.post_code = @send_to_address.post_code
        @order.send_to_address = @send_to_address.address
        @order.addressee = @send_to_address.addressee
      when 3
        @order.post_code = params[:order][:new_add][:post_code]
        @order.send_to_address = params[:order][:new_add][:address]
        @order.addressee = params[:order][:new_add][:addressee]
    end
  end

  def thanks
  end

  private
  def set_customer
    @customer = current_customer
  end

  def order_params
    params.require(:order).permit(
      :created_at, :send_to_address, :addressee, :order_status, :how_to_pay, :post_code, :deliver_fee,
      order_items_attributes: [:order_id, :product_id, :quantity, :order_price, :make_status]
      )
  end

end

It would be nice if the information sent by form_with could be saved as it is as usual, but form_with cannot insert the confirmation screen. Therefore, the confirm and create actions retrieve the parameters received from view in the form of params [: hoge].

Also, in create, within one action, "** Create new Order data " " Move product data from CartItem to OrderItem ** (Create new OrderItem and delete registered CartItem)" ** Only when there is no matching data in the Address model, you have to do the three things of "new registration **". Each operation is not complicated, but the total amount of code is quite large.

View new

html:app/views/orders/.html.erb


<div class="col-lg-10 offset-1 space">

  <div class="row">
    <div class="col-lg-4">
      <h2>Enter order information</h2>
    </div>
  </div>

  <%= form_with(model: @order, local: true, url: {action: 'confirm'}) do |f| %>

  <!--Payment Method-->
  <div class="row space">
    <h3><strong><%= f.label :Payment Method%></strong></h3>
  </div>

  <div class="row">
    <div class="col-lg-4 btn-group" data-toggle="buttons">
      <label class="btn btn-outline-secondary active" style="width:50%">
        <%= f.radio_button :how_to_pay, true, {checked: true} %>credit card
      </label>
      <label class="btn btn-outline-secondary" style="width:50%">
        <%= f.radio_button :how_to_pay, false, {} %>Bank transfer
      </label>
    </div>
  </div>

  <!--Addressee-->
  <div class="row space">
    <h3><strong><%= f.label :Addressee%></strong></h3>
  </div>
  <!--Own address-->
  <div class="row">
    <p>
      <label><%= f.radio_button :add, 1, checked: true, checked: "checked" %>Your own address</label><br>
      <%= @customer.post_code %>
      <%= @customer.address %>
      <%= @customer.full_name %>
    </p>
  </div>

  <!--Registered address-->
  <div class="row space-sm">
    <p>
      <label><%= f.radio_button :add, 2, style: "display: inline-block" %>Select from registered address</label><br>
      <%= f.collection_select :send_to_address, @customer.addresses, :id, :address %>
    </p>
  </div>

  <!--New address-->
  <div class="row space-sm">
    <p><label><%= f.radio_button :add, 3 %>New destination</label></p>
  </div>
  <div class="row">
    <div class="col-lg-12">
      <%= f.fields_for :new_add do |na| %>
      <div class="row">
        <div class="col-lg-3">
          <strong>Postal code(no hyphen)</strong>
        </div>
        <div class="col-lg-6">
          <%= na.text_field :post_code, class: 'form-control' %>
        </div>
      </div>

      <div class="row">
        <div class="col-lg-3">
          <strong>Street address</strong>
        </div>
        <div class="col-lg-6">
          <%= na.text_field :address, class: 'form-control' %>
        </div>
      </div>

      <div class="row">
        <div class="col-lg-3">
          <strong>address</strong>
        </div>
        <div class="col-lg-6">
          <%= na.text_field :addressee, class: 'form-control' %>
        </div>
      </div>
      <% end %>
    </div>
  </div>
  <!--To here-->

  <div class="row space">
    <div class="col-lg-2 offset-lg-7">
      <%= f.submit "Proceed to confirmation screen", class: "btn btn-danger"%>
    </div>
  </div>

  <% end %>
</div>

confirm

html:app/views/orders/.html.erb


<div class="col-lg-10 offset-1 space">
  <div class="row">
    <h2>Confirm order information</h2>
  </div>

  <%= form_with(model: @order, local: true) do |f| %>

  <div class="d-none d-lg-block space">
    <div class="row">
      <div class="col-lg-5"><h4>Product name</h4></div>
      <div class="col-lg-2"><h4>Unit price (tax included)</h4></div>
      <div class="col-lg-2"><h4>quantity</h4></div>
      <div class="col-lg-2"><h4>subtotal</h4></div>
    </div>
  </div>

  <% sum_all = 0 %>
  <% @cart_items.each do |cart_item| %>
  <div class="row space-sm">
    <div class="col-lg-3">
      <%= link_to product_path(cart_item.product) do %>
      <%= attachment_image_tag(cart_item.product, :image, :fill, 100, 100, fallback: "no_img.jpg ") %>
      <% end %>
    </div>
    <div class="col-lg-2">
      <%= link_to product_path(cart_item.product) do %>
      <%= cart_item.product.name %>
      <% end %>
    </div>
    <div class="col-lg-2">
      <%= price_include_tax(cart_item.product.price) %>
    </div>
    <div class="col-lg-2">
      <%= cart_item.quantity %>
    </div>
    <div class="col-lg-2">
      <%= sum_product = price_include_tax(cart_item.product.price).to_i * cart_item.quantity %>Circle
      <% sum_all += sum_product %>
    </div>
  </div>
  <% end %>

  <div class="row space">
    <div class="col-lg-12">
      <div class="row">
        <div class="col-lg-3">
          <strong>Shipping</strong>
        </div>
        <div class="col-lg-3">
          <%= @order.deliver_fee %>Circle
        </div>
      </div>
      <div class="row">
        <div class="col-lg-3">
          <strong>Product total</strong>
        </div>
        <div class="col-lg-3">
          <%= sum_all.to_i %>Circle
        </div>
      </div>
      <div class="row">
        <div class="col-lg-3">
          <strong>Billed amount</strong>
        </div>
        <div class="col-lg-3">
          <% billling_amount = sum_all + @order.deliver_fee.to_i %>
          <%= billling_amount.to_i %>Circle
        </div>
      </div>
    </div>
  </div>

  <div class="row space-sm">
    <div class="col-lg-2">
      <h3>Payment Method</h3>
    </div>
    <div class="col-lg-4">
      <%= how_to_pay(@order.how_to_pay) %>
    </div>
  </div>

  <div class="row space-sm">
    <div class="col-lg-2">
      <h3>Addressee</h3>
    </div>
    <div class="col-lg-4">
      <%= @order.post_code %>
      <%= @order.send_to_address %>
      <%= @order.addressee %>
    </div>
  </div>

  <%= f.hidden_field :customer_id, :value => current_customer.id %>
  <%= f.hidden_field :post_code, :value => "#{@order.post_code}" %>
  <%= f.hidden_field :send_to_address, :value => "#{@order.send_to_address}" %>
  <%= f.hidden_field :addressee, :value => "#{@order.addressee}" %>
</div>

<div class="row space">
  <div class="col-lg-2 offset-lg-5">
    <%= f.submit "Confirm purchase", class: "btn btn-danger btn-lg" %>
  </div>
</div>


<% end %>
</div>

thanks

html:app/views/orders/.html.erb


<div class="col-lg-10 offset-1 space">
  <h2>Thank you for your purchase!</h2>
  <h3><%= link_to 'Return to TOP', customer_top_path %></h3>
</div>

This page transitions after pressing the "Confirm purchase" button. Since it is a static page and does not have such a function, it is good to put only simple words as described above, or to display a photo with a slider etc.

index

html:app/views/orders/.html.erb


<div class="col-lg-10 offset-1 space">

  <div class="row">
    <h2>Order history list</h2>
  </div>

  <div class="d-none d-lg-block space">
    <div class="row">
      <div class="col-lg-2">Order date</div>
      <div class="col-lg-3">Shipping address</div>
      <div class="col-lg-2">Ordered products</div>
      <div class="col-lg-2">payment</div>
      <div class="col-lg-1">Status</div>
      <div class="col-lg-2">Order details</div>
    </div>
  </div>

  <% @orders.each do |order| %>
  <div class="row space-sm">
    <div class="col-lg-2">
      <%= simple_time(order.created_at) %>
    </div>
    <div class="col-lg-3">
      <div class="row">
        <%= order.post_code + " " + order.send_to_address %>
      </div>
      <div class="row">
        <%= order.addressee %>
      </div>
    </div>
    <div class="col-lg-2">
      <% sum_all = 0 %>
      <% order.order_items.each do |order_item| %>
      <%= order_item.product.name %><br>
      <% sub_total = price_include_tax(order_item.order_price).to_i * order_item.quantity %>
      <% sum_all += sub_total.to_i %>
      <% end %>
    </div>
    <div class="col-lg-2">
      <%= sum_all += order.deliver_fee.to_i %>Circle
    </div>
    <div class="col-lg-1">
      <%= order_status(order) %>
    </div>
    <div class="col-lg-2">
      <%= link_to 'indicate', order_path(order), class: "btn btn-sm btn-danger" %>
    </div>
  </div>
  <% end %>

</div>

show

html:app/views/orders/.html.erb


<div class="col-lg-10 offset-1 space">
  <div class="row">
    <h2>Order history details</h2>
  </div>

  <div class="row">
    <div class="col-lg-7">
      <div class="row space">
        <h3>Order information</h3>
      </div>
      <div class="row">
        <div class="container">
          <div class="row space-sm">
            <div class="col-lg-3">
              <strong>Order date</strong>
            </div>
            <div class="col-lg-9">
              <%= simple_time(@order.created_at) %>
            </div>
          </div>
          <div class="row space-sm">
            <div class="col-lg-3">
              <strong>Shipping address</strong>
            </div>
            <div class="col-lg-9">
              <%= @order.send_to_address %>
            </div>
          </div>
          <div class="row space-sm">
            <div class="col-lg-3">
              <strong>Payment Method</strong>
            </div>
            <div class="col-lg-9">
              <%= how_to_pay(@order.how_to_pay) %>
            </div>
          </div>
          <div class="row space-sm">
            <div class="col-lg-3">
              <strong>Status</strong>
            </div>
            <div class="col-lg-9">
              <%= order_status(@order) %>
            </div>
          </div>
        </div>
      </div>

      <div class="row space">
        <h3>Order details</h3>
      </div>
      <div class="d-none d-lg-block">
        <div class="row">
          <div class="col-lg-4">
            <strong>Product</strong>
          </div>
          <div class="col-lg-3">
            <strong>Unit price (tax included)</strong>
          </div>
          <div class="col-lg-2">
            <strong>Quantity</strong>
          </div>
          <div class="col-lg-2">
            <strong>subtotal</strong>
          </div>
        </div>
      </div>
      <% sum_all = 0 %>
      <% @order.order_items.each do |order_item| %>
      <div class="row space-sm">
        <div class="col-lg-4">
          <%= order_item.product.name %>
        </div>
        <div class="col-lg-3">
          <%= price_include_tax(order_item.order_price) %>
        </div>
        <div class="col-lg-2">
          <%= order_item.quantity %>Pieces
        </div>
        <div class="col-lg-2">
          <%= sub_total = price_include_tax(order_item.order_price).to_i * order_item.quantity %>Circle
          <% sum_all += sub_total %>
        </div>
      </div>
      <% end %>
    </div>

    <div class="col-lg-5">
      <div class="row space">
        <h3>Billing information</h3>
      </div>
      <div class="row">
        <div class="container">
          <div class="row space-sm">
            <div class="col-lg-6">
              <strong>Product total</strong>
            </div>
            <div class="col-6">
              <%= sum_all %>Circle
            </div>
          </div>
          <div class="row space-sm">
            <div class="col-lg-6">
              <strong>Delivery charge</strong>
            </div>
            <div class="col-lg-6">
              <%= @order.deliver_fee %>Circle
            </div>
          </div>
          <div class="row space-sm">
            <div class="col-lg-6">
              <strong>Billed amount</strong>
            </div>
            <div class="col-lg-6">
              <%= sum_all + @order.deliver_fee.to_i %>Circle
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

orders_show.jpg

In the above figure, I would like to display the total amount of products in the "Billing information" column, but if I build the screen in order from the top, it will be necessary to go through a loop for "Billing information" and "Order details" twice. It will be a hassle. So, thinking that it is a sloppy method, divide the screen vertically into two with col, first create columns for "order information" and "order details", and then calculate the total amount in the "order details" loop. It is stored in a variable and called in the "billing information" field. (In the "Billing Information" column, I forgot to set the default value of the shipping fee in the DB, so the total product and the billed amount are the same amount. Originally, the shipping fee of 800 yen is the billed amount for the total product. It is a specification to be added, and the view code also corresponds to it.)

Postscript

There was only the central function of this EC site, and the amount of code was large and the content was complicated. In this implementation, while roughly following what I made in the school team implementation before, modifications such as newly defining helper and model methods to make the code easier to see and making the screen responsive Was added.

It is an additional note about the specification that address data is registered in Address at the same time as Order when a new address is selected in the create action. In the above description, the address column of the Address model is searched, and if there is no match, it is registered. For a more accurate search

unless Address.find_by(addressee: @order.addressee, address: @order.send_to_address).exists?

As such, you may want to make sure that both the address and address match. Although the code will be longer.

Anyway, this completes the functionality of the customer site. I'm happy.

Recommended Posts

Create an EC site with Rails 5 ⑩ ~ Create an order function ~
Create an EC site with Rails 5 ⑨ ~ Create a cart function ~
Create an EC site with Rails5 ⑦ ~ Address, Genre model ~
Create an EC site with Rails5 ④ ~ Header and footer ~
Create an EC site with Rails5 ⑥ ~ seed data input ~
Create an EC site with Rails5 ② ~ Bootstrap4 settings, Controller / action definition ~
[Rails] EC site cart function
Create an EC site with Rails5 ③-Set model associations and other things-
Create pagination function with Rails Kaminari
[Rails] Create an email sending function with ActionMailer (complete version)
[Rails] Create an evaluation function using raty.js
[Rails withdrawal] Create a simple withdrawal function with rails
Create an or search function with Ransack.
Create an EC site using stripe! (Account creation)
[Rails] Create an application
Introduced graph function with rails
Login function implementation with rails
Create an immutable class with JAVA
Create portfolio with rails + postgres sql
Create an app with Spring Boot 2
[Rails] DB design for EC site
Implemented mail sending function with rails
Create an excel file with poi
Create an app with Spring Boot
Create an app catalog site using CLI for Microsoft 365 with Docker
Rails Tutorial Extension: I tried to create an RSS feed function
(EC site) Validation when entering order information
Let's create an instance with .new yourself. .. ..
Create an infinite scroll with Infinite Scroll and kaminari
[Java] Create an executable module with Gradle
[Rails6] Create a new app with Rails [Beginner]
Easy deployment with Capistrano + AWS (EC2) + Rails
Create Rails 6 + MySQL environment with Docker compose
Make a login function with Rails anyway
[Rails 5] Create a new app with Rails [Beginner]
Make a site template easily with Rails
Create a SPA with authentication function with Rails API mode + devise_token_auth + Vue.js 3 (Rails edition)
Let's make an error screen with Rails
Nuxt.js × Create an application in Rails API mode
Downgrade an existing app created with rails 5.2.4 to 5.1.6
[Rails] rails new to create a database with PostgreSQL
Create an RSA encryption-enabled JSON API with wicket
Create a team chat with Rails Action Cable
Let's make a search function with Rails (ransack)
Rails6.0 ~ How to create an eco-friendly development environment
Create an Annotator that uses kuromoji with NLP4J [007]
[Swift] Create an image selection UI with PhotoKit
[Rails] How to build an environment with Docker
I tried to create a shopping site administrator function / screen with Java and Spring
[Rails 6] Ranking function
Rails follow function
[Rails] Notification function
[No.003] Create an order list screen for the orderer
[Rails] Implementation of drag and drop function (with effect)
How to push an app developed with Rails to Github
Create an HTTPS file server for development with ring-jetty-adapter
How to make an almost static page with rails
Create an E2E test environment with Docker x Cypress
Create a Service with an empty model Liferay 7.0 / DXP
Create a simple demo site with Spring Security with Spring Boot 2.1
[Rails] Create initial data with seed.rb [Faker] [Japanese localization]