Define an endpoint with a global summary of expenses and attendance
This commit is contained in:
parent
98c1c0d18c
commit
28f1daf1dc
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
|
module Expenses
|
||||||
class TotalQuery
|
class TotalQuery
|
||||||
|
private attr_reader :wedding
|
||||||
|
def initialize(wedding:)
|
||||||
|
@wedding = wedding
|
||||||
|
end
|
||||||
|
|
||||||
def call
|
def call
|
||||||
ActiveRecord::Base.connection.execute(query).first
|
ActiveRecord::Base.connection.execute(
|
||||||
|
ActiveRecord::Base.sanitize_sql_array([query, { wedding_id: wedding.id }])
|
||||||
|
).first
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@ -12,16 +19,10 @@ module Expenses
|
|||||||
<<~SQL
|
<<~SQL
|
||||||
WITH guest_count AS (#{guest_count_per_status}),
|
WITH guest_count AS (#{guest_count_per_status}),
|
||||||
expense_summary AS (#{expense_summary})
|
expense_summary AS (#{expense_summary})
|
||||||
SELECT expense_summary.fixed,
|
SELECT guest_count.confirmed as confirmed_guests,
|
||||||
expense_summary.fixed_count,
|
guest_count.projected as projected_guests,
|
||||||
expense_summary.variable,
|
expense_summary.fixed + expense_summary.variable * guest_count.confirmed as total_confirmed,
|
||||||
expense_summary.variable_count,
|
expense_summary.fixed + expense_summary.variable * guest_count.projected as total_projected
|
||||||
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
|
|
||||||
FROM guest_count, expense_summary;
|
FROM guest_count, expense_summary;
|
||||||
SQL
|
SQL
|
||||||
end
|
end
|
||||||
@ -29,20 +30,19 @@ module Expenses
|
|||||||
def expense_summary
|
def expense_summary
|
||||||
<<~SQL
|
<<~SQL
|
||||||
SELECT coalesce(sum(amount) filter (where pricing_type = 'fixed'), 0) as fixed,
|
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(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
|
|
||||||
FROM expenses
|
FROM expenses
|
||||||
|
WHERE wedding_id = :wedding_id
|
||||||
SQL
|
SQL
|
||||||
end
|
end
|
||||||
|
|
||||||
def guest_count_per_status
|
def guest_count_per_status
|
||||||
<<~SQL
|
<<~SQL
|
||||||
SELECT COALESCE(count(*) filter(where status = #{Guest.statuses["confirmed"]}), 0) as confirmed,
|
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
|
COALESCE(count(*) filter(where status IN (#{Guest.statuses.values_at('confirmed', 'invited', 'tentative').join(',')})), 0) as projected
|
||||||
FROM guests
|
FROM guests
|
||||||
|
WHERE wedding_id = :wedding_id
|
||||||
SQL
|
SQL
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -31,6 +31,7 @@ Rails.application.routes.draw do
|
|||||||
get :summary, on: :collection
|
get :summary, on: :collection
|
||||||
end
|
end
|
||||||
resources :tables_arrangements, only: %i[index show]
|
resources :tables_arrangements, only: %i[index show]
|
||||||
|
resources :summary, only: :index
|
||||||
|
|
||||||
root to: redirect("/%{slug}")
|
root to: redirect("/%{slug}")
|
||||||
end
|
end
|
||||||
|
@ -5,88 +5,66 @@ require 'rails_helper'
|
|||||||
module Expenses
|
module Expenses
|
||||||
RSpec.describe TotalQuery do
|
RSpec.describe TotalQuery do
|
||||||
describe '#call' do
|
describe '#call' do
|
||||||
let(:response) { described_class.new.call }
|
let(:wedding) { create(:wedding) }
|
||||||
|
let(:response) { described_class.new(wedding:).call }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
create_list(:guest, 2, status: :confirmed)
|
create_list(:guest, 2, wedding:, status: :confirmed)
|
||||||
create_list(:guest, 3, status: :considered)
|
create_list(:guest, 3, wedding:, status: :considered)
|
||||||
create_list(:guest, 4, status: :invited)
|
create_list(:guest, 4, wedding:, status: :invited)
|
||||||
create_list(:guest, 5, status: :tentative)
|
create_list(:guest, 5, wedding:, status: :tentative)
|
||||||
create_list(:guest, 6, status: :declined)
|
create_list(:guest, 6, wedding:, status: :declined)
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when there is no expense" do
|
context 'when there is no expense' do
|
||||||
it "returns zero in all values", :aggregate_failures do
|
it 'returns zero in all values', :aggregate_failures do
|
||||||
expect(response["fixed"]).to be_zero
|
expect(response['total_confirmed']).to be_zero
|
||||||
expect(response["fixed_count"]).to be_zero
|
expect(response['total_projected']).to be_zero
|
||||||
expect(response["variable"]).to be_zero
|
expect(response['confirmed_guests']).to eq(2)
|
||||||
expect(response["variable_count"]).to be_zero
|
expect(response['projected_guests']).to eq(2 + 4 + 5)
|
||||||
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)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when there are only fixed expenses" do
|
context 'when there are only fixed expenses' do
|
||||||
before do
|
before do
|
||||||
create(:expense, :fixed, amount: 100)
|
create(:expense, :fixed, wedding:, amount: 100)
|
||||||
create(:expense, :fixed, amount: 200)
|
create(:expense, :fixed, wedding:, amount: 200)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns the sum of fixed expenses", :aggregate_failures do
|
it 'returns the sum of fixed expenses', :aggregate_failures do
|
||||||
expect(response["fixed"]).to eq(300)
|
expect(response['total_confirmed']).to eq(300)
|
||||||
expect(response["fixed_count"]).to eq(2)
|
expect(response['total_projected']).to eq(300)
|
||||||
expect(response["variable"]).to be_zero
|
expect(response['confirmed_guests']).to eq(2)
|
||||||
expect(response["variable_count"]).to be_zero
|
expect(response['projected_guests']).to eq(2 + 4 + 5)
|
||||||
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)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when there are only variable expenses" do
|
context 'when there are only variable expenses' do
|
||||||
before do
|
before do
|
||||||
create(:expense, :per_person, amount: 100)
|
create(:expense, :per_person, wedding:, amount: 100)
|
||||||
create(:expense, :per_person, amount: 200)
|
create(:expense, :per_person, wedding:, amount: 200)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns zero in the values and nonzero in the count", :aggregate_failures do
|
it 'returns zero in the values and nonzero in the count', :aggregate_failures do
|
||||||
expect(response["fixed"]).to be_zero
|
expect(response['total_confirmed']).to eq(2 * 300)
|
||||||
expect(response["fixed_count"]).to be_zero
|
expect(response['total_projected']).to eq(11 * 300)
|
||||||
expect(response["variable"]).to eq(300)
|
expect(response['confirmed_guests']).to eq(2)
|
||||||
expect(response["variable_count"]).to eq(2)
|
expect(response['projected_guests']).to eq(2 + 4 + 5)
|
||||||
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)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when there are both fixed and variable expenses" do
|
context 'when there are both fixed and variable expenses' do
|
||||||
before do
|
before do
|
||||||
create(:expense, :fixed, amount: 100)
|
create(:expense, :fixed, wedding:, amount: 100)
|
||||||
create(:expense, :fixed, amount: 200)
|
create(:expense, :fixed, wedding:, amount: 200)
|
||||||
create(:expense, :per_person, amount: 50)
|
create(:expense, :per_person, wedding:, amount: 50)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns the sum of fixed and variable expenses", :aggregate_failures do
|
it 'returns the sum of fixed and variable expenses', :aggregate_failures do
|
||||||
expect(response["fixed"]).to eq(300)
|
expect(response['total_confirmed']).to eq(100 + 200 + 50 * 2)
|
||||||
expect(response["fixed_count"]).to eq(2)
|
expect(response['total_projected']).to eq(100 + 200 + 11 * 50)
|
||||||
expect(response["variable"]).to eq(50)
|
expect(response['confirmed_guests']).to eq(2)
|
||||||
expect(response["variable_count"]).to eq(1)
|
expect(response['projected_guests']).to eq(2 + 4 + 5)
|
||||||
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)
|
|
||||||
end
|
end
|
||||||
end
|
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