Compare commits

...

1 Commits

5 changed files with 143 additions and 78 deletions

View File

@ -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

View File

@ -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
end

View File

@ -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

View File

@ -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

View File

@ -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