Define an endpoint with a global summary of expenses and attendance
This commit is contained in:
parent
71eecf94a6
commit
da8f3c7618
27
app/controllers/summary_controller.rb
Normal file
27
app/controllers/summary_controller.rb
Normal 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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
59
spec/requests/summary_spec.rb
Normal file
59
spec/requests/summary_spec.rb
Normal 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
|
Loading…
x
Reference in New Issue
Block a user