Solidus Brazilian Adaptations

Essa gem tem como objetivo adaptar os modelos do Solidus para se adequar aos padrões brasileiros, tais como a criação dos atributos CPF/CNPJ (tax_id) em Spree::Order, e número (number) do imóvel e bairro (district) em Spree::Address.

Installation

Add solidus_brazilian_adaptations to your Gemfile:

gem 'solidus_brazilian_adaptations'

Bundle your dependencies and run the installation generator:

bin/rails generate solidus_brazilian_adaptations:install

Usage

Tradução e moeda padrão

Editar config/initializers/spree.rb adicionando as seguintes configurações:

Mudar moeda e país padrão:

Spree.config do |config|
  # ...

  # Define BRL como moeda padrão
  config.currency = "BRL"
  # Preenche automaticamente o campo de pais que vamos remover do formulário de endereço:
  config.default_country_iso = "BR"

  # ...
end

Mudar idioma da interface do admin:

Spree::Backend::Config.configure do |config|
  # ...

  config.locale = 'pt-BR'

  # ...
end

Incluir no config/application.rb para definir os locales disponiveis:

module SuaLoja
  class Application < Rails::Application
    # ...

    config.i18n.available_locales = ['pt-BR']
    config.i18n.default_locale = 'pt-BR'

    # ...
  end
end

Seeds

Substituir Spree::Core::Engine.load_seed por SolidusBrazilianAdaptations::Engine.load_seed em db/seeds.rb para utilizar o seeds da gem.

Configurações

A partir do initializer da gem é possivel configurar se será permitido efetuar compras utilizando CNPJ. Por padrão é essa opção é true.

Storefront

Essa gem utiliza o starter frontend como base, então os exemplos serão feitos a partir de suas views.

Para adicionar os campos de CPF/CNPJ, número e bairro é possivel editar o form da etapa de endereço. Porém a edição é feita em duas partials diferentes:

Na partial app/views/checkouts/_checkout_step.html.erb o seguinte HTML referente ao CPF/CNPJ pode ser inserido logo abaixo ao input de e-mail:


<%= form_for order, url: update_checkout_path(order.state), html: { id: "checkout_form_#{order.state}" } do |form| %>
  <% if order.state == "address" || !order.email? %>
    <div class="text-input">
      <%= form.label :email, 'E-Mail:' %>
      <%= form.email_field :email, required: true, placeholder: '[email protected]' %>
    </div>

    <!-- Adicionar -->
    <% label = SolidusBrazilianAdaptations.config.allow_cnpj ? "CPF/CPNJ" : "CPF" %>
    <div class="text-input" style="margin-top: 4px;">
      <%= form.label :tax_id, "#{label}:" %>
      <%= form.text_field :tax_id, required: true, placeholder: label %>
    </div>
    <!-- ********* -->

  <% end %>

  <%= render "checkouts/steps/#{order.state}_step", form: form, differentiator: @differentiator %>
<% end %>

E na partial app/views/checkouts/steps/address_step/_address_inputs.html.erb adicionar o seguinte código referente ao número e bairro, podendo ser alterada a ordem dos campos:

  <!-- Adicionar -->
  <div class="text-input">
    <%= form.label :number, "Número:" %>
    <%= form.text_field :number, required: true %>
  </div>
  <!-- ********* -->

  <div class="text-input">
    <%= form.label :address2, "#{I18n.t("spree.street_address_2")}:" %>
    <%= form.text_field :address2, autocomplete: "#{address_type} address-line2" %>
  </div>

  <!-- Adicionar -->
  <div class="text-input">
    <%= form.label :district, "Bairro:" %>
    <%= form.text_field :district, required: true %>
  </div>
  <!-- ********* -->

  <div class="text-input">
    <%= form.label :city, "#{I18n.t("spree.city")}:" %>
    <%= form.text_field :city, required: true, autocomplete: "#{address_type} address-level2" %>
  </div>

Admin

Para visualizar os campos de CPF/CNPJ, número e bairro no painel do Admin na aba Order/Order_Number/Customer Details é necessário criar as seguintes views em seu projeto:

Essas novas views irão sobrescrever as padrões, localizadas na gem solidus_backend. Portanto, você deve ficar atento às atualizações do Solidus, pois ao sobrescrever as views as atualizações não serão aplicadas.

app/views/spree/admin/orders/customer_details/_form.html.erb

<fieldset data-hook="admin_customer_detail_form_fields" class="no-border-top">

  <fieldset class="index no-border-bottom" data-hook="customer_guest">
    <legend align="center"><%= t('spree.account') %></legend>

    <div data-hook="customer_fields" class="row">
      <div class="col-9">
        <div class="field">
          <%= f.label :email %>
          <%= f.email_field :email, required: true, class: 'fullwidth' %>
        </div>

        <!-- Adicionado -->
        <div class="field">
          <%= f.label :tax_id, "CPF/CNPJ" %>
          <%= f.text_field :tax_id, required: true, class: 'fullwidth' %>
        </div>
        <!-- ********* -->
      </div>
      <div class="col-3">
        <div class="field">
          <%= label_tag nil, t('spree.guest_checkout') %>
          <ul>
            <% if @order.completed? %>
              <li>
                <%= @order.user.nil? ? t('spree.say_yes') : t('spree.say_no') %>
              </li>
            <% else %>
              <% guest = @order.user.nil? %>
              <li>
                <label>
                  <%= radio_button_tag :guest_checkout, true, guest %>
                  <%= t('spree.say_yes') %>
                </label>
              </li>
              <li>
                <label>
                  <%= radio_button_tag :guest_checkout, false, !guest, disabled: @order.cart? %>
                  <%= t('spree.say_no') %>
                </label>
              </li>
              <%= hidden_field_tag :user_id, @order.user_id %>
            <% end %>
          </ul>
        </div>
      </div>
    </div>
  </fieldset>

  <div class="row">
    <% if Spree::Config[:order_bill_address_used] %>
      <div class="col-6" data-hook="bill_address_wrapper">
        <fieldset class="no-border-bottom">
          <legend align="center"><%= t('spree.billing_address') %></legend>
          <div class="js-billing-address">
            <%= f.fields_for :bill_address do |ba_form| %>
              <%= render partial: 'spree/admin/shared/address_form', locals: { f: ba_form, type: "billing" } %>
            <% end %>
          </div>
        </fieldset>
      </div>
    <% end %>

    <div class="col-6" data-hook="ship_address_wrapper">
      <fieldset class="no-border-bottom">
        <legend align="center"><%= t('spree.shipping_address') %></legend>
        <% if Spree::Config[:order_bill_address_used] %>
          <div class="field">
            <span data-hook="use_billing">
              <label>
                <%= check_box_tag 'order[use_billing]', '1', (@order.ship_address.new_record? && @order.bill_address == @order.ship_address) %>
                <%= t('spree.use_billing_address') %>
              </label>
            </span>
          </div>
        <% end %>

        <div class="js-shipping-address">
          <%= f.fields_for :ship_address do |ba_form| %>
            <%= render partial: 'spree/admin/shared/address_form', locals: { f: ba_form, type: "shipping" } %>
          <% end %>
        </div>
      </fieldset>
    </div>
  </div>

  <div class="clear"></div>

  <div class="form-buttons filter-actions actions" data-hook="buttons">
    <%= button_tag t('spree.actions.update'), class: 'btn btn-primary' %>
  </div>
</fieldset>

app/views/spree/admin/shared/_address_form.html.erb

<% s_or_b = type.chars.first %>

<div id="<%= type %>" data-hook="address_fields">
  <div class="field <%= "#{type}-row" %>">
    <%= f.label :name %>
    <%= f.text_field :name, class: 'fullwidth' %>
  </div>

  <% if Spree::Config[:company] %>
    <div class="field <%= "#{type}-row" %>">
      <%= f.label :company %>
      <%= f.text_field :company, class: 'fullwidth' %>
    </div>
  <% end %>

  <div class="field <%= "#{type}-row" %>">
    <%= f.label :address1 %>
    <%= f.text_field :address1, class: 'fullwidth' %>
  </div>

  <!-- Adicionado -->
  <div class="field <%= "#{type}-row" %>">
    <%= f.label :number, "Número" %>
    <%= f.text_field :number, class: 'fullwidth' %>
  </div>
  <!-- ********** -->

  <div class="field <%= "#{type}-row" %>">
    <%= f.label :address2 %>
    <%= f.text_field :address2, class: 'fullwidth' %>
  </div>

  <!-- Adicionado -->
  <div class="field <%= "#{type}-row" %>">
    <%= f.label :district, "Bairro" %>
    <%= f.text_field :district, class: 'fullwidth' %>
  </div>
  <!-- ********** -->

  <div class="field <%= "#{type}-row" %>">
    <%= f.label :city %>
    <%= f.text_field :city, class: 'fullwidth' %>
  </div>

  <div class="field <%= "#{type}-row" %>">
    <%= f.label :zipcode %>
    <%= f.text_field :zipcode, class: 'fullwidth' %>
  </div>

  <div class="field <%= "#{type}-row" %>">
    <%= f.label :country_id, Spree::Country.model_name.human %>
    <span id="<%= s_or_b %>country">
      <%= f.collection_select :country_id, available_countries, :id, :name, {}, {class: 'custom-select fullwidth js-country_id'} %>
    </span>
  </div>

  <div class="field <%= "#{type}-row" %>">
    <%= f.label :state_id, Spree::State.model_name.human %>
    <span id="<%= s_or_b %>state">
      <%= f.hidden_field :state_name, value: nil %>
      <% states = f.object.country.try(:states).nil? ? [] : f.object.country.states %>
      <%= f.text_field :state_name,
            style: "display: #{states.empty? ? 'block' : 'none' };",
            disabled: !states.empty?, class: 'fullwidth state_name js-state_name' %>
      <%= f.hidden_field :state_id, value: nil %>
      <%= f.collection_select :state_id,
            states.sort,
            :id, :name,
            { include_blank: true },
            { class: 'custom-select fullwidth js-state_id',
              style: "display: #{states.empty? ? 'none' : 'block' };",
              disabled: states.empty? } %>
    </span>
  </div>

  <div class="field <%= "#{type}-row" %>">
    <%= f.label :phone %>
    <%= f.phone_field :phone, class: 'fullwidth' %>
  </div>
</div>