Files
openfoodnetwork/app/controllers/admin/customers_controller.rb
Pau Perez d62ab06504 Refactor DB query to aggregate customer balance
It's simpler and many orders of magnitude more efficient to ask the DB
to aggregate the customer balance based on their orders. It removes
a nasty N+1.

The resulting SQL query is:

```sql
SELECT customers.*, SUM(spree_orders.total - spree_orders.payment_total) AS balance
FROM "customers"
INNER JOIN "spree_orders"
    ON "spree_orders"."customer_id" = "customers"."id"
WHERE "customers"."enterprise_id" = 1
    AND (completed_at IS NOT NULL)
    AND (state != 'canceled')
GROUP BY customers.id
ORDER BY email;
```
2021-01-11 15:50:19 +01:00

132 lines
4.1 KiB
Ruby

require 'open_food_network/address_finder'
module Admin
class CustomersController < Admin::ResourceController
before_action :load_managed_shops, only: :index, if: :html_request?
respond_to :json
respond_override update: { json: {
success: lambda {
tag_rule_mapping = TagRule.mapping_for(Enterprise.where(id: @customer.enterprise))
render_as_json @customer, tag_rule_mapping: tag_rule_mapping
},
failure: lambda { render json: { errors: @customer.errors.full_messages }, status: :unprocessable_entity }
} }
def index
respond_to do |format|
format.html
format.json do
render_as_json @collection,
tag_rule_mapping: tag_rule_mapping,
customer_tags: customer_tags_by_id
end
end
end
def show
render_as_json @customer, ams_prefix: params[:ams_prefix]
end
def create
@customer = Customer.new(customer_params)
if user_can_create_customer?
if @customer.save
tag_rule_mapping = TagRule.mapping_for(Enterprise.where(id: @customer.enterprise))
render_as_json @customer, tag_rule_mapping: tag_rule_mapping
else
render json: { errors: @customer.errors.full_messages }, status: :bad_request
end
else
redirect_to '/unauthorized'
end
end
# copy of Admin::ResourceController without flash notice
def destroy
invoke_callbacks(:destroy, :before)
if @object.destroy
invoke_callbacks(:destroy, :after)
respond_with(@object) do |format|
format.html { redirect_to location_after_destroy }
format.js { render partial: "spree/admin/shared/destroy" }
end
else
invoke_callbacks(:destroy, :fails)
respond_with(@object) do |format|
format.html { redirect_to location_after_destroy }
format.json { render json: { errors: @object.errors.full_messages }, status: :conflict }
end
end
end
private
def collection
return Customer.where("1=0") unless json_request? && params[:enterprise_id].present?
Customer.of(managed_enterprise_id).
includes(:bill_address, :ship_address, user: :credit_cards).
joins(:orders).
merge(Spree::Order.complete.not_state(:canceled)).
group("customers.id").
select("customers.*").
select("SUM(total - payment_total) AS balance_value")
end
def managed_enterprise_id
@managed_enterprise_id ||= Enterprise.managed_by(spree_current_user).
select('enterprises.id').find_by(id: params[:enterprise_id])
end
def load_managed_shops
@shops = Enterprise.managed_by(spree_current_user).is_distributor
end
def user_can_create_customer?
spree_current_user.admin? ||
spree_current_user.enterprises.include?(@customer.enterprise)
end
def ams_prefix_whitelist
[:subscription]
end
def customer_params
params.require(:customer).permit(
:enterprise_id, :name, :email, :code, :tag_list,
ship_address_attributes: PermittedAttributes::Address.attributes,
bill_address_attributes: PermittedAttributes::Address.attributes,
)
end
# Used in Admin::ResourceController#update
def permitted_resource_params
customer_params
end
def tag_rule_mapping
TagRule.mapping_for(Enterprise.where(id: managed_enterprise_id))
end
# Fetches tags for all customers of the enterprise and returns a hash indexed by customer_id
def customer_tags_by_id
customer_tags = ::ActsAsTaggableOn::Tag.
joins(:taggings).
includes(:taggings).
where(taggings:
{ taggable_type: 'Customer',
taggable_id: Customer.of(managed_enterprise_id),
context: 'tags' })
customer_tags.each_with_object({}) do |tag, indexed_hash|
tag.taggings.each do |tagging|
customer_id = tagging.taggable_id
indexed_hash[customer_id] ||= []
indexed_hash[customer_id] << tag.name
end
end
end
end
end