Files
project-work/worker-toolkit-stocks-in-the-future/repo/app/controllers/admin/students_controller.rb
Eric Bell 2854619bc9 chore: init commit
in worker.../repo/GITFOLDER.zip is the .git folder.
2026-08-11 14:44:09 -04:00

236 lines
7.0 KiB
Ruby

# frozen_string_literal: true
module Admin
# rubocop:disable Metrics/ClassLength
class StudentsController < BaseController
include SoftDeletableFiltering
before_action :set_student, only: %i[show edit update destroy add_transaction]
before_action :set_discarded_student, only: %i[restore]
def index
@students = apply_sorting(scoped_by_discard_status(Student), default: "username")
@breadcrumbs = [
{ label: "Students" }
]
end
def show
@attendance_entries = AttendanceEntryPresenter.for_student(@student)
@earnings_summary = EarningsSummary.new(@student.portfolio) if @student.portfolio.present?
@breadcrumbs = [
{ label: "Students", path: admin_students_path },
{ label: @student.username }
]
end
def new
@student = Student.new
@breadcrumbs = [
{ label: "Students", path: admin_students_path },
{ label: "New Student" }
]
end
def edit
@breadcrumbs = [
{ label: "Students", path: admin_students_path },
{ label: @student.username, path: admin_student_path(@student) },
{ label: "Edit" }
]
end
def create
@student = Student.new(student_params)
# Generate a memorable password if not provided
if @student.password.blank?
generated_password = MemorablePasswordGenerator.generate
@student.password = generated_password
@student.password_confirmation = generated_password
else
generated_password = @student.password
end
if @student.save
redirect_to admin_student_path(@student),
notice: t(".notice", username: @student.username, password: generated_password)
else
@breadcrumbs = [
{ label: "Students", path: admin_students_path },
{ label: "New Student" }
]
render :new, status: :unprocessable_content
end
end
def update
if @student.update(student_params)
redirect_to admin_student_path(@student), notice: t(".notice")
else
@breadcrumbs = [
{ label: "Students", path: admin_students_path },
{ label: @student.username, path: admin_student_path(@student) },
{ label: "Edit" }
]
render :edit, status: :unprocessable_content
end
end
def destroy
username = @student.username
@student.discard
redirect_to admin_students_path, notice: t(".notice", username: username)
end
def restore
username = @student.username
@student.undiscard
redirect_to admin_students_path(discarded: true), notice: t(".notice", username: username)
end
def add_transaction
errors = validate_transaction_params
if errors.present?
redirect_to edit_admin_student_path(@student), alert: errors.join(", ")
else
transaction = PortfolioTransaction.new(
portfolio: @student.portfolio,
amount_cents: transaction_amount_cents,
transaction_type: transaction_type,
reason: transaction_reason,
description: transaction_description
)
if transaction.save
redirect_to admin_student_path(@student), notice: t(".notice")
else
redirect_to edit_admin_student_path(@student), alert: transaction.errors.full_messages.join(", ")
end
end
end
def import
return redirect_with_missing_file_error if params[:csv_file].blank?
begin
results = BulkStudentImportService.import_from_csv(params.expect(:csv_file).path)
redirect_with_import_results(results)
rescue CSV::MalformedCSVError => e
redirect_to admin_students_path, alert: "Invalid CSV format: #{e.message}"
end
end
def template
send_data BulkStudentImportService.generate_csv_template,
filename: "student_import_template.csv",
type: "text/csv",
disposition: "attachment"
end
private
def set_discarded_student
@student = Student.with_discarded.find(params.expect(:id))
end
def set_student
@student = Student.find(params.expect(:id))
end
def student_params
params.expect(student: %i[username classroom_id password password_confirmation])
end
def transaction_params
params.permit(:add_fund_amount, :transaction_type, :transaction_reason, :transaction_description)
end
def transaction_amount_cents
amount = transaction_params[:add_fund_amount]
amount.present? ? (amount.to_f * 100).to_i : nil
end
def transaction_type
transaction_params[:transaction_type]
end
def transaction_reason
transaction_params[:transaction_reason]
end
def transaction_description
transaction_params[:transaction_description]
end
def validate_transaction_params
errors = []
errors << t("admin.students.add_transaction.errors.transaction_type_blank") if transaction_type.blank?
errors << t("admin.students.add_transaction.errors.amount_blank") if transaction_amount_cents.blank?
errors << t("admin.students.add_transaction.errors.reason_blank") if transaction_reason.blank?
errors
end
def redirect_with_missing_file_error
redirect_to admin_students_path, alert: t(".errors.no_file")
end
def redirect_with_import_results(results)
return redirect_with_no_results_error if results.empty?
created, skipped, failed = partition_results(results)
success_messages = build_success_messages(created, skipped)
if failed.any?
redirect_with_mixed_results(success_messages, failed)
else
redirect_to admin_students_path, notice: success_messages.join(". ")
end
end
def redirect_with_no_results_error
redirect_to admin_students_path, alert: t(".errors.no_students")
end
def partition_results(results)
[
results.select(&:created?),
results.select(&:skipped?),
results.select(&:failed?)
]
end
def build_success_messages(created, skipped)
messages = []
messages << build_created_message(created) if created.any?
messages << build_skipped_message(skipped) if skipped.any?
messages
end
def build_created_message(created)
usernames = created.map { |item| item.student.username }
"Successfully created #{created.count} students: #{usernames.join(', ')}"
end
def build_skipped_message(skipped)
"Skipped #{skipped.count} existing usernames"
end
def redirect_with_mixed_results(success_messages, failed)
error_messages = failed.map { |item| "Row #{item.line_number}: #{item.error_message}" }
alert_message = "#{failed.count} errors occurred: #{error_messages.join(', ')}"
if success_messages.any?
redirect_to admin_students_path, notice: success_messages.join(". "), alert: alert_message
else
redirect_to admin_students_path, alert: alert_message
end
end
end
# rubocop:enable Metrics/ClassLength
end