diff --git a/app/controllers/budgets_controller.rb b/app/controllers/budgets_controller.rb new file mode 100644 index 0000000..db6621a --- /dev/null +++ b/app/controllers/budgets_controller.rb @@ -0,0 +1,33 @@ +require 'csv' + +class BudgetsController < ApplicationController + skip_before_action :verify_authenticity_token + + def process_budget + user = User.find(params[:user_id]) + budget.process_budget + + Turbo::StreamsChannel.broadcast_replace_to( + user, + target: "spend_forecast", + partial: "spend_forecasts/form", + locals: { user:, spend_forecast: budget.spend_forecast_from_budget} + ) + end + + private + + def user + @user ||= User.find(params[:user_id]) + end + + def budget + @budget ||= Budget.new( + budget_contents: params[:budget_file_contents], + start_date: Date.parse(params[:start_date]), + end_date: Date.parse(params[:end_date]), + channel_daily_spend_limit: params[:channel_daily_spend_limit].to_i, + user: + ) + end +end diff --git a/app/controllers/spend_forecasts_controller.rb b/app/controllers/spend_forecasts_controller.rb index dac6059..4f71b49 100644 --- a/app/controllers/spend_forecasts_controller.rb +++ b/app/controllers/spend_forecasts_controller.rb @@ -24,6 +24,22 @@ def update end end + def update + if spend_forecast.update(spend_forecast_params) + redirect_to spend_forecast_path(spend_forecast) + else + render :new, status: :unprocessable_entity + end + end + + def create + if spend_forecast.update(spend_forecast_params) + redirect_to spend_forecast_path(spend_forecast) + else + render :new, status: :unprocessable_entity + end + end + def show;end def download_budget_csv @@ -43,13 +59,6 @@ def set_spend_forecast end def spend_forecast_params - temp_params = params.require(:spend_forecast).permit(:name, :start_date, :end_date) - temp_params.merge!(budget: process_csv(params[:spend_forecast][:budget_file])) if params[:spend_forecast][:budget_file] - temp_params - end - - def process_csv(file) - require 'csv' - CSV.read(file.path, headers: true) + params.require(:spend_forecast).permit(:name, :start_date, :end_date, :channel_daily_spend_limit, :budget) end end diff --git a/app/javascript/controllers/budget_file_controller.js b/app/javascript/controllers/budget_file_controller.js new file mode 100644 index 0000000..f6049a2 --- /dev/null +++ b/app/javascript/controllers/budget_file_controller.js @@ -0,0 +1,43 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ["input", "form"]; + + connect() { + this.inputTarget.addEventListener("change", this.uploadFile.bind(this)); + } + + uploadFile() { + const file = this.inputTarget.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (event) => { + const fileContents = event.target.result; + this.sendFileContents(fileContents); + }; + reader.readAsText(file); + } + } + + sendFileContents(contents) { + const url = '/process_budget'; + const formData = new FormData(); + formData.append("budget_file_contents", contents); + formData.append("user_id", this.formTarget.dataset.budgetFileUserId); + formData.append("start_date", document.getElementById("start_date").value); + formData.append("end_date", document.getElementById("end_date").value); + formData.append("channel_daily_spend_limit", document.getElementById("channel_daily_spend_limit").value); + + fetch(url, { + method: "POST", + body: formData, + headers: { + "Accept": "application/json" + } + }); + } + + deleteFile() { + this.inputTarget.value = ""; + } +} diff --git a/app/models/budget.rb b/app/models/budget.rb new file mode 100644 index 0000000..f222c36 --- /dev/null +++ b/app/models/budget.rb @@ -0,0 +1,31 @@ +class Budget + attr_reader :processed_budget, :rows_lost, :start_date, :end_date, :channel_daily_spend_limit, :rows_lost, :user + + def initialize(budget_contents:, start_date:, end_date:, channel_daily_spend_limit:, user:) + @budget_contents = CSV.parse(budget_contents, headers: true) + @start_date = start_date + @end_date = end_date + @channel_daily_spend_limit = channel_daily_spend_limit.to_i + @user = user + end + + def spend_forecast_from_budget + user.spend_forecasts.new(start_date:, end_date:, budget: processed_budget, channel_daily_spend_limit:, rows_lost:) + end + + def process_budget + filtered_budget = @budget_contents.select do |row| + date = Date.parse(row['date']) + total_spending = row.to_h.values[1..-1].map(&:to_i).sum + date >= start_date && date <= end_date && total_spending < channel_daily_spend_limit + end + + filtered_budget_array = filtered_budget.map(&:fields) + filtered_budget_array.unshift(@budget_contents.headers) + rows_lost = (@budget_contents.to_a - filtered_budget_array) + rows_lost.unshift(@budget_contents.headers) if rows_lost.any? + + @processed_budget = filtered_budget_array + @rows_lost = rows_lost + end +end diff --git a/app/models/spend_forecast.rb b/app/models/spend_forecast.rb index 7bdafe3..b914d2b 100644 --- a/app/models/spend_forecast.rb +++ b/app/models/spend_forecast.rb @@ -8,7 +8,7 @@ class SpendForecast < ApplicationRecord validates :status, presence: true, inclusion: { in: STATUSES } validate :end_date_after_start_date - attr_accessor :budget_file + attr_accessor :budget_file, :rows_lost enum status: STATUSES.index_by(&:to_sym) diff --git a/app/views/spend_forecasts/_form.html.erb b/app/views/spend_forecasts/_form.html.erb index 5d6ca74..1ca1b8f 100644 --- a/app/views/spend_forecasts/_form.html.erb +++ b/app/views/spend_forecasts/_form.html.erb @@ -1,5 +1,5 @@ -<%= form_for spend_forecast do |f| %> - <% if @spend_forecast.errors.any? %> +<%= form_for spend_forecast, id: 'spend_forecast', html: { data: { controller: "budget-file", budget_file_target: "form", budget_file_user_id: user.id } } do |f| %> + <% if spend_forecast.errors.any? %> \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 4a2b926..8c12e82 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -7,4 +7,6 @@ resources :users, only: [] do get :download_budget_template_csv, on: :member end + + post :process_budget, to: 'budgets#process_budget' end diff --git a/db/migrate/20240517143647_add_limit_to_spend_forecasts.rb b/db/migrate/20240517143647_add_limit_to_spend_forecasts.rb new file mode 100644 index 0000000..1e0b9a4 --- /dev/null +++ b/db/migrate/20240517143647_add_limit_to_spend_forecasts.rb @@ -0,0 +1,5 @@ +class AddLimitToSpendForecasts < ActiveRecord::Migration[7.1] + def change + add_column :spend_forecasts, :channel_daily_spend_limit, :decimal, default: 0 + end +end diff --git a/db/schema.rb b/db/schema.rb index e04595b..4549fda 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_05_17_084102) do +ActiveRecord::Schema[7.1].define(version: 2024_05_17_143647) do create_table "spend_forecasts", force: :cascade do |t| t.string "name" t.date "start_date" @@ -20,6 +20,7 @@ t.integer "user_id", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.decimal "channel_daily_spend_limit", default: "0.0" t.index ["user_id"], name: "index_spend_forecasts_on_user_id" end