diff --git a/app/controllers/summary_controller.rb b/app/controllers/summary_controller.rb new file mode 100644 index 0000000..83cfea8 --- /dev/null +++ b/app/controllers/summary_controller.rb @@ -0,0 +1,27 @@ +class SummaryController < ApplicationController + def index + expense_summary = Expenses::TotalQuery.new(wedding: ActsAsTenant.current_tenant).call + render json: { + expenses: { + projected: { + total: expense_summary['total_projected'], + guests: expense_summary['projected_guests'] + }, + confirmed: { + total: expense_summary['total_confirmed'], + guests: expense_summary['confirmed_guests'] + }, + status: { + paid: 0 + } + }, + guests: { + total: 200, + confirmed: 100, + declined: 50, + tentative: 25, + invited: 25 + } + } + end +end diff --git a/app/queries/expenses/total_query.rb b/app/queries/expenses/total_query.rb index 5ed4ca9..7dc47d6 100644 --- a/app/queries/expenses/total_query.rb +++ b/app/queries/expenses/total_query.rb @@ -2,8 +2,15 @@ module Expenses class TotalQuery + private attr_reader :wedding + def initialize(wedding:) + @wedding = wedding + end + def call - ActiveRecord::Base.connection.execute(query).first + ActiveRecord::Base.connection.execute( + ActiveRecord::Base.sanitize_sql_array([query, { wedding_id: wedding.id }]) + ).first end private @@ -12,16 +19,10 @@ module Expenses <<~SQL WITH guest_count AS (#{guest_count_per_status}), expense_summary AS (#{expense_summary}) - SELECT expense_summary.fixed, - expense_summary.fixed_count, - expense_summary.variable, - expense_summary.variable_count, - expense_summary.total_count, - guest_count.confirmed as confirmed_guests, - guest_count.projected as projected_guests, - expense_summary.fixed + expense_summary.variable * guest_count.confirmed as total, - expense_summary.fixed + expense_summary.variable * guest_count.projected as max_projected, - (expense_summary.fixed + expense_summary.variable * guest_count.confirmed) / guest_count.confirmed as per_person + SELECT guest_count.confirmed as confirmed_guests, + guest_count.projected as projected_guests, + expense_summary.fixed + expense_summary.variable * guest_count.confirmed as total_confirmed, + expense_summary.fixed + expense_summary.variable * guest_count.projected as total_projected FROM guest_count, expense_summary; SQL end @@ -29,20 +30,19 @@ module Expenses def expense_summary <<~SQL SELECT coalesce(sum(amount) filter (where pricing_type = 'fixed'), 0) as fixed, - coalesce(count(amount) filter (where pricing_type = 'fixed'), 0) as fixed_count, - coalesce(sum(amount) filter (where pricing_type = 'per_person'), 0) as variable, - coalesce(count(amount) filter (where pricing_type = 'per_person'), 0) as variable_count, - count(*) as total_count + coalesce(sum(amount) filter (where pricing_type = 'per_person'), 0) as variable FROM expenses + WHERE wedding_id = :wedding_id SQL end def guest_count_per_status <<~SQL - SELECT COALESCE(count(*) filter(where status = #{Guest.statuses["confirmed"]}), 0) as confirmed, - COALESCE(count(*) filter(where status IN (#{Guest.statuses.values_at("confirmed", "invited", "tentative").join(",")})), 0) as projected + SELECT COALESCE(count(*) filter(where status = #{Guest.statuses['confirmed']}), 0) as confirmed, + COALESCE(count(*) filter(where status IN (#{Guest.statuses.values_at('confirmed', 'invited', 'tentative').join(',')})), 0) as projected FROM guests + WHERE wedding_id = :wedding_id SQL end end -end \ No newline at end of file +end diff --git a/config/routes.rb b/config/routes.rb index 6b88b41..c3bc335 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -31,6 +31,7 @@ Rails.application.routes.draw do get :summary, on: :collection end resources :tables_arrangements, only: %i[index show] + resources :summary, only: :index root to: redirect("/%{slug}") end diff --git a/spec/queries/expenses/total_query_spec.rb b/spec/queries/expenses/total_query_spec.rb index 46cffdf..f721844 100644 --- a/spec/queries/expenses/total_query_spec.rb +++ b/spec/queries/expenses/total_query_spec.rb @@ -5,88 +5,66 @@ require 'rails_helper' module Expenses RSpec.describe TotalQuery do describe '#call' do - let(:response) { described_class.new.call } + let(:wedding) { create(:wedding) } + let(:response) { described_class.new(wedding:).call } before do - create_list(:guest, 2, status: :confirmed) - create_list(:guest, 3, status: :considered) - create_list(:guest, 4, status: :invited) - create_list(:guest, 5, status: :tentative) - create_list(:guest, 6, status: :declined) + create_list(:guest, 2, wedding:, status: :confirmed) + create_list(:guest, 3, wedding:, status: :considered) + create_list(:guest, 4, wedding:, status: :invited) + create_list(:guest, 5, wedding:, status: :tentative) + create_list(:guest, 6, wedding:, status: :declined) end - context "when there is no expense" do - it "returns zero in all values", :aggregate_failures do - expect(response["fixed"]).to be_zero - expect(response["fixed_count"]).to be_zero - expect(response["variable"]).to be_zero - expect(response["variable_count"]).to be_zero - expect(response["total"]).to be_zero - expect(response["total_count"]).to be_zero - expect(response["max_projected"]).to be_zero - expect(response["per_person"]).to be_zero - expect(response["confirmed_guests"]).to eq(2) - expect(response["projected_guests"]).to eq(2 + 4 + 5) + context 'when there is no expense' do + it 'returns zero in all values', :aggregate_failures do + expect(response['total_confirmed']).to be_zero + expect(response['total_projected']).to be_zero + expect(response['confirmed_guests']).to eq(2) + expect(response['projected_guests']).to eq(2 + 4 + 5) end end - context "when there are only fixed expenses" do + context 'when there are only fixed expenses' do before do - create(:expense, :fixed, amount: 100) - create(:expense, :fixed, amount: 200) + create(:expense, :fixed, wedding:, amount: 100) + create(:expense, :fixed, wedding:, amount: 200) end - it "returns the sum of fixed expenses", :aggregate_failures do - expect(response["fixed"]).to eq(300) - expect(response["fixed_count"]).to eq(2) - expect(response["variable"]).to be_zero - expect(response["variable_count"]).to be_zero - expect(response["total"]).to eq(300) - expect(response["total_count"]).to eq(2) - expect(response["max_projected"]).to eq(300) - expect(response["per_person"]).to eq(150) - expect(response["confirmed_guests"]).to eq(2) - expect(response["projected_guests"]).to eq(2 + 4 + 5) + it 'returns the sum of fixed expenses', :aggregate_failures do + expect(response['total_confirmed']).to eq(300) + expect(response['total_projected']).to eq(300) + expect(response['confirmed_guests']).to eq(2) + expect(response['projected_guests']).to eq(2 + 4 + 5) end end - context "when there are only variable expenses" do + context 'when there are only variable expenses' do before do - create(:expense, :per_person, amount: 100) - create(:expense, :per_person, amount: 200) + create(:expense, :per_person, wedding:, amount: 100) + create(:expense, :per_person, wedding:, amount: 200) end - it "returns zero in the values and nonzero in the count", :aggregate_failures do - expect(response["fixed"]).to be_zero - expect(response["fixed_count"]).to be_zero - expect(response["variable"]).to eq(300) - expect(response["variable_count"]).to eq(2) - expect(response["total"]).to eq(2*300) - expect(response["total_count"]).to eq(2) - expect(response["max_projected"]).to eq(11*300) - expect(response["confirmed_guests"]).to eq(2) - expect(response["projected_guests"]).to eq(2 + 4 + 5) + it 'returns zero in the values and nonzero in the count', :aggregate_failures do + expect(response['total_confirmed']).to eq(2 * 300) + expect(response['total_projected']).to eq(11 * 300) + expect(response['confirmed_guests']).to eq(2) + expect(response['projected_guests']).to eq(2 + 4 + 5) end end - context "when there are both fixed and variable expenses" do + context 'when there are both fixed and variable expenses' do before do - create(:expense, :fixed, amount: 100) - create(:expense, :fixed, amount: 200) - create(:expense, :per_person, amount: 50) + create(:expense, :fixed, wedding:, amount: 100) + create(:expense, :fixed, wedding:, amount: 200) + create(:expense, :per_person, wedding:, amount: 50) end - it "returns the sum of fixed and variable expenses", :aggregate_failures do - expect(response["fixed"]).to eq(300) - expect(response["fixed_count"]).to eq(2) - expect(response["variable"]).to eq(50) - expect(response["variable_count"]).to eq(1) - expect(response["total"]).to eq(100 + 200 + 50 * 2) - expect(response["total_count"]).to eq(3) - expect(response["max_projected"]).to eq(100 + 200 + 11*50) - expect(response["per_person"]).to eq(200) - expect(response["confirmed_guests"]).to eq(2) - expect(response["projected_guests"]).to eq(2 + 4 + 5) + it 'returns the sum of fixed and variable expenses', :aggregate_failures do + expect(response['total_confirmed']).to eq(100 + 200 + 50 * 2) + expect(response['total_projected']).to eq(100 + 200 + 11 * 50) + expect(response['confirmed_guests']).to eq(2) + expect(response['projected_guests']).to eq(2 + 4 + 5) end end end diff --git a/spec/requests/summary_spec.rb b/spec/requests/summary_spec.rb new file mode 100644 index 0000000..0b3cc3a --- /dev/null +++ b/spec/requests/summary_spec.rb @@ -0,0 +1,59 @@ +require 'swagger_helper' + +RSpec.describe 'summary', type: :request do + path '/{slug}/summary' do + get('list summaries') do + tags 'Summary' + produces 'application/json' + consumes 'application/json' + parameter Swagger::Schema::SLUG + response(200, 'successful') do + schema type: :object, + required: %i[expenses guests], + properties: { + expenses: { + type: :object, + required: %i[projected confirmed status], + properties: { + projected: { + type: :object, + required: %i[total guests], + properties: { + total: { type: :number }, + guests: { type: :number } + } + }, + confirmed: { + type: :object, + required: %i[total guests], + properties: { + total: { type: :number }, + guests: { type: :number } + } + }, + status: { + type: :object, + required: [:paid], + properties: { + paid: { type: :number } + } + } + } + }, + guests: { + type: :object, + required: %i[total confirmed declined tentative invited], + properties: { + total: { type: :number }, + confirmed: { type: :number }, + declined: { type: :number }, + tentative: { type: :number }, + invited: { type: :number } + } + } + } + xit + end + end + end +end