[
  {
    "path": ".allow_skipping_tests",
    "content": ""
  },
  {
    "path": ".better-html.yml",
    "content": "# defaults\n"
  },
  {
    "path": ".browserslistrc",
    "content": "defaults\n"
  },
  {
    "path": ".devcontainer/Dockerfile",
    "content": "FROM mcr.microsoft.com/devcontainers/ruby:dev-3.3-bookworm\nRUN apt-get update && apt-get install -y vim curl gpg postgresql postgresql-contrib tzdata imagemagick\n"
  },
  {
    "path": ".devcontainer/devcontainer.json",
    "content": "// For format details, see https://aka.ms/devcontainer.json. For config options, see the\n// README at: https://github.com/devcontainers/templates/tree/main/src/postgres\n{\n  \"dockerComposeFile\": \"docker-compose.yml\",\n  \"forwardPorts\": [3000, 5432],\n  \"workspaceFolder\": \"/workspaces/casa\",\n  \"service\": \"app\",\n  \"customizations\": {\n    \"vscode\": {\n      \"extensions\": [\"Shopify.ruby-extensions-pack\"],\n      \"settings\": {\n        \"rubyLsp.rubyVersionManager\": {\n          \"identifier\": \"rvm\"\n        }\n      }\n    }\n  },\n  \"containerEnv\": {\n    \"DATABASE_HOST\": \"postgres\",\n    \"POSTGRES_USER\": \"postgres\",\n    \"POSTGRES_PASSWORD\": \"postgres\"\n  },\n  \"features\": {\n    \"ghcr.io/devcontainers/features/github-cli:1\": {\n      \"version\": \"latest\"\n    },\n    \"ghcr.io/devcontainers/features/node:1\": {\n      \"version\": \"24.13.0\"\n    }\n  },\n\n  \"postCreateCommand\": \".devcontainer/post-create.sh\"\n}\n"
  },
  {
    "path": ".devcontainer/docker-compose.yml",
    "content": "version: \"3.8\"\n\nservices:\n  app:\n    build:\n      context: ..\n      dockerfile: .devcontainer/Dockerfile\n\n    volumes:\n      - ../..:/workspaces:cached\n\n    # Overrides default command so things don't shut down after the process ends.\n    command: sleep infinity\n    networks:\n      - default\n\n  postgres:\n    image: postgres:latest\n    restart: always\n    networks:\n      - default\n    volumes:\n      - postgres-data:/var/lib/postgresql/data\n    environment:\n      POSTGRES_USER: postgres\n      POSTGRES_DB: postgres\n      POSTGRES_PASSWORD: postgres\n    # Note that these ports are ignored by the devcontainer.\n    # Instead, the ports are specified in .devcontainer/devcontainer.json.\n    # ports:\n    #  - \"5432:5432\"\nvolumes:\n  postgres-data:\n"
  },
  {
    "path": ".devcontainer/post-create.sh",
    "content": "RUBY_VERSION=\"$(cat .ruby-version | tr -d '\\n')\"\n\n# copy the file only if it doesn't already exist\ncp -n .devcontainer/.env.codespaces .env\n\n# If the project's required ruby version changes from 4.0.2, this command\n# will download and compile the correct version, but it will take a long time.\nif [ \"$RUBY_VERSION\" != \"4.0.2\" ]; then\n\trvm install $RUBY_VERSION\n\trvm use $RUBY_VERSION\n\techo \"Ruby $RUBY_VERSION installed\"\nfi\n\nbin/setup\n"
  },
  {
    "path": ".dockerignore",
    "content": "/.bundle\n\n/log/*\n/tmp/*\n!/log/.keep\n!/tmp/.keep\n\n/storage/*\n!/storage/.keep\n\n/public/assets\n.byebug_history\n\n/config/master.key\n\n/public/packs\n/public/packs-test\n/node_modules\n/npm-debug.log\n/npm-error.log\n"
  },
  {
    "path": ".erb_lint.yml",
    "content": "EnableDefaultLinters: true\nexclude:\n  - '**/vendor/**/*'\nlinters:\n  ErbSafety:\n    enabled: true\n    better_html_config: .better-html.yml\n  Rubocop:\n    enabled: true\n    rubocop_config:\n      Layout/LineLength:\n        Enabled: false\n    only:\n      - Layout/LineLength\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners\n*       @compwron @FireLemons @elasticspoon\n\n/app/javascript/     @schoork @FireLemons @elasticspoon\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: [rubyforgood]\npatreon:  # Replace with a single Patreon username\nopen_collective:  # Replace with a single Open Collective username\nko_fi:  # Replace with a single Ko-fi username\ntidelift:  # Replace with a Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge:  # Replace with a Community Bridge project-name e.g., cloud-foundry\nliberapay:  # Replace with a single Liberapay username\nissuehunt:  # Replace with a single IssueHunt username\notechie:  # Replace with a single Otechie username\ncustom:  # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: 🪲Bug report\nabout: 🔨What needs fixing? ✨\ntitle: \"Bug: \"\nlabels: [\"Type: Bug\", \"Help Wanted\"]\n---\n\n## Impacted User Types\n - volunteers?\n - supervisors?\n - admins?\n - all casa admins?\n\n## Environment\nex: staging, desktop web, Safari\n\n## Current Behavior\nex: When I click \"generate report,\" no report is generated.\n_Please include a screenshot!_\n\n## Expected Behavior\nex: When I click \"generate report\", a downloadable report should be generated.\n\n## How to Replicate\nex: \n1. - Log in as an admin or supervisor.\n2. - Click on \"Generate Reports\" in the left sidebar menu.\n3. - Filter by a volunteer who has logged at least one case contact.\n4. - Click \"Generate report\" at the bottom of the page.\n\n## How to access the QA site\n_Login Details:_  \n[Link to QA site](https://casa-qa.herokuapp.com/)  \n\nLogin Emails: \n- volunteer1@example.com  view site as a volunteer\n- supervisor1@example.com view site as a supervisor\n- casa_admin1@example.com view site as an admin\n- all_casa_admin1@example.com view site as an all casa admin\n  - go to `/all_casa_admins/sign_in`  \n\npassword for all users: 12345678  \n\n### Questions? Join Slack!\n\nWe highly recommend that you join us in [slack](https:https://join.slack.com/t/rubyforgood/shared_invite/zt-35218k86r-vlIiWqig54c9t~_LkGpQ7Q) #casa channel to ask questions quickly. And [discord](https://discord.gg/qJcw2RZH8Q) for office hours (currently Tuesday 5-7pm Pacific), stakeholder news, and upcoming new issues.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/chore.md",
    "content": "**Description**\n\n**Existing gem(s), file(s) needing updating, changing or deleting**\n\n**New file(s) needing creating**\n\n### Questions? Join Slack!\n\nWe highly recommend that you join us in [slack](https:https://join.slack.com/t/rubyforgood/shared_invite/zt-35218k86r-vlIiWqig54c9t~_LkGpQ7Q) #casa channel to ask questions quickly and hear about office hours (currently Tuesday 5-7pm Pacific), stakeholder news, and upcoming new issues.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: 📄Documentation addition/change\n    about: 🔨What needs documenting? ✨\n    url: https://github.com/rubyforgood/casa/issues/new?template=documentation.md&projects=rubyforgood/casa/1\n  - name: 🧹Chore\n    about: 🔨What needs updating or removing? ✨\n    url: https://github.com/rubyforgood/casa/issues/new?template=chore.md&projects=rubyforgood/casa/1\n  - name: 🙏Problem Validation\n    about: 🔨Describe a stakeholder's issue✨\n    url: https://github.com/rubyforgood/casa/issues/new?template=problem_validation.md&projects=rubyforgood/casa/1\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/documentation.md",
    "content": "**Description**\n\n**Existing file(s) needing changing**\n\n**New file(s) needing creating**\n\n### Questions? Join Slack!\n\nWe highly recommend that you join us in [slack](https:https://join.slack.com/t/rubyforgood/shared_invite/zt-35218k86r-vlIiWqig54c9t~_LkGpQ7Q) #casa channel to ask questions quickly and hear about office hours (currently Tuesday 5-7pm Pacific), stakeholder news, and upcoming new issues.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: 💡Feature request\nabout: 🔨What needs building? ✨ \nlabels: [\"✨ New Feature\", \"Help Wanted\"]\n---\n\n## What type(s) of user does this feature affect?\n - volunteers?\n - supervisors?\n - admins?\n - all casa admins?\n\n## Description\n\n\n## Screenshots of current behavior, if any\nYou can paste images on the clipboard here\n\n## How to access the QA site\n_Login Details:_  \n[Link to QA site](https://casa-qa.herokuapp.com/)  \n\nLogin Emails: \n- volunteer1@example.com  view site as a volunteer\n- supervisor1@example.com view site as a supervisor\n- casa_admin1@example.com view site as an admin\n- all_casa_admin1@example.com view site as an all casa admin\n  - go to `/all_casa_admins/sign_in`  \n\npassword for all users: 12345678  \n\n### Questions? Join Slack!\n\nWe highly recommend that you join us in [slack](https:https://join.slack.com/t/rubyforgood/shared_invite/zt-35218k86r-vlIiWqig54c9t~_LkGpQ7Q) #casa channel to ask questions quickly. And [discord](https://discord.gg/qJcw2RZH8Q) for office hours (currently Tuesday 5-7pm Pacific), stakeholder news, and upcoming new issues.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/flaky_test.md",
    "content": "---\nname: Flaky Test\nabout: one of the tests is inconsistent and likely producing false positives\ntitle: \"Bug: Flaky Test\"\nlabels: [\"Type: Bug\", \"Help Wanted\"]\n---\nFlaky tests are defined as tests that return both passes and failures despite no changes to the code or the test itself\nFix the test so it runs consistently.\n\n### CI Workflow\nrspec or rspec in docker?\n\n### Sample Error Output:\n```\n```\n\n### Questions? Join Slack!\n\nWe highly recommend that you join us in [slack](https:https://join.slack.com/t/rubyforgood/shared_invite/zt-35218k86r-vlIiWqig54c9t~_LkGpQ7Q) #casa channel to ask questions quickly and hear about office hours (currently Tuesday 5-7pm Pacific), stakeholder news, and upcoming new issues.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/problem_validation.md",
    "content": "Please use this template to document problems mentioned by CASA stakeholders so we make the best decisions for the solution.\n\n\n**Which role has this problem (AllCasaAdmin, CasaAdmin, Supervisor, Volunteer)?**\n\n\n**What is the problem they are facing? (short description)**\n\n\n**When does the problem happen? At what moment in the workflow or after how long?**\n\n\n**Attach a design mockup if possible**\n\n\n### Questions? Join Slack!\n\nWe highly recommend that you join us in [slack](https:https://join.slack.com/t/rubyforgood/shared_invite/zt-35218k86r-vlIiWqig54c9t~_LkGpQ7Q) #casa channel to ask questions quickly and hear about office hours (currently Tuesday 5-7pm Pacific), stakeholder news, and upcoming new issues.\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "### What github issue is this PR for, if any?\nResolves #XXXX\n\n### What changed, and _why_?\n\n\n### How is this **tested**? (please write rspec and jest tests!) 💖💪\n_Note: if you see a flake in your test build in github actions, please post in slack #casa \"Flaky test: <link to failed build>\" :) 💪_\n_Note: We love [capybara](https://rubydoc.info/github/teamcapybara/capybara) tests! If you are writing both haml/js and ruby, please try to test your work with tests at every level including system tests like https://github.com/rubyforgood/casa/tree/main/spec/system_ \n\n\n### Screenshots please :)\n_Run your local server and take a screenshot of your work! Try to include the URL of the page as well as the contents of the page._ \n\n\n### Feelings gif (optional)\n_What gif best describes your feeling working on this issue? https://giphy.com/\nHow to embed:_\n`![alt text](https://media.giphy.com/media/1nP7ThJFes5pgXKUNf/giphy.gif)`\n"
  },
  {
    "path": ".github/autoapproval.yml",
    "content": "from_owner:\n  - dependabot-preview\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where the package manifests are located.\n# Please see the documentation for all configuration options:\n# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates\n\n# Basic set up for three package managers\n\nversion: 2\nupdates:\n\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n\n  - package-ecosystem: \"npm\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n    ignore:\n      # Keep this locked to 1.77.6 until\n      # bootstrap 5.3.4 is out to silence\n      # sass deprecation warnings\n      # https://getbootstrap.com/docs/versions/\n      - dependency-name: \"sass\"\n        versions: [\"1.77.*\", \"1.78.*\"]\n\n  - package-ecosystem: \"bundler\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n"
  },
  {
    "path": ".github/instructions/copilot-review.instructions.md",
    "content": "---\napplyTo: \"**\"\n---\n\n# CASA Code Review Instructions\n\nYou are reviewing pull requests for CASA (Court Appointed Special Advocates), a Ruby on Rails application that helps CASA volunteers track their work with youth in foster care. This is an open-source project maintained by Ruby for Good with contributors of all experience levels. Be constructive and kind.\n\n## Tech Stack\n\n- **Backend**: Rails 7.2, Ruby 3.x, PostgreSQL\n- **Frontend**: Stimulus (Hotwire) + Turbo, Bootstrap 5, ESBuild\n- **Auth**: Devise + Devise-Invitable (no UI sign-ups; admin invitation only)\n- **Authorization**: Pundit (policy-based)\n- **Background Jobs**: Delayed Job\n- **Feature Flags**: Flipper\n- **Testing**: RSpec + Capybara (Ruby), Jest (JavaScript)\n- **Linting**: Standard.rb (Ruby), StandardJS (JavaScript), erb-lint (ERB)\n- **View Layer**: ERB templates, ViewComponent, Draper decorators\n\n## Architecture Rules\n\n### Authorization & Multi-Tenancy\n\n- All controller actions must be authorized via Pundit (`authorize` / `policy_scope`).\n- Data must be scoped to the current user's `casa_org`. Never allow cross-organization data access.\n- User roles: `AllCasaAdmin`, `CasaAdmin`, `Supervisor`, `Volunteer`. Each has a separate model (not STI).\n- Permission changes must include or update the corresponding Pundit policy file in `app/policies/`.\n\n### Controllers\n\n- Controllers should be thin. Complex logic belongs in service objects or models.\n- Controllers must call `authorize` and typically use `after_action :verify_authorized`.\n- Use `policy_scope` for index actions to scope records to the current org/user.\n\n### Models\n\n- Models use `include ByOrganizationScope` for org-scoped queries where applicable.\n- Prefer scopes over class methods for query logic.\n- Soft deletes via the Paranoia gem: `destroy` marks records deleted, does not hard-delete.\n- Use Strong Migrations: flag any unsafe migration operations (e.g., removing a column without `safety_assured`, adding an index without `algorithm: :concurrently`).\n\n### Views & Frontend\n\n- Use ERB (not HAML). Complex view logic should live in a Draper decorator (`app/decorators/`), not in the template.\n- Use ViewComponent (`app/components/`) for reusable UI elements.\n- Style with Bootstrap 5 utility classes and existing project SCSS. UI changes should match the rest of the site.\n- JavaScript should use Stimulus controllers. Avoid inline `<script>` tags or jQuery for new code.\n- Email views require inline CSS (email client compatibility — see ADR 0007).\n\n### Services & Decorators\n\n- Service objects live in `app/services/` and follow the pattern: `ServiceName.new(args).perform`.\n- Decorators live in `app/decorators/` and handle presentation logic that doesn't belong in models or views.\n\n## Review Checklist\n\n### Security\n\n- [ ] No mass-assignment vulnerabilities: strong parameters used for all user input.\n- [ ] No raw SQL interpolation — use parameterized queries or ActiveRecord methods.\n- [ ] No `html_safe` or `raw` on user-supplied content (XSS risk).\n- [ ] Pundit authorization is present on all controller actions.\n- [ ] Data is scoped to the user's organization — no cross-tenant leaks.\n- [ ] File uploads validated (type, size) before storage.\n- [ ] Secrets and credentials are not committed.\n\n### Testing\n\n- [ ] New features have tests. Bug fixes include a test that would fail without the fix.\n- [ ] System tests (Capybara) are preferred for UI changes over controller tests (see ADR 0006).\n- [ ] Factories use traits for scenario variations (e.g., `create(:casa_case, :active)`).\n- [ ] Tests cover edge cases: nil/empty values, missing optional fields, special characters.\n- [ ] No `sleep` calls in tests — use Capybara's built-in waiting/retry mechanisms.\n- [ ] Flaky tests are disabled with a tracking issue, not deleted.\n\n### Code Quality\n\n- [ ] Follows Standard.rb style (Ruby) and StandardJS style (JavaScript).\n- [ ] No N+1 queries introduced — use `includes`, `preload`, or `eager_load` as needed.\n- [ ] ActiveRecord collections iterated with `find_each` instead of `each` for large sets.\n- [ ] Multi-record writes wrapped in `ActiveRecord::Base.transaction`.\n- [ ] No business logic in controllers or views — delegate to models, services, or decorators.\n- [ ] Dead code and unused variables removed.\n\n### Pull Request Quality\n\n- [ ] PR references a GitHub issue (`Resolves #XXXX`).\n- [ ] Small, focused diff. Multiple small PRs preferred over one large PR.\n- [ ] Migrations are reversible and safe (no data loss, follows Strong Migrations).\n- [ ] No unrelated formatting changes that inflate the diff.\n\n## What to Flag\n\n- **Authorization gaps**: Any controller action missing `authorize` or `policy_scope`.\n- **Cross-org data access**: Queries that don't scope to `casa_org`.\n- **Missing tests**: New behavior without corresponding test coverage.\n- **N+1 queries**: Loading associations inside loops without eager loading.\n- **Unsafe migrations**: Column removals, type changes, or index additions without safety guards.\n- **XSS vectors**: `html_safe`, `raw`, or `sanitize` misuse on user content.\n- **Large PRs**: Suggest splitting if the diff touches many unrelated areas.\n\n## What NOT to Flag\n\n- Older migration files (2020–2024) — these are excluded from linting intentionally.\n- Minor style issues already handled by Standard.rb or StandardJS linters.\n- Lack of TypeScript — this project uses plain JavaScript with Stimulus.\n- jQuery usage in existing code — new code should use Stimulus, but don't flag legacy jQuery.\n\n## Tone\n\nThis is a volunteer-driven open-source project. Contributors range from first-time open source contributors to experienced Rails developers. Be encouraging, explain the \"why\" behind suggestions, and link to relevant docs or examples in the codebase when possible. Avoid nitpicks on subjective style — trust the linters for formatting.\n"
  },
  {
    "path": ".github/instructions/ruby.instructions.md",
    "content": "---\napplyTo: \"**/*.rb\"\n---\n\n# Ruby / Rails Review Instructions\n\n## Authorization (Pundit)\n\nEvery controller action that reads or writes data must be authorized.\n\n```ruby\n# Required pattern\ndef index\n  authorize Model\n  @records = policy_scope(current_organization.models)\nend\n\ndef show\n  authorize @record\nend\n```\n\n- Controllers must include `after_action :verify_authorized` (with exceptions listed in `except:`).\n- Index actions must use `policy_scope` to scope records to the current organization.\n- Custom policy methods are called with `authorize @record, :custom_action?`.\n- Policy files live in `app/policies/`. Permission changes must update the corresponding policy.\n- Policies define `permitted_attributes` that return role-based field lists — check that new fields are added there when models gain attributes.\n\nFlag: any controller action missing `authorize` or `policy_scope`.\n\n## Multi-Tenancy\n\nAll data access must be scoped to the user's `casa_org`.\n\n- Models include `ByOrganizationScope` which provides `.by_organization(casa_org)`.\n- Policy scopes must filter by organization: `scope.by_organization(user.casa_org)`.\n- `current_organization` (from the `Organizational` concern) returns the signed-in user's org.\n\nFlag: queries that could return records from another organization.\n\n## Controllers\n\n- Keep controllers thin. Business logic belongs in models or service objects (`app/services/`).\n- Complex view logic belongs in Draper decorators (`app/decorators/`), not controllers or ERB.\n- Use parameter objects (`app/values/`) for strong params when the logic is non-trivial. They follow a builder pattern: `FooParameters.new(params).with_password(pw).without_active`.\n- Standard flash pattern:\n  ```ruby\n  if @record.save\n    redirect_to path, notice: \"Record created successfully.\"\n  else\n    render :new, status: :unprocessable_content\n  end\n  ```\n- Use `respond_to` blocks when supporting multiple formats (HTML, JSON, CSV).\n- Set up models with `before_action :set_model` and list exceptions in `except:`.\n\n## Models\n\n- Prefer scopes over class methods for query logic.\n- Scope naming: descriptive, chainable (`.active`, `.by_organization`, `.with_assigned_cases`).\n- Use `find_each` (not `each`) when iterating over ActiveRecord collections.\n- Wrap multi-record writes in `ActiveRecord::Base.transaction`.\n- Enums use the prefix syntax: `enum :status, {active: 0, inactive: 1}, prefix: :status`.\n- Soft deletes via Paranoia — `destroy` marks as deleted, does not hard-delete.\n- Extract complex validations into concern modules (e.g., `CasaCase::Validations`).\n- Associations with conditions use lambdas:\n  ```ruby\n  has_many :active_assignments, -> { active }, class_name: \"CaseAssignment\"\n  ```\n- `accepts_nested_attributes_for` with `reject_if` guards for blank entries.\n\n## Services\n\nService objects follow a consistent pattern:\n\n```ruby\nclass MyService\n  def initialize(args)\n    @args = args\n  end\n\n  def perform\n    # single responsibility logic\n  end\nend\n```\n\nCalled as `MyService.new(args).perform`. Services should preload associations to avoid N+1 queries.\n\n## Decorators\n\n- Draper decorators in `app/decorators/` handle presentation logic.\n- Access view helpers via `h.helper_method` inside decorators.\n- Called via `.decorate` on model instances or collections.\n\nFlag: presentation logic (formatting dates, conditional display text) in models or controllers.\n\n## Concerns\n\n- Use `extend ActiveSupport::Concern` for shared behavior.\n- `included do ... end` block for validations, scopes, callbacks.\n- Place model concerns in `app/models/concerns/`, controller concerns in `app/controllers/concerns/`.\n\n## Migrations\n\n- Must be reversible.\n- Use Strong Migrations: flag unsafe operations (removing columns without `safety_assured`, adding indexes without `algorithm: :concurrently` on large tables, changing column types).\n- New columns with NOT NULL constraints need a default value or a multi-step migration.\n\n## Common Anti-Patterns to Flag\n\n- **N+1 queries**: Loading associations inside loops without `includes`, `preload`, or `eager_load`.\n- **Cross-org data leaks**: Queries missing organization scope.\n- **Business logic in views**: ERB files with complex conditionals or calculations — should be in a decorator.\n- **Fat controllers**: More than a few lines of logic — extract to a service or model method.\n- **Raw SQL interpolation**: Use parameterized queries or ActiveRecord methods.\n- **`html_safe` / `raw` on user input**: XSS risk.\n- **Missing authorization**: Controller actions without `authorize`.\n- **`.each` on AR collections**: Use `find_each` for batch processing.\n- **Unscoped `destroy`**: Verify soft-delete behavior is intended; Paranoia intercepts `destroy`.\n\n## Testing (RSpec)\n\n- System tests (Capybara) are preferred for UI flows over controller tests.\n- Model tests use shoulda-matchers for associations and validations:\n  ```ruby\n  it { is_expected.to have_many(:case_assignments).dependent(:destroy) }\n  it { is_expected.to validate_uniqueness_of(:case_number).scoped_to(:casa_org_id) }\n  ```\n- Use `build` for unit tests, `create` only when persistence is needed.\n- Use `let` for lazy evaluation, `let!` when the record must exist before the test runs.\n- Factory traits for scenario variations: `create(:casa_case, :active)`.\n- Context blocks describe the scenario: `context \"when the user is a supervisor\"`.\n- Test edge cases: nil values, empty strings, special characters, missing optional fields.\n- No `sleep` in tests — use Capybara's built-in waiting.\n- Flaky tests are disabled with a tracking issue (`xit` + comment), never deleted.\n\n## Style\n\nThis project uses Standard.rb (not vanilla RuboCop). Do not flag style issues that Standard.rb handles automatically (spacing, string quotes, trailing commas). Focus review on logic, security, and architecture.\n"
  },
  {
    "path": ".github/labeler.yml",
    "content": "# Configuration for the Labeler action\n# The action is set up in ./workflows/label.yml\n# See https://github.com/actions/labeler for more details\n#\n# Warning - Testing changes to this file can be tricky\n# At the time of writing, you need to have changes committed to master for the\n# Action to use them. This isn't ideal for rapid iteration. If you want to make\n# changes to this file and experiment with changes, please consider testing the\n# changes in a fork of this repo first. This allows you to add changes to\n# master as needed and also create multiple Pull Requests to confirm labels are\n# properly added.\n\nruby:\n- changed-files:\n  - any-glob-to-any-file: \"**/*.rb\"\n\nerb:\n- changed-files:\n  - any-glob-to-any-file: \"**/*.erb\"\n\njavascript:\n- changed-files:\n  - any-glob-to-any-file: [\"**/*.js\", \"package*.json\", \"package-lock.json\"]\n\nTests! 🎉💖👏:\n- changed-files:\n  - any-glob-to-any-file: [\"app/javascript/__tests__/**.test.js\", \"spec/**/*_spec.rb\"]\n\ndependencies:\n- changed-files:\n  - any-glob-to-any-file: [\"Gemfile*\", \"package*.json\", \"package-lock.json\"]\n"
  },
  {
    "path": ".github/workflows/add-labels-based-on-column.yml",
    "content": "on:\n  schedule:\n    # * is a special character in YAML so you have to quote this string\n    - cron: '0 * * * *'\n  workflow_dispatch:  # Enable manual runs of the bot\n\njobs:\n  add_help_wanted_labels:\n    runs-on: ubuntu-latest\n    name: Add help wanted labels\n    steps:\n      - name: Add help wanted labels\n        uses: rubyforgood/add-label-to-cards@v3.3\n        id: add-help-wanted-labels\n        with:\n          columns_labels: >\n            [\n              {\n                \"column_name\": \"Not ready to build\",\n                \"labels\": [\"not-ready-to-build\"],\n                \"project_name\": \"CASA Volunteer Portal\"\n              },\n              {\n                \"column_name\": \"To do\",\n                \"labels\": [\"Help Wanted\"],\n                \"project_name\": \"CASA Volunteer Portal\"\n              },\n              {\n                \"column_name\": \"To do\",\n                \"labels\": [\"Stakeholder-Feature\"],\n                \"project_name\": \"Stakeholder Requested Features\"\n              },\n              {\n                \"column_name\": \"In progress\",\n                \"labels\": [\"Stakeholder-Feature\"],\n                \"project_name\": \"Stakeholder Requested Features\"\n              },\n              {\n                \"column_name\": \"Done\",\n                \"labels\": [\"Stakeholder-Feature\"],\n                \"project_name\": \"Stakeholder Requested Features\"\n              }\n            ]\n          token: ${{secrets.GITHUB_TOKEN}}\n"
  },
  {
    "path": ".github/workflows/after-deploy.yml",
    "content": "on:\n  schedule:\n    - cron: '0 7 * * *'  # Run every day at 7AM UTC\n  workflow_dispatch:  # Enable Manual Runs\n\njobs:\n  on_deploy:\n    runs-on: ubuntu-latest\n    name: On Deploy\n    steps:\n      - name: After Deploy\n        uses: Firelemons/on-deploy@v2.2.1\n        with:\n          project_name: \"CASA Volunteer Portal\"\n          done_column_card_limit: \"16\"\n          done_column_name: \"Done (in prod!)\"\n          QA_column_name: \"Merged to QA\"\n          token: ${{secrets.GITHUB_TOKEN}}\n"
  },
  {
    "path": ".github/workflows/codeql-analysis.yml",
    "content": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# You may wish to alter this file to override the set of languages analyzed,\n# or to provide custom queries or build logic.\n#\n# ******** NOTE ********\n# We have attempted to detect the languages in your repository. Please check\n# the `language` matrix defined below to confirm you have the correct set of\n# supported CodeQL languages.\n#\nname: CodeQL\n\non:\n  push:\n    branches:\n      - main\n    paths-ignore:\n      - 'doc/**'\n      - '**/*.md'\n  pull_request:\n    # The branches below must be a subset of the branches above\n    branches:\n      - main\n      - rfg-event-2025\n    paths-ignore:\n      - 'doc/**'\n      - '**/*.md'\n  schedule:\n    - cron: '36 22 * * 3'\n\njobs:\n  analyze:\n    name: Analyze\n    runs-on: ubuntu-latest\n\n    strategy:\n      fail-fast: false\n      matrix:\n        language: ['javascript']\n        # CodeQL supports ['cpp', 'csharp', 'go', 'java', 'javascript', 'python']\n        # Learn more:\n        # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v6\n\n      # Initializes the CodeQL tools for scanning.\n      - name: Initialize CodeQL\n        uses: github/codeql-action/init@v3\n        with:\n          languages: ${{ matrix.language }}\n          # If you wish to specify custom queries, you can do so here or in a config file.\n          # By default, queries listed here will override any specified in a config file.\n          # Prefix the list here with \"+\" to use these queries and those in the config file.\n          # queries: ./path/to/local/query, your-org/your-repo/queries@main\n\n      # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).\n      # If this step fails, then you should remove it and run the build manually (see below)\n      - name: Autobuild\n        uses: github/codeql-action/autobuild@v3\n\n      # ℹ️ Command-line programs to run using the OS shell.\n      # 📚 https://git.io/JvXDl\n\n      # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines\n      #    and modify them (or add more) to build your code if your project\n      #    uses a compiled language\n\n      #- run: |\n      #   make bootstrap\n      #   make release\n\n      - name: Perform CodeQL Analysis\n        uses: github/codeql-action/analyze@v3\n"
  },
  {
    "path": ".github/workflows/combine_and_report.yml",
    "content": "on:\n  workflow_call:\njobs:\n  combine_and_report:\n    runs-on: ubuntu-latest\n    env:\n      AZURE_STORAGE_KEY: ${{ secrets.STORAGE_ACCESS_KEY }}\n      AZURE_STORAGE_ACCOUNT: ${{ secrets.ACCOUNT_NAME }}\n      STORAGE_CONTAINER: ${{ secrets.STORAGE_CONTAINER }}\n    steps:\n      - name: Checkout Project\n        if: ${{ !cancelled() }}\n        uses: actions/checkout@v6\n      - name: Download artifacts\n        if: ${{ !cancelled() }}\n        uses: actions/download-artifact@v8\n        with:\n          path: artifacts\n\n      - name: Decompress chunk test reports\n        if: ${{ !cancelled() }}\n        run: |\n          find artifacts -name \"test_reports*.zip\" -exec unzip -d test_reports {} \\;\n          find test_reports -name \"**/test_reports*.zip\" -exec unzip -d test_reports {} \\;\n\n      - name: Merge parallel runtime log parts\n        if: ${{ !cancelled() && env.AZURE_STORAGE_KEY != '' }}\n        run: |\n          cat artifacts/**/parallel_runtime_rspec*.log > parallel_runtime.log\n\n      - name: Upload log file to Azure Blob Storage\n        if: ${{ !cancelled() && env.AZURE_STORAGE_KEY != '' }}\n        run: |\n          az storage blob upload \\\n          -c $STORAGE_CONTAINER \\\n          --file parallel_runtime.log \\\n          -n parallel_runtime.log \\\n          --overwrite true\n\n      - name: Test Summary\n        id: test_summary\n        uses: test-summary/action@v2\n        with:\n          paths: |\n            test_reports/**/rspec*.xml\n      - name: Set job status\n        if: ${{ steps.test_summary.outputs.failed > 0 }}\n        uses: actions/github-script@v9\n        with:\n          script: |\n            core.setFailed('There are test failures')\n"
  },
  {
    "path": ".github/workflows/docker.yml",
    "content": "name: docker\n\non:\n  push:\n    branches:\n      - main\n    paths-ignore:\n      - \"doc/**\"\n      - \"**/*.md\"\n  pull_request:\n    branches:\n      - main\n      - rfg-event-2025\n    paths-ignore:\n      - \"doc/**\"\n      - \"**/*.md\"\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\n\njobs:\n  docker:\n    runs-on: ubuntu-latest\n    timeout-minutes: 20\n    env:\n      RAILS_ENV: test\n      TEST_MAX_DURATION: 60\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v6\n      - name: Create downloads folder\n        run: |\n          mkdir -p tmp/downloads\n          chmod 777 tmp tmp/downloads\n      - name: docker UP\n        run: docker compose up -d\n      - name: db:setup\n        run: docker compose exec -T web rails db:setup\n      - name: compile assets\n        run: docker compose exec -T web bundle exec rails assets:precompile\n      - name: Test\n        run: docker compose exec -T web bundle exec rspec spec --fail-fast\n\n      - name: Archive selenium screenshots\n        if: ${{ failure() }}\n        uses: actions/upload-artifact@v7\n        with:\n          name: selenium-screenshots\n          path: |\n            ${{ github.workspace }}/tmp/capybara/*.png\n            ${{ github.workspace }}/tmp/capybara/*.html\n"
  },
  {
    "path": ".github/workflows/erb_lint.yml",
    "content": "name: ERB lint\n\non:\n  push:\n    branches:\n      - main\n    paths:\n      - '**/*.erb'\n      - '**/*.html'\n  pull_request:\n    branches:\n      - main\n      - rfg-event-2025\n    paths:\n      - '**/*.erb'\n      - '**/*.html'\n\njobs:\n  erb_lint:\n\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: Set up Ruby\n        uses: ruby/setup-ruby@v1\n        with:\n          bundler-cache: true\n\n      - name: ERB lint\n        run: bundle exec erb_lint --lint-all\n"
  },
  {
    "path": ".github/workflows/factory_bot_lint.yml",
    "content": "name: factory bot lint\n\non:\n  push:\n    branches:\n      - main\n    paths-ignore:\n      - 'doc/**'\n      - '**/*.md'\n      - 'bin/**'\n  pull_request:\n    branches:\n      - main\n      - rfg-event-2025\n    paths-ignore:\n      - 'doc/**'\n      - '**/*.md'\n      - 'bin/**'\n\njobs:\n  lint:\n    runs-on: ubuntu-latest\n\n    services:\n      db:\n        image: postgres:12.3\n        env:\n          POSTGRES_PASSWORD: password\n        ports:\n          - 5432:5432\n        options: >-\n          --health-cmd pg_isready\n          --health-interval 10s\n          --health-timeout 5s\n          --health-retries 5\n\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: Set up Ruby\n        uses: ruby/setup-ruby@v1\n        with:\n          bundler-cache: true\n\n      - name: Install PostgreSQL client\n        run: |\n          sudo apt-get -yqq install libpq-dev\n\n      - name: factory_bot:lint\n        env:\n          POSTGRES_HOST: localhost\n          DATABASE_HOST: localhost\n          POSTGRES_USER: postgres\n          CASA_DATABASE_PASSWORD: password\n          POSTGRES_PASSWORD: password\n          POSTGRES_HOST_AUTH_METHOD: trust\n          POSTGRES_PORT: 5432\n          PGHOST: localhost\n          PGUSER: postgres\n          RAILS_ENV: test\n        run: |\n          bundle exec rake db:create\n          bundle exec rake db:schema:load\n          bundle exec rake factory_bot:lint\n"
  },
  {
    "path": ".github/workflows/issue-auto-close-done.yml",
    "content": "name: 'Close issue when Done'\n\non:\n  project_card:\n    types: [moved]\n\njobs:\n  set-state:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: dessant/issue-states@v3\n        with:\n          github-token: ${{secrets.GITHUB_TOKEN}}\n          open-issue-columns: ''\n          closed-issue-columns: 'Done'\n"
  },
  {
    "path": ".github/workflows/issue-auto-unassign.yml",
    "content": "on:\n  schedule:\n    # * is a special character in YAML so you have to quote this string\n    - cron: '0 0 * * *'\n  workflow_dispatch:  # Enable manual runs of the bot\n\njobs:\n  unassign_issues:\n    runs-on: ubuntu-latest\n    name: Unassign issues\n    steps:\n      - name: Unassign issues\n        uses: rubyforgood/unassign-issues@v1.3\n        id: unassign_issues\n        with:\n          token: ${{secrets.GITHUB_TOKEN}}\n          unassign_inactive_in_hours: 360  # 15 days\n          warning_inactive_in_hours: 240  # 10 days\n          office_hours: 'Tuesdays 6-8 PM PST'\n          warning_inactive_message: 'If you are still working on this, comment here to tell the bot to give you more time'\n      - name: Print the unassigned issues\n        run: echo \"Unassigned issues = ${{steps.unassign_issues.outputs.unassigned_issues}}\"\n      - name: Print the warned issues\n        run: echo \"Warned issues = ${{steps.unassign_issues.outputs.warned_issues}}\"\n      - name: Move unassigned issues from In Progress to To Do\n        uses: bjthompson805/move-issues@v1\n        id: move_issues\n        with:\n          token: ${{secrets.GITHUB_TOKEN}}\n          issues: ${{steps.unassign_issues.outputs.unassigned_issues}}\n          from_column: '8461148'\n          to_column: '15690587'\n      - name: Print moved issues\n        run: echo \"Moved issues = ${{steps.move_issues.outputs.moved_issues}}\"\n"
  },
  {
    "path": ".github/workflows/label.yml",
    "content": "# This workflow will triage pull requests and apply a label based on the\n# paths that are modified in the pull request.\n#\n# See ../labeler.yml for mapping of files and labels\n# See https://github.com/actions/labeler for more details on the action\n\nname: filetype labeler\non:\n- pull_request_target\n\njobs:\n  label:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/labeler@v5\n      with:\n        repo-token: \"${{ secrets.GITHUB_TOKEN }}\"\n"
  },
  {
    "path": ".github/workflows/npm_lint_and_test.yml",
    "content": "name: npm lint\n\non:\n  push:\n    branches:\n      - main\n    paths:\n      - '**/*.js'\n      - '**/*.json'\n      - '**/*.jsx'\n      - 'package.lock.json'\n      - '.github/workflows/*.yml'\n  pull_request:\n    branches:\n      - main\n      - rfg-event-2025\n    paths:\n      - '**/*.js'\n      - '**/*.json'\n      - '**/*.jsx'\n      - 'package.lock.json'\n      - '.github/workflows/*.yml'\n\njobs:\n  npm_lint:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v6\n\n      - uses: actions/setup-node@v6\n        with:\n          node-version-file: '.nvmrc'\n\n      - run: npm ci\n      - run: npm run lint\n      - run: npm run test\n"
  },
  {
    "path": ".github/workflows/rake-after_party.yml",
    "content": "name: Run rake after_party\n\non:\n  push:\n    branches:\n      - main\n    paths-ignore:\n      - \"doc/**\"\n      - \"**/*.md\"\n      - \"bin/**\"\n  pull_request:\n    branches:\n      - main\n      - rfg-event-2025\n    paths-ignore:\n      - \"doc/**\"\n      - \"**/*.md\"\n      - \"bin/**\"\n\njobs:\n  rake-after_party:\n    runs-on: ubuntu-latest\n\n    services:\n      db:\n        image: postgres:12.3\n        env:\n          POSTGRES_PASSWORD: password\n        ports:\n          - 5432:5432\n        options: >-\n          --health-cmd pg_isready\n          --health-interval 10s\n          --health-timeout 5s\n          --health-retries 5\n\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: Set up Ruby\n        uses: ruby/setup-ruby@v1\n        with:\n          bundler-cache: true\n\n      - name: Install PostgreSQL client\n        run: |\n          sudo apt-get -yqq install libpq-dev\n\n      - name: Build App and run rake after_party\n        env:\n          POSTGRES_HOST: localhost\n          DATABASE_HOST: localhost\n          POSTGRES_USER: postgres\n          CASA_DATABASE_PASSWORD: password\n          POSTGRES_PASSWORD: password\n          POSTGRES_HOST_AUTH_METHOD: trust\n          POSTGRES_PORT: 5432\n          PGHOST: localhost\n          PGUSER: postgres\n          RAILS_ENV: test\n        run: |\n          bundle exec rake db:create\n          bundle exec rake db:schema:load\n          bundle exec rake after_party:run\n"
  },
  {
    "path": ".github/workflows/remove-helped-wanted.yml",
    "content": "name: Remove Help Wanted Label on Issue Assignment\n\non:\n  issues:\n    types: [assigned]\n\njobs:\n  automate-issues-labels:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: andymckay/labeler@master\n        with:\n          remove-labels: \"Help Wanted\"\n"
  },
  {
    "path": ".github/workflows/remove-label-based-on-column.yml",
    "content": "on:\n  schedule:\n    # * is a special character in YAML so you have to quote this string\n    - cron: '0 * * * *'\n  workflow_dispatch:  # Enable manual runs of the bot\n\njobs:\n  remove_help_wanted_labels:\n    runs-on: ubuntu-latest\n    name: Remove help wanted labels based on column\n    steps:\n      - name: Remove help wanted labels based on column\n        uses: rubyforgood/remove-label-from-cards@2.0\n        id: remove-help-wanted-labels\n        with:\n          token: ${{secrets.GITHUB_TOKEN}}\n          columns_labels: >\n            [\n              {\n                \"column_name\": \"Done (in prod!)\",\n                \"labels\": [\"Help Wanted\"],\n                \"project_name\": \"CASA Volunteer Portal\"\n              },\n              {\n                \"column_id\": \"47fc9ee4\",\n                \"labels\": [\"Help Wanted\"]\n              },\n              {\n                \"column_name\": \"Merged to QA\",\n                \"labels\": [\"Help Wanted\"],\n                \"project_name\": \"CASA Volunteer Portal\"\n              },\n              {\n                \"column_name\": \"Not ready to build\",\n                \"labels\": [\"Help Wanted\"],\n                \"project_name\": \"CASA Volunteer Portal\"\n              },\n              {\n                \"column_name\": \"Requires Design / Issues To Be\",\n                \"labels\": [\"Help Wanted\"],\n                \"project_name\": \"CASA Volunteer Portal\"\n              }\n            ]\n"
  },
  {
    "path": ".github/workflows/rspec.yml",
    "content": "name: rspec\n\non:\n  push:\n    branches:\n      - main\n    paths-ignore:\n      - \"doc/**\"\n      - \"**/*.md\"\n  pull_request:\n    branches:\n      - main\n      - rfg-event-2025\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\n\njobs:\n  rspec:\n    runs-on: ubuntu-latest\n    timeout-minutes: 20\n    env:\n      RAILS_ENV: test\n      TEST_MAX_DURATION: 60\n\n    services:\n      db:\n        image: postgres:14.8\n        env:\n          POSTGRES_PASSWORD: password\n        ports:\n          - 5432:5432\n        options: >-\n          --health-cmd pg_isready\n          --health-interval 10s\n          --health-timeout 5s\n          --health-retries 5\n\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: Set up Ruby\n        uses: ruby/setup-ruby@v1\n        with:\n          bundler-cache: true\n\n      - name: Set up JS\n        uses: actions/setup-node@v6\n        with:\n          node-version-file: '.nvmrc'\n          cache: \"npm\"\n      - run: npm ci\n\n      - name: Install PostgreSQL client\n        run: |\n          sudo apt-get -yqq install libpq-dev\n\n      - name: Build App\n        env:\n          POSTGRES_HOST: localhost\n          DATABASE_HOST: localhost\n          POSTGRES_USER: postgres\n          CASA_DATABASE_PASSWORD: password\n          POSTGRES_PASSWORD: password\n          POSTGRES_HOST_AUTH_METHOD: trust\n          POSTGRES_PORT: 5432\n        run: |\n          bundle exec rake db:create\n          bundle exec rake db:schema:load\n          bundle exec rails assets:precompile\n\n      - name: Run rspec\n        env:\n          DATABASE_HOST: localhost\n          POSTGRES_USER: postgres\n          CASA_DATABASE_PASSWORD: password\n          POSTGRES_HOST_AUTH_METHOD: trust\n          RUN_SIMPLECOV: true\n        run: |\n          RUBYOPT='-W:no-deprecated -W:no-experimental' bundle exec rspec\n\n      - uses: qltysh/qlty-action/coverage@v2\n        with:\n          token: ${{ secrets.QLTY_COVERAGE_TOKEN }}\n          files: coverage/.resultset.json\n\n      - name: Archive selenium screenshots\n        if: ${{ failure() }}\n        uses: actions/upload-artifact@v7\n        with:\n          name: selenium-screenshots\n          path: |\n            ${{ github.workspace }}/tmp/capybara/*.png\n            ${{ github.workspace }}/tmp/capybara/*.html\n"
  },
  {
    "path": ".github/workflows/ruby_lint.yml",
    "content": "name: standardrb lint\n\non:\n  push:\n    branches:\n      - main\n    paths-ignore:\n      - 'doc/**'\n      - '**/*.md'\n      - 'bin/**'\n  pull_request:\n    branches:\n      - main\n      - rfg-event-2025\n    paths-ignore:\n      - 'doc/**'\n      - '**/*.md'\n      - 'bin/**'\n\njobs:\n  ruby_lint:\n\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: Set up Ruby\n        uses: ruby/setup-ruby@v1\n        with:\n          bundler-cache: true\n\n      - name: lint\n        run: bundle exec standardrb\n"
  },
  {
    "path": ".github/workflows/security.yml",
    "content": "name: brakeman\n\non:\n  push:\n    branches:\n      - main\n    paths-ignore:\n      - 'doc/**'\n      - '**/*.md'\n      - 'bin/**'\n  pull_request:\n    branches:\n      - main\n      - rfg-event-2025\n    paths-ignore:\n      - 'doc/**'\n      - '**/*.md'\n      - 'bin/**'\n\njobs:\n  brakeman:\n\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: Set up Ruby\n        uses: ruby/setup-ruby@v1\n        with:\n          bundler-cache: true\n\n      - name: brakeman\n        run: bundle exec brakeman\n"
  },
  {
    "path": ".github/workflows/spec_checker.yml",
    "content": "name: spec checker\n\non:\n  push:\n    branches:\n      - main\n    paths-ignore:\n      - 'doc/**'\n      - '**/*.md'\n      - 'bin/**'\n  pull_request:\n    branches:\n      - main\n      - rfg-event-2025\n    paths-ignore:\n      - 'doc/**'\n      - '**/*.md'\n      - 'bin/**'\n\njobs:\n  spec_checker:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - name: Set up Ruby\n        uses: ruby/setup-ruby@v1\n        with:\n          bundler-cache: true\n      - name: rake test_checker\n        run: bundle exec rake test_checker\n"
  },
  {
    "path": ".github/workflows/stale.yml",
    "content": "name: Mark stale issues and pull requests\n\non:\n  schedule:\n  - cron: \"30 1 * * *\"\n\njobs:\n  stale:\n\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: actions/stale@v9\n      with:\n        repo-token: ${{ secrets.GITHUB_TOKEN }}\n        stale-issue-message: \"This issue has been open without changes for a long time! What's up?\"\n        stale-pr-message: \"This PR has been open for a long time without any pushes or comments! What's up?\"\n        stale-issue-label: 'no-issue-activity'\n        stale-pr-label: 'no-pr-activity'\n        days-before-issue-close: 9999  # don't do it\n        days-before-pr-close: 30\n"
  },
  {
    "path": ".github/workflows/toc.yml",
    "content": "on:\n  push:\n    branches:\n      - main\nname: TOC Generator\njobs:\n  generateTOC:\n    name: TOC Generator\n    runs-on: ubuntu-latest\n    steps:\n      - uses: technote-space/toc-generator@v4\n        with:\n          CREATE_PR: true\n"
  },
  {
    "path": ".github/workflows/yaml_lint.yml",
    "content": "name: Yaml Lint\non:\n  push:\n    branches:\n      - main\n    paths-ignore:\n      - 'doc/**'\n      - '**/*.md'\n  pull_request:\n    # The branches below must be a subset of the branches above\n    branches:\n      - main\n    paths-ignore:\n      - 'doc/**'\n      - '**/*.md'\njobs:\n  lintAllTheThings:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - name: yaml-lint\n        uses: ibiqlik/action-yamllint@v3\n"
  },
  {
    "path": ".gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files for more about ignoring files.\n#\n# If you find yourself ignoring temporary files generated by your text editor\n# or operating system, you probably want to add a global ignore instead:\n#   git config --global core.excludesfile '~/.gitignore_global'\n\n# Ignore bundler config.\n/.bundle\n\n# Ignore all logfiles and tempfiles.\n/log/*\n/tmp/*\n!/log/.keep\n!/tmp/.keep\n.DS_Store\n.env\n\n# Ignore uploaded files in development.\n/storage/*\n!/storage/.keep\n\n.byebug_history\n\n# Ignore master key for decrypting credentials and more.\n/config/master.key\n/config/credentials/production.key\n/config/credentials.yml.enc\n\n# intellij IDE\n*.iml\n\n/public/packs\n/public/packs-test\n/node_modules\n/yarn-error.log\nyarn-debug.log*\n.yarn-integrity\n\n.rails_routes*\n.vscode\n\ncoverage/\n.idea/\nvendor/bundle/\n.generators\n\n# produced by tests (?)\n.rakeTasks\n\n.*.swp\n\n# Used for local developmen on Nix systems\n.direnv/\n.envrc\ndefault.nix\n\nnpm-debug.log\n\n# Ignore compiled assets\n/app/assets/builds/*\n\n# In case we want to keep something in the compiled assets directory\n!/app/assets/builds/.keep\n.aider*\n"
  },
  {
    "path": ".npmrc",
    "content": "engine-strict=true"
  },
  {
    "path": ".nvmrc",
    "content": "lts/krypton\n"
  },
  {
    "path": ".prettierrc.yml",
    "content": "printWidth: 100\n"
  },
  {
    "path": ".rspec",
    "content": "--require spec_helper\n"
  },
  {
    "path": ".rspec_parallel",
    "content": "--format RspecJunitFormatter --out tmp/reports/rspec_<%= ENV[\"GROUPS_UNDERSCORE\"] %>_<%= ENV[\"TEST_ENV_NUMBER\"] %>.xml\n--format ParallelTests::RSpec::SummaryLogger\n--format ParallelTests::RSpec::RuntimeLogger --out tmp/parallel_runtime.log\n--format ParallelTests::RSpec::FailuresLogger --out tmp/failing_specs.log\n"
  },
  {
    "path": ".rubocop.yml",
    "content": "require:\n  - standard\n\nplugins:\n  - rubocop-capybara\n  - rubocop-factory_bot\n  - rubocop-rspec\n  - rubocop-rspec_rails\n\ninherit_gem:\n  pundit: config/rubocop-rspec.yml\n"
  },
  {
    "path": ".ruby-gemset",
    "content": "casa"
  },
  {
    "path": ".ruby-version",
    "content": "4.0.2\n"
  },
  {
    "path": ".slugignore",
    "content": "*.md\n/docs\n/spec"
  },
  {
    "path": ".standard.yml",
    "content": "extend_config:\n  - .rubocop.yml\n\nplugins:\n  - standard-rails\n\nignore:\n  - 'storage/**/*'\n  - 'db/migrate/2020*.rb'\n  - 'db/migrate/2021*.rb'\n  - 'db/migrate/2022*.rb'\n  - 'db/migrate/2023*.rb'\n  - 'db/migrate/202405*.rb'\n  - 'db/migrate/202406*.rb'\n  - 'lib/tasks/deployment/2020*'\n  - 'lib/tasks/deployment/2021*'\n  - 'lib/tasks/deployment/2022*'\n  - 'lib/tasks/deployment/2023*'\n"
  },
  {
    "path": ".standard_todo.yml",
    "content": "# Auto generated files with errors to ignore.\n# Remove from this list as you refactor files.\n---\nignore:\n- app/controllers/all_casa_admins/sessions_controller.rb:\n  - Layout/EmptyLinesAfterModuleInclusion\n  - Rails/LexicallyScopedActionFilter\n- app/controllers/api/v1/base_controller.rb:\n  - Rails/LexicallyScopedActionFilter\n- app/controllers/casa_cases_controller.rb:\n  - Rails/TimeZone\n  - Style/EmptyStringInsideInterpolation\n- app/controllers/case_contacts/followups_controller.rb:\n  - Layout/EmptyLineAfterGuardClause\n- app/controllers/case_contacts_controller.rb:\n  - Rails/LexicallyScopedActionFilter\n  - Style/HashSlice\n- app/controllers/checklist_items_controller.rb:\n  - Rails/TimeZone\n- app/controllers/court_dates_controller.rb:\n  - Rails/TimeZone\n- app/controllers/emancipation_checklists_controller.rb:\n  - Layout/EmptyLinesAfterModuleInclusion\n- app/controllers/imports_controller.rb:\n  - Layout/EmptyLinesAfterModuleInclusion\n- app/controllers/other_duties_controller.rb:\n  - Layout/EmptyLineAfterGuardClause\n- app/controllers/reimbursements_controller.rb:\n  - Rails/TimeZone\n- app/controllers/users/sessions_controller.rb:\n  - Layout/EmptyLinesAfterModuleInclusion\n  - Rails/LexicallyScopedActionFilter\n- app/datatables/volunteer_datatable.rb:\n  - Rails/CompactBlank\n  - Style/EmptyStringInsideInterpolation\n- app/decorators/casa_case_decorator.rb:\n  - Layout/EmptyLineAfterGuardClause\n  - Rails/Date\n- app/decorators/case_assignment_decorator.rb:\n  - Rails/Date\n- app/decorators/contact_type_decorator.rb:\n  - Layout/EmptyLinesAfterModuleInclusion\n- app/helpers/banner_helper.rb:\n  - Rails/TimeZone\n- app/helpers/followup_helper.rb:\n  - Layout/EmptyLineAfterGuardClause\n- app/helpers/phone_number_helper.rb:\n  - Rails/Blank\n  - Style/RedundantRegexpEscape\n- app/helpers/sidebar_helper.rb:\n  - Layout/EmptyLineAfterGuardClause\n- app/mailers/fund_request_mailer.rb:\n  - Rails/Date\n- app/mailers/learning_hours_mailer.rb:\n  - Rails/Date\n- app/mailers/supervisor_mailer.rb:\n  - Rails/Date\n- app/models/casa_case.rb:\n  - Rails/IgnoredColumnsAssignment\n  - Rails/Date\n  - Rails/PluckInWhere\n- app/models/casa_case_contact_type.rb:\n  - Rails/UniqueValidationWithoutIndex\n- app/models/casa_case_emancipation_category.rb:\n  - Rails/UniqueValidationWithoutIndex\n- app/models/casa_org.rb:\n  - Layout/EmptyLinesAfterModuleInclusion\n  - Rails/UniqueValidationWithoutIndex\n- app/models/case_assignment.rb:\n  - Rails/UniqueValidationWithoutIndex\n  - Rails/RedundantPresenceValidationOnBelongsTo\n- app/models/case_contact.rb:\n  - Layout/EmptyLinesAfterModuleInclusion\n  - Rails/FindEach\n- app/models/case_contact_contact_type.rb:\n  - Rails/UniqueValidationWithoutIndex\n- app/models/case_group.rb:\n  - Rails/UniqueValidationWithoutIndex\n- app/models/concerns/CasaCase/validations.rb:\n  - Rails/TimeZone\n- app/models/concerns/api.rb:\n  - Layout/EmptyLinesAfterModuleInclusion\n- app/models/contact_type.rb:\n  - Rails/UniqueValidationWithoutIndex\n- app/models/contact_type_group.rb:\n  - Rails/UniqueValidationWithoutIndex\n- app/models/hearing_type.rb:\n  - Rails/UniqueValidationWithoutIndex\n- app/models/judge.rb:\n  - Rails/UniqueValidationWithoutIndex\n- app/models/language.rb:\n  - Rails/UniqueValidationWithoutIndex\n- app/models/learning_hour.rb:\n  - Rails/Date\n- app/models/learning_hour_topic.rb:\n  - Rails/UniqueValidationWithoutIndex\n- app/models/learning_hour_type.rb:\n  - Rails/UniqueValidationWithoutIndex\n- app/models/mileage_rate.rb:\n  - Rails/IgnoredColumnsAssignment\n- app/models/supervisor_volunteer.rb:\n  - Rails/UniqueValidationWithoutIndex\n- app/models/user.rb:\n  - Rails/Date\n- app/models/volunteer.rb:\n  - Rails/WhereEquals\n  - Layout/EmptyLineAfterGuardClause\n- app/notifications/delivery_methods/sms.rb:\n  - Layout/EmptyLinesAfterModuleInclusion\n- app/policies/user_policy.rb:\n  - Layout/EmptyLineAfterGuardClause\n- app/services/casa_case_change_service.rb:\n  - Rails/Pluck\n  - Layout/EmptyLineAfterGuardClause\n- app/services/case_contacts_export_csv_service.rb:\n  - Layout/EmptyLineAfterGuardClause\n- app/services/fdf_inputs_service.rb:\n  - Layout/EmptyLineAfterGuardClause\n  - Rails/Blank\n  - Rails/RootPathnameMethods\n- app/services/learning_hours_export_csv_service.rb:\n  - Layout/EmptyLineAfterGuardClause\n- app/services/short_url_service.rb:\n  - Layout/EmptyLinesAfterModuleInclusion\n- app/validators/user_validator.rb:\n  - Rails/Blank\n- config/application.rb:\n  - Style/RedundantInterpolation\n- config/initializers/bugsnag.rb:\n  - Style/FileNull\n- db/migrate/20250507011754_remove_unused_indexes.rb:\n  - Rails/ReversibleMigration\n- db/seeds/db_populator.rb:\n  - Layout/EmptyLineAfterGuardClause\n  - Rails/TimeZone\n  - Rails/FindEach\n  - Rails/Date\n- lib/mailers/debug_preview_mailer.rb:\n  - Rails/ApplicationMailer\n- lib/mailers/previews/devise_mailer_preview.rb:\n  - Rails/Date\n- lib/mailers/previews/fund_request_mailer_preview.rb:\n  - Rails/Date\n- lib/mailers/previews/volunteer_mailer_preview.rb:\n  - Rails/Date\n- lib/tasks/case_contact_types_reminder.rb:\n  - Layout/EmptyLineAfterGuardClause\n  - Rails/ActiveRecordAliases\n- lib/tasks/data_post_processors/contact_topic_populator.rb:\n  - Rails/FindEach\n- lib/tasks/data_post_processors/contact_type_populator.rb:\n  - Rails/FindEach\n- lib/tasks/deployment/20240604121427_migrate_notifications.rake:\n  - Rails/ApplicationRecord\n- lib/tasks/deployment/99991023145114_store_deploy_time.rake:\n  - Rails/TimeZone\n- lib/tasks/example_recurring_task.rb:\n  - Layout/EmptyLinesAfterModuleInclusion\n- lib/tasks/lint_factory_bot.rake:\n  - Layout/EmptyLineAfterGuardClause\n- lib/tasks/no_contact_made_reminder.rb:\n  - Rails/ActiveRecordAliases\n- lib/tasks/supervisor_weekly_digest.rb:\n  - Rails/TimeZone\n- lib/tasks/test_checker.rake:\n  - Layout/EmptyLineAfterGuardClause\n- scripts/import_casa_case_date_of_birth.rb:\n  - Layout/EmptyLineAfterGuardClause\n- spec/controllers/concerns/accessible_spec.rb:\n  - Layout/EmptyLinesAfterModuleInclusion\n- spec/datatables/reimbursement_datatable_spec.rb:\n  - Rails/TimeZone\n- spec/decorators/casa_case_decorator_spec.rb:\n  - Rails/TimeZone\n- spec/decorators/user_decorator_spec.rb:\n  - Rails/TimeZone\n- spec/factories/healths.rb:\n  - Rails/TimeZone\n- spec/factories/users.rb:\n  - Rails/TimeZone\n- spec/helpers/notifications_helper_spec.rb:\n  - Rails/TimeZone\n- spec/lib/tasks/data_post_processors/contact_topic_populator_spec.rb:\n  - Rails/FindEach\n- spec/lib/tasks/data_post_processors/contact_type_populator_spec.rb:\n  - Rails/FindEach\n- spec/mailers/learning_hours_mailer_spec.rb:\n  - Rails/Date\n- spec/mailers/supervisor_mailer_spec.rb:\n  - Rails/Date\n- spec/models/casa_case_spec.rb:\n  - Rails/TimeZone\n- spec/models/casa_org_spec.rb:\n  - Style/RedundantInterpolation\n  - Rails/Date\n- spec/models/case_contact_report_spec.rb:\n  - Performance/Count\n- spec/models/case_contact_spec.rb:\n  - Rails/TimeZone\n- spec/models/case_court_report_spec.rb:\n  - Layout/EmptyLinesAfterModuleInclusion\n- spec/models/learning_hours_report_spec.rb:\n  - Layout/EmptyLineAfterGuardClause\n- spec/requests/bulk_court_dates_spec.rb:\n  - Rails/Date\n- spec/requests/casa_cases_spec.rb:\n  - Rails/Date\n  - Rails/TimeZone\n- spec/requests/case_contact_reports_spec.rb:\n  - Rails/Date\n- spec/requests/case_court_reports_spec.rb:\n  - Layout/EmptyLinesAfterModuleInclusion\n- spec/requests/court_dates_spec.rb:\n  - Layout/EmptyLinesAfterModuleInclusion\n- spec/requests/notifications_spec.rb:\n  - Rails/Date\n- spec/system/casa_cases/edit_spec.rb:\n  - Rails/Date\n- spec/system/casa_cases/new_spec.rb:\n  - Rails/Date\n- spec/system/case_contacts/new_spec.rb:\n  - Rails/Date\n- spec/system/checklist_items/destroy_spec.rb:\n  - Rails/TimeZone\n- spec/system/checklist_items/edit_spec.rb:\n  - Rails/TimeZone\n- spec/system/checklist_items/new_spec.rb:\n  - Rails/TimeZone\n- spec/system/learning_hours/edit_spec.rb:\n  - Rails/Date\n- spec/system/other_duties/new_spec.rb:\n  - Rails/Date\n- spec/system/reports/index_spec.rb:\n  - Rails/Date\n- spec/system/volunteers/edit_spec.rb:\n  - Rails/Date\n- spec/views/casa_orgs/edit.html.erb_spec.rb:\n  - Style/RedundantInterpolation\n- spec/views/templates/email_templates_spec.rb:\n  - Rails/RootPathnameMethods\n"
  },
  {
    "path": ".tool-versions",
    "content": "ruby 4.0.2\nnodejs 24.13.0\n"
  },
  {
    "path": ".yamllint.yml",
    "content": "extends: default\n\nignore:\n  - .standard_todo.yml  # generated by standard\n  - .git\n  - node_modules\n\nrules:\n  document-start:\n    present: false\n\n  # 80 chars should be enough, but don't fail if a line is longer\n  line-length:\n    max: 80\n    level: warning\n    allow-non-breakable-words: true\n    allow-non-breakable-inline-mappings: true\n\n  indentation:\n    spaces: 2\n    indent-sequences: consistent\n    check-multi-line-strings: false\n  comments:\n    require-starting-space: true\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "[Please press here if you want to contribute!](./doc/CONTRIBUTING.md)\n"
  },
  {
    "path": "DEPLOY_CHECKLIST.md",
    "content": "See https://github.com/rubyforgood/casa/wiki/deploy-checklist\n"
  },
  {
    "path": "Dockerfile",
    "content": "ARG ROOT=/usr/src/app/\n\n# available alpine packages: https://pkgs.alpinelinux.org/packages\n\nFROM node:24-alpine AS node-source\n\nFROM ruby:4.0.2-alpine AS build\n  ARG ROOT\n  WORKDIR $ROOT\n\n  RUN apk update && apk upgrade && apk add --update --no-cache \\\n    build-base \\\n    curl-dev \\\n    libffi-dev \\\n    yaml-dev \\\n    linux-headers \\\n    postgresql-dev \\\n    tzdata\n\n  RUN bundle config set force_ruby_platform true\n\n  COPY Gemfile* $ROOT\n  RUN bundle install\n\nFROM ruby:4.0.2-alpine\n  ARG ROOT\n  WORKDIR $ROOT\n\n  RUN apk update && apk upgrade && apk add --update --no-cache \\\n    bash \\\n    build-base \\\n    curl \\\n    imagemagick \\\n    postgresql-client \\\n    tzdata \\\n    vim \\\n    && rm -rf /var/cache/apk/*\n\n  COPY . .\n  COPY --from=node-source /usr/local/bin/node /usr/local/bin/node\n  COPY --from=node-source /usr/local/lib/node_modules /usr/local/lib/node_modules\n  RUN ln -s /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm\n  RUN npm ci\n\n  COPY --from=build /usr/local/bundle/ /usr/local/bundle/\n\n  EXPOSE 3000\n\n  ENTRYPOINT [\"./docker-entrypoint.sh\"]\n  CMD [\"bin/rails\", \"s\", \"-b\", \"0.0.0.0\"]\n"
  },
  {
    "path": "Gemfile",
    "content": "# frozen_string_literal: true\n\nsource \"https://rubygems.org\"\n\nruby \"4.0.2\"\ngem \"rails\", \"~> 7.2\"\n\ngem \"after_party\" # Post-deployment tasks\ngem \"amazing_print\" # Easier console reading\ngem \"authtrail\" # Track Devise login activity\ngem \"azure-storage-blob\", require: false\ngem \"blueprinter\" # JSON serialization\ngem \"bugsnag\" # Error tracking in production\ngem \"caxlsx\", \"~> 4.4\" # Excel spreadsheets - TODO can we remove this version restriction?\ngem \"caxlsx_rails\", \"~> 0.6.4\" # Excel spreadsheets - TODO can we remove this version restriction?\ngem \"cssbundling-rails\", \"~> 1.4\" # CSS compilation\ngem \"delayed_job_active_record\" # Background job processing\ngem \"devise\" # Authentication\ngem \"devise_invitable\" # User invitation system for Devise\ngem \"draper\" # Decorators for cleaner presentation logic\ngem \"filterrific\" # Filtering and sorting of models\ngem \"flipper\" # Feature flag management\ngem \"flipper-active_record\" # Active Record adapter for Flipper\ngem \"flipper-ui\" # Web UI for managing feature flags\ngem \"friendly_id\", \"~> 5.6.0\" # Allows us to use a slug instead of CASA case IDs in their URLs\ngem \"groupdate\" # Group data by time periods\ngem \"httparty\" # HTTP network requests\ngem \"image_processing\", \"~> 1.14\" # Image processing helpers\ngem \"jbuilder\" # JSON API builder\ngem \"jsbundling-rails\" # JavaScript bundling\ngem \"lograge\" # Log less so Heroku Papertrail quits rate limiting our logs\ngem \"net-imap\" # Ruby 3.1+ requires explicit inclusion of standard library gems\ngem \"net-pop\" # Ruby 3.1+ requires explicit inclusion of standard library gems\ngem \"net-smtp\", require: false # Ruby 3.1+ requires explicit inclusion of standard library gems\ngem \"noticed\" # Notifications\ngem \"oj\" # Faster JSON parsing\ngem \"pagy\" # Fast and lightweight pagination\ngem \"paranoia\" # Soft-delete support for Active Record models\ngem \"pdf-forms\" # Filling in fund request PDFs with user input\ngem \"pg\" # Use PostgreSQL as the database for Active Record\ngem \"pghero\" # PostgreSQL performance monitoring and query insights\ngem \"pg_query\" # PostgreSQL query parser\ngem \"pretender\" # Allows admins to impersonate users\ngem \"puma\", \"~> 8.0\" # Use Puma as the app server\ngem \"pundit\" # Authorization management based on user.role field\ngem \"rack-attack\" # Blocking & throttling abusive requests\ngem \"rack-cors\" # Cross-origin resource sharing\ngem \"request_store\" # Per-request global storage for thread-safe data\ngem \"rexml\" # PDF-forms needs this to deploy to Heroku\ngem \"rswag-api\" # Swagger API documentation\ngem \"rswag-ui\" # Swagger UI\ngem \"sablon\" # Word document templating tool for Case Court Reports\ngem \"scout_apm\" # Application performance monitoring\ngem \"scout_apm_logging\", \"~> 2.1\" # Scout APM logging integration\ngem \"sprockets-rails\" # Asset pipeline for Rails\ngem \"stimulus-rails\" # Stimulus JavaScript framework\ngem \"strong_migrations\" # Catch unsafe database migrations\ngem \"turbo-rails\", \"~> 2.0\" # Turbo framework for Rails\ngem \"twilio-ruby\" # Twilio helper functions\ngem \"tzinfo-data\", platforms: %i[mingw mswin x64_mingw jruby] # Windows does not include zoneinfo files\ngem \"view_component\" # View components for reusability\ngem \"wicked\" # Multi-step form wizard for Rails\n\ngroup :development, :test, :production do\n  gem \"brakeman\" # Security inspection\n  gem \"prosopite\" # N+1 query detection via SQL pattern analysis\n  gem \"byebug\", platforms: %i[mri mingw x64_mingw] # Debugger console\n  gem \"dotenv-rails\" # Environment variable management\n  gem \"erb_lint\", require: false # ERB linter\n  gem \"factory_bot_rails\" # Test data factories\n  gem \"faker\" # Creates realistic seed data, valuable for staging and demos\n  gem \"parallel_tests\" # Run tests in parallel\n  gem \"pry\" # Enhanced Ruby console\n  gem \"pry-byebug\" # Pry debugger integration\n  gem \"rspec_junit_formatter\" # JUnit XML formatter for RSpec\n  gem \"rspec-rails\" # RSpec testing framework\n  gem \"rubocop-capybara\", require: false # Capybara linting rules\n  gem \"rubocop-factory_bot\", require: false # FactoryBot linting rules\n  gem \"rubocop-performance\", require: false # Performance linting rules\n  gem \"rubocop-rspec\", require: false # RSpec linting rules\n  gem \"rubocop-rspec_rails\", require: false # RSpec Rails linting rules\n  gem \"rswag-specs\" # Swagger spec generation\n  gem \"shoulda-matchers\" # RSpec matchers for common Rails functionality\n  gem \"standard\", require: false # Ruby style guide\n  gem \"standard-rails\", require: false # Rails-specific style guide\nend\n\ngroup :development do\n  gem \"annotate\" # Adds database field listings to models as comments\n  gem \"bundler-audit\" # Checks for security issues in gems\n  gem \"letter_opener\" # Opens emails in new tab for easier testing\n  gem \"simplecov-mcp\" # SimpleCov MCP integration\n  gem \"spring\" # Speeds up development by keeping your application running in the background\n  gem \"spring-commands-rspec\" # Spring integration for RSpec\n  gem \"traceroute\" # Finds unused routes\n  gem \"web-console\", \"~> 4.0\" # Interactive console on exception pages\nend\n\ngroup :test do\n  gem \"capybara\" # Integration testing framework\n  gem \"capybara-screenshot\" # Automatic screenshot on test failure\n  gem \"database_cleaner-active_record\" # Database cleaning strategies for tests\n  gem \"docx\" # For testing Word document generation\n  gem \"email_spec\" # Email testing helpers\n  gem \"rails-controller-testing\" # Controller testing helpers\n  gem \"rspec-retry\" # Retries flaky tests\n  gem \"selenium-webdriver\" # Browser automation for system tests\n  gem \"simplecov\", require: false # Code coverage analysis\n  gem \"webmock\" # HTTP request stubber\nend\n"
  },
  {
    "path": "LICENSE.md",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2020 Ruby for Good\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "PROSOPITE_TODO.md",
    "content": "# Prosopite N+1 Query Issues\n\nIssues detected by Prosopite during test suite run. Fix by adding eager loading (`includes`, `preload`) or restructuring queries.\n\n## High Priority (20+ occurrences)\n\n### app/decorators/casa_case_decorator.rb:34\n- **Method:** `map` in decorator\n- **Likely fix:** Add `includes` for associated records being accessed in iteration\n\n### app/models/user.rb:84\n- **Method:** `create_preference_set`\n- **Query:** `SELECT \"preference_sets\".* FROM \"preference_sets\" WHERE \"preference_sets\".\"user_id\" = $1`\n- **Likely fix:** Check if preference_set already loaded before querying\n\n### app/models/concerns/api.rb:11\n- **Method:** `initialize_api_credentials`\n- **Query:** `SELECT \"api_credentials\".* FROM \"api_credentials\" WHERE \"api_credentials\".\"user_id\" = $1`\n- **Likely fix:** Check if api_credentials already loaded before querying\n\n### app/lib/importers/file_importer.rb:50\n- **Method:** `create_user_record`\n- **Queries:** Multiple user lookups during import\n- **Likely fix:** Batch lookups or use `Prosopite.pause` for intentional import operations\n\n## Medium Priority (10-19 occurrences)\n\n### app/validators/user_validator.rb:56\n- **Method:** `validate_uniqueness`\n- **Query:** `SELECT \"users\".* FROM \"users\" WHERE \"users\".\"type\" = $1 AND \"users\".\"email\" = $2`\n- **Likely fix:** Consider if validation is necessary during bulk operations\n\n### app/lib/importers/supervisor_importer.rb:47\n- **Method:** `block in assign_volunteers`\n- **Query:** `SELECT \"users\".* FROM \"users\" INNER JOIN \"supervisor_volunteers\"...`\n- **Likely fix:** Preload volunteers before assignment loop\n\n### app/lib/importers/supervisor_importer.rb:51\n- **Method:** `block in assign_volunteers`\n- **Query:** `SELECT \"users\".* FROM \"users\" WHERE \"users\".\"id\" = $1`\n- **Likely fix:** Batch load users by ID before iteration\n\n### app/controllers/case_contacts/form_controller.rb:156\n- **Method:** `block in create_additional_case_contacts`\n- **Likely fix:** Eager load case contact associations\n\n## Lower Priority (5-9 occurrences)\n\n### app/models/court_date.rb:32\n- **Method:** `associated_reports`\n- **Likely fix:** Add `includes` for court report associations\n\n### app/lib/importers/supervisor_importer.rb:23\n- **Method:** `block in import_supervisors`\n- **Query:** `SELECT \"users\".* FROM \"users\" WHERE \"users\".\"type\" = $1 AND \"users\".\"email\" = $2`\n- **Likely fix:** Batch check existing supervisors before import loop\n\n## Lower Priority (2-4 occurrences)\n\n### app/lib/importers/file_importer.rb:57\n- **Method:** `email_addresses_to_users`\n- **Likely fix:** Batch load users by email\n\n### app/lib/importers/case_importer.rb:41\n- **Method:** `create_casa_case`\n- **Likely fix:** Preload or batch casa_case lookups\n\n### app/decorators/contact_type_decorator.rb:14\n- **Method:** `last_time_used_with_cases`\n- **Likely fix:** Eager load case associations\n\n### app/datatables/reimbursement_datatable.rb:25\n- **Method:** `block in data`\n- **Query:** `SELECT \"addresses\".* FROM \"addresses\" WHERE \"addresses\".\"user_id\" = $1`\n- **Likely fix:** Add `includes(:address)` to reimbursement query\n\n### app/services/volunteer_birthday_reminder_service.rb:7\n- **Method:** `block in send_reminders`\n- **Likely fix:** Eager load volunteer associations\n\n### app/models/contact_topic.rb:27\n- **Method:** `block in generate_for_org!`\n- **Likely fix:** Batch operations or use `Prosopite.pause` for setup\n\n### app/models/casa_org.rb:82\n- **Method:** `user_count`\n- **Likely fix:** Use counter cache or single count query\n\n### app/models/casa_org.rb:62\n- **Method:** `case_contacts_count`\n- **Likely fix:** Use counter cache or single count query\n\n### app/lib/importers/volunteer_importer.rb:23\n- **Method:** `block in import_volunteers`\n- **Likely fix:** Batch check existing volunteers before import loop\n\n### app/lib/importers/case_importer.rb:20\n- **Method:** `block in import_cases`\n- **Likely fix:** Batch check existing cases before import loop\n\n### app/controllers/case_contacts/form_controller.rb:26\n- **Method:** `block (2 levels) in update`\n- **Likely fix:** Eager load contact associations\n\n## Single Occurrence\n\n### app/services/missing_data_export_csv_service.rb:40\n- **Method:** `full_data`\n\n### app/policies/contact_topic_answer_policy.rb:18\n- **Method:** `create?`\n\n### app/models/casa_case.rb:152\n- **Method:** `next_court_date`\n\n### app/models/all_casa_admins/casa_org_metrics.rb:16\n- **Method:** `map`\n\n### config/initializers/sent_email_event.rb:7\n- **Method:** `block in <top (required)>`\n- **Query:** `SELECT \"casa_orgs\".* FROM \"casa_orgs\" WHERE \"casa_orgs\".\"id\" = $1`\n- **Note:** Initializer callback - consider caching org lookup\n\n## Notes\n\n- **Importers:** Many N+1s occur in import code. Consider wrapping entire import operations in `Prosopite.pause { }` if the N+1 pattern is intentional for per-record processing, or batch-load records before iteration.\n\n- **Decorators:** Add `includes` at the controller/query level before passing to decorators.\n\n- **Callbacks:** User model callbacks (`create_preference_set`, `initialize_api_credentials`) fire on each create. Consider if these can be optimized or if the N+1 is acceptable for single-record operations.\n\n## How to Fix\n\n```ruby\n# Before (N+1)\nusers.each { |u| u.orders.count }\n\n# After (eager loading)\nusers.includes(:orders).each { |u| u.orders.count }\n\n# For intentional batch operations\nProsopite.pause do\n  records.each { |r| process_individually(r) }\nend\n```\n"
  },
  {
    "path": "Procfile",
    "content": "release: bundle exec rake db:migrate && bundle exec rake after_party:run\nweb: bundle exec puma\ncustom_web: bundle exec puma\n"
  },
  {
    "path": "Procfile.dev",
    "content": "web: bin/rails server -p 3000 -b 0.0.0.0\njs: npm run build:dev --watch\ncss: npm run build:css:dev --watch\n"
  },
  {
    "path": "README.md",
    "content": "# CASA Project and Organization Overview\n\n[![rspec](https://github.com/rubyforgood/casa/workflows/rspec/badge.svg)](https://github.com/rubyforgood/casa/actions/workflows/rspec.yml)\n[![erb lint](https://github.com/rubyforgood/casa/actions/workflows/erb_lint.yml/badge.svg)](https://github.com/rubyforgood/casa/actions/workflows/erb_lint.yml)\n[![standardrb lint](https://github.com/rubyforgood/casa/actions/workflows/ruby_lint.yml/badge.svg)](https://github.com/rubyforgood/casa/actions/workflows/ruby_lint.yml)\n[![brakeman](https://github.com/rubyforgood/casa/workflows/brakeman/badge.svg)](https://github.com/rubyforgood/casa/actions/workflows/security.yml)\n[![npm lint](https://github.com/rubyforgood/casa/actions/workflows/npm_lint_and_test.yml/badge.svg)](https://github.com/rubyforgood/casa/actions/workflows/npm_lint_and_test.yml)\n\n[![Maintainability](https://api.codeclimate.com/v1/badges/24f3bb10db6afac417e2/maintainability)](https://codeclimate.com/github/rubyforgood/casa/trends/technical_debt)\n[![Test Coverage](https://api.codeclimate.com/v1/badges/24f3bb10db6afac417e2/test_coverage)](https://codeclimate.com/github/rubyforgood/casa/trends/test_coverage_total)\n[![Snyk Vulnerabilities](https://snyk.io/test/github/rubyforgood/casa/badge.svg)](https://snyk.io/test/github/rubyforgood/casa)\n[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/rubyforgood/casa.svg)](http://isitmaintained.com/project/rubyforgood/casa \"Average time to resolve an issue\")\n\nA CASA (Court Appointed Special Advocate) is a role where a volunteer advocates on behalf of a youth in their county's foster care system. CASA is also the namesake role of the national organization, CASA, which exists to cultivate and supervise volunteers carrying out this work – with county level chapters (operating relatively independently of each other) across the country.\n\n<!-- START doctoc generated TOC please keep comment here to allow auto update -->\n<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->\n**Table of Contents**\n\n  - [Welcome contributors!](#welcome-contributors)\n    - [Communication and Collaboration](#communication-and-collaboration)\n    - [About this project](#about-this-project)\n- [Developing! ✨🛠✨](#developing-)\n  - [How to Contribute](#how-to-contribute)\n  - [Installation](#installation)\n    - [Getting Started (Codespaces - EXPERIMENTAL) 🛠️](#getting-started-codespaces---experimental-)\n    - [Local Setup Instructions](#local-setup-instructions)\n    - [Platform Specific Installation Instructions](#platform-specific-installation-instructions)\n    - [Common issues](#common-issues)\n  - [Running the App / Verifying Installation](#running-the-app--verifying-installation)\n- [Other Documentation](#other-documentation)\n- [Acknowledgements](#acknowledgements)\n- [Feedback](#feedback)\n\n<!-- END doctoc generated TOC please keep comment here to allow auto update -->\n\n## Welcome contributors!\n\nWe are very happy to have you! CASA and Ruby for Good are committed to welcoming new contributors of all skill levels.\n\nFind issues to work on [here](https://github.com/rubyforgood/casa/issues?q=is%3Aissue+is%3Aopen+no%3Aassignee) on the issue board. Issues on the [project's](https://github.com/rubyforgood/casa/projects/1) TODO column are another way to browse issues. Check to see that no one is assigned to the issue. Then comment on it to claim the issue. Commenting on an issue doesn't automatically get the issue assigned so double check the comments on an issue to see that no one is requesting assignment.\n\nPull requests which are not for an issue but which improve the codebase are also welcome! Feel free to make GitHub issues for bugs and improvements. A maintainer will be keeping an eye on issues and PRs every day or three.\n\n### Communication and Collaboration\n\nWe highly recommend that you join us in [slack](https://join.slack.com/t/rubyforgood/shared_invite/zt-34b5p4vk3-NWIw6hKs2ma~wm7mYSe0_A) in the #casa channel so you can get fast help for any questions you may have.\n\nCheck out [our google calendar](https://bit.ly/casacal) to see when office hours and stakeholder meetings are.\n\nYou can also open an issue or comment on an issue on GitHub and a maintainer will reply to you.\n\n### About this project\n\nCASA is a national organization with many regional chapters. We currently work with [Prince George's County CASA in Maryland](https://pgcasa.org/), [Montgomery CASA Maryland](https://casaspeaks4kids.com), and [Howard County Maryland](https://marylandcasa.org/programs/howard-county/)\n\nThis system provides value by:\n\n- providing volunteers with a portal for logging activity\n- allow supervisors to oversee volunteer activity\n- generate reports on volunteer activity for admins to use in grant proposals\n\nRead about the [product sense](doc/productsense.md) that guides our approach to this work.\n\n**How CASA works:**\n\n- A foster youth is represented as a **CASA case**.\n- The **CASA case** is assigned to a **volunteer**.\n- The **volunteer** records their efforts spent on the CASA case as **case contacts**.\n- **Supervisors** oversee CASA **volunteers** by monitoring, tracking, and advising them on **CASA case** activities.\n- At PG CASA, the minimum volunteer commitment is one year (this varies by CASA chapter, in San Francisco the minimum commitment is ~ two years).  A volunteer's  lifecycle is very long, so there's a lot of activity for chapters to organize.\n\n**Project Considerations**\n\n- PG CASA is operating under a very tight budget. Right now, they manually input volunteer data into [a volunteer management software built specifically for CASA](http://www.simplyoptima.com/), but upgrading their account for multiple user licenses to allow volunteers to self-log activity data is beyond their budget. Hence why we are building as lightweight a solution as possible that can sustain itself with Ruby for Good's support.\n- While the scope of this platform's use is currently for PG County CASA and Montgomery county CASA, we are building with a mind toward multitenancy so this platform could prospectively be used by other CASA chapters across the country.\n\n**More information:**\n\nThe complete [role description of a CASA volunteer](https://pgcasa.org/volunteer-description/) in Prince George's County.\n\n# Developing! ✨🛠✨\n## How to Contribute\n  See our [contributing guide](./doc/CONTRIBUTING.md) 💖 ✨\n## Installation\n\n###  Getting Started (Codespaces - EXPERIMENTAL) 🛠️\n\n[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/rubyforgood/casa/tree/main?quickstart=1)\n\n1. Follow the link above or follow instructions to [create a new Codespace.](https://docs.github.com/en/codespaces/developing-in-a-codespace/creating-a-codespace-for-a-repository); You can use the web editor, or even better open the Codespace in VSCode\n2. Wait for the container to start. This will take a few (10-15) minutes since Ruby needs to be installed, the database needs to be created, and the `bin/setup` script needs to run\n3. Run `bin/dev` and visit the URL that pops in VSCode up to see the CASA page\n4. Login as a sample user with these default credentials (which also work for [QA](https://casa-qa.herokuapp.com/)):\n\n### Local Setup Instructions\n**Downloading the Project**\n(*on a Mac or Linux machine*)\n1. `git clone https://github.com/rubyforgood/casa.git` clone the repo to your local machine.\n2. You can ask a [maintainer](https://github.com/rubyforgood/casa/wiki/Who's-who%3F) for permission to make a branch on this repo.\n3. You can also [create a fork on GitHub](https://docs.github.com/en/get-started/quickstart/fork-a-repo) and make a pull request from the fork.\n\n**Ruby**\n1. Install a ruby version manager: [rvm](https://rvm.io/) or [rbenv](https://github.com/rbenv/rbenv)\n1. when you cd into the project directory, let your version manager install the ruby version in `.ruby-version`. Right now that's Ruby 4.0.2\n1. `gem install bundler`\n\n**node.js**\n1. (Recommended) Install [nvm](https://github.com/nvm-sh/nvm#installing-and-updating), which is a **n**ode **v**ersion **m**anager.\n    - If you use asdf, the node version from `.tool-versions` will be used, but may be out of sync with the codename version in `.nvmrc`. To use the version from `.nvmrc`, see one of these options: [legacy file codename support](https://github.com/asdf-vm/asdf-nodejs?tab=readme-ov-file#partial-and-codename-versions) or [installing via custom script](https://github.com/asdf-vm/asdf-nodejs/issues/382#issuecomment-2258647554).\n1. Install a current LTS version of Node. Running `nvm install` from this directory will read the `.nvmrc` file to install the correct version.\n\n**PostgreSQL (\"postgres\")**\n1. Make sure that postgres is installed.\n  - If you're on Ubuntu/WSL, use `sudo apt-get install libpq-dev` so the gem can install. [Use the Postgres repo for Ubuntu or WSL to get the server and client tools](https://www.postgresql.org/download/linux/ubuntu/).\n  - If you're on Fedora/Cent Os use `sudo dnf install libpq-devel`. [If you prefer choose package of libpq-devel via rpm](https://pkgs.org/download/libpq-devel)\n  - If you're on Windows, use the official [installer](https://www.postgresql.org/download/windows/) and accept all defaults.  Alternatively, a [Chocolatey](https://chocolatey.org/packages/postgresql) package is available with `choco install postgresql`.\n\n**Chrome Browser**\n\n1. The Spec tests uses Chrome Browser and Chromedriver for some of the tests. A current version of chromedriver will be installed when `bundle install` is run. TO install Chrome, see [Chrome Install](https://support.google.com/chrome/answer/95346?hl=en&ref_topic=7439538).\n\nAnother option is to install the Chromium browser for your operating system so the browser-based Ruby feature/integration tests can run. Installing `chromium-browser` is enough, including for many WSL (Windows subsystem for Linux) distributions.\n\nIf you are using Ubuntu on WSL and receive the following message when trying to run the test suite...\n\n> Command '/usr/bin/chromium-browser' requires the chromium snap to be installed. Please install it with:\n> `snap install chromium`\n\n...check out the instructions on [installing google-chrome and chromedriver for WSL Ubuntu](https://github.com/rubyforgood/casa/blob/main/doc/WSL_SETUP.md#google-chrome).\n\n### Platform Specific Installation Instructions\n - [Docker](doc/DOCKER.md)\n - [Linux](doc/LINUX_SETUP.md)\n - [Mac](doc/MAC_SETUP.md)\n - Windows(Help Wanted)\n - [Windows Subsystem for Linux(WSL)](https://github.com/rubyforgood/casa/blob/main/doc/WSL_SETUP.md)\n - [Nix](doc/NIX_SETUP.md)\n\n### Common issues\n\n1. If your rails/rake commands hang forever instead of running, try: `rails app:update:bin`\n1. There is currently no option for a user to sign up and create an account through the UI. This is intentional. If you want to log in, use a pre-seeded user account and its credentials.\n1. If you are on windows and see the error \"Requirements support for mingw is not implemented yet\" then use https://rubyinstaller.org/ instead\n1. Install imagemagick to see images locally. Instructions: https://imagemagick.org/script/download.php\n1. _If you are running on an M1 mac, run the following command before you start the installation process:_\n   1. _Set the architecture_: `$env /usr/bin/arch -arm64 /bin/zsh ---login`\n   1. _Remove all gems before you proceed_: `gem uninstall -aIx`\n1. If `bin/setup` fails with a credentials error:\n   1. Open the `.env` file.\n   1. Update the values of `POSTGRES_USER` and `POSTGRES_PASSWORD`to match your PostgreSQL credentials.\n   1. Run `bin/setup`\n\n## Running the App / Verifying Installation\n1. `cd casa/`\n1. Run `bin/setup`\n1. Run `bin/dev` and visit http://localhost:3000/ to see the app running.\n\n**Logging in with seed users**\n\nLogin as a regular user at http://localhost:3000/users/sign_in. Some example seed users:\n- volunteer1@example.com    view site as a volunteer\n- supervisor1@example.com   view site as a supervisor\n- casa_admin1@example.com   view site as an admin\n- casa_admin2-1@example.com view site as admin from a different org\n\nLogin as an all CASA admin at http://localhost:3000/all_casa_admins/sign_in. An example seed user:\n- allcasaadmin@example.com view site as an all CASA admin\n\nThe password for all seed users is `12345678`\n\n**Local email**\n\nWe are using [Letter Opener](https://github.com/ryanb/letter_opener) in\ndevelopment to receive mail. All emails sent in development should open in a\nnew tab in the browser.\n\nTo see local email previews, check out http://localhost:3000/rails/mailers\n\n**Running Tests**\n - run the ruby test suite `bin/rails spec`\n - run the javascript test suite `npm run test`\n\nIf you have trouble running tests, check out CI scripts in [`.github/workflows/`](.github/workflows/) for sample commands.\nTest coverage is run by simplecov on all builds and aggregated by CodeClimate\n\n**Cleaning up before you pull request**\n\nRun `bin/lint` to run all linters and fix issues. This will run:\n\n1. `bundle exec standardrb --fix` auto-fix Ruby linting issues [more linter info](https://github.com/testdouble/standard)\n1. `bundle exec erb_lint --lint-all --autocorrect` [ERB linter](https://github.com/Shopify/erb-lint)\n1. `npm run lint:fix` to run the [JS linter](https://standardjs.com/index.html) and fix issues\n1. `rake factory_bot:lint` if you have been editing factories and want to find factories and traits which produce invalid objects\n\nIf additional work arises from your pull request that is outside the scope of the issue it resolves, please open a new issue.\n\n**Stimulus**\n\n[Issue 5016](https://github.com/rubyforgood/casa/issues/5016) started a refactor of Javascript to use\n[Hotwire's Stimulus](https://stimulus.hotwired.dev/handbook/origin). To see if it's working for you, go to\n`/casa_cases` and see **Stimulus is working!** in your browser console.\n\n**Post-deployment tasks**\n\nWe are using [After Party](https://github.com/theSteveMitchell/after_party) to\nrun post-deployment tasks. These tasks may include one-time necessary updates to the\ndatabase. Run the tasks manually by:\n```\nbundle exec rake after_party:run\n```\n\nAlternatively, every time you pull the main branch, run:\n```\nbin/update\n```\n\nwhich will run any database migrations, update gems and node packages, and run\nthe after party post-deployment tasks.\n\n# Other Documentation\nCheck out [the wiki](https://github.com/rubyforgood/casa/wiki)\n\nThere is a `doc` directory at the top level that includes:\n* an `architecture-decisions` directory containing important architectural decisions and entity relationship diagrams of various models\n  (see the article [Architectural Decision Records](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions) describing this approach).\n* [Code of Conduct](doc/code-of-conduct.md)\n* [productsense.md](doc/productsense.md)(for team leads & product interested contributors)\n* [SECURITY.md](doc/SECURITY.md)\n\n# Acknowledgements\n\nThank you to [Scout](https://ter.li/h8k29r) for letting us use their dashboard for free!\n[<img src=\"https://user-images.githubusercontent.com/578159/165240278-c2c0ac30-c86f-4b67-9da6-e6a5e4ab4c37.png\" width=\"400\" height=\"400\" />](https://ter.li/h8k29r)\n\nJoin info for all public meetings is posted in the rubyforgood slack in the #casa channel\n\n# Feedback\n\nWe are very interested in your feedback! Please give us some :) https://forms.gle/1D5ACNgTs2u9gSdh9\n"
  },
  {
    "path": "Rakefile",
    "content": "# Add your own tasks in files placed in lib/tasks ending in .rake,\n# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.\n\nrequire_relative \"config/application\"\n\nRails.application.load_tasks\n\nif Rails.env.development?\n  require \"bundler/audit/task\"\n  Bundler::Audit::Task.new\nend\n"
  },
  {
    "path": "SECURITY.md",
    "content": "See [doc/SECURITY.md](./doc/SECURITY.md) 💖"
  },
  {
    "path": "app/assets/builds/.keep",
    "content": ""
  },
  {
    "path": "app/assets/config/manifest.js",
    "content": "//= link_directory ../builds .css\n//= link_directory ../builds .js\n//= link_tree ../images\n//= link_tree ../fonts\n//= link shared/noscript.css\n"
  },
  {
    "path": "app/assets/stylesheets/actiontext.css",
    "content": "/*\n * Provides a drop-in pointer for the default Trix stylesheet that will format the toolbar and\n * the trix-editor content (whether displayed or under editing). Feel free to incorporate this\n * inclusion directly in any other asset bundle and remove this file.\n *\n *= require trix\n*/\n\n/*\n * We need to override trix.css’s image gallery styles to accommodate the\n * <action-text-attachment> element we wrap around attachments. Otherwise,\n * images in galleries will be squished by the max-width: 33%; rule.\n*/\n.trix-content .attachment-gallery > action-text-attachment,\n.trix-content .attachment-gallery > .attachment {\n  flex: 1 0 33%;\n  padding: 0 0.5em;\n  max-width: 33%;\n}\n\n.trix-content .attachment-gallery.attachment-gallery--2 > action-text-attachment,\n.trix-content .attachment-gallery.attachment-gallery--2 > .attachment, .trix-content .attachment-gallery.attachment-gallery--4 > action-text-attachment,\n.trix-content .attachment-gallery.attachment-gallery--4 > .attachment {\n  flex-basis: 50%;\n  max-width: 50%;\n}\n\n.trix-content action-text-attachment .attachment {\n  padding: 0 !important;\n  max-width: 100% !important;\n}\n"
  },
  {
    "path": "app/assets/stylesheets/android_app_associations.css",
    "content": "/*\n  Place all the styles related to the matching controller here.\n  They will automatically be included in application.css.\n*/\n"
  },
  {
    "path": "app/assets/stylesheets/application.scss",
    "content": "// links to stylesheets inside node_modules\n@use \"bootstrap/scss/bootstrap.scss\" with (\n  $primary: #00447c\n);\n@use \"bootstrap-datepicker/dist/css/bootstrap-datepicker\";\n@use \"datatables.net-dt/css/jquery.dataTables\";\n@use \"@fortawesome/fontawesome-free/scss/fontawesome\";\n@use \"@fortawesome/fontawesome-free/scss/solid\";\n@use \"select2/dist/css/select2\";\n@use \"tom-select/dist/css/tom-select.bootstrap5.css\";\n\n// custom css for the app\n@use \"base/breakpoints\";\n\n@use \"shared/notifier\";\n@use \"shared/dashboard\";\n@use \"shared/data_tables_overide\";\n@use \"shared/flashes\";\n@use \"shared/footer\";\n@use \"shared/form\";\n@use \"shared/layout\";\n@use \"shared/navbar\";\n@use \"shared/noscript\";\n@use \"shared/select2_additional_styles\";\n@use \"shared/sidebar\";\n@use \"shared/tomselect_additional_styles.scss\";\n@use \"shared/typography\";\n@use \"shared/utilities\";\n@use \"shared/validated_form_component\";\n@use \"shared/truncated_text_component\";\n\n@use \"pages/all_casa_admin_dashboard\";\n@use \"pages/casa_cases\";\n@use \"pages/casa_org\";\n@use \"pages/case_contacts\";\n@use \"pages/case_contacts_form\";\n@use \"pages/court_dates\";\n@use \"pages/emancipation\";\n@use \"pages/login\";\n@use \"pages/password\";\n@use \"pages/patch_notes\";\n@use \"pages/reimbursements\";\n@use \"pages/supervisors\";\n@use \"pages/volunteers\";\n\n@use \"trix/dist/trix.css\";\n@use \"actiontext\";\n"
  },
  {
    "path": "app/assets/stylesheets/base/breakpoints.scss",
    "content": "$small: 640px;\n$medium: 1024px;\n$large: 1200px;\n\n// If you adjust this breakpoint, you also need to adjust the breakpoint value in app/javascript/controllers/sidebar-controller.js\n$mobile: 768px;\n"
  },
  {
    "path": "app/assets/stylesheets/base/variables.scss",
    "content": "$primary: #00447c;\n$red: #dc3545;\n\n// ======== Sidebar colors\n$sidebar-inactive: #9AA4CA;\n$sidebar-active: #365CF5;\n$sidebar-dark: #1A2142;\n\n:root {\n  --casa-red: #D50100;\n  --casa-primary-blue: #4A6CF7;\n\n  --gunmetal: #262D3F;\n  --dark-gunmetal: #212529;\n  --dark: #262d3f;\n  --dark-electric-blue: #5D657B;\n  --gray: #5d657b;\n  --dark-gray: #AAAAAA;\n  --gray-80: #CCCCCC;\n  --silver: #D9D9D9;\n\n  --inactive: #9AA4CA;\n\n  --input-placeholder-text: var(--gray);\n  --input-border-color: var(--gray-80);\n}\n"
  },
  {
    "path": "app/assets/stylesheets/pages/all_casa_admin_dashboard.scss",
    "content": ".dashboard, .all_casa_admins-dashboard {\n  .container {\n    @media (max-width: 1200px) {\n      max-width: 95%;\n    }\n  }\n\n  .dashboard-table-header {\n    h1 {\n      display: inline-block;\n      margin-right: 15px;\n      vertical-align: middle;\n    }\n\n    a {\n      display: inline-block;\n    }\n  }\n\n  .cases {\n    background: lightgrey;\n    padding: 10px;\n  }\n\n  .dropdown {\n    margin-left: 8px;\n  }\n\n  .button {\n    border: 1px black solid;\n    padding: 10px;\n    background-color: #00447c;\n  }\n\n  .button > a {\n    color: white;\n    &:visited {\n      color: white;\n    }\n  }\n}\n"
  },
  {
    "path": "app/assets/stylesheets/pages/casa_cases.scss",
    "content": "@use \"../base/breakpoints.scss\" as screen-sizes;\n\nbody.casa_cases {\n  .form-header {\n    h1 {\n      display: inline-block;\n      margin-right: 15px;\n      vertical-align: middle;\n    }\n\n    a {\n      display: inline-block;\n    }\n  }\n\n  .a2cldr {\n    width: 190px;\n  }\n\n  .a2cldr .a2cldr-btn {\n    width: 190px;\n    text-align: center;\n  }\n\n  .a2cldr-list {\n    position: absolute;\n    width: 100%;\n  }\n  .cal-btn {\n    display: inline-block;\n  }\n\n  .court-date-row {\n    display: flex;\n    align-items: center;\n    gap: 10px;\n\n    .court-date-link {\n      flex: 1;\n    }\n\n    .cal-btn {\n      flex-shrink: 0;\n\n      .a2cldr {\n        width: 120px !important;\n\n        .a2cldr-btn {\n          width: 120px !important;\n          font-size: 12px;\n          padding: 4px 8px;\n          text-align: center;\n        }\n      }\n    }\n  }\n\n  #cc-check {\n    p {\n      display: flex;\n      align-items: center;\n    }\n\n    #with_cc {\n      margin-right: 5px;\n    }\n  }\n\n  #reminder_button {\n    margin-left: 30px;\n    margin-bottom: 0;\n  }\n\n  .case-contact-list {\n      margin-bottom: 20px;\n  }\n\n  .datetime-year-month select.date-input {\n    width: 130px;\n    padding-bottom: 15px;\n  }\n\n  .court-orders {\n    textarea {\n      display: block;\n      margin-bottom: 5px;\n    }\n\n    textarea, .add-court-order {\n      width: 500px;\n    }\n\n    .implementation-status {\n      align-self: flex-start;\n      min-height: 38px;\n    }\n\n    .remove-court-order-button {\n      align-self: flex-start;\n    }\n  }\n\n  .court-order-entry {\n    display: flex;\n    gap: 8px;\n    align-items: center;\n  }\n\n  select {\n    padding: 5px;\n  }\n\n  .emancipation-btn {\n    padding: 5px 10px;\n    font-size: 10px;\n    line-height: 16px;\n    vertical-align: middle;\n  }\n}\n\n@media only screen and (max-width: screen-sizes.$small) {\n  body.casa_cases {\n    .court-orders {\n      textarea, .add-court-orders {\n        width: 100%;\n      }\n    }\n\n    .court-order-entry {\n      display: grid;\n      grid-template-columns: minmax(100px, 100%) 1fr;\n      gap: 2px;\n\n      .implementation-status {\n        grid-row: 2;\n      }\n\n      margin: 1em 1em 2em 1em;\n    }\n  }\n}\n\nbody.casa_cases-show {\n  .past-court-dates.add-container {\n    display: block;\n  }\n}\n\n#generate-docx-report-modal {\n  .dates-container {\n    display: flex;\n  }\n\n  .docx-report__modal-body {\n    display: flex;\n    flex-direction: column;\n    row-gap: 1rem;\n  }\n\n  .modal-footer {\n    [data-bs-dismiss=\"modal\"] {\n      padding-block: 11px;\n    }\n  }\n}\n\n.generate-report-button {\n  display: flex;\n  flex-direction: row;\n  width: 100%;\n  border: solid 1px var(--silver);\n  padding: 1.2rem;\n  column-gap: 1.2rem;\n\n  &:hover {\n    border-color: black;\n  }\n\n  svg {\n    min-inline-size: 40px;\n    width: 40px;\n    height: auto;\n  }\n\n  div {\n    text-align: left;\n    h3 {\n      margin-block-end: .25rem;\n    }\n    p {\n    }\n  }\n}\n\n.card-text {\n  ul {\n    display: flex;\n    flex-direction: column;\n    gap: .2rem;\n  }\n}\n\n#case-selection:required, .select2.select2-container {\n  background: transparent;\n  border: 1px solid #e5e5e5;\n  border-radius: 10px;\n  padding: 8px;\n  appearance: none;\n  -webkit-appearance: none;\n  -moz-appearance: none;\n}\n\n// removes select2 dropdown default border\nspan.select2-selection.select2-selection--single {\n  border: none;\n}\n\n// removes select2 dropdown default arrow\n.select2-selection__arrow {\n  display: none;\n}\n\n.select-wrapper {\n  display: flex;\n  flex-direction: column;\n  gap: 8px;\n}\n\nspan[role=combobox] {\n  padding-top: 2px;\n}\n"
  },
  {
    "path": "app/assets/stylesheets/pages/casa_org.scss",
    "content": "#casa_org_logo{\n  height: 2.7rem;\n  overflow: hidden;\n}\n"
  },
  {
    "path": "app/assets/stylesheets/pages/case_contacts.scss",
    "content": ".slide-container {\n    padding: 0rem 1rem;\n}\n\n.slider {\n    display: inline;\n}\n\n.slider-row {\n    margin: 0px;\n}\n\n.dataTables_scrollHeadInner {\n  width: 100% !important;\n}\n\n.followup-button {\n  min-width: max-content;\n}\n\n.flex-container {\n  position: relative;\n  display: flex;\n  flex-direction: column;\n  min-width: 0;\n  word-wrap: break-word;\n}\n\n.field-card {\n    background-color: #fff;\n    background-clip: border-box;\n    border: 1px solid rgba(0,0,0,0.125);\n    border-radius: .25rem;\n    padding: 1rem;\n    box-shadow: 0 4px 12px 5px rgba(0, 0, 0, 0.08);\n}\n\n.pr-6 {\n    padding-right: 1.5rem !important;\n}\n\n.red-letter {\n    color: #d50100;\n    font-weight: 900;\n    display: inline;\n}\n\n.label-font-weight {\n    font-weight: 900;\n}\n\n.cc-field {\n    height: calc(1.5em + .75rem + 2px);\n    padding: .375rem .75rem;\n    font-size: 1rem;\n    line-height: 1.5;\n    color: #495057;\n    background-color: #fff;\n    background-clip: padding-box;\n    border: 1px solid #ced4da;\n    border-radius: .25rem;\n    transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out;\n}\n\n.expense-container {\n    width: 100%;\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    gap: .5rem;\n    justify-content: flex-end;\n}\n\n.expense-container {\n    & > *:last-child {\n        width: 80%;\n    };\n    .field_with_errors {\n        input {\n            width: 100%;\n        }\n    }\n }\n\n.other-expenses li {\n    margin-bottom: .2rem;\n}\n\n.other-expense-amount {\n    width: 20%;\n    margin-left: .4rem;\n    color: #5D657B !important;\n}\n\n.add-expense-link {\n    margin-top: -.5rem;\n    margin-bottom: .5rem;\n    display: block;\n    padding-left: .8rem;\n    text-decoration: underline;\n}\n\n#add-another-expense {\n    cursor: pointer;\n}\n\n.dollar-sign {\n    margin: 0px 10px 0 0;\n    display: flex;\n    align-items: center;\n  }\n\n.dollar-sign .other-expense-amount {\n    position: relative;\n    padding-left: 32px;\n}\n\n.dollar-sign input::placeholder {\n    color: #5D657B;\n}\n\n.dollar-sign:before {\n    position: absolute;\n    top: 17px;\n    content: \"$\";\n    left: 28px;\n    z-index: 100;\n}\n\n.pr-7 {\n    padding-right: 1.5rem !important;\n    padding-left: 2.5rem !important;\n}\n\n.pr-9 {\n    padding-left: 3.1rem !important;\n}\n\n.wide_button {\n    width: 100%;\n    height: 3rem;\n    font-weight: 900;\n}\n\n.cc-italic {\n    font-style: italic;\n}\n\n.expand-toggle {\n  display: inline-block;\n  cursor: pointer;\n  background: none;\n  border: 0;\n  padding: 0;\n  transition: transform 0.3s;\n\n  &.expanded {\n    transform: rotate(180deg);\n  }\n}\n\n.expanded-content {\n  padding: 0.75rem 1rem;\n\n  .expanded-topic {\n    margin-bottom: 0.75rem;\n\n    strong {\n      display: block;\n    }\n\n    p {\n      margin: 0.25rem 0 0;\n    }\n  }\n}\n\n.expanded-empty {\n  padding: 0.75rem 1rem;\n  margin: 0;\n}\n"
  },
  {
    "path": "app/assets/stylesheets/pages/case_contacts_form.scss",
    "content": "@use \"../base/breakpoints.scss\" as screen-sizes;\n\n#case-contact-form {\n  color: var(--gunmetal);\n\n  & section {\n    background: var(--white, #FFFFFF);\n    border: 1px solid var(--form-element-stroke, #E5E5E5);\n    border-radius: 10px;\n    box-shadow: 0px 0px 5px 0px #1A252F0D;\n    margin-bottom: 30px;\n    padding: 30px 20px;\n  }\n\n  & h2 {\n    font-size: 28px;\n    margin-bottom: 24px;\n  }\n\n  & h3 {\n    font-size: 16px;\n  }\n\n  & legend {\n    font-size: 16px;\n    font-weight: 400;\n    line-height: 19px;\n    color: var(--dark-electric-blue);\n  }\n\n  & label {\n    font-size: 16px;\n    font-weight: 500;\n    line-height: 19px;\n  }\n\n  & input,\n  textarea,\n  select {\n    border: 1px solid var(--input-border-color);\n\n    &::placeholder {\n      color: var(--input-placeholder-text);\n      opacity: 1;\n    }\n  }\n\n  & input[type=\"text\"],\n  input[type=\"date\"],\n  input[type=\"number\"],\n  textarea,\n  select {\n    padding: 16px;\n  }\n\n  & .form-check {\n    margin-left: 0px;\n    padding-left: 0px;\n\n    & input {\n      width: 20px;\n      height: 20px;\n      margin-left: 0px;\n      margin-right: 3px;\n      float: none;\n\n      &:checked {\n        background-color: var(--casa-primary-blue);\n        border-color: var(--casa-primary-blue);\n      }\n\n      &[type=\"checkbox\"] + label {\n        padding-top: 5px;\n        font-weight: 300;\n      }\n\n      &[type=\"radio\"] + label {\n        padding-top: 1px;\n        font-size: 16px;\n        font-weight: 300;\n        line-height: 25px;\n        color: var(--dark-electric-blue)\n      }\n    }\n\n    label {\n      font-size: 16px;\n      font-weight: 600;\n    }\n  }\n\n  & button {\n    --bs-link-color: var(--casa-primary-blue);\n\n    &.btn-link {\n      text-decoration: none;\n      padding: 0px;\n    }\n  }\n\n  & .row {\n    --bs-gutter-x: .5rem;\n  }\n\n  & #contact-form-types {\n    --group-width: 250px;\n\n    columns: var(--group-width);\n\n    & fieldset {\n      display: inline-block;\n      width: var(--group-width);\n      margin-bottom: .25rem;\n\n      & .form-check {\n        & label {\n          display: inline;\n          color: var(--dark);\n          font-weight: 400;\n          color: var(--dark-gunmetal);\n        }\n\n        & small {\n          display: block;\n          margin-left: 28px;\n          font-style: italic;\n          font-weight: 300;\n          color: var(--dark-electric-blue);\n        }\n      }\n    }\n  }\n\n  & .input-group-text {\n    border-top-right-radius: 0;\n    border-bottom-right-radius: 0;\n  }\n\n  & #contact-form-action-buttons {\n    display: flex;\n    justify-content: flex-end;\n    gap: 10px;\n\n    @media only screen and (max-width: screen-sizes.$mobile) {\n      & > a, & > button {\n        flex-grow: 1;\n      }\n    }\n  }\n}\n\n"
  },
  {
    "path": "app/assets/stylesheets/pages/case_court_reports.scss",
    "content": ""
  },
  {
    "path": "app/assets/stylesheets/pages/court_dates.scss",
    "content": "body.court_dates {\n  .court-orders {\n    textarea {\n      display: block;\n      margin-bottom: 5px;\n    }\n\n    textarea, .add-court-order {\n      width: 500px;\n    }\n  }\n\n  .court-order-entry {\n    display: flex;\n    gap: 8px;\n    align-items: center;\n  }\n\n  select {\n    padding: 5px;\n  }\n}\n"
  },
  {
    "path": "app/assets/stylesheets/pages/emancipation.scss",
    "content": ".category-options {\n  margin-left: 2em;\n\n  input[type=\"checkbox\"], input[type=\"radio\"] {\n    pointer-events: none\n  }\n}\n\n.check-item.disabled {\n  cursor: default;\n\n  label {\n    color: #757575\n  }\n}\n\n.check-item {\n  label {\n    width: 80%;\n    vertical-align: middle;\n  }\n}\n\n.emancipation-category {\n  border-bottom: 2px solid #bfe3ff;\n  margin: 0;\n  padding: 0 .5em;\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n\n  input[type=\"checkbox\"] {\n    pointer-events: none\n  }\n\n  label {\n    margin: 0;\n    padding: .5em;\n    vertical-align: middle;\n  }\n\n  span {\n    color: #00447c;\n    font-weight: bold;\n    font-size: 18pt\n  }\n}\n\n.emancipation-category:hover {\n  background-color: #f5f5f5;\n  cursor: pointer;\n\n  input[type=\"checkbox\"], label{\n    cursor: pointer\n  }\n}\n\n.emancipation-category.disabled {\n  cursor: default;\n\n  span, label{\n    color: #757575\n  }\n}\n\n.emancipation-category.disabled:hover{\n  background-color: unset\n}\n\n.no-select {\n  -webkit-touch-callout: none; /* iOS Safari */\n    -webkit-user-select: none; /* Safari */\n     -khtml-user-select: none; /* Konqueror HTML */\n       -moz-user-select: none; /* Old versions of Firefox */\n        -ms-user-select: none; /* Internet Explorer/Edge */\n            user-select: none; /* Non-prefixed version, currently\n                                  supported by Chrome, Edge, Opera and Firefox */\n}\n\n.emacipation-category-input-label-pair {\n  display: flex;\n}\n"
  },
  {
    "path": "app/assets/stylesheets/pages/feature_flags.scss",
    "content": "#feature-flags {\n  padding-top: 1em;\n  h1 {\n    padding-bottom: 1em;\n  }\n  .flag-name {\n    padding-right: 2em;\n  }\n}"
  },
  {
    "path": "app/assets/stylesheets/pages/login.scss",
    "content": "body.devise-sessions-new, body.users-sessions-new, body.devise-passwords-new {\n  .container {\n    max-width: none;\n  }\n\n  .login-email-link {\n    font-size: small;\n  }\n\n  aside {\n    background-position: right;\n    background-image: url(\"login.jpg\");\n    min-height: 700px;\n    background-repeat: no-repeat;\n    background-size: cover;\n  }\n}\n"
  },
  {
    "path": "app/assets/stylesheets/pages/password.scss",
    "content": "@use \"../base/variables.scss\" as globals;\n\n.password-box {\n  width: 80%;\n  margin: 10% auto;\n  text-align: center;\n  border-radius: 25px;\n  border: 2px solid globals.$primary;\n\n  form#edit_user.edit_user div.field {\n    margin: 0 auto;\n    width: 90%;\n    max-width: 90%;\n    flex: none;\n  }\n\n  input#user_password {\n    margin: 10px 0 10px;\n    width: 100%;\n  }\n\n  input#user_password_confirmation {\n    margin: 10px 0 10px;\n    width: 100%;\n  }\n\n  form#edit_user.edit_user div.actions input {\n    color: #fff;\n    background-color: #002f56;\n    border-color: #002849;\n    margin: 10px;\n  }\n}\n\nbody.devise-invitations.devise-invitations-edit footer {\n  width: 100%;\n}\n\nbody.devise-passwords.devise-passwords-edit div.password-box {\n  div.row {\n    margin: 0 0;\n\n    div.col-sm-6 {\n      flex: none;\n      margin: 0 auto;\n      max-width: 90%;\n      padding: 0;\n    }\n  }\n\n  a.btn.btn-info {\n    margin-bottom: 10px;\n  }\n}\n"
  },
  {
    "path": "app/assets/stylesheets/pages/patch_notes.scss",
    "content": "#patch-notes {\n  display: flex;\n\n  #patch-note-list {\n    width: 50%;\n\n    h1 {\n      text-align: left;\n    }\n\n    .patch-note-list-item {\n      & .card-body>* {\n        margin: 0.25em 0;\n      }\n\n      &.new {\n        background-color: #ffecb3;\n      }\n\n      textarea {\n        width: 100%;\n      }\n\n      .label-and-select {\n        display: flex;\n\n        label {\n          padding-right: 0.5rem;\n        }\n\n        select {\n         flex-grow: 1\n        }\n      }\n\n      .patch-note-button-controls {\n        display: flex;\n\n        button {\n          flex-grow: 1;\n\n          &:nth-child(2) {\n            margin-left: 0.5em\n          };\n\n          &.button-cancel {\n            flex-grow: 3\n          }\n        }\n      }\n    };\n  };\n}\n"
  },
  {
    "path": "app/assets/stylesheets/pages/reimbursements.scss",
    "content": ".reimbursements-index {\n  .select2-container {\n    .select2-selection {\n      max-height: 60px;\n      overflow-y: auto;\n      overflow-x: hidden;\n    }\n  }\n}\n"
  },
  {
    "path": "app/assets/stylesheets/pages/supervisors.scss",
    "content": "$indictator_negative_color: #f44336;\n$indicator_positive_color: #4caf50;\n\n.supervisor_case_contact_stats {\n  display: flex;\n\n  div {\n    &:first-child {\n      display: flex;\n      margin-right: 1em;\n      flex-grow: 1;\n    }\n\n    span {\n      border-radius: 0.25em;\n      padding-top: .25em;\n      text-align: center;\n\n      &.attempted-contact {\n        background-color: $indicator_positive_color;\n        border-top-right-radius: 0;\n        border-bottom-right-radius: 0;\n\n        & + span.no-attempted-contact {\n          border-top-left-radius: 0;\n          border-bottom-left-radius: 0;\n        }\n\n        & + span.attempted-contact-end {\n          background-color: $indicator_positive_color;\n          border-top-left-radius: 0;\n          border-bottom-left-radius: 0;\n          width: 0.25em;\n        }\n      }\n\n      &.no-attempted-contact {\n        background-color: $indictator_negative_color;\n      }\n\n      &.no-volunteers {\n        background-color: #ccc;\n      }\n\n      &:last-child {\n        margin-left: 0.5em;\n        background-color: #cfd8dc;\n        border-radius: 100vw;\n        padding: 0.2em 1em;\n      }\n    }\n  }\n\n  &.legend {\n    align-items: center;\n    margin-bottom: 0.25rem;\n    margin-top: 1em;\n\n    span {\n      &:last-child {\n        padding: .5em;\n      }\n\n      padding: .5em 1em;\n      flex-grow: 1;\n    }\n  }\n}\n\n.supervisor_table_row {\n  display: flex;\n\n  td:nth-of-type(1), th:nth-of-type(1) {\n    flex: 3;\n  }\n\n  td:nth-of-type(2), th:nth-of-type(2) {\n    flex: 3;\n  }\n\n  td:nth-of-type(3), th:nth-of-type(3) {\n    flex: 1;\n  }\n}\n"
  },
  {
    "path": "app/assets/stylesheets/pages/volunteers.scss",
    "content": "@use \"../base/breakpoints.scss\" as screen-sizes;\n\nbody.volunteers {\n  @media only screen and (max-width: screen-sizes.$mobile) {\n    table#volunteers {\n      tbody tr {\n        padding-bottom: 2.5rem;\n        border-bottom: none;\n\n        td {\n          border-left: none;\n          border-right: none;\n        }\n        td:last-child {\n          border-bottom: none;\n        }\n      }\n    }\n  }\n}\ntable#volunteers.dataTable.hover tbody tr.selected > * {\n  background-color: rgba(54, 92, 245, 0.1);\n  box-shadow: none; \n  color: inherit;\n}\ntable#volunteers.dataTable.hover > tbody > tr.selected:hover > * {\n  background-color: rgba(54, 92, 245, 0.15);\n  box-shadow: none !important; \n  color: inherit;\n}\n\n.form-check-input {\n  border: 1px solid #757575;\n}\n\n"
  },
  {
    "path": "app/assets/stylesheets/shared/dashboard.scss",
    "content": ".dashboard-table-header {\n  h1,h2,h3,h4,h5,h6 {\n    display: inline-block;\n    margin-right: 15px;\n    vertical-align: middle;\n  }\n\n  a.btn {\n    display: inline-block;\n  }\n}\n\n.dropdown-menu {\n  li {\n    input[type=\"checkbox\"] {\n      transform: scale(1.5);\n      margin: 0 8px;\n    }\n    &:not(:last-child) {\n      margin-bottom: 7px;\n    }\n  }\n  padding: 8px;\n}\n\n.supervisor-options {\n  min-width: 15rem;\n}\n\n.language-icon {\n  cursor: context-menu;\n  font-size: 1.5rem;\n  display: flex;\n  justify-content: center;\n}\n"
  },
  {
    "path": "app/assets/stylesheets/shared/data_tables_overide.scss",
    "content": "/**\n  Needed to display the prev, next, number, and ellipsis buttons on the same line. This causes issues on mobile with\n  lots of pages, but there is no current way to change the number of pages in a view or in JS based on window size.\n */\n.dataTables_paginate {\n  white-space: nowrap !important;\n\n  span {\n    display: inline-block !important;\n  }\n}\n"
  },
  {
    "path": "app/assets/stylesheets/shared/flashes.scss",
    "content": ".notice {\n  text-align: center;\n  padding: 8px;\n  margin: 12px 0;\n  list-style-position: inside;\n}\n\n.alert-dismissible .close {\n  padding: 0.5rem 0.75rem;\n}\n"
  },
  {
    "path": "app/assets/stylesheets/shared/footer.scss",
    "content": "//@use \"../base/variables\" as globals;\n//@use \"../base/breakpoints.scss\" as screen-sizes;\n//\n//footer {\n//  padding: 2rem 0;\n//  text-align: center;\n//  background-color: globals.$primary;\n//  color: #fff;\n//\n//  .default-footer {\n//    a.rfglink {\n//      color: #fff;\n//    }\n//\n//    .rfglogo {\n//      max-height: 3em;\n//    }\n//  }\n//  .terms-conditions-link {\n//    color:#fff;\n//  }\n//\n//  .footer-logos {\n//    .rfglink {\n//      .rfglogo {\n//        max-height: 6em;\n//      }\n//    }\n//  }\n//\n//  ul {\n//    list-style: none;\n//    padding-left: 0;\n//    margin-top: 2rem;\n//  }\n//\n//  li {\n//    display: inline-block;\n//\n//    a, a:hover {\n//      color: white;\n//    }\n//  }\n//\n//  li:not(:first-child):before {\n//    content: \" | \";\n//  }\n//\n//  .address {\n//    margin-bottom: 2rem;\n//  }\n//\n//  .copyright {\n//    font-size: 18px;\n//    font-weight: 700;\n//  }\n//}\n//\n//.footer.container-fluid {\n//  min-height: calc(100vh - 45px);\n//}\n//\n//footer.logged-in {\n//  position: relative;\n//  height: 40px;\n//  left: 0px;\n//  bottom: 0px;\n//  background: #F8F8F8;\n//  box-shadow: 0px -2px 12px rgba(0, 0, 0, 0.25);\n//  padding: 2rem;\n//  padding-top: 1rem;\n//  padding-left: 2%;\n//  margin-left: 320px;\n//  width: calc(100% - 320px);\n//\n//  span {\n//    font-family: 'Montserrat';\n//    font-style: normal;\n//    font-weight: 400;\n//    font-size: 14px;\n//    line-height: 140%;\n//    color: #2E2E2E;\n//  }\n//\n//  a {\n//    white-space: nowrap;\n//  }\n//\n//  @media only screen and (max-width: screen-sizes.$mobile) {\n//    width: 100%;\n//    margin-left: 0;\n//    padding-bottom: 1rem;\n//    height: 100px;\n//  }\n//}\n"
  },
  {
    "path": "app/assets/stylesheets/shared/form.scss",
    "content": "@use \"../base/variables.scss\" as globals;\n\nform {\n  .field_with_errors {\n    label {\n      @extend .text-danger !optional;\n      @extend .font-weight-bold !optional;\n    }\n\n    input {\n      border: 1px solid globals.$red;\n    }\n\n  }\n\n  .read-more {\n    display: inline;\n  }\n\n  .alert.alert-danger {\n    ul {\n      margin-bottom: 0;\n    }\n  }\n\n  #with_cc {\n    margin-left: 20px;\n  }\n}\n\n#cc-check {\n  margin-left: 30px;\n  display: initial;\n}\n\n"
  },
  {
    "path": "app/assets/stylesheets/shared/header.scss",
    "content": "@use \"../base/breakpoints.scss\" as screen-sizes;\n\n.header {\n  .header-left {\n    .menu-toggle-btn {\n      display: none;\n\n      .main-btn {\n        border-radius: 4px !important;\n      }\n    }\n  }\n}\n\n@media only screen and (max-width: screen-sizes.$mobile) {\n  .header {\n    .header-left {\n      .menu-toggle-btn {\n        display: block;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "app/assets/stylesheets/shared/layout.scss",
    "content": "@use \"../base/variables.scss\" as globals;\n@use \"../base/breakpoints.scss\" as screen-sizes;\n\nbody {\n  background-color: #f4f7fa;\n  min-width: 320px;\n}\n\n.content {\n  margin-left: 320px;\n  padding: 2%;\n  position: relative;\n  overflow: auto;\n}\n\n.casa-case-scroll {\n  width: 13rem;\n  height: auto;\n  overflow-y: auto;\n}\n\n// This may effect other pages than the case contact form!!!\nh5 label {\n  font-weight: 500;\n}\n\n\n.unbold {\n  font-weight: 100;\n}\n\n.fa-car {\n  color: globals.$primary;\n}\n\ndiv.row div.col-sm-12.form-header h1 {\n  display: inline-block;\n  margin-right: 15px;\n  vertical-align: middle;\n}\n\ndiv.row.volunteer-filters {\n  margin-top: 2rem;\n}\n\n.card.card-container {\n  box-shadow: 0 4px 12px 5px rgba(0, 0, 0, 0.08);\n  margin-bottom: 1.5rem;\n}\n\n.casa-case-button {\n  margin: 0 10px 10px 0;\n}\n\n.card-title {\n  &__hint {\n    font-size: 11px;\n    font-style: italic;\n  }\n}\n\n/* ASSUMPTION: default screen size is desktop view (> 1024px) */\n\n/* As 'mobile-label' class is mobile only, not display them on desktop view */\n\n@media only screen and (min-width: screen-sizes.$mobile) {\n  table.table {\n    span.mobile-label {\n      display: none;\n    }\n  }\n}\n\n#dropdownMenuButton {\n  margin-bottom: 0.5em;\n}\n\n@media only screen and (max-width: screen-sizes.$mobile) {\n  .login-header {\n    font-size: 0.75em !important;\n  }\n  .content {\n    padding: 3rem 0;\n    margin-left: 0;\n  }\n  .card-body {\n    padding: 0.5rem;\n  }\n  table.table {\n    thead {\n      display: none;\n    }\n    tr {\n      display: table;\n      width: 100%;\n      border-bottom: 1px solid #efefef;\n      > * {\n        display: block;\n      }\n    }\n    th,\n    td {\n      border-top: none;\n      font-size: 1.25rem;\n      span.mobile-label {\n        color: #757575;\n        display: block;\n        font-size: 0.9rem;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "app/assets/stylesheets/shared/navbar.scss",
    "content": "@use \"../base/breakpoints.scss\" as screen-sizes;\n\n.navbar {\n  color: #fff;\n  background: #00447c;\n  box-shadow: 0 0 4px 4px rgba(0,0,0,0.28);\n  height: 80px;\n  justify-content: space-between;\n  padding: 16px;\n  width: 100%;\n  z-index: 3;\n}\n\n.navbar strong {\n  font-size: 2rem;\n}\n\n.navbar.mobile {\n  display: none;\n}\n\n.mobile-icon {\n  top: -2px;\n  width: 40px;\n}\n\n@media only screen and (max-width: screen-sizes.$mobile) {\n  .navbar.mobile {\n    display: flex;\n    position: sticky;\n    top: 0;\n  }\n}\n"
  },
  {
    "path": "app/assets/stylesheets/shared/noscript.css",
    "content": ".noscript {\n  position: fixed;\n  left: 50%;\n  top: 25%;\n  transform: translateX(-50%);\n  padding: 5vw;\n  z-index: 99;\n}\n"
  },
  {
    "path": "app/assets/stylesheets/shared/notifier.scss",
    "content": "@keyframes spin {\n  0% {\n    transform: rotate(0deg)\n  }\n  100% {\n    transform: rotate(360deg)\n  }\n}\n\n#notifications {\n  align-items: end;\n  bottom: 0;\n  display: flex;\n  flex-direction: column;\n  font-weight: 900;\n  max-height: 100vh;\n  max-width: 40%;\n  overflow-y: auto;\n  pointer-events: none;\n  position: fixed;\n  right: 2em;\n  z-index: 100;\n\n  &>* {\n    pointer-events: auto;\n  }\n\n  div {\n    border-radius: .25em;\n    margin-bottom: .5em;\n    margin-left: auto;\n    margin-right: 0;\n    padding: .5em;\n\n    span {\n      display: inline\n    }\n  }\n\n  .danger-notification {\n    background-color: #b71c1c;\n    color: white\n  }\n\n  .messages {\n    margin-bottom: 0;\n    padding: 0;\n    pointer-events: none;\n\n    &>* {\n      pointer-events: auto;\n    }\n\n    div {\n      display: flex;\n      width: fit-content;\n    }\n  }\n\n  .success-notification {\n    background-color: #28a745;\n    color: white\n  }\n\n  .warning-notification {\n    background-color: #ffc107;\n  }\n\n  #async-waiting-indicator {\n    background-color: #007bff;\n    color: white;\n\n    .load-spinner {\n      animation: spin 1.5s linear infinite;\n      border: .25em solid #f3f3f3;\n      border-top: .25em solid #3498db;\n      border-radius: 50%;\n      display: inline-block;\n      height: 1em;\n      margin: 0;\n      padding: 0;\n      width: 1em\n    }\n  }\n\n  #toggle-minimize-notifications {\n    background-color: #333;\n    border: 0;\n    border-radius: 10px 10px 0 0;\n    color: white;\n    padding: 0.25em 1em;\n    width: fit-content;\n\n    span {\n      display: inline;\n    }\n  }\n}\n"
  },
  {
    "path": "app/assets/stylesheets/shared/select2_additional_styles.scss",
    "content": ".select2 {\n  input {\n    min-width: 250px;\n  }\n}\n"
  },
  {
    "path": "app/assets/stylesheets/shared/sidebar.scss",
    "content": "@use \"../base/variables.scss\" as globals;\n@use \"../base/breakpoints.scss\" as screen-sizes;\n\n#wrapper {\n    overflow-x: hidden;\n }\n\n.sidebar-wrapper {\n  background: rgba(0,0,0,0.25);\n  display: flex;\n  flex-direction: column;\n  max-height: 100vh;\n  position: fixed;\n  width: 320px;\n  overflow: auto;\n}\n\n.sidebar-wrapper .sidebar-heading {\n  position: sticky;\n  top: 0;\n  z-index: 10;\n  background: globals.$primary;\n  border-bottom: 1px solid #103862;\n  font-size: 1.2rem;\n  padding-bottom: 16px;\n  padding: 0.875rem 1.25rem;\n}\n\n.sidebar-wrapper .list-group {\n  width: 100%;\n  .list-group-item {\n    background: none;\n    color: #fff;\n    padding: 1.25rem 1.75rem;\n    text-decoration: none;\n\n    &:hover {\n      text-decoration: none;\n    }\n  }\n}\n\n#page-content-wrapper {\n  width: 100%;\n}\n\n.sidebar-container {\n  background: globals.$primary;\n  display: flex;\n  flex-direction: column;\n  justify-content: space-between;\n  height: 100vh;\n  overflow-y: auto;\n}\n\n.logo-div {\n  height: 80px;\n}\n\n// Stops weird text wrapping when opening and closing sidebar\n.nav-item {\n  width: 250px;\n}\n\n.nav-item > a {\n  color: globals.$sidebar-inactive !important;\n  height: 44px;\n\n  &:hover {\n    color: globals.$sidebar-active !important;\n\n    span {\n      color: globals.$sidebar-dark !important;\n    }\n  }\n}\n\n.nav-item.active > a {\n  color: globals.$sidebar-dark !important;\n}\n\n.btn-sidebar {\n  color: #fff !important;\n  background-color: globals.$sidebar-active !important;\n}\n\n.sidebar-wrapper .sidebar-menu {\n  margin-top: 24px;\n  .list-group-item {\n    border: none;\n    border-left: 6px solid transparent;\n    padding: 1.25rem 1.75rem;\n    a {\n      color: #fff;\n      text-decoration: none;\n    }\n    &:hover {\n      border-left: 6px solid #fff;\n    }\n    &.active {\n      background: #103862;\n      border-left: 6px solid #fff;\n      font-weight: bold;\n    }\n  }\n}\n\n.sidebar-wrapper .sidebar-footer {\n  .list-group.account-details {\n    padding-bottom: 1rem;\n    .list-group-item {\n      border: none;\n      padding: 1rem 2rem;\n      > * {\n        font-size: 0.9em;\n      }\n      .label {\n        color: #729DCB;\n        display: block;\n\n      }\n      .value, a {\n        color: #fff;\n      }\n    }\n  }\n\n  .list-group.footer-nav {\n    background: #103862;\n    padding: 1rem 0 5rem;\n    width: 100%;\n    a.list-group-item {\n      background: none !important;\n      border: none;\n      color: #fff;\n      font-size: 0.9em;\n      padding: 1rem 1.75rem;\n      text-decoration: none;\n\n      &:hover {\n        text-decoration: underline;\n      }\n    }\n  }\n}\n\n.group-actions {\n  &:hover::before {\n    opacity: 1 !important;\n    visibility: visible !important;\n  }\n  &::before {\n    opacity: 0 !important;\n    visibility: hidden !important;\n  }\n}\n// Media Queries\n@media only screen and (max-width: screen-sizes.$mobile) {\n  .sidebar-wrapper {\n    display: none;\n    width: 100%;\n    &.sidebar-open {\n      display: inherit;\n      z-index: 1;\n    }\n  }\n\n  .sidebar-container {\n    height: calc(100vh - 72px);\n    width: 320px;\n  }\n\n  .sidebar-heading {\n    display: none;\n  }\n\n  #wrapper.toggled .sidebar-wrapper {\n    margin-left: -15rem;\n  }\n\n  .footer-nav #mobile-space {\n    margin-top: 72px;\n  }\n}\n\n// These overrides are for larger than mobile screens\n@media only screen and (min-width: screen-sizes.$mobile) {\n  .sidebar-nav-wrapper {\n    -webkit-transform: translateX(0px) !important;\n    -moz-transform: translateX(0px) !important;\n    -ms-transform: translateX(0px) !important;\n    -o-transform: translateX(0px) !important;\n    transform: translateX(0px) !important;\n\n    &.active {\n      width: 66px;\n\n      -webkit-transform: translateX(0px) !important;\n      -moz-transform: translateX(0px) !important;\n      -ms-transform: translateX(0px) !important;\n      -o-transform: translateX(0px) !important;\n      transform: translateX(0px) !important;\n    }\n  }\n\n  .main-wrapper {\n    margin-left: 250px !important;\n\n    &.active {\n      margin-left: 66px !important;\n    }\n  }\n}\n"
  },
  {
    "path": "app/assets/stylesheets/shared/tomselect_additional_styles.scss",
    "content": ".ts-dropdown [data-selectable] .highlight {\n    display: inline-block;\n}"
  },
  {
    "path": "app/assets/stylesheets/shared/truncated_text_component.scss",
    "content": ".truncation-container {\n  display: flex;\n\n  a {\n    min-width: fit-content;\n    margin-inline-start: auto;\n  }\n}\n"
  },
  {
    "path": "app/assets/stylesheets/shared/typography.scss",
    "content": "// TODO check in css so tests can run without internet\n@use \"../base/variables.scss\" as globals;\n@import url(https://fonts.googleapis.com/css?family=Montserrat:400,700);\n\nbody {\n  font-family: \"Montserrat\";\n}\n\noption {\n  font-family: Helvetica, Arial, sans-serif;\n}\n\n.content-1 {\n  font-family: Inter;\n  font-size: 16px;\n  font-weight: 500;\n  line-height: 19px;\n  color: var(--dark);\n}\n\n.content-2 {\n  font-family: Inter;\n  font-size: 14px;\n  font-weight: 500;\n  line-height: 22px;\n  color: var(--gray)\n}\n\n.content-3 {\n  font-family: Inter;\n  font-size: 14px;\n  font-weight: 400;\n}\n\n.pre-line {\n  white-space: pre-line;\n}\n"
  },
  {
    "path": "app/assets/stylesheets/shared/utilities.scss",
    "content": "@use \"../base/variables.scss\" as globals;\n\n.pull-left {\n  float: left;\n}\n\n.pull-right {\n  float: right;\n}\n\n.vertically-center {\n  display: flex;\n  align-items: center;\n}\n\n.add-container {\n  display: flex;\n  justify-content: flex-start;\n  align-items: center;\n  gap: 10px;\n\n  .add-button {\n    color: white;\n    border: none;\n    background-color: #{globals.$primary};\n\n    padding: 0.25em 1.5em;\n    border-radius: 10px;\n    font-size: 1.25em;\n\n    :hover {\n      cursor: pointer;\n    }\n  }\n}\n\n.hidden {\n  display: none;\n}\n\n.line-clamp-1 {\n  overflow: hidden;\n  display: -webkit-box;\n  -webkit-box-orient: vertical;\n  -webkit-line-clamp: 1;\n}\n"
  },
  {
    "path": "app/assets/stylesheets/shared/validated_form_component.scss",
    "content": ".warning-required-checkbox {\n    background-color: #ffc107;\n    padding: 0.5em;\n\n    input[type=\"checkbox\"] {\n        width: 18px;\n        height: 18px;\n        margin-right: 0.5em;\n    }\n}"
  },
  {
    "path": "app/blueprints/api/v1/session_blueprint.rb",
    "content": "class Api::V1::SessionBlueprint < Blueprinter::Base\n  field :user do |user|\n    {\n      id: user.id,\n      display_name: user.display_name,\n      email: user.email,\n      refresh_token_expires_at: user.api_credential&.refresh_token_expires_at,\n      token_expires_at: user.api_credential&.token_expires_at\n    }\n  end\n\n  field :api_token do |user|\n    token = user.api_credential\n    token.return_new_api_token![:api_token]\n  end\n\n  field :refresh_token do |user, options|\n    token = user.api_credential\n    if options[:remember_me]\n      token.return_new_refresh_token!(true)[:refresh_token]\n    else\n      token.return_new_refresh_token!(false)[:refresh_token]\n    end\n  end\nend\n"
  },
  {
    "path": "app/callbacks/case_contact_metadata_callback.rb",
    "content": "class CaseContactMetadataCallback\n  def after_commit(case_contact)\n    changes = case_contact.saved_changes\n\n    set_status(changes, case_contact) if changes[\"id\"]\n    update_status(changes, case_contact) if changes[\"status\"] && changes[\"id\"].nil?\n  end\n\n  private\n\n  def set_status(changes, case_contact)\n    metadata = {\"status\" => {case_contact.status => case_contact.created_at}}\n    update_metadata(case_contact, metadata)\n  end\n\n  def update_status(changes, case_contact)\n    metadata = {\"status\" => {changes[\"status\"].last => Time.zone.now}}\n    update_metadata(case_contact, metadata)\n  end\n\n  def update_metadata(record, new_data)\n    metadata = record.metadata.deep_merge(new_data)\n    record.update_columns(metadata: metadata)\n  end\nend\n"
  },
  {
    "path": "app/channels/application_cable/channel.rb",
    "content": "module ApplicationCable\n  class Channel < ActionCable::Channel::Base; end\nend\n"
  },
  {
    "path": "app/channels/application_cable/connection.rb",
    "content": "module ApplicationCable\n  class Connection < ActionCable::Connection::Base; end\nend\n"
  },
  {
    "path": "app/components/badge_component.html.erb",
    "content": "<span class='badge <%= style %> text-uppercase display-1'>\n  <%= text %>\n</span>\n"
  },
  {
    "path": "app/components/badge_component.rb",
    "content": "class BadgeComponent < ViewComponent::Base\n  DARK_TEXT_TYPES = [:warning, :light]\n\n  attr_reader :text\n\n  def initialize(text:, type:, rounded: false, margin: true)\n    @text = text\n    @type = type.to_sym\n    @rounded = rounded ? \"rounded-pill\" : nil\n    @margin = margin ? \"my-1\" : nil\n  end\n\n  def style\n    badge_style = [\"bg-#{@type}\", @rounded, @margin]\n    badge_style.push(\"text-dark\") if DARK_TEXT_TYPES.include?(@type)\n    badge_style.compact.join(\" \")\n  end\nend\n"
  },
  {
    "path": "app/components/dropdown_menu_component.html.erb",
    "content": "<div class=\"dropdown <%= @class %>\">\n  <button class=\"btn btn-secondary dropdown-toggle\" type=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n    <% if icon? %>\n      <%= render_icon %>\n    <% else %>\n      <svg width=\"5\" height=\"20\" viewBox=\"0 0 5 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n        <title><%= @menu_title %></title>\n        <path d=\"M2.5 0.9375C1.82292 0.9375 1.23698 1.17188 0.742187 1.64063C0.247396 2.10938 -7.68364e-08 2.69531 -1.07571e-07 3.39844C-1.38306e-07 4.10156 0.247396 4.70052 0.742187 5.19531C1.23698 5.6901 1.82292 5.9375 2.5 5.9375C3.17708 5.9375 3.76302 5.6901 4.25781 5.19531C4.7526 4.70052 5 4.10156 5 3.39844C5 2.69531 4.7526 2.10938 4.25781 1.64063C3.76302 1.17188 3.17708 0.9375 2.5 0.9375ZM2.5 7.5C1.82292 7.5 1.23698 7.7474 0.742187 8.24219C0.247395 8.73698 -3.66538e-07 9.32292 -3.96134e-07 10C-4.25731e-07 10.6771 0.247395 11.263 0.742187 11.7578C1.23698 12.2526 1.82292 12.5 2.5 12.5C3.17708 12.5 3.76302 12.2526 4.25781 11.7578C4.7526 11.263 5 10.6771 5 10C5 9.32292 4.7526 8.73698 4.25781 8.24219C3.76302 7.7474 3.17708 7.5 2.5 7.5ZM2.5 14.0625C1.82292 14.0625 1.23698 14.3099 0.742187 14.8047C0.247395 15.2995 -6.53963e-07 15.8984 -6.84698e-07 16.6016C-7.15432e-07 17.3047 0.247395 17.8906 0.742187 18.3594C1.23698 18.8281 1.82292 19.0625 2.5 19.0625C3.17708 19.0625 3.76302 18.8281 4.25781 18.3594C4.7526 17.8906 5 17.3047 5 16.6016C5 15.8984 4.7526 15.2995 4.25781 14.8047C3.76302 14.3099 3.17708 14.0625 2.5 14.0625Z\" fill=\"#5D657B\" />\n      </svg>\n    <% end %>\n    <%= button_label %>\n  </button>\n  <ul class=\"dropdown-menu\">\n    <%= content %>\n  </ul>\n</div>\n"
  },
  {
    "path": "app/components/dropdown_menu_component.rb",
    "content": "# frozen_string_literal: true\n\nclass DropdownMenuComponent < ViewComponent::Base\n  renders_one :icon\n\n  def initialize(menu_title:, icon_name: nil, hide_label: false, render_check: true, klass: nil)\n    @menu_title = menu_title\n    @render_check = render_check\n    @hide_label = hide_label\n    @icon_name = icon_name\n    @class = klass\n  end\n\n  def render_icon\n    return icon if icon.present?\n\n    content_tag(:i, nil, class: \"lni mr-10 lni-#{@icon_name}\")\n  end\n\n  def icon?\n    icon.present? || @icon_name.present?\n  end\n\n  def render?\n    @render_check && @menu_title.present? && content.present?\n  end\n\n  def button_label\n    content_tag(:span, @menu_title, class: @hide_label ? \"sr-only\" : nil)\n  end\nend\n"
  },
  {
    "path": "app/components/form/hour_minute_duration_component.html.erb",
    "content": "<div class=\"row input-style-1\">\n  <div class=\"col-12 col-md\">\n    <%= @form.label :duration_hours, \"Hour(s)\" %>\n    <%= @form.number_field :duration_hours,\n      min: 0,\n      class: \"form-control\",\n      style: \"background:white\",\n      value: @hour_value,\n      required: true %>\n  </div>\n  <div class=\"col-12 col-md\">\n    <%= @form.label :duration_minutes, \"Minute(s)\" %>\n    <%= @form.number_field :duration_minutes,\n      min: 0,\n      class: \"form-control\",\n      style: \"background:white\",\n      value: @minute_value,\n      required: true %>\n  </div>\n</div>\n"
  },
  {
    "path": "app/components/form/hour_minute_duration_component.rb",
    "content": "# frozen_string_literal: true\n\nclass Form::HourMinuteDurationComponent < ViewComponent::Base\n  def initialize(form:, hour_value:, minute_value:)\n    @form = form\n\n    if hour_value.is_a?(String)\n      begin\n        hour_value = Integer(hour_value)\n      rescue ArgumentError\n        raise ArgumentError.new(\"Could not convert parameter hour_value to an integer\")\n      end\n    end\n\n    if hour_value.is_a?(Integer) && hour_value < 0\n      raise RangeError.new(\"Parameter hour_value must be positive\")\n    end\n\n    if hour_value.nil?\n      @hour_value = 0\n    elsif hour_value.is_a?(Integer)\n      @hour_value = hour_value\n    else\n      raise TypeError.new(\"Parameter hour_value must be an integer\")\n    end\n\n    if minute_value.is_a?(String)\n      begin\n        minute_value = Integer(minute_value)\n      rescue ArgumentError\n        raise ArgumentError.new(\"Could not convert parameter minute_value to an integer\")\n      end\n    end\n\n    if minute_value.is_a?(Integer) && minute_value < 0\n      raise RangeError.new(\"Parameter minute_value must be positive\")\n    end\n\n    if minute_value.nil?\n      @minute_value = 0\n    elsif minute_value.is_a?(Integer)\n      @minute_value = minute_value\n    else\n      raise TypeError.new(\"Parameter minute_value must be an integer\")\n    end\n  end\nend\n"
  },
  {
    "path": "app/components/form/multiple_select/item_component.html.erb",
    "content": "<div class=\"badge rounded-pill bg-primary active px-3\">\n  ${escape(data.text)}\n</div>\n"
  },
  {
    "path": "app/components/form/multiple_select/item_component.rb",
    "content": "# frozen_string_literal: true\n\nclass Form::MultipleSelect::ItemComponent < ViewComponent::Base\n  strip_trailing_whitespace\nend\n"
  },
  {
    "path": "app/components/form/multiple_select_component.html.erb",
    "content": "<div class=\"select-style-1 mr-8\"\n     data-controller=\"multiple-select\"\n     data-multiple-select-options-value=\"<%= @options %>\"\n     data-multiple-select-selected-items-value=\"<%= @selected_items %>\"\n     data-multiple-select-placeholder-term-value=\"<%= @placeholder_term %>\"\n     data-multiple-select-show-all-option-value=\"<%= @show_all_option %>\"\n     data-multiple-select-with-options-value=\"true\">\n\n  <template data-multiple-select-target=\"option\">\n    <div class=\"d-flex align-items-baseline\">\n      <span class='mr-5'>DATA_LABEL</span>\n      <% if @render_option_subtext %>\n        <small class=\"fst-italic fw-lighter\"><em>DATA_SUB_TEXT</em></small>\n      <% end %>\n    </div>\n  </template>\n\n  <template data-multiple-select-target=\"item\">\n    <div class=\"badge rounded-pill bg-primary active px-3\">DATA_LABEL</div>\n  </template>\n\n  <%# not needed, but want to be explicit when including this markup %>\n  <% if @show_all_option %>\n    <template data-multiple-select-target=\"hiddenItem\">\n      <div class=\"d-none\"></div>\n    </template>\n\n    <template data-multiple-select-target=\"selectAllOption\">\n      <div class=\"d-flex align-items-baseline\">\n        <span data-test=\"select-all-input\" class='mr-5'>DATA_LABEL</span>\n      </div>\n    </template>\n  <% end %>\n\n  <%= @form.select @name, {}, { multiple: true } , {\n    data: { \"multiple-select-target\": \"select\",  } , class: \"form-control-lg form-select form-select-lg input-group-lg\"\n  } %>\n</div>\n"
  },
  {
    "path": "app/components/form/multiple_select_component.rb",
    "content": "# frozen_string_literal: true\n\nclass Form::MultipleSelectComponent < ViewComponent::Base\n  def initialize(form:, name:, options:, selected_items:, render_option_subtext: false, placeholder_term: nil, show_all_option: false)\n    @form = form\n    @name = name\n    @options = options.to_json\n    @selected_items = selected_items\n    @render_option_subtext = render_option_subtext\n    @placeholder_term = placeholder_term\n    @show_all_option = show_all_option\n  end\nend\n"
  },
  {
    "path": "app/components/local_time_component.html.erb",
    "content": "<span title=\"<%= specific_time %>\"><%= local_time %></span>\n"
  },
  {
    "path": "app/components/local_time_component.rb",
    "content": "# frozen_string_literal: true\n\nclass LocalTimeComponent < ViewComponent::Base\n  attr_reader :format, :unix_timestamp, :time_zone\n\n  def initialize(format:, unix_timestamp:, time_zone:)\n    @format = format\n    @time_zone = time_zone\n    @unix_timestamp = unix_timestamp\n  end\n\n  def local_time\n    time = Time.at(unix_timestamp).in_time_zone(@time_zone)\n    time.strftime(@format)\n  end\n\n  def specific_time\n    Time.at(unix_timestamp).in_time_zone(@time_zone).strftime(\"%b %d, %Y, %l:%M %p %Z\")\n  end\nend\n"
  },
  {
    "path": "app/components/modal/body_component.html.erb",
    "content": "<div class=\"modal-body <%= @class %>\">\n  <%= body_content %>\n</div>\n"
  },
  {
    "path": "app/components/modal/body_component.rb",
    "content": "# frozen_string_literal: true\n\nclass Modal::BodyComponent < ViewComponent::Base\n  def initialize(text: nil, klass: nil, render_check: true)\n    @text = text\n    @render_check = render_check\n    @class = klass\n  end\n\n  def body_content\n    return content if content.present?\n\n    Array.wrap(@text).map do |text|\n      content_tag :p, text\n    end.join.html_safe\n  end\n\n  def render?\n    @render_check && (@text.present? || content.present?)\n  end\nend\n"
  },
  {
    "path": "app/components/modal/footer_component.html.erb",
    "content": "<div class=\"modal-footer <%= @class %>\">\n  <button type=\"button\" class=\"btn btn-sm btn-secondary\" data-bs-dismiss=\"modal\">Close</button>\n  <%= content %>\n</div>\n"
  },
  {
    "path": "app/components/modal/footer_component.rb",
    "content": "# frozen_string_literal: true\n\nclass Modal::FooterComponent < ViewComponent::Base\n  def initialize(klass: nil, render_check: true)\n    @render_check = render_check\n    @class = klass\n  end\n\n  def render?\n    @render_check && content.present?\n  end\nend\n"
  },
  {
    "path": "app/components/modal/group_component.html.erb",
    "content": "<div class=\"modal fade <%= @class %>\" id=\"<%= @id %>\" tabindex=\"-1\" aria-labelledby=\"<%= @id %>-label\" aria-hidden=\"true\">\n  <div class=\"modal-dialog modal-dialog-centered\">\n    <div class=\"modal-content\">\n      <%= header %>\n      <%= body %>\n      <%= footer %>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "app/components/modal/group_component.rb",
    "content": "# frozen_string_literal: true\n\nclass Modal::GroupComponent < ViewComponent::Base\n  renders_one :header, Modal::HeaderComponent\n  renders_one :body, Modal::BodyComponent\n  renders_one :footer, Modal::FooterComponent\n\n  def initialize(id:, klass: nil, render_check: true)\n    @id = id\n    @class = klass\n    @render_check = render_check\n  end\n\n  def render?\n    @render_check && (body.present? || header.present?)\n  end\nend\n"
  },
  {
    "path": "app/components/modal/header_component.html.erb",
    "content": "<div class=\"modal-header <%= @class %>\">\n  <%= header_content %>\n  <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>\n</div>\n"
  },
  {
    "path": "app/components/modal/header_component.rb",
    "content": "# frozen_string_literal: true\n\nclass Modal::HeaderComponent < ViewComponent::Base\n  def initialize(id:, text: nil, icon: nil, klass: nil, render_check: true)\n    @text = text\n    @id = id\n    @icon = icon\n    @render_check = render_check\n    @class = klass\n  end\n\n  def header_content\n    return content if content.present?\n\n    content_tag :h1, class: \"modal-title fs-5\", id: \"#{@id}-label\" do\n      concat(content_tag(:i, nil, class: \"lni mr-10 lni-#{@icon}\")) if @icon.present?\n      concat(@text)\n    end\n  end\n\n  def render?\n    @render_check && (@text.present? || content.present?)\n  end\nend\n"
  },
  {
    "path": "app/components/modal/open_button_component.html.erb",
    "content": "<button type=\"button\" class=\"<%= @class %>\" data-bs-toggle=\"modal\" data-bs-target=\"<%= \"##{@target}\" %>\">\n  <% if @icon %>\n    <i class=\"lni mr-10 lni-<%= @icon %>\"></i>\n  <% end %>\n  <%= open_button %>\n</button>\n"
  },
  {
    "path": "app/components/modal/open_button_component.rb",
    "content": "# frozen_string_literal: true\n\nclass Modal::OpenButtonComponent < ViewComponent::Base\n  def initialize(target:, text: nil, klass: nil, icon: nil, render_check: true)\n    @target = target\n    @text = text\n    @icon = icon\n    @render_check = render_check\n    @class = klass\n  end\n\n  def open_button\n    return content if content.present?\n\n    @text\n  end\n\n  def render?\n    @render_check && (@text.present? || content.present?)\n  end\nend\n"
  },
  {
    "path": "app/components/modal/open_link_component.html.erb",
    "content": "<a href=\"#\" role=\"button\" class=\"btn <%= @class %>\" data-bs-toggle=\"modal\" data-bs-target=\"<%= \"##{@target}\" %>\">\n  <% if @icon %>\n    <i class=\"lni mr-10 lni-<%= @icon %>\"></i>\n  <% end %>\n  <%= open_link %>\n</a>\n"
  },
  {
    "path": "app/components/modal/open_link_component.rb",
    "content": "# frozen_string_literal: true\n\nclass Modal::OpenLinkComponent < ViewComponent::Base\n  def initialize(target:, text: nil, icon: nil, klass: nil, render_check: true)\n    @target = target\n    @text = text\n    @icon = icon\n    @class = klass\n    @render_check = render_check\n  end\n\n  def open_link\n    return content if content.present?\n\n    @text\n  end\n\n  def render?\n    @render_check && (@text.present? || content.present?)\n  end\nend\n"
  },
  {
    "path": "app/components/notification_component.html.erb",
    "content": "<a\n  href=\"<%= mark_as_read_notification_path(notification) %>\"\n  class=\"<%= muted_display %> list-group-item list-group-item-action flex-column align-items-start\"\n  data-method=\"post\">\n  <div class=\"d-flex flex-row\">\n    <div class=\"d-flex-shrink-0\">\n      <% unless notification.read? %>\n        <i class='fas fa-bell'></i>\n      <% end %>\n    </div>\n    <div class=\"ml-2 flex-grow-1\">\n      <div class=\"d-flex justify-content-between\">\n        <h5 class=\"mb-1\"><%= notification.event.title %></h5>\n        <small><%= time_ago_in_words(notification.created_at) %> ago</small>\n      </div>\n      <div class=\"my-1\">\n        <%= simple_format notification.event.message %>\n      </div>\n    </div>\n  </div>\n</a>\n"
  },
  {
    "path": "app/components/notification_component.rb",
    "content": "# frozen_string_literal: true\n\nclass NotificationComponent < ViewComponent::Base\n  attr_reader :notification\n\n  def initialize(notification:)\n    @notification = notification\n  end\n\n  def muted_display\n    \"bg-light text-muted\" if notification.read?\n  end\nend\n"
  },
  {
    "path": "app/components/sidebar/group_component.html.erb",
    "content": "<li class=\"nav-item nav-item-has-children group-item\" data-sidebar-target=\"groupList\" data-controller=\"sidebar-group\">\n  <a\n    href=\"#0\"\n    class=\"<%= @class %>\"\n    data-bs-toggle=\"collapse\"\n    data-bs-target=\"#ddmenu_<%= @identifier %>\"\n    aria-controls=\"ddmenu_<%= @identifier %>\"\n    aria-expanded=\"true\"\n    aria-label=\"Toggle navigation\"\n    data-sidebar-group-target=\"title\">\n              <span class=\"icon\">\n                <i class=\"lni mr-10 lni-<%= @icon %>\"></i>\n              </span>\n    <span data-sidebar-target=\"linkTitle\"><%= @title %></span>\n  </a>\n  <ul id=\"ddmenu_<%= @identifier %>\" class=\"collapse dropdown-nav\" data-sidebar-group-target=\"list\">\n    <% links.each do |link| %>\n      <%= link %>\n    <% end %>\n  </ul>\n</li>\n"
  },
  {
    "path": "app/components/sidebar/group_component.rb",
    "content": "# frozen_string_literal: true\n\nclass Sidebar::GroupComponent < ViewComponent::Base\n  renders_many :links, Sidebar::LinkComponent\n\n  # @param title [String] the title/label for the link\n  # @param icon [String] the lni icon, pass just the name of the icon (ie. for lni-star --> icon: \"star\")\n  # @param render_check [Boolean] whether or not to display the link\n  def initialize(title:, icon:, render_check: true)\n    @title = title\n    @icon = icon\n    @render_check = render_check\n    @identifier = title.downcase.tr(\" \", \"-\")\n    @class = \"#{@identifier} collapsed\"\n  end\n\n  # If there are no links or all links fail their render_check, then don't render this group\n  # @return [Boolean]\n  def render?\n    @render_check && !links.empty? && !links.select(&:render?).empty?\n  end\nend\n"
  },
  {
    "path": "app/components/sidebar/link_component.html.erb",
    "content": "<li class=\"<%= @class %>\" data-sidebar-group-target=\"link\">\n  <%= link_to @path do %>\n    <% if @icon %>\n      <i class=\"lni mr-10 lni-<%= @icon %>\"></i>\n    <% end %>\n    <span data-sidebar-target=\"linkTitle\" class=\"sidebar-link\"><%= @title %></span>\n  <% end %>\n</li>\n"
  },
  {
    "path": "app/components/sidebar/link_component.rb",
    "content": "# frozen_string_literal: true\n\nclass Sidebar::LinkComponent < ViewComponent::Base\n  include SidebarHelper\n\n  # @param title [String] the title/label for the link\n  # @param path [String] the path to navigate to\n  # @param icon [String] the lni icon, pass just the name of the icon (ie. for lni-star --> icon: \"star\")\n  # @param nav_item [Boolean] whether or not the link should have the nav-item class\n  # @param render_check [Boolean] whether or not to display the link\n  def initialize(title:, path:, icon: nil, nav_item: true, render_check: true)\n    @title = title\n    @icon = icon\n    @path = path\n    @nav_item = nav_item\n    @render_check = render_check\n  end\n\n  # Must be moved to this method in order to use the SidebarHelper\n  def before_render\n    @class = @nav_item ? \"nav-item #{active_class(@path)}\" : \"\"\n  end\n\n  # @return [Boolean]\n  def render?\n    @render_check\n  end\nend\n"
  },
  {
    "path": "app/components/truncated_text_component.html.erb",
    "content": "<div class=\"truncation-container\" data-controller=\"truncated-text\">\n  <div class=\"line-clamp-1\" data-truncated-text-target=\"text\">\n    <% if label %>\n      <span class=\"text-bold d-inline\"><%= label %>:</span>\n    <% end %>\n    <%= text %>\n  </div>\n  <a\n    role=\"button\"\n    href=\"#\"\n    data-truncated-text-target=\"hideButton\"\n    data-action=\"truncated-text#toggle:prevent\"\n    class=\"d-none\">[hide]</a>\n  <a\n    role=\"button\"\n    href=\"#\"\n    data-truncated-text-target=\"moreButton\"\n    data-action=\"truncated-text#toggle:prevent\">[read more]</a>\n</div>\n"
  },
  {
    "path": "app/components/truncated_text_component.rb",
    "content": "# frozen_string_literal: true\n\nclass TruncatedTextComponent < ViewComponent::Base\n  attr_reader :text, :label\n  def initialize(text = nil, label: nil)\n    @text = text\n    @label = label\n  end\nend\n"
  },
  {
    "path": "app/controllers/additional_expenses_controller.rb",
    "content": "class AdditionalExpensesController < ApplicationController\n  before_action :force_json_format\n\n  def create\n    @additional_expense = AdditionalExpense.new(additional_expense_params)\n    authorize @additional_expense\n\n    if @additional_expense.save\n      render json: @additional_expense.as_json, status: :created\n    else\n      render json: @additional_expense.errors.as_json, status: :unprocessable_content\n    end\n  end\n\n  def destroy\n    @additional_expense = AdditionalExpense.find(params[:id])\n    authorize @additional_expense\n\n    @additional_expense.destroy!\n\n    head :no_content\n  end\n\n  private\n\n  def additional_expense_params\n    params.require(:additional_expense)\n      .permit(:case_contact_id, :other_expense_amount, :other_expenses_describe)\n  end\nend\n"
  },
  {
    "path": "app/controllers/all_casa_admins/casa_admins_controller.rb",
    "content": "class AllCasaAdmins::CasaAdminsController < AllCasaAdminsController\n  before_action :set_casa_org\n\n  def new\n    @casa_admin = CasaAdmin.new\n  end\n\n  def create\n    service = ::CreateCasaAdminService.new(@casa_org, params, current_user)\n    @casa_admin = service.build\n    begin\n      service.create!\n      redirect_to all_casa_admins_casa_org_path(@casa_org), notice: \"New admin created successfully\"\n    rescue ActiveRecord::RecordInvalid\n      render :new, status: :unprocessable_content\n    end\n  end\n\n  def edit\n    @casa_admin = CasaAdmin.find(params[:id])\n  end\n\n  def update\n    @casa_admin = CasaAdmin.find(params[:id])\n    if @casa_admin.update(all_casa_admin_params)\n      notice = check_unconfirmed_email_notice(@casa_admin)\n\n      @casa_admin.filter_old_emails!(@casa_admin.email)\n      redirect_to edit_all_casa_admins_casa_org_casa_admin_path(@casa_org), notice: notice\n    else\n      render :edit, status: :unprocessable_content\n    end\n  end\n\n  def activate\n    @casa_admin = CasaAdmin.find(params[:id])\n    if @casa_admin.activate\n      CasaAdminMailer.account_setup(@casa_admin).deliver\n\n      redirect_to edit_all_casa_admins_casa_org_casa_admin_path, notice: \"Admin was activated. They have been sent an email.\"\n    else\n      render :edit, status: :unprocessable_content\n    end\n  end\n\n  def deactivate\n    @casa_admin = CasaAdmin.find(params[:id])\n    if @casa_admin.deactivate\n      CasaAdminMailer.deactivation(@casa_admin).deliver\n\n      redirect_to edit_all_casa_admins_casa_org_casa_admin_path, notice: \"Admin was deactivated.\"\n    else\n      render :edit, status: :unprocessable_content\n    end\n  end\n\n  private\n\n  def set_casa_org\n    @casa_org = CasaOrg.find(params[:casa_org_id])\n    # @casa_org = CasaOrg.find_by(slug: params[:casa_org_id]) # TODO when using slugs\n  end\nend\n"
  },
  {
    "path": "app/controllers/all_casa_admins/casa_orgs_controller.rb",
    "content": "class AllCasaAdmins::CasaOrgsController < AllCasaAdminsController\n  def show\n    @casa_org = CasaOrg.find(params[:id])\n    @casa_org_metrics = AllCasaAdmins::CasaOrgMetrics.new(@casa_org).metrics\n  end\n\n  def new\n    @casa_org = CasaOrg.new\n  end\n\n  def create\n    @casa_org = CasaOrg.new(casa_org_params)\n\n    if @casa_org.save\n      @casa_org.generate_defaults\n      respond_to do |format|\n        format.html do\n          redirect_to all_casa_admins_casa_org_path(@casa_org),\n            notice: \"CASA Organization was successfully created.\"\n        end\n\n        format.json do\n          render json: @casa_org, status: :created\n        end\n      end\n    else\n      respond_to do |format|\n        format.html { render :new, status: :unprocessable_content }\n        format.json { render json: @casa_org.errors.full_messages, status: :unprocessable_content }\n      end\n    end\n  end\n\n  private\n\n  def casa_org_params\n    params.require(:casa_org).permit(:name, :display_name, :address)\n  end\nend\n"
  },
  {
    "path": "app/controllers/all_casa_admins/dashboard_controller.rb",
    "content": "class AllCasaAdmins::DashboardController < AllCasaAdminsController\n  def show\n    @organizations = CasaOrg.all\n  end\nend\n"
  },
  {
    "path": "app/controllers/all_casa_admins/patch_notes_controller.rb",
    "content": "class AllCasaAdmins::PatchNotesController < AllCasaAdminsController\n  # GET /patch_notes or /patch_notes.json\n  def index\n    @patch_note_groups = PatchNoteGroup.all\n    @patch_note_types = PatchNoteType.all\n    @patch_notes = PatchNote.order(created_at: :desc)\n  end\n\n  # POST /patch_notes or /patch_notes.json\n  def create\n    @patch_note = PatchNote.new(patch_note_params)\n\n    if @patch_note.save\n      render json: {status: :created, id: @patch_note.id}, status: :created\n    else\n      render json: {errors: @patch_note.errors.full_messages.to_json}, status: :unprocessable_content\n    end\n  end\n\n  # PATCH/PUT /patch_notes/1 or /patch_notes/1.json\n  def update\n    @patch_note = PatchNote.find(params[:id])\n    if @patch_note.update(patch_note_params)\n      render json: {status: :ok}\n    else\n      render json: {errors: @patch_note.errors.full_messages.to_json}, status: :unprocessable_content\n    end\n  end\n\n  # DELETE /patch_notes/1 or /patch_notes/1.json\n  def destroy\n    @patch_note = PatchNote.find(params[:id])\n\n    if @patch_note.destroy\n      render json: {status: :ok}\n    else\n      render json: {errors: @patch_note.errors.full_messages.to_json}, status: :unprocessable_content\n    end\n  end\n\n  private\n\n  # Use callbacks to share common setup or constraints between actions.\n  def set_patch_note\n    @patch_note = PatchNote.find(params[:id])\n  end\n\n  # Only allow a list of trusted parameters through.\n  def patch_note_params\n    params.permit(:note, :patch_note_group_id, :patch_note_type_id)\n  end\nend\n"
  },
  {
    "path": "app/controllers/all_casa_admins/sessions_controller.rb",
    "content": "# frozen_string_literal: true\n\nclass AllCasaAdmins::SessionsController < Devise::SessionsController\n  include Accessible\n  skip_before_action :check_user, only: :destroy\nend\n"
  },
  {
    "path": "app/controllers/all_casa_admins_controller.rb",
    "content": "class AllCasaAdminsController < ApplicationController\n  skip_before_action :authenticate_user!\n  before_action :authenticate_all_casa_admin!\n  before_action :set_custom_error_heading, only: [:update_password]\n  after_action :reset_custom_error_heading, only: [:update_password]\n  skip_after_action :verify_authorized\n\n  def new\n    @all_casa_admin = AllCasaAdmin.new\n  end\n\n  def edit\n    @user = current_all_casa_admin\n  end\n\n  def create\n    service = ::CreateAllCasaAdminService.new(params, current_user)\n    @all_casa_admin = service.build\n\n    begin\n      service.create!\n\n      respond_to do |format|\n        format.html do\n          redirect_to authenticated_all_casa_admin_root_path,\n            notice: \"New All CASA admin created successfully\"\n        end\n\n        format.json { render json: @all_casa_admin, status: :created }\n      end\n    rescue ActiveRecord::RecordInvalid\n      respond_to do |format|\n        format.html { render :new, status: :unprocessable_content }\n\n        format.json do\n          render json: @all_casa_admin.errors.full_messages, status: :unprocessable_content\n        end\n      end\n    end\n  end\n\n  def update\n    @user = current_all_casa_admin\n\n    if @user.update(all_casa_admin_params)\n      respond_to do |format|\n        format.html do\n          flash[:success] = \"Profile was successfully updated.\"\n          redirect_to edit_all_casa_admins_path\n        end\n\n        format.json { render json: @user, status: :ok }\n      end\n    else\n      respond_to do |format|\n        format.html { render :edit, status: :unprocessable_content }\n        format.json { render json: @user.errors.full_messages, status: :unprocessable_content }\n      end\n    end\n  end\n\n  def update_password\n    @user = current_all_casa_admin\n\n    if @user.update(password_params)\n      bypass_sign_in(@user)\n\n      UserMailer.password_changed_reminder(@user).deliver\n\n      respond_to do |format|\n        format.html do\n          flash[:success] = \"Password was successfully updated.\"\n\n          redirect_to edit_all_casa_admins_path\n        end\n\n        format.json { render json: \"Password was successfully updated.\", status: :ok }\n      end\n    else\n      respond_to do |format|\n        format.html { render :edit, status: :unprocessable_content }\n        format.json { render json: @user.errors.full_messages, status: :unprocessable_content }\n      end\n    end\n  end\n\n  private\n\n  def all_casa_admin_params\n    params.require(:all_casa_admin).permit(:email)\n  end\n\n  def password_params\n    params.require(:all_casa_admin).permit(:password, :password_confirmation)\n  end\n\n  def set_custom_error_heading\n    @custom_error_header = \"password change\"\n  end\n\n  def reset_custom_error_heading\n    @custom_error_header = nil\n  end\nend\n"
  },
  {
    "path": "app/controllers/android_app_associations_controller.rb",
    "content": "class AndroidAppAssociationsController < ApplicationController\n  skip_before_action :authenticate_user!\n\n  def index\n    android_asset_link_data = [\n      {\n        relation: [\"delegate_permission/common.handle_all_urls\"],\n        target: {\n          namespace: \"android_app\",\n          package_name: \"org.rubyforgood.casa\",\n          sha256_cert_fingerprints: [ENV[\"ANDROID_CERTIFICATE_FINGERPRINT\"]]\n        }\n      }\n    ]\n\n    render json: android_asset_link_data.to_json\n  end\nend\n"
  },
  {
    "path": "app/controllers/api/v1/base_controller.rb",
    "content": "class Api::V1::BaseController < ActionController::API\n  rescue_from ActiveRecord::RecordNotFound, with: :not_found\n  before_action :authenticate_user!, except: [:create, :destroy]\n\n  def authenticate_user!\n    api_token, options = ActionController::HttpAuthentication::Token.token_and_options(request)\n    user = User.find_by(email: options[:email])\n    if user && api_token && ActiveSupport::SecurityUtils.secure_compare(user.api_credential.api_token_digest, Digest::SHA256.hexdigest(api_token))\n      @current_user = user\n    else\n      render json: {message: \"Incorrect email or password.\"}, status: 401\n    end\n  end\n\n  def not_found\n    api_error(status: 404, errors: \"Not found\")\n  end\nend\n"
  },
  {
    "path": "app/controllers/api/v1/users/sessions_controller.rb",
    "content": "class Api::V1::Users::SessionsController < Api::V1::BaseController\n  def create\n    load_resource\n    if @user\n      render json: Api::V1::SessionBlueprint.render(@user, remember_me: user_params[:remember_me]), status: 201\n    else\n      render json: {message: \"Incorrect email or password.\"}, status: 401\n    end\n  end\n\n  def destroy\n    # fetch access token from request header\n    api_token = request.headers[\"Authorization\"]&.split(\" \")&.last\n    # find user's api credentials by access token\n    api_credential = ApiCredential.find_by(api_token_digest: Digest::SHA256.hexdigest(api_token))\n    # set api and refresh tokens to nil; otherwise render 401\n    if api_credential\n      api_credential.revoke_api_token\n      api_credential.revoke_refresh_token\n      render json: {message: \"Signed out successfully.\"}, status: 200\n    else\n      render json: {message: \"An error occured when signing out.\"}, status: 401\n      nil\n    end\n  end\n\n  private\n\n  def user_params\n    params.permit(:email, :password, :remember_me)\n  end\n\n  def load_resource\n    @user = User.find_by(email: user_params[:email])\n    unless @user&.valid_password?(user_params[:password])\n      @user = nil\n    end\n  end\nend\n"
  },
  {
    "path": "app/controllers/application_controller.rb",
    "content": "class ApplicationController < ActionController::Base\n  include Pundit::Authorization\n  include Pagy::Backend\n  include Organizational\n  include Users::TimeZone\n\n  protect_from_forgery\n  before_action :store_user_location!, if: :storable_location?\n  before_action :authenticate_user!\n  before_action :set_current_user\n  before_action :set_timeout_duration\n  before_action :set_current_organization\n  before_action :set_active_banner\n  after_action :verify_authorized, except: :index, unless: :devise_controller?\n  # after_action :verify_policy_scoped, only: :index\n\n  KNOWN_ERRORS = [Pundit::NotAuthorizedError, Organizational::UnknownOrganization]\n  rescue_from StandardError, with: :log_and_reraise\n  rescue_from Pundit::NotAuthorizedError, with: :not_authorized\n  rescue_from Organizational::UnknownOrganization, with: :not_authorized\n  rescue_from ActionController::UnknownFormat, with: :unsupported_media_type\n\n  impersonates :user\n\n  def after_sign_in_path_for(resource_or_scope)\n    stored_location_for(resource_or_scope) || super\n  end\n\n  def after_sign_out_path_for(resource_or_scope)\n    session[:user_return_to] = nil\n    if resource_or_scope == :all_casa_admin\n      new_all_casa_admin_session_path\n    else\n      root_path\n    end\n  end\n\n  def set_active_banner\n    return nil unless request.format.html?\n    return nil unless current_organization\n\n    @active_banner = current_organization.banners.active.first\n\n    @active_banner = nil if session[:dismissed_banner] == @active_banner&.id\n    @active_banner = nil if @active_banner&.expired?\n  end\n\n  protected\n\n  def handle_short_url(url_list)\n    hash_of_short_urls = {}\n    url_list.each_with_index { |val, index|\n      # call short io service to shorten url\n      # create an entry in hash if api is success\n      short_io_service = ShortUrlService.new\n      response = short_io_service.create_short_url(val)\n      short_url = short_io_service.short_url\n      hash_of_short_urls[index] = (response.code == 201 || response.code == 200) ? short_url : nil\n    }\n    hash_of_short_urls\n  end\n\n  # volunteer/supervisor/casa_admin controller uses to send SMS\n  # returns appropriate flash notice for SMS\n  def deliver_sms_to(resource, body_msg)\n    if resource.phone_number.blank? || !resource.casa_org.twilio_enabled?\n      return \"blank\"\n    end\n\n    body = body_msg\n    to = resource.phone_number\n    from = current_user.casa_org.twilio_phone_number\n\n    @twilio = TwilioService.new(current_user.casa_org)\n    req_params = {\n      From: from,\n      Body: body,\n      To: to\n    }\n\n    begin\n      twilio_res = @twilio.send_sms(req_params)\n      twilio_res.error_code.nil? ? \"sent\" : \"error\"\n    rescue Twilio::REST::RestError => error\n      @error = error\n      \"error\"\n    rescue # unverfied error isnt picked up by Twilio::Rest::RestError\n      # https://www.twilio.com/docs/errors/21608\n      @error = \"Phone number is unverifiied\"\n      \"error\"\n    end\n  end\n\n  def sms_acct_creation_notice(resource_name, sms_status)\n    case sms_status\n    when \"blank\"\n      \"New #{resource_name} created successfully.\"\n    when \"error\"\n      \"New #{resource_name} created successfully. SMS not sent. Error: #{@error}.\"\n    when \"sent\"\n      \"New #{resource_name} created successfully. SMS has been sent!\"\n    end\n  end\n\n  def store_referring_location\n    if request.referer && !request.referer.end_with?(\"users/sign_in\") && params[:ignore_referer].blank?\n      session[:return_to] = request.referer\n    end\n  end\n\n  def redirect_back_to_referer(fallback_location:)\n    redirect_to(session[:return_to] || fallback_location)\n  end\n\n  private\n\n  # Allows us to not specify respond_to formats in json-only controller or action.\n  # Same behavior as when a request format is not defined in a respond_to block.\n  def force_json_format\n    raise ActionController::UnknownFormat unless request.format.json?\n  end\n\n  def store_user_location!\n    # the current URL can be accessed from a session\n    store_location_for(:user, request.fullpath)\n  end\n\n  def storable_location?\n    request.get? && is_navigational_format? && !devise_controller? && !request.xhr?\n  end\n\n  def set_current_user\n    RequestStore.store[:current_user] = current_user\n  end\n\n  def set_timeout_duration\n    return unless current_user\n\n    @timeout_duration = current_user.timeout_in\n  end\n\n  def set_current_organization\n    RequestStore.store[:current_organization] = current_organization\n  end\n\n  def not_authorized\n    message = \"Sorry, you are not authorized to perform this action.\"\n    respond_to do |format|\n      format.json do\n        render json: {error: message}, status: :unauthorized\n      end\n      format.any do\n        session[:user_return_to] = nil\n        flash[:notice] = message\n        redirect_to(root_url)\n      end\n    end\n  end\n\n  def unsupported_media_type\n    respond_to do |format|\n      format.json do\n        render json: {error: \"json unsupported\"}, status: :unsupported_media_type\n      end\n      format.any do\n        flash[:alert] = \"Page not found\"\n        redirect_back_or_to root_url\n      end\n    end\n  end\n\n  def log_and_reraise(error)\n    unless KNOWN_ERRORS.include?(error.class)\n      Bugsnag.notify(error)\n    end\n    raise\n  end\n\n  def check_unconfirmed_email_notice(user)\n    notice = \"#{user.role} was successfully updated.\"\n    if user.saved_changes.include?(\"unconfirmed_email\")\n      notice += \" Confirmation Email Sent.\"\n    end\n    notice\n  end\nend\n"
  },
  {
    "path": "app/controllers/banners_controller.rb",
    "content": "class BannersController < ApplicationController\n  after_action :verify_authorized, except: %i[dismiss]\n  before_action :set_banner, only: %i[edit update destroy dismiss]\n\n  def index\n    authorize :application, :admin_or_supervisor?\n\n    @banners = current_organization.banners.includes(:user)\n  end\n\n  def new\n    authorize :application, :admin_or_supervisor?\n\n    @banner = Banner.new\n  end\n\n  def edit\n    authorize :application, :admin_or_supervisor?\n  end\n\n  def dismiss\n    session[:dismissed_banner] = @banner.id\n    render json: {status: :ok}\n  end\n\n  def create\n    authorize :application, :admin_or_supervisor?\n\n    @banner = current_organization.banners.build(banner_params)\n\n    Banner.transaction do\n      deactivate_alternate_active_banner\n      @banner.save!\n    end\n\n    redirect_to banners_path\n  rescue\n    render :new, status: :unprocessable_content\n  end\n\n  def update\n    authorize :application, :admin_or_supervisor?\n\n    Banner.transaction do\n      deactivate_alternate_active_banner\n      @banner.update!(banner_params)\n    end\n\n    redirect_to banners_path\n  rescue\n    render :edit, status: :unprocessable_content\n  end\n\n  def destroy\n    authorize :application, :admin_or_supervisor?\n\n    @banner.destroy\n    redirect_to banners_path\n  end\n\n  private\n\n  def set_banner\n    @banner = current_organization.banners.find(params[:id])\n  end\n\n  def banner_params\n    BannerParameters.new(params, current_user, browser_time_zone)\n  end\n\n  def deactivate_alternate_active_banner\n    if banner_params[:active].to_i == 1\n      alternate_active_banner = current_organization.banners.where(active: true).where.not(id: @banner.id).first\n      alternate_active_banner&.update!(active: false)\n    end\n  end\nend\n"
  },
  {
    "path": "app/controllers/bulk_court_dates_controller.rb",
    "content": "class BulkCourtDatesController < ApplicationController\n  include CourtDateParams\n\n  before_action :require_organization!\n\n  def new\n    authorize :bulk_court_date, :new?\n\n    @court_date = CourtDate.new\n  end\n\n  def create\n    authorize :bulk_court_date, :create?\n\n    case_group_id = params[:court_date][:case_group_id]\n    if case_group_id.empty?\n      @court_date = build_court_date_with_error_message\n      render :new, status: :unprocessable_content\n      return\n    end\n\n    case_group = current_organization.case_groups.find(case_group_id)\n    court_dates = build_court_dates(case_group)\n\n    court_date_with_error = create_court_dates(court_dates)\n\n    if court_date_with_error\n      @court_date = court_date_with_error\n      render :new, status: :unprocessable_content\n    else\n      redirect_to new_bulk_court_date_path, notice: \"#{court_dates.size} #{\"court date\".pluralize(court_dates.size)} created!\"\n    end\n  end\n\n  private\n\n  def build_court_date_with_error_message\n    court_date = CourtDate.new(court_date_params(nil))\n    court_date.errors.add(:base, \"Case group must be selected.\")\n    court_date\n  end\n\n  def build_court_dates(case_group)\n    case_group.casa_cases.map do |casa_case|\n      CourtDate.new(court_date_params(casa_case).merge(casa_case: casa_case))\n    end\n  end\n\n  def create_court_dates(court_dates)\n    court_date_with_error = nil\n    ActiveRecord::Base.transaction do\n      court_dates.each do |court_date|\n        if !court_date.save\n          court_date_with_error = court_date\n          raise ActiveRecord::Rollback\n        end\n      end\n    end\n    court_date_with_error\n  end\nend\n"
  },
  {
    "path": "app/controllers/casa_admins_controller.rb",
    "content": "class CasaAdminsController < ApplicationController\n  include SmsBodyHelper\n\n  before_action :set_admin, except: [:index, :new, :create]\n  before_action :require_organization!\n  after_action :verify_authorized\n\n  def index\n    authorize CasaAdmin\n    @admins = policy_scope(current_organization.casa_admins)\n  end\n\n  def edit\n    authorize @casa_admin\n  end\n\n  def update\n    authorize @casa_admin\n    if @casa_admin.update(update_casa_admin_params)\n      notice = check_unconfirmed_email_notice(@casa_admin)\n\n      @casa_admin.filter_old_emails!(@casa_admin.email)\n      respond_to do |format|\n        format.html { redirect_to edit_casa_admin_path(@casa_admin), notice: notice }\n        format.json { render json: @casa_admin, status: :ok }\n      end\n    else\n      respond_to do |format|\n        format.html { render :edit, status: :unprocessable_content }\n        format.json { render json: @casa_admin.errors.full_messages, status: :unprocessable_content }\n      end\n    end\n  end\n\n  def new\n    authorize CasaAdmin\n    @casa_admin = CasaAdmin.new\n  end\n\n  def create\n    service = ::CreateCasaAdminService.new(current_organization, params, current_user)\n    @casa_admin = service.build\n    authorize @casa_admin\n    sms_status = \"blank\"\n\n    begin\n      casa_admin = service.create!\n      if !casa_admin.phone_number.blank?\n        raw_token = casa_admin.raw_invitation_token\n        base_domain = request.base_url + \"/users/edit\"\n        invitation_url = Rails.application.routes.url_helpers.accept_user_invitation_url(invitation_token: raw_token, host: request.base_url)\n        hash_of_short_urls = handle_short_url([invitation_url, base_domain])\n        body_msg = account_activation_msg(\"admin\", hash_of_short_urls)\n        sms_status = deliver_sms_to casa_admin, body_msg\n      end\n      respond_to do |format|\n        format.html { redirect_to casa_admins_path, notice: sms_acct_creation_notice(\"admin\", sms_status) }\n        format.json { render json: @casa_admin, status: :created }\n      end\n    rescue ActiveRecord::RecordInvalid\n      respond_to do |format|\n        format.html { render :new, status: :unprocessable_content }\n        format.json { render json: service.casa_admin.errors.full_messages, status: :unprocessable_content }\n      end\n    end\n  end\n\n  def activate\n    authorize @casa_admin\n\n    if @casa_admin.activate\n      CasaAdminMailer.account_setup(@casa_admin).deliver\n\n      respond_to do |format|\n        format.html do\n          redirect_to edit_casa_admin_path(@casa_admin),\n            notice: \"Admin was activated. They have been sent an email.\"\n        end\n\n        format.json { render json: @casa_admin, status: :ok }\n      end\n    else\n      respond_to do |format|\n        format.html { render :edit, status: :unprocessable_content }\n        format.json { render json: @casa_admin.errors.full_messages, status: :unprocessable_content }\n      end\n    end\n  rescue Errno::ECONNREFUSED => error\n    redirect_to_casa_admin_edition_page(error)\n  end\n\n  def deactivate\n    authorize @casa_admin\n    if @casa_admin.deactivate\n      CasaAdminMailer.deactivation(@casa_admin).deliver\n\n      respond_to do |format|\n        format.html { redirect_to edit_casa_admin_path(@casa_admin), notice: \"Admin was deactivated.\" }\n        format.json { render json: @casa_admin, status: :ok }\n      end\n    else\n      respond_to do |format|\n        format.html { render :edit, status: :unprocessable_content }\n        format.json { render json: @casa_admin.errors.full_messages, status: :unprocessable_content }\n      end\n    end\n  rescue Errno::ECONNREFUSED => error\n    redirect_to_casa_admin_edition_page(error)\n  end\n\n  def resend_invitation\n    authorize @casa_admin\n    @casa_admin.invite!\n\n    redirect_to edit_casa_admin_path(@casa_admin), notice: \"Invitation sent\"\n  end\n\n  def change_to_supervisor\n    authorize @casa_admin\n    @casa_admin.change_to_supervisor!\n\n    redirect_to edit_supervisor_path(@casa_admin), notice: \"Admin was changed to Supervisor.\"\n  end\n\n  private\n\n  def redirect_to_casa_admin_edition_page(error)\n    Bugsnag.notify(error)\n\n    redirect_to edit_casa_admin_path(@casa_admin), alert: \"Email not sent.\"\n  end\n\n  def set_admin\n    @casa_admin = CasaAdmin.find(params[:id])\n  end\n\n  def update_casa_admin_params\n    CasaAdminParameters.new(params).with_only(:email, :display_name, :phone_number, :date_of_birth, :monthly_learning_hours_report)\n  end\n\n  def learning_hours_checked?\n    ActiveModel::Type::Boolean.new.cast(params[:monthly_learning_hours_report])\n  end\nend\n"
  },
  {
    "path": "app/controllers/casa_cases_controller.rb",
    "content": "class CasaCasesController < ApplicationController\n  before_action :set_casa_case, only: %i[show edit update deactivate reactivate copy_court_orders]\n  before_action :set_contact_types, only: %i[new edit update create deactivate reactivate]\n  before_action :require_organization!\n  after_action :verify_authorized\n\n  def index\n    authorize CasaCase\n    org_cases = current_user.casa_org.casa_cases.includes(:assigned_volunteers)\n    @casa_cases = policy_scope(org_cases).includes([:hearing_type, :judge])\n    @casa_cases_filter_id = policy(CasaCase).can_see_filters? ? \"casa-cases\" : \"\"\n    @duties = OtherDuty.where(creator_id: current_user.id)\n  end\n\n  def show\n    authorize @casa_case\n\n    respond_to do |format|\n      format.html {}\n      format.csv do\n        case_contacts = @casa_case.decorate.case_contacts_ordered_by_occurred_at\n        csv = CaseContactsExportCsvService.new(case_contacts, CaseContactReport::COLUMNS).perform\n        send_data csv, filename: case_contact_csv_name(case_contacts)\n      end\n      format.xlsx do\n        filename = @casa_case.case_number + \"-case-contacts-\" + Time.now.strftime(\"%Y-%m-%d\") + \".xlsx\"\n        response.headers[\"Content-Disposition\"] = \"attachment; filename=#{filename}\"\n      end\n    end\n  end\n\n  def new\n    @casa_case = CasaCase.new(casa_org: current_organization)\n    authorize @casa_case\n  end\n\n  def edit\n    @siblings_casa_cases = CasaCasePolicy::Scope.new(current_user, @casa_case).sibling_cases\n    authorize @casa_case\n  end\n\n  def create\n    @casa_case = CasaCase.new(\n      casa_case_create_params.merge(\n        casa_org: current_organization\n      )\n    )\n\n    authorize @casa_case\n\n    @casa_case.validate_contact_type = true\n\n    if @casa_case.save\n      respond_to do |format|\n        format.html { redirect_to @casa_case, notice: \"CASA case was successfully created.\" }\n        format.json { render json: @casa_case, status: :created }\n      end\n    else\n      set_contact_types\n      @empty_court_date = court_date_unknown?\n      respond_to do |format|\n        format.html { render :new, status: :unprocessable_content }\n        format.json { render json: @casa_case.errors.full_messages, status: :unprocessable_content }\n      end\n    end\n  end\n\n  def update\n    authorize @casa_case\n    original_attributes = @casa_case.full_attributes_hash\n    @casa_case.validate_contact_type = true unless current_role == \"Volunteer\"\n    if @casa_case.update_cleaning_contact_types(casa_case_update_params)\n      updated_attributes = @casa_case.full_attributes_hash\n      changed_attributes_list = CasaCaseChangeService.new(original_attributes, updated_attributes).calculate\n\n      respond_to do |format|\n        format.html { redirect_to edit_casa_case_path, notice: \"CASA case was successfully updated.#{changed_attributes_list}\" }\n        format.json { render json: @casa_case, status: :ok }\n      end\n    else\n      respond_to do |format|\n        format.html { render :edit, status: :unprocessable_content }\n        format.json { render json: @casa_case.errors.full_messages, status: :unprocessable_content }\n      end\n    end\n  end\n\n  def deactivate\n    authorize @casa_case, :update_case_status?\n\n    if @casa_case.deactivate\n      respond_to do |format|\n        format.html do\n          flash_message = \"Case #{@casa_case.case_number} has been deactivated.\"\n          redirect_to edit_casa_case_path(@casa_case), notice: flash_message\n        end\n\n        format.json do\n          render json: \"Case #{@casa_case.case_number} has been deactivated.\", status: :ok\n        end\n      end\n    else\n      respond_to do |format|\n        format.html { render :edit, status: :unprocessable_content }\n        format.json { render json: @casa_case.errors.full_messages, status: :unprocessable_content }\n      end\n    end\n  end\n\n  def reactivate\n    authorize @casa_case, :update_case_status?\n\n    if @casa_case.reactivate\n      respond_to do |format|\n        format.html do\n          flash_message = \"Case #{@casa_case.case_number} has been reactivated.\"\n          redirect_to edit_casa_case_path(@casa_case), notice: flash_message\n        end\n\n        format.json do\n          render json: \"Case #{@casa_case.case_number} has been reactivated.\", status: :ok\n        end\n      end\n    else\n      respond_to do |format|\n        format.html { render :edit, status: :unprocessable_content }\n        format.json { render json: @casa_case.errors.full_messages, status: :unprocessable_content }\n      end\n    end\n  end\n\n  def copy_court_orders\n    authorize @casa_case, :update_court_orders?\n    CasaCase.find_by_case_number(params[:case_number_cp]).case_court_orders.each do |court_order|\n      dup_court_order = court_order.dup\n      dup_court_order.save\n      @casa_case.case_court_orders.append dup_court_order\n    end\n  end\n\n  private\n\n  # Use callbacks to share common setup or constraints between actions.\n  def set_casa_case\n    @casa_case = current_organization.casa_cases.friendly.find(params[:id])\n  rescue ActiveRecord::RecordNotFound\n    respond_to do |format|\n      format.html { redirect_to casa_cases_path, notice: \"Sorry, you are not authorized to perform this action.\" }\n      format.json { render json: {error: \"Sorry, you are not authorized to perform this action.\"}, status: :not_found }\n    end\n  end\n\n  # Only allow a list of trusted parameters through.\n  def casa_case_params\n    params.require(:casa_case).permit(\n      :case_number,\n      :birth_month_year_youth,\n      :date_in_care,\n      :court_report_due_date,\n      :empty_court_date,\n      contact_type_ids: [],\n      court_dates_attributes: [:date],\n      case_assignments_attributes: [:volunteer_id]\n    )\n  end\n\n  def casa_case_create_params\n    create_params = casa_case_params\n    create_params = create_params.except(:court_dates_attributes) if court_date_unknown?\n    create_params.except(:empty_court_date)\n  end\n\n  # Separate params so only admins can update the case_number\n  def casa_case_update_params\n    params.require(:casa_case).permit(policy(@casa_case).permitted_attributes)\n  end\n\n  def set_contact_types\n    @contact_types = current_organization.contact_types\n    @selected_contact_type_ids = (!@casa_case.nil?) ? @casa_case.contact_type_ids : []\n  end\n\n  def case_contact_csv_name(case_contacts)\n    casa_case_number = case_contacts&.first&.casa_case&.case_number\n    current_date = Time.now.strftime(\"%Y-%m-%d\")\n\n    \"#{casa_case_number.nil? ? \"\" : casa_case_number + \"-\"}case-contacts-#{current_date}.csv\"\n  end\n\n  def court_date_unknown?\n    casa_case_params[:empty_court_date] == \"1\"\n  end\nend\n"
  },
  {
    "path": "app/controllers/casa_org_controller.rb",
    "content": "class CasaOrgController < ApplicationController\n  before_action :set_casa_org, only: %i[edit update]\n  before_action :set_contact_type_data, only: %i[edit update]\n  before_action :set_hearing_types, only: %i[edit update]\n  before_action :set_judges, only: %i[edit update]\n  before_action :set_learning_hour_types, only: %i[edit update]\n  before_action :set_learning_hour_topics, only: %i[edit update]\n  before_action :set_sent_emails, only: %i[edit update]\n  before_action :set_contact_topics, only: %i[edit update]\n  before_action :set_custom_org_links, only: %i[edit update]\n  before_action :set_placement_types, only: %i[edit update]\n  before_action :require_organization!\n  after_action :verify_authorized\n  before_action :set_active_storage_url_options, only: %i[edit update]\n\n  def edit\n    authorize @casa_org\n  end\n\n  def update\n    authorize @casa_org\n\n    if @casa_org.update(casa_org_update_params)\n      respond_to do |format|\n        format.html do\n          redirect_to edit_casa_org_path, notice: \"CASA organization was successfully updated.\"\n        end\n\n        format.json { render json: @casa_org, status: :ok }\n      end\n    else\n      respond_to do |format|\n        format.html { render :edit, status: :unprocessable_content }\n        format.json { render json: @casa_org.errors.full_messages, status: :unprocessable_content }\n      end\n    end\n  end\n\n  private\n\n  def set_casa_org\n    @casa_org = current_organization\n  rescue ActiveRecord::RecordNotFound\n    head :not_found\n  end\n\n  def casa_org_update_params\n    params.require(:casa_org).permit(\n      :name,\n      :display_name,\n      :address,\n      :logo,\n      :court_report_template,\n      :show_driving_reimbursement,\n      :additional_expenses_enabled,\n      :other_duties_enabled,\n      :twilio_account_sid,\n      :twilio_phone_number,\n      :twilio_api_key_sid,\n      :twilio_api_key_secret,\n      :twilio_enabled,\n      :learning_topic_active\n    )\n  end\n\n  def set_contact_type_data\n    @contact_type_groups = @casa_org.contact_type_groups.order(:name)\n    @contact_types = ContactType.for_organization(@casa_org).order(:name)\n  end\n\n  def set_hearing_types\n    @hearing_types = HearingType.for_organization(@casa_org)\n  end\n\n  def set_judges\n    @judges = Judge.for_organization(@casa_org)\n  end\n\n  def set_learning_hour_types\n    @learning_hour_types = LearningHourType.for_organization(@casa_org)\n  end\n\n  def set_sent_emails\n    @sent_emails = SentEmail.for_organization(@casa_org).order(\"created_at DESC\").limit(10)\n  end\n\n  def set_learning_hour_topics\n    @learning_hour_topics = LearningHourTopic.for_organization(@casa_org)\n  end\n\n  def set_contact_topics\n    @contact_topics = @casa_org.contact_topics.where(soft_delete: false)\n  end\n\n  def set_custom_org_links\n    @custom_org_links = @casa_org.custom_org_links\n  end\n\n  def set_placement_types\n    @placement_types = @casa_org.placement_types\n  end\n\n  def set_active_storage_url_options\n    ActiveStorage::Current.url_options = {host: request.base_url}\n  end\nend\n"
  },
  {
    "path": "app/controllers/case_assignments_controller.rb",
    "content": "class CaseAssignmentsController < ApplicationController\n  before_action :load_case_assignment, only: %i[destroy unassign show_hide_contacts reimbursement]\n  after_action :verify_authorized\n\n  def create\n    authorize CaseAssignment\n\n    if existing_case_assignment.present?\n      if existing_case_assignment.update(active: true)\n        handle_successful_assignment(\"Volunteer reassigned to case\")\n      else\n        errors = existing_case_assignment.errors.full_messages.join(\". \")\n        handle_failed_assignment(\"Unable to reassign volunteer to case: #{errors}.\")\n      end\n    else\n      case_assignment = case_assignment_parent.case_assignments.new(case_assignment_params)\n      if case_assignment.save\n        handle_successful_assignment(\"Volunteer assigned to case\")\n      else\n        errors = case_assignment.errors.full_messages.join(\". \")\n        handle_failed_assignment(\"Unable to assign volunteer to case: #{errors}.\")\n      end\n    end\n\n    respond_to do |format|\n      format.html { redirect_to after_action_path(case_assignment_parent) }\n      format.json { render json: @message, status: @status }\n    end\n  end\n\n  # TODO don't delete this, just deactivate it\n  def destroy\n    authorize @case_assignment\n    @case_assignment.destroy\n\n    redirect_to after_action_path(case_assignment_parent)\n  end\n\n  def unassign\n    authorize @case_assignment, :unassign?\n    casa_case = @case_assignment.casa_case\n    volunteer = @case_assignment.volunteer\n    message = \"Volunteer was unassigned from Case #{casa_case.case_number}.\"\n\n    if @case_assignment.update(active: false)\n      if params[:redirect_to_path] == \"volunteer\"\n        redirect_to edit_volunteer_path(volunteer), notice: message\n      else\n        respond_to do |format|\n          format.html { redirect_to after_action_path(casa_case), notice: message }\n          format.json { render json: message, status: :ok }\n        end\n      end\n    else\n      respond_to do |format|\n        format.html { render :edit, status: :unprocessable_content }\n        format.json { render json: @case_assignment.errors.full_messages, status: :unprocessable_content }\n      end\n    end\n  end\n\n  def show_hide_contacts\n    authorize @case_assignment, :show_or_hide_contacts?\n    casa_case = @case_assignment.casa_case\n    volunteer = @case_assignment.volunteer\n\n    flash_message = \"Old Case Contacts created by #{volunteer.display_name} #{@case_assignment.hide_old_contacts? ? \"are now visible\" : \"were successfully hidden\"}.\"\n\n    @case_assignment.toggle!(:hide_old_contacts)\n    redirect_to after_action_path(casa_case), notice: flash_message\n  end\n\n  def reimbursement\n    casa_case = @case_assignment.casa_case\n    message = \"Volunteer allow reimbursement changed from Case #{casa_case.case_number}.\"\n    authorize @case_assignment, :reimbursement?\n    if @case_assignment.update(allow_reimbursement: !@case_assignment.allow_reimbursement)\n      redirect_to after_action_path(casa_case), notice: message\n    end\n  end\n\n  private\n\n  def case_assignment_parent\n    if params[:volunteer_id]\n      User.find(params[:volunteer_id])\n    else\n      CasaCase.friendly.find(params[:casa_case_id])\n    end\n  end\n\n  def after_action_path(resource)\n    if resource.is_a? User\n      edit_volunteer_path(resource)\n    else\n      edit_casa_case_path(resource)\n    end\n  end\n\n  def case_assignment_params\n    params.require(:case_assignment).permit(:casa_case_id, :volunteer_id, :reimbursement)\n  end\n\n  def load_case_assignment\n    @case_assignment =\n      CaseAssignment\n        .joins(:casa_case)\n        .where(casa_cases: {casa_org_id: current_organization.id})\n        .find(params[:id])\n  rescue ActiveRecord::RecordNotFound\n    head :not_found\n  end\n\n  def load_existing_case_assignment\n    case_assignments = case_assignment_parent.case_assignments\n    if params[:volunteer_id]\n      case_assignments.where(casa_case_id: case_assignment_params[:casa_case_id], active: false).first\n    else\n      case_assignments.where(volunteer_id: case_assignment_params[:volunteer_id], active: false).first\n    end\n  end\n\n  def existing_case_assignment\n    @existing_case_assignment ||= load_existing_case_assignment\n  end\n\n  def handle_successful_assignment(msg)\n    @message = msg\n    flash.notice = msg\n    @status = :ok\n  end\n\n  def handle_failed_assignment(msg)\n    @message = msg\n    flash.alert = msg\n    @status = :unprocessable_content\n  end\nend\n"
  },
  {
    "path": "app/controllers/case_contact_reports_controller.rb",
    "content": "require \"csv\"\n\nclass CaseContactReportsController < ApplicationController\n  after_action :verify_authorized\n\n  def index\n    authorize :application, :see_reports_page?\n    case_contact_report = CaseContactReport.new(report_params)\n\n    respond_to do |format|\n      format.csv do\n        send_data case_contact_report.to_csv,\n          filename: \"case-contacts-report-#{Time.zone.now.to_i}.csv\"\n      end\n    end\n  end\n\n  private\n\n  def report_params\n    parameters = params.require(:report).permit(\n      :start_date,\n      :end_date,\n      :contact_made,\n      :has_transitioned,\n      :want_driving_reimbursement,\n      :other_expense_amount,\n      :other_expenses_describe,\n      contact_type_ids: [],\n      contact_type_group_ids: [],\n      creator_ids: [],\n      supervisor_ids: [],\n      casa_case_ids: [],\n      filtered_csv_cols: {}\n    ).merge(casa_org_id: current_organization.id)\n    convert_radio_options_to_boolean(parameters)\n    parameters\n  end\n\n  def convert_radio_options_to_boolean(parameters)\n    parameters[:contact_made] = string_to_boolean(parameters[:contact_made])\n    parameters[:has_transitioned] = string_to_boolean(parameters[:has_transitioned])\n    parameters[:want_driving_reimbursement] = string_to_boolean(parameters[:want_driving_reimbursement])\n  end\n\n  def string_to_boolean(value)\n    if value == \"true\"\n      true\n    else\n      ((value == \"false\") ? false : \"\")\n    end\n  end\nend\n"
  },
  {
    "path": "app/controllers/case_contacts/case_contacts_new_design_controller.rb",
    "content": "class CaseContacts::CaseContactsNewDesignController < ApplicationController\n  include LoadsCaseContacts\n\n  before_action :check_feature_flag\n\n  def index\n    load_case_contacts\n  end\n\n  def datatable\n    authorize CaseContact\n    case_contacts = policy_scope(current_organization.case_contacts)\n    datatable = CaseContactDatatable.new(case_contacts, params, current_user)\n\n    render json: datatable\n  end\n\n  private\n\n  def check_feature_flag\n    unless Flipper.enabled?(:new_case_contact_table)\n      redirect_to case_contacts_path, alert: \"This feature is not available.\"\n    end\n  end\nend\n"
  },
  {
    "path": "app/controllers/case_contacts/followups_controller.rb",
    "content": "class CaseContacts::FollowupsController < ApplicationController\n  after_action :verify_authorized\n\n  def create\n    authorize Followup\n    case_contact = CaseContact.find(params[:case_contact_id])\n    note = simple_followup_params[:note]\n    FollowupService.create_followup(case_contact, current_user, note)\n\n    respond_to do |format|\n      format.html { redirect_to casa_case_path(case_contact.casa_case) }\n      format.json { head :no_content }\n    end\n  end\n\n  def resolve\n    @followup = Followup.find(params[:id])\n    authorize @followup\n\n    @followup.resolved!\n    create_notification\n\n    respond_to do |format|\n      format.html { redirect_to casa_case_path(@followup.case_contact.casa_case) }\n      format.json { head :no_content }\n    end\n  end\n\n  private\n\n  def simple_followup_params\n    params.permit(:note)\n  end\n\n  def create_notification\n    return if current_user == @followup.creator\n    FollowupResolvedNotifier\n      .with(followup: @followup, created_by: current_user)\n      .deliver(@followup.creator)\n  end\nend\n"
  },
  {
    "path": "app/controllers/case_contacts/form_controller.rb",
    "content": "class CaseContacts::FormController < ApplicationController\n  include Wicked::Wizard\n\n  before_action :require_organization!\n  before_action :set_case_contact, only: [:show, :update]\n  after_action :verify_authorized\n\n  steps :details\n\n  def show\n    authorize @case_contact\n\n    prepare_form\n\n    render_wizard\n  end\n\n  def update\n    authorize @case_contact\n\n    remove_nil_draft_ids\n\n    respond_to do |format|\n      format.html do\n        params[:case_contact][:status] = CaseContact.statuses[step] if !@case_contact.active?\n        if @case_contact.update(case_contact_params)\n          finish_editing\n        else\n          prepare_form\n          render step\n        end\n      end\n      format.json do\n        if @case_contact.update(case_contact_params)\n          render json: @case_contact, status: :ok\n        else\n          render json: @case_contact.errors.full_messages, status: :unprocessable_content\n        end\n      end\n    end\n  end\n\n  private\n\n  def set_case_contact\n    @case_contact = CaseContact\n      .includes(:creator, :contact_topic_answers)\n      .find(params[:case_contact_id])\n  end\n\n  def prepare_form\n    @casa_cases = get_casa_cases\n    contact_types = get_contact_types.decorate\n    @grouped_contact_types = group_contact_types_by_name(contact_types)\n    @contact_topics = get_contact_topics\n\n    if !@case_contact.active? && @case_contact.contact_topic_answers.empty?\n      if @contact_topics.present?\n        @case_contact.contact_topic_answers.create\n      end\n    end\n  end\n\n  def get_casa_cases\n    casa_cases = policy_scope(current_organization.casa_cases).includes([:volunteers])\n    casa_cases = casa_cases.where(id: @case_contact.casa_case_id) if @case_contact.active?\n    casa_cases\n  end\n\n  def get_contact_types\n    case_contact_types = ContactType.includes(:contact_type_group)\n      .joins(:casa_case_contact_types)\n      .active\n      .where(casa_case_contact_types: {casa_case_id: @casa_cases.pluck(:id)})\n      .distinct\n\n    case_contact_types.presence || ContactType\n      .includes(:contact_type_group)\n      .joins(:contact_type_group)\n      .active\n      .where(contact_type_group: {casa_org: current_organization})\n      .order(\"contact_type_group.name ASC\", :name)\n      .distinct\n  end\n\n  def get_contact_topics\n    ContactTopic\n      .active\n      .where(casa_org: current_organization)\n      .order(:question)\n  end\n\n  def group_contact_types_by_name(contact_types)\n    contact_types.group_by { |ct| ct.contact_type_group.name }\n  end\n\n  def finish_editing\n    message = \"\"\n    send_reimbursement_email(@case_contact)\n    draft_case_ids = @case_contact.draft_case_ids\n    if @case_contact.active?\n      message = @case_contact.decorate.form_updated_message\n    else\n      message = \"Case #{\"contact\".pluralize(draft_case_ids.count)} successfully created.\"\n      create_additional_case_contacts(@case_contact)\n      first_casa_case_id = draft_case_ids.first\n      @case_contact.update(status: \"active\", draft_case_ids: [first_casa_case_id], casa_case_id: first_casa_case_id)\n    end\n    update_volunteer_address(@case_contact)\n    flash[:notice] = message\n    if @case_contact.metadata[\"create_another\"]\n      redirect_to new_case_contact_path(params: {draft_case_ids:, ignore_referer: true})\n    else\n      redirect_back_to_referer(fallback_location: case_contacts_path(success: true))\n    end\n  end\n\n  def send_reimbursement_email(case_contact)\n    if case_contact.should_send_reimbursement_email?\n      SupervisorMailer.reimbursement_request_email(case_contact.creator, case_contact.supervisor).deliver_later\n    end\n  end\n\n  def update_volunteer_address(case_contact)\n    return unless case_contact.volunteer_address.present? && !case_contact.address_field_disabled?\n\n    address = case_contact.volunteer.address || case_contact.volunteer.build_address\n    address.update(content: case_contact.volunteer_address)\n  end\n\n  # Makes a copy of the draft for all selected cases not including the first one. The draft becomes the contact for\n  # the first case.\n  #\n  # Duplication does not duplicate associated records, so if other associations are made in the form, they need to be\n  # added here, explicitly (ie. case_contact_contact_type, additional_expenses). Alternatively, could look at a gem\n  # that does deep associations.\n  def create_additional_case_contacts(case_contact)\n    case_contact.draft_case_ids.drop(1).each do |casa_case_id|\n      new_case_contact = case_contact.dup\n      new_case_contact.status = \"active\"\n      new_case_contact.draft_case_ids = [casa_case_id]\n      new_case_contact.casa_case_id = casa_case_id\n      case_contact.case_contact_contact_types.each do |ccct|\n        new_case_contact.case_contact_contact_types.new(contact_type_id: ccct.contact_type_id)\n      end\n      case_contact.additional_expenses.each do |ae|\n        new_case_contact.additional_expenses.new(\n          other_expense_amount: ae.other_expense_amount,\n          other_expenses_describe: ae.other_expenses_describe\n        )\n      end\n      case_contact.contact_topic_answers.each do |cta|\n        new_case_contact.contact_topic_answers << cta.dup\n      end\n\n      new_case_contact.save!\n    end\n  end\n\n  def case_contact_params\n    CaseContactParameters.new(params)\n  end\n\n  def remove_nil_draft_ids\n    params[:case_contact][:draft_case_ids] -= [\"\"] if params.dig(:case_contact, :draft_case_ids)\n  end\nend\n"
  },
  {
    "path": "app/controllers/case_contacts_controller.rb",
    "content": "# frozen_string_literal: true\n\nclass CaseContactsController < ApplicationController\n  include LoadsCaseContacts\n\n  before_action :set_case_contact, only: %i[edit destroy]\n  before_action :set_contact_types, only: %i[new edit create]\n  before_action :require_organization!\n  after_action :verify_authorized, except: %i[leave]\n\n  def index\n    load_case_contacts\n  end\n\n  def drafts\n    authorize CaseContact\n\n    @case_contacts = current_organization.case_contacts.not_active\n  end\n\n  def new\n    store_referring_location\n\n    casa_cases = policy_scope(current_organization.casa_cases)\n    draft_case_ids = build_draft_case_ids(params, casa_cases)\n\n    @case_contact = CaseContact.create(creator: current_user, draft_case_ids: draft_case_ids, contact_made: true)\n    authorize @case_contact\n\n    if @case_contact.errors.any?\n      flash[:alert] = @case_contact.errors.full_messages.join(\"\\n\")\n      redirect_to request.referer\n    else\n      redirect_to case_contact_form_path(:details, case_contact_id: @case_contact.id)\n    end\n  end\n\n  def edit\n    authorize @case_contact\n    redirect_to case_contact_form_path(:details, case_contact_id: @case_contact.id)\n  end\n\n  def destroy\n    authorize @case_contact\n\n    @case_contact.destroy\n\n    respond_to do |format|\n      format.html do\n        flash[:notice] = \"Contact is successfully deleted.\"\n        redirect_to request.referer\n      end\n      format.json { head :no_content }\n    end\n  end\n\n  def restore\n    authorize CasaAdmin\n\n    case_contact = authorize(current_organization.case_contacts.with_deleted.find(params[:id]))\n    case_contact.restore(recursive: true)\n    flash[:notice] = \"Contact is successfully restored.\"\n    redirect_to request.referer\n  end\n\n  def leave\n    redirect_back_to_referer(fallback_location: case_contacts_path)\n  end\n\n  private\n\n  def update_or_create_additional_expense(all_ae_params, cc)\n    all_ae_params.each do |ae_params|\n      id = ae_params[:id]\n      current = AdditionalExpense.find_by(id: id)\n      if current\n        current.assign_attributes(other_expense_amount: ae_params[:other_expense_amount], other_expenses_describe: ae_params[:other_expenses_describe])\n        save_or_add_error(current, cc)\n      else\n        create_new_exp = cc.additional_expenses.build(ae_params)\n        save_or_add_error(create_new_exp, cc)\n      end\n    end\n  end\n\n  def set_contact_types\n    @contact_types = ContactType.for_organization(current_organization)\n  end\n\n  def additional_expense_params\n    @additional_expense_params ||= AdditionalExpenseParamsService.new(params).calculate\n  end\n\n  def set_case_contact\n    @case_contact = authorize(current_organization.case_contacts.find_by(id: params[:id]))\n    redirect_to authenticated_user_root_path unless @case_contact\n  end\n\n  def build_draft_case_ids(params, casa_cases)\n    # Use case(s) from params if present\n    return params[:draft_case_ids] if params[:draft_case_ids].present?\n    return casa_cases.where(id: params.dig(:case_contact, :casa_case_id)).pluck(:id) if params.dig(:case_contact, :casa_case_id).present?\n    return [casa_cases.first.id] if casa_cases.count == 1\n\n    []\n  end\nend\n"
  },
  {
    "path": "app/controllers/case_court_orders_controller.rb",
    "content": "class CaseCourtOrdersController < ApplicationController\n  before_action :set_case_court_order, only: %i[destroy]\n  before_action :require_organization!\n  after_action :verify_authorized\n\n  def destroy\n    authorize @case_court_order\n    @case_court_order.destroy\n  end\n\n  private\n\n  def set_case_court_order\n    @case_court_order = CaseCourtOrder.find(params[:id])\n  rescue ActiveRecord::RecordNotFound\n    head :not_found\n  end\nend\n"
  },
  {
    "path": "app/controllers/case_court_reports_controller.rb",
    "content": "# frozen_string_literal: true\n\nclass CaseCourtReportsController < ApplicationController\n  before_action :set_casa_case, only: %i[show]\n  after_action :verify_authorized\n\n  def index\n    authorize CaseCourtReport\n    assigned_cases.select(:id, :case_number, :birth_month_year_youth)\n  end\n\n  def show\n    authorize CaseCourtReport\n    if !@casa_case || !@casa_case.court_reports.attached?\n      flash[:alert] = \"Report #{params[:id]} is not found.\"\n      redirect_to(case_court_reports_path) and return\n    end\n\n    respond_to do |format|\n      format.docx do\n        @casa_case.latest_court_report.open do |file|\n          # TODO test this .read being present, we've broken it twice now\n          send_data File.read(file.path), type: :docx, disposition: \"attachment\", status: :ok\n        end\n      end\n    end\n  end\n\n  def generate\n    authorize CaseCourtReport\n    casa_case = CasaCase.find_by(case_number: case_params[:case_number], casa_org: current_user.casa_org)\n\n    respond_to do |format|\n      format.json do\n        if casa_case\n          report_data = generate_report_to_string(casa_case, date_range_params)\n          save_report(report_data, casa_case)\n\n          render json: {link: case_court_report_path(casa_case.case_number, format: \"docx\"), status: :ok}\n        else\n          error_messages = generate_error(\"Report #{params[:case_number]} is not found.\")\n\n          render json: {link: \"\", status: :not_found, error_messages: error_messages}, status: :not_found\n        end\n      end\n    end\n  rescue Zip::Error\n    error_messages = generate_error(\"Template is not found\")\n    render json: {status: :not_found, error_messages: error_messages}, status: :not_found\n  rescue => e\n    error_messages = generate_error(e.to_s)\n    render json: {status: :unprocessable_content, error_messages: error_messages}, status: :unprocessable_content\n  end\n\n  private\n\n  def date_range_params\n    params.permit(:time_zone, case_court_report: %i[start_date end_date])\n  end\n\n  def case_params\n    params.require(:case_court_report).permit(:case_number)\n  end\n\n  def set_casa_case\n    @casa_case = CasaCase.find_by(case_number: params[:id], casa_org: current_user.casa_org)\n  end\n\n  def assigned_cases\n    @assigned_cases = if current_user.volunteer?\n      CasaCase.actively_assigned_to(current_user)\n    else\n      current_user.casa_org.casa_cases.active\n    end\n  end\n\n  def generate_report_to_string(casa_case, time_range)\n    return unless casa_case\n\n    casa_case.casa_org.open_org_court_report_template do |template_docx_file|\n      args = {\n        volunteer_id: current_user.volunteer? ? current_user.id : casa_case.assigned_volunteers.first&.id,\n        case_id: casa_case.id,\n        path_to_template: template_docx_file.to_path,\n        time_zone: time_range[:time_zone],\n        start_date: time_range[:case_court_report][:start_date],\n        end_date: time_range[:case_court_report][:end_date]\n      }\n      context = CaseCourtReportContext.new(args).context\n      court_report = CaseCourtReport.new(path_to_template: template_docx_file.to_path, context: context)\n\n      return court_report.generate_to_string\n    end\n  end\n\n  def save_report(report_data, casa_case)\n    Tempfile.create do |t|\n      t.binmode.write(report_data)\n      t.rewind\n      casa_case.court_reports.attach(\n        io: File.open(t.path), filename: \"#{casa_case.case_number}.docx\"\n      )\n    end\n  end\n\n  def generate_error(message)\n    flash[:alert] = message\n    error_messages = render_to_string partial: \"layouts/flash_messages\", formats: :html, layout: false, locals: flash\n    flash.discard\n\n    error_messages\n  end\nend\n"
  },
  {
    "path": "app/controllers/case_groups_controller.rb",
    "content": "class CaseGroupsController < ApplicationController\n  before_action :require_organization!\n  before_action :set_case_group, only: %i[edit update destroy]\n\n  def index\n    authorize CaseGroup\n    @case_groups = policy_scope(CaseGroup).includes(:casa_cases)\n  end\n\n  def new\n    @case_group = CaseGroup.new(casa_org: current_organization)\n    authorize @case_group\n  end\n\n  def edit\n    authorize @case_group\n  end\n\n  def create\n    @case_group = current_organization.case_groups.build(case_group_params)\n    authorize @case_group\n\n    if @case_group.save\n      redirect_to case_groups_path, notice: \"Case group created!\"\n    else\n      render :new, status: :unprocessable_content\n    end\n  end\n\n  def update\n    authorize @case_group\n\n    if @case_group.update(case_group_params)\n      redirect_to case_groups_path, notice: \"Case group updated!\"\n    else\n      render :edit, status: :unprocessable_content\n    end\n  end\n\n  def destroy\n    authorize @case_group\n\n    @case_group.destroy\n    redirect_to case_groups_path, notice: \"Case group deleted!\"\n  end\n\n  private\n\n  def case_group_params\n    params.merge(casa_org: current_organization)\n    params.require(:case_group).permit(:name, casa_case_ids: [])\n  end\n\n  def set_case_group\n    @case_group = policy_scope(CaseGroup).find(params[:id])\n  end\nend\n"
  },
  {
    "path": "app/controllers/checklist_items_controller.rb",
    "content": "class ChecklistItemsController < ApplicationController\n  before_action :authorize_checklist_item\n  before_action :set_hearing_type\n  before_action :set_checklist_item, except: [:new, :create]\n\n  def new\n    @checklist_item = ChecklistItem.new\n  end\n\n  def create\n    @checklist_item = @hearing_type.checklist_items.create(checklist_item_params)\n    if @checklist_item.save\n      set_checklist_updated_date(@hearing_type)\n      redirect_to edit_hearing_type_path(@hearing_type), notice: \"Checklist item was successfully created.\"\n    else\n      render :new, status: :unprocessable_content\n    end\n  end\n\n  def edit\n  end\n\n  def update\n    if @checklist_item.update(checklist_item_params)\n      set_checklist_updated_date(@hearing_type)\n      redirect_to edit_hearing_type_path(@hearing_type), notice: \"Checklist item was successfully updated.\"\n    else\n      render :edit, status: :unprocessable_content\n    end\n  end\n\n  def destroy\n    if @checklist_item.destroy\n      set_checklist_updated_date(@hearing_type)\n      redirect_to edit_hearing_type_path(@hearing_type), notice: \"Checklist item was successfully deleted.\"\n    else\n      flash[:error] = \"Failed to delete checklist item.\"\n      redirect_to edit_hearing_type_path(@hearing_type)\n    end\n  end\n\n  private\n\n  def set_checklist_updated_date(hearing_type)\n    hearing_type.update_attribute(:checklist_updated_date, \"Updated #{Time.new.strftime(\"%m/%d/%Y\")}\")\n  end\n\n  def authorize_checklist_item\n    authorize ChecklistItem\n  end\n\n  def set_hearing_type\n    @hearing_type ||= policy_scope(HearingType).find(params[:hearing_type_id])\n  end\n\n  def set_checklist_item\n    @checklist_item ||= ChecklistItem.find(params[:id])\n  end\n\n  def checklist_item_params\n    params.require(:checklist_item).permit(:category, :description, :mandatory)\n  end\nend\n"
  },
  {
    "path": "app/controllers/concerns/accessible.rb",
    "content": "module Accessible\n  extend ActiveSupport::Concern\n\n  included do\n    before_action :check_user\n  end\n\n  protected\n\n  def check_user\n    if current_all_casa_admin\n      flash.clear\n      redirect_to(authenticated_all_casa_admin_root_path) and return\n      # override \"after_sign_in_path_for\" and redirect user to root path if no target URL is stored in session\n    elsif current_user && session[:user_return_to].nil?\n      flash.clear\n      # The authenticated root path can be defined in your routes.rb in: devise_scope :user do...\n      redirect_to(authenticated_user_root_path) and return\n    end\n  end\nend\n"
  },
  {
    "path": "app/controllers/concerns/court_date_params.rb",
    "content": "module CourtDateParams\n  private\n\n  def sanitized_court_date_params(casa_case)\n    params.require(:court_date).tap do |p|\n      p[:case_court_orders_attributes]&.reject! do |k, _|\n        p[:case_court_orders_attributes][k][:text].blank? && p[:case_court_orders_attributes][k][:implementation_status].blank?\n      end\n\n      p[:case_court_orders_attributes]&.each do |k, _|\n        p[:case_court_orders_attributes][k][:casa_case_id] = casa_case.id\n      end\n    end\n  end\n\n  def court_date_params(casa_case)\n    sanitized_court_date_params(casa_case).permit(\n      :date,\n      :hearing_type_id,\n      :judge_id,\n      :court_report_due_date,\n      {case_court_orders_attributes: %i[text _destroy implementation_status id casa_case_id]}\n    )\n  end\nend\n"
  },
  {
    "path": "app/controllers/concerns/loads_case_contacts.rb",
    "content": "module LoadsCaseContacts\n  extend ActiveSupport::Concern\n\n  private\n\n  def load_case_contacts\n    authorize CaseContact\n\n    @current_organization_groups = current_organization_groups\n\n    @filterrific = initialize_filterrific(\n      all_case_contacts,\n      params[:filterrific],\n      select_options: {\n        sorted_by: CaseContact.options_for_sorted_by\n      }\n    ) || return\n\n    @pagy, @filtered_case_contacts = pagy(@filterrific.find)\n    case_contacts = CaseContact.case_hash_from_cases(@filtered_case_contacts)\n    case_contacts = case_contacts.slice(*current_user.casa_cases.pluck(:id)) if current_user.volunteer?\n    case_contacts = case_contacts.select { |k, _v| k == params[:casa_case_id].to_i } if params[:casa_case_id].present?\n\n    @presenter = CaseContactPresenter.new(case_contacts)\n  end\n\n  def current_organization_groups\n    current_organization.contact_type_groups\n      .includes(:contact_types)\n      .joins(:contact_types)\n      .where(contact_types: {active: true})\n      .uniq\n  end\n\n  def all_case_contacts\n    policy_scope(current_organization.case_contacts).preload(\n      :creator,\n      :followups,\n      :contact_topics,\n      :casa_org,\n      contact_types: :contact_type_group,\n      contact_topic_answers: :contact_topic,\n      casa_case: :volunteers\n    )\n  end\nend\n"
  },
  {
    "path": "app/controllers/concerns/organizational.rb",
    "content": "module Organizational\n  extend ActiveSupport::Concern\n\n  class UnknownOrganization < StandardError\n  end\n\n  def require_organization!\n    raise UnknownOrganization.new if current_organization.nil?\n  end\n\n  def current_organization\n    @current_organization ||= current_user&.casa_org\n  end\n\n  def current_role\n    @current_role ||= if user_signed_in?\n      current_user.role\n    elsif all_casa_admin_signed_in?\n      current_all_casa_admin.role\n    end\n  end\n\n  included do\n    helper_method :current_organization\n    helper_method :current_role\n  end\nend\n"
  },
  {
    "path": "app/controllers/concerns/users/time_zone.rb",
    "content": "module Users\n  module TimeZone\n    extend ActiveSupport::Concern\n\n    included do\n      helper_method :browser_time_zone\n      helper_method :to_user_timezone\n    end\n\n    def browser_time_zone\n      browser_tz = ActiveSupport::TimeZone.find_tzinfo(cookies[:browser_time_zone])\n      ActiveSupport::TimeZone.all.find { |zone| zone.tzinfo == browser_tz } || Time.zone\n    rescue TZInfo::UnknownTimezone, TZInfo::InvalidTimezoneIdentifier\n      Time.zone\n    end\n\n    def to_user_timezone(time_date)\n      return \"\" if time_date.nil? || (time_date.instance_of?(String) && time_date.empty?)\n\n      time_zone = user_timezone\n      return time_date.in_time_zone(time_zone) if time_date.respond_to?(:in_time_zone)\n\n      time_date.to_time(time_zone)\n    end\n\n    def user_timezone\n      (browser_time_zone && browser_time_zone != Time.zone) ? browser_time_zone : \"Eastern Time (US & Canada)\"\n    end\n  end\nend\n"
  },
  {
    "path": "app/controllers/contact_topic_answers_controller.rb",
    "content": "class ContactTopicAnswersController < ApplicationController\n  before_action :force_json_format\n\n  def create\n    @contact_topic_answer = ContactTopicAnswer.new(contact_topic_answer_params)\n    authorize @contact_topic_answer\n\n    if @contact_topic_answer.save\n      render json: @contact_topic_answer.as_json, status: :created\n    else\n      render json: @contact_topic_answer.errors.as_json, status: :unprocessable_content\n    end\n  end\n\n  def destroy\n    @contact_topic_answer = ContactTopicAnswer.find(params[:id])\n    authorize @contact_topic_answer\n\n    @contact_topic_answer.destroy!\n\n    head :no_content\n  end\n\n  private\n\n  def contact_topic_answer_params\n    params.require(:contact_topic_answer)\n      .permit(:id, :contact_topic_id, :case_contact_id, :value, :_destroy)\n  end\nend\n"
  },
  {
    "path": "app/controllers/contact_topics_controller.rb",
    "content": "class ContactTopicsController < ApplicationController\n  before_action :set_contact_topic, only: %i[edit update soft_delete]\n  after_action :verify_authorized\n\n  # GET /contact_topics/new\n  def new\n    @contact_topic = ContactTopic.new(casa_org_id: current_user.casa_org_id)\n    authorize @contact_topic\n  end\n\n  # GET /contact_topics/1/edit\n  def edit\n    authorize @contact_topic\n  end\n\n  # POST /contact_topics or /contact_topics.json\n  def create\n    @contact_topic = ContactTopic.new(contact_topic_params)\n    authorize @contact_topic\n\n    if @contact_topic.save\n      redirect_to edit_casa_org_path(current_organization), notice: \"Contact topic was successfully created.\"\n    else\n      render :new, status: :unprocessable_content\n    end\n  end\n\n  # PATCH/PUT /contact_topics/1 or /contact_topics/1.json\n  def update\n    authorize @contact_topic\n\n    if @contact_topic.update(contact_topic_params)\n      redirect_to edit_casa_org_path(current_organization), notice: \"Contact topic was successfully updated.\"\n    else\n      render :edit, status: :unprocessable_content\n    end\n  end\n\n  # DELETE /contact_topics/1/soft_delete\n  def soft_delete\n    authorize @contact_topic\n\n    if @contact_topic.update(soft_delete: true)\n      redirect_to edit_casa_org_path(current_organization), notice: \"Contact topic was successfully removed.\"\n    else\n      render :show, status: :unprocessable_content\n    end\n  end\n\n  private\n\n  # Use callbacks to share common setup or constraints between actions.\n  def set_contact_topic\n    @contact_topic = ContactTopic.find(params[:id])\n  end\n\n  # Only allow a list of trusted parameters through.\n  def contact_topic_params\n    params.require(:contact_topic).permit(:casa_org_id, :question, :details, :active, :exclude_from_court_report)\n  end\nend\n"
  },
  {
    "path": "app/controllers/contact_type_groups_controller.rb",
    "content": "class ContactTypeGroupsController < ApplicationController\n  before_action :set_contact_type_group, except: [:new, :create]\n  after_action :verify_authorized\n\n  def new\n    authorize ContactTypeGroup\n    @contact_type_group = ContactTypeGroup.new\n  end\n\n  def create\n    authorize ContactTypeGroup\n    @contact_type_group = ContactTypeGroup.new(contact_type_group_params.merge(casa_org: current_organization))\n\n    if @contact_type_group.save\n      redirect_to edit_casa_org_path(current_organization), notice: \"Contact Type Group was successfully created.\"\n    else\n      render :new, status: :unprocessable_content\n    end\n  end\n\n  def edit\n    authorize @contact_type_group\n  end\n\n  def update\n    authorize @contact_type_group\n    if @contact_type_group.update(contact_type_group_params)\n      redirect_to edit_casa_org_path(current_organization), notice: \"Contact Type Group was successfully updated.\"\n    else\n      render :edit, status: :unprocessable_content\n    end\n  end\n\n  private\n\n  def contact_type_group_params\n    params.require(:contact_type_group).permit(:name, :active)\n  end\n\n  def set_contact_type_group\n    @contact_type_group = ContactTypeGroup.find(params[:id])\n  end\nend\n"
  },
  {
    "path": "app/controllers/contact_types_controller.rb",
    "content": "class ContactTypesController < ApplicationController\n  before_action :set_contact_type, except: [:new, :create]\n  after_action :verify_authorized\n\n  def new\n    authorize ContactType\n    @contact_type = ContactType.new\n  end\n\n  def create\n    authorize ContactType\n    @contact_type = ContactType.new(contact_type_params)\n\n    if @contact_type.save\n      redirect_to edit_casa_org_path(current_organization), notice: \"Contact Type was successfully created.\"\n    else\n      render :new, status: :unprocessable_content\n    end\n  end\n\n  def edit\n    authorize @contact_type\n  end\n\n  def update\n    authorize @contact_type\n    if @contact_type.update(contact_type_params)\n      redirect_to edit_casa_org_path(current_organization), notice: \"Contact Type was successfully updated.\"\n    else\n      render :edit, status: :unprocessable_content\n    end\n  end\n\n  private\n\n  def set_contact_type\n    @contact_type = ContactType.find(params[:id])\n  end\n\n  def contact_type_params\n    params.require(:contact_type).permit(:name, :contact_type_group_id, :active)\n  end\nend\n"
  },
  {
    "path": "app/controllers/court_dates_controller.rb",
    "content": "class CourtDatesController < ApplicationController\n  include CourtDateParams\n\n  before_action :set_casa_case\n  before_action :set_court_date, only: %i[edit show update destroy]\n  before_action :require_organization!\n\n  rescue_from ActiveRecord::RecordNotFound, with: -> { head :not_found }\n\n  def show\n    authorize @casa_case\n\n    respond_to do |format|\n      format.html {}\n      format.docx do\n        send_data generate_report_to_string(@court_date, params[:time_zone]),\n          type: :docx,\n          filename: \"#{@court_date.display_name}.docx\",\n          disposition: \"attachment\",\n          status: :ok\n      end\n    end\n  end\n\n  def new\n    @court_date = CourtDate.new(casa_case: @casa_case)\n    authorize @court_date\n  end\n\n  def edit\n    authorize @court_date\n  end\n\n  def create\n    @court_date = CourtDate.new(court_date_params(@casa_case).merge(casa_case: @casa_case))\n    authorize @court_date\n\n    if @court_date.save && @casa_case.save\n      redirect_to casa_case_court_date_path(@casa_case, @court_date), notice: \"Court date was successfully created.\"\n    else\n      render :new, status: :unprocessable_content\n    end\n  end\n\n  def update\n    authorize @court_date\n    if @court_date.update(court_date_params(@casa_case))\n      redirect_to casa_case_court_date_path(@casa_case, @court_date), notice: \"Court date was successfully updated.\"\n    else\n      render :edit, status: :unprocessable_content\n    end\n  end\n\n  def destroy\n    authorize @court_date\n    if @court_date.date > Time.now\n      @court_date.destroy\n      redirect_to casa_case_path(@casa_case), notice: \"Court date was successfully deleted.\"\n    else\n      redirect_to casa_case_court_date_path(@casa_case, @court_date), notice: \"You can delete only future court dates.\"\n    end\n  end\n\n  private\n\n  def set_casa_case\n    @casa_case = current_organization.casa_cases.friendly.find(params[:casa_case_id])\n  rescue ActiveRecord::RecordNotFound\n    respond_to do |format|\n      format.html { redirect_to casa_cases_path, notice: \"Sorry, you are not authorized to perform this action.\" }\n      format.json { render json: {error: \"Sorry, you are not authorized to perform this action.\"}, status: :unauthorized }\n    end\n  end\n\n  def set_court_date\n    @court_date = @casa_case.court_dates.find(params[:id])\n  end\n\n  def generate_report_to_string(court_date, time_zone)\n    casa_case = court_date.casa_case\n    casa_case.casa_org.open_org_court_report_template do |template_docx_file|\n      args = {\n        volunteer_id: current_user.volunteer? ? current_user.id : casa_case.assigned_volunteers.first&.id,\n        case_id: casa_case.id,\n        path_to_template: template_docx_file.to_path,\n        time_zone: time_zone,\n        court_date: court_date,\n        case_court_orders: court_date.case_court_orders\n      }\n      context = CaseCourtReportContext.new(args).context\n      court_report = CaseCourtReport.new(path_to_template: template_docx_file.to_path, context: context)\n\n      court_report.generate_to_string\n    end\n  end\nend\n"
  },
  {
    "path": "app/controllers/custom_org_links_controller.rb",
    "content": "class CustomOrgLinksController < ApplicationController\n  before_action :set_custom_org_link, only: %i[edit update destroy]\n  after_action :verify_authorized\n\n  def new\n    authorize CustomOrgLink\n    @custom_org_link = CustomOrgLink.new\n  end\n\n  def create\n    authorize CustomOrgLink\n\n    @custom_org_link = current_organization.custom_org_links.new(custom_org_link_params)\n\n    if @custom_org_link.save\n      redirect_to edit_casa_org_path(current_organization), notice: \"Custom link was successfully created.\"\n    else\n      render :new, status: :unprocessable_content\n    end\n  end\n\n  def edit\n    authorize @custom_org_link\n  end\n\n  def update\n    authorize @custom_org_link\n    if @custom_org_link.update(custom_org_link_params)\n      redirect_to edit_casa_org_path(current_organization), notice: \"Custom link was successfully updated.\"\n    else\n      render :edit, status: :unprocessable_content\n    end\n  end\n\n  def destroy\n    authorize @custom_org_link\n    @custom_org_link.destroy\n    redirect_to edit_casa_org_path(current_organization), notice: \"Custom link was successfully deleted.\"\n  end\n\n  private\n\n  def set_custom_org_link\n    @custom_org_link = CustomOrgLink.find(params[:id])\n  end\n\n  def custom_org_link_params\n    params.require(:custom_org_link).permit(:text, :url, :active)\n  end\nend\n"
  },
  {
    "path": "app/controllers/dashboard_controller.rb",
    "content": "class DashboardController < ApplicationController\n  before_action :require_organization!\n  after_action :verify_authorized\n\n  def show\n    authorize :dashboard\n    if volunteer_with_only_one_active_case?\n      redirect_to new_case_contact_path\n    elsif current_user.volunteer?\n      redirect_to casa_cases_path\n    elsif current_user.supervisor?\n      redirect_to volunteers_path\n    elsif current_user.casa_admin?\n      redirect_to supervisors_path\n    end\n  end\n\n  private\n\n  def volunteer_with_only_one_active_case?\n    current_user.volunteer? && current_user.casa_cases.active.count == 1\n  end\nend\n"
  },
  {
    "path": "app/controllers/emancipation_checklists_controller.rb",
    "content": "class EmancipationChecklistsController < ApplicationController\n  include DateHelper\n  before_action :require_organization!\n  after_action :verify_authorized\n\n  def index\n    authorize :application, :see_emancipation_checklist?\n    org_cases = current_user.casa_org.casa_cases.includes(:assigned_volunteers)\n    @casa_transitioning_cases = policy_scope(org_cases).is_transitioned.includes([:hearing_type, :judge])\n\n    if @casa_transitioning_cases.count == 1\n      redirect_to casa_case_emancipation_path(@casa_transitioning_cases[0])\n    end\n  end\nend\n"
  },
  {
    "path": "app/controllers/emancipations_controller.rb",
    "content": "class EmancipationsController < ApplicationController\n  before_action :require_organization!\n  after_action :verify_authorized\n  ADD_CATEGORY = \"add_category\"\n  ADD_OPTION = \"add_option\"\n  DELETE_CATEGORY = \"delete_category\"\n  DELETE_OPTION = \"delete_option\"\n  SET_OPTION = \"set_option\"\n  CHECK_ITEM_ACTIONS = [ADD_CATEGORY, ADD_OPTION, DELETE_CATEGORY, DELETE_OPTION, SET_OPTION].freeze\n\n  def show\n    @current_case = CasaCase.friendly.find(params[:casa_case_id])\n    authorize @current_case\n    @emancipation_form_data = EmancipationCategory.all\n\n    respond_to do |format|\n      format.html\n      format.docx {\n        template_filename = File.join(\"app\", \"documents\", \"templates\", \"emancipation_checklist_template.docx\")\n        @template = Sablon.template(File.expand_path(template_filename))\n\n        html_body = EmancipationChecklistDownloadHtml.new(@current_case, @emancipation_form_data).call\n\n        context = {\n          case_number: @current_case.case_number,\n          emancipation_checklist: Sablon.content(:html, html_body)\n        }\n\n        send_data((@template.render_to_string context, type: :docx), filename: \"#{@current_case.case_number} Emancipation Checklist.docx\")\n      }\n    end\n  end\n\n  def save\n    authorize CasaCase, :save_emancipation?\n    params.permit(:casa_case_id, :check_item_action)\n\n    begin\n      current_case = CasaCase.friendly.find(params[:casa_case_id])\n      authorize current_case, :update_emancipation_option?\n    rescue ActiveRecord::RecordNotFound\n      render json: {error: \"Could not find case from id given by casa_case_id\"}, status: :not_found\n      return\n    end\n\n    unless current_case.in_transition_age?\n      render json: {error: \"The current case is not marked as transitioning\"}, status: :bad_request\n      return\n    end\n    check_item_action = params[:check_item_action]\n    begin\n      case check_item_action\n      when ADD_CATEGORY\n        current_case.add_emancipation_category(params[:check_item_id])\n        render json: \"success\".to_json, status: :ok\n      when ADD_OPTION\n        current_case.add_emancipation_option(params[:check_item_id])\n        render json: \"success\".to_json, status: :ok\n      when DELETE_CATEGORY\n        current_case.remove_emancipation_category(params[:check_item_id])\n        current_case.emancipation_options.delete(EmancipationOption.category_options(params[:check_item_id]))\n        render json: \"success\".to_json, status: :ok\n      when DELETE_OPTION\n        current_case.remove_emancipation_option(params[:check_item_id])\n        render json: \"success\".to_json, status: :ok\n      when SET_OPTION\n        option = EmancipationOption.find(params[:check_item_id])\n        current_case.emancipation_options.delete(EmancipationOption.category_options(option.emancipation_category_id))\n        current_case.add_emancipation_option(params[:check_item_id])\n        render json: \"success\".to_json, status: :ok\n      else\n        render json: {error: \"Check item action: #{check_item_action} is not a supported action\"}, status: :bad_request\n      end\n    rescue ActiveRecord::RecordInvalid\n      render json: {error: \"The record already exists as an association on the case\"}, status: :bad_request\n    rescue ActiveRecord::RecordNotFound\n      render json: {error: \"Tried to destroy an association that does not exist\"}, status: :bad_request\n    end\n  end\n\n  # Render a json error for json endpoints\n  def not_authorized(exception)\n    if exception.backtrace[2].end_with?(\"save'\")\n      render json: {error: \"Sorry, you are not authorized to perform this action. Did the session expire?\"}, status: :unauthorized\n    else\n      super()\n    end\n  end\nend\n"
  },
  {
    "path": "app/controllers/error_controller.rb",
    "content": "# frozen_string_literal: true\n\nclass ErrorController < ApplicationController\n  skip_before_action :authenticate_user!\n  skip_after_action :verify_authorized\n\n  def index\n  end\n\n  def create\n    raise StandardError.new \"This is an intentional test exception\"\n  end\nend\n"
  },
  {
    "path": "app/controllers/followup_reports_controller.rb",
    "content": "# frozen_string_literal: true\n\nclass FollowupReportsController < ApplicationController\n  after_action :verify_authorized\n\n  def index\n    authorize :application, :see_reports_page?\n    followup_report = FollowupExportCsvService.new(current_organization).perform\n\n    respond_to do |format|\n      format.csv do\n        send_data followup_report,\n          filename: \"followup-report-#{Time.current.strftime(\"%Y-%m-%d\")}.csv\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "app/controllers/fund_requests_controller.rb",
    "content": "class FundRequestsController < ApplicationController\n  before_action :verify_casa_case\n  after_action :verify_authorized\n\n  def new\n    @fund_request = FundRequest.new\n    authorize @fund_request\n  end\n\n  def create\n    @fund_request = FundRequest.new(parsed_params)\n    authorize @fund_request\n\n    if @fund_request.save\n      FundRequestMailer.send_request(nil, @fund_request).deliver\n      redirect_to casa_case_path(@casa_case), notice: \"Fund Request was sent for case #{@casa_case.case_number}\"\n    else\n      render :new, status: :unprocessable_content\n    end\n  end\n\n  private\n\n  def verify_casa_case\n    @casa_case = CasaCase.friendly.find(params[:casa_case_id])\n    unless @casa_case.casa_org == current_user.casa_org\n      redirect_to root_path\n    end\n  end\n\n  def parsed_params\n    params.require(:fund_request).permit(\n      :submitter_email,\n      :youth_name,\n      :payment_amount,\n      :deadline,\n      :request_purpose,\n      :payee_name,\n      :requested_by_and_relationship,\n      :other_funding_source_sought,\n      :impact,\n      :extra_information\n    )\n  end\nend\n"
  },
  {
    "path": "app/controllers/health_controller.rb",
    "content": "class HealthController < ApplicationController\n  skip_before_action :authenticate_user!\n  skip_after_action :verify_authorized\n  before_action :verify_token_for_gc_stats, only: [:gc]\n\n  def index\n    respond_to do |format|\n      format.html do\n        render :index\n      end\n\n      format.json { render json: {latest_deploy_time: Health.instance.latest_deploy_time} }\n    end\n  end\n\n  def gc\n    render body: JSON.pretty_generate([\n      Time.now.in_time_zone(\"Central Time (US & Canada)\").strftime(\"%H\"),\n      GC.stat\n    ]),\n      content_type: \"application/json\"\n  end\n\n  def case_contacts_creation_times_in_last_week\n    case_contacts_created_in_last_week = CaseContact.where(\"created_at >= ?\", 1.week.ago)\n\n    unix_timestamps_of_case_contacts_created_in_last_week = case_contacts_created_in_last_week.pluck(:created_at).map { |creation_time| creation_time.to_i }\n\n    render json: {timestamps: unix_timestamps_of_case_contacts_created_in_last_week}\n  end\n\n  def monthly_line_graph_data\n    first_day_of_last_12_months = (12.months.ago.to_date..Date.current).select { |date| date.day == 1 }.map { |date| date.beginning_of_month }\n\n    if first_day_of_last_12_months.size > 12\n      first_day_of_last_12_months = first_day_of_last_12_months[1..12]\n    end\n\n    monthly_counts_of_case_contacts_created = CaseContact.group_by_month(:created_at, last: 12).count\n    monthly_counts_of_case_contacts_with_notes_created = CaseContact.left_outer_joins(:contact_topic_answers).where(\"case_contacts.notes != '' OR contact_topic_answers.value != ''\").select(:id).distinct.group_by_month(:created_at, last: 12).count\n    monthly_counts_of_users_who_have_created_case_contacts = CaseContact.select(:creator_id).distinct.group_by_month(:created_at, last: 12).count\n\n    monthly_line_graph_combined_data = first_day_of_last_12_months.map do |month|\n      [\n        month.strftime(\"%b %Y\"),\n        monthly_counts_of_case_contacts_created[month],\n        monthly_counts_of_case_contacts_with_notes_created[month],\n        monthly_counts_of_users_who_have_created_case_contacts[month]\n      ]\n    end\n\n    render json: monthly_line_graph_combined_data\n  end\n\n  def monthly_unique_users_graph_data\n    first_day_of_last_12_months = (12.months.ago.to_date..Date.current).select { |date| date.day == 1 }.map { |date| date.beginning_of_month.strftime(\"%b %Y\") }\n\n    if first_day_of_last_12_months.size > 12\n      first_day_of_last_12_months = first_day_of_last_12_months[1..12]\n    end\n\n    monthly_counts_of_volunteers = LoginActivity.joins(\"INNER JOIN users ON users.id = login_activities.user_id AND login_activities.user_type = 'User'\").where(users: {type: \"Volunteer\"}, success: true).group_by_month(:created_at, format: \"%b %Y\").distinct.count(:user_id)\n    monthly_counts_of_supervisors = LoginActivity.joins(\"INNER JOIN users ON users.id = login_activities.user_id AND login_activities.user_type = 'User'\").where(users: {type: \"Supervisor\"}, success: true).group_by_month(:created_at, format: \"%b %Y\").distinct.count(:user_id)\n    monthly_counts_of_casa_admins = LoginActivity.joins(\"INNER JOIN users ON users.id = login_activities.user_id AND login_activities.user_type = 'User'\").where(users: {type: \"CasaAdmin\"}, success: true).group_by_month(:created_at, format: \"%b %Y\").distinct.count(:user_id)\n    monthly_logged_counts_of_volunteers = CaseContact.joins(supervisor_volunteer: :volunteer).group_by_month(:created_at, format: \"%b %Y\").distinct.count(:creator_id)\n\n    monthly_line_graph_combined_data = first_day_of_last_12_months.map do |month|\n      [\n        month,\n        monthly_counts_of_volunteers[month] || 0,\n        monthly_counts_of_supervisors[month] || 0,\n        monthly_counts_of_casa_admins[month] || 0,\n        monthly_logged_counts_of_volunteers[month] || 0\n      ]\n    end\n\n    render json: monthly_line_graph_combined_data\n  end\n\n  private\n\n  def verify_token_for_gc_stats\n    gc_access_token = ENV[\"GC_ACCESS_TOKEN\"]\n\n    head :forbidden unless params[:token] == gc_access_token && !gc_access_token.nil?\n  end\nend\n"
  },
  {
    "path": "app/controllers/hearing_types_controller.rb",
    "content": "class HearingTypesController < ApplicationController\n  before_action :set_hearing_type, except: [:new, :create]\n  after_action :verify_authorized\n\n  def new\n    authorize HearingType\n    @hearing_type = HearingType.new\n  end\n\n  def create\n    authorize HearingType\n    @hearing_type = HearingType.new(hearing_type_params)\n\n    if @hearing_type.save\n      redirect_to edit_casa_org_path(current_organization), notice: \"Hearing Type was successfully created.\"\n    else\n      render :new, status: :unprocessable_content\n    end\n  end\n\n  def edit\n    authorize @hearing_type\n  end\n\n  def update\n    authorize @hearing_type\n    if @hearing_type.update(hearing_type_params)\n      redirect_to edit_casa_org_path(current_organization), notice: \"Hearing Type was successfully updated.\"\n    else\n      render :edit, status: :unprocessable_content\n    end\n  end\n\n  private\n\n  def set_hearing_type\n    @hearing_type = HearingType.find(params[:id])\n  end\n\n  def hearing_type_params\n    params.require(:hearing_type).permit(:name, :active).merge(\n      casa_org: current_organization\n    )\n  end\nend\n"
  },
  {
    "path": "app/controllers/imports_controller.rb",
    "content": "class ImportsController < ApplicationController\n  require \"csv\"\n\n  include ActionView::Helpers::UrlHelper\n  before_action :failed_csv_service, only: [:create, :download_failed]\n  after_action :verify_authorized\n\n  ERR_FAILED_IMPORT_NOTE = \"Note: An additional 'error' column has been added to the file. \" \\\n    \"Please note the failure reason and remove the column when resubmitting.\"\n  ERR_FILE_NOT_ATTACHED = \"You must attach a CSV file in order to import information!\"\n  ERR_FILE_NOT_FOUND = \"CSV import file not found.\"\n  ERR_FILE_EMPTY = \"File can not be empty.\"\n  ERR_INVALID_HEADER = \"Looks like this CSV contains invalid formatting. \" \\\n    \"Please download an example CSV for reference and try again.\"\n\n  def index\n    authorize :import\n    @import_type = params.fetch(:import_type, \"volunteer\")\n    @import_error = session[:import_error]\n    @sms_opt_in_warning = session[:sms_opt_in_warning]\n    session[:import_error] = nil\n    session[:sms_opt_in_warning] = nil\n  end\n\n  def create\n    authorize :import\n    @failed_csv_service.cleanup\n\n    import = import_from_csv(params[:import_type], params[:sms_opt_in], params[:file], current_user.casa_org_id)\n    message = import[:message]\n\n    # If there were failed imports\n    if import[:exported_rows]\n      @failed_csv_service.failed_rows = import[:exported_rows]\n      @failed_csv_service.store\n      message << \"<p class='mt-4'>\" + link_to(\"Click here to download failed rows.\", download_failed_imports_path(import_type: params[:import_type])) +\n        \"</p>\" + \"<p>#{ERR_FAILED_IMPORT_NOTE}</p>\"\n    end\n\n    if import[:type] == :error\n      session[:import_error] = message\n    elsif import[:type] == :sms_opt_in_warning\n      session[:sms_opt_in_warning] = import[:import_type]\n    # Only use flash for success messages. Otherwise may cause CookieOverflow\n    else\n      flash[:success] = message\n    end\n\n    redirect_to imports_path(import_type: params[:import_type])\n  end\n\n  def download_failed\n    authorize :import\n    csv_contents = @failed_csv_service.read\n\n    send_data csv_contents, format: :csv, filename: \"failed_rows.csv\"\n  end\n\n  private\n\n  def failed_csv_service(failed_rows: \"\")\n    @failed_csv_service = FailedImportCsvService.new(\n      failed_rows: failed_rows,\n      import_type: params[:import_type],\n      user: current_user\n    )\n  end\n\n  def header\n    {\n      \"volunteer\" => VolunteerImporter::IMPORT_HEADER,\n      \"supervisor\" => SupervisorImporter::IMPORT_HEADER,\n      \"casa_case\" => CaseImporter::IMPORT_HEADER\n    }\n  end\n\n  def header_valid?(file_header, import_type)\n    file_header == header[import_type]\n  end\n\n  def import_from_csv(import_type, sms_opt_in, file, org_id)\n    validated_file = validate_file(file, import_type)\n\n    return validated_file unless validated_file.nil?\n\n    if requires_sms_opt_in(file, import_type, sms_opt_in)\n      return {type: :sms_opt_in_warning, import_type: import_type}\n    end\n\n    case import_type\n    when \"volunteer\"\n      VolunteerImporter.import_volunteers(file, org_id)\n    when \"supervisor\"\n      SupervisorImporter.import_supervisors(file, org_id)\n    when \"casa_case\"\n      CaseImporter.import_cases(file, org_id)\n    else\n      valid_import_types_string = %w[volunteer supervisor casa_case].to_s\n      {type: :error, message: \"Bad import type '#{import_type}'. Must be one of #{valid_import_types_string}.\"}\n    end\n  end\n\n  def validate_file(file, import_type)\n    # Validate that file is attached\n    if file.blank?\n      return {type: :error, message: ERR_FILE_NOT_ATTACHED}\n    end\n\n    # Validate that file exists\n    unless File.file?(file)\n      return {type: :error, message: ERR_FILE_NOT_FOUND + \": #{file}\"}\n    end\n\n    # Validate that the file is not empty\n    if File.zero?(file)\n      return {type: :error, message: ERR_FILE_EMPTY}\n    end\n\n    # Validate header\n    file_header = File.open(file, \"r:bom|utf-8\", &:readline).squish.split(\",\")\n\n    unless header_valid?(file_header, import_type)\n      message = \"#{ERR_INVALID_HEADER}<p class='mt-4'>\" \\\n        \"<b>Expected Header</b>: #{header[import_type].join(\", \")}.</p>\" \\\n        \"<p><b>Received Header</b>: #{file_header.join(\", \")}</p>\"\n\n      {type: :error, message: message}\n    end\n  end\n\n  def requires_sms_opt_in(file, import_type, sms_opt_in)\n    if (import_type == \"volunteer\" || import_type == \"supervisor\") && import_contains_phone_numbers(file)\n      return sms_opt_in != \"1\"\n    end\n\n    false\n  end\n\n  def import_contains_phone_numbers(file)\n    CSV.foreach(file, headers: true, header_converters: :symbol) do |row|\n      phone_number = row[:phone_number]\n      if !phone_number.nil? && !phone_number.strip.empty?\n        return true\n      end\n    end\n\n    false\n  end\nend\n"
  },
  {
    "path": "app/controllers/judges_controller.rb",
    "content": "class JudgesController < ApplicationController\n  before_action :set_judge, except: [:new, :create]\n  after_action :verify_authorized\n\n  def new\n    authorize Judge\n    @judge = Judge.new\n  end\n\n  def create\n    authorize Judge\n    @judge = Judge.new(judge_params)\n\n    if @judge.save\n      redirect_to edit_casa_org_path(current_organization), notice: \"Judge was successfully created.\"\n    else\n      render :new, status: :unprocessable_content\n    end\n  end\n\n  def edit\n    authorize @judge\n  end\n\n  def update\n    authorize @judge\n    if @judge.update(judge_params)\n      redirect_to edit_casa_org_path(current_organization), notice: \"Judge was successfully updated.\"\n    else\n      render :edit, status: :unprocessable_content\n    end\n  end\n\n  private\n\n  def set_judge\n    @judge = Judge.find(params[:id])\n  end\n\n  def judge_params\n    params.require(:judge).permit(:name, :active).merge(\n      casa_org: current_organization\n    )\n  end\nend\n"
  },
  {
    "path": "app/controllers/languages_controller.rb",
    "content": "class LanguagesController < ApplicationController\n  before_action :set_language, only: %i[edit update]\n\n  def new\n    authorize Language\n    @language = Language.new\n  end\n\n  def edit\n    authorize @language\n  end\n\n  def create\n    authorize Language\n    @language = Language.new(language_params)\n    @language.casa_org = current_organization\n    respond_to do |format|\n      if @language.save\n        format.html { redirect_to edit_casa_org_path(current_organization.id), notice: \"Language was successfully created.\" }\n      else\n        format.html { render :new, status: :unprocessable_content }\n      end\n    end\n  end\n\n  def update\n    authorize @language\n    respond_to do |format|\n      if @language.update(language_params)\n        format.html { redirect_to edit_casa_org_path(current_organization.id), notice: \"Language was successfully updated.\" }\n      else\n        format.html { render :edit, status: :unprocessable_content }\n      end\n    end\n  end\n\n  private\n\n  def set_language\n    @language = Language.find(params[:id] || params[:language_id])\n  end\n\n  def language_params\n    params.require(:language).permit(:name)\n  end\nend\n"
  },
  {
    "path": "app/controllers/learning_hour_topics_controller.rb",
    "content": "# frozen_string_literal: true\n\nclass LearningHourTopicsController < ApplicationController\n  before_action :set_learning_hour_topic, only: %i[edit update]\n  after_action :verify_authorized\n\n  def new\n    authorize LearningHourTopic\n    @learning_hour_topic = LearningHourTopic.new\n  end\n\n  def edit\n    authorize @learning_hour_topic\n  end\n\n  def create\n    authorize LearningHourTopic\n    @learning_hour_topic = LearningHourTopic.new(learning_hour_topic_params)\n\n    if @learning_hour_topic.save\n      redirect_to edit_casa_org_path(current_organization), notice: \"Learning Topic was successfully created.\"\n    else\n      render :new, status: :unprocessable_content\n    end\n  end\n\n  def update\n    authorize @learning_hour_topic\n\n    if @learning_hour_topic.update(learning_hour_topic_params)\n      redirect_to edit_casa_org_path(current_organization), notice: \"Learning Topic was successfully updated.\"\n    else\n      render :edit, status: :unprocessable_content\n    end\n  end\n\n  private\n\n  def set_learning_hour_topic\n    @learning_hour_topic = LearningHourTopic.find(params[:id])\n  end\n\n  def learning_hour_topic_params\n    params.require(:learning_hour_topic).permit(:name).merge(\n      casa_org: current_organization\n    )\n  end\nend\n"
  },
  {
    "path": "app/controllers/learning_hour_types_controller.rb",
    "content": "# frozen_string_literal: true\n\nclass LearningHourTypesController < ApplicationController\n  before_action :set_learning_hour_type, only: %i[edit update]\n  after_action :verify_authorized\n\n  def new\n    authorize LearningHourType\n    @learning_hour_type = LearningHourType.new\n  end\n\n  def edit\n    authorize @learning_hour_type\n  end\n\n  def create\n    authorize LearningHourType\n    @learning_hour_type = LearningHourType.new(learning_hour_type_params)\n\n    if @learning_hour_type.save\n      redirect_to edit_casa_org_path(current_organization), notice: \"Learning Type was successfully created.\"\n    else\n      render :new, status: :unprocessable_content\n    end\n  end\n\n  def update\n    authorize @learning_hour_type\n\n    if @learning_hour_type.update(learning_hour_type_params)\n      redirect_to edit_casa_org_path(current_organization), notice: \"Learning Type was successfully updated.\"\n    else\n      render :edit, status: :unprocessable_content\n    end\n  end\n\n  private\n\n  def set_learning_hour_type\n    @learning_hour_type = LearningHourType.find(params[:id])\n  end\n\n  def learning_hour_type_params\n    params.require(:learning_hour_type).permit(:name, :active).merge(\n      casa_org: current_organization\n    )\n  end\nend\n"
  },
  {
    "path": "app/controllers/learning_hours/volunteers_controller.rb",
    "content": "class LearningHours::VolunteersController < ApplicationController\n  before_action :set_volunteer, only: :show\n  after_action :verify_authorized\n\n  def show\n    authorize @volunteer\n    @learning_hours = LearningHour.where(user: @volunteer)\n  end\n\n  private\n\n  def set_volunteer\n    @volunteer = User.includes(:learning_hours).find(params[:id])\n  end\nend\n"
  },
  {
    "path": "app/controllers/learning_hours_controller.rb",
    "content": "class LearningHoursController < ApplicationController\n  before_action :set_learning_hour, only: %i[show edit update destroy]\n  after_action :verify_authorized, except: :index # TODO add this back and fix all tests\n\n  def index\n    authorize LearningHour\n    @learning_hours = policy_scope(LearningHour)\n  end\n\n  def show\n    authorize @learning_hour\n  end\n\n  def new\n    authorize LearningHour\n    @learning_hour = LearningHour.new\n  end\n\n  def create\n    @learning_hour = LearningHour.new(learning_hours_params)\n    authorize @learning_hour\n\n    respond_to do |format|\n      if @learning_hour.save\n        format.html { redirect_to learning_hours_path, notice: \"New entry was successfully created.\" }\n      else\n        format.html { render :new, status: :unprocessable_content }\n      end\n    end\n  end\n\n  def edit\n    authorize @learning_hour\n  end\n\n  def update\n    authorize @learning_hour\n    respond_to do |format|\n      if @learning_hour.update(update_learning_hours_params)\n        format.html { redirect_to learning_hour_path(@learning_hour), notice: \"Entry was successfully updated.\" }\n      else\n        format.html { render :edit, status: :unprocessable_content }\n      end\n    end\n  end\n\n  def destroy\n    authorize @learning_hour\n    @learning_hour.destroy\n    flash[:notice] = \"Entry was successfully deleted.\"\n    redirect_to learning_hours_path\n  end\n\n  private\n\n  def set_learning_hour\n    @learning_hour = LearningHour.find(params[:id])\n  rescue ActiveRecord::RecordNotFound\n    redirect_to learning_hours_path\n  end\n\n  def learning_hours_params\n    params.require(:learning_hour).permit(:occurred_at, :duration_minutes, :duration_hours, :name, :user_id,\n      :learning_hour_type_id, :learning_hour_topic_id)\n  end\n\n  def update_learning_hours_params\n    params.require(:learning_hour).permit(:occurred_at, :duration_minutes, :duration_hours, :name,\n      :learning_hour_type_id, :learning_hour_topic_id)\n  end\nend\n"
  },
  {
    "path": "app/controllers/learning_hours_reports_controller.rb",
    "content": "class LearningHoursReportsController < ApplicationController\n  after_action :verify_authorized\n\n  def index\n    authorize :application, :see_reports_page?\n    learning_hours_report = LearningHoursReport.new(current_organization.id)\n\n    respond_to do |format|\n      format.csv do\n        send_data learning_hours_report.to_csv,\n          filename: \"learning-hours-report-#{Time.current.strftime(\"%Y-%m-%d\")}.csv\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "app/controllers/mileage_rates_controller.rb",
    "content": "class MileageRatesController < ApplicationController\n  after_action :verify_authorized\n  before_action :set_mileage_rate, only: %i[edit update]\n\n  def index\n    authorize :application, :see_mileage_rate?\n    @mileage_rates = MileageRate.where(casa_org: current_organization).order(effective_date: :asc)\n  end\n\n  def new\n    authorize CasaAdmin\n    @mileage_rate = current_organization.mileage_rates.build\n  end\n\n  def create\n    authorize CasaAdmin\n    @mileage_rate = MileageRate.new(mileage_rate_params.merge(casa_org: current_organization))\n    if @mileage_rate.save\n      redirect_to mileage_rates_path\n    else\n      render :new, status: :unprocessable_content\n    end\n  end\n\n  def edit\n    authorize CasaAdmin\n  end\n\n  def update\n    authorize CasaAdmin\n\n    if @mileage_rate.update(mileage_rate_params)\n      redirect_to mileage_rates_path\n    else\n      render :edit, status: :unprocessable_content\n    end\n  end\n\n  private\n\n  def mileage_rate_params\n    params.require(:mileage_rate).permit(:effective_date, :amount, :is_active)\n  end\n\n  def set_mileage_rate\n    @mileage_rate = MileageRate.find(params[:id])\n  end\nend\n"
  },
  {
    "path": "app/controllers/mileage_reports_controller.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"csv\"\n\nclass MileageReportsController < ApplicationController\n  after_action :verify_authorized\n\n  def index\n    authorize :application, :see_reports_page?\n    mileage_report = MileageReport.new(current_organization.id)\n\n    respond_to do |format|\n      format.csv do\n        send_data mileage_report.to_csv,\n          filename: \"mileage-report-#{Time.current.strftime(\"%Y-%m-%d\")}.csv\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "app/controllers/missing_data_reports_controller.rb",
    "content": "class MissingDataReportsController < ApplicationController\n  after_action :verify_authorized\n\n  def index\n    authorize :application, :see_reports_page?\n    missing_data_report = MissingDataReport.new(current_organization.id)\n\n    respond_to do |format|\n      format.csv do\n        send_data missing_data_report.to_csv,\n          filename: \"missing-data-report-#{Time.current.strftime(\"%Y-%m-%d\")}.csv\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "app/controllers/notes_controller.rb",
    "content": "class NotesController < ApplicationController\n  before_action :find_volunteer\n  before_action :find_note, only: %i[edit update destroy]\n\n  def create\n    authorize Note\n    @volunteer.notes.create(note_params)\n    redirect_to edit_volunteer_path(@volunteer)\n  end\n\n  def edit\n    authorize @note\n  end\n\n  def update\n    authorize @note\n    @note.update(note_params)\n\n    redirect_to edit_volunteer_path(@volunteer)\n  end\n\n  def destroy\n    authorize @note\n    @note.destroy\n\n    redirect_to edit_volunteer_path(@volunteer)\n  end\n\n  private\n\n  def find_note\n    @note = @volunteer.notes.find_by(id: params[:id])\n    redirect_to root_path unless @note\n  end\n\n  def find_volunteer\n    @volunteer = current_user.casa_org.volunteers.find_by(id: params[:volunteer_id])\n    redirect_to root_path unless @volunteer\n  end\n\n  def note_params\n    params.require(:note).permit(:content).merge({creator_id: current_user.id})\n  end\nend\n"
  },
  {
    "path": "app/controllers/notifications_controller.rb",
    "content": "class NotificationsController < ApplicationController\n  after_action :verify_authorized\n  before_action :set_notification, only: %i[mark_as_read]\n\n  def index\n    authorize Noticed::Notification, policy_class: NotificationPolicy\n\n    @deploy_time = Health.instance.latest_deploy_time\n    @notifications = current_user.notifications.includes([:event]).newest_first\n    @patch_notes = PatchNote.notes_available_for_user(current_user)\n  end\n\n  def mark_as_read\n    authorize @notification, policy_class: NotificationPolicy\n\n    @notification.mark_as_read unless @notification.read?\n    redirect_to @notification.event.url\n  end\n\n  private\n\n  def set_notification\n    @notification = Noticed::Notification.find(params[:id])\n  end\nend\n"
  },
  {
    "path": "app/controllers/other_duties_controller.rb",
    "content": "class OtherDutiesController < ApplicationController\n  before_action :set_other_duty, except: [:new, :create, :index]\n  before_action :convert_duration_minutes, only: [:update, :create]\n\n  def index\n    authorize OtherDuty\n\n    @volunteer_duties = if current_user.casa_admin?\n      generate_other_duty_list(policy_scope(Volunteer))\n    elsif current_user.supervisor?\n      generate_other_duty_list(current_user.volunteers)\n    else # for volunteer user\n      generate_other_duty_list([current_user])\n    end\n  end\n\n  def new\n    authorize OtherDuty\n    @other_duty = OtherDuty.new\n  end\n\n  def create\n    authorize OtherDuty\n    @other_duty = OtherDuty.new(other_duty_params)\n\n    if @other_duty.save\n      redirect_to other_duties_path, notice: \"Duty was successfully created.\"\n    else\n      render :new, status: :unprocessable_content\n    end\n  end\n\n  def edit\n    authorize @other_duty\n  end\n\n  def update\n    authorize @other_duty\n\n    if @other_duty.update(other_duty_params)\n      redirect_to other_duties_path, notice: \"Duty was successfully updated.\"\n    else\n      render :edit, status: :unprocessable_content\n    end\n  end\n\n  private\n\n  def convert_duration_minutes\n    duration_hours = params[:other_duty][:duration_hours].to_i\n    converted_duration_hours = duration_hours * 60\n    duration_minutes = params[:other_duty][:duration_minutes].to_i\n    params[:other_duty][:duration_minutes] = (converted_duration_hours + duration_minutes).to_s\n  end\n\n  def generate_other_duty_list(volunteers)\n    return [] if no_other_duties_for(volunteers)\n    volunteers.map do |volunteer|\n      {\n        volunteer: volunteer,\n        other_duties: volunteer.other_duties\n      }\n    end\n  end\n\n  def no_other_duties_for(volunteers)\n    no_duties_found = true\n    volunteers.each do |volunteer|\n      if volunteer.other_duties.present?\n        no_duties_found = false\n      end\n    end\n    no_duties_found\n  end\n\n  def other_duty_params\n    params.require(:other_duty).permit(:occurred_at, :creator_type, :duration_minutes, :notes).merge({creator_id: current_user.id})\n  end\n\n  def set_other_duty\n    @other_duty = OtherDuty.find(params[:id])\n  end\nend\n"
  },
  {
    "path": "app/controllers/placement_reports_controller.rb",
    "content": "class PlacementReportsController < ApplicationController\n  after_action :verify_authorized\n\n  def index\n    authorize :application, :see_reports_page?\n    placement_report = PlacementExportCsvService.new(casa_org: current_organization).perform\n\n    respond_to do |format|\n      format.csv do\n        send_data placement_report,\n          filename: \"placement-report-#{Time.current.strftime(\"%Y-%m-%d\")}.csv\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "app/controllers/placement_types_controller.rb",
    "content": "class PlacementTypesController < ApplicationController\n  before_action :set_placement_type, only: %i[edit update]\n  after_action :verify_authorized\n  after_action :verify_policy_scoped\n\n  def new\n    @placement_type = policy_scope(PlacementType).new(casa_org: current_organization)\n    authorize @placement_type\n  end\n\n  def create\n    @placement_type = policy_scope(PlacementType).new(placement_type_params)\n    authorize @placement_type\n\n    if @placement_type.save\n      redirect_to edit_casa_org_path(current_organization), notice: \"Placement Type was successfully created.\"\n    else\n      render :new, status: :unprocessable_content\n    end\n  end\n\n  def edit\n    authorize @placement_type\n  end\n\n  def update\n    authorize @placement_type\n\n    if @placement_type.update(placement_type_params)\n      redirect_to edit_casa_org_path(current_organization), notice: \"Placement Type was successfully updated.\"\n    else\n      render :edit, status: :unprocessable_content\n    end\n  end\n\n  private\n\n  def set_placement_type\n    @placement_type = policy_scope(PlacementType).find(params[:id])\n  rescue ActiveRecord::RecordNotFound\n    redirect_to edit_casa_org_path\n  end\n\n  def placement_type_params\n    params.require(:placement_type).permit(:name).merge(casa_org: current_organization)\n  end\nend\n"
  },
  {
    "path": "app/controllers/placements_controller.rb",
    "content": "class PlacementsController < ApplicationController\n  before_action :set_casa_case\n  before_action :set_placement, only: %i[edit show update destroy]\n  before_action :require_organization!\n\n  def index\n    @placements = policy_scope(@casa_case.placements).includes(:placement_type).order(placement_started_at: :desc)\n  end\n\n  def show\n    authorize @placement\n  end\n\n  def new\n    @placement = Placement.new(casa_case: @casa_case)\n    authorize @placement\n  end\n\n  def edit\n    authorize @placement\n  end\n\n  def create\n    @placement = Placement.new(placement_params)\n    authorize @placement\n\n    if @placement.save\n      redirect_to casa_case_placements_path(@casa_case), notice: \"Placement was successfully created.\"\n    else\n      render :new, status: :unprocessable_content\n    end\n  end\n\n  def update\n    authorize @placement\n\n    if @placement.update(placement_params)\n      redirect_to casa_case_placements_path(@casa_case), notice: \"Placement was successfully updated.\"\n    else\n      render :edit, status: :unprocessable_content\n    end\n  end\n\n  def destroy\n    authorize @placement\n\n    if @placement.destroy\n      redirect_to casa_case_placements_path(@casa_case), notice: \"Placement was successfully deleted.\"\n    else\n      render :edit, status: :unprocessable_content\n    end\n  end\n\n  private\n\n  def set_casa_case\n    @casa_case = current_organization.casa_cases.friendly.find(params[:casa_case_id])\n  end\n\n  def set_placement\n    @placement = @casa_case.placements.find(params[:id])\n  end\n\n  def placement_params\n    params.require(:placement).permit(\n      :placement_started_at,\n      :placement_type_id\n    ).merge({creator_id: current_user.id, casa_case_id: @casa_case.id})\n  end\nend\n"
  },
  {
    "path": "app/controllers/preference_sets_controller.rb",
    "content": "class PreferenceSetsController < ApplicationController\n  before_action :skip_authorization, :set_table_name\n\n  def table_state\n    render json: PreferenceSetTableStateService.new(user_id: current_user.id).table_state(\n      table_name: @table_name\n    )\n  end\n\n  def table_state_update\n    render json: PreferenceSetTableStateService.new(user_id: current_user.id).update!(\n      table_state: params[\"table_state\"],\n      table_name: @table_name\n    )\n  rescue PreferenceSetTableStateService::TableStateUpdateFailed\n    render json: {error: \"Failed to update table state for '#{@table_name}'\"}\n  end\n\n  private\n\n  def set_table_name\n    @table_name = params[:table_name]\n  end\nend\n"
  },
  {
    "path": "app/controllers/reimbursements_controller.rb",
    "content": "class ReimbursementsController < ApplicationController\n  def new\n  end\n\n  def index\n    authorize :reimbursement\n\n    @complete_status = params[:status] == \"complete\"\n    @datatable_url = datatable_reimbursements_path(format: :json, status: params[:status])\n    @volunteers_for_filter = volunteers_for_filter(\n      fetch_filtered_reimbursements(@complete_status)\n    )\n    @occurred_at_filter_start_date = (Time.now - 1.year).strftime(\"%Y/%m/%d\")\n    # @grouped_reimbursements = @reimbursements.group_by { |cc| \"#{cc.occurred_at}-#{cc.creator_id}\" }\n  end\n\n  def datatable\n    authorize :reimbursement\n\n    @complete_status = params[:status] == \"complete\"\n    datatable = ReimbursementDatatable.new(\n      fetch_filtered_reimbursements(@complete_status), params\n    )\n\n    render json: datatable\n  end\n\n  def change_complete_status\n    authorize :reimbursement\n\n    @case_contact = fetch_reimbursements.find(params[:reimbursement_id])\n    @grouped_case_contacts = fetch_reimbursements\n      .where({occurred_at: @case_contact.occurred_at, creator_id: @case_contact.creator_id})\n    @grouped_case_contacts.update_all(reimbursement_params.to_h)\n    notification_recipients = [@case_contact.creator]\n    notification_recipients << @case_contact.supervisor if @case_contact.supervisor\n    ReimbursementCompleteNotifier.with(case_contact: @case_contact).deliver(notification_recipients)\n    redirect_to reimbursements_path unless params[:ajax]\n  end\n\n  private\n\n  def apply_filters_to_query(query)\n    query = query.where(creator_id: params[:volunteers]) if params[:volunteers]\n\n    apply_occurred_at_filters(query)\n  end\n\n  def apply_occurred_at_filters(query)\n    return query unless params[:occurred_at]\n\n    apply_occurred_at_filter(\n      :start,\n      apply_occurred_at_filter(:end, query)\n    )\n  end\n\n  def apply_occurred_at_filter(key, query)\n    return query if params[:occurred_at][key].empty?\n\n    query.where(\n      (key == :end) ? \"? >= occurred_at\" : \"occurred_at >= ?\",\n      get_normalised_time_for_occurred_at_filter(key)\n    )\n  rescue ArgumentError\n    query\n  end\n\n  def fetch_reimbursements\n    case_contacts = CaseContact.joins(:casa_case).includes(\n      :creator,\n      :case_contact_contact_types,\n      contact_types: [:contact_type_group]\n    ).preload(:casa_case)\n    policy_scope(case_contacts, policy_scope_class: ReimbursementPolicy::Scope)\n  end\n\n  def fetch_filtered_reimbursements(complete_only)\n    apply_filters_to_query(\n      fetch_reimbursements\n        .want_driving_reimbursement(true)\n        .created_max_ago(1.year.ago)\n        .filter_by_reimbursement_status(complete_only)\n    )\n  end\n\n  def get_normalised_time_for_occurred_at_filter(key)\n    normalised_date = Date.strptime(params[:occurred_at][key], \"%Y/%m/%d\")\n    normalised_time = DateTime.new(normalised_date.year, normalised_date.month, normalised_date.day)\n\n    return normalised_time if key == :start\n\n    normalised_time + 1.day - 1 * 10e-6.seconds if key == :end\n  end\n\n  def reimbursement_params\n    params.require(:case_contact).permit(:reimbursement_complete)\n  end\n\n  def volunteers_for_filter(reimbursements)\n    reimbursements\n      .map { |reimbursement| [reimbursement.creator.id, reimbursement.creator.display_name] }\n      .sort_by { |_, name| name }\n      .to_h\n  end\nend\n"
  },
  {
    "path": "app/controllers/reports_controller.rb",
    "content": "class ReportsController < ApplicationController\n  after_action :verify_authorized\n\n  def index\n    authorize :application, :see_reports_page?\n  end\n\n  def export_emails\n    authorize :application, :see_reports_page?\n\n    respond_to do |format|\n      format.csv do\n        send_data VolunteersEmailsExportCsvService.new(current_user.casa_org).call,\n          filename: \"volunteers-emails-#{Time.current.strftime(\"%Y-%m-%d\")}.csv\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "app/controllers/static_controller.rb",
    "content": "# Provides a means to present static pages that still use the site layout\nclass StaticController < ApplicationController\n  skip_before_action :authenticate_user!\n  skip_before_action :set_current_user\n  skip_before_action :set_current_organization\n\n  layout false\n\n  def index\n    redirect_to dashboard_path_from_current_role if current_user\n    @casa_logos = CasaOrg.with_logo\n  end\n\n  def register\n  end\n\n  def page\n    # This allows for a flexible addition of static content\n    # Anything under the url /pages/:name will render the file /app/views/static/[name].html.erb\n    # Example: /pages/contact renders /app/views/static/contact.html.erb\n    # Example2: /pages/index renders /app/views/static/index.html.erb, even when logged in\n    render template: \"static/#{params[:name]}\"\n  end\nend\n"
  },
  {
    "path": "app/controllers/supervisor_volunteers_controller.rb",
    "content": "class SupervisorVolunteersController < ApplicationController\n  after_action :verify_authorized\n\n  def create\n    authorize :supervisor_volunteer\n    volunteer = Volunteer.find(supervisor_volunteer_params[:volunteer_id])\n    supervisor = set_supervisor\n    if assign_volunteer_to_supervisor(volunteer, supervisor)\n      flash[:notice] = \"#{volunteer.display_name} successfully assigned to #{supervisor.display_name}.\"\n    else\n      flash[:alert] = \"Something went wrong. Please try again.\"\n    end\n\n    redirect_to request.referer\n  end\n\n  def unassign\n    authorize :supervisor_volunteer\n    volunteer = Volunteer.find(params[:id])\n    if unassign_volunteers_supervisor(volunteer)\n      supervisor = volunteer.supervisor_volunteer.supervisor\n      flash[:notice] = \"#{volunteer.display_name} was unassigned from #{supervisor.display_name}.\"\n    else\n      flash[:alert] = \"Something went wrong. Please try again.\"\n    end\n\n    redirect_to request.referer\n  end\n\n  def bulk_assignment\n    authorize :supervisor_volunteer\n\n    volunteers = policy_scope(current_organization.volunteers).where(id: params[:supervisor_volunteer][:volunteer_ids])\n    supervisor = policy_scope(current_organization.supervisors).where(id: params[:supervisor_volunteer][:supervisor_id]).first\n    if bulk_change_supervisor(supervisor, volunteers)\n      flash[:notice] = \"#{\"Volunteer\".pluralize(volunteers.count)} successfully assigned to new supervisor.\"\n    else\n      flash[:alert] = \"Something went wrong. The #{\"volunteer\".pluralize(volunteers.count)} could not be assigned.\"\n    end\n\n    redirect_to volunteers_path\n  end\n\n  private\n\n  def supervisor_volunteer_params\n    params.require(:supervisor_volunteer).permit(:supervisor_id, :volunteer_id, volunteer_ids: [])\n  end\n\n  def set_supervisor\n    Supervisor.find(params[:supervisor_id] || supervisor_volunteer_params[:supervisor_id])\n  end\n\n  def bulk_change_supervisor(supervisor, volunteers)\n    if supervisor\n      volunteers.each do |volunteer|\n        assign_volunteer_to_supervisor(volunteer, supervisor)\n      end\n    else\n      volunteers.each do |volunteer|\n        unassign_volunteers_supervisor(volunteer)\n      end\n    end\n  end\n\n  def assign_volunteer_to_supervisor(volunteer, supervisor)\n    unassign_volunteers_supervisor(volunteer)\n    supervisor_volunteer = supervisor.supervisor_volunteers.find_or_create_by!(volunteer: volunteer)\n    supervisor_volunteer.update!(is_active: true)\n  end\n\n  def unassign_volunteers_supervisor(volunteer)\n    volunteer.supervisor_volunteer&.update(is_active: false)\n  end\nend\n"
  },
  {
    "path": "app/controllers/supervisors_controller.rb",
    "content": "# frozen_string_literal: true\n\nclass SupervisorsController < ApplicationController\n  include SmsBodyHelper\n\n  before_action :available_volunteers, only: [:edit, :update, :index]\n  before_action :set_supervisor, only: [:edit, :update, :activate, :deactivate, :resend_invitation, :change_to_admin]\n  before_action :all_volunteers_ever_assigned, only: [:update]\n  before_action :supervisor_has_unassigned_volunteers, only: [:edit]\n\n  after_action :verify_authorized\n\n  def index\n    authorize Supervisor\n    @supervisors = policy_scope(current_organization.supervisors)\n    @casa_cases = current_organization.casa_cases.missing_court_dates\n  end\n\n  def new\n    authorize Supervisor\n    @supervisor = Supervisor.new\n  end\n\n  def create\n    authorize Supervisor\n    @supervisor = Supervisor.new(supervisor_params.merge(supervisor_values))\n\n    if @supervisor.save\n      @supervisor.invite!(current_user)\n      # call short io api here\n      raw_token = @supervisor.raw_invitation_token\n      invitation_url = Rails.application.routes.url_helpers.accept_user_invitation_url(invitation_token: raw_token, host: request.base_url)\n      hash_of_short_urls = @supervisor.phone_number.blank? ? {0 => nil, 1 => nil} : handle_short_url([invitation_url, request.base_url + \"/users/edit\"])\n      body_msg = account_activation_msg(\"supervisor\", hash_of_short_urls)\n      sms_status = deliver_sms_to @supervisor, body_msg\n      redirect_to edit_supervisor_path(@supervisor), notice: sms_acct_creation_notice(\"supervisor\", sms_status)\n    else\n      render new_supervisor_path, status: :unprocessable_content\n    end\n  end\n\n  def edit\n    authorize @supervisor\n    if params[:include_unassigned] == \"true\"\n      all_volunteers_ever_assigned\n    end\n    @unassigned_volunteer_count ||= 0\n  end\n\n  def update\n    authorize @supervisor\n    if @supervisor.update(update_supervisor_params)\n      notice = check_unconfirmed_email_notice(@supervisor)\n\n      @supervisor.filter_old_emails!(@supervisor.email)\n      redirect_to edit_supervisor_path(@supervisor), notice: notice\n    else\n      render :edit, status: :unprocessable_content\n    end\n  end\n\n  def activate\n    authorize @supervisor\n    if @supervisor.activate\n      SupervisorMailer.account_setup(@supervisor).deliver\n\n      redirect_to edit_supervisor_path(@supervisor), notice: \"Supervisor was activated. They have been sent an email.\"\n    else\n      render :edit, notice: \"Supervisor could not be activated.\"\n    end\n  end\n\n  def deactivate\n    authorize @supervisor\n    if @supervisor.deactivate\n      redirect_to edit_supervisor_path(@supervisor), notice: \"Supervisor was deactivated.\"\n    else\n      render :edit, notice: \"Supervisor could not be deactivated.\"\n    end\n  end\n\n  def resend_invitation\n    authorize @supervisor\n    @supervisor.invite!\n\n    redirect_to edit_supervisor_path(@supervisor), notice: \"Invitation sent\"\n  end\n\n  def change_to_admin\n    authorize @supervisor\n    @supervisor.change_to_admin!\n\n    redirect_to edit_casa_admin_path(@supervisor), notice: \"Supervisor was changed to Admin.\"\n  end\n\n  def datatable\n    authorize Supervisor\n    supervisors = policy_scope(current_organization.supervisors)\n    datatable = SupervisorDatatable.new supervisors, params\n\n    render json: datatable\n  end\n\n  private\n\n  def set_supervisor\n    @supervisor = Supervisor.find(params[:id])\n  end\n\n  def all_volunteers_ever_assigned\n    @unassigned_volunteer_count = @supervisor.volunteers_ever_assigned.count - @supervisor.volunteers.count\n    @all_volunteers_ever_assigned = @supervisor.volunteers_ever_assigned\n  end\n\n  def supervisor_has_unassigned_volunteers\n    @supervisor_has_unassigned_volunteers = @supervisor.volunteers_ever_assigned.count > @supervisor.volunteers.count\n  end\n\n  def available_volunteers\n    @available_volunteers = Volunteer.with_no_supervisor(current_user.casa_org)\n  end\n\n  def supervisor_values\n    {password: SecureRandom.hex(10), casa_org_id: current_user.casa_org_id}\n  end\n\n  def supervisor_params\n    params.require(:supervisor)\n      .permit(\n        :display_name,\n        :email,\n        :old_emails,\n        :phone_number,\n        :active,\n        :monthly_learning_hours_report,\n        :receive_reimbursement_email,\n        :monthly_learning_hours_report,\n        volunteer_ids: [],\n        supervisor_volunteer_ids: []\n      )\n  end\n\n  def update_supervisor_params\n    return SupervisorParameters.new(params).without_type if current_user.casa_admin?\n\n    SupervisorParameters.new(params).without_type.without_active\n  end\nend\n"
  },
  {
    "path": "app/controllers/users/invitations_controller.rb",
    "content": "class Users::InvitationsController < Devise::InvitationsController\n  # Override the edit action to ensure the invitation_token is properly set in the form\n  def edit\n    self.resource = resource_class.new\n    set_minimum_password_length if respond_to?(:set_minimum_password_length, true)\n    resource.invitation_token = params[:invitation_token]\n    render :edit\n  end\nend\n"
  },
  {
    "path": "app/controllers/users/passwords_controller.rb",
    "content": "class Users::PasswordsController < Devise::PasswordsController\n  include ApplicationHelper\n  include PhoneNumberHelper\n  include SmsBodyHelper\n\n  def create\n    @email = params.dig(resource_name, :email)\n    @phone_number = params.dig(resource_name, :phone_number)\n    @resource = @email.blank? ? User.find_by(phone_number: @phone_number) : User.find_by(email: @email)\n\n    unless valid_params?(@email, @phone_number)\n      render_error\n      return if @errors\n    end\n\n    send_password\n    redirect_to after_sending_reset_password_instructions_path_for(resource_name),\n      notice: \"If the account exists you will receive an email or SMS with instructions on how to reset your password in a few minutes.\"\n  end\n\n  def update\n    self.resource = resource_class.reset_password_by_token(resource_params)\n\n    yield resource if block_given?\n\n    if resource.errors.empty?\n      flash[:notice] = \"Your password has been changed successfully.\"\n      redirect_to new_session_path(resource_name)\n    else\n      respond_with resource\n    end\n  end\n\n  private\n\n  def render_error\n    respond_with(@resource) # re-render and display any errors\n    @errors = true\n  end\n\n  def send_password\n    return if @resource.nil?\n\n    send_password_reset_mail if email?\n    send_password_reset_sms if phone_number?\n  end\n\n  def send_password_reset_mail\n    @reset_token = @resource.send_reset_password_instructions # generate a reset token and call devise mailer\n  end\n\n  def send_password_reset_sms\n    # for case where user enters ONLY a phone number, generate a new reset token to use;\n    # otherwise, use the same reset token as sent by devise mailer\n    @reset_token ||= @resource.generate_password_reset_token\n\n    create_short_url\n    twilio_service = TwilioService.new(@resource.casa_org)\n    sms_params = {\n      From: @resource.casa_org.twilio_phone_number,\n      Body: password_reset_msg(@resource.display_name, @short_io_service.short_url),\n      To: @phone_number\n    }\n    twilio_service.send_sms(sms_params)\n  end\n\n  def valid_params?(email, phone_number)\n    return empty_fields_error if params_not_present(email, phone_number)\n\n    valid_phone_number, error_message = valid_phone_number(phone_number)\n    return invalid_phone_number_error(error_message) unless valid_phone_number\n\n    true\n  end\n\n  def email?\n    !@email.blank?\n  end\n\n  def phone_number?\n    !@phone_number.blank?\n  end\n\n  def empty_fields_error\n    @resource ||= resource\n    @resource.errors.add(:base, \"Please enter at least one field.\")\n\n    false\n  end\n\n  def invalid_phone_number_error(error_message)\n    @resource.errors.add(:phone_number, error_message)\n\n    false\n  end\n\n  def params_not_present(email, phone_number)\n    email.blank? && phone_number.blank?\n  end\n\n  def user_exists\n    !@resource.nil?\n  end\n\n  def no_user_found_error\n    resource.errors.add(:base, \"User does not exist.\")\n\n    false\n  end\n\n  def create_short_url\n    @short_io_service = ShortUrlService.new\n    @short_io_service.create_short_url(request.base_url + \"/users/password/edit?reset_password_token=#{@reset_token}\")\n  end\nend\n"
  },
  {
    "path": "app/controllers/users/sessions_controller.rb",
    "content": "# frozen_string_literal: true\n\nclass Users::SessionsController < Devise::SessionsController\n  include Accessible\n  skip_before_action :check_user, only: :destroy\nend\n"
  },
  {
    "path": "app/controllers/users_controller.rb",
    "content": "class UsersController < ApplicationController\n  before_action :get_user\n  before_action :authorize_user_with_policy\n  before_action :set_active_casa_admins\n  before_action :set_language, only: %i[add_language remove_language]\n  after_action :verify_authorized\n  before_action :set_custom_error_heading, only: [:update_password]\n  after_action :reset_custom_error_heading, only: [:update_password]\n\n  def edit\n    set_initial_address\n  end\n\n  def update\n    if @user.update(user_params)\n      flash[:success] = \"Profile was successfully updated.\"\n      redirect_to edit_users_path\n    else\n      render :edit, status: :unprocessable_content\n    end\n  end\n\n  def add_language\n    if @language.nil?\n      @user.errors.add(:language_id, \"can not be blank. Please select a language before adding.\")\n      return render \"edit\", status: :unprocessable_content\n    end\n\n    if current_user.languages.include?(@language)\n      @user.errors.add(:language_id, \"#{@language.name} is already in your languages list.\")\n      return render \"edit\", status: :unprocessable_content\n    end\n\n    current_user.languages << @language\n    if current_user.save\n      redirect_to edit_users_path, notice: \"#{@language.name} was added to your languages list.\"\n    else\n      redirect_to edit_users_path, alert: \"Error unable to add #{@language.name} to your languages list!\"\n    end\n  end\n\n  def remove_language\n    set_language\n    raise ActiveRecord::RecordNotFound unless @language\n\n    current_user.languages.delete @language\n    if current_user.save\n      redirect_to edit_users_path, notice: \"#{@language.name} was removed from your languages list.\"\n    else\n      redirect_to edit_users_path, alert: \"Unable to remove language.\"\n    end\n  end\n\n  def update_password\n    unless valid_user_password\n      @user.errors.add(:base, \"Current password is incorrect\")\n      return render \"edit\", status: :unprocessable_content\n    end\n\n    unless update_user_password\n      return render \"edit\", status: :unprocessable_content\n    end\n\n    bypass_sign_in(@user) if @user == true_user\n\n    UserMailer.password_changed_reminder(@user).deliver\n    flash[:success] = \"Password was successfully updated.\"\n\n    redirect_to edit_users_path\n  end\n\n  def update_email\n    unless valid_user_password\n      @user.errors.add(:base, \"Current password is incorrect\")\n      return render \"edit\", status: :unprocessable_content\n    end\n\n    unless update_user_email\n      return render \"edit\", status: :unprocessable_content\n    end\n\n    bypass_sign_in(@user) if @user == true_user\n\n    flash[:success] = \"Click the link in your new email to finalize the email transfer\"\n    redirect_to edit_users_path\n  end\n\n  private\n\n  def set_language\n    @language = Language.find_by(id: params[:id] || params[:language_id])\n  end\n\n  def set_initial_address\n    Address.create(user_id: current_user.id, content: \"\") if !current_user.address\n  end\n\n  def set_active_casa_admins\n    @active_casa_admins = CasaAdmin.in_organization(current_organization).active\n  end\n\n  def authorize_user_with_policy\n    authorize @user, policy_class: UserPolicy\n  end\n\n  def get_user\n    @user = current_user\n  end\n\n  def password_params\n    params.require(:user).permit(:current_password, :password, :password_confirmation)\n  end\n\n  def update_user_password\n    @user.update({password: password_params[:password], password_confirmation: password_params[:password_confirmation]})\n  end\n\n  def email_params\n    params.require(:user).permit(:current_password, :email, :unconfirmed_email)\n  end\n\n  def update_user_email\n    @user.update({email: email_params[:email]&.strip})\n    @user.filter_old_emails!(@user.email)\n  end\n\n  def user_params\n    if !current_user.casa_admin?\n      params.require(:user).permit(:display_name, :phone_number, :date_of_birth, :receive_sms_notifications, :receive_email_notifications, sms_notification_event_ids: [], address_attributes: [:id, :content])\n    else\n      params.require(:user).permit(:email, :display_name, :phone_number, :date_of_birth, :receive_sms_notifications, :receive_email_notifications, sms_notification_event_ids: [], address_attributes: [:id, :content])\n    end\n  end\n\n  def valid_user_password\n    if password_params\n      @user.valid_password?(password_params[:current_password])\n    elsif email_params\n      @user.valid_password?(email_params[:current_password])\n    end\n  end\n\n  def set_custom_error_heading\n    @custom_error_header = \"password change\"\n  end\n\n  def reset_custom_error_heading\n    @custom_error_header = nil\n  end\nend\n"
  },
  {
    "path": "app/controllers/volunteers_controller.rb",
    "content": "class VolunteersController < ApplicationController\n  include SmsBodyHelper\n\n  before_action :set_volunteer, except: %i[index new create datatable stop_impersonating]\n  after_action :verify_authorized, except: %i[stop_impersonating]\n\n  def index\n    authorize Volunteer\n    @supervisors = policy_scope(current_organization.supervisors)\n  end\n\n  def show\n    authorize @volunteer\n    redirect_to action: :edit\n  end\n\n  def datatable\n    authorize Volunteer\n    volunteers = policy_scope current_organization.volunteers\n    datatable = VolunteerDatatable.new volunteers, params\n\n    render json: datatable\n  end\n\n  def new\n    @volunteer = current_organization.volunteers.new\n    authorize @volunteer\n  end\n\n  def create\n    @volunteer = current_organization.volunteers.new(create_volunteer_params)\n    authorize @volunteer\n\n    if @volunteer.save\n      # invitation error handling\n      begin\n        @volunteer.invite!(current_user)\n      rescue => e\n        flash[:alert] = \"Volunteer invitation failed. Reason: #{e.message}\"\n      end\n\n      # call short io api here\n      invitation_url = Rails.application.routes.url_helpers.accept_user_invitation_url(invitation_token: @volunteer.raw_invitation_token, host: request.base_url)\n\n      hash_of_short_urls = {0 => nil, 1 => nil}\n      if @volunteer.phone_number.present?\n        hash_of_short_urls = handle_short_url([invitation_url, request.base_url + \"/users/edit\"])\n      end\n\n      sms_status = deliver_sms_to @volunteer, account_activation_msg(\"volunteer\", hash_of_short_urls)\n      redirect_to edit_volunteer_path(@volunteer), notice: sms_acct_creation_notice(\"volunteer\", sms_status)\n    else\n      render :new, status: :unprocessable_content\n    end\n  end\n\n  def edit\n    authorize @volunteer\n    @supervisors = policy_scope current_organization.supervisors.active\n  end\n\n  def update\n    authorize @volunteer\n    if @volunteer.update(update_volunteer_params)\n      notice = check_unconfirmed_email_notice(@volunteer)\n\n      @volunteer.filter_old_emails!(@volunteer.email)\n      redirect_to edit_volunteer_path(@volunteer), notice: notice\n    else\n      render :edit, status: :unprocessable_content\n    end\n  end\n\n  def activate\n    authorize @volunteer\n    if @volunteer.activate\n      VolunteerMailer.account_setup(@volunteer).deliver\n\n      if (params[:redirect_to_path] == \"casa_case\") && (casa_case = CasaCase.friendly.find(params[:casa_case_id]))\n        redirect_to edit_casa_case_path(casa_case), notice: \"Volunteer was activated. They have been sent an email.\"\n      else\n        redirect_to edit_volunteer_path(@volunteer), notice: \"Volunteer was activated. They have been sent an email.\"\n      end\n    else\n      render :edit, status: :unprocessable_content\n    end\n  end\n\n  def deactivate\n    authorize @volunteer\n    if @volunteer.deactivate\n      redirect_to edit_volunteer_path(@volunteer), notice: \"Volunteer was deactivated.\"\n    else\n      render :edit, status: :unprocessable_content\n    end\n  end\n\n  def resend_invitation\n    authorize @volunteer\n    @volunteer = Volunteer.find(params[:id])\n    if @volunteer.invitation_accepted_at.nil?\n      @volunteer.invite!(current_user)\n      redirect_to edit_volunteer_path(@volunteer), notice: \"Invitation sent\"\n    else\n      redirect_to edit_volunteer_path(@volunteer), notice: \"User already accepted invitation\"\n    end\n  end\n\n  def send_reactivation_alert\n    authorize @volunteer\n    if @volunteer.save\n      begin\n        send_sms_to(volunteers_phone_number, \"Hello #{@volunteer.display_name}, \\n \\n Your CASA/Prince George’s County volunteer console account has been reactivated. You can login using the credentials you were already using. \\n \\n If you have any questions, please contact your most recent Case Supervisor for assistance. \\n \\n CASA/Prince George’s County\")\n        redirect_to edit_volunteer_path(@volunteer), notice: \"Volunteer reactivation alert sent\"\n      rescue\n        redirect_to edit_volunteer_path(@volunteer), notice: \"Volunteer reactivation alert not sent. Twilio is disabled for #{@volunteer.casa_org.name}.\"\n      end\n    end\n  end\n\n  def reminder\n    authorize @volunteer\n    with_cc = params[:with_cc].present?\n\n    cc_recipients = []\n    if with_cc\n      if current_user.casa_admin?\n        cc_recipients.append(current_user.email)\n      end\n      cc_recipients.append(@volunteer.supervisor.email) if @volunteer.supervisor\n    end\n    VolunteerMailer.case_contacts_reminder(@volunteer, cc_recipients).deliver\n\n    redirect_to edit_volunteer_path(@volunteer), notice: \"Reminder sent to volunteer.\"\n  end\n\n  def impersonate\n    authorize @volunteer\n    impersonate_user(@volunteer)\n    redirect_to root_path\n  end\n\n  def stop_impersonating\n    stop_impersonating_user\n    redirect_to root_path\n  end\n\n  private\n\n  def set_volunteer\n    @volunteer = Volunteer.find(params[:id])\n  end\n\n  def generate_devise_password\n    Devise.friendly_token.first(8)\n  end\n\n  def create_volunteer_params\n    VolunteerParameters\n      .new(params)\n      .with_password(generate_devise_password)\n      .without_active\n  end\n\n  def update_volunteer_params\n    VolunteerParameters\n      .new(params)\n      .without_active\n  end\n\n  def volunteers_phone_number\n    authorize @volunteer\n    @volunteers_phone_number = @volunteer.phone_number\n  end\n\n  def send_sms_to(phone_number, body)\n    twilio = TwilioService.new(current_user.casa_org)\n    req_params = {From: current_user.casa_org.twilio_phone_number, Body: body, To: phone_number}\n    twilio_res = twilio.send_sms(req_params)\n\n    # Error handling for spec test purposes\n    if twilio_res.error_code.nil?\n      \"SMS has been sent to Volunteer!\"\n    else\n      \"SMS was not sent to Volunteer due to an error.\"\n    end\n  end\nend\n"
  },
  {
    "path": "app/datatables/application_datatable.rb",
    "content": "class ApplicationDatatable\n  prepend ActiveSupport::ToJsonWithActiveSupportEncoder\n\n  attr_reader :base_relation, :params\n\n  DEFAULT_PER_PAGE = 10\n\n  def initialize(base_relation, params)\n    @base_relation = base_relation\n    @params = params\n  end\n\n  def as_json(*)\n    {\n      data: sanitize(data),\n      recordsFiltered: filtered_records.size,\n      recordsTotal: base_relation.size\n    }\n  end\n\n  private\n\n  def sanitize(data)\n    if data.is_a? Array\n      data.map { |datum| sanitize datum }\n    elsif data.is_a? Hash\n      data.transform_values! { |value| sanitize value }\n    else\n      ERB::Util.html_escape data\n    end\n  end\n\n  def filtered_records\n    raw_records\n  end\n\n  def paginated_records\n    filtered_records\n      .offset(offset)\n      .limit(limit)\n  end\n  alias_method :records, :paginated_records\n\n  def additional_filters\n    @additional_filters ||= params[:additional_filters] || {}\n  end\n\n  def search_term\n    @search_term ||= params[:search][:value]\n  end\n\n  def build_order_clause\n    sanitize_sql \"#{order_by} #{order_direction} NULLS LAST\" if order_by.present?\n  end\n\n  def order_by\n    @order_by ||=\n      lambda {\n        order_by = params[:columns][order_column_index][:name]\n        order_by if self.class::ORDERABLE_FIELDS.include?(order_by.try(:downcase))\n      }.call\n  end\n\n  def order_column_index\n    order_params[:column]\n  end\n\n  def order_params\n    @order_params ||= params[:order][\"0\"]\n  end\n\n  def order_direction\n    order_direction = order_params[:dir] || \"ASC\"\n    %w[asc desc].include?(order_direction.downcase) ? order_direction : \"ASC\"\n  end\n\n  def limit\n    (params[:length] || DEFAULT_PER_PAGE).to_i\n  end\n\n  def offset\n    params[:start].to_i\n  end\n\n  def bool_filter(filter)\n    # expects filter to be an array\n\n    if filter.blank?\n      \"FALSE\"\n    elsif filter.length > 1\n      \"TRUE\"\n    else\n      yield\n    end\n  end\n\n  def sanitize_sql(sql)\n    ActiveRecord::Base.sanitize_sql(sql)\n  end\nend\n"
  },
  {
    "path": "app/datatables/case_contact_datatable.rb",
    "content": "# frozen_string_literal: true\n\nclass CaseContactDatatable < ApplicationDatatable\n  ORDERABLE_FIELDS = %w[\n    occurred_at\n    contact_made\n    medium_type\n    duration_minutes\n  ].freeze\n\n  def initialize(base_relation, params, current_user)\n    super(base_relation, params)\n    @current_user = current_user\n  end\n\n  private\n\n  attr_reader :current_user\n\n  def data\n    records.map do |case_contact|\n      policy = CaseContactPolicy.new(current_user, case_contact)\n      requested_followup = case_contact.followups.find(&:requested?)\n\n      {\n        id: case_contact.id,\n        occurred_at: I18n.l(case_contact.occurred_at, format: :full, default: nil),\n        casa_case: {\n          id: case_contact.casa_case_id,\n          case_number: case_contact.casa_case&.case_number\n        },\n        contact_types: case_contact.contact_types.map(&:name).join(\", \"),\n        medium_type: case_contact.medium_type&.titleize,\n        creator: {\n          id: case_contact.creator_id,\n          display_name: case_contact.creator&.display_name,\n          email: case_contact.creator&.email,\n          role: case_contact.creator&.role\n        },\n        contact_made: case_contact.contact_made,\n        duration_minutes: case_contact.duration_minutes,\n        contact_topics: case_contact.contact_topics.map(&:question),\n        contact_topic_answers: case_contact.contact_topic_answers\n          .reject { |a| a.value.blank? }\n          .map { |a| {question: a.contact_topic&.question, value: a.value} },\n        notes: case_contact.notes.presence,\n        is_draft: !case_contact.active?,\n        has_followup: requested_followup.present?,\n        can_edit: policy.update?,\n        can_destroy: policy.destroy?,\n        edit_path: Rails.application.routes.url_helpers.edit_case_contact_path(case_contact),\n        followup_id: requested_followup&.id\n      }\n    end\n  end\n\n  def filtered_records\n    raw_records.where(search_filter)\n  end\n\n  def raw_records\n    base_relation\n      .joins(\"INNER JOIN users creators ON creators.id = case_contacts.creator_id\")\n      .left_joins(:casa_case)\n      .includes(:casa_case, :contact_types, :contact_topics, :followups, :creator, contact_topic_answers: :contact_topic)\n      .preload(:casa_org, :creator_casa_org)\n      .order(order_clause)\n      .order(:id)\n  end\n\n  def search_filter\n    return \"TRUE\" if search_term.blank?\n\n    ilike_fields = %w[\n      creators.display_name\n      creators.email\n      casa_cases.case_number\n      case_contacts.notes\n    ]\n\n    ilike_clauses = ilike_fields.map { |field| \"#{field} ILIKE ?\" }.join(\" OR \")\n    contact_type_clause = \"case_contacts.id IN (#{contact_type_search_subquery})\"\n\n    full_clause = \"#{ilike_clauses} OR #{contact_type_clause}\"\n    [full_clause, ilike_fields.count.times.map { \"%#{search_term}%\" }].flatten\n  end\n\n  def contact_type_search_subquery\n    @contact_type_search_subquery ||= lambda {\n      return \"SELECT NULL WHERE FALSE\" if search_term.blank?\n\n      CaseContact\n        .select(\"DISTINCT case_contacts.id\")\n        .joins(case_contact_contact_types: :contact_type)\n        .where(\"contact_types.name ILIKE ?\", \"%#{search_term}%\")\n        .to_sql\n    }.call\n  end\n\n  def order_clause\n    @order_clause ||= build_order_clause\n  end\nend\n"
  },
  {
    "path": "app/datatables/reimbursement_datatable.rb",
    "content": "class ReimbursementDatatable < ApplicationDatatable\n  ORDERABLE_FIELDS = %w[\n    display_name\n    case_number\n    occurred_at\n    miles_driven\n  ].freeze\n\n  private\n\n  def data\n    records.map do |case_contact|\n      {\n        casa_case: {\n          id: case_contact.casa_case.id,\n          case_number: case_contact.casa_case.case_number\n        },\n        complete: case_contact.reimbursement_complete,\n        contact_types: case_contact_types(case_contact),\n        id: case_contact.id,\n        mark_as_complete_path: mark_as_complete_path(case_contact),\n        miles_driven: case_contact.miles_driven,\n        occurred_at: case_contact.occurred_at,\n        volunteer: {\n          address: case_contact.creator.address&.content,\n          display_name: case_contact.creator.display_name,\n          email: case_contact.creator.email,\n          id: case_contact.creator.id\n        }\n      }\n    end\n  end\n\n  def case_contact_types(case_contact)\n    case_contact.contact_types.map do |contact_type|\n      {\n        name: contact_type.name,\n        group_name: contact_type.contact_type_group.name\n      }\n    end\n  end\n\n  def mark_as_complete_path(case_contact)\n    \"/reimbursements/#{case_contact.id}/mark_as_complete\"\n  end\n\n  def raw_records\n    base_relation\n      .order(order_clause)\n      .select(\n        <<-SQL\n          case_contacts.*,\n          users.display_name AS volunteer\n        SQL\n      )\n      .joins(:creator)\n  end\n\n  def order_clause\n    @order_clause ||= build_order_clause\n  end\nend\n"
  },
  {
    "path": "app/datatables/supervisor_datatable.rb",
    "content": "class SupervisorDatatable < ApplicationDatatable\n  ORDERABLE_FIELDS = %w[\n    active\n    display_name\n    email\n  ]\n\n  private\n\n  def data\n    records.map do |supervisor|\n      {\n        id: supervisor.id,\n        active: supervisor.active?,\n        display_name: supervisor.display_name,\n        email: supervisor.email,\n        volunteer_assignments: supervisor.volunteers.count,\n        transitions_volunteers: supervisor.volunteers_serving_transition_aged_youth,\n        no_attempt_for_two_weeks: supervisor.no_attempt_for_two_weeks\n      }\n    end\n  end\n\n  def raw_records\n    base_relation.order(order_clause, :id)\n  end\n\n  def filtered_records\n    raw_records.where(active_filter)\n  end\n\n  def active_filter\n    @active_filter ||=\n      lambda do\n        filter = additional_filters[:active]\n\n        bool_filter filter do\n          [\"users.active = ?\", filter[0]]\n        end\n      end.call\n  end\n\n  def order_clause\n    @order_clause ||=\n      build_order_clause || Arel.sql(\"COALESCE(users.display_name, users.email) #{order_direction}\")\n  end\nend\n"
  },
  {
    "path": "app/datatables/volunteer_datatable.rb",
    "content": "class VolunteerDatatable < ApplicationDatatable\n  ORDERABLE_FIELDS = %w[\n    active\n    contacts_made_in_past_days\n    display_name\n    email\n    has_transition_aged_youth_cases\n    most_recent_attempt_occurred_at\n    supervisor_name\n    hours_spent_in_days\n  ]\n\n  private\n\n  def data\n    records.map do |volunteer|\n      {\n        active: volunteer.active?,\n        casa_cases: volunteer.casa_cases.map { |cc| {id: cc.id, case_number: cc.case_number} },\n        contacts_made_in_past_days: volunteer.contacts_made_in_past_days,\n        display_name: volunteer.display_name,\n        email: volunteer.email,\n        has_transition_aged_youth_cases: volunteer.has_transition_aged_youth_cases?,\n        id: volunteer.id,\n        made_contact_with_all_cases_in_days: volunteer.made_contact_with_all_cases_in_days?,\n        most_recent_attempt: {\n          case_id: volunteer.most_recent_attempt_case_id,\n          occurred_at: I18n.l(volunteer.most_recent_attempt_occurred_at, format: :full, default: nil)\n        },\n        supervisor: {id: volunteer.supervisor_id, name: volunteer.supervisor_name},\n        hours_spent_in_days: volunteer.hours_spent_in_days(30),\n        extra_languages: volunteer.languages&.map { |lang| {id: lang.id, name: lang.name} }\n      }\n    end\n  end\n\n  def filtered_records\n    extra_languages_filter do\n      raw_records\n        .where(supervisor_filter)\n        .where(active_filter)\n        .where(transition_aged_youth_filter)\n        .where(search_filter)\n    end\n  end\n\n  def raw_records\n    base_relation\n      .select(\n        <<-SQL\n          users.*,\n          COALESCE(users.display_name, users.email) AS default_sort_order,\n          COALESCE(supervisors.display_name, supervisors.email) AS supervisor_name,\n          supervisors.id AS supervisor_id,\n          transition_aged_youth_cases.volunteer_id IS NOT NULL AS has_transition_aged_youth_cases,\n          most_recent_attempts.casa_case_id AS most_recent_attempt_case_id,\n          most_recent_attempts.occurred_at AS most_recent_attempt_occurred_at,\n          contacts_made_in_past_days.contact_count AS contacts_made_in_past_days,\n          hours_spent_in_days.duration_minutes AS hours_spent_in_days\n        SQL\n      )\n      .joins(\n        <<-SQL\n          LEFT JOIN supervisor_volunteers ON supervisor_volunteers.volunteer_id = users.id AND supervisor_volunteers.is_active\n          LEFT JOIN users supervisors ON supervisors.id = supervisor_volunteers.supervisor_id AND supervisors.active\n          LEFT JOIN (\n            #{sanitize_sql(transition_aged_youth_cases_subquery)}\n          ) transition_aged_youth_cases ON transition_aged_youth_cases.volunteer_id = users.id\n          LEFT JOIN (\n            #{sanitize_sql(most_recent_attempts_subquery)}\n          ) most_recent_attempts ON most_recent_attempts.creator_id = users.id AND most_recent_attempts.contact_index = 1\n          LEFT JOIN (\n            #{sanitize_sql(contacts_made_in_past_days_subquery)}\n          ) contacts_made_in_past_days ON contacts_made_in_past_days.creator_id = users.id\n          LEFT JOIN (\n            #{sanitize_sql(hours_spent_in_days_subquery)}\n          ) hours_spent_in_days ON hours_spent_in_days.creator_id = users.id\n        SQL\n      )\n      .order(order_clause)\n      .order(:id)\n      .includes(:casa_cases)\n  end\n\n  def transition_aged_youth_cases_subquery\n    @transition_aged_youth_cases_subquery ||=\n      CaseAssignment\n        .select(:volunteer_id)\n        .joins(:casa_case)\n        .where(casa_cases: {birth_month_year_youth: ..CasaCase::TRANSITION_AGE.years.ago})\n        .active\n        .group(:volunteer_id)\n        .to_sql\n  end\n\n  def most_recent_attempts_subquery\n    @most_recent_attempts_subquery ||=\n      CaseContact\n        .select(\n          <<-SQL\n          *,\n          ROW_NUMBER() OVER(PARTITION BY creator_id ORDER BY occurred_at DESC NULLS LAST) AS contact_index\n          SQL\n        )\n        .to_sql\n  end\n\n  def contacts_made_in_past_days_subquery\n    @contacts_made_in_past_days_subquery ||=\n      CaseContact\n        .select(\n          <<-SQL\n          creator_id,\n          COUNT(*) AS contact_count\n          SQL\n        )\n        .where(contact_made: true, occurred_at: Volunteer::CONTACT_MADE_IN_PAST_DAYS_NUM.days.ago.to_date..)\n        .group(:creator_id)\n        .to_sql\n  end\n\n  def hours_spent_in_days_subquery\n    @hours_spent_in_days_subquery ||=\n      CaseContact\n        .select(\n          <<-SQL\n          creator_id,\n          SUM(duration_minutes) AS duration_minutes\n          SQL\n        )\n        .where(contact_made: true, occurred_at: 60.days.ago.to_date..)\n        .group(:creator_id)\n        .to_sql\n  end\n\n  def order_clause\n    @order_clause ||= build_order_clause || Arel.sql(\"default_sort_order ASC\")\n  end\n\n  def supervisor_filter\n    @supervisor_filter ||=\n      if (filter = additional_filters[:supervisor]).blank?\n        \"FALSE\"\n      elsif filter.all?(&:blank?)\n        \"supervisors.id IS NULL\"\n      else\n        null_filter = \"supervisors.id IS NULL OR\" if filter.any?(&:blank?)\n        [\"#{null_filter} COALESCE(supervisors.id) IN (?)\", filter.select(&:present?)]\n      end\n  end\n\n  def active_filter\n    @active_filter ||=\n      lambda {\n        filter = additional_filters[:active]\n\n        bool_filter filter do\n          [\"users.active = ?\", filter[0]]\n        end\n      }.call\n  end\n\n  def transition_aged_youth_filter\n    @transition_aged_youth_filter ||=\n      lambda {\n        filter = additional_filters[:transition_aged_youth]\n\n        bool_filter filter do\n          \"transition_aged_youth_cases.volunteer_id IS #{(filter[0] == \"true\") ? \"NOT\" : nil} NULL\"\n        end\n      }.call\n  end\n\n  def extra_languages_filter\n    filter = additional_filters[:extra_languages]\n    return yield unless filter\n\n    if filter.count > 1\n      yield.includes(:languages).distinct\n    elsif filter[0] == \"true\"\n      yield.joins(:languages).distinct\n    elsif filter[0] == \"false\"\n      yield.includes(:languages).excluding(base_relation.joins(:languages)).distinct\n    end\n  end\n\n  def search_filter\n    @search_filter ||=\n      lambda {\n        return \"TRUE\" if search_term.blank?\n\n        ilike_fields = %w[\n          users.display_name\n          users.email\n          supervisors.display_name\n          supervisors.email\n        ]\n        ilike_clauses = ilike_fields.map { |field| \"#{field} ILIKE ?\" }.join(\" OR \")\n        casa_case_number_clause = \"users.id IN (#{casa_case_number_filter_subquery})\"\n        full_clause = \"#{ilike_clauses} OR #{casa_case_number_clause}\"\n\n        [full_clause, ilike_fields.count.times.map { \"%#{search_term}%\" }].flatten\n      }.call\n  end\n\n  def casa_case_number_filter_subquery\n    @casa_case_number_filter_subquery ||=\n      lambda {\n        return \"\" if search_term.blank?\n\n        CaseAssignment\n          .select(:volunteer_id)\n          .joins(:casa_case)\n          .where(\"casa_cases.case_number ILIKE ? AND case_assignments.active = true\", \"%#{search_term}%\")\n          .group(:volunteer_id)\n          .to_sql\n      }.call\n  end\nend\n"
  },
  {
    "path": "app/decorators/android_app_association_decorator.rb",
    "content": "class AndroidAppAssociationDecorator < ApplicationDecorator\n  delegate_all\n\n  # Define presentation-specific methods here. Helpers are accessed through\n  # `helpers` (aka `h`). You can override attributes, for example:\n  #\n  #   def created_at\n  #     helpers.content_tag :span, class: 'time' do\n  #       object.created_at.strftime(\"%a %m/%d/%y\")\n  #     end\n  #   end\nend\n"
  },
  {
    "path": "app/decorators/application_decorator.rb",
    "content": "class ApplicationDecorator < Draper::Decorator; end\n"
  },
  {
    "path": "app/decorators/casa_case_decorator.rb",
    "content": "class CasaCaseDecorator < Draper::Decorator\n  include ActionView::Helpers::DateHelper\n\n  delegate_all\n\n  def case_contacts_ordered_by_occurred_at\n    object.case_contacts.order(occurred_at: :desc)\n  end\n\n  def case_contacts_latest\n    object.case_contacts.order(occurred_at: :desc, created_at: :desc).first\n  end\n\n  def case_contacts_latest_before(date)\n    object.case_contacts.where(\"occurred_at < ?\", date).max_by(&:occurred_at)\n  end\n\n  def case_contacts_filtered_by_active_assignment_ordered_by_occurred_at\n    object.case_contacts\n      .joins(\"INNER JOIN case_assignments on case_assignments.casa_case_id = case_contacts.casa_case_id and case_assignments.volunteer_id = case_contacts.creator_id\")\n      .where(\"case_assignments.hide_old_contacts = false\")\n      .order(occurred_at: :desc)\n  end\n\n  def court_report_submission\n    object.court_report_status.humanize\n  end\n\n  def court_report_submitted_date\n    I18n.l(object.court_report_submitted_at, format: :full, default: nil)\n  end\n\n  def court_report_select_option\n    volunteer_names = object.assigned_volunteers.map(&:display_name).join(\",\")\n\n    [\n      \"#{object.case_number} - #{object.in_transition_age? ? \"transition\" : \"non-transition\"}(assigned to #{(volunteer_names.length > 0) ? volunteer_names : \"no one\"})\",\n      object.case_number,\n      {\n        \"data-transitioned\": object.in_transition_age?,\n        \"data-lookup\": volunteer_names\n      }\n    ]\n  end\n\n  def date_in_care\n    return nil unless object.date_in_care\n    I18n.l(object.date_in_care, format: :youth_date_of_birth)\n  end\n\n  def duration_in_care\n    return nil unless object.date_in_care\n    \"(#{time_ago_in_words(object.date_in_care)} ago)\"\n  end\n\n  def calendar_next_court_date\n    return nil unless object.next_court_date\n    {start: calendar_format(object.next_court_date.date), end: calendar_format(object.next_court_date.date + 1.day)}\n  end\n\n  def calendar_court_report_due_date\n    return nil unless object.court_report_due_date\n    {start: calendar_format(object.court_report_due_date), end: calendar_format(object.court_report_due_date + 1.day)}\n  end\n\n  def calendar_format(date)\n    I18n.l(date, format: :long, default: \"\")\n  end\n\n  def formatted_updated_at\n    I18n.l(object.updated_at, format: :standard, default: nil)\n  end\n\n  def inactive_class\n    (!object.active) ? \"table-secondary\" : \"\"\n  end\n\n  def status\n    object.active ? \"Active\" : \"Inactive\"\n  end\n\n  def successful_contacts_this_week\n    this_week = Date.today - 7.days..Date.today\n    object.case_contacts.where(occurred_at: this_week).where(contact_made: true).count\n  end\n\n  def successful_contacts_this_week_before(date)\n    this_week_before_date = Date.today - 7.days..date\n    object.case_contacts.where(occurred_at: this_week_before_date).where(contact_made: true).count\n  end\n\n  def transition_aged_youth\n    text = object.in_transition_age? ? \"Yes #{CasaCase::TRANSITION_AGE_YOUTH_ICON}\" : \"No #{CasaCase::NON_TRANSITION_AGE_YOUTH_ICON}\"\n    if object.in_transition_age?\n      badge_html = h.render(partial: \"shared/emancipation_link\", locals: {casa_case: object})\n      h.safe_join([text, \" \", badge_html])\n    else\n      text\n    end\n  end\n\n  def transition_aged_youth_icon\n    object.in_transition_age? ? CasaCase::TRANSITION_AGE_YOUTH_ICON : CasaCase::NON_TRANSITION_AGE_YOUTH_ICON\n  end\n\n  def unsuccessful_contacts_this_week\n    this_week = Date.today - 7.days..Date.today\n    object.case_contacts.where(occurred_at: this_week).where(contact_made: false).count\n  end\n\n  def unsuccessful_contacts_this_week_before(date)\n    this_week_before_date = Date.today - 7.days..date\n    object.case_contacts.where(occurred_at: this_week_before_date).where(contact_made: false).count\n  end\n\n  def emancipation_checklist_count\n    \"#{object.casa_case_emancipation_categories.count} / #{EmancipationCategory.count}\"\n  end\n\n  def show_contact_type?(contact_type_id)\n    object.casa_case_contact_types.map(&:contact_type_id).include?(contact_type_id)\n  end\n\n  def hash_for_multi_select\n    volunteers = object.volunteers.map(&:display_name).join(\", \")\n\n    {value: object.id, text: object.case_number, group: object&.casa_org_id, subtext: volunteers}\n  end\nend\n"
  },
  {
    "path": "app/decorators/case_assignment_decorator.rb",
    "content": "class CaseAssignmentDecorator < Draper::Decorator\n  delegate_all\n\n  def unassigned_in_past_week?\n    this_week = Date.today - 7.days..Date.today\n    object.active == false && this_week.cover?(object.updated_at)\n  end\nend\n"
  },
  {
    "path": "app/decorators/case_contact_decorator.rb",
    "content": "class CaseContactDecorator < Draper::Decorator\n  delegate_all\n\n  NOTES_CHARACTER_LIMIT = 100\n\n  def duration_minutes\n    minutes = object.duration_minutes\n\n    if !minutes\n      \"Duration not set\"\n    elsif minutes <= 60\n      \"#{minutes} minutes\"\n    else\n      formatted_hours_and_minutes(minutes)\n    end\n  end\n\n  def report_duration_minutes\n    object.duration_minutes\n  end\n\n  def miles_traveled\n    object.miles_driven.zero? ? nil : \"#{object.miles_driven} miles driven\"\n  end\n\n  def reimbursement\n    object.want_driving_reimbursement ? \"Reimbursement\" : nil\n  end\n\n  def contact_made\n    object.contact_made ? nil : \"No Contact Made\"\n  end\n\n  def report_contact_made\n    object.contact_made\n  end\n\n  def subheading\n    [\n      I18n.l(object.occurred_at, format: :full, default: nil),\n      duration_minutes,\n      contact_made,\n      miles_traveled,\n      reimbursement\n    ].compact.join(\" | \")\n  end\n\n  def paragraph_notes\n    if object.notes && object.notes.length > CaseContactDecorator::NOTES_CHARACTER_LIMIT\n      helpers.content_tag(:p, limited_notes)\n    else\n      helpers.simple_format(full_notes)\n    end\n  end\n\n  def contact_types\n    if object.contact_types.any?\n      object.contact_types&.map { |ct| ct.name }&.to_sentence(last_word_connector: \", and \")\n    else\n      \"No contact type specified\"\n    end\n  end\n\n  def contact_types_comma_separated\n    if object.contact_types.any?\n      object.contact_types&.map { |ct| ct.name }&.join(\", \")\n    else\n      \"No contact type specified\"\n    end\n  end\n\n  def report_contact_types\n    object.contact_types&.map { |ct| ct.name }&.join(\"|\")\n  end\n\n  def medium_icon_classes\n    case object.medium_type\n    when CaseContact::IN_PERSON\n      \"lni lni-users\"\n    when CaseContact::TEXT_EMAIL\n      \"lni lni-envelope\"\n    when CaseContact::VIDEO\n      \"lni lni-camera\"\n    when CaseContact::VOICE_ONLY\n      \"lni lni-phone\"\n    when CaseContact::LETTER\n      \"lni lni-empty-file\"\n    else\n      \"lni lni-question-circle\"\n    end\n  end\n\n  def contact_groups\n    groups = contact_groups_with_types.keys\n    if groups.count > 0\n      groups.join(\", \")\n    else\n      \"Not Selected\"\n    end\n  end\n\n  def limited_notes\n    object.notes.truncate(NOTES_CHARACTER_LIMIT)\n  end\n\n  def full_notes\n    object.notes\n  end\n\n  def show_contact_type?(contact_type_id)\n    object.case_contact_contact_type.map(&:contact_type_id).include?(contact_type_id)\n  end\n\n  def additional_expenses_count\n    object.additional_expenses.any? ? object.additional_expenses.length : 0\n  end\n\n  def address_of_volunteer\n    if volunteer_address&.present?\n      volunteer_address\n    elsif volunteer\n      volunteer.address&.content\n    end\n  end\n\n  def ambiguous_volunteer_address_message\n    \"There are two or more volunteers assigned to this case and you are trying to set the address for both of them. This is not currently possible.\"\n  end\n\n  def form_title\n    active? ? \"Editing Existing Case Contact\" : \"Record New Case Contact\"\n  end\n\n  def form_page_notes\n    {\n      details: nil,\n      notes: \"This question will be included in the court report for your assigned foster youth. Your response here will appear on the generated report for this case. To download the report, head to 'Group Actions'.\",\n      expenses: nil\n    }\n  end\n\n  def form_updated_message\n    \"Case contact created at #{I18n.l(created_at, format: :time_on_date)}, was successfully updated.\"\n  end\n\n  private\n\n  def formatted_hours_and_minutes(minutes)\n    formatted_hour_value = minutes / 60\n    formatted_minutes_value = minutes.remainder(60)\n\n    if formatted_minutes_value.zero?\n      \"#{formatted_hour_value} #{\"hour\".pluralize(formatted_hour_value)}\"\n    else\n      \"#{formatted_hour_value} #{\"hour\".pluralize(formatted_hour_value)} #{formatted_minutes_value} minutes\"\n    end\n  end\nend\n"
  },
  {
    "path": "app/decorators/case_contacts/form_decorator.rb",
    "content": "class CaseContacts::FormDecorator < ApplicationDecorator\n  delegate_all\n\n  # Define presentation-specific methods here. Helpers are accessed through\n  # `helpers` (aka `h`). You can override attributes, for example:\n  #\n  #   def created_at\n  #     helpers.content_tag :span, class: 'time' do\n  #       object.created_at.strftime(\"%a %m/%d/%y\")\n  #     end\n  #   end\nend\n"
  },
  {
    "path": "app/decorators/contact_type_decorator.rb",
    "content": "class ContactTypeDecorator < Draper::Decorator\n  include ActionView::Helpers::DateHelper\n  delegate_all\n\n  def hash_for_multi_select_with_cases(casa_case_ids)\n    if casa_case_ids.nil?\n      casa_case_ids = []\n    end\n\n    {value: object.id, text: object.name, group: object.contact_type_group.name, subtext: last_time_used_with_cases(casa_case_ids)}\n  end\n\n  def last_time_used_with_cases(casa_case_ids)\n    last_contact = CaseContact.joins(:contact_types).where(casa_case_id: casa_case_ids, contact_types: {id: object.id}).order(occurred_at: :desc).first\n\n    last_contact&.occurred_at.blank? ? \"never\" : \"#{time_ago_in_words(last_contact.occurred_at)} ago\"\n  end\nend\n"
  },
  {
    "path": "app/decorators/court_date_decorator.rb",
    "content": "class CourtDateDecorator < Draper::Decorator\n  delegate_all\n\n  def formatted_date\n    I18n.l(object.date, format: :full, default: nil)\n  end\n\n  def court_date_info\n    [formatted_date, hearing_type&.name].compact.join(\" - \")\n  end\nend\n"
  },
  {
    "path": "app/decorators/learning_hour_decorator.rb",
    "content": "class LearningHourDecorator < ApplicationDecorator\n  delegate_all\n\n  # Define presentation-specific methods here. Helpers are accessed through\n  # `helpers` (aka `h`). You can override attributes, for example:\n  #\n  #   def created_at\n  #     helpers.content_tag :span, class: 'time' do\n  #       object.created_at.strftime(\"%a %m/%d/%y\")\n  #     end\n  #   end\nend\n"
  },
  {
    "path": "app/decorators/learning_hour_topic_decorator.rb",
    "content": "class LearningHourTopicDecorator < ApplicationDecorator\n  delegate_all\n\n  # Define presentation-specific methods here. Helpers are accessed through\n  # `helpers` (aka `h`). You can override attributes, for example:\n  #\n  #   def created_at\n  #     helpers.content_tag :span, class: 'time' do\n  #       object.created_at.strftime(\"%a %m/%d/%y\")\n  #     end\n  #   end\nend\n"
  },
  {
    "path": "app/decorators/other_duty_decorator.rb",
    "content": "class OtherDutyDecorator < Draper::Decorator\n  delegate_all\n\n  NOTES_WORD_LIMIT = 10\n\n  def duration_in_minutes\n    return \"#{object.duration_minutes} minutes\" if object.duration_minutes <= 60\n\n    formatted_hour_value = object.duration_minutes / 60\n    formatted_minutes_value = object.duration_minutes.remainder(60)\n\n    if formatted_minutes_value.zero?\n      \"#{formatted_hour_value} #{\"hour\".pluralize(formatted_hour_value)}\"\n    else\n      \"#{formatted_hour_value} #{\"hour\".pluralize(formatted_hour_value)} #{formatted_minutes_value} minutes\"\n    end\n  end\n\n  def truncate_notes\n    if object.notes && object.notes.split.size > OtherDutyDecorator::NOTES_WORD_LIMIT\n      helpers.content_tag(:p, limited_notes)\n    else\n      helpers.simple_format(full_notes)\n    end\n  end\n\n  private\n\n  def limited_notes\n    object.notes.truncate_words(NOTES_WORD_LIMIT)\n  end\n\n  def full_notes\n    object.notes\n  end\nend\n"
  },
  {
    "path": "app/decorators/patch_note_decorator.rb",
    "content": "class PatchNoteDecorator < ApplicationDecorator\n  delegate_all\n\n  # Define presentation-specific methods here. Helpers are accessed through\n  # `helpers` (aka `h`). You can override attributes, for example:\n  #\n  #   def created_at\n  #     helpers.content_tag :span, class: 'time' do\n  #       object.created_at.strftime(\"%a %m/%d/%y\")\n  #     end\n  #   end\nend\n"
  },
  {
    "path": "app/decorators/placement_decorator.rb",
    "content": "class PlacementDecorator < Draper::Decorator\n  delegate_all\n\n  def formatted_date\n    I18n.l(object.placement_started_at, format: :full, default: nil)\n  end\n\n  def placement_info\n    [\"Started At:  #{formatted_date}\", \"Placement Type: #{placement_type&.name}\"].compact.join(\" - \")\n  end\n\n  def placement_started_at\n    I18n.l(placement.placement_started_at, format: :full, default: nil)\n  end\n\n  def created_at\n    I18n.l(placement.created_at, format: :full, default: nil)\n  end\nend\n"
  },
  {
    "path": "app/decorators/user_decorator.rb",
    "content": "class UserDecorator < Draper::Decorator\n  delegate_all\n\n  def status\n    object.active ? \"Active\" : \"Inactive\"\n  end\n\n  def local_time_zone\n    h.browser_time_zone\n  end\n\n  # helper method to 'DRY' up the other methods : )\n  def formatted_timestamp(attribute)\n    format_key = context[:format] || :full\n    timestamp = object.public_send(attribute)\n\n    if format_key == :edit_profile\n      I18n.l(timestamp&.in_time_zone(local_time_zone), format: format_key, default: nil)\n    else\n      I18n.l(timestamp, format: format_key, default: nil)\n    end\n  end\n\n  def formatted_created_at\n    formatted_timestamp(:created_at)\n  end\n\n  def formatted_updated_at\n    formatted_timestamp(:updated_at)\n  end\n\n  def formatted_current_sign_in_at\n    formatted_timestamp(:current_sign_in_at)\n  end\n\n  def formatted_invitation_accepted_at\n    formatted_timestamp(:invitation_accepted_at)\n  end\n\n  def formatted_reset_password_sent_at\n    formatted_timestamp(:reset_password_sent_at)\n  end\n\n  def formatted_invitation_sent_at\n    formatted_timestamp(:invitation_sent_at)\n  end\n\n  def formatted_birthday\n    return \"\" unless object.date_of_birth.respond_to?(:strftime)\n\n    object.date_of_birth.to_date.to_fs(:short_ordinal)\n  end\n\n  def formatted_date_of_birth\n    return \"\" unless object.date_of_birth.respond_to?(:strftime)\n\n    object.date_of_birth.to_date.to_fs(:slashes)\n  end\nend\n"
  },
  {
    "path": "app/decorators/volunteer_decorator.rb",
    "content": "class VolunteerDecorator < UserDecorator\n  include Draper::LazyHelpers\n\n  def cc_reminder_text\n    if h.current_user.supervisor?\n      \"Send CC to Supervisor\"\n    elsif h.current_user.casa_admin?\n      \"Send CC to Supervisor and Admin\"\n    end\n  end\nend\n"
  },
  {
    "path": "app/helpers/all_casa_admins/casa_orgs_helper.rb",
    "content": "module AllCasaAdmins\n  module CasaOrgsHelper\n    def selected_organization\n      # this is in the context of the all casa admin\n      # without this, the current_organization gets set to the current admin's org\n      @casa_org\n    end\n  end\nend\n"
  },
  {
    "path": "app/helpers/api_base_helper.rb",
    "content": "module ApiBaseHelper\n  SHORT_IO = \"https://api.short.io/\"\nend\n"
  },
  {
    "path": "app/helpers/application_helper.rb",
    "content": "module ApplicationHelper\n  include Pagy::Frontend\n\n  def body_class\n    qualified_controller_name = controller.controller_path.tr(\"/\", \"-\")\n    \"#{qualified_controller_name} #{qualified_controller_name}-#{controller.action_name}\"\n  end\n\n  def logged_in?\n    user_signed_in? || all_casa_admin_signed_in?\n  end\n\n  def not_logged_in?\n    !logged_in?\n  end\n\n  def page_header\n    return default_page_header unless user_signed_in?\n\n    current_organization.display_name\n  end\n\n  def default_page_header\n    \"CASA / Volunteer Tracking\"\n  end\n\n  def session_link\n    if user_signed_in?\n      link_to(\"Log out\", destroy_user_session_path, class: \"list-group-item\")\n    elsif all_casa_admin_signed_in?\n      link_to(\"Log out\", destroy_all_casa_admin_session_path, class: \"list-group-item\")\n    else\n      link_to(\"Log in\", new_user_session_path, class: \"list-group-item\")\n    end\n  end\n\n  def flash_class(level)\n    case level\n    when \"notice\" then \"alert notice alert-info\"\n    when \"success\" then \"alert success alert-success\"\n    when \"error\" then \"alert error alert-danger\"\n    when \"alert\" then \"alert alert-warning\"\n    end\n  end\n\n  def og_tag(type, options = {})\n    tag.meta(property: \"og:#{type}\", **options)\n  end\n\n  def resource_name\n    :user\n  end\n\n  def resource\n    @resource ||= User.new\n  end\n\n  def devise_mapping\n    @devise_mapping ||= Devise.mappings[:user]\n  end\nend\n"
  },
  {
    "path": "app/helpers/banner_helper.rb",
    "content": "module BannerHelper\n  def conditionally_add_hidden_class(current_banner_is_active)\n    unless current_banner_is_active && current_organization.has_alternate_active_banner?(@banner.id)\n      \"d-none\"\n    end\n  end\n\n  def banner_expiration_time_in_words(banner)\n    if banner.expired?\n      \"Expired\"\n    elsif banner.expires_at\n      \"in #{distance_of_time_in_words(Time.now, banner.expires_at)}\"\n    else\n      \"No Expiration\"\n    end\n  end\nend\n"
  },
  {
    "path": "app/helpers/case_contacts_helper.rb",
    "content": "# Helper methods for new case contact form\nmodule CaseContactsHelper\n  def duration_hours(case_contact)\n    case_contact.duration_minutes.to_i.div(60)\n  end\n\n  def duration_minutes(case_contact)\n    case_contact.duration_minutes.to_i.remainder(60)\n  end\n\n  def contact_mediums\n    CaseContact::CONTACT_MEDIUMS.map { |contact_medium|\n      OpenStruct.new(value: contact_medium, label: contact_medium.titleize)\n    }\n  end\n\n  def render_back_link(casa_case)\n    return send_home if !current_user || current_user&.volunteer?\n\n    send_to_case(casa_case)\n  end\n\n  def thank_you_message\n    [\n      \"Thanks for all you do!\",\n      \"Thank you for your hard work!\",\n      \"Thank you for a job well done!\",\n      \"Thank you for volunteering!\",\n      \"Thanks for being a great volunteer!\",\n      \"One of the greatest gifts you can give is your time!\",\n      \"Those who can do, do. Those who can do more, volunteer.\",\n      \"Volunteers do not necessarily have the time, they just have the heart.\"\n    ].sample\n  end\n\n  def show_volunteer_reimbursement(casa_cases)\n    if current_user.role == \"Volunteer\"\n      show = casa_cases.map do |casa_case|\n        casa_case.case_assignments.where(volunteer_id: current_user).first&.allow_reimbursement == true\n      end\n      show.any?\n    else\n      true\n    end\n  end\n\n  def expand_filters?(surfaced_keys = %i[no_drafts sorted_by])\n    params.fetch(:filterrific, {})\n      .except(*surfaced_keys)\n      .reject { |_, value| value == \"\" }\n      .present?\n  end\n\n  private\n\n  def send_home\n    root_path\n  end\n\n  def send_to_case(casa_case)\n    casa_case_path(casa_case)\n  end\nend\n"
  },
  {
    "path": "app/helpers/contact_types_helper.rb",
    "content": "# frozen_string_literal: true\n\n# Helper methods for new/edit contact type form\nmodule ContactTypesHelper\n  def set_group_options\n    @group_options = ContactTypeGroup.for_organization(current_organization).collect { |group| [group.name, group.id] }\n  end\nend\n"
  },
  {
    "path": "app/helpers/court_dates_helper.rb",
    "content": "# frozen_string_literal: true\n\n# Helper methods for court_dates\nmodule CourtDatesHelper\n  def when_do_we_have_court_dates(casa_case)\n    return if casa_case.blank?\n\n    court_dates = casa_case.court_dates.ordered_ascending\n    date_now = Date.current\n\n    if court_dates.blank?\n      \"none\"\n    elsif court_dates.last.date < date_now\n      \"past\"\n    elsif court_dates.first.date > date_now\n      \"future\"\n    end\n  end\nend\n"
  },
  {
    "path": "app/helpers/court_orders_helper.rb",
    "content": "module CourtOrdersHelper\n  def court_order_select_options\n    CaseCourtOrder.implementation_statuses.map do |status|\n      [status[0].humanize, status[0]]\n    end\n  end\nend\n"
  },
  {
    "path": "app/helpers/date_helper.rb",
    "content": "module DateHelper\n  JQUERY_MONTH_DAY_YEAR_FORMAT = \"MM d, yyyy\"\n  RUBY_MONTH_DAY_YEAR_FORMAT = \"%B %d, %Y\"\n\n  def validate_date(day, month, year)\n    raise Date::Error if day.blank? || month.blank? || year.blank?\n\n    Date.parse(\"#{day}-#{month}-#{year}\")\n  end\n\n  def parse_date(errors, date_field_name, args)\n    day = args.delete(\"#{date_field_name}(3i)\")\n    month = args.delete(\"#{date_field_name}(2i)\")\n    year = args.delete(\"#{date_field_name}(1i)\")\n\n    return args if day.blank? && month.blank? && year.blank?\n\n    args[date_field_name.to_sym] = validate_date(day, month, year)\n    args\n  rescue Date::Error\n    errors.add(date_field_name.to_sym, \"was not a valid date.\")\n    args\n  end\nend\n"
  },
  {
    "path": "app/helpers/emancipations_helper.rb",
    "content": "module EmancipationsHelper\n  def emancipation_category_checkbox_checked(casa_case, emancipation_category)\n    case_contains_category?(casa_case, emancipation_category) ? \"checked\" : nil\n  end\n\n  def emancipation_category_collapse_hidden(casa_case, emancipation_category)\n    case_contains_category?(casa_case, emancipation_category) ? nil : \"display: none;\"\n  end\n\n  def emancipation_category_collapse_icon(casa_case, emancipation_category)\n    case_contains_category?(casa_case, emancipation_category) ? \"−\" : \"+\"\n  end\n\n  def emancipation_option_checkbox_checked(casa_case, emancipation_option)\n    casa_case.emancipation_options.include?(emancipation_option) ? \"checked\" : nil\n  end\n\n  def emancipation_category_checkbox_checked_download(casa_case, emancipation_category)\n    case_contains_category?(casa_case, emancipation_category) ? \"🗹\" : \"☐\"\n  end\n\n  def emancipation_option_checkbox_checked_download(casa_case, emancipation_option)\n    casa_case.emancipation_options.include?(emancipation_option) ? \"🗹\" : \"☐\"\n  end\n\n  def emancipation_option_radio_checked_download(casa_case, emancipation_option)\n    casa_case.emancipation_options.include?(emancipation_option) ? \"⦿\" : \"◯\"\n  end\n\n  private\n\n  def case_contains_category?(casa_case, emancipation_category)\n    casa_case.emancipation_categories.include?(emancipation_category)\n  end\nend\n"
  },
  {
    "path": "app/helpers/followup_helper.rb",
    "content": "module FollowupHelper\n  def followup_icon(creator)\n    return \"fa-exclamation-circle text-warning\" if creator.volunteer?\n    \"fa-exclamation-triangle text-danger\"\n  end\nend\n"
  },
  {
    "path": "app/helpers/learning_hours_helper.rb",
    "content": "module LearningHoursHelper\n  def format_time(minutes)\n    hours = minutes / 60\n    remaining_minutes = minutes % 60\n    \"#{hours} hours #{remaining_minutes} minutes\"\n  end\nend\n"
  },
  {
    "path": "app/helpers/mileage_rates_helper.rb",
    "content": "module MileageRatesHelper\n  def effective_date_parser(date)\n    date = DateTime.current if date.blank?\n    date.strftime(::DateHelper::RUBY_MONTH_DAY_YEAR_FORMAT)\n  end\nend\n"
  },
  {
    "path": "app/helpers/notifications_helper.rb",
    "content": "module NotificationsHelper\n  def read_when_seen_notifications\n    %w[YouthBirthdayNotifier::Notification VolunteerBirthdayNotifier::Notification EmancipationChecklistReminderNotifier::Notification]\n  end\n\n  def notifications_after_and_including_deploy(notifications)\n    latest_deploy_time = Health.instance.latest_deploy_time\n\n    if latest_deploy_time.nil?\n      []\n    else\n      notifications.where(created_at: latest_deploy_time..)\n    end\n  end\n\n  def notifications_before_deploy(notifications)\n    notifications.where(created_at: ...Health.instance.latest_deploy_time)\n  end\n\n  def patch_notes_as_hash_keyed_by_type_name(patch_notes)\n    patch_notes_hash = {}\n\n    patch_notes.each do |patch_note|\n      patch_note_type_name = patch_note.patch_note_type.name\n\n      unless patch_notes_hash.has_key?(patch_note_type_name)\n        patch_notes_hash[patch_note_type_name] = []\n      end\n\n      patch_notes_hash[patch_note_type_name].push(patch_note.note)\n    end\n\n    patch_notes_hash\n  end\nend\n"
  },
  {
    "path": "app/helpers/other_duties_helper.rb",
    "content": "# Helper methods for new case contact form\nmodule OtherDutiesHelper\n  def duration_hours(duty)\n    duty.duration_minutes.to_i.div(60)\n  end\n\n  def duration_minutes(duty)\n    duty.duration_minutes.to_i.remainder(60)\n  end\nend\n"
  },
  {
    "path": "app/helpers/phone_number_helper.rb",
    "content": "module PhoneNumberHelper\n  VALID_PHONE_NUMBER_LENGTHS = [10, 11]\n  VALID_COUNTRY_CODE = \"+1\"\n  ERROR_MESSAGE = \"must be 10 digits or 12 digits including country code (+1)\"\n\n  def valid_phone_number(number)\n    if number.nil? || number.empty?\n      return true, nil\n    end\n\n    number = strip_unnecessary_characters(number)\n\n    if !VALID_PHONE_NUMBER_LENGTHS.include?(number.length)\n      return false, ERROR_MESSAGE\n    end\n\n    if number.length == 11\n      country_code = number[0..0]\n      phone_number = number[1..number.length]\n      valid_country_code = VALID_COUNTRY_CODE[1..1]\n\n      if country_code != valid_country_code\n        return false, ERROR_MESSAGE\n      end\n    else\n      phone_number = number\n    end\n\n    if !phone_number.scan(/\\D/).empty?\n      return false, ERROR_MESSAGE\n    end\n\n    [true, nil]\n  end\n\n  def strip_unnecessary_characters(number)\n    number.gsub(/[()\\+\\s\\-\\.]/, \"\")\n  end\nend\n"
  },
  {
    "path": "app/helpers/preference_sets_helper.rb",
    "content": "module PreferenceSetsHelper\nend\n"
  },
  {
    "path": "app/helpers/report_helper.rb",
    "content": "module ReportHelper\n  def boolean_choices\n    [[\"Both\", \"\"], [\"Yes\", true], [\"No\", false]]\n  end\nend\n"
  },
  {
    "path": "app/helpers/request_header_helper.rb",
    "content": "module RequestHeaderHelper\n  ACCEPT_JSON = {\"Accept\" => \"application/json\"}\n  CONTENT_TYPE_JSON = {\"Content-Type\" => \"application/json\"}\nend\n"
  },
  {
    "path": "app/helpers/sidebar_helper.rb",
    "content": "module SidebarHelper\n  def cases_index_title\n    return \"My Cases\" if current_user.volunteer?\n\n    \"Cases\"\n  end\n\n  def inbox_label\n    unread_count = current_user.notifications.unread.count\n    return \"Inbox\" if unread_count == 0\n    \"Inbox <span class='badge bg-danger'>#{unread_count}</span>\".html_safe\n  end\n\n  def menu_item(label:, path:, visible: false)\n    link_to label, path, class: \"list-group-item #{active_class(path)}\" if visible\n  end\n\n  private # private doesn't work in modules. It's here for semantic purposes\n\n  def active_class(link_path)\n    if request_path_active?(link_path)\n      \"active\"\n    else\n      \"\"\n    end\n  rescue ActionController::UrlGenerationError\n    \"\"\n  end\n\n  def request_path_active?(link_path)\n    # The second check is needed because Sidebar menu item 'Emancipation\n    # Checklist(s)' contains a redirect if any @casa_transitioning_cases are\n    # found\n    (request.path == link_path) ||\n      (link_path == \"/emancipation_checklists\" && request.path.match(\"emancipation\"))\n  end\nend\n"
  },
  {
    "path": "app/helpers/sms_body_helper.rb",
    "content": "module SmsBodyHelper\n  def account_activation_msg(resource = \"primorgens\", hash_of_links = {})\n    password_link = hash_of_links[0]\n    edit_link = hash_of_links[1]\n    first_msg = \"A CASA #{resource} account was created for you.\"\n    second_msg = \"First, set your password here #{hash_of_links[0]}.\"\n    third_msg = \"Then visit #{hash_of_links[1]} to change your text message settings.\"\n    # default msg\n    body_msg = first_msg + \" \" + \"Please check your email to set up your password. Go to profile edit page to change SMS settings.\"\n\n    if password_link && edit_link\n      body_msg = first_msg + \" \" + second_msg + \" \" + third_msg\n    elsif password_link.nil? && edit_link\n      body_msg = first_msg + \" \" + \"Please check your email to set up your password.\" + \" \" + third_msg\n    elsif hash_of_links[0] && hash_of_links[1].nil?\n      body_msg = first_msg + \" \" + second_msg + \" \" + \"Go to profile edit page to change SMS settings.\"\n    end\n    body_msg\n  end\n\n  def court_report_due_msg(report_due_date, short_link)\n    \"Your court report is due on #{report_due_date}. Generate a court report to complete & submit here: #{short_link}\"\n  end\n\n  def no_contact_made_msg(contact_type, short_link)\n    \"It's been two weeks since you've tried reaching '#{contact_type}'. Try again! #{short_link}\"\n  end\n\n  def case_contact_flagged_msg(display_name, short_link)\n    \"#{display_name} has flagged a Case Contact that needs follow up. Click to see more: #{short_link}\"\n  end\n\n  def password_reset_msg(display_name, short_link)\n    \"Hi #{display_name}, click here to reset your password: #{short_link}\"\n  end\nend\n"
  },
  {
    "path": "app/helpers/template_helper.rb",
    "content": "module TemplateHelper\n  def sanitize_opening_tags(opening_tags)\n    self_closing_tags = [\"<%>\", \"<%=>\", \"<area>\", \"<base>\", \"<br>\", \"<col>\", \"<command>\", \"<!DOCTYPE>\", \"<embed>\", \"<hr>\", \"<img>\", \"<input>\", \"<keygen>\", \"<link>\", \"<meta>\", \"<param>\", \"<source>\", \"<track>\", \"<wbr>\"]\n\n    opening_tags = opening_tags.map { |tag|\n      if tag.include? \" \"\n        tag.squish.partition(\" \").first << \">\"\n      else\n        tag\n      end\n    }\n\n    opening_tags - self_closing_tags\n  end\n\n  def sanitize_closing_tags(closing_tags)\n    closing_tags.map { |tag|\n      tag.squish.tr(\"\\\\/\", \"\")\n    }\n  end\n\n  def validate_closing_tags_exist(file_content)\n    opening_regex = /<[^\\/](?:\"[^\"]*\"['\"]*|'[^']*'['\"]*|[^'\">])*>/\n    closing_regex = /<\\/(?:\"[^\"]*\"['\"]*|'[^']*'['\"]*|[^'\">])*>/\n\n    opening_tags = file_content.scan(opening_regex)\n    closing_tags = file_content.scan(closing_regex)\n\n    opening_tags = sanitize_opening_tags(opening_tags)\n    closing_tags = sanitize_closing_tags(closing_tags)\n\n    opening_tags.sort == closing_tags.sort\n  end\n\n  def active_if(cond)\n    cond ? \"active\" : nil\n  end\n\n  def active_if_status(status)\n    (status == \"complete\") ? \"active\" : nil\n  end\nend\n"
  },
  {
    "path": "app/helpers/ui_helper.rb",
    "content": "module UiHelper\n  include VolunteerHelper\n\n  def grouped_options_for_assigning_case(volunteer)\n    [\n      [\n        \"Not Assigned\",\n        CasaCase.eager_load([:assigned_volunteers])\n          .not_assigned(@volunteer.casa_org).active\n          .uniq { |casa_case| casa_case.case_number }\n          .map { |casa_case| [\"#{casa_case.case_number} - #{volunteer_badge(casa_case, current_user)}\".html_safe, casa_case.id] }\n      ],\n      [\n        \"Assigned\",\n        CasaCase.eager_load([:assigned_volunteers])\n          .actively_assigned_excluding_volunteer(@volunteer)\n          .uniq { |casa_case| casa_case.case_number }\n          .map { |casa_case| [\"#{casa_case.case_number} - #{volunteer_badge(casa_case, current_user)}\".html_safe, casa_case.id] }\n      ]\n    ]\n  end\n\n  def contact_types_list(reimbursement)\n    reimbursement\n      .contact_groups_with_types\n      .map { |cg, types_arr| \"#{cg} (#{types_arr.join(\", \")})\" }\n      .join(\", \")\n  end\nend\n"
  },
  {
    "path": "app/helpers/volunteer_helper.rb",
    "content": "module VolunteerHelper\n  def volunteer_badge(casa_case, current_user)\n    return \"\" if current_user.volunteer?\n\n    badge_content = if casa_case.assigned_volunteers.present?\n      casa_case.assigned_volunteers.map(&:display_name).join(\", \")\n    else\n      \"Unassigned\"\n    end\n\n    content_tag(:span, badge_content, class: \"badge badge-pill light-bg text-black fs-6 fw-medium\")\n  end\nend\n"
  },
  {
    "path": "app/javascript/__mocks__/fileMock.js",
    "content": "module.exports = 'test-file-stub'\n"
  },
  {
    "path": "app/javascript/__mocks__/styleMock.js",
    "content": "module.exports = {}\n"
  },
  {
    "path": "app/javascript/__tests__/add_to_calendar_button.test.js",
    "content": "/* eslint-env jest */\n/**\n * @jest-environment jsdom\n */\n\nrequire('jest')\n\n// Mock the add-to-calendar-button module\njest.mock('add-to-calendar-button', () => ({}))\n\ndescribe('add_to_calendar_button', () => {\n  let mockJQuery\n\n  beforeEach(() => {\n    // Clear the document body\n    document.body.innerHTML = ''\n\n    // Mock jQuery\n    mockJQuery = jest.fn((callback) => {\n      if (typeof callback === 'function') {\n        callback()\n      }\n    })\n    global.$ = mockJQuery\n  })\n\n  afterEach(() => {\n    jest.resetModules()\n    delete global.$\n  })\n\n  const createCalendarButton = (dataset = {}) => {\n    const div = document.createElement('div')\n    div.className = 'cal-btn'\n    Object.assign(div.dataset, {\n      title: 'Court Hearing',\n      start: '2025-11-15',\n      end: '2025-11-15',\n      tooltip: 'Add to calendar',\n      ...dataset\n    })\n    return div\n  }\n\n  describe('createCalendarEvents', () => {\n    test('creates add-to-calendar-button elements for all cal-btn divs', () => {\n      // Setup\n      const calBtn1 = createCalendarButton()\n      const calBtn2 = createCalendarButton({\n        title: 'Court Date 2',\n        start: '2025-12-01',\n        end: '2025-12-01'\n      })\n      document.body.appendChild(calBtn1)\n      document.body.appendChild(calBtn2)\n\n      // Execute\n      require('../src/add_to_calendar_button')\n\n      // Verify\n      const buttons = document.querySelectorAll('add-to-calendar-button')\n      expect(buttons.length).toBe(2)\n    })\n\n    test('sets correct attributes on the calendar button', () => {\n      // Setup\n      const calBtn = createCalendarButton({\n        title: 'Important Meeting',\n        start: '2025-11-20',\n        end: '2025-11-20',\n        tooltip: 'Add this event'\n      })\n      document.body.appendChild(calBtn)\n\n      // Execute\n      require('../src/add_to_calendar_button')\n\n      // Verify\n      const button = document.querySelector('add-to-calendar-button')\n      expect(button.getAttribute('name')).toBe('Important Meeting')\n      expect(button.getAttribute('startDate')).toBe('2025-11-20')\n      expect(button.getAttribute('endDate')).toBe('2025-11-20')\n      expect(button.getAttribute('description')).toBe('Important Meeting')\n      expect(button.getAttribute('options')).toBe(\"'Apple','Google','iCal','Microsoft365','Outlook.com','Yahoo'\")\n      expect(button.getAttribute('timeZone')).toBe('currentBrowser')\n      expect(button.getAttribute('lightMode')).toBe('bodyScheme')\n      expect(button.title).toBe('Add this event')\n    })\n\n    test('clears existing content in cal-btn div', () => {\n      // Setup\n      const calBtn = createCalendarButton()\n      calBtn.innerHTML = '<p>Old content</p>'\n      document.body.appendChild(calBtn)\n\n      // Execute\n      require('../src/add_to_calendar_button')\n\n      // Verify\n      const oldContent = calBtn.querySelector('p')\n      expect(oldContent).toBeNull()\n      const button = calBtn.querySelector('add-to-calendar-button')\n      expect(button).not.toBeNull()\n    })\n\n    test('handles empty dataset gracefully', () => {\n      // Setup\n      const div = document.createElement('div')\n      div.className = 'cal-btn'\n      document.body.appendChild(div)\n\n      // Execute\n      require('../src/add_to_calendar_button')\n\n      // Verify\n      const button = document.querySelector('add-to-calendar-button')\n      expect(button).not.toBeNull()\n      expect(button.getAttribute('name')).toBe('undefined')\n      expect(button.getAttribute('startDate')).toBe('undefined')\n      expect(button.getAttribute('endDate')).toBe('undefined')\n    })\n\n    test('does nothing when no cal-btn elements exist', () => {\n      // Setup\n      document.body.innerHTML = '<div class=\"other-content\"></div>'\n\n      // Execute\n      require('../src/add_to_calendar_button')\n\n      // Verify\n      const buttons = document.querySelectorAll('add-to-calendar-button')\n      expect(buttons.length).toBe(0)\n    })\n\n    test('calls createCalendarEvents on DOM ready', () => {\n      // Setup\n      const calBtn = createCalendarButton()\n      document.body.appendChild(calBtn)\n\n      // Execute\n      require('../src/add_to_calendar_button')\n\n      // Verify jQuery was called\n      expect(mockJQuery).toHaveBeenCalled()\n      expect(mockJQuery).toHaveBeenCalledWith(expect.any(Function))\n\n      // Verify the calendar button was created\n      const button = document.querySelector('add-to-calendar-button')\n      expect(button).not.toBeNull()\n    })\n\n    test('preserves cal-btn class on parent div', () => {\n      // Setup\n      const calBtn = createCalendarButton()\n      document.body.appendChild(calBtn)\n\n      // Execute\n      require('../src/add_to_calendar_button')\n\n      // Verify\n      const divs = document.querySelectorAll('div.cal-btn')\n      expect(divs.length).toBe(1)\n      expect(divs[0].querySelector('add-to-calendar-button')).not.toBeNull()\n    })\n  })\n})\n"
  },
  {
    "path": "app/javascript/__tests__/casa_case.test.js",
    "content": "/* eslint-env jest */\n/**\n * @jest-environment jsdom\n */\n\nimport { showAlert } from '../src/casa_case'\n\ndescribe('showAlert', () => {\n  const defaultErrorMessageHtml = '<div>Error Message</div>'\n  const subject = ({\n    initialBody = '<div class=\"header-flash\"></div>',\n    errorMessageHtml = defaultErrorMessageHtml\n  } = {}) => {\n    document.body.innerHTML = initialBody\n    showAlert(errorMessageHtml)\n  }\n\n  test('must render error messages', () => {\n    subject()\n    expect(document.body.innerHTML).toEqual(defaultErrorMessageHtml)\n  })\n\n  describe('when there is no element with header-flash class', () => {\n    test('does not render messages', () => {\n      const initialBody = '<div></div>'\n      subject({ initialBody })\n      expect(document.body.innerHTML).toEqual(initialBody)\n    })\n  })\n})\n"
  },
  {
    "path": "app/javascript/__tests__/case_button_states.test.js",
    "content": "/* eslint-env jest */\n/**\n * @jest-environment jsdom\n */\n\nimport {\n  showBtn,\n  hideBtn,\n  disableBtn,\n  enableBtn\n} from '../src/casa_case'\n\nlet button\n\nbeforeEach(() => {\n  document.body.innerHTML =\n    '<button id=\"test-button\">Disable Reports</button>'\n  button = document.getElementById('test-button')\n})\n\ndescribe('casa_case generate report button applies correct classes and attributes', () => {\n  test('show button', () => {\n    button.classList.add('d-none')\n    showBtn(button)\n    expect(button.classList.contains('d-none')).toBe(false)\n  })\n\n  test('hide button', () => {\n    hideBtn(button)\n    expect(button.classList.contains('d-none')).toBe(true)\n  })\n\n  test('disable button', () => {\n    disableBtn(button)\n    expect(button.classList.contains('disabled')).toBe(true)\n    expect(button.hasAttribute('aria-disabled')).toBe(true)\n    expect(button.disabled).toBe(true)\n  })\n\n  test('enable button', () => {\n    button.disabled = true\n    button.classList.add('disabled')\n    button.setAttribute('aria-disabled', true)\n    enableBtn(button)\n    expect(button.classList.contains('disabled')).toBe(false)\n    expect(button.hasAttribute('aria-disabled')).toBe(false)\n    expect(button.disabled).toBe(false)\n  })\n})\n"
  },
  {
    "path": "app/javascript/__tests__/case_contact.test.js",
    "content": "/* eslint-env jest */\n/**\n * @jest-environment jsdom\n */\n\nimport { convertDateToSystemTimeZone } from '../src/case_contact'\n\ndescribe('convertDateToSystemTimeZone', () => {\n  test('converts a UTC date string to a Date object', () => {\n    const dateString = '2022-06-22 17:14:50 UTC'\n    const result = convertDateToSystemTimeZone(dateString)\n\n    expect(result).toBeInstanceOf(Date)\n    expect(result.getTime()).toBe(new Date(dateString).getTime())\n  })\n\n  test('converts a date string without timezone to a Date object', () => {\n    const dateString = '2022-06-22T12:00:00'\n    const result = convertDateToSystemTimeZone(dateString)\n\n    expect(result).toBeInstanceOf(Date)\n    expect(result.getFullYear()).toBe(2022)\n    expect(result.getMonth()).toBe(5) // June is month 5 (0-indexed)\n    expect(result.getDate()).toBe(22)\n  })\n\n  test('creates a copy of an existing Date object', () => {\n    const originalDate = new Date('2022-06-22 17:14:50 UTC')\n    const result = convertDateToSystemTimeZone(originalDate)\n\n    expect(result).toBeInstanceOf(Date)\n    expect(result.getTime()).toBe(originalDate.getTime())\n    expect(result).not.toBe(originalDate) // Should be a different object\n  })\n\n  test('handles ISO 8601 format', () => {\n    const dateString = '2022-06-22T17:14:50.000Z'\n    const result = convertDateToSystemTimeZone(dateString)\n\n    expect(result).toBeInstanceOf(Date)\n    expect(result.toISOString()).toBe(dateString)\n  })\n\n  test('returns Invalid Date for invalid date strings', () => {\n    const result = convertDateToSystemTimeZone('not a valid date')\n\n    expect(result).toBeInstanceOf(Date)\n    expect(isNaN(result.getTime())).toBe(true)\n  })\n})\n"
  },
  {
    "path": "app/javascript/__tests__/case_emancipations.test.js",
    "content": "/* eslint-env jest */\n/**\n * @jest-environment jsdom\n */\n\nimport { Toggler } from '../src/case_emancipation'\n\nlet category\nlet categoryOptions\nlet categoryOptionsControl\nlet categoryCollapseIcon\nlet checkBox\nlet toggler\n\nbeforeEach(() => {\n  document.body.innerHTML = `\n  <div class=\"card card-container\">\n    <div class=\"card-body\">\n        <div>\n          <h6 class=\"emancipation-category no-select\" id=\"category-under-test\" data-is-open='false'>\n            <input type=\"checkbox\" class=\"emancipation-category-check-box\" value=\"1\">\n            <label>Youth has housing.</label>\n              <span class=\"category-collapse-icon\" id=\"icon-under-test\">+</span>\n          </h6>\n          <div\n            class=\"category-options\"\n            id=\"category-options-under-test\"\n            style=\"display: none;\">\n              <div class=\"check-item\">\n                <input type=\"checkbox\" id=\"O1\" class=\"emancipation-option-check-box\" value=\"1\" checked>\n                <label>With Friend</label>\n              </div>\n          </div>\n          <h6 class=\"emancipation-category no-select\" id=\"category-control\" data-is-open='false'>\n            <input type=\"checkbox\" class=\"emancipation-category-check-box\" value=\"1\">\n            <label>Youth has housing.</label>\n              <span class=\"category-collapse-icon\">+</span>\n          </h6>\n          <div\n            class=\"category-options\"\n            id=\"category-options-control\"\n            style=\"display: none;\">\n              <div class=\"check-item\">\n                <input type=\"checkbox\" id=\"O2\" class=\"emancipation-option-check-box\" value=\"1\" checked>\n                <label>With Friend</label>\n              </div>\n          </div>\n        </div>\n    </div>\n  </div>\n  `\n\n  category = $('#category-under-test')\n  categoryOptions = $('#category-options-under-test')\n  categoryOptionsControl = $('#category-options-control')\n  categoryCollapseIcon = $('#icon-under-test')\n  checkBox = $('#O1')\n  toggler = new Toggler(category)\n})\n\ndescribe('Function that changes the text of the Toggler based on the state of the parent', () => {\n  test('Changes the toggler text to + when category is closed', () => {\n    category.attr('data-is-open', 'false')\n\n    toggler.manageTogglerText()\n    expect(categoryCollapseIcon.text()).toEqual('+')\n  })\n})\n\ndescribe('Function that opens the children of a given parent', () => {\n  test('Opens the categoryOptionsContainer', () => {\n    toggler.openChildren()\n    expect(category.data('is-open')).toEqual(true)\n    expect(categoryOptions.css('display')).toEqual('block')\n    expect(categoryOptionsControl.css('display')).toEqual('none')\n  })\n})\n\ndescribe('Function that closes the children of a given parent', () => {\n  test('Closes the categoryOptionsContainer', () => {\n    toggler.closeChildren()\n    expect(category.data('is-open')).toEqual(false)\n  })\n})\n\ndescribe('Function that deselects the children of a deselected parent', () => {\n  test('Deselects the inputs in the categoryOptionsContainer', () => {\n    toggler.deselectChildren(() => '')\n    expect(checkBox.prop('checked')).toEqual(false)\n  })\n})\n"
  },
  {
    "path": "app/javascript/__tests__/dashboard.test.js",
    "content": "/* eslint-env jest */\n/**\n * @jest-environment jsdom\n */\n\nimport Swal from 'sweetalert2'\nimport { defineCaseContactsTable } from '../src/dashboard'\nimport { fireSwalFollowupAlert } from '../src/case_contact'\njest.mock('sweetalert2', () => ({\n  __esModule: true,\n  default: { fire: jest.fn() }\n}))\n\njest.mock('../src/case_contact', () => ({\n  fireSwalFollowupAlert: jest.fn()\n}))\n\n// Mock DataTable\nconst mockDataTable = jest.fn()\n$.fn.DataTable = mockDataTable\n\ndescribe('defineCaseContactsTable', () => {\n  let tableElement\n\n  beforeEach(() => {\n    // Reset mocks\n    mockDataTable.mockClear()\n\n    // Set up DOM\n    document.body.innerHTML = `\n      <table id=\"case_contacts\" data-source=\"/case_contacts/new_design/datatable.json\">\n        <thead>\n          <tr>\n            <th></th>\n            <th></th>\n            <th>Date</th>\n            <th>Case</th>\n            <th>Relationship</th>\n            <th>Medium</th>\n            <th>Created By</th>\n            <th>Contacted</th>\n            <th>Topics</th>\n            <th>Draft</th>\n            <th></th>\n          </tr>\n        </thead>\n        <tbody></tbody>\n      </table>\n    `\n\n    tableElement = $('table#case_contacts')\n  })\n\n  describe('DataTable initialization', () => {\n    it('initializes DataTable on the case_contacts table', () => {\n      defineCaseContactsTable()\n\n      expect(mockDataTable).toHaveBeenCalledTimes(1)\n      expect(mockDataTable.mock.instances[0][0]).toBe(tableElement[0])\n    })\n\n    it('configures DataTable with server-side processing', () => {\n      defineCaseContactsTable()\n\n      const config = mockDataTable.mock.calls[0][0]\n\n      expect(config.serverSide).toBe(true)\n      expect(config.processing).toBe(true)\n      expect(config.searching).toBe(true)\n    })\n\n    it('configures scrollX for horizontal scrolling', () => {\n      defineCaseContactsTable()\n\n      const config = mockDataTable.mock.calls[0][0]\n\n      expect(config.scrollX).toBe(true)\n    })\n\n    it('sets default sort to Date column descending', () => {\n      defineCaseContactsTable()\n\n      const config = mockDataTable.mock.calls[0][0]\n\n      expect(config.order).toEqual([[2, 'desc']])\n    })\n\n    it('disables ordering on bell, chevron, and ellipsis columns', () => {\n      defineCaseContactsTable()\n\n      const config = mockDataTable.mock.calls[0][0]\n\n      expect(config.columnDefs).toEqual([\n        { orderable: false, targets: [0, 1, 10] }\n      ])\n    })\n  })\n\n  describe('AJAX configuration', () => {\n    it('uses the data-source URL from the table', () => {\n      defineCaseContactsTable()\n\n      const config = mockDataTable.mock.calls[0][0]\n\n      expect(config.ajax.url).toBe('/case_contacts/new_design/datatable.json')\n      expect(config.ajax.type).toBe('POST')\n      expect(config.ajax.dataType).toBe('json')\n    })\n\n    it('includes error handler for AJAX requests', () => {\n      const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation()\n\n      defineCaseContactsTable()\n\n      const config = mockDataTable.mock.calls[0][0]\n      const mockError = 'Network error'\n      const mockCode = 500\n\n      config.ajax.error({}, mockError, mockCode)\n\n      expect(consoleErrorSpy).toHaveBeenCalledWith('DataTable error:', mockError, mockCode)\n\n      consoleErrorSpy.mockRestore()\n    })\n  })\n\n  describe('column configurations', () => {\n    let columns\n\n    beforeEach(() => {\n      defineCaseContactsTable()\n      columns = mockDataTable.mock.calls[0][0].columns\n    })\n\n    it('configures 11 columns', () => {\n      expect(columns).toHaveLength(11)\n    })\n\n    describe('Bell icon column (index 0)', () => {\n      it('is not orderable or searchable', () => {\n        expect(columns[0].orderable).toBe(false)\n        expect(columns[0].searchable).toBe(false)\n      })\n\n      it('renders filled bell icon when has_followup is \"true\"', () => {\n        const rendered = columns[0].render('true', 'display', {})\n\n        expect(rendered).toBe('<i class=\"fas fa-bell\"></i>')\n      })\n\n      it('renders faded bell icon when has_followup is \"false\"', () => {\n        const rendered = columns[0].render('false', 'display', {})\n\n        expect(rendered).toBe('<i class=\"fas fa-bell\" style=\"opacity: 0.3;\"></i>')\n      })\n    })\n\n    describe('Chevron icon column (index 1)', () => {\n      it('is not orderable or searchable', () => {\n        expect(columns[1].orderable).toBe(false)\n        expect(columns[1].searchable).toBe(false)\n      })\n\n      it('renders chevron-down icon as an accessible button', () => {\n        const rendered = columns[1].render(null, 'display', {})\n\n        expect(rendered).toBe('<button type=\"button\" class=\"expand-toggle\" aria-expanded=\"false\" aria-label=\"Expand row details\"><i class=\"fa-solid fa-chevron-down\" aria-hidden=\"true\"></i></button>')\n      })\n    })\n\n    describe('Date column (index 2)', () => {\n      it('uses occurred_at data field', () => {\n        expect(columns[2].data).toBe('occurred_at')\n        expect(columns[2].name).toBe('occurred_at')\n      })\n\n      it('renders date string or empty string', () => {\n        expect(columns[2].render('January 15, 2024')).toBe('January 15, 2024')\n        expect(columns[2].render(null)).toBe('')\n        expect(columns[2].render('')).toBe('')\n      })\n    })\n\n    describe('Case column (index 3)', () => {\n      it('is not orderable', () => {\n        expect(columns[3].orderable).toBe(false)\n      })\n\n      it('renders link to casa_case when data exists', () => {\n        const data = { id: '123', case_number: 'CASA-2024-001' }\n        const rendered = columns[3].render(data, 'display', {})\n\n        expect(rendered).toBe('<a href=\"/casa_cases/123\">CASA-2024-001</a>')\n      })\n\n      it('renders empty string when casa_case is null', () => {\n        expect(columns[3].render(null, 'display', {})).toBe('')\n      })\n\n      it('renders empty string when casa_case has no id', () => {\n        const data = { id: null, case_number: 'CASA-2024-001' }\n\n        expect(columns[3].render(data, 'display', {})).toBe('')\n      })\n    })\n\n    describe('Relationship (Contact Types) column (index 4)', () => {\n      it('is not orderable', () => {\n        expect(columns[4].orderable).toBe(false)\n      })\n\n      it('renders contact types string', () => {\n        expect(columns[4].render('Family, School')).toBe('Family, School')\n        expect(columns[4].render(null)).toBe('')\n      })\n    })\n\n    describe('Medium column (index 5)', () => {\n      it('renders medium type', () => {\n        expect(columns[5].render('In-person')).toBe('In-person')\n        expect(columns[5].render('Text/Email')).toBe('Text/Email')\n        expect(columns[5].render(null)).toBe('')\n      })\n    })\n\n    describe('Created By column (index 6)', () => {\n      it('is not orderable', () => {\n        expect(columns[6].orderable).toBe(false)\n      })\n\n      it('renders empty string when creator is null', () => {\n        expect(columns[6].render(null, 'display', {})).toBe('')\n      })\n\n      it('renders link to volunteer edit page for volunteers', () => {\n        const data = {\n          id: '456',\n          display_name: 'John Doe',\n          role: 'Volunteer'\n        }\n        const rendered = columns[6].render(data, 'display', {})\n\n        expect(rendered).toBe('<a href=\"/volunteers/456/edit\" data-turbo=\"false\">John Doe</a>')\n      })\n\n      it('renders link to supervisor edit page for supervisors', () => {\n        const data = {\n          id: '789',\n          display_name: 'Jane Smith',\n          role: 'Supervisor'\n        }\n        const rendered = columns[6].render(data, 'display', {})\n\n        expect(rendered).toBe('<a href=\"/supervisors/789/edit\" data-turbo=\"false\">Jane Smith</a>')\n      })\n\n      it('renders link to users edit page for casa admins', () => {\n        const data = {\n          id: '999',\n          display_name: 'Admin User',\n          role: 'Casa Admin'\n        }\n        const rendered = columns[6].render(data, 'display', {})\n\n        expect(rendered).toBe('<a href=\"/users/edit\" data-turbo=\"false\">Admin User</a>')\n      })\n    })\n\n    describe('Contacted column (index 7)', () => {\n      it('is not orderable', () => {\n        expect(columns[7].orderable).toBe(false)\n      })\n\n      it('renders checkmark icon when contact was made', () => {\n        const row = { contact_made: 'true', duration_minutes: null }\n        const rendered = columns[7].render('true', 'display', row)\n\n        expect(rendered).toContain('<i class=\"lni lni-checkmark-circle\" style=\"color: green;\"></i>')\n      })\n\n      it('renders cross icon when contact was not made', () => {\n        const row = { contact_made: 'false', duration_minutes: null }\n        const rendered = columns[7].render('false', 'display', row)\n\n        expect(rendered).toContain('<i class=\"lni lni-cross-circle\" style=\"color: orange;\"></i>')\n      })\n\n      it('includes formatted duration when present', () => {\n        const row = { contact_made: 'true', duration_minutes: 90 }\n        const rendered = columns[7].render('true', 'display', row)\n\n        expect(rendered).toContain('(01:30)')\n      })\n\n      it('formats duration with leading zeros', () => {\n        const row = { contact_made: 'true', duration_minutes: 5 }\n        const rendered = columns[7].render('true', 'display', row)\n\n        expect(rendered).toContain('(00:05)')\n      })\n\n      it('handles hours and minutes correctly', () => {\n        const row = { contact_made: 'true', duration_minutes: 125 }\n        const rendered = columns[7].render('true', 'display', row)\n\n        expect(rendered).toContain('(02:05)')\n      })\n\n      it('does not include duration when not present', () => {\n        const row = { contact_made: 'true', duration_minutes: null }\n        const rendered = columns[7].render('true', 'display', row)\n\n        expect(rendered).not.toContain('(')\n      })\n    })\n\n    describe('Topics column (index 8)', () => {\n      it('is not orderable', () => {\n        expect(columns[8].orderable).toBe(false)\n      })\n\n      it('renders each topic as a pill badge', () => {\n        const rendered = columns[8].render(['Topic 1', 'Topic 2'])\n        expect(rendered).toContain('<span class=\"badge badge-pill light-bg text-black\">Topic 1</span>')\n        expect(rendered).toContain('<span class=\"badge badge-pill light-bg text-black\">Topic 2</span>')\n      })\n\n      it('renders empty string when there are no topics', () => {\n        expect(columns[8].render(null)).toBe('')\n        expect(columns[8].render([])).toBe('')\n      })\n\n      it('shows only the first two topics when there are more than two', () => {\n        const rendered = columns[8].render(['A', 'B', 'C', 'D'])\n        expect(rendered).toContain('>A<')\n        expect(rendered).toContain('>B<')\n        expect(rendered).not.toContain('>C<')\n        expect(rendered).not.toContain('>D<')\n      })\n\n      it('shows a +N More badge for overflow topics', () => {\n        const rendered = columns[8].render(['A', 'B', 'C', 'D'])\n        expect(rendered).toContain('+2 More')\n      })\n\n      it('does not show an overflow badge when there are two or fewer topics', () => {\n        expect(columns[8].render(['A', 'B'])).not.toContain('More')\n        expect(columns[8].render(['A'])).not.toContain('More')\n      })\n    })\n\n    describe('Draft column (index 9)', () => {\n      it('is not orderable', () => {\n        expect(columns[9].orderable).toBe(false)\n      })\n\n      it('renders Draft badge when is_draft is true', () => {\n        const rendered = columns[9].render(true, 'display', {})\n\n        expect(rendered).toBe('<span class=\"badge badge-pill light-bg text-black\" data-testid=\"draft-badge\">Draft</span>')\n      })\n\n      it('renders empty string when is_draft is false', () => {\n        const rendered = columns[9].render(false, 'display', {})\n\n        expect(rendered).toBe('')\n      })\n\n      it('handles string \"true\" as truthy', () => {\n        const rendered = columns[9].render('true', 'display', {})\n\n        expect(rendered).toBe('<span class=\"badge badge-pill light-bg text-black\" data-testid=\"draft-badge\">Draft</span>')\n      })\n\n      it('handles string \"false\" as falsy (explicit check for \"true\")', () => {\n        const rendered = columns[9].render('false', 'display', {})\n\n        // With explicit check for === true || === \"true\", string \"false\" should not render badge\n        expect(rendered).toBe('')\n      })\n\n      it('handles empty string as falsy', () => {\n        const rendered = columns[9].render('', 'display', {})\n\n        expect(rendered).toBe('')\n      })\n    })\n\n    describe('Ellipsis menu column (index 10)', () => {\n      it('is not orderable or searchable', () => {\n        expect(columns[10].orderable).toBe(false)\n        expect(columns[10].searchable).toBe(false)\n      })\n\n      it('renders a button toggle with aria-label containing the contact date', () => {\n        const row = { id: '1', occurred_at: 'July 01, 2024', can_edit: 'true', can_destroy: 'true', edit_path: '/case_contacts/1/edit', followup_id: '' }\n        const rendered = columns[10].render(null, 'display', row)\n\n        expect(rendered).toContain('class=\"fas fa-ellipsis-v\"')\n        expect(rendered).toContain('aria-label=\"Actions for case contact on July 01, 2024\"')\n        expect(rendered).toContain('type=\"button\"')\n      })\n\n      it('renders the ellipsis icon as aria-hidden', () => {\n        const row = { id: '1', occurred_at: 'July 01, 2024', can_edit: 'true', can_destroy: 'true', edit_path: '/case_contacts/1/edit', followup_id: '' }\n        const rendered = columns[10].render(null, 'display', row)\n\n        expect(rendered).toContain('aria-hidden=\"true\"')\n      })\n\n      it('renders Edit item when can_edit is \"true\"', () => {\n        const row = { id: '1', occurred_at: 'July 01, 2024', can_edit: 'true', can_destroy: 'false', edit_path: '/case_contacts/1/edit', followup_id: '' }\n        const rendered = columns[10].render(null, 'display', row)\n\n        expect(rendered).toContain('href=\"/case_contacts/1/edit\"')\n        expect(rendered).toContain('Edit')\n      })\n\n      it('renders Edit as disabled when can_edit is \"false\"', () => {\n        const row = { id: '1', occurred_at: 'July 01, 2024', can_edit: 'false', can_destroy: 'false', edit_path: '/case_contacts/1/edit', followup_id: '' }\n        const rendered = columns[10].render(null, 'display', row)\n\n        expect(rendered).toContain('Edit')\n        expect(rendered).toContain('disabled')\n        expect(rendered).toContain('aria-disabled=\"true\"')\n        expect(rendered).not.toContain('href=\"/case_contacts/1/edit\"')\n      })\n\n      it('renders Delete item when can_destroy is \"true\"', () => {\n        const row = { id: '1', occurred_at: 'July 01, 2024', can_edit: 'false', can_destroy: 'true', edit_path: '/case_contacts/1/edit', followup_id: '' }\n        const rendered = columns[10].render(null, 'display', row)\n\n        expect(rendered).toContain('cc-delete-action')\n        expect(rendered).toContain('data-id=\"1\"')\n        expect(rendered).toContain('Delete')\n      })\n\n      it('renders Delete as disabled when can_destroy is \"false\"', () => {\n        const row = { id: '1', occurred_at: 'July 01, 2024', can_edit: 'false', can_destroy: 'false', edit_path: '/case_contacts/1/edit', followup_id: '' }\n        const rendered = columns[10].render(null, 'display', row)\n\n        expect(rendered).toContain('Delete')\n        expect(rendered).toContain('disabled')\n        expect(rendered).toContain('aria-disabled=\"true\"')\n        expect(rendered).not.toContain('cc-delete-action')\n      })\n\n      it('renders Set Reminder when followup_id is empty', () => {\n        const row = { id: '1', occurred_at: 'July 01, 2024', can_edit: 'false', can_destroy: 'false', edit_path: '/case_contacts/1/edit', followup_id: '' }\n        const rendered = columns[10].render(null, 'display', row)\n\n        expect(rendered).toContain('cc-set-reminder-action')\n        expect(rendered).toContain('Set Reminder')\n        expect(rendered).not.toContain('Resolve Reminder')\n      })\n\n      it('renders Resolve Reminder when followup_id is present', () => {\n        const row = { id: '1', occurred_at: 'July 01, 2024', can_edit: 'false', can_destroy: 'false', edit_path: '/case_contacts/1/edit', followup_id: '42' }\n        const rendered = columns[10].render(null, 'display', row)\n\n        expect(rendered).toContain('cc-resolve-reminder-action')\n        expect(rendered).toContain('data-followup-id=\"42\"')\n        expect(rendered).toContain('Resolve Reminder')\n        expect(rendered).not.toContain('Set Reminder')\n      })\n\n      it('always renders the reminder menu item', () => {\n        const row = { id: '1', occurred_at: 'July 01, 2024', can_edit: 'false', can_destroy: 'false', edit_path: '/case_contacts/1/edit', followup_id: '' }\n        const rendered = columns[10].render(null, 'display', row)\n\n        expect(rendered).toMatch(/Set Reminder|Resolve Reminder/)\n      })\n    })\n  })\n\n  describe('click handlers', () => {\n    let mockAjaxReload\n    let mockTableInstance\n\n    const clickActionButton = (action, attrs = {}) => {\n      const dataAttrs = Object.entries(attrs).map(([k, v]) => `data-${k}=\"${v}\"`).join(' ')\n      $('table#case_contacts tbody').append(\n        `<tr><td><button class=\"cc-${action}-action\" ${dataAttrs}>${action}</button></td></tr>`\n      )\n      $(`.cc-${action}-action`).trigger('click')\n    }\n\n    beforeEach(() => {\n      mockAjaxReload = jest.fn()\n      mockTableInstance = { ajax: { reload: mockAjaxReload } }\n      mockDataTable.mockReturnValue(mockTableInstance)\n\n      // Add CSRF meta tag\n      document.head.innerHTML = '<meta name=\"csrf-token\" content=\"test-csrf-token\">'\n\n      defineCaseContactsTable()\n    })\n\n    afterEach(() => {\n      Swal.fire.mockReset()\n      fireSwalFollowupAlert.mockReset()\n    })\n\n    describe('Delete action', () => {\n      it('shows a SweetAlert confirmation dialog when cc-delete-action is clicked', () => {\n        Swal.fire.mockResolvedValue({ isConfirmed: false })\n\n        clickActionButton('delete', { id: '42' })\n\n        expect(Swal.fire).toHaveBeenCalled()\n      })\n\n      it('sends DELETE request when confirmed', async () => {\n        Swal.fire.mockResolvedValue({ isConfirmed: true })\n        const ajaxSpy = jest.spyOn($, 'ajax').mockImplementation(({ success }) => success && success())\n\n        clickActionButton('delete', { id: '42' })\n\n        await Promise.resolve()\n\n        expect(ajaxSpy).toHaveBeenCalledWith(expect.objectContaining({\n          url: '/case_contacts/42',\n          type: 'DELETE',\n          headers: { 'X-CSRF-Token': 'test-csrf-token', Accept: 'application/json' }\n        }))\n        expect(ajaxSpy.mock.calls[0][0]).not.toHaveProperty('dataType')\n\n        ajaxSpy.mockRestore()\n      })\n\n      it('does not send DELETE request when cancelled', async () => {\n        Swal.fire.mockResolvedValue({ isConfirmed: false })\n        const ajaxSpy = jest.spyOn($, 'ajax').mockImplementation()\n\n        clickActionButton('delete', { id: '42' })\n\n        await Promise.resolve()\n\n        expect(ajaxSpy).not.toHaveBeenCalled()\n\n        ajaxSpy.mockRestore()\n      })\n\n      it('reloads the DataTable without resetting pagination after successful delete', async () => {\n        Swal.fire.mockResolvedValue({ isConfirmed: true })\n        jest.spyOn($, 'ajax').mockImplementation(({ success }) => success && success())\n\n        clickActionButton('delete', { id: '42' })\n\n        await Promise.resolve()\n\n        expect(mockAjaxReload).toHaveBeenCalledWith(null, false)\n      })\n    })\n\n    describe('Set Reminder action', () => {\n      it('calls fireSwalFollowupAlert when cc-set-reminder-action is clicked', () => {\n        fireSwalFollowupAlert.mockResolvedValue({ isConfirmed: false })\n\n        clickActionButton('set-reminder', { id: '5' })\n\n        expect(fireSwalFollowupAlert).toHaveBeenCalled()\n      })\n\n      it('posts to the followups endpoint with CSRF header when confirmed without a note', async () => {\n        fireSwalFollowupAlert.mockResolvedValue({ value: '', isConfirmed: true })\n        const ajaxSpy = jest.spyOn($, 'ajax').mockImplementation(({ success }) => success && success())\n\n        clickActionButton('set-reminder', { id: '5' })\n\n        await Promise.resolve()\n\n        expect(ajaxSpy).toHaveBeenCalledWith(expect.objectContaining({\n          url: '/case_contacts/5/followups',\n          type: 'POST',\n          data: {},\n          headers: { 'X-CSRF-Token': 'test-csrf-token', Accept: 'application/json' }\n        }))\n\n        ajaxSpy.mockRestore()\n      })\n\n      it('posts with note when confirmed with a note', async () => {\n        fireSwalFollowupAlert.mockResolvedValue({ value: 'My note', isConfirmed: true })\n        const ajaxSpy = jest.spyOn($, 'ajax').mockImplementation(({ success }) => success && success())\n\n        clickActionButton('set-reminder', { id: '5' })\n\n        await Promise.resolve()\n\n        expect(ajaxSpy).toHaveBeenCalledWith(expect.objectContaining({\n          url: '/case_contacts/5/followups',\n          type: 'POST',\n          data: { note: 'My note' },\n          headers: { 'X-CSRF-Token': 'test-csrf-token', Accept: 'application/json' }\n        }))\n\n        ajaxSpy.mockRestore()\n      })\n\n      it('does not post when cancelled', async () => {\n        fireSwalFollowupAlert.mockResolvedValue({ isConfirmed: false })\n        const ajaxSpy = jest.spyOn($, 'ajax').mockImplementation()\n\n        clickActionButton('set-reminder', { id: '5' })\n\n        await Promise.resolve()\n\n        expect(ajaxSpy).not.toHaveBeenCalled()\n\n        ajaxSpy.mockRestore()\n      })\n\n      it('reloads the DataTable without resetting pagination after creating a reminder', async () => {\n        fireSwalFollowupAlert.mockResolvedValue({ value: '', isConfirmed: true })\n        jest.spyOn($, 'ajax').mockImplementation(({ success }) => success && success())\n\n        clickActionButton('set-reminder', { id: '5' })\n\n        await Promise.resolve()\n\n        expect(mockAjaxReload).toHaveBeenCalledWith(null, false)\n      })\n    })\n\n    describe('Resolve Reminder action', () => {\n      it('sends PATCH request when cc-resolve-reminder-action is clicked', () => {\n        const ajaxSpy = jest.spyOn($, 'ajax').mockImplementation(({ success }) => success && success())\n\n        clickActionButton('resolve-reminder', { id: '5', 'followup-id': '42' })\n\n        expect(ajaxSpy).toHaveBeenCalledWith(expect.objectContaining({\n          url: '/followups/42/resolve',\n          type: 'PATCH',\n          headers: { 'X-CSRF-Token': 'test-csrf-token', Accept: 'application/json' }\n        }))\n        expect(ajaxSpy.mock.calls[0][0]).not.toHaveProperty('dataType')\n\n        ajaxSpy.mockRestore()\n      })\n\n      it('reloads the DataTable without resetting pagination after resolving a reminder', () => {\n        jest.spyOn($, 'ajax').mockImplementation(({ success }) => success && success())\n\n        clickActionButton('resolve-reminder', { id: '5', 'followup-id': '42' })\n\n        expect(mockAjaxReload).toHaveBeenCalledWith(null, false)\n      })\n    })\n  })\n\n  describe('edge cases', () => {\n    it('handles missing data-source attribute gracefully', () => {\n      tableElement.removeAttr('data-source')\n\n      expect(() => defineCaseContactsTable()).not.toThrow()\n\n      const config = mockDataTable.mock.calls[0][0]\n      expect(config.ajax.url).toBeUndefined()\n    })\n\n    it('handles table element not existing', () => {\n      document.body.innerHTML = ''\n\n      // Should not throw when table doesn't exist\n      expect(() => defineCaseContactsTable()).not.toThrow()\n    })\n  })\n\n  describe('DataTable integration', () => {\n    it('passes all required configuration options', () => {\n      defineCaseContactsTable()\n\n      const config = mockDataTable.mock.calls[0][0]\n\n      // Verify all critical config options are present\n      expect(config).toHaveProperty('scrollX')\n      expect(config).toHaveProperty('searching')\n      expect(config).toHaveProperty('processing')\n      expect(config).toHaveProperty('serverSide')\n      expect(config).toHaveProperty('order')\n      expect(config).toHaveProperty('ajax')\n      expect(config).toHaveProperty('columnDefs')\n      expect(config).toHaveProperty('columns')\n    })\n\n    it('configures columns array matching table structure', () => {\n      defineCaseContactsTable()\n\n      const config = mockDataTable.mock.calls[0][0]\n      const headerColumns = $('table#case_contacts thead th').length\n\n      expect(config.columns.length).toBe(headerColumns)\n    })\n  })\n})\n"
  },
  {
    "path": "app/javascript/__tests__/notifier.test.js",
    "content": "/* eslint-env jest */\n/**\n * @jest-environment jsdom\n */\n\nimport { escape } from 'lodash'\n\nconst { Notifier, Notification } = require('../src/notifier.js')\n\nlet notificationsElement\nlet notifier\n\nbeforeEach(() => {\n  document.body.innerHTML = `<div id=\"notifications\">\n  <div class=\"messages\">\n  </div>\n  <div id=\"async-waiting-indicator\" style=\"display: none\">\n    Saving <i class=\"load-spinner\"></i>\n  </div>\n  <div id=\"async-success-indicator\" class=\"success-notification\" style=\"display: none\">\n    Saved\n  </div>\n  <button id=\"toggle-minimize-notifications\" style=\"display: none;\">\n    <span>minimize notifications </span>\n    <span class=\"badge rounded-pill bg-success\" style=\"display: none;\"></span>\n    <span class=\"badge rounded-pill bg-warning\" style=\"display: none;\"></span>\n    <span class=\"badge rounded-pill bg-danger\" style=\"display: none;\"></span>\n    <i class=\"fa-solid fa-minus\"></i>\n  </button>\n</div>`\n\n  notificationsElement = $('#notifications')\n  notifier = new Notifier(notificationsElement)\n})\n\ndescribe('Notifier', () => {\n  describe('clicking the minify button', () => {\n    let minimizeButton\n\n    beforeEach(() => {\n      notifier.notify('a notification', 'info')\n      minimizeButton = notificationsElement.find('#toggle-minimize-notifications')\n    })\n\n    test('should toggle the notifier between the minified and expanded state', () => {\n      const messageNotificationsContainer = notificationsElement.find('.messages')\n      const minimizeButtonIcon = minimizeButton.children('i')\n      const minimizeButtonText = minimizeButton.children('span').first()\n\n      expect(minimizeButton.css('display')).not.toBe('none')\n      expect(messageNotificationsContainer.css('display')).not.toBe('none')\n      expect(minimizeButtonIcon.hasClass('fa-minus')).toBe(true)\n      expect(minimizeButtonIcon.hasClass('fa-plus')).toBe(false)\n      expect(minimizeButtonText.css('display')).not.toBe('none')\n\n      minimizeButton.click()\n\n      expect(messageNotificationsContainer.css('display')).toBe('none')\n      expect(minimizeButtonIcon.hasClass('fa-minus')).toBe(false)\n      expect(minimizeButtonIcon.hasClass('fa-plus')).toBe(true)\n      expect(minimizeButtonText.css('display')).toBe('none')\n\n      minimizeButton.click()\n\n      expect(messageNotificationsContainer.css('display')).not.toBe('none')\n      expect(minimizeButtonIcon.hasClass('fa-minus')).toBe(true)\n      expect(minimizeButtonIcon.hasClass('fa-plus')).toBe(false)\n      expect(minimizeButtonText.css('display')).not.toBe('none')\n    })\n\n    test('should show only badges where there exists at least one notification matching the badge level when minimized', () => {\n      const minimizeButtonBadgeError = minimizeButton.children('.bg-danger')\n      const minimizeButtonBadgeInfo = minimizeButton.children('.bg-success')\n      const minimizeButtonBadgeWarning = minimizeButton.children('.bg-warning')\n\n      expect(minimizeButton.css('display')).not.toBe('none')\n\n      minimizeButton.click()\n\n      expect(minimizeButtonBadgeInfo.css('display')).not.toContain('none')\n      expect(minimizeButtonBadgeInfo.text()).toBe('1')\n      expect(minimizeButtonBadgeError.css('display')).toContain('none')\n      expect(minimizeButtonBadgeWarning.css('display')).toContain('none')\n\n      minimizeButton.click()\n      const notification2 = notifier.notify('msg', 'error')\n      notifier.notify('msg', 'warn')\n\n      minimizeButton.click()\n      expect(minimizeButtonBadgeInfo.css('display')).not.toContain('none')\n      expect(minimizeButtonBadgeInfo.text()).toBe('1')\n      expect(minimizeButtonBadgeError.css('display')).not.toContain('none')\n      expect(minimizeButtonBadgeError.text()).toBe('1')\n      expect(minimizeButtonBadgeWarning.css('display')).not.toContain('none')\n      expect(minimizeButtonBadgeWarning.text()).toBe('1')\n\n      minimizeButton.click()\n      notification2.dismiss()\n\n      minimizeButton.click()\n      expect(minimizeButtonBadgeInfo.css('display')).not.toContain('none')\n      expect(minimizeButtonBadgeInfo.text()).toBe('1')\n      expect(minimizeButtonBadgeError.css('display')).toContain('none')\n      expect(minimizeButtonBadgeWarning.css('display')).not.toContain('none')\n      expect(minimizeButtonBadgeWarning.text()).toBe('1')\n    })\n  })\n\n  describe('notify', () => {\n    test(\"displays a green notification when passed a message and level='info'\", () => {\n      const notificationMessage = \"'Y$deH[|%ROii]jy\"\n\n      // Clear any existing notifications from previous tests\n      notificationsElement.find('.messages').empty()\n\n      notifier.notify(notificationMessage, 'info')\n\n      const successMessages = notificationsElement.children('.messages').find('.success-notification')\n\n      expect(successMessages.length).toBe(1)\n      expect(successMessages[0].innerHTML).toContain(notificationMessage)\n    })\n\n    test(\"displays a red notification when passed a message and level='error'\", () => {\n      const notificationMessage = '\\\\+!h0bbH\"yN7dx9.'\n\n      notifier.notify(notificationMessage, 'error')\n\n      const failureMessages = notificationsElement.find('.danger-notification')\n\n      expect(failureMessages.length).toBe(1)\n      expect(failureMessages[0].innerHTML).toContain(notificationMessage)\n    })\n\n    test('displays the minimize button after no notifications were present before', () => {\n      const messageNotificationsContainer = notificationsElement.find('.messages')\n      const minimizeButton = notificationsElement.find('#toggle-minimize-notifications')\n\n      expect(minimizeButton.css('display')).toBe('none')\n      expect(messageNotificationsContainer.children().length).toBe(0)\n\n      notifier.notify('msg', 'info')\n\n      expect(minimizeButton.css('display')).not.toBe('none')\n      expect(messageNotificationsContainer.children().length).toBeGreaterThan(0)\n    })\n\n    describe('when the notifier is minimized', () => {\n      let minimizeButton\n\n      beforeEach(() => {\n        $(() => {\n          minimizeButton = notificationsElement.find('#toggle-minimize-notifications')\n\n          notifier.notify('msg', 'info')\n          minimizeButton.click()\n        })\n      })\n\n      test(\"un-hides the badge corresponding to the notification's level if it is the only notification with that level\", (done) => {\n        $(() => {\n          try {\n            const minimizeButtonBadgeError = notificationsElement.find('#toggle-minimize-notifications .bg-danger')\n\n            expect(notificationsElement.children('.messages').css('display')).toBe('none')\n            expect(minimizeButton.css('display')).not.toBe('none')\n            expect(minimizeButtonBadgeError.css('display')).toBe('none')\n\n            notifier.notify('msg', 'error')\n\n            expect(minimizeButtonBadgeError.css('display')).not.toBe('none')\n\n            done()\n          } catch (error) {\n            done(error)\n          }\n        })\n      })\n\n      test(\"increments the number on badge corresponding to the notification's level when the badge is already displayed\", (done) => {\n        $(() => {\n          try {\n            expect(notificationsElement.children('.messages').css('display')).toBe('none')\n            expect(minimizeButton.css('display')).not.toBe('none')\n\n            const minimizeButtonBadgeInfo = notificationsElement.find('#toggle-minimize-notifications .bg-success')\n\n            expect(minimizeButtonBadgeInfo.css('display')).not.toBe('none')\n            expect(minimizeButtonBadgeInfo.text()).toBe('1')\n\n            notifier.notify('msg', 'info')\n\n            expect(minimizeButtonBadgeInfo.text()).toBe('2')\n\n            const minimizeButtonBadgeError = notificationsElement.find('#toggle-minimize-notifications .bg-danger')\n            notifier.notify('msg', 'error')\n\n            expect(minimizeButtonBadgeError.css('display')).not.toBe('none')\n            expect(minimizeButtonBadgeError.text()).toBe('1')\n\n            notifier.notify('msg', 'error')\n\n            expect(minimizeButtonBadgeError.text()).toBe('2')\n\n            const minimizeButtonBadgeWarning = notificationsElement.find('#toggle-minimize-notifications .bg-warning')\n            notifier.notify('msg', 'warn')\n\n            expect(minimizeButtonBadgeWarning.css('display')).not.toBe('none')\n            expect(minimizeButtonBadgeWarning.text()).toBe('1')\n\n            notifier.notify('msg', 'warn')\n\n            expect(minimizeButtonBadgeWarning.text()).toBe('2')\n            done()\n          } catch (error) {\n            done(error)\n          }\n        })\n      })\n    })\n\n    test('appends a dismissable message to the notifications widget', (done) => {\n      $(() => {\n        try {\n          notifier.notify('', 'error')\n          notifier.notify('', 'info')\n\n          const messagesContainer = notificationsElement.children('.messages')\n          let failureMessages = messagesContainer.find('.danger-notification')\n          let successMessages = messagesContainer.find('.success-notification')\n\n          expect(failureMessages.length).toBe(1)\n          expect(successMessages.length).toBe(1)\n\n          failureMessages.children('button').click()\n          failureMessages = notificationsElement.find('.danger-notification')\n\n          expect(failureMessages.length).toBe(0)\n\n          $(successMessages[0]).children('button').click()\n          successMessages = notificationsElement.find('.success-notification')\n\n          expect(successMessages.length).toBe(1)\n\n          done()\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n\n    test('appends a non dismissable message to the notifications widget when message dismissable is turned off', (done) => {\n      $(() => {\n        try {\n          notifier.notify('', 'error', false)\n\n          let failureMessages = notificationsElement.find('.danger-notification')\n\n          expect(failureMessages.length).toBe(1)\n\n          failureMessages.children('button').click()\n          failureMessages = notificationsElement.find('.danger-notification')\n\n          expect(failureMessages.length).toBe(1)\n\n          done()\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n\n    test('throws a RangeError when passed an unsupported message level', (done) => {\n      $(() => {\n        try {\n          expect(() => {\n            notifier.notify('message', 'unsupported level')\n          }).toThrow(RangeError)\n\n          done()\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n\n    test('throws a TypeError when param message is not a string', (done) => {\n      $(() => {\n        try {\n          expect(() => {\n            notifier.notify(6, 'info')\n          }).toThrow(TypeError)\n\n          done()\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n\n    test('returns a Notification object representing the new notification', (done) => {\n      $(() => {\n        try {\n          const notification = notifier.notify('test', 'info')\n          const onlyNotification = notificationsElement.children('.messages').children('.success-notification')\n          expect(notification.notificationElement.is(onlyNotification)).toBe(true)\n\n          done()\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n  })\n\n  describe('resolveAsyncOperation', () => {\n    test('displays the saved toast for 2 seconds when not passed an error', (done) => {\n      $(() => {\n        const savedToast = $('#async-success-indicator')\n\n        try {\n          notifier.waitForAsyncOperation()\n          expect(savedToast.css('display')).toBe('none')\n\n          setTimeout(() => {\n            expect(savedToast.attr('style')).toEqual(expect.not.stringContaining('display: none'))\n            done()\n          }, 2000)\n\n          notifier.resolveAsyncOperation()\n          expect(savedToast.attr('style')).toEqual(expect.not.stringContaining('display: none'))\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n\n    test('displays the saved toast for 2 seconds after the last call in a quick succession of calls when not passed an error', (done) => {\n      $(() => {\n        const savedToast = $('#async-success-indicator')\n\n        try {\n          notifier.waitForAsyncOperation()\n          notifier.waitForAsyncOperation()\n          notifier.waitForAsyncOperation()\n          expect(savedToast.css('display')).toBe('none')\n\n          setTimeout(() => {\n            expect(savedToast.attr('style')).toEqual(expect.not.stringContaining('display: none'))\n            done()\n          }, 4000)\n\n          notifier.resolveAsyncOperation()\n          expect(savedToast.attr('style')).toEqual(expect.not.stringContaining('display: none'))\n\n          // call resolveAsyncOperation before the previous resolveAsyncOperation call dismisses the saved Toast\n          setTimeout(() => {\n            notifier.resolveAsyncOperation()\n          }, 1000)\n\n          setTimeout(() => {\n            notifier.resolveAsyncOperation()\n          }, 2000)\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n\n    test('displays a red notification when passed an error', (done) => {\n      const errorMessage = 'hxDEe@no$~Bl%m^]'\n\n      $(() => {\n        try {\n          notifier.waitForAsyncOperation()\n          notifier.resolveAsyncOperation(errorMessage)\n\n          const failureMessages = notificationsElement.find('.danger-notification')\n\n          expect(failureMessages.length).toBe(1)\n          expect(failureMessages[0].innerHTML).toContain(errorMessage)\n          done()\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n\n    test('hides the loading toast when there are no more async operations to wait on', (done) => {\n      $(() => {\n        const loadingToast = $('#async-waiting-indicator')\n\n        try {\n          notifier.waitForAsyncOperation()\n          notifier.waitForAsyncOperation()\n          expect(loadingToast.attr('style')).toEqual(expect.not.stringContaining('display: none'))\n\n          notifier.resolveAsyncOperation()\n          notifier.resolveAsyncOperation()\n\n          expect(loadingToast.css('display')).toBe('none')\n\n          done()\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n\n    test('throws an error and display it in a red notification when trying to stop an async operation when it\\'s expecting to resolve none', (done) => {\n      $(() => {\n        try {\n          expect(() => {\n            notifier.resolveAsyncOperation()\n          }).toThrow()\n\n          done()\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n  })\n\n  describe('totalNotificationCount', () => {\n    test('returns the number of notifications the notifier currently has displayed', (done) => {\n      $(() => {\n        try {\n          expect(notifier.totalNotificationCount()).toBe(0)\n\n          const notificationCount = Math.floor(Math.random() * 10) + 1\n          const notifications = []\n\n          for (let i = 0; i < notificationCount; i++) {\n            notifications.push(notifier.notify('message', 'error'))\n          }\n\n          expect(notifier.totalNotificationCount()).toBe(notificationCount)\n\n          const aboutHalfNotificationCount = Math.floor(notificationCount / 2)\n\n          for (let i = 0; i < aboutHalfNotificationCount; i++) {\n            notifications.pop().dismiss()\n          }\n\n          expect(notifier.totalNotificationCount()).toBe(notificationCount - aboutHalfNotificationCount)\n\n          done()\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n  })\n\n  describe('waitForAsyncOperation', () => {\n    test('displays the loading indicator', (done) => {\n      $(() => {\n        const loadingToast = $('#async-waiting-indicator')\n\n        try {\n          expect(loadingToast.css('display')).toBe('none')\n\n          notifier.waitForAsyncOperation()\n          expect(loadingToast.attr('style')).toEqual(expect.not.stringContaining('display: none'))\n\n          done()\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n  })\n})\n\ndescribe('Notification', () => {\n  let notification\n  const notificationDefaultMessage = 'm*GV}.n?@D\\\\~]jW=JD$d'\n\n  beforeEach(() => {\n    $(() => {\n      notification = notifier.notify(notificationDefaultMessage, 'warn')\n    })\n  })\n\n  describe('constructor', () => {\n    test('throws a TypeError when passed a non jQuery object', (done) => {\n      $(() => {\n        try {\n          expect(() => {\n            // eslint-disable-next-line no-new\n            new Notification(3)\n          }).toThrow(TypeError)\n          done()\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n\n    test('throws a ReferenceError when passed jQuery object not representing anything in the dom', (done) => {\n      $(() => {\n        try {\n          expect(() => {\n            // eslint-disable-next-line no-new\n            new Notification($('#non-existant-element'))\n          }).toThrow(ReferenceError)\n          done()\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n  })\n\n  describe('dismiss', () => {\n    test('removes the notification elements', (done) => {\n      $(() => {\n        try {\n          expect(notificationsElement[0].innerHTML).toContain(notificationDefaultMessage)\n\n          notification.dismiss()\n          expect(notificationsElement[0].innerHTML).not.toContain(notificationDefaultMessage)\n          done()\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n\n    test('should throw an error if the notification has already been dismissed', (done) => {\n      $(() => {\n        try {\n          expect(notificationsElement[0].innerHTML).toContain(notificationDefaultMessage)\n\n          notification.dismiss()\n          expect(notificationsElement[0].innerHTML).not.toContain(notificationDefaultMessage)\n\n          expect(() => {\n            notification.dismiss()\n          }).toThrow(ReferenceError)\n          done()\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n\n    test('causes the notifier to hide the minimize button if it dismisses the last notification', (done) => {\n      $(() => {\n        try {\n          expect(notificationsElement[0].innerHTML).toContain(notificationDefaultMessage)\n          expect($('#toggle-minimize-notifications').css('display')).not.toContain('none')\n\n          notification.dismiss()\n          expect(notificationsElement[0].innerHTML).not.toContain(notificationDefaultMessage)\n          expect($('#toggle-minimize-notifications').css('display')).toContain('none')\n          done()\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n\n    test('hides the minimize button if no notifications are left', (done) => {\n      $(() => {\n        try {\n          const messageNotificationsContainer = notificationsElement.find('.messages')\n          const minimizeButton = notificationsElement.find('#toggle-minimize-notifications')\n\n          expect(minimizeButton.css('display')).not.toBe('none')\n          expect(messageNotificationsContainer.children().length).toBe(1)\n\n          notification.dismiss()\n\n          expect(minimizeButton.css('display')).toBe('none')\n          expect(messageNotificationsContainer.children().length).toBe(0)\n\n          done()\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n\n    describe('when the parent notifier is minimized', () => {\n      let minimizeButton\n\n      beforeEach(() => {\n        $(() => {\n          minimizeButton = notificationsElement.find('#toggle-minimize-notifications')\n\n          minimizeButton.click()\n        })\n      })\n\n      test(\"hides the badge corresponding to the notification's level when there are no more notifications matching the dismissed notification's level\", (done) => {\n        $(() => {\n          try {\n            const minimizeButtonBadgeError = notificationsElement.find('#toggle-minimize-notifications .bg-danger')\n\n            const errorNotification = notifier.notify('msg', 'error')\n\n            expect(notificationsElement.children('.messages').css('display')).toBe('none')\n            expect(minimizeButton.css('display')).not.toBe('none')\n            expect(minimizeButtonBadgeError.css('display')).not.toBe('none')\n\n            errorNotification.dismiss()\n\n            expect(minimizeButtonBadgeError.css('display')).toBe('none')\n\n            done()\n          } catch (error) {\n            done(error)\n          }\n        })\n      })\n\n      test(\"decrements the number on badge corresponding to the notification's level when there are still notifications matching the dismissed notification's level left\", (done) => {\n        $(() => {\n          try {\n            expect(notificationsElement.children('.messages').css('display')).toBe('none')\n            expect(minimizeButton.css('display')).not.toBe('none')\n\n            const minimizeButtonBadgeInfo = notificationsElement.find('#toggle-minimize-notifications .bg-success')\n            const infoNotification = notifier.notify('msg', 'info')\n            notifier.notify('msg', 'info')\n\n            expect(minimizeButtonBadgeInfo.css('display')).not.toBe('none')\n            expect(minimizeButtonBadgeInfo.text()).toBe('2')\n\n            infoNotification.dismiss()\n\n            expect(minimizeButtonBadgeInfo.text()).toBe('1')\n\n            const minimizeButtonBadgeError = notificationsElement.find('#toggle-minimize-notifications .bg-danger')\n            const errorNotification = notifier.notify('msg', 'error')\n            notifier.notify('msg', 'error')\n\n            expect(minimizeButtonBadgeError.css('display')).not.toBe('none')\n            expect(minimizeButtonBadgeError.text()).toBe('2')\n\n            errorNotification.dismiss()\n\n            expect(minimizeButtonBadgeError.text()).toBe('1')\n\n            const minimizeButtonBadgeWarning = notificationsElement.find('#toggle-minimize-notifications .bg-warning')\n            const warningNotification = notifier.notify('msg', 'warn')\n\n            expect(minimizeButtonBadgeWarning.css('display')).not.toBe('none')\n            expect(minimizeButtonBadgeWarning.text()).toBe('2')\n\n            warningNotification.dismiss()\n\n            expect(minimizeButtonBadgeWarning.text()).toBe('1')\n            done()\n          } catch (error) {\n            done(error)\n          }\n        })\n      })\n    })\n  })\n\n  describe('getText', () => {\n    test('should return the text of the notification', (done) => {\n      $(() => {\n        try {\n          expect(notificationsElement[0].innerHTML).toContain(notificationDefaultMessage)\n          expect(notification.getText()).toBe(notificationDefaultMessage)\n\n          done()\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n  })\n\n  describe('isUserDismissable', () => {\n    test('returns a truthy value if there is a dismiss button, false otherwise', (done) => {\n      $(() => {\n        try {\n          expect(notificationsElement[0].innerHTML).toContain(notificationDefaultMessage)\n          expect(notification.isUserDismissable()).toBeTruthy()\n\n          notification.notificationElement.children('button').remove()\n\n          expect(notification.isUserDismissable()).not.toBeTruthy()\n          done()\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n  })\n\n  describe('isDismissed', () => {\n    test('returns a falsy value if the notification could be found as a child of the notificatons component', (done) => {\n      $(() => {\n        try {\n          expect($(document).find(notification.notificationElement).length).toBe(1)\n          expect(notification.isDismissed()).not.toBeTruthy()\n          done()\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n\n    test('returns a truthy value if the notification could not be found as a child of the notificatons component', (done) => {\n      $(() => {\n        try {\n          expect($(document).find(notification.notificationElement).length).toBe(1)\n\n          notification.notificationElement.remove()\n\n          expect($(document).find(notification.notificationElement).length).toBe(0)\n          expect(notification.isDismissed()).toBeTruthy()\n          done()\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n  })\n\n  describe('setUserDismissable', () => {\n    test('adds a dismiss button that removes the notification element when clicked if one is not present', (done) => {\n      $(() => {\n        try {\n          const notificationMessage = 'mn6#:6C^*hnQ/:cC;2mM'\n          notification = notifier.notify(notificationMessage, 'info', false)\n\n          expect(notificationsElement[0].innerHTML).toContain(notificationMessage)\n          expect(notification.notificationElement.children('button').length).toBe(0)\n\n          notification.setUserDismissable(true)\n\n          expect(notification.notificationElement.children('button').length).toBe(1)\n\n          notification.notificationElement.children('button').click()\n\n          expect($(document).find(notification.notificationElement).length).toBe(0)\n          done()\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n\n    test('does nothing if the notification is already in the desired state', (done) => {\n      $(() => {\n        try {\n          expect(notificationsElement[0].innerHTML).toContain(notificationDefaultMessage)\n          expect(notification.notificationElement.children('button').length).toBe(1)\n\n          notification.setUserDismissable(true)\n\n          expect(notification.notificationElement.children('button').length).toBe(1)\n\n          const notificationMessage = 'fd@4g*G@.6sV{!^Yj*TR'\n          notification = notifier.notify(notificationMessage, 'info', false)\n\n          expect(notificationsElement[0].innerHTML).toContain(notificationMessage)\n          expect(notification.notificationElement.children('button').length).toBe(0)\n\n          notification.setUserDismissable(false)\n\n          expect(notification.notificationElement.children('button').length).toBe(0)\n          done()\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n\n    test('removes the dismiss button that removes the notification element when clicked if the button is present', (done) => {\n      $(() => {\n        try {\n          expect(notificationsElement[0].innerHTML).toContain(notificationDefaultMessage)\n          expect(notification.notificationElement.children('button').length).toBe(1)\n\n          notification.setUserDismissable(false)\n\n          expect(notification.notificationElement.children('button').length).toBe(0)\n          done()\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n\n    test('throws an error if the notification is dismissed', (done) => {\n      $(() => {\n        try {\n          notification.notificationElement.remove()\n          expect(notificationsElement[0].innerHTML).not.toContain(notificationDefaultMessage)\n\n          expect(() => {\n            notification.setUserDismissable(true)\n          }).toThrow(ReferenceError)\n          done()\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n  })\n\n  describe('setText', () => {\n    test('changes the text of the notification', (done) => {\n      $(() => {\n        try {\n          expect(notificationsElement[0].innerHTML).toContain(notificationDefaultMessage)\n\n          const newNotificationMessage = 'VOr%%:#Vc*tbNbM}iUT}'\n\n          notification.setText(newNotificationMessage)\n\n          expect(notification.notificationElement.text()).toContain(newNotificationMessage)\n          done()\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n\n    test('throws an error if the notification has been dismissed', (done) => {\n      $(() => {\n        try {\n          notification.notificationElement.remove()\n          expect(notificationsElement[0].innerHTML).not.toContain(notificationDefaultMessage)\n\n          expect(() => {\n            notification.setText('new Text')\n          }).toThrow(ReferenceError)\n          done()\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n  })\n\n  describe('toggleUserDismissable', () => {\n    test('will add a functioning dismiss button for the user if there is none', (done) => {\n      $(() => {\n        try {\n          const notificationMessage = 'nm8j$<w*HszPHkObK._n'\n          notification = notifier.notify(notificationMessage, 'info', false)\n\n          expect(notificationsElement[0].innerHTML).toContain(escape(notificationMessage))\n          expect(notification.notificationElement.children('button').length).toBe(0)\n\n          notification.toggleUserDismissable()\n\n          expect(notification.notificationElement.children('button').length).toBe(1)\n\n          notification.notificationElement.children('button').click()\n\n          expect($(document).find(notification.notificationElement).length).toBe(0)\n          done()\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n\n    test('will remove the dismiss button for the user if is present', (done) => {\n      $(() => {\n        try {\n          expect(notificationsElement[0].innerHTML).toContain(escape(notificationDefaultMessage))\n          expect(notification.notificationElement.children('button').length).toBe(1)\n\n          notification.toggleUserDismissable()\n\n          expect(notification.notificationElement.children('button').length).toBe(0)\n          done()\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n\n    test('throws an error if the notification has been dismissed', (done) => {\n      $(() => {\n        try {\n          notification.notificationElement.remove()\n          expect(notificationsElement[0].innerHTML).not.toContain(escape(notificationDefaultMessage))\n\n          expect(() => {\n            notification.toggleUserDismissable()\n          }).toThrow(ReferenceError)\n          done()\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "app/javascript/__tests__/password_confirmation.test.js",
    "content": "/* eslint-env jest */\n/**\n * @jest-environment jsdom\n */\n\nimport { checkPasswordsAndDisplayPopup } from '../src/password_confirmation'\n\nlet submitButton, passwordField, confirmationField\n\nbeforeEach(() => {\n  document.body.innerHTML = `\n  <form action=\".\" method=\"post\" class=\"test-form\">\n    <div>\n      <label for=\"user_password\">New Password</label><br>\n      <input autocomplete=\"off\" class=\"form-control password-new\" minlength=\"6\" type=\"password\" name=\"user[password]\" id=\"user_password\" />\n    </div>\n    <div>\n      <label for=\"user_password_confirmation\">New Password Confirmation</label><br>\n      <input class=\"form-control password-confirmation\" minlength=\"6\" type=\"password\" name=\"user[password_confirmation]\" id=\"user_password_confirmation\" />\n    </div>\n    <input type=\"submit\" name=\"commit\" value=\"Update Password\" class=\"btn btn-danger submit-password\" data-disable-with=\"Update Password\" />\n  </form>\n  `\n  submitButton = document.getElementsByClassName('submit-password')[0]\n  passwordField = document.getElementsByClassName('password-new')[0]\n  confirmationField = document.getElementsByClassName('password-confirmation')[0]\n\n  submitButton.disabled = true\n  submitButton.classList.add('disabled')\n  submitButton.setAttribute('aria-disabled', true)\n})\n\ndescribe('ensure the password field matches the confirmation field on the client side', () => {\n  function isDisabled (el) {\n    return el.disabled && el.classList.contains('disabled') && el.hasAttribute('aria-disabled')\n  }\n\n  test('password fields match', () => {\n    passwordField.value = '12345678'\n    confirmationField.value = '12345678'\n    checkPasswordsAndDisplayPopup(submitButton, passwordField, confirmationField)\n    expect(isDisabled(submitButton)).toBe(false)\n    expect(document.getElementsByClassName('swal2-container').length).toBe(0)\n  })\n\n  test(\"password fields don't match\", () => {\n    passwordField.value = '12345678'\n    confirmationField.value = 'bad'\n    checkPasswordsAndDisplayPopup(submitButton, passwordField, confirmationField, true)\n    expect(document.getElementsByClassName('swal2-container').length).toBe(1)\n  })\n\n  test(\"password fields don't match and pop-up suppressed\", () => {\n    passwordField.value = '12345678'\n    confirmationField.value = 'bad'\n    checkPasswordsAndDisplayPopup(submitButton, passwordField, confirmationField)\n    expect(document.getElementsByClassName('swal2-container').length).toBe(0)\n  })\n\n  test('password fields match but are empty', () => {\n    expect(isDisabled(submitButton)).toBe(true)\n    expect(document.getElementsByClassName('swal2-container').length).toBe(0)\n  })\n})\n"
  },
  {
    "path": "app/javascript/__tests__/read_more.test.js",
    "content": "/* eslint-env jest, browser */\n/**\n * @jest-environment jsdom\n */\n\ndescribe('read_more', () => {\n  beforeEach(() => {\n    document.body.innerHTML = `\n      <div class=\"js-read-more-text-wrapper\">\n        <div class=\"js-truncated-text\" style=\"display: block;\">\n          Short text...\n        </div>\n        <div class=\"js-full-text\" style=\"display: none;\">\n          This is the full text that is initially hidden.\n        </div>\n        <button class=\"js-read-more\">Read More</button>\n        <button class=\"js-read-less\" style=\"display: none;\">Read Less</button>\n      </div>\n    `\n\n    // Require the module after setting up the DOM\n    jest.isolateModules(() => {\n      require('../src/read_more')\n    })\n\n    // Trigger DOMContentLoaded\n    const event = new Event('DOMContentLoaded')\n    document.dispatchEvent(event)\n  })\n\n  test('shows full text and hides truncated text when Read More is clicked', () => {\n    const readMoreButton = document.querySelector('.js-read-more')\n    const truncatedText = document.querySelector('.js-truncated-text')\n    const fullText = document.querySelector('.js-full-text')\n\n    expect(truncatedText.style.display).toBe('block')\n    expect(fullText.style.display).toBe('none')\n\n    readMoreButton.click()\n\n    expect(truncatedText.style.display).toBe('none')\n    expect(fullText.style.display).toBe('block')\n  })\n\n  test('shows truncated text and hides full text when Read Less is clicked', () => {\n    const readMoreButton = document.querySelector('.js-read-more')\n    const readLessButton = document.querySelector('.js-read-less')\n    const truncatedText = document.querySelector('.js-truncated-text')\n    const fullText = document.querySelector('.js-full-text')\n\n    // First expand\n    readMoreButton.click()\n    expect(fullText.style.display).toBe('block')\n\n    // Then collapse\n    readLessButton.click()\n    expect(truncatedText.style.display).toBe('block')\n    expect(fullText.style.display).toBe('none')\n  })\n\n  test('prevents default event behavior on Read More click', () => {\n    const readMoreButton = document.querySelector('.js-read-more')\n    const event = new MouseEvent('click', { bubbles: true, cancelable: true })\n    const preventDefaultSpy = jest.spyOn(event, 'preventDefault')\n\n    readMoreButton.dispatchEvent(event)\n\n    expect(preventDefaultSpy).toHaveBeenCalled()\n  })\n\n  test('prevents default event behavior on Read Less click', () => {\n    const readLessButton = document.querySelector('.js-read-less')\n    const event = new MouseEvent('click', { bubbles: true, cancelable: true })\n    const preventDefaultSpy = jest.spyOn(event, 'preventDefault')\n\n    readLessButton.dispatchEvent(event)\n\n    expect(preventDefaultSpy).toHaveBeenCalled()\n  })\n\n  test('handles multiple read more/less wrappers independently', () => {\n    document.body.innerHTML = `\n      <div class=\"js-read-more-text-wrapper\" id=\"wrapper1\">\n        <div class=\"js-truncated-text\" style=\"display: block;\">Short 1</div>\n        <div class=\"js-full-text\" style=\"display: none;\">Full 1</div>\n        <button class=\"js-read-more\">Read More</button>\n      </div>\n      <div class=\"js-read-more-text-wrapper\" id=\"wrapper2\">\n        <div class=\"js-truncated-text\" style=\"display: block;\">Short 2</div>\n        <div class=\"js-full-text\" style=\"display: none;\">Full 2</div>\n        <button class=\"js-read-more\">Read More</button>\n      </div>\n    `\n\n    jest.isolateModules(() => {\n      require('../src/read_more')\n    })\n    document.dispatchEvent(new Event('DOMContentLoaded'))\n\n    const wrapper1ReadMore = document.querySelector('#wrapper1 .js-read-more')\n    const wrapper1Full = document.querySelector('#wrapper1 .js-full-text')\n    const wrapper2Full = document.querySelector('#wrapper2 .js-full-text')\n\n    wrapper1ReadMore.click()\n\n    // Only wrapper1 should be expanded\n    expect(wrapper1Full.style.display).toBe('block')\n    expect(wrapper2Full.style.display).toBe('none')\n  })\n})\n"
  },
  {
    "path": "app/javascript/__tests__/require_communication_preference.test.js",
    "content": "/* eslint-env jest */\n/**\n * @jest-environment jsdom\n */\n\nimport { displayPopUpIfPreferencesIsInvalid } from '../src/require_communication_preference'\n\nlet saveButton\nlet emailCheckbox\nlet smsCheckbox\n\nbeforeEach(() => {\n  document.body.innerHTML = `\n <form action=\".\" method=\"post\" class=\"test-form\">\n   <div>\n       <input class=\"toggle-email-notifications\" type=\"checkbox\" value=\"1\" checked=\"checked\" name=\"user[receive_email_notifications]\" id=\"user_receive_email_notifications\">\n       <label for=\"user_receive_email_notifications\">Email Me</label>\n   </div>\n\n   <div>\n       <input class=\"toggle-sms-notifications\" type=\"checkbox\" value=\"1\" name=\"user[receive_sms_notifications]\" id=\"user_receive_sms_notifications\">\n       <label for=\"user_receive_sms_notifications\">Text Me</label>\n\n       <div class=\"action_container\">\n           <input type=\"submit\" name=\"commit\" value=\"Save Preferences\" class=\"btn btn-primary mb-3 save-preference\" data-disable-with=\"Save Preferences\">\n       </div>\n </form>\n `\n  saveButton = document.getElementsByClassName('save-preference')[0]\n  emailCheckbox = document.getElementById('user_receive_email_notifications')\n  smsCheckbox = document.getElementById('user_receive_sms_notifications')\n})\n\ndescribe('ensure the save button is enabled when at least one preference is selected', () => {\n  function isDisabled (el) {\n    return el.disabled && el.classList.contains('disabled') && el.hasAttribute('aria-disabled')\n  }\n\n  test('default user preferences state is always valid', () => {\n    displayPopUpIfPreferencesIsInvalid()\n    expect(isDisabled(saveButton)).toBe(false)\n    expect(document.getElementsByClassName('swal2-container').length).toBe(0)\n  })\n\n  test('at least email preference is selected', () => {\n    emailCheckbox.checked = true\n    smsCheckbox.checked = false\n    displayPopUpIfPreferencesIsInvalid()\n    expect(isDisabled(saveButton)).toBe(false)\n    expect(document.getElementsByClassName('swal2-container').length).toBe(0)\n  })\n\n  test('at least SMS preference is selected', () => {\n    emailCheckbox.checked = false\n    smsCheckbox.checked = true\n    displayPopUpIfPreferencesIsInvalid()\n    expect(isDisabled(saveButton)).toBe(false)\n    expect(document.getElementsByClassName('swal2-container').length).toBe(0)\n  })\n\n  test('both preferences are selected', () => {\n    emailCheckbox.checked = true\n    smsCheckbox.checked = true\n    displayPopUpIfPreferencesIsInvalid()\n    expect(isDisabled(saveButton)).toBe(false)\n    expect(document.getElementsByClassName('swal2-container').length).toBe(0)\n  })\n\n  test('no preferences selected shows popup', () => {\n    emailCheckbox.checked = false\n    smsCheckbox.checked = false\n    displayPopUpIfPreferencesIsInvalid()\n    expect(document.getElementsByClassName('swal2-container').length).toBe(1)\n  })\n})\n"
  },
  {
    "path": "app/javascript/__tests__/setup-jest.js",
    "content": "import $ from 'jquery'\nglobal.$ = global.jQuery = $\n"
  },
  {
    "path": "app/javascript/__tests__/time_zone.test.js",
    "content": "/* eslint-env jest */\n/**\n * @jest-environment jsdom\n */\n\nimport { findTimeZone } from '../src/time_zone'\n\ndescribe('findTimeZone', () => {\n  test('returns a string timezone name', () => {\n    const timezone = findTimeZone()\n\n    expect(typeof timezone).toBe('string')\n    expect(timezone.length).toBeGreaterThan(0)\n  })\n\n  test('returns a valid timezone string format', () => {\n    const timezone = findTimeZone()\n\n    // Timezone should be in format like \"America/New_York\" or \"Europe/London\"\n    // Common formats: Continent/City or UTC\n    expect(timezone).toMatch(/^[A-Za-z_]+\\/[A-Za-z_]+$|^UTC$|^[A-Z]{3,4}$/)\n  })\n\n  test('returns consistent result when called multiple times', () => {\n    const timezone1 = findTimeZone()\n    const timezone2 = findTimeZone()\n\n    expect(timezone1).toBe(timezone2)\n  })\n\n  test('handles environments where Intl is available', () => {\n    // This test just verifies the function doesn't throw when Intl exists\n    expect(() => {\n      findTimeZone()\n    }).not.toThrow()\n  })\n})\n"
  },
  {
    "path": "app/javascript/__tests__/two_minute_warning_session_timeout.test.js",
    "content": "/* eslint-env jest */\n/**\n * @jest-environment jsdom\n */\n\ndescribe('session_timeout_poller', () => {\n  let mockAlert\n  let mockReload\n  let originalTimeout\n\n  beforeEach(() => {\n    // Mock window.alert and window.location.reload\n    mockAlert = jest.fn()\n    mockReload = jest.fn()\n    global.alert = mockAlert\n    delete window.location\n    window.location = { reload: mockReload }\n\n    // Set a short timeout for testing (in minutes)\n    originalTimeout = global.window.timeout\n    global.window.timeout = 0.05 // 3 seconds total, warning at 1.2 seconds\n\n    jest.useFakeTimers()\n  })\n\n  afterEach(() => {\n    jest.useRealTimers()\n    global.window.timeout = originalTimeout\n    jest.resetModules()\n  })\n\n  test('timer runs every second', () => {\n    jest.resetModules()\n\n    // Spy on setInterval before requiring the module\n    const setIntervalSpy = jest.spyOn(global, 'setInterval')\n\n    // Start the poller\n    require('../src/session_timeout_poller')\n\n    // Verify setInterval was called with 1000ms (1 second)\n    expect(setIntervalSpy).toHaveBeenCalledWith(expect.any(Function), 1000)\n\n    setIntervalSpy.mockRestore()\n  })\n})\n"
  },
  {
    "path": "app/javascript/__tests__/type_checker.test.js",
    "content": "/* eslint-env jest */\n/**\n * @jest-environment jsdom\n */\n\nconst TypeChecker = require('../src/type_checker')\n\ndescribe('TypeChecker', () => {\n  describe('checkNonEmptyJQueryObject', () => {\n    beforeEach(() => {\n      document.body.innerHTML = '<div id=\"test-element\"></div>'\n    })\n\n    test('accepts a non-empty jQuery object', () => {\n      const element = $('#test-element')\n      expect(() => {\n        TypeChecker.checkNonEmptyJQueryObject(element, 'element')\n      }).not.toThrow()\n    })\n\n    test('throws TypeError when passed a non-jQuery object', () => {\n      expect(() => {\n        TypeChecker.checkNonEmptyJQueryObject('string', 'element')\n      }).toThrow(TypeError)\n      expect(() => {\n        TypeChecker.checkNonEmptyJQueryObject('string', 'element')\n      }).toThrow('Param element must be a jQuery object')\n    })\n\n    test('throws TypeError when passed null', () => {\n      expect(() => {\n        TypeChecker.checkNonEmptyJQueryObject(null, 'element')\n      }).toThrow(TypeError)\n    })\n\n    test('throws TypeError when passed undefined', () => {\n      expect(() => {\n        TypeChecker.checkNonEmptyJQueryObject(undefined, 'element')\n      }).toThrow(TypeError)\n    })\n\n    test('throws TypeError when passed a plain object', () => {\n      expect(() => {\n        TypeChecker.checkNonEmptyJQueryObject({}, 'element')\n      }).toThrow(TypeError)\n    })\n\n    test('throws ReferenceError when jQuery object contains no elements', () => {\n      const emptyElement = $('#non-existent')\n      expect(() => {\n        TypeChecker.checkNonEmptyJQueryObject(emptyElement, 'element')\n      }).toThrow(ReferenceError)\n      expect(() => {\n        TypeChecker.checkNonEmptyJQueryObject(emptyElement, 'element')\n      }).toThrow('Param element contains no elements')\n    })\n  })\n\n  describe('checkNonEmptyString', () => {\n    test('accepts a non-empty string', () => {\n      expect(() => {\n        TypeChecker.checkNonEmptyString('hello', 'myString')\n      }).not.toThrow()\n    })\n\n    test('throws TypeError when passed a non-string', () => {\n      expect(() => {\n        TypeChecker.checkNonEmptyString(123, 'myString')\n      }).toThrow(TypeError)\n      expect(() => {\n        TypeChecker.checkNonEmptyString(123, 'myString')\n      }).toThrow('Param myString must be a string')\n    })\n\n    test('throws TypeError when passed null', () => {\n      expect(() => {\n        TypeChecker.checkNonEmptyString(null, 'myString')\n      }).toThrow(TypeError)\n    })\n\n    test('throws TypeError when passed undefined', () => {\n      expect(() => {\n        TypeChecker.checkNonEmptyString(undefined, 'myString')\n      }).toThrow(TypeError)\n    })\n\n    test('throws RangeError when passed an empty string', () => {\n      expect(() => {\n        TypeChecker.checkNonEmptyString('', 'myString')\n      }).toThrow(RangeError)\n      expect(() => {\n        TypeChecker.checkNonEmptyString('', 'myString')\n      }).toThrow('Param myString cannot be empty string')\n    })\n  })\n\n  describe('checkObject', () => {\n    test('accepts a plain object', () => {\n      expect(() => {\n        TypeChecker.checkObject({}, 'myObject')\n      }).not.toThrow()\n    })\n\n    test('accepts an object with properties', () => {\n      expect(() => {\n        TypeChecker.checkObject({ key: 'value' }, 'myObject')\n      }).not.toThrow()\n    })\n\n    test('throws TypeError when passed a string', () => {\n      expect(() => {\n        TypeChecker.checkObject('string', 'myObject')\n      }).toThrow(TypeError)\n      expect(() => {\n        TypeChecker.checkObject('string', 'myObject')\n      }).toThrow('Param myObject is not an object')\n    })\n\n    test('throws TypeError when passed a number', () => {\n      expect(() => {\n        TypeChecker.checkObject(123, 'myObject')\n      }).toThrow(TypeError)\n    })\n\n    test('throws TypeError when passed an array', () => {\n      expect(() => {\n        TypeChecker.checkObject([1, 2, 3], 'myObject')\n      }).toThrow(TypeError)\n      expect(() => {\n        TypeChecker.checkObject([1, 2, 3], 'myObject')\n      }).toThrow('Param myObject is not an object')\n    })\n\n    test('throws TypeError when passed null', () => {\n      expect(() => {\n        TypeChecker.checkObject(null, 'myObject')\n      }).toThrow(TypeError)\n    })\n\n    test('throws TypeError when passed undefined', () => {\n      expect(() => {\n        TypeChecker.checkObject(undefined, 'myObject')\n      }).toThrow(TypeError)\n    })\n  })\n\n  describe('checkPositiveInteger', () => {\n    test('accepts zero', () => {\n      expect(() => {\n        TypeChecker.checkPositiveInteger(0, 'myNumber')\n      }).not.toThrow()\n    })\n\n    test('accepts positive integers', () => {\n      expect(() => {\n        TypeChecker.checkPositiveInteger(1, 'myNumber')\n      }).not.toThrow()\n      expect(() => {\n        TypeChecker.checkPositiveInteger(100, 'myNumber')\n      }).not.toThrow()\n    })\n\n    test('throws TypeError when passed a float', () => {\n      expect(() => {\n        TypeChecker.checkPositiveInteger(1.5, 'myNumber')\n      }).toThrow(TypeError)\n      expect(() => {\n        TypeChecker.checkPositiveInteger(1.5, 'myNumber')\n      }).toThrow('Param myNumber is not an integer')\n    })\n\n    test('throws TypeError when passed a string', () => {\n      expect(() => {\n        TypeChecker.checkPositiveInteger('123', 'myNumber')\n      }).toThrow(TypeError)\n    })\n\n    test('throws TypeError when passed NaN', () => {\n      expect(() => {\n        TypeChecker.checkPositiveInteger(NaN, 'myNumber')\n      }).toThrow(TypeError)\n    })\n\n    test('throws RangeError when passed a negative integer', () => {\n      expect(() => {\n        TypeChecker.checkPositiveInteger(-1, 'myNumber')\n      }).toThrow(RangeError)\n      expect(() => {\n        TypeChecker.checkPositiveInteger(-1, 'myNumber')\n      }).toThrow('Param myNumber cannot be negative')\n    })\n\n    test('throws RangeError when passed a large negative integer', () => {\n      expect(() => {\n        TypeChecker.checkPositiveInteger(-100, 'myNumber')\n      }).toThrow(RangeError)\n    })\n  })\n\n  describe('checkString', () => {\n    test('accepts a string', () => {\n      expect(() => {\n        TypeChecker.checkString('hello', 'myString')\n      }).not.toThrow()\n    })\n\n    test('accepts an empty string', () => {\n      expect(() => {\n        TypeChecker.checkString('', 'myString')\n      }).not.toThrow()\n    })\n\n    test('throws TypeError when passed a number', () => {\n      expect(() => {\n        TypeChecker.checkString(123, 'myString')\n      }).toThrow(TypeError)\n      expect(() => {\n        TypeChecker.checkString(123, 'myString')\n      }).toThrow('Param myString must be a string')\n    })\n\n    test('throws TypeError when passed null', () => {\n      expect(() => {\n        TypeChecker.checkString(null, 'myString')\n      }).toThrow(TypeError)\n    })\n\n    test('throws TypeError when passed undefined', () => {\n      expect(() => {\n        TypeChecker.checkString(undefined, 'myString')\n      }).toThrow(TypeError)\n    })\n\n    test('throws TypeError when passed an object', () => {\n      expect(() => {\n        TypeChecker.checkString({}, 'myString')\n      }).toThrow(TypeError)\n    })\n\n    test('throws TypeError when passed an array', () => {\n      expect(() => {\n        TypeChecker.checkString([], 'myString')\n      }).toThrow(TypeError)\n    })\n  })\n})\n"
  },
  {
    "path": "app/javascript/__tests__/validated_form.test.js",
    "content": "/* eslint-env jest */\n/**\n * @jest-environment jsdom\n */\n\nconst { Notifier } = require('../src/notifier.js')\nconst { NonDrivingContactMediumWarning } = require('../src/validated_form.js')\n\ndescribe('NonDrivingContactMediumWarning', () => {\n  let checkboxes\n  let drivingOption\n  let milesDrivenInput\n  let nonDrivingOptions\n  let notifier\n\n  beforeEach(() => {\n    document.body.innerHTML =\n`<form class=\"component-validated-form\">\n  <div class=\"field contact-medium form-group\">\n    <h5 classs=\"mb-3\"><label for=\"case_contact_medium_type\">b. Contact Medium</label></h5>\n    <input type=\"hidden\" name=\"case_contact[medium_type]\" value=\"\" autocomplete=\"off\">\n    <div class=\"form-check radio-style mb-20\">\n      <input class=\"form-check-input\" type=\"radio\" value=\"in-person\" name=\"case_contact[medium_type]\" id=\"case_contact_medium_type_in-person\">\n      <label class=\"form-check-label\" for=\"case_contact_medium_type_in-person\">In Person</label>\n    </div>\n    <div class=\"form-check radio-style mb-20\">\n      <input class=\"form-check-input\" type=\"radio\" value=\"text/email\" checked=\"checked\" name=\"case_contact[medium_type]\" id=\"case_contact_medium_type_textemail\">\n      <label class=\"form-check-label\" for=\"case_contact_medium_type_textemail\">Text/Email</label>\n    </div>\n\n    <div class=\"form-check radio-style mb-20\">\n      <input class=\"form-check-input\" type=\"radio\" value=\"video\" name=\"case_contact[medium_type]\" id=\"case_contact_medium_type_video\">\n      <label class=\"form-check-label\" for=\"case_contact_medium_type_video\">Video</label>\n    </div>\n\n    <div class=\"form-check radio-style mb-20\">\n      <input class=\"form-check-input\" type=\"radio\" value=\"voice-only\" name=\"case_contact[medium_type]\" id=\"case_contact_medium_type_voice-only\">\n      <label class=\"form-check-label\" for=\"case_contact_medium_type_voice-only\">Voice Only</label>\n    </div>\n\n    <div class=\"form-check radio-style mb-20\">\n      <input class=\"form-check-input\" type=\"radio\" value=\"letter\" name=\"case_contact[medium_type]\" id=\"case_contact_medium_type_letter\">\n      <label class=\"form-check-label\" for=\"case_contact_medium_type_letter\">Letter</label>\n    </div>\n  </div>\n\n  <div class=\"field miles-driven form-group\">\n    <h5 class=\"mb-3\"><label for=\"case_contact_miles_driven\">a. Miles Driven</label></h5>\n    <div class=\"input-style-1\">\n      <input class=\"form-control\" min=\"0\" max=\"10000\" type=\"number\" value=\"0\" name=\"case_contact[miles_driven]\" autocomplete=\"off\" id=\"case_contact_miles_driven\">\n    </div>\n  </div>\n</form>\n\n<div id=\"notifications\">\n  <div class=\"messages\">\n  </div>\n  <div id=\"async-waiting-indicator\" style=\"display: none\">\n    Saving <i class=\"load-spinner\"></i>\n  </div>\n  <div id=\"async-success-indicator\" class=\"success-notification\" style=\"display: none\">\n    Saved\n  </div>\n  <button id=\"toggle-minimize-notifications\" style=\"display: none;\">\n    <span>minimize notifications </span>\n    <span class=\"badge rounded-pill bg-success\" style=\"display: none;\"></span>\n    <span class=\"badge rounded-pill bg-warning\" style=\"display: none;\"></span>\n    <span class=\"badge rounded-pill bg-danger\" style=\"display: none;\"></span>\n    <i class=\"fa-solid fa-minus\"></i>\n  </button>\n</div>`\n    checkboxes = $('.contact-medium.form-group input:not([type=hidden])')\n    drivingOption = checkboxes.filter('#case_contact_medium_type_in-person')\n    milesDrivenInput = $('#case_contact_miles_driven')\n    nonDrivingOptions = checkboxes.not(drivingOption)\n    notifier = new Notifier($('#notifications'))\n  })\n\n  describe('constructor', () => {\n    test('Throws appropriate errors when initialized with values other than a valid jQuery object', () => {\n      expect(() => {\n        // eslint-disable-next-line no-new\n        new NonDrivingContactMediumWarning(3, notifier)\n      }).toThrow(TypeError)\n\n      expect(() => {\n        // eslint-disable-next-line no-new\n        new NonDrivingContactMediumWarning($('#non-existant'), notifier)\n      }).toThrow(ReferenceError)\n    })\n  })\n\n  describe('getWarningState', () => {\n    let component\n\n    beforeEach(() => {\n      component = new NonDrivingContactMediumWarning($('.contact-medium.form-group input:not([type=hidden]), #case_contact_miles_driven'), notifier)\n    })\n\n    test('returns the warning message if a non driving contact medium is selected and the miles driven count is > 0', () => {\n      expect(nonDrivingOptions.length).toBeGreaterThan(0)\n\n      milesDrivenInput.val(1)\n\n      nonDrivingOptions.each(function () {\n        const option = $(this)\n\n        option.trigger('click')\n\n        expect(component.getWarningState()).toBe('You requested driving reimbursement for a contact medium that typically does not involve driving. Are you sure that\\'s right?')\n      })\n    })\n\n    test('returns a falsy value if the driving contact medium is selected or the miles driven count is 0', () => {\n      expect(nonDrivingOptions.length).toBeGreaterThan(0)\n\n      milesDrivenInput.val(0)\n\n      nonDrivingOptions.each(function () {\n        const option = $(this)\n\n        option.trigger('click')\n\n        expect(component.getWarningState()).toBeFalsy()\n      })\n\n      milesDrivenInput.val(1)\n      drivingOption.trigger('click')\n\n      expect(component.getWarningState()).toBeFalsy()\n    })\n  })\n\n  describe('warningHighlightUI', () => {\n    let checkboxContainer\n    let component\n\n    beforeEach(() => {\n      $(() => {\n        checkboxContainer = $('.contact-medium.form-group')\n        component = new NonDrivingContactMediumWarning($('.contact-medium.form-group input:not([type=hidden]), #case_contact_miles_driven'), notifier)\n      })\n    })\n\n    test('when passed a truthy value, it makes the parent container for the checkboxes yellow and draws a yellow border around the miles driven input', (done) => {\n      $(() => {\n        try {\n          expect(checkboxContainer.css('background-color')).not.toBe('rgb(255, 248, 225)')\n          expect(milesDrivenInput.css('border')).not.toBe('2px solid rgb(255, 193, 7)')\n\n          component.warningHighlightUI('A warning message')\n\n          expect(checkboxContainer.css('background-color')).toBe('rgb(255, 248, 225)')\n          expect(milesDrivenInput.css('border')).toBe('2px solid rgb(255, 193, 7)')\n          done()\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n\n    test('when passed a falsy value, it removes the error highlighting', (done) => {\n      $(() => {\n        try {\n          expect(checkboxContainer.css('background-color')).not.toBe('rgb(255, 248, 225)')\n          expect(milesDrivenInput.css('border')).not.toBe('2px solid rgb(255, 193, 7)')\n\n          component.warningHighlightUI('A warning message')\n\n          expect(checkboxContainer.css('background-color')).toBe('rgb(255, 248, 225)')\n          expect(milesDrivenInput.css('border')).toBe('2px solid rgb(255, 193, 7)')\n\n          component.warningHighlightUI()\n\n          expect(checkboxContainer.css('background-color')).not.toBe('rgb(255, 248, 225)')\n          expect(milesDrivenInput.css('border')).not.toBe('2px solid rgb(255, 193, 7)')\n\n          done()\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n  })\n\n  describe('showUserWarning', () => {\n    let component\n    let notifierElement\n\n    beforeEach(() => {\n      $(() => {\n        component = new NonDrivingContactMediumWarning($('.contact-medium.form-group input:not([type=hidden]), #case_contact_miles_driven'), notifier)\n        notifierElement = $('#notifications')\n      })\n    })\n\n    test('it shows the user a warning through the notifier', (done) => {\n      $(() => {\n        try {\n          const warningText = 'Q~Au\\\\`FMET\"[\"8.JKB_M'\n\n          component.showUserWarning(warningText)\n\n          const notifications = notifierElement.find('.warning-notification')\n          expect(notifications[0].innerHTML).toContain(warningText)\n\n          done()\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n\n    test('idempotence test', (done) => {\n      $(() => {\n        try {\n          const warningText = 'Q~Au\\\\`FMET\"[\"8.JKB_M'\n\n          component.showUserWarning(warningText)\n          component.showUserWarning(warningText)\n\n          const notifications = notifierElement.find('.warning-notification')\n          expect(notifications[0].innerHTML).toContain(warningText)\n          expect(notifications.length).toBe(1)\n\n          done()\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n  })\n\n  describe('removeUserWarning', () => {\n    let component\n    let notifierElement\n\n    beforeEach(() => {\n      $(() => {\n        component = new NonDrivingContactMediumWarning($('.contact-medium.form-group input:not([type=hidden]), #case_contact_miles_driven'), notifier)\n        notifierElement = $('#notifications')\n      })\n    })\n\n    test('it removes the user warning if it is present', (done) => {\n      $(() => {\n        try {\n          const warningText = 'Q~Au\\\\`FMET\"[\"8.JKB_M'\n\n          component.showUserWarning(warningText)\n          const componentNotification = component.warningNotification\n\n          let notifications = notifierElement.find('.warning-notification')\n          expect(notifications[0].innerHTML).toContain(warningText)\n          expect(componentNotification.isDismissed()).toBe(false)\n\n          component.removeUserWarning()\n\n          notifications = notifierElement.find('.warning-notification')\n          expect(notifications.length).toBe(0)\n          expect(componentNotification.isDismissed()).toBe(true)\n          done()\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n\n    test('idempotence test', (done) => {\n      $(() => {\n        try {\n          const warningText = 'Q~Au\\\\`FMET\"[\"8.JKB_M'\n\n          component.showUserWarning(warningText)\n          const componentNotification = component.warningNotification\n\n          let notifications = notifierElement.find('.warning-notification')\n          expect(notifications[0].innerHTML).toContain(warningText)\n          expect(componentNotification.isDismissed()).toBe(false)\n\n          component.removeUserWarning()\n          component.removeUserWarning()\n\n          notifications = notifierElement.find('.warning-notification')\n          expect(notifications.length).toBe(0)\n          expect(componentNotification.isDismissed()).toBe(true)\n          done()\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n  })\n\n  describe('showWarningConfirmation', () => {\n    let component\n\n    beforeEach(() => {\n      $(() => {\n        component = new NonDrivingContactMediumWarning($('.contact-medium.form-group input:not([type=hidden]), #case_contact_miles_driven'), notifier)\n      })\n    })\n\n    test('it adds the required checkbox with the warning label', (done) => {\n      $(() => {\n        try {\n          expect($('input#warning-non-driving-contact-medium-check[required=true]').length).toBe(0)\n\n          component.showWarningConfirmation()\n\n          expect($('input#warning-non-driving-contact-medium-check[required=true]').length).toBe(1)\n          expect($('label[for=warning-non-driving-contact-medium-check]').text()).toBe('I\\'m sure I drove for this contact medium.')\n          done()\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n\n    test('idempotence test', (done) => {\n      $(() => {\n        try {\n          expect($('input#warning-non-driving-contact-medium-check[required=true]').length).toBe(0)\n\n          component.showWarningConfirmation()\n          component.showWarningConfirmation()\n\n          expect($('input#warning-non-driving-contact-medium-check[required=true]').length).toBe(1)\n          expect($('label[for=warning-non-driving-contact-medium-check]').text()).toBe('I\\'m sure I drove for this contact medium.')\n          done()\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n  })\n\n  describe('removeWarningConfirmation', () => {\n    let component\n\n    beforeEach(() => {\n      $(() => {\n        component = new NonDrivingContactMediumWarning($('.contact-medium.form-group input:not([type=hidden]), #case_contact_miles_driven'), notifier)\n      })\n    })\n\n    test('it removes the required checkbox with the warning label', (done) => {\n      $(() => {\n        try {\n          component.showWarningConfirmation()\n\n          expect($('input#warning-non-driving-contact-medium-check[required=true]').length).toBe(1)\n          expect($('label[for=warning-non-driving-contact-medium-check]').text()).toBe('I\\'m sure I drove for this contact medium.')\n\n          component.removeWarningConfirmation()\n\n          expect($('input#warning-non-driving-contact-medium-check[required=true]').length).toBe(0)\n          done()\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n\n    test('idempotence test', (done) => {\n      $(() => {\n        try {\n          component.showWarningConfirmation()\n\n          expect($('input#warning-non-driving-contact-medium-check[required=true]').length).toBe(1)\n          expect($('label[for=warning-non-driving-contact-medium-check]').text()).toBe('I\\'m sure I drove for this contact medium.')\n\n          component.removeWarningConfirmation()\n          component.removeWarningConfirmation()\n\n          expect($('input#warning-non-driving-contact-medium-check[required=true]').length).toBe(0)\n          done()\n        } catch (error) {\n          done(error)\n        }\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "app/javascript/all_casa_admin.js",
    "content": "require('./src/all_casa_admin/tables')\nrequire('./src/all_casa_admin/patch_notes')\nrequire('./src/session_timeout_poller.js')\nrequire('./src/display_app_metric.js')\n"
  },
  {
    "path": "app/javascript/application.js",
    "content": "/* global window */\nimport './jQueryGlobalizer.js'\nimport '@hotwired/turbo-rails'\nimport 'bootstrap'\nimport 'bootstrap-select'\nimport './sweet-alert-confirm.js'\nimport './controllers'\nimport 'trix'\nimport '@rails/actiontext'\nimport './datatable.js'\nTurbo.session.drive = false\n\nrequire('datatables.net-dt')(null, window.jQuery) // First parameter is the global object. Defaults to window if null\nrequire('select2')(window.jQuery)\nrequire('@rails/ujs').start()\nrequire('@rails/activestorage').start()\nrequire('bootstrap-datepicker')\nrequire('./src/add_to_calendar_button')\nrequire('./src/case_contact')\nrequire('./src/case_emancipation')\nrequire('./src/casa_case')\nrequire('./src/new_casa_case')\nrequire('./src/dashboard')\nrequire('./src/emancipations')\nrequire('./src/import')\nrequire('./src/password_confirmation')\nrequire('./src/read_more')\nrequire('./src/reimbursements')\nrequire('./src/reports')\nrequire('./src/require_communication_preference')\nrequire('./src/select')\nrequire('./src/tooltip')\nrequire('./src/time_zone')\nrequire('./src/session_timeout_poller.js')\nrequire('./src/display_app_metric.js')\nrequire('./src/casa_org')\nrequire('./src/sms_reactivation_toggle')\nrequire('./src/validated_form')\nrequire('./src/learning_hours')\n"
  },
  {
    "path": "app/javascript/controllers/alert_controller.js",
    "content": "import { Controller } from '@hotwired/stimulus'\nimport Swal from 'sweetalert2'\n\n// Connects to data-controller=\"alert\"\nexport default class extends Controller {\n  // Any of these can be overridden from the view that calls this controller\n  static values = {\n    ignore: { type: Boolean, default: false },\n    message: { type: String, default: 'Are you sure?' },\n    title: { type: String, default: 'Confirm your choice' },\n    icon: { type: String, default: 'warning' },\n    showCloseButton: { type: Boolean, default: false },\n    showCancelButton: { type: Boolean, default: true },\n    focusConfirm: { type: Boolean, default: false },\n    confirmColor: { type: String, default: '#d50100' },\n    cancelColor: { type: String, default: '#4a6cf7' },\n    confirmText: { type: String, default: 'Ok' },\n    cancelText: { type: String, default: 'Cancel' }\n  }\n\n  confirm (e) {\n    if (this.ignoreValue) return\n\n    e.preventDefault()\n\n    const text = this.messageValue\n    Swal.fire({\n      icon: this.iconValue,\n      title: this.titleValue,\n      text,\n      showCloseButton: this.showCloseButtonValue,\n      showCancelButton: this.showCancelButtonValue,\n      focusConfirm: this.focusConfirmValue,\n\n      confirmButtonColor: this.confirmColorValue,\n      cancelButtonColor: this.cancelColorValue,\n\n      confirmButtonText: this.confirmTextValue,\n      cancelButtonText: this.cancelTextValue\n    }).then((result) => {\n      if (result.isConfirmed) {\n        window.location.href = e.target.href\n      }\n    })\n  }\n}\n"
  },
  {
    "path": "app/javascript/controllers/application.js",
    "content": "import { Application } from '@hotwired/stimulus'\n\nconst application = Application.start()\n\n// Configure Stimulus development experience\napplication.debug = false\nwindow.Stimulus = application\n\nexport { application }\n"
  },
  {
    "path": "app/javascript/controllers/autosave_controller.js",
    "content": "import { Controller } from '@hotwired/stimulus'\nimport { debounce } from 'lodash'\n\nexport default class extends Controller {\n  static targets = ['form', 'alert']\n  static values = {\n    delay: {\n      type: Number,\n      default: 1000 // milliseconds to delay form submission\n    },\n    clearDelay: {\n      type: Number,\n      default: 3000 // milliseconds to delay hiding alert\n    }\n  }\n\n  static classes = ['goodAlert', 'badAlert']\n\n  connect () {\n    this.visibleClass = 'visible'\n    this.hiddenClass = 'invisible'\n    this.save = debounce(this.save, this.delayValue).bind(this)\n  }\n\n  save () {\n    this.autosaveAlert()\n    this.submitForm()\n  }\n\n  submitForm () {\n    fetch(this.formTarget.action, {\n      method: 'POST',\n      headers: { Accept: 'application/json' },\n      body: new FormData(this.formTarget)\n    }).then(response => {\n      if (response.ok) {\n        this.goodAlert()\n        const event = new CustomEvent('autosave:success', { bubbles: true }) // eslint-disable-line no-undef\n        this.element.dispatchEvent(event)\n      } else {\n        return Promise.reject(response)\n      }\n    }).catch(error => {\n      console.error(error.status, error.statusText)\n      switch (error.status) {\n        case 504:\n          this.badAlert('Connection lost: Changes will be saved when connection is restored.')\n          break\n        case 422:\n          error.json().then(errorJson => {\n            console.error('errorJson', errorJson)\n            const errorMessage = errorJson.join('. ')\n            this.badAlert(`Unable to save: ${errorMessage}`)\n          })\n          break\n        case 401:\n          this.badAlert('You must be signed in to save changes.')\n          break\n        default:\n          this.badAlert('Error: Unable to save changes.')\n      }\n    })\n  }\n\n  autosaveAlert () {\n    this.removeBadAlert()\n    this.alertTargets.forEach(alertTarget => {\n      alertTarget.innerHTML = 'Autosaving...'\n    })\n    this.revealAlert()\n  }\n\n  goodAlert () {\n    this.removeBadAlert()\n    this.alertTargets.forEach(alertTarget => {\n      alertTarget.innerHTML = 'Saved!'\n    })\n  }\n\n  removeBadAlert () {\n    this.alertTargets.forEach(alertTarget => {\n      alertTarget.classList.add(this.goodAlertClass)\n      alertTarget.classList.remove(this.badAlertClass)\n    })\n  }\n\n  badAlert (message) {\n    this.alertTargets.forEach(alertTarget => {\n      alertTarget.classList.remove(this.goodAlertClass)\n      alertTarget.classList.add(this.badAlertClass)\n      alertTarget.innerHTML = message\n    })\n  }\n\n  hideAlert () {\n    this.alertTargets.forEach(alertTarget => {\n      alertTarget.classList.add(this.hiddenClass)\n      alertTarget.classList.remove(this.visibleClass)\n    })\n  }\n\n  revealAlert (hide = true) {\n    this.alertTargets.forEach(alertTarget => {\n      alertTarget.classList.remove(this.hiddenClass)\n      alertTarget.classList.add(this.visibleClass)\n    })\n    if (hide) {\n      setTimeout(() => {\n        this.hideAlert()\n      }, this.clearDelayValue)\n    }\n  }\n}\n"
  },
  {
    "path": "app/javascript/controllers/casa_nested_form_controller.js",
    "content": "import NestedForm from '@stimulus-components/rails-nested-form'\n\n/**\n * Allows nested forms to be used with the autosave controller,\n * creating and destroying records so that the autosave updates do not attempt\n * to create/destroy same nested records repeatedly.\n *\n * Extends stimulus-rails-nested-form.\n * https://www.stimulus-components.com/docs/stimulus-rails-nested-form/\n * add() & remove() are standard, so can be used as stimulus-rails-nested-form.\n * No values are necessary in that case.\n *\n * Created for the the CaseContact form (details), see its usage there.\n *\n * Connects to data-controller=\"casa-nested-form\"\n */\nexport default class extends NestedForm {\n  static values = {\n    route: String, // path to create/destroy a record, e.g. \"/contact_topic_answers\"\n    parentName: String, // snake case name of parent model, e.g. \"case_contact\"\n    parentId: Number, // id of record this form is nested within e.g. @case_contact.id\n    modelName: String, // name of nested form, e.g. \"contact_topic_answer\"\n    requiredFields: { // fields required (belongs_to etc...) to pass validation (e.g. contact_topic_id)\n      type: Array, default: []\n    }\n  }\n\n  connect () {\n    super.connect()\n\n    const headers = new Headers()\n    headers.append('Content-Type', 'application/json')\n    headers.append('Accept', 'application/json')\n    const tokenTag = document.querySelector('meta[name=\"csrf-token\"]')\n    if (tokenTag) { // does not exist in test environment\n      headers.append('X-CSRF-Token', tokenTag.content)\n    }\n    this.headers = headers\n\n    document.addEventListener('autosave:success', this.onAutosaveSuccess)\n  }\n\n  disconnect () {\n    document.removeEventListener('autosave:success', this.onAutosaveSuccess)\n  }\n\n  getRecordId (wrapper) {\n    const recordInput = wrapper.querySelector('input[name*=\"id\"]')\n    if (!recordInput) {\n      console.warn('id input not found for nested item:', wrapper)\n      return ''\n    }\n    return recordInput.value\n  }\n\n  /* removes any items that have been marked as _destroy: true */\n  /* must be marked for destroy elsewhere, see case_contact_form_controller clearExpenses() */\n  onAutosaveSuccess = (_e) => {\n    const wrappers = this.element.querySelectorAll(this.wrapperSelectorValue)\n    wrappers.forEach(wrapper => {\n      const destroyInput = wrapper.querySelector(\"input[name*='_destroy']\")\n      if (!destroyInput) {\n        console.warn('Destroy input not found for nested item:', wrapper)\n        return\n      }\n      if (destroyInput.value === '1') {\n        // autosave has already destroyed the record, remove the element from DOM\n        wrapper.remove()\n      }\n    })\n  }\n\n  dispatchChangeEvent (action) {\n    const detail = { modelName: this.modelNameValue, action }\n    const changeEvent = new CustomEvent('casa-nested-form:change', { detail, bubbles: true }) // eslint-disable-line no-undef\n    document.dispatchEvent(changeEvent)\n  }\n\n  /* Adds item to the form. Item will not be created until form submission. */\n  add (e) {\n    super.add(e)\n    this.dispatchChangeEvent('add')\n  }\n\n  /* Creates a new record for the added item (before submission). */\n  addAndCreate (e) {\n    this.add(e)\n    const items = this.element.querySelectorAll(this.wrapperSelectorValue)\n    const addedItem = items[items.length - 1]\n    // childIndex will be 0,1,... for items at page load, timestamps for items added to form.\n    const childIndex = addedItem.dataset.childIndex\n    const domIdBase = `${this.parentNameValue}_${this.modelNameValue}s_attributes_${childIndex}`\n\n    const fields = {}\n    fields[`${this.parentNameValue}_id`] = this.parentIdValue\n\n    this.requiredFieldsValue.forEach(field => {\n      const fieldId = `${domIdBase}_${field}`\n      const fieldEl = document.querySelector(`#${fieldId}`)\n      if (!fieldEl) {\n        console.warn('Aborting: Field not found:', fieldId)\n        return\n      }\n      fields[field] = fieldEl.value\n    })\n\n    if (Object.values(fields).some(value => value === '')) {\n      console.warn('Aborting: Required field empty:', fields)\n      return\n    }\n\n    const body = {}\n    body[this.modelNameValue] = fields\n\n    fetch(this.routeValue, {\n      method: 'POST',\n      headers: this.headers,\n      body: JSON.stringify(body)\n    })\n      .then(response => {\n        if (response.ok) {\n          return response.json()\n        } else {\n          return Promise.reject(response)\n        }\n      })\n      .then(data => {\n        const idAttr = `${domIdBase}_id`\n        const idField = document.querySelector(`#${idAttr}`)\n        idField.setAttribute('value', data.id)\n        addedItem.dataset.newRecord = false\n      })\n      .catch(error => {\n        console.error(error.status, error.statusText)\n        error.json().then(errorJson => {\n          console.error('errorJson', errorJson)\n        })\n      })\n  }\n\n  /* Removes item from the form. Will not destroy record until form submission. */\n  remove (e) {\n    super.remove(e)\n    this.dispatchChangeEvent('remove')\n  }\n\n  /* Destroys a record when removing the item (before submission). */\n  destroyAndRemove (e) {\n    const wrapper = e.target.closest(this.wrapperSelectorValue)\n    const recordId = this.getRecordId(wrapper)\n    if (wrapper.dataset.newRecord === 'false' && (recordId.length > 0)) {\n      fetch(`${this.routeValue}/${recordId}`, {\n        method: 'DELETE',\n        headers: this.headers\n      })\n        .then(response => {\n          if (response.ok) {\n            // destroy successful; remove as if new record\n            wrapper.dataset.newRecord = true\n            this.remove(e)\n          } else {\n            return Promise.reject(response)\n          }\n        })\n        .catch(error => {\n          console.error(error.status, error.statusText)\n          if (error.status === 404) {\n            // NOT FOUND: already deleted -> remove as if new record\n            wrapper.dataset.newRecord = true\n            this.remove(e)\n          } else {\n            error.json().then(errorJson => {\n              console.error('errorJson', errorJson)\n            })\n          }\n        })\n    } else {\n      console.warn(\n        'Conflicting information while trying to destroy record:', {\n          wrapperDatasetNewRecord: wrapper.dataset.newRecord,\n          recordId\n        }\n      )\n      this.remove(e) // treat as typical removal\n    }\n  }\n\n  confirmDestroyAndRemove (e) {\n    const text = 'Are you sure you want to remove this item?'\n    if (window.confirm(text)) {\n      this.destroyAndRemove(e)\n    }\n  }\n}\n"
  },
  {
    "path": "app/javascript/controllers/case_contact_form_controller.js",
    "content": "import { Controller } from '@hotwired/stimulus'\n\n// Connects to data-controller=\"case-contact-form\"\nexport default class extends Controller {\n  static targets = [\n    'addTopicButton',\n    'contactTopicSelect',\n    'expenseDestroy',\n    'milesDriven',\n    'volunteerAddress',\n    'reimbursementForm',\n    'wantDrivingReimbursement'\n  ]\n\n  connect () {\n    this.contactTopicCount = 0\n    if (this.hasContactTopicSelectTarget) {\n      this.contactTopicCount = (this.contactTopicSelectTargets[0].querySelectorAll('option').length) - 1\n      this.onContactTopicSelect()\n      this.toggleAddAnotherTopicButton()\n    }\n\n    this.setReimbursementFormVisibility()\n    document.addEventListener('casa-nested-form:change', (e) => this.onNestedFormChange(e))\n  }\n\n  toggleAddAnotherTopicButton () {\n    const currentCount = this.contactTopicSelectTargets.length\n    if (currentCount < this.contactTopicCount) {\n      this.addTopicButtonTarget.disabled = false\n    } else {\n      this.addTopicButtonTarget.disabled = true\n    }\n  }\n\n  onNestedFormChange (e) {\n    const { modelName } = e.detail\n    if (modelName === 'contact_topic_answer') {\n      this.onContactTopicSelect()\n      this.toggleAddAnotherTopicButton()\n    }\n  }\n\n  onContactTopicSelect (_e) {\n    const selectedValues = this.contactTopicSelectTargets.map(el => el.value)\n    this.contactTopicSelectTargets.forEach(el => {\n      const options = el.querySelectorAll('option')\n      options.forEach(option => {\n        if (selectedValues.includes(option.value)) {\n          if (option.selected) {\n            option.disabled = false\n          } else {\n            option.disabled = true\n          }\n        } else {\n          option.disabled = false\n        }\n      })\n    })\n  }\n\n  clearExpenses = () => {\n    // mark as _destroy: true. autosave has already created the records.\n    // if autosaved again, nested form controller will remove destroy: true items\n    // if the form is submitted, expense will be destroyed.\n    this.expenseDestroyTargets.forEach(el => (el.value = '1'))\n  }\n\n  clearMileage = () => {\n    this.milesDrivenTarget.value = 0\n    this.volunteerAddressTarget.value = ''\n  }\n\n  setReimbursementFormVisibility = () => {\n    if (this.wantDrivingReimbursementTarget.checked) {\n      this.reimbursementFormTarget.classList.remove('d-none')\n      this.expenseDestroyTargets.forEach(el => (el.value = '0'))\n    } else {\n      this.clearExpenses()\n      this.clearMileage()\n      this.reimbursementFormTarget.classList.add('d-none')\n    }\n  }\n}\n"
  },
  {
    "path": "app/javascript/controllers/court_order_form_controller.js",
    "content": "import NestedForm from '@stimulus-components/rails-nested-form'\nimport Swal from 'sweetalert2'\n\nexport default class extends NestedForm {\n  //\n  static targets = ['selectedCourtOrder']\n\n  remove (e) {\n    const wrapper = e.target.closest(this.wrapperSelectorValue)\n    if (wrapper.dataset.newRecord !== 'true' && wrapper.dataset.type === 'COURT_ORDER') {\n      this.removeCourtOrderWithConfirmation(e, wrapper)\n    } else {\n      super.remove(e)\n    }\n  }\n\n  add (e) {\n    super.add(e)\n    const selectedValue = $(this.selectedCourtOrderTarget).val()\n\n    if (selectedValue !== '') {\n      const $textarea = $('#court-orders-list-container .court-order-entry:last textarea.court-order-text-entry')\n      $textarea.val(selectedValue)\n    }\n  }\n\n  removeCourtOrderWithConfirmation (e, wrapper) {\n    const text = 'Are you sure you want to remove this court order? Doing so will ' +\n      'delete all records of it unless it was included in a previous court report.'\n    Swal.fire({\n      icon: 'warning',\n      title: 'Delete court order?',\n      text,\n      showCloseButton: true,\n      showCancelButton: true,\n      focusConfirm: false,\n\n      confirmButtonColor: '#d33',\n      cancelButtonColor: '#39c',\n\n      confirmButtonText: 'Delete',\n      cancelButtonText: 'Go back'\n    }).then((result) => {\n      if (result.isConfirmed) {\n        this.removeCourtOrder(e, wrapper)\n      }\n    })\n  }\n\n  removeCourtOrder (e, wrapper) {\n    super.remove(e)\n    wrapper.classList.remove('d-flex')\n  }\n}\n"
  },
  {
    "path": "app/javascript/controllers/disable_form_controller.js",
    "content": "import { Controller } from '@hotwired/stimulus'\n\nexport default class extends Controller {\n  static targets = ['submitButton', 'input']\n  static values = {\n    unallowed: { type: Array }\n  }\n\n  static classes = ['disabled', 'enabled']\n\n  validate () {\n    let invalid = false\n    this.inputTargets.forEach(input => {\n      if (this.unallowedValue.includes(input.value)) {\n        invalid = true\n      }\n    })\n\n    if (invalid) {\n      this.submitButtonTarget.disabled = true\n      this.submitButtonTarget.classList.add(this.disabledClass)\n      this.submitButtonTarget.classList.remove(...this.enabledClasses)\n    } else {\n      this.submitButtonTarget.disabled = false\n      this.submitButtonTarget.classList.remove(this.disabledClass)\n      this.submitButtonTarget.classList.add(...this.enabledClasses)\n    }\n  }\n}\n"
  },
  {
    "path": "app/javascript/controllers/dismiss_controller.js",
    "content": "import { Controller } from '@hotwired/stimulus'\n\nexport default class extends Controller {\n  static targets = ['element']\n  static values = {\n    url: String\n  }\n\n  dismiss (event) {\n    event.preventDefault()\n\n    fetch(this.urlValue)\n      .then(response => response.json())\n      .then(data => {\n        if (data.status === 'ok') {\n          this.elementTarget.classList.add('d-none')\n        }\n      })\n  }\n}\n"
  },
  {
    "path": "app/javascript/controllers/hello_controller.js",
    "content": "import { Controller } from '@hotwired/stimulus'\n\nexport default class extends Controller {\n  connect () {\n    console.log('Stimulus is working!')\n  }\n}\n"
  },
  {
    "path": "app/javascript/controllers/icon_toggle_controller.js",
    "content": "import { Controller } from '@hotwired/stimulus'\n\nexport default class extends Controller {\n  static targets = ['icon', 'margin']\n  static values = {\n    icons: Array\n  }\n\n  toggle () {\n    this.iconsValue.forEach((icon) => {\n      this.iconTarget.classList.toggle(icon)\n    })\n    this.marginTarget.classList.toggle('mb-3')\n  }\n}\n"
  },
  {
    "path": "app/javascript/controllers/index.js",
    "content": "// This file is auto-generated by ./bin/rails stimulus:manifest:update\n// Run that command whenever you add a new controller or create them with\n// ./bin/rails generate stimulus controllerName\nimport RailsNestedForm from '@stimulus-components/rails-nested-form'\n\nimport { application } from './application'\n\nimport AlertController from './alert_controller'\n\nimport AutosaveController from './autosave_controller'\n\nimport CasaNestedFormController from './casa_nested_form_controller'\n\nimport CaseContactFormController from './case_contact_form_controller'\n\nimport CourtOrderFormController from './court_order_form_controller'\n\nimport DisableFormController from './disable_form_controller'\n\nimport DismissController from './dismiss_controller'\n\nimport HelloController from './hello_controller'\n\nimport IconToggleController from './icon_toggle_controller'\n\nimport MultipleSelectController from './multiple_select_controller'\n\nimport NavbarController from './navbar_controller'\n\nimport SelectAllController from './select_all_controller'\n\nimport SidebarController from './sidebar_controller'\n\nimport SidebarGroupController from './sidebar_group_controller'\n\nimport TruncatedTextController from './truncated_text_controller'\n\napplication.register('nested-form', RailsNestedForm)\napplication.register('alert', AlertController)\napplication.register('autosave', AutosaveController)\napplication.register('casa-nested-form', CasaNestedFormController)\napplication.register('case-contact-form', CaseContactFormController)\napplication.register('court-order-form', CourtOrderFormController)\napplication.register('disable-form', DisableFormController)\napplication.register('dismiss', DismissController)\napplication.register('hello', HelloController)\napplication.register('icon-toggle', IconToggleController)\napplication.register('multiple-select', MultipleSelectController)\napplication.register('navbar', NavbarController)\napplication.register('select-all', SelectAllController)\napplication.register('sidebar', SidebarController)\napplication.register('sidebar-group', SidebarGroupController)\napplication.register('truncated-text', TruncatedTextController)\n"
  },
  {
    "path": "app/javascript/controllers/multiple_select_controller.js",
    "content": "import { Controller } from '@hotwired/stimulus'\nimport TomSelect from 'tom-select'\n\nexport default class extends Controller {\n  static targets = ['select', 'option', 'item', 'hiddenItem', 'selectAllOption']\n  static values = {\n    options: Array,\n    selectedItems: Array,\n    withOptions: Boolean,\n    placeholderTerm: {\n      type: String,\n      default: 'contact(s)'\n    },\n    showAllOption: Boolean\n  }\n\n  connect () {\n    if (this.withOptionsValue) {\n      this.createMultiSelectWithOptionGroups()\n    } else {\n      this.createBasicMultiSelect()\n    }\n  }\n\n  createBasicMultiSelect () {\n    /* eslint-disable no-new */\n    new TomSelect(this.selectTarget, {\n      plugins: {\n        remove_button: {\n          title: 'Remove this item'\n        }\n      }\n    })\n  }\n\n  createMultiSelectWithOptionGroups () {\n    const optionTemplate = this.optionTarget.innerHTML\n    const itemTemplate = this.itemTarget.innerHTML\n    const placeholder = `Select or search ${this.placeholderTermValue}`\n\n    const showAllOptionCheck = this.showAllOptionValue\n    const hiddenItemTemplate = showAllOptionCheck && this.hiddenItemTarget && this.hiddenItemTarget.innerHTML\n    const showAllOptionTemplate = showAllOptionCheck && this.selectAllOptionTarget && this.selectAllOptionTarget.innerHTML\n\n    // orderedOptionVals is of type (\" \" | number)[] - the \" \" could appear\n    // because using it as the value for the select/unselect all option\n    let orderedOptionVals = this.optionsValue.map(opt => opt.value)\n    if (showAllOptionCheck) {\n      // using \" \" as value instead of \"\" bc tom-select doesn't init the \"\" in the item list\n      orderedOptionVals = [' '].concat(orderedOptionVals)\n    }\n\n    const hasInitialItems = Array.isArray(this.selectedItemsValue) && this.selectedItemsValue.length\n    // initItems: number[], possibly empty\n    let initItems = this.selectedItemsValue\n    if (showAllOptionCheck) {\n      const emptyItem = [' ']\n      initItems = hasInitialItems ? emptyItem.concat(this.selectedItemsValue) : orderedOptionVals\n    }\n\n    const dropdownOptions = showAllOptionCheck\n      ? [{ text: 'Select/Unselect all', subtext: '', value: ' ', group: '' }].concat(this.optionsValue)\n      : this.optionsValue\n\n    /* eslint-disable no-new */\n    new TomSelect(this.selectTarget, {\n      onItemRemove: function (value) {\n        if (value === ' ') {\n          this.clear()\n        }\n      },\n      onItemAdd: function (value) {\n        this.setTextboxValue('')\n        this.refreshOptions()\n\n        if (value === ' ') {\n          this.addItems(orderedOptionVals)\n        }\n      },\n      plugins: {\n        remove_button: {\n          title: 'Remove this item',\n          className: 'btn text-white rounded-circle',\n          label: '<i class=\"lni lni-cross-circle\"></i>'\n        },\n        checkbox_options: {\n          checkedClassNames: ['form-check-input', 'form-check-input--checked'],\n          uncheckedClassNames: ['form-check-input', 'form-check-input--unchecked']\n        }\n      },\n      options: dropdownOptions,\n      items: initItems,\n      placeholder,\n      hidePlaceholder: true,\n      searchField: ['text', 'group'],\n      render: {\n        option: function (data, escape) {\n          let html\n\n          if (showAllOptionCheck && data && data.value === ' ') {\n            html = showAllOptionTemplate.replace(/DATA_LABEL/g, escape(data.text))\n          } else {\n            html = optionTemplate.replace(/DATA_LABEL/g, escape(data.text))\n            html = html.replace(/DATA_SUB_TEXT/g, escape(data.subtext))\n          }\n          return html\n        },\n        item: function (data, escape) {\n          return showAllOptionCheck && data.value === ' ' ? hiddenItemTemplate : itemTemplate.replace(/DATA_LABEL/g, escape(data.text))\n        }\n      }\n    })\n  }\n}\n"
  },
  {
    "path": "app/javascript/controllers/navbar_controller.js",
    "content": "import { Controller } from '@hotwired/stimulus'\n\nexport default class extends Controller {\n  static outlets = ['sidebar']\n\n  click () {\n    // This simulates a click action on the sidebar-controller\n    this.sidebarOutlet.click()\n  }\n}\n"
  },
  {
    "path": "app/javascript/controllers/select_all_controller.js",
    "content": "import { Controller } from '@hotwired/stimulus'\n\n// Connects to data-controller=\"select-all\"\nexport default class extends Controller {\n  static targets = ['checkboxAll', 'checkbox', 'button', 'buttonLabel']\n  static values = {\n    allChecked: { type: Boolean, default: false },\n    buttonLabel: { type: String }\n  }\n\n  static classes = ['hidden']\n\n  toggleAll () {\n    this.allCheckedValue = !this.allCheckedValue\n\n    this.checkboxTargets.forEach(checkbox => {\n      checkbox.checked = this.allCheckedValue\n    })\n\n    this.toggleButton()\n  }\n\n  toggleSingle () {\n    this.toggleCheckedAll()\n    this.toggleButton()\n  }\n\n  toggleCheckedAll () {\n    const numChecked = this.getNumberChecked()\n    const numTotal = this.getTotalCheckboxes()\n\n    this.allCheckedValue = numChecked === numTotal\n    this.checkboxAllTarget.checked = this.allCheckedValue\n\n    if (numChecked === 0) {\n      this.checkboxAllTarget.indeterminate = false\n    } else {\n      this.checkboxAllTarget.indeterminate = numChecked < numTotal\n    }\n  }\n\n  toggleButton () {\n    if (this.hasButtonTarget) {\n      const numChecked = this.getNumberChecked()\n      if (numChecked > 0) {\n        this.setButtonLabel(numChecked)\n\n        this.buttonTarget.classList.remove(this.hiddenClass)\n      } else {\n        this.buttonTarget.classList.add(this.hiddenClass)\n      }\n    }\n  }\n\n  setButtonLabel (numChecked) {\n    if (this.hasButtonLabelTarget) {\n      let label = this.buttonLabelValue\n\n      if (numChecked > 1) {\n        label += 's'\n      }\n      label += ' (' + numChecked + ')'\n\n      this.buttonLabelTarget.innerHTML = label\n    }\n  }\n\n  getNumberChecked () {\n    return this.checkboxTargets.filter((checkbox) => checkbox.checked).length\n  }\n\n  getTotalCheckboxes () {\n    return this.checkboxTargets.length\n  }\n}\n"
  },
  {
    "path": "app/javascript/controllers/sidebar_controller.js",
    "content": "import { Controller } from '@hotwired/stimulus'\n\nexport default class extends Controller {\n  static targets = ['sidebar', 'menu', 'logo', 'linkTitle', 'groupList']\n  static values = {\n    open: Boolean,\n    breakpoint: { type: Number, default: 768 }\n  }\n\n  static outlets = ['sidebar-group']\n\n  click () {\n    this.openValue = !this.openValue\n    this.toggleSidebar()\n    if (this.isNotMobile()) {\n      this.toggleLinks()\n      const mainWrapper = document.querySelector('.main-wrapper')\n      mainWrapper.classList.toggle('active')\n    } else {\n      this.toggleOverlay()\n    }\n  }\n\n  hoverOn () {\n    this.toggleHover()\n  }\n\n  hoverOff () {\n    this.toggleHover()\n  }\n\n  toggleHover () {\n    if (!this.openValue && this.isNotMobile()) {\n      this.toggleSidebar()\n      this.toggleLinks()\n    }\n  }\n\n  toggleSidebar () {\n    this.sidebarTarget.classList.toggle('active')\n  }\n\n  toggleLinks () {\n    this.linkTitleTargets.forEach((target) => {\n      target.classList.toggle('d-none')\n    })\n    this.groupListTargets.forEach((list) => {\n      list.classList.toggle('nav-item-has-children')\n    })\n    this.logoTarget.classList.toggle('d-none')\n  }\n\n  toggleOverlay () {\n    const overlay = document.querySelector('.overlay')\n    overlay.classList.toggle('active')\n  }\n\n  isNotMobile () {\n    return window.innerWidth >= this.breakpointValue\n  }\n}\n"
  },
  {
    "path": "app/javascript/controllers/sidebar_group_controller.js",
    "content": "import { Controller } from '@hotwired/stimulus'\n\nexport default class extends Controller {\n  static targets = ['title', 'list', 'link']\n\n  connect () {\n    const performToggleShow = this.linkTargets.find((link) => {\n      return link.classList.contains('active')\n    }) || this.isAnchorGroupPage()\n\n    if (performToggleShow) {\n      this.toggleShow()\n    }\n\n    if (this.isAnchorGroupPage()) {\n      this.initializeMenuHighlight()\n    }\n  }\n\n  // Expands list if a link is active\n  toggleShow () {\n    this.titleTarget.classList.remove('collapsed')\n    this.listTarget.classList.add('show')\n  }\n\n  anchorLinkMap () {\n    const hash = {}\n    this.linkTargets.forEach(link => {\n      const href = link.firstElementChild.href\n      if (href.includes('#')) {\n        const headerId = href.substring(href.indexOf('#') + 1)\n        hash[headerId] = link\n      }\n    })\n    return hash\n  }\n\n  initializeMenuHighlight () {\n    const linkHash = this.anchorLinkMap()\n\n    const observer = new window.IntersectionObserver(entries => {\n      entries.forEach(entry => {\n        if (entry.intersectionRatio > 0) {\n          this.linkTargets.forEach(link => link.classList.remove('active'))\n\n          const headerId = entry.target.id\n          const activeLink = linkHash[headerId]\n          if (activeLink) {\n            activeLink.classList.add('active')\n          }\n        }\n      })\n    })\n\n    document.querySelectorAll('h1[id], h2[id]').forEach((header) => {\n      observer.observe(header)\n    })\n  }\n\n  isAnchorGroupPage () {\n    const href = this.linkTarget.firstElementChild.href\n    const hrefAnchorString = href.substring(href.indexOf('#') + 1)\n    if (document.getElementById(hrefAnchorString)) {\n      return true\n    }\n  }\n}\n"
  },
  {
    "path": "app/javascript/controllers/truncated_text_controller.js",
    "content": "import { Controller } from '@hotwired/stimulus'\n\n// Connects to data-controller=\"truncated-text\"\nexport default class extends Controller {\n  static targets = ['moreButton', 'hideButton', 'text']\n\n  toggle () {\n    this.hideButtonTarget.classList.toggle('d-none')\n    this.moreButtonTarget.classList.toggle('d-none')\n    this.textTarget.classList.toggle('line-clamp-1')\n  }\n}\n"
  },
  {
    "path": "app/javascript/datatable.js",
    "content": "/* global $ */\n\nexport function initializeDataTable (selector) {\n  if ($(selector).length > 0) {\n    $(selector).DataTable({ searching: true, order: [[0, 'asc']] })\n  }\n}\n"
  },
  {
    "path": "app/javascript/jQueryGlobalizer.js",
    "content": "import jquery from 'jquery'\nwindow.jQuery = jquery\nwindow.$ = jquery\n"
  },
  {
    "path": "app/javascript/src/add_to_calendar_button.js",
    "content": "/* global $ */\n\nimport 'add-to-calendar-button'\n\nfunction createCalendarEvents () {\n  const calendarButtons = document.querySelectorAll('div.cal-btn')\n\n  for (const calendarButton of calendarButtons) {\n    // Create the add-to-calendar-button web component\n    const button = document.createElement('add-to-calendar-button')\n\n    // Set attributes from data attributes\n    button.setAttribute('name', calendarButton.dataset.title)\n    button.setAttribute('startDate', calendarButton.dataset.start)\n    button.setAttribute('endDate', calendarButton.dataset.end)\n    button.setAttribute('description', calendarButton.dataset.title)\n    button.setAttribute('options', \"'Apple','Google','iCal','Microsoft365','Outlook.com','Yahoo'\")\n    button.setAttribute('timeZone', 'currentBrowser')\n    button.setAttribute('lightMode', 'bodyScheme')\n\n    // Set tooltip\n    button.title = calendarButton.dataset.tooltip\n\n    // Replace the div content with the button\n    calendarButton.innerHTML = ''\n    calendarButton.appendChild(button)\n  }\n}\n\n$(() => { // JQuery's callback for the DOM loading\n  createCalendarEvents()\n})\n"
  },
  {
    "path": "app/javascript/src/all_casa_admin/patch_notes.js",
    "content": "const { Notifier } = require('../notifier')\nconst TypeChecker = require('../type_checker')\nconst patchNotePath = window.location.pathname\nconst patchNoteFormBeforeEditData = {}\nconst patchNoteFunctions = {} // A hack to be able to alphabetize functions\n\nlet pageNotifier\n\n// Inserts a patch note display after the create patch note form in the patch note list and styles it as new\n//  @param    {number} patchNoteGroupId  The id of the group allowed to view the patch note\n//  @param    {jQuery} patchNoteList     A jQuery object representing the patch note list\n//  @param    {string} patchNoteText     The text of the patch note\n//  @param    {number} patchNoteTypeId   The id of the patch note type\n//  @throws   {TypeError}  for a parameter of the incorrect type\n//  @throws   {RangeError} if an id parameter is negative\npatchNoteFunctions.addPatchNoteUI = function (patchNoteGroupId, patchNoteId, patchNoteList, patchNoteText, patchNoteTypeId) {\n  TypeChecker.checkPositiveInteger(patchNoteGroupId, 'patchNoteGroupId')\n  TypeChecker.checkPositiveInteger(patchNoteId, 'patchNoteId')\n  TypeChecker.checkPositiveInteger(patchNoteTypeId, 'patchNoteTypeId')\n  TypeChecker.checkNonEmptyJQueryObject(patchNoteList, 'patchNoteList')\n  TypeChecker.checkString(patchNoteText, 'patchNoteText')\n\n  const newPatchNoteForm = patchNoteList.children().eq(1)\n\n  if (!(newPatchNoteForm.length)) {\n    throw new ReferenceError('Could not find new patch note form')\n  }\n\n  const newPatchNoteUI = newPatchNoteForm.clone()\n  const newPatchNoteUIFormInputs = patchNoteFunctions.getPatchNoteFormInputs(newPatchNoteUI.children())\n\n  newPatchNoteUI.addClass('new')\n  newPatchNoteUI.children().attr('id', `patch-note-${patchNoteId}`)\n  newPatchNoteUIFormInputs.noteTextArea.val(patchNoteText)\n  newPatchNoteUIFormInputs.dropdownGroup.children().removeAttr('selected')\n  newPatchNoteUIFormInputs.dropdownGroup.children(`option[value=\"${patchNoteGroupId}\"]`).attr('selected', true)\n  newPatchNoteUIFormInputs.dropdownType.children().removeAttr('selected')\n  newPatchNoteUIFormInputs.dropdownType.children(`option[value=\"${patchNoteTypeId}\"]`).attr('selected', true)\n  newPatchNoteUIFormInputs.buttonControls.parent().html(`\n    <button type=\"button\" class=\"main-btn primary-btn btn-hovert button-edit\">\n      <i class=\"lni lni-pencil-alt mr-10\"></i>Edit\n    </button>\n    <button type=\"button\" class=\"main-btn danger-btn btn-hover button-delete\">\n      <i class=\"lni lni-trash-can mr-10\"></i>Delete\n    </button>\n  `)\n\n  newPatchNoteForm.after(newPatchNoteUI)\n  patchNoteFunctions.initPatchNoteForm(newPatchNoteUI)\n}\n\n// Creates a patch note\n//  @param    {number} patchNoteGroupId  The id of the group allowed to view the patch note\n//  @param    {string} patchNoteText     The text of the patch note\n//  @param    {number} patchNoteTypeId   The id of the patch note type\n//  @returns  {array} a jQuery jqXHR object. See https://api.jquery.com/jQuery.ajax/#jqXHR\n//  @throws   {TypeError}  for a parameter of the incorrect type\n//  @throws   {RangeError} if an id parameter is negative\npatchNoteFunctions.createPatchNote = function (patchNoteGroupId, patchNoteText, patchNoteTypeId) {\n  // Input check\n  TypeChecker.checkPositiveInteger(patchNoteGroupId, 'patchNoteGroupId')\n  TypeChecker.checkPositiveInteger(patchNoteTypeId, 'patchNoteTypeId')\n  TypeChecker.checkString(patchNoteText, 'patchNoteText')\n\n  // Post request\n  // return $.post(patchNotePath, {\n  //   note: patchNoteText,\n  //   patch_note_group_id: patchNoteGroupId,\n  //   patch_note_type_id: patchNoteTypeId\n  // })\n  return $.ajax({\n    url: patchNotePath,\n    type: 'POST',\n    data: {\n      note: patchNoteText,\n      patch_note_group_id: patchNoteGroupId,\n      patch_note_type_id: patchNoteTypeId\n    },\n    beforeSend: function () {\n      pageNotifier.waitForAsyncOperation()\n    }\n  })\n    .then(function (response, textStatus, jqXHR) {\n      if (response.errors) {\n        return $.Deferred().reject(jqXHR, textStatus, response.error)\n      } else if (response.status && response.status === 'created') {\n        patchNoteFunctions.resolveAsyncOperation()\n      } else {\n        patchNoteFunctions.resolveAsyncOperation('Unknown response')\n      }\n\n      return response\n    })\n    .fail(function (jqXHR, textStatus, error) {\n      patchNoteFunctions.resolveAsyncOperation(error)\n    })\n}\n\n// Deletes a patch note\n//  @param    {number} .parent().parent()patchNoteId The id of the patch note deleted\n//  @returns  {array} a jQuery jqXHR object. See https://api.jquery.com/jQuery.ajax/#jqXHR\n//  @throws   {TypeError}  for a parameter of the incorrect type\n//  @throws   {RangeError} if optionId is negative\npatchNoteFunctions.deletePatchNote = function (patchNoteId) {\n  TypeChecker.checkPositiveInteger(patchNoteId, 'patchNoteId')\n\n  return $.ajax({\n    url: `${patchNotePath}/${patchNoteId}`,\n    type: 'DELETE',\n    beforeSend: function () {\n      pageNotifier.waitForAsyncOperation()\n    }\n  })\n    .then(function (response, textStatus, jqXHR) {\n      if (response.errors) {\n        return $.Deferred().reject(jqXHR, textStatus, response.error)\n      } else if (response.status && response.status === 'ok') {\n        patchNoteFunctions.resolveAsyncOperation()\n      } else {\n        patchNoteFunctions.resolveAsyncOperation('Unknown response')\n      }\n\n      return response\n    })\n    .fail(function (jqXHR, textStatus, error) {\n      patchNoteFunctions.resolveAsyncOperation(error)\n    })\n}\n\n// Disables all form elements of a patch note form\n//  @param    {object} patchNoteFormInputs An object containing the form elements as jQuery objects like the object returned from getPatchNoteFormInputs()\n//  @throws   {TypeError} for a parameter of the incorrect type\npatchNoteFunctions.disablePatchNoteForm = function (patchNoteFormInputs) {\n  for (const formInput of Object.values(patchNoteFormInputs)) {\n    formInput.prop('disabled', true)\n  }\n}\n\n// Enables all form elements of a patch note form\n//  @param    {object} patchNoteFormInputs An object containing the form elements as jQuery objects like the object returned from getPatchNoteFormInputs()\n//  @throws   {TypeError} for a parameter of the incorrect type\npatchNoteFunctions.enablePatchNoteForm = function (patchNoteFormInputs) {\n  for (const formInput of Object.values(patchNoteFormInputs)) {\n    formInput.removeAttr('disabled')\n  }\n}\n\n// Change a patch note form into edit mode\n//  @param  {object} patchNoteFormInputs An object containing the form elements as jQuery objects like the object returned from getPatchNoteFormInputs()\n//  @throws {TypeError} for a parameter of the incorrect type\npatchNoteFunctions.enablePatchNoteFormEditMode = function (patchNoteFormInputs) {\n  TypeChecker.checkObject(patchNoteFormInputs, 'patchNoteFormInputs')\n\n  patchNoteFunctions.enablePatchNoteForm(patchNoteFormInputs)\n\n  // Change button controls\n  //   Clear click listeners\n  patchNoteFormInputs.buttonControls.off()\n\n  const buttonLeft = patchNoteFormInputs.buttonControls.siblings('.button-edit')\n  const buttonRight = patchNoteFormInputs.buttonControls.siblings('.button-delete')\n\n  buttonLeft.html('<i class=\"fas fa-save\"></i> Save')\n  buttonLeft.removeClass('button-edit')\n  buttonLeft.addClass('button-save')\n\n  buttonRight.html('<i class=\"fa-solid fa-xmark\"></i> Cancel')\n  buttonRight.removeClass('button-delete')\n  buttonRight.removeClass('btn-danger')\n  buttonRight.addClass('button-cancel')\n  buttonRight.addClass('btn-secondary')\n\n  patchNoteFunctions.initPatchNoteForm(patchNoteFormInputs.noteTextArea.parent())\n}\n\n// Change a patch note form out of edit mode\n//  @param  {object} patchNoteFormInputs An object containing the form elements as jQuery objects like the object returned from getPatchNoteFormInputs()\n//  @throws {TypeError} for a parameter of the incorrect type\npatchNoteFunctions.exitPatchNoteFormEditMode = function (patchNoteFormInputs) {\n  TypeChecker.checkObject(patchNoteFormInputs, 'patchNoteFormInputs')\n\n  patchNoteFormInputs.noteTextArea.prop('disabled', true)\n  patchNoteFormInputs.dropdownGroup.prop('disabled', true)\n  patchNoteFormInputs.dropdownType.prop('disabled', true)\n\n  // Change button controls\n  //   Clear click listeners\n  patchNoteFormInputs.buttonControls.off()\n\n  const buttonLeft = patchNoteFormInputs.buttonControls.siblings('.button-save')\n  const buttonRight = patchNoteFormInputs.buttonControls.siblings('.button-cancel')\n\n  buttonLeft.html('<i class=\"fa-solid fa-pen-to-square\"></i> Edit')\n  buttonLeft.removeClass('button-save')\n  buttonLeft.addClass('button-edit')\n\n  buttonRight.html('<i class=\"fa-solid fa-trash-can\"></i> Delete')\n  buttonRight.removeClass('btn-secondary')\n  buttonRight.removeClass('button-cancel')\n  buttonRight.addClass('btn-danger')\n  buttonRight.addClass('button-delete')\n\n  patchNoteFunctions.initPatchNoteForm(patchNoteFormInputs.noteTextArea.parent())\n}\n\n// Get all form elements of a patch note in edit mode\n//  @param    {jQuery} patchNoteElement The direct parent of the form elements\n//  @returns  {object} An object containing jQuery objects in this form\n//    {\n//      dropdownGroup:  The select for the patch note's user visibility group\n//      dropdownType:   The select for the patch note's type\n//      noteTextArea:   The textarea containing the patch note\n//      buttonControls: A list of all the buttons at the bottom of the form\n//    }\n//  @throws   {TypeError}      for a parameter of the incorrect type\n//  @throws   {ReferenceError} if an element could not be found\npatchNoteFunctions.getPatchNoteFormInputs = function (patchNoteElement) {\n  TypeChecker.checkNonEmptyJQueryObject(patchNoteElement, 'patchNoteElement')\n\n  const selects = patchNoteElement.children('.label-and-select').children('select')\n\n  const fields = {\n    dropdownGroup: selects.eq(1),\n    dropdownType: selects.eq(0),\n    noteTextArea: patchNoteElement.children('textarea'),\n    buttonControls: patchNoteElement.children('.patch-note-button-controls').children('button')\n  }\n\n  for (const fieldName of Object.keys(fields)) {\n    const field = fields[fieldName]\n\n    if (!((field instanceof jQuery) && field.length)) {\n      throw new ReferenceError(`Could not find form element ${fieldName}`)\n    }\n  }\n\n  return fields\n}\n\n// Get the id of a patch note from its form\n//  @param   {object} patchNoteForm A jQuery object representing the div with the patch note's id\n//  @returns {number} The id of the patch note as a number\n//  @throws  {TypeError}  for a parameter of the incorrect type\npatchNoteFunctions.getPatchNoteId = function (patchNoteForm) {\n  TypeChecker.checkNonEmptyJQueryObject(patchNoteForm, 'patchNoteForm')\n\n  return Number.parseInt(patchNoteForm.attr('id').match(/patch-note-(\\d+)/)[1])\n}\n\n// Add event listeners to a patch note form\n//  @param {object} patchNoteForm A jQuery object representing the patch note form\n//  @throws   {TypeError}  for a parameter of the incorrect type\npatchNoteFunctions.initPatchNoteForm = function (patchNoteForm) {\n  TypeChecker.checkNonEmptyJQueryObject(patchNoteForm, 'patchNoteForm')\n\n  patchNoteForm.find('.button-cancel').click(patchNoteFunctions.onCancelEdit)\n  patchNoteForm.find('.button-delete').click(patchNoteFunctions.onDeletePatchNote)\n  patchNoteForm.find('.button-edit').click(patchNoteFunctions.onEditPatchNote)\n  patchNoteForm.find('.button-save').click(patchNoteFunctions.onSavePatchNote)\n}\n\n// Called when the cancel button is pressed on a patch note form\npatchNoteFunctions.onCancelEdit = function () {\n  const patchNoteFormContainer = $(this).parent().parent()\n  const formInputs = patchNoteFunctions.getPatchNoteFormInputs(patchNoteFormContainer)\n\n  patchNoteFunctions.patchNoteFormDataResetBeforeEdit(formInputs)\n  patchNoteFunctions.exitPatchNoteFormEditMode(formInputs)\n}\n\n// Called when the create button is pressed on the new patch note form\npatchNoteFunctions.onCreate = function () {\n  try {\n    const patchNoteList = $('#patch-note-list')\n    const newPatchNoteFormInputs = patchNoteFunctions.getPatchNoteFormInputs($('#new-patch-note'))\n\n    if (!(newPatchNoteFormInputs.noteTextArea.val())) {\n      pageNotifier.notify('Cannot save an empty patch note', 'warn')\n      return\n    }\n\n    patchNoteFunctions.disablePatchNoteForm(newPatchNoteFormInputs)\n\n    const patchNoteGroupId = Number.parseInt(newPatchNoteFormInputs.dropdownGroup.val())\n    const patchNoteTypeId = Number.parseInt(newPatchNoteFormInputs.dropdownType.val())\n    const patchNoteText = newPatchNoteFormInputs.noteTextArea.val()\n\n    patchNoteFunctions.createPatchNote(\n      patchNoteGroupId,\n      patchNoteText,\n      patchNoteTypeId\n    ).then(function (response) {\n      newPatchNoteFormInputs.noteTextArea.val('')\n      patchNoteFunctions.addPatchNoteUI(patchNoteGroupId, response.id, patchNoteList, patchNoteText, patchNoteTypeId)\n    }).fail(function (err) {\n      pageNotifier.notify('Failed to update UI', 'error')\n      pageNotifier.notify(err.message, 'error')\n      console.error(err)\n    }).always(function () {\n      patchNoteFunctions.enablePatchNoteForm(newPatchNoteFormInputs)\n    })\n  } catch (err) {\n    pageNotifier.notify('Failed to save patch note', 'error')\n    pageNotifier.notify(err.message, 'error')\n    console.error(err)\n  }\n}\n\n// Called when the delete button is pressed on a patch note form\npatchNoteFunctions.onDeletePatchNote = function () {\n  const deleteButton = $(this)\n  const patchNoteFormContainer = deleteButton.parent().parent()\n  const formInputs = patchNoteFunctions.getPatchNoteFormInputs(patchNoteFormContainer)\n\n  switch (deleteButton.text().trim()) {\n    case 'Delete':\n      pageNotifier.notify('Click 2 more times to delete', 'warn')\n      deleteButton.text('2')\n      break\n    case '2':\n      deleteButton.text('1')\n      break\n    case '1':\n      patchNoteFunctions.disablePatchNoteForm(formInputs)\n\n      patchNoteFunctions.deletePatchNote(\n        patchNoteFunctions.getPatchNoteId(patchNoteFormContainer)\n      ).then(function () {\n        patchNoteFormContainer.parent().remove()\n      }).fail(function () {\n        patchNoteFunctions.enablePatchNoteForm(formInputs)\n        deleteButton.html('<i class=\"fa-solid fa-trash-can\"></i> Delete')\n      })\n\n      break\n  }\n}\n\n// Called when the delete button is pressed on a patch note form\npatchNoteFunctions.onEditPatchNote = function () {\n  const patchNoteFormInputs = patchNoteFunctions.getPatchNoteFormInputs($(this).parent().parent())\n\n  patchNoteFunctions.patchNoteFormDataSaveTemp(patchNoteFormInputs)\n  patchNoteFunctions.enablePatchNoteFormEditMode(patchNoteFormInputs)\n}\n\n// Called when the save button is pressed on a patch note form in edit mode\npatchNoteFunctions.onSavePatchNote = function () {\n  const patchNoteForm = $(this).parents('.card-body')\n  const patchNoteFormInputs = patchNoteFunctions.getPatchNoteFormInputs(patchNoteForm)\n\n  if ($(this).parent().siblings('textarea').val() === '') {\n    pageNotifier.notify('Cannot save a blank patch note', 'warn')\n    return\n  }\n\n  const patchNoteGroupId = Number.parseInt(patchNoteFormInputs.dropdownGroup.val())\n  const patchNoteId = patchNoteFunctions.getPatchNoteId(patchNoteForm)\n  const patchNoteTypeId = Number.parseInt(patchNoteFormInputs.dropdownType.val())\n  const patchNoteText = patchNoteFormInputs.noteTextArea.val()\n\n  patchNoteFunctions.disablePatchNoteForm(patchNoteFormInputs)\n\n  patchNoteFunctions.savePatchNote(\n    patchNoteGroupId,\n    patchNoteId,\n    patchNoteText,\n    patchNoteTypeId\n  ).then(function (response) {\n    patchNoteFormInputs.noteTextArea.prop('disabled', true)\n    patchNoteFormInputs.dropdownGroup.prop('disabled', true)\n    patchNoteFormInputs.dropdownType.prop('disabled', true)\n\n    // Change button controls\n    //   Clear click listeners\n    patchNoteFormInputs.buttonControls.off()\n\n    const buttonLeft = patchNoteFormInputs.buttonControls.siblings('.button-save')\n    const buttonRight = patchNoteFormInputs.buttonControls.siblings('.button-cancel')\n\n    buttonLeft.html('<i class=\"fa-solid fa-pen-to-square\"></i> Edit')\n    buttonLeft.removeClass('button-save')\n    buttonLeft.addClass('button-edit')\n\n    buttonRight.html('<i class=\"fa-solid fa-trash-can\"></i> Delete')\n    buttonRight.removeClass('btn-secondary')\n    buttonRight.removeClass('button-cancel')\n    buttonRight.addClass('btn-danger')\n    buttonRight.addClass('button-delete')\n\n    patchNoteFunctions.initPatchNoteForm(patchNoteFormInputs.noteTextArea.parent())\n  }).fail(function (err) {\n    pageNotifier.notify('Failed to update patch note', 'error')\n    pageNotifier.notify(err.message, 'error')\n    console.error(err)\n\n    patchNoteFunctions.enablePatchNoteForm(patchNoteFormInputs)\n  }).always(function () {\n    patchNoteFormInputs.buttonControls.prop('disabled', false)\n  })\n}\n\n// Set the value of a patch note form's inputs to before the form was put in edit mode\n//  @param  {object} patchNoteFormInputs An object containing the form elements as jQuery objects like the object returned from getPatchNoteFormInputs()\n//  @throws {TypeError} for a parameter of the incorrect type\npatchNoteFunctions.patchNoteFormDataResetBeforeEdit = function (patchNoteFormInputs) {\n  TypeChecker.checkObject(patchNoteFormInputs, 'patchNoteFormInputs')\n\n  let patchNoteDataBeforeEditing\n\n  try {\n    patchNoteDataBeforeEditing = patchNoteFormBeforeEditData[patchNoteFunctions.getPatchNoteId(patchNoteFormInputs.noteTextArea.parent())]\n\n    patchNoteFormInputs.noteTextArea.val(patchNoteDataBeforeEditing.note)\n    patchNoteFormInputs.dropdownGroup.val(patchNoteDataBeforeEditing.groupId)\n    patchNoteFormInputs.dropdownType.val(patchNoteDataBeforeEditing.typeId)\n  } catch (e) {\n    pageNotifier.notify('Failed to load patch note data from before editing', 'error')\n    throw e\n  }\n}\n\n// Save the values of the patch note form inputs\n//  @param  {object} patchNoteFormInputs An object containing the form elements as jQuery objects like the object returned from getPatchNoteFormInputs()\n//  @throws {TypeError} for a parameter of the incorrect type\npatchNoteFunctions.patchNoteFormDataSaveTemp = function (patchNoteFormInputs) {\n  TypeChecker.checkObject(patchNoteFormInputs, 'patchNoteFormInputs')\n\n  try {\n    patchNoteFormBeforeEditData[patchNoteFunctions.getPatchNoteId(patchNoteFormInputs.noteTextArea.parent())] = {\n      note: patchNoteFormInputs.noteTextArea.val(),\n      groupId: Number.parseInt(patchNoteFormInputs.dropdownGroup.val()),\n      typeId: Number.parseInt(patchNoteFormInputs.dropdownType.val())\n    }\n  } catch (e) {\n    pageNotifier.notify('Failed to save patch note form data before editing', 'error')\n    throw e\n  }\n}\n\n// Called when an async operation completes. May show notifications describing how the operation completed\n//  @param    {string | Error=}  error The error to be displayed(optional)\n//  @throws   {TypeError}  for a parameter of the incorrect type\n//  @throws   {Error}      for trying to resolve more async operations than the amount currently awaiting\npatchNoteFunctions.resolveAsyncOperation = function (error) {\n  if (error instanceof Error) {\n    error = error.message\n  }\n\n  pageNotifier.resolveAsyncOperation(error)\n}\n\n// Saves an edited patch note\n//  @param    {number} patchNoteGroupId  The id of the group allowed to view the patch note\n//  @param    {number} patchNoteId  The id of the patch note\n//  @param    {string} patchNoteText     The text of the patch note\n//  @param    {number} patchNoteTypeId   The id of the patch note type\n//  @returns  {array} a jQuery jqXHR object. See https://api.jquery.com/jQuery.ajax/#jqXHR\n//  @throws   {TypeError}  for a parameter of the incorrect type\n//  @throws   {RangeError} if an id parameter is negative\npatchNoteFunctions.savePatchNote = function (patchNoteGroupId, patchNoteId, patchNoteText, patchNoteTypeId) {\n  // Input check\n  TypeChecker.checkPositiveInteger(patchNoteGroupId, 'patchNoteGroupId')\n  TypeChecker.checkPositiveInteger(patchNoteId, 'patchNoteGroupId')\n  TypeChecker.checkPositiveInteger(patchNoteTypeId, 'patchNoteTypeId')\n  TypeChecker.checkString(patchNoteText, 'patchNoteText')\n\n  // Post request\n  return $.ajax({\n    url: `${patchNotePath}/${patchNoteId}`,\n    type: 'PUT',\n    data: {\n      note: patchNoteText,\n      patch_note_group_id: patchNoteGroupId,\n      patch_note_type_id: patchNoteTypeId\n    },\n    beforeSend: function () {\n      pageNotifier.waitForAsyncOperation()\n    }\n  })\n    .then(function (response, textStatus, jqXHR) {\n      if (response.errors) {\n        return $.Deferred().reject(jqXHR, textStatus, response.error)\n      } else if (response.status && response.status === 'ok') {\n        patchNoteFunctions.resolveAsyncOperation()\n      } else {\n        patchNoteFunctions.resolveAsyncOperation('Unknown response')\n        console.error('Unexpected repsonse')\n        console.error(response)\n      }\n\n      return response\n    })\n    .fail(function (jqXHR, textStatus, error) {\n      patchNoteFunctions.resolveAsyncOperation(error)\n    })\n}\n\n$(() => { // JQuery's callback for the DOM loading\n  if (!(window.location.pathname.includes('patch_notes'))) {\n    return\n  }\n\n  try {\n    const asyncNotificationsElement = $('#notifications')\n    pageNotifier = new Notifier(asyncNotificationsElement)\n\n    $('#new-patch-note button').on('click', patchNoteFunctions.onCreate)\n    $('#patch-note-list .button-delete').on('click', patchNoteFunctions.onDeletePatchNote)\n    $('#patch-note-list .button-edit').on('click', patchNoteFunctions.onEditPatchNote)\n  } catch (err) {\n    pageNotifier.notify('Could not intialize app', 'error')\n    pageNotifier.notify(err.message, 'error')\n    console.error(err)\n  }\n})\n"
  },
  {
    "path": "app/javascript/src/all_casa_admin/tables.js",
    "content": "/* global $ */\n\nimport { initializeDataTable } from '../../datatable'\n\n$(() => {\n  initializeDataTable('table.admin-list')\n  initializeDataTable('table.organization-list')\n})\n"
  },
  {
    "path": "app/javascript/src/casa_case.js",
    "content": "/* eslint-env jquery */\n/* global FormData */\n/* global DOMParser */\n/* global spinner */\n/* global $ */\n\nimport Swal from 'sweetalert2'\n\nfunction copyOrdersFromCaseWithConfirmation () {\n  const id = $(this).next().val()\n  const caseNumber = $('select.siblings-casa-cases').find(':selected').text()\n  const text = `Are you sure you want to copy all orders from case #${caseNumber}?`\n  Swal.fire({\n    icon: 'warning',\n    title: `Copy all orders from case #${caseNumber}?`,\n    text,\n    showCloseButton: true,\n    showCancelButton: true,\n    focusConfirm: false,\n\n    confirmButtonColor: '#d33',\n    cancelButtonColor: '#39c',\n\n    confirmButtonText: 'Copy',\n    cancelButtonText: 'Cancel'\n  }).then((result) => {\n    if (result.isConfirmed) {\n      copyOrdersFromCaseAction(id, caseNumber)\n    }\n  })\n}\n\nfunction copyOrdersFromCaseAction (id, caseNumber) {\n  $.ajax({\n    url: `/casa_cases/${id}/copy_court_orders`,\n    method: 'patch',\n    data: {\n      case_number_cp: caseNumber\n    },\n    success: () => {\n      Swal.fire({\n        icon: 'success',\n        text: 'Court orders have been copied.',\n        showCloseButton: true,\n        timer: 2000\n      }).then(() => window.location.reload(true))\n    },\n    error: () => {\n      Swal.fire({\n        icon: 'error',\n        text: 'Something went wrong when attempting to copy court orders.',\n        showCloseButton: true\n      })\n    }\n  })\n}\n\nfunction showBtn (el) {\n  if (!el) return\n  el.classList.remove('d-none')\n}\n\nfunction hideBtn (el) {\n  if (!el) return\n  el.classList.add('d-none')\n}\n\nfunction disableBtn (el) {\n  if (!el) return\n  el.disabled = true\n  el.classList.add('disabled')\n  el.setAttribute('aria-disabled', true)\n}\n\nfunction enableBtn (el) {\n  if (!el) return\n  el.disabled = false\n  el.classList.remove('disabled')\n  el.removeAttribute('aria-disabled')\n}\n\nfunction showAlert (html) {\n  const alertEl = new DOMParser().parseFromString(html, 'text/html').body.firstElementChild\n  const flashContainer = document.querySelector('.header-flash')\n  flashContainer && flashContainer.replaceWith(alertEl)\n}\n\nfunction validateForm (formEl, errorEl) {\n  if (!formEl) {\n    return\n  }\n\n  // check html validations, checkValidity returns false if doesn't pass validation\n  if (errorEl && !formEl.checkValidity()) {\n    errorEl.classList.remove('d-none')\n  }\n}\n\nfunction handleGenerateReport (e) {\n  e.preventDefault()\n\n  const form = e.currentTarget.form\n\n  const formData = Object.fromEntries(new FormData(form))\n  const errorEl = document.querySelector('.select-required-error')\n  validateForm(form, errorEl ?? null)\n  if (formData.case_number.length === 0) return\n\n  const generateBtn = e.currentTarget\n  disableBtn(generateBtn)\n\n  const url = e.currentTarget.form.action\n  const options = {\n    method: 'POST',\n    headers: {\n      Accept: 'application/json',\n      'Content-Type': 'application/json'\n    },\n    body: JSON.stringify(formData)\n  }\n  showBtn(spinner)\n  hideBtn($('#btnGenerateReport .lni-download')[0])\n  window.fetch(url, options)\n    .then(response => {\n      return response.json()\n    })\n    .then(data => {\n      if (data.status !== 'ok') {\n        showAlert(data.error_messages)\n        enableBtn(generateBtn)\n        hideBtn(spinner)\n        return\n      }\n      hideBtn(spinner)\n      showBtn($('#btnGenerateReport .lni-download')[0])\n      enableBtn(generateBtn)\n      window.open(data.link, '_blank')\n    })\n    .catch((error) => {\n      console.error('Debugging info, error:', error)\n    })\n}\n\nfunction clearSelectErrors () {\n  const errorEl = document.querySelector('.select-required-error')\n\n  if (!errorEl) return\n\n  errorEl.classList.add('d-none')\n}\n\nfunction handleModalClose () {\n  const selectEl = document.querySelector('#case-selection')\n\n  if (!selectEl) return\n\n  clearSelectErrors()\n  // this line taken from docs https://select2.org/programmatic-control/add-select-clear-items\n  $('#case-selection').val(null).trigger('change')\n}\n\n// re-initialized for setting modal as dropdownParent\nfunction handleDropdownSelection () {\n  if ($('#case-selection').hasClass('select2')) {\n    $('#case-selection').select2()\n  }\n}\n\n$(() => { // JQuery's callback for the DOM loading\n  $('button.copy-court-button').on('click', copyOrdersFromCaseWithConfirmation)\n\n  if ($('button.copy-court-button').length) {\n    disableBtn($('button.copy-court-button')[0])\n  }\n\n  $('#case-selection').on('change', clearSelectErrors)\n\n  $('select.siblings-casa-cases').on('change', () => {\n    if ($('select.siblings-casa-cases').find(':selected').text()) {\n      enableBtn($('button.copy-court-button')[0])\n    } else {\n      disableBtn($('button.copy-court-button')[0])\n    }\n  })\n  // modal id is defined in _generate_docx.html.erb so would like to be able to implement modal close logic in that file\n  // but not sure how to\n  $('#generate-docx-report-modal').on('hidden.bs.modal', () => handleModalClose())\n\n  $('#generate-docx-report-modal').on('shown.bs.modal', () => handleDropdownSelection())\n\n  $('#btnGenerateReport').on('click', handleGenerateReport)\n\n  if (/\\/casa_cases\\/.*\\?.*success=true/.test(window.location.href)) {\n    $('#thank_you').modal()\n  }\n})\n\nexport {\n  showBtn,\n  hideBtn,\n  disableBtn,\n  enableBtn,\n  showAlert\n}\n"
  },
  {
    "path": "app/javascript/src/casa_org.js",
    "content": "function twilioToggle () {\n  const phoneNumber = $('#casa_org_twilio_phone_number')\n  const accSid = $('#casa_org_twilio_account_sid')\n  const keySid = $('#casa_org_twilio_api_key_sid')\n  const secret = $('#casa_org_twilio_api_key_secret')\n\n  if ($('.accordionTwilio').is(':checked')) {\n    addCheckedAttr(phoneNumber)\n    addCheckedAttr(accSid)\n    addCheckedAttr(keySid)\n    addCheckedAttr(secret)\n  } else {\n    removeCheckedAttr(phoneNumber)\n    removeCheckedAttr(accSid)\n    removeCheckedAttr(keySid)\n    removeCheckedAttr(secret)\n  }\n}\n\nfunction addCheckedAttr (el) {\n  el.attr('required', true)\n  el.attr('aria-disabled', false)\n  el.attr('aria-required', true)\n  el.removeAttr('disabled')\n}\n\nfunction removeCheckedAttr (el) {\n  el.removeAttr('required')\n  el.attr('aria-required', false)\n  el.attr('aria-disabled', true)\n  el.attr('disabled', true)\n}\n\n$(() => { // JQuery's callback for the DOM loading\n  $('.accordionTwilio').attr('data-bs-toggle', 'collapse')\n  $('.accordionTwilio').attr('data-bs-target', '#collapseTwilio')\n  $('.accordionTwilio').attr('aria-expanded', 'false')\n\n  if ($('.accordionTwilio').is(':checked')) {\n    $('.accordionTwilio').attr('aria_expanded')\n    $('.accordionTwilio').removeClass('collapsed')\n    $('#collapseTwilio').addClass('show')\n  }\n\n  ($('.accordionTwilio').on('click', twilioToggle))\n  twilioToggle()\n})\n"
  },
  {
    "path": "app/javascript/src/case_contact.js",
    "content": "/* global window */\n/* global $ */\n\nimport Swal from 'sweetalert2'\nfunction convertDateToSystemTimeZone (date) {\n  return new Date((typeof date === 'string' ? new Date(date) : date))\n}\n\nasync function displayFollowupAlert () {\n  const { value: text, isConfirmed } = await fireSwalFollowupAlert()\n\n  if (!isConfirmed) return\n\n  const params = text ? { note: text } : {}\n  const caseContactId = this.id.replace('followup-button-', '')\n\n  $.post(\n    `/case_contacts/${caseContactId}/followups`,\n    params,\n    () => window.location.reload()\n  )\n}\n\nasync function fireSwalFollowupAlert () {\n  const inputLabel = 'Optional: Add a note about what followup is needed.'\n\n  return await Swal.fire({\n    input: 'textarea',\n    title: inputLabel,\n    inputPlaceholder: 'Type your note here...',\n    inputAttributes: { 'aria-label': 'Type your note here' },\n\n    showCancelButton: true,\n    showCloseButton: true,\n\n    confirmButtonText: 'Confirm',\n    confirmButtonColor: '#dc3545',\n\n    customClass: {\n      inputLabel: 'mx-5'\n    }\n  })\n}\n\n$(document).on('turbo:load', function () {\n  $('.filter-form').on('change', '.filter-input', function () {\n    $(this).closest('form').submit()\n  })\n})\n\n$(() => { // JQuery's callback for the DOM loading\n  $('[data-toggle=\"tooltip\"]').tooltip()\n  $('.followup-button').on('click', displayFollowupAlert)\n})\n\nexport {\n  convertDateToSystemTimeZone,\n  fireSwalFollowupAlert\n}\n"
  },
  {
    "path": "app/javascript/src/case_emancipation.js",
    "content": "/* eslint-env jquery */\n/* global $ */\n\nconst { Notifier } = require('./notifier')\nconst TypeChecker = require('./type_checker')\n\nconst emancipationPage = {\n  savePath: window.location.pathname + '/save'\n}\n\n// Called when an async operation completes. May show notifications describing how the operation completed\n//  @param    {string | Error=}  error The error to be displayed(optional)\n//  @throws   {TypeError}  for a parameter of the incorrect type\n//  @throws   {Error}      for trying to resolve more async operations than the amount currently awaiting\nfunction resolveAsyncOperation (error) {\n  if (error instanceof Error) {\n    error = error.message\n  }\n\n  emancipationPage.notifier.resolveAsyncOperation(error)\n}\n\n// Adds or deletes an option from the current casa case\n//  @param    {string}  action One of the following:\n//    \"add_category\"    to add a category to the case\n//    \"add_option\"      to add an option to the case\n//    \"delete_category\" to remove a category from the case\n//    \"delete_option\"   to remove an option from the case\n//    \"set_option\"      to set the option for a mutually exclusive category\n//  @param    {integer | string}  checkItemId The id of either an emancipation option or an emancipation category to perform an action on\n//  @returns  {array} a jQuery jqXHR object. See https://api.jquery.com/jQuery.ajax/#jqXHR\n//  @throws   {TypeError}  for a parameter of the incorrect type\n//  @throws   {RangeError} if optionId is negative\nfunction saveCheckState (action, checkItemId) {\n  // Input check\n  if (typeof checkItemId === 'string') {\n    checkItemId = parseInt(checkItemId)\n  }\n\n  TypeChecker.checkPositiveInteger(checkItemId, 'checkItemId')\n\n  emancipationPage.notifier.waitForAsyncOperation()\n\n  // Post request\n  return $.post(emancipationPage.savePath, {\n    check_item_action: action,\n    check_item_id: checkItemId\n  })\n    .then(function (response, textStatus, jqXHR) {\n      if (response.error) {\n        return $.Deferred().reject(jqXHR, textStatus, response.error)\n      } else if (response === 'success') {\n        resolveAsyncOperation()\n      } else {\n        resolveAsyncOperation('Unknown response')\n      }\n\n      return response\n    })\n    .fail(function (jqXHR, textStatus, error) {\n      resolveAsyncOperation(error)\n    })\n}\n\nexport class Toggler {\n  constructor (emancipationCategory) {\n    this.emancipationCategory = emancipationCategory\n    this.categoryCollapseIcon = this.emancipationCategory.find('.category-collapse-icon')\n    this.categoryOptionsContainer = this.emancipationCategory.next('.category-options')\n  }\n\n  manageTogglerText () {\n    if (this.emancipationCategory.attr('data-is-open') === 'true') {\n      this.categoryCollapseIcon.text('–')\n    } else if (this.emancipationCategory.attr('data-is-open') === 'false') {\n      this.categoryCollapseIcon.text('+')\n    }\n  }\n\n  openChildren () {\n    this.categoryOptionsContainer.show()\n    this.emancipationCategory.attr('data-is-open', 'true')\n  }\n\n  closeChildren () {\n    this.categoryOptionsContainer.hide()\n    this.emancipationCategory.attr('data-is-open', 'false')\n  }\n\n  deselectChildren (notifierCallback) {\n    this.categoryOptionsContainer.children().filter(function () {\n      return $(this).find('input').prop('checked')\n    }).each(function () {\n      const checkbox = $(this).find('input')\n\n      checkbox.prop('checked', false)\n      notifierCallback(checkbox.next().text())\n    })\n  }\n}\n\n$(() => { // JQuery's callback for the DOM loading\n  if (!((/casa_cases\\/[A-Za-z\\-0-9]+\\/emancipation/).test(window.location.pathname))) {\n    return\n  }\n\n  const notificationsElement = $('#notifications')\n  emancipationPage.notifier = new Notifier(notificationsElement)\n\n  $('.category-collapse-icon').on('click', function () {\n    const categoryCollapseIcon = $(this)\n    const emancipationCategory = categoryCollapseIcon.parent()\n    const toggler = new Toggler(emancipationCategory)\n\n    if (emancipationCategory.attr('data-is-open') === 'true') {\n      toggler.closeChildren()\n      toggler.manageTogglerText()\n    } else if (emancipationCategory.attr('data-is-open') === 'false') {\n      toggler.openChildren()\n      toggler.manageTogglerText()\n    }\n  })\n\n  $('.emacipation-category-input-label-pair').on('click', function () {\n    const emacipationCategoryInputLabelPair = $(this)\n    const emancipationCategory = emacipationCategoryInputLabelPair.parent()\n    const toggler = new Toggler(emancipationCategory)\n    const categoryCheckbox = emancipationCategory.find('.emancipation-category-check-box')\n    const categoryCheckboxChecked = categoryCheckbox.is(':checked')\n\n    if (!emancipationCategory.data('disabled')) {\n      emancipationCategory.data('disabled', true)\n      emancipationCategory.addClass('disabled')\n      categoryCheckbox.prop('disabled', 'disabled')\n\n      let saveAction,\n        doneCallback\n\n      if (categoryCheckboxChecked) {\n        doneCallback = () => {\n          toggler.manageTogglerText()\n          toggler.deselectChildren((text) => emancipationPage.notifier.notify('Unchecked ' + text, 'info'))\n        }\n        saveAction = 'delete_category'\n      } else {\n        doneCallback = () => {\n          toggler.openChildren()\n          toggler.manageTogglerText()\n        }\n        saveAction = 'add_category'\n      }\n\n      saveCheckState(saveAction, categoryCheckbox.val())\n        .done(function () {\n          doneCallback()\n          categoryCheckbox.prop('checked', !categoryCheckboxChecked)\n          toggler.manageTogglerText()\n        })\n        .always(function () {\n          emancipationCategory.data('disabled', false)\n          emancipationCategory.removeClass('disabled')\n          categoryCheckbox.prop('disabled', false)\n        })\n    }\n  })\n\n  $('.check-item').on('click', function () {\n    const checkComponent = $(this)\n    const checkElement = checkComponent.find('input')\n\n    if (checkComponent.data('disabled')) {\n      return\n    }\n\n    if (checkElement.attr('type') === 'radio') {\n      if (checkElement.prop('checked')) {\n        return\n      }\n\n      const radioButtons = checkComponent.parent().children()\n\n      radioButtons.each(function () {\n        const radioComponent = $(this)\n        const radioInput = radioComponent.find('input')\n\n        radioComponent.data('disabled', true)\n        radioComponent.addClass('disabled')\n        radioInput.prop('disabled', 'disabled')\n      })\n\n      saveCheckState('set_option', checkElement.val())\n        .done(function () {\n          checkElement.prop('checked', true)\n          radioButtons.each(function () {\n            const radioComponent = $(this)\n            const radioInput = radioComponent.find('input')\n\n            radioComponent.data('disabled', false)\n            radioComponent.removeClass('disabled')\n            radioInput.prop('disabled', false)\n          })\n        })\n    } else { // Expecting type=checkbox\n      checkComponent.data('disabled', true)\n      checkComponent.addClass('disabled')\n      checkElement.prop('disabled', 'disabled')\n\n      const originallyChecked = checkElement.prop('checked')\n      let asyncCall\n\n      if (!originallyChecked) {\n        asyncCall = saveCheckState('add_option', checkElement.val())\n      } else {\n        asyncCall = saveCheckState('delete_option', checkElement.val())\n      }\n\n      asyncCall.done(function () {\n        checkComponent.data('disabled', false)\n        checkComponent.removeClass('disabled')\n        checkElement.prop('checked', !originallyChecked)\n        checkElement.prop('disabled', false)\n      })\n    }\n  })\n})\n"
  },
  {
    "path": "app/javascript/src/dashboard.js",
    "content": "/* global alert */\n/* global $ */\nimport Swal from 'sweetalert2'\nimport { fireSwalFollowupAlert } from './case_contact'\nconst { Notifier } = require('./notifier')\nlet pageNotifier\n\nconst MAX_VISIBLE_TOPIC_PILLS = 2\n\nfunction buildTopicPills (topics) {\n  if (!topics || topics.length === 0) return ''\n  const visible = topics.slice(0, MAX_VISIBLE_TOPIC_PILLS)\n  const overflowCount = topics.length - visible.length\n  const pills = visible\n    .map(topic => `<span class=\"badge badge-pill light-bg text-black\">${topic}</span>`)\n    .join(' ')\n  const overflowPill = overflowCount > 0\n    ? ` <span class=\"badge badge-pill light-bg text-black\">+${overflowCount} More</span>`\n    : ''\n  return pills + overflowPill\n}\n\nfunction buildExpandedContent (data) {\n  const answers = (data.contact_topic_answers || [])\n    .map(answer => `<div class=\"expanded-topic\"><strong>${answer.question}</strong><p>${answer.value}</p></div>`)\n    .join('')\n\n  const notes = data.notes && data.notes.trim()\n    ? `<div class=\"expanded-topic\"><strong>Additional Notes</strong><p>${data.notes}</p></div>`\n    : ''\n\n  if (!answers && !notes) return '<p class=\"expanded-empty\">No additional details.</p>'\n\n  return `<div class=\"expanded-content\">${answers}${notes}</div>`\n}\n\nconst defineCaseContactsTable = function () {\n  const table = $('table#case_contacts').DataTable({\n    scrollX: true,\n    searching: true,\n    processing: true,\n    serverSide: true,\n    order: [[2, 'desc']], // Sort by Date column (index 2, after bell and chevron)\n    ajax: {\n      url: $('table#case_contacts').data('source'),\n      type: 'POST',\n      error: function (xhr, error, code) {\n        console.error('DataTable error:', error, code)\n      },\n      dataType: 'json'\n    },\n    columnDefs: [\n      { orderable: false, targets: [0, 1, 10] } // Bell, Chevron, and Ellipsis columns not orderable\n    ],\n    columns: [\n      { // Bell icon column (index 0)\n        data: 'has_followup',\n        orderable: false,\n        searchable: false,\n        render: (data, type, row) => {\n          return data === 'true'\n            ? '<i class=\"fas fa-bell\"></i>'\n            : '<i class=\"fas fa-bell\" style=\"opacity: 0.3;\"></i>'\n        }\n      },\n      { // Chevron icon column (index 1)\n        data: null,\n        orderable: false,\n        searchable: false,\n        render: () => '<button type=\"button\" class=\"expand-toggle\" aria-expanded=\"false\" aria-label=\"Expand row details\"><i class=\"fa-solid fa-chevron-down\" aria-hidden=\"true\"></i></button>'\n      },\n      { // Date column (index 2)\n        data: 'occurred_at',\n        name: 'occurred_at',\n        render: (data) => data || ''\n      },\n      { // Case column (index 3)\n        data: 'casa_case',\n        orderable: false,\n        render: (data) => {\n          if (!data || !data.id) return ''\n          const a = document.createElement('a')\n          a.href = `/casa_cases/${data.id}`\n          a.textContent = data.case_number\n          return a.outerHTML\n        }\n      },\n      { // Relationship (Contact Types) column (index 4)\n        data: 'contact_types',\n        orderable: false,\n        render: (data) => data || ''\n      },\n      { // Medium column (index 5)\n        data: 'medium_type',\n        render: (data) => data || ''\n      },\n      { // Created By column (index 6)\n        data: 'creator',\n        orderable: false,\n        render: (data) => {\n          if (!data) return ''\n\n          // Build edit link based on role\n          let editPath = ''\n          if (data.role === 'Supervisor') {\n            editPath = `/supervisors/${data.id}/edit`\n          } else if (data.role === 'Casa Admin') {\n            editPath = '/users/edit'\n          } else {\n            editPath = `/volunteers/${data.id}/edit`\n          }\n\n          return $('<a>')\n            .attr('href', editPath)\n            .attr('data-turbo', 'false')\n            .text(data.display_name)\n            .prop('outerHTML')\n        }\n      },\n      { // Contacted column (index 7)\n        data: 'contact_made',\n        orderable: false,\n        render: (data, type, row) => {\n          const icon = data === 'true'\n            ? '<i class=\"lni lni-checkmark-circle\" style=\"color: green;\"></i>'\n            : '<i class=\"lni lni-cross-circle\" style=\"color: orange;\"></i>'\n\n          let duration = ''\n          if (row.duration_minutes) {\n            const hours = Math.floor(row.duration_minutes / 60)\n            const minutes = row.duration_minutes % 60\n            duration = ` (${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')})`\n          }\n          return icon + duration\n        }\n      },\n      { // Topics column (index 8)\n        data: 'contact_topics',\n        orderable: false,\n        render: (data) => buildTopicPills(data)\n      },\n      { // Draft column (index 9)\n        data: 'is_draft',\n        orderable: false,\n        render: (data) => {\n          return (data === true || data === 'true')\n            ? '<span class=\"badge badge-pill light-bg text-black\" data-testid=\"draft-badge\">Draft</span>'\n            : ''\n        }\n      },\n      { // Ellipsis menu column (index 10)\n        data: null,\n        orderable: false,\n        searchable: false,\n        render: (_data, _type, row) => {\n          const buttonId = `cc-actions-btn-${row.id}`\n          const label = `Actions for case contact${row.occurred_at ? ' on ' + row.occurred_at : ''}`\n\n          const editItem = row.can_edit === 'true'\n            ? `<li role=\"none\"><a class=\"dropdown-item\" role=\"menuitem\" href=\"${row.edit_path}\" data-turbo=\"false\">Edit</a></li>`\n            : '<li role=\"none\"><span class=\"dropdown-item disabled\" role=\"menuitem\" aria-disabled=\"true\">Edit</span></li>'\n\n          const deleteItem = row.can_destroy === 'true'\n            ? `<li role=\"none\"><button class=\"dropdown-item cc-delete-action\" role=\"menuitem\" type=\"button\" data-id=\"${row.id}\">Delete</button></li>`\n            : '<li role=\"none\"><button class=\"dropdown-item disabled\" role=\"menuitem\" type=\"button\" disabled aria-disabled=\"true\">Delete</button></li>'\n\n          const reminderItem = row.followup_id\n            ? `<li role=\"none\"><button class=\"dropdown-item cc-resolve-reminder-action\" role=\"menuitem\" type=\"button\" data-id=\"${row.id}\" data-followup-id=\"${row.followup_id}\">Resolve Reminder</button></li>`\n            : `<li role=\"none\"><button class=\"dropdown-item cc-set-reminder-action\" role=\"menuitem\" type=\"button\" data-id=\"${row.id}\">Set Reminder</button></li>`\n\n          return `\n            <div class=\"dropdown\">\n              <button type=\"button\"\n                      id=\"${buttonId}\"\n                      class=\"btn btn-link cc-ellipsis-toggle p-0\"\n                      data-bs-toggle=\"dropdown\"\n                      aria-haspopup=\"true\"\n                      aria-expanded=\"false\"\n                      aria-label=\"${label}\">\n                <i class=\"fas fa-ellipsis-v\" aria-hidden=\"true\"></i>\n              </button>\n              <ul class=\"dropdown-menu dropdown-menu-end\"\n                  role=\"menu\"\n                  aria-labelledby=\"${buttonId}\">\n                ${editItem}\n                ${deleteItem}\n                ${reminderItem}\n              </ul>\n            </div>\n          `\n        }\n      }\n    ]\n  })\n\n  $('table#case_contacts tbody').on('click', '.expand-toggle', function () {\n    const tr = $(this).closest('tr')\n    const row = table.row(tr)\n\n    if (row.child.isShown()) {\n      row.child.hide()\n      $(this).removeClass('expanded').attr('aria-expanded', 'false')\n    } else {\n      row.child(buildExpandedContent(row.data())).show()\n      $(this).addClass('expanded').attr('aria-expanded', 'true')\n    }\n  })\n\n  const csrfToken = () => $('meta[name=\"csrf-token\"]').attr('content')\n\n  $('table#case_contacts').on('click', '.cc-delete-action', async function () {\n    const id = $(this).data('id')\n    const { isConfirmed } = await Swal.fire({\n      title: 'Delete this contact?',\n      showCancelButton: true,\n      confirmButtonText: 'Delete',\n      confirmButtonColor: '#dc3545'\n    })\n    if (!isConfirmed) return\n    $.ajax({\n      url: `/case_contacts/${id}`,\n      type: 'DELETE',\n      headers: { 'X-CSRF-Token': csrfToken(), Accept: 'application/json' },\n      success: () => table.ajax.reload(null, false)\n    })\n  })\n\n  $('table#case_contacts').on('click', '.cc-set-reminder-action', async function () {\n    const id = $(this).data('id')\n    const { value: text, isConfirmed } = await fireSwalFollowupAlert()\n    if (!isConfirmed) return\n    const params = text ? { note: text } : {}\n    $.ajax({\n      url: `/case_contacts/${id}/followups`,\n      type: 'POST',\n      data: params,\n      headers: { 'X-CSRF-Token': csrfToken(), Accept: 'application/json' },\n      success: () => table.ajax.reload(null, false)\n    })\n  })\n\n  $('table#case_contacts').on('click', '.cc-resolve-reminder-action', function () {\n    const followupId = $(this).data('followup-id')\n    $.ajax({\n      url: `/followups/${followupId}/resolve`,\n      type: 'PATCH',\n      headers: { 'X-CSRF-Token': csrfToken(), Accept: 'application/json' },\n      success: () => table.ajax.reload(null, false)\n    })\n  })\n}\n\n$(() => { // JQuery's callback for the DOM loading\n  const notificationsElement = $('#notifications')\n\n  if (notificationsElement.length && ($('table#case_contacts').length || $('table#casa_cases').length || $('table#volunteers').length || $('table#supervisors').length)) {\n    pageNotifier = new Notifier(notificationsElement)\n  }\n\n  $.fn.dataTable.ext.search.push(\n    function (settings, data, dataIndex) {\n      if (settings.nTable.id !== 'casa-cases') {\n        return true\n      }\n\n      const statusArray = []\n      const assignedToVolunteerArray = []\n      const assignedToMoreThanOneVolunteerArray = []\n      const assignedToTransitionYouthArray = []\n      const caseNumberPrefixArray = []\n\n      $('.status-options').find('input[type=\"checkbox\"]').each(function () {\n        if ($(this).is(':checked')) {\n          statusArray.push($(this).data('value'))\n        }\n      })\n\n      $('.assigned-to-volunteer-options').find('input[type=\"checkbox\"]').each(function () {\n        if ($(this).is(':checked')) {\n          assignedToVolunteerArray.push($(this).data('value'))\n        }\n      })\n\n      $('.more-than-one-volunteer-options').find('input[type=\"checkbox\"]').each(function () {\n        if ($(this).is(':checked')) {\n          assignedToMoreThanOneVolunteerArray.push($(this).data('value'))\n        }\n      })\n\n      $('.transition-youth-options').find('input[type=\"checkbox\"]').each(function () {\n        if ($(this).is(':checked')) {\n          assignedToTransitionYouthArray.push($(this).data('value'))\n        }\n      })\n\n      $('.case-number-prefix-options').find('input[type=\"checkbox\"]').each(function () {\n        if ($(this).is(':checked')) {\n          caseNumberPrefixArray.push($(this).data('value'))\n        }\n      })\n\n      const possibleCaseNumberPrefixes = ['CINA', 'TPR']\n      const status = data[3]\n      const assignedToVolunteer = (data[5] !== '' && data[5].split(',').length >= 1) ? 'Yes' : 'No'\n      const assignedToMoreThanOneVolunteer = (data[5] !== '' && data[5].split(',').length > 1) ? 'Yes' : 'No'\n      const assignedToTransitionYouth = data[4]\n      const caseNumberPrefix = possibleCaseNumberPrefixes.includes(data[0].split('-')[0]) ? data[0].split('-')[0] : 'None'\n\n      return statusArray.includes(status) &&\n        assignedToVolunteerArray.includes(assignedToVolunteer) &&\n        assignedToMoreThanOneVolunteerArray.includes(assignedToMoreThanOneVolunteer) &&\n        assignedToTransitionYouthArray.some(filterValue =>\n          assignedToTransitionYouth.toLowerCase().includes(filterValue.toLowerCase().replace(/[^a-zA-Z]/g, ''))\n        ) &&\n        caseNumberPrefixArray.includes(caseNumberPrefix)\n    }\n  )\n\n  const handleAjaxError = e => {\n    console.error(e)\n    if (e.responseJSON && e.responseJSON.error) {\n      alert(e.responseJSON.error)\n    } else {\n      const responseErrorMessage = e.response.statusText\n        ? `\\n${e.response.statusText}\\n`\n        : ''\n\n      alert(`Sorry, try that again?\\n${responseErrorMessage}\\nIf you're seeing a problem, please fill out the Report A Site Issue\n      link to the bottom left near your email address.`)\n    }\n  }\n\n  // Enable all data tables on dashboard but only filter on volunteers table\n  const editSupervisorPath = id => `/supervisors/${id}/edit`\n  const editVolunteerPath = id => `/volunteers/${id}/edit`\n  const impersonateVolunteerPath = id => `/volunteers/${id}/impersonate`\n  const casaCasePath = id => `/casa_cases/${id}`\n  const volunteersTable = $('table#volunteers').DataTable({\n    autoWidth: false,\n    stateSave: true,\n    initComplete: function (settings, json) {\n      this.api().columns().every(function (index) {\n        const columnVisible = this.visible()\n        return $('#visibleColumns input[data-column=\"' + index + '\"]').prop('checked', columnVisible)\n      })\n    },\n    stateSaveCallback: function (settings, data) {\n      $.ajax({\n        url: '/preference_sets/table_state_update/' + settings.nTable.id + '_table',\n\n        data: {\n          table_state: JSON.stringify(data)\n        },\n        dataType: 'json',\n        type: 'POST',\n        error: function (jqXHR, textStatus, errorMessage) {\n          console.error(errorMessage)\n          pageNotifier.notify('Error while saving preferences', 'error')\n        }\n      })\n    },\n    stateSaveParams: function (settings, data) {\n      data.search.search = ''\n      return data\n    },\n    stateLoadCallback: function (settings, callback) {\n      $.ajax({\n        url: '/preference_sets/table_state/' + settings.nTable.id + '_table',\n        dataType: 'json',\n        type: 'GET',\n        success: function (json) {\n          callback(json)\n        }\n      })\n    },\n    order: [[7, 'desc']],\n    columns: [\n      {\n        data: 'id',\n        targets: 0,\n        searchable: false,\n        orderable: false,\n        render: (data, type, row, meta) => {\n          return `\n            <input type=\"checkbox\" name=\"supervisor_volunteer[volunteer_ids][]\" id=\"supervisor_volunteer_volunteer_ids_${row.id}\" value=\"${row.id}\" class=\"form-check-input\" data-select-all-target=\"checkbox\" data-action=\"select-all#toggleSingle\">\n          `\n        }\n      },\n      {\n        name: 'display_name',\n        render: (data, type, row, meta) => {\n          return `\n            <span class=\"mobile-label\">Name</span>\n            <a href=\"${editVolunteerPath(row.id)}\">\n              ${row.display_name || row.email}\n            </a>\n          `\n        }\n      },\n      {\n        name: 'email',\n        render: (data, type, row, meta) => row.email\n      },\n      {\n        className: 'supervisor-column',\n        name: 'supervisor_name',\n        render: (data, type, row, meta) => {\n          return row.supervisor.id\n            ? `\n            <span class=\"mobile-label\">Supervisor</span>\n              <a href=\"${editSupervisorPath(row.supervisor.id)}\">\n                ${row.supervisor.name}\n              </a>\n            `\n            : ''\n        }\n      },\n      {\n        name: 'active',\n        render: (data, type, row, meta) => {\n          return `\n            <span class=\"mobile-label\">Status</span>\n            ${row.active === 'true' ? 'Active' : 'Inactive'}\n          `\n        },\n        searchable: false\n      },\n      {\n        name: 'has_transition_aged_youth_cases',\n        render: (data, type, row, meta) => {\n          return `\n          <span class=\"mobile-label\">Assigned to Transitioned Aged Youth</span>\n          ${row.has_transition_aged_youth_cases === 'true' ? 'Yes 🦋' : 'No 🐛'}`\n        },\n        searchable: false\n      },\n      {\n        name: 'casa_cases',\n        render: (data, type, row, meta) => {\n          const links = row.casa_cases.map(casaCase => {\n            return `\n            <a href=\"${casaCasePath(casaCase.id)}\">${casaCase.case_number}</a>\n            `\n          })\n          const caseNumbers = `\n            <span class=\"mobile-label\">Case Number(s)</span>\n            ${links.join(', ')}\n          `\n          return caseNumbers\n        },\n        orderable: false\n      },\n      {\n        name: 'most_recent_attempt_occurred_at',\n        render: (data, type, row, meta) => {\n          return row.most_recent_attempt.case_id\n            ? `\n              <span class=\"mobile-label\">Last Attempted Contact</span>\n              <a href=\"${casaCasePath(row.most_recent_attempt.case_id)}\">\n                ${row.most_recent_attempt.occurred_at}\n              </a>\n            `\n            : 'None ❌'\n        },\n        searchable: false\n      },\n      {\n        name: 'contacts_made_in_past_days',\n        render: (data, type, row, meta) => {\n          return `\n          <span class=\"mobile-label\">Contacts</span>\n          ${row.contacts_made_in_past_days}\n          `\n        },\n        searchable: false\n      },\n      {\n        name: 'hours_spent_in_days',\n        render: (data, type, row, meta) => {\n          return `\n            <span class=\"mobile-label\">Hours spent in last 30 days</span>\n            ${row.hours_spent_in_days}\n          `\n        },\n        searchable: false\n      },\n      {\n        name: 'has_any_extra_languages ',\n        render: (data, type, row, meta) => {\n          const languages = row.extra_languages.map(x => x.name).join(', ')\n          return row.extra_languages.length > 0 ? `<span class=\"language-icon\" data-toggle=\"tooltip\" title=\"${languages}\">🌎</span>` : ''\n        },\n        searchable: false\n      },\n      {\n        name: 'actions',\n        orderable: false,\n        render: (data, type, row, meta) => {\n          return `\n          <span class=\"mobile-label\">Actions</span>\n            <a href=\"${editVolunteerPath(row.id)}\" class=\"btn btn-primary text-white\">\n              Edit\n            </a>\n            <a href=\"${impersonateVolunteerPath(row.id)}\" class=\"btn btn-secondary text-white\">\n              Impersonate\n            </a>\n          `\n        },\n        searchable: false\n      }\n    ],\n    processing: true,\n    serverSide: true,\n    ajax: {\n      url: $('table#volunteers').data('source'),\n      type: 'POST',\n      data: function (d) {\n        const supervisorOptions = $('.supervisor-options input:checked')\n        const supervisorFilter = Array.from(supervisorOptions).map(option => option.dataset.value)\n\n        const statusOptions = $('.status-options input:checked')\n        const statusFilter = Array.from(statusOptions).map(option => JSON.parse(option.dataset.value))\n\n        const transitionYouthOptions = $('.transition-youth-options input:checked')\n        const transitionYouthFilter = Array.from(transitionYouthOptions).map(option => JSON.parse(option.dataset.value))\n\n        const extraLanguageOptions = $('.extra-language-options input:checked')\n        const extraLanguageFilter = Array.from(extraLanguageOptions).map(option => JSON.parse(option.dataset.value))\n        return $.extend({}, d, {\n          additional_filters: {\n            supervisor: supervisorFilter,\n            active: statusFilter,\n            transition_aged_youth: transitionYouthFilter,\n            extra_languages: extraLanguageFilter\n          }\n        })\n      },\n      error: handleAjaxError,\n      dataType: 'json'\n    },\n    drawCallback: function (settings) {\n      $('[data-toggle=tooltip]').tooltip()\n    }\n  })\n\n  // Because the table saves state, we have to check/uncheck modal inputs based on what\n  // columns are visible\n  volunteersTable.columns().every(function (index) {\n    const columnVisible = this.visible()\n    $('#visibleColumns input[data-column=\"' + index + '\"]').prop('checked', columnVisible)\n    return true\n  })\n\n  // Add Supervisors Table\n  const supervisorsTable = $('table#supervisors').DataTable({\n    autoWidth: false,\n    stateSave: false,\n    order: [[1, 'asc']], // order by cast contacts\n    columns: [\n      {\n        name: 'display_name',\n        className: 'min-width',\n        render: (data, type, row, meta) => {\n          return `\n            <a href=\"${editSupervisorPath(row.id)}\">\n              ${row.display_name || row.email}\n            </a>\n          `\n        }\n      },\n      {\n        name: '',\n        className: 'min-width',\n        render: (data, type, row, meta) => {\n          const noContactVolunteers = Number(row.no_attempt_for_two_weeks)\n          const transitionAgedCaseVolunteers = Number(row.transitions_volunteers)\n          const activeContactVolunteers = Number(row.volunteer_assignments) - noContactVolunteers\n          const activeContactElement = activeContactVolunteers\n            ? (\n            `\n            <span class=\"attempted-contact supervisor-stat success-bg text-white pl-${activeContactVolunteers * 15} pr-${activeContactVolunteers * 15}\" title=\"Number of Volunteers attempting contact (within 2 weeks)\">\n              ${activeContactVolunteers}\n            </span>\n            `\n              )\n            : ''\n\n          const noContactElement = noContactVolunteers > 0\n            ? (\n            `\n            <span class=\"no-attempted-contact supervisor-stat danger-bg text-white pl-${noContactVolunteers * 15} pr-${noContactVolunteers * 15}\" title=\"Number of Volunteers not attempting contact (within 2 weeks)\">\n              ${noContactVolunteers}\n            </span>\n            `\n              )\n            : ''\n\n          let volunteersCounterElement = ''\n          if (activeContactVolunteers <= 0 && noContactVolunteers <= 0) {\n            volunteersCounterElement = '<span class=\"no-volunteers\" style=\"flex-grow: 1\">No assigned volunteers</span>'\n          } else {\n            volunteersCounterElement = `<span class=\"supervisor-stat deactive-bg text-black pl-${transitionAgedCaseVolunteers * 15} pr-${transitionAgedCaseVolunteers * 15}\" title=\"Count of Transition Aged Youth\">${transitionAgedCaseVolunteers}</span>`\n          }\n\n          return `\n            <div class=\"supervisor_case_contact_stats\">\n              ${activeContactElement + noContactElement + volunteersCounterElement}\n            </div>\n          `\n        }\n      },\n      {\n        name: 'actions',\n        orderable: false,\n        render: (data, type, row, meta) => {\n          return `\n            <a href=\"${editSupervisorPath(row.id)}\">\n              <div class=\"action\">\n                <button class=\"text-danger\">\n                 <i class=\"lni lni-pencil-alt\"></i>Edit\n                </button>\n              </div>\n            </a>\n          `\n        },\n        searchable: false\n      }\n    ],\n    processing: true,\n    serverSide: true,\n    ajax: {\n      url: $('table#supervisors').data('source'),\n      type: 'POST',\n      data: function (d) {\n        const statusOptions = $('.status-options input:checked')\n        const statusFilter = Array.from(statusOptions).map(option => JSON.parse(option.dataset.value))\n\n        return $.extend({}, d, {\n          additional_filters: {\n            active: statusFilter\n          }\n        })\n      },\n      error: handleAjaxError,\n      dataType: 'json'\n    },\n    drawCallback: function (settings) {\n      $('[data-toggle=tooltip]').tooltip()\n    },\n    createdRow: function (row, data, dataIndex, cells) {\n      row.setAttribute('id', `supervisor-${data.id}-information`)\n    }\n  })\n\n  const casaCasesTable = $('table#casa-cases').DataTable({\n    autoWidth: false,\n    stateSave: false,\n    columnDefs: [],\n    language: {\n      emptyTable: 'No active cases'\n    }\n  })\n\n  casaCasesTable.columns().every(function (index) {\n    const columnVisible = this.visible()\n\n    if (columnVisible) {\n      $('#visibleColumns input[data-column=\"' + index + '\"]').prop('checked', true)\n    } else {\n      $('#visibleColumns input[data-column=\"' + index + '\"]').prop('checked', false)\n    }\n\n    return true\n  })\n\n  defineCaseContactsTable()\n\n  function filterOutUnassignedVolunteers (checked) {\n    $('.supervisor-options').find('input[type=\"checkbox\"]').not('#unassigned-vol-filter').each(function () {\n      this.checked = checked\n    })\n  }\n\n  $('#unassigned-vol-filter').on('click', function () {\n    if ($('#unassigned-vol-filter').is(':checked')) {\n      filterOutUnassignedVolunteers(false)\n    } else {\n      filterOutUnassignedVolunteers(true)\n    }\n    volunteersTable.draw()\n  })\n\n  $('.volunteer-filters input[type=\"checkbox\"]').not('#unassigned-vol-filter').on('click', function () {\n    volunteersTable.draw()\n  })\n\n  $('.supervisor-filters input[type=\"checkbox\"]').on('click', function () {\n    supervisorsTable.draw()\n  })\n\n  $('.casa-case-filters input[type=\"checkbox\"]').on('click', function () {\n    casaCasesTable.draw()\n  })\n\n  $('input.toggle-visibility').on('click', function (e) {\n    // Get the column API object and toggle the visibility\n    const column = volunteersTable.column($(this).attr('data-column'))\n    column.visible(!column.visible())\n    volunteersTable.columns.adjust().draw()\n\n    const caseColumn = casaCasesTable.column($(this).attr('data-column'))\n    caseColumn.visible(!caseColumn.visible())\n    casaCasesTable.columns.adjust().draw()\n  })\n})\n\nexport { defineCaseContactsTable }\n"
  },
  {
    "path": "app/javascript/src/display_app_metric.js",
    "content": "import { Chart, registerables } from 'chart.js'\nimport 'chartjs-adapter-luxon'\n\nconst { Notifier } = require('./notifier')\n\nChart.register(...registerables)\n\nconst days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']\n\n$(() => { // JQuery's callback for the DOM loading\n  const caseContactCreationTimesBubbleChart = document.getElementById('caseContactCreationTimeBubbleChart')\n  const monthLineChart = document.getElementById('monthLineChart')\n  const uniqueUsersMonthLineChart = document.getElementById('uniqueUsersMonthLineChart')\n\n  const notificationsElement = $('#notifications')\n  const pageNotifier = notificationsElement.length ? new Notifier(notificationsElement) : null\n\n  if (caseContactCreationTimesBubbleChart) {\n    fetchDataAndCreateChart('/health/case_contacts_creation_times_in_last_week', caseContactCreationTimesBubbleChart, function (data) {\n      const timestamps = data.timestamps\n      const graphData = formatTimestampsAsBubbleChartData(timestamps)\n      createChart(caseContactCreationTimesBubbleChart, graphData)\n    })\n  }\n\n  if (monthLineChart) {\n    fetchDataAndCreateChart('/health/monthly_line_graph_data', monthLineChart, function (data) {\n      console.log(data)\n      createLineChartForCaseContacts(monthLineChart, data)\n    })\n  }\n\n  if (uniqueUsersMonthLineChart) {\n    fetchDataAndCreateChart('/health/monthly_unique_users_graph_data', uniqueUsersMonthLineChart, function (data) {\n      console.log(data)\n      createLineChartForUniqueUsersMonthly(uniqueUsersMonthLineChart, data)\n    })\n  }\n\n  function fetchDataAndCreateChart (url, chartElement, successCallback) {\n    $.ajax({\n      type: 'GET',\n      url,\n      success: successCallback,\n      error: handleAjaxError\n    })\n  }\n\n  function handleAjaxError (xhr, status, error) {\n    console.error('Failed to fetch data for case contact entry times chart display')\n    console.error(error)\n    pageNotifier?.notify('Failed to display metric chart. Check the console for error details.', 'error')\n  }\n})\n\nfunction formatTimestampsAsBubbleChartData (timestamps) {\n  const bubbleDataAsObject = {}\n\n  for (const timestamp of timestamps) {\n    const contactCreationTime = new Date(timestamp * 1000)\n    const day = contactCreationTime.getDay()\n    const hour = contactCreationTime.getHours()\n\n    // Group case contacts with the same hour and day creation time into the same data point\n\n    let dayData\n\n    if (!(day in bubbleDataAsObject)) {\n      dayData = {}\n      bubbleDataAsObject[day] = dayData\n    } else {\n      dayData = bubbleDataAsObject[day]\n    }\n\n    if (!(hour in dayData)) {\n      dayData[hour] = 1\n    } else {\n      dayData[hour]++\n    }\n  }\n\n  // Flatten data points\n\n  const bubbleDataAsArray = []\n\n  for (const day in bubbleDataAsObject) {\n    const hours = bubbleDataAsObject[day]\n\n    for (const hour in hours) {\n      bubbleDataAsArray.push({\n        x: hour,\n        y: day,\n        r: Math.sqrt(hours[hour]) * 4\n      })\n    }\n  }\n\n  return bubbleDataAsArray\n}\n\nfunction createChart (chartElement, dataset) {\n  const ctx = chartElement.getContext('2d')\n\n  return new Chart(ctx, {\n    type: 'bubble',\n    data: {\n      datasets: [\n        {\n          label: 'Case Contact Creation Times',\n          data: dataset,\n          backgroundColor: 'rgba(255, 99, 132, 0.2)',\n          borderColor: 'rgba(255, 99, 132, 1)'\n        }\n      ]\n    },\n    options: {\n      scales: {\n        x: {\n          min: 0,\n          max: 23,\n          ticks: {\n            beginAtZero: true,\n            stepSize: 1\n          }\n        },\n        y: {\n          min: 0,\n          max: 6,\n          ticks: {\n            beginAtZero: true,\n            callback: getYTickCallback,\n            stepSize: 1\n          }\n        }\n      },\n      plugins: {\n        legend: {\n          display: false\n        },\n        title: {\n          display: true,\n          font: {\n            size: 18\n          },\n          text: 'Case Contact Creation Times in the Past Week'\n        },\n        tooltip: {\n          callbacks: {\n            label: getTooltipLabelCallback\n          }\n        }\n      }\n    }\n  })\n}\n\nfunction getYTickCallback (value) {\n  return days[value]\n}\n\nfunction getTooltipLabelCallback (context) {\n  const bubbleData = context.dataset.data[context.dataIndex]\n  const caseContactCountSqrt = bubbleData.r / 4\n  return `${Math.round(caseContactCountSqrt * caseContactCountSqrt)} case contacts created on ${days[bubbleData.y]} at ${bubbleData.x}:00`\n}\n\nfunction createLineChartForCaseContacts (chartElement, dataset) {\n  const datasetLabels = ['Total Case Contacts', 'Total Case Contacts with Notes', 'Total Case Contact Users']\n  return createLineChart(chartElement, dataset, 'Case Contact Creation', datasetLabels)\n}\n\nfunction createLineChartForUniqueUsersMonthly (chartElement, dataset) {\n  const datasetLabels = ['Total Volunteers', 'Total Supervisors', 'Total Admins', 'Total logged Volunteers']\n  return createLineChart(chartElement, dataset, 'Monthly Active Users', datasetLabels)\n}\n\nfunction createLineChart (chartElement, dataset, chartTitle, datasetLabels) {\n  const ctx = chartElement.getContext('2d')\n  const allMonths = extractChartData(dataset, 0)\n  const datasets = []\n  const colors = ['#308af3', '#48ba16', '#FF0000', '#FFA500']\n\n  for (let i = 1; i < dataset[0].length; i++) {\n    const data = extractChartData(dataset, i)\n    const label = datasetLabels[i - 1]\n    const color = colors[i - 1]\n    datasets.push(createLineChartDataset(label, data, color, color))\n  }\n\n  return new Chart(ctx, {\n    type: 'line',\n    data: {\n      labels: allMonths,\n      datasets\n    },\n    options: createChartOptions(chartTitle)\n  })\n}\n\nfunction extractChartData (dataset, index) {\n  return dataset.map(data => data[index])\n}\n\nfunction createLineChartDataset (label, data, borderColor, pointBackgroundColor) {\n  return {\n    label,\n    data,\n    fill: false,\n    borderColor,\n    pointBackgroundColor,\n    pointBorderWidth: 2,\n    pointHoverBackgroundColor: '#fff',\n    pointHoverBorderWidth: 2,\n    lineTension: 0.05\n  }\n}\n\nfunction createChartOptions (label) {\n  return {\n    legend: { display: true },\n    plugins: {\n      legend: { display: true, position: 'bottom' },\n      title: {\n        display: true,\n        font: { size: 18 },\n        text: label\n      },\n      tooltips: {\n        callbacks: {\n          label: function (tooltipItem, data) {\n            let label = data.datasets[tooltipItem.datasetIndex].label || ''\n            if (label) {\n              label += ': '\n            }\n            label += Math.round(tooltipItem.yLabel * 100) / 100\n            return label\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "app/javascript/src/emancipations.js",
    "content": "/* global $ */\n\n$(() => { // JQuery's callback for the DOM loading\n  $('table#all-case-emancipations').DataTable({\n    autoWidth: false,\n    searching: false,\n    stateSave: false,\n    columnDefs: [\n      { orderable: false, targets: 1 }\n    ],\n    language: {\n      emptyTable: 'No transitioning cases'\n    }\n  })\n})\n"
  },
  {
    "path": "app/javascript/src/import.js",
    "content": "/* global atob */\n/* global Blob */\n/* global FileReader */\n/* global localStorage */\n/* global File */\n/* global DataTransfer */\n/* global $ */\n\nfunction dataURItoBlob (dataURI) {\n  // convert base64 to raw binary data held in a string\n  const byteString = atob(dataURI.split(',')[1])\n\n  // separate out the mime component\n  const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]\n\n  // write the bytes of the string to an ArrayBuffer\n  const arrayBuffer = new ArrayBuffer(byteString.length)\n  const _ia = new Uint8Array(arrayBuffer)\n  for (let i = 0; i < byteString.length; i++) {\n    _ia[i] = byteString.charCodeAt(i)\n  }\n\n  const dataView = new DataView(arrayBuffer)\n  const blob = new Blob([dataView], { type: mimeString })\n  return blob\n}\n\nfunction storeCSVFile (file, key) {\n  const reader = new FileReader()\n  reader.onload = (fileEvent) => {\n    localStorage[key] = JSON.stringify({\n      name: file.name,\n      data: fileEvent.target.result\n    })\n  }\n  reader.readAsDataURL(file)\n}\n\nfunction fetchCSVFile (key) {\n  const storedFileData = JSON.parse(localStorage[key])\n  const fileContent = dataURItoBlob(storedFileData.data)\n  const file = new File([fileContent], storedFileData.name, { type: 'text/csv' })\n  return file\n}\n\nfunction populateFileInput (inputId) {\n  const csvInput = document.getElementById(inputId)\n  if (csvInput.files.length === 0 && localStorage[inputId]) {\n    const file = fetchCSVFile(inputId)\n    const container = new DataTransfer()\n    container.items.add(file)\n    csvInput.files = container.files\n  }\n}\n\n$(() => { // JQuery's callback for the DOM loading\n  ['volunteer', 'supervisor'].forEach((importType) => {\n    const inputFileElementId = `${importType}-file`\n    const inputFileElement = $(`#${inputFileElementId}`)[0]\n    const importButtonElement = $(`#${importType}-import-button`)[0]\n\n    if (inputFileElement && importButtonElement) {\n      inputFileElement.addEventListener('change', function (event) {\n        importButtonElement.disabled = event.target.value === ''\n        const file = inputFileElement.files[0]\n        storeCSVFile(file, inputFileElementId)\n      })\n\n      if ($('#smsOptIn') == null) {\n        delete localStorage[inputFileElementId]\n      } else {\n        populateFileInput(inputFileElementId)\n      }\n    }\n  })\n})\n"
  },
  {
    "path": "app/javascript/src/learning_hours.js",
    "content": "import { initializeDataTable } from '../datatable'\n\n$(() => {\n  initializeDataTable('table#learning-hours')\n})\n"
  },
  {
    "path": "app/javascript/src/new_casa_case.js",
    "content": "/* eslint-env jquery */\n\nconst COURT_DATE_TOGGLE_CLASS = 'toggle-court-date-input'\nconst COURT_DATE_INPUT_ID = 'casa_case_court_dates_attributes_0_date'\n\n$(() => { // JQuery's callback for the DOM loading\n  const courtDateToggle = $(`.${COURT_DATE_TOGGLE_CLASS}`)[0]\n  const courtDateInput = $(`#${COURT_DATE_INPUT_ID}`)[0]\n\n  courtDateToggle?.addEventListener('change', () => {\n    courtDateInput.hidden = courtDateToggle.checked\n  })\n})\n"
  },
  {
    "path": "app/javascript/src/notifier.js",
    "content": "/* global $ */\nimport { escape } from 'lodash'\n\nconst TypeChecker = require('./type_checker.js')\n\nconst levelClasses = {\n  error: 'danger',\n  info: 'success',\n  warn: 'warning'\n}\n\nclass Notification {\n  constructor (notificationElementAsJQuery, parentNotifier) {\n    TypeChecker.checkNonEmptyJQueryObject(notificationElementAsJQuery, 'notificationElementAsJQuery')\n\n    if (!(parentNotifier instanceof Notifier)) {\n      throw new TypeError('Param parentNotifier must be an instance of Notifier')\n    }\n\n    const levelCapturedViaClassNames = notificationElementAsJQuery.attr('class').match(/(warning|danger|success)-notification/)\n\n    if (!levelCapturedViaClassNames) {\n      throw new RangeError('Failed to parse notification level from notification level class')\n    }\n\n    this.level = levelCapturedViaClassNames[1]\n\n    if (this.level === 'danger') {\n      this.level = 'error'\n    } else if (this.level === 'success') {\n      this.level = 'info'\n    } else if (this.level === 'warning') {\n      this.level = 'warn'\n    }\n\n    notificationElementAsJQuery.children('button').on('click', () => {\n      this.dismiss()\n    })\n\n    this.notificationElement = notificationElementAsJQuery\n    this.parentNotifier = parentNotifier\n  }\n\n  dismiss () {\n    this.#throwErrorIfDismissed()\n\n    this.notificationElement.remove()\n    this.parentNotifier.notificationsCount[this.level]--\n  }\n\n  getText () {\n    return this.notificationElement.children('span').text()\n  }\n\n  isUserDismissable () {\n    return this.notificationElement.children('button').length\n  }\n\n  isDismissed () {\n    return !($('#notifications').find(this.notificationElement).length)\n  }\n\n  setUserDismissable (dismissable) {\n    this.#throwErrorIfDismissed()\n\n    if (dismissable && !(this.isUserDismissable())) {\n      this.#userDismissableEnable()\n    } else if (!dismissable && this.isUserDismissable()) {\n      this.#userDismissableDisable()\n    }\n  }\n\n  setText (newText) {\n    this.#throwErrorIfDismissed()\n    TypeChecker.checkString(newText, 'newText')\n\n    return this.notificationElement.children('span').text(newText)\n  }\n\n  #throwErrorIfDismissed () { // Methods prefixed with a hash are private\n    if (this.isDismissed()) {\n      throw new ReferenceError('Invalid Operation. This notification has been dismissed.')\n    }\n  }\n\n  toggleUserDismissable () {\n    this.#throwErrorIfDismissed()\n\n    if (this.isUserDismissable()) {\n      this.#userDismissableDisable()\n    } else {\n      this.#userDismissableEnable()\n    }\n  }\n\n  #userDismissableDisable () {\n    this.notificationElement.children('button').remove()\n  }\n\n  #userDismissableEnable () {\n    const dismissButton = $(`<button class=\"btn btn-${levelClasses[this.level]} btn-sm\">×</button>`)\n    this.notificationElement.append(dismissButton)\n\n    dismissButton.on('click', () => {\n      this.dismiss()\n    })\n  }\n}\n\nclass Notifier {\n  //  @param {object} notificationsElement The notification DOM element as a jQuery object\n  constructor (notificationsElement) {\n    TypeChecker.checkNonEmptyJQueryObject(notificationsElement, 'notificationsElement')\n\n    const outer = this\n\n    this.elements = {\n      loadingToast: notificationsElement.find('#async-waiting-indicator'),\n      messageCountBadges: {\n        all: notificationsElement.find('#toggle-minimize-notifications .badge'),\n        error: notificationsElement.find('#toggle-minimize-notifications .bg-danger'),\n        info: notificationsElement.find('#toggle-minimize-notifications .bg-success'),\n        warn: notificationsElement.find('#toggle-minimize-notifications .bg-warning')\n      },\n      messagesContainer: notificationsElement.children('.messages'),\n      minimizeButton: notificationsElement.children('#toggle-minimize-notifications'),\n      minimizeButtonIcon: notificationsElement.find('#toggle-minimize-notifications i'),\n      minimizeButtonText: notificationsElement.find('#toggle-minimize-notifications span').first(),\n      notificationsElement,\n      savedToast: notificationsElement.find('#async-success-indicator')\n    }\n\n    this.notificationsCount = new Proxy({\n      error: 0,\n      info: 0,\n      warn: 0\n    }, {\n      set (target, propertyKey, value) {\n        const defaultSet = Reflect.set(target, propertyKey, value)\n\n        if (outer.totalNotificationCount()) {\n          outer.#setMinimizeButtonVisibility(true)\n        } else {\n          outer.#setMinimizeButtonVisibility(false)\n        }\n\n        const levelBadge = outer.elements.messageCountBadges[propertyKey]\n\n        levelBadge.text(value)\n\n        if (value && outer.elements.messagesContainer.css('display') === 'none') {\n          levelBadge.show()\n        } else {\n          levelBadge.hide()\n        }\n\n        return defaultSet\n      }\n    })\n\n    this.savedToastTimeouts = []\n    this.waitingAsyncOperationCount = 0\n\n    this.elements.minimizeButton.on('click', () => {\n      this.#toggleMinimize()\n    })\n  }\n\n  // Adds notification messages to the notification element\n  //  @param   {string} message The message to be displayed\n  //  @param   {string} level One of the following logging levels\n  //    \"error\"  Shows a red notification\n  //    \"info\"   Shows a green notification\n  //    \"warn\"   Shows an orange notification\n  //  @returns {jQuery} a jQuery object representing the new notification\n  //  @throws  {TypeError}  for a parameter of the incorrect type\n  //  @throws  {RangeError} for unsupported logging levels\n\n  notify (message, level, isDismissable = true) {\n    TypeChecker.checkString(message, 'message')\n\n    const escapedMessage = escape(message)\n\n    if (!(levelClasses[level])) {\n      throw new RangeError('Unsupported option for param level')\n    }\n\n    const dismissButtonAsHTML = isDismissable ? `<button class=\"btn btn-${levelClasses[level]} btn-sm\">×</button>` : ''\n    const newNotificationAsJQuery =\n      $(\n        `<div class=\"${levelClasses[level]}-notification\">\n          <span>${escapedMessage}</span>\n          ${dismissButtonAsHTML}\n        </div>`\n      )\n\n    this.elements.messagesContainer.append(newNotificationAsJQuery)\n\n    const newNotification = new Notification(newNotificationAsJQuery, this)\n\n    this.notificationsCount[level]++\n\n    return newNotification\n  }\n\n  // Shows the saved toast for 2 seconds and hides the loading indicator if no more async operations are pending\n  //  @param  {string=}  error The error to be displayed(optional)\n  //  @throws {Error}    for trying to resolve more async operations than the amount currently awaiting\n  resolveAsyncOperation (errorMsg) {\n    if (this.waitingAsyncOperationCount < 1) {\n      const resolveNonexistantOperationError = 'Attempted to resolve an async operation when awaiting none'\n      this.notify(resolveNonexistantOperationError, 'error')\n      throw new Error(resolveNonexistantOperationError)\n    }\n\n    this.waitingAsyncOperationCount--\n\n    if (this.waitingAsyncOperationCount === 0) {\n      this.elements.loadingToast.hide()\n    }\n\n    if (!errorMsg) {\n      this.elements.savedToast.show()\n\n      this.savedToastTimeouts.forEach((timeoutID) => {\n        clearTimeout(timeoutID)\n      })\n\n      this.savedToastTimeouts.push(setTimeout(() => {\n        this.elements.savedToast.hide()\n        this.savedToastTimeouts.shift()\n      }, 2000))\n    } else {\n      if (!(typeof errorMsg === 'string' || errorMsg instanceof String)) {\n        throw new TypeError('Param errorMsg must be a string')\n      }\n\n      this.notify(errorMsg, 'error')\n    }\n  }\n\n  #setMinimizeButtonVisibility (visible) {\n    if (visible) {\n      this.elements.minimizeButton.show()\n    } else {\n      this.elements.minimizeButton.hide()\n    }\n  }\n\n  #toggleMinimize () {\n    const { messagesContainer } = this.elements\n\n    if (messagesContainer.css('display') === 'none') {\n      messagesContainer.show()\n      this.elements.minimizeButtonText.show()\n      this.elements.messageCountBadges.all.hide()\n      this.elements.minimizeButtonIcon.removeClass('fa-plus').addClass('fa-minus')\n    } else {\n      messagesContainer.hide()\n\n      for (const level in this.notificationsCount) {\n        const levelMessageCount = this.notificationsCount[level]\n\n        if (levelMessageCount) {\n          const levelBadge = this.elements.messageCountBadges[level]\n          levelBadge.show()\n        }\n      }\n\n      this.elements.minimizeButtonText.hide()\n      this.elements.minimizeButtonIcon.removeClass('fa-minus').addClass('fa-plus')\n    }\n  }\n\n  totalNotificationCount () {\n    return Object.values(this.notificationsCount).reduce((acc, currentValue) => {\n      return acc + currentValue\n    }, 0)\n  }\n\n  // Shows a loading indicator until all operations resolve\n  waitForAsyncOperation () {\n    this.elements.loadingToast.show()\n    this.waitingAsyncOperationCount++\n  }\n}\n\nmodule.exports = { Notifier, Notification }\n"
  },
  {
    "path": "app/javascript/src/password_confirmation.js",
    "content": "/* eslint-env jquery */\n/* global $ */\n\nimport Swal from 'sweetalert2'\nimport { disableBtn, enableBtn } from './casa_case'\n\nconst SUBMIT_BUTTON_CLASS = 'submit-password'\nconst PASSWORD_FIELD_CLASS = 'password-new'\nconst CONFIRMATION_FIELD_CLASS = 'password-confirmation'\n\nfunction disableButtonWhenEmptyString (str, button) {\n  str.length === 0 ? disableBtn(button) : enableBtn(button)\n}\n\n// Checks if the password is equivalent to confirmation and has at least 1 character. If not,\n// it will disable the button.\n//  @param    {HTMLElement}  button - submit button for the form field\n//  @param    {HTMLElement}  password - text input form field\n//  @param    {HTMLElement}  confirmation - text input form field\n//  @param    {boolean}  enablePopup - display popup when field is not in focus\nfunction checkPasswordsAndDisplayPopup (button, password, confirmation, enablePopup = false) {\n  const passwordText = password.value\n  const confirmationText = confirmation.value\n\n  if (passwordText === confirmationText) {\n    disableButtonWhenEmptyString(passwordText, button)\n  } else {\n    if (enablePopup) {\n      Swal.fire({\n        icon: 'error',\n        title: 'Password Error',\n        text: 'The password and the confirmation password do not match'\n      })\n    }\n    disableBtn(button)\n  }\n}\n\n// Expects the class name constants above are applied to the correct fields. See\n// `app/views/users/edit.html.erb` for usage\n$(() => { // JQuery's callback for the DOM loading\n  if ($(`.${SUBMIT_BUTTON_CLASS}`).length > 0) {\n    const button = $(`.${SUBMIT_BUTTON_CLASS}`)[0]\n    const password = $(`.${PASSWORD_FIELD_CLASS}`)[0]\n    const confirmation = $(`.${CONFIRMATION_FIELD_CLASS}`)[0]\n\n    disableBtn(button)\n\n    $(`.${PASSWORD_FIELD_CLASS}`).on('blur', () => {\n      checkPasswordsAndDisplayPopup(button, password, confirmation)\n    })\n\n    $(`.${CONFIRMATION_FIELD_CLASS}`).on('blur', () => {\n      checkPasswordsAndDisplayPopup(button, password, confirmation, true)\n    })\n  }\n})\n\nexport {\n  checkPasswordsAndDisplayPopup\n}\n"
  },
  {
    "path": "app/javascript/src/read_more.js",
    "content": "document.addEventListener('DOMContentLoaded', () => {\n  document.addEventListener('click', (event) => {\n    if (event.target.matches('.js-read-more')) {\n      return handleReadMore(event)\n    }\n\n    if (event.target.matches('.js-read-less')) {\n      return handleReadLess(event)\n    }\n  })\n})\n\nconst handleReadMore = (event) => {\n  event.preventDefault()\n\n  const wrapper = event.target.closest('.js-read-more-text-wrapper')\n  wrapper.querySelector('.js-full-text').style.display = 'block'\n  wrapper.querySelector('.js-truncated-text').style.display = 'none'\n}\n\nconst handleReadLess = (event) => {\n  event.preventDefault()\n\n  const wrapper = event.target.closest('.js-read-more-text-wrapper')\n  wrapper.querySelector('.js-truncated-text').style.display = 'block'\n  wrapper.querySelector('.js-full-text').style.display = 'none'\n}\n"
  },
  {
    "path": "app/javascript/src/reimbursements.js",
    "content": "/* global $ */\n\n$(() => { // JQuery's callback for the DOM loading\n  const { groupBy, map, mapValues } = require('lodash')\n\n  const formatOccurredAtDate = (record) => new Date(record.occurred_at.replaceAll('-', ' ')).toDateString()\n\n  const mapContactTypes = (contactTypes) => {\n    return mapValues(\n      groupBy(contactTypes, 'group_name'),\n      contactType => map(contactType, 'name').join(', ')\n    )\n  }\n\n  const renderContactTypes = (record) => {\n    if (!record || !Array.isArray(record.contact_types)) {\n      return ''\n    }\n\n    return map(\n      mapContactTypes(record.contact_types),\n      (names, groupName) => `${groupName} (${names})`\n    ).join(', ')\n  }\n\n  const renderCompleteCheckbox = (record) => `\n    <label>\n      Yes\n      <input\n        ${record.complete === 'true' ? 'checked' : ''}\n        name=\"case_contact[reimbursement_complete]\"\n        type=\"checkbox\"\n        data-submit-to=\"${record.mark_as_complete_path}\"\n      />\n    </label>\n  `\n\n  const editVolunteerPath = id => `/volunteers/${id}/edit`\n  const casaCasePath = id => `/casa_cases/${id}`\n\n  const onMarkAsCompleteChange = (event) => {\n    const $checkbox = $(event.target)\n\n    try {\n      const url = $checkbox.data('submit-to')\n      const reimbursementComplete = $checkbox.is(':checked')\n\n      if (!url) {\n        throw new Error('URL missing')\n      }\n\n      $.ajax(url, {\n        data: JSON.stringify({\n          case_contact: {\n            reimbursement_complete: reimbursementComplete\n          },\n          ajax: true\n        }),\n        method: 'PATCH',\n        contentType: 'application/json'\n      }).then(() => reimbursementsTable.draw())\n    } catch (error) {\n      console.error(error)\n      event.target.checked = !event.target.checked\n      window.alert('Failed to update reimbursement complete setting')\n    }\n\n    return false\n  }\n\n  $('table#reimbursements-datatable').on('draw.dt', function (e) {\n    $(e.target)\n      .find('input[name=\"case_contact[reimbursement_complete]\"]')\n      .on('change', onMarkAsCompleteChange)\n  })\n\n  $('[data-filter=\"volunteer\"] .select2').on('select2:select select2:unselect', () => reimbursementsTable.draw())\n  $('[data-filter=\"occurred_at\"] input').on('change', () => reimbursementsTable.draw())\n\n  const handleAjaxError = e => {\n    console.error('AJAX Error:')\n    console.error(e)\n\n    if (e.responseJSON && e.responseJSON.error) {\n      window.alert(e.responseJSON.error)\n    } else {\n      const responseErrorMessage = e.response.statusText\n        ? `\\n${e.response.statusText}\\n`\n        : ''\n\n      window.alert(`Sorry, try that again?\\n${responseErrorMessage}\\nIf you're seeing a problem, please fill out the Report A Site Issue\n      link to the bottom left near your email address.`)\n    }\n  }\n\n  const getDatatableFilterParams = () => {\n    return {\n      volunteers: selectedVolunteerIdsOrAll(),\n      occurred_at: {\n        start: $('input[name=\"occurred_at_starting\"]').val(),\n        end: $('input[name=\"occurred_at_ending\"]').val()\n      }\n    }\n  }\n\n  const selectedVolunteerIdsOrAll = () => {\n    let selectedVols = $('[data-filter=\"volunteer\"] .creator_ids').val()\n    if (selectedVols.length === 0) {\n      selectedVols = map($('[data-filter=\"volunteer\"] .creator_ids option'), 'value')\n    }\n    return selectedVols\n  }\n\n  const reimbursementsTableCols = [\n    {\n      name: 'display_name',\n      render: (data, type, row, meta) => {\n        return `\n          <span class=\"mobile-label\">Volunteer</span>\n          <a href=\"${editVolunteerPath(row.volunteer.id)}\">\n            ${row.volunteer.display_name || row.volunteer.email}\n          </a>\n        `\n      }\n    },\n    {\n      name: 'case_number',\n      render: (data, type, row, meta) => {\n        return `\n          <span class=\"mobile-label\">Case Number(s)</span>\n          <a href=\"${casaCasePath(row.casa_case.id)}\">${row.casa_case.case_number}</a>\n        `\n      }\n    },\n    {\n      name: 'contact_types',\n      render: (data, type, row, meta) => {\n        return `\n          <span class=\"mobile-label\">Contact Type(s)</span>\n            ${renderContactTypes(row)}\n          `\n      },\n      orderable: false\n    },\n    {\n      name: 'occurred_at',\n      render: (data, type, row, meta) => {\n        return `\n          <span class=\"mobile-label\">Date Added</span>\n          ${formatOccurredAtDate(row)}\n        `\n      }\n    },\n    {\n      name: 'miles_driven',\n      render: (data, type, row, meta) => {\n        return `\n          <span class=\"mobile-label\">Expense Type</span>\n          ${row.miles_driven}\n        `\n      }\n    },\n    {\n      name: 'address',\n      render: (data, type, row, meta) => {\n        return `\n          <span class=\"mobile-label\">Description</span>\n          ${row.volunteer.address}\n        `\n      },\n      orderable: false\n    },\n    {\n      name: 'reimbursement_complete',\n      render: (data, type, row, meta) => {\n        return `\n          <span class=\"mobile-label\">Reimbursement Complete?</span>\n          ${renderCompleteCheckbox(row)}\n        `\n      },\n      orderable: false\n    }\n  ]\n\n  const reimbursementsTable = $('table#reimbursements-datatable').DataTable({\n    autoWidth: false,\n    stateSave: false,\n    order: [[3, 'desc']],\n    searching: false,\n    columns: reimbursementsTableCols,\n    processing: true,\n    serverSide: true,\n    ajax: {\n      url: $('table#reimbursements-datatable').data('source'),\n      type: 'POST',\n      data: function (data) {\n        return $.extend({}, data, getDatatableFilterParams())\n      },\n      error: handleAjaxError,\n      dataType: 'json'\n    }\n  })\n})\n"
  },
  {
    "path": "app/javascript/src/reports.js",
    "content": "document.addEventListener('DOMContentLoaded', () => {\n  document.addEventListener('click', (event) => {\n    if (event.target.matches('.report-form-submit')) {\n      return handleReportFormSubmit(event)\n    }\n  })\n})\n\nconst handleReportFormSubmit = (event) => {\n  event.preventDefault()\n\n  const buttonText = event.target.innerHTML\n\n  event.target.disabled = 'disabled'\n  event.target.innerHTML = event.target.dataset.disableWith\n  event.target.form.submit()\n\n  setTimeout(() => {\n    event.target.disabled = false\n    event.target.innerHTML = buttonText\n  }, 3000)\n}\n"
  },
  {
    "path": "app/javascript/src/require_communication_preference.js",
    "content": "/* eslint-env jquery */\n\nimport Swal from 'sweetalert2'\n\nimport { disableBtn, enableBtn } from './casa_case'\n\nconst EMAIL_TOGGLE_CLASS = 'toggle-email-notifications'\nconst SMS_TOGGLE_CLASS = 'toggle-sms-notifications'\nconst SAVE_BUTTON_CLASS = 'save-preference'\nconst SMS_NOTIFICATION_EVENT_ID = 'toggle-sms-notification-event'\n\nfunction displayPopUpIfPreferencesIsInvalid () {\n  const emailNotificationState = $('#user_receive_email_notifications').prop('checked')\n  const smsNotificationState = $('#user_receive_sms_notifications').prop('checked')\n\n  if (smsNotificationState === false && emailNotificationState === false) {\n    disableBtn($(`.${SAVE_BUTTON_CLASS}`)[0])\n    Swal.fire({\n      icon: 'error',\n      title: 'Contact Method Needed',\n      text: 'Please select at least one method of contact so we can communicate with you.'\n    })\n  } else {\n    enableBtn($(`.${SAVE_BUTTON_CLASS}`)[0])\n  }\n}\n\n$(() => { // JQuery's callback for the DOM loading\n  const smsToggle = $(`.${SMS_TOGGLE_CLASS}`)[0]\n  const emailToggle = $(`.${EMAIL_TOGGLE_CLASS}`)[0]\n\n  emailToggle?.addEventListener('change', () => {\n    displayPopUpIfPreferencesIsInvalid()\n  })\n\n  smsToggle?.addEventListener('change', () => {\n    displayPopUpIfPreferencesIsInvalid()\n  })\n\n  const smsEventToggle = $(`#${SMS_NOTIFICATION_EVENT_ID}`)[0]\n  if (smsToggle && smsEventToggle) {\n    smsEventToggle.disabled = !smsToggle.checked\n    smsToggle.addEventListener('change', () => {\n      smsEventToggle.disabled = !smsToggle.checked\n    })\n  }\n})\n\nexport { displayPopUpIfPreferencesIsInvalid }\n"
  },
  {
    "path": "app/javascript/src/select.js",
    "content": "/* global $ */\n\n$(() => { // JQuery's callback for the DOM loading\n  $('.select2').select2(\n    {\n      theme: 'bootstrap-5',\n      width: $(this).data('width') ? $(this).data('width') : $(this).hasClass('w-100') ? '100%' : 'style',\n      placeholder: $(this).data('placeholder')\n    }\n  )\n})\n"
  },
  {
    "path": "app/javascript/src/session_timeout_poller.js",
    "content": "const timeout = window.timeout || 180\nconst twoMinuteWarning = timeout - 2\nconst totalTimerAmount = twoMinuteWarning * 60 * 1000\nconst deviseTimeoutInMilliseconds = timeout * 60 * 1000\nconst startTime = new Date().getTime()\nlet lastTime = new Date().getTime()\nlet currentTime\nlet timeElapsed\n\nfunction warningBoxAndReload () {\n  window.alert('Warning: You will be logged off in 2 minutes due to inactivity.')\n  window.location.reload()\n}\n\nsetInterval(myTimer, 1000)\n\nfunction myTimer () {\n  timeElapsed = Math.abs(lastTime - startTime)\n  currentTime = new Date().getTime()\n  if (timeElapsed > deviseTimeoutInMilliseconds) {\n    window.location.reload()\n  } else if (timeElapsed > totalTimerAmount) {\n    warningBoxAndReload()\n  } else {\n    lastTime = currentTime\n  }\n};\n"
  },
  {
    "path": "app/javascript/src/sms_reactivation_toggle.js",
    "content": "$(() => { // JQuery's callback for the DOM loading\n  if ($('#twilio_disabled').length) {\n    $('#twilio_disabled').removeClass('main-btn danger-btn-outline btn-hover btn-sm my-1')\n    $('#twilio_disabled').addClass('main-btn deactive-btn btn-sm my-1')\n    $('#twilio_tooltip').attr('data-bs-toggle', 'tooltip')\n    $('#twilio_tooltip').attr('data-bs-placement', 'bottom')\n    $('#twilio_tooltip').attr('data-turbo', 'false')\n    $('#twilio_tooltip').attr('title', \"Twilio is not enabled for this user's CASA org\")\n\n    $('#twilio_disabled').on('click', function (event) {\n      event.preventDefault()\n    })\n  }\n})\n"
  },
  {
    "path": "app/javascript/src/time_zone.js",
    "content": "import Cookies from 'js-cookie'\nimport jstz from 'jstz'\n\n// Rails doesn't support every timezone that Intl supports\nexport function findTimeZone () {\n  const oldIntl = window.Intl\n  try {\n    window.Intl = undefined\n    const tz = jstz.determine().name()\n    window.Intl = oldIntl\n    return tz\n  } catch (e) {\n    // sometimes (on android) you can't override intl\n    return jstz.determine().name()\n  }\n}\n\ndocument.addEventListener('DOMContentLoaded', () => {\n  Cookies.set('browser_time_zone', findTimeZone(), { expires: 365, path: '/', secure: true, sameSite: 'strict' })\n})\n"
  },
  {
    "path": "app/javascript/src/tooltip.js",
    "content": "/* global $ */\n\n$(() => { // JQuery's callback for the DOM loading\n  $('[data-toggle=\"tooltip\"]').tooltip()\n})\n"
  },
  {
    "path": "app/javascript/src/type_checker.js",
    "content": "// Object.keys({variable})[0]\n\nmodule.exports = {\n  // Checks if a variable is a JQuery object\n  //  @param  {any}    variable The variable to be checked\n  //  @param  {string} varName  The name of the variable to be checked\n  //  @throws {TypeError} If variable is not a JQuery object\n  //  @throws {ReferenceError} If variable is a JQuery object but points to no elements\n  checkNonEmptyJQueryObject (variable, varName) {\n    if (!(variable instanceof jQuery)) {\n      throw new TypeError(`Param ${varName} must be a jQuery object`)\n    }\n\n    if (!variable.length) {\n      throw new ReferenceError(`Param ${varName} contains no elements`)\n    }\n  },\n\n  // Checks if a variable is a non empty string\n  //  @param  {any}    variable The variable to be checked\n  //  @param  {string} varName  The name of the variable to be checked\n  //  @throws {TypeError} If variable is not a string\n  //  @throws {RangeError} If variable is empty string\n  checkNonEmptyString (variable, varName) {\n    this.checkString(variable, varName)\n\n    if (!(variable.length)) {\n      throw new RangeError(`Param ${varName} cannot be empty string`)\n    }\n  },\n\n  // Checks if a variable is an object\n  //  @param  {any}    variable The variable to be checked\n  //  @param  {string} varName  The name of the variable to be checked\n  //  @throws {TypeError}  If variable is not an object\n  checkObject (variable, varName) {\n    if (typeof variable !== 'object' || Array.isArray(variable) || variable === null) {\n      throw new TypeError(`Param ${varName} is not an object`)\n    }\n  },\n\n  // Checks if a variable is a positive integer\n  //  @param  {any}    variable The variable to be checked\n  //  @param  {string} varName  The name of the variable to be checked\n  //  @throws {TypeError}  If variable is not an integer\n  //  @throws {RangeError} If variable is less than 0\n  checkPositiveInteger (variable, varName) {\n    if (!Number.isInteger(variable)) {\n      throw new TypeError(`Param ${varName} is not an integer`)\n    } else if (variable < 0) {\n      throw new RangeError(`Param ${varName} cannot be negative`)\n    }\n  },\n\n  // Checks if a variable is a string or not\n  //  @param  {any}    variable The variable to be checked\n  //  @param  {string} varName  The name of the variable to be checked\n  //  @throws {TypeError} If variable is not a string\n  checkString (variable, varName) {\n    if (typeof variable !== 'string') {\n      throw new TypeError(`Param ${varName} must be a string`)\n    }\n  }\n}\n"
  },
  {
    "path": "app/javascript/src/validated_form.js",
    "content": "/* global $ */\nconst { Notifier } = require('./notifier')\nconst TypeChecker = require('./type_checker')\n\nconst GET_ERROR_STATE_UNDEFINED_MESSAGE = 'getErrorState for the component is not defined'\nconst GET_WARNING_STATE_UNDEFINED_MESSAGE = 'getWarningState for the component is not defined'\n\n// Abstract Class\nclass ValidatableFormSectionComponent {\n  constructor (componentElementsAsJQuery, notifier) {\n    TypeChecker.checkNonEmptyJQueryObject(componentElementsAsJQuery, 'componentElementsAsJQuery')\n\n    if (!(notifier instanceof Notifier)) {\n      console.error('Warning: unable to show notifications to the user')\n    } else {\n      this.notifier = notifier\n    }\n\n    this.componentElementsAsJQuery = componentElementsAsJQuery\n  }\n\n  // Implement the 4 methods below for an error validation component\n\n  // @returns A string describing the invalid state of the inputs for the user to read, empty string if the inputs are valid\n  getErrorState () {\n    throw new ReferenceError(GET_ERROR_STATE_UNDEFINED_MESSAGE)\n  }\n\n  // @param  {string} errorState The value returned by getErrorState()\n  errorHighlightUI (errorState) {\n    // Highlights the error input area for the user to see easier\n    // If there is no error, returns the component back to the original state\n    throw new ReferenceError('errorHighlightUI for the component is not defined')\n  }\n\n  showUserError (errorMsg) {\n    // Shows the error message to the user\n    throw new ReferenceError('showUserError for the component is not defined')\n  }\n\n  removeUserError () {\n    // Removes the error displayed to the user\n    throw new ReferenceError('clearUserError for the component is not defined')\n  }\n\n  // Implement the 6 methods below for a warning validation component\n\n  // @returns A string describing the potentially invalid state of the inputs for the user to read, empty string if there is nothing to warn about\n  getWarningState () {\n    throw new ReferenceError(GET_WARNING_STATE_UNDEFINED_MESSAGE)\n  }\n\n  // @param  {string} errorState The value returned by getWarningState()\n  warningHighlightUI (errorState) {\n    // Highlights the warning input area for the user to see easier\n    // If there is no warning, returns the component back to the original state\n    throw new ReferenceError('warningHighlightUI for the component is not defined')\n  }\n\n  showUserWarning (warningMsg) {\n    // Shows the warning notification to the user\n    throw new ReferenceError('showUserWarning for the component is not defined')\n  }\n\n  removeUserWarning () {\n    // Removes the warning notification displayed to the user\n    throw new ReferenceError('clearUserWarning for the component is not defined')\n  }\n\n  showWarningConfirmation () {\n    // Shows UI requiring the user to acknowledge the warning\n    throw new ReferenceError('showWarningConfirmation for the component is not defined')\n  }\n\n  removeWarningConfirmation () {\n    // Removes UI requiring the user to acknowledge the warning\n    throw new ReferenceError('removeWarningConfirmation for the component is not defined')\n  }\n\n  validate () {\n    let errorMsg\n    let errorValidationDisabled = false\n    let warningMsg\n    let warningValidationDisabled = false\n\n    try {\n      errorMsg = this.#validateError()\n    } catch (err) {\n      if (err instanceof ReferenceError && err.message === GET_ERROR_STATE_UNDEFINED_MESSAGE) {\n        errorValidationDisabled = true\n      } else {\n        throw err\n      }\n    }\n\n    try {\n      warningMsg = this.#validateWarning()\n    } catch (err) {\n      if (err instanceof ReferenceError && err.message === GET_WARNING_STATE_UNDEFINED_MESSAGE) {\n        warningValidationDisabled = true\n      } else {\n        throw err\n      }\n    }\n\n    if (errorValidationDisabled && warningValidationDisabled) {\n      throw new ReferenceError('No validations are implemented for this component')\n    }\n\n    const messages = {}\n\n    if (errorMsg) {\n      messages.error = errorMsg\n    }\n\n    if (warningMsg) {\n      messages.warning = warningMsg\n    }\n\n    return messages\n  }\n\n  #validateError () {\n    const errorState = this.getErrorState()\n\n    if (errorState) {\n      this.showUserError(errorState)\n    } else {\n      this.removeUserError()\n    }\n\n    this.errorHighlightUI(errorState)\n    return errorState\n  }\n\n  #validateWarning () {\n    const warningMsg = this.getWarningState()\n\n    if (warningMsg) {\n      this.showUserWarning(warningMsg)\n      this.showWarningConfirmation()\n    } else {\n      this.removeUserWarning(warningMsg)\n      this.removeWarningConfirmation()\n    }\n\n    this.warningHighlightUI(warningMsg)\n\n    return warningMsg\n  }\n}\n\nclass NonDrivingContactMediumWarning extends ValidatableFormSectionComponent {\n  constructor (allInputs, notifier) {\n    super(allInputs, notifier)\n\n    const milesDrivenInput = allInputs.filter('#case_contact_miles_driven')\n    const contactMediumCheckboxes = allInputs.not(milesDrivenInput)\n\n    this.drivingContactMediumCheckbox = contactMediumCheckboxes.filter('#case_contact_medium_type_in-person')\n    this.nonDrivingContactMediumCheckboxes = contactMediumCheckboxes.not(this.drivingContactMediumCheckbox)\n    this.checkboxContainer = this.drivingContactMediumCheckbox.parents('.contact-medium.form-group')\n    this.milesDrivenInput = milesDrivenInput\n\n    allInputs.on('change', (e) => {\n      this.validate()\n    })\n\n    this.notifier = notifier\n  }\n\n  getWarningState () {\n    if (this.nonDrivingContactMediumCheckboxes.filter(':checked').length && Number.parseInt(this.milesDrivenInput.val())) {\n      return 'You requested driving reimbursement for a contact medium that typically does not involve driving. Are you sure that\\'s right?'\n    }\n\n    return ''\n  }\n\n  // @param  {string} warningState The value returned by getWarningState()\n  warningHighlightUI (warningState) {\n    if (warningState) {\n      this.checkboxContainer.css('background-color', '#fff8e1')\n      this.milesDrivenInput.css('border', '2px solid #ffc107')\n    } else {\n      this.checkboxContainer.css('background-color', '')\n      this.milesDrivenInput.css('border', '')\n    }\n  }\n\n  showUserWarning (warningMsg) {\n    TypeChecker.checkNonEmptyString(warningMsg, 'warningMsg')\n\n    if (this.warningNotification && !(this.warningNotification.isDismissed())) {\n      this.warningNotification.setText(warningMsg)\n    } else if (this.notifier) {\n      this.warningNotification = this.notifier.notify(warningMsg, 'warn')\n    }\n  }\n\n  removeUserWarning () {\n    if (this.warningNotification) {\n      if (!(this.warningNotification.isDismissed())) {\n        this.warningNotification.dismiss()\n      }\n\n      delete this.warningNotification\n    }\n  }\n\n  showWarningConfirmation () {\n    if (!(this.warningConfirmationShown)) {\n      this.checkboxContainer.append($(\n`<div class=\"warning-required-checkbox\">\n  <input type=\"checkbox\" id=\"warning-non-driving-contact-medium-check\" class=\"form-check-input\" required=\"true\">\n  <label for=\"warning-non-driving-contact-medium-check\">I'm sure I drove for this contact medium.</label>\n</div>`\n      ))\n    }\n\n    this.warningConfirmationShown = true\n  }\n\n  removeWarningConfirmation () {\n    delete this.warningConfirmationShown\n\n    this.checkboxContainer.find('.warning-required-checkbox').remove()\n  }\n}\n\nfunction safeInstantiateComponent (componentName, instantiate) {\n  try {\n    instantiate()\n  } catch (e) {\n    console.error(`Failed to instantiate ${componentName} with the following jQuery object:`, $(this))\n    console.error('Instantiation failed with error:', e)\n  }\n}\n\n$(() => { // JQuery's callback for the DOM loading\n  const validatedFormCollection = $('.component-validated-form')\n  const validatableFormSectionComponents = []\n\n  let formErrorCountNotification\n\n  if (!(validatedFormCollection.length)) {\n    return\n  }\n\n  const notificationsElement = $('#notifications')\n  const pageNotifier = notificationsElement.length ? new Notifier(notificationsElement) : null\n\n  if ($('#case_contact_miles_driven').length) {\n    safeInstantiateComponent('non driving contact medium warning', () => {\n      const contactMediumWithMilesDrivenWarning = new NonDrivingContactMediumWarning(validatedFormCollection.find('.contact-medium.form-group input:not([type=hidden]), #case_contact_miles_driven'), pageNotifier)\n      validatableFormSectionComponents.push(contactMediumWithMilesDrivenWarning)\n    })\n  }\n\n  validatedFormCollection.on('submit', function (e) {\n    let errorCount = 0\n\n    for (const validatableFormSectionComponent of validatableFormSectionComponents) {\n      try {\n        const validationResult = validatableFormSectionComponent.validate()\n\n        if (validationResult.error) {\n          errorCount++\n        }\n      } catch (err) {\n        console.error('Failed to validate the following component:', validatableFormSectionComponent)\n        console.error('Validation threw error:', err)\n      }\n    }\n\n    if (errorCount) {\n      e.preventDefault()\n\n      if (formErrorCountNotification) {\n        formErrorCountNotification.setText(`${errorCount} error${errorCount > 1 ? 's' : ''} need${errorCount > 1 ? '' : 's'} to be fixed before you can submit.`)\n      } else {\n        formErrorCountNotification = pageNotifier.notify(`${errorCount} error${errorCount > 1 ? 's' : ''} need${errorCount > 1 ? '' : 's'} to be fixed before you can submit.`, 'error', false)\n      }\n    } else {\n      if (formErrorCountNotification) {\n        formErrorCountNotification.dismiss()\n        $(e.currentTarget).trigger('submit')\n      }\n    }\n  })\n})\n\nmodule.exports = { NonDrivingContactMediumWarning }\n"
  },
  {
    "path": "app/javascript/sweet-alert-confirm.js",
    "content": "import Swal from 'sweetalert2'\nimport Rails from '@rails/ujs'\n\nwindow.Swal = Swal\n\n// Behavior after click to confirm button\nconst confirmed = (element, result) => {\n  // If result `success`\n  if (result.value) {\n    // Removing attribute for unbinding JS event.\n    element.removeAttribute('data-confirm-swal')\n    // Following a destination link\n    element.click()\n  }\n}\n\n// Display the confirmation dialog\nconst showConfirmationDialog = (element) => {\n  const message = element.getAttribute('data-confirm-swal')\n  const text = element.getAttribute('data-text')\n  const onSuccess = element.getAttribute('data-success')\n  const onFail = element.getAttribute('data-fail')\n\n  Swal.fire({\n    title: message || 'Are you sure?',\n    text: text || '',\n    icon: 'warning',\n    showCancelButton: true,\n    confirmButtonText: onSuccess || 'Ok',\n    cancelButtonText: onFail || 'No'\n  }).then(result => confirmed(element, result))\n}\n\nconst allowAction = (element) => {\n  if (element.getAttribute('data-confirm-swal') === null) {\n    return true\n  }\n\n  showConfirmationDialog(element)\n  return false\n}\n\nfunction handleConfirm (element) {\n  if (!allowAction(this)) {\n    Rails.stopEverything(element)\n  }\n}\n\nRails.delegate(document, 'a[data-confirm-swal]', 'click', handleConfirm)\n"
  },
  {
    "path": "app/jobs/application_job.rb",
    "content": "class ApplicationJob < ActiveJob::Base; end\n"
  },
  {
    "path": "app/lib/importers/case_importer.rb",
    "content": "class CaseImporter < FileImporter\n  IMPORT_HEADER = [\"case_number\", \"case_assignment\", \"birth_month_year_youth\", \"next_court_date\"]\n\n  def self.import_cases(csv_filespec, org_id)\n    new(csv_filespec, org_id).import_cases\n  end\n\n  def initialize(csv_filespec, org_id)\n    super(csv_filespec, org_id, \"casa_cases\", [\"case_number\", \"case_assignment\", \"birth_month_year_youth\", \"next_court_date\"])\n  end\n\n  def import_cases\n    import do |row|\n      casa_case_params = row.to_hash.slice(:case_number, :birth_month_year_youth).compact\n\n      unless casa_case_params.key?(:case_number)\n        raise \"Row does not contain a case number.\"\n      end\n\n      casa_case = CasaCase.find_by(case_number: casa_case_params[:case_number], casa_org_id: org_id)\n      volunteer_assignment_list = email_addresses_to_users(Volunteer, String(row[:case_assignment]))\n      next_court_date = row[:next_court_date]\n\n      if casa_case # Case exists try to update it\n        unless casa_case.active\n          raise \"Case #{casa_case.case_number} already exists, but is inactive. Reactivate the CASA case instead.\"\n        end\n\n        update_casa_case(casa_case, casa_case_params, volunteer_assignment_list, next_court_date)\n      else # Case doesn't exist try to create a new case\n        create_casa_case(casa_case_params, volunteer_assignment_list, next_court_date)\n      end\n    end\n  end\n\n  private\n\n  def create_casa_case(case_params, volunteer_assignment_list, next_court_date)\n    casa_case = CasaCase.new(case_params)\n    casa_case.casa_org_id = org_id\n    casa_case.save!\n\n    casa_case.court_dates.create(date: next_court_date)\n    casa_case.volunteers << volunteer_assignment_list\n\n    casa_case\n  end\n\n  def update_casa_case(casa_case, case_params, volunteer_assignment_list, next_court_date)\n    if record_outdated?(casa_case, case_params)\n      casa_case.update(case_params)\n    end\n\n    if casa_case.next_court_date.nil?\n      casa_case.court_dates.create(date: next_court_date)\n    end\n\n    volunteer_assignment_list.each do |volunteer|\n      # Expecting an array of length 0 or 1\n      assignments = casa_case.case_assignments.where(volunteer: volunteer)\n\n      if assignments.empty?\n        casa_case.volunteers << volunteer\n      elsif assignments[0].inactive?\n        assignments[0].update(active: true)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "app/lib/importers/file_importer.rb",
    "content": "class FileImporter\n  require \"csv\"\n\n  ERR_NOT_ALL_IMPORTED = \"Not all rows were imported.\"\n  ERR_NO_ROWS = \"File did not contain any rows.\"\n\n  attr_reader :csv_filespec, :org_id, :type_label, :header_names, :number_imported, :failed_imports\n\n  def initialize(csv_filespec, org_id, type_label, header_names)\n    @csv_filespec = csv_filespec\n    @org_id = org_id\n    @type_label = type_label\n    @header_names = header_names\n    @failed_imports = []\n    @number_imported = 0\n  end\n\n  def import\n    @number_imported = 0\n    @file_no_rows = true\n\n    CSV.foreach(csv_filespec || [], headers: true, header_converters: :symbol) do |row|\n      @file_no_rows = false\n      yield(row)\n      @number_imported += 1\n    rescue => error\n      # email for supervisor or volunteer\n      # display name for supervisor or volunteer\n      # case number for casa_case\n      # birth month and year for casa_case\n\n      failed_row = row.to_hash\n      failed_row[:errors] = error.to_s\n      @failed_imports << failed_row.values\n    end\n\n    {\n      type: status,\n      message: message,\n      exported_rows: export_failed_imports\n    }\n  end\n\n  private\n\n  def create_user_record(user_class, user_params)\n    user = user_class.new(user_params)\n    user.casa_org_id, user.password = org_id, SecureRandom.hex(10)\n    user.save!\n    user.invite!\n\n    user\n  end\n\n  def email_addresses_to_users(clazz, comma_separated_emails)\n    emails = comma_separated_emails.split(\",\").map!(&:strip)\n    clazz.where(email: [emails]).distinct.order(:email).to_a\n  end\n\n  def export_failed_imports\n    return unless failed?\n\n    headers = header_names\n    headers << \"errors (please remove this column before uploading again)\"\n\n    CSV.generate do |csv|\n      csv << header_names\n\n      failed_imports.each do |failed_import|\n        csv << failed_import\n      end\n    end\n  end\n\n  def failed?\n    !failed_imports.blank?\n  end\n\n  def message\n    messages = []\n    messages << \"You successfully imported #{@number_imported} #{@type_label}.\" if @number_imported > 0\n    messages << ERR_NO_ROWS if @file_no_rows\n    messages << ERR_NOT_ALL_IMPORTED if failed?\n    messages.join(\" \")\n  end\n\n  def record_outdated?(record, new_data)\n    new_data.each do |key, value|\n      # The parser keeps boolean values as strings\n      if record[key] != value || (value == ((record[key].in?([true, false]) && record[key]) ? \"FALSE\" : \"TRUE\"))\n        return true\n      end\n    end\n\n    false\n  end\n\n  def success?\n    !failed? && !@file_no_rows\n  end\n\n  def status\n    success? ? :success : :error\n  end\nend\n"
  },
  {
    "path": "app/lib/importers/supervisor_importer.rb",
    "content": "class SupervisorImporter < FileImporter\n  IMPORT_HEADER = [\"email\", \"display_name\", \"supervisor_volunteers\", \"phone_number\"]\n\n  def self.import_supervisors(csv_filespec, org_id)\n    new(csv_filespec, org_id).import_supervisors\n  end\n\n  def initialize(csv_filespec, org_id)\n    super(csv_filespec, org_id, \"supervisors\", [\"email\", \"display_name\", \"supervisor_volunteers\", \"phone_number\"])\n  end\n\n  def import_supervisors\n    import do |row|\n      supervisor_params = row.to_hash.slice(:display_name, :email, :phone_number).compact\n\n      unless supervisor_params.key?(:email)\n        raise \"Row does not contain e-mail address.\"\n      end\n\n      supervisor_params[:phone_number] = supervisor_params.key?(:phone_number) ? \"+#{supervisor_params[:phone_number]}\" : \"\"\n      supervisor_params[:receive_sms_notifications] = !supervisor_params[:phone_number].empty?\n\n      supervisor = Supervisor.find_by(email: supervisor_params[:email])\n      volunteer_assignment_list = email_addresses_to_users(Volunteer, String(row[:supervisor_volunteers]))\n\n      if volunteer_assignment_list.count != String(row[:supervisor_volunteers]).split(\",\").count\n        raise \"Row contains unimported volunteers.\"\n      end\n\n      if supervisor # Supervisor exists try to update it\n        update_supervisor(supervisor, supervisor_params, volunteer_assignment_list)\n      else # Supervisor doesn't exist try to create a new supervisor\n        supervisor = create_user_record(Supervisor, supervisor_params)\n      end\n\n      assign_volunteers(supervisor, volunteer_assignment_list)\n    end\n  end\n\n  def update_supervisor(supervisor, supervisor_params, volunteer_assignment_list)\n    if record_outdated?(supervisor, supervisor_params)\n      supervisor.update(supervisor_params)\n    end\n  end\n\n  def assign_volunteers(supervisor, volunteer_assignment_list)\n    volunteer_assignment_list.select { |v| v.supervisor != supervisor }.each do |volunteer|\n      if volunteer.supervisor\n        raise \"Volunteer #{volunteer.email} already has a supervisor\"\n      else\n        supervisor.volunteers << volunteer\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "app/lib/importers/volunteer_importer.rb",
    "content": "class VolunteerImporter < FileImporter\n  IMPORT_HEADER = [\"display_name\", \"email\", \"phone_number\"]\n\n  def self.import_volunteers(csv_filespec, org_id)\n    new(csv_filespec, org_id).import_volunteers\n  end\n\n  def initialize(csv_filespec, org_id)\n    super(csv_filespec, org_id, \"volunteers\", [\"display_name\", \"email\", \"phone_number\"])\n  end\n\n  def import_volunteers\n    import do |row|\n      volunteer_params = row.to_hash.slice(:display_name, :email, :phone_number).compact\n\n      unless volunteer_params.key?(:email)\n        raise \"Row does not contain an e-mail address.\"\n      end\n\n      volunteer_params[:phone_number] = volunteer_params.key?(:phone_number) ? \"+#{volunteer_params[:phone_number]}\" : \"\"\n      volunteer_params[:receive_sms_notifications] = !volunteer_params[:phone_number].empty?\n\n      volunteer = Volunteer.find_by(email: volunteer_params[:email])\n\n      if volunteer # Volunteer exists try to update it\n        update_volunteer(volunteer, volunteer_params)\n      else # Volunteer doesn't exist try to create a new supervisor\n        create_user_record(Volunteer, volunteer_params)\n      end\n    end\n  end\n\n  def update_volunteer(volunteer, volunteer_params)\n    if record_outdated?(volunteer, volunteer_params)\n      volunteer.update(volunteer_params)\n    end\n  end\nend\n"
  },
  {
    "path": "app/mailers/application_mailer.rb",
    "content": "class ApplicationMailer < ActionMailer::Base\n  address = Mail::Address.new \"no-reply@#{ENV[\"DOMAIN\"] || \"example.com\"}\"\n  address.display_name = \"CASA Admin\"\n\n  default from: address.format\n  layout \"mailer\"\nend\n"
  },
  {
    "path": "app/mailers/casa_admin_mailer.rb",
    "content": "class CasaAdminMailer < UserMailer\n  def deactivation(user)\n    @user = user\n    @casa_organization = CasaOrg.find(@user.casa_org_id)\n    mail(to: @user.email, subject: \"Your account has been deactivated\")\n  end\n\n  def account_setup(user)\n    @user = user\n    @casa_organization = CasaOrg.find(@user.casa_org_id)\n    @token = @user.generate_password_reset_token\n    mail(to: @user.email, subject: \"Create a password & set up your account\")\n  end\nend\n"
  },
  {
    "path": "app/mailers/fund_request_mailer.rb",
    "content": "class FundRequestMailer < ApplicationMailer\n  layout \"fund_layout\"\n\n  def send_request(_user, fund_request, bugsnag_active = true)\n    to_recipient_email = ENV[\"FUND_REQUEST_RECIPIENT_EMAIL\"]\n\n    submitter_email = fund_request.submitter_email\n    if bugsnag_active && !to_recipient_email\n      Bugsnag.notify(\"No user for FUND_REQUEST_RECIPIENT_EMAIL for fund request from: #{submitter_email}\")\n    end\n    @inputs = fund_request.as_json\n    submitter_name = fund_request.submitter_email&.split(\"@\")&.first\n    begin\n      pdf_attachment = FdfInputsService.new(\n        inputs: @inputs,\n        pdf_template_path: File.join([\"data\", \"fund_request.pdf\"]),\n        basename: [\"fund_request\", \"pdf\"]\n      ).write_to_file.read\n      attachments.inline[\"fund-request-#{Date.today}-#{submitter_name}.pdf\"] = pdf_attachment\n    rescue => e\n      Bugsnag.notify(e) if bugsnag_active\n    end\n    mail(layout: nil, to: [to_recipient_email, submitter_email], subject: \"Fund request from #{submitter_email}\")\n  end\nend\n"
  },
  {
    "path": "app/mailers/learning_hours_mailer.rb",
    "content": "class LearningHoursMailer < ApplicationMailer\n  def learning_hours_report_email(user)\n    # user is either an Admin or Supervisor, this mailer is invoked through the rake task :monthly_learning_hours_report.rake\n    @user = user\n    @casa_org = @user.casa_org\n\n    # Generate the learning hours CSV for the current month\n    start_date = Date.today.beginning_of_month\n    end_date = Date.today.end_of_month\n    learning_hours = LearningHour.where(user: @casa_org.users, occurred_at: start_date..end_date)\n    csv_data = LearningHoursExportCsvService.new(learning_hours).perform\n\n    attachments[\"learning-hours-report-#{Date.today}.csv\"] = csv_data\n\n    mail(to: @user.email, subject: \"Learning Hours Report for #{end_date.strftime(\"%B, %Y\")}.\")\n  end\nend\n"
  },
  {
    "path": "app/mailers/supervisor_mailer.rb",
    "content": "class SupervisorMailer < UserMailer\n  def account_setup(supervisor)\n    @supervisor = supervisor\n    @casa_organization = supervisor.casa_org\n    @token = @supervisor.generate_password_reset_token\n    mail(to: @supervisor.email, subject: \"Create a password and set up your account\")\n  end\n\n  def weekly_digest(supervisor)\n    @supervisor = supervisor\n    @casa_organization = supervisor.casa_org\n    @inactive_messages = InactiveMessagesService.new(supervisor).inactive_messages\n    @inactive_volunteers = supervisor.inactive_volunteers.where(casa_org_id: @casa_organization.id)\n    if supervisor.receive_reimbursement_email\n      mileage_report_attachment = MileageReport.new(@casa_organization.id).to_csv\n      attachments[\"mileage-report-#{Time.current.strftime(\"%Y-%m-%d\")}.csv\"] = mileage_report_attachment\n    end\n    mail(\n      to: @supervisor.email,\n      subject: \"Weekly summary of volunteers' activities for the week of #{Date.today - 7.days}\"\n    )\n  end\n\n  def reimbursement_request_email(volunteer, supervisor)\n    @volunteer = volunteer\n    @casa_organization = volunteer.casa_org\n    @supervisor = supervisor\n    if supervisor.receive_reimbursement_email\n      mail(to: @supervisor.email, subject: \"New reimbursement request from #{@volunteer.display_name}\")\n    end\n  end\nend\n"
  },
  {
    "path": "app/mailers/user_mailer.rb",
    "content": "class UserMailer < ApplicationMailer\n  def password_changed_reminder(user)\n    @user = user\n    @casa_organization = user.try(:casa_org) || nil\n\n    mail(to: @user.email, subject: \"CASA Password Changed\")\n  end\nend\n"
  },
  {
    "path": "app/mailers/volunteer_mailer.rb",
    "content": "class VolunteerMailer < UserMailer\n  def account_setup(user)\n    @user = user\n    @casa_organization = user.casa_org\n    @token = @user.generate_password_reset_token\n    mail(to: @user.email, subject: \"Create a password & set up your account\")\n  end\n\n  def court_report_reminder(user, court_report_due_date)\n    @user = user\n    @casa_organization = user.casa_org\n    @court_report_due_date = court_report_due_date\n    mail(to: @user.email, subject: \"Your court report is due on: #{court_report_due_date}\")\n  end\n\n  def case_contacts_reminder(user, cc_recipients)\n    @user = user\n    @casa_organization = user.casa_org\n    mail(to: @user.email, cc: cc_recipients, subject: \"Reminder to input case contacts\")\n  end\nend\n"
  },
  {
    "path": "app/models/additional_expense.rb",
    "content": "class AdditionalExpense < ApplicationRecord\n  belongs_to :case_contact\n  has_one :casa_case, through: :case_contact\n  has_one :casa_org, through: :case_contact\n  # case_contact.casa_org may be nil for draft contacts, use for fallback:\n  has_one :contact_creator, through: :case_contact, source: :creator\n  has_one :contact_creator_casa_org, through: :contact_creator, source: :casa_org\n\n  validates :other_expenses_describe, presence: true, if: :describe_required?\n\n  alias_attribute :amount, :other_expense_amount\n  alias_attribute :describe, :other_expenses_describe\n\n  def describe_required?\n    other_expense_amount&.positive?\n  end\nend\n\n# == Schema Information\n#\n# Table name: additional_expenses\n#\n#  id                      :bigint           not null, primary key\n#  other_expense_amount    :decimal(, )\n#  other_expenses_describe :string\n#  created_at              :datetime         not null\n#  updated_at              :datetime         not null\n#  case_contact_id         :bigint           not null\n#\n# Indexes\n#\n#  index_additional_expenses_on_case_contact_id  (case_contact_id)\n#\n# Foreign Keys\n#\n#  fk_rails_...  (case_contact_id => case_contacts.id)\n#\n"
  },
  {
    "path": "app/models/address.rb",
    "content": "class Address < ApplicationRecord\n  belongs_to :user\nend\n\n# == Schema Information\n#\n# Table name: addresses\n#\n#  id         :bigint           not null, primary key\n#  content    :string\n#  created_at :datetime         not null\n#  updated_at :datetime         not null\n#  user_id    :bigint           not null\n#\n# Indexes\n#\n#  index_addresses_on_user_id  (user_id)\n#\n# Foreign Keys\n#\n#  fk_rails_...  (user_id => users.id)\n#\n"
  },
  {
    "path": "app/models/all_casa_admin.rb",
    "content": "class AllCasaAdmin < ApplicationRecord\n  prepend ActiveSupport::ToJsonWithActiveSupportEncoder\n  include Roles\n\n  # Include default devise modules. Others available are:\n  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable\n  devise :database_authenticatable, :invitable, :recoverable, :validatable, :timeoutable, invite_for: 1.weeks\n\n  def casa_admin?\n    false\n  end\n\n  def supervisor?\n    false\n  end\n\n  def volunteer?\n    false\n  end\nend\n\n# == Schema Information\n#\n# Table name: all_casa_admins\n#\n#  id                     :bigint           not null, primary key\n#  email                  :string           default(\"\"), not null\n#  encrypted_password     :string           default(\"\"), not null\n#  invitation_accepted_at :datetime\n#  invitation_created_at  :datetime\n#  invitation_limit       :integer\n#  invitation_sent_at     :datetime\n#  invitation_token       :string\n#  invited_by_type        :string\n#  reset_password_sent_at :datetime\n#  reset_password_token   :string\n#  created_at             :datetime         not null\n#  updated_at             :datetime         not null\n#  invited_by_id          :integer\n#\n# Indexes\n#\n#  index_all_casa_admins_on_email                 (email) UNIQUE\n#  index_all_casa_admins_on_invitation_token      (invitation_token) UNIQUE\n#  index_all_casa_admins_on_reset_password_token  (reset_password_token) UNIQUE\n#\n"
  },
  {
    "path": "app/models/all_casa_admins/casa_org_metrics.rb",
    "content": "module AllCasaAdmins\n  class CasaOrgMetrics\n    def initialize(casa_org)\n      @casa_org = casa_org\n    end\n\n    def metrics\n      {\n        \"Number of admins\" => @casa_org.casa_admins.count,\n        \"Number of supervisors\" => @casa_org.supervisors.count,\n        \"Number of active volunteers\" => @casa_org.volunteers.active.count,\n        \"Number of inactive volunteers\" => @casa_org.volunteers.inactive.count,\n        \"Number of active cases\" => @casa_org.casa_cases.active.count,\n        \"Number of inactive cases\" => @casa_org.casa_cases.inactive.count,\n        \"Number of all case contacts including inactives\" => @casa_org.case_assignments.count,\n        \"Number of active supervisor to volunteer assignments\" => @casa_org.volunteers.map(&:supervisor).count,\n        \"Number of active case assignments\" => @casa_org.case_assignments.active.count\n      }\n    end\n  end\nend\n"
  },
  {
    "path": "app/models/api_credential.rb",
    "content": "require \"digest\"\n\nclass ApiCredential < ApplicationRecord\n  belongs_to :user\n\n  before_save :generate_api_token\n  before_save :generate_refresh_token\n\n  # Securely confirm/deny that Hash in db is same as current users token Hash\n  def authenticate_api_token(api_token)\n    Digest::SHA256.hexdigest(api_token) == api_token_digest\n  end\n\n  def authenticate_refresh_token(refresh_token)\n    Digest::SHA256.hexdigest(refresh_token) == refresh_token_digest\n  end\n\n  # Securely generate and then return new tokens\n  def return_new_api_token!\n    new_token = generate_api_token\n    update_column(:api_token_digest, api_token_digest)\n    {api_token: new_token}\n  end\n\n  def return_new_refresh_token!(remember_me)\n    new_token = generate_refresh_token\n    if remember_me\n      update_column(:refresh_token_expires_at, 1.year.from_now)\n    else\n      update_column(:refresh_token_expires_at, 30.days.from_now)\n    end\n    update_column(:refresh_token_digest, refresh_token_digest)\n    {refresh_token: new_token}\n  end\n\n  # Verifying token has or has not expired\n  def is_api_token_expired?\n    token_expires_at < Time.current\n  end\n\n  def is_refresh_token_expired?\n    refresh_token_expires_at < Time.current\n  end\n\n  def revoke_api_token\n    update_columns(api_token_digest: nil)\n  end\n\n  def revoke_refresh_token\n    update_columns(refresh_token_digest: nil)\n  end\n\n  private\n\n  # Generate unique tokens and hashes them for secure db storage\n  def generate_api_token\n    new_api_token = SecureRandom.hex(18)\n    self.api_token_digest = Digest::SHA256.hexdigest(new_api_token)\n    new_api_token\n  end\n\n  def generate_refresh_token\n    new_refresh_token = SecureRandom.hex(18)\n    self.refresh_token_digest = Digest::SHA256.hexdigest(new_refresh_token)\n    new_refresh_token\n  end\nend\n\n# == Schema Information\n#\n# Table name: api_credentials\n#\n#  id                       :bigint           not null, primary key\n#  api_token_digest         :string\n#  refresh_token_digest     :string\n#  refresh_token_expires_at :datetime\n#  token_expires_at         :datetime\n#  created_at               :datetime         not null\n#  updated_at               :datetime         not null\n#  user_id                  :bigint           not null\n#\n# Indexes\n#\n#  index_api_credentials_on_api_token_digest      (api_token_digest) UNIQUE WHERE (api_token_digest IS NOT NULL)\n#  index_api_credentials_on_refresh_token_digest  (refresh_token_digest) UNIQUE WHERE (refresh_token_digest IS NOT NULL)\n#\n# Foreign Keys\n#\n#  fk_rails_...  (user_id => users.id)\n#\n"
  },
  {
    "path": "app/models/application_record.rb",
    "content": "class ApplicationRecord < ActiveRecord::Base\n  self.abstract_class = true\nend\n"
  },
  {
    "path": "app/models/banner.rb",
    "content": "class Banner < ApplicationRecord\n  belongs_to :casa_org\n  belongs_to :user\n  has_rich_text :content\n\n  scope :active, -> { where(active: true) }\n\n  validates :name, presence: true\n  validates :content, presence: true\n  validate :only_one_banner_is_active_per_organization\n  validate :expires_at_must_be_in_future\n\n  def expired?\n    expired = expires_at && Time.current > expires_at\n    update(active: false) if active && expired\n    expired\n  end\n\n  # `expires_at` is stored in the database as UTC, but timezone information will be stripped before displaying on frontend\n  # so this method converts the time to the user's timezone before displaying it\n  def expires_at_in_time_zone(timezone)\n    expires_at&.in_time_zone(timezone)\n  end\n\n  private\n\n  def only_one_banner_is_active_per_organization\n    is_other_banner_active = casa_org.banners.where.not(id: id).any?(&:active?)\n    more_than_one_banner_active = is_other_banner_active && active?\n    if more_than_one_banner_active\n      errors.add(:base, \"Only one banner can be active at a time. Mark the other banners as not active before marking this banner as active.\")\n    end\n  end\n\n  # Validation using line below doesn't work with `travel_to` in specs. Must use detailed method\n  # validates_comparison_of :expires_at, greater_than: Time.current, message: \"must take place in the future (after #{Time.current})\", allow_blank: true\n  def expires_at_must_be_in_future\n    if expires_at.present? && expires_at < Time.current\n      errors.add(:expires_at, \"must take place in the future (after #{Time.current})\")\n    end\n  end\nend\n\n# == Schema Information\n#\n# Table name: banners\n#\n#  id          :bigint           not null, primary key\n#  active      :boolean          default(FALSE)\n#  expires_at  :datetime\n#  name        :string\n#  created_at  :datetime         not null\n#  updated_at  :datetime         not null\n#  casa_org_id :bigint           not null\n#  user_id     :bigint           not null\n#\n# Foreign Keys\n#\n#  fk_rails_...  (casa_org_id => casa_orgs.id)\n#  fk_rails_...  (user_id => users.id)\n#\n"
  },
  {
    "path": "app/models/casa_admin.rb",
    "content": "class CasaAdmin < User\n  prepend ActiveSupport::ToJsonWithActiveSupportEncoder\n\n  devise :invitable, invite_for: 2.weeks\n\n  default_scope { order(email: :asc) }\n\n  def activate\n    update(active: true)\n  end\n\n  def deactivate\n    update(active: false)\n  end\n\n  def change_to_supervisor!\n    becomes!(Supervisor).save\n  end\nend\n\n# == Schema Information\n#\n# Table name: users\n#\n#  id                            :bigint           not null, primary key\n#  active                        :boolean          default(TRUE)\n#  confirmation_sent_at          :datetime\n#  confirmation_token            :string\n#  confirmed_at                  :datetime\n#  current_sign_in_at            :datetime\n#  current_sign_in_ip            :string\n#  date_of_birth                 :datetime\n#  display_name                  :string           default(\"\"), not null\n#  email                         :string           default(\"\"), not null\n#  encrypted_password            :string           default(\"\"), not null\n#  invitation_accepted_at        :datetime\n#  invitation_created_at         :datetime\n#  invitation_limit              :integer\n#  invitation_sent_at            :datetime\n#  invitation_token              :string\n#  invitations_count             :integer          default(0)\n#  invited_by_type               :string\n#  last_sign_in_at               :datetime\n#  last_sign_in_ip               :string\n#  monthly_learning_hours_report :boolean          default(FALSE), not null\n#  old_emails                    :string           default([]), is an Array\n#  phone_number                  :string           default(\"\")\n#  receive_email_notifications   :boolean          default(TRUE)\n#  receive_reimbursement_email   :boolean          default(FALSE)\n#  receive_sms_notifications     :boolean          default(FALSE), not null\n#  reset_password_sent_at        :datetime\n#  reset_password_token          :string\n#  sign_in_count                 :integer          default(0), not null\n#  type                          :string\n#  unconfirmed_email             :string\n#  created_at                    :datetime         not null\n#  updated_at                    :datetime         not null\n#  casa_org_id                   :bigint           not null\n#  invited_by_id                 :bigint\n#\n# Indexes\n#\n#  index_users_on_casa_org_id           (casa_org_id)\n#  index_users_on_confirmation_token    (confirmation_token) UNIQUE\n#  index_users_on_email                 (email) UNIQUE\n#  index_users_on_invitation_token      (invitation_token) UNIQUE\n#  index_users_on_invited_by_id         (invited_by_id)\n#  index_users_on_reset_password_token  (reset_password_token) UNIQUE\n#\n# Foreign Keys\n#\n#  fk_rails_...  (casa_org_id => casa_orgs.id)\n#\n"
  },
  {
    "path": "app/models/casa_case.rb",
    "content": "class CasaCase < ApplicationRecord\n  prepend ActiveSupport::ToJsonWithActiveSupportEncoder\n  include ByOrganizationScope\n  include DateHelper\n  include CasaCase::Validations\n  extend FriendlyId\n\n  self.ignored_columns = %w[transition_aged_youth court_report_due_date]\n\n  attr_accessor :validate_contact_type\n\n  TABLE_COLUMNS = %w[\n    case_number\n    hearing_type_name\n    judge_name\n    status\n    birth_month_year_youth\n    assigned_to\n    actions\n  ].freeze\n\n  TRANSITION_AGE = 14\n  TRANSITION_AGE_YOUTH_ICON = \"🦋\".freeze\n  NON_TRANSITION_AGE_YOUTH_ICON = \"🐛\".freeze\n\n  friendly_id :case_number, use: :scoped, scope: :casa_org\n\n  has_many :case_assignments, dependent: :destroy\n  has_many(:volunteers, through: :case_assignments, source: :volunteer, class_name: \"User\")\n  has_many :active_case_assignments, -> { active }, class_name: \"CaseAssignment\"\n  has_many :assigned_volunteers, -> { active }, through: :active_case_assignments, source: :volunteer, class_name: \"Volunteer\"\n  has_many :case_contacts, dependent: :destroy\n  has_many :casa_case_emancipation_categories, dependent: :destroy\n  has_many :emancipation_categories, through: :casa_case_emancipation_categories\n  has_many :casa_case_emancipation_options, dependent: :destroy\n  has_many :emancipation_options, through: :casa_case_emancipation_options\n  has_many :court_dates, dependent: :destroy\n  has_many :placements, dependent: :destroy\n  has_many :case_group_memberships\n  has_many :case_groups, through: :case_group_memberships\n  has_many_attached :court_reports\n\n  belongs_to :hearing_type, optional: true\n  belongs_to :judge, optional: true\n  belongs_to :casa_org\n\n  has_many :casa_case_contact_types\n  has_many :contact_types, through: :casa_case_contact_types\n  accepts_nested_attributes_for :casa_case_contact_types\n  accepts_nested_attributes_for :court_dates\n  accepts_nested_attributes_for :volunteers\n  accepts_nested_attributes_for :case_assignments, reject_if: proc { |attributes| attributes[\"volunteer_id\"].blank? }\n\n  has_many :case_court_orders, -> { order \"id asc\" }, dependent: :destroy\n  accepts_nested_attributes_for :case_court_orders, reject_if: :all_blank, allow_destroy: true\n\n  enum :court_report_status, {not_submitted: 0, submitted: 1, in_review: 2, completed: 3}, prefix: :court_report\n\n  scope :ordered, -> { order(updated_at: :desc) }\n  scope :actively_assigned_to, ->(volunteer) {\n    joins(:case_assignments).where(\n      case_assignments: {volunteer: volunteer, active: true}\n    )\n  }\n  scope :actively_assigned_excluding_volunteer, ->(volunteer) {\n    joins(:case_assignments)\n      .where(case_assignments: {active: true})\n      .where.not(case_assignments: {volunteer: volunteer})\n      .where(casa_org: volunteer.casa_org)\n      .order(:case_number)\n  }\n  scope :not_assigned, ->(casa_org) {\n    where(casa_org_id: casa_org.id)\n      .left_outer_joins(:case_assignments)\n      .where(\"case_assignments.id IS NULL OR NOT case_assignments.active\")\n      .order(:case_number)\n  }\n  scope :should_transition, -> {\n    where(\"birth_month_year_youth <= ?\", TRANSITION_AGE.years.ago)\n  }\n\n  scope :birthday_next_month, -> {\n    where(\"EXTRACT(month from birth_month_year_youth) = ?\", DateTime.now.next_month.month)\n  }\n\n  scope :due_date_passed, -> {\n    # No more future court dates\n    where.not(id: CourtDate.where(\"date >= ?\", Date.today).pluck(:casa_case_id))\n  }\n\n  scope :is_transitioned, -> {\n    where(\"birth_month_year_youth < ?\", TRANSITION_AGE.years.ago)\n  }\n\n  scope :active, -> {\n    where(active: true)\n  }\n\n  scope :inactive, -> {\n    where(active: false)\n  }\n\n  scope :missing_court_dates, -> {\n    where.missing(:court_dates)\n  }\n\n  delegate :name, to: :hearing_type, prefix: true, allow_nil: true\n  delegate :name, to: :judge, prefix: true, allow_nil: true\n\n  def add_emancipation_category(category_id)\n    emancipation_categories << EmancipationCategory.find(category_id)\n  end\n\n  def add_emancipation_option(option_id)\n    option = EmancipationOption.find(option_id)\n    option_category = option.emancipation_category\n\n    if !(option_category.mutually_exclusive && EmancipationOption.options_with_category_and_case(option_category, id).any?)\n      emancipation_options << option\n    else\n      raise \"Attempted adding multiple options belonging to a mutually exclusive category\"\n    end\n  end\n\n  def clear_court_dates\n    if next_court_date.nil?\n      update(\n        court_report_status: :not_submitted\n      )\n    end\n  end\n\n  def court_report_status=(value)\n    super\n    if court_report_not_submitted?\n      self.court_report_submitted_at = nil\n    else\n      self.court_report_submitted_at ||= Time.current\n    end\n    court_report_status\n  end\n\n  def in_transition_age?\n    birth_month_year_youth.nil? ? false : birth_month_year_youth <= TRANSITION_AGE.years.ago\n  end\n\n  def latest_court_report\n    court_reports.order(\"created_at\").last\n  end\n\n  def next_court_date\n    court_dates.where(\"date >= ?\", Date.today).order(:date).first\n  end\n\n  def most_recent_past_court_date\n    court_dates.where(\"date < ?\", Date.today).order(:date).last\n  end\n\n  def formatted_latest_court_date\n    most_recent = most_recent_past_court_date&.date&.in_time_zone || Time.zone.now\n\n    most_recent.strftime(::DateHelper::RUBY_MONTH_DAY_YEAR_FORMAT)\n  end\n\n  def has_judge_name?\n    judge_name\n  end\n\n  def remove_emancipation_category(category_id)\n    category = EmancipationCategory.find(category_id)\n    raise ActiveRecord::RecordNotFound unless emancipation_categories.include?(category)\n\n    emancipation_categories.destroy(category)\n  end\n\n  def remove_emancipation_option(option_id)\n    option = EmancipationOption.find(option_id)\n    raise ActiveRecord::RecordNotFound unless emancipation_options.include?(option)\n\n    emancipation_options.destroy(option)\n  end\n\n  def update_cleaning_contact_types(args)\n    args = parse_date(errors, \"court_report_due_date\", args)\n\n    return false unless errors.messages.empty?\n\n    transaction do\n      contact_types.clear\n      update!(args)\n    rescue ActiveRecord::RecordInvalid\n      raise ActiveRecord::Rollback\n    end\n  end\n\n  def deactivate\n    update(active: false)\n    case_assignments.map { |ca| ca.update(active: false) }\n  end\n\n  def reactivate\n    update(active: true)\n  end\n\n  def unassigned_volunteers\n    volunteers_unassigned_to_case = Volunteer.active.where.not(id: assigned_volunteers).in_organization(casa_org)\n    volunteers_unassigned_to_case.with_no_assigned_cases + volunteers_unassigned_to_case.with_assigned_cases\n  end\n\n  def full_attributes_hash\n    attributes.symbolize_keys.merge({contact_types: contact_types.reload.map(&:attributes), court_orders: case_court_orders.map(&:attributes)})\n  end\n\n  def contact_type_validation?\n    validate_update\n  end\n\n  def should_generate_new_friendly_id?\n    case_number_changed? || super\n  end\nend\n\n# == Schema Information\n#\n# Table name: casa_cases\n#\n#  id                        :bigint           not null, primary key\n#  active                    :boolean          default(TRUE), not null\n#  birth_month_year_youth    :datetime\n#  case_number               :string           not null\n#  court_report_status       :integer          default(\"not_submitted\")\n#  court_report_submitted_at :datetime\n#  date_in_care              :datetime\n#  slug                      :string\n#  created_at                :datetime         not null\n#  updated_at                :datetime         not null\n#  casa_org_id               :bigint           not null\n#\n# Indexes\n#\n#  index_casa_cases_on_casa_org_id                  (casa_org_id)\n#  index_casa_cases_on_case_number_and_casa_org_id  (case_number,casa_org_id) UNIQUE\n#  index_casa_cases_on_slug                         (slug)\n#\n# Foreign Keys\n#\n#  fk_rails_...  (casa_org_id => casa_orgs.id)\n#\n"
  },
  {
    "path": "app/models/casa_case_contact_type.rb",
    "content": "class CasaCaseContactType < ApplicationRecord\n  belongs_to :casa_case\n  belongs_to :contact_type\n\n  validates :casa_case_id, uniqueness: {scope: :contact_type_id}\nend\n\n# == Schema Information\n#\n# Table name: casa_case_contact_types\n#\n#  id              :bigint           not null, primary key\n#  created_at      :datetime         not null\n#  updated_at      :datetime         not null\n#  casa_case_id    :bigint           not null\n#  contact_type_id :bigint           not null\n#\n# Indexes\n#\n#  index_casa_case_contact_types_on_casa_case_id     (casa_case_id)\n#  index_casa_case_contact_types_on_contact_type_id  (contact_type_id)\n#\n"
  },
  {
    "path": "app/models/casa_case_emancipation_category.rb",
    "content": "class CasaCaseEmancipationCategory < ApplicationRecord\n  belongs_to :casa_case\n  belongs_to :emancipation_category\n\n  validates :casa_case_id, uniqueness: {scope: :emancipation_category_id}\nend\n\n# == Schema Information\n#\n# Table name: casa_case_emancipation_categories\n#\n#  id                       :bigint           not null, primary key\n#  created_at               :datetime         not null\n#  updated_at               :datetime         not null\n#  casa_case_id             :bigint           not null\n#  emancipation_category_id :bigint           not null\n#\n# Indexes\n#\n#  index_casa_case_emancipation_categories_on_casa_case_id  (casa_case_id)\n#\n# Foreign Keys\n#\n#  fk_rails_...  (casa_case_id => casa_cases.id)\n#  fk_rails_...  (emancipation_category_id => emancipation_categories.id)\n#\n"
  },
  {
    "path": "app/models/casa_case_emancipation_option.rb",
    "content": "class CasaCaseEmancipationOption < ApplicationRecord\n  belongs_to :casa_case\n  belongs_to :emancipation_option\n\n  validates :casa_case_id, uniqueness: {scope: :emancipation_option_id}\nend\n\n# == Schema Information\n#\n# Table name: casa_case_emancipation_options\n#\n#  id                     :bigint           not null, primary key\n#  created_at             :datetime         not null\n#  updated_at             :datetime         not null\n#  casa_case_id           :bigint           not null\n#  emancipation_option_id :bigint           not null\n#\n# Indexes\n#\n#  index_case_options_on_case_id_and_option_id  (casa_case_id,emancipation_option_id) UNIQUE\n#\n# Foreign Keys\n#\n#  fk_rails_...  (casa_case_id => casa_cases.id)\n#  fk_rails_...  (emancipation_option_id => emancipation_options.id)\n#\n"
  },
  {
    "path": "app/models/casa_org.rb",
    "content": "class CasaOrg < ApplicationRecord\n  prepend ActiveSupport::ToJsonWithActiveSupportEncoder\n  # NOTE: location of the default report template\n  CASA_DEFAULT_COURT_REPORT = File.new(Rails.root.join(\"app/documents/templates/default_report_template.docx\"), \"r\")\n  CASA_DEFAULT_LOGO = Rails.public_path.join(\"logo.jpeg\")\n\n  scope :with_logo, -> { joins(:logo_attachment) }\n\n  before_save :normalize_phone_number\n  before_create :set_slug\n  before_update :sanitize_svg\n\n  validates :name, presence: true, uniqueness: true\n  validates_with CasaOrgValidator\n  validate :validate_twilio_credentials, if: :twilio_enabled, on: :update\n\n  has_many :users, dependent: :destroy\n  has_many :casa_cases, dependent: :destroy\n  has_many :contact_type_groups, dependent: :destroy\n  has_many :hearing_types, dependent: :destroy\n  has_many :mileage_rates, dependent: :destroy\n  has_many :case_assignments, through: :users, source: :casa_cases\n  has_many :languages, dependent: :destroy\n  has_many :placements, through: :casa_cases\n  has_many :banners, dependent: :destroy\n  has_many :learning_hour_types, dependent: :destroy\n  has_many :learning_hour_topics, dependent: :destroy\n  has_many :case_groups, dependent: :destroy\n  has_many :contact_topics\n  has_many :custom_org_links, dependent: :destroy\n  has_one_attached :logo\n  has_one_attached :court_report_template\n  has_many :placement_types, dependent: :destroy\n\n  def casa_admins\n    CasaAdmin.in_organization(self)\n  end\n\n  def supervisors\n    Supervisor.in_organization(self)\n  end\n\n  def volunteers\n    Volunteer.in_organization(self)\n  end\n\n  def case_contacts\n    CaseContact.includes(:creator).where(\n      casa_case_id: CasaCase.where(casa_org_id: id)\n    ).or(\n      CaseContact.includes(:creator).where(\n        casa_case_id: nil, creator: {casa_org: self}\n      )\n    )\n  end\n\n  def followups\n    Followup.in_organization(self)\n  end\n\n  def case_contacts_count\n    case_contacts.count\n  end\n\n  def org_logo\n    if logo.attached?\n      Rails.application.routes.url_helpers.rails_blob_path(logo, only_path: true)\n    else\n      CASA_DEFAULT_LOGO\n    end\n  end\n\n  def open_org_court_report_template(&)\n    if court_report_template.attached?\n      court_report_template.open(&)\n    else\n      yield CASA_DEFAULT_COURT_REPORT\n    end\n  end\n\n  def user_count\n    users.count\n  end\n\n  def set_slug\n    self.slug = name.parameterize\n  end\n\n  def generate_defaults\n    ActiveRecord::Base.transaction do\n      ContactTopic.generate_for_org!(self)\n      ContactTypeGroup.generate_for_org!(self)\n      HearingType.generate_for_org!(self)\n    end\n  end\n\n  def contact_types_by_group\n    contact_type_groups.joins(:contact_types).where(contact_types: {active: true}).alphabetically.uniq\n  end\n\n  # Returns contact types that are active and tied to the CasaOrg as a an array of hashes that can be used by the multiple select component\n  # @return [ActiveRecord::Relation<ContactType>]\n  def contact_types\n    ContactType.joins(:contact_type_group).where(active: true, contact_type_group: {casa_org: self}).order(:name)\n  end\n\n  # Given a specific date, returns the active mileage rate.\n  # If more than one mileage rate is active for a given date, assumes the rate for the most recent date takes precedence.\n  # For instance, given two mileage rates that are active, one set on January 1, 1970 and one set on January 3, 1970:\n  # then the active rate for the given date of January 5, 1970 would be the January 3 rate.\n  # If no rates are active for the given date, will return nil.\n  # @param date [Date]\n  # @return [BigDecimal, nil]\n  def mileage_rate_for_given_date(date)\n    mileage_rates.where(is_active: true, effective_date: ..date).order(effective_date: :desc).first&.amount\n  end\n\n  def has_alternate_active_banner?(current_banner_id)\n    banners.where(active: true).where.not(id: current_banner_id).exists?\n  end\n\n  private\n\n  def sanitize_svg\n    if attachment_changes[\"logo\"]\n      file = attachment_changes[\"logo\"].attachable\n      sanitized_file = SvgSanitizerService.sanitize(file)\n      logo.unfurl(sanitized_file)\n    end\n  end\n\n  # def to_param\n  #   id\n  #   # slug # TODO use slug eventually for routes\n  # end\n\n  def validate_twilio_credentials\n    client = Twilio::REST::Client.new(twilio_api_key_sid, twilio_api_key_secret, twilio_account_sid)\n    begin\n      client.messages.list(limit: 1)\n    rescue Twilio::REST::RestError, URI::InvalidURIError\n      errors.add(:base, \"Your Twilio credentials are incorrect, kindly check and try again.\")\n    end\n  end\n\n  def normalize_phone_number\n    if twilio_phone_number&.length == 10\n      self.twilio_phone_number = \"+1#{twilio_phone_number}\"\n    end\n  end\nend\n\n# == Schema Information\n#\n# Table name: casa_orgs\n#\n#  id                          :bigint           not null, primary key\n#  additional_expenses_enabled :boolean          default(FALSE)\n#  address                     :string\n#  display_name                :string\n#  footer_links                :string           default([]), is an Array\n#  learning_topic_active       :boolean          default(FALSE)\n#  name                        :string           not null\n#  other_duties_enabled        :boolean          default(TRUE)\n#  show_driving_reimbursement  :boolean          default(TRUE)\n#  show_fund_request           :boolean          default(FALSE)\n#  slug                        :string\n#  twilio_account_sid          :string\n#  twilio_api_key_secret       :string\n#  twilio_api_key_sid          :string\n#  twilio_enabled              :boolean          default(FALSE)\n#  twilio_phone_number         :string\n#  created_at                  :datetime         not null\n#  updated_at                  :datetime         not null\n#\n# Indexes\n#\n#  index_casa_orgs_on_slug  (slug) UNIQUE\n#\n"
  },
  {
    "path": "app/models/case_assignment.rb",
    "content": "class CaseAssignment < ApplicationRecord\n  belongs_to :casa_case\n  belongs_to :volunteer, class_name: \"User\"\n  has_one :casa_org, through: :casa_case\n\n  validates :casa_case_id, uniqueness: {scope: :volunteer_id} # only 1 row allowed per case-volunteer pair\n  validates :volunteer, presence: true\n  validate :assignee_must_be_volunteer\n  validate :casa_case_and_volunteer_must_belong_to_same_casa_org, if: -> { casa_case.present? && volunteer.present? }\n\n  scope :is_active, -> { where(is_active: true) }\n  scope :active, -> { where(active: true) }\n\n  def self.inactive_this_week(volunteer_id)\n    where(\"updated_at > ?\", 1.week.ago).where(active: false).where(volunteer_id: volunteer_id)\n  end\n\n  def inactive? = !active?\n\n  private\n\n  def assignee_must_be_volunteer\n    errors.add(:volunteer, \"Case assignee must be an active volunteer\") unless volunteer.is_a?(Volunteer) && volunteer.active?\n  end\n\n  def casa_case_and_volunteer_must_belong_to_same_casa_org\n    return if casa_case.casa_org_id == volunteer.casa_org_id\n\n    errors.add(:volunteer, \"and case must belong to the same organization\")\n  end\nend\n\n# == Schema Information\n#\n# Table name: case_assignments\n#\n#  id                  :bigint           not null, primary key\n#  active              :boolean          default(TRUE), not null\n#  allow_reimbursement :boolean          default(TRUE)\n#  hide_old_contacts   :boolean          default(FALSE)\n#  created_at          :datetime         not null\n#  updated_at          :datetime         not null\n#  casa_case_id        :bigint           not null\n#  volunteer_id        :bigint           not null\n#\n# Indexes\n#\n#  index_case_assignments_on_casa_case_id  (casa_case_id)\n#  index_case_assignments_on_volunteer_id  (volunteer_id)\n#\n# Foreign Keys\n#\n#  fk_rails_...  (casa_case_id => casa_cases.id)\n#  fk_rails_...  (volunteer_id => users.id)\n#\n"
  },
  {
    "path": "app/models/case_contact.rb",
    "content": "class CaseContact < ApplicationRecord\n  include ByOrganizationScope\n  acts_as_paranoid\n\n  attr_accessor :duration_hours\n\n  validates :contact_made, inclusion: {in: [true, false], message: :must_be_true_or_false}\n  validates :miles_driven, numericality: {greater_than_or_equal_to: 0, less_than: 10000}\n  validates :medium_type, presence: true, if: :active_or_details?\n  validates :duration_minutes, presence: true, if: :active_or_details?\n  validates :occurred_at, presence: true, if: :active_or_details?\n  MINIMUM_DATE = \"1989-01-01\".to_date\n  validates :occurred_at, comparison: {\n    greater_than_or_equal_to: MINIMUM_DATE,\n    message: \"can't be prior to #{I18n.l(MINIMUM_DATE)}.\",\n    allow_nil: true\n  }\n  # NOTE: 'extra' day is a temporary fix for user selecting current date, but this validation failing\n  validates :occurred_at, comparison: {\n    less_than: Time.zone.tomorrow + 1.day,\n    message: :cant_be_future,\n    allow_nil: true\n  }\n  validate :reimbursement_only_when_miles_driven, if: :active_or_details?\n  validate :volunteer_address_when_reimbursement_wanted, if: :active_or_details?\n  validate :volunteer_address_is_valid, if: :active_or_details?\n\n  belongs_to :creator, class_name: \"User\"\n  has_one :supervisor_volunteer, -> {\n    where(is_active: true)\n  }, primary_key: :creator_id, foreign_key: :volunteer_id\n  has_one :supervisor, through: :creator\n  has_many :followups\n\n  # Draft casa_case_id is nil until active\n  belongs_to :casa_case, optional: true\n  has_one :casa_org, through: :casa_case\n  # Use creator_casa_org as fallback org relationship for drafts\n  has_one :creator_casa_org, through: :creator, source: :casa_org\n  validates :casa_case_id, presence: true, if: :active?\n  validates :draft_case_ids, presence: {message: :must_be_selected}, if: :active_or_details?\n\n  has_many :case_contact_contact_types\n  validates :case_contact_contact_types, presence: {message: :must_be_selected}, if: :active_or_details?\n  has_many :contact_types, through: :case_contact_contact_types\n\n  has_many :additional_expenses\n  has_many :contact_topic_answers, dependent: :destroy\n  has_many :contact_topics, through: :contact_topic_answers\n\n  after_save_commit ::CaseContactMetadataCallback.new\n\n  # NOTE: 'notes' & 'expenses' statuses are no longer used. Could be removed from enum if\n  # existing records are migrated to started/details status (draft).\n  # NOTE: enum defines methods (active?) and scopes (.active, .not_active) for each member\n  enum :status, {\n    started: \"started\",\n    active: \"active\",\n    details: \"details\",\n    notes: \"notes\",\n    expenses: \"expenses\"\n  }, validate: true, default: :started\n\n  def active_or_details?\n    details? || active?\n  end\n\n  def active_or_expenses?\n    expenses? || active?\n  end\n\n  def active_or_notes?\n    notes? || active?\n  end\n\n  accepts_nested_attributes_for :additional_expenses, reject_if: :all_blank, allow_destroy: true\n\n  accepts_nested_attributes_for :contact_topic_answers, allow_destroy: true,\n    reject_if: proc { |attrs| attrs[\"contact_topic_id\"].blank? && attrs[\"value\"].blank? }  # .notes sent without topic_id, but must have a value.\n\n  scope :supervisors, ->(supervisor_ids = nil) {\n    joins(:supervisor_volunteer).where(supervisor_volunteers: {supervisor_id: supervisor_ids}) if supervisor_ids.present?\n  }\n  scope :creators, ->(creator_ids = nil) {\n    where(creator_id: creator_ids) if creator_ids.present?\n  }\n  scope :casa_org, ->(casa_org_id = nil) {\n    joins(:casa_case).where(casa_cases: {casa_org_id: casa_org_id}) if casa_org_id.present?\n  }\n  scope :occurred_between, ->(start_date = nil, end_date = nil) {\n    where(\"occurred_at BETWEEN ? AND ?\", start_date, end_date) if start_date.present? && end_date.present?\n  }\n  scope :occurred_starting_at, ->(start_date = nil) {\n    where(\"occurred_at >= ?\", start_date) if start_date.present?\n  }\n  scope :occurred_ending_at, ->(end_date = nil) {\n    where(\"occurred_at <= ?\", end_date) if end_date.present?\n  }\n  scope :created_max_ago, ->(time_range = nil) {\n    where(\"case_contacts.created_at > ?\", time_range) if time_range.present?\n  }\n  scope :contact_made, ->(contact_made = nil) {\n    where(contact_made: contact_made) if /true|false/.match?(contact_made.to_s)\n  }\n  scope :has_transitioned, ->(has_transitioned = nil) {\n    if /true|false/.match?(has_transitioned.to_s)\n      operator = has_transitioned ? \"<=\" : \">\"\n\n      joins(:casa_case).where(\"casa_cases.birth_month_year_youth #{operator} ?\", CasaCase::TRANSITION_AGE.years.ago)\n    end\n  }\n  scope :want_driving_reimbursement, ->(want_driving_reimbursement = nil) {\n    if /true|false/.match?(want_driving_reimbursement.to_s)\n      where(want_driving_reimbursement: want_driving_reimbursement)\n    end\n  }\n  scope :contact_type, ->(contact_type_ids = nil) {\n    includes(:contact_types).where(\"contact_types.id\": [contact_type_ids]) if contact_type_ids.present?\n  }\n  scope :contact_types, ->(contact_type_id_list = nil) {\n    contact_type_id_list.reject! { |id| id.blank? }\n\n    return if contact_type_id_list.blank?\n\n    includes(:contact_types).where(\"contact_types.id\": contact_type_id_list)\n  }\n  scope :contact_type_groups, ->(contact_type_group_ids = nil) {\n    # to handle case when passing ids == [''] && ids == nil\n    if contact_type_group_ids&.join&.length&.positive?\n      joins(contact_types: :contact_type_group)\n        .where(contact_type_groups: {id: contact_type_group_ids})\n        .group(:id)\n    end\n  }\n  scope :grab_all, ->(current_user) {\n    with_deleted if current_user.is_a?(CasaAdmin) # TODO since this cases on user type it should be in a Policy file\n  }\n\n  scope :contact_medium, ->(medium_type) {\n    where(medium_type: medium_type) if medium_type.present?\n  }\n\n  scope :filter_by_reimbursement_status, ->(boolean) { where reimbursement_complete: boolean }\n\n  scope :sorted_by, ->(sort_option) {\n    direction = /desc$/.match?(sort_option) ? \"desc\" : \"asc\"\n\n    case sort_option.to_s\n    when /^occurred_at/\n      order(occurred_at: direction)\n    when /^contact_type/\n      joins(:contact_types).merge(ContactType.order(name: direction))\n    when /^medium_type/\n      order(medium_type: direction)\n    when /^want_driving_reimbursement/\n      order(want_driving_reimbursement: direction)\n    when /^contact_made/\n      order(contact_made: direction)\n    else\n      raise(ArgumentError, \"Invalid sort option: #{sort_option.inspect}\")\n    end\n  }\n\n  scope :with_casa_case, ->(case_ids) {\n    where(casa_case_id: case_ids) if case_ids.present?\n  }\n\n  scope :no_drafts, ->(checked) { (checked == 1) ? active : all }\n\n  scope :with_metadata_pair, ->(key, value) { where(\"metadata -> ? @> ?::jsonb\", key.to_s, value.to_s) }\n  scope :used_create_another, -> { with_metadata_pair(:create_another, true) }\n\n  filterrific(\n    default_filter_params: {sorted_by: \"occurred_at_desc\"},\n    available_filters: [\n      :sorted_by,\n      :occurred_starting_at,\n      :occurred_ending_at,\n      :contact_type,\n      :contact_made,\n      :contact_medium,\n      :want_driving_reimbursement,\n      :no_drafts\n    ]\n  )\n\n  IN_PERSON = \"in-person\".freeze\n  TEXT_EMAIL = \"text/email\".freeze\n  VIDEO = \"video\".freeze\n  VOICE_ONLY = \"voice-only\".freeze\n  LETTER = \"letter\".freeze\n  CONTACT_MEDIUMS = [IN_PERSON, TEXT_EMAIL, VIDEO, VOICE_ONLY, LETTER].freeze\n\n  def update_cleaning_contact_types(args)\n    transaction do\n      contact_types.clear\n      update(args)\n    end\n  end\n\n  # Displays occurred_at in the format January 1, 1970\n  # @return [String]\n  def occurred_at_display\n    occurred_at.strftime(\"%B %-d, %Y\")\n  end\n\n  # Returns the mileage rate if the casa_org has a mileage_rate for the date of the contact. Otherwise returns nil.\n  # @return [BigDecimal, nil]\n  def reimbursement_amount\n    mileage_rate = casa_case.casa_org.mileage_rate_for_given_date(occurred_at.to_datetime)\n    return nil unless mileage_rate\n\n    mileage_rate * miles_driven\n  end\n\n  def reimbursement_only_when_miles_driven\n    return if miles_driven&.positive? || !want_driving_reimbursement\n\n    errors.add(:base, \"Must enter miles driven to receive driving reimbursement.\")\n  end\n\n  def volunteer_address_when_reimbursement_wanted\n    if want_driving_reimbursement && volunteer_address&.empty?\n      errors.add(:base, \"Must enter a valid mailing address for the reimbursement.\")\n    end\n  end\n\n  def volunteer_address_is_valid\n    if volunteer_address&.present?\n      if Address.new(user_id: creator.id, content: volunteer_address).invalid?\n        errors.add(:base, \"The volunteer's address is not valid.\")\n      end\n    end\n  end\n\n  def supervisor_id\n    supervisor.id\n  end\n\n  def has_casa_case_transitioned\n    casa_case.in_transition_age?\n  end\n\n  def contact_groups_with_types\n    hash = Hash.new { |h, k| h[k] = [] }\n    contact_types.includes(:contact_type_group).each do |contact_type|\n      hash[contact_type.contact_type_group.name] << contact_type.name\n    end\n    hash\n  end\n\n  def requested_followup\n    followups.find(&:requested?)\n  end\n\n  def should_send_reimbursement_email?\n    want_driving_reimbursement? && supervisor_active?\n  end\n\n  def supervisor_active?\n    !supervisor.blank? && supervisor.active?\n  end\n\n  def address_field_disabled?\n    !volunteer\n  end\n\n  def volunteer\n    if creator.is_a?(Volunteer)\n      creator\n    elsif draft_case_ids.first && CasaCase.find(draft_case_ids.first).volunteers.count == 1\n      CasaCase.find(draft_case_ids.first).volunteers.first\n    end\n  end\n\n  def self.options_for_sorted_by\n    sorted_by_params.each.map { |option_pair| option_pair.reverse }\n  end\n\n  def self.case_hash_from_cases(cases)\n    casa_case_ids = cases.map(&:draft_case_ids).flatten.uniq.sort\n    casa_case_ids.each_with_object({}) do |casa_case_id, hash|\n      hash[casa_case_id] = cases.select { |c| c.casa_case_id == casa_case_id || c.draft_case_ids.include?(casa_case_id) }\n    end\n  end\n\n  def casa_org_any_expenses_enabled?\n    creator.casa_org.additional_expenses_enabled || creator.casa_org.show_driving_reimbursement\n  end\n\n  private_class_method def self.sorted_by_params\n    {\n      occurred_at_asc: \"Date of contact (oldest first)\",\n      occurred_at_desc: \"Date of contact (newest first)\",\n      contact_type_asc: \"Contact type (A-z)\",\n      contact_type_desc: \"Contact type (z-A)\",\n      medium_type_asc: \"Contact medium (A-z)\",\n      medium_type_desc: \"Contact medium (z-A)\",\n      want_driving_reimbursement_asc: \"Want driving reimbursement ('no' first)\",\n      want_driving_reimbursement_desc: \"Want driving reimbursement ('yes' first)\",\n      contact_made_asc: \"Contact made ('no' first)\",\n      contact_made_desc: \"Contact made ('yes' first)\"\n    }\n  end\nend\n\n# == Schema Information\n#\n# Table name: case_contacts\n#\n#  id                         :bigint           not null, primary key\n#  contact_made               :boolean          default(FALSE)\n#  deleted_at                 :datetime\n#  draft_case_ids             :integer          default([]), is an Array\n#  duration_minutes           :integer\n#  medium_type                :string\n#  metadata                   :jsonb\n#  miles_driven               :integer          default(0), not null\n#  notes                      :string\n#  occurred_at                :datetime\n#  reimbursement_complete     :boolean          default(FALSE)\n#  status                     :string           default(\"started\")\n#  volunteer_address          :string\n#  want_driving_reimbursement :boolean          default(FALSE)\n#  created_at                 :datetime         not null\n#  updated_at                 :datetime         not null\n#  casa_case_id               :bigint\n#  creator_id                 :bigint           not null\n#\n# Indexes\n#\n#  index_case_contacts_on_casa_case_id  (casa_case_id)\n#  index_case_contacts_on_creator_id    (creator_id)\n#\n# Foreign Keys\n#\n#  fk_rails_...  (casa_case_id => casa_cases.id)\n#  fk_rails_...  (creator_id => users.id)\n#\n"
  },
  {
    "path": "app/models/case_contact_contact_type.rb",
    "content": "class CaseContactContactType < ApplicationRecord\n  belongs_to :case_contact\n  belongs_to :contact_type\n\n  validates :case_contact_id, uniqueness: {scope: :contact_type_id}\nend\n\n# == Schema Information\n#\n# Table name: case_contact_contact_types\n#\n#  id              :bigint           not null, primary key\n#  created_at      :datetime         not null\n#  updated_at      :datetime         not null\n#  case_contact_id :bigint           not null\n#  contact_type_id :bigint           not null\n#\n# Indexes\n#\n#  index_case_contact_contact_types_on_case_contact_id  (case_contact_id)\n#  index_case_contact_contact_types_on_contact_type_id  (contact_type_id)\n#\n"
  },
  {
    "path": "app/models/case_contact_report.rb",
    "content": "class CaseContactReport\n  COLUMNS = [\n    :internal_contact_number,\n    :duration_minutes,\n    :contact_types,\n    :contact_made,\n    :contact_medium,\n    :occurred_at,\n    :added_to_system_at,\n    :miles_driven,\n    :wants_driving_reimbursement,\n    :casa_case_number,\n    :creator_email,\n    :creator_name,\n    :supervisor_name,\n    :case_contact_notes,\n    :court_topics\n  ]\n\n  attr_reader :case_contacts, :columns\n  def initialize(args = {})\n    @columns = filtered_columns(args)\n    @case_contacts = filtered_case_contacts(args)\n  end\n\n  def to_csv\n    case_contacts_for_csv = @case_contacts\n    CaseContactsExportCsvService.new(case_contacts_for_csv, columns).perform\n  end\n\n  private\n\n  def filtered_case_contacts(args)\n    CaseContact\n      .supervisors(args[:supervisor_ids])\n      .creators(args[:creator_ids])\n      .casa_org(args[:casa_org_id])\n      .occurred_between(args[:start_date], args[:end_date])\n      .contact_made(args[:contact_made])\n      .has_transitioned(args[:has_transitioned])\n      .want_driving_reimbursement(args[:want_driving_reimbursement])\n      .contact_type(args[:contact_type_ids])\n      .contact_type_groups(args[:contact_type_group_ids])\n      .with_casa_case(args[:casa_case_ids])\n  end\n\n  def filtered_columns(args)\n    if args[:filtered_csv_cols].present?\n      args[:filtered_csv_cols].select { |_key, value| value == \"true\" }.keys.map(&:to_sym)\n    else\n      COLUMNS\n    end\n  end\nend\n"
  },
  {
    "path": "app/models/case_court_order.rb",
    "content": "class CaseCourtOrder < ApplicationRecord\n  IMPLEMENTATION_STATUSES = {unimplemented: 1, partially_implemented: 2, implemented: 3}\n  STANDARD_COURT_ORDERS = [\n    \"Birth certificate for the Respondent’s\",\n    \"Domestic Violence Education/Group\",\n    \"Educational monitoring for the Respondent\",\n    \"Educational or Vocational referrals\",\n    \"Family therapy\",\n    \"Housing support for the [parent]\",\n    \"Independent living skills classes or workshops\",\n    \"Individual therapy for the [parent]\",\n    \"Individual therapy for the Respondent\",\n    \"Learners’ permit for the Respondent, drivers’ education and driving hours when needed\",\n    \"No contact with (mother, father, other guardian)\",\n    \"Parenting Classes (mother, father, other guardian)\",\n    \"Psychiatric Evaluation and follow all recommendations (child, mother, father, other guardian)\",\n    \"Substance abuse assessment for the [parent]\",\n    \"Substance Abuse Evaluation and follow all recommendations (child, mother, father, other guardian)\",\n    \"Substance Abuse Treatment (child, mother, father, other guardian)\",\n    \"Supervised visits\",\n    \"Supervised visits at DSS\",\n    \"Therapy (child, mother, father, other guardian)\",\n    \"Tutor for the Respondent\",\n    \"Urinalysis (child, mother, father, other guardian)\",\n    \"Virtual Visits\",\n    \"Visitation assistance for the Respondent to see [family]\"\n  ].freeze\n\n  belongs_to :casa_case\n  belongs_to :court_date, optional: true\n\n  validates :text, presence: true\n\n  enum :implementation_status, IMPLEMENTATION_STATUSES\n\n  def self.court_order_options\n    STANDARD_COURT_ORDERS.map { |o| [o, o] }\n  end\n\n  def implementation_status_symbol\n    case implementation_status\n    when \"implemented\"\n      \"✅\".freeze\n    when \"partially_implemented\"\n      \"🕗\".freeze\n    else\n      \"❌\".freeze\n    end\n  end\nend\n\n# == Schema Information\n#\n# Table name: case_court_orders\n#\n#  id                    :bigint           not null, primary key\n#  implementation_status :integer\n#  text                  :string\n#  created_at            :datetime         not null\n#  updated_at            :datetime         not null\n#  casa_case_id          :bigint           not null\n#  court_date_id         :bigint\n#\n# Indexes\n#\n#  index_case_court_orders_on_casa_case_id   (casa_case_id)\n#  index_case_court_orders_on_court_date_id  (court_date_id)\n#\n# Foreign Keys\n#\n#  fk_rails_...  (casa_case_id => casa_cases.id)\n#\n"
  },
  {
    "path": "app/models/case_court_report.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"sablon\"\n\nclass CaseCourtReport\n  attr_reader :report_path, :context, :template\n\n  def initialize(path_to_template:, context:)\n    @context = context\n    # Validate template exists before processing (sablon 0.4+ no longer raises Zip::Error)\n    raise Zip::Error, \"Template file not found: #{path_to_template}\" unless File.exist?(path_to_template)\n\n    # NOTE: this is what is used for docx templates\n    @template = Sablon.template(path_to_template)\n  end\n\n  def generate_to_string\n    @template.render_to_string(@context)\n  end\nend\n"
  },
  {
    "path": "app/models/case_court_report_context.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"date\"\n\nclass CaseCourtReportContext\n  attr_reader :report_path, :template, :date_range\n\n  def initialize(args = {})\n    @casa_case = CasaCase.friendly.find(args[:case_id])\n    @volunteer = Volunteer.find(args[:volunteer_id]) if args[:volunteer_id]\n    @time_zone = args[:time_zone]\n    @path_to_template = args[:path_to_template]\n    @court_date = args[:court_date] || @casa_case.next_court_date\n    @case_court_orders = args[:case_court_orders] || @casa_case.case_court_orders\n    @date_range = calculate_date_range(args)\n  end\n\n  def context\n    {\n      created_date: I18n.l(Time.current.in_time_zone(@time_zone).to_date, format: :full, default: nil),\n      casa_case: case_details,\n      case_contacts: case_contacts,\n      case_court_orders: case_orders(@case_court_orders),\n      case_mandates: case_orders(@case_court_orders), # backwards compatible with old Montgomery template - keep this! TODO test full generation\n      latest_hearing_date: latest_hearing_date,\n      org_address: org_address(@path_to_template),\n      volunteer: volunteer_info,\n      hearing_type_name: @court_date&.hearing_type&.name || \"None\",\n      case_topics: court_topics.values\n    }\n  end\n\n  # @return [Array<Hash>]\n  #   Each hash includes:\n  #   - :name [String]\n  #   - :type [String]\n  #   - :dates [Array<String>]\n  #   - :dates_by_medium_type [Array<String>]\n  def case_contacts\n    interviewees = filtered_interviewees\n    return [] unless interviewees.size.positive?\n\n    CaseContactsContactDates.new(interviewees).contact_dates_details\n  end\n\n  def latest_hearing_date\n    latest_hearing_date = @casa_case.most_recent_past_court_date\n    latest_hearing_date.nil? ? \"___<LATEST HEARING DATE>____\" : I18n.l(latest_hearing_date.date, format: :full, default: nil)\n  end\n\n  def case_orders(orders)\n    orders.map do |case_order|\n      {\n        order: case_order.text,\n        status: case_order.implementation_status&.humanize\n      }\n    end\n  end\n\n  def filtered_interviewees\n    CaseContactContactType\n      .joins(:contact_type, case_contact: :casa_case)\n      .where(\"case_contacts.casa_case_id\": @casa_case.id)\n      .where(\"case_contacts.occurred_at\": @date_range)\n  end\n\n  def case_details\n    {\n      court_date: I18n.l(@court_date&.date, format: :full, default: nil),\n      case_number: @casa_case.case_number,\n      dob: I18n.l(@casa_case.birth_month_year_youth, format: :youth_date_of_birth, default: nil),\n      is_transitioning: @casa_case.in_transition_age?,\n      judge_name: @court_date&.judge&.name\n    }\n  end\n\n  def volunteer_info\n    if @volunteer\n      {\n        name: @volunteer.display_name,\n        supervisor_name: @volunteer.supervisor&.display_name || \"\",\n        assignment_date: I18n.l(@casa_case.case_assignments.find_by(volunteer: @volunteer).created_at, format: :full, default: nil)\n      }\n    end\n  end\n\n  def org_address(path_to_template)\n    is_default_template = path_to_template.end_with?(\"default_report_template.docx\")\n    @volunteer.casa_org.address if @volunteer && is_default_template\n  end\n\n  # Sample output\n  #\n  # expected_topics = {\n  # \"Question 1\" => {topic: \"Question 1\", details: \"Details 1\", answers: [\n  #   {date: \"12/02/20\", medium: \"Type A1, Type B1\", value: \"Answer 1\"},\n  #   {date: \"12/03/20\", medium: \"Type A2, Type B2\", value: \"Answer 3\"}\n  # ]},\n  # \"Question 2\" => {topic: \"Question 2\", details: \"Details 2\", answers: [\n  #   {date: \"12/02/20\", medium: \"Type A1, Type B1\", value: \"Answer 2\"},\n  #   {date: \"12/04/20\", medium: \"Type A3, Type B3\", value: \"Answer 5\"}\n  # ]},\n  # \"Question 3\" => {topic: \"Question 3\", details: \"Details 3\", answers: [\n  #   {date: \"12/03/20\", medium: \"Type A2, Type B2\", value: \"No Answer Provided\"},\n  #   {date: \"12/04/20\", medium: \"Type A3, Type B3\", value: \"No Answer Provided\"}\n  # ]}\n  # }\n  def court_topics\n    topics = ContactTopic\n      .joins(contact_topic_answers: {case_contact: [:casa_case, :contact_types]}).distinct\n      .where(contact_topics: {exclude_from_court_report: false})\n      .where(\"casa_cases.id\": @casa_case.id)\n      .where(\"case_contacts.occurred_at\": @date_range)\n      .order(:occurred_at, :value)\n      .select(:details, :question, :occurred_at, :value, :contact_made,\n        \"STRING_AGG(contact_types.name, ', ' ORDER BY contact_types.name) AS medium_types\")\n      .group(:details, :question, :occurred_at, :value, :contact_made)\n\n    topics.each_with_object({}) do |topic, hash|\n      hash[topic.question] ||= {\n        answers: [],\n        topic: topic.question,\n        details: topic.details\n      }\n\n      formatted_date = CourtReportFormatContactDate.new(topic).format_long\n      answer_value = topic.value.presence || \"No Answer Provided\"\n      answer = {\n        date: formatted_date,\n        medium: topic.medium_types,\n        value: answer_value\n      }\n\n      hash[topic.question][:answers].push(answer)\n    end\n  end\n\n  private\n\n  def calculate_date_range(args)\n    zone = args[:time_zone] ? ActiveSupport::TimeZone.new(args[:time_zone]) : Time.zone\n\n    start_date = @casa_case.most_recent_past_court_date&.date&.in_time_zone(zone)\n    start_date = zone.parse(args[:start_date]) if args[:start_date]&.present?\n\n    end_date = args[:end_date]&.present? ? zone.parse(args[:end_date]) : nil\n\n    start_date..end_date\n  end\nend\n"
  },
  {
    "path": "app/models/case_group.rb",
    "content": "class CaseGroup < ApplicationRecord\n  belongs_to :casa_org\n  has_many :case_group_memberships, dependent: :destroy\n  has_many :casa_cases, through: :case_group_memberships\n  before_validation :strip_name\n\n  validates :case_group_memberships, presence: true\n\n  validates :name, presence: true, uniqueness: {scope: :casa_org, case_sensitive: false}\n\n  private\n\n  def strip_name\n    self.name = name.strip if name\n  end\nend\n\n# == Schema Information\n#\n# Table name: case_groups\n#\n#  id          :bigint           not null, primary key\n#  name        :string\n#  created_at  :datetime         not null\n#  updated_at  :datetime         not null\n#  casa_org_id :bigint           not null\n#\n# Foreign Keys\n#\n#  fk_rails_...  (casa_org_id => casa_orgs.id)\n#\n"
  },
  {
    "path": "app/models/case_group_membership.rb",
    "content": "class CaseGroupMembership < ApplicationRecord\n  belongs_to :case_group\n  belongs_to :casa_case\n  has_one :casa_org, through: :case_group\nend\n\n# == Schema Information\n#\n# Table name: case_group_memberships\n#\n#  id            :bigint           not null, primary key\n#  created_at    :datetime         not null\n#  updated_at    :datetime         not null\n#  casa_case_id  :bigint           not null\n#  case_group_id :bigint           not null\n#\n# Foreign Keys\n#\n#  fk_rails_...  (casa_case_id => casa_cases.id)\n#  fk_rails_...  (case_group_id => case_groups.id)\n#\n"
  },
  {
    "path": "app/models/checklist_item.rb",
    "content": "class ChecklistItem < ApplicationRecord\n  belongs_to :hearing_type\n  has_one :casa_org, through: :hearing_type\n  validates :category, presence: true\n  validates :description, presence: true\nend\n\n# == Schema Information\n#\n# Table name: checklist_items\n#\n#  id              :bigint           not null, primary key\n#  category        :string           not null\n#  description     :text             not null\n#  mandatory       :boolean          default(FALSE), not null\n#  created_at      :datetime         not null\n#  updated_at      :datetime         not null\n#  hearing_type_id :integer\n#\n"
  },
  {
    "path": "app/models/concerns/CasaCase/validations.rb",
    "content": "require \"active_support/concern\"\n\nmodule CasaCase::Validations\n  extend ActiveSupport::Concern\n\n  included do\n    validates :birth_month_year_youth, presence: true\n    validates :birth_month_year_youth, comparison: {\n      less_than_or_equal_to: -> { Time.now.end_of_day },\n      message: \"is not valid: Youth's Birth Month & Year cannot be a future date.\",\n      allow_nil: true\n    }\n    validates :birth_month_year_youth, comparison: {\n      greater_than_or_equal_to: \"1989-01-01\".to_date,\n      message: \"is not valid: Youth's Birth Month & Year cannot be prior to 1/1/1989.\",\n      allow_nil: true\n    }\n\n    validates :date_in_care, comparison: {\n      less_than_or_equal_to: -> { Time.now.end_of_day },\n      message: \"is not valid: Youth's Date in Care cannot be a future date.\",\n      allow_nil: true\n    }\n    validates :date_in_care, comparison: {\n      greater_than_or_equal_to: \"1989-01-01\".to_date,\n      message: \"is not valid: Youth's Date in Care cannot be prior to 1/1/1989.\",\n      allow_nil: true\n    }\n\n    validates :case_number, uniqueness: {scope: :casa_org_id, case_sensitive: false}, presence: true\n\n    validates :casa_case_contact_types,\n      presence: {message: \": At least one contact type must be selected\",\n                 if: :validate_contact_type}\n\n    # Validation to check timestamp and submission status of a case\n    validates_with CourtReportValidator, fields: [:court_report_status, :court_report_submitted_at]\n  end\nend\n"
  },
  {
    "path": "app/models/concerns/api.rb",
    "content": "module Api\n  extend ActiveSupport::Concern\n  included do\n    has_one :api_credential, dependent: :destroy\n    after_create :initialize_api_credentials\n  end\n\n  private\n\n  def initialize_api_credentials\n    create_api_credential unless api_credential\n  end\nend\n"
  },
  {
    "path": "app/models/concerns/by_organization_scope.rb",
    "content": "require \"active_support/concern\"\n\nmodule ByOrganizationScope\n  extend ActiveSupport::Concern\n\n  included do\n    scope :by_organization, ->(casa_org) { where(casa_org: casa_org) }\n  end\nend\n"
  },
  {
    "path": "app/models/concerns/roles.rb",
    "content": "module Roles\n  extend ActiveSupport::Concern\n\n  def role\n    model_name.human.titleize\n  end\nend\n"
  },
  {
    "path": "app/models/contact_topic.rb",
    "content": "class ContactTopic < ApplicationRecord\n  CASA_DEFAULT_COURT_TOPICS = Rails.root.join(\"db/seeds/default_contact_topics.yml\")\n  belongs_to :casa_org\n\n  has_many :contact_topic_answers\n\n  validates :active, inclusion: [true, false]\n  validates :soft_delete, inclusion: [true, false]\n  validates :question, presence: true\n  validates :details, presence: true\n\n  scope :active, -> { where(active: true, soft_delete: false) }\n\n  scope :with_answers_in, ->(case_contacts_scope) do\n    # unscope order in case it collides with distinct\n    case_contact_ids = case_contacts_scope.unscope(:order).select(:id).distinct\n\n    # AR will use the query above as a subquery within this one's where clause, ie:\n    ContactTopic                                    # select …\n      .joins(contact_topic_answers: :case_contact)  # from contact_topics inner join contact_topics …\n      .where(case_contact: {id: case_contact_ids})  # where case_contact.id in (select distinct case_contacts.id from …\n  end\n\n  class << self\n    def generate_for_org!(casa_org)\n      default_contact_topics.each do |topic|\n        ContactTopic.find_or_create_by!(\n          casa_org:, question: topic[\"question\"], details: topic[\"details\"]\n        )\n      end\n    end\n\n    private\n\n    def default_contact_topics\n      YAML.load_file(CASA_DEFAULT_COURT_TOPICS)\n    end\n  end\nend\n\n# == Schema Information\n#\n# Table name: contact_topics\n#\n#  id                        :bigint           not null, primary key\n#  active                    :boolean          default(TRUE), not null\n#  details                   :text\n#  exclude_from_court_report :boolean          default(FALSE), not null\n#  question                  :string\n#  soft_delete               :boolean          default(FALSE), not null\n#  created_at                :datetime         not null\n#  updated_at                :datetime         not null\n#  casa_org_id               :bigint           not null\n#\n# Foreign Keys\n#\n#  fk_rails_...  (casa_org_id => casa_orgs.id)\n#\n"
  },
  {
    "path": "app/models/contact_topic_answer.rb",
    "content": "class ContactTopicAnswer < ApplicationRecord\n  belongs_to :case_contact\n  belongs_to :contact_topic, optional: true\n  acts_as_paranoid\n\n  has_one :casa_case, through: :case_contact\n  has_one :casa_org, through: :case_contact\n  # case_contact.casa_org may be nil for draft contacts, use for fallback:\n  has_one :contact_creator, through: :case_contact, source: :creator\n  has_one :contact_creator_casa_org, through: :contact_creator, source: :casa_org\n\n  validates :selected, inclusion: [true, false]\n  validates :contact_topic, presence: {if: ->(cta) { cta.value&.present? }, message: :must_be_selected}\nend\n\n# == Schema Information\n#\n# Table name: contact_topic_answers\n#\n#  id               :bigint           not null, primary key\n#  deleted_at       :datetime\n#  selected         :boolean          default(FALSE), not null\n#  value            :text\n#  created_at       :datetime         not null\n#  updated_at       :datetime         not null\n#  case_contact_id  :bigint           not null\n#  contact_topic_id :bigint\n#\n# Indexes\n#\n#  index_contact_topic_answers_on_case_contact_id  (case_contact_id)\n#\n# Foreign Keys\n#\n#  fk_rails_...  (case_contact_id => case_contacts.id)\n#  fk_rails_...  (contact_topic_id => contact_topics.id)\n#\n"
  },
  {
    "path": "app/models/contact_type.rb",
    "content": "class ContactType < ApplicationRecord\n  belongs_to :contact_type_group\n  has_one :casa_org, through: :contact_type_group\n\n  has_many :casa_case_contact_types\n  has_many :casa_cases, through: :casa_case_contact_types\n\n  validates :name, presence: true, uniqueness: {scope: :contact_type_group_id,\n                                                message: \"should be unique per contact type group\"}\n\n  scope :for_organization, ->(org) {\n    joins(:contact_type_group)\n      .where(contact_type_groups: {casa_org: org})\n  }\n  scope :active, -> { where(active: true) }\n  scope :alphabetically, -> { order(:name) }\nend\n\n# == Schema Information\n#\n# Table name: contact_types\n#\n#  id                    :bigint           not null, primary key\n#  active                :boolean          default(TRUE)\n#  name                  :string           not null\n#  created_at            :datetime         not null\n#  updated_at            :datetime         not null\n#  contact_type_group_id :bigint           not null\n#\n# Indexes\n#\n#  index_contact_types_on_contact_type_group_id  (contact_type_group_id)\n#\n"
  },
  {
    "path": "app/models/contact_type_group.rb",
    "content": "class ContactTypeGroup < ApplicationRecord\n  belongs_to :casa_org\n  has_many :contact_types\n\n  validates :name, presence: true, uniqueness: {scope: :casa_org_id}\n\n  scope :for_organization, ->(org) {\n    where(casa_org: org)\n      .order(:name)\n  }\n\n  scope :alphabetically, -> { order(:name) }\n\n  DEFAULT_CONTACT_TYPE_GROUPS = {\n    CASA: [\"Youth\", \"Supervisor\"],\n    Family: [\"Parent\", \"Other Family\", \"Sibling\", \"Grandparent\", \"Aunt Uncle or Cousin\", \"Fictive Kin\"],\n    Placement: [\"Foster Parent\", \"Caregiver Family\", \"Therapeutic Agency Worker\"],\n    \"Social Services\": [\"Social Worker\"],\n    Legal: [\"Court\", \"Attorney\"],\n    Health: [\"Medical Professional\", \"Mental Health Therapist\", \"Other Therapist\", \"Psychiatric Practitioner\"],\n    Education: [\"School\", \"Guidance Counselor\", \"Teacher\", \"IEP Team\"]\n  }.freeze\n\n  class << self\n    def generate_for_org!(casa_org)\n      DEFAULT_CONTACT_TYPE_GROUPS.each do |group_name, type_names|\n        group = ContactTypeGroup.find_or_create_by!(\n          casa_org: casa_org,\n          name: group_name\n        )\n\n        type_names.each do |contact_type_name|\n          ContactType.find_or_create_by!(\n            contact_type_group: group,\n            name: contact_type_name\n          )\n        end\n      end\n    end\n  end\nend\n\n# == Schema Information\n#\n# Table name: contact_type_groups\n#\n#  id          :bigint           not null, primary key\n#  active      :boolean          default(TRUE)\n#  name        :string           not null\n#  created_at  :datetime         not null\n#  updated_at  :datetime         not null\n#  casa_org_id :bigint           not null\n#\n"
  },
  {
    "path": "app/models/court_date.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"sablon\"\n\nclass CourtDate < ApplicationRecord\n  belongs_to :casa_case\n  has_one :casa_org, through: :casa_case\n  validates :date, presence: true\n  validates :date, comparison: {\n    less_than_or_equal_to: -> { 1.year.from_now },\n    message: \"is not valid. Court date must be within one year from today.\",\n    allow_nil: true\n  }\n  validates :date, comparison: {\n    greater_than_or_equal_to: \"1989-01-01\".to_date,\n    message: \"is not valid. Court date cannot be prior to 1/1/1989.\",\n    allow_nil: true\n  }\n\n  has_many :case_court_orders\n  belongs_to :hearing_type, optional: true\n  belongs_to :judge, optional: true\n\n  accepts_nested_attributes_for :case_court_orders, reject_if: :all_blank, allow_destroy: true\n\n  before_save :set_court_report_due_date\n\n  scope :ordered_ascending, -> { order(\"date asc\") }\n\n  # get reports associated with the case this belongs to before this court date but after the court date before this one\n  def associated_reports\n    prev = casa_case.court_dates.where(\"date < ?\", date).order(:date).last\n    if prev\n      casa_case.court_reports.where(\"created_at > ?\", prev.date).where(\"created_at < ?\", date)\n    else\n      casa_case.court_reports.where(\"created_at < ?\", date)\n    end\n  end\n\n  def latest_associated_report\n    associated_reports.order(:created_at).last\n  end\n\n  def additional_info?\n    case_court_orders.any? || hearing_type || judge\n  end\n\n  def display_name\n    \"#{casa_case.case_number} - Court Date - #{I18n.l(date.to_date, format: :year_first)}\"\n  end\n\n  private\n\n  def set_court_report_due_date\n    if date.present? && court_report_due_date.blank?\n      self.court_report_due_date = date - 3.weeks\n    end\n  end\nend\n# == Schema Information\n#\n# Table name: court_dates\n#\n#  id                    :bigint           not null, primary key\n#  court_report_due_date :datetime\n#  date                  :datetime         not null\n#  created_at            :datetime         not null\n#  updated_at            :datetime         not null\n#  casa_case_id          :bigint           not null\n#  hearing_type_id       :bigint\n#  judge_id              :bigint\n#\n# Indexes\n#\n#  index_court_dates_on_casa_case_id  (casa_case_id)\n#\n# Foreign Keys\n#\n#  fk_rails_...  (casa_case_id => casa_cases.id)\n#\n"
  },
  {
    "path": "app/models/custom_org_link.rb",
    "content": "class CustomOrgLink < ApplicationRecord\n  TEXT_MAX_LENGTH = 30\n\n  belongs_to :casa_org\n  validates :text, :url, presence: true\n  validates :text, length: {maximum: TEXT_MAX_LENGTH}\n  validates :active, inclusion: {in: [true, false]}\n  validates :url, url: true\n\n  before_save :trim_name\n\n  private\n\n  def trim_name\n    self.text = text.strip if text.present?\n  end\nend\n\n# == Schema Information\n#\n# Table name: custom_org_links\n#\n#  id          :bigint           not null, primary key\n#  active      :boolean          default(TRUE), not null\n#  text        :string           not null\n#  url         :string           not null\n#  created_at  :datetime         not null\n#  updated_at  :datetime         not null\n#  casa_org_id :bigint           not null\n#\n# Indexes\n#\n#  index_custom_org_links_on_casa_org_id  (casa_org_id)\n#\n# Foreign Keys\n#\n#  fk_rails_...  (casa_org_id => casa_orgs.id)\n#\n"
  },
  {
    "path": "app/models/emancipation_category.rb",
    "content": "class EmancipationCategory < ApplicationRecord\n  has_many :casa_case_emancipation_categories, dependent: :destroy\n  has_many :casa_cases, through: :casa_case_emancipation_categories\n  has_many :emancipation_options\n  validates :name, presence: true\n  validates :mutually_exclusive, inclusion: {in: [true, false]}\n\n  def add_option(option_name)\n    emancipation_options.where(name: option_name).first_or_create\n  end\n\n  def delete_option(option_name)\n    emancipation_options.find_by(name: option_name)&.destroy\n  end\nend\n\n# == Schema Information\n#\n# Table name: emancipation_categories\n#\n#  id                 :bigint           not null, primary key\n#  mutually_exclusive :boolean          not null\n#  name               :string           not null\n#  created_at         :datetime         not null\n#  updated_at         :datetime         not null\n#\n# Indexes\n#\n#  index_emancipation_categories_on_name  (name) UNIQUE\n#\n"
  },
  {
    "path": "app/models/emancipation_option.rb",
    "content": "class EmancipationOption < ApplicationRecord\n  belongs_to :emancipation_category\n  has_many :casa_case_emancipation_options, dependent: :destroy\n  has_many :casa_cases, through: :casa_case_emancipation_options\n\n  validates :name, presence: true, uniqueness: {scope: :emancipation_category_id}\n\n  scope :category_options, ->(emancipation_category_id) {\n    where(emancipation_category_id: emancipation_category_id)\n  }\n\n  scope :options_with_category_and_case, ->(emancipation_category_id, casa_case_id) {\n    joins(:casa_cases)\n      .where(casa_case_emancipation_options: {casa_case_id: casa_case_id})\n      .where(emancipation_category_id: emancipation_category_id)\n  }\nend\n\n# == Schema Information\n#\n# Table name: emancipation_options\n#\n#  id                       :bigint           not null, primary key\n#  name                     :string           not null\n#  created_at               :datetime         not null\n#  updated_at               :datetime         not null\n#  emancipation_category_id :bigint           not null\n#\n# Indexes\n#\n#  index_emancipation_options_on_emancipation_category_id_and_name  (emancipation_category_id,name) UNIQUE\n#\n# Foreign Keys\n#\n#  fk_rails_...  (emancipation_category_id => emancipation_categories.id)\n#\n"
  },
  {
    "path": "app/models/followup.rb",
    "content": "class Followup < ApplicationRecord\n  belongs_to :followupable, polymorphic: true, optional: true # TODO polymorph: remove optional after data is safely migrated\n  belongs_to :case_contact\n  has_one :casa_org, through: :case_contact\n  belongs_to :creator, class_name: \"User\"\n  enum :status, {requested: 0, resolved: 1}\n\n  validate :uniqueness_of_requested\n\n  def self.in_organization(casa_org)\n    Followup.joins(case_contact: :casa_case).where(casa_cases: {casa_org_id: casa_org.id})\n  end\n\n  def uniqueness_of_requested\n    return if resolved?\n    return if existing_requested_followup?\n\n    errors.add(:base, \"Only 1 Followup can be in requested status.\")\n  end\n\n  private\n\n  def existing_requested_followup?\n    Followup.where(status: :requested, case_contact: case_contact).count == 0\n  end\nend\n\n# == Schema Information\n#\n# Table name: followups\n#\n#  id                :bigint           not null, primary key\n#  followupable_type :string\n#  note              :text\n#  status            :integer          default(\"requested\")\n#  created_at        :datetime         not null\n#  updated_at        :datetime         not null\n#  case_contact_id   :bigint\n#  creator_id        :bigint\n#  followupable_id   :bigint\n#\n# Indexes\n#\n#  index_followups_on_case_contact_id  (case_contact_id)\n#\n# Foreign Keys\n#\n#  fk_rails_...  (creator_id => users.id)\n#\n"
  },
  {
    "path": "app/models/fund_request.rb",
    "content": "class FundRequest < ApplicationRecord\n  validates :submitter_email, presence: true\nend\n\n# == Schema Information\n#\n# Table name: fund_requests\n#\n#  id                            :bigint           not null, primary key\n#  deadline                      :text\n#  extra_information             :text\n#  impact                        :text\n#  other_funding_source_sought   :text\n#  payee_name                    :text\n#  payment_amount                :text\n#  request_purpose               :text\n#  requested_by_and_relationship :text\n#  submitter_email               :text\n#  timestamps                    :text\n#  youth_name                    :text\n#\n"
  },
  {
    "path": "app/models/health.rb",
    "content": "class Health < ApplicationRecord\n  # The \"singleton_guard\" column is a unique column which must always be set to '0'\n  # This ensures that only one Health row is created\n  validates :singleton_guard, inclusion: {in: [0]}\n\n  def self.instance\n    first_or_create!(singleton_guard: 0)\n  end\nend\n\n# == Schema Information\n#\n# Table name: healths\n#\n#  id                 :bigint           not null, primary key\n#  latest_deploy_time :datetime\n#  singleton_guard    :integer\n#  created_at         :datetime         not null\n#  updated_at         :datetime         not null\n#\n# Indexes\n#\n#  index_healths_on_singleton_guard  (singleton_guard) UNIQUE\n#\n"
  },
  {
    "path": "app/models/hearing_type.rb",
    "content": "class HearingType < ApplicationRecord\n  belongs_to :casa_org\n  has_many :checklist_items\n\n  validates :name, presence: true, uniqueness: {scope: %i[casa_org]}\n\n  default_scope { order(name: :asc) }\n  scope :for_organization, ->(org) { where(casa_org: org) }\n  scope :active, -> { where(active: true) }\n\n  DEFAULT_HEARING_TYPES = [\n    \"emergency hearing\",\n    \"trial on the merits\",\n    \"scheduling conference\",\n    \"uncontested hearing\",\n    \"pendente lite hearing\",\n    \"pretrial conference\"\n  ].freeze\n\n  class << self\n    def generate_for_org!(casa_org)\n      DEFAULT_HEARING_TYPES.each do |hearing_type|\n        HearingType.find_or_create_by!(\n          casa_org: casa_org,\n          name: hearing_type,\n          active: true\n        )\n      end\n    end\n  end\nend\n\n# == Schema Information\n#\n# Table name: hearing_types\n#\n#  id                     :bigint           not null, primary key\n#  active                 :boolean          default(TRUE), not null\n#  checklist_updated_date :string           default(\"None\"), not null\n#  name                   :string           not null\n#  casa_org_id            :bigint           not null\n#\n"
  },
  {
    "path": "app/models/judge.rb",
    "content": "class Judge < ApplicationRecord\n  belongs_to :casa_org\n\n  validates :name, presence: true, uniqueness: {scope: %i[casa_org]}\n  default_scope { order(name: :asc) }\n  scope :for_organization, ->(org) { where(casa_org: org).order(:name) }\n  scope :active, -> { where(active: true) }\nend\n\n# == Schema Information\n#\n# Table name: judges\n#\n#  id          :bigint           not null, primary key\n#  active      :boolean          default(TRUE)\n#  name        :string\n#  created_at  :datetime         not null\n#  updated_at  :datetime         not null\n#  casa_org_id :bigint           not null\n#\n# Foreign Keys\n#\n#  fk_rails_...  (casa_org_id => casa_orgs.id)\n#\n"
  },
  {
    "path": "app/models/language.rb",
    "content": "class Language < ApplicationRecord\n  belongs_to :casa_org\n  has_many :user_languages\n  has_many :users, through: :user_languages\n  before_validation :strip_name\n\n  validates :name, presence: true, uniqueness: {scope: :casa_org, case_sensitive: false}\n\n  private\n\n  def strip_name\n    self.name = name.strip if name\n  end\nend\n\n# == Schema Information\n#\n# Table name: languages\n#\n#  id          :bigint           not null, primary key\n#  name        :string\n#  created_at  :datetime         not null\n#  updated_at  :datetime         not null\n#  casa_org_id :bigint           not null\n#\n# Foreign Keys\n#\n#  fk_rails_...  (casa_org_id => casa_orgs.id)\n#\n"
  },
  {
    "path": "app/models/learning_hour.rb",
    "content": "class LearningHour < ApplicationRecord\n  belongs_to :user\n  belongs_to :learning_hour_type\n  belongs_to :learning_hour_topic, optional: true\n  has_one :casa_org, through: :user\n\n  validates :duration_minutes, presence: true\n  validates :duration_minutes, numericality: {greater_than: 0, message: \"and hours (total duration) must be greater than 0\"}, if: :zero_duration_hours?\n  validates :name, presence: {message: \"/ Title cannot be blank\"}\n  validates :occurred_at, presence: true\n  validate :occurred_at_not_in_future\n  validates :occurred_at, comparison: {\n    greater_than_or_equal_to: \"1989-01-01\".to_date,\n    message: \"is not valid: Occurred on Date cannot be prior to 1/1/1989.\",\n    allow_nil: true\n  }\n  validates :learning_hour_topic, presence: true, if: :user_org_learning_topic_enable?\n\n  scope :supervisor_volunteers_learning_hours, ->(supervisor_id) {\n    joins(user: :supervisor_volunteer)\n      .where(supervisor_volunteers: {supervisor_id: supervisor_id})\n      .select(\"users.id as user_id, users.display_name, SUM(learning_hours.duration_minutes + learning_hours.duration_hours * 60) AS total_time_spent\")\n      .group(\"users.display_name, users.id\")\n      .order(\"users.display_name\")\n  }\n\n  scope :all_volunteers_learning_hours, ->(casa_admin_org_id) {\n    joins(:user)\n      .where(users: {casa_org_id: casa_admin_org_id})\n      .select(\"users.id as user_id, users.display_name, SUM(learning_hours.duration_minutes + learning_hours.duration_hours * 60) AS total_time_spent\")\n      .group(\"users.display_name, users.id\")\n      .order(\"users.display_name\")\n  }\n\n  private\n\n  def zero_duration_hours?\n    duration_hours == 0\n  end\n\n  def occurred_at_not_in_future\n    return false if !occurred_at\n\n    if occurred_at > Date.today\n      errors.add(:date, \"cannot be in the future\")\n    end\n  end\n\n  def user_org_learning_topic_enable?\n    user.casa_org.learning_topic_active\n  end\nend\n\n# == Schema Information\n#\n# Table name: learning_hours\n#\n#  id                     :bigint           not null, primary key\n#  duration_hours         :integer          not null\n#  duration_minutes       :integer          not null\n#  name                   :string           not null\n#  occurred_at            :datetime         not null\n#  created_at             :datetime         not null\n#  updated_at             :datetime         not null\n#  learning_hour_topic_id :bigint\n#  learning_hour_type_id  :bigint\n#  user_id                :bigint           not null\n#\n# Indexes\n#\n#  index_learning_hours_on_user_id  (user_id)\n#\n# Foreign Keys\n#\n#  fk_rails_...  (learning_hour_type_id => learning_hour_types.id)\n#  fk_rails_...  (user_id => users.id)\n#\n"
  },
  {
    "path": "app/models/learning_hour_topic.rb",
    "content": "class LearningHourTopic < ApplicationRecord\n  belongs_to :casa_org\n  validates :name, presence: true, uniqueness: {scope: %i[casa_org], case_sensitive: false}\n  before_validation :strip_name\n  scope :for_organization, ->(org) { where(casa_org: org).order(:name) }\n\n  private\n\n  def strip_name\n    self.name = name.strip if name\n  end\nend\n\n# == Schema Information\n#\n# Table name: learning_hour_topics\n#\n#  id          :bigint           not null, primary key\n#  name        :string           not null\n#  position    :integer          default(1)\n#  created_at  :datetime         not null\n#  updated_at  :datetime         not null\n#  casa_org_id :bigint           not null\n#\n# Foreign Keys\n#\n#  fk_rails_...  (casa_org_id => casa_orgs.id)\n#\n"
  },
  {
    "path": "app/models/learning_hour_type.rb",
    "content": "class LearningHourType < ApplicationRecord\n  belongs_to :casa_org\n\n  validates :name, presence: true, uniqueness: {scope: %i[casa_org], case_sensitive: false}\n  before_validation :strip_name\n  default_scope { order(position: :asc, name: :asc) }\n  scope :for_organization, ->(org) { where(casa_org: org).order(:name) }\n  scope :active, -> { where(active: true) }\n\n  private\n\n  def strip_name\n    self.name = name.strip if name\n  end\nend\n\n# == Schema Information\n#\n# Table name: learning_hour_types\n#\n#  id          :bigint           not null, primary key\n#  active      :boolean          default(TRUE)\n#  name        :string\n#  position    :integer          default(1)\n#  created_at  :datetime         not null\n#  updated_at  :datetime         not null\n#  casa_org_id :bigint           not null\n#\n# Foreign Keys\n#\n#  fk_rails_...  (casa_org_id => casa_orgs.id)\n#\n"
  },
  {
    "path": "app/models/learning_hours_report.rb",
    "content": "class LearningHoursReport\n  attr_reader :learning_hours\n\n  def initialize(casa_org_id)\n    @learning_hours = LearningHour.includes(:user)\n      .where(user: {casa_org_id: casa_org_id})\n      .order(:id)\n  end\n\n  def to_csv\n    LearningHoursExportCsvService.new(@learning_hours).perform\n  end\nend\n"
  },
  {
    "path": "app/models/login_activity.rb",
    "content": "class LoginActivity < ApplicationRecord\n  belongs_to :user, polymorphic: true, optional: true\nend\n\n# == Schema Information\n#\n# Table name: login_activities\n#\n#  id             :bigint           not null, primary key\n#  city           :string\n#  context        :string\n#  country        :string\n#  failure_reason :string\n#  identity       :string\n#  ip             :string\n#  latitude       :float\n#  longitude      :float\n#  referrer       :text\n#  region         :string\n#  scope          :string\n#  strategy       :string\n#  success        :boolean\n#  user_agent     :text\n#  user_type      :string\n#  created_at     :datetime\n#  user_id        :bigint\n#\n"
  },
  {
    "path": "app/models/mileage_rate.rb",
    "content": "class MileageRate < ApplicationRecord\n  self.ignored_columns = [:user_id] # mileage rates are per casa, not per user\n\n  belongs_to :casa_org\n\n  validates :effective_date, presence: true, allow_blank: false\n  validates :effective_date, uniqueness: {scope: [:is_active, :casa_org], message: \"must not have duplicate active dates\"}, if: :is_active?\n  validates :effective_date, comparison: {\n    greater_than_or_equal_to: \"1989-01-01\".to_date,\n    message: \"cannot be prior to 1/1/1989.\"\n  }\n  validates :effective_date, comparison: {\n    less_than_or_equal_to: -> { 1.year.from_now },\n    message: \"must not be more than one year in the future.\"\n  }\n  validates :amount, presence: true, allow_blank: false\n  validates :casa_org, presence: true, allow_blank: false\n  scope :for_organization, ->(org) { where(casa_org: org) }\nend\n\n# == Schema Information\n#\n# Table name: mileage_rates\n#\n#  id             :bigint           not null, primary key\n#  amount         :decimal(, )\n#  effective_date :date\n#  is_active      :boolean          default(TRUE)\n#  created_at     :datetime         not null\n#  updated_at     :datetime         not null\n#  casa_org_id    :bigint           not null\n#\n# Foreign Keys\n#\n#  fk_rails_...  (casa_org_id => casa_orgs.id)\n#  fk_rails_...  (user_id => users.id)\n#\n"
  },
  {
    "path": "app/models/mileage_report.rb",
    "content": "class MileageReport\n  attr_reader :case_contacts\n\n  def initialize(org_id)\n    @case_contacts = CaseContact.casa_org(org_id).where(\n      want_driving_reimbursement: true,\n      reimbursement_complete: false\n    ).where(\"case_contacts.created_at > ?\", 1.year.ago)\n  end\n\n  def to_csv\n    MileageExportCsvService.new(@case_contacts).perform\n  end\nend\n"
  },
  {
    "path": "app/models/missing_data_report.rb",
    "content": "class MissingDataReport\n  attr_reader :casa_cases\n\n  def initialize(org_id)\n    @casa_cases = CasaCase.where(casa_org_id: org_id)\n      .includes(:court_dates, :case_court_orders)\n  end\n\n  def to_csv\n    MissingDataExportCsvService.new(@casa_cases).perform\n  end\nend\n"
  },
  {
    "path": "app/models/note.rb",
    "content": "class Note < ApplicationRecord\n  belongs_to :notable, polymorphic: true\n  belongs_to :creator, class_name: \"User\"\nend\n\n# == Schema Information\n#\n# Table name: notes\n#\n#  id           :bigint           not null, primary key\n#  content      :string\n#  notable_type :string\n#  created_at   :datetime         not null\n#  updated_at   :datetime         not null\n#  creator_id   :bigint\n#  notable_id   :bigint\n#\n# Foreign Keys\n#\n#  fk_rails_...  (creator_id => users.id)\n#\n"
  },
  {
    "path": "app/models/other_duty.rb",
    "content": "class OtherDuty < ApplicationRecord\n  belongs_to :creator, class_name: \"User\"\n  delegate :type, to: :creator, prefix: true\n\n  validates :notes, presence: true\n  validates :occurred_at, comparison: {\n    less_than_or_equal_to: -> { 1.year.from_now },\n    message: \"is not valid. Occured on date must be within one year from today.\"\n  }\n  validates :occurred_at, comparison: {\n    greater_than_or_equal_to: \"1989-01-01\".to_date,\n    message: \"is not valid. Occured on date cannot be prior to 1/1/1989.\"\n  }\nend\n\n# == Schema Information\n#\n# Table name: other_duties\n#\n#  id               :bigint           not null, primary key\n#  creator_type     :string\n#  duration_minutes :bigint\n#  notes            :text\n#  occurred_at      :datetime\n#  created_at       :datetime         not null\n#  updated_at       :datetime         not null\n#  creator_id       :bigint           not null\n#\n# Foreign Keys\n#\n#  fk_rails_...  (creator_id => users.id)\n#\n"
  },
  {
    "path": "app/models/patch_note.rb",
    "content": "class PatchNote < ApplicationRecord\n  validates :note, presence: true\n\n  belongs_to :patch_note_type\n  belongs_to :patch_note_group\n\n  scope :notes_available_for_user, ->(user) {\n    joins(:patch_note_group)\n      .where(\"POSITION(? IN value)>0\", user.type)\n      .where(\"patch_notes.created_at < ?\", Health.instance.latest_deploy_time)\n  }\nend\n\n# == Schema Information\n#\n# Table name: patch_notes\n#\n#  id                  :bigint           not null, primary key\n#  note                :text             not null\n#  created_at          :datetime         not null\n#  updated_at          :datetime         not null\n#  patch_note_group_id :bigint           not null\n#  patch_note_type_id  :bigint           not null\n#\n# Foreign Keys\n#\n#  fk_rails_...  (patch_note_group_id => patch_note_groups.id)\n#  fk_rails_...  (patch_note_type_id => patch_note_types.id)\n#\n"
  },
  {
    "path": "app/models/patch_note_group.rb",
    "content": "class PatchNoteGroup < ApplicationRecord\n  validates :value, uniqueness: true, presence: true\nend\n\n# == Schema Information\n#\n# Table name: patch_note_groups\n#\n#  id         :bigint           not null, primary key\n#  value      :string           not null\n#  created_at :datetime         not null\n#  updated_at :datetime         not null\n#\n# Indexes\n#\n#  index_patch_note_groups_on_value  (value) UNIQUE\n#\n"
  },
  {
    "path": "app/models/patch_note_type.rb",
    "content": "class PatchNoteType < ApplicationRecord\n  validates :name, presence: true\n  validates :name, uniqueness: true, presence: true\nend\n\n# == Schema Information\n#\n# Table name: patch_note_types\n#\n#  id         :bigint           not null, primary key\n#  name       :string           not null\n#  created_at :datetime         not null\n#  updated_at :datetime         not null\n#\n# Indexes\n#\n#  index_patch_note_types_on_name  (name) UNIQUE\n#\n"
  },
  {
    "path": "app/models/placement.rb",
    "content": "class Placement < ApplicationRecord\n  belongs_to :casa_case\n  belongs_to :placement_type\n  belongs_to :creator, class_name: \"User\"\n  has_one :casa_org, through: :casa_case\n\n  validates :placement_started_at, comparison: {\n    greater_than_or_equal_to: \"1989-01-01\".to_date,\n    message: \"cannot be prior to 1/1/1989.\",\n    allow_nil: true\n  }\n  validates :placement_started_at, comparison: {\n    less_than_or_equal_to: -> { 1.year.from_now },\n    message: \"must not be more than one year in the future.\",\n    allow_nil: true\n  }\nend\n\n# == Schema Information\n#\n# Table name: placements\n#\n#  id                   :bigint           not null, primary key\n#  placement_started_at :datetime         not null\n#  created_at           :datetime         not null\n#  updated_at           :datetime         not null\n#  casa_case_id         :bigint           not null\n#  creator_id           :bigint           not null\n#  placement_type_id    :bigint           not null\n#\n# Foreign Keys\n#\n#  fk_rails_...  (casa_case_id => casa_cases.id)\n#  fk_rails_...  (creator_id => users.id)\n#  fk_rails_...  (placement_type_id => placement_types.id)\n#\n"
  },
  {
    "path": "app/models/placement_type.rb",
    "content": "class PlacementType < ApplicationRecord\n  belongs_to :casa_org\n\n  validates :name, presence: true\n  scope :for_organization, ->(org) { where(casa_org: org) }\n  scope :order_alphabetically, -> { order(:name) }\nend\n\n# == Schema Information\n#\n# Table name: placement_types\n#\n#  id          :bigint           not null, primary key\n#  name        :string           not null\n#  created_at  :datetime         not null\n#  updated_at  :datetime         not null\n#  casa_org_id :bigint           not null\n#\n# Foreign Keys\n#\n#  fk_rails_...  (casa_org_id => casa_orgs.id)\n#\n"
  },
  {
    "path": "app/models/preference_set.rb",
    "content": "class PreferenceSet < ApplicationRecord\n  belongs_to :user\nend\n\n# == Schema Information\n#\n# Table name: preference_sets\n#\n#  id                     :bigint           not null, primary key\n#  case_volunteer_columns :jsonb            not null\n#  table_state            :jsonb\n#  created_at             :datetime         not null\n#  updated_at             :datetime         not null\n#  user_id                :bigint\n#\n# Indexes\n#\n#  index_preference_sets_on_user_id  (user_id)\n#\n# Foreign Keys\n#\n#  fk_rails_...  (user_id => users.id)\n#\n"
  },
  {
    "path": "app/models/sent_email.rb",
    "content": "class SentEmail < ApplicationRecord\n  belongs_to :user\n  belongs_to :casa_org\n\n  validates :mailer_type, presence: true\n  validates :category, presence: true\n  validates :sent_address, presence: true\n\n  scope :for_organization, ->(org) { where(casa_org: org) }\nend\n\n# == Schema Information\n#\n# Table name: sent_emails\n#\n#  id           :bigint           not null, primary key\n#  category     :string\n#  mailer_type  :string\n#  sent_address :string\n#  created_at   :datetime         not null\n#  updated_at   :datetime         not null\n#  casa_org_id  :bigint           not null\n#  user_id      :bigint\n#\n# Foreign Keys\n#\n#  fk_rails_...  (casa_org_id => casa_orgs.id)\n#  fk_rails_...  (user_id => users.id)\n#\n"
  },
  {
    "path": "app/models/sms_notification_event.rb",
    "content": "class SmsNotificationEvent < ApplicationRecord\n  has_many :user_sms_notification_events\n  has_many :users, through: :user_sms_notification_events\nend\n\n# == Schema Information\n#\n# Table name: sms_notification_events\n#\n#  id         :bigint           not null, primary key\n#  name       :string\n#  user_type  :string\n#  created_at :datetime         not null\n#  updated_at :datetime         not null\n#\n"
  },
  {
    "path": "app/models/supervisor.rb",
    "content": "class Supervisor < User\n  devise :invitable, invite_for: 2.weeks\n\n  has_many :supervisor_volunteers\n  has_many :active_supervisor_volunteers, -> { where(is_active: true) }, class_name: \"SupervisorVolunteer\", foreign_key: \"supervisor_id\"\n  has_many :unassigned_supervisor_volunteers, -> { where(is_active: false) }, class_name: \"SupervisorVolunteer\", foreign_key: \"supervisor_id\"\n\n  has_many :volunteers, -> { includes(:supervisor_volunteer).order(:display_name) }, through: :active_supervisor_volunteers\n  has_many :volunteers_ever_assigned, -> { includes(:supervisor_volunteer).order(:display_name) }, through: :supervisor_volunteers, source: :volunteer\n\n  scope :active, -> { where(active: true) }\n\n  # Activates supervisor.\n  def activate\n    update(active: true)\n  end\n\n  # Deactivates supervisor and unassign all volunteers.\n  def deactivate\n    transaction do\n      updated = update(active: false)\n      if updated\n        supervisor_volunteers.update_all(is_active: false)\n      end\n\n      updated\n    end\n  end\n\n  def change_to_admin!\n    becomes!(CasaAdmin).save\n  end\n\n  def pending_volunteers\n    Volunteer.where(invited_by_id: id).or(\n      Volunteer.where(id: volunteers.pluck(:id))\n    ).where(invitation_accepted_at: nil).where.not(invitation_created_at: nil)\n  end\n\n  def inactive_volunteers\n    recent_case_contact_volunteer_ids = volunteers.joins(:case_contacts).where(\n      case_contacts: {created_at: 30.days.ago..}\n    ).pluck(:id)\n\n    volunteers.no_recent_sign_in.where.not(id: recent_case_contact_volunteer_ids)\n  end\n\n  def recently_unassigned_volunteers\n    unassigned_supervisor_volunteers.joins(:volunteer).includes(:volunteer)\n      .where(updated_at: 1.week.ago..Time.zone.now).map(&:volunteer)\n  end\nend\n\n# == Schema Information\n#\n# Table name: users\n#\n#  id                            :bigint           not null, primary key\n#  active                        :boolean          default(TRUE)\n#  confirmation_sent_at          :datetime\n#  confirmation_token            :string\n#  confirmed_at                  :datetime\n#  current_sign_in_at            :datetime\n#  current_sign_in_ip            :string\n#  date_of_birth                 :datetime\n#  display_name                  :string           default(\"\"), not null\n#  email                         :string           default(\"\"), not null\n#  encrypted_password            :string           default(\"\"), not null\n#  invitation_accepted_at        :datetime\n#  invitation_created_at         :datetime\n#  invitation_limit              :integer\n#  invitation_sent_at            :datetime\n#  invitation_token              :string\n#  invitations_count             :integer          default(0)\n#  invited_by_type               :string\n#  last_sign_in_at               :datetime\n#  last_sign_in_ip               :string\n#  monthly_learning_hours_report :boolean          default(FALSE), not null\n#  old_emails                    :string           default([]), is an Array\n#  phone_number                  :string           default(\"\")\n#  receive_email_notifications   :boolean          default(TRUE)\n#  receive_reimbursement_email   :boolean          default(FALSE)\n#  receive_sms_notifications     :boolean          default(FALSE), not null\n#  reset_password_sent_at        :datetime\n#  reset_password_token          :string\n#  sign_in_count                 :integer          default(0), not null\n#  type                          :string\n#  unconfirmed_email             :string\n#  created_at                    :datetime         not null\n#  updated_at                    :datetime         not null\n#  casa_org_id                   :bigint           not null\n#  invited_by_id                 :bigint\n#\n# Indexes\n#\n#  index_users_on_casa_org_id           (casa_org_id)\n#  index_users_on_confirmation_token    (confirmation_token) UNIQUE\n#  index_users_on_email                 (email) UNIQUE\n#  index_users_on_invitation_token      (invitation_token) UNIQUE\n#  index_users_on_invited_by_id         (invited_by_id)\n#  index_users_on_reset_password_token  (reset_password_token) UNIQUE\n#\n# Foreign Keys\n#\n#  fk_rails_...  (casa_org_id => casa_orgs.id)\n#\n"
  },
  {
    "path": "app/models/supervisor_volunteer.rb",
    "content": "# relationship between a supervisor and volunteer\nclass SupervisorVolunteer < ApplicationRecord\n  belongs_to :volunteer, class_name: \"User\"\n  belongs_to :supervisor, class_name: \"User\"\n\n  validates :supervisor_id, uniqueness: {scope: :volunteer_id}\n  validates :volunteer_id, uniqueness: {scope: :is_active}, if: :is_active?\n  validate :ensure_supervisor_and_volunteer_belong_to_same_casa_org, if: -> { supervisor.present? && volunteer.present? }\n\n  private\n\n  def ensure_supervisor_and_volunteer_belong_to_same_casa_org\n    return if supervisor.casa_org_id == volunteer.casa_org_id\n\n    errors.add(:volunteer, \"and supervisor must belong to the same organization\")\n  end\nend\n\n# == Schema Information\n#\n# Table name: supervisor_volunteers\n#\n#  id            :bigint           not null, primary key\n#  is_active     :boolean          default(TRUE)\n#  created_at    :datetime         not null\n#  updated_at    :datetime         not null\n#  supervisor_id :bigint           not null\n#  volunteer_id  :bigint           not null\n#\n# Indexes\n#\n#  index_supervisor_volunteers_on_supervisor_id  (supervisor_id)\n#  index_supervisor_volunteers_on_volunteer_id   (volunteer_id)\n#\n# Foreign Keys\n#\n#  fk_rails_...  (supervisor_id => users.id)\n#  fk_rails_...  (volunteer_id => users.id)\n#\n"
  },
  {
    "path": "app/models/user.rb",
    "content": "# frozen_string_literal: true\n\n# model for all user roles: volunteer supervisor casa_admin inactive\nclass User < ApplicationRecord\n  include Roles\n  include Api\n  include ByOrganizationScope\n  include DateHelper\n\n  before_save :normalize_phone_number\n  after_create :skip_email_confirmation_upon_creation\n  after_create :create_preference_set\n  before_update :record_previous_email\n\n  validates_with UserValidator\n\n  devise :database_authenticatable, :invitable, :recoverable, :validatable, :timeoutable, :trackable, :confirmable\n\n  belongs_to :casa_org\n\n  has_many :case_assignments, foreign_key: \"volunteer_id\", dependent: :destroy # TODO destroy is wrong\n  has_many :casa_cases, -> { where(case_assignments: {active: true}) }, through: :case_assignments\n\n  has_many :case_contacts, foreign_key: \"creator_id\"\n\n  has_many :followups, foreign_key: \"creator_id\"\n\n  has_many :notifications, as: :recipient, dependent: :destroy, class_name: \"Noticed::Notification\"\n  has_many :sent_emails, dependent: :destroy\n\n  has_many :other_duties, foreign_key: \"creator_id\", dependent: :destroy\n\n  has_many :learning_hours, dependent: :destroy\n\n  has_one :supervisor_volunteer, -> {\n    where(is_active: true)\n  }, foreign_key: \"volunteer_id\", dependent: :destroy\n  has_one :supervisor, through: :supervisor_volunteer\n  has_one :preference_set, dependent: :destroy\n\n  has_many :user_sms_notification_events\n  has_many :sms_notification_events, through: :user_sms_notification_events\n  has_many :notes, as: :notable\n  has_one :address, dependent: :destroy\n  has_many :user_languages\n  has_many :languages, through: :user_languages\n  has_many :login_activities, as: :user\n\n  accepts_nested_attributes_for :user_sms_notification_events, :address, allow_destroy: true\n\n  scope :active, -> { where(active: true) }\n\n  scope :inactive, -> { where(active: false) }\n\n  scope :in_organization, lambda { |org|\n    where(casa_org_id: org.id)\n  }\n\n  scope :no_recent_sign_in, -> {\n    active.where(\"last_sign_in_at <= ? or last_sign_in_at is null\", 30.days.ago)\n  }\n\n  def casa_admin?\n    is_a?(CasaAdmin)\n  end\n\n  def supervisor?\n    is_a?(Supervisor)\n  end\n\n  def volunteer?\n    is_a?(Volunteer)\n  end\n\n  def actively_assigned_and_active_cases\n    casa_cases.active.merge(CaseAssignment.active)\n  end\n\n  def active_volunteers\n    volunteers.active.size\n  end\n\n  def create_preference_set\n    self.preference_set = PreferenceSet.create\n  end\n\n  # all contacts this user has with this casa case\n  def case_contacts_for(casa_case_id)\n    found_casa_case = actively_assigned_and_active_cases.find { |cc| cc.id == casa_case_id }\n\n    if found_casa_case.nil?\n      raise ActiveRecord::RecordNotFound.new \"Could not find case with id: #{casa_case_id} belonging to this user\"\n    end\n\n    found_casa_case.case_contacts.filter { |contact| contact.creator_id == id }\n  end\n\n  def recent_contacts_made(days_counter = 60)\n    case_contacts.where(contact_made: true, occurred_at: days_counter.days.ago..Date.today).size\n  end\n\n  def most_recent_contact\n    case_contacts.where(contact_made: true).order(:occurred_at).last\n  end\n\n  # Wrong? Linda/Shen/Joshua - unassigned / inactive volunteers\n  def volunteers_serving_transition_aged_youth\n    volunteers.includes(\n      case_assignments: :casa_case\n    ).where(\n      case_assignments: {active: true}, casa_cases: {active: true, birth_month_year_youth: ..CasaCase::TRANSITION_AGE.years.ago}\n    ).size\n  end\n\n  def no_attempt_for_two_weeks\n    # Get ACTIVE volunteers that have ACTIVE supervisor assignments with at least one ACTIVE case\n    # 1st condition: Volunteer has not created a contact AT ALL within the past 14 days\n\n    no_attempt_count = 0\n    volunteers.map do |volunteer|\n      if volunteer.supervisor.active? &&\n          volunteer.case_assignments.any? { |assignment| assignment.active? } &&\n          (volunteer.case_contacts.none? ||\n          volunteer.case_contacts.maximum(:created_at) < 14.days.ago)\n\n        no_attempt_count += 1\n      end\n    end\n    no_attempt_count\n  end\n\n  # Generate a Devise reset_token, used for the account_setup mailer. This happens automatically\n  # when a user clicks \"Reset My Password\", so do not use this method in that flow.\n  def generate_password_reset_token\n    raw_token, hashed_token = Devise.token_generator.generate(self.class, :reset_password_token)\n\n    self.reset_password_token = hashed_token\n    self.reset_password_sent_at = Time.now.utc\n    save(validate: false)\n\n    raw_token\n  end\n\n  # Called by Devise during initial authentication and on each request to\n  # validate the user is active. For our purposes, the user is active if they\n  # are not inactive\n  def active_for_authentication?\n    super && active\n  end\n\n  def serving_transition_aged_youth?\n    actively_assigned_and_active_cases.is_transitioned.any?\n  end\n\n  def record_previous_email\n    if email_changed? && !old_emails.include?(email_was)\n      old_emails.push(email_was)\n    end\n  end\n\n  def filter_old_emails!(previous_email)\n    updated_emails = old_emails.reject { |old| old == previous_email }\n    update(old_emails: updated_emails)\n  end\n\n  def skip_email_confirmation_upon_creation\n    skip_confirmation!\n    confirm\n  end\n\n  def send_email_changed_notification?\n    false\n  end\n\n  def after_confirmation\n    send_email_changed_notification\n  end\n\n  private\n\n  def normalize_phone_number\n    if phone_number&.length == 10\n      self.phone_number = \"+1#{phone_number}\"\n    end\n  end\nend\n# == Schema Information\n#\n# Table name: users\n#\n#  id                            :bigint           not null, primary key\n#  active                        :boolean          default(TRUE)\n#  confirmation_sent_at          :datetime\n#  confirmation_token            :string\n#  confirmed_at                  :datetime\n#  current_sign_in_at            :datetime\n#  current_sign_in_ip            :string\n#  date_of_birth                 :datetime\n#  display_name                  :string           default(\"\"), not null\n#  email                         :string           default(\"\"), not null\n#  encrypted_password            :string           default(\"\"), not null\n#  invitation_accepted_at        :datetime\n#  invitation_created_at         :datetime\n#  invitation_limit              :integer\n#  invitation_sent_at            :datetime\n#  invitation_token              :string\n#  invitations_count             :integer          default(0)\n#  invited_by_type               :string\n#  last_sign_in_at               :datetime\n#  last_sign_in_ip               :string\n#  monthly_learning_hours_report :boolean          default(FALSE), not null\n#  old_emails                    :string           default([]), is an Array\n#  phone_number                  :string           default(\"\")\n#  receive_email_notifications   :boolean          default(TRUE)\n#  receive_reimbursement_email   :boolean          default(FALSE)\n#  receive_sms_notifications     :boolean          default(FALSE), not null\n#  reset_password_sent_at        :datetime\n#  reset_password_token          :string\n#  sign_in_count                 :integer          default(0), not null\n#  type                          :string\n#  unconfirmed_email             :string\n#  created_at                    :datetime         not null\n#  updated_at                    :datetime         not null\n#  casa_org_id                   :bigint           not null\n#  invited_by_id                 :bigint\n#\n# Indexes\n#\n#  index_users_on_casa_org_id           (casa_org_id)\n#  index_users_on_confirmation_token    (confirmation_token) UNIQUE\n#  index_users_on_email                 (email) UNIQUE\n#  index_users_on_invitation_token      (invitation_token) UNIQUE\n#  index_users_on_invited_by_id         (invited_by_id)\n#  index_users_on_reset_password_token  (reset_password_token) UNIQUE\n#\n# Foreign Keys\n#\n#  fk_rails_...  (casa_org_id => casa_orgs.id)\n#\n"
  },
  {
    "path": "app/models/user_language.rb",
    "content": "class UserLanguage < ApplicationRecord\n  belongs_to :user\n  belongs_to :language\n\n  validates :language, uniqueness: {scope: :user}\nend\n\n# == Schema Information\n#\n# Table name: user_languages\n#\n#  id          :bigint           not null, primary key\n#  created_at  :datetime         not null\n#  updated_at  :datetime         not null\n#  language_id :bigint\n#  user_id     :bigint\n#\n# Indexes\n#\n#  index_user_languages_on_language_id_and_user_id  (language_id,user_id) UNIQUE\n#\n"
  },
  {
    "path": "app/models/user_reminder_time.rb",
    "content": "class UserReminderTime < ApplicationRecord\n  belongs_to :user\nend\n\n# == Schema Information\n#\n# Table name: user_reminder_times\n#\n#  id                 :bigint           not null, primary key\n#  case_contact_types :datetime\n#  no_contact_made    :datetime\n#  created_at         :datetime         not null\n#  updated_at         :datetime         not null\n#  user_id            :bigint           not null\n#\n# Indexes\n#\n#  index_user_reminder_times_on_user_id  (user_id)\n#\n# Foreign Keys\n#\n#  fk_rails_...  (user_id => users.id)\n#\n"
  },
  {
    "path": "app/models/user_sms_notification_event.rb",
    "content": "class UserSmsNotificationEvent < ApplicationRecord\n  belongs_to :user\n  belongs_to :sms_notification_event\nend\n\n# == Schema Information\n#\n# Table name: user_sms_notification_events\n#\n#  id                        :bigint           not null, primary key\n#  created_at                :datetime         not null\n#  updated_at                :datetime         not null\n#  sms_notification_event_id :bigint           not null\n#  user_id                   :bigint           not null\n#\n# Foreign Keys\n#\n#  fk_rails_...  (sms_notification_event_id => sms_notification_events.id)\n#  fk_rails_...  (user_id => users.id)\n#\n"
  },
  {
    "path": "app/models/volunteer.rb",
    "content": "# not a database model -- used for display in tables\n# volunteer is a user role and is controlled by User model\nclass Volunteer < User\n  devise :invitable, invite_for: 1.year\n\n  BULK_COLUMN = \"bulk\"\n  NAME_COLUMN = \"name\"\n  EMAIL_COLUMN = \"email\"\n  SUPERVISOR_COLUMN = \"supervisor\"\n  STATUS_COLUMN = \"status\"\n  ASSIGNED_TO_TRANSITION_AGED_YOUTH_COLUMN = \"assigned_to_transition_aged_youth\"\n  CASE_NUMBER_COLUMN = \"case_number\"\n  LAST_ATTEMPTED_CONTACT_COLUMN = \"last_attempted_contact\"\n  CONTACT_MADE_IN_PAST_DAYS_NUM = 60\n  CONTACT_MADE_IN_PAST_DAYS_COLUMN = \"contact_made_in_past_#{CONTACT_MADE_IN_PAST_DAYS_NUM}_days\".freeze\n  HOURS_SPENT_IN_DAYS_COLUMN = \"hours_spent_in_days\"\n  EXTRA_LANGUAGES_COLUMN = \"has_any_extra_languages\"\n  ACTIONS_COLUMN = \"actions\"\n  TABLE_COLUMNS = [\n    BULK_COLUMN,\n    NAME_COLUMN,\n    EMAIL_COLUMN,\n    SUPERVISOR_COLUMN,\n    STATUS_COLUMN,\n    ASSIGNED_TO_TRANSITION_AGED_YOUTH_COLUMN,\n    CASE_NUMBER_COLUMN,\n    LAST_ATTEMPTED_CONTACT_COLUMN,\n    CONTACT_MADE_IN_PAST_DAYS_COLUMN,\n    HOURS_SPENT_IN_DAYS_COLUMN,\n    EXTRA_LANGUAGES_COLUMN,\n    ACTIONS_COLUMN\n  ].freeze\n  CONTACT_MADE_IN_DAYS_NUM = 14\n  COURT_REPORT_SUBMISSION_REMINDER = 7.days\n\n  scope :with_no_supervisor, lambda { |org|\n    joins(\"left join supervisor_volunteers \" \\\n          \"on supervisor_volunteers.volunteer_id = users.id \" \\\n          \"and supervisor_volunteers.is_active\")\n      .active\n      .in_organization(org)\n      .where(supervisor_volunteers: {id: nil})\n      .active\n  }\n\n  scope :with_supervisor, -> {\n    joins(:supervisor_volunteer)\n  }\n\n  scope :with_assigned_cases, -> {\n    joins(:case_assignments)\n      .where(\"case_assignments.active is true\")\n      .distinct\n      .order(:display_name)\n  }\n\n  scope :with_no_assigned_cases, -> {\n                                   joins(\"left join case_assignments \" \\\n                                         \"on case_assignments.volunteer_id = users.id \" \\\n                                         \"and case_assignments.active\")\n                                     .where(\"case_assignments.volunteer_id is NULL\")\n                                     .distinct\n                                     .order(:display_name)\n                                 }\n\n  scope :birthday_next_month, -> {\n    where(\"EXTRACT(month from date_of_birth) = ?\", DateTime.current.next_month.month)\n  }\n\n  def self.send_court_report_reminder\n    active.includes(:case_assignments).where.not(case_assignments: nil).find_each do |volunteer|\n      volunteer.case_assignments.active.each do |case_assignment|\n        current_case = case_assignment.casa_case\n        report_due_date = current_case.court_dates.order(:date).last&.court_report_due_date\n        if (report_due_date == Date.current + COURT_REPORT_SUBMISSION_REMINDER) && current_case.court_report_not_submitted?\n          VolunteerMailer.court_report_reminder(volunteer, report_due_date)\n          CourtReportDueSmsReminderService.court_report_reminder(volunteer, report_due_date)\n        end\n      end\n    end\n  end\n\n  # Activates this volunteer.\n  def activate\n    update(active: true)\n  end\n\n  # Deactivates this volunteer and all of their case assignments.\n  def deactivate\n    transaction do\n      if update(active: false)\n        case_assignments.update_all(active: false)\n        supervisor_volunteer&.update(is_active: false)\n      end\n    end\n    self\n  end\n\n  def case_assignments_with_cases\n    case_assignments.includes(casa_case: :assigned_volunteers)\n  end\n\n  def has_supervisor?\n    supervisor_volunteer.present? && supervisor_volunteer&.is_active?\n  end\n\n  def supervised_by?(supervisor)\n    supervisor_volunteer&.supervisor_id == supervisor.id\n  end\n\n  # false if volunteer has any case with no contact in the past 30 days\n  def made_contact_with_all_cases_in_days?(num_days = CONTACT_MADE_IN_DAYS_NUM)\n    # TODO this should do the same thing as no_contact_for_two_weeks but for a volunteer\n    total_active_case_count = actively_assigned_and_active_cases.size\n    return true if total_active_case_count.zero?\n    current_contact_cases_count = cases_where_contact_made_in_days(num_days).count\n    current_contact_cases_count == total_active_case_count\n  end\n\n  def hours_spent_in_days(num_days)\n    minutes = actively_assigned_and_active_cases\n      .includes(:case_contacts)\n      .where(case_contacts: {contact_made: true, occurred_at: num_days.days.ago.to_date..})\n      .sum(:duration_minutes)\n\n    [\"#{minutes / 60}h\", \"#{minutes % 60}m\"].select { |str| str =~ /[1-9]/ }.join(\" \")\n  end\n\n  def learning_hours_spent_in_one_year\n    year_duration = learning_hours\n      .where(\"learning_hours.occurred_at > ?\", 1.year.ago)\n      .pluck(:duration_hours, :duration_minutes)\n\n    total_hours = year_duration.map(&:first).reduce(:+) || 0\n    total_minutes = year_duration.map(&:last).reduce(:+) || 0\n    total_duration = total_minutes + total_hours * 60\n    \"#{total_duration / 60}h #{total_duration % 60}min\"\n  end\n\n  private\n\n  def cases_where_contact_made_in_days(num_days = CONTACT_MADE_IN_DAYS_NUM)\n    actively_assigned_and_active_cases\n      .joins(:case_contacts)\n      .where(case_contacts: {contact_made: true, occurred_at: num_days.days.ago.to_date..})\n  end\nend\n\n# == Schema Information\n#\n# Table name: users\n#\n#  id                            :bigint           not null, primary key\n#  active                        :boolean          default(TRUE)\n#  confirmation_sent_at          :datetime\n#  confirmation_token            :string\n#  confirmed_at                  :datetime\n#  current_sign_in_at            :datetime\n#  current_sign_in_ip            :string\n#  date_of_birth                 :datetime\n#  display_name                  :string           default(\"\"), not null\n#  email                         :string           default(\"\"), not null\n#  encrypted_password            :string           default(\"\"), not null\n#  invitation_accepted_at        :datetime\n#  invitation_created_at         :datetime\n#  invitation_limit              :integer\n#  invitation_sent_at            :datetime\n#  invitation_token              :string\n#  invitations_count             :integer          default(0)\n#  invited_by_type               :string\n#  last_sign_in_at               :datetime\n#  last_sign_in_ip               :string\n#  monthly_learning_hours_report :boolean          default(FALSE), not null\n#  old_emails                    :string           default([]), is an Array\n#  phone_number                  :string           default(\"\")\n#  receive_email_notifications   :boolean          default(TRUE)\n#  receive_reimbursement_email   :boolean          default(FALSE)\n#  receive_sms_notifications     :boolean          default(FALSE), not null\n#  reset_password_sent_at        :datetime\n#  reset_password_token          :string\n#  sign_in_count                 :integer          default(0), not null\n#  type                          :string\n#  unconfirmed_email             :string\n#  created_at                    :datetime         not null\n#  updated_at                    :datetime         not null\n#  casa_org_id                   :bigint           not null\n#  invited_by_id                 :bigint\n#\n# Indexes\n#\n#  index_users_on_casa_org_id           (casa_org_id)\n#  index_users_on_confirmation_token    (confirmation_token) UNIQUE\n#  index_users_on_email                 (email) UNIQUE\n#  index_users_on_invitation_token      (invitation_token) UNIQUE\n#  index_users_on_invited_by_id         (invited_by_id)\n#  index_users_on_reset_password_token  (reset_password_token) UNIQUE\n#\n# Foreign Keys\n#\n#  fk_rails_...  (casa_org_id => casa_orgs.id)\n#\n"
  },
  {
    "path": "app/notifications/base_notifier.rb",
    "content": "class BaseNotifier < Noticed::Event\n  # Require title, url and message methods to be implemented on children\n  def title\n    raise NotImplementedError, \"#{self.class} has not implemented method '#{__method__}\"\n  end\n\n  def message\n    raise NotImplementedError, \"#{self.class} has not implemented method '#{__method__}\"\n  end\n\n  def url\n    raise NotImplementedError, \"#{self.class} has not implemented method '#{__method__}\"\n  end\n\n  # Utility methods\n  def read?\n    record.read?\n  end\n\n  def created_at\n    record.created_at\n  end\n\n  def updated_at\n    record.updated_at\n  end\n\n  def created_by\n    created_by_name\n  end\n\n  private\n\n  def created_by_name\n    if params.key?(:created_by)\n      params[:created_by].display_name\n    else # keep backward compatibility with older notifications\n      params[:created_by_name]\n    end\n  end\nend\n"
  },
  {
    "path": "app/notifications/delivery_methods/sms.rb",
    "content": "class DeliveryMethods::Sms < Noticed::DeliveryMethod\n  include SmsBodyHelper\n  def deliver\n    if sender.casa_admin? || sender.supervisor?\n      short_io_api = ShortUrlService.new\n      short_io_api.create_short_url(case_contact_url)\n      shortened_url = short_io_api.short_url\n      twilio_api = TwilioService.new(sender.casa_org)\n      twilio_api.send_sms({From: sender.casa_org.twilio_phone_number, Body: case_contact_flagged_msg(sender.display_name, shortened_url), To: recipient.phone_number})\n    end\n  end\n\n  def case_contact_url\n    Rails.application.credentials[:BASE_URL] + \"/case_contacts/\" + case_contact_id.to_s + \"/edit?notification_id=\" + record.id.to_s\n  end\n\n  private\n\n  def sender\n    User.find(params[:followup][:creator_id])\n  end\n\n  def case_contact_id\n    params[:followup][:case_contact_id]\n  end\nend\n"
  },
  {
    "path": "app/notifications/emancipation_checklist_reminder_notifier.rb",
    "content": "# To deliver this notification:\n#\n# EmancipationChecklistReminderNotifier.with(post: @post).deliver(current_user)\n#\nclass EmancipationChecklistReminderNotifier < BaseNotifier\n  # deliver_by :email do |config|\n  #   config.mailer = \"UserMailer\"\n  #   ...\n  # end\n  # deliver_by :slack\n  # deliver_by :custom, class: \"MyDeliveryMethod\"\n\n  # Add required params\n  required_param :casa_case\n\n  # Define helper methods to make rendering easier.\n\n  def message\n    casa_case = params[:casa_case]\n    \"Your case #{casa_case[:case_number]} is a transition aged youth. \" \\\n      \"We want to make sure that along the way, we’re preparing our youth for emancipation. \" \\\n      \"Make sure to check the emancipation checklist.\"\n  end\n\n  def title\n    \"Emancipation Checklist Reminder\"\n  end\n\n  def url\n    casa_case_emancipation_path(params[:casa_case].id)\n  end\nend\n"
  },
  {
    "path": "app/notifications/followup_notifier.rb",
    "content": "# To deliver this notification:\n#\n# FollowupNotifier.with(followup: @followup).deliver(current_user)\n#\nclass FollowupNotifier < BaseNotifier\n  # deliver_by :email do |config|\n  #   config.mailer = \"UserMailer\"\n  #   ...\n  # end\n  # deliver_by :sms, class: \"DeliveryMethods::Sms\", if: :sms_notifications?\n  # deliver_by :slack\n  # deliver_by :custom, class: \"MyDeliveryMethod\"\n\n  # Add required params\n  required_params :followup, :created_by\n\n  # Define helper methods to make rendering easier.\n  def title\n    \"New followup\"\n  end\n\n  def message\n    build_message\n  end\n\n  def url\n    edit_case_contact_path(params[:followup].case_contact_id)\n  end\n\n  private\n\n  def sms_notifications?\n    recipient.receive_sms_notifications == true\n  end\n\n  def email_notifications?\n    recipient.receive_email_notifications == true\n  end\n\n  def build_message\n    note = params[:followup].note\n    join_char = note.present? ? \"\\n\" : \" \"\n    result = [\"#{created_by} has flagged a Case Contact that needs follow up.\"]\n    result << \"Note: #{note}\" if note.present?\n    result << \"Click to see more.\"\n    result.join(join_char)\n  end\nend\n"
  },
  {
    "path": "app/notifications/followup_resolved_notifier.rb",
    "content": "# To deliver this notification:\n#\n# FollowupResolvedNotifier.with(followup: @followup).deliver(current_user)\n#\nclass FollowupResolvedNotifier < BaseNotifier\n  # deliver_by :email do |config|\n  #   config.mailer = \"UserMailer\"\n  #   ...\n  # end\n  # deliver_by :slack\n  # deliver_by :custom, class: \"MyDeliveryMethod\"\n\n  # Add required params\n  required_params :followup, :created_by\n\n  # Define helper methods to make rendering easier.\n  #\n  def title\n    \"Followup resolved\"\n  end\n\n  def message\n    \"#{created_by_name} resolved a follow up. Click to see more.\"\n  end\n\n  def url\n    edit_case_contact_path(params[:followup].case_contact_id, notification_id: id)\n  end\nend\n"
  },
  {
    "path": "app/notifications/reimbursement_complete_notifier.rb",
    "content": "# To deliver this notification:\n#\n# ReimbursementCompleteNotifier.with(case_contact: @case_contact).deliver(current_user)\n#\nclass ReimbursementCompleteNotifier < BaseNotifier\n  # deliver_by :email do |config|\n  #   config.mailer = \"UserMailer\"\n  #   ...\n  # end\n  # deliver_by :slack\n  # deliver_by :custom, class: \"MyDeliveryMethod\"\n\n  required_param :case_contact\n\n  def title\n    \"Reimbursement Approved\"\n  end\n\n  def message\n    case_contact = params[:case_contact]\n    msg = \"Volunteer #{case_contact.creator.display_name}'s request for reimbursement for #{case_contact.miles_driven}mi \"\n    msg += \" for $#{case_contact.reimbursement_amount} \" if case_contact.reimbursement_amount\n    msg += \"on #{case_contact.occurred_at_display} has been processed and is en route.\"\n    msg\n  end\n\n  def url\n    case_contacts_path(casa_case_id: params[:case_contact].casa_case_id)\n  end\nend\n"
  },
  {
    "path": "app/notifications/volunteer_birthday_notifier.rb",
    "content": "# To deliver this notification:\n#\n# VolunteerBirthdayNotifier.with(post: @post).deliver(current_user)\n#\nclass VolunteerBirthdayNotifier < BaseNotifier\n  # deliver_by :email do |config|\n  #   config.mailer = \"UserMailer\"\n  #   ...\n  # end\n  # deliver_by :sms, class: \"DeliveryMethods::Sms\", if: :sms_notifications?\n  # deliver_by :slack\n  # deliver_by :custom, class: \"MyDeliveryMethod\"\n\n  # Add required params\n  required_param :volunteer\n\n  # Define helper methods to make rendering easier.\n  def message\n    \"🎉 🎂  #{params[:volunteer].display_name}'s birthday is on #{params[:volunteer].decorate.formatted_birthday}!\"\n  end\n\n  def title\n    \"Volunteer Birthday Notification\"\n  end\n\n  def url\n    \"\"\n  end\nend\n"
  },
  {
    "path": "app/notifications/youth_birthday_notifier.rb",
    "content": "# To deliver this notification:\n#\n# YouthBirthdayNotifier.with(post: @post).deliver(current_user)\n#\nclass YouthBirthdayNotifier < BaseNotifier\n  # deliver_by :email do |config|\n  #   config.mailer = \"UserMailer\"\n  #   ...\n  # end\n  # deliver_by :slack\n  # deliver_by :custom, class: \"MyDeliveryMethod\"\n\n  # Add required params\n  required_param :casa_case\n\n  # Define helper methods to make rendering easier.\n  def message\n    \"Your youth, case number: #{params[:casa_case].case_number} has a birthday next month.\"\n  end\n\n  def title\n    \"Youth Birthday Notification\"\n  end\n\n  def url\n    casa_case_path(params[:casa_case].id)\n  end\nend\n"
  },
  {
    "path": "app/policies/additional_expense_policy.rb",
    "content": "class AdditionalExpensePolicy < ApplicationPolicy\n  class Scope < ApplicationPolicy::Scope\n    def resolve\n      case user\n      when CasaAdmin, Supervisor\n        scope.joins(:contact_creator).where(contact_creator: {casa_org: user.casa_org})\n      when Volunteer\n        scope.where(case_contact: user.case_contacts)\n      else\n        scope.none\n      end\n    end\n  end\n\n  def create?\n    case user\n    when Volunteer\n      user.case_contacts.exists?(record.case_contact_id)\n    when CasaAdmin, Supervisor\n      same_org?\n    else\n      false\n    end\n  end\n\n  alias_method :destroy?, :create?\n\n  private\n\n  def same_org?\n    record_org = record.casa_org || record.contact_creator_casa_org\n    user&.casa_org == record_org\n  end\nend\n"
  },
  {
    "path": "app/policies/application_policy.rb",
    "content": "class ApplicationPolicy\n  class Scope\n    attr_reader :user, :scope\n\n    def initialize(user, scope)\n      @user = user\n      @scope = scope\n    end\n\n    def resolve\n      raise NotImplementedError\n    end\n  end\n\n  attr_reader :user, :record\n\n  def initialize(user, record)\n    @user = user\n    @record = record\n  end\n\n  def index?\n    is_admin?\n  end\n\n  def show?\n    is_admin?\n  end\n\n  def create?\n    is_admin?\n  end\n\n  def new?\n    is_admin?\n  end\n\n  def update?\n    is_admin?\n  end\n\n  def edit?\n    is_admin?\n  end\n\n  def destroy?\n    is_admin?\n  end\n\n  def is_admin?\n    user&.casa_admin?\n  end\n\n  def same_org?\n    # NOTE: must have casa_org association on a Policy's associated Model\n    # that is: `has_one :casa_org, through: :some_association` (may need to define :some_association)\n    # do not use for collection actions (index), check user type & use policy_scope() on the collection\n    user&.casa_org.present? && user.casa_org == record&.casa_org\n  end\n\n  def is_admin_same_org?\n    # eventually everything should use this\n    user&.casa_admin? && same_org?\n  end\n\n  def is_supervisor?\n    user&.supervisor?\n  end\n\n  def is_supervisor_same_org?\n    # eventually everything should use this\n    is_supervisor? && same_org?\n  end\n\n  def is_volunteer? # deprecated in favor of is_volunteer_same_org?\n    user.volunteer?\n  end\n\n  def is_volunteer_same_org?\n    user.volunteer? && same_org?\n  end\n\n  def admin_or_supervisor?\n    is_admin? || is_supervisor?\n  end\n\n  def admin_or_supervisor_same_org?\n    # eventually everything should use this\n    is_admin_same_org? || is_supervisor_same_org?\n  end\n\n  def admin_or_supervisor_or_volunteer?\n    admin_or_supervisor? || is_volunteer?\n  end\n\n  def admin_or_supervisor_or_volunteer_same_org?\n    admin_or_supervisor_same_org? || is_volunteer_same_org?\n  end\n\n  def see_reports_page?\n    is_supervisor? || is_admin?\n  end\n\n  def see_emancipation_checklist?\n    is_volunteer?\n  end\n\n  def see_court_reports_page?\n    is_volunteer? || is_supervisor? || is_admin?\n  end\n\n  def see_mileage_rate?\n    is_admin? && reimbursement_enabled? # && matches_casa_org? # TODO do this *in* is_admin - what might that break?\n  end\n\n  def matches_casa_org?\n    @record&.casa_org == @user&.casa_org && !@record.casa_org.nil?\n  end\n\n  def reimbursement_enabled?\n    current_organization&.show_driving_reimbursement\n  end\n\n  def current_organization\n    user&.casa_org\n  end\n\n  alias_method :modify_organization?, :is_admin?\n  alias_method :see_import_page?, :is_admin?\n  alias_method :see_banner_page?, :admin_or_supervisor?\nend\n"
  },
  {
    "path": "app/policies/bulk_court_date_policy.rb",
    "content": "class BulkCourtDatePolicy < ApplicationPolicy\n  # https://github.com/varvet/pundit#headless-policies\n  # record will be `:bulk_court_date`\n\n  def new?\n    admin_or_supervisor?\n  end\n\n  def create?\n    admin_or_supervisor?\n  end\nend\n"
  },
  {
    "path": "app/policies/casa_admin_policy.rb",
    "content": "class CasaAdminPolicy < UserPolicy\n  def index?\n    is_admin?\n  end\n\n  alias_method :new?, :index?\n  alias_method :create?, :index?\n  alias_method :update?, :index?\n  alias_method :activate?, :index?\n  alias_method :resend_invitation?, :index?\n  alias_method :restore?, :is_admin?\n  alias_method :datatable?, :index?\n  alias_method :change_to_supervisor?, :is_admin?\n\n  def edit?\n    is_admin_same_org?\n  end\n\n  def deactivate?\n    see_deactivate_option? && CasaAdmin.in_organization(current_organization).active.size > 1\n  end\n\n  def see_deactivate_option?\n    is_admin? && user.active?\n  end\nend\n"
  },
  {
    "path": "app/policies/casa_case_policy.rb",
    "content": "class CasaCasePolicy < ApplicationPolicy\n  class Scope\n    attr_reader :user, :scope\n\n    def initialize(user, scope)\n      @user = user\n      @scope = scope\n    end\n\n    def resolve\n      case @user\n      when CasaAdmin, Supervisor\n        scope.by_organization(@user.casa_org)\n      when Volunteer\n        scope.actively_assigned_to(user)\n      else\n        raise \"unrecognized user type #{@user.type}\"\n      end\n    end\n\n    def sibling_cases\n      case @user\n      when CasaAdmin, Supervisor\n        user.casa_org.casa_cases.excluding(scope)\n      when Volunteer\n        user.casa_cases.excluding(scope)\n      else\n        raise \"unrecognized user type #{@user.type}\"\n      end\n    end\n  end\n\n  def update_contact_types?\n    admin_or_supervisor_same_org?\n  end\n\n  def update_birth_month_year_youth?\n    is_admin_same_org?\n  end\n\n  def update_date_in_care_youth?\n    admin_or_supervisor_same_org?\n  end\n\n  def update_emancipation_option?\n    # This permission is used in the Emancipations controller\n    admin_or_supervisor_same_org? || is_volunteer_actively_assigned_to_case?\n  end\n\n  def assign_volunteers?\n    admin_or_supervisor_same_org?\n  end\n\n  def can_see_filters?\n    admin_or_supervisor?\n  end\n\n  alias_method :update_case_number?, :is_admin_same_org?\n  alias_method :update_case_status?, :is_admin_same_org?\n  alias_method :update_court_date?, :admin_or_supervisor_or_volunteer_same_org?\n  alias_method :update_hearing_type?, :admin_or_supervisor_or_volunteer_same_org?\n  alias_method :update_judge?, :admin_or_supervisor_or_volunteer_same_org?\n  alias_method :update_court_report_due_date?, :admin_or_supervisor_or_volunteer_same_org?\n  alias_method :update_court_orders?, :admin_or_supervisor_or_volunteer_same_org?\n\n  def permitted_attributes\n    common_attrs = [\n      :court_report_submitted,\n      :court_report_status,\n      contact_type_ids: []\n    ]\n\n    case @user\n    when CasaAdmin\n      common_attrs.concat(\n        %i[case_number birth_month_year_youth court_date court_report_due_date hearing_type_id judge_id date_in_care]\n      )\n      common_attrs << case_court_orders_attributes\n    when Supervisor\n      common_attrs.concat(%i[court_date court_report_due_date hearing_type_id judge_id date_in_care])\n      common_attrs << case_court_orders_attributes\n    when Volunteer\n      common_attrs.concat(%i[court_date court_report_due_date hearing_type_id judge_id])\n      common_attrs << case_court_orders_attributes\n    else\n      common_attrs\n    end\n  end\n\n  def same_org_supervisor_admin_or_assigned?\n    admin_or_supervisor_same_org? || is_volunteer_actively_assigned_to_case?\n  end\n\n  def same_org_supervisor_admin?\n    admin_or_supervisor_same_org?\n  end\n\n  def index?\n    admin_or_supervisor_or_volunteer?\n  end\n\n  alias_method :show?, :same_org_supervisor_admin_or_assigned?\n  alias_method :save_emancipation?, :index? # Should this be the same as edit?\n  alias_method :edit?, :same_org_supervisor_admin_or_assigned?\n  alias_method :update?, :same_org_supervisor_admin_or_assigned?\n  alias_method :new?, :is_admin_same_org?\n  alias_method :create?, :is_admin_same_org?\n  alias_method :destroy?, :is_admin_same_org?\n\n  private\n\n  def is_volunteer_actively_assigned_to_case?\n    return false if record.nil? # no record, no auth\n    return false unless same_org?\n\n    record.case_assignments.exists?(volunteer_id: user.id, active: true)\n  end\n\n  def case_court_orders_attributes\n    {case_court_orders_attributes: %i[text implementation_status id _destroy]}\n  end\nend\n"
  },
  {
    "path": "app/policies/casa_org_policy.rb",
    "content": "class CasaOrgPolicy < ApplicationPolicy\n  def edit?\n    record.users.include?(user) && is_admin?\n  end\n\n  def update?\n    record.users.include?(user) && is_admin?\n  end\n\n  def same_org?\n    user&.casa_org.present? && user.casa_org == record\n  end\nend\n"
  },
  {
    "path": "app/policies/case_assignment_policy.rb",
    "content": "class CaseAssignmentPolicy < ApplicationPolicy\n  class Scope\n    attr_reader :user, :scope\n\n    def initialize(user, scope)\n      @user = user\n      @scope = scope\n    end\n\n    def resolve\n      scope.all # TODO limit to one CASA\n    end\n  end\n\n  def create?\n    admin_or_supervisor?\n  end\n\n  def destroy?\n    admin_or_supervisor_same_org?\n  end\n\n  def unassign?\n    record.active? && admin_or_supervisor_same_org?\n  end\n\n  def reimbursement?\n    admin_or_supervisor_same_org?\n  end\n\n  def show_or_hide_contacts?\n    record.inactive? && admin_or_supervisor_same_org?\n  end\nend\n"
  },
  {
    "path": "app/policies/case_contact_policy.rb",
    "content": "class CaseContactPolicy < ApplicationPolicy\n  def new?\n    is_creator? || admin_or_supervisor_same_org?\n  end\n\n  def show?\n    creator_or_supervisor_or_admin?\n  end\n\n  def update?\n    creator_or_supervisor_or_admin?\n  end\n\n  def destroy?\n    admin_or_supervisor_same_org? || (is_creator? && is_draft?)\n  end\n\n  def additional_expenses_allowed?\n    Flipper.enabled?(:show_additional_expenses) &&\n      current_organization.additional_expenses_enabled\n  end\n\n  alias_method :index?, :admin_or_supervisor_or_volunteer?\n  alias_method :datatable?, :admin_or_supervisor_or_volunteer?\n  alias_method :drafts?, :admin_or_supervisor?\n  alias_method :edit?, :update?\n  alias_method :restore?, :is_admin_same_org?\n\n  class Scope < ApplicationPolicy::Scope\n    def resolve\n      case user\n      when CasaAdmin, Supervisor\n        scope.joins(:creator).where(creator: {casa_org: user.casa_org})\n      when Volunteer\n        scope.where(creator: user)\n      else\n        scope.none\n      end\n    end\n  end\n\n  private\n\n  def creator_or_supervisor_or_admin?\n    is_creator? || admin_or_supervisor_same_org?\n  end\n\n  def is_draft?\n    !record.active?\n  end\n\n  def is_creator?\n    record.creator == user\n  end\n\n  def same_org?\n    record_org = record.casa_org || record.creator_casa_org\n    user&.casa_org_id == record_org&.id\n  end\nend\n"
  },
  {
    "path": "app/policies/case_court_order_policy.rb",
    "content": "class CaseCourtOrderPolicy < ApplicationPolicy\n  alias_method :destroy?, :admin_or_supervisor_or_volunteer?\nend\n"
  },
  {
    "path": "app/policies/case_court_report_policy.rb",
    "content": "class CaseCourtReportPolicy < ApplicationPolicy\n  def index?\n    user.casa_admin? || user.supervisor? || user.volunteer?\n  end\n\n  alias_method :show?, :index?\n  alias_method :generate?, :index?\nend\n"
  },
  {
    "path": "app/policies/case_group_policy.rb",
    "content": "class CaseGroupPolicy < ApplicationPolicy\n  class Scope < ApplicationPolicy::Scope\n    def resolve\n      case user\n      when CasaAdmin, Supervisor\n        scope.where(casa_org: @user.casa_org)\n      when Volunteer\n        scope.none\n      else\n        scope.none\n      end\n    end\n  end\n\n  def index?\n    is_admin? || is_supervisor?\n  end\n\n  def new?\n    admin_or_supervisor_same_org?\n  end\n\n  def show?\n    admin_or_supervisor_same_org?\n  end\n\n  def create?\n    admin_or_supervisor_same_org?\n  end\n\n  def edit?\n    admin_or_supervisor_same_org?\n  end\n\n  def update?\n    admin_or_supervisor_same_org?\n  end\n\n  def destroy?\n    admin_or_supervisor_same_org?\n  end\n\n  def same_org?\n    user&.casa_org.present? && user&.casa_org == record&.casa_org\n  end\nend\n"
  },
  {
    "path": "app/policies/checklist_item_policy.rb",
    "content": "class ChecklistItemPolicy < ApplicationPolicy\n  def new?\n    is_admin?\n  end\n\n  def create?\n    is_admin?\n  end\n\n  def edit?\n    is_admin?\n  end\n\n  def update?\n    is_admin?\n  end\n\n  def destroy?\n    is_admin?\n  end\nend\n"
  },
  {
    "path": "app/policies/contact_topic_answer_policy.rb",
    "content": "class ContactTopicAnswerPolicy < ApplicationPolicy\n  class Scope < ApplicationPolicy::Scope\n    def resolve\n      case user\n      when CasaAdmin, Supervisor\n        scope.joins(:contact_creator).where(contact_creator: {casa_org: user.casa_org})\n      when Volunteer\n        scope.where(case_contact: user.case_contacts)\n      else\n        scope.none\n      end\n    end\n  end\n\n  def create?\n    case user\n    when Volunteer\n      user.case_contacts.exists?(record.case_contact_id)\n    when CasaAdmin, Supervisor\n      same_org?\n    else\n      false\n    end\n  end\n\n  alias_method :destroy?, :create?\n\n  private\n\n  def same_org?\n    record_org = record.casa_org || record.contact_creator_casa_org\n    user&.casa_org == record_org\n  end\nend\n"
  },
  {
    "path": "app/policies/contact_topic_policy.rb",
    "content": "class ContactTopicPolicy < ApplicationPolicy\n  alias_method :create?, :is_admin_same_org?\n  alias_method :edit?, :is_admin_same_org?\n  alias_method :new?, :is_admin_same_org?\n  alias_method :show?, :is_admin_same_org?\n  alias_method :update?, :is_admin_same_org?\n  alias_method :soft_delete?, :is_admin_same_org?\nend\n"
  },
  {
    "path": "app/policies/contact_type_group_policy.rb",
    "content": "class ContactTypeGroupPolicy < ApplicationPolicy\nend\n"
  },
  {
    "path": "app/policies/contact_type_policy.rb",
    "content": "class ContactTypePolicy < ApplicationPolicy\nend\n"
  },
  {
    "path": "app/policies/court_date_policy.rb",
    "content": "class CourtDatePolicy < ApplicationPolicy\n  def allowed_to_edit_casa_case?\n    casa_case_policy.edit?\n  end\n\n  alias_method :show?, :allowed_to_edit_casa_case?\n  alias_method :edit?, :allowed_to_edit_casa_case?\n  alias_method :update?, :allowed_to_edit_casa_case?\n  alias_method :new?, :allowed_to_edit_casa_case?\n  alias_method :create?, :allowed_to_edit_casa_case?\n  alias_method :destroy?, :admin_or_supervisor?\n\n  private\n\n  def casa_case_policy\n    CasaCasePolicy.new(user, record.casa_case)\n  end\nend\n"
  },
  {
    "path": "app/policies/custom_org_link_policy.rb",
    "content": "class CustomOrgLinkPolicy < ApplicationPolicy\n  class Scope < ApplicationPolicy::Scope\n    def resolve\n      case user\n      when CasaAdmin\n        scope.where(casa_org: @user.casa_org)\n      else\n        scope.none\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "app/policies/dashboard_policy.rb",
    "content": "class DashboardPolicy < ApplicationPolicy\n  def all_allowed\n    true\n  end\n\n  def is_admin?\n    user.casa_admin?\n  end\n\n  def create_case_contacts?\n    # TODO this is not really permissions, probably move it out of policyfile\n    user.volunteer? && user.casa_cases.size > 0\n  end\n\n  alias_method :see_volunteers_section?, :is_admin?\n  alias_method :see_admins_section?, :is_admin?\n  alias_method :show?, :all_allowed\n  alias_method :see_cases_section?, :all_allowed\nend\n"
  },
  {
    "path": "app/policies/followup_policy.rb",
    "content": "class FollowupPolicy < ApplicationPolicy\n  def create?\n    admin_or_supervisor_or_volunteer?\n  end\n\n  alias_method :resolve?, :create?\nend\n"
  },
  {
    "path": "app/policies/fund_request_policy.rb",
    "content": "class FundRequestPolicy < ApplicationPolicy\n  def new?\n    true\n  end\n\n  def create?\n    new?\n  end\nend\n"
  },
  {
    "path": "app/policies/hearing_type_policy.rb",
    "content": "class HearingTypePolicy < ApplicationPolicy\n  class Scope < ApplicationPolicy::Scope\n    attr_reader :user, :scope\n\n    def initialize(user, scope)\n      @user = user\n      @scope = scope\n    end\n\n    def resolve\n      case @user\n      when CasaAdmin\n        scope.where(casa_org_id: @user.casa_org.id)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "app/policies/import_policy.rb",
    "content": "class ImportPolicy < ApplicationPolicy\n  def index?\n    user.casa_admin?\n  end\n\n  alias_method :create?, :index?\n  alias_method :download_failed?, :index?\nend\n"
  },
  {
    "path": "app/policies/judge_policy.rb",
    "content": "class JudgePolicy < ApplicationPolicy\nend\n"
  },
  {
    "path": "app/policies/language_policy.rb",
    "content": "class LanguagePolicy < ApplicationPolicy\n  alias_method :add_language?, :is_volunteer?\n  alias_method :remove_from_volunteer?, :is_volunteer?\nend\n"
  },
  {
    "path": "app/policies/learning_hour_policy.rb",
    "content": "class LearningHourPolicy < ApplicationPolicy\n  def index?\n    admin_or_supervisor_or_volunteer?\n  end\n\n  def show?\n    record.user_id == @user.id\n  end\n\n  def new?\n    @user.volunteer?\n  end\n\n  alias_method :edit?, :show?\n  alias_method :destroy?, :show?\n  alias_method :create?, :show?\n  alias_method :update?, :show?\n\n  class Scope\n    attr_reader :user, :scope\n\n    def initialize(user, scope)\n      @user = user\n      @scope = scope\n    end\n\n    def resolve\n      case user\n      when CasaAdmin\n        scope.all_volunteers_learning_hours(@user.casa_org_id)\n      when Supervisor\n        scope.supervisor_volunteers_learning_hours(@user.id)\n      when Volunteer\n        scope.where(user_id: @user.id)\n      else\n        raise \"unrecognized role #{@user.type}\"\n      end\n    end\n\n    alias_method :index?, :resolve\n  end\nend\n"
  },
  {
    "path": "app/policies/learning_hour_topic_policy.rb",
    "content": "class LearningHourTopicPolicy < ApplicationPolicy\nend\n"
  },
  {
    "path": "app/policies/learning_hour_type_policy.rb",
    "content": "class LearningHourTypePolicy < ApplicationPolicy\nend\n"
  },
  {
    "path": "app/policies/nil_class_policy.rb",
    "content": "class NilClassPolicy < ApplicationPolicy\n  def method_missing(*)\n    false\n  end\n\n  def respond_to_missing?(*)\n    true\n  end\nend\n"
  },
  {
    "path": "app/policies/note_policy.rb",
    "content": "class NotePolicy < ApplicationPolicy\n  def create?\n    admin_or_supervisor?\n  end\n\n  def edit?\n    create?\n  end\n\n  def update?\n    edit?\n  end\n\n  def destroy?\n    edit?\n  end\nend\n"
  },
  {
    "path": "app/policies/notification_policy.rb",
    "content": "class NotificationPolicy < ApplicationPolicy\n  def index?\n    admin_or_supervisor_or_volunteer?\n  end\n\n  def mark_as_read?\n    record&.recipient == user\n  end\nend\n"
  },
  {
    "path": "app/policies/other_duty_policy.rb",
    "content": "class OtherDutyPolicy < UserPolicy\n  class Scope\n    attr_reader :user, :scope\n\n    def initialize(user, scope)\n      @user = user\n      @scope = scope\n    end\n  end\n\n  def index?\n    admin_or_supervisor_or_volunteer? && casa_org_other_duties_enabled?\n  end\n\n  def new?\n    user.volunteer? && casa_org_other_duties_enabled?\n  end\n\n  def create?\n    new?\n  end\n\n  def edit?\n    user.volunteer? && record.creator == user && casa_org_other_duties_enabled?\n  end\n\n  def update?\n    edit?\n  end\n\n  def casa_org_other_duties_enabled?\n    user.casa_org.other_duties_enabled\n  end\nend\n"
  },
  {
    "path": "app/policies/patch_note_policy.rb",
    "content": "class PatchNotePolicy < ApplicationPolicy\n  class Scope < ApplicationPolicy::Scope\n    def initialize(user, scope)\n      @user = user\n      @scope = scope\n    end\n\n    def resolve\n      scope.notes_available_for_user(@user)\n    end\n  end\nend\n"
  },
  {
    "path": "app/policies/placement_policy.rb",
    "content": "class PlacementPolicy < ApplicationPolicy\n  class Scope < ApplicationPolicy::Scope\n    def resolve\n      return scope.none unless @user&.casa_org\n\n      scope.joins(:casa_case).where(casa_cases: {casa_org: @user.casa_org})\n    end\n  end\n\n  def allowed_to_edit_casa_case?\n    casa_case_policy.edit?\n  end\n\n  alias_method :show?, :allowed_to_edit_casa_case?\n  alias_method :edit?, :allowed_to_edit_casa_case?\n  alias_method :update?, :allowed_to_edit_casa_case?\n  alias_method :new?, :allowed_to_edit_casa_case?\n  alias_method :create?, :allowed_to_edit_casa_case?\n  alias_method :destroy?, :admin_or_supervisor?\n\n  private\n\n  def casa_case_policy\n    CasaCasePolicy.new(user, record.casa_case)\n  end\nend\n"
  },
  {
    "path": "app/policies/placement_type_policy.rb",
    "content": "class PlacementTypePolicy < ApplicationPolicy\n  class Scope < ApplicationPolicy::Scope\n    def resolve\n      case user\n      when CasaAdmin\n        scope.where(casa_org: @user.casa_org)\n      when Volunteer, Supervisor\n        scope.none\n      else\n        scope.none\n      end\n    end\n  end\n\n  def edit?\n    is_admin_same_org?\n  end\n\n  alias_method :new?, :edit?\n  alias_method :create?, :edit?\n  alias_method :update?, :edit?\nend\n"
  },
  {
    "path": "app/policies/reimbursement_policy.rb",
    "content": "class ReimbursementPolicy < ApplicationPolicy\n  class Scope < ApplicationPolicy::Scope\n    def resolve\n      # scope must INNER JOIN casa_case\n      scope.where(casa_case: {casa_org_id: user.casa_org.id})\n    end\n  end\n\n  def index?\n    (is_admin? || is_supervisor?) && reimbursement_enabled?\n  end\n\n  def datatable?\n    (is_admin? || is_supervisor?) && reimbursement_enabled?\n  end\n\n  def change_complete_status?\n    index? && reimbursement_enabled?\n  end\nend\n"
  },
  {
    "path": "app/policies/supervisor_policy.rb",
    "content": "class SupervisorPolicy < UserPolicy\n  class Scope < ApplicationPolicy::Scope\n    def resolve\n      scope.includes(\n        :volunteers,\n        volunteers: [:supervisor, :case_assignments, :case_contacts]\n      )\n    end\n  end\n\n  def index?\n    admin_or_supervisor?\n  end\n\n  def new?\n    is_admin?\n  end\n\n  def update?\n    (is_admin? || (is_supervisor? && record == user)) && same_org?\n  end\n\n  def activate?\n    is_admin_same_org?\n  end\n\n  def deactivate?\n    is_admin_same_org?\n  end\n\n  def resend_invitation?\n    is_admin_same_org?\n  end\n\n  def edit?\n    admin_or_supervisor_same_org?\n  end\n\n  alias_method :create?, :new?\n  alias_method :datatable?, :index?\n  alias_method :change_to_admin?, :is_admin?\nend\n"
  },
  {
    "path": "app/policies/supervisor_volunteer_policy.rb",
    "content": "class SupervisorVolunteerPolicy < ApplicationPolicy\n  def create?\n    user.casa_admin? || user.supervisor? || user.volunteer?\n  end\n\n  def unassign?\n    user.casa_admin? || user.supervisor?\n  end\n\n  def bulk_assignment?\n    user.casa_admin? || user.supervisor?\n  end\nend\n"
  },
  {
    "path": "app/policies/user_policy.rb",
    "content": "class UserPolicy < ApplicationPolicy\n  def add_language?\n    admin_or_supervisor_same_org? || record == user\n  end\n\n  def remove_language?\n    admin_or_supervisor_same_org? || record == user\n  end\n\n  def edit?\n    admin_or_supervisor_or_volunteer?\n  end\n\n  def update_volunteer_email?\n    admin_or_supervisor?\n  end\n\n  def unassign_case?\n    admin_or_supervisor?\n  end\n\n  alias_method :activate?, :unassign_case?\n\n  def deactivate?\n    activate?\n  end\n\n  def update_supervisor_email?\n    is_admin? || record == user\n  end\n\n  def update_supervisor_name?\n    update_supervisor_email?\n  end\n\n  def update_user_setting?\n    if is_supervisor_same_org?\n      # allow access to own record or volunteer record\n      return record == user || record.volunteer?\n    end\n    is_admin?\n  end\n\n  def edit_name?(viewed_user)\n    is_admin? || viewed_user == user\n  end\n\n  alias_method :update?, :edit?\n  alias_method :update_password?, :edit?\n  alias_method :update_email?, :edit?\n\n  class Scope\n    attr_reader :user, :scope\n\n    def initialize(user, scope)\n      @user = user\n      @scope = scope\n    end\n\n    def resolve\n      case user\n      when CasaAdmin, Supervisor # scope.in_casa_administered_by(user)\n        scope.by_organization(@user.casa_org)\n      when Volunteer\n        scope.where(id: user.id)\n      else\n        raise \"unrecognized role #{@user.type}\"\n      end\n    end\n\n    alias_method :edit?, :resolve\n  end\nend\n"
  },
  {
    "path": "app/policies/volunteer_policy.rb",
    "content": "class VolunteerPolicy < UserPolicy\n  class Scope\n    attr_reader :user, :scope\n\n    def initialize(user, scope)\n      @user = user\n      @scope = scope\n    end\n\n    def resolve\n      case user\n      when CasaAdmin, Supervisor, Volunteer\n        scope.by_organization(@user.casa_org)\n      else\n        raise \"unrecognized role #{@user.type}\"\n      end\n    end\n  end\n\n  def index?\n    admin_or_supervisor?\n  end\n\n  def edit?\n    admin_or_supervisor_same_org?\n  end\n\n  def impersonate?\n    admin_or_supervisor_same_org?\n  end\n\n  def stop_impersonating?\n    admin_or_supervisor_or_volunteer?\n  end\n\n  alias_method :datatable?, :index?\n  alias_method :new?, :admin_or_supervisor_same_org?\n  alias_method :create?, :admin_or_supervisor_same_org?\n  alias_method :show?, :admin_or_supervisor_same_org?\n  alias_method :update?, :admin_or_supervisor_same_org?\n  alias_method :activate?, :admin_or_supervisor_same_org?\n  alias_method :deactivate?, :admin_or_supervisor_same_org?\n  alias_method :resend_invitation?, :admin_or_supervisor_same_org?\n  alias_method :send_reactivation_alert?, :admin_or_supervisor_same_org?\n  alias_method :reminder?, :admin_or_supervisor_same_org?\nend\n"
  },
  {
    "path": "app/presenters/base_presenter.rb",
    "content": "class BasePresenter\n  private\n\n  def current_user\n    @current_user ||= RequestStore.read(:current_user)\n  end\n\n  def current_organization\n    @current_organization ||= RequestStore.read(:current_organization)\n  end\n\n  def policy_scope(scope)\n    Pundit.policy_scope!(current_user, scope)\n  end\nend\n"
  },
  {
    "path": "app/presenters/case_contact_presenter.rb",
    "content": "class CaseContactPresenter < BasePresenter\n  attr_reader :case_contacts\n  attr_reader :casa_cases\n\n  def initialize(case_contacts)\n    @case_contacts = case_contacts\n    @casa_cases = policy_scope(org_cases).group_by(&:id).transform_values(&:first)\n  end\n\n  def display_case_number(casa_case_id)\n    if casa_cases[casa_case_id]&.case_number.present?\n      \"#{casa_cases[casa_case_id].decorate.transition_aged_youth_icon} #{casa_cases[casa_case_id].case_number}\"\n    else\n      \"\"\n    end\n  end\n\n  def boolean_select_options\n    [\n      [\"Yes\", true],\n      [\"No\", false]\n    ]\n  end\n\n  private\n\n  def org_cases\n    CasaOrg.find(current_user.casa_org_id).casa_cases.active\n  end\nend\n"
  },
  {
    "path": "app/services/additional_expense_params_service.rb",
    "content": "class AdditionalExpenseParamsService\n  def initialize(params)\n    @params = params\n  end\n\n  def calculate\n    additional_expenses = @params.dig(\"case_contact\", \"additional_expenses_attributes\")\n    additional_expenses && 0.upto(10).map do |i|\n      possible_key = i.to_s\n      if additional_expenses&.key?(possible_key) && additional_expenses[i.to_s][\"other_expense_amount\"].present?\n        additional_expenses[i.to_s]&.permit(:other_expense_amount, :other_expenses_describe, :id)\n      end\n    end.compact\n  end\nend\n"
  },
  {
    "path": "app/services/backfill_followupable_service.rb",
    "content": "class BackfillFollowupableService\n  def fill_followup_id_and_type\n    Followup.find_each(batch_size: 500) do |followup|\n      followup.update_columns(\n        followupable_id: followup.case_contact_id,\n        followupable_type: \"CaseContact\"\n      )\n    rescue => e\n      Bugsnag.notify(e) do |event|\n        event.add_metadata(:followup, {\n          followup_id: followup.id,\n          case_contact_id: followup.case_contact_id\n        })\n      end\n      Rails.logger.error \"Failed to update Followup ##{followup.id}: #{e.message}\"\n    end\n  end\nend\n"
  },
  {
    "path": "app/services/casa_case_change_service.rb",
    "content": "class CasaCaseChangeService\n  def initialize(original, changed)\n    @original = original\n    @changed = changed\n  end\n\n  attr_reader :original, :changed\n\n  def calculate\n    html_formatted_list(changed_attributes_messages)\n  end\n\n  def changed_attributes_messages\n    changed_attributes = changed.select { |k, v| original[k] != v }.keys.delete_if { |k| k.in?(%i[updated_at slug]) }\n    return if changed_attributes.empty?\n\n    changed_attributes.map do |att|\n      change_message_text(att, original[att], changed[att])\n    end.delete_if(&:nil?)\n  end\n\n  private\n\n  def html_formatted_list(messages)\n    html_string = messages&.join(\"</li><li>\")\n    if html_string.present?\n      \"<ul><li>#{html_string}</li></ul>\"\n    end\n  end\n\n  def change_message_text(attribute, original_attribute, updated_attribute)\n    if attribute == :contact_types\n      new_contact_types = updated_attribute.map { |contact| contact[\"name\"] }\n      previous_contact_types = original_attribute.map { |contact| contact[\"name\"] }\n      changed_contact_types = new_contact_types - previous_contact_types\n      return if changed_contact_types.empty?\n      \"#{changed_contact_types} #{attribute.to_s.humanize.singularize.pluralize(changed_contact_types)} added\"\n    elsif attribute == :court_orders\n      changed_count = (updated_attribute - original_attribute).count\n      \"#{changed_count} #{attribute.to_s.humanize.singularize.pluralize(changed_count)} added or updated\"\n    else\n      \"Changed #{attribute.to_s.gsub(/_id\\Z/, \"\").humanize}\"\n    end\n  end\nend\n"
  },
  {
    "path": "app/services/case_contacts_contact_dates.rb",
    "content": "class CaseContactsContactDates\n  def initialize(case_contact_contact_types)\n    @case_contact_contact_types = case_contact_contact_types\n  end\n\n  def contact_dates_details\n    contact_type_names = @case_contact_contact_types.map(&:contact_type).map(&:name).uniq # .sort # TODO sort after refactor\n    contact_type_names.map do |contact_type_name|\n      case_contacts = case_contacts_for_type(contact_type_name)\n\n      {\n        name: \"Names of persons involved, starting with the child's name\",\n        type: contact_type_name,\n        dates: order_and_format(case_contacts),\n        dates_by_medium_type: case_contacts.group_by(&:medium_type).transform_values { |vals| order_and_format(vals) }\n      }\n    end\n  end\n\n  private\n\n  def case_contacts_for_type(contact_type_name)\n    @case_contact_contact_types\n      .select { |ccct| ccct.contact_type.name == contact_type_name }\n      .map(&:case_contact)\n  end\n\n  def format_dates(case_contacts)\n    case_contacts.map { |case_contact| CourtReportFormatContactDate.new(case_contact).format }.join(\", \")\n  end\n\n  def chron_sort(case_contacts)\n    case_contacts.sort_by { |case_contact| case_contact.occurred_at }\n  end\n\n  def order_and_format(case_contacts)\n    case_contacts.then { chron_sort it }.then { format_dates it }\n  end\nend\n"
  },
  {
    "path": "app/services/case_contacts_export_csv_service.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"csv\"\n\nclass CaseContactsExportCsvService\n  attr_reader :case_contacts_scope, :filtered_columns\n\n  def initialize(case_contacts_scope, filtered_columns)\n    @filtered_columns = filtered_columns\n    @case_contacts_scope = case_contacts_scope\n  end\n\n  def perform\n    case_contacts = case_contacts_scope.preload({creator: :supervisor}, :contact_types, :casa_case, :contact_topic_answers)\n\n    CSV.generate do |csv|\n      csv << fixed_column_headers + court_topics.values\n\n      if case_contacts.present?\n        case_contacts.decorate.each do |case_contact|\n          csv << fixed_column_values(case_contact) + court_topic_answers(case_contact)\n        end\n      end\n    end\n  end\n\n  def fixed_column_values(case_contact)\n    # Note: these header labels are for stakeholders and do not match the\n    # Rails DB names in all cases, e.g. added_to_system_at header is case_contact.created_at\n    mappings = {\n      internal_contact_number: case_contact.id,\n      duration_minutes: case_contact.report_duration_minutes,\n      contact_types: case_contact.report_contact_types,\n      contact_made: case_contact.report_contact_made,\n      contact_medium: case_contact.medium_type,\n      occurred_at: I18n.l(case_contact.occurred_at, format: :full, default: nil),\n      added_to_system_at: case_contact.created_at,\n      miles_driven: case_contact.miles_driven,\n      wants_driving_reimbursement: case_contact.want_driving_reimbursement,\n      casa_case_number: case_contact.casa_case&.case_number,\n      creator_email: case_contact.creator&.email,\n      creator_name: case_contact.creator&.display_name,\n      supervisor_name: case_contact.creator&.supervisor&.display_name,\n      case_contact_notes: case_contact.notes\n    }\n\n    mappings.slice(*filtered_columns).values\n  end\n\n  def fixed_column_headers\n    filtered_columns.excluding(:court_topics).map(&:to_s).map(&:titleize)\n  end\n\n  def court_topic_answers(case_contact)\n    return [] if court_topics_filtered?\n\n    # index_by so we don't loop through answers multiple times\n    answers_by_topic_id = case_contact.contact_topic_answers.index_by(&:contact_topic_id)\n\n    # we have to map values for all topics so we 'skip' unanswered ones (with a blank cell)\n    court_topics.keys.map { |topic_id| answers_by_topic_id[topic_id]&.value }\n  end\n\n  def court_topics\n    return {} if court_topics_filtered?\n\n    @court_topics ||= ContactTopic\n      .with_answers_in(case_contacts_scope)\n      .order(:id)\n      .select(:id, :question)\n      .distinct\n      .to_h { |topic| [topic.id, topic.question] }\n  end\n\n  def court_topics_filtered?\n    return @court_topics_filtered if defined? @court_topics_filtered\n    @court_topics_filtered = filtered_columns.exclude?(:court_topics)\n  end\nend\n"
  },
  {
    "path": "app/services/court_report_due_sms_reminder_service.rb",
    "content": "module CourtReportDueSmsReminderService\n  extend self\n  include SmsReminderService\n  include SmsBodyHelper\n\n  GENERATE_CASE_COURT_REPORT_LINK = \"/case_court_reports\"\n\n  def court_report_reminder(user, report_due_date)\n    short_link = create_short_link(GENERATE_CASE_COURT_REPORT_LINK)\n    message = court_report_due_msg(report_due_date, short_link)\n    send_reminder(user, message)\n  end\nend\n"
  },
  {
    "path": "app/services/court_report_format_contact_date.rb",
    "content": "# frozen_string_literal: true\n\nclass CourtReportFormatContactDate\n  CONTACT_SUCCESSFUL_SUFFIX = \"\"\n  CONTACT_UNSUCCESSFUL_SUFFIX = \"*\"\n\n  def initialize(case_contact)\n    @case_contact = case_contact\n  end\n\n  def format\n    I18n.l(@case_contact.occurred_at, format: :short_date, default: nil).concat(contact_made_suffix)\n  end\n\n  def format_long\n    I18n.l(@case_contact.occurred_at, format: :long_date, default: nil)\n  end\n\n  private\n\n  attr_reader :case_contact\n\n  def contact_made_suffix\n    @case_contact.contact_made ? CONTACT_SUCCESSFUL_SUFFIX : CONTACT_UNSUCCESSFUL_SUFFIX\n  end\nend\n"
  },
  {
    "path": "app/services/create_all_casa_admin_service.rb",
    "content": "class CreateAllCasaAdminService\n  def initialize(params, current_user)\n    @params = params\n    @current_user = current_user\n  end\n\n  def build\n    processed_params = AllCasaAdminParameters.new(@params)\n      .with_password(SecureRandom.hex(10))\n    @all_casa_admin = AllCasaAdmin.new(processed_params)\n  end\n\n  def create!\n    @all_casa_admin.save!\n    @all_casa_admin.invite!(@current_user)\n  end\nend\n"
  },
  {
    "path": "app/services/create_casa_admin_service.rb",
    "content": "class CreateCasaAdminService\n  attr_reader :casa_admin\n\n  def initialize(current_organization, params, current_user)\n    @current_organization = current_organization\n    @params = params\n    @current_user = current_user\n  end\n\n  def build\n    processed_params = CasaAdminParameters.new(@params)\n      .with_password(SecureRandom.hex(10))\n      .with_organization(@current_organization)\n      .without(:active, :type)\n\n    @casa_admin = CasaAdmin.new(processed_params)\n  end\n\n  def create!\n    @casa_admin.save!\n    @casa_admin.invite!(@current_user)\n    @casa_admin\n  end\nend\n"
  },
  {
    "path": "app/services/deployment/backfill_case_contact_started_metadata_service.rb",
    "content": "module Deployment\n  class BackfillCaseContactStartedMetadataService\n    def backfill_metadata\n      case_contacts = CaseContact.where(\"metadata->'status' IS NOT NULL AND metadata->'status'->'started' IS NULL\")\n\n      case_contacts.each do |case_contact|\n        case_contact.metadata[\"status\"][\"started\"] = case_contact.created_at.as_json\n        case_contact.save!\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "app/services/emancipation_checklist_download_html.rb",
    "content": "class EmancipationChecklistDownloadHtml\n  def initialize(current_case, emancipation_form_data)\n    @current_case = current_case\n    @emancipation_form_data = emancipation_form_data\n  end\n\n  def call\n    html_body = ApplicationController.render(\n      template: \"emancipations/download\",\n      layout: false,\n      assigns: {\n        current_case: @current_case,\n        emancipation_form_data: @emancipation_form_data\n      }\n    ).squish\n\n    html_body = html_body.gsub(\"<!-- BEGIN app/views/emancipations/download.html.erb -->\", \"\")\n    html_body = html_body.gsub(\"<!-- END app/views/emancipations/download.html.erb -->\", \"\")\n\n    html_body.gsub(\"> <\", \"><\")\n  end\nend\n"
  },
  {
    "path": "app/services/emancipation_checklist_reminder_service.rb",
    "content": "class EmancipationChecklistReminderService\n  attr_reader :cases\n\n  def initialize\n    @cases = CaseAssignment\n      .active\n      .includes(:casa_case, :volunteer)\n      .where(casa_cases: {\n        birth_month_year_youth: ..CasaCase::TRANSITION_AGE.years.ago\n      })\n  end\n\n  def send_reminders\n    if Time.now.utc.to_date.day == 1\n      cases.each do |assignment|\n        ::EmancipationChecklistReminderNotifier\n          .with(casa_case: assignment.casa_case)\n          .deliver(assignment.volunteer)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "app/services/failed_import_csv_service.rb",
    "content": "require \"digest\"\n\nclass FailedImportCsvService\n  attr_accessor :failed_rows\n  attr_reader :import_type, :user, :csv_filepath\n\n  MAX_FILE_SIZE_BYTES = 250.kilobytes\n  EXPIRATION_TIME = 24.hours\n\n  def initialize(import_type:, user:, failed_rows: \"\", filepath: nil)\n    @failed_rows = failed_rows\n    @import_type = import_type\n    @user = user\n    @csv_filepath = filepath || generate_filepath\n  end\n\n  def store\n    if failed_rows.bytesize > MAX_FILE_SIZE_BYTES\n      Rails.logger.warn(\"CSV too large to save for user=#{user.id}, size=#{failed_rows.bytesize}\")\n      @failed_rows = max_size_warning\n    end\n\n    FileUtils.mkdir_p(File.dirname(csv_filepath))\n    File.write(csv_filepath, failed_rows)\n  end\n\n  def read\n    return exists_warning unless csv_exists?\n\n    if expired?\n      remove_csv\n      return expired_warning\n    end\n\n    File.read(csv_filepath)\n  end\n\n  def cleanup\n    return unless csv_exists?\n\n    log_info(\"Removing old failed rows CSV\")\n    remove_csv\n  end\n\n  private\n\n  def log_info(msg)\n    Rails.logger.info(\"User=#{user.id}, Type=#{import_type}: #{msg}\")\n  end\n\n  def exists_warning\n    \"No failed import file found. Please upload a #{humanised_import_type} CSV.\"\n  end\n\n  def expired_warning\n    \"The failed import file has expired. Please upload a new #{humanised_import_type} CSV.\"\n  end\n\n  def max_size_warning\n    \"The file was too large to save. Please make sure your CSV is smaller than 250 KB and try again.\"\n  end\n\n  def generate_filepath\n    Pathname.new(Rails.root.join(\"tmp\", import_type.to_s, filename)).cleanpath\n  end\n\n  def csv_exists?\n    File.exist?(csv_filepath)\n  end\n\n  def expired?\n    csv_exists? && File.mtime(csv_filepath) < Time.current - EXPIRATION_TIME\n  end\n\n  def remove_csv\n    FileUtils.rm_f(csv_filepath)\n  end\n\n  def filename\n    short_hash = Digest::SHA256.hexdigest(user.id.to_s)[0..15]\n    \"failed_rows_userid_#{short_hash}.csv\"\n  end\n\n  def humanised_import_type\n    I18n.t(\"imports.labels.#{import_type}\", default: import_type.to_s.humanize.downcase)\n  end\nend\n"
  },
  {
    "path": "app/services/fdf_inputs_service.rb",
    "content": "# frozen_string_literal: true\n\nclass FdfInputsService\n  FDF_ERB_TEMPLATE_PATH = [\"data\", \"inputs_fdf.erb\"].freeze\n\n  def self.clean(str)\n    return unless str.present?\n\n    str\n      .to_s\n      .gsub(/[)(\\\\]/, '\\\\\\\\\\0')\n  end\n\n  def initialize(inputs:, pdf_template_path:, basename:)\n    @inputs = inputs\n    @pdf_template_path = pdf_template_path\n    @basename = basename\n  end\n\n  def write_to_file(flatten: true)\n    file ||= Tempfile.new(basename)\n    with_fdf_tempfile do |fdf|\n      pdftk = PdfForms.new\n      pdftk.fill_form_with_fdf(pdf_template_path, file.path, fdf.path, flatten: flatten)\n    end\n    file\n  end\n\n  private\n\n  attr_reader :inputs, :pdf_template_path, :basename\n\n  def with_fdf_tempfile\n    Tempfile.open(basename) do |fdf|\n      fdf.puts ERB.new(fdf_template).result(binding)\n      fdf.rewind\n      yield(fdf)\n    end\n  end\n\n  def fdf_template\n    File.read(Rails.root.join(*FDF_ERB_TEMPLATE_PATH))\n  end\nend\n"
  },
  {
    "path": "app/services/followup_export_csv_service.rb",
    "content": "require \"csv\"\n\nclass FollowupExportCsvService\n  def initialize(casa_org)\n    @casa_org = casa_org\n  end\n\n  def perform\n    # Call to includes will run 5 selects, one for each association\n    # regardless of how many followup records that exist in the\n    # export. This prevents an N+1 query for getting the case_number\n    # and volunteer display_name.\n    followups = @casa_org.followups.includes(case_contact: {casa_case: :volunteers})\n\n    CSV.generate(headers: true) do |csv|\n      # generate the header row\n      csv << full_data.keys.map(&:to_s).map(&:titleize)\n      # data rows\n      followups.each do |followup|\n        csv << full_data(followup).values\n      end\n    end\n  end\n\n  private\n\n  def full_data(followup = nil)\n    {\n      case_number: followup&.case_contact&.casa_case&.case_number,\n      \"volunteer_name(s)\": followup&.case_contact&.casa_case&.volunteers&.sort_by(&:display_name)&.map(&:display_name)&.join(\" and \"),\n      note_creator_name: followup&.creator&.display_name,\n      note: followup&.note\n    }\n  end\nend\n"
  },
  {
    "path": "app/services/followup_service.rb",
    "content": "class FollowupService\n  def self.create_followup(case_contact, creator, note)\n    followup = case_contact.followups.new(\n      creator: creator,\n      status: :requested,\n      note: note\n    )\n\n    # TODO Dual writing logic (temporary) polymorph\n    followup.followupable = case_contact\n\n    if followup.save\n      send_notification(followup, creator)\n    end\n\n    followup\n  end\n\n  private_class_method def self.send_notification(followup, creator)\n    FollowupNotifier\n      .with(followup: followup, created_by: creator)\n      .deliver(followup.case_contact.creator)\n  end\nend\n"
  },
  {
    "path": "app/services/inactive_messages_service.rb",
    "content": "class InactiveMessagesService\n  attr_reader :inactive_messages\n\n  def initialize(supervisor)\n    @inactive_messages = calculate_inactive_messages(supervisor)\n  end\n\n  private\n\n  def calculate_inactive_messages(supervisor)\n    supervisor.volunteers.map do |volunteer|\n      inactive_cases = CaseAssignment.inactive_this_week(volunteer.id)\n      inactive_cases.map do |case_assignment|\n        inactive_case_number = case_assignment.casa_case.case_number\n        \"#{volunteer.display_name} Case #{inactive_case_number} marked inactive this week.\"\n      end\n    end.flatten\n  end\nend\n"
  },
  {
    "path": "app/services/learning_hours_export_csv_service.rb",
    "content": "require \"csv\"\n\nclass LearningHoursExportCsvService\n  attr_reader :learning_hours\n\n  def initialize(learning_hours)\n    @learning_hours = learning_hours\n  end\n\n  def perform\n    CSV.generate(headers: true) do |csv|\n      csv << filtered_learning_hours.keys.map(&:to_s).map(&:titleize)\n      @learning_hours.each do |learning_hour|\n        csv << filtered_learning_hours(learning_hour).values\n      end\n    end\n  end\n\n  private\n\n  def get_duration(learning_hour = nil)\n    return \"0:00\" unless learning_hour\n    \"#{learning_hour.duration_hours}:#{learning_hour.duration_minutes}\"\n  end\n\n  def filtered_learning_hours(learning_hour = nil)\n    {\n      volunteer_name: learning_hour&.user&.display_name,\n      learning_hours_title: learning_hour&.name,\n      learning_hours_type: learning_hour&.learning_hour_type&.name,\n      duration: get_duration(learning_hour),\n      date_of_learning: learning_hour&.occurred_at&.strftime(\"%F\")\n    }\n  end\nend\n"
  },
  {
    "path": "app/services/mileage_export_csv_service.rb",
    "content": "require \"csv\"\n\nclass MileageExportCsvService\n  def initialize(case_contacts)\n    @case_contacts = case_contacts.preload({creator: :supervisor}, :contact_types, :casa_case)\n  end\n\n  def perform\n    CSV.generate(headers: true) do |csv|\n      csv << full_data.keys.map(&:to_s).map(&:titleize)\n      if @case_contacts.present?\n        @case_contacts.decorate.each do |case_contact|\n          csv << full_data(case_contact).values\n        end\n      end\n    end\n  end\n\n  private\n\n  def full_data(case_contact = nil)\n    # Note: these header labels are for stakeholders and do not match the\n    # Rails DB names in all cases, e.g. added_to_system_at header is case_contact.created_at\n    {\n      contact_types: case_contact&.report_contact_types,\n      occurred_at: I18n.l(case_contact&.occurred_at, format: :full, default: nil),\n      miles_driven: case_contact&.miles_driven,\n      casa_case_number: case_contact&.casa_case&.case_number,\n      creator_name: case_contact&.creator&.display_name,\n      supervisor_name: case_contact&.creator&.supervisor&.display_name,\n      volunteer_address: case_contact&.creator&.address&.content,\n      reimbursed: case_contact&.reimbursement_complete\n    }\n  end\nend\n"
  },
  {
    "path": "app/services/missing_data_export_csv_service.rb",
    "content": "require \"csv\"\n\nclass MissingDataExportCsvService\n  attr_reader :casa_cases\n\n  def initialize(casa_cases)\n    @casa_cases = casa_cases\n  end\n\n  def perform\n    CSV.generate(headers: true) do |csv|\n      csv << full_data.keys.map(&:to_s).map(&:titleize)\n      if casa_cases.present?\n        casa_cases.decorate.each do |casa_case|\n          if has_missing_values?(casa_case)\n            csv << full_data(casa_case).values\n          end\n        end\n      end\n    end\n  end\n\n  private\n\n  def has_missing_values?(casa_case)\n    !casa_case.birth_month_year_youth? ||\n      casa_case.next_court_date.nil? ||\n      casa_case.case_court_orders.empty?\n  end\n\n  def get_status(missing)\n    missing ? \"MISSING\" : \"OK\"\n  end\n\n  def full_data(casa_case = nil)\n    {\n      casa_case_number: casa_case&.case_number,\n      youth_birth_month_and_year: get_status(!casa_case&.birth_month_year_youth?),\n      upcoming_hearing_date: get_status(casa_case&.next_court_date.nil?),\n      court_orders: get_status(casa_case&.case_court_orders&.empty?)\n    }\n  end\nend\n"
  },
  {
    "path": "app/services/no_contact_made_sms_reminder_service.rb",
    "content": "module NoContactMadeSmsReminderService\n  extend self\n  include SmsReminderService\n  include SmsBodyHelper\n\n  NEW_CASE_CONTACT_LINK = \"/case_contacts/new\"\n\n  def no_contact_made_reminder(user, contact_type)\n    short_link = create_short_link(NEW_CASE_CONTACT_LINK)\n    message = no_contact_made_msg(contact_type, short_link)\n    send_reminder(user, message)\n  end\nend\n"
  },
  {
    "path": "app/services/placement_export_csv_service.rb",
    "content": "require \"csv\"\n\nclass PlacementExportCsvService\n  attr_reader :placements\n\n  def initialize(casa_org:)\n    @casa_org = casa_org\n  end\n\n  def perform\n    placements = fetch_placements\n\n    CSV.generate(headers: true) do |csv|\n      csv << full_data.keys.map(&:to_s).map(&:titleize)\n      placements.decorate.each do |placement|\n        csv << full_data(placement).values\n      end\n    end\n  end\n\n  private\n\n  def full_data(placement = nil)\n    {\n      casa_org: placement&.id,\n      casa_case_number: placement&.casa_case&.case_number,\n      placement_type_id: placement&.placement_type_id,\n      placement_started_at: placement&.placement_started_at,\n      created_at: placement&.created_at,\n      creator_name: placement&.creator&.display_name\n    }\n  end\n\n  def fetch_placements\n    @casa_org.placements\n  end\nend\n"
  },
  {
    "path": "app/services/preference_set_table_state_service.rb",
    "content": "class PreferenceSetTableStateService\n  class TableStateUpdateFailed < StandardError; end\n\n  def initialize(user_id:)\n    @user_id = user_id\n  end\n\n  def table_state(table_name:)\n    preference_set.table_state[table_name]\n  end\n\n  def update!(table_name:, table_state:)\n    preference_set.table_state[table_name] = table_state\n    preference_set.save!\n  rescue\n    raise TableStateUpdateFailed, \"Failed to update table state for '#{table_name}'\"\n  end\n\n  private\n\n  attr_reader :user_id\n  def preference_set\n    @preference_set ||= PreferenceSet.find_by!(user_id: user_id)\n  end\nend\n"
  },
  {
    "path": "app/services/short_url_service.rb",
    "content": "require \"json\"\n\nclass ShortUrlService\n  attr_reader :short_url\n\n  include ApiBaseHelper\n  include RequestHeaderHelper\n  include HTTParty\n  base_uri SHORT_IO\n  headers ACCEPT_JSON\n  headers CONTENT_TYPE_JSON\n\n  def initialize\n    validate_credentials\n    @short_domain = Rails.application.credentials[:SHORT_IO_DOMAIN]\n    @short_api_key = Rails.application.credentials[:SHORT_IO_API_KEY]\n    @short_url = nil\n  end\n\n  # return response containing body, headers ...ect in a hash\n  # currently, only need short url from body\n  # refer to docs: https://developers.short.io/docs/cre\n  def create_short_url(original_url = nil)\n    params = {body: {originalURL: original_url, domain: @short_domain}.to_json, headers: {\"Authorization\" => @short_api_key}}\n    response = self.class.post(\"/links\", params)\n    @short_url = JSON.parse(response.body)[\"shortURL\"]\n    response\n  end\n\n  private\n\n  def validate_credentials\n    variables = [Rails.application.credentials[:SHORT_IO_DOMAIN], Rails.application.credentials[:SHORT_IO_API_KEY]]\n    variables.each do |var|\n      if var.blank?\n        Rails.logger.error \"#{var} environment variable missing for Short IO serivce\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "app/services/sms_reminder_service.rb",
    "content": "module SmsReminderService\n  extend self\n\n  BASE_URL = Rails.application.credentials[:BASE_URL]\n  def send_reminder(user, message)\n    return if !user[:receive_sms_notifications] || user[:phone_number].blank? || !user.casa_org.twilio_enabled?\n\n    user_casa_org = user.casa_org\n    twilio_service = TwilioService.new(user_casa_org)\n    sms_params = {\n      From: user_casa_org.twilio_phone_number,\n      Body: message,\n      To: user.phone_number\n    }\n    twilio_service.send_sms(sms_params)\n  end\n\n  def create_short_link(path)\n    if BASE_URL.blank?\n      raise \"BASE_URL environment variable not defined\"\n    end\n\n    short_url_service = ShortUrlService.new\n    short_url_service.create_short_url(BASE_URL + path)\n    short_url_service.short_url\n  end\nend\n"
  },
  {
    "path": "app/services/svg_sanitizer_service.rb",
    "content": "class SvgSanitizerService\n  class << self\n    def sanitize(file)\n      return file unless file&.content_type == \"image/svg+xml\"\n\n      content = file.read\n      content.force_encoding(\"UTF-8\")\n      document = Loofah.xml_document(content)\n      safe_content = document.scrub!(scrubber).to_s\n      File.write(file.path, safe_content)\n      file.rewind\n      file\n    end\n\n    private\n\n    def scrubber\n      Loofah::Scrubber.new do |node|\n        node.remove if node.name == \"script\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "app/services/twilio_service.rb",
    "content": "require \"json\"\nrequire \"twilio-ruby\"\n\nclass TwilioService\n  attr_writer :api_key, :api_secret, :acc_sid, :casa_org\n\n  def initialize(casa_org)\n    @api_key = casa_org.twilio_api_key_sid\n    @api_secret = casa_org.twilio_api_key_secret\n    @acc_sid = casa_org.twilio_account_sid\n    @enabled = casa_org.twilio_enabled\n  end\n\n  def client # lazy create client only if twilio enabled\n    @client = Twilio::REST::Client.new(@api_key, @api_secret, @acc_sid)\n  end\n\n  def enabled?\n    @enabled\n  end\n\n  # this method takes in a hash\n  # required keys are: From:, To:, Body:\n  # to send a short url, set URL: key in hash\n  def send_sms(params)\n    if !enabled?\n      return nil\n    end\n\n    from = params[:From]\n    body = params.key?(:URL) ? params[:Body] + params[:URL] : params[:Body]\n    to = params[:To]\n    # returns a twilio API message object\n    # refer to docs: https://www.twilio.com/docs/sms/api/message-resource#message-properties\n    begin\n      client\n      client.messages.create(\n        from: from,\n        body: body,\n        to: to\n      )\n    rescue => error\n      Rails.logger.error(\"send SMS failed: #{error}\")\n      error\n    end\n  end\nend\n"
  },
  {
    "path": "app/services/volunteer_birthday_reminder_service.rb",
    "content": "class VolunteerBirthdayReminderService\n  def send_reminders\n    if Time.now.utc.to_date.day == 15\n      Volunteer.active.with_supervisor.birthday_next_month.each do |volunteer|\n        VolunteerBirthdayNotifier\n          .with(volunteer:)\n          .deliver(volunteer.supervisor)\n      end\n    else\n      Rails.logger.info { \"Volunteer Birthday Reminder Rake task skipped. Today is not the 15th of the month.\" }\n    end\n  end\nend\n"
  },
  {
    "path": "app/services/volunteers_emails_export_csv_service.rb",
    "content": "require \"csv\"\n\nclass VolunteersEmailsExportCsvService\n  attr_reader :volunteers\n\n  def initialize(casa_org)\n    @volunteers = Volunteer.active.in_organization(casa_org)\n  end\n\n  def call\n    CSV.generate(headers: true) do |csv|\n      csv << full_data.keys.map(&:to_s).map(&:titleize)\n      @volunteers.each do |volunteer|\n        csv << full_data(volunteer).values\n      end\n    end\n  end\n\n  private\n\n  def full_data(volunteer = nil)\n    active_casa_cases = volunteer&.casa_cases&.active&.map { |c| [c.case_number, c.in_transition_age?] }.to_h\n    old_emails = volunteer&.old_emails? ? volunteer.old_emails.join(\", \") : \"No Old Emails\"\n    {\n      email: volunteer&.email,\n      old_emails: old_emails,\n      case_number: active_casa_cases.keys.join(\", \"),\n      volunteer_name: volunteer&.display_name,\n      case_transition_aged_status: active_casa_cases.values.join(\", \")\n    }\n  end\nend\n"
  },
  {
    "path": "app/validators/casa_org_validator.rb",
    "content": "class CasaOrgValidator < ActiveModel::Validator\n  include PhoneNumberHelper\n\n  def validate(record)\n    valid_twilio_phone_number(record.twilio_phone_number, record)\n  end\n\n  private\n\n  def valid_twilio_phone_number(number, record)\n    valid, error = valid_phone_number(number)\n\n    if !valid\n      record.errors.add(:number, error)\n    end\n  end\nend\n"
  },
  {
    "path": "app/validators/court_report_validator.rb",
    "content": "# Validation class for :court_report_status & :court_report_submitted_at. Adds specific errors based on record data.\nclass CourtReportValidator < ActiveModel::Validator\n  def validate(record)\n    # Check for no timestamp and a submission value other than 'not_submitted'.\n    if record.court_report_submitted_at.nil? && !record.court_report_not_submitted?\n      record.errors.add(:court_report_status, \"Court report submission date can't be nil if status is anything but not_submitted.\")\n    # Check for timestamp with a 'not_submitted' status.\n    elsif record.court_report_submitted_at && record.court_report_not_submitted?\n      record.errors.add(:court_report_submitted_at, \"Submission date must be nil if court report status is not submitted.\")\n    end\n  end\nend\n"
  },
  {
    "path": "app/validators/url_validator.rb",
    "content": "class UrlValidator < ActiveModel::EachValidator\n  InvalidSchemeError = Class.new StandardError\n  MissingHostError = Class.new StandardError\n\n  DEFAULT_SCHEMES = %w[http https].freeze\n\n  def validate_each(record, attribute, value)\n    uri = URI.parse(value)\n    validate_scheme uri\n    validate_host uri\n  rescue URI::InvalidURIError\n    record.errors.add(attribute, \"format is invalid\")\n  rescue InvalidSchemeError, MissingHostError => e\n    record.errors.add(attribute, e.message)\n  end\n\n  private\n\n  def validate_scheme(uri)\n    accepted_schemes = Array.wrap(options[:scheme] || DEFAULT_SCHEMES)\n    return if uri.scheme.in? accepted_schemes\n\n    raise InvalidSchemeError, \"scheme invalid - only #{accepted_schemes.join(\", \")} allowed\"\n  end\n\n  def validate_host(uri)\n    raise MissingHostError, \"host cannot be blank\" if uri.host.blank?\n  end\nend\n"
  },
  {
    "path": "app/validators/user_validator.rb",
    "content": "class UserValidator < ActiveModel::Validator\n  include PhoneNumberHelper\n\n  def validate(record)\n    valid_phone_number_contents(record.phone_number, record)\n    validate_presence(:display_name, record)\n    at_least_one_communication_preference_selected(record)\n    valid_phone_number_if_receive_sms_notifications(record)\n    validate_date_of_birth_in_past(record.date_of_birth, record)\n    validate_date_of_birth_not_before_1920(record.date_of_birth, record)\n    validate_uniqueness(:email, record, I18n.t(\"activerecord.errors.messages.email_uniqueness\"))\n  end\n\n  private\n\n  def valid_phone_number_contents(number, record)\n    valid, error = valid_phone_number(number)\n\n    if !valid\n      record.errors.add(:phone_number, error)\n    end\n  end\n\n  def validate_presence(attribute, record)\n    if record[attribute].blank?\n      record.errors.add(attribute, \" can't be blank\")\n      return false\n    end\n\n    true\n  end\n\n  def at_least_one_communication_preference_selected(record)\n    record.errors.add(:base, \" At least one communication preference must be selected.\") unless record.receive_email_notifications || record.receive_sms_notifications\n  end\n\n  def valid_phone_number_if_receive_sms_notifications(record)\n    if record.receive_sms_notifications && record.phone_number.blank?\n      record.errors.add(:base, \" Must add a valid phone number to receive SMS notifications.\")\n    end\n  end\n\n  def validate_date_of_birth_in_past(date_of_birth, record)\n    return unless date_of_birth.present?\n\n    record.errors.add(:base, \" Date of birth must be in the past.\") unless date_of_birth.past?\n  end\n\n  def validate_date_of_birth_not_before_1920(date_of_birth, record)\n    return unless date_of_birth.present?\n\n    record.errors.add(:base, \" Date of birth must be on or after 1/1/1920.\") unless date_of_birth >= \"1920-01-01\".to_date\n  end\n\n  def validate_uniqueness(attribute, record, message)\n    existing_record = record.class.find_by(attribute => record[attribute])\n\n    if existing_record && existing_record.id != record.id\n      record.errors.add(:base, message)\n      return false\n    end\n\n    true\n  end\nend\n"
  },
  {
    "path": "app/values/all_casa_admin_parameters.rb",
    "content": "class AllCasaAdminParameters < SimpleDelegator\n  def initialize(params)\n    params =\n      params.require(:all_casa_admin).permit(:email, :password)\n\n    super\n  end\n\n  def with_password(password)\n    params[:password] = password\n    self\n  end\n\n  private\n\n  def params\n    __getobj__\n  end\nend\n"
  },
  {
    "path": "app/values/banner_parameters.rb",
    "content": "# Calculate values when using banner parameters\nclass BannerParameters < SimpleDelegator\n  def initialize(params, user, timezone)\n    new_params = params.require(:banner).permit(:active, :content, :name, :expires_at).merge(user: user)\n\n    if params.dig(:banner, :expires_at)\n      new_params[:expires_at] = convert_expires_at_with_user_time_zone(params, timezone)\n    end\n\n    super(new_params)\n  end\n\n  private\n\n  # `expires_at` comes from the frontend without any timezone information, so we use `in_time_zone` to attach\n  # timezone information to it before saving to the database. If we don't do this, the time will be stored at UTC\n  # by default.\n  def convert_expires_at_with_user_time_zone(params, timezone)\n    params[:banner][:expires_at].in_time_zone(timezone)\n  end\n\n  def params\n    __getobj__\n  end\nend\n"
  },
  {
    "path": "app/values/casa_admin_parameters.rb",
    "content": "class CasaAdminParameters < UserParameters\n  def initialize(params)\n    super(params, :casa_admin)\n  end\nend\n"
  },
  {
    "path": "app/values/case_contact_parameters.rb",
    "content": "# Calculate values when using case contact parameters\nclass CaseContactParameters < SimpleDelegator\n  def initialize(params)\n    params = normalize_params(params)\n    new_params =\n      params.require(:case_contact).permit(\n        :duration_minutes,\n        :occurred_at,\n        :contact_made,\n        :medium_type,\n        :miles_driven,\n        :want_driving_reimbursement,\n        :notes,\n        :status,\n        :volunteer_address,\n        contact_type_ids: [],\n        draft_case_ids: [],\n        metadata: %i[create_another],\n        additional_expenses_attributes: %i[id other_expense_amount other_expenses_describe _destroy],\n        contact_topic_answers_attributes: %i[id contact_topic_id value selected _destroy]\n      )\n\n    super(new_params)\n  end\n\n  private\n\n  def normalize_params(params)\n    if params.dig(:case_contact, :metadata)\n      params[:case_contact][:metadata] = convert_metadata(params[:case_contact][:metadata])\n    end\n    if params.dig(:case_contact, :duration_minutes)\n      params[:case_contact][:duration_minutes] = convert_duration_minutes(params)\n    end\n    if params.dig(:case_contact, :miles_driven)\n      params[:case_contact][:miles_driven] = convert_miles_driven(params)\n    end\n    params[:case_contact][:notes] = params.dig(:case_contact, :notes).presence\n    params[:case_contact][:contact_type_ids] = params[:case_contact][:contact_type_ids]&.uniq\n\n    params\n  end\n\n  def convert_metadata(metadata)\n    if metadata[\"create_another\"]\n      metadata[\"create_another\"] = ActiveRecord::Type::Boolean.new.cast metadata[\"create_another\"]\n    end\n    metadata\n  end\n\n  def convert_duration_minutes(params)\n    duration_hours = params[:case_contact][:duration_hours].to_i\n    converted_duration_hours = duration_hours * 60\n    duration_minutes = params[:case_contact][:duration_minutes].to_i\n    converted_duration_hours + duration_minutes\n  end\n\n  def convert_miles_driven(params)\n    miles_driven = params[:case_contact][:miles_driven]\n    miles_driven.to_i\n  end\n\n  private\n\n  def params\n    __getobj__\n  end\nend\n"
  },
  {
    "path": "app/values/supervisor_parameters.rb",
    "content": "class SupervisorParameters < UserParameters\n  def initialize(params)\n    super(params, :supervisor)\n  end\nend\n"
  },
  {
    "path": "app/values/user_parameters.rb",
    "content": "class UserParameters < SimpleDelegator\n  def initialize(params, key = :user)\n    params =\n      params.require(key).permit(\n        :email,\n        :casa_org_id,\n        :display_name,\n        :phone_number,\n        :date_of_birth,\n        :password,\n        :active,\n        :receive_reimbursement_email,\n        :type,\n        :monthly_learning_hours_report,\n        address_attributes: [:id, :content]\n      )\n\n    super(params)\n  end\n\n  def with_organization(organization)\n    params[:casa_org_id] = organization.id\n    self\n  end\n\n  def with_password(password)\n    params[:password] = password\n    self\n  end\n\n  def with_type(type)\n    params[:type] = type\n    self\n  end\n\n  def without_type\n    params.delete(:type)\n    self\n  end\n\n  def without_active\n    params.delete(:active)\n    self\n  end\n\n  def with_only(*)\n    params.slice!(*)\n    self\n  end\n\n  def without(*keys)\n    params.reject! { |key| keys.include?(key) }\n  end\n\n  private\n\n  def params\n    __getobj__\n  end\nend\n"
  },
  {
    "path": "app/values/volunteer_parameters.rb",
    "content": "class VolunteerParameters < UserParameters\n  include PhoneNumberHelper\n\n  def initialize(params)\n    params[:volunteer][:phone_number] = params[:volunteer][:phone_number].present? ? strip_unnecessary_characters(params[:volunteer][:phone_number]) : \"\"\n\n    super(params, :volunteer)\n  end\nend\n"
  },
  {
    "path": "app/views/active_storage/blobs/_blob.html.erb",
    "content": "<figure class=\"attachment attachment--<%= blob.representable? ? \"preview\" : \"file\" %> attachment--<%= blob.filename.extension %>\">\n  <% if blob.representable? %>\n    <%= image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %>\n  <% end %>\n\n  <figcaption class=\"attachment__caption\">\n    <% if caption = blob.try(:caption) %>\n      <%= caption %>\n    <% else %>\n      <span class=\"attachment__name\"><%= blob.filename %></span>\n      <span class=\"attachment__size\"><%= number_to_human_size blob.byte_size %></span>\n    <% end %>\n  </figcaption>\n</figure>\n"
  },
  {
    "path": "app/views/all_casa_admins/casa_admins/_form.html.erb",
    "content": "<%= form_with(model: casa_admin, url: all_casa_admins_casa_org_casa_admins_path(casa_org), id: :new_casa_admin) do |form| %>\n  <%= render \"/shared/error_messages\", resource: casa_admin %>\n  <div class=\"input-style-1\">\n    <%= form.label :email, \"Email\" %>\n    <%= form.text_field :email, class: \"form-control\" %>\n  </div>\n  <div class=\"input-style-1\">\n    <%= form.label :display_name, \"Display name\" %>\n    <%= form.text_field :display_name, class: \"form-control\" %>\n  </div>\n  <div class=\"actions\">\n    <%= button_tag( type: \"submit\" , class: \"btn-sm main-btn primary-btn btn-hover\" ) do %>\n      <i class=\"lni lni-checkmark-circle mr-5\"></i> Submit\n    <% end %>\n  </div>\n<% end %>\n"
  },
  {
    "path": "app/views/all_casa_admins/casa_admins/edit.html.erb",
    "content": "<div class=\"col-md-6\">\n  <div class=\"title mb-30 mt-30\">\n    <h1>Edit Admin</h1>\n  </div>\n</div>\n<%= render 'layouts/flash_messages' %>\n<div class=\"card-style\">\n  <div class=\"card-body\">\n    <%= form_with(model: @casa_admin, as: :all_casa_admin, url: all_casa_admins_casa_org_casa_admin_path) do |form| %>\n      <%= render \"/shared/error_messages\", resource: @casa_admin %>\n\n      <div class=\"input-style-1\">\n        <%= form.label :email, \"Email\" %>\n        <%= form.email_field :email %>\n      </div>\n      <% if @casa_admin.persisted? %>\n        <div class=\"field form-group\">\n          <% if @casa_admin.active? %>\n            Admin is <%= render BadgeComponent.new(text: \"Active\", type: :success, rounded: true) %>\n              <%= link_to \"Deactivate\",\n                          deactivate_all_casa_admins_casa_org_casa_admin_path,\n                          method: :patch,\n                          class: \"main-btn danger-btn-outline btn-hover\",\n                          data: { confirm: \"WARNING: Marking an admin inactive will make them unable to login. \" \\\n                                           \"Are you sure you want to do this?\"} %>\n            <div class=\"actions\">\n              <%= form.submit \"Update Profile\", class: \"main-btn primary-btn btn-hover mt-3\" %>\n            </div>\n          <% else %>\n            <div class=\"alert alert-danger\">\n              Admin was deactivated on: <%= I18n.l(@casa_admin.updated_at, format: :standard, default: nil) %>\n            </div>\n            <% if @casa_admin.active == false %>\n\n              <div class=\"actions\">\n                <%= link_to \"Activate\",\n                            activate_all_casa_admins_casa_org_casa_admin_path,\n                            method: :patch,\n                            class: \"btn btn-outline-success\" %>\n                <%= form.submit \"Update Profile\", class: \"btn btn-primary\" %>\n              </div>\n            <% end %>\n          <% end %>\n        </div>\n      <% end %>\n    <% end %>\n    <br>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/all_casa_admins/casa_admins/new.html.erb",
    "content": "<div class=\"col-md-6\">\n  <div class=\"title mb-30 mt-30\">\n    <h1>New CASA Admin for <%= selected_organization.name %></h1>\n  </div>\n</div>\n\n<div class=\"card card-container\">\n  <div class=\"card-body\">\n    <%= render \"form\", casa_org: selected_organization, casa_admin: @casa_admin %>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/all_casa_admins/casa_orgs/new.html.erb",
    "content": "<div class=\"col-md-6\">\n  <div class=\"title mb-30 mt-30\">\n    <h1>Create a new CASA Organization</h1>\n  </div>\n</div>\n\n<div class=\"card card-container\">\n  <div class=\"card-body\">\n    <%= form_with model: selected_organization, url: all_casa_admins_casa_orgs_path, method: :post, local: true do |form| %>\n      <%= render \"/shared/error_messages\", resource: selected_organization %>\n\n      <div class=\"input-style-1\">\n        <%= form.label :name, \"Name\" %>\n        <%= form.text_field :name, class: \"form-control\", required: true %>\n      </div>\n\n      <div class=\"input-style-1\">\n        <%= form.label :display_name, \"Display name\" %>\n        <%= form.text_field :display_name, class: \"form-control\" %>\n      </div>\n\n      <div class=\"input-style-1\">\n        <%= form.label :address, \"Address\" %>\n        <%= form.text_field :address, class: \"form-control\" %>\n      </div>\n\n      <div class=\"actions\">\n        <%= button_tag( type: \"submit\" , class: \"btn-sm main-btn primary-btn btn-hover\", id:\"submit\" ) do %>\n          <i class=\"lni lni-checkmark-circle mr-5\"></i> Create CASA Organization\n        <% end %>\n      </div>\n    <% end %>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/all_casa_admins/casa_orgs/show.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h1><%= selected_organization.name %></h1>\n      </div>\n    </div>\n  </div>\n</div>\n<%= render \"layouts/flash_messages\" %>\n<div class=\"card card-container\">\n  <div class=\"card-body\">\n    <div class=\"dashboard-table-header\">\n      <div class=\"row align-items-center\">\n        <div class=\"col-md-6\">\n          <div class=\"title mb-30\">\n            <h2>Administrators</h2>\n          </div>\n        </div>\n        <div class=\"col-md-6\">\n          <div class=\"breadcrumb-wrapper mb-30\">\n            <span class=\"ml-5\">\n              <%= link_to new_all_casa_admins_casa_org_casa_admin_path(selected_organization),\n                class: \"btn-sm main-btn primary-btn btn-hover\" do %>\n                <i class=\"lni lni-plus mr-10\"></i>New CASA Admin\n              <% end %>\n              <%=\n                link_to \"Return to Organizations\",\n                  authenticated_all_casa_admin_root_path,\n                  class: \"btn-sm main-btn primary-btn btn-hover\"\n              %>\n            </span>\n          </div>\n        </div>\n      </div>\n    </div>\n    <table class=\"table admin-list\">\n      <thead>\n      <tr>\n        <th>Name</th>\n        <th>Email</th>\n        <th>Created At</th>\n        <th>Actions</th>\n      </tr>\n      </thead>\n      <tbody>\n      <% selected_organization.casa_admins.each do |admin| %>\n        <tr class=\"admin-<%= admin.id %>\">\n          <td><%= admin.display_name %></td>\n          <td><%= admin.email %></td>\n          <td><%= I18n.l(admin.created_at, format: :full, default: nil) %></td>\n          <td><%= link_to \"Edit\", edit_all_casa_admins_casa_org_casa_admin_path(selected_organization, admin) %></td>\n        </tr>\n      <% end %>\n      </tbody>\n    </table>\n  </div>\n</div>\n\n<div class=\"card card-container\">\n  <div class=\"card-body\">\n    <div class=\"card-title\">\n      <h3>Details</h3>\n    </div>\n    <ul class=\"list-group list-group-flush\">\n      <% @casa_org_metrics.each do |item_description, item_value| %>\n        <li class=\"list-group-item\">\n          <strong><%= item_description %>:</strong> <%= item_value %>\n        </li>\n      <% end %>\n    </ul>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/all_casa_admins/dashboard/show.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h1>All CASA Admin</h1>\n      </div>\n    </div>\n    <%= render \"layouts/flash_messages\" %>\n    <div class=\"col-md-6\">\n      <div class=\"breadcrumb-wrapper mb-30\">\n        <span class=\"ml-5\">\n          <%= link_to new_all_casa_admins_casa_org_path,\n              class: \"btn-sm main-btn primary-btn btn-hover\" do %>\n            <i class=\"lni lni-plus mr-10\"></i>New CASA Organization\n          <% end %>\n          <%= link_to new_all_casa_admin_path,\n              class: \"btn-sm main-btn primary-btn btn-hover\" do %>\n            <i class=\"lni lni-plus mr-10\"></i>New All CASA Admin\n          <% end %>\n        </span>\n      </div>\n    </div>\n  </div>\n</div>\n<div class=\"card card-container\">\n  <div class=\"card-body\">\n    <h2 class=\"mb-30\">CASA Organizations</h2>\n    <table class=\"table organization-list\" data-test=\"organization-table\">\n      <thead>\n        <tr>\n          <th>Name</th>\n          <th>Created At</th>\n          <th>User Count</th>\n          <th>Case Contact Count</th>\n        </tr>\n      </thead>\n      <tbody>\n      <% @organizations.each do |organization| %>\n        <tr>\n          <td><%= link_to organization.name, all_casa_admins_casa_org_path(organization) %></td>\n          <td><%= I18n.l(organization.created_at, format: :full, default: nil) %></td>\n          <td><%= organization.user_count %></td>\n          <td><%= organization.case_contacts_count %></td>\n        </tr>\n      <% end %>\n      </tbody>\n    </table>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/all_casa_admins/edit.html.erb",
    "content": "<div class=\"col-md-6\">\n  <div class=\"title mb-30 mt-30\">\n    <h1>Edit Profile</h1>\n  </div>\n</div>\n\n<div class=\"card-style\">\n  <div class=\"card-body\">\n    <%= form_with(model: @user, scope: :all_casa_admin, url: all_casa_admins_path, method: :patch) do |form| %>\n\n        <%= render \"/shared/error_messages\", resource: @user %>\n\n        <%= render '/layouts/flash_messages' %>\n\n      <div class=\"input-style-1\">\n        <%= form.label :email, \"Email\" %>\n        <%= form.email_field :email, class: \"form-control\" %>\n      </div>\n\n      <div class=\"actions\">\n        <%= form.button \"Update Profile\", type: \"submit\", class: \"main-btn primary-btn btn-hover\" %>\n        <button class=\"main-btn primary-btn btn-hover\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#collapseOne\" aria-expanded=\"false\" aria-controls=\"collapseOne\">\n          Change Password\n        </button>\n      </div>\n    <% end %>\n    <br>\n    <div id=\"collapseOne\" class=\"collapse\" aria-labelledby=\"headingOne\">\n      <%= form_with(model: @user, scope: :all_casa_admin, url: {action: \"update_password\"}, method: :patch) do |f| %>\n        <div class=\"input-style-1\">\n          <%= f.label :password, \"Password\" %><br>\n          <%= f.password_field :password, autocomplete: \"off\", class: \"form-control\", minlength: 6 %>\n        </div>\n        <div class=\"input-style-1\">\n          <%= f.label :password_confirmation, \"Password Confirmation\" %><br>\n          <%= f.password_field :password_confirmation, class: \"form-control\", minlength: 6 %>\n        </div>\n        <div class=\"action_container\">\n          <%= f.submit \"Update Password\", class: \"main-btn danger-btn btn-hover\" %>\n        </div>\n      <% end %>\n    </div>\n    <br>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/all_casa_admins/new.html.erb",
    "content": "<div class=\"col-md-6\">\n  <div class=\"title mb-30 mt-30\">\n    <h1>New All CASA Admin</h1>\n  </div>\n</div>\n\n<div class=\"card card-container\">\n  <div class=\"card-body\">\n    <%= form_with(model: @all_casa_admin, as: :all_casa_admin, url: all_casa_admins_path) do |form| %>\n      <%= render \"/shared/error_messages\", resource: @all_casa_admin %>\n\n      <div class=\"input-style-1\">\n        <%= form.label :email, \"Email\" %>\n        <%= form.email_field :email, class: \"form-control\" %>\n      </div>\n      <%= button_tag( type: \"submit\" , class: \"btn-sm main-btn primary-btn btn-hover\" ) do %>\n        <i class=\"lni lni-checkmark-circle mr-5\"></i> Submit\n      <% end %>\n    <% end %>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/all_casa_admins/passwords/new.html.erb",
    "content": "<div class=\"row\">\n  <div class=\"col-lg-5 col-md-12 vertically-center\">\n    <div class=\"container\">\n      <div class=\"row\">\n        <div class=\"offset-xl-2 col-xl-8 col-lg-12 col-sm-8 col-md-8\">\n          <br>\n          <h1>Forgot your password?</h1>\n          <br>\n\n          <%= form_with(model: resource, as: resource_name, url: password_path(resource_name), method: :post) do |f| %>\n            <%= render \"/shared/error_messages\", resource: resource %>\n\n            <h4>Please enter email or phone number to receive reset instructions.</h4>\n\n            <div class=\"field form-group\">\n              <%= f.label :email %><br>\n              <%= f.email_field :email, autofocus: true, autocomplete: \"email\", class: \"form-control\" %>\n            </div>\n\n            <div class=\"actions\">\n              <%= f.submit \"Send me reset password instructions\", class: \"btn btn-primary\" %>\n            </div>\n          <% end %>\n\n          <br>\n          <%= render \"devise/shared/links\" %>\n          <br>\n        </div>\n      </div>\n    </div>\n  </div>\n\n  <div class=\"col-lg-7 d-none d-lg-block\">\n    <aside class=\"display-image\"></aside>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/all_casa_admins/patch_notes/_patch_note.html.erb",
    "content": "<div class=\"patch-note-list-item card\">\n  <div id=\"patch-note-<%= patch_note.id %>\" class=\"card-body\">\n    <textarea disabled=\"true\"><%= patch_note.note %></textarea>\n    <div class=\"select-style-1 label-and-select\">\n      <label for=\"patch-note-<%= patch_note.id %>-type\">Patch Note Type:</label>\n      <select id=\"patch-note-<%= patch_note.id %>-type\" disabled=\"true\">\n        <% @patch_note_types&.each do |patch_note_type| %>\n          <option value=\"<%= patch_note_type.id %>\" <% if patch_note_type.id === patch_note.patch_note_type_id %> selected=\"selected\" <% end %>>\n            <%= patch_note_type.name %>\n          </option>\n        <% end %>\n      </select>\n    </div>\n    <div class=\"select-style-1 label-and-select\">\n      <label for=\"patch-note-<%= patch_note.id %>-group\">User Group:</label>\n      <select id=\"patch-note-<%= patch_note.id %>-group\" disabled=\"true\">\n        <% @patch_note_groups&.each do |patch_note_group| %>\n          <% if patch_note_group.id === patch_note.patch_note_group_id %>\n            <option value=\"<%= patch_note_group.id %>\" selected=\"selected\">\n          <% else %>\n            <option value=\"<%= patch_note_group.id %>\">\n          <% end %>\n              <%= patch_note_group.value %>\n            </option>\n        <% end %>\n      </select>\n    </div>\n    <div class=\"patch-note-button-controls\">\n      <button type=\"button\" class=\"main-btn primary-btn btn-hover button-edit\"><i class=\"lni lni-pencil-alt mr-10\"></i>Edit</button>\n      <button type=\"button\" class=\"main-btn danger-btn btn-hover button-delete\"><i class=\"lni lni-trash-can mr-10\"></i>Delete</button>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/all_casa_admins/patch_notes/_patch_note.json.jbuilder",
    "content": "json.extract! patch_note, :id, :created_at, :updated_at\njson.url patch_note_url(patch_note, format: :json)\n"
  },
  {
    "path": "app/views/all_casa_admins/patch_notes/index.html.erb",
    "content": "<p style=\"color: green\"><%= notice %></p>\n\n<div id=\"patch-notes\">\n  <div id=\"patch-note-list\">\n    <div class=\"col-md-6\">\n      <div class=\"title mt-30 mb-30\">\n        <h1>Patch Notes</h1>\n      </div>\n    </div>\n    <div class=\"patch-note-list-item card\">\n      <div id=\"new-patch-note\" class=\"card-body\">\n        <textarea placeholder=\"New Patch Note\"></textarea>\n        <div class=\"select-style-1 label-and-select\">\n          <label for=\"new-patch-note-type\">Patch Note Type:</label>\n          <select id=\"new-patch-note-type\">\n            <% @patch_note_types&.each do |patch_note_type| %>\n              <option value=\"<%= patch_note_type.id %>\"><%= patch_note_type.name %></option>\n            <% end %>\n          </select>\n        </div>\n        <div class=\"select-style-1 label-and-select\">\n          <label for=\"new-patch-note-group\">User Group:</label>\n          <select id=\"new-patch-note-group\">\n            <% @patch_note_groups&.each do |patch_note_group| %>\n              <option value=\"<%= patch_note_group.id %>\"><%= patch_note_group.value %></option>\n            <% end %>\n          </select>\n        </div>\n        <div class=\"patch-note-button-controls\">\n          <button type=\"button\" class=\"main-btn primary-btn btn-hover\">\n            <i class=\"lni lni-checkmark-circle mr-5\"></i>Create</button>\n        </div>\n      </div>\n    </div>\n    <%# Paginate this %>\n    <% @patch_notes&.each do |patch_note| %>\n      <%= render \"all_casa_admins/patch_notes/patch_note\", patch_note: patch_note %>\n    <% end %>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/all_casa_admins/patch_notes/index.json.jbuilder",
    "content": "json.array! @patch_notes, partial: \"patch_notes/patch_note\", as: :patch_note\n"
  },
  {
    "path": "app/views/all_casa_admins/sessions/new.html.erb",
    "content": "<div class=\"signin-wrapper\">\n  <div class=\"form-wrapper\">\n    <div class=\"col-md-12 vertically-center\">\n      <div class=\"container\">\n        <div class=\"row\">\n          <div class=\"col-md-6\">\n            <h2 class=\"mb-15\">All CASA Log In</h2>\n            <br>\n            <%= render \"layouts/flash_messages\" %>\n            <%= form_with(model: resource, as: resource_name, url: session_path(resource_name)) do |f| %>\n              <div class=\"input-style-1\">\n                <%= f.label :email %>\n                <%= f.email_field :email, autofocus: true, autocomplete: \"email\", class: \"form-control\", required: true %>\n              </div>\n\n              <div class=\"input-style-1\">\n                <%= f.label :password %>\n                <%= f.password_field :password, autocomplete: \"current-password\", class: \"form-control\", required: true %>\n              </div>\n\n              <div class=\"input-style-1\">\n                <%= link_to \"Forgot your password?\", new_password_path(resource_name), class: \"hover-underline\" %>\n              </div>\n\n              <div class=\"actions mb-30\">\n                <%= f.submit \"Log in\", class: \"main-btn primary-btn btn-hover w-100 text-center\" %>\n              </div>\n            <% end %>\n\n            <p class=\"text-sm mb-25\">\n              Not able to sign in? You might be looking for the <a href=\"/\">CASA user sign in page</a>.\n            </p>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/all_casa_admins/shared/_links.html.erb",
    "content": "<%- if controller_name != 'sessions' %>\n  <%= link_to \"Log in\", new_session_path(resource_name) %><br>\n<% end %>\n\n<%- if devise_mapping.registerable? && controller_name != 'registrations' %>\n  <%= link_to \"Sign up\", new_registration_path(resource_name) %><br>\n<% end %>\n\n<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>\n  <%= link_to \"Forgot your password?\", new_password_path(resource_name), class: 'btn btn-outline-primary' %><br>\n<% end %>\n\n<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>\n  <%= link_to \"Didn't receive confirmation instructions?\", new_confirmation_path(resource_name) %><br>\n<% end %>\n\n<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>\n  <%= link_to \"Didn't receive unlock instructions?\", new_unlock_path(resource_name) %><br>\n<% end %>\n\n<%- if devise_mapping.omniauthable? %>\n  <%- resource_class.omniauth_providers.each do |provider| %>\n    <%= link_to \"Sign in with #{OmniAuth::Utils.camelize(provider)}\", omniauth_authorize_path(resource_name, provider) %>\n    <br>\n  <% end %>\n<% end %>\n"
  },
  {
    "path": "app/views/banners/_form.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h1>\n          <% if banner.persisted? %>\n            Edit Banner\n          <% else %>\n            New Banner\n          <% end %>\n        </h1>\n      </div>\n    </div>\n  </div>\n</div><!-- ==== end title ==== -->\n<div class=\"card-style mb-30\">\n  <%= form_with model: banner do |form| %>\n    <%= render \"shared/error_messages\", resource: banner %>\n\n    <div class=\"input-style-1\">\n      <%= form.label :name %>\n      <%= form.text_field :name, required: true %>\n    </div>\n    <div data-controller=\"reveal\" data-reveal-hidden-class=\"d-none\" class=\"form-check checkbox-style mb-20\">\n      <%= form.check_box :active, data: { action: current_organization.has_alternate_active_banner?(@banner.id) && 'click->reveal#toggle' }, class: 'form-check-input' %>\n      <%= form.label :active, \"Active?\", class: 'form-check-label' %>\n      <span data-reveal-target=\"item\" class=\"text-danger <%= conditionally_add_hidden_class(form.object.active) %>\">Warning: This will replace your current active banner</span>\n    </div>\n    <span class=\"input-style-1\">\n      <%= form.label :expires_at, \"Expires at (optional)\" %>\n      <%= form.datetime_field :expires_at,\n                              value: banner.expires_at_in_time_zone(browser_time_zone),\n                              required: false,\n                              include_seconds: false,\n                              min: Time.current.in_time_zone(browser_time_zone) %>\n    </span>\n    <div class=\"input-style-1\">\n      <%= form.label :content %>\n      <%= form.rich_text_area :content %>\n    </div>\n    <div class=\"actions mb-10\">\n      <%= button_tag(type: \"submit\", class: \"btn-sm main-btn primary-btn btn-hover\") do %>\n        <i class=\"lni lni-checkmark-circle mr-10\"></i> Submit\n      <% end %>\n    </div>\n  <% end %>\n</div>\n"
  },
  {
    "path": "app/views/banners/edit.html.erb",
    "content": "<%= render 'banners/form', banner: @banner, org_has_alternate_active_banner: @org_has_alternate_active_banner %>\n"
  },
  {
    "path": "app/views/banners/index.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h1>Banners</h1>\n      </div>\n    </div>\n  </div>\n</div>\n<div class=\"tables-wrapper\">\n  <div class=\"row\">\n    <div class=\"col-lg-12\">\n      <div class=\"card-style mb-30\">\n        <h3 class=\"mb-10\">Banners</h3>\n        <p class=\"text-sm mb-20\">\n          Banners display at the top of the page and can be used to callout a message to volunteers. One example is displaying a banner with a link to a survey that volunteers can fill out. You can create multiple banners but only one can be active at a time.\n        </p>\n        <%= link_to new_banner_path, class: \"btn-sm main-btn primary-btn btn-hover\" do %>\n          <i class=\"lni lni-plus mr-10\"></i>\n          New Banner\n        <% end %>\n        <div class=\"table-wrapper table-responsive\">\n          <table class=\"table striped-table\" id=\"banners\">\n            <thead>\n            <tr>\n              <th>Name</th>\n              <th>Active?</th>\n              <th>Expiration</th>\n              <th>Last Updated By</th>\n              <th>Updated At</th>\n              <th>Actions</th>\n            </tr>\n            </thead>\n            <tbody>\n            <% @banners.each do |banner| %>\n              <tr>\n                <td class=\"min-width\">\n                  <%= banner.name %>\n                </td>\n                <td class=\"min-width\">\n                  <% if banner.active? %>\n                    Yes\n                  <% else %>\n                    No\n                  <% end %>\n                </td>\n                <td class=\"min-width\">\n                  <%= banner_expiration_time_in_words(banner) %>\n                </td>\n                <td class=\"min-width\">\n                  <%= banner.user.display_name %>\n                </td>\n                <td class=\"min-width\">\n                  <%= time_ago_in_words(banner.updated_at) %> ago\n                </td>\n                <td>\n                  <%= link_to edit_banner_path(banner) do %>\n                    <div class=\"action\">\n                      <button class=\"text-danger\">\n                        <i class=\"lni lni-pencil-alt\"></i> Edit\n                      </button>\n                    </div>\n                  <% end %>\n                  <%= link_to 'Delete', banner_path(banner), class: 'btn btn-danger', method: :delete, data: { confirm: \"Are you sure that you want to delete this banner?\" } %>\n                </td>\n              </tr>\n            <% end %>\n            </tbody>\n          </table>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/banners/new.html.erb",
    "content": "<%= render 'banners/form', banner: @banner, org_has_alternate_active_banner: @org_has_alternate_active_banner %>\n"
  },
  {
    "path": "app/views/bulk_court_dates/new.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h1>New Bulk Court Date</h1>\n      </div>\n    </div>\n  </div>\n</div>\n<div class=\"card-style mb-30\">\n  <%= form_with(model: @court_date, url: bulk_court_dates_path, method: :post, local: true, data: { controller: \"court-order-form\", nested_form_wrapper_selector_value: \".nested-form-wrapper\" }) do |form| %>\n    <%= render \"/shared/error_messages\", resource: @court_date %>\n    <div class=\"row align-items-center\">\n      <div class=\"col-md-6\">\n        <h6>Create a court date for all cases in a group</h6>\n      </div>\n      <div class=\"col-md-6\">\n        <div class=\"breadcrumb-wrapper\">\n          <span class=\"top-page-actions ml-5\">\n            <%= button_tag(\n                  type: \"submit\",\n                  class: \"btn-sm main-btn primary-btn btn-hover\"\n                ) do %>\n              <% if @court_date.persisted? %>\n                <i class=\"lni lni-pencil-alt\"></i>\n                Update\n              <% else %>\n                <i class=\"lni lni-plus\"></i>\n                Create\n              <% end %>\n            <% end %>\n          </span>\n        </div>\n      </div>\n    </div>\n    <div class=\"row align-items-center\">\n      <div class=\"select-style-1\">\n        <%= form.label :case_group_id, \"Case Group\" %>\n        <div class=\"select-position\">\n          <%= form.collection_select(\n                :case_group_id,\n                current_organization.case_groups,\n                :id, :name,\n                {include_hidden: false, include_blank: \"-Select Case Group-\"},\n                {class: \"form-control\"}\n              ) %>\n        </div>\n      </div>\n\n      <%= render 'court_dates/fields', court_date: @court_date, form: form, casa_case: nil %>\n    </div>\n  <% end %>\n</div>\n"
  },
  {
    "path": "app/views/casa_admin_mailer/account_setup.html.erb",
    "content": "<meta itemprop=\"name\" content=\"Confirm Email\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n<style>/* Email styles need to be inline */</style>\n<table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n  <tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n    <td class=\"content-block\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\" valign=\"top\">\n      A <%= @casa_organization.display_name %>’s County Admin account has been created for you. Your console account is\n      associated with this email. If this is not the correct email to use, please stop here and contact an administrator\n      at your county's CASA.\n    </td>\n  </tr>\n  <tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n    <td class=\"content-block\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\" valign=\"top\">\n      If you are ready to get started, please set your password. This is the first step to accessing your new admin\n      account.\n    </td>\n  </tr>\n  <tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n    <td class=\"content-block\" itemprop=\"handler\" itemscope itemtype=\"http://schema.org/HttpActionHandler\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\" valign=\"top\">\n      <a href=\"<%= \"#{edit_password_url(@user, reset_password_token: @token)}\" %>\" target=\"_blank\" class=\"btn-primary\" itemprop=\"url\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2em; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; text-transform: capitalize; background-color: #348eda; margin: 0; border-color: #348eda; border-style: solid; border-width: 10px 20px;\">Set\n        Your Password</a>\n    </td>\n  </tr>\n</table>\n"
  },
  {
    "path": "app/views/casa_admin_mailer/deactivation.html.erb",
    "content": "<meta itemprop=\"name\" content=\"Confirm Email\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n<style>/* Email styles need to be inline */</style>\n<table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n  <tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n    <td class=\"content-block\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\" valign=\"top\">\n      Hello <%= @user.display_name %>,\n    </td>\n  </tr>\n  <tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n    <td class=\"content-block\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\" valign=\"top\">\n      Your <%= @casa_organization.display_name %>’s Admin account has been deactivated.\n    </td>\n  </tr>\n  <tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n    <td class=\"content-block\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\" valign=\"top\">\n      If you have any questions, please contact a <%= @casa_organization.display_name %> administrator for assistance.\n    </td>\n  </tr>\n</table>\n"
  },
  {
    "path": "app/views/casa_admins/_form.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h1><%= title %></h1>\n      </div>\n    </div>\n  </div>\n</div>\n\n<div class=\"card-style-1 pl-25 mb-10\">\n  <%= form_with(model: casa_admin, local: true) do |form| %>\n    <div class=\"alert-box danger-alert\">\n      <%= render \"/shared/error_messages\", resource: casa_admin %>\n    </div>\n    <div class=\"min-wdth\">\n      <%= render \"/shared/edit_form\", resource: casa_admin, f: form %>\n    </div>\n    <div class=\"mb-30\">\n      <%= render \"/shared/invite_login\", resource: casa_admin %>\n    </div>\n\n    <div>\n      <% if current_user.casa_admin? %>\n        <div class=\"form-group\">\n          <%= form.label :monthly_learning_hours_report, \"Receive Monthly Learning Hours Report\" %>\n          <%= form.check_box :monthly_learning_hours_report %>\n        </div>\n      <% end %>\n    </div>\n\n    <br>\n\n    <% if casa_admin.persisted? %>\n      <div class=\"mb-20 col\">\n        <% if casa_admin.active? %>\n          <strong class=\"text-dark mr-5 h4\">Admin is</strong>\n          <span class=\"mr-10 mb-1 status-btn success-bg text-white text-uppercase display-1\">Active</span>\n          <%= link_to \"Deactivate\",\n                      deactivate_casa_admin_path(casa_admin),\n                      method: :patch,\n                      class: \"status-btn light-bg btn-outline-danger\",\n                      data: {\n                        confirm: \"WARNING: Marking an admin inactive will make them unable to login. Are you sure\" +\n                          \" you want to do this?\"\n                      } %>\n        <% else %>\n          <div class=\"alert-box danger-alert\">\n            Admin was deactivated on: <%= I18n.l(casa_admin.updated_at, format: :standard, default: nil) %>\n          </div>\n          <% if policy(casa_admin).activate? %>\n            <%= link_to \"Activate Admin\",\n                        activate_casa_admin_path(casa_admin),\n                        method: :patch,\n                        class: \"status-btn btn-outline-success\" %>\n          <% end %>\n        <% end %>\n\n        <% if current_user.casa_admin? && casa_admin.invitation_accepted_at.nil? %>\n          <%= link_to \"Resend Invitation\",\n                      resend_invitation_casa_admin_path(casa_admin),\n                      method: :patch,\n                      class: \"status-btn light-bg  btn-outline-danger\" %>\n        <% end %>\n\n        <% if current_user.casa_admin? %>\n          <%= link_to \"Change to Supervisor\",\n                      change_to_supervisor_casa_admin_path(casa_admin),\n                      method: :patch,\n                      class: \"status-btn light-bg btn-outline-danger\" %>\n        <% end %>\n      </div>\n    <% end %>\n\n    <div class=\"actions mb-10\">\n      <%= button_tag(type: \"submit\", class: \"btn-sm main-btn primary-btn btn-hover\") do %>\n        <i class=\"lni lni-checkmark-circle mr-5\"></i> Submit\n      <% end %>\n    </div>\n  <% end %>\n</div>\n"
  },
  {
    "path": "app/views/casa_admins/edit.html.erb",
    "content": "<%= render partial: \"form\", locals: {title: \"Edit Casa Admin\", casa_admin: @casa_admin} %>\n"
  },
  {
    "path": "app/views/casa_admins/index.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h1>Casa Admins</h1>\n      </div>\n    </div>\n    <!-- end col -->\n    <div class=\"col-md-6\">\n      <div class=\"breadcrumb-wrapper mb-30\">\n        <span class=\"ml-5\">\n          <%= link_to new_casa_admin_path, class: \"main-btn mt-2 mb-2 primary-btn btn-sm btn-hover\" do %>\n            <i class=\"lni lni-plus mr-10\"></i>\n            New Admin\n          <% end %>\n          </span>\n      </div>\n    </div>\n  </div>\n</div>\n\n<!-- end col -->\n<div class=\"tables-wrapper\">\n  <div class=\"row\">\n    <div class=\"col-lg-12\">\n      <div class=\"card-style mb-30\">\n        <table class=\"table \" id=\"admins\">\n          <thead>\n          <tr>\n            <th><h5 class=\"ml-10 \">Admin Email</h5></th>\n            <th><h5 class=\"ml-10 \">Actions</h5></th>\n          </tr>\n          </thead>\n\n          <tbody>\n          <% @admins.each do |admin| %>\n            <tr id=\"admin-<%= admin.id %>\">\n              <td scope=\"row\" class=\"min-wdth\">\n                <span class=\"ml-10 mb-10 mt-10  pb-1\"><%= admin.email %></span>\n                <% unless admin.active? %>\n                    <span class=\"status-btn close-btn\">\n                      Deactivated\n                    </span>\n                <% end %>\n              </td>\n              <td class=\"min-width\">\n                  <span class=\"ml-10 mb-10 mt-10 pb-1\">\n                  <%= link_to edit_casa_admin_path(admin), class: \"text-danger\" do %>\n                    <i class=\"lni lni-pencil-alt\"></i> Edit\n                  <% end %>\n                  </span>\n              </td>\n            </tr>\n          <% end %>\n          </tbody>\n        </table>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/casa_admins/new.html.erb",
    "content": "<%= render partial: \"form\", locals: {title: \"Create New Casa Admin\", casa_admin: @casa_admin} %>\n"
  },
  {
    "path": "app/views/casa_cases/_calendar_button.html.erb",
    "content": "<!-- Button code -->\n<% if date.present? %>\n  <%= tag.div class: \"cal-btn\", id: id, data: { title: title, start: date[:start], end: date[:end], tooltip: tooltip } %>\n<% end %>\n"
  },
  {
    "path": "app/views/casa_cases/_court_dates.html.erb",
    "content": "<% court_dates = casa_case.court_dates.includes(:hearing_type).ordered_ascending.load %>\n<label>Court dates: </label>\n<% if court_dates.empty? %>\n  No Court Dates\n<% else %>\n  <ul>\n    <% court_dates.each do |pcd| %>\n      <li>\n        <div class=\"court-date-row\">\n          <%= link_to(pcd.decorate.court_date_info, casa_case_court_date_path(casa_case, pcd), class: \"court-date-link\") %>\n          <%= render 'calendar_button',\n              id: \"court-date-#{pcd.id}\",\n              title: \"Court Date #{pcd.casa_case.case_number}\",\n              date: {\n                start: pcd.date.strftime(\"%Y-%m-%d\"),\n                end: pcd.date.strftime(\"%Y-%m-%d\")\n              },\n              tooltip: \"Add court date to calendar\" %>\n        </div>\n        <% if report = pcd.latest_associated_report %>\n          <%= link_to(rails_blob_path(report, disposition: 'attachment')) do %>\n            (Attached Report)\n          <% end %>\n        <% end %>\n      </li>\n    <% end %>\n  </ul>\n<% end %>\n<ul>\n  <div class=\"add-container past-court-dates my-3\">\n    <%= link_to new_casa_case_court_date_path(casa_case), class: \"main-btn btn-sm primary-btn btn-hover ml-3\" do %>\n      <i class=\"lni lni-plus\"></i>\n      Add a court date\n    <% end %>\n  </div>\n</ul>\n"
  },
  {
    "path": "app/views/casa_cases/_filter.html.erb",
    "content": "<div>\n  <div class=\"row casa-case-filters\">\n    <div class=\"col-sm-12 flex align-items-center\">\n      <h4 class=\"pull-left my-1 mr-2\">Filter by:</h4>\n      <div class=\"dropdown pull-left mx-2 my-1\">\n        <button class=\"btn btn-secondary dropdown-toggle\" type=\"button\" id=\"dropdownMenuButton1\" data-bs-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\">\n          Status\n        </button>\n        <div class=\"dropdown-menu status-options\" aria-labelledby=\"dropdownMenuButton1\">\n          <div class=\"dropdown-item form-check checkbox-style\">\n            <%= check_box_tag \"status_option_active\", \"Active\", true,\n                              class: \"form-check-input\",\n                              data: { value: \"Active\" } %>\n            <%= label_tag \"status_option_active\", \"Active\", class: \"form-check-label\" %>\n          </div>\n          <div class=\"dropdown-item form-check checkbox-style\">\n            <%= check_box_tag \"status_option_inactive\", \"Inactive\", false,\n                              class: \"form-check-input\",\n                              data: { value: \"Inactive\" } %>\n            <%= label_tag \"status_option_inactive\", \"Inactive\", class: \"form-check-label\" %>\n          </div>\n        </div>\n      </div>\n      <div class=\"dropdown pull-left mx-2 my-1\">\n        <button class=\"btn btn-secondary dropdown-toggle\" type=\"button\" id=\"dropdownMenuButton2\" data-bs-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\">\n          Assigned to Volunteer\n        </button>\n        <div class=\"dropdown-menu assigned-to-volunteer-options\" aria-labelledby=\"dropdownMenuButton2\">\n          <div class=\"dropdown-item form-check checkbox-style\">\n            <%= check_box_tag \"assigned_to_volunteer_option_yes\", \"Yes\", true,\n                              class: \"form-check-input\",\n                              data: { value: \"Yes\" } %>\n            <%= label_tag \"assigned_to_volunteer_option_yes\", \"Yes\", class: \"form-check-label\" %>\n          </div>\n          <div class=\"dropdown-item form-check form-check checkbox-style\">\n            <%= check_box_tag \"assigned_to_volunteer_option_no\", \"No\", true,\n                              class: \"form-check-input\",\n                              data: { value: \"No\" } %>\n            <%= label_tag \"assigned_to_volunteer_option_no\", \"No\", class: \"form-check-label\" %>\n          </div>\n        </div>\n      </div>\n      <div class=\"dropdown pull-left mx-2 my-1\">\n        <button class=\"btn btn-secondary dropdown-toggle\" type=\"button\" id=\"dropdownMenuButton3\" data-bs-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\">\n          Assigned to more than 1 Volunteer\n        </button>\n        <div class=\"dropdown-menu more-than-one-volunteer-options\" aria-labelledby=\"dropdownMenuButton3\">\n          <div class=\"dropdown-item form-check checkbox-style\">\n            <%= check_box_tag \"more_than_one_volunteer_option_yes\", \"Yes\", true,\n                              class: \"form-check-input\",\n                              data: { value: \"Yes\" } %>\n            <%= label_tag \"more_than_one_volunteer_option_yes\", \"Yes\", class: \"form-check-label\" %>\n          </div>\n          <div class=\"dropdown-item form-check checkbox-style\">\n            <%= check_box_tag \"more_than_one_volunteer_option_no\", \"No\", true,\n                              class: \"form-check-input\",\n                              data: { value: \"No\" } %>\n            <%= label_tag \"more_than_one_volunteer_option_no\", \"No\", class: \"form-check-label\" %>\n          </div>\n        </div>\n      </div>\n      <div class=\"dropdown pull-left mx-2 my-1\">\n        <button class=\"btn btn-secondary dropdown-toggle\" type=\"button\" id=\"dropdownMenuButton4\" data-bs-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\">\n          Assigned to Transition Aged Youth\n        </button>\n        <div class=\"dropdown-menu transition-youth-options\" aria-labelledby=\"dropdownMenuButton4\">\n          <div class=\"dropdown-item form-check checkbox-style\">\n            <%= check_box_tag \"transition_youth_option_yes\", \"Yes 🦋\", true,\n                              class: \"form-check-input\",\n                              data: { value: \"Yes 🦋\" } %>\n            <%= label_tag \"transition_youth_option_yes\", \"Yes\", class: \"form-check-label\" %>\n          </div>\n          <div class=\"dropdown-item form-check checkbox-style\">\n            <%= check_box_tag \"transition_youth_option_no\", \"No\", true,\n                              class: \"form-check-input\",\n                              data: { value: \"No 🐛\" } %>\n            <%= label_tag \"transition_youth_option_no\", \"No\", class: \"form-check-label\" %>\n          </div>\n        </div>\n      </div>\n      <div class=\"dropdown pull-left mx-2 my-1\">\n        <% unless current_user.volunteer? %>\n          <button class=\"btn btn-secondary dropdown-toggle\" type=\"button\" id=\"dropdownMenuButton5\" data-bs-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\">\n            Casa Case Prefix\n          </button>\n        <% end %>\n        <div class=\"dropdown-menu case-number-prefix-options\" aria-labelledby=\"dropdownMenuButton5\">\n          <% [\"CINA\", \"None\", \"TPR\"].each do |option| %>\n            <div class=\"dropdown-item form-check checkbox-style\">\n              <% option_for_name = option.downcase.gsub(/[^a-z]+/, '') -%>\n              <% tag_name = \"case_case_prefix_option_#{option_for_name}\" -%>\n              <%= check_box_tag tag_name, option, true,\n                                class: \"form-check-input\",\n                                data: { value: option } %>\n              <%= label_tag tag_name, option, class: \"form-check-label\" %>\n            </div>\n          <% end %>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n\n<div class=\"warning-modal\">\n  <div class=\"modal fade\" id=\"visibleColumns\" tabindex=\"-1\" aria-hidden=\"true\">\n    <div class=\"modal-dialog modal-dialog-centered\">\n      <div class=\"modal-content card-style\">\n        <div class=\"modal-header px-0 border-0\">\n          <h5 class=\"text-bold\">Pick Displayed Columns</h5>\n          <button\n            class=\"border-0 bg-transparent h1\"\n            data-bs-dismiss=\"modal\">\n            <i class=\"lni lni-cross-circle\"></i>\n          </button>\n        </div>\n        <div class=\"modal-body px-0\">\n          <div class=\"mb-30\">\n            <h6 class=\"mb-20\">\n              Select columns:\n  </h6>\n            <% CasaCase::TABLE_COLUMNS.each_with_index do |column, index| %>\n              <div class=\"form-check checkbox-style m-2\">\n                <%= check_box_tag \"pick-#{column}\",\n                                  \"1\",\n                                  false,\n                                  class: \"form-check-input toggle-visibility\",\n                                  data: { column: index } %>\n                <%= label_tag \"pick-#{column}\", column.titleize, class: \"form-check-label\" %>\n              </div>\n            <% end %>\n          </div>\n          <div class=\"action d-flex flex-wrap justify-content-end\">\n            <button\n              data-bs-dismiss=\"modal\"\n              class=\"main-btn danger-btn-outline btn-hover m-1\"><i class=\"lni lni-ban mr-10\"></i>\n              Close\n            </button>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/casa_cases/_form.html.erb",
    "content": "<div class=\"card card-container\">\n  <div class=\"card-body\">\n    <%= form_with(model: casa_case, local: true,\n      data: { controller: \"court-order-form\", nested_form_wrapper_selector_value: \".nested-form-wrapper\" }) do |form| %>\n      <%= render \"/shared/error_messages\", resource: casa_case %>\n      <% if casa_case.active %>\n        <div class=\"top-page-actions pull-right z-1\">\n          <%= button_tag(\n                type: \"submit\",\n                class: \"btn-sm main-btn primary-btn btn-hove mb-3\"\n              ) do %>\n            <% if casa_case.persisted? %>\n              <i class=\"lni lni-pencil-alt\"></i>\n              Update CASA Case\n            <% else %>\n              <i class=\"lni lni-plus\"></i>\n              Create CASA Case\n            <% end %>\n          <% end %>\n        </div>\n      <% end %>\n      <br>\n      <% if casa_case.new_record? || policy(casa_case).update_case_number? %>\n        <div class=\"input-style-1\">\n          <%= form.label :case_number, \"Case number\" %>\n          <%= form.text_field :case_number, class: \"form-control\", :required => 'required' %>\n        </div>\n      <% end %>\n      <h2><label>Court Details</label></h2>\n        <div class=\"form-group my-3\">\n          <label class=\"my-2\">Youth's Birth Month & Year</label>\n          <br>\n          <span class=\"datetime-year-month\">\n            <%= form.date_select :birth_month_year_youth,\n                                 {\n                                   order: [:month, :year],\n                                   start_year: Date.current.year,\n                                   end_year: 1989,\n                                   prompt: {month: \"Month\", year: \"Year\"},\n                                   discard_day: true,\n                                   disabled: !policy(casa_case).update_birth_month_year_youth?\n                                 }, class: \"form-control select-style-2\" %>\n          </span>\n        </div>\n      <% if policy(casa_case).update_date_in_care_youth? %>\n        <div class=\"field form-group\">\n          <%= form.label :date_in_care, \"Youth's Date in Care\", class: 'my-2' %>\n          <br>\n          <span class=\"datetime-year-month\">\n            <%= form.date_select :date_in_care,\n                                 {\n                                   order: [:month, :year],\n                                   start_year: Date.current.year,\n                                   end_year: 1989,\n                                   prompt: {month: \"Month\", year: \"Year\"},\n                                   discard_day: true\n                                 }, class: \"form-control select-style-2\" %>\n          </span>\n        </div>\n      <% end %>\n      <% if policy(casa_case).update_court_date? && casa_case.new_record? %>\n        <%= form.fields_for :court_dates, casa_case.court_dates.new do |cdf| %>\n          <div class=\"field form-group\">\n            <%= cdf.label :date, \"Next Court Date\" %>\n            <br>\n            <div class=\"input-style-1\">\n              <%= cdf.date_field :date, class: \"form-control\", hidden: @empty_court_date %>\n            </div>\n          </div>\n        <% end %>\n        <%= form.check_box :empty_court_date, checked: @empty_court_date, class: \"toggle-court-date-input\" %>\n        <%= form.label :empty_court_date, \"I don't know the court date yet\" %>\n      <% end %>\n      <div class=\"select-style-1\">\n        <%= form.label :court_report_status, \"Court Report Status\" %>\n        <div class=\"select-position\">\n          <%= form.select :court_report_status,\n                          CasaCase.court_report_statuses&.map { |status| [status.first.humanize, status.first] } %>\n        </div>\n      </div>\n      <% if casa_case.persisted? %>\n        <div class=\"field form-group court-orders\">\n          <% if policy(casa_case).update_court_orders? %>\n            <%= render partial: \"shared/court_order_list\",\n                       locals: {casa_case: @casa_case, siblings_casa_cases: @siblings_casa_cases, form: form,\n                                resource: 'casa_case'} %>\n          <% else %>\n            <%= form.label :case_court_orders, \"Court Orders\" %>\n            <div id=\"court-orders-list-container\">\n              <% @casa_case.case_court_orders.each do |order| %>\n                <div class=\"court-order-entry\">\n                  <textarea disabled><%= order.text %></textarea>\n                  <% if order.implementation_status %>\n                    <p class=\"implementation-status\">\n                      <strong>Status:</strong>\n                      <%= order.implementation_status.humanize %>\n                    </p>\n                  <% end %>\n                </div>\n              <% end %>\n            </div>\n          <% end %>\n        </div>\n        <div class=\"field form-group past-court-dates\">\n          <%= render \"court_dates\", casa_case: @casa_case %>\n        </div>\n      <% end %>\n      <% if Pundit.policy(current_user, casa_case).update_contact_types? %>\n        <label for=\"casa_case_contact_type_ids\">Contact Types</label>\n        <%= render \"case_contacts/form/contact_types\", form: form, options: @contact_types, selected_items: @selected_contact_type_ids, casa_cases: [@casa_case] %>\n      <% end %>\n      <% unless casa_case.persisted? %>\n        <div class=\"select-style-1\">\n          <%= form.fields_for :case_assignments, casa_case.case_assignments.new do |caf| %>\n            <%= caf.label :volunteer_id, \"Assign a Volunteer\" %>\n            <div class=\"select-position\">\n              <%= caf.collection_select(:volunteer_id, @casa_case.unassigned_volunteers, :id, :display_name, prompt: \"Please select a volunteer\") %>\n            </div>\n          <% end %>\n        </div>\n      <% end %>\n      <br>\n      <div class=\"actions-cc\">\n        <% if casa_case.active %>\n          <%= button_tag(\n                type: \"submit\",\n                class: \"btn-sm main-btn primary-btn btn-hove mb-3\"\n              ) do %>\n            <% if casa_case.persisted? %>\n              <i class=\"lni lni-pencil-alt\"></i>\n              Update CASA Case\n            <% else %>\n              <i class=\"lni lni-plus\"></i>\n              Create CASA Case\n            <% end %>\n          <% end %>\n          <%= link_to(\"Deactivate CASA Case\",\n                      deactivate_casa_case_path(casa_case),\n                      method: :patch,\n                      class: \"main-btn danger-btn-outline pull-right\",\n                      data: {\n                        confirm_swal:\n                          \"Deactivating a CASA Case will unassign any volunteers currently assigned to this case.\",\n                        reload: true,\n                        success: \"Yes, deactivate\",\n                        fail: \"No, go back\"\n                      }) if Pundit.policy(current_user, casa_case).update_case_status? && casa_case.persisted? %>\n        <% else %>\n          <%= link_to(\"Reactivate CASA Case\",\n                      reactivate_casa_case_path(casa_case),\n                      method: :patch,\n                      class: \"main-btn primary-btn btn-hover\") if Pundit.policy(current_user, casa_case).update_case_status? %>\n        <% end %>\n      </div>\n    <% end %>\n  </div>\n</div>\n<% if Pundit.policy(current_user, @casa_case).assign_volunteers? && casa_case.persisted? %>\n  <%= render \"volunteer_assignment\" %>\n<% end %>\n"
  },
  {
    "path": "app/views/casa_cases/_generate_report_modal.html.erb",
    "content": "<%= form_with url: generate_case_court_reports_path, local: false do |form| %>\n  <% id = \"generate-docx-report-modal\" %>\n  <%= render(Modal::OpenButtonComponent.new(\n    target: id,\n    klass: \"d-inline main-btn btn-hover btn-sm success-btn pull-right\")) do %>\n    Generate Report\n  <% end %>\n  <%= render(Modal::GroupComponent.new(id: id)) do |component| %>\n    <% component.with_header(text: \"Download Court Report as a .docx\", id: id, klass: \"content-1\") %>\n    <% component.with_body do %>\n      <div class=\"docx-report__modal-body\">\n      <p class=\"content-3 md-10\">\n      To download a court report specify the date range.\n      </p>\n      <div class=\"input-style-1\" id=\"case_select_body\">\n        <%= form.hidden_field :case_number, value: @casa_case.case_number %>\n        <%= form.hidden_field :time_zone, id: \"user-time-zone\" %>\n        <h4 class=\"mb-10\">Casa Case:</h4>\n        <p><%= @casa_case.decorate.court_report_select_option.first %></p>\n      </div>\n      <div class=\"dates-container\">\n        <div class=\"field form-group mb-20\">\n          <h6><label class=\"form-label\" for=\"start_date\">Starting From</label></h6>\n          <%= form.text_field :start_date,\n            value: @casa_case.formatted_latest_court_date,\n            data: { provide: \"datepicker\",\n                    date_format: ::DateHelper::JQUERY_MONTH_DAY_YEAR_FORMAT },\n                    class: \"form-control\" %>\n        </div>\n\n        <div class=\"field form-group mb-20\">\n          <h6><label class=\"form-label\" for=\"end_date\">Ending At</label></h6>\n          <%= form.text_field :end_date,\n            value: Time.zone.now.strftime(::DateHelper::RUBY_MONTH_DAY_YEAR_FORMAT),\n            data: { provide: \"datepicker\",\n                    date_format: ::DateHelper::JQUERY_MONTH_DAY_YEAR_FORMAT },\n                    class: \"form-control\" %>\n        </div>\n      </div>\n      </div>\n    <% end %>\n    <% component.with_footer do %>\n        <%= button_tag type: :submit,\n          data: {\n            button_name: \"Generate Report\"\n          },\n          id: \"btnGenerateReport\",\n          class: \"main-btn primary-btn btn-hover btn-sm\",\n          onclick: \"setTimeZone()\" do %>\n          <i class=\"lni lni-download mr-10\"></i>\n          <i id=\"spinner\" class='fas fa-spin d-none mr-10'>⏳</i>\n          Generate Report\n        <% end %>\n    <% end %>\n  <% end %>\n<% end %>\n"
  },
  {
    "path": "app/views/casa_cases/_inactive_case.html.erb",
    "content": "<div class=\"alert alert-danger\">\n  <p>Case was deactivated on: <%= I18n.l(@casa_case.updated_at, format: :standard, default: nil) %>\n    <%= link_to(\n            \"Reactivate CASA Case\",\n            reactivate_casa_case_path(@casa_case),\n            method: :patch,\n            class: \"main-btn primary-btn btn-hover pull-right\"\n        ) if Pundit.policy(current_user, @casa_case).update_case_status? %>\n  </p>\n</div>\n"
  },
  {
    "path": "app/views/casa_cases/_placements.html.erb",
    "content": "<% current_placement = casa_case.placements.order(placement_started_at: :desc).first %>\n\n<% if current_placement %>\n  <h6>\n    <strong>Current Placement:</strong>\n    <%= current_placement.placement_type.name %>\n  </h6>\n\n  <p>Placed since: <%= current_placement.decorate.formatted_date %></p>\n  <p><%= link_to \"See All Placements\", casa_case_placements_path(casa_case), class: 'text-primary hover-underline' %></p>\n<% elsif casa_case.casa_org.placement_types.present? %>\n  <h6>\n    <strong>Current Placement:</strong>\n    Unknown\n  </h6>\n\n  <p><%= link_to \"See All Placements\", casa_case_placements_path(casa_case), class: 'text-primary hover-underline' %></p>\n<% end %>\n"
  },
  {
    "path": "app/views/casa_cases/_thank_you_modal.html.erb",
    "content": "<div id=\"thank-you-modal-wrapper\">\n  <div class=\"modal fade\" id=\"thank_you\" data-bs-backdrop=\"static\" data-bs-keyboard=\"false\" tabindex=\"-1\" aria-labelledby=\"staticBackdropLabel\" aria-hidden=\"true\">\n    <div class=\"modal-dialog modal-dialog-centered\">\n      <div class=\"modal-content\">\n        <div class=\"modal-header\">\n          <%= \"Case contact was successfully created. #{thank_you_message}\" %>\n        </div>\n        <% when_do_we_have_court_dates = when_do_we_have_court_dates(@casa_case) %>\n        <% if when_do_we_have_court_dates == \"future\" %>\n          <div id=\"prompt-update-user\" class=\"modal-body\">\n            Please enter the previous court date if you know it so that court reports can be generated with the correct case contacts.\n          </div>\n        <% elsif when_do_we_have_court_dates == \"past\" %>\n          <div id=\"prompt-update-user\" class=\"modal-body\">\n            Please enter the next court date if you know it so that court reports can be generated with the correct case contacts.\n          </div>\n        <% elsif when_do_we_have_court_dates == \"none\" %>\n          <div id=\"prompt-update-user\" class=\"modal-body\">\n            Please enter any past or future court dates if you know them so that court reports can be generated with the correct case contacts.\n          </div>\n        <% end %>\n        <div class=\"modal-footer\">\n          <button type=\"button\" class=\"main-btn secondary-btn btn-sm\" data-bs-dismiss=\"modal\">Close</button>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/casa_cases/_volunteer_assignment.html.erb",
    "content": "<% content_for :table_title do %>\n  <div class=\"title-wrapper pt-30\">\n    <div class=\"row align-items-center\">\n      <div class=\"col-md-6\">\n        <div class=\"title mb-30\">\n          <h3>Assigned Volunteers</h3>\n        </div>\n      </div>\n    </div>\n  </div>\n<% end %>\n<% content_for :table_body do %>\n  <tbody>\n    <% @casa_case.case_assignments.each do |assignment| %>\n      <tr>\n        <td data-test=\"volunteer-name\">\n          <%= link_to assignment.volunteer.display_name, edit_volunteer_path(assignment.volunteer) %>\n        </td>\n        <td data-test=\"volunteer-email\"><%= assignment&.volunteer&.email %></td>\n        <td data-test=\"assigned\">\n          <% if assignment.active? %>\n            <%= render BadgeComponent.new(text: \"Assigned\", type: :success, margin: false) %>\n          <% else %>\n            <%= render BadgeComponent.new(text: assignment.volunteer.active? ? \"Unassigned\" : \"Deactivated volunteer\",\n                                          type: :danger,\n                                          margin: false) %>\n          <% end %>\n        </td>\n        <td data-test=\"assignment-start\"><%= I18n.l(assignment.created_at, format: :full, default: nil) %></td>\n        <td data-test=\"assignment-end\">\n          <% unless assignment.active? %>\n            <%= I18n.l(assignment.updated_at, format: :full, default: nil) %>\n          <% end %>\n        </td>\n        <td>\n          <div class=\"form-check form-switch\">\n            <%= form_with model: assignment, url: reimbursement_case_assignment_path(assignment), method: :patch do |form| %>\n              <%= form.check_box :allow_reimbursement, { class: \"form-check-input\", role: \"switch\", onchange: \"this.form.submit();\" } %>\n            <% end %>\n          </div>\n\n        </td>\n        <td data-test=\"action\">\n          <% if policy(assignment).unassign? %>\n            <%= button_to(\"Unassign Volunteer\",\n                        unassign_case_assignment_path(assignment),\n                        method: :patch,\n                        class: \"btn btn-outline-danger text-wrap\") %>\n          <% else %>\n            <% if !assignment.volunteer.active? && policy(assignment.volunteer).activate? %>\n              <%= link_to \"Activate Volunteer\",\n                        activate_volunteer_path(assignment.volunteer,\n                                                redirect_to_path: 'casa_case',\n                                                casa_case_id: @casa_case.id\n                        ),\n                        method: :patch,\n                        class: \"btn btn-outline-success\" %>\n            <% end %>\n            <% if assignment.hide_old_contacts? %>\n              <%= button_to(\"Show Old Contacts\",\n                        show_hide_contacts_case_assignment_path(assignment),\n                        method: :patch,\n                        class: \"btn btn-outline-warning text-wrap\") %>\n            <% else %>\n              <%= button_to(\"Hide Old Contacts\",\n                        show_hide_contacts_case_assignment_path(assignment),\n                        method: :patch,\n                        class: \"btn btn-outline-secondary text-wrap\") %>\n            <% end %>\n          <% end %>\n        </td>\n      </tr>\n    <% end %>\n  </tbody>\n<% end %>\n<%=\n  render(\n  \"shared/manage_volunteers\",\n  assignable_obj: CaseAssignment,\n  assign_action: case_assignments_path(casa_case_id: @casa_case.id),\n  available_volunteers: @casa_case.unassigned_volunteers,\n  select_id: 'case_assignment_casa_case_id',\n  select_name: 'case_assignment[volunteer_id]',\n  show_assigned_volunteers: @casa_case.volunteers.present?\n  )\n%>\n"
  },
  {
    "path": "app/views/casa_cases/edit.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h1>Editing&nbsp;<%= link_to(@casa_case.case_number, @casa_case) %></h1>\n      </div>\n    </div>\n  </div>\n</div>\n<% if @casa_case.active %>\n  <%= render 'form', casa_case: @casa_case %>\n<% else %>\n  <%= render 'inactive_case', casa_case: @casa_case %>\n<% end %>\n"
  },
  {
    "path": "app/views/casa_cases/index.html.erb",
    "content": "<% if Rails.env.development? %>\n  <div data-controller=\"hello\">\n  </div>\n<% end %>\n\n<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <% if current_user.volunteer? %>\n          <h1>My <%= \"Case\".pluralize(@casa_cases.count) %></h1>\n        <% else %>\n          <h1><%= \"Case\".pluralize(@casa_cases.count) %></h1>\n        <% end %>\n      </div>\n    </div>\n    <!-- end col -->\n    <div class=\"col-md-6\">\n      <div class=\"breadcrumb-wrapper mb-30\">\n        <span class=\"ml-5\">\n          <% if policy(:dashboard).see_volunteers_section? %>\n            <%= link_to new_casa_case_path, class: \"main-btn btn-sm primary-btn btn-hover ml-3\" do %>\n              <i class=\"lni lni-plus\"></i>\n              New Case\n            <% end %>\n          <% end %>\n        </span>\n        <% if policy(:application).admin_or_supervisor? %>\n          <span class=\"ml-5\">\n            <%= link_to case_groups_path, class: \"main-btn btn-sm primary-btn btn-hover ml-3\" do %>\n              Case Groups\n            <% end %>\n          </span>\n          <span class=\"ml-5\">\n            <%= link_to new_bulk_court_date_path, class: \"main-btn btn-sm primary-btn btn-hover ml-3\" do %>\n              <i class=\"lni lni-plus\"></i>\n              New Bulk Court Date\n            <% end %>\n          </span>\n        <% end %>\n        <span class=\"ml-5\">\n          <% if policy(CasaCase).can_see_filters? %>\n            <button\n              type=\"button\"\n              class=\"main-btn btn-sm primary-btn btn-hover\"\n              data-bs-toggle=\"modal\"\n              data-bs-target=\"#visibleColumns\"><i class=\"lni lni-pin mr-10\"></i>\n              Pick displayed columns\n            </button>\n          <% end %>\n          </span>\n      </div>\n    </div>\n  </div>\n</div>\n<% if policy(CasaCase).can_see_filters? %>\n  <%= render 'filter' %>\n<% end %>\n<div class=\"row\">\n  <div class=\"col-lg-12\">\n    <div class=\"card-style\">\n      <div class=\"table-wrapper table-responsive\">\n        <table class=\"table striped-table\" id=\"<%= @casa_cases_filter_id %>\">\n          <thead>\n            <tr>\n              <th>Case Number</th>\n              <th>Hearing Type</th>\n              <th>Judge</th>\n              <th>Status</th>\n              <th>Transition Aged Youth</th>\n              <% unless current_user.volunteer? %>\n                <th>Assigned To</th>\n              <% end %>\n              <th></th>\n            </tr>\n          </thead>\n          <tbody>\n            <% @casa_cases.each do |casa_case| %>\n              <tr class=\"<%= casa_case.decorate.inactive_class %>\">\n                <td class=\"min-width\"><%= link_to(casa_case.case_number, casa_case) %>\n                </td>\n                <td class=\"min-width\"><%= casa_case.hearing_type_name %></td>\n                <td class=\"min-width\"><%= casa_case.judge_name %></td>\n                <td class=\"min-width\"><%= casa_case.decorate.status %></td>\n                <td class=\"min-width\"><%= casa_case.decorate.transition_aged_youth %></td>\n                <% unless current_user.volunteer? %>\n                  <td class=\"min-width\">\n                    <% if casa_case.active? %>\n                      <%= safe_join(casa_case.assigned_volunteers.map { |vol|\n                    link_to(vol.display_name, edit_volunteer_path(vol)) },\n                                \", \") %>\n                    <% else %>\n                      Case was deactivated on: <%= I18n.l(casa_case.updated_at, format: :standard, default: nil) %>\n                    <% end %>\n                  </td>\n                <% end %>\n                <td class=\"min-width\">\n                  <i class=\"lni lni-pencil-alt text-danger\"></i>\n                  <%= link_to \"Edit\", edit_casa_case_path(casa_case), class: 'text-danger' %>\n                </td>\n              </tr>\n            <% end %>\n          </tbody>\n        </table>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/casa_cases/new.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h1>New CASA Case</h1>\n      </div>\n    </div>\n  </div>\n</div>\n<%= render 'form', casa_case: @casa_case %>\n"
  },
  {
    "path": "app/views/casa_cases/show.html.erb",
    "content": "<div class=\"row\">\n  <%= render \"thank_you_modal\" %>\n  <div class=\"col-sm-12 form-header d-flex flex-wrap justify-content-between\">\n    <h1 class=\"pull-left my-4\"><%= @casa_case.decorate.transition_aged_youth_icon %>CASA Case Details</h1>\n    <div class=\"d-inline-flex flex-wrap my-4 gap-3\">\n      <%- if @casa_case.active? %>\n        <%= link_to new_case_contact_path(case_contact: {casa_case_id: @casa_case.id}), class: \"main-btn primary-btn btn-sm btn-hover pull-left\" do %>\n          <i class=\"lni lni-plus mr-10\"></i>\n          New Case Contact\n        <% end %>\n      <%- end %>\n      <% if @casa_case.active? %>\n        <%= link_to edit_casa_case_path(@casa_case), class: \"main-btn primary-btn btn-sm btn-hover pull-left\" do %>\n          <i class=\"lni lni-pencil mr-10\"></i>\n          Edit Case Details\n        <% end %>\n      <% end %>\n      <% if @casa_case.in_transition_age? %>\n        <%= link_to(casa_case_emancipation_path(@casa_case),\n                class: \"main-btn primary-btn btn-sm\") do %>\n          Emancipation\n          <%= render BadgeComponent.new(text: @casa_case.decorate.emancipation_checklist_count,\n                                        type: :light,\n                                        margin: false) %>\n        <% end %>\n      <% end %>\n      <% if @casa_case.casa_org.show_fund_request %>\n        <%= link_to(\"New Fund Request\", new_casa_case_fund_request_path(@casa_case),\n  class: \"main-btn primary-btn btn-sm\") %>\n      <% end %>\n      <%= render \"casa_cases/generate_report_modal\" %>\n    </div>\n  </div>\n</div>\n<div class=\"card card-container\">\n  <div class=\"card-body\">\n    <% if !@casa_case.active %>\n      <%= render \"inactive_case\", casa_case: @casa_case %>\n    <% end %>\n    <div>\n      <h6><strong>Case number:</strong> <%= @casa_case.case_number %>\n        <%= volunteer_badge(@casa_case, current_user) %>\n      </h6>\n    </div>\n    <% if @casa_case.hearing_type %>\n      <div>\n        <h6><strong>Hearing Type:</strong> <%= @casa_case.hearing_type_name %></h6>\n      </div>\n    <% end %>\n    <% if @casa_case.has_judge_name? %>\n      <div>\n        <h6><strong>Judge:</strong> <%= @casa_case.judge_name %></h6>\n      </div>\n    <% end %>\n    <div>\n      <h6><strong>Transition Aged Youth:</strong> <%= @casa_case.decorate.transition_aged_youth %></h6>\n    </div>\n    <div>\n      <h6>\n        <strong>\n          Youth's Date in Care:\n        </strong>\n        <%= @casa_case.decorate.date_in_care %>\n        <%= @casa_case.decorate.duration_in_care %>\n      </h6>\n    </div>\n    <div>\n      <h6>\n        <strong>Next Court Date:</strong>\n        <%= I18n.l(@casa_case.next_court_date&.date, format: :day_and_date, default: '') %>\n        <%= render \"calendar_button\", date: @casa_case.decorate.calendar_next_court_date,\n                 id: \"btnNextCourtDate\",\n                 tooltip: \"Add Next Court Date to Calendar\",\n                 title: \"Next Court Date for [#{@casa_case.case_number}]\" %>\n      </h6>\n    </div>\n    <div>\n      <h6>\n        <strong>Court Report Status:</strong>\n        <%= @casa_case.decorate.court_report_submission %></h6>\n    </div>\n    <% unless @casa_case.court_report_not_submitted? %>\n      <% if @casa_case.court_report_submitted? && @casa_case.court_reports.attached? %>\n        <%= link_to('Click to download',\n                    rails_blob_path(@casa_case.latest_court_report, disposition: 'attachment')) %>\n      <% end %>\n      <div>\n        <h6>\n          <strong>Court Report Submitted Date:</strong>\n          <%= @casa_case.decorate.court_report_submitted_date %></h6>\n      </div>\n    <% end %>\n    <% if @casa_case.case_court_orders.exists? %>\n      <h6><strong>Court Orders:</strong></h6>\n      <% @casa_case.case_court_orders.each do |court_order| %>\n        <p><%= court_order.implementation_status_symbol %> <%= court_order.text %></p>\n      <% end %>\n    <% end %>\n    <%= render \"court_dates\", casa_case: @casa_case %>\n    <%= render \"placements\", casa_case: @casa_case %>\n    <div>\n      <h6><strong>Assigned Volunteers:</strong></h6>\n      <div class=\"container\">\n        <% Pundit.policy_scope(current_user, @casa_case.assigned_volunteers).each do |volunteer| %>\n          <div class=\"row mb-3\">\n            <% if current_user.volunteer? %>\n              <div class=\"col-12\"><%= volunteer.display_name %></div>\n            <% else %>\n              <div class=\"col-2\">\n                <%= link_to \"#{volunteer.display_name} \", edit_volunteer_path(volunteer) %>\n              </div>\n              <div class=\"col-10\">\n                <%= render \"volunteers/volunteer_reminder_form\", volunteer: volunteer %>\n              </div>\n            <% end %>\n          </div>\n        <% end %>\n      </div>\n      <br>\n      <% if @casa_case.case_contacts.present? %>\n        <div class=\"text-right mb-2\">\n          <%= link_to casa_case_path(params[:id], format: :xlsx), class: \"main-btn primary-btn btn-sm btn-hover\" do %>\n            <i class=\"lni lni-download mr-10\"></i>\n            Download All\n          <% end %>\n        </div>\n      <% end %>\n      <div id=\"case_contacts_list\">\n        <% if current_user.supervisor? || current_user.casa_admin? %>\n          <%= render(partial: \"case_contacts/case_contact\",\n                    collection: @casa_case.decorate\n                      .case_contacts_ordered_by_occurred_at\n                      .includes(\n                        :creator,\n                        :followups,\n                        :contact_types,\n                        contact_topic_answers: [:contact_topic]\n                      )\n                      .grab_all(current_user),\n                    as: :contact) %>\n        <% else %>\n          <%= render(partial: \"case_contacts/case_contact\",\n                    collection: @casa_case.decorate\n                      .case_contacts_filtered_by_active_assignment_ordered_by_occurred_at\n                      .includes(\n                        :creator,\n                        :followups,\n                        :contact_types,\n                        contact_topic_answers: [:contact_topic]\n                      )\n                      .grab_all(current_user),\n                    as: :contact) %>\n        <% end %>\n      </div>\n    </div>\n  </div>\n</div>\n\n<%# clear local storage when successfully created case_contacts %>\n<script>\n  document.addEventListener(\"DOMContentLoaded\", () => {\n    if (/\\/casa_cases\\/.*\\?.*success=true/.test(window.location.href)) {\n      window.localStorage.removeItem('casa-contact-form')\n    }\n  });\n  const ELEMENTS = {\n    'caseSelect': '#case-selection',\n    'generateBtn': '#btnGenerateReport',\n  }\n  const showBtn = el => el.classList.remove('d-none')\n  const enableBtn = (el) => {\n    el.disabled = false\n    el.classList.remove('disabled')\n    el.removeAttribute('aria-disabled')\n  }\n\n  const handleSelect = (e) => {\n    const selectEl = e.target\n    const generateBtn = selectEl.form.querySelector(ELEMENTS.generateBtn)\n\n    // when selecting a case, reset buttons to initial state\n    enableBtn(generateBtn)\n    showBtn(generateBtn)\n  }\n\n  const bindElements = () => {\n    const caseSelectEl = document.querySelector(ELEMENTS.caseSelect)\n\n    if (caseSelectEl)\n      caseSelectEl.addEventListener('change', handleSelect)\n  }\n\n  const setTimeZone = () => {\n    const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone\n    document.getElementById(\"user-time-zone\").value = timeZone\n  }\n\n  window.onload = function () {\n    bindElements()\n  }\n</script>\n"
  },
  {
    "path": "app/views/casa_cases/show.xlsx.axlsx",
    "content": "column_names = [\n  \"Internal Contact Number\",\n  \"Duration Minutes\",\n  \"Contact Types\",\n  \"Contact Made\",\n  \"Contact Medium\",\n  \"Occurred At\",\n  \"Added To System At\",\n  \"Miles Driven\",\n  \"Wants Driving Reimbursement\",\n  \"Casa Case Number\",\n  \"Creator Email\",\n  \"Creator Name\",\n  \"Supervisor Name\",\n  \"Case Contact Notes\"\n]\n\nwb = xlsx_package.workbook\ncase_contacts = @casa_case.decorate.case_contacts_ordered_by_occurred_at\nwb.add_worksheet(name: @casa_case.case_number) do |sheet|\n  sheet.add_row column_names\n  case_contacts.each do |case_contact|\n    sheet.add_row [\n      case_contact.id,\n      case_contact.duration_minutes,\n      case_contact.contact_types.map(&:name).join(\"|\"),\n      case_contact.contact_made,\n      case_contact.medium_type,\n      case_contact.occurred_at.strftime(\"%B %d, %Y\"),\n      case_contact.created_at.strftime(\"%F %T UTC\"),\n      case_contact.miles_driven,\n      case_contact.want_driving_reimbursement,\n      @casa_case.case_number,\n      case_contact.creator.email,\n      case_contact.creator.display_name,\n      case_contact.creator.supervisor&.display_name,\n      case_contact.notes\n    ]\n  end\nend\n"
  },
  {
    "path": "app/views/casa_org/_contact_topics.html.erb",
    "content": "<div class=\"row\">\n  <div class=\"col-lg-12\">\n    <div class=\"card-style mb-30\">\n      <div class=\"row align-items-center\">\n        <div class=\"col-md-6\">\n          <h3>Contact Topics</h3>\n        </div>\n        <div class=\"col-md-6\">\n          <div class=\"breadcrumb-wrapper\">\n            <span class=\"ml-5\">\n              <%= link_to new_contact_topic_path, class: \"btn-sm main-btn primary-btn btn-hover\" do %>\n                <i class=\"lni lni-plus mr-10\"></i>\n                New Contact Topic\n              <% end %>\n            </span>\n          </div>\n        </div>\n      </div>\n      <div class=\"table-wrapper table-responsive\">\n        <table class=\"table striped-table\" id=\"contact-topics\">\n          <thead>\n            <tr>\n              <th>Question</th>\n              <th>Details</th>\n              <th>Active?</th>\n            </tr>\n          </thead>\n          <tbody>\n            <% @contact_topics.each do |contact_topic| %>\n              <% id = \"contact_topic-#{contact_topic.id}\" %>\n              <tr>\n                <td scope=\"row\" class=\"min-width\">\n                  <%= contact_topic.question %>\n                </td>\n                <td scope=\"row\" class=\"min-width pre-line\"><%= contact_topic.details %></td>\n                <td scope=\"row\" class=\"min-width\">\n                  <%= contact_topic.active ? \"Yes\" : \"No\" %>\n                </td>\n                <td>\n                  <%= render(DropdownMenuComponent.new(menu_title: \"Actions Menu\", hide_label: true)) do %>\n                    <li><%= link_to \"Edit\", edit_contact_topic_path(contact_topic), class: \"dropdown-item\" %></li>\n                    <li><%= render(Modal::OpenLinkComponent.new(text: \"Delete\", target: id, klass: \"dropdown-item\")) %></li>\n                  <% end %>\n                </td>\n              </tr>\n              <%= render(Modal::GroupComponent.new(id: id)) do |component| %>\n                <% component.with_header(text: \"Delete Contact Topic?\", id: id) %>\n                <% component.with_body(text: [\n                  \"This topic and its related questions will be deleted and will no longer be presented while filling out case contacts.\",\n                  \"This will not affect case contacts that have already been created.\"]) %>\n                <% component.with_footer do %>\n                  <%= link_to soft_delete_contact_topic_path(contact_topic), method: :delete,\n                    class: \"btn-sm main-btn danger-btn btn-hover ms-auto\" do %>\n                    <i class=\"lni lni-trash-can mr-10\"></i>\n                    Delete Court Report Topic\n                  <% end %>\n                <% end %>\n              <% end %>\n            <% end %>\n          </tbody>\n        </table>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/casa_org/_contact_type_groups.html.erb",
    "content": "<div class=\"row\">\n  <div class=\"col-lg-12\">\n    <div class=\"card-style mb-30\">\n      <div class=\"row align-items-center\">\n        <div class=\"col-md-6\">\n          <h3>Contact Type Groups</h3>\n        </div>\n        <div class=\"col-md-6\">\n          <div class=\"breadcrumb-wrapper\">\n            <span class=\"ml-5\">\n              <%= link_to new_contact_type_group_path, class: \"btn-sm main-btn primary-btn btn-hover\" do %>\n                <i class=\"lni lni-plus mr-10\"></i>\n                New Group\n              <% end %>\n            </span>\n          </div>\n        </div>\n      </div>\n      <!-- end col -->\n      <!-- end col -->\n      <!-- end col -->\n      <!-- end col -->\n      <div class=\"table-wrapper table-responsive\">\n        <table class=\"table striped-table\" id=\"contact-type-groups\">\n          <thead>\n            <tr>\n              <th>Name</th>\n              <th>Active?</th>\n              <th>Actions</th>\n            </tr>\n          </thead>\n          <tbody>\n            <% contact_type_groups.each do |group| %>\n              <tr id=\"group-<%= group.id %>\">\n                <td scope=\"row\" class=\"min-width\">\n                  <%= group.name %>\n                </td>\n                <td scope=\"row\" class=\"min-width\">\n                  <%= group.active? ? \"Yes\" : \"No\" %>\n                </td>\n                <td>\n                  <%= link_to edit_contact_type_group_path(group) do %>\n                    <div class=\"action\">\n                      <button class=\"text-danger\">\n                        <i class=\"lni lni-pencil-alt\"></i> Edit\n                      </button>\n                    </div>\n                  <% end %>\n                </td>\n              </tr>\n            <% end %>\n          </tbody>\n        </table>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/casa_org/_contact_types.html.erb",
    "content": "<div class=\"row\">\n  <div class=\"col-lg-12\">\n    <div class=\"card-style mb-30\">\n      <div class=\"row align-items-center\">\n        <div class=\"col-md-6\">\n          <h3>Contact Types</h3>\n        </div>\n        <div class=\"col-md-6\">\n          <div class=\"breadcrumb-wrapper\">\n            <span class=\"ml-5\">\n              <%= link_to new_contact_type_path, class: \"btn-sm main-btn primary-btn btn-hover\" do %>\n                <i class=\"lni lni-plus mr-10\"></i>\n                New Contact Type\n              <% end %>\n            </span>\n          </div>\n        </div>\n      </div>\n      <div class=\"table-wrapper table-responsive\">\n        <table class=\"table striped-table\" id=\"contact-types\">\n          <thead>\n            <tr>\n              <th>Name</th>\n              <th>Group</th>\n              <th>Active?</th>\n              <th>Actions</th>\n            </tr>\n          </thead>\n          <tbody>\n            <% contact_types.each do |contact_type| %>\n              <tr id=\"contact_type-<%= contact_type.id %>\">\n                <td scope=\"row\" class=\"min-width\">\n                  <%= contact_type.name %>\n                </td>\n                <td scope=\"row\" class=\"min-width\">\n                  <%= contact_type.contact_type_group.name %>\n                </td>\n                <td scope=\"row\" class=\"min-width\">\n                  <%= contact_type.active? ? \"Yes\" : \"No\" %>\n                </td>\n                <td>\n                  <%= link_to edit_contact_type_path(contact_type) do %>\n                    <div class=\"action\">\n                      <button class=\"text-danger\">\n                        <i class=\"lni lni-pencil-alt\"></i> Edit\n                      </button>\n                    </div>\n                  <% end %>\n                </td>\n              </tr>\n            <% end %>\n          </tbody>\n        </table>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/casa_org/_custom_org_links.html.erb",
    "content": "<div class=\"row\">\n  <div class=\"col-lg-12\">\n    <div class=\"card-style mb-30\">\n      <div class=\"row align-items-center\">\n        <div class=\"col-md-6\">\n          <h3>Custom Links</h3>\n        </div>\n        <div class=\"col-md-6\">\n          <div class=\"breadcrumb-wrapper\">\n            <span class=\"ml-5\">\n              <%= link_to new_custom_org_link_path, class: \"btn-sm main-btn primary-btn btn-hover\" do %>\n                <i class=\"lni lni-plus mr-10\"></i> New Custom Link\n              <% end %>\n            </span>\n          </div>\n        </div>\n      </div>\n      <div class=\"table-wrapper table-responsive\">\n        <table class=\"table striped-table\">\n          <thead>\n            <tr>\n              <th>Display Text</th>\n              <th>URL</th>\n              <th>Active?</th>\n              <th>Actions</th>\n            </tr>\n          </thead>\n          <tbody>\n            <% if @custom_org_links.blank? %>\n              <tr>\n                <td colspan=\"4\">\n                  <span class=\"fw-light fst-italic text-center\">No custom links have been added for this organization.</span>\n                </td>\n              </tr>\n            <% else %>\n              <% @custom_org_links.each do |custom_link| %>\n                <tr>\n                  <td><%= custom_link.text %></td>\n                  <td><%= truncate(custom_link.url, length: 90) %></td>\n                  <td><%= custom_link.active ? \"Yes\" : \"No\" %></td>\n                  <td>\n                    <%= link_to edit_custom_org_link_path(custom_link) do %>\n                      <div class=\"action\">\n                        <button class=\"text-danger\">\n                          <i class=\"lni lni-pencil-alt\"></i> Edit\n                        </button>\n                      </div>\n                    <% end %>\n                    <%= link_to custom_org_link_path(custom_link), method: :delete do %>\n                      <div class=\"action\">\n                        <button class=\"text-danger\">\n                          <i class=\"lni lni-trash-can\"></i> Delete\n                        </button>\n                      </div>\n                    <% end %>\n                  </td>\n                </tr>\n              <% end %>\n            <% end %>\n          </tbody>\n        </table>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/casa_org/_hearing_types.html.erb",
    "content": "<div class=\"row\">\n  <div class=\"col-lg-12\">\n    <div class=\"card-style mb-30\">\n      <div class=\"row align-items-center\">\n        <div class=\"col-md-6\">\n          <h3>Hearing Types</h3>\n        </div>\n        <div class=\"col-md-6\">\n          <div class=\"breadcrumb-wrapper\">\n            <span class=\"ml-5\">\n              <%= link_to new_hearing_type_path, class: \"btn-sm main-btn primary-btn btn-hover\" do %>\n                <i class=\"lni lni-plus mr-10\"></i>\n                New Hearing Type\n              <% end %>\n            </span>\n          </div>\n        </div>\n      </div>\n      <div class=\"table-wrapper table-responsive\">\n        <table class=\"table striped-table\" id=\"hearing-types\">\n          <thead>\n            <tr>\n              <th>Name</th>\n              <th>Checklist</th>\n              <th>Active?</th>\n              <th>Actions</th>\n            </tr>\n          </thead>\n          <tbody>\n            <% @hearing_types.each do |hearing_type| %>\n              <tr id=\"hearing_type-<%= hearing_type.id %>\">\n                <td scope=\"row\" class=\"min-width\">\n                  <%= hearing_type.name %>\n                </td>\n                <td class=\"min-width\">\n                  <%= \"#{hearing_type.checklist_updated_date}\" %>\n                </td>\n                <td scope=\"row\" class=\"min-width\">\n                  <%= hearing_type.active ? \"Yes\" : \"No\" %>\n                </td>\n                <td>\n                  <%= link_to edit_hearing_type_path(hearing_type) do %>\n                    <div class=\"action\">\n                      <button class=\"text-danger\">\n                        <i class=\"lni lni-pencil-alt\"></i> Edit\n                      </button>\n                    </div>\n                  <% end %>\n                </td>\n              </tr>\n            <% end %>\n          </tbody>\n        </table>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/casa_org/_judges.html.erb",
    "content": "<div class=\"row\">\n  <div class=\"col-lg-12\">\n    <div class=\"card-style mb-30\">\n      <div class=\"row align-items-center\">\n        <div class=\"col-md-6\">\n          <h3>Judge</h3>\n        </div>\n        <div class=\"col-md-6\">\n          <div class=\"breadcrumb-wrapper\">\n            <span class=\"ml-5\">\n              <%= link_to new_judge_path, class: \"btn-sm main-btn primary-btn btn-hover\" do %>\n                <i class=\"lni lni-plus mr-10\"></i>\n                New Judge\n              <% end %>\n            </span>\n          </div>\n        </div>\n      </div>\n      <div class=\"table-wrapper table-responsive\">\n        <table class=\"table striped-table\">\n          <thead>\n            <tr>\n              <th>Name</th>\n              <th>Active?</th>\n              <th>Actions</th>\n            </tr>\n          </thead>\n          <tbody>\n            <% @judges.each do |judge| %>\n              <tr id=\"judge-<%= judge.id %>\">\n                <td scope=\"row\" class=\"min-width\">\n                  <%= judge.name %>\n                </td>\n                <td scope=\"row\" class=\"min-width\">\n                  <%= judge.active ? \"Yes\" : \"No\" %>\n                </td>\n                <td>\n                  <%= link_to edit_judge_path(judge) do %>\n                    <div class=\"action\">\n                      <button class=\"text-danger\">\n                        <i class=\"lni lni-pencil-alt\"></i> Edit\n                      </button>\n                    </div>\n                  <% end %>\n                </td>\n              </tr>\n            <% end %>\n          </tbody>\n        </table>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/casa_org/_languages.html.erb",
    "content": "<div class=\"row\">\n  <div class=\"col-lg-12\">\n    <div class=\"card-style mb-30\">\n      <h3 class=\"mb-10\">Languages</h3>\n      <p class=\"text-sm mb-20\">\n        A list of languages volunteers can choose from to add to their profile to let supervisors and admins know they can speak the language.\n      </p>\n      <%= link_to new_language_path, class: \"btn-sm main-btn primary-btn btn-hover\" do %>\n        <i class=\"lni lni-plus mr-10\"></i>\n        New Language\n      <% end %>\n      <!-- end table title -->\n      <div class=\"table-wrapper table-responsive\">\n        <table class=\"table striped-table\">\n          <thead>\n            <tr>\n              <th>Name</th>\n              <th>Actions</th>\n            </tr>\n            <!-- end table row-->\n          </thead>\n          <tbody>\n            <tr>\n              <td class=\"min-width\">English (default language)</td>\n              <td></td>\n            </tr>\n            <% languages.each do |lang| %>\n              <tr id=\"language-<%= lang.id %>\">\n                <td class=\"min-width\">\n                  <%= lang.name %>\n                </td>\n                <td>\n                  <%= link_to edit_language_path(lang) do %>\n                    <div class=\"action\">\n                      <button class=\"text-danger\">\n                        <i class=\"lni lni-pencil-alt\"></i> Edit\n                      </button>\n                    </div>\n                  <% end %>\n                </td>\n              </tr>\n            <% end %>\n          </tbody>\n        </table>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/casa_org/_learning_hour_topics.html.erb",
    "content": "<div class=\"row\">\n  <div class=\"col-lg-12\">\n    <div class=\"card-style mb-30\">\n      <div class=\"row align-items-center\">\n        <div class=\"col-md-6\">\n          <h3>Learning Topic</h3>\n        </div>\n        <div class=\"col-md-6\">\n          <div class=\"breadcrumb-wrapper\">\n            <span class=\"ml-5\">\n              <%= link_to new_learning_hour_topic_path, class: \"btn-sm main-btn primary-btn btn-hover\" do %>\n                <i class=\"lni lni-plus mr-10\"></i>\n                New Learning Topic\n              <% end %>\n            </span>\n          </div>\n        </div>\n      </div>\n      <div class=\"table-wrapper table-responsive\">\n        <table class=\"table striped-table\">\n          <thead>\n          <tr>\n            <th>Name</th>\n            <th>Actions</th>\n          </tr>\n          </thead>\n          <tbody>\n          <% @learning_hour_topics.each do |learning_hour_topic| %>\n            <tr id=\"learning_hour_topic-<%= learning_hour_topic.id %>\">\n              <td scope=\"row\" class=\"min-width\">\n                <%= learning_hour_topic.name %>\n              </td>\n\n              <td>\n                <%= link_to edit_learning_hour_topic_path(learning_hour_topic) do %>\n                  <div class=\"action\">\n                    <button class=\"text-danger\">\n                      <i class=\"lni lni-pencil-alt\"></i> Edit\n                    </button>\n                  </div>\n                <% end %>\n              </td>\n            </tr>\n          <% end %>\n          </tbody>\n        </table>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/casa_org/_learning_hour_types.html.erb",
    "content": "<div class=\"row\">\n  <div class=\"col-lg-12\">\n    <div class=\"card-style mb-30\">\n      <div class=\"row align-items-center\">\n        <div class=\"col-md-6\">\n          <h3>Type of Learning</h3>\n        </div>\n        <div class=\"col-md-6\">\n          <div class=\"breadcrumb-wrapper\">\n            <span class=\"ml-5\">\n              <%= link_to new_learning_hour_type_path, class: \"btn-sm main-btn primary-btn btn-hover\" do %>\n                <i class=\"lni lni-plus mr-10\"></i>\n                New Type of Learning\n              <% end %>\n            </span>\n          </div>\n        </div>\n      </div>\n      <div class=\"table-wrapper table-responsive\">\n        <table class=\"table striped-table\">\n          <thead>\n          <tr>\n            <th>Name</th>\n            <th>Active?</th>\n            <th>Actions</th>\n          </tr>\n          </thead>\n          <tbody>\n          <% @learning_hour_types.each do |learning_hour_type| %>\n            <tr id=\"learning_hour_type-<%= learning_hour_type.id %>\">\n              <td scope=\"row\" class=\"min-width\">\n                <%= learning_hour_type.name %>\n              </td>\n              <td scope=\"row\" class=\"min-width\">\n                <%= learning_hour_type.active ? \"Yes\" : \"No\" %>\n              </td>\n              <td>\n                <%= link_to edit_learning_hour_type_path(learning_hour_type) do %>\n                  <div class=\"action\">\n                    <button class=\"text-danger\">\n                      <i class=\"lni lni-pencil-alt\"></i> Edit\n                    </button>\n                  </div>\n                <% end %>\n              </td>\n            </tr>\n          <% end %>\n          </tbody>\n        </table>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/casa_org/_placement_types.html.erb",
    "content": "<div class=\"row\">\n  <div class=\"col-lg-12\">\n    <div class=\"card-style mb-30\">\n      <div class=\"row align-items-center\">\n        <div class=\"col-md-6\">\n          <h3>Placement Types</h3>\n        </div>\n        <div class=\"col-md-6\">\n          <div class=\"breadcrumb-wrapper\">\n            <span class=\"ml-5\">\n              <%= link_to new_placement_type_path, class: \"btn-sm main-btn primary-btn btn-hover\" do %>\n                <i class=\"lni lni-plus mr-10\"></i>\n                New Placement Type\n              <% end %>\n            </span>\n          </div>\n        </div>\n      </div>\n      <div class=\"table-wrapper table-responsive\">\n        <table class=\"table striped-table\" id=\"placement-types-table\">\n          <thead>\n            <tr>\n              <th>Name</th>\n              <th>Actions</th>\n            </tr>\n          </thead>\n          <tbody>\n            <% placement_types.each do |placement_type| %>\n              <tr id=\"placement_type-<%= placement_type.id %>\">\n                <td scope=\"row\" class=\"min-width\">\n                  <%= placement_type.name %>\n                </td>\n                <td>\n                  <%= link_to edit_placement_type_path(placement_type) do %>\n                    <div class=\"action\">\n                      <button class=\"text-danger\">\n                        <i class=\"lni lni-pencil-alt\"></i> Edit\n                      </button>\n                    </div>\n                  <% end %>\n                </td>\n              </tr>\n            <% end %>\n          </tbody>\n        </table>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/casa_org/_sent_emails.html.erb",
    "content": "<div class=\"row\">\n  <div class=\"col-lg-12\">\n    <div class=\"card-style mb-30\">\n      <h3 class=\"mb-10\">Sent Emails</h3>\n      <div class=\"table-wrapper table-responsive\">\n        <table class=\"table striped-table\" id=\"sent-emails\">\n          <thead>\n            <tr>\n              <th>Mailer Type</th>\n              <th>Category</th>\n              <th>Recipient</th>\n              <th>Time Sent</th>\n            </tr>\n          </thead>\n          <tbody>\n            <% @sent_emails.each do |sent_email| %>\n              <tr id=\"email-<%= sent_email.id %>\">\n                <td scope=\"row\" class=\"min-width\">\n                  <%= sent_email.mailer_type %>\n                </td>\n                <td scope=\"row\" class=\"min-width\">\n                  <%= sent_email.category %>\n                </td>\n                <td class=\"min-width\">\n                  <%= sent_email.user.display_name %> &lt;<%= sent_email.sent_address %>&gt;\n                </td>\n                <td>\n                  <%= to_user_timezone(sent_email.created_at).strftime(\"%l:%M%P %d %b %Y\") %>\n                </td>\n              </tr>\n            <% end %>\n          </tbody>\n        </table>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/casa_org/edit.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h1 id=\"organization-details\">Editing CASA Organization</h1>\n      </div>\n    </div>\n  </div>\n</div>\n<div class=\"card-style mb-30\">\n  <%= form_with(model: current_organization, local: true) do |form| %>\n    <%= render \"/shared/error_messages\", resource: current_organization %>\n    <div class=\"input-style-1\">\n      <%= form.label :name, \"Name\" %>\n      <%= form.text_field :name, class: \"form-control\", required: true %>\n    </div>\n    <div class=\"input-style-1\">\n      <%= form.label :display_name, \"Display name\" %>\n      <%= form.text_field :display_name, class: \"form-control\" %>\n    </div>\n    <div class=\"input-style-1\">\n      <%= form.label :address, \"Address\" %>\n      <%= form.text_field :address, class: \"form-control\" %>\n    </div>\n    <div class=\"input-style-1\">\n      <%= form.label :logo, \"Logo\" %>\n      <%= form.file_field :logo, class: \"form-control h-auto\", accept: \".png,.gif,.jpg,.jpeg,.webp,.svg\" %>\n    </div>\n    <div class=\"input-style-1\">\n      <%= form.label :court_report_template, \"Court report template\" %>\n      <div class=\"row\">\n        <div class=\"col-sm\">\n          <%= form.file_field :court_report_template, class: \"form-control\" %>\n        </div>\n         <div class=\"col-sm align-middle my-auto\">\n          <% if current_organization.court_report_template.attached? %>\n          <% ActiveStorage::Current.url_options = { host: request.base_url } %>\n           <%= link_to 'Download Current Template', current_organization.court_report_template.url(only_path: true), class: \"btn btn-info\" %>\n          <% end %>\n        </div>\n      </div>\n    </div>\n    <hr>\n    <h3 class=\"mb-2\">Organization Features</h3>\n    <div class=\"form-check checkbox-style mb-20\">\n      <%= form.check_box :show_driving_reimbursement, class: 'form-check-input' %>\n      <%= form.label :show_driving_reimbursement, \"Show driving reimbursement\", class: 'form-check-label mb-2' %>\n    </div>\n    <div class=\"form-check checkbox-style mb-20\">\n      <%= form.check_box :learning_topic_active, class: 'form-check-input' %>\n      <%= form.label :learning_topic_active, \"Enable Learning Topic\", class: 'form-check-label mb-2' %>\n    </div>\n    <div class=\"form-check checkbox-style mb-20\">\n      <%= form.check_box :other_duties_enabled, class: 'form-check-input' %>\n      <%= form.label :other_duties_enabled, \"Enable Other Duties\", class: 'form-check-label mb-2' %>\n    </div>\n    <div class=\"form-check checkbox-style mb-20\">\n      <%= form.check_box :twilio_enabled, class: 'form-check-input accordionTwilio' %>\n      <%= form.label :twilio_enabled, \"Enable Twilio\", class: 'form-check-label mb-2' %>\n    </div>\n    <%# Twilio Form Begin %>\n      <div class=\"accordionGroup\">\n        <div id=\"collapseTwilio\" class=\"collapse\" aria-labelledby=\"headingOne\" data-bs-parent=\"#accordionTwilio\">\n          <div class=\"input-style-1\">\n            <%= form.label :twilio_phone_number, \"Twilio Phone Number\" %>\n            <%= form.text_field :twilio_phone_number, class: \"form-control\", required: true %>\n          </div>\n          <div class=\"input-style-1\">\n            <%= form.label :twilio_account_sid, \"Twilio Account SID\" %>\n            <%= form.text_field :twilio_account_sid, class: \"form-control\", required: true %>\n          </div>\n          <div class=\"input-style-1\">\n            <%= form.label :twilio_api_key_sid, \"Twilio API Key SID\" %>\n            <%= form.text_field :twilio_api_key_sid, class: \"form-control\", required: true %>\n          </div>\n          <div class=\"input-style-1\">\n            <%= form.label :twilio_api_key_secret, \"Twilio API Key Secret\" %>\n            <%= form.text_field :twilio_api_key_secret, class: \"form-control\", required: true %>\n          </div>\n        </div>\n      </div>\n    <%# Twilio Form End %>\n    <% if Flipper.enabled?(:show_additional_expenses) %>\n        <div class=\"form-check checkbox-style mb-20\">\n          <%= form.check_box :additional_expenses_enabled, class: 'form-check-input' %>\n          <%= form.label :additional_expenses_enabled, \"Volunteers can add Other Expenses\", class: 'form-check-label mb-2' %>\n        </div>\n    <% end %>\n    <div class=\"actions mb-10\">\n      <%= button_tag(\n            type: \"submit\",\n            class: \"btn-sm main-btn primary-btn btn-hover\"\n          ) do %>\n        <i class=\"lni lni-checkmark-circle mr-10\"></i> Submit\n      <% end %>\n    </div>\n  <% end %>\n</div>\n<div class=\"tables-wrapper\">\n  <%= render \"languages\", languages: current_organization.languages %>\n</div>\n<div class=\"tables-wrapper\">\n  <%= render \"custom_org_links\" %>\n</div>\n<!-- end -->\n<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h2 id=\"contact-types\">\n          Manage Contact Types\n        </h2>\n      </div>\n    </div>\n  </div>\n</div>\n<div class=\"tables-wrapper\">\n  <%= render \"contact_type_groups\", contact_type_groups: @contact_type_groups %>\n  <%= render \"contact_types\", contact_types: @contact_types %>\n</div>\n<!-- end -->\n<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h2 id=\"court-details\">\n          Manage Court Details\n        </h2>\n      </div>\n    </div>\n  </div>\n</div>\n<div class=\"tables-wrapper\">\n  <%= render \"hearing_types\" %>\n  <%= render \"judges\" %>\n  <%= render \"sent_emails\" %>\n</div>\n<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h2 id=\"learning-hours\">\n          Manage Learning Hours\n        </h2>\n      </div>\n    </div>\n  </div>\n</div>\n<div class=\"tables-wrapper\">\n  <%= render \"learning_hour_types\" %>\n</div>\n<div class=\"tables-wrapper\">\n  <%= render \"learning_hour_topics\" %>\n</div>\n<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h2 id=\"case-contact-topics\">\n          Manage Case Contact Topics\n        </h2>\n      </div>\n    </div>\n  </div>\n</div>\n<div class=\"tables-wrapper\">\n  <%= render \"contact_topics\" %>\n</div>\n\n<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h2 id=\"placement-types\">\n          Manage Case Placement Types\n        </h2>\n      </div>\n    </div>\n  </div>\n</div>\n<div class=\"tables-wrapper\">\n  <%= render \"placement_types\", placement_types: @placement_types %>\n</div>\n"
  },
  {
    "path": "app/views/case_assignments/index.html.erb",
    "content": "<h1><%= \"Editing #{@volunteer.display_name}\" %></h1>\n\n<br>\n\n<% if @volunteer.casa_cases %>\n  <table class='table' id='casa_cases'>\n    <thead>\n    <tr>\n      <th>Case Number</th>\n      <th>Transition Aged Youth</th>\n      <th>Actions</th>\n    </tr>\n    </thead>\n    <tbody>\n    <% @volunteer.case_assignments.each do |assignment| %>\n      <tr>\n        <td>\n          <span class=\"mobile-label\">Case Number</span>\n          <%= assignment.casa_case.case_number %>\n        </td>\n        <td>\n          <span class=\"mobile-label\">Transition Aged Youth</span>\n          <%= assignment.casa_case.decorate.transition_aged_youth %>\n        </td>\n        <td>\n          <%= button_to \"Unassign Volunteer\",\n                        volunteer_case_assignment_path(@volunteer, assignment),\n                        method: :delete,\n                        class: \"btn btn-danger\" %>\n        </td>\n      </tr>\n    <% end %>\n    </tbody>\n  </table>\n<% end %>\n\n<br>\n\n<div class=\"row\">\n  <div class=\"col-sm-6\">\n    <h3>Assign a New Volunteer</h3>\n\n    <%= form_with(model: CaseAssignment.new, url: volunteer_case_assignments_path) do |form| %>\n      <div class='form-group'>\n        <label for=\"case_assignment_casa_case_id\">Select a Case</label>\n        <select id=\"is-this-used-anywhere\" class='form-control select2'>\n          <% CasaCase.all.each do |casa_case| %>\n            <option value=\"<%= casa_case.id %>\"><%= casa_case.case_number %></option>\n          <% end %>\n        </select>\n      </div>\n      <%= form.submit \"Assign Case\", class: 'btn btn-primary' %>\n    <% end %>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/case_contacts/_case_contact.html.erb",
    "content": "<div class=\"container-fluid mb-1\">\n  <div class=\"card-style-1 mb-15 pt-0 pb-3 full-card\">\n    <div class=\"card-content\">\n      <div>\n        <div class=\"mt-0\">\n          <i class=\" <%= \"text-primary\" %> <%= contact.decorate.medium_icon_classes %>\"></i>\n          <div>\n            <h5 class=\"pt-0 mb-10 card-title d-flex align-items-center gap-2\">\n              <strong class=\"<%= \"text-primary\" %>\">\n                <%= \"[DELETE] \" if policy(contact).restore? && contact.deleted? %>\n                <%= contact.decorate.contact_groups %>\n              </strong>\n              <% if !contact.active? %>\n                <span class=\"badge badge-pill light-bg text-black\">Draft</span>\n              <% end %>\n              <%= link_to(\"undelete\", restore_case_contact_path(contact.id), method: :post, data: { turbo: false },\n                          class: \"btn btn-info\") if policy(contact).restore? && contact.deleted? %>\n            </h5>\n            <h6 class=\"card-subtitle mb-2 text-muted\">\n              <%= contact.decorate.contact_types %>\n              <br>\n              <%= contact.decorate.subheading %>\n            </h6>\n            <div class=\"card-text mb-2\">\n              <ul>\n                <% contact.contact_topic_answers.reject { _1.value.blank? }.each do |answer| %>\n                  <li><%= render TruncatedTextComponent.new(answer.value, label: answer.contact_topic.question) %></li>\n                <% end %>\n                <% if contact.notes %>\n                  <li><%= render TruncatedTextComponent.new(contact.notes, label: \"Additional Notes\") %></li>\n                <% end %>\n              </ul>\n            </div>\n          </div>\n        </div>\n      </div>\n      <div class=\"d-flex justify-content-between align-items-center\">\n        <% if Pundit.policy(current_user, contact).update? %>\n          <%= render \"case_contacts/followup\", contact: contact, followup: contact.requested_followup %>\n          <div class=\"mr-2\">\n            <%= link_to edit_case_contact_path(contact), class: \"text-danger\", data: { turbo: false } do %>\n              <i class=\"lni lni-pencil-alt\"></i> Edit\n            <% end %>\n          </div>\n        <% end %>\n        <div class=\"action\">\n        <% if policy(contact).destroy? && !contact.deleted? %>\n          <%= link_to case_contact_path(contact.id), class: \"main-btn btn-sm danger-btn btn-hover\", method: :delete, data: { turbo: false }  do %>\n          <i class=\"lni lni-trash-can mr-5\"></i>Delete\n        <% end %>\n        <% end %>\n        </div>\n      </div>\n    </div>\n    <h6 class=\"mb-1 text-primary ml-30 mt-3\">\n      Created by:\n      <% if policy(contact).edit? %>\n        <% if current_user.volunteer? %>\n          <%= contact.creator&.display_name %>\n        <% else %>\n          <% if contact.creator&.supervisor? %>\n            <%= link_to contact.creator&.display_name, edit_supervisor_path(contact.creator), data: { turbo: false } %>\n          <% elsif contact.creator&.casa_admin? %>\n            <%= link_to contact.creator&.display_name, edit_users_path, data: { turbo: false } %>\n          <% else %>\n            <%= link_to contact.creator&.display_name, edit_volunteer_path(contact.creator), data: { turbo: false } %>\n          <% end %>\n        <% end %>\n      <% else %>\n        <%= contact.creator&.display_name %>\n      <% end %>\n    </h6>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/case_contacts/_confirm_note_content_dialog.html.erb",
    "content": "<div class=\"modal fade\" id=\"confirm-submit\" tabindex=\"-1\" role=\"dialog\" aria-labelledby=\"myModalLabel\" aria-hidden=\"true\">\n  <div class=\"modal-dialog\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <strong>Confirm Note Content</strong>\n      </div>\n      <div class=\"modal-body\">\n        Please double check your notes to ensure they don't contain any identifying details about your youth or anyone else.\n        <div class=\"mt-3\">\n        <h6>Note</h6>\n        <div id=\"note-content\"></div>\n        </div>\n      </div>\n\n      <div class=\"modal-footer justify-content-between\">\n        <button type=\"button\" class=\"main-btn btn-sm secondary-btn-outline btn-hover mr-10\" data-bs-dismiss=\"modal\" id=\"modal-case-contact-cancel\">\n          Go Back to Form\n        </button>\n        <%= form.submit \"Continue Submitting\", class: \"btn-sm main-btn primary-btn btn-hover\", id: \"modal-case-contact-submit\" %>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/case_contacts/_followup.html.erb",
    "content": "<div>\n  <% if followup %>\n    <%= button_to resolve_followup_path(followup), method: :patch, class: \"main-btn btn-sm success-btn btn-hover\", id:\"resolve\", data: { turbo: false } do %>\n      <i class=\"bi bi-calendar\"></i>Resolve Reminder\n    <% end %>\n  <% else %>\n    <button type=\"button\"\n            class=\"followup-button main-btn btn-sm primary-btn-outline btn-hover\"\n            id=\"followup-button-<%= contact.id %>\"\n            data-turbo=\"false\">\n      <i class=\"lni lni-calendar mr-5\"></i>\n      Make Reminder\n    </button>\n  <% end %>\n</div>\n"
  },
  {
    "path": "app/views/case_contacts/case_contacts_new_design/index.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"d-flex justify-content-between align-items-center flex-wrap gap-2 mb-30\">\n    <h1>Case Contacts</h1>\n    <%= link_to new_case_contact_path, class: \"main-btn primary-btn btn-sm btn-hover\" do %>\n      <i class=\"lni lni-plus mr-10\" aria-hidden=\"true\"></i>\n      New Case Contact\n    <% end %>\n  </div>\n</div>\n\n<div class=\"card-style mb-30\">\n  <div class=\"table-wrapper\">\n    <table\n      id=\"case_contacts\"\n      class=\"table\"\n      data-source=\"<%= datatable_case_contacts_new_design_path format: :json %>\">\n      <thead>\n      <tr>\n        <th data-orderable=\"false\"></th>\n        <th data-orderable=\"false\"></th>\n        <th><h6>Date</h6></th>\n        <th><h6>Case</h6></th>\n        <th><h6>Relationship</h6></th>\n        <th><h6>Medium</h6></th>\n        <th><h6>Created By</h6></th>\n        <th><h6>Contacted</h6></th>\n        <th><h6>Topics</h6></th>\n        <th><h6>Draft</h6></th>\n        <th data-orderable=\"false\"></th>\n      </tr>\n      <!-- end table row-->\n      </thead>\n      <tbody>\n        <% @presenter.case_contacts.each do |casa_case_id, case_contacts| %>\n          <% case_contacts.each do |case_contact| %>\n            <tr data-testid=\"case_contact-row\">\n              <td>\n                <i class='fas fa-bell'></i>\n              </td>\n              <td>\n                <i class=\"fa-solid fa-chevron-down\"></i>\n              </td>\n              <td>\n                <%= I18n.l(case_contact[:occurred_at], format: :full) if case_contact[:occurred_at].present? %>\n              </td>\n              <td>\n                <%= @presenter.display_case_number(casa_case_id) %>\n              </td>\n              <td>\n                <%= case_contact.decorate.contact_types_comma_separated %>\n              </td>\n              <td>\n                <%= case_contact.medium_type&.capitalize %>\n              </td>\n              <td>\n                <% if policy(case_contact).edit? %>\n                  <% if current_user.volunteer? %>\n                    <%= case_contact.creator&.display_name %>\n                  <% else %>\n                    <% if case_contact.creator&.supervisor? %>\n                      <%= link_to case_contact.creator&.display_name, edit_supervisor_path(case_contact.creator), data: { turbo: false } %>\n                    <% elsif case_contact.creator&.casa_admin? %>\n                      <%= link_to case_contact.creator&.display_name, edit_users_path, data: { turbo: false } %>\n                    <% else %>\n                      <%= link_to case_contact.creator&.display_name, edit_volunteer_path(case_contact.creator), data: { turbo: false } %>\n                    <% end %>\n                  <% end %>\n                <% else %>\n                  <%= case_contact.creator&.display_name %>\n                <% end %>\n              </td>\n              <td>\n                <% if case_contact.contact_made %>\n                  <i class=\"lni lni-checkmark-circle\" style=\"color: green;\"></i>\n                <% else %>\n                  <i class=\"lni lni-cross-circle\" style=\"color: orange;\"></i>\n                <% end %>\n                <%= \"(#{\"%02d:%02d\" % [case_contact.duration_minutes / 60, case_contact.duration_minutes % 60]})\" if case_contact.duration_minutes %>\n              </td>\n              <td>\n                <%= case_contact.contact_topics.map(&:question).join(\" | \") %>\n              </td>\n              <td>\n                <% if !case_contact.active? %>\n                  <span class=\"badge badge-pill light-bg text-black\" data-testid=\"draft-badge\">\n                    Draft\n                  </span>\n                <% end %>\n              </td>\n              <td>\n                <i class=\"fas fa-ellipsis-v\"></i>\n              </td>\n            </tr>\n          <% end %>\n        <% end %>\n      </tbody>\n    </table>\n    <!-- end table -->\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/case_contacts/drafts.html.erb",
    "content": "<%= render partial: \"case_contacts/case_contact\", collection: @case_contacts, as: :contact %>\n"
  },
  {
    "path": "app/views/case_contacts/form/_contact_topic_answer.html.erb",
    "content": "<div class=\"nested-form-wrapper mb-2\"\n  data-new-record=\"<%= form.object.new_record? %>\"\n  data-casa-nested-form-target=\"wrapper\"\n  data-child-index=\"<%= form.options[:child_index] %>\">\n  <div class=\"row gy-3\">\n    <div class=\"col-12 col-md-4\">\n      <%= form.label :contact_topic_id, \"Discussion Topic\", class: \"form-label\" %>\n      <%= form.select(:contact_topic_id,\n        select_options,\n        {include_blank: \"Select a discussion topic\"},\n        class: [\"form-select\", \"contact-topic-id-select\"],\n        data: {\n          action: \"change->case-contact-form#onContactTopicSelect\",\n          case_contact_form_target: \"contactTopicSelect\"\n        }\n      ) %>\n    </div>\n    <div class=\"col-12 col-md\">\n      <%= form.label :value, \"Discussion Notes\", class: \"form-label\" %>\n      <%= form.text_area(\n        :value,\n        data: { action: \"input->autosave#save\" },\n        rows: 3,\n        class: [\"form-control\", \"contact-topic-answer-input\"],\n        placeholder: \"Enter discussion notes here to be included in the court report. Refer to individuals by role, not by name.\"\n      ) %>\n    </div>\n    <div class=\"col-12 col-lg-auto d-flex align-items-end justify-content-end\">\n      <button type=\"button\"\n        data-action=\"casa-nested-form#confirmDestroyAndRemove\"\n        class=\"btn btn-sm danger-btn-outline btn-hover\">\n        Delete\n      </button>\n    </div>\n  </div>\n  <%= form.hidden_field(:id, value: form.object.id) %>\n  <%= form.hidden_field :_destroy %>\n</div>\n"
  },
  {
    "path": "app/views/case_contacts/form/_contact_types.html.erb",
    "content": "<p>Choose from the available options below by searching or selecting from the dropdown menu.</p>\n<div id=\"contact-type-id-selector\">\n  <%= render(Form::MultipleSelectComponent.new(\n    form: form,\n    name: :contact_type_ids,\n    options: options.decorate.map { |ct| ct.hash_for_multi_select_with_cases(casa_cases&.pluck(:id)) },\n    selected_items: selected_items,\n    render_option_subtext: true,\n    show_all_option: true,\n  )) %>\n</div>\n"
  },
  {
    "path": "app/views/case_contacts/form/details.html.erb",
    "content": "<div class=\"title-wrapper\">\n  <h1 class=\"title my-4\">\n    <%= @case_contact.decorate.form_title %>\n  </h1>\n</div>\n\n<div data-controller=\"autosave\"\n  data-autosave-delay-value=\"2000\"\n  data-autosave-good-alert-class=\"text-muted\"\n  data-autosave-bad-alert-class=\"text-danger\">\n  <%= form_with(\n    model: @case_contact,\n    url: wizard_path(nil, case_contact_id: @case_contact.id),\n    id: \"case-contact-form\",\n    data: {\n      controller: \"case-contact-form\",\n      \"autosave-target\": \"form\",\n    },\n    local: true\n  ) do |form| %>\n\n    <%= render \"/shared/error_messages\", resource: @case_contact %>\n\n    <!-- DETAILS -->\n    <section id=\"contact-form-details\">\n      <h2>Details</h2>\n\n      <div class=\"row\">\n        <!-- RELEVANT CASES -->\n        <div class=\"col\">\n          <h3>\n            <label class=\"form-label\" for=\"case_contact_draft_case_ids\">\n              Relevant Case(s)<span class=\"red-letter\">*</span>\n            </label>\n          </h3>\n          <div id=\"draft-case-id-selector\">\n            <%= render(Form::MultipleSelectComponent.new(\n              form: form,\n              name: :draft_case_ids,\n              options: @casa_cases.decorate.map { |casa_case| casa_case.hash_for_multi_select },\n              selected_items: @case_contact.draft_case_ids,\n              render_option_subtext: current_user.supervisor?,\n              placeholder_term: \"case(s)\"\n            )) %>\n          </div>\n        </div>\n\n        <!--DATE-->\n        <div class=\"col\">\n          <h3>\n            <%= form.label :occurred_at, class: \"form-label\" do %>\n              Contact Date<span class=\"red-letter\">*</span>\n            <% end %>\n          </h3>\n          <% min_date = CaseContact::MINIMUM_DATE %>\n          <% current_date = Time.zone.today %>\n          <%= form.date_field(:occurred_at,\n            required: true,\n            max: (current_date + 1.day).to_fs(:iso8601),\n            min: min_date.to_fs(:iso8601),\n            class: \"form-control\") %>\n        </div>\n      </div>\n\n      <!--CONTACT TYPE-->\n      <h3 class=\"mb-2\">\n        Contact Type(s)<span class=\"red-letter\">*</span>\n      </h3>\n      <div id=\"contact-form-types\" class=\"mb-3\">\n        <% casa_case_ids = @casa_cases.pluck(:id) %>\n        <% @grouped_contact_types.each do |group_name, contact_types| %>\n          <fieldset class=\"form-group\">\n            <legend><%= group_name %></legend>\n            <%= form.collection_check_boxes(:contact_type_ids, contact_types, :id, :name) do |b| %>\n              <div class=\"form-check\">\n                <%= b.check_box(class: [\"form-check-input\", \"contact-form-type-checkbox\"]) %>\n                <%= b.label(class: \"form-check-label\") %>\n                <small>\n                  <%= b.object.last_time_used_with_cases(casa_case_ids) %>\n                </small>\n              </div>\n            <% end %>\n          </fieldset>\n        <% end %>\n      </div>\n\n      <!--CONTACT MADE-->\n      <div class=\"form-check mb-20\">\n        <%= form.check_box :contact_made, class: \"form-check-input\" %>\n        <%= form.label :contact_made, class: \"form-check-label\" do %>\n          <h3>Contact was made</h3>\n        <% end %>\n      </div>\n\n      <!--MEDIUM-->\n      <fieldset id=\"contact-medium\" class=\"form-group mb-20\">\n        <legend>\n          <h3>Contact Medium<span class=\"red-letter\">*</span></h3>\n        </legend>\n        <%= form.collection_radio_buttons(:medium_type, contact_mediums, 'value', 'label') do |b| %>\n          <div class=\"form-check\">\n            <%= b.radio_button(class: \"form-check-input\") %>\n            <%= b.label(class: \"form-check-label\") %>\n          </div>\n        <% end %>\n      </fieldset>\n\n      <!--DURATION-->\n      <h3 class=\"mb-3\">Contact Duration</h3>\n      <%= render(Form::HourMinuteDurationComponent.new(\n        form: form, hour_value: duration_hours(@case_contact), minute_value: duration_minutes(@case_contact))\n      ) %>\n    </section>\n\n    <!--NOTES (CONTACT TOPIC ANSWERS)-->\n    <section id=\"contact-form-notes\"\n      data-controller=\"casa-nested-form\"\n      data-casa-nested-form-route-value=\"/contact_topic_answers\"\n      data-casa-nested-form-parent-name-value=\"case_contact\"\n      data-casa-nested-form-parent-id-value=\"<%= @case_contact.id %>\"\n      data-casa-nested-form-model-name-value=\"contact_topic_answer\">\n      <h2>Notes</h2>\n\n      <% if @contact_topics.empty? %>\n        <p class=\"mb-5\"><small>\n        <% if current_user.casa_admin? %>\n          Visit <%= link_to \"Manage Case Contact Topics\", edit_casa_org_path(current_organization, anchor: \"case-contact-topics\") %> to set your organization Court report topics.\n        <% else %>\n          Your organization has not set any Court Report Topics yet. Contact your admin to learn more.\n        <% end %>\n        </small></p>\n      <% else %>\n        <% select_options = @contact_topics.map { |topic| [topic.question, topic.id] } %>\n\n        <template data-casa-nested-form-target=\"template\">\n          <%= form.fields_for :contact_topic_answers, ContactTopicAnswer.new, child_index: \"NEW_RECORD\" do |topic_fields| %>\n            <%= render(\"contact_topic_answer\", form: topic_fields, select_options:) %>\n          <% end %>\n        </template>\n\n        <% if form.object.contact_topic_answers.any? %>\n          <%= form.fields_for :contact_topic_answers do |topic_fields| %>\n            <%= render(\"contact_topic_answer\", form: topic_fields, select_options:) %>\n          <% end %>\n        <% end %>\n\n        <div data-casa-nested-form-target=\"target\"></div>\n\n        <button type=\"button\"\n          class=\"main-btn secondary-btn  btn-hover\"\n          data-case-contact-form-target=\"addTopicButton\"\n          data-action=\"casa-nested-form#addAndCreate\">\n          + Add Another Discussion Topic\n        </button>\n      <% end %>\n\n      <% if form.object.notes.present? || @contact_topics.empty? %>\n        <div class=\"col mb-3\">\n          <%= form.label :notes, \"Additional Notes\", class: \"form-label\" %>\n          <%= form.text_area(\n            :notes,\n            data: { action: \"input->autosave#save\" },\n            rows: 5,\n            class: [\"form-control\"],\n          ) %>\n        </div>\n      <% end %>\n\n      <small role=\"alert\"\n        class=\"d-block invisible\"\n        data-autosave-target=\"alert alert-light\">\n        No changes have been saved.\n      </small>\n    </section>\n\n    <% org_driving_reimbursement = current_organization.show_driving_reimbursement %>\n    <% show_driving_reimbursement = org_driving_reimbursement && show_volunteer_reimbursement(@casa_cases) %>\n    <% org_additional_expenses = current_organization.additional_expenses_enabled %>\n    <% show_additional_expenses = org_additional_expenses && Pundit.policy(current_user, @case_contact).additional_expenses_allowed? %>\n    <% if show_driving_reimbursement %>\n      <!-- REIMBURSMENT -->\n      <section id=\"contact-form-reimbursement\">\n        <h2>Reimbursement</h2>\n        <% if Flipper.enabled?(:reimbursement_warning, current_organization) %>\n          <article class=\"card-style-1 p-3 mb-10\" style=\"background-color: #fcab553d;\">\n            <h4 class=\"mb-3\"><label>Volunteers are eligible to be reimbursed for case-related travel.</label></h4>\n            <span> Volunteers are reimbursed at the federal mileage rate.</span>\n            <br>\n            <span>Please note that there is a $35.00 per month cap per volunteer for your mileage.</span>\n            <span>We aim to mail your reimbursement to you via check within 14-28 business days of your request for reimbursement.</span>\n          </article>\n        <% end %>\n\n        <div class=\"form-check mb-3\">\n          <%= form.check_box(:want_driving_reimbursement,\n            class: \"form-check-input\",\n            data: {\n              case_contact_form_target: \"wantDrivingReimbursement\",\n              action: \"click->case-contact-form#setReimbursementFormVisibility\",\n            }\n          ) %>\n          <%= form.label :want_driving_reimbursement, class: \"form-check-label\" do %>\n            <h3>Request travel or other reimbursement</h3>\n          <% end %>\n        </div>\n\n        <!-- DRIVING REIMBURSEMENT -->\n        <div data-case-contact-form-target=\"reimbursementForm\" class=\"d-none\">\n          <div class=\"row py-2\">\n            <div class=\"col-md-4\">\n              <%= form.label :miles_driven, class: \"form-label\" do %>\n                Total Miles Driven<span class=\"red-letter\">*</span>\n              <% end %>\n              <%= form.number_field(:miles_driven,\n                min: \"0\", max: 10000, placeholder: \"0\",\n                class: \"form-control\",\n                data: { case_contact_form_target: \"milesDriven\" },\n              ) %>\n            </div>\n\n            <div class=\"col\">\n              <%= form.label :volunteer_address, class: \"form-label\" do %>\n                Mailing Address for Reimbursement Check<span class=\"red-letter\">*</span>\n              <% end %>\n              <%= form.text_area(:volunteer_address,\n                value: @case_contact.decorate.address_of_volunteer,\n                disabled: @case_contact.address_field_disabled?,\n                placeholder: \"Enter mailing address\",\n                class: \"form-control\",\n                data: { case_contact_form_target: \"volunteerAddress\" },\n              ) %>\n              <% if @case_contact.address_field_disabled? %>\n                <small class=\"text-danger\">\n                  <%= @case_contact.decorate.ambiguous_volunteer_address_message %>\n                </small>\n              <% end %>\n            </div>\n          </div>\n\n          <% if show_additional_expenses %>\n            <!-- ADDITIONAL EXPENSES -->\n            <div id=\"contact-form-expenses\"\n              data-controller=\"casa-nested-form\"\n              data-casa-nested-form-route-value=\"/additional_expenses\"\n              data-casa-nested-form-parent-name-value=\"case_contact\"\n              data-casa-nested-form-parent-id-value=\"<%= @case_contact.id %>\"\n              data-casa-nested-form-model-name-value=\"additional_expense\">\n\n              <template data-casa-nested-form-target=\"template\">\n                <%= form.fields_for :additional_expenses, AdditionalExpense.new, child_index: \"NEW_RECORD\" do |expense_fields| %>\n                  <%= render \"shared/additional_expense_form\", form: expense_fields %>\n                <% end %>\n              </template>\n\n              <% if form.object.additional_expenses.any? %>\n                <%= form.fields_for :additional_expenses do |expense_fields| %>\n                  <%= render \"shared/additional_expense_form\", form: expense_fields %>\n                <% end %>\n              <% end %>\n\n              <div data-casa-nested-form-target=\"target\"></div>\n\n              <button type=\"button\" class=\"btn btn-sm btn-link\"\n                data-action=\"casa-nested-form#addAndCreate\">\n                + Add Another Expense\n              </button>\n            </div>\n          <% end %>\n        </div>\n\n        <small role=\"alert\"\n          class=\"d-block invisible\"\n          data-autosave-target=\"alert alert-light\">\n          No changes have been saved.\n        </small>\n      </section>\n    <% end %>\n\n    <div id=\"contact-form-action-buttons\" class=\"actions mb-10\">\n      <div class=\"pt-1\">\n        <%= form.fields_for :metadata do |metadata_form| %>\n          <%= metadata_form.check_box :create_another, class: \"form-check-input\" %>\n          <%= metadata_form.label :create_another, class: \"form-check-label d-inline align-bottom\" do %>\n            Create Another\n            <i class=\"lni lni-question-circle\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"Start a new contact for the same case(s) after submitting.\">\n            </i>\n          <% end %>\n        <% end %>\n      </div>\n\n      <%= button_tag type: \"submit\", class: \"btn-sm main-btn primary-btn btn-hover\" do %>\n        <i class=\"lni lni-checkmark-circle mr-5\"></i>Submit\n      <% end %>\n    </div>\n  <% end %>\n</div>\n"
  },
  {
    "path": "app/views/case_contacts/index.html.erb",
    "content": "<div class=\"row\">\n  <%= render \"casa_cases/thank_you_modal\" %>\n  <div class=\"title-wrapper pt-30\">\n    <div class=\"row align-items-center\">\n      <div class=\"col-md-6\">\n        <div class=\"title d-flex justify-content-between\">\n          <h1 class=\"mb-10\">Case Contacts</h1>\n          <%= link_to new_case_contact_path, class: \"main-btn secondary-btn btn-sm btn-hover\" do %>\n            <i class=\"lni lni-plus mr-10\"></i>\n            New Case Contact\n          <% end %>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n\n<%= form_for_filterrific @filterrific, url: case_contacts_path, html: {class: \"my-4 filter-form\"}, remote: true,\n  data: {turbo: true, turbo_frame: :case_contacts, turbo_action: :advance } do |f| %>\n  <%= hidden_field_tag 'casa_case_id', params[:casa_case_id] %>\n\n  <div class=\"card-style\">\n    <div class=\"card-content\">\n      <div class=\"d-flex justify-content-between align-items-center\">\n        <div class=\"h4 btn-lg\">Filter by</div>\n        <button href=\"#\"\n                class=\"btn btn-lg btn-link\"\n                type=\"button\"\n                data-bs-toggle=\"collapse\"\n                data-bs-target=\"#filter-card-body\"\n                aria-expanded=\"<%= expand_filters? %>>\"\n                aria-controls=\"filter-card-body\">\n          <div class=\"h4\">Expand / Hide</div>\n        </button>\n      </div>\n      <div class=\"d-flex flex-wrap justify-content-between align-items-end gap-3 mb-4\">\n        <div class=\"d-flex flex-column gap-3\">\n          <div class=\"select-style-1 mb-0\">\n            <%= f.label :sorted_by %>\n            <div class=\"select-position\">\n              <%= f.select(:sorted_by, @filterrific.select_options[:sorted_by], {}, {class: \"filter-input\"}) %>\n            </div>\n          </div>\n          <div class=\"form-check checkbox-style\">\n            <%= f.check_box :no_drafts, class: \"form-check-input case-contact-contact-type filter-input\" %>\n            <label class=\"form-check-label\" for=\"filterrific_no_drafts\">Hide drafts</label>\n          </div>\n        </div>\n        <div>\n          <%= link_to(\"Reset filters\", reset_filterrific_url,\n            class: \"btn-sm main-btn dark-btn-outline btn-hove\",\n            data: { turbo: true, turbo_frame: :case_contacts, turbo_action: :advance }) %>\n        </div>\n      </div>\n    </div>\n\n    <% collapse_class = expand_filters? ? \"collapse show\" : \"collapse\" %>\n    <div class=\"card-content mb-10 ml-20 <%= collapse_class %>\" id=\"filter-card-body\">\n      <div class=\"row\">\n        <div class=\"col-12\">\n          <h3 class=\"mt-10\"><label>Date of contact</label></h3>\n        </div>\n        <div class=\"col-sm-6 input-style-1\">\n          <%= f.label \"Starting from\", for: \"filterrific_occurred_starting_at\" %>\n          <%= f.date_field(:occurred_starting_at, class: \"filter-input\") %>\n        </div>\n        <div class=\"col-sm-6 input-style-1\">\n          <%= f.label \"Ending at\", for: \"filterrific_occurred_ending_at\" %>\n          <%= f.date_field(:occurred_ending_at, class: \"filter-input\") %>\n        </div>\n      </div>\n      <div class=\"row mb-4\">\n        <div class=\"col-12\">\n          <h3 class=\"mb-4\"><label>Contact types</label></h3>\n          <div id=\"contact-type-form\" class=\"\">\n            <div class=\"row\">\n              <% @current_organization_groups.each do |group| %>\n                <div class=\"col-md-4 justify-content-start pb-5\">\n                  <h5 class=\"mb-1\"> <%= group.name %> </h5>\n                  <% group.contact_types.each do |contact_type| %>\n                    <div class=\"form-check checkbox-style\">\n                      <%=\n                      f.check_box :contact_type,\n                        {multiple: true, class: \"form-check-input case-contact-contact-type filter-input\"},\n                        contact_type.id,\n                        nil\n                      %>\n                      <label class=\"form-check-label\" for=\"filterrific_contact_type_<%= contact_type.id %>\">\n                        <%= contact_type.name %>\n                      </label>\n                    </div>\n                  <% end %>\n                </div>\n              <% end %>\n            </div>\n          </div>\n        </div>\n      </div>\n      <div class=\"row align-items-end  mb-10\">\n        <div class=\"col-12\">\n          <h3>Other filters</h3>\n        </div>\n        <div class=\"col-md-6 select-style-1 pr-5\">\n          <%= f.label :contact_medium %>\n          <div class=\"select-position\">\n            <%= f.select(:contact_medium, options_from_collection_for_select(contact_mediums, \"value\", \"label\"),\n                                        {include_blank: \"Display all\", class: \"filter-input\"}) %>\n          </div>\n        </div>\n        <div class=\"col-md-3 select-style-1 pr-5\">\n          <%= f.label :want_driving_reimbursement %>\n          <div class=\"select-position\">\n            <%= f.select(:want_driving_reimbursement, @presenter.boolean_select_options,\n                                                    {include_blank: \"Display all\", class: \"filter-input\"}) %>\n          </div>\n        </div>\n        <div class=\"col-md-3 select-style-1 pr-5\">\n          <%= f.label :contact_made %>\n          <div class=\"select-position\">\n            <%= f.select(:contact_made, @presenter.boolean_select_options,\n                                      {include_blank: \"Display all\", class: \"filter-input\"}) %>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n<% end %>\n\n<%= turbo_frame_tag :case_contacts do %>\n  <% @presenter.case_contacts.each do |casa_case_id, data| %>\n    <div class=\"card-style-1 mb-5\">\n      <div class=\"card-content\">\n        <h3 class=\"mb-10\"><%= @presenter.display_case_number(casa_case_id) %></h3>\n        <%= render partial: \"case_contacts/case_contact\", collection: data, as: :contact %>\n      </div>\n    </div>\n  <% end %>\n  <% if @presenter.case_contacts.empty? %>\n    <% if params.key?(:casa_case_id) %>\n      <div class=\"card-style-1 mb-5\">\n        <div class=\"card-content\">\n          <h3 class=\"mb-10\"><%= @presenter.display_case_number(params[:casa_case_id].to_i) %></h3>\n          <%= params[:filterrific] ?\n              \"No case contacts have been found.\" :\n              \"You have no case contacts for this case. \\\n        Please click New Case Contact button above to create a case contact for your youth!\" %>\n        </div>\n      </div>\n    <% else %>\n      <div class=\"card-style-1\">\n        <div class=\"card-content\">\n          <%= params[:filterrific] ?\n              \"No case contacts have been found.\" :\n              \"You have no case contacts for this case. \\\n        Please click New Case Contact button above to create a case contact for your youth!\" %>\n        </div>\n      </div>\n    <% end %>\n  <% end %>\n  <%== pagy_bootstrap_nav(@pagy) %>\n<% end %>\n"
  },
  {
    "path": "app/views/case_court_reports/_generate_docx.html.erb",
    "content": "<%= form_with url: generate_case_court_reports_path, local: false do |form| %>\n  <% id = \"generate-docx-report-modal\" %>\n  <%= render(Modal::OpenButtonComponent.new(target: id, klass: \"btn generate-report-button\")) do %>\n    <svg viewBox=\"0 0 40 50\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n      <title>Word Document Logo</title>\n      <path d=\"M25 0H5C3.67392 0 2.40215 0.526784 1.46447 1.46447C0.526784 2.40215 0 3.67392 0 5V45C0 46.3261 0.526784 47.5979 1.46447 48.5355C2.40215 49.4732 3.67392 50 5 50H35C36.3261 50 37.5979 49.4732 38.5355 48.5355C39.4732 47.5979 40 46.3261 40 45V15L25 0ZM28 45H24.5L20 28L15.5 45H12L6.5 22.5H10.25L13.75 39.5L18.25 22.5H21.5L26 39.5L29.5 22.5H33.25L28 45ZM22.5 17.5V3.75L36.25 17.5H22.5Z\" fill=\"#4A6CF7\" />\n    </svg>\n    <div>\n      <h3 class=\"content-1\">Download Court Report as .docx</h3>\n      <p class=\"content-2\">\n      The Court Report is pre-filled with information for your case. You can select among currently active cases assigned to you. The document is in Microsoft Word format (.docx).\n      </p>\n    </div>\n  <% end %>\n  <%= render(Modal::GroupComponent.new(id: id)) do |component| %>\n    <% component.with_header(text: \"Download Court Report as a .docx\", id: id, klass: \"content-1\") %>\n    <% component.with_body do %>\n      <div class=\"docx-report__modal-body\">\n      <p class=\"content-3 md-10\">\n      To download a court report, choose an active case and specify the date range.\n      </p>\n      <div class=\"input-style-1\" id=\"case_select_body\">\n        <%= form.label :case_selection, \"Case\" %>\n        <% select_options = @assigned_cases.map { |casa_case| casa_case.decorate.court_report_select_option } %>\n\n        <% show_search = !current_user.volunteer? %>\n\n        <% select_case_prompt = show_search ? \"Search by volunteer name or case number\" : \"Select case number\" %>\n        <% select2_class = show_search ? \" select2\" : \"\" %>\n        <div class=\"select-wrapper\">\n          <%= select_tag :case_number,\n            options_for_select(select_options),\n            prompt: select_case_prompt,\n            include_blank: false,\n            id: \"case-selection\",\n            class: \"custom-select#{select2_class}\",\n            required: true,\n            data: { dropdown_parent: \"##{id}\", width: \"100%\" } %>\n          <p class=\"select-required-error text-danger d-none\">Case selection is required.</p>\n        </div>\n      <%= form.hidden_field :time_zone, id: \"user-time-zone\" %>\n      </div>\n      <div class=\"dates-container\">\n        <div class=\"field form-group mb-20\">\n          <h6><label class=\"form-label\" for=\"start_date\">Starting From</label></h6>\n          <%= form.text_field :start_date,\n            value: Time.zone.now.strftime(::DateHelper::RUBY_MONTH_DAY_YEAR_FORMAT),\n            data: { provide: \"datepicker\",\n                    date_format: ::DateHelper::JQUERY_MONTH_DAY_YEAR_FORMAT },\n                    class: \"form-control\" %>\n        </div>\n\n        <div class=\"field form-group mb-20\">\n          <h6><label class=\"form-label\" for=\"end_date\">Ending At</label></h6>\n          <%= form.text_field :end_date,\n            value: Time.zone.now.strftime(::DateHelper::RUBY_MONTH_DAY_YEAR_FORMAT),\n            data: { provide: \"datepicker\",\n                    date_format: ::DateHelper::JQUERY_MONTH_DAY_YEAR_FORMAT },\n                    class: \"form-control\" %>\n        </div>\n      </div>\n      </div>\n    <% end %>\n    <% component.with_footer do %>\n        <%= button_tag type: :submit,\n          data: {\n            button_name: \"Generate Report\"\n          },\n          id: \"btnGenerateReport\",\n          class: \"main-btn primary-btn btn-hover btn-sm\",\n          onclick: \"setTimeZone()\" do %>\n          <i class=\"lni lni-download mr-10\"></i>\n          <i id=\"spinner\" class='fas fa-spin d-none mr-10'>⏳</i>\n          Generate Report\n        <% end %>\n    <% end %>\n  <% end %>\n<% end %>\n"
  },
  {
    "path": "app/views/case_court_reports/index.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h1>Generate Reports</h1>\n      </div>\n    </div>\n  </div>\n</div>\n<div class=\"tables-wrapper\">\n  <div class=\"row\">\n    <div class=\"col-lg-12\">\n      <div class=\"card-style mb-30\">\n        <h6 class=\"mb-10\">Court Reports</h6>\n        <%= render \"case_court_reports/generate_docx\" %>\n      </div>\n    </div>\n  </div>\n</div>\n\n<script>\n  const ELEMENTS = {\n    'caseSelect': '#case-selection',\n    'generateBtn': '#btnGenerateReport',\n  }\n  const showBtn = el => el.classList.remove('d-none')\n  const enableBtn = (el) => {\n    el.disabled = false\n    el.classList.remove('disabled')\n    el.removeAttribute('aria-disabled')\n  }\n\n  const handleSelect = (e) => {\n    const selectEl = e.target\n    const generateBtn = selectEl.form.querySelector(ELEMENTS.generateBtn)\n\n    // when selecting a case, reset buttons to initial state\n    enableBtn(generateBtn)\n    showBtn(generateBtn)\n  }\n\n  const bindElements = () => {\n    const caseSelectEl = document.querySelector(ELEMENTS.caseSelect)\n\n    if (caseSelectEl)\n      caseSelectEl.addEventListener('change', handleSelect)\n  }\n\n  const setTimeZone = () => {\n    const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone\n    document.getElementById(\"user-time-zone\").value = timeZone\n  }\n\n  window.onload = function () {\n    bindElements()\n  }\n</script>\n"
  },
  {
    "path": "app/views/case_groups/_form.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h1>\n          <% if case_group.persisted? %>\n            Edit Case Group\n          <% else %>\n            New Case Group\n          <% end %>\n        </h1>\n      </div>\n    </div>\n  </div>\n</div>\n<div class=\"card-style mb-30\">\n  <%= form_with model: case_group do |form| %>\n    <%= render \"shared/error_messages\", resource: case_group %>\n\n    <div class=\"input-style-1\">\n      <%= form.label :name %>\n      <%= form.text_field :name, required: true %>\n    </div>\n\n    <div class=\"input-style-1\" data-controller=\"multiple-select\">\n      <%= form.label :casa_case_ids, 'Cases' %>\n      <%= form.select(\n        :casa_case_ids,\n        current_organization.casa_cases.map { |casa_case| [sanitize(\"#{casa_case.case_number} - #{volunteer_badge(casa_case, current_user)}\"), casa_case.id] },\n        { include_hidden: false, autocomplete: \"off\" },\n        { class: \"form-control\", multiple: true,\n          data: { \"multiple-select-target\": \"select\" }\n        }\n      ) %>\n    </div>\n\n    <div class=\"actions mb-10\">\n      <%= button_tag(type: \"submit\", class: \"btn-sm main-btn primary-btn btn-hover\") do %>\n        <i class=\"lni lni-checkmark-circle mr-10\"></i> Submit\n      <% end %>\n    </div>\n  <% end %>\n</div>\n"
  },
  {
    "path": "app/views/case_groups/edit.html.erb",
    "content": "<%= render 'case_groups/form', case_group: @case_group %>\n"
  },
  {
    "path": "app/views/case_groups/index.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h1>Case Groups</h1>\n      </div>\n    </div>\n  </div>\n</div>\n<div class=\"tables-wrapper\">\n  <div class=\"row\">\n    <div class=\"col-lg-12\">\n      <div class=\"card-style mb-30\">\n        <h3 class=\"mb-10\">Case Groups</h3>\n        <p class=\"text-sm mb-20\">\n          Case groups are used to bulk create court dates for all cases in a group. For example, if siblings attend the same court data you can create a case group for them and then use the\n          <%= link_to 'Bulk Court Date', new_bulk_court_date_path %>\n          form to create a court date for all of them.\n        </p>\n        <%= link_to new_case_group_path, class: \"btn-sm main-btn primary-btn btn-hover\" do %>\n          <i class=\"lni lni-plus mr-10\"></i>\n          New Case Group\n        <% end %>\n        <div class=\"table-wrapper table-responsive\">\n          <table class=\"table striped-table\" id=\"case-groups\">\n            <thead>\n            <tr>\n              <th>Name</th>\n              <th>Case Numbers</th>\n              <th>Updated At</th>\n              <th>Actions</th>\n            </tr>\n            </thead>\n            <tbody>\n            <% @case_groups.each do |case_group| %>\n              <tr>\n                <td class=\"min-width\">\n                  <%= case_group.name %>\n                </td>\n                <td class=\"min-width\">\n                  <ul>\n                    <% case_group.casa_cases.each do |casa_case| %>\n                      <li>\n                        <%= link_to casa_case.case_number, casa_case_path(casa_case) %>\n                        <%= volunteer_badge(casa_case, current_user) %>\n                      </li>\n                    <% end %>\n                  </ul>\n                </td>\n                <td class=\"min-width\">\n                  <%= time_ago_in_words(case_group.updated_at) %> ago\n                </td>\n                <td>\n                  <%= link_to edit_case_group_path(case_group) do %>\n                    <div class=\"action\">\n                      <button class=\"text-danger\">\n                        <i class=\"lni lni-pencil-alt\"></i> Edit\n                      </button>\n                    </div>\n                  <% end %>\n                  <%= link_to 'Delete', case_group_path(case_group), class: 'btn btn-danger', method: :delete, data: { confirm: \"Are you sure that you want to delete this case group?\" } %>\n                </td>\n              </tr>\n            <% end %>\n            </tbody>\n          </table>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/case_groups/new.html.erb",
    "content": "<%= render 'case_groups/form', case_group: @case_group %>\n"
  },
  {
    "path": "app/views/checklist_items/_form.html.erb",
    "content": "<div class=\"pt-30\">\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <div class=\"mb-30\">\n        <h1><%= title %></h1>\n      </div>\n    </div>\n  </div>\n</div>\n\n<!-- ========== card start ========== -->\n<div class=\"card-style\">\n  <%= form_with(model: [hearing_type, checklist_item], local: true) do |form| %>\n    <div class=\"danger-alert\">\n      <%= render \"/shared/error_messages\", resource: checklist_item %>\n    </div>\n    <div class=\"input-style-1\">\n      <%= form.label :category %>\n      <%= form.text_field :category, class: \"form-control\", required: true %>\n    </div>\n    <div class=\"select-style-1\">\n      <%= form.label :description %>\n      <%= form.text_field :description, class: \"form-control\", required: true %>\n    </div>\n    <div class=\"checkbox-style mb-20\">\n      <%= form.check_box :mandatory, class: 'form-check-input' %>\n      <%= form.label :mandatory, \"Mandatory\" %>\n    </div>\n    <%= button_tag(type: \"submit\", class: \"btn-sm main-btn primary-btn btn-hover\") do %>\n      <i class=\"lni lni-checkmark-circle mr-10\"></i> Submit\n    <% end %>\n  <% end %>\n</div>\n<!-- card end -->\n"
  },
  {
    "path": "app/views/checklist_items/edit.html.erb",
    "content": "<%= render partial: \"form\",\nlocals: {title: \"Edit this checklist item\", hearing_type: @hearing_type, checklist_item: @checklist_item} %>\n"
  },
  {
    "path": "app/views/checklist_items/new.html.erb",
    "content": "<%= render partial: \"form\",\nlocals: {title: \"Add a new checklist item\", hearing_type: @hearing_type, checklist_item: @checklist_item} %>\n"
  },
  {
    "path": "app/views/contact_topics/_form.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h1>\n          <%= title %>\n        </h1>\n      </div>\n    </div>\n  </div>\n</div><!-- ==== end title ==== -->\n\n<!-- ========== card start ========== -->\n<div class=\"card-style mb-30\">\n  <%= form_with(model: contact_topic, local: true) do |form| %>\n    <%= form.hidden_field :casa_org_id %>\n    <div class=\"alert-box danger-alert\">\n      <%= render \"/shared/error_messages\", resource: contact_topic %>\n    </div>\n    <div class=\"input-style-1\">\n      <%= form.label :question, \"Question\" %>\n      <%= form.text_field :question, class: \"form-control\", required: true %>\n    </div>\n    <div class=\"input-style-1\">\n      <%= form.label :details, \"Details?\" %>\n      <%= form.text_area :details, rows: 5, class: \"form-control\", required: true %>\n    </div>\n    <div class=\"form-check checkbox-style mb-20\">\n      <%= form.check_box :active, class: 'form-check-input' %>\n      <%= form.label :active, \"Active?\", class: 'form-check-label' %>\n    </div>\n    <div class=\"form-check checkbox-style mb-20\">\n      <%= form.check_box :exclude_from_court_report, class: 'form-check-input' %>\n      <%= form.label :exclude_from_court_report, \"Exclude from Court Report?\", class: 'form-check-label' %>\n    </div>\n    <div class=\"actions mb-10\">\n      <%= button_tag(type: \"submit\", class: \"btn-sm main-btn primary-btn btn-hover\") do %>\n        <i class=\"lni lni-checkmark-circle mr-5\"></i> Submit\n      <% end %>\n    </div>\n  <% end %>\n</div>\n<!-- card end -->\n"
  },
  {
    "path": "app/views/contact_topics/edit.html.erb",
    "content": "<%= render partial: \"form\", locals: {title: \"Contact Topic\", contact_topic: @contact_topic} %>\n"
  },
  {
    "path": "app/views/contact_topics/new.html.erb",
    "content": "<%= render partial: \"form\", locals: {title: \"New Contact Topic\", contact_topic: @contact_topic} %>\n"
  },
  {
    "path": "app/views/contact_type_groups/_form.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h1>\n          <%= title %>\n        </h1>\n      </div>\n    </div>\n  </div>\n</div><!-- ==== end title ==== -->\n\n<div class=\"card-style mb-30\">\n  <p class=\"fs-6 mb-2\">Case contact groups are used to group case contact types. For example a Family group could contain the case contact types: parent, grandpa, aunt.</p>\n\n  <%= form_with(model: contact_type_group, local: true) do |form| %>\n    <div class=\"alert-box danger-alert\">\n      <%= render \"/shared/error_messages\", resource: contact_type_group %>\n    </div>\n\n    <div class=\"input-style-1\">\n      <%= form.label :name, \"Name\" %>\n      <%= form.text_field :name, class: \"form-control\" %>\n    </div>\n\n    <div class=\"form-check checkbox-style mb-20\">\n      <%= form.check_box :active, class: 'form-check-input' %>\n      <%= form.label :active, \"Active\", class: 'form-check-label' %>\n    </div>\n\n    <div class=\"actions mb-10\">\n      <%= button_tag( type: \"submit\" , class: \"btn-sm main-btn primary-btn btn-hover\" ) do %>\n        <i class=\"lni lni-checkmark-circle mr-5\"></i> Submit\n      <% end %>\n    </div>\n  <% end %>\n</div>\n<!-- card end -->\n"
  },
  {
    "path": "app/views/contact_type_groups/edit.html.erb",
    "content": "<%= render partial: \"form\", locals: {title: \"Edit Contact Type Groups\", contact_type_group: @contact_type_group} %>\n"
  },
  {
    "path": "app/views/contact_type_groups/new.html.erb",
    "content": "<%= render partial: \"form\", locals: {title: \"New Contact Type Group\", contact_type_group: @contact_type_group} %>\n"
  },
  {
    "path": "app/views/contact_types/_form.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h1>\n          <%= title %>\n        </h1>\n      </div>\n    </div>\n  </div>\n</div><!-- ==== end title ==== -->\n\n<!-- ========== card start ========== -->\n<div class=\"card-style mb-30\">\n  <%= form_with(model: contact_type, local: true) do |form| %>\n  <div class=\"alert-box danger-alert\">\n    <%= render \"/shared/error_messages\", resource: contact_type %>\n  </div>\n  <div class=\"mb-20\">\n    Case contact types are the types of people that can be contacted for a case contact. Some examples are parents,\n    teachers, and social workers.\n  </div>\n    <div class=\"input-style-1\">\n      <%= form.label :name, \"Name\" %>\n      <%= form.text_field :name %>\n    </div>\n    <div class=\"select-style-1\">\n      <%= form.label :contact_type_group, \"Contact type group\" %>\n      <div class=\"select-position\">\n        <%= form.select :contact_type_group_id, set_group_options %>\n      </div>\n    </div>\n    <div class=\"form-check checkbox-style mb-20\">\n      <%= form.check_box :active, class: 'form-check-input' %>\n      <%= form.label :active, \"Active\", class: 'form-check-label' %>\n    </div>\n    <div class=\"actions mb-10\">\n      <%= button_tag( type: \"submit\" , class: \"btn-sm main-btn primary-btn btn-hover\" ) do %>\n        <i class=\"lni lni-checkmark-circle mr-5\"></i> Submit\n      <% end %>\n    </div>\n  <% end %>\n</div>\n"
  },
  {
    "path": "app/views/contact_types/edit.html.erb",
    "content": "<%= render partial: \"form\",\n           locals: {title: \"Editing\", contact_type: @contact_type} %>\n"
  },
  {
    "path": "app/views/contact_types/new.html.erb",
    "content": "<%= render partial: \"form\",\n           locals: {title: \"New Case Contact Type\", contact_type: @contact_type} %>\n"
  },
  {
    "path": "app/views/court_dates/_fields.html.erb",
    "content": "<div class=\"input-style-1\">\n  <%= form.label :date, \"Add Court Date\" %>\n  <%= form.date_field :date,\n                      value: court_date.date&.to_date || Time.zone.now,\n                      class: \"form-control\" %>\n</div>\n<div class=\"input-style-1\">\n  <%= form.label :court_report_due_date, \"Add Court Report Due Date\" %>\n  <%= form.date_field :court_report_due_date,\n                      value: court_date.court_report_due_date&.to_date,\n                      class: \"form-control\" %>\n</div>\n<div class=\"select-style-1\">\n  <%= form.label :judge_id, \"Judge\" %>\n  <div class=\"select-position\">\n    <%= form.collection_select(\n          :judge_id,\n          Judge.for_organization(current_organization),\n          :id, :name,\n          {include_hidden: false, include_blank: \"-Select Judge-\"},\n          {class: \"form-control\"}\n        ) %>\n  </div>\n</div>\n<div class=\"select-style-1\">\n  <%= form.label :hearing_type_id, \"Hearing type\" %>\n  <div class=\"select-position\">\n    <%= form.collection_select(\n          :hearing_type_id,\n          HearingType.active.for_organization(current_organization),\n          :id, :name,\n          {include_hidden: false, include_blank: \"-Select Hearing Type-\"},\n          {class: \"form-control\"}\n        ) %>\n  </div>\n</div>\n<div class=\"field form-group court-orders\">\n  <%= render partial: \"shared/court_order_list\",\n             locals: {casa_case: casa_case, siblings_casa_cases: nil, form: form, resource: 'court_date'} %>\n</div>\n"
  },
  {
    "path": "app/views/court_dates/_form.html.erb",
    "content": "<div class=\"card-style mb-30\">\n  <%= form_with(model: court_date, url: [casa_case, court_date], local: true,\n    data: { controller: \"court-order-form\", nested_form_wrapper_selector_value: \".nested-form-wrapper\" }) do |form| %>\n    <%= render \"/shared/error_messages\", resource: court_date %>\n    <div class=\"row align-items-center\">\n      <div class=\"col-md-6\">\n        <h6><strong>Case Number:</strong> <%= link_to casa_case.case_number, casa_case %></h6>\n      </div>\n      <div class=\"col-md-6\">\n        <div class=\"breadcrumb-wrapper\">\n          <span class=\"top-page-actions ml-5\">\n            <%= button_tag(\n            type: \"submit\",\n            class: \"btn-sm main-btn primary-btn btn-hover\"\n          ) do %>\n              <% if court_date.persisted? %>\n                <i class=\"lni lni-pencil-alt\"></i>\n                Update\n              <% else %>\n                <i class=\"lni lni-plus\"></i>\n                Create\n              <% end %>\n            <% end %>\n          </span>\n        </div>\n      </div>\n    </div>\n    <div class=\"row align-items-center\">\n      <%= render 'court_dates/fields', court_date: court_date, form: form, casa_case: casa_case %>\n    </div>\n  <% end %>\n</div>\n"
  },
  {
    "path": "app/views/court_dates/edit.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h1>Editing Court Date</h1>\n      </div>\n    </div>\n  </div>\n</div>\n\n<%= render 'form', casa_case: @casa_case, court_date: @court_date %>\n"
  },
  {
    "path": "app/views/court_dates/new.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h1>New Court Date</h1>\n      </div>\n    </div>\n  </div>\n</div>\n\n<%= render 'form', casa_case: @casa_case, court_date: @court_date %>\n"
  },
  {
    "path": "app/views/court_dates/show.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h1>Court Date</h1>\n        <time datetime=\"<%= @court_date.date %>\" class=\"d-inline-block h4\"><%= @court_date.decorate.formatted_date %></time>\n      </div>\n    </div>\n    <div class=\"col-md-6\">\n      <div class=\"breadcrumb-wrapper mb-30\">\n        <%= link_to edit_casa_case_court_date_path(@casa_case, @court_date), class: \"btn-sm main-btn primary-btn btn-hover\" do %>\n          <i class=\"lni lni-pencil-alt mr-10\"></i>\n          Edit\n        <% end %>\n      </div>\n    </div>\n  </div>\n</div>\n<div class=\"col-lg-12\">\n  <div class=\"card-style mb-30\">\n    <dl>\n      <dt>\n        <h6>Case Number:</h6>\n      </dt>\n      <dd class=\"mb-3\"><%= link_to \"#{@casa_case.case_number}\", casa_case_path(@casa_case) %></dd>\n      <dt>\n        <h6>Court Report Due Date:</h6>\n      </dt>\n      <dd class=\"mb-3\"><%= I18n.l(@court_date.court_report_due_date, format: :full, default: \"None\") %></dd>\n      <dt>\n        <h6>Judge:</h6>\n      </dt>\n      <dd class=\"mb-3\"><%= @court_date.judge&.name || \"None\" %></dd>\n      <dt>\n        <h6>Hearing Type:</h6>\n      </dt>\n      <dd class=\"mb-3\"><%= @court_date.hearing_type&.name || \"None\" %></dd>\n    </dl>\n    <h6>Court Orders:</h6>\n    <% if @court_date.case_court_orders.any? %>\n      <div class=\"table-wrapper table-responsive\">\n        <table class=\"table striped-table\">\n          <thead>\n            <th>Case Order Text</th>\n            <th class=\"text-center\">Implementation Status</th>\n          </thead>\n          <tbody>\n            <% @court_date.case_court_orders.each do |court_order| %>\n              <tr>\n                <td class=\"min-width\"><%= court_order.text %></td>\n                <td class=\"text-center\"><%= court_order.implementation_status&.humanize %></td>\n              </tr>\n            <% end %>\n          </tbody>\n        </table>\n      </div>\n    <% else %>\n      <p>\n        There are no court orders associated with this court date.\n      </p>\n    <% end %>\n    <div class=\"d-flex\">\n      <%= link_to casa_case_court_date_path(@casa_case, @court_date, format: :docx), class: \"btn-sm main-btn primary-btn btn-hover\" do %>\n        <i class=\"lni lni-download mr-10\"></i>\n        Download Report (.docx)\n      <% end %>\n      <% if policy(:court_date).destroy? && @court_date.date > Time.now %>\n        <%= link_to [@casa_case, @court_date], method: :delete, data: { confirm: 'Are you sure?' },class: \"btn-sm main-btn danger-btn-outline btn-hover ms-auto\" do %>\n          <i class=\"lni lni-trash-can mr-10\"></i>\n          Delete Future Court Date\n        <% end %>\n      <% end %>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/custom_org_links/_form.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h1>\n          <%= title %>\n        </h1>\n      </div>\n    </div>\n  </div>\n</div><!-- ==== end title ==== -->\n\n<!-- ========== card start ========== -->\n<div class=\"card-style mb-30\">\n  <%= form_with model: @custom_org_link, local: true do |form| %>\n    <div class=\"alert-box danger-alert\">\n      <%= render \"/shared/error_messages\", resource: @custom_org_link %>\n    </div>\n\n    <div class=\"input-style-1\">\n      <%= form.label :name, \"Display Text\" %>\n      <%= form.text_field :text, class: \"form-control\", required: true %>\n    </div>\n\n    <div class=\"input-style-1\">\n      <%= form.label :name, \"URL\" %>\n      <%= form.text_field :url, class: \"form-control\", required: true %>\n    </div>\n\n    <div class=\"form-check checkbox-style mb-20\">\n      <%= form.check_box :active, as: :boolean, class: 'form-check-input' %>\n      <%= form.label :active, \"Active?\", class: 'form-check-label' %>\n    </div>\n\n   <div class=\"actions mb-10\">\n      <%= button_tag type: \"submit\", class: \"btn-sm main-btn primary-btn btn-hover\" do %>\n        <i class=\"lni lni-checkmark-circle mr-5\"></i> <%= action %>\n      <% end %>\n    </div>\n  <% end %>\n</div>\n<!-- card end -->\n"
  },
  {
    "path": "app/views/custom_org_links/edit.html.erb",
    "content": "<%= render partial: \"form\", locals: { title: \"Edit Custom Link\", action: 'Update' } %>\n"
  },
  {
    "path": "app/views/custom_org_links/new.html.erb",
    "content": "<%= render partial: \"form\", locals: { title: \"New Custom Link\", action: 'Create' } %>\n"
  },
  {
    "path": "app/views/devise/invitations/edit.html.erb",
    "content": "<div class=\"password-box px-4 pb-3\">\n  <h2 class=\"my-3\">Set password</h2>\n\n  <%= form_with(model: resource, as: resource_name, url: invitation_path(resource_name), local: true, html: {method: :put}) do |f| %>\n    <%= render \"/shared/error_messages\", resource: resource %>\n    <%= f.hidden_field :invitation_token, readonly: true %>\n\n    <% if f.object.class.require_password_on_accepting %>\n      <div class=\"input-style-1\">\n        <%= f.label :password %>\n        <%= f.password_field :password, placeholder: \"Password\", required: true %>\n      </div>\n\n      <div class=\"input-style-1\">\n        <%= f.label :password_confirmation %>\n        <%= f.password_field :password_confirmation, placeholder: \"Password Confirmation\", required: true %>\n      </div>\n    <% end %>\n\n    <div class=\"actions mt-3\">\n      <%= button_tag(\n        type: \"submit\",\n        class: \"main-btn primary-btn btn-hover btn-sm\"\n      ) do %>\n        <i class=\"lni lni-lock-alt mr-10\"></i>Set my password\n      <% end %>\n    </div>\n  <% end %>\n</div>\n"
  },
  {
    "path": "app/views/devise/invitations/new.html.erb",
    "content": "<div class=\"row p-3\">\n  <h2 class=\"mb-2\">Send invitation</h2>\n\n  <%= form_with(model: resource, as: resource_name, url: invitation_path(resource_name), html: {method: :post}) do |f| %>\n    <%= render \"/shared/error_messages\", resource: resource %>\n\n    <% resource.class.invite_key_fields.each do |field| -%>\n      <div class=\"input-style-1\">\n        <%= f.label field %>\n        <%= f.text_field field, placeholder: field %>\n      </div>\n    <% end -%>\n\n    <div class=\"actions mt-1\">\n      <%= button_tag(\n        type: \"submit\",\n        class: \"main-btn primary-btn btn-hover btn-sm\"\n      ) do %>\n        <i class=\"lni lni-telegram-original mr-10\"></i>Send an invitation\n      <% end %>\n    </div>\n  <% end %>\n</div>\n"
  },
  {
    "path": "app/views/devise/mailer/confirmation_instructions.html.erb",
    "content": "<p>Click here to confirm your email.</p>\n<p><%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %></p>\n<p>If you weren't expecting this email, please disregard.</p>\n"
  },
  {
    "path": "app/views/devise/mailer/email_changed.html.erb",
    "content": "<% if @resource.try(:unconfirmed_email?) %>\n  <p>Your CASA account's email has been updated to <%= @resource.unconfirmed_email %>.</p>\n<% else %>\n  <p>Your CASA account's email has been updated to <%= @resource.email %>.</p>\n<% end %>\n"
  },
  {
    "path": "app/views/devise/mailer/invitation_instructions.html.erb",
    "content": "<meta itemprop=\"name\"\n      content=\"Confirm Email\"\n      style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n<style>/* Email styles need to be inline */</style>\n<table\n  width=\"100%\"\n  cellpadding=\"0\"\n  cellspacing=\"0\"\n  style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n  <% if @resource.is_a?(User) %>\n    <tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n      <td\n        class=\"content-block\"\n        style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\"\n        valign=\"top\">\n        A <%= @resource.casa_org.display_name %>’s County <%= @resource.type %> console account has been created for you.\n        This console is for logging the time you spend and actions you take on your CASA case.\n        You can log activity with your CASA youth, their family members, their foster family or placement, the DSS worker,\n        your Case Supervisor and others associated with your CASA case (such as teachers and therapists).\n      </td>\n    </tr>\n    <tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n      <td\n        class=\"content-block\"\n        style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\"\n        valign=\"top\">\n        Your console account is associated with this email. If this is not the correct email to use, please stop here\n        and contact your Case Supervisor to change the email address. If you are ready to get started, please set your\n        password. This is the first step to accessing your new <%= @resource.type %> account.\n      </td>\n    </tr>\n  <% else %>\n    <tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n      <td\n        class=\"content-block\"\n        style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\"\n        valign=\"top\">\n        A CASA console admin account has been created for you. Your console account is associated with this email.\n        If you are ready to get started, please set your password. This is the first step to accessing your new account.\n      </td>\n    </tr>\n  <% end %>\n  <tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n    <td\n      class=\"content-block\"\n      itemprop=\"handler\"\n      itemscope\n      itemtype=\"http://schema.org/HttpActionHandler\"\n      style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\"\n      valign=\"top\">\n      <p><%= link_to \"Set your password\", accept_invitation_url(@resource, invitation_token: @token) %></p>\n    </td>\n  </tr>\n  <tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n    <td class=\"content-block\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\" valign=\"top\">\n      This invitation will expire on <%= I18n.l(@resource.invitation_due_at, format: :full, default: nil) %>\n      <% if @resource.is_a?(AllCasaAdmin) %>\n        (one week).\n      <% elsif @resource.is_a?(Supervisor) || @resource.is_a?(CasaAdmin) %>\n        (two weeks).\n      <% elsif @resource.is_a?(Volunteer) %>\n        (one year).\n      <% end %>\n    </td>\n  </tr>\n</table>\n"
  },
  {
    "path": "app/views/devise/mailer/invitation_instructions.text.erb",
    "content": "<% if @resource.is_a?(User) %>\n  A <%= @resource.casa_org.display_name %>’s County <%= @resource.type %> console account has been created for you. This console is for logging the time you spend and actions you take on your CASA case. You can log activity with your CASA youth, their family members, their foster family or placement, the DSS worker, your Case Supervisor and others associated with your CASA case (such as teachers and therapists).\n\n  Your console account is associated with this email. If this is not the correct email to use, please stop here and contact your Case Supervisor to change the email address. If you are ready to get started, please set your password. This is the first step to accessing your new <%= @resource.type %> account.\n<% else %>\n  A CASA console admin account has been created for you. Your console account is associated with this email.\n  If you are ready to get started, please set your password. This is the first step to accessing your new account.\n  <%= \"This invitation will be due in #{I18n.l(@resource.invitation_due_at, format: :'devise.mailer.invitation_instructions.accept_until_format')}.\" %>\n<% end %>\n<%= link_to \"Set your password\", accept_invitation_url(@resource, invitation_token: @token) %>\n"
  },
  {
    "path": "app/views/devise/mailer/password_change.html.erb",
    "content": "<p>Hello <%= @resource.email %>!</p>\n\n<p>We're contacting you to notify you that your password has been changed.</p>\n"
  },
  {
    "path": "app/views/devise/mailer/reset_password_instructions.html.erb",
    "content": "<!DOCTYPE html>\n<html style=\"font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n  <head>\n    <meta name=\"viewport\" content=\"width=device-width\">\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n    <title>Actionable emails e.g. reset password</title>\n    <style>/* Email styles need to be inline */</style>\n    <style type=\"text/css\">\n        @media only screen and (max-width: 640px) {\n            body {\n                padding: 0 !important;\n            }\n\n            h3 {\n                font-weight: 800 !important;\n                margin: 20px 0 5px !important;\n            }\n\n            h3 {\n                font-size: 16px !important;\n            }\n\n            .container {\n                padding: 0 !important;\n                width: 100% !important;\n            }\n        }\n    </style>\n  </head>\n\n  <body itemscope itemtype=\"http://schema.org/EmailMessage\" style=\"background-color: #f6f6f6; line-height: 1.6em; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;\" bgcolor=\"#f6f6f6\">\n    <table class=\"main-body\" style=\"box-sizing: border-box; min-height: 150px; padding-top: 5px; padding-right: 5px; padding-bottom: 5px; padding-left: 5px; width: 100%; height: 100%; background-color: rgb(234, 236, 237);\" width=\"100%\" height=\"100%\" bgcolor=\"rgb(234, 236, 237)\">\n      <tbody style=\"box-sizing: border-box;\">\n        <tr class=\"row\" style=\"box-sizing: border-box; vertical-align: top;\" valign=\"top\">\n          <td class=\"main-body-cell\" style=\"box-sizing: border-box;\">\n            <table class=\"container\" style=\"box-sizing: border-box; font-family: Helvetica, serif; min-height: 150px; padding-top: 5px; padding-right: 5px; padding-bottom: 5px; padding-left: 5px; margin-top: auto; margin-right: auto; margin-bottom: auto; margin-left: auto; height: 0px; width: 90%; max-width: 550px;\" width=\"90%\" height=\"0\">\n              <tbody style=\"box-sizing: border-box;\">\n                <tr style=\"box-sizing: border-box;\">\n                  <td class=\"container-cell\" style=\"box-sizing: border-box; vertical-align: top; font-size: medium; padding-bottom: 50px;\" valign=\"top\">\n                    <table class=\"c1766\" style=\"box-sizing: border-box; margin-top: 0px; margin-right: auto; background:#728089;margin-left: 0px; padding-top: 5px; padding-right: 5px; padding-bottom: 5px; padding-left: 5px; width: 100%; min-height: 30px;\" width=\"100%\">\n                      <tbody style=\"box-sizing: border-box;\">\n                        <tr style=\"box-sizing: border-box;\">\n                          <td class=\"cell c1776\" style=\"box-sizing: border-box; width: 70%; vertical-align: middle;\" width=\"70%\" valign=\"middle\">\n                            <div class=\"c1144\" style=\"box-sizing: border-box; padding-top: 10px; padding-right: 10px; padding-bottom: 10px; padding-left: 10px; font-size: 17px; font-weight: 500;text-align: center;color: #fff;\">Court Appointed Special Advocate (CASA)\n                              <br style=\"box-sizing: border-box;\">\n                            </div>\n                          </td>\n                        </tr>\n                      </tbody>\n                    </table>\n                    <table class=\"c1766\" style=\"box-sizing: border-box; margin-top: 0px; margin-right: auto; background:#00447c;margin-left: 0px; padding-top: 5px; padding-right: 5px; padding-bottom: 5px; padding-left: 5px; width: 100%; min-height: 30px;\" width=\"100%\">\n                      <tbody style=\"box-sizing: border-box;\">\n                        <tr style=\"box-sizing: border-box;\">\n                          <td class=\"cell c1776\" style=\"box-sizing: border-box; width: 70%; vertical-align: middle;text-align:center;\" width=\"70%\" valign=\"middle\">\n                            <img src=\"https://user-images.githubusercontent.com/8918762/120879374-7c813a00-c588-11eb-92d7-efa6fdfdfbf0.png\" alt=\"Logo\" class=\"c926\" style=\"max-width: 100%; box-sizing: border-box; color: rgb(158, 83, 129); font-size: 50px;padding-top:10px;background:#00447c;\">\n                          </td>\n                        </tr>\n                      </tbody>\n                    </table>\n                    <table class=\"card\" style=\"background:#fff;box-sizing: border-box; min-height: 150px; padding-top: 5px; padding-right: 5px; padding-bottom: 5px; padding-left: 5px; margin-bottom: 20px; height: 0px;\" height=\"0\">\n                      <tbody style=\"box-sizing: border-box;\">\n                        <tr style=\"box-sizing: border-box;\">\n                          <td class=\"card-cell\" style=\"box-sizing: border-box; background-color: rgb(255, 255, 255); overflow-x: hidden; overflow-y: hidden; border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; text-align: center;\" bgcolor=\"rgb(255, 255, 255)\" align=\"center\">\n                            <table class=\"table100 c1357\" style=\"box-sizing: border-box; width: 100%; min-height: 150px; padding-top: 5px; padding-right: 5px; padding-bottom: 5px; padding-left: 5px; height: 0px; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; border-collapse: collapse;\" width=\"100%\" height=\"0\">\n                              <tbody style=\"box-sizing: border-box;\">\n                                <tr style=\"box-sizing: border-box;\">\n                                  <td class=\"card-content\" style=\"box-sizing: border-box; font-size: 13px; line-height: 20px; color: rgb(111, 119, 125); padding-top: 10px; padding-right: 20px; padding-bottom: 0px; padding-left: 20px; vertical-align: top;\" valign=\"top\">\n                                    <table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\" style=\"max-width: 530px;\">\n                                      <tr>\n                                        <td>\n                                          <table width=\"100%\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n                                            <tr>\n                                              <td>\n                                                <table width=\"100%\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n                                                  <tr>\n                                                    <td align=\"left\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\" class=\"padding\">\n                                                      If you've lost your password or wish to reset it, click the button below. If\n                                                      you didn’t request this, you can safely ignore this email.\n                                                    </td>\n                                                  </tr>\n                                                </table>\n                                              </td>\n                                            </tr>\n                                            <tr>\n                                              <td>\n                                                <table width=\"100%\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n                                                  <tr>\n                                                    <td align=\"left\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\" class=\"padding\">\n                                                      Your password must be reset within the next <%= User.reset_password_within.inspect %>. If you do not\n                                                      reset your password within the next <%= User.reset_password_within.inspect %>, request a new password\n                                                      reset message from the login page.\n                                                    </td>\n                                                  </tr>\n                                                </table>\n                                              </td>\n                                            </tr>\n                                            <tr>\n                                              <td align=\"center\">\n                                                <table width=\"100%\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n                                                  <tr>\n                                                    <td align=\"center\" style=\"padding-top: 15px;\">\n                                                      <table border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n                                                        <tr>\n                                                          <td align=\"left\">\n                                                            <a href=\"<%= \"#{edit_password_url(@resource, reset_password_token: @token)}\" %>\" target=\"_blank\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2em; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; text-transform: capitalize; background-color: #00447c; margin: 0; border-color: #00447c; border-style: solid; border-width: 10px 20px;\">Reset your password</a>\n                                                          </td>\n                                                        </tr>\n                                                      </table>\n                                                    </td>\n                                                  </tr>\n                                                </table>\n                                              </td>\n                                            </tr>\n                                          </table>\n                                        </td>\n                                      </tr>\n                                    </table>\n                                  </td>\n                                </tr>\n                              </tbody>\n                            </table>\n                          </td>\n                        </tr>\n                      </tbody>\n                    </table>\n                    <table class=\"footer\" style=\"box-sizing: border-box; margin-top: 50px; color: rgb(152, 156, 165); text-align: center; font-size: 11px; padding-top: 5px; padding-right: 5px; padding-bottom: 5px; padding-left: 5px;\" align=\"center\">\n                      <tbody style=\"box-sizing: border-box;\">\n                        <tr style=\"box-sizing: border-box;\">\n                          <td class=\"footer-cell\" style=\"box-sizing: border-box;\">\n                          </td>\n                        </tr>\n                      </tbody>\n                    </table>\n                    <div class=\"c2577\" style=\"text-align:center;box-sizing: border-box; padding-top: 10px; padding-right: 10px; padding-bottom: 10px; padding-left: 10px;\">\n                      <p class=\"footer-info\" style=\"box-sizing: border-box;\">Court Appointed Special Advocate (CASA)\n                        <br style=\"box-sizing: border-box;\">\n                      </p>\n                    </div>\n                  </td>\n                </tr>\n              </tbody>\n            </table>\n          </td>\n        </tr>\n      </tbody>\n    </table>\n  </body>\n</html>\n"
  },
  {
    "path": "app/views/devise/mailer/unlock_instructions.html.erb",
    "content": "<p>Hello <%= @resource.email %>!</p>\n\n<p>Your account has been locked due to an excessive number of unsuccessful sign in attempts.</p>\n\n<p>Click the link below to unlock your account:</p>\n\n<p><%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %></p>\n"
  },
  {
    "path": "app/views/devise/passwords/edit.html.erb",
    "content": "<div class=\"row\">\n  <div class=\"col-lg-9 col-md-12 vertically-center\">\n    <div class=\"container\">\n      <div class=\"row\">\n        <div class=\"offset-xl-2 col-xl-8 col-lg-12 col-sm-8 col-md-8 pb-4\">\n          <h2 class=\"my-4\">Change your password</h2>\n\n              <%= form_with(model: resource, as: resource_name, url: password_path(resource_name), method: :put) do |f| %>\n                <%= render \"/shared/error_messages\", resource: resource %>\n                <%= f.hidden_field :reset_password_token %>\n\n                <div class=\"input-style-1\">\n                  <%= f.label :password, \"New password\" %>\n                  <% if @minimum_password_length %>\n                    <em>(<%= @minimum_password_length %> characters minimum)</em>\n                  <% end %>\n                  <%= f.password_field :password,\n                    autofocus: true,\n                    autocomplete: \"new-password\",\n                    class: \"password-new\",\n                    placeholder: \"New Password\",\n                    required: true,\n                    minlength: @minimum_password_length %>\n                </div>\n\n                <div class=\"input-style-1\">\n                  <%= f.label :password_confirmation, \"Confirm new password\" %>\n                  <%= f.password_field :password_confirmation,\n                    autocomplete: \"new-password\",\n                    class: \"password-confirmation\",\n                    placeholder: \"Confirm Password\",\n                    required: true,\n                    minlength: @minimum_password_length %>\n                </div>\n\n                <div class=\"actions my-2\">\n                  <%= button_tag(\n                    type: \"submit\",\n                    class: \"main-btn primary-btn btn-hover btn-sm submit-password\"\n                  ) do %>\n                    <i class=\"lni lni-save mr-10\"></i>Change my password\n                  <% end %>\n                </div>\n              <% end %>\n\n          <%= render \"devise/shared/links\" %>\n        </div>\n      </div>\n    </div>\n  </div>\n\n  <div class=\"col-lg-7 d-none d-lg-block\">\n    <aside class=\"display-image\"></aside>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/devise/passwords/new.html.erb",
    "content": "<div class=\"row\">\n  <div class=\"col-lg-9 col-md-12 vertically-center\">\n    <div class=\"container\">\n      <div class=\"row\">\n        <div class=\"offset-xl-2 col-xl-8 col-lg-12 col-sm-8 col-md-8 pb-4\">\n          <h2 class=\"my-4\">Forgot your password?</h2>\n\n          <%= form_with(model: resource, as: resource_name, url: password_path(resource_name), html: {method: :post}) do |f| %>\n            <%= render \"/shared/error_messages\", resource: resource %>\n\n            <h4 class=\"mb-3\">Please enter email or phone number to receive reset instructions.</h4>\n\n            <div class=\"input-style-1\">\n              <%= f.label :email %>\n              <%= f.email_field :email, placeholder: \"Email\", autofocus: true, autocomplete: \"email\" %>\n            </div>\n\n            <div class=\"input-style-1\">\n              <%= f.label :phone_number %>\n              <%= f.text_field :phone_number, placeholder: \"Phone Number\", autofocus: true, autocomplete: \"phone number\" %>\n            </div>\n\n            <div class=\"actions my-2\">\n              <%= button_tag(\n                type: \"submit\",\n                class: \"main-btn primary-btn btn-hover btn-sm\"\n              ) do %>\n                <i class=\"lni lni-telegram-original mr-10\"></i>Send me reset password instructions\n              <% end %>\n            </div>\n          <% end %>\n\n          <%= render \"devise/shared/links\" %>\n        </div>\n      </div>\n    </div>\n  </div>\n\n  <div class=\"col-lg-7 d-none d-lg-block\">\n    <aside class=\"display-image\"></aside>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/devise/sessions/new.html.erb",
    "content": "<div class=\"signin-wrapper\">\n  <div class=\"form-wrapper\">\n    <h6 class=\"mb-15\">Sign In</h6>\n    <p class=\"text-sm mb-25\">\n      Not registered but want to join?\n      <a class='text-blue-500' href=\"mailto:casa@rubyforgood.org?Subject=Application%20Interest\" target=\"_top\">casa@rubyforgood.org</a>\n    </p>\n    <div class=\"row\">\n      <%= form_with(model: resource, as: resource_name, url: session_path(resource_name)) do |f| %>\n      <div class=\"alert-box\">\n        <% if resource.errors.any? %>\n          <%= render \"/shared/error_messages\", resource: resource %>\n        <% else %>\n          <%= render \"layouts/flash_messages\" %>\n        <% end %>\n      </div>\n        <div class=\"col-12\">\n          <div class=\"input-style-1\" id=\"email-input\">\n            <%= f.label \"Email\", for: \"email\" %>\n            <%= f.email_field :email, autofocus: true, autocomplete: \"email\", placeholder: \"Email\", class: \"form-control\", required: true, id:\"email\" %>\n          </div>\n        </div>\n        <!-- end col -->\n        <div class=\"col-12\">\n          <div class=\"input-style-1\">\n            <%= f.label :password %>\n            <%= f.password_field :password, autocomplete: \"current-password\", placeholder: \"Password\", class: \"form-control\", required: true %>\n          </div>\n        </div>\n        <div class=\"col-12\">\n          <div class=\"input-style-1\">\n            <%= link_to new_password_path(resource_name), class: \"hover-underline\" do %>\n              <i class=\"lni lni-lock mr-10\"></i>Forgot your password?\n            <% end %>\n          </div>\n        </div>\n        <!-- end col -->\n        <div class=\"col-xxl-6 col-lg-12 col-md-6\">\n          <div class=\"form-check checkbox-style mb-30\">\n            <input\n              class=\"form-check-input\"\n              type=\"checkbox\"\n              value=\"\"\n              id=\"checkbox-remember\">\n            <label\n              class=\"form-check-label\"\n              for=\"checkbox-remember\">\n              Remember me next time</label>\n          </div>\n        </div>\n        <!-- end col -->\n        <!-- end col -->\n        <div class=\"col-12\">\n          <div\n            class=\"\n                            button-group\n                            d-flex\n                            justify-content-center\n                            flex-wrap\n                            actions\n                          \">\n            <%= button_tag(\n              type: \"submit\",\n              class: \"main-btn primary-btn btn-hover w-100 text-center\",\n              id: \"log-in\"\n            ) do %>\n              <i class=\"lni lni-chevron-right-circle mr-10\"></i>Log In\n            <% end %>\n          </div>\n        </div>\n      <% end %>\n      <div class=\"col-xxl-6 col-lg-12 col-md-6\">\n        <div class=\"text-start text-md-end text-lg-start text-xxl-end mb-30\">\n        </div>\n      </div>\n      <div class=\"col-xxl-6 col-lg-12 col-md-6\">\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/devise/shared/_links.html.erb",
    "content": "<%- if controller_name != 'sessions' %>\n  <%= link_to new_session_path(resource_name), class: \"btn-sm main-btn info-btn btn-hover\" do %>\n    <i class=\"lni lni-chevron-right-circle mr-10\"></i>Log in\n  <% end %>\n  <br>\n<% end %>\n\n<%- if devise_mapping.registerable? && controller_name != 'registrations' %>\n  <%= link_to \"Sign up\", new_registration_path(resource_name) %><br>\n<% end %>\n\n<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>\n  <%= link_to \"Forgot your password?\", new_password_path(resource_name), class: 'btn-sm main-btn primary-btn-outline btn-hover' %><br>\n<% end %>\n\n<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>\n  <span class='login-email-link'>Want to add your CASA? Email: <%= mail_to \"casa@rubyforgood.org\" %></span><br>\n<% end %>\n\n<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>\n  <%= link_to \"Didn't receive confirmation instructions?\", new_confirmation_path(resource_name) %><br>\n<% end %>\n\n<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>\n  <%= link_to \"Didn't receive unlock instructions?\", new_unlock_path(resource_name) %><br>\n<% end %>\n\n<%- if devise_mapping.omniauthable? %>\n  <%- resource_class.omniauth_providers.each do |provider| %>\n    <%= link_to \"Sign in with #{OmniAuth::Utils.camelize(provider)}\", omniauth_authorize_path(resource_name, provider) %>\n    <br>\n  <% end %>\n<% end %>\n"
  },
  {
    "path": "app/views/emancipation_checklists/index.html.erb",
    "content": "<div class=\"pt-30\">\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <div class=\"mb-30\">\n        <h1>Emancipation Checklists</h1>\n      </div>\n    </div>\n  </div>\n</div>\n\n<!-- ========== tables-wrapper start ========== -->\n<div class=\"tables-wrapper\">\n  <div class=\"row\">\n    <div class=\"col-lg-12\">\n      <div class=\"card-style mb-30\">\n        <div class=\"table-wrapper table-responsive\">\n          <table class=\"table\" id=\"all-case-emancipations\">\n            <thead>\n            <tr>\n              <th><h6>Case Number</h6></th>\n              <th><h6>Checklist</h6></th>\n            </tr>\n            <!-- end table row-->\n            </thead>\n            <tbody>\n            <% @casa_transitioning_cases.each do |casa_case| %>\n              <tr class=\"<%= casa_case.decorate.inactive_class %>\">\n                <td><%= link_to(casa_case.case_number, casa_case, title: \"Details\") %></td>\n                <td><%= link_to \"Checklist\", casa_case_emancipation_path(casa_case) %></td>\n              </tr>\n            <% end %>\n            </tbody>\n          </table>\n          <!-- end table -->\n        </div>\n      </div>\n      <!-- end card -->\n    </div>\n    <!-- end col -->\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/emancipations/download.html.erb",
    "content": "<table>\n  <% @emancipation_form_data.each do |category| %>\n    <tr>\n      <td><%= emancipation_category_checkbox_checked_download(@current_case, category) %></td>\n      <td><%= category.name %></td>\n    </tr>\n    <% if category.emancipation_options.any? %>\n      <tr>\n        <td></td>\n        <td>\n          <table>\n            <% category.emancipation_options.each do |option| %>\n              <tr>\n                <td>\n                  <% if category.mutually_exclusive? %>\n                    <%= emancipation_option_radio_checked_download(@current_case, option) %>\n                  <% else %>\n                    <%= emancipation_option_checkbox_checked_download(@current_case, option) %>\n                  <% end %>\n                </td>\n                <td><%= option.name %></td>\n              </tr>\n            <% end %>\n          </table>\n        </td>\n      </tr>\n    <% end %>\n  <% end %>\n</table>\n"
  },
  {
    "path": "app/views/emancipations/show.html.erb",
    "content": "<div class=\"row justify-content-between align-items-center pt-30 mb-30\">\n  <div class=\"col-sm-auto dashboard-table-header\">\n    <h1>Emancipation Checklist</h1>\n    <%= link_to @current_case.case_number, casa_case_path(@current_case) %>\n  </div>\n  <div class=\"col-sm-auto\">\n    <span class=\"mt-md-0 text-center\">\n      <%= link_to casa_case_emancipation_path(@current_case, format: :docx), class: \"main-btn primary-btn btn-sm btn-hover\" do %>\n        <i class=\"lni lni-download mr-10\"></i>\n            Download Checklist\n      <% end %>\n    </span>\n  </div>\n</div>\n\n<!-- ========== card start ========== -->\n<div class=\"card-style mb-40\">\n  <% @emancipation_form_data.each do |category| %>\n    <h6 class=\"emancipation-category no-select\" data-is-open=\"<%= emancipation_category_checkbox_checked(@current_case, category) ? \"true\" : \"false\" %>\">\n      <div class=\"checkbox-style emacipation-category-input-label-pair\">\n        <%= tag.input(\n          type: \"checkbox\",\n          class: \"emancipation-category-check-box form-check-input\",\n          value: category.id,\n          checked: emancipation_category_checkbox_checked(@current_case, category)) %>\n\n        <div class=\"pt-10\"><%= category.name %></div>\n      </div>\n      <% if category.emancipation_options.count > 0 %>\n        <span class=\"category-collapse-icon pt-10\"><%= emancipation_category_collapse_icon(@current_case, category) %></span>\n      <% end %>\n    </h6>\n    <div class=\"category-options\" style=\"<%= emancipation_category_collapse_hidden(@current_case, category) %>\">\n      <% category.emancipation_options.each do |option| %>\n        <div class=\"check-item\">\n          <% if category.mutually_exclusive %>\n            <%= tag.input(\n              type: \"radio\",\n              id: \"O#{option.id}\",\n              class: \"emancipation-radio-button\",\n              name: \"C#{category.id}\",\n              value: option.id,\n              checked: emancipation_option_checkbox_checked(@current_case, option)) %>\n          <% else %>\n            <%= tag.input(\n              type: \"checkbox\",\n              id: \"O#{option.id}\",\n              class: \"emancipation-option-check-box\",\n              value: option.id,\n              checked: emancipation_option_checkbox_checked(@current_case, option)) %>\n          <% end %>\n          <label><%= option.name %></label>\n        </div>\n      <% end %>\n      <!-- The labels here intentionally do not have for attributes matching the inputs -->\n      <!-- This is because javascript sets the state of the checkbox depending on whether the ajax based saving was successful -->\n    </div>\n  <% end %>\n</div>\n<!-- card end -->\n"
  },
  {
    "path": "app/views/error/index.html.erb",
    "content": "<div class=\"card text-center\">\n  <div class=\"card-body\">\n    <h1 class=\"card-title\">Intentional Error Test</h1>\n    <p class=\"card-text\" style=\"color:grey\">Click the button below to trigger an intentional test exception.</p>\n    <%= button_to \"Trigger Exception\", error_path, method: :post, class: \"btn btn-danger\", data: { turbo: false } %>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/fund_request_mailer/send_request.html.erb",
    "content": "Fund Request: see attached PDF<br>\n<br>\nSubmitter email: <%= @inputs[\"submitter_email\"] %><br>\nYouth name: <%= @inputs[\"youth_name\"] %><br>\nPayment amount: <%= @inputs[\"payment_amount\"] %> <br>\nDeadline: <%= @inputs[\"deadline\"] %> <br>\nRequest purpose: <%= @inputs[\"request_purpose\"] %> <br>\nPayee name: <%= @inputs[\"payee_name\"] %> <br>\nRequested by and relationship: <%= @inputs[\"requested_by_and_relationship\"] %><br>\nOther funding source sought: <%= @inputs[\"other_funding_source_sought\"] %><br>\nImpact: <%= @inputs[\"impact\"] %><br>\nExtra information: <%= @inputs[\"extra_information\"] %><br>\n"
  },
  {
    "path": "app/views/fund_requests/new.html.erb",
    "content": "<div class=\"pt-30\">\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <div class=\"mb-30\">\n        <h1>New Fund Request</h1>\n      </div>\n    </div>\n  </div>\n</div>\n\n<!-- ========== card start ========== -->\n<div class=\"card-style mb-50\">\n  <%= form_with(model: @fund_request, local: true, url: casa_case_fund_request_path(@casa_case), method: :post) do |form| %>\n\n    <% if @fund_request.errors.any? %>\n      <% @fund_request.errors.full_messages.each do |msg| %>\n        <p><%= msg %></p>\n      <% end %>\n    <% end %>\n\n    <div class=\"input-style-1\">\n    <%= form.label :submitter_email, \"Your email\" %>\n      <%= form.email_field :submitter_email, class: \"form-control\", required: false, value: current_user.email %>\n    </div>\n\n    <div class=\"input-style-1\">\n      <%= form.label :youth_name, \"Name or case number of youth\" %>\n      <%= form.text_field :youth_name, class: \"form-control\", required: false, value: @casa_case&.case_number %>\n    </div>\n\n    <div class=\"input-style-1\">\n      <%= form.label :payment_amount, \"Amount of payment*\" %>\n      <%= form.text_field :payment_amount, class: \"form-control\", required: false %>\n    </div>\n\n    <div class=\"input-style-1\">\n      <%= form.label :deadline, \"Deadline / date needed\" %>\n      <%= form.text_field :deadline, class: \"form-control\", required: false %>\n    </div>\n\n    <div class=\"input-style-1\">\n      <%= form.label :request_purpose, \"Request is for...\" %>\n      <%= form.text_area :request_purpose, class: \"form-control\", required: false %>\n    </div>\n\n    <div class=\"input-style-1\">\n      <%= form.label :payee_name, \"Name of payee**\" %>\n      <%= form.text_field :payee_name, class: \"form-control\", required: false %>\n    </div>\n\n    <div class=\"input-style-1\">\n      <%= form.label :requested_by_and_relationship, \"Requested by & relationship to youth\" %>\n      <%= form.text_field :requested_by_and_relationship, class: \"form-control\", required: false,\nvalue: \"#{current_user.display_name} CASA Volunteer\" %>\n    </div>\n\n    <div class=\"input-style-1\">\n      <%= form.label :other_funding_source_sought,\n        \"Other source of funding available/sought please include status of these requests, if applicable.\" %>\n      <%= form.text_area :other_funding_source_sought, class: \"form-control\", required: false %>\n    </div>\n\n    <div class=\"input-style-1\">\n      <%= form.label :impact, \"How will this funding positively impact the personal goals or aspirations of\nthe youth? If this is for emergency funding, please share any support that is\nor can be in place to maintain stability or alleviate the emergency moving\nforward. If funding is for a program or a service, please describe how this will\nsupport the youth in the short or long-term.\" %>\n      <%= form.text_area :impact, class: \"form-control\", required: false %>\n    </div>\n\n    <div class=\"input-style-1\">\n      <%= form.label :extra_information,\n        \"Please use this space if it is necessary/helpful to provide additional\n      information that will assist us in understanding the need and making a decision.\" %>\n      <%= form.text_area :extra_information, class: \"form-control\", required: false %>\n    </div>\n\n    <%= button_tag(type: \"submit\", class: \"btn-sm main-btn primary-btn btn-hover\", required: false) do %>\n      <i class=\"lni lni-checkmark-circle mr-10\"></i> Submit Fund Request\n    <% end %>\n  <% end %>\n  <br>\n  * Please provide all documentation available for the requested expense (brochure,\n  receipt, estimate, invoice, proof of online enrollment information, etc.). If\n  additional funds have been secured, please provide evidence of the funding\n  commitment (an email, a partial payment on a bill, etc).\n  <br>\n  **Payment will be made directly to the vendor for all services. If the payment\n  must be made directly to the youth, please be sure to include why this is the\n  preferred method of payment.\n</div>\n<!-- card end -->\n"
  },
  {
    "path": "app/views/health/index.html.erb",
    "content": "<div id=\"health\">\n  <canvas id=\"caseContactCreationTimeBubbleChart\"></canvas>\n  <canvas id=\"monthLineChart\" class=\"mt-5\"></canvas>\n  <canvas id=\"uniqueUsersMonthLineChart\" class=\"mt-5\"></canvas>\n</div>\n"
  },
  {
    "path": "app/views/hearing_types/_form.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h1>\n          <%= title %>\n        </h1>\n      </div>\n    </div>\n  </div>\n</div>\n<div class=\"card-style mb-30\">\n  <%= form_with(model: hearing_type, local: true) do |form| %>\n    <%= render \"/shared/error_messages\", resource: hearing_type %>\n    <div class=\"input-style-1\">\n      <%= form.label :name, \"Name\" %>\n      <%= form.text_field :name, class: \"form-control\", required: true %>\n    </div>\n    <div class=\"form-check checkbox-style mb-20\">\n      <%= form.check_box :active, class: 'form-check-input' %>\n      <%= form.label :active, class: 'form-check-label' %>\n    </div>\n    <% if !hearing_type.id.nil? %>\n      <div class=\"dashboard-table-header pt-2 pb-2\">\n        <div class=\"row align-items-center\">\n          <div class=\"col-md-6\">\n            <h3>Checklist</h3>\n          </div>\n          <!-- end col -->\n          <div class=\"col-md-6\">\n            <div class=\"breadcrumb-wrapper\">\n              <span class=\"ml-5\">\n                <%= link_to new_hearing_type_checklist_item_path(hearing_type), class: \"btn-sm main-btn primary-btn btn-hover\" do %>\n                  <i class=\"lni lni-plus mr-10\"></i>\n                  New List Item\n                <% end %>\n              </span>\n            </div>\n          </div>\n          <!-- end col -->\n        </div>\n      </div>\n      <!-- end table title -->\n      <div class=\"table-wrapper table-responsive\">\n        <table class=\"table striped-table\" id=\"checklist_items\">\n          <thead>\n            <tr>\n              <th>Description</th>\n              <th>Category</th>\n              <th>Mandatory</th>\n              <th>Actions</th>\n            </tr>\n            <!-- end table row-->\n          </thead>\n          <tbody>\n            <% hearing_type.checklist_items.each do |checklist_item| %>\n              <tr id=\"checklist_item-<%= checklist_item.id %>\">\n                <td class=\"min-width\">\n                  <%= checklist_item.description %>\n                </td>\n                <td class=\"min-width\">\n                  <%= checklist_item.category %>\n                </td>\n                <td class=\"min-width\">\n                  <%= checklist_item.mandatory ? \"Yes\" : \"Optional\" %>\n                </td>\n                <td>\n                  <%= link_to edit_hearing_type_checklist_item_path(hearing_type, checklist_item) do %>\n                    <div class=\"action\">\n                      <button class=\"text-danger\">\n                        <i class=\"lni lni-pencil-alt\"></i> Edit\n                      </button>\n                    </div>\n                  <% end %>\n                  <%= link_to hearing_type_checklist_item_path(hearing_type, checklist_item),\n                    method: :delete, data: { confirm: \"Are you sure that you want to delete this checklist item?\" } do %>\n                    <div class=\"action\">\n                      <button class=\"text-danger\">\n                        <i class=\"lni lni-trash-can\"></i> Delete\n                      </button>\n                    </div>\n                  <% end %>\n                </td>\n              </tr>\n            <% end %>\n          </tbody>\n        </table>\n        <!-- end table -->\n      </div>\n    <% end %>\n    <div class=\"actions mb-10\">\n      <%= button_tag(\n            type: \"submit\",\n            class: \"btn-sm main-btn primary-btn btn-hover\"\n          ) do %>\n          <i class=\"lni lni-checkmark-circle mr-10\"></i> Submit\n      <% end %>\n    </div>\n  <% end %>\n</div>\n"
  },
  {
    "path": "app/views/hearing_types/edit.html.erb",
    "content": "<%= render partial: \"form\", locals: {title: \"Edit\", hearing_type: @hearing_type} %>\n"
  },
  {
    "path": "app/views/hearing_types/new.html.erb",
    "content": "<%= render partial: \"form\", locals: {title: \"New Hearing Type\", hearing_type: @hearing_type} %>\n"
  },
  {
    "path": "app/views/imports/_cases.html.erb",
    "content": "<div class=\"mb-3\">\n  <h4>\n    1.\n    <%= link_to \"/casa_cases.csv\", format: :csv, download: \"casa_cases.csv\" do %>\n      Download and reference example Case CSV file\n      <i class=\"lni lni-download\"></i>\n    <% end %>\n  </h4>\n</div>\n\n<div class=\"mb-3\">\n  <h4>\n    2. Upload your CSV file\n    <i class=\"lni lni-upload\" aria-hidden=\"true\"></i>\n  </h4>\n  <%= form_with(url: imports_path, local: :true) do |f| %>\n    <%= f.hidden_field :import_type, value: \"casa_case\" %>\n    <ul style=\"list-style-type: disc;\" class=\"mx-4\">\n      <li>Click the choose file button and navigate to the saved file and select it.</li>\n      <li><strong>Do not</strong> change any of the values in the first line of the example csv file.</li>\n      <li>Then click the \"Import Cases CSV\" button to import your cases.</li>\n    </ul>\n    <%= f.file_field :file,\n                     id: 'case-file',\n                     accept: 'text/csv',\n                     class: 'form-control mt-4',\n                     type: 'file',\n                     style: \"margin: auto;\" %>\n\n    <%= button_tag id: \"case-import-button\", class: \"main-btn primary-btn btn-hover pull-right\",\n                   disabled: true, data: {disable_with: \"<i class='fa fa-spinner fa-spin'></i> Importing File\"} do %>\n      <i class=\"lni lni-upload\"></i> Import Cases CSV\n    <% end %>\n\n    <script type=\"text/javascript\">\n      document.getElementById('case-file').addEventListener('change', function (event) {\n        document.getElementById('case-import-button').disabled = (event.target.value == '')\n      })\n    </script>\n  <% end %>\n</div>\n"
  },
  {
    "path": "app/views/imports/_csv_error_modal.html.erb",
    "content": "<div class=\"modal show csv-error\" style=\"display: block;\">\n  <div class=\"modal-dialog\" role=\"document\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <h5 class=\"modal-title text-danger\">CSV Import Error</h5>\n      </div>\n      <div class=\"modal-body\">\n        <%= sanitize import_error %>\n      </div>\n      <div class=\"modal-footer\">\n        <button type=\"button\" class=\"main-btn primary-btn btn-hover\" onclick=\"dismissCsvErrorModal()\">OK</button>\n      </div>\n    </div>\n  </div>\n</div>\n\n<div class=\"modal-backdrop show csv-error\"></div>\n\n<script type=\"text/javascript\">\n  function dismissCsvErrorModal () {\n    document.querySelectorAll('.csv-error').forEach(function (el) {\n      el.remove()\n    })\n  }\n</script>\n"
  },
  {
    "path": "app/views/imports/_sms_opt_in_modal.html.erb",
    "content": "<div class=\"modal show\" id=\"smsOptIn\" style=\"display: block;\">\n  <div class=\"modal-dialog\" role=\"document\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <h5 class=\"modal-title\" id=\"smsOptIn\">\n          SMS Opt In\n        </h5>\n        <a type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-label=\"Close\">\n          <span aria-hidden=\"true\">&times;</span>\n        </a>\n      </div>\n      <div class=\"modal-body\">\n        <div>\n          Please check this box to verify that these mobile numbers have opted in to receive SMS notifications. (They will have the opportunity to opt out or update their preferences.)\n        </div>\n        <br>\n        <span class=\"form-check checkbox-style\">\n          <%= form.label :sms_opt_in_label, \"Opt into SMS notifications\", { class: \"form-check-label\"} %>\n          <%= form.check_box :sms_opt_in, { id: \"sms-opt-in-checkbox\", class: \"form-check-input\" } %>\n        </span>\n      </div>\n      <div class=\"modal-footer\">\n        <%= form.submit \"Continue Import\",\n{ id: \"sms-opt-in-continue-button\", class: \"main-btn primary-btn btn-hover\", disabled: true } %>\n      </div>\n    </div>\n  </div>\n\n  <script type=\"text/javascript\">\n      document.getElementById('sms-opt-in-checkbox').addEventListener('change', function (event) {\n        document.getElementById('sms-opt-in-continue-button').disabled = !event.target.checked;\n      })\n  </script>\n</div>\n"
  },
  {
    "path": "app/views/imports/_supervisors.html.erb",
    "content": "<div class=\"mb-3\">\n  <h4>\n    1.\n    <%= link_to \"/supervisors.csv\", format: :csv, download: \"supervisors.csv\" do %>\n       Download and reference example Supervisor CSV file\n      <i class=\"lni lni-download\"></i>\n    <% end %>\n  </h4>\n</div>\n\n<div class=\"mb-3\">\n  <h4>\n    2. Upload your CSV file\n    <i class=\"lni lni-upload\" aria-hidden=\"true\"></i>\n  </h4>\n  <%= form_with(url: imports_path, local: :true, id: \"supervisor-import-form\") do |f| %>\n    <%= f.hidden_field :import_type, value: \"supervisor\" %>\n    <ul style=\"list-style-type: disc;\" class=\"mx-4\">\n      <li>Click the choose file button and navigate to the saved file and select it.</li>\n      <li><strong>Do not</strong> change any of the values in the first line of the example csv file.</li>\n      <li>Then click the \"Import Supervisors CSV\" button to import your supervisors.</li>\n    </ul>\n    <%= f.file_field :file,\n                     id: 'supervisor-file',\n                     accept: 'text/csv',\n                     class: 'form-control mt-4',\n                     type: 'file',\n                     style: \"margin: auto;\" %>\n\n    <%= render \"sms_opt_in_modal\", { form: f } if @sms_opt_in_warning == \"supervisor\" %>\n    <%= button_tag id: \"supervisor-import-button\", class: \"main-btn primary-btn btn-hover pull-right\",\n                   disabled: true, data: { disable_with: \"<div class='spinner-border spinner-border-sm'></div> Importing File\"} do %>\n      <i class=\"lni lni-upload\"></i> Import Supervisors CSV\n    <% end %>\n  <% end %>\n</div>\n"
  },
  {
    "path": "app/views/imports/_volunteers.html.erb",
    "content": "<div class=\"mb-3\">\n  <h4>\n    1.\n    <%= link_to \"/volunteers.csv\", format: :csv, download: \"volunteers.csv\" do %>\n      Download and reference example Volunteer CSV file\n      <i class=\"lni lni-download\"></i>\n    <% end %>\n  </h4>\n</div>\n\n<div class=\"mb-3\">\n  <h4>\n    2. Upload your CSV file\n    <i class=\"lni lni-upload\" aria-hidden=\"true\"></i>\n  </h4>\n  <%= form_with(url: imports_path, local: :true, id: \"volunteer-import-form\") do |f| %>\n    <%= f.hidden_field :import_type, value: \"volunteer\" %>\n    <%= f.hidden_field :sms_opt_in, value: false %>\n    <ul style=\"list-style-type: disc;\" class=\"mx-4\">\n      <li>Click the choose file button and navigate to the saved file and select it.</li>\n      <li><strong>Do not</strong> change any of the values in the first line of the example csv file.</li>\n      <li>Then click the \"Import Volunteers CSV\" button to import your volunteers. <strong>This will email the new volunteers asking them to log in!</strong></li>\n    </ul>\n    <%= f.file_field :file,\n                     id: 'volunteer-file',\n                     accept: 'text/csv',\n                     class: 'form-control mt-4',\n                     type: 'file',\n                     style: \"margin: auto;\" %>\n\n    <%= render \"sms_opt_in_modal\", { form: f } if @sms_opt_in_warning == \"volunteer\" %>\n    <%= button_tag id: \"volunteer-import-button\", class: \"main-btn primary-btn btn-hover pull-right\",\n      disabled: true, data: { disable_with: \"<div class='spinner-border spinner-border-sm'></div> Importing File\" } do %>\n      <i class=\"lni lni-upload\"></i> Import Volunteers CSV\n    <% end %>\n  <% end %>\n</div>\n"
  },
  {
    "path": "app/views/imports/index.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h1>Imports</h1>\n      </div>\n    </div>\n  </div>\n</div>\n<div class=\"row\">\n  <div class=\"col-sm-12\">\n    <div class=\"card card-style\">\n      <div class=\"card-body\">\n        <h5 class=\"card-title\">System Imports</h5>\n        <p class=\"card-text\">\n          You should import files in the following order:\n        </p>\n        <ol style=\"list-style-type: decimal;\" class=\"mx-4\">\n          <li>Volunteers</li>\n          <li>Supervisors</li>\n          <li>Cases</li>\n        </ol>\n        <div>\n          <nav class=\"nav nav-tabs justify-content-center nav-justified mt-3\">\n            <%= content_tag :a,\n                            role: \"tab\",\n                            href: \"#volunteer\",\n                            id: \"volunteer-tab\",\n                            class: [\n                                \"nav-link\",\n                                (\"active\" if @import_type == \"volunteer\")\n                            ].compact,\n                            data: {\n                              \"bs-toggle\" => \"tab\",\n                              \"bs-target\" => \"#volunteer\"\n                            },\n                            aria: {controls: \"volunteer\", selected: (@import_type == \"volunteer\")} do %>\n              Import Volunteers\n            <% end %>\n            <%= content_tag :a,\n                            role: \"tab\",\n                            href: \"#supervisor\",\n                            id: \"supervisor-tab\",\n                            class: [\n                                \"nav-link\",\n                                (\"active\" if @import_type == \"supervisor\")\n                            ].compact,\n                            data: {\n                              \"bs-toggle\" => \"tab\",\n                              \"bs-target\" => \"#supervisor\"\n                            },\n                            aria: {controls: \"supervisor\", selected: (@import_type == \"supervisor\")} do %>\n              Import Supervisors\n            <% end %>\n            <%= content_tag :a,\n                            role: \"tab\",\n                            href: \"#casa-case\",\n                            id: \"casa-case-tab\",\n                            class: [\n                                \"nav-link\",\n                                (\"active\" if @import_type == \"casa_case\")\n                            ].compact,\n                            data: {\n                              \"bs-toggle\" => \"tab\",\n                              \"bs-target\" => \"#casa-case\"\n                            },\n                            aria: {controls: \"case\", selected: (@import_type == \"casa_case\")} do %>\n              Import Cases\n            <% end %>\n          </nav>\n          <div class=\"tab-content\">\n            <%= content_tag :div,\n                            role: \"tabpanel\",\n                            id: \"volunteer\",\n                            class: [\n                                \"tab-pane\",\n                                \"fade\",\n                                (\"show\" if @import_type == \"volunteer\"),\n                                (\"active\" if @import_type == \"volunteer\")\n                            ].compact,\n                            aria: {labelledby: \"volunteer-tab\"} do %>\n              <br>\n              <%= render \"volunteers\" %>\n            <%- end %>\n            <%= content_tag :div,\n                            role: \"tabpanel\",\n                            id: \"supervisor\",\n                            class: [\n                                \"tab-pane\",\n                                \"fade\",\n                                (\"show\" if @import_type == \"supervisor\"),\n                                (\"active\" if @import_type == \"supervisor\")\n                            ].compact,\n                            aria: {labelledby: \"supervisor-tab\"} do %>\n                <br>\n                <%= render \"supervisors\" %>\n              <%- end %>\n              <%= content_tag :div,\n                              role: \"tabpanel\",\n                              id: \"casa-case\",\n                              class: [\n                                  \"tab-pane\",\n                                  \"fade\",\n                                  (\"show\" if @import_type == \"casa_case\"),\n                                  (\"active\" if @import_type == \"casa_case\")\n                              ].compact,\n                              aria: {labelledby: \"casa-case-tab\"} do %>\n                <br>\n                <%= render \"cases\" %>\n              <%- end %>\n          </div>\n        </div>\n        <br>\n      </div>\n    </div>\n  </div>\n  <%= render \"csv_error_modal\", {import_error: @import_error} if @import_error %>\n</div>\n"
  },
  {
    "path": "app/views/judges/_form.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h1>\n          <%= title %>\n        </h1>\n      </div>\n    </div>\n  </div>\n</div><!-- ==== end title ==== -->\n\n<!-- ========== card start ========== -->\n<div class=\"card-style mb-30\">\n  <%= form_with(model: judge, local: true) do |form| %>\n    <div class=\"alert-box danger-alert\">\n      <%= render \"/shared/error_messages\", resource: judge %>\n    </div>\n    <div class=\"input-style-1\">\n      <%= form.label :name, \"Name\" %>\n      <%= form.text_field :name, class: \"form-control\", required: true %>\n    </div>\n\n    <div class=\"form-check checkbox-style mb-20\">\n      <%= form.check_box :active, class: 'form-check-input' %>\n      <%= form.label :active, \"Active?\", class: 'form-check-label' %>\n    </div>\n\n   <div class=\"actions mb-10\">\n      <%= button_tag(type: \"submit\", class: \"btn-sm main-btn primary-btn btn-hover\") do %>\n        <i class=\"lni lni-checkmark-circle mr-5\"></i> Submit\n      <% end %>\n    </div>\n  <% end %>\n</div>\n<!-- card end -->\n"
  },
  {
    "path": "app/views/judges/edit.html.erb",
    "content": "<%= render partial: \"form\", locals: {title: \"Judge\", judge: @judge} %>\n"
  },
  {
    "path": "app/views/judges/new.html.erb",
    "content": "<%= render partial: \"form\", locals: {title: \"New Judge\", judge: @judge} %>\n"
  },
  {
    "path": "app/views/languages/_form.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h1>\n          <%= title %>\n        </h1>\n      </div>\n    </div>\n  </div>\n</div><!-- ==== end title ==== -->\n\n<div class=\"card-style mb-30\">\n  <%= form_with(model: language, local: true) do |form| %>\n    <div class=\"alert-box danger-alert\">\n      <%= render \"/shared/error_messages\", resource: language %>\n    </div>\n\n    <div>\n      A list of languages volunteers can add to their profile to let supervisors and admins know they can speak the language.\n    </div>\n\n    <br>\n\n    <div class=\"input-style-1\">\n      <%= form.label :name, \"Name\" %>\n      <%= form.text_field :name, class: \"form-control\", required: true %>\n    </div>\n\n    <div class=\"actions mb-10\">\n      <%= button_tag(type: \"submit\", class: \"btn-sm main-btn primary-btn btn-hover\") do %>\n        <i class=\"lni lni-checkmark-circle mr-5\"></i> Submit\n      <% end %>\n    </div>\n  <% end %>\n</div>\n"
  },
  {
    "path": "app/views/languages/edit.html.erb",
    "content": "<%= render partial: \"form\", locals: { title: \"Edit Language\", language: @language } %>\n"
  },
  {
    "path": "app/views/languages/new.html.erb",
    "content": "<%= render partial: \"form\", locals: { title: \"New Language\", language: @language } %>\n"
  },
  {
    "path": "app/views/layouts/_all_casa_admin_sidebar.html.erb",
    "content": "<aside class=\"sidebar-nav-wrapper\">\n  <div class=\"navbar-logo\">\n    <%= link_to image_tag(\"default-logo.png\",\n                          id: \"casa-logo\",\n                          alt: \"CASA Logo\",\n                          class: \"d-inline-block align-text-bottom\"),\n                \"/\" %>\n  </div>\n  <nav class=\"sidebar-nav\">\n    <nav class=\"sidebar-nav\">\n      <ul>\n        <li class=\"<%= active_class(all_casa_admins_patch_notes_path) %> nav-item\">\n          <%= link_to all_casa_admins_patch_notes_path do %>\n            <i class=\"lni lni-notepad mr-10\"></i>\n            Patch Notes\n          <% end %>\n        </li>\n        <li class=\"<%= active_class(edit_all_casa_admins_path) %> nav-item\">\n          <%= link_to edit_all_casa_admins_path do %>\n            <i class=\"lni lni-pencil-alt mr-10\"></i>\n            Edit Profile\n          <% end %>\n        </li>\n        <li class=\"nav-item\">\n          <%= link_to \"/flipper\" do %>\n            <i class=\"lni lni-pencil-alt mr-10\"></i>\n            Feature Flags\n          <% end %>\n        </li>\n        <li class=\"nav-item\">\n          <%= link_to \"/pg_dashboard\" do %>\n            <i class=\"lni lni-pencil-alt mr-10\"></i>\n            Pg Dashboard\n          <% end %>\n        </li>\n        <li class=\"nav-item\">\n          <%= link_to destroy_all_casa_admin_session_path, id:\"all-casa-log-out\" do %>\n            <i class=\"lni lni-exit mr-10\"></i>\n            Log Out\n          <% end %>\n        </li>\n      </ul>\n    </nav>\n  </nav>\n</aside>\n<div class=\"overlay\"></div>\n"
  },
  {
    "path": "app/views/layouts/_banner.html.erb",
    "content": "<% if @active_banner %>\n  <div class=\"bg-secondary-100 text-xl p-5 d-flex\" data-controller=\"dismiss\" data-dismiss-target=\"element\" data-dismiss-url-value=\"<%= dismiss_banner_path(@active_banner) %>\">\n    <div>\n      <%= @active_banner.content %>\n    </div>\n    <div class=\"ms-auto\">\n      <a href=\"#\" data-action=\"click->dismiss#dismiss\">Dismiss</a>\n    </div>\n  </div>\n<% end %>\n"
  },
  {
    "path": "app/views/layouts/_flash_messages.html.erb",
    "content": "<div class=\"header-flash\">\n  <% flash.delete(:timedout).each do |key, value| %>\n    <div class=\"<%= flash_class(key) %> alert-dismissible fade show\" role=\"alert\">\n      <%= sanitize(value, tags: %w(ul li)) %>\n    </div>\n  <% end %>\n</div>\n"
  },
  {
    "path": "app/views/layouts/_header.html.erb",
    "content": "<header class=\"header pt-0\">\n  <%= render 'layouts/banner' %>\n\n  <% if current_user != true_user %>\n    <%= link_to stop_impersonating_volunteers_path, method: :post, class: \"pt-4 pb-4 bg-danger\", style: \"padding-left: 15px; display: block;\" do %>\n      You (<%= true_user.display_name %>) are signed in as <%= current_user.display_name %>.\n      Click here to stop impersonating.\n    <% end %>\n  <% end %>\n\n  <div class=\"container-fluid pt-30\">\n    <div class=\"row\">\n      <div class=\"col-lg-5 col-md-5 col-6\">\n        <button type=\"button\" class=\"btn btn-sidebar d-md-none\" data-controller=\"navbar\" data-action=\"click->navbar#click\" data-navbar-sidebar-outlet=\".sidebar-controller\">\n          <i class=\"lni lni-menu\"></i>\n        </button>\n      </div>\n\n      <div class=\"col-lg-7 col-md-7 col-6\">\n        <div class=\"header-right\">\n          <!-- notification start -->\n          <!-- message start -->\n\n          <div class=\"header-message-box ml-15 d-none d-md-flex\">\n            <%= link_to notifications_path do %>\n\n              <button\n                class=\"dropdown-toggle\"\n                type=\"button\"\n                id=\"message\"\n                aria-expanded=\"false\">\n\n                <i class=\"lni lni-envelope\"></i>\n                <% notifications = current_user.notifications.unread.count %>\n                <% if notifications > 0 %>\n                  <span><%= notifications %></span>\n                <% end %>\n              </button>\n            <% end %>\n\n          </div>\n\n          <div class=\"profile-box ml-15\">\n            <button\n              class=\"dropdown-toggle bg-transparent border-0\"\n              type=\"button\"\n              id=\"profile\"\n              data-bs-toggle=\"dropdown\"\n              aria-expanded=\"false\">\n              <div class=\"profile-info\">\n                <div class=\"info\">\n                  <h6><%= current_user.display_name %>\n                  </h6>\n                  <div class=\"image\">\n                    <img\n                      src=\"<%= \"/assets/images/profile/profile-image-#{rand(8) + 1}.png\" %>\">\n                  </div>\n                </div>\n              </div>\n              <i class=\"lni lni-chevron-down\"></i>\n            </button>\n            <ul\n              class=\"dropdown-menu dropdown-menu-end\"\n              aria-labelledby=\"profile\">\n              <li>\n                <span>\n                  <strong>Role: <%= current_role %></strong>\n                </span>\n              </li>\n              <li>\n                <span>\n                  <strong> <%= current_user.email %></strong>\n                </span>\n              </li>\n              <li>\n                <%= link_to edit_users_path do %>\n                  <i class=\"lni lni-pencil\"></i>\n                  Edit Profile\n                <% end %>\n              </li>\n              <li>\n                <%= link_to notifications_path do %>\n                  <i class=\"lni lni-envelope\"></i>\n                  Messages\n                <% end %>\n              </li>\n              <% if policy(:application).modify_organization? %>\n                <li>\n                  <%= link_to edit_casa_org_path(current_organization) do %>\n                    <i class=\"lni lni-cogs mr-10\"></i>\n                    Edit Organization\n                  <% end %>\n                </li>\n              <% end %>\n              <li>\n                <% help_url = current_user.volunteer? ? help_volunteers_url : help_admins_supervisors_url %>\n                <%= link_to help_url, target: :_blank do %>\n                  <i class=\"lni lni-question-circle\"></i>\n                  Help\n                <% end %>\n              </li>\n              <li>\n                <%= link_to destroy_user_session_path do %>\n                  <i class=\"lni lni-exit\"></i>\n                  Sign Out\n                <% end %>\n              </li>\n            </ul>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</header>\n<%= render 'layouts/flash_messages' %>\n"
  },
  {
    "path": "app/views/layouts/_login_header.html.erb",
    "content": "<header>\n  <nav class=\"navbar navbar-expand-lg bg-primary justify-content-between\">\n    <div class=\"navbar-brand py-0\">\n      <%= image_tag(\"default-logo.png\", alt: \"CASA logo\", size: \"70x38\") %>\n      <strong class=\"login-header\"><%= page_header %></strong>\n    </div>\n  </nav>\n</header>\n"
  },
  {
    "path": "app/views/layouts/_mobile_navbar.html.erb",
    "content": "<nav class=\"navbar mobile\">\n  <%= image_tag(\"icon-logo.png\",\n                     sizes: \"72x72\",\n                     rel: \"icon\",\n                     type: \"image/png\",\n                     class: \"mobile-icon\") %>\n  <button id=\"toggle-sidebar-js\" type=\"button\" class=\"btn btn-outline-light\">\n    Menu\n  </button>\n</nav>\n"
  },
  {
    "path": "app/views/layouts/_sidebar.html.erb",
    "content": "<div class=\"sidebar-controller\" data-controller=\"sidebar\" data-sidebar-open-value=\"true\" data-sidebar-sidebar-group-outlet=\".group-item\">\n  <aside class=\"sidebar-nav-wrapper\" data-sidebar-target=\"sidebar\">\n    <div class=\"container d-flex logo-div\">\n      <div class=\"d-flex flex-fill\">\n        <div>\n          <button type=\"button\" class=\"btn btn-sidebar\" data-action=\"click->sidebar#click\">\n            <i class=\"lni lni-menu\"></i>\n          </button>\n        </div>\n        <div class=\"flex-grow-1 d-flex justify-content-center\" data-sidebar-target=\"logo\">\n          <%= link_to \"/\", class: \"d-flex\" do %>\n            <% if current_organization&.logo&.attached? %>\n              <%= image_tag(current_organization.logo, alt: \"CASA Logo\", class: \"img-fluid flex-fill\") %>\n            <% else %>\n              <%= image_tag(\"default-logo.png\", id: \"casa-logo\", alt: \"CASA Logo\", class: \"img-fluid flex-fill align-text-bottom\") %>\n            <% end %>\n          <% end %>\n        </div>\n      </div>\n    </div>\n    <nav class=\"sidebar-nav\" data-action=\"mouseover->sidebar#hoverOn mouseout->sidebar#hoverOff\">\n      <nav class=\"sidebar-nav\">\n        <ul>\n          <%= render(Sidebar::LinkComponent.new(title: \"Supervisors\", icon: \"network\", path: supervisors_path, render_check: policy(Supervisor).index?)) %>\n          <%= render(Sidebar::LinkComponent.new(title: \"Volunteers\", icon: \"heart-filled\", path: volunteers_path, render_check: policy(Volunteer).index?)) %>\n          <%= render(Sidebar::LinkComponent.new(title: \"Other Duties\", icon: \"agenda\", path: other_duties_path, render_check: policy(OtherDuty).index?)) %>\n          <!-- Renders Cases for Volunteers -->\n          <%= render(Sidebar::GroupComponent.new(title: cases_index_title, icon: \"folder\", render_check: current_user.volunteer?)) do |group| %>\n            <% group.with_link(title: \"All\", path: casa_cases_path, nav_item: false) %>\n            <% group.with_links(current_user.casa_cases.map { |cc| { title: cc.case_number, path: casa_case_path(cc), nav_item: false }}) %>\n          <% end %>\n          <!-- Renders Cases for Non-Volunteers -->\n          <%= render(Sidebar::LinkComponent.new(title: cases_index_title, icon: \"folder\", path: casa_cases_path, render_check: !current_user.volunteer?)) %>\n\n          <%= render(Sidebar::LinkComponent.new(title: \"Admins\", icon: \"star-filled\", path: casa_admins_path, render_check: policy(CasaAdmin).index?)) %>\n          <%= render(Sidebar::LinkComponent.new(title: \"Patch Notes\", icon: \"notepad\", path: all_casa_admins_patch_notes_path, render_check: all_casa_admin_signed_in?)) %>\n          <%= render(Sidebar::GroupComponent.new(title: \"Case Contacts\", icon: \"users\", render_check: current_user.volunteer?)) do |group| %>\n            <%= group.with_link(title: \"All\", path: case_contacts_path, nav_item: false) %>\n            <% group.with_links(current_user.casa_cases.map { |cc| { title: cc.case_number, path: case_contacts_path(casa_case_id: cc.id), nav_item: false } }) %>\n          <% end %>\n          <%= render(Sidebar::LinkComponent.new(title: \"Learning Hours\", icon: \"timer\", path: learning_hours_path, render_check: policy(LearningHour).index?)) %>\n          <%= render(Sidebar::LinkComponent.new(title: \"Emancipation Checklist(s)\", icon: \"list\", path: emancipation_checklists_path, render_check: current_user.serving_transition_aged_youth?)) %>\n          <%= render(Sidebar::LinkComponent.new(title: \"Banners\", icon: \"flag\", path: banners_path, render_check: policy(:application).see_banner_page?)) %>\n          <%= render(Sidebar::LinkComponent.new(title: \"Generate Court Reports\", icon: \"paperclip\", path: case_court_reports_path, render_check: policy(:application).see_court_reports_page? && current_user.volunteer?)) %>\n          <%= render(Sidebar::GroupComponent.new(title: \"Group Actions\", icon: \"list\", render_check: !current_user.volunteer?)) do |group| %>\n            <% group.with_links([\n              { title: \"Generate Court Reports\", icon: \"paperclip\", path: case_court_reports_path, render_check: policy(:application).see_court_reports_page? },\n              { title: \"Reimbursement Queue\", icon: \"money-location\", path: reimbursements_path, render_check: policy(:reimbursement).index? },\n              { title: \"Export Data\", icon: \"folder\", path: reports_path, render_check: policy(:application).see_reports_page? },\n              { title: \"System Imports\", icon: \"archive\", path: imports_path, render_check: policy(:application).see_reports_page? },\n              { title: \"Mileage Rates\", icon: \"car\", path: mileage_rates_path, render_check: policy(:application).see_mileage_rate? }\n            ]) %>\n          <% end %>\n          <%= render(Sidebar::GroupComponent.new(title: \"Edit Organization\", icon: \"list\", render_check: !current_user.volunteer?)) do |group| %>\n            <% group.with_links([\n              { title: \"Organization Details\", icon: \"briefcase\", path: edit_casa_org_path(current_organization, anchor: 'organization-details'), render_check: policy(:application).modify_organization? },\n              { title: \"Contact Types\", icon: \"user\", path: edit_casa_org_path(current_organization, anchor: 'contact-types'), render_check: policy(:application).modify_organization? },\n              { title: \"Court Details\", icon: \"library\", path: edit_casa_org_path(current_organization, anchor: 'court-details'), render_check: policy(:application).modify_organization? },\n              { title: \"Learning Hours\", icon: \"timer\", path: edit_casa_org_path(current_organization, anchor: 'learning-hours'), render_check: policy(:application).modify_organization? },\n              { title: \"Case Contact Topics\", icon: \"comments\", path: edit_casa_org_path(current_organization, anchor: 'case-contact-topics'), render_check: policy(:application).modify_organization? },\n              { title: \"Placement Types\", icon: \"comments\", path: edit_casa_org_path(current_organization, anchor: 'placement-types'), render_check: policy(:application).modify_organization? }\n            ]) %>\n          <% end %>\n          <% if current_organization.custom_org_links.any?(&:active) %>\n            <hr>\n            <% current_organization.custom_org_links.select(&:active).each do |custom_link| %>\n              <%= render(Sidebar::LinkComponent.new(title: custom_link.text, icon: \"link\", path: custom_link.url)) %>\n            <% end %>\n          <% end %>\n        </ul>\n      </nav>\n    </nav>\n  </aside>\n\n  <div class=\"overlay\" data-action=\"click->sidebar#click\"></div>\n</div>\n"
  },
  {
    "path": "app/views/layouts/action_text/contents/_content.html.erb",
    "content": "<div class=\"trix-content\">\n  <%= yield -%>\n</div>\n"
  },
  {
    "path": "app/views/layouts/application.html.erb",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <title>CASA Volunteer Tracking</title>\n\n  <meta name=\"description\" content=\"Volunteer activity tracking for CASA volunteers, supervisors, and administrators.\">\n  <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n\n  <%= csrf_meta_tags %>\n  <%= csp_meta_tag %>\n\n  <%= og_tag :title, content: \"CASA Volunteer Tracking\" %>\n  <%= og_tag :description, content: \"Volunteer activity tracking for CASA volunteers, supervisors, and administrators.\" %>\n  <%= og_tag :url, content: root_url %>\n  <%= og_tag :image, content: image_url('login.jpg') %>\n\n  <%= render 'shared/favicons' %>\n  <script>\n    window.timeout = <%= @timeout_duration.to_json %>\n  </script>\n\n  <%= javascript_include_tag \"application\", \"data-turbo-track\": \"reload\", defer: true %>\n  <%= stylesheet_link_tag \"application\", \"data-turbo-track\": \"reload\" %>\n\n  <% if all_casa_admin_signed_in? %>\n    <%= javascript_include_tag \"all_casa_admin\", \"data-turbo-track\": \"reload\", defer: true %>\n  <% end %>\n\n  <link rel=\"stylesheet\" href=\"/assets/css/lineicons.css\">\n  <link rel=\"stylesheet\" href=\"/assets/css/materialdesignicons.min.css\">\n  <link rel=\"stylesheet\" href=\"/assets/css/main.css\">\n\n</head>\n<body class=\"<%= body_class %>\">\n<noscript>\n  <div class=\"noscript alert alert-danger\">\n    <h2 class=\"alert-heading\">\n      Please enable javascript\n    </h2>\n    <p>\n      This app requires javascript to work\n    </p>\n  </div>\n</noscript>\n<% if all_casa_admin_signed_in? %>\n  <%= render 'layouts/all_casa_admin_sidebar' %>\n<% elsif signed_in? %>\n  <%= render 'layouts/sidebar' %>\n<% end %>\n<main class=\"main-wrapper\">\n\n<% if all_casa_admin_signed_in? %>\n<% elsif signed_in? %>\n<%= render 'layouts/header' %>\n<% end %>\n\n  <!-- ========== section start ========== -->\n  <section class=\"section\">\n    <div class=\"container-fluid\">\n      <!-- ========== title-wrapper start ========== -->\n      <%= yield %>\n    </div>\n  </section>\n\n  <%= render \"layouts/components/notifier\" %>\n\n  <footer class=\"footer\">\n    <div class=\"container-fluid\">\n      <div class=\"row\">\n        <div class=\"col-md-4 order-last order-md-first\">\n          <div class=\"copyright text-center text-md-start\">\n            <p class=\"text-sm\">\n              © <%= default_page_header %>\n            </p>\n          </div>\n        </div>\n        <!-- end col-->\n        <div class=\"col-md-8\">\n          <div\n            class=\"\n                  terms\n                  d-flex\n                  justify-content-center justify-content-md-end\n                \">\n            <p class=\"text-sm pr-4\">\n              Built with <i class=\"lni lni-heart-filled\" style=\"color:red;\"></i> by\n              <a href=\"https://rubyforgood.org/\">Ruby For Good</a>\n            </p>\n            <p class=\"text-sm ml-15\">\n              <%= link_to \"Report a site issue\", \"https://form.typeform.com/to/iXY4BubB\" %>\n            </p>\n            <p class=\"text-sm ml-15\">\n              <%= link_to \"SMS Terms & Conditions\", \"/sms-terms-conditions.html\" %>\n            </p>\n          </div>\n        </div>\n      </div>\n      <!-- end row -->\n    </div>\n    <!-- end container -->\n  </footer>\n  <!-- ========== footer end =========== -->\n</main>\n<!-- ======== main-wrapper end =========== -->\n</body>\n</html>\n"
  },
  {
    "path": "app/views/layouts/components/_notifier.html.erb",
    "content": "<div id=\"notifications\">\n  <div class=\"messages\">\n  </div>\n  <div id=\"async-waiting-indicator\" style=\"display: none\">\n    Saving <i class=\"load-spinner\"></i>\n  </div>\n  <div id=\"async-success-indicator\" class=\"success-notification\" style=\"display: none\">\n    Saved\n  </div>\n  <button id=\"toggle-minimize-notifications\" style=\"display: none;\">\n    <span>minimize notifications </span>\n    <span class=\"badge rounded-pill bg-success\" style=\"display: none;\"></span>\n    <span class=\"badge rounded-pill bg-warning\" style=\"display: none;\"></span>\n    <span class=\"badge rounded-pill bg-danger\" style=\"display: none;\"></span>\n    <i class=\"fa-solid fa-minus\"></i>\n  </button>\n</div>\n"
  },
  {
    "path": "app/views/layouts/devise.html.erb",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>CASA Volunteer Tracker</title>\n\n  <%= javascript_include_tag \"application\", \"data-turbo-track\": \"reload\", defer: true %>\n  <%= stylesheet_link_tag \"application\", \"data-turbo-track\": \"reload\" %>\n\n  <% if all_casa_admin_signed_in? %>\n    <%= javascript_include_tag \"all_casa_admin\", \"data-turbo-track\": \"reload\", defer: true %>\n  <% end %>\n\n  <link rel=\"stylesheet\" href=\"/assets/css/lineicons.css\">\n  <link rel=\"stylesheet\" href=\"/assets/css/materialdesignicons.min.css\">\n  <link rel=\"stylesheet\" href=\"/assets/css/main.css\">\n</head>\n<body>\n<section class=\"signin-section\">\n  <div class=\"container-fluid\">\n    <div class=\"row g-0 auth-row\">\n      <div class=\"col-lg-6\">\n        <div class=\"auth-cover-wrapper bg-primary-100\">\n          <div class=\"auth-cover\">\n            <div class=\"title text-center\">\n              <h1 class=\"text-primary mb-10\">Welcome Back</h1>\n              <p class=\"text-medium\">\n                Sign in to your Existing account to continue\n              </p>\n            </div>\n          </div>\n        </div>\n      </div>\n      <!-- end col -->\n      <div class=\"col-lg-6\">\n        <%= yield %>\n      </div>\n      <!-- end col -->\n    </div>\n    <!-- end row -->\n  </div>\n</section>\n</body>\n</html>\n"
  },
  {
    "path": "app/views/layouts/footers/_logged_in.html.erb",
    "content": "<footer class=\"logged-in\">\n  <span class=\"copyright pull-left\">© <%= default_page_header %></span>\n  <span class=\"default-footer pull-right\">\n    <span class=\"col-12 col-sm-2\">\n      Built by <a href=\"https://rubyforgood.org/\">Ruby For Good</a>\n    </span>\n    <span class=\"col-12 col-sm-2\">\n      <%= link_to \"Report a site issue\", \"https://form.typeform.com/to/iXY4BubB\" %>\n    </span>\n    <span class=\"col-12 col-sm-2\">\n      <%= link_to \"SMS Terms & Conditions\", \"/sms-terms-conditions.html\" %>\n    </span>\n  </span>\n</footer>\n"
  },
  {
    "path": "app/views/layouts/footers/_not_logged_in.html.erb",
    "content": "<footer>\n  <p class=\"copyright\">© <%= default_page_header %></p>\n  <span class=\"default-footer\">\n    Built by <a href=\"https://rubyforgood.org/\" class=\"rfglink\">Ruby For Good</a>\n    <a href=\"https://rubyforgood.org/\" class=\"rfglink\" target=\"_blank\">\n      <%= image_tag(\"rfglogo.png\", class: \"rfglogo\") %>\n    </a>\n    <%= link_to \"Report a site issue\", \"https://form.typeform.com/to/iXY4BubB\", class: \"rfglink\" %><br>\n    <%= link_to \"SMS Terms & Conditions\", \"/sms-terms-conditions.html\", class: \"terms-conditions-link\" %>\n  </span>\n\n  <div class=\"footer-logos\">\n    <%= image_tag(\"default-logo.png\") %>\n  </div>\n</footer>\n"
  },
  {
    "path": "app/views/layouts/fund_layout.html.erb",
    "content": "<%= yield %>\n"
  },
  {
    "path": "app/views/layouts/mailer.html.erb",
    "content": "<!DOCTYPE html>\n<html style=\"font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n<head>\n  <meta name=\"viewport\" content=\"width=device-width\">\n  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n  <title>Actionable emails e.g. reset password</title>\n  <style>/* Email styles need to be inline, except @media queries */</style>\n  <style type=\"text/css\">\n      @media only screen and (max-width: 640px) {\n          body {\n              padding: 0 !important;\n          }\n\n          h3 {\n              font-weight: 800 !important;\n              margin: 20px 0 5px !important;\n          }\n\n          h3 {\n              font-size: 16px !important;\n          }\n\n          .container {\n              padding: 0 !important;\n              width: 100% !important;\n          }\n      }\n  </style>\n</head>\n\n<body itemscope itemtype=\"http://schema.org/EmailMessage\" style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;\" bgcolor=\"#f6f6f6\">\n<table class=\"main-body\" style=\"box-sizing: border-box; min-height: 150px; padding-top: 5px; padding-right: 5px; padding-bottom: 5px; padding-left: 5px; width: 100%; height: 100%; background-color: rgb(234, 236, 237);\" width=\"100%\" height=\"100%\" bgcolor=\"rgb(234, 236, 237)\">\n  <tbody style=\"box-sizing: border-box;\">\n  <tr class=\"row\" style=\"box-sizing: border-box; vertical-align: top;\" valign=\"top\">\n    <td class=\"main-body-cell\" style=\"box-sizing: border-box;\">\n      <table class=\"container\" style=\"box-sizing: border-box; font-family: Helvetica, serif; min-height: 150px; padding-top: 5px; padding-right: 5px; padding-bottom: 5px; padding-left: 5px; margin-top: auto; margin-right: auto; margin-bottom: auto; margin-left: auto; height: 0px; width: 90%; max-width: 550px;\" width=\"90%\" height=\"0\">\n        <tbody style=\"box-sizing: border-box;\">\n        <tr style=\"box-sizing: border-box;\">\n          <td class=\"container-cell\" style=\"box-sizing: border-box; vertical-align: top; font-size: medium; padding-bottom: 50px;\" valign=\"top\">\n            <table class=\"c1766\" style=\"box-sizing: border-box; margin-top: 0px; margin-right: auto; background:#728089;margin-left: 0px; padding-top: 5px; padding-right: 5px; padding-bottom: 5px; padding-left: 5px; width: 100%; min-height: 30px;\" width=\"100%\">\n              <tbody style=\"box-sizing: border-box;\">\n              <tr style=\"box-sizing: border-box;\">\n                <td class=\"cell c1776\" style=\"box-sizing: border-box; width: 70%; vertical-align: middle;\" width=\"70%\" valign=\"middle\">\n                  <div class=\"c1144\" style=\"box-sizing: border-box; padding-top: 10px; padding-right: 10px; padding-bottom: 10px; padding-left: 10px; font-size: 17px; font-weight: 500;text-align: center;color: #fff;\">Court\n                    Appointed Special Advocate (CASA) / <%= @casa_organization&.name %>\n                    <br style=\"box-sizing: border-box;\">\n                  </div>\n                </td>\n              </tr>\n              </tbody>\n            </table>\n            <table class=\"card\" style=\"background:#fff;box-sizing: border-box; min-height: 150px; padding-top: 5px; padding-right: 5px; padding-bottom: 5px; padding-left: 5px; margin-bottom: 20px; height: 0px;\" height=\"0\">\n              <tbody style=\"box-sizing: border-box;\">\n              <tr style=\"box-sizing: border-box;\">\n                <td class=\"card-cell\" style=\"box-sizing: border-box; background-color: rgb(255, 255, 255); overflow-x: hidden; overflow-y: hidden; border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; text-align: center;\" bgcolor=\"rgb(255, 255, 255)\" align=\"center\">\n                  <table class=\"table100 c1357\" style=\"box-sizing: border-box; width: 100%; min-height: 150px; padding-top: 5px; padding-right: 5px; padding-bottom: 5px; padding-left: 5px; height: 0px; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; border-collapse: collapse;\" width=\"100%\" height=\"0\">\n                    <tbody style=\"box-sizing: border-box;\">\n                    <tr style=\"box-sizing: border-box;\">\n                      <td class=\"card-content\" style=\"box-sizing: border-box; font-size: 13px; line-height: 20px; color: rgb(111, 119, 125); padding-top: 10px; padding-right: 20px; padding-bottom: 0px; padding-left: 20px; vertical-align: top;\" valign=\"top\">\n                        <%= yield %>\n                      </td>\n                    </tr>\n                    </tbody>\n                  </table>\n                </td>\n              </tr>\n              </tbody>\n            </table>\n            <table class=\"footer\" style=\"box-sizing: border-box; margin-top: 50px; color: rgb(152, 156, 165); text-align: center; font-size: 11px; padding-top: 5px; padding-right: 5px; padding-bottom: 5px; padding-left: 5px;\" align=\"center\">\n              <tbody style=\"box-sizing: border-box;\">\n              <tr style=\"box-sizing: border-box;\">\n                <td class=\"footer-cell\" style=\"box-sizing: border-box;\">\n                </td>\n              </tr>\n              </tbody>\n            </table>\n            <div class=\"c2577\" style=\"text-align:center;box-sizing: border-box; padding-top: 10px; padding-right: 10px; padding-bottom: 10px; padding-left: 10px;\">\n              <p class=\"footer-info\" style=\"box-sizing: border-box;\">Court Appointed Special Advocate (CASA) /\n                <%= @casa_organization&.name %>\n                <br style=\"box-sizing: border-box;\">\n                <br style=\"box-sizing: border-box;\"><%= @casa_organization&.address %>\n              </p>\n            </div>\n          </td>\n        </tr>\n        </tbody>\n      </table>\n    </td>\n  </tr>\n  </tbody>\n</table>\n</body>\n</html>\n"
  },
  {
    "path": "app/views/layouts/mailer.text.erb",
    "content": "<%= yield %>\n"
  },
  {
    "path": "app/views/learning_hour_topics/_form.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h1>\n          <%= title %>\n        </h1>\n      </div>\n    </div>\n  </div>\n</div><!-- ==== end title ==== -->\n\n<!-- ========== card start ========== -->\n<div class=\"card-style mb-30\">\n  <%= form_with(model: learning_hour_topic, local: true) do |form| %>\n    <div class=\"alert-box danger-alert\">\n      <%= render \"/shared/error_messages\", resource: learning_hour_topic %>\n    </div>\n    <div class=\"input-style-1\">\n      <%= form.label :name, \"Name\" %>\n      <%= form.text_field :name, class: \"form-control\", required: true %>\n    </div>\n\n    <div class=\"actions mb-10\">\n      <%= button_tag(type: \"submit\", class: \"btn-sm main-btn primary-btn btn-hover\") do %>\n        <i class=\"lni lni-checkmark-circle mr-5\"></i> Submit\n      <% end %>\n    </div>\n  <% end %>\n</div>\n<!-- card end -->\n"
  },
  {
    "path": "app/views/learning_hour_topics/edit.html.erb",
    "content": "<%= render partial: \"form\", locals: {title: \"Learning Topic\", learning_hour_topic: @learning_hour_topic} %>\n"
  },
  {
    "path": "app/views/learning_hour_topics/new.html.erb",
    "content": "<%= render partial: \"form\", locals: {title: \"New Learning Topic\", learning_hour_topic: @learning_hour_topic} %>\n"
  },
  {
    "path": "app/views/learning_hour_types/_form.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h1>\n          <%= title %>\n        </h1>\n      </div>\n    </div>\n  </div>\n</div><!-- ==== end title ==== -->\n\n<!-- ========== card start ========== -->\n<div class=\"card-style mb-30\">\n  <%= form_with(model: learning_hour_type, local: true) do |form| %>\n    <div class=\"alert-box danger-alert\">\n      <%= render \"/shared/error_messages\", resource: learning_hour_type %>\n    </div>\n    <div class=\"input-style-1\">\n      <%= form.label :name, \"Name\" %>\n      <%= form.text_field :name, class: \"form-control\", required: true %>\n    </div>\n\n    <div class=\"form-check checkbox-style mb-20\">\n      <%= form.check_box :active, class: 'form-check-input' %>\n      <%= form.label :active, \"Active?\", class: 'form-check-label' %>\n    </div>\n\n    <div class=\"actions mb-10\">\n      <%= button_tag(type: \"submit\", class: \"btn-sm main-btn primary-btn btn-hover\") do %>\n        <i class=\"lni lni-checkmark-circle mr-5\"></i> Submit\n      <% end %>\n    </div>\n  <% end %>\n</div>\n<!-- card end -->\n"
  },
  {
    "path": "app/views/learning_hour_types/edit.html.erb",
    "content": "<%= render partial: \"form\", locals: {title: \"Type of Learning\", learning_hour_type: @learning_hour_type} %>\n"
  },
  {
    "path": "app/views/learning_hour_types/new.html.erb",
    "content": "<%= render partial: \"form\", locals: {title: \"New Type of Learning\", learning_hour_type: @learning_hour_type} %>\n"
  },
  {
    "path": "app/views/learning_hours/_confirm_note.html.erb",
    "content": "<div class=\"modal fade\" id=\"confirm-submit\" tabindex=\"-1\" role=\"dialog\" aria-labelledby=\"myModalLabel\" aria-hidden=\"true\">\n  <div class=\"modal-dialog\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        Congratulations\n      </div>\n      <div class=\"modal-body\">\n        New entry created\n        <div>\n        <h6>Note</h6>\n        <div id=\"note-content\"></div>\n        </div>\n      </div>\n\n      <div class=\"modal-footer\">\n        <button type=\"button\" class=\"btn-sm main-btn light-btn-outline btn-hover\" data-dismiss=\"modal\" id=\"modal-learning-hours-cancel\">\n          <i class=\"lni lni-arrow-left mr-10\"></i>Go Back to Form\n        </button>\n        <%= button_tag(\n          type: \"submit\",\n          class: \"main-btn primary-btn btn-hover btn-sm\",\n          id: \"modal-learning-hours-submit\"\n        ) do %>\n          <i class=\"lni lni-checkmark-circle mr-10\"></i>Continue Submitting\n        <% end %>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/learning_hours/_form.html.erb",
    "content": "<%= form_with(model: learning_hour,\n              url: learning_hour.persisted? ? learning_hour_path(learning_hour) : learning_hours_path,\n              local: true,\n              html: { id: \"learning-hours-form\"}) do |form| %>\n\n  <div class=\"mt-2\">\n    <%= render \"/shared/error_messages\", resource: learning_hour %>\n  </div>\n\n  <%= form.hidden_field :user_id, value: current_user.id %>\n\n  <div class=\"field-card mt-3\">\n    <div class=\"input-style-1\">\n      <%= form.label :name, \"Learning Hours Title\" %>\n      <%= form.text_field :name,\n                          placeholder: \"-- Enter a title --\",\n                          value: learning_hour.name,\n                          class:\"mr-5\",\n                          required: true %>\n    </div>\n    <div class=\"select-style-1\">\n      <%= form.label :learning_hour_type_id, \"Type of Learning\" %>\n      <div class=\"select-position\">\n        <%= form.collection_select :learning_hour_type_id,\n          LearningHourType.for_organization(current_user.casa_org).active,\n                                   :id,\n                                   :name,\n                                   prompt: \"Select learning type\",\n                                   value: learning_hour.learning_hour_type_id %>\n      </div>\n    </div>\n    <% if current_user.casa_org.learning_topic_active %>\n      <div class=\"select-style-1\">\n        <%= form.label :learning_hour_topic_id, \"Learning Topic\" %>\n        <div class=\"select-position\">\n          <%= form.collection_select :learning_hour_topic_id,\n            LearningHourTopic.for_organization(current_user.casa_org),\n                                    :id,\n                                    :name,\n                                    prompt: \"Select learning topic\",\n                                    value: learning_hour.learning_hour_topic_id %>\n        </div>\n      </div>\n    <% end %>\n      </div>\n  <div class=\"field-card duration mt-4\">\n    <h5 class=\"mb-3\">Learning Duration</h5>\n    <%= render(Form::HourMinuteDurationComponent.new(form: form, hour_value: learning_hour.duration_hours, minute_value: learning_hour.duration_minutes)) %>\n  </div>\n  <div class=\"field-card mt-4\">\n    <h5 class=\"mb-3\"><%= form.label :occurred_at, \"Occurred On\" %>:</h5>\n    <% occurred_at = learning_hour.occurred_at || Date.today %>\n    <div class=\"input-style-1\">\n      <%= form.date_field :occurred_at,\n                          value: occurred_at.to_date,\n                          class: \"form-control label-font-weight\" %>\n    </div>\n  </div>\n  <div class=\"actions mt-3\">\n    <%= button_tag(\n      type: \"submit\",\n      class: \"main-btn primary-btn btn-hover btn-sm wide_button\"\n    ) do %>\n      <% if learning_hour.persisted? %>\n        <i class=\"lni lni-save mr-10\"></i> Update Learning Hours Entry\n      <% else %>\n        <i class=\"lni lni-check-box mr-10\"></i> Create New Learning Hours Entry\n      <% end %>\n    <% end %>\n  </div>\n<% end %>\n"
  },
  {
    "path": "app/views/learning_hours/_learning_hours_table.html.erb",
    "content": "<div class=\"card card-container\">\n  <div class=\"card-body\">\n    <div class=\"table-wrapper table-responsive\">\n      <table class=\"table learning-hours-list\" id=\"all-learning-hours\">\n        <thead>\n          <tr>\n            <th>Title</th>\n            <th>Learning Type</th>\n            <% if current_user.casa_org.learning_topic_active %>\n              <th>Learning Topic</th>\n            <% end %>\n            <th>Date</th>\n            <th>Time Spent</th>\n          </tr>\n        </thead>\n        <tbody>\n          <% learning_hours.order(occurred_at: :desc).each do |learning_hour| %>\n            <tr>\n              <td>\n                <% if current_user.volunteer? %>\n                  <%= link_to learning_hour.name, learning_hour_path(id: learning_hour.id) %>\n                <% else %>\n                  <%= learning_hour.name %>\n                <% end %>\n              </td>\n              <td> <%= learning_hour.learning_hour_type.name %> </td>\n              <% if current_user.casa_org.learning_topic_active %>\n                <td> <%= learning_hour.learning_hour_topic&.name %> </td>\n              <% end %>\n              <td> <%= learning_hour.occurred_at.strftime(\"%B %-d, %Y\") %> </td>\n              <td>\n              <% if (learning_hour.duration_hours > 0) %>\n                <%= learning_hour.duration_hours %> hr\n              <% end %>\n              <%= learning_hour.duration_minutes %> min\n              </td>\n            </tr>\n          <% end %>\n        </tbody>\n      </table>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/learning_hours/_supervisor_admin_learning_hours.html.erb",
    "content": "<div class=\"pt-30\">\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <div class=\"mb-30\">\n        <h1>Learning Hours</h1>\n      </div>\n    </div>\n  </div>\n</div>\n\n<!-- ========== tables-wrapper start ========== -->\n<div class=\"tables-wrapper\">\n  <div class=\"row\">\n    <div class=\"col-lg-12\">\n      <div class=\"card-style mb-30\">\n        <div class=\"table-wrapper table-responsive\">\n          <table class=\"table\" id=\"learning-hours\">\n            <thead>\n            <tr>\n              <th><h6>Volunteer</h6></th>\n              <th><h6>Time Completed YTD</h6></th>\n            </tr>\n            <!-- end table row-->\n            </thead>\n            <tbody>\n            <% @learning_hours.each do |learning_hour| %>\n              <tr>\n                <td><%= link_to(learning_hour.display_name, learning_hours_volunteer_path(learning_hour.user_id)) %></td>\n                <td><%= format_time(learning_hour.total_time_spent) %></td>\n              </tr>\n            <% end %>\n            </tbody>\n          </table>\n          <!-- end table -->\n        </div>\n      </div>\n      <!-- end card -->\n    </div>\n    <!-- end col -->\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/learning_hours/_volunteer_learning_hours.html.erb",
    "content": "<div class=\"row my-3\">\n  <div class=\"col-sm-12 dashboard-table-header\">\n    <h1>Learning Hours</h1>\n    <%= link_to new_learning_hour_path, class: \"btn-sm main-btn primary-btn btn-hover\" do %>\n      <i class=\"lni lni-check-box mr-10\"></i>Record Learning Hours\n    <% end %>\n  </div>\n</div>\n\n<%= render \"learning_hours_table\", learning_hours: %>\n"
  },
  {
    "path": "app/views/learning_hours/edit.html.erb",
    "content": "<h1 class=\"mt-3\">Edit Learning Hours</h1>\n\n<div class=\"flex-container\">\n  <%= render 'form', learning_hour: @learning_hour, form_type: 'edit' %>\n</div>\n"
  },
  {
    "path": "app/views/learning_hours/index.html.erb",
    "content": "<% if current_user.volunteer? %>\n  <%= render \"volunteer_learning_hours\", learning_hours: @learning_hours %>\n<% else %>\n  <%= render \"supervisor_admin_learning_hours\" %>\n<% end %>\n"
  },
  {
    "path": "app/views/learning_hours/new.html.erb",
    "content": "<h1 class=\"mt-3\">Record Learning Hours</h1>\n\n<div class=\"flex-container\">\n  <%= render 'form', learning_hour: @learning_hour, form_type: 'new' %>\n</div>\n"
  },
  {
    "path": "app/views/learning_hours/show.html.erb",
    "content": "<div class=\"row my-3\">\n  <div class=\"col-sm-12 dashboard-table-header\">\n    <h1><%= @learning_hour.name %></h1>\n  </div>\n</div>\n\n<div class=\"card card-container\">\n  <div class=\"card-body\">\n    <div class=\"table-wrapper table-responsive\">\n      <table class=\"table learning-hours-list\" id=\"all-learning-hours\">\n        <thead>\n          <tr>\n            <th>Title</th>\n            <th>Learning Type</th>\n            <th>Date</th>\n            <th>Time Spent</th>\n            <th>Action</th>\n          </tr>\n        </thead>\n        <tbody>\n          <tr>\n            <td><%= @learning_hour.name %></td>\n            <td><%= @learning_hour.learning_hour_type.name %></td>\n            <td><%= I18n.l(@learning_hour.occurred_at, format: :full) %></td>\n            <td>\n              <% if (@learning_hour.duration_hours > 0) %>\n                <%= @learning_hour.duration_hours %> hr\n              <% end %>\n              <%= @learning_hour.duration_minutes %> min\n            </td>\n            <td>\n              <%= link_to edit_learning_hour_path, class: \"text-primary\" do %><i class=\"lni lni-pencil-alt mr-5\"></i>Edit<% end %> |\n              <%= link_to learning_hour_path, class: \"text-danger\", method: :delete,\n        data: { confirm: \"Are you sure to delete this learning record?\" } do %><i class=\"lni lni-trash-can mr-5\"></i>Delete<% end %>\n            </td>\n          </tr>\n        </tbody>\n      </table>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/learning_hours/volunteers/show.html.erb",
    "content": "<div class=\"row my-3\">\n  <div class=\"col-sm-12 dashboard-table-header\">\n    <h1><%= @volunteer.display_name %>'s Learning Hours</h1>\n  </div>\n</div>\n\n<%= render \"learning_hours/learning_hours_table\", learning_hours: @learning_hours %>\n"
  },
  {
    "path": "app/views/learning_hours_mailer/learning_hours_report_email.html.erb",
    "content": "<!DOCTYPE html>\n<html style=\"font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n<head>\n  <meta name=\"viewport\" content=\"width=device-width\">\n  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n  <title>Actionable emails e.g. reset password</title>\n  <style>/* Email styles need to be inline, except @media queries */</style>\n  <style type=\"text/css\">\n    @media only screen and (max-width: 640px) {\n      body {\n        padding: 0 !important;\n      }\n\n      h3 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h3 {\n        font-size: 16px !important;\n      }\n\n      .container {\n        padding: 0 !important;\n        width: 100% !important;\n      }\n    }\n  </style>\n</head>\n\n<body itemscope itemtype=\"http://schema.org/EmailMessage\" style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;\" bgcolor=\"#f6f6f6\">\n  <table class=\"main-body\" style=\"box-sizing: border-box; min-height: 150px; padding-top: 5px; padding-right: 5px; padding-bottom: 5px; padding-left: 5px; width: 100%; height: 100%; background-color: rgb(234, 236, 237);\" width=\"100%\" height=\"100%\" bgcolor=\"rgb(234, 236, 237)\">\n    <tbody style=\"box-sizing: border-box;\">\n      <tr class=\"row\" style=\"box-sizing: border-box; vertical-align: top;\" valign=\"top\">\n        <td class=\"main-body-cell\" style=\"box-sizing: border-box;\">\n          <table class=\"container\" style=\"box-sizing: border-box; font-family: Helvetica, serif; min-height: 150px; padding-top: 5px; padding-right: 5px; padding-bottom: 5px; padding-left: 5px; margin-top: auto; margin-right: auto; margin-bottom: auto; margin-left: auto; height: 0px; width: 90%; max-width: 550px;\" width=\"90%\" height=\"0\">\n            <tbody style=\"box-sizing: border-box;\">\n              <tr style=\"box-sizing: border-box;\">\n                <td class=\"container-cell\" style=\"box-sizing: border-box; vertical-align: top; font-size: medium; padding-bottom: 50px;\" valign=\"top\">\n                  <table class=\"c1766\" style=\"box-sizing: border-box; margin-top: 0px; margin-right: auto; background:#728089;margin-left: 0px; padding-top: 5px; padding-right: 5px; padding-bottom: 5px; padding-left: 5px; width: 100%; min-height: 30px; background:#fff; min-height: 150px; margin-bottom: 20px; border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px;\" width=\"100%\" height=\"0\">\n                    <tbody style=\"box-sizing: border-box;\">\n                      <tr style=\"box-sizing: border-box;\">\n                        <td class=\"cell c1776\" style=\"box-sizing: border-box; width: 70%; vertical-align: middle;\" width=\"70%\" valign=\"middle\">\n                          <div class=\"c1144\" style=\"box-sizing: border-box; padding-right: 10px; padding-left: 10px; font-size: 17px; font-weight: 500;text-align: center;color: #fff;\">\n                          </div>\n                        </td>\n                      </tr>\n                      <tr style=\"box-sizing: border-box;\">\n                        <td class=\"card-content\" style=\"box-sizing: border-box; font-size: 13px; line-height: 20px; color: rgb(111, 119, 125); padding-top: 10px; padding-right: 35px; padding-bottom: 10px; padding-left: 34px; vertical-align: top;\" valign=\"top\">\n                          <h3 style=\"box-sizing: border-box; font-weight: 800; margin: 0px 0 5px; font-size: 16px;\">Learning hours have been added this week!</h3>\n                          <p style=\"box-sizing: border-box;\">Please see the attached full report or export the full report <%= link_to \"here\", reports_url %></p>\n                        </td>\n                      </tr>\n                    </tbody>\n                  </table>\n                  <table class=\"footer\" style=\"box-sizing: border-box; margin-top: 40px; color: rgb(152, 156, 165); text-align: center; font-size: 11px; padding-top: 5px; padding-right: 5px; padding-bottom: 5px; padding-left: 5px;\" align=\"center\">\n                    <tbody style=\"box-sizing: border-box;\">\n                      <tr style=\"box-sizing: border-box;\">\n                        <td class=\"footer-cell\" style=\"box-sizing: border-box;\"></td>\n                      </tr>\n                    </tbody>\n                  </table>\n                  <div class=\"c2577\" style=\"text-align:center;box-sizing: border-box; padding-top: 10px; padding-right: 10px; padding-bottom: 10px; padding-left: 10px;\">\n                    <p class=\"footer-info\" style=\"box-sizing: border-box;\">Court Appointed Special Advocate (CASA) <%= @casa_org.name %>\n                      <br style=\"box-sizing: border-box;\">\n                      <br style=\"box-sizing: border-box;\"><%= @casa_org.address %>\n                    </p>\n                  </div>\n                </td>\n              </tr>\n            </tbody>\n          </table>\n        </td>\n      </tr>\n    </tbody>\n  </table>\n</body>\n</html>\n"
  },
  {
    "path": "app/views/mileage_rates/_form.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h1>\n          <%= title %>\n        </h1>\n      </div>\n    </div>\n  </div>\n</div><!-- ==== end title ==== -->\n\n<!-- ========== card start ========== -->\n<div class=\"card-style mb-30\">\n  <%= form_with(model: mileage_rate, local: true, id: :new_mileage_rate) do |form| %>\n    <div class=\"alert-box danger-alert\">\n      <%= render \"/shared/error_messages\", resource: mileage_rate %>\n    </div>\n    <div class=\"input-style-1\">\n      <%= form.label :effective_date, 'Effective date' %>\n      <%= form.date_field :effective_date,\n                          value: mileage_rate.effective_date,\n                          class: \"form-control\" %>\n    </div>\n    <div class=\"input-style-1\">\n      <%= form.label :amount, 'Amount' %>\n      <%= form.number_field :amount,\n                      required: true,\n                      placeholder: '0.0',\n                      min: 0,\n                      step: 0.01,\n                      class: \"form-control\",\n                      aria: { label: \"Amount in US Dollars\" } %>\n    </div>\n\n    <div class=\"form-check checkbox-style mb-20\">\n      <%= form.check_box :is_active, class: 'form-check-input' %>\n      <%= form.label :is_active, \"Currently active?\", class: \"form-check-label\" %>\n    </div>\n\n    <div class=\"actions mb-10\">\n      <%= form.submit \"Save Mileage Rate\", class: \"main-btn primary-btn btn-hover\" %>\n    </div>\n  <% end %>\n</div>\n<!-- card end -->\n"
  },
  {
    "path": "app/views/mileage_rates/edit.html.erb",
    "content": "<%= render partial: \"form\", locals: { mileage_rate: @mileage_rate,\n                                      form_url: mileage_rate_path(@mileage_rate),\n                                      title: \"Edit Milage Rate\" } %>\n"
  },
  {
    "path": "app/views/mileage_rates/index.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h1>Mileage Rates</h1>\n      </div>\n    </div>\n    <!-- end col -->\n    <div class=\"col-md-6\">\n      <div class=\"breadcrumb-wrapper mb-30\">\n          <span class=\"ml-5\">\n            <%= link_to new_mileage_rate_path, class: \"btn-sm main-btn primary-btn\" do %>\n              <i class=\"lni lni-plus mr-10\"></i>\n              New Mileage Rate\n            <% end %>\n          </span>\n      </div>\n    </div>\n  </div>\n</div>\n\n<div class=\"tables-wrapper\">\n  <div class=\"row\">\n    <div class=\"col-lg-12\">\n      <div class=\"card-style mb-30\">\n        <div class=\"table-wrapper table-responsive\">\n          <table class=\"table\" id=\"mileage_rates\">\n            <thead>\n            <tr>\n              <th><h6>Effective date</h6></th>\n              <th><h6>Amount</h6></th>\n              <th><h6>Active?</h6></th>\n              <th><h6>Actions</h6></th>\n            </tr>\n            </thead>\n            <tbody>\n              <% @mileage_rates.each do |mileage_rate| %>\n                <tr>\n                  <td><%= I18n.l(mileage_rate.effective_date, format: :full, default: :nil) %></td>\n                  <td><%= number_to_currency(mileage_rate.amount) %></td>\n                  <td><%= mileage_rate.is_active? ? 'Yes' : 'No' %></td>\n                  <td>\n                  <%= link_to edit_mileage_rate_path(mileage_rate) do %>\n\n                    <div class=\"action\">\n                      <button class=\"text-danger\">\n                        <i class=\"lni lni-pencil-alt\"></i> Edit\n                      </button>\n                    </div>\n                  <% end %>\n                </td>\n                </tr>\n                <!-- end table row-->\n              <% end %>\n            </tbody>\n          </table>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/mileage_rates/new.html.erb",
    "content": "<%= render partial: \"form\", locals: { mileage_rate: @mileage_rate,\n                                      form_url: mileage_rates_path,\n                                      title: \"New Mileage Rate\" } %>\n"
  },
  {
    "path": "app/views/notes/edit.html.erb",
    "content": "<%= form_with(model: @note, local: true, url: volunteer_note_path(@volunteer, @note)) do |form| %>\n  <h1>Update Volunteer Note</h1>\n\n  <div class=\"input-style-1\">\n    <%= form.text_area :content,\n      rows: 5,\n      placeholder: \"Enter a note regarding the volunteer. These notes are only visible to CASA administrators and supervisors.\",\n      class: \"form-control\" %>\n  </div>\n\n  <%= button_tag(\n    type: \"submit\",\n    class: \"main-btn primary-btn btn-hover btn-sm\",\n    id: \"note-submit\"\n  ) do %>\n    Update Note\n  <% end %>\n<% end %>\n"
  },
  {
    "path": "app/views/notifications/_notification.html.erb",
    "content": "<a\n  href=\"<%= notification.url %>\"\n  class=\"<%= notification_row_class(notification) %> list-group-item list-group-item-action flex-column align-items-start\">\n  <div class=\"d-flex flex-row\">\n    <div class=\"d-flex-shrink-0\"><%= notification_icon(notification) %></div>\n    <div class=\"ml-2 flex-grow-1\">\n      <div class=\"d-flex justify-content-between\">\n        <h5 class=\"mb-1\"><%= notification.title %></h5>\n        <small><%= time_ago_in_words(notification.created_at) %> ago</small>\n      </div>\n      <div class=\"my-1\">\n        <%= simple_format notification.message %>\n      </div>\n    </div>\n  </div>\n</a>\n"
  },
  {
    "path": "app/views/notifications/_patch_notes.html.erb",
    "content": "<div id=\"patch-note-notification\" class=\"list-group-item\">\n  <div class=\"d-flex flex-row\">\n    <div class=\"d-flex-shrink-0\">\n      <i class='fas fa-bell'></i>\n    </div>\n    <div class=\"ml-2 flex-grow-1\">\n      <div class=\"d-flex justify-content-between\">\n        <h4 class=\"mb-1\">Patch Notes</h4>\n        <small><%= time_ago_in_words(deploy_time) %> ago</small>\n      </div>\n      <% patch_notes.each do |type, notes_per_type| %>\n        <div class=\"my-1\">\n          <h5 class=\"ml-4 fw-bold\"><%= type %></h5>\n          <ul>\n            <% notes_per_type.each do |note_text| %>\n              <li><%= simple_format note_text %></li>\n            <% end %>\n          </ul>\n        </div>\n      <% end %>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/notifications/index.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h1>Notifications</h1>\n      </div>\n    </div>\n  </div>\n</div>\n<div class=\"list-group\">\n  <% notifications_after_and_including_deploy(@notifications).each do | notification | %>\n    <%= render NotificationComponent.new(notification: notification) %>\n  <% end %>\n  <% unless (@deploy_time.nil? or @patch_notes.empty?) %>\n    <%= render partial: \"patch_notes\", locals: {deploy_time: @deploy_time, patch_notes: patch_notes_as_hash_keyed_by_type_name(@patch_notes)} %>\n  <% end %>\n  <% notifications_before_deploy(@notifications).each do | notification | %>\n    <%= render NotificationComponent.new(notification: notification) %>\n  <% end %>\n\n  <% if @notifications.empty? and (@patch_notes.empty? or @deploy_time.nil?) %>\n    <div class=\"card-style my-4\">\n      <div class=\"card-body\">\n        You currently don't have any notifications. Notifications are generated when someone requests follow-up on a case contact.\n      </div>\n    </div>\n  <% end %>\n</div>\n\n<% @notifications.each do |notification| %>\n  <% if read_when_seen_notifications.include? notification.type %>\n    <% notification.mark_as_read %>\n  <% end %>\n<% end %>\n"
  },
  {
    "path": "app/views/other_duties/_form.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h1><%= title %></h1>\n      </div>\n    </div>\n  </div>\n</div>\n<div class=\"card-style mb-30\">\n  <%= form_with(model: other_duty, local: true) do |form| %>\n  <div class=\"alert-box danger-alert\">\n    <%= render \"/shared/error_messages\", resource: other_duty %>\n  </div>\n  <div class=\"input-style-1\">\n    <%= form.label :occurred_at, \"Occurred On\" %>\n    <% occurred_at = @other_duty.occurred_at || Time.zone.now %>\n    <%= form.date_field :occurred_at, value: occurred_at.to_date %>\n  </div>\n\n    <div class=\"row input-style-1\">\n      <label>Duty Duration</label>\n      <div class=\"col-sm-6\">\n        <div class=\"input-style-1 duration-hours\">\n          <%= form.number_field :duration_hours,\n                              min: 0,\n                              size: \"10\",\n                              value: duration_hours(other_duty),\n                              required: true %> &nbsp; <span class=\"label-font-weight\"> hour(s)</span>\n        </div>\n      </div>\n      <div class=\"col-sm-6\">\n        <div class=\"input-style-1 duration-minutes\">\n          <%= form.number_field :duration_minutes,\n                                min: 0,\n                                size: \"10\",\n                                value: duration_minutes(other_duty),\n                                required: true %> &nbsp; <span class=\"label-font-weight\"> minute(s)</span>\n        </div>\n      </div>\n    </div>\n\n    <div class=\"mb-30\">\n      <div class=\"input-style-1\">\n      <%= form.label :notes, \"Enter Notes *\" %>\n        <%= form.text_area :notes, rows: 5, placeholder: \"Enter notes here\",\nclass: \"cc-italic form-control\", required: true %>\n      </div>\n    </div>\n    <div class=\"actions mb-10\">\n      <%= button_tag(\n            type: \"submit\",\n            class: \"btn-sm main-btn primary-btn btn-hover\"\n          ) do %>\n          <i class=\"lni lni-checkmark-circle mr-10\"></i> Submit\n      <% end %>\n    </div>\n  <% end %>\n</div>\n"
  },
  {
    "path": "app/views/other_duties/edit.html.erb",
    "content": "<%= render partial: \"form\",\n           locals: {title: \"Editing Duty\", other_duty: @other_duty} %>\n"
  },
  {
    "path": "app/views/other_duties/index.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"col-sm-12 dashboard-table-header mb-30\">\n        <h1>Other Duties</h1>\n        <% if current_user.volunteer? %>\n          <%= link_to new_other_duty_path, class: \"main-btn btn-sm primary-btn btn-hover\" do %>\n            <i class=\"lni lni-plus\"></i>\n            New Duty\n          <% end %>\n        <% end %>\n      </div>\n    </div>\n  </div>\n</div>\n<div class=\"tables-wrapper\">\n  <div class=\"row\">\n    <div class=\"col-lg-12\">\n      <div class=\"card-style mb-30\">\n        <% if @volunteer_duties.empty? %>\n          There are no duties to display!\n        <% else %>\n          <% @volunteer_duties.each do |volunteer| %>\n            <% if volunteer[:other_duties].any? %>\n                <% unless current_user.id == volunteer[:volunteer].id %>\n                  <%= link_to(volunteer[:volunteer].display_name, volunteer_path(volunteer[:volunteer].id)) %>\n                <% end %>\n                <div class=\"table-wrapper table-responsive\">\n                  <table class=\"table\">\n                    <thead>\n                    <tr>\n                      <th>Duties Occurred</th>\n                      <th>Created</th>\n                      <th>Duration</th>\n                      <th>Title</th>\n                    </tr>\n                    </thead>\n                    <tbody>\n                    <% volunteer[:other_duties].decorate.each do |duty| %>\n                      <tr>\n                      <td><%= I18n.l(duty.occurred_at, format: :full, default: nil) %></td>\n                      <td><%= I18n.l(duty.created_at, format: :full, default: nil) %></td>\n                      <td><%= duty.duration_in_minutes %></td>\n                      <td><%= duty.truncate_notes %></td>\n                      <% if current_user.volunteer? %>\n                        <td><%= link_to \"Edit\", edit_other_duty_path(duty) %></td>\n                      <% end %>\n                      </tr>\n                    <% end %>\n                    </tbody>\n                  </table>\n                </div>\n            <% end %>\n          <% end %>\n        <% end %>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/other_duties/new.html.erb",
    "content": "<%= render partial: \"form\",\n           locals: {title: \"New Duty\", other_duty: @other_duty} %>\n"
  },
  {
    "path": "app/views/placement_types/_fields.html.erb",
    "content": "<div class=\"input-style-1\">\n  <%= form.label :placement_started_at, \"Placement Started At\" %>\n  <%= form.date_field :placement_started_at,\n                      value: placement.placement_started_at&.to_date || Time.zone.now,\n                      class: \"form-control\" %>\n</div>\n<div class=\"select-style-1\">\n  <%= form.label :placement_type_id, \"Placement Type\" %>\n  <div class=\"select-position\">\n    <%= form.collection_select(\n          :placement_type_id,\n          PlacementType.for_organization(current_organization).order_alphabetically,\n          :id, :name,\n          {include_hidden: false, include_blank: \"-Select Placement Type-\"},\n          {class: \"form-control\"}\n        ) %>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/placement_types/_form.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h1>\n          <%= title %>\n        </h1>\n      </div>\n    </div>\n  </div>\n</div><!-- ==== end title ==== -->\n\n<!-- ========== card start ========== -->\n<div class=\"card-style mb-30\">\n  <%= form_with(model: placement_type, local: true) do |form| %>\n    <div class=\"alert-box danger-alert\">\n      <%= render \"/shared/error_messages\", resource: placement_type %>\n    </div>\n    <div class=\"input-style-1\">\n      <%= form.label :name, \"Name\" %>\n      <%= form.text_field :name, class: \"form-control\", required: true %>\n    </div>\n\n    <div class=\"actions mb-10\">\n      <%= button_tag(type: \"submit\", class: \"btn-sm main-btn primary-btn btn-hover\") do %>\n        <i class=\"lni lni-checkmark-circle mr-5\"></i> Submit\n      <% end %>\n    </div>\n  <% end %>\n</div>\n<!-- card end -->\n"
  },
  {
    "path": "app/views/placement_types/edit.html.erb",
    "content": "<%= render partial: \"form\", locals: {title: \"Edit Placement Type\", placement_type: @placement_type} %>\n"
  },
  {
    "path": "app/views/placement_types/new.html.erb",
    "content": "<%= render partial: \"form\", locals: {title: \"New Placement Type\", placement_type: @placement_type} %>\n"
  },
  {
    "path": "app/views/placements/_fields.html.erb",
    "content": "<div class=\"input-style-1\">\n  <%= form.label :placement_started_at, \"Placement Started At\" %>\n  <%= form.date_field :placement_started_at,\n                      value: placement.placement_started_at&.to_date || Time.zone.now,\n                      class: \"form-control\" %>\n</div>\n<div class=\"select-style-1\">\n  <%= form.label :placement_type_id, \"Placement Type\" %>\n  <div class=\"select-position\">\n    <%= form.collection_select(\n          :placement_type_id,\n          PlacementType.for_organization(current_organization).order_alphabetically,\n          :id, :name,\n          {include_hidden: false, include_blank: \"-Select Placement Type-\"},\n          {class: \"form-control\"}\n        ) %>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/placements/_form.html.erb",
    "content": "<div class=\"card-style mb-30\">\n  <div class=\"alert-box danger-alert\">\n    <%= render \"/shared/error_messages\", resource: placement %>\n  </div>\n  <%= form_with(model: placement, url: [casa_case, placement], local: true,\n                data: { controller: \"placement-form\", nested_form_wrapper_selector_value: \".nested-form-wrapper\" }) do |form| %>\n    <div class=\"row align-items-center\">\n      <div class=\"col-md-6\">\n        <h6><strong>Case Number:</strong> <%= link_to casa_case.case_number, casa_case %></h6>\n      </div>\n      <div class=\"col-md-6\">\n        <div class=\"breadcrumb-wrapper\">\n          <span class=\"top-page-actions ml-5\">\n            <%= button_tag(type: \"submit\", class: \"btn-sm main-btn primary-btn btn-hover\") do %>\n              <% if placement.persisted? %>\n                <i class=\"lni lni-pencil-alt\"></i> Update\n              <% else %>\n                <i class=\"lni lni-plus\"></i> Create\n              <% end %>\n            <% end %>\n          </span>\n        </div>\n      </div>\n    </div>\n    <div class=\"row align-items-center\">\n      <%= render 'placements/fields', placement: placement, form: form, casa_case: casa_case %>\n    </div>\n  <% end %>\n</div>\n"
  },
  {
    "path": "app/views/placements/edit.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h1>Editing Placement</h1>\n      </div>\n    </div>\n  </div>\n</div>\n\n<%= render 'form', casa_case: @casa_case, placement: @placement %>\n"
  },
  {
    "path": "app/views/placements/index.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h1>Placement History for\n          <%= link_to \"#{@casa_case.case_number}\", casa_case_path(@casa_case) %>\n        </h1>\n      </div>\n    </div>\n    <div class=\"col-md-6\">\n      <div class=\"breadcrumb-wrapper mb-30\">\n        <%= link_to new_casa_case_placement_path(@casa_case), class: \"main-btn btn-sm primary-btn btn-hover ml-3\" do %>\n          <i class=\"lni lni-plus\"></i>\n          New Placement\n        <% end %>\n      </div>\n    </div>\n  </div>\n</div>\n<div class=\"col-lg-12\">\n  <div class=\"card-style mb-30\">\n    <table class=\"table\">\n      <thead>\n      <tr>\n        <th>Placement Type</th>\n        <th>Date</th>\n        <th></th>\n        <th></th>\n      </tr>\n      </thead>\n      <tbody>\n      <% @placements.each_with_index do |placement, idx| %>\n        <tr>\n          <td><%= placement.placement_type.name %></td>\n          <td>\n            <%= placement.decorate.formatted_date %>\n            -\n            <% if idx.zero? %>\n              Present\n            <% elsif @placements[idx - 1].placement_started_at %>\n              <%= @placements[idx - 1].decorate.formatted_date %>\n            <% end %>\n          </td>\n          <td><%= link_to edit_casa_case_placement_path(@casa_case, placement), class: \"btn-sm main-btn primary-btn-outline btn-hover ms-auto\" do %>\n              <i class=\"lni lni-pencil-alt\"></i>\n              Edit\n            <% end %>\n          </td>\n          <td>\n            <%= render(Modal::OpenLinkComponent.new(target: placement.id, klass: \"btn-sm main-btn danger-btn-outline btn-hover ms-auto\")) do %>\n              <i class=\"lni lni-trash-can\"></i>\n              Delete\n            <% end %>\n          </td>\n        </tr>\n        <%= render(Modal::GroupComponent.new(id: placement.id)) do |component| %>\n          <% component.with_header(text: \"Delete Placement?\", id: placement.id) %>\n          <% component.with_footer do %>\n            <%= link_to casa_case_placement_path(@casa_case, placement), method: :delete, class: \"btn-sm main-btn danger-btn btn-hover ms-auto\" do %>\n              <i class=\"lni lni-trash-can\"></i>\n              Confirm\n            <% end %>\n          <% end %>\n        <% end %>\n      <% end %>\n      </tbody>\n    </table>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/placements/new.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h1>New Placement</h1>\n      </div>\n    </div>\n  </div>\n</div>\n\n<%= render 'form', casa_case: @casa_case, placement: @placement %>\n"
  },
  {
    "path": "app/views/placements/show.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h1>Placement</h1>\n        <time datetime=\"<%= @placement.placement_started_at %>\" class=\"d-inline-block h4\"><%= @placement.decorate.formatted_date %></time>\n      </div>\n    </div>\n    <div class=\"col-md-6\">\n      <div class=\"breadcrumb-wrapper mb-30\">\n        <%= link_to edit_casa_case_placement_path(@casa_case, @placement), class: \"btn-sm main-btn primary-btn btn-hover\" do %>\n          <i class=\"lni lni-pencil-alt mr-10\"></i>\n          Edit\n        <% end %>\n      </div>\n    </div>\n  </div>\n</div>\n<div class=\"col-lg-12\">\n  <div class=\"card-style mb-30\">\n    <dl>\n      <dt>\n        <h6>Case Number:</h6>\n      </dt>\n      <dd class=\"mb-3\"><%= link_to \"#{@casa_case.case_number}\", casa_case_path(@casa_case) %></dd>\n      <dt>\n        <h6>Placement Type:</h6>\n      </dt>\n      <dd class=\"mb-3\"><%= @placement.placement_type.name %></dd>\n    </dl>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/reimbursements/_datatable.html.erb",
    "content": "<div class=\"card-style\">\n  <div class=\"table-responsive\">\n    <table\n      class=\"table\"\n      data-source=\"<%= @datatable_url %>\"\n      id=\"reimbursements-datatable\">\n      <thead>\n        <tr>\n          <th>Volunteer</th>\n          <th>Case Number</th>\n          <th>Contact Types</th>\n          <th>Occurred At</th>\n          <th>Miles Driven</th>\n          <th>Address</th>\n          <th>Reimbursement Complete</th>\n        </tr>\n      </thead>\n      <tbody>\n      </tbody>\n    </table>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/reimbursements/_filter_trigger.html.erb",
    "content": "<button\n  class=\"btn btn-secondary dropdown-toggle\"\n  type=\"button\"\n  data-bs-toggle=\"dropdown\"\n  aria-haspopup=\"true\"\n  aria-expanded=\"false\">\n  <%= title %>\n</button>\n"
  },
  {
    "path": "app/views/reimbursements/_occurred_at_filter_input.html.erb",
    "content": "<div>\n  <%= label %>\n  <%= date_field(\n    nil,\n    name,\n    data: {\n      date_end_date: Time.now,\n      date_start_date: @occurred_at_filter_start_date\n    },\n    class: \"form-control\"\n  ) %>\n</div>\n"
  },
  {
    "path": "app/views/reimbursements/_reimbursement_complete.html.erb",
    "content": "<div class=\"card card-container\">\n  <div class=\"card-body\">\n    <table class=\"table table-striped table-bordered\" id=\"<%= @casa_cases_filter_id %>\">\n      <thead>\n      <tr>\n\n      </tr>\n      </thead>\n      <tbody>\n\n      </tbody>\n    </table>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/reimbursements/_table.html.erb",
    "content": "<div class=\"card card-container\">\n  <div class=\"card-body\">\n    <div class=\"table-responsive\">\n      <table class=\"table table-striped table-bordered\" id=\"reimbursements\">\n        <thead>\n        <tr>\n          <th><%= \"Volunteer\" %></th>\n          <th><%= \"Case Number\" %></th>\n          <th><%= \"Contact Types\" %></th>\n          <th><%= \"Occurred At\" %></th>\n          <th><%= \"Miles Driven\" %></th>\n          <th><%= \"Address\" %></th>\n          <th><%= \"Reimbursement Complete\" %></th>\n        </tr>\n        </thead>\n        <tbody>\n        <% @grouped_reimbursements.each do |_, reimbursements| %>\n          <% reimbursement = reimbursements.first %>\n          <tr>\n            <td id=\"volunteer-<%= reimbursement.id %>\">\n              <span class=\"mobile-label\">Volunteer</span>\n              <%= link_to(reimbursement.creator.display_name, volunteer_path(reimbursement.creator)) %>\n            </td>\n\n            <td id=\"case-number-<%= reimbursement.id %>\">\n              <span class=\"mobile-label\">Case Number</span>\n              <% reimbursements.each do |r| %>\n                <%= link_to(r.casa_case.case_number, casa_case_path(r.casa_case)) %><br>\n              <% end %>\n            </td>\n\n            <td id=\"contact-types-<%= reimbursement.id %>\">\n              <%= contact_types_list(reimbursement) %>\n            </td>\n\n            <td id=\"date-attempted-<%= reimbursement.id %>\">\n              <%= reimbursement.occurred_at.strftime(\"%B %d %Y\") %>\n            </td>\n\n            <td id=\"amount-<%= reimbursement.id %>\">\n              <%= reimbursement.miles_driven %>\n            </td>\n\n            <td id=\"volunteer-address-<%= reimbursement.id %>\">\n              <%= reimbursement.creator.address&.content %>\n            </td>\n\n            <td>\n              <%= form_with(model: reimbursement, url: reimbursement_mark_as_complete_path(reimbursement),\n                            method: \"patch\") do |form| %>\n                <%= form.label :reimbursement_complete, \"Yes\" %>\n                <%= form.check_box(:reimbursement_complete, value: reimbursement.reimbursement_complete,\n                                   onchange: 'this.form.submit();') %>\n              <% end %>\n            </td>\n          </tr>\n        <% end %>\n        </tbody>\n      </table>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/reimbursements/index.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h1>Reimbursement Queue</h1>\n      </div>\n    </div>\n\n    <div class=\"col-md-6\">\n      <div class=\"breadcrumb-wrapper mb-30\">\n        <span class=\"ml-5\">\n          <%= form_with url: mileage_reports_path(format: :csv), method: :get do |f| %>\n                 <%= button_tag(\n                       type: \"submit\",\n                       class: \"btn-sm main-btn primary-btn btn-hover report-form-submit\",\n                       data: { disable_with: \"Downloading Mileage Report\" }\n                     ) do %>\n            <i class=\"lni lni-download mr-10\"></i> Download Mileage Report\n            <% end %>\n          <% end %>\n        </span>\n      </div>\n    </div>\n  </div>\n</div>\n\n<div>\n  <div class=\"row volunteer-filters\">\n    <div class=\"col-sm-12 flex align-items-center\">\n      <h4 class=\"pull-left mr-2 my-1\">Filter by:</h4>\n      <div class=\"dropdown pull-left mx-2\" data-filter=\"volunteer\">\n        <select class=\"form-control select2 creator_ids\" multiple=\"true\" data-placeholder=\"Volunteer\">\n          <% @volunteers_for_filter.each do |volunteer| %>\n            <option value=\"<%= volunteer[0] %>\"><%= volunteer[1] %></option>\n          <% end %>\n        </select>\n      </div>\n      <!-- Occurred at filter -->\n      <div class=\"dropdown pull-left mr-2\" data-filter=\"occurred_at\">\n        <%= render partial: \"filter_trigger\", locals: { title: \"Occurred at\" } %>\n        <div class=\"dropdown-menu\">\n          <%= render partial: \"occurred_at_filter_input\", locals: {\n            label: \"Starting from\",\n            name: :occurred_at_starting\n          } %>\n          <%= render partial: \"occurred_at_filter_input\", locals: {\n            label: \"Ending at\",\n            name: :occurred_at_ending\n          } %>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n\n<div>\n  <nav class=\"nav nav-tabs justify-content-center nav-justified mt-3\">\n    <%= link_to \"Needs Review\", reimbursements_path(status: \"needs_review\"),\n                class: \"nav-item nav-link #{active_if(!@complete_status)}\" %>\n\n    <%= link_to \"Reimbursement Complete\", reimbursements_path(status: \"complete\"),\n                class: \"nav-item nav-link #{active_if(@complete_status)}\" %>\n  </nav>\n  <%= render \"datatable\" %>\n</div>\n"
  },
  {
    "path": "app/views/reports/_filter.html.erb",
    "content": "<div class=\"warning-modal\">\n  <div class=\"modal fade\" id=\"filterColumns\" tabindex=\"-1\" aria-hidden=\"true\">\n    <div class=\"modal-dialog modal-dialog-centered\">\n      <div class=\"modal-content card-style\">\n        <div class=\"modal-header px-0 border-0\">\n          <h5 class=\"text-bold\">Filter Exported Columns</h5>\n          <button\n            type=\"button\"\n            class=\"border-0 bg-transparent h1\"\n            data-bs-dismiss=\"modal\">\n            <i class=\"lni lni-cross-circle\"></i>\n          </button>\n        </div>\n        <div class=\"modal-body px-0\">\n          <div class=\"mb-30\">\n            <h6 class=\"mb-20\">\n              Select columns:\n            </h6>\n            <%= form.fields_for :filtered_csv_cols do |ff| %>\n              <% CaseContactReport::COLUMNS.each_with_index do |column, index| %>\n                <div class=\"form-check checkbox-style m-2\">\n                  <%= ff.check_box column, { class: \"form-check-input\", checked: true }, true, false %>\n                  <%= ff.label column, column.to_s.titleize, class: \"form-check-label\" %>\n                </div>\n              <% end %>\n            <% end %>\n          </div>\n          <div class=\"action d-flex flex-wrap justify-content-end\">\n            <button\n              data-bs-dismiss=\"modal\"\n              class=\"main-btn success-btn-outline btn-hover m-1\"><i class=\"lni lni-download mr-10\"></i>\n              Export\n            </button>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/reports/index.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n\n    <div class=\"col-lg-12\">\n      <div class=\"title mb-30\">\n        <h1>Export Data</h1>\n      </div>\n    </div>\n\n    <div class=\"col-lg-12\">\n      <ul class=\"buttons-group mb-30\">\n        <li>\n          <%= form_with url: mileage_reports_path(format: :csv), method: :get do |f| %>\n            <%= button_tag(\n                  data: { disable_with: \"Downloading Mileage Report\" },\n                  class: \"btn-sm main-btn primary-btn btn-hover report-form-submit\"\n                ) do %>\n              <i class=\"lni lni-dashboard mr-10\"></i> Mileage Report\n            <% end %>\n          <% end %>\n        </li>\n\n        <li>\n          <%= form_with url: missing_data_reports_path(format: :csv), method: :get do |f| %>\n            <%= button_tag(\n                  data: { disable_with: \"Downloading Missing Data Report\" },\n                  class: \"btn-sm main-btn primary-btn btn-hover report-form-submit\"\n                ) do %>\n              <i class=\"lni lni-database mr-10\"></i> Missing Data Report\n            <% end %>\n          <% end %>\n        </li>\n\n        <li>\n          <%= form_with url: learning_hours_reports_path(format: :csv), method: :get do |f| %>\n            <%= button_tag(\n                  data: { disable_with: \"Downloading Learning Hours Report\" },\n                  class: \"btn-sm main-btn primary-btn btn-hover report-form-submit\"\n                ) do %>\n              <i class=\"lni lni-hourglass mr-10\"></i> Learning Hours Report\n            <% end %>\n          <% end %>\n        </li>\n\n        <li>\n          <%= form_with url: export_emails_path(format: :csv), method: :get do |f| %>\n            <%= button_tag(\n                  data: { disable_with: \"Downloading Export Volunteers Emails\" },\n                  class: \"btn-sm main-btn primary-btn btn-hover report-form-submit\"\n                ) do %>\n              <i class=\"lni lni-envelope mr-10\"></i> Export Volunteers Emails\n            <% end %>\n          <% end %>\n        </li>\n\n        <li>\n          <%= form_with url: followup_reports_path(format: :csv), method: :get do |f| %>\n            <%= button_tag(\n                  data: { disable_with: \"Downloading Followups Report\" },\n                  class: \"btn-sm main-btn primary-btn btn-hover report-form-submit\"\n                ) do %>\n              <i class=\"lni lni-check-box mr-10\"></i> Followups Report\n            <% end %>\n          <% end %>\n        </li>\n        <li>\n          <%= form_with url: placement_reports_path(format: :csv), method: :get do |f| %>\n            <%= button_tag(\n                  data: { disable_with: \"Downloading Placements Report\" },\n                  class: \"btn-sm main-btn primary-btn btn-hover report-form-submit\"\n                ) do %>\n              <i class=\"lni lni-check-box mr-10\"></i> Placements Report\n            <% end %>\n          <% end %>\n        </li>\n      </ul>\n    </div>\n\n    <div class=\"col-lg-12\">\n      <div class=\"card-style mb-30\">\n        <h6 class=\"mb-10\">Case Contacts Report</h6>\n        <p class=\"text-sm mb-20\">\n          This CSV is a listing of all fields for the case contacts of all volunteers. Select the start and end date.\n        </p>\n\n        <hr>\n\n        <div class=\"table-wrapper table-responsive\">\n          <%= form_with url: case_contact_reports_path(format: :csv), scope: 'report', method: :get, local: true do |f| %>\n\n            <div class=\"field form-group mb-20\">\n              <label class=\"form-label\"><h6>Starting From</h6></label>\n              <%= f.date_field :start_date,\n                               value: 6.months.ago,\n                               class: \"form-control\" %>\n            </div>\n\n            <div class=\"field form-group mb-20\">\n              <label class=\"form-label\"><h6>Ending At</h6></label>\n              <%= f.date_field :end_date,\n                               value: Date.today,\n                               class: \"form-control\" %>\n            </div>\n\n            <% if current_user&.casa_admin? || current_user&.supervisor? %>\n              <div class=\"select-style-1\">\n                <label><h6>Assigned To</h6></label>\n                <div class=\"select-position\">\n                  <%= f.collection_select(\n                        :supervisor_ids,\n                        Supervisor.where(casa_org: current_user.casa_org),\n                        :id,\n                        :display_name,\n                        {prompt: false, :include_hidden => false},\n                        {class: \"form-control form-select select2\", id:\"multiple-select-field1\", multiple:true, data: { placeholder: '-Select Supervisors-' }}\n                      ) %>\n                </div>\n              </div>\n\n              <div class=\"select-style-1\">\n                <label><h6>Volunteers</h6></label>\n                <div class=\"select-position\">\n                  <%= f.collection_select(\n                        :creator_ids,\n                        Volunteer.where(casa_org: current_user.casa_org),\n                        :id,\n                        :display_name,\n                        {prompt: false, :include_hidden => false},\n                        {class: \"form-control form-select select2\", id:\"multiple-select-field2\", multiple:true, data: { placeholder: '-Select Volunteers-' }}\n                      ) %>\n                </div>\n              </div>\n\n              <div class=\"select-style-1\">\n                <label><h6>Contact Type</h6></label>\n                <div class=\"select-position\">\n                  <%= f.collection_select(\n                        :contact_type_ids,\n                        ContactType.for_organization(current_user.casa_org),\n                        :id,\n                        :name,\n                        {prompt: false, :include_hidden => false},\n                        {class: \"form-control form-select select2\", id:\"multiple-select-field3\", multiple:true, data: { placeholder: '-Select Contact Types-' }}\n                      ) %>\n                </div>\n              </div>\n\n              <div class=\"select-style-1\">\n                <label><h6>Contact Type Group</h6></label>\n                <div class=\"select-position\">\n                  <%= f.collection_select(\n                        :contact_type_group_ids,\n                        ContactTypeGroup.for_organization(current_user.casa_org),\n                        :id,\n                        :name,\n                        { },\n                        { class: \"form-control form-select select2\", id:\"multiple-select-field4\", multiple:true, data: { placeholder: '-Select Contact Type Groups-' }  }\n                      ) %>\n                </div>\n              </div>\n\n              <h6 class=\"mb-10\">Want Driving Reimbursement</h6>\n              <div class=\"form-check radio-style mb-10\">\n                <%= f.collection_radio_buttons :want_driving_reimbursement, boolean_choices, :last, :first do |b| %>\n                  <div class=\"form-check\">\n                    <%= b.radio_button(class: \"form-check-input\") %>\n                    <%= b.label(class: \"form-check-label\") %>\n                  </div>\n                <% end %>\n              </div>\n\n              <h6 class=\"mb-10\">Contact Made</h6>\n              <div class=\"form-check radio-style mb-20\">\n                <%= f.collection_radio_buttons :contact_made, boolean_choices, :last, :first do |b| %>\n                  <div class=\"form-check\">\n                    <%= b.radio_button(class: \"form-check-input\") %>\n                    <%= b.label(class: \"form-check-label\") %>\n                  </div>\n                <% end %>\n              </div>\n\n              <h6 class=\"mb-10\">Transition Aged Youth</h6>\n              <div class=\"form-check radio-style mb-20\">\n                <%= f.collection_radio_buttons :has_transitioned, boolean_choices, :last, :first do |b| %>\n                  <div class=\"form-check\">\n                    <%= b.radio_button(class: \"form-check-input\") %>\n                    <%= b.label(class: \"form-check-label\") %>\n                  </div>\n                <% end %>\n              </div>\n            <% end %>\n\n            <%= render 'filter', form: f %>\n\n            <ul class=\"buttons-group mt-30 col-8\">\n              <li>\n                <button\n                  type=\"button\"\n                  class=\"btn-sm main-btn primary-btn-outline btn-hover\"\n                  data-bs-toggle=\"modal\"\n                  data-bs-target=\"#filterColumns\"><i class=\"lni lni-funnel mr-10\"></i>\n                  Filter Columns\n                </button>\n              </li>\n\n              <li>\n                <%= button_tag(\n                      type: \"submit\",\n                      data: { disable_with: \"Downloading Report\" },\n                      class: \"btn-sm main-btn primary-btn btn-hover report-form-submit\"\n                    ) do %>\n                  <i class=\"lni lni-download mr-10\"></i> Download Report\n                <% end %>\n              </li>\n            </ul>\n          <% end %>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/shared/_additional_expense_form.html.erb",
    "content": "<div class=\"nested-form-wrapper mb-2\"\n  data-new-record=\"<%= form.object.new_record? %>\"\n  data-casa-nested-form-target=\"wrapper\"\n  data-child-index=\"<%= form.options[:child_index] %>\">\n  <div class=\"row gy-3\">\n    <div class=\"col-12 col-md-4\">\n      <%= form.label :other_expense_amount, \"Other Expense Amount\", class: \"form-label\" %>\n      <div class=\"input-group\">\n        <span class=\"input-group-text\">$</span>\n        <%= form.number_field(:other_expense_amount,\n          value: number_with_precision(form.object.other_expense_amount, precision: 2),\n          placeholder: \"0\", min: \"0\", max: 1000, step: 0.01,\n          class: [\"form-control\", \"expense-amount-input\"],\n        ) %>\n      </div>\n    </div>\n    <div class=\"cl-12 col-md\">\n      <%= form.label :other_expenses_describe, class: \"form-label\" do %>\n        Other Expense Details<span class=\"red-letter\">*</span>\n      <% end %>\n      <%= form.text_area(:other_expenses_describe,\n          placeholder: \"Enter other expense details\",\n          class: [\"form-control\", \"expense-describe-input\"],\n          data: { action: \"input->autosave#save\" },\n      ) %>\n    </div>\n    <div class=\"col-12 col-md-auto d-flex align-items-center justify-content-end\">\n      <button type=\"button\"\n        class=\"remove-expense-button btn btn-sm danger-btn-outline btn-hover\"\n        data-action=\"casa-nested-form#destroyAndRemove\">\n        Delete\n      </button>\n    </div>\n\n    <%= form.hidden_field :id, value: form.object.id %>\n    <%= form.hidden_field :_destroy, data: {case_contact_form_target: \"expenseDestroy\"} %>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/shared/_court_order_form.html.erb",
    "content": "<div class=\"court-order-entry d-flex align-items-center select-style-1 nested-form-wrapper\"\n     data-new-record=\"<%= f.object.new_record? %>\" data-type=\"COURT_ORDER\">\n  <div class=\"input-style-1\">\n    <%= f.text_area :text, cols: 50, class: \"court-order-text-entry\" %>\n  </div>\n  <div class=\"select-position\">\n    <%= f.select :implementation_status,\n        court_order_select_options,\n        {include_blank: 'Set Implementation Status'},\n        {class: 'implementation-status'} %>\n  </div>\n  <!-- remove-court-order-button -->\n  <button type=\"button\" class=\"align-self-center main-btn danger-btn btn-hover btn-sm\"\n          data-action=\"click->court-order-form#remove\">\n    Delete</button>\n\n  <%= f.hidden_field :_destroy %>\n</div>\n"
  },
  {
    "path": "app/views/shared/_court_order_list.erb",
    "content": "<label>Court Orders - Please check that you didn't enter any youth names</label>\n<template data-court-order-form-target=\"template\">\n  <%= form.fields_for :case_court_orders, CaseCourtOrder.new, child_index: \"NEW_RECORD\" do |ff| %>\n    <%= render \"shared/court_order_form\", f: ff %>\n  <% end %>\n</template>\n<div id=\"court-orders-list-container\" data-resource=\"<%= resource %>\" data-court-order-list-target=\"list\">\n  <% if siblings_casa_cases && siblings_casa_cases.count >= 1 %>\n    <div class=\"mb-4 mt-2\">\n      <% siblings_casa_cases_options = siblings_casa_cases.map { |scc| [scc.case_number, scc.id] } %>\n      <div class=\"select-style-1\">\n        <%= form.label :\"siblings_casa_cases\", \"Copy all orders from case: \" %>\n        <div class=\"select-position\">\n          <%= form.select :\"siblings_casa_cases\", siblings_casa_cases_options,\n                                                {include_blank: true},\n                                                {class: \"siblings-casa-cases col-3 ml-2\"}\n                                                %>\n        </div>\n      </div>\n      <%= button_tag \"Copy\", type: :button, class: \"copy-court-button main-btn primary-btn btn-hover ml-1\", id: \"copy-court-button\" %>\n      <% if casa_case %>\n        <%= form.hidden_field :casa_case, value: casa_case.id %>\n      <% end %>\n    </div>\n  <% end %>\n  <%= form.fields_for :case_court_orders do |ff| %>\n    <%= render \"shared/court_order_form\", f: ff %>\n  <% end %>\n\n  <div data-court-order-form-target=\"target\"></div>\n\n  <div class=\"select-style-1\">\n    <%= label_tag :selectedCourtOrder, \"Court Order Type\" %>\n    <div class=\"select-position\">\n      <%= select_tag :selectedCourtOrder,\n          options_for_select(CaseCourtOrder.court_order_options),\n          { include_blank: \"Create custom court order\", data: {court_order_form_target: \"selectedCourtOrder\"} }\n        %>\n    </div>\n  </div>\n\n</div>\n\n<div class=\"add-container\">\n  <button id=\"add-standard-court-order-button\" type=\"button\" class=\"main-btn primary-btn btn-sm btn-hover\" data-action=\"court-order-form#add\">\n    <i class=\"lni lni-plus mr-10\"></i> Add a court order\n  </button>\n</div>\n"
  },
  {
    "path": "app/views/shared/_edit_form.html.erb",
    "content": "<div class=\"input-style-1\">\n  <%= f.label :email, \"Email\" %>\n  <% if policy(resource).update_user_setting? %>\n\n    <%= f.text_field :email, placeholder: \"Email\" %>\n\n  <% else %>\n    <input type=\"text\" placeholder=\"<%= resource.email %>\" autocomplete=\"off\" readonly>\n  <% end %>\n</div>\n\n<div class=\"input-style-1\">\n  <%= f.label :display_name, \"Display name\" %>\n  <% if policy(resource).update_user_setting? %>\n\n    <%= f.text_field :display_name, placeholder: \"Display Name\" %>\n  <% else %>\n    <input type=\"text\" placeholder=\"<%= resource.display_name %>\" autocomplete=\"off\" readonly>\n  <% end %>\n</div>\n\n<div class=\"input-style-1\">\n  <%= f.label :phone_number, \"Phone number\" %>\n  <% if policy(resource).update_user_setting? %>\n    <%= f.text_field :phone_number, placeholder: \"Phone Number\" %>\n  <% else %>\n    <input type=\"text\" placeholder=\"<%= resource.phone_number %>\" autocomplete=\"off\" readonly>\n  <% end %>\n</div>\n\n<div class=\"input-style-1\">\n  <%= f.label :date_of_birth, \"Date of birth\" %>\n  <% if policy(resource).update_user_setting? %>\n    <%= f.date_field :date_of_birth,\n                     value: resource.date_of_birth,\n                     class: \"form-control label-font-weight\" %>\n  <% else %>\n    <input type=\"text\" placeholder=\"<%= resource.decorate.formatted_date_of_birth %>\" autocomplete=\"off\" readonly>\n  <% end %>\n</div>\n\n<% if resource.role == \"Volunteer\" %>\n  <div class=\"input-style-1\">\n    <%= f.label :address_attributes_content, \"Mailing address\" %>\n    <% if policy(resource).update_user_setting? %>\n      <%= f.fields_for :address, (resource.address ? nil : Address.new) do |a| %>\n        <%= a.text_field :content, placeholder: \"Mailing Address\" %>\n      <% end %>\n    <% else %>\n      <%= f.fields_for :address, (resource.address ? nil : Address.new) do |a| %>\n        <%= a.text_field :content, readonly: true, placeholder: \"Mailing Address\" %>\n      <% end %>\n    <% end %>\n  </div>\n<% end %>\n\n<div class=\"form-check checkbox-style mb-20\">\n  <%= f.check_box :receive_reimbursement_email, class: \"form-check-input\" %>\n  <%= f.label :receive_reimbursement_email, \"Email Reimbursement Requests\", class: \"form-check-label\" %>\n</div>\n"
  },
  {
    "path": "app/views/shared/_emancipation_link.html.erb",
    "content": "<%= link_to(casa_case_emancipation_path(casa_case.id),\n            class: \"main-btn primary-btn btn-sm emancipation-btn\") do %>\n  Emancipation\n  <%= render BadgeComponent.new(text: casa_case.decorate.emancipation_checklist_count,\n                                type: :light,\n                                margin: false) %>\n<% end %>\n"
  },
  {
    "path": "app/views/shared/_error_messages.html.erb",
    "content": "<% if resource.errors.any? %>\n  <div class=\"alert-box danger-alert\">\n    <div id=\"error_explanation\" class=\"alert\">\n      <h6>\n        <%= pluralize(resource.errors.count, \"error\") %> prohibited this\n        <%= @custom_error_header || resource.model_name.human %> from being saved:\n      </h6>\n\n      <ul>\n        <% resource.errors.full_messages.each do |message| %>\n          <li><%= message %></li>\n        <% end %>\n      </ul>\n    </div>\n  </div>\n<% end %>\n"
  },
  {
    "path": "app/views/shared/_favicons.html.erb",
    "content": "<%= favicon_link_tag \"apple-icon-57x57.png\", rel: \"apple-touch-icon\", sizes: \"57x57\" %>\n<%= favicon_link_tag \"apple-icon-60x60.png\", rel: \"apple-touch-icon\", sizes: \"60x60\" %>\n<%= favicon_link_tag \"apple-icon-72x72.png\", rel: \"apple-touch-icon\", sizes: \"72x72\" %>\n<%= favicon_link_tag \"apple-icon-76x76.png\", rel: \"apple-touch-icon\", sizes: \"76x76\" %>\n<%= favicon_link_tag \"apple-icon-114x114.png\", rel: \"apple-touch-icon\", sizes: \"114x114\" %>\n<%= favicon_link_tag \"apple-icon-120x120.png\", rel: \"apple-touch-icon\", sizes: \"120x120\" %>\n<%= favicon_link_tag \"apple-icon-144x144.png\", rel: \"apple-touch-icon\", sizes: \"144x144\" %>\n<%= favicon_link_tag \"apple-icon-152x152.png\", rel: \"apple-touch-icon\", sizes: \"152x152\" %>\n<%= favicon_link_tag \"apple-icon-180x180.png\", rel: \"apple-touch-icon\", sizes: \"180x180\" %>\n<%= favicon_link_tag \"android-icon-192x192.png\", sizes: \"192x192\", rel: \"icon\", type: \"image/png\" %>\n<%= favicon_link_tag \"favicon-32x32.png\", sizes: \"32x32\", rel: \"icon\", type: \"image/png\" %>\n<%= favicon_link_tag \"favicon-96x96.png\", sizes: \"96x96\", rel: \"icon\", type: \"image/png\" %>\n<%= favicon_link_tag \"favicon-16x16.png\", sizes: \"16x16\", rel: \"icon\", type: \"image/png\" %>\n<meta name=\"msapplication-TileColor\" content=\"#ffffff\">\n<meta name=\"msapplication-TileImage\" content=\"<%= image_path(\"ms-icon-144x144.png\") %>\">\n<meta name=\"theme-color\" content=\"#ffffff\">\n"
  },
  {
    "path": "app/views/shared/_invite_login.html.erb",
    "content": "\n<div class=\"mb-1\">\n  <strong class=\"text-dark\">CASA organization </strong>\n  <%= resource&.casa_org&.name %>\n</div>\n<div class=\"mb-1\">\n  <strong class=\"text-dark\">Added to system </strong>\n  <%= resource&.decorate&.formatted_created_at %>\n</div>\n<div class=\"mb-1\">\n  <strong class=\"text-dark\">Invitation email sent </strong>\n  <%= resource&.decorate&.formatted_invitation_sent_at || \"never\" %>\n</div>\n<div class=\"mb-1\">\n  <strong class=\"text-dark\">Last logged in </strong>\n  <%= resource&.decorate&.formatted_current_sign_in_at || \"never\" %>\n</div>\n<div class=\"mb-1\">\n  <strong class=\"text-dark\">Invitation accepted </strong>\n  <%= resource&.decorate&.formatted_invitation_accepted_at || \"never\" %>\n</div>\n<div class=\"mb-1\">\n  <strong class=\"text-dark\">Password reset last sent </strong>\n  <%= resource&.decorate&.formatted_reset_password_sent_at || \"never\" %>\n</div>\n<div class=\"mb-1\">\n  <strong class=\"text-dark\">Will receive reimbursement request emails </strong>\n  <%= resource.receive_reimbursement_email ? \"Yes\" : \"No\" %>\n</div>\n\n<% if resource.is_a?(Volunteer) %>\n  <div class=\"mb-1\">\n    <strong class=\"text-dark\">Learning Hours This Year</strong>\n    <%= resource.learning_hours_spent_in_one_year %>\n  </div>\n<% end %>\n"
  },
  {
    "path": "app/views/shared/_manage_volunteers.html.erb",
    "content": "<div class=\"col-md-6\">\n  <div class=\"title mb-30 mt-30\">\n    <h2 class=\"pt-5\">Manage Volunteers</h2>\n  </div>\n</div>\n<div id=\"volunteer-assignment\" class=\"card-style\">\n  <div>\n    <% if show_assigned_volunteers %>\n      <%= yield :table_title %>\n      <div class=\"table-wrapper table-responsive\">\n        <table class='table'>\n          <thead>\n            <tr>\n              <th>Volunteer Name</th>\n              <th>Volunteer Email</th>\n              <% if @casa_case %>\n                <th>Status</th>\n                <th>Start Date</th>\n                <th>End Date</th>\n              <% end %>\n              <th>Enable Reimbursement</th>\n              <% if local_assigns[:button_text] == \"Hide unassigned\" %>\n                <th>Currently Assigned To</th>\n              <% end %>\n              <th>Actions</th>\n            </tr>\n          </thead>\n          <%= yield :table_body %>\n        </table>\n      </div>\n    <% end %>\n    <br>\n    <h3 class=\"title\">Assign a Volunteer to Case</h3>\n    <fieldset class=\"border-0\" <% if available_volunteers.none? %> disabled <% end %>>\n      <%= form_with(model: assignable_obj.new, url: assign_action) do |form| %>\n        <div class='form-group select-style-1'>\n          <label for=\"<%= select_id %>\" class=\"mt-2\">Select a Volunteer</label>\n          <div class=\"select-position\">\n            <select id=\"<%= select_id %>\" name=\"<%= select_name %>\" class='form-control select-style-2 mt-1'>\n              <option value=\"\">Please Select Volunteer</option>\n              <% available_volunteers.each do |volunteer| %>\n                <option value=\"<%= volunteer.id %>\"><%= volunteer.display_name %></option>\n              <% end %>\n            </select>\n          </div>\n        </div>\n        <% if @supervisor %>\n          <%= form.hidden_field :supervisor_id, :value => @supervisor.id %>\n        <% end %>\n        <div class=\"actions mb-10\">\n          <%= button_tag(\n            type: \"submit\",\n            class: \"main-btn primary-btn btn-sm btn-hover\"\n          ) do %><i class=\"lni lni-plus ml-10\"></i> Assign Volunteer\n          <% end %>\n        </div>\n      <% end %>\n    </fieldset>\n    <% unless available_volunteers.any? %>\n      <p class=\"text-danger\">There are no active, unassigned volunteers available.</p>\n    <% end %>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/static/index.html.erb",
    "content": "<!doctype html>\n<html class=\"no-js\" lang=\"en\">\n\n<head>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>CASA Volunteer Tracking</title>\n\n  <script src=\"https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.x.x/dist/alpine.min.js\" defer></script>\n  <script src=\"https://cdn.tailwindcss.com\"></script>\n  <%= stylesheet_link_tag \"shared/noscript\" %>\n    <style>\n      .app-images {\n        height: 400px;\n      }\n\n      .org_logo {\n        height: 200px;\n      }\n    </style>\n</head>\n\n<body>\n  <noscript>\n    <div class=\"noscript alert alert-danger\">\n      <h2 class=\"alert-heading\">\n        Please enable javascript\n      </h2>\n      <p>\n        This app requires javascript to work\n      </p>\n    </div>\n  </noscript>\n  <header class=\"pt-2\" style=\"background: #0949D7\">\n    <div class=\"container max-w-7xl mx-auto pt-5 px-5\">\n      <nav>\n        <div class=\"flex justify-between items-center\">\n          <div>\n            <a class=\"text-lg lg:text-3xl lg:text-4xl font-bold text-white\" href=\"/\">CasaVolunteerTracking.org</a>\n          </div>\n\n          <div class=\"flex\" id=\"myNavbar\">\n            <div class=\"flex text-white space-x-4 md:space-x-10 text-md lg:text-2xl items-center\">\n              <a class=\"hidden md:block hover:underline hover:decoration-blue-300 hover:opacity-90 focus:underline focus:decoration-blue-300\" href=\"#about\">About</a>\n              <a class=\"hidden md:block hover:underline hover:decoration-blue-300 hover:opacity-90 focus:underline focus:decoration-blue-300\" href=\"#testimonials\">Testimonials</a>\n              <a class=\"hidden md:block hover:underline hover:decoration-blue-300 hover:opacity-90 focus:underline focus:decoration-blue-300\" href=\"#contact\">Contact</a>\n              <a href=\"<%= new_user_session_path %>\">\n                <button\n                  class=\"flex flex-row items-center w-full px-4 py-2 bg-green-800 hover:bg-green-700 rounded-2xl text-md\">\n                  <span class=\"text-lg\">Login</span>\n                </button>\n              </a>\n            </div>\n          </div>\n        </div>\n      </nav>\n    </div>\n\n    <div class=\"container max-w-7xl mx-auto text-white font-bold flex px-5\" style='height:60vh; min-height: 500px'>\n      <div class=\"flex flex-col justify-center items-center mx-auto lg:mx-0 text-center lg:text-left\">\n        <div class=\"text-4xl\">\n          <div>\n            <h1>Casa Volunteer Tracking removes<br>the complexity of managing your<br> CASA and tracking your\n              volunteers.</h1>\n            <p class=\"text-lg font-normal mb-3 mt-2\">Ready to make yourself more efficient?</p>\n          </div>\n        </div>\n        <div class=\"hidden lg:block absolute right-52 w-1/4\">\n          <%= image_tag(\"hero-image.svg\", alt: \"\" ) %>\n        </div>\n      </div>\n    </div>\n  </header>\n\n  <main>\n    <div class=\"container max-w-7xl mx-auto pt-5\" id=\"about\">\n      <div class=\"text-center mt-10\">\n        <h2 class=\"text-4xl text-gray-700\">The Casa Volunteer Tracking app removes the <br> complexity from your day\n        </h2>\n        <p class=\"mt-1 text-gray-600\">and lets you spend time helping those who need it.</p>\n        <a href=\"mailto:casa@rubyforgood.org?Subject=CasaVolunteerTracking%20Interest\"><button type=\"button\"\n            class=\"will-change-transform text-lg bg-green-800 text-white px-3 py-2 rounded-2xl mt-5 hover:bg-green-700 hover:-translate-y-0.5 hover:scale-x-110 transition-transform duration-200 ease-linear\">Get In Touch</button></a>\n      </div>\n    </div>\n\n    <div class=\"w-3/4 mx-auto py-24\">\n      <div class=\"flex flex-col md:flex-row justify-center\">\n        <div class=\"flex flex-col justify-center items-center w-full sm:w-1/2\">\n          <div class=\"pb-2\">\n            <%= image_tag(\"case-contact.svg\", class: \"app-images\" , alt: \"man reading a book to a girl\" ) %>\n          </div>\n          <div class=\"text-center\">\n            <h3 class=\"text-3xl\">Case Contacts</h3>\n            <p class=\"text-gray-600 mt-2\">Volunteers can record case contacts and learning hours.</p>\n          </div>\n        </div>\n        <div class=\"flex flex-col justify-center items-center w-full sm:w-1/2\">\n          <div class=\"pb-2\">\n            <%= image_tag(\"court-report.svg\", class: \"app-images\" , alt: \"judge raising a gavel\" ) %>\n          </div>\n          <div class=\"text-center\">\n            <h3 class=\"text-3xl\">Court Reports</h3>\n            <p class=\"text-gray-600 mt-2\">Volunteers, supervisors, and admins can generate a court report for any case\n              with all recorded case contacts pre-filled.</p>\n          </div>\n        </div>\n      </div>\n\n      <div class=\"flex flex-col md:flex-row justify-center\">\n        <div class=\"flex flex-col justify-center items-center w-full sm:w-1/2\">\n          <div class=\"pb-2\">\n            <%= image_tag(\"reimbursement.svg\", class: \"app-images\" , alt: \"car with two people\" ) %>\n          </div>\n          <div class=\"text-center\">\n            <h3 class=\"text-3xl\">Reimbursements</h3>\n            <p class=\"text-gray-600 mt-2\">Volunteers can submit reimbursement requests and admins can generate reports.\n            </p>\n          </div>\n        </div>\n\n        <div class=\"flex flex-col justify-center items-center w-full sm:w-1/2\">\n          <div class=\"pb-2\">\n            <%= image_tag(\"add-case.svg\", class: \"app-images\" , alt: \"file folders and file cabinet\" ) %>\n          </div>\n          <div class=\"text-center\">\n            <h3 class=\"text-3xl\">Add Cases</h3>\n            <p class=\"text-gray-600 mt-2\">Supervisors and administrators can add cases and assign volunteers.</p>\n          </div>\n        </div>\n      </div>\n\n      <div class=\"flex flex-col md:flex-row justify-center\">\n        <div class=\"flex flex-col justify-center items-center w-full sm:w-1/2\">\n          <div class=\"pb-2\">\n            <%= image_tag(\"spreadsheets.svg\", class: \"app-images\" , alt: \"computer screen with graphs and charts\" ) %>\n          </div>\n          <div class=\"text-center\">\n            <h3 class=\"text-3xl\">Exportable Data</h3>\n            <p class=\"text-gray-600 mt-2\">All CASA data is easily exportable in CSV format.</p>\n          </div>\n        </div>\n\n        <div class=\"flex flex-col justify-center items-center w-full sm:w-1/2\">\n          <div class=\"pb-2\">\n            <%= image_tag(\"communicate.svg\", class: \"app-images\" ,\n              alt: \"woman holding a phone with a notification alert\" ) %>\n          </div>\n          <div class=\"text-center\">\n            <h3 class=\"text-3xl\">Communication</h3>\n            <p class=\"text-gray-600 mt-2\">Easily communicate with volunteers by email, SMS or both.</p>\n          </div>\n        </div>\n      </div>\n    </div>\n\n    <div class=\"py-5 px-0 bg-blue-600 text-white\" id=\"testimonials\">\n      <div class=\"container max-w-7xl mx-auto flex flex-col md:flex-row py-5 justify-center items-center\">\n        <div class=\"w-1/2 flex justify-center\">\n          <%= image_tag(\"quote.svg\", class: \"quote-image\" , style: \"width: 300px\" ,\n            alt: \"Quote: No one is useless in this world who lightens the burdens of another - Charles Dickens\" ) %>\n        </div>\n        <div class=\"pt-5 px-4 w-full sm:w-1/2\">\n          <h3 class=\"text-4xl\">Testimonials</h3>\n          <div class='mt-5'>\n            <div class='space-y-12'>\n              <div>\n                <p>The tracker has helped us streamline our processes, and we no longer have to worry about losing track\n                  of important documents.\n                </p>\n                <strong class='mt-2'> - Sarah B. | Program Manager</strong>\n              </div>\n              <div>\n                <p class=\"text-testimonials\">Thanks to the tracker, we can now make quicker decisions and effectively\n                  monitor our cases, resulting in a more pleasant experience for our volunteers.</p>\n                <strong> - Nancy K. | Office Manager</strong>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n\n    <div class=\"w-3/4 mx-auto py-24\" id=\"organizations\">\n      <div class=\"text-center\">\n        <h2 class=\"text-4xl\"> CASA Organizations Powered by Our App </h2>\n      </div>\n      <div class=\"flex flex-col flex-wrap md:flex-row justify-center py-14\">\n        <% @casa_logos.each do |org| %>\n          <div class=\"flex flex-col justify-center items-center w-full sm:w-1/3\">\n            <div class=\"py-8\">\n              <%= image_tag org.logo, class: \"org_logo\" %>\n            </div>\n            <%= org.display_name %>\n          </div>\n          <% end %>\n      </div>\n    </div>\n\n    <div class=\"py-5 px-0 bg-blue-600 text-white\">\n      <div class=\"container max-w-7xl mx-auto py-32 bg-blue-600 text-white\" id=\"contact\">\n        <div class=\"text-center\">\n          <h2 class=\"text-4xl\">Want to use the CASA Volunteer Tracking App?</h2>\n          <br><br>\n          <p>Have questions? Email us at <a class=\"text-green-400 hover:underline transition duration-100 hover:text-green-300\"\n              href=\"mailto:casa@rubyforgood.org?Subject=CASA%20Interest\" target=\"_top\">casa@rubyforgood.org</a></p>\n        </div>\n      </div>\n    </div>\n  </main>\n\n  <footer class=\"bg-gray-200\">\n    <div class=\"container max-w-7xl mx-auto py-32 text-white\">\n      <div class=\"flex flex-col mx-auto text-center sm:text-left sm:flex-row space-y-10 sm:space-y-0\">\n        <div class=\"w-full sm:w-1/2 text-3xl text-gray-700\">\n          casavolunteertracking.org\n        </div>\n        <div class=\"w-full sm:w-1/2\">\n          <strong class=\"text-gray-700\">The CASA Volunteer Tracking app was lovingly built by:</strong>\n          <br>\n          <a class=\"text-blue-800 hover:underline transition duration-100 \" href=\"http://rubyforgood.org/\">Ruby for Good</a>\n          <br><br>\n          <a class=\"text-gray-700 hover:underline transition duration-100 \" href=\"https://storyset.com/data\">Data illustrations by Storyset</a>\n        </div>\n      </div>\n    </div>\n  </footer>\n</body>\n\n</html>\n"
  },
  {
    "path": "app/views/supervisor_mailer/_active_volunteer_info.html.erb",
    "content": "<td class=\"content-block\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\" valign=\"top\">\n  <b>\n    Case <%= link_to casa_case.case_number, casa_case_url(casa_case) %>\n  </b>\n  <br>\n  <% successful_contacts = recently_unassigned ?\n                               casa_case.decorate.successful_contacts_this_week_before(case_assignment.updated_at) :\n                               casa_case.decorate.successful_contacts_this_week %>\n  <% unsuccessful_contacts = recently_unassigned ?\n                                 casa_case.decorate.unsuccessful_contacts_this_week_before(case_assignment.updated_at) :\n                                 casa_case.decorate.unsuccessful_contacts_this_week %>\n  <% if successful_contacts + unsuccessful_contacts > 0 %>\n    <%= \"Number of successful case contacts made this week: #{successful_contacts}\" %>\n    <br>\n    <%= \"Number of unsuccessful case contacts made this week: #{unsuccessful_contacts} \" %>\n    <br>\n    <% recent_contact = recently_unassigned ?\n                            casa_case.decorate.case_contacts_latest_before(case_assignment.updated_at) :\n                            casa_case.decorate.case_contacts_latest %>\n    <%= \"Most recent contact attempted:\" %>\n    <br>\n    <%= \" - Date: #{I18n.l(recent_contact&.occurred_at, format: :full, default: nil)}\" %>\n    <br>\n    <%= \" - Type: #{recent_contact&.decorate.contact_types}\" %>\n    <br>\n    <%= \" - Duration: #{recent_contact&.duration_minutes}\" %>\n    <br>\n    <%= \" - Contact Made: #{recent_contact&.contact_made}\" %>\n    <br>\n    <%= \" - Medium Type: #{recent_contact&.medium_type}\" %>\n    <% recent_contact.contact_topic_answers.reject { _1.value.blank? }.each do |answer| %>\n      <br>\n      - <b><%= \"#{answer.contact_topic.question}\" %></b><%= \": #{answer.value}\" %>\n    <% end %>\n    <br>\n    <%= \" - Notes: #{recent_contact&.notes}\" %>\n  <% else %>\n    No contact attempts were logged for this week.\n  <% end %>\n  <% if recently_unassigned %>\n    <br>\n    This case was unassigned from <%= volunteer_display_name %> on\n    <%= case_assignment.updated_at.to_date.to_fs(:long_ordinal) %>\n    <% if successful_contacts + unsuccessful_contacts > 0 %>\n      The above activity only describes the part of the week when the case was still assigned to\n      <% volunteer_display_name %>\n    <% end %>\n  <% end %>\n</td>\n"
  },
  {
    "path": "app/views/supervisor_mailer/_active_volunteers.html.erb",
    "content": "<% active_ever_assigned.each do |volunteer| %>\n  <tr>\n    <td class=\"content-block\" style=\"font-size: 16px; padding: 0 0 10px\">\n      <b>Summary for <%= link_to volunteer.display_name, edit_volunteer_url(volunteer) %></b>\n    </td>\n  </tr>\n  <% volunteer.case_assignments_with_cases.each do |case_assignment| %>\n    <% recently_unassigned = case_assignment.decorate.unassigned_in_past_week? %>\n    <% if case_assignment.active || recently_unassigned %>\n      <% casa_case = case_assignment.casa_case %>\n      <tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; text-align: left;\">\n        <%= render(\"active_volunteer_info\",\n                   casa_case: casa_case,\n                   recently_unassigned: recently_unassigned,\n                   case_assignment: case_assignment,\n                   volunteer_display_name: volunteer.display_name\n            ) %>\n      </tr>\n    <% end %>\n  <% end %>\n  <!-- If the table elements don't wrap this <hr> then the <hr> gets moved to the outside of the table by the HTML renderer -->\n  <tr>\n    <td style=\"padding: 0 0 10px\">\n      <hr>\n    </td>\n  </tr>\n<% end %>\n"
  },
  {
    "path": "app/views/supervisor_mailer/_additional_notes.html.erb",
    "content": "<tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n  <td class=\"content-block\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 18px; vertical-align: top; margin: 0; padding: 0 0 20px;\" valign=\"top\">\n    <b>Additional Notes:</b>\n    <br>\n  </td>\n</tr>\n\n<tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n  <% inactive_messages&.each do |message| %>\n    <td class=\"content-block\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; margin: 0; padding: 0 0 20px;\" valign=\"top\">\n      <%= message %>\n      <br>\n    </td>\n  <% end %>\n</tr>\n\n<tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n  <td class=\"content-block\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 18px; vertical-align: top; margin: 0; padding: 0 0 20px;\" valign=\"top\">\n    <% if inactive_messages&.none? %>\n      There are no additional notes.\n    <% end %>\n  </td>\n</tr>\n"
  },
  {
    "path": "app/views/supervisor_mailer/_no_recent_sign_in.html.erb",
    "content": "<% if inactive_volunteers.any? %>\n  <tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; text-align: left;\">\n    <td class=\"content-block\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\" valign=\"top\">\n      <b><%= \"The following volunteers have not signed in or created case contacts in the last 30 days\" %></b>\n      <br>\n\n      <% inactive_volunteers.each do |volunteer| %>\n        - <%= volunteer.display_name %>\n        <br>\n      <% end %>\n    </td>\n  </tr>\n<% end %>\n"
  },
  {
    "path": "app/views/supervisor_mailer/_pending_volunteers.html.erb",
    "content": "<tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n  <td class=\"content-block\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 18px; vertical-align: top; margin: 0; padding: 0 0 20px;\" valign=\"top\">\n    <b>Pending Volunteers:</b>\n    <br>\n    <% if @supervisor.pending_volunteers.empty? %>\n      <br>\n      There are no pending volunteers.\n    <% end %>\n  </td>\n</tr>\n<% supervisor.pending_volunteers.each do |volunteer| %>\n  <tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; display: flex; justify-content: flex-start;\">\n    <td style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin-right: 30px; padding: 0 0 20px; text-transform: capitalize;\" valign=\"top\">\n      <%= volunteer.display_name %>\n    </td>\n    <td>\n      <button> <%= link_to \"Re-invite\", resend_invitation_volunteer_url(volunteer) %></button>\n    </td>\n  </tr>\n<% end %>\n"
  },
  {
    "path": "app/views/supervisor_mailer/_recently_unassigned_volunteers.html.erb",
    "content": "<% if supervisor.recently_unassigned_volunteers.any? %>\n  <tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; text-align: left;\">\n    <td class=\"content-block\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\" valign=\"top\">\n      <b><%= \"The following volunteers have been unassigned from you:\" %></b>\n      <br>\n\n      <% supervisor.recently_unassigned_volunteers.each  do |volunteer| %>\n        - <%= volunteer.display_name %>\n\n        <% unless volunteer.has_supervisor? %>\n          (not assigned to a new supervisor)\n        <% end %>\n        <br>\n      <% end %>\n    </td>\n  </tr>\n<% end %>\n"
  },
  {
    "path": "app/views/supervisor_mailer/_summary_header.html.erb",
    "content": "<tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n  <td class=\"content-block\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 18px; vertical-align: top; margin: 0; padding: 0 0 20px;\" valign=\"top\">\n    <%= supervisor_display_name %>,\n  </td>\n</tr>\n<tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; margin: 0;\">\n  <td class=\"content-block\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; margin: 0; padding: 0 0 20px;\" valign=\"top\">\n    <% if has_active_volunteers %>\n      You have no volunteers with assigned cases at the moment. When you do, you will see their status here.\n    <% else %>\n      Here's a summary of what happened with your volunteers this last week.\n    <% end %>\n  </td>\n</tr>\n"
  },
  {
    "path": "app/views/supervisor_mailer/account_setup.html.erb",
    "content": "<meta itemprop=\"name\" content=\"Confirm Email\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n<style>/* Email styles need to be inline */</style>\n<table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n  <tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n    <td class=\"content-block\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\" valign=\"top\">\n      A <%= @supervisor.casa_org.display_name %>’s County supervisor console account has been created for you. This console is\n      for logging the time you spend and actions you take on your CASA case. You can log activity with your CASA youth,\n      their family members, their foster family or placement, the DSS worker, your Case Supervisor and others associated\n      with your CASA case (such as teachers and therapists).\n    </td>\n  </tr>\n  <tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n    <td class=\"content-block\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\" valign=\"top\">\n      Your console account is associated with this email. If this is not the correct email to use, please stop here and\n      contact your Administrator to change the email address. If you are ready to get started, please set your\n      password. This is the first step to accessing your new supervisor account.\n    </td>\n  </tr>\n  <tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n    <td class=\"content-block\" itemprop=\"handler\" itemscope itemtype=\"http://schema.org/HttpActionHandler\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\" valign=\"top\">\n      <a href=\"<%= \"#{edit_password_url(@supervisor, reset_password_token: @token)}\" %>\" target=\"_blank\" class=\"btn-primary\" itemprop=\"url\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2em; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; text-transform: capitalize; background-color: #348eda; margin: 0; border-color: #348eda; border-style: solid; border-width: 10px 20px;\">Set\n        Your Password</a>\n    </td>\n  </tr>\n</table>\n"
  },
  {
    "path": "app/views/supervisor_mailer/reimbursement_request_email.html.erb",
    "content": "<meta itemprop=\"name\" content=\"Volunteer Reimbursement Request Reminder\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n<style>/* Email styles need to be inline */</style>\n<table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n  <tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n    <td class=\"content-block\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\" valign=\"top\">\n      Hello <%= @supervisor.display_name %>,\n    </td>\n  </tr>\n  <tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n    <td class=\"content-block\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\" valign=\"top\">\n      <%= @volunteer.display_name %> has submitted a reimbursement request, please follow up on the reimbursements page\n      <%= link_to \"using this link\", reimbursements_url %>.\n    </td>\n  </tr>\n</table>\n"
  },
  {
    "path": "app/views/supervisor_mailer/weekly_digest.html.erb",
    "content": "<meta itemprop=\"name\" content=\"Weekly Volunteer Activity Summary Email\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n<style>/* Email styles need to be inline */</style>\n<table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n  <%= render(\"summary_header\", has_active_volunteers: @supervisor.volunteers.length == 0, supervisor_display_name: @supervisor.display_name) %>\n  <%= render(\"active_volunteers\", supervisor: @supervisor, active_ever_assigned: @supervisor.volunteers_ever_assigned.where(\"supervisor_volunteers.is_active = ?\", true).active) %>\n  <%= render(\"recently_unassigned_volunteers\", supervisor: @supervisor) %>\n  <%= render(\"additional_notes\", inactive_messages: @inactive_messages) %>\n  <%= render(\"pending_volunteers\", supervisor: @supervisor) %>\n  <%= render(\"no_recent_sign_in\", supervisor: @supervisor, inactive_volunteers: @inactive_volunteers) %>\n</table>\n"
  },
  {
    "path": "app/views/supervisors/_manage_active.html.erb",
    "content": "<div class=\"field form-group my-3\">\n  <% if user.active? %>\n    Supervisor is <%= render BadgeComponent.new(text: \"Active\", type: :success, rounded: true) %><br>\n    <% if current_user.casa_admin? %>\n      <% if policy(user).deactivate? %>\n        <%= link_to deactivate_supervisor_path(user), class: \"btn-sm main-btn danger-btn-outline btn-hover\", method: :patch, data: { confirm: \"WARNING: Marking a supervisor inactive will make them unable to login. Are you sure you want to do this?\" } do %>\n          <i class=\"lni lni-ban mr-10\"></i>\n          Deactivate Supervisor\n        <% end %>\n      <% end %>\n    <% end %>\n  <% else %>\n    <div class=\"alert-box danger-alert\">\n      <div class=\"alert\">\n        Supervisor was deactivated on: <%= user.decorate.formatted_updated_at %>\n      </div>\n    </div>\n    <% if policy(user).activate? %>\n      <%= link_to \"Activate supervisor\",\n                  activate_supervisor_path(user),\n                  method: :patch,\n                  class: \"btn-sm main-btn danger-btn-outline btn-hover\" %>\n    <% end %>\n  <% end %>\n  <% if current_user.casa_admin? && user.invitation_accepted_at.nil? %>\n    <%= link_to resend_invitation_supervisor_path(user), class: \"btn-sm main-btn danger-btn-outline btn-hover\", method: :patch do %>\n      <i class=\"lni lni-envelope mr-10\"></i>\n      Resend Invitation\n    <% end %>\n    <% if current_user.casa_admin? %>\n      <%= link_to change_to_admin_supervisor_path(user), class: \"btn-sm main-btn danger-btn-outline btn-hover\", method: :patch do %>\n        <i class=\"lni lni-crown mr-10\"></i>\n        Change to Admin\n      <% end %>\n    <% end %>\n  <% end %>\n</div>\n"
  },
  {
    "path": "app/views/supervisors/edit.html.erb",
    "content": "<div class=\"col-md-6\">\n  <div class=\"title mb-30 mt-30\">\n    <h1>Editing Supervisor</h1>\n  </div>\n</div>\n<div class=\"card-style\">\n  <div class=\"card-body\">\n    <%= form_with(model: @supervisor, as: :supervisor, url: supervisor_path(@supervisor), method: :patch) do |form| %>\n      <%= render \"/shared/error_messages\", resource: @supervisor %>\n      <%= render \"/shared/edit_form\", resource: @supervisor, f: form %>\n      <p>\n        <%= render \"/shared/invite_login\", resource: @supervisor %>\n      </p>\n\n      <div>\n        <% if current_user.casa_admin? || current_user.supervisor? %>\n          <div class=\"form-group\">\n            <%= form.label :monthly_learning_hours_report, \"Receive Monthly Learning Hours Report\" %>\n            <%= form.check_box :monthly_learning_hours_report %>\n          </div>\n        <% end %>\n      </div>\n\n      <p>\n        <%= render \"manage_active\", user: @supervisor %>\n      </p>\n      <div class=\"actions\">\n        <% if policy(@supervisor).update_supervisor_email? || policy(@supervisor).update_supervisor_name? %>\n          <div class=\"actions mb-10\">\n            <%= button_tag(\n            type: \"submit\",\n            class: \"btn-sm main-btn primary-btn btn-hover\"\n          ) do %>\n              <i class=\"lni lni-checkmark-circle mr-10\"></i> Submit\n            <% end %>\n          </div>\n        <% end %>\n      </div>\n    <br>\n    <% end %>\n  </div>\n</div>\n<% button_text = @all_volunteers_ever_assigned.nil? ? \"Include unassigned\" : \"Hide unassigned\" %>\n<% volunteer_title = @unassigned_volunteer_count == 0 ? \"Assigned Volunteers\" : \"All Volunteers\" %>\n<% content_for :table_title do %>\n  <h3><%= volunteer_title %></h3>\n  <% if @supervisor_has_unassigned_volunteers %>\n    <%= button_to button_text,\n                  edit_supervisor_path(@supervisor),\n                  params: { include_unassigned: @all_volunteers_ever_assigned.nil? },\n                  method: :get,\n                  class: \"main-btn primary-btn-outline btn-hover my-3\" %>\n  <% end %>\n<% end %>\n<% content_for :table_body do %>\n  <tbody>\n    <% (@all_volunteers_ever_assigned || @supervisor.volunteers).each do |volunteer| %>\n      <tr>\n        <td><%= link_to volunteer.display_name, edit_volunteer_path(volunteer) %></td>\n        <td><%= volunteer.email %></td>\n        <% if button_text == \"Hide unassigned\" %>\n          <td>\n            <%= volunteer.has_supervisor? ? volunteer.supervisor.display_name : \"No One\" %>\n          </td>\n        <% end %>\n        <td>\n          <% if volunteer.supervised_by?(@supervisor) %>\n            <div class=\"action\">\n              <%= button_to \"Unassign\",\n                            unassign_supervisor_volunteer_path(volunteer),\n                            method: :patch,\n                            class: \"text-danger\" %>\n              <i class=\"lni lni-ban text-danger\"></i>\n            </div>\n          <% else %>\n            Unassigned\n          <% end %>\n        </td>\n      </tr>\n    <% end %>\n  </tbody>\n<% end %>\n<%=\n  render(\n  \"shared/manage_volunteers\",\n  assignable_obj: SupervisorVolunteer,\n  assign_action: supervisor_volunteers_path(supervisor_id: @supervisor.id),\n  available_volunteers: @available_volunteers,\n  select_id: 'supervisor_volunteer_volunteer_id',\n  select_name: 'supervisor_volunteer[volunteer_id]',\n  show_assigned_volunteers: @supervisor_has_unassigned_volunteers || @supervisor.volunteers.any?,\n  button_text: button_text\n  )\n%>\n"
  },
  {
    "path": "app/views/supervisors/index.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h1>Supervisors</h1>\n      </div>\n    </div>\n    <!-- end col -->\n    <div class=\"col-md-6\">\n      <div class=\"breadcrumb-wrapper mb-30\">\n        <span class=\"ml-5\">\n        <div class=\"dropdown pull-left mx-2 supervisor-filters\">\n          <button class=\"btn-sm main-btn dark-btn dropdown-toggle\" type=\"button\" id=\"dropdownMenuButton1\" data-bs-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\">\n          <i class=\"lni lni-funnel mr-10\"></i>Filter Status</button>\n          <div class=\"dropdown-menu status-options\" aria-labelledby=\"dropdownMenuButton\">\n            <div class=\"dropdown-item form-check checkbox-style\">\n              <%= check_box_tag \"status_option_active\", \"true\", true,\n                            class: \"active form-check-input\",\n                            data: { value: \"true\" } %>\n              <%= label_tag \"status_option_active\", \"Active\", class: \"form-check-label\" %>\n            </div>\n            <div class=\"dropdown-item form-check checkbox-style\">\n              <%= check_box_tag \"status_option_inactive\", \"false\", false,\n                            class: \"inactive form-check-input\",\n                            data: { value: \"false\" } %>\n              <%= label_tag \"status_option_inactive\", \"Inactive\", class: \"form-check-label\" %>\n            </div>\n          </div>\n        </div>\n        </span>\n        <span class=\"ml-5\">\n          <% if policy(Supervisor).create? %>\n            <%= link_to new_supervisor_path, class: \"btn-sm main-btn secondary-btn\" do %>\n              <i class=\"lni lni-plus mr-10\"></i>\n              New Supervisor\n            <% end %>\n          <% end %>\n        </span>\n      </div>\n    </div>\n  </div>\n</div>\n\n<!-- ========== tables-wrapper start ========== -->\n<div class=\"tables-wrapper\">\n  <div class=\"row\">\n    <div class=\"col-lg-12\">\n      <div class=\"card-style mb-30\">\n        <h6 class=\"mb-10\">Legend for Supervisor List</h6>\n        <p class=\"text-sm mb-20\">\n          <span class=\"supervisor-stat success-bg text-white mb-10\">Number of Volunteers attempting contact (within 2 weeks)</span><br>\n          <span class=\"supervisor-stat danger-bg text-white mb-10\">Number of Volunteers not attempting contact (within 2 weeks)</span><br>\n          <span class=\"supervisor-stat deactive-bg text-black\">Count of Transition Aged Youth</span>\n        </p>\n        <div class=\"table-wrapper table-responsive\">\n          <table\n            id=\"supervisors\"\n            class=\"table\"\n            data-source=\"<%= datatable_supervisors_path format: :json %>\">\n            <thead>\n            <tr>\n              <th><h6>Supervisor Name</h6></th>\n              <th><h6>Volunteer Info</h6></th>\n              <th><h6>Actions</h6></th>\n            </tr>\n            <!-- end table row-->\n            </thead>\n            <tbody>\n            <% @supervisors.each do |supervisor| %>\n              <tr>\n                <td id=\"name-<%= supervisor.id %>\" class=\"min-width\">\n                  <%= link_to(supervisor.display_name, edit_supervisor_path(supervisor)) %>\n                </td>\n                <td class=\"min-width\">\n                  <% no_attempt_volunteers = supervisor.no_attempt_for_two_weeks %>\n                  <% active_volunteers = (supervisor.active_volunteers - no_attempt_volunteers).nonzero? %>\n                  <% transition_volunteers = supervisor.volunteers_serving_transition_aged_youth %>\n\n                    <div class=\"d-flex flex-row w-100\">\n                      <% if active_volunteers %>\n                        <div class=\"supervisor-stat success-bg text-white flex-grow-1\" style=\"max-width: <%= (active_volunteers * 15) %>%; text-align: center\"><%= active_volunteers %></div>\n                      <% end %>\n                      <% if no_attempt_volunteers.nonzero? %>\n                        <div class=\"supervisor-stat danger-bg text-white flex-grow-1\" style=\"max-width: <%= (no_attempt_volunteers * 15) %>%; text-align: center\"><%= no_attempt_volunteers %></div>\n                      <% else %>\n                        <div class=\"flex-grow-1\"></div>\n                      <% end %>\n                    </div>\n\n                    <div class=\"d-flex flex-row py-2\" style=\"max-width: 50%;\">\n                      <% if !(active_volunteers || no_attempt_volunteers.nonzero?) %>\n                        <div class=\"supervisor-stat bg-secondary text-white\">No assigned volunteers</div>\n                      <% else %>\n                        <div class=\"supervisor-stat deactive-bg text-black\" style=\"width:<%= (transition_volunteers * 15) %>; % text-align: center\">\n                          <%= transition_volunteers %>\n                        </div>\n                      <% end %>\n                    </div>\n                </td>\n                <td>\n                  <%= link_to edit_supervisor_path(supervisor) do %>\n                    <div class=\"action\">\n                      <button class=\"text-danger\">\n                        <i class=\"lni lni-pencil-alt\"></i> Edit\n                      </button>\n                    </div>\n                  <% end %>\n                </td>\n              </tr>\n            <% end %>\n            </tbody>\n          </table>\n          <!-- end table -->\n        </div>\n      </div>\n      <!-- end card -->\n    </div>\n    <!-- end col -->\n  </div>\n</div>\n\n<div class=\"tables-wrapper\">\n  <div class=\"row\">\n    <div class=\"col-lg-12\">\n      <div class=\"card-style mb-30\">\n        <div class=\"title mb-30\">\n          <h2>Volunteers without Supervisors</h2>\n        </div>\n        <% if @available_volunteers.any? %>\n          <div class=\"table-wrapper table-responsive\">\n            <table id=\"active_volunteers\" class=\"table\">\n              <thead>\n              <tr>\n                <th>Active volunteers not assigned to supervisors</th>\n                <th>Assigned to Case(s)</th>\n              </tr>\n              <!-- end table row-->\n              </thead>\n              <tbody>\n              <% @available_volunteers.each do |volunteer| %>\n                <tr>\n                  <td>\n                    <%= link_to volunteer.display_name, edit_volunteer_path(volunteer) %>\n                  </td>\n                  <td>\n                    <% volunteer.casa_cases.map do |casa_case| %>\n                      <%= link_to(casa_case.case_number, casa_case_path(casa_case)) %> <br>\n                    <% end %>\n                  </td>\n                </tr>\n              <% end %>\n              </tbody>\n            </table>\n          </div>\n        <% else %>\n          There are no active volunteers without supervisors to display here\n        <% end %>\n      </div>\n    </div>\n  </div>\n\n  <div class=\"row\">\n    <div class=\"col-lg-12\">\n      <div class=\"card-style\">\n        <div class=\"title mb-30\">\n          <h2>Casa Cases without Court Dates</h2>\n        </div>\n        <div class=\"table-wrapper table-responsive\">\n          <table class=\"table striped-table\" id=\"casa_cases\">\n            <thead>\n              <tr>\n                <th>Case Number</th>\n                <th>Hearing Type</th>\n                <th>Judge</th>\n                <th>Status</th>\n                <th>Transition Aged Youth</th>\n                <th>Assigned To</th>\n                <th>Actions</th>\n                <th></th>\n              </tr>\n            </thead>\n            <tbody>\n              <% @casa_cases.each do |casa_case| %>\n                <tr class=\"<%= casa_case.decorate.inactive_class %>\">\n                  <td class=\"min-width\"><%= link_to(casa_case.case_number, casa_case) %></td>\n                  <td class=\"min-width\"><%= casa_case.hearing_type_name %></td>\n                  <td class=\"min-width\"><%= casa_case.judge_name %></td>\n                  <td class=\"min-width\"><%= casa_case.decorate.status %></td>\n                  <td class=\"min-width\"><%= casa_case.decorate.transition_aged_youth %></td>\n                  <td class=\"min-width\">\n                    <% if casa_case.active? %>\n                      <% if current_user.volunteer? %>\n                        <%= safe_join(casa_case.assigned_volunteers.map { |vol|\n                      vol.display_name }, \", \") %>\n                      <% else %>\n                        <%= safe_join(casa_case.assigned_volunteers.map { |vol|\n                      link_to(vol.display_name, edit_volunteer_path(vol)) },\n                                  \", \") %>\n                      <% end %>\n                    <% else %>\n                      Case was deactivated on: <%= I18n.l(casa_case.updated_at, format: :standard, default: nil) %>\n                    <% end %>\n                  </td>\n                  <td class=\"min-width\">\n                    <i class=\"lni lni-search-alt text-active\"></i>\n                    <%= link_to \"Detail View\", casa_case_path(casa_case) %>\n                  </td>\n                  <td>\n                    <i class=\"lni lni-pencil-alt text-danger\"></i>\n                    <%= link_to \"Edit\", edit_casa_case_path(casa_case), class: 'text-danger' %>\n                  </td>\n                </tr>\n              <% end %>\n            </tbody>\n          </table>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/supervisors/new.html.erb",
    "content": "<div class=\"col-md-6\">\n  <div class=\"title mb-30 mt-30\">\n    <h1>Create New Supervisor</h1>\n  </div>\n</div>\n<div class=\"card card-container\">\n  <div class=\"card-body\">\n    <%= form_with model: @supervisor, local: true, url: supervisors_path do |form| %>\n      <%= render \"/shared/error_messages\", resource: @supervisor %>\n      <div class=\"input-style-1\">\n        <%= form.label :email, \"Email\" %>\n        <%= form.text_field :email, class: \"form-control\" %>\n      </div>\n      <div class=\"input-style-1\">\n        <%= form.label :display_name, \"Display name\" %>\n        <%= form.text_field :display_name, class: \"form-control\" %>\n      </div>\n      <div class=\"input-style-1\">\n        <%= form.label :phone_number, \"Phone number\" %>\n        <%= form.telephone_field :phone_number, class: \"form-control\" %>\n      </div>\n      <div class=\"actions mb-10\">\n        <%= button_tag(\n              type: \"submit\",\n              class: \"main-btn btn-sm primary-btn btn-hover mb-3\"\n            ) do %><i class=\"lni lni-plus ml-10\"></i> Create Supervisor\n        <% end %>\n      </div>\n    <% end %>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/user_mailer/password_changed_reminder.html.erb",
    "content": "<meta itemprop=\"name\" content=\"Password changed reminder\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n<style>/* Email styles need to be inline */</style>\n<table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n  <tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n    <td class=\"content-block\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\" valign=\"top\">\n      Hello <%= @user.try(:display_name) || @user.email %>\n    </td>\n  </tr>\n  <tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n    <td class=\"content-block\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\" valign=\"top\">\n      Your CASA password has been changed.\n    </td>\n  </tr>\n  <tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n    <td class=\"content-block\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\" valign=\"top\">\n     If you have any questions, please contact a (Name of relevant CASA) CASA administrator for assistance.\n    </td>\n  </tr>\n</table>\n"
  },
  {
    "path": "app/views/users/_edit_profile.erb",
    "content": "<div class=\"mb-1\">\n  <strong class=\"text-dark\">CASA organization </strong>\n  <%= resource&.casa_org&.name %>\n</div>\n<div class=\"mb-1\">\n  <strong class=\"text-dark\">Added to system </strong>\n  <%= resource&.decorate(context: {format: :edit_profile})&.formatted_created_at %>\n</div>\n<div class=\"mb-1\">\n  <strong class=\"text-dark\">Email</strong>\n  <%= resource&.email %>\n</div>\n<div class=\"mb-1\">\n  <strong class=\"text-dark\">Invitation email sent </strong>\n  <%= resource&.decorate(context: {format: :edit_profile})&.formatted_invitation_sent_at || \"never\" %>\n</div>\n<div class=\"mb-1\">\n  <strong class=\"text-dark\">Last logged in </strong>\n  <%= resource&.decorate(context: {format: :edit_profile})&.formatted_current_sign_in_at || \"never\" %>\n</div>\n<div class=\"mb-1\">\n  <strong class=\"text-dark\">Invitation accepted </strong>\n  <%= resource&.decorate(context: {format: :edit_profile})&.formatted_invitation_accepted_at || \"never\" %>\n</div>\n<div class=\"mb-1\">\n  <strong class=\"text-dark\">Password reset last sent </strong>\n  <%= resource&.decorate(context: {format: :edit_profile})&.formatted_reset_password_sent_at || \"never\" %>\n</div>\n\n<% if resource.is_a?(Volunteer) %>\n  <div class=\"mb-1\">\n    <strong class=\"text-dark\">Learning Hours This Year</strong>\n    <%= resource.learning_hours_spent_in_one_year %>\n  </div>\n<% end %>\n"
  },
  {
    "path": "app/views/users/_languages.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-lg-12\">\n      <div class=\"title mb-30\">\n        <h2>My Languages</h2>\n      </div>\n    </div>\n    <div class=\"col-lg-12\">\n      <div class=\"card-style mb-30\">\n        <div class=\"table-wrapper table-responsive\">\n          <table class=\"table\">\n            <thead>\n              <tr>\n                <th><h4>Languages</h4></th>\n                <th><h4>Actions</h4></th>\n              </tr>\n            </thead>\n            <tbody>\n              <tr>\n                <td>English (default language)</td>\n                <td></td>\n              </tr>\n            <% volunteer.languages.each do |lang| %>\n              <tr>\n                <td><%= lang.name %></td>\n                <td>\n                  <%= link_to \"Delete\", remove_language_users_path(language_id: lang.id), class: \"btn btn-danger\",\n  method: :delete %>\n                </td>\n              </tr>\n            <% end %>\n            </tbody>\n          </table>\n        </div>\n\n        <%= form_with(model: volunteer, url: add_language_users_path) do |form| %>\n          <div class=\"row select-style-1 mt-3\">\n            <div class=\"col-lg-12\">\n              <h5><%= form.label :languages, \"Add Language\" %></h5>\n            </div>\n            <div class=\"col-lg-4\">\n              <div class=\"select-position\">\n                <%= form.select :languages,\n                          current_organization.languages.map { |lang| [lang.name, lang.id] },\n                          {include_blank: true},\n                          {name: :language_id} %>\n              </div>\n            </div>\n            <div class=\"col-lg-8\">\n              <%= button_tag \"Add\", type: :submit, class: \"main-btn primary-btn btn-hover\", id: \"add-language-button\" %>\n            </div>\n          </div>\n        <% end %>\n\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/users/edit.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-lg-12\">\n      <div class=\"title mb-30\">\n        <h1>Edit Profile</h1>\n      </div>\n    </div>\n    <div class=\"col-lg-12\">\n      <div class=\"card-style mb-30\">\n        <div class=\"card-body\">\n          <%= form_with(model: @user, scope: :user, url: users_path, method: :patch) do |form| %>\n            <div class=\"mb-30\">\n              <%= render \"/shared/error_messages\", resource: @user %>\n            </div>\n\n            <div class=\"input-style-1\">\n              <%= form.label :display_name, \"Display name\" %>\n              <%= form.text_field :display_name, class: \"form-control\" %>\n            </div>\n\n            <div class=\"input-style-1\">\n              <%= form.label :phone_number, \"Phone number\" %>\n              <%= form.text_field :phone_number, class: \"form-control\" %>\n            </div>\n\n            <div class=\"input-style-1\">\n              <%= form.label :date_of_birth, \"Date of birth\" %>\n              <%= form.date_field :date_of_birth,\n                                  value: @user.date_of_birth,\n                                  class: \"form-control label-font-weight\" %>\n            </div>\n\n            <% if current_user.address %>\n              <% address = current_user.address %>\n            <% else %>\n              <% address = Address.new %>\n            <% end %>\n\n            <div class=\"input-style-1\">\n              <%= form.fields_for :address, address do |f| %>\n                <%= f.label :content, \"Address\" %>\n                <%= f.text_field :content, class: \"form-control\" %>\n              <% end %>\n            </div>\n\n            <%= form.hidden_field :casa_org_id, value: current_user.casa_org_id %>\n\n            <%= render \"edit_profile\", resource: current_user %>\n\n            <br>\n          <div class=\"actions\" id=\"accordionExample\">\n\n              <%= form.button \"Update Profile\", type: \"submit\", class: \"main-btn primary-btn btn-hover mb-3\" %>\n              <button class=\"main-btn primary-btn btn-hover accordion mb-3\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#collapseOne\" aria-expanded=\"true\" aria-controls=\"collapseOne\">\n                Change Password\n              </button>\n\n              <button class=\"main-btn primary-btn btn-hover accordion mb-3\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#collapseTwo\" aria-expanded=\"true\" aria-controls=\"collapseTwo\">\n                Change Email\n              </button>\n\n            <% if policy(CasaAdmin).see_deactivate_option? %>\n              <% if @active_casa_admins.length > 1 %>\n                <%= link_to \"Deactivate\",\n                            deactivate_casa_admin_path(current_user),\n                            method: :patch,\n                            class: \"btn btn-outline-danger mb-3\",\n                            data: {confirm: \"WARNING: Marking an admin inactive will make them unable to login. Are you sure you want to do this?\"} %>\n              <% else %>\n                <%= link_to \"Deactivate\",\n                            \"#\",\n                            class: \"main-btn danger-btn-outline btn-hover mb-3\",\n                            data: {confirm: \"Contact your administrator at Ruby For Good to deactivate this account.\"} %>\n              <% end %>\n            <% end %>\n          </div>\n          <% end %>\n          <div class=\"accordionGroup\">\n            <div id=\"collapseOne\" class=\"collapse\" aria-labelledby=\"headingOne\" data-bs-parent=\"#accordionExample\">\n              <br>\n              <%= form_with(model: @user, scope: :user, url: {action: \"update_password\"}, method: :patch) do |f| %>\n                <div class=\"input-style-1\">\n                  <%= f.label :current_password, \"Current Password\" %><br>\n                  <%= f.password_field :current_password, autocomplete: \"off\", class: \"form-control\" %>\n                </div>\n                <div class=\"input-style-1\">\n                  <%= f.label :password, \"New Password\" %><br>\n                  <%= f.password_field :password, autocomplete: \"off\", class: \"form-control password-new\",\n        minlength: User.password_length.min %>\n                </div>\n                <div class=\"input-style-1\">\n                  <%= f.label :password_confirmation, \"New Password Confirmation\" %><br>\n                  <%= f.password_field :password_confirmation, class: \"form-control password-confirmation\",\n        minlength: User.password_length.min %>\n                </div>\n                <div class=\"actions mb-10\">\n                  <%= f.submit \"Update Password\", class: \"btn btn-danger submit-password\" %>\n                </div>\n              <% end %>\n            </div>\n            <div id=\"collapseTwo\" class=\"collapse\" aria-labelledby=\"headingOne\" data-bs-parent=\"#accordionExample\">\n              <br>\n              <%= form_with(model: @user, scope: :user, url: {action: \"update_email\"}, method: :patch) do |f| %>\n                <div class=\"input-style-1\">\n                  <%= f.label :current_password, \"Current Password\" %><br>\n                  <%= f.password_field :current_password, autocomplete: \"off\", class: \"form-control\", id: \"current_password_email\", required: true %>\n                </div>\n                <div class=\"input-style-1\">\n                  <%= f.label :email, \"New Email\" %><br>\n                  <%= f.text_field :email, type: \"email\", class: \"form-control email-new\", autocomplete: \"off\", value: nil, required: true %>\n                </div>\n                <div class=\"actions mb-10\">\n                  <%= f.submit \"Update Email\", class: \"btn btn-danger submit-email\" %>\n                </div>\n              <% end %>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n\n<div>\n  <%= form_with(model: @user, scope: :user, url: users_path, method: :patch) do |form| %>\n    <div class=\"title mb-30\">\n      <h2>Communication Preferences</h2>\n    </div>\n\n    <div class=\"card-style\">\n      <p>Tell us how you'd like to receive notifications.</p>\n      <div class=\"form-check checkbox-style mb-20\">\n        <%= form.check_box :receive_email_notifications, class: \"toggle-email-notifications form-check-input\" %>\n        <%= form.label :receive_email_notifications, \"Email Me\", class: \"form-check-label\" %>\n      </div>\n\n      <% if @user.casa_org.twilio_enabled? %>\n        <div class=\"form-check checkbox-style mb-20\">\n            <%= form.check_box :receive_sms_notifications, class: \"toggle-sms-notifications form-check-input\" %>\n            <%= form.label :receive_sms_notifications, \"Text Me\", class: \"form-check-label\" %>\n        </div>\n        <div class=\"ps-4 pb-4\">\n          <%= form.collection_check_boxes(\"sms_notification_event_ids\", SmsNotificationEvent.where(user_type: @user.type),\n    :id, :name) do |event| %>\n            <div class=\"form-check checkbox-style mb-20\">\n              <%= event.check_box(class: \"form-check-input form-check-input\", id: \"toggle-sms-notification-event\") %>\n              <%= event.label(class: \"form-check-label\") %>\n            </div>\n          <% end %>\n        </div>\n      <% else %>\n        <div class=\"form-check checkbox-style mb-20\">\n            <%= form.check_box :receive_sms_notifications, class: \"toggle-sms-notifications form-check-input\", disabled: true %>\n            <%= form.label :receive_sms_notifications, \"Enable Twilio For Text Messaging\", class: \"form-check-label\" %>\n        </div>\n      <% end %>\n\n      <div class=\"actions mb-10\">\n        <%= form.submit \"Save Preferences\", class: \"main-btn primary-btn btn-hover mb-3 save-preference\" %>\n      </div>\n    </div>\n  <% end %>\n</div>\n\n<% if current_user.volunteer? %>\n    <%= render partial: \"languages\", locals: { volunteer: current_user } %>\n<% end %>\n"
  },
  {
    "path": "app/views/volunteer_mailer/account_setup.html.erb",
    "content": "<meta itemprop=\"name\" content=\"Confirm Email\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n<style>/* Email styles need to be inline */</style>\n<table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n  <tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n    <td class=\"content-block\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\" valign=\"top\">\n      A <%= @user.casa_org.display_name %>’s County volunteer console account has been created for you. This console is\n      for logging the time you spend and actions you take on your CASA case. You can log activity with your CASA youth,\n      their family members, their foster family or placement, the DSS worker, your Case Supervisor and others associated\n      with your CASA case (such as teachers and therapists).\n    </td>\n  </tr>\n  <tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n    <td class=\"content-block\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\" valign=\"top\">\n      Your console account is associated with this email. If this is not the correct email to use, please stop here and\n      contact your Case Supervisor to change the email address. If you are ready to get started, please set your\n      password. This is the first step to accessing your new volunteer account.\n    </td>\n  </tr>\n  <tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n    <td class=\"content-block\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\" valign=\"top\">\n      You can see a video tour <a href=\"https://www.youtube.com/watch?v=eJ0HpjGJGRY\">on YouTube</a>\n    </td>\n  </tr>\n  <tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n    <td class=\"content-block\" itemprop=\"handler\" itemscope itemtype=\"http://schema.org/HttpActionHandler\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\" valign=\"top\">\n      <a href=\"<%= \"#{edit_password_url(@user, reset_password_token: @token)}\" %>\" target=\"_blank\" class=\"btn-primary\" itemprop=\"url\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2em; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; text-transform: capitalize; background-color: #348eda; margin: 0; border-color: #348eda; border-style: solid; border-width: 10px 20px;\">Set Your Password</a>\n    </td>\n  </tr>\n</table>\n"
  },
  {
    "path": "app/views/volunteer_mailer/case_contacts_reminder.html.erb",
    "content": "<meta itemprop=\"name\" content=\"Case Contacts Reminder\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n<style>/* Email styles need to be inline */</style>\n<table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n  <tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n    <td class=\"content-block\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\" valign=\"top\">\n      Hello <%= @user.display_name %>,\n    </td>\n  </tr>\n  <tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n    <td class=\"content-block\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\" valign=\"top\">\n      You are receiving this email as a reminder to input the case contacts which you have made. You can visit\n      <a href=\"<%= \"#{case_contacts_url}\" %>\" target=\"_blank\">this link</a> to edit your case contacts.\n    </td>\n  </tr>\n  <tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n    <td class=\"content-block\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\" valign=\"top\">\n      If you have any questions, please contact your most recent CASA supervisor for assistance.\n    </td>\n  </tr>\n</table>\n"
  },
  {
    "path": "app/views/volunteer_mailer/court_report_reminder.html.erb",
    "content": "<meta itemprop=\"name\" content=\"Court Report Due Reminder Email\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n<style>/* Email styles need to be inline */</style>\n<table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n  <tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n    <td class=\"content-block\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\" valign=\"top\">\n      <%= @user.display_name %>,\n    </td>\n  </tr>\n  <tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n    <td class=\"content-block\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\" valign=\"top\">\n      This is a reminder that your next court report is due on <%= @court_report_due_date %>.\n      Please submit your court report to your supervisor no later than this date.\n    </td>\n  </tr>\n  <tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n    <td class=\"content-block\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\" valign=\"top\">\n      You can generate a court report by clicking on \"Generate Court Reports\" in your volunteer portal.\n      Log in at https://www.casavolunteertracking.org to get started.\n    </td>\n  </tr>\n  <tr style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n    <td class=\"content-block\" style=\"font-family: Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\" valign=\"top\">\n      Please email your supervisor directly if you need further assistance.\n    </td>\n  </tr>\n</table>\n"
  },
  {
    "path": "app/views/volunteers/_form.html.erb",
    "content": "<%= form_with(model: @volunteer, url: volunteers_path, id: :new_volunteer) do |form| %>\n  <%= render \"/shared/error_messages\", resource: volunteer %>\n\n  <div class=\"input-style-1\">\n    <%= form.label :email, \"Email\" %>\n    <%= form.text_field :email, placeholder: \"Email\", class: \"form-control\" %>\n  </div>\n\n  <div class=\"input-style-1\">\n    <%= form.label :display_name, \"Display name\" %>\n    <%= form.text_field :display_name, placeholder: \"Display Name\", class: \"form-control\" %>\n  </div>\n\n  <div class=\"input-style-1\">\n    <%= form.label :phone_number, \"Phone number\" %>\n    <%= form.telephone_field :phone_number, placeholder: \"Phone Number\", class: \"form-control\" %>\n  </div>\n\n  <div class=\"input-style-1\">\n    <%= form.label :date_of_birth, \"Date of birth\" %>\n    <%= form.date_field :date_of_birth,\n                        value: @volunteer.date_of_birth,\n                        class: \"form-control label-font-weight\" %>\n  </div>\n\n  <div class=\"actions\">\n    <%= button_tag(\n      type: \"submit\",\n      class: \"main-btn primary-btn btn-hover btn-sm\"\n    ) do %>\n      <i class=\"lni lni-user mr-5\"></i> Create Volunteer\n    <% end %>\n  </div>\n<% end %>\n"
  },
  {
    "path": "app/views/volunteers/_manage_active.html.erb",
    "content": "<div class=\"field form-group\">\n  <% if user.active? %>\n    Volunteer is <%= render BadgeComponent.new(text: \"Active\", type: :success) %><br>\n    <% if policy(user).deactivate? %>\n      <%= link_to deactivate_volunteer_path(user),\n                  method: :patch,\n                  class: \"main-btn danger-btn-outline btn-hover btn-sm my-1\",\n                  data: {confirm: \"WARNING: Marking a volunteer inactive will make them unable to login. Are you sure you want to do this?\"} do %>\n        <i class=\"lni lni-bulb mr-10\"></i> Deactivate volunteer\n      <% end %>\n    <% end %>\n  <% else %>\n    <div class=\"alert alert-danger\">\n      Volunteer was deactivated on: <%= user.decorate.formatted_updated_at %>\n    </div>\n    <% if policy(user).activate? %>\n      <%= link_to activate_volunteer_path(user),\n                  method: :patch,\n                  class: \"main-btn success-btn-outline btn-hover btn-sm my-1\" do %>\n        <i class=\"lni lni-bulb mr-10\"></i> Activate volunteer\n      <% end %>\n    <% end %>\n  <% end %>\n  <% if (current_user.supervisor? ||\n      current_user.casa_admin?) &&\n      user.invitation_accepted_at.nil? %>\n    <%= link_to resend_invitation_volunteer_path(user),\n                class: \"main-btn danger-btn-outline btn-hover btn-sm my-1\" do %>\n      <i class=\"lni lni-telegram-original mr-10\"></i> Resend Invitation (Email)\n    <% end %>\n  <% end %>\n  <% if current_user.casa_admin? %>\n    <%= link_to send_reactivation_alert_volunteer_path(user),\n               id: \"#{current_user.casa_org.twilio_enabled? ? \"twilio_enabled\" : \"twilio_disabled\"}\",\n               class: \"main-btn danger-btn-outline btn-hover btn-sm my-1\" do %>\n      <i class=\"lni lni-alarm mr-10\"></i><%= current_user.casa_org.twilio_enabled? ? \"Send Reactivation Alert (SMS)\" : \"Enable Twilio To Send Reactivation Alert (SMS)\" %>\n    <% end %>\n  <% end %>\n</div>\n"
  },
  {
    "path": "app/views/volunteers/_manage_cases.erb",
    "content": "<div>\n  <h1 class=\"pt-5 mb-2\">Manage Cases</h1>\n\n  <div class=\"card card-container\">\n    <div class=\"card-body\">\n      <% if @volunteer.case_assignments.any? %>\n        <h3 class=\"my-3\">Assigned Cases</h3>\n        <div class=\"table-wrapper table-responsive\">\n          <table class='table' id='manage_cases'>\n            <thead>\n              <tr>\n                <th>Case Number</th>\n                <th>Transition Aged Youth</th>\n                <th>Assignment status</th>\n                <th>Actions</th>\n              </tr>\n            </thead>\n            <tbody>\n              <% @volunteer.case_assignments_with_cases.each do |assignment| %>\n                <tr id=\"<%= dom_id(assignment) %>\">\n                  <td>\n                    <%= link_to assignment.casa_case.case_number, casa_case_path(assignment.casa_case) %>\n                    <%= volunteer_badge(assignment.casa_case, current_user) %>\n                  </td>\n                  <td><%= assignment.casa_case.decorate.transition_aged_youth %></td>\n                  <td>\n                    <% if @volunteer.active? && assignment.casa_case.active? && assignment.active? %>\n                      Volunteer is <%= render BadgeComponent.new(text: \"Active\", type: :success) %>\n                    <% elsif @volunteer.active? && assignment.casa_case.active? && assignment.inactive? %>\n                      Volunteer is <%= render BadgeComponent.new(text: \"Unassigned\", type: :danger) %>\n                    <% elsif @volunteer.active? %>\n                      Case was deactivated\n                      on: <%= I18n.l(assignment.casa_case.updated_at, format: :standard, default: nil) %>\n                    <% else %>\n                      Deactivated\n                    <% end %>\n                  </td>\n                  <td>\n                    <% if @volunteer.active? && assignment.active && assignment.casa_case.active? %>\n                      <%- if Pundit.policy(current_user, @volunteer).unassign_case? %>\n                        <%= button_to unassign_case_assignment_path(assignment, volunteer_id: @volunteer.id),\n                                      method: :patch,\n                                      class: \"main-btn danger-btn btn-hover btn-sm\" do %>\n                          <i class=\"lni lni-cross-circle mr-5\"></i> Unassign Case\n                        <% end %>\n                      <%- end %>\n                    <% else %>\n                      None\n                    <% end %>\n                  </td>\n                </tr>\n              <% end %>\n            </tbody>\n          </table>\n        </div>\n      <% end %>\n\n      <% if @volunteer.active? %>\n        <br/>\n        <h3 class=\"mb-3\">Assign a New Case</h3>\n\n        <%= form_with(model: CaseAssignment.new, url: case_assignments_path(volunteer_id: @volunteer.id)) do |form| %>\n\n          <div class=\"select-style-1\">\n            <label for='case_assignment_casa_case_id'>Select a Case</label>\n            <%= form.select :casa_case_id, grouped_options_for_assigning_case(@volunteer), {},\n                            {class: \"form-control select2\"} %>\n          </div>\n\n          <%= button_tag(\n            type: \"submit\",\n            class: \"main-btn primary-btn btn-hover btn-sm\"\n          ) do %>\n            <i class=\"lni lni-checkmark mr-5\"></i> Assign Case\n          <% end %>\n        <% end %>\n      <% end %>\n    </div>\n  </div>\n</div>"
  },
  {
    "path": "app/views/volunteers/_manage_supervisor.erb",
    "content": "<div>\n  <h1 class=\"pt-5 mb-2\">Manage Supervisor</h1>\n\n  <div class=\"card card-container\">\n    <div class=\"card-body\">\n      <% if @volunteer.has_supervisor? %>\n        <h5 class=\"my-4\">\n          <span class=\"font-weight-bold\">\n            Current Supervisor:\n          </span>\n          <%= link_to(@volunteer.supervisor.display_name, edit_supervisor_path(@volunteer.supervisor.id)) %>\n        </h5>\n        <%= button_to unassign_supervisor_volunteer_path(@volunteer),\n                      method: :patch,\n                      class: \"main-btn danger-btn btn-hover btn-sm\" do %>\n          <i class=\"lni lni-cross-circle mr-5\"></i> Unassign from Supervisor\n        <% end %>\n      <% else %>\n        <h3 class=\"mb-3\">Assign a Supervisor</h3>\n\n        <%= form_with(model: SupervisorVolunteer.new, url: supervisor_volunteers_path(volunteer_id: @volunteer.id)) do |form| %>\n          <div class=\"select-style-1\">\n            <label for='supervisor_volunteer_supervisor_id'>Select a Supervisor</label>\n            <select name='supervisor_volunteer[supervisor_id]' id=\"supervisor_volunteer_supervisor_id\" class='form-control select2'>\n              <% @supervisors.each do |supervisor| %>\n                <option value=\"<%= supervisor.id %>\"><%= supervisor.display_name %></option>\n              <% end %>\n            </select>\n          </div>\n          <%= form.hidden_field :volunteer_id, :value => @volunteer.id %>\n          <%= button_tag(\n            type: \"submit\",\n            class: \"main-btn primary-btn btn-hover btn-sm\"\n          ) do %>\n            <i class=\"lni lni-checkmark mr-5\"></i> Assign Supervisor\n          <% end %>\n        <% end %>\n      <% end %>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/volunteers/_notes.html.erb",
    "content": "<div class=\"notes\">\n  <h1 class=\"pt-5 mb-2\">Notes</h1>\n\n  <div class=\"card card-container\">\n    <div class=\"card-body\">\n      <% if @volunteer.notes != [] %>\n        <h3 class=\"mb-4\">Notes About This Volunteer</h3>\n        <div class=\"table-wrapper table-responsive\">\n          <table class=\"table\">\n            <thead>\n              <tr>\n                <th>Note</th>\n                <th>Creator</th>\n                <th>Date</th>\n                <th>Actions</th>\n              </tr>\n            </thead>\n            <tbody>\n              <% @volunteer.notes.each do |note| %>\n                <tr>\n                  <td class=\"min-width\"><%= note.content %></td>\n                  <td class=\"min-width\"><%= note.creator.display_name %></td>\n                  <td class=\"min-width\"><%= l(note.created_at, format: :standard) %></td>\n                  <td class=\"min-width\">\n                    <%= link_to edit_volunteer_note_path(@volunteer, note), class: \"main-btn primary-btn btn-hover btn-sm\" do %>\n                      <i class=\"lni lni-pencil-alt mr-5\"></i> Edit\n                    <% end %>\n                    <%= link_to volunteer_note_path(@volunteer, note), class: \"main-btn danger-btn btn-hover btn-sm\", method: :delete do %>\n                      <i class=\"lni lni-trash-can mr-5\"></i> Delete\n                    <% end %>\n                  </td>\n                </tr>\n              <% end %>\n            </tbody>\n          </table>\n        </div>\n      <% end %>\n      <br>\n      <%= form_with(model: @volunteer.notes.new, local: true, url: volunteer_notes_path(@volunteer),\nid: \"volunteer-note-form\") do |form| %>\n        <h3 class=\"mb-3\">Create a New Note</h3>\n        <div class=\"input-style-1\">\n          <%= form.text_area :content, :rows => 5, placeholder: \"Enter a note regarding the volunteer. These notes are only visible to CASA administrators and supervisors.\",\n          class: \"form-control\" %>\n        </div>\n\n        <div class=\"actions\">\n          <%= button_tag(\n            type: \"submit\",\n            class: \"main-btn primary-btn btn-hover btn-sm\",\n            id: \"note-submit\"\n          ) do %>\n            <i class=\"lni lni-save mr-5\"></i> Save Note\n          <% end %>\n        </div>\n      <% end %>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/volunteers/_send_reminder_button.html.erb",
    "content": "<div class=\"d-inline-flex align-items-center\">\n  <%= button_tag(\n    type: \"submit\",\n    class: \"main-btn primary-btn btn-hover btn-sm mr-3\",\n    id: \"reminder_button\",\n    data_toggle: \"tooltip\",\n    title: \"Remind volunteer to input case contacts\".to_s\n  ) do %>\n    <i class=\"lni lni-alarm mr-10\"></i> Send Reminder\n  <% end %>\n\n  <div class=\"form-check\">\n    <%= check_box_tag 'with_cc', 1, false, class: 'form-check-input' %>\n    <%= label_tag 'with_cc', 'Send CC to Supervisor and Admin', class: 'unbold form-check-label ml-2' %>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/volunteers/_volunteer_reminder_form.erb",
    "content": "<%= form_with url: reminder_volunteer_path(volunteer), method: :patch, id: \"cc-check\" do %>\n  <%= render \"volunteers/send_reminder_button\", volunteer: volunteer %>\n<% end %>\n"
  },
  {
    "path": "app/views/volunteers/edit.html.erb",
    "content": "<%= link_to 'Back', volunteers_path, class: \"my-2\" %>\n\n<div class=\"row\">\n  <div class=\"col-sm-12 form-header\">\n    <h1>Editing Volunteer</h1>\n    <%= render \"volunteer_reminder_form\", volunteer: @volunteer %>\n  </div>\n  <div class=\"col-sm-12 form-header\">\n    <%= link_to impersonate_volunteer_path(@volunteer),\n      class: \"main-btn primary-btn btn-hover casa-case-button btn-sm my-3\" do %>\n        <i class=\"lni lni-user mr-10\"></i> Impersonate\n    <% end %>\n  </div>\n</div>\n<div class=\"card card-container\">\n  <div class=\"card-body\">\n    <%= form_with(model: @volunteer, url: volunteer_path(@volunteer), method: :patch) do |form| %>\n      <%= render \"/shared/error_messages\", resource: @volunteer %>\n\n      <%= render \"/shared/edit_form\", resource: @volunteer, f: form %>\n\n      <%= render \"/shared/invite_login\", resource: @volunteer %>\n\n      <p>\n        <%= render \"manage_active\", user: @volunteer %>\n      </p>\n\n      <div class=\"actions\">\n        <%= button_tag(\n          type: \"submit\",\n          class: \"main-btn primary-btn btn-hover btn-sm my-1\"\n        ) do %>\n          <i class=\"lni lni-checkmark-circle mr-5\"></i> Submit\n        <% end %>\n      </div>\n    <% end %>\n  </div>\n</div>\n\n<%= render 'notes' %>\n\n<%= render 'manage_cases' %>\n\n<%= render 'manage_supervisor' %>\n"
  },
  {
    "path": "app/views/volunteers/index.html.erb",
    "content": "<div class=\"title-wrapper pt-30\">\n  <div class=\"row align-items-center\">\n    <div class=\"col-md-6\">\n      <div class=\"title mb-30\">\n        <h1>Volunteers</h1>\n      </div>\n    </div>\n    <!-- end col -->\n    <div class=\"col-md-6\">\n      <div class=\"breadcrumb-wrapper mb-30\">\n        <span class=\"ml-5\">\n          <% if policy(current_organization.volunteers.new).new? %>\n            <%= link_to new_volunteer_path, class: \"main-btn primary-btn btn-hover btn-sm mb-2 mb-md-0\" do %>\n              <i class=\"lni lni-plus mr-10\"></i> New Volunteer\n            <% end %>\n          <% end %>\n            </span>\n        <span class=\"ml-5\">\n            <button type=\"button\" class=\"main-btn primary-btn btn-sm mb-2 mb-md-0\" data-bs-toggle=\"modal\" data-bs-target=\"#visibleColumns\">\n            <i class=\"lni lni-pin mr-10\"></i> Pick displayed columns\n            </button>\n          </span>\n      </div>\n    </div>\n  </div>\n</div>\n\n<div>\n  <div class=\"row volunteer-filters\">\n    <div class=\"col-sm-12 flex align-items-center\">\n      <h4 class=\"pull-left my-1 mr-2\">Filter by:</h4>\n      <div class=\"dropdown pull-left mx-2 my-1\">\n        <button class=\"btn btn-secondary dropdown-toggle\" type=\"button\" id=\"dropdownMenuButton1\" data-bs-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\">\n          Supervisor\n        </button>\n        <div class=\"dropdown-menu supervisor-options\">\n          <div class=\"dropdown-item form-check checkbox-style\">\n            <%= check_box_tag \"supervisor-option-none\", \"\", true,\n                              class: \"form-check-input\",\n                              data: { value: \"\" },\n                              id: \"unassigned-vol-filter\" %>\n            <%= label_tag \"supervisor-option-none\", \"No supervisor assignment\", class: \"form-check-label\", for: \"unassigned-vol-filter\" %>\n          </div>\n          <% current_organization.supervisors.where(active: true).each do |supervisor| %>\n            <div class=\"dropdown-item form-check checkbox-style\">\n              <% option_for_name = supervisor.display_name.downcase.gsub(/ /, '_').gsub(/[^a-z_]+/, '') -%>\n              <% tag_name = \"supervisor-option-#{option_for_name}\" -%>\n              <%= check_box_tag tag_name, supervisor.id,\n                                policy(User).edit_name?(supervisor), # checked?\n                                class: \"form-check-input\",\n                                data: { value: supervisor.id } %>\n              <%= label_tag tag_name, supervisor.display_name, class: \"form-check-label\" %>\n            </div>\n          <% end %>\n        </div>\n      </div>\n      <div class=\"dropdown pull-left mx-2 my-1\">\n        <button class=\"btn btn-secondary dropdown-toggle\" type=\"button\" id=\"dropdownMenuButton1\" data-bs-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\">\n          Status\n        </button>\n        <div class=\"dropdown-menu status-options\" aria-labelledby=\"dropdownMenuButton\">\n          <div class=\"dropdown-item form-check checkbox-style\">\n            <%= check_box_tag \"status_option_active\", \"true\", true,\n                              class: \"form-check-input\",\n                              data: { value: \"true\" } %>\n            <%= label_tag \"status_option_active\", \"Active\", class: \"form-check-label\" %>\n          </div>\n          <div class=\"dropdown-item form-check checkbox-style\">\n            <%= check_box_tag \"status_option_inactive\", \"false\", false,\n                              class: \"form-check-input\",\n                              data: { value: \"false\" } %>\n            <%= label_tag \"status_option_inactive\", \"Inactive\", class: \"form-check-label\" %>\n          </div>\n        </div>\n      </div>\n\n      <div class=\"dropdown pull-left mx-2 my-1\">\n        <button class=\"btn btn-secondary dropdown-toggle\" type=\"button\" id=\"dropdownMenuButton\" data-bs-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\">\n          Assigned to Transition Aged Youth\n        </button>\n        <div class=\"dropdown-menu transition-youth-options\" aria-labelledby=\"dropdownMenuButton\">\n          <div class=\"dropdown-item form-check checkbox-style\">\n            <%= check_box_tag \"transition_youth_option_yes\", \"true\", true,\n                              class: \"form-check-input\",\n                              data: { value: \"true\" } %>\n            <%= label_tag \"transition_youth_option_yes\", \"Yes\", class: \"form-check-label\" %>\n          </div>\n          <div class=\"dropdown-item form-check checkbox-style\">\n            <%= check_box_tag \"transition_youth_option_no\", \"false\", true,\n                              class: \"form-check-input\",\n                              data: { value: \"false\" } %>\n            <%= label_tag \"transition_youth_option_no\", \"No\", class: \"form-check-label\" %>\n          </div>\n        </div>\n      </div>\n      <div class=\"dropdown pull-left mx-2 my-1\">\n        <button class=\"btn btn-secondary dropdown-toggle\" type=\"button\" id=\"dropdownMenuButton\" data-bs-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\">\n          Has Extra Languages\n        </button>\n        <div class=\"dropdown-menu extra-language-options\" aria-labelledby=\"dropdownMenuButton\">\n          <div class=\"dropdown-item form-check checkbox-style\">\n            <%= check_box_tag \"extra_language_option_yes\", \"true\", true,\n                              class: \"form-check-input\",\n                              data: { value: \"true\" } %>\n            <%= label_tag \"extra_language_option_yes\", \"Yes\", class: \"form-check-label\" %>\n          </div>\n          <div class=\"dropdown-item form-check checkbox-style\">\n            <%= check_box_tag \"extra_language_option_no\", \"false\", true,\n                              class: \"form-check-input\",\n                              data: { value: \"false\" } %>\n            <%= label_tag \"extra_language_option_no\", \"No\", class: \"form-check-label\" %>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n\n<div class=\"warning-modal\">\n  <div class=\"modal fade\" id=\"visibleColumns\" tabindex=\"-1\" aria-hidden=\"true\">\n    <div class=\"modal-dialog modal-dialog-centered\">\n      <div class=\"modal-content card-style\">\n        <div class=\"modal-header px-0 border-0\">\n          <h5 class=\"text-bold\">Pick Displayed Columns</h5>\n          <button\n            class=\"border-0 bg-transparent h1\"\n            data-bs-dismiss=\"modal\">\n            <i class=\"lni lni-cross-circle\"></i>\n          </button>\n        </div>\n        <div class=\"modal-body px-0\">\n          <div class=\"mb-30\">\n            <h6 class=\"mb-20\">\n              Select columns:\n            </h6>\n            <% Volunteer::TABLE_COLUMNS.each_with_index do |column, index| %>\n              <div class=\"form-check checkbox-style m-2\">\n                <%= check_box_tag \"pick-#{column}\",\n                                  \"1\",\n                                  false,\n                                  class: \"form-check-input toggle-visibility\",\n                                  data: { column: index } %>\n                <%= label_tag \"pick-#{column}\", column.titleize, class: \"form-check-label\" %>\n              </div>\n            <% end %>\n          </div>\n          <div class=\"action d-flex flex-wrap justify-content-end\">\n            <button\n              data-bs-dismiss=\"modal\"\n              class=\"main-btn danger-btn-outline btn-hover m-1\"><i class=\"lni lni-ban mr-10\"></i>\n              Close\n            </button>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n\n<div class=\"tables-wrapper\" data-controller=\"select-all\" data-select-all-hidden-class=\"invisible\" data-select-all-button-label-value=\"Manage Volunteer\">\n  <div class=\"row\">\n    <div class=\"col-lg-12\">\n      <div class=\"card-style mb-30\">\n        <div class=\"mb-3\">\n          <button type=\"button\" class=\"main-btn dark-btn btn-sm mb-2 mb-md-0 invisible\" data-bs-toggle=\"modal\" data-bs-target=\"#bulk-assigment-modal\" data-select-all-target=\"button\">\n            <i class=\"lni lni-users mr-10\"></i>\n            <span class=\"d-inline\" data-select-all-target=\"buttonLabel\"></span>\n          </button>\n        </div>\n        <div class=\"table-responsive\">\n          <%= form_with(url: bulk_assignment_supervisor_volunteers_path, id: \"form-bulk-assignment\", data: { controller: \"disable-form\", \"disable-form-unallowed-value\": '[\"unselected\"]', \"disable-form-disabled-class\": \"deactive-btn\", \"disable-form-enabled-class\": \"btn-hover dark-btn\" }) do |form| %>\n            <table\n              class=\"table hover\"\n              id=\"volunteers\"\n              data-source=\"<%= datatable_volunteers_path format: :json %>\">\n              <thead>\n              <tr>\n                <th>\n                  <input id=\"checkbox-toggle-all\" type=\"checkbox\" class=\"form-check-input\" data-select-all-target=\"checkboxAll\" data-action=\"select-all#toggleAll\">\n                </th>\n                <th>Name</th>\n                <th>Email</th>\n                <th>Supervisor</th>\n                <th>Status</th>\n                <th>Assigned To Transition Aged Youth</th>\n                <th>Case Number(s)</th>\n                <th>Last Attempted Contact</th>\n                <th>Contacts Made in Past 60 Days</th>\n                <th>Hours spent in last 30 days</th>\n                <th>Extra Languages</th>\n                <th>Actions</th>\n              </tr>\n              </thead>\n              <tbody>\n              </tbody>\n            </table>\n\n            <div class=\"warning-modal\">\n              <div class=\"modal fade\" id=\"bulk-assigment-modal\" tabindex=\"-1\" aria-hidden=\"true\">\n                <div class=\"modal-dialog modal-dialog-centered\">\n                  <div class=\"modal-content card-style\">\n                    <div class=\"modal-header px-0 border-0\">\n                      <h5 class=\"text-bold\">Bulk Volunteer Assignment</h5>\n                      <button\n                        class=\"border-0 bg-transparent h1\"\n                        data-bs-dismiss=\"modal\"\n                        type=\"button\">\n                        <i class=\"lni lni-cross-circle\"></i>\n                      </button>\n                    </div>\n                    <div class=\"modal-body px-0\">\n                      <div class=\"mb-30\">\n                       <div class=\"select-style-1\">\n                        <label for='supervisor_volunteer_supervisor_id'>\n                           Choose a new supervisor from the dropdown below to reassign selected volunteers. Once you're ready, click the \"Confirm\" button to apply the changes.\n                        </label>\n                        <select name='supervisor_volunteer[supervisor_id]' id=\"supervisor_volunteer_supervisor_id\" class='supervisor-bulk-assignment form-control' required=\"required\" data-disable-form-target=\"input\" data-action=\"disable-form#validate\">\n                          <option value=\"unselected\">Choose a supervisor</option>\n                          <option value=\"\">None</option>\n                          <% @supervisors&.active&.each do |supervisor| %>\n                            <option value=\"<%= supervisor.id %>\"><%= supervisor.display_name %></option>\n                          <% end %>\n                       </select>\n                       </div>\n                      </div>\n                      <div class=\"action d-flex flex-wrap justify-content-end\">\n                        <button\n                          data-bs-dismiss=\"modal\"\n                          class=\"main-btn danger-btn-outline btn-hover m-1\"\n                          type=\"button\"><i class=\"lni lni-ban mr-10\"></i>\n                          Close\n                        </button>\n                        <%= form.button type: :submit, class: \"main-btn deactive-btn m-1\", data: { \"disable-form-target\": \"submitButton\" }, disabled: true do %>\n                          Confirm\n                        <% end %>\n                      </div>\n                    </div>\n                  </div>\n                </div>\n              </div>\n            </div>\n          <% end %>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "app/views/volunteers/new.html.erb",
    "content": "<div class=\"col-md-6\">\n  <div class=\"title mb-30 mt-30\">\n    <h1>Create New Volunteer</h1>\n  </div>\n</div>\n\n<div class=\"card card-container\">\n  <div class=\"card-body\">\n    <%= render 'form', volunteer: @volunteer %>\n  </div>\n</div>\n"
  },
  {
    "path": "app.json",
    "content": "{\n  \"name\": \"CASA\",\n  \"addons\": [\n    \"papertrail:choklad\",\n    \"scheduler:standard\",\n    {\n      \"plan\": \"heroku-postgresql\",\n      \"options\": {\n        \"version\": \"12\"\n      }\n    }\n  ],\n  \"buildpacks\": [\n    {\n      \"url\": \"heroku/ruby\"\n    }\n  ],\n  \"formation\": {\n    \"web\": {\n      \"quantity\": 1,\n      \"size\": \"hobby\"\n    }\n  },\n  \"scripts\": {\n    \"postdeploy\": \"bundle exec rake db:migrate db:seed\"\n  },\n  \"stack\": \"heroku-20\"\n}\n"
  },
  {
    "path": "babel.config.js",
    "content": "module.exports = { presets: ['@babel/preset-env'] }\n"
  },
  {
    "path": "bin/asset_bundling_scripts/build_js.js",
    "content": "#!/usr/bin/env node\n\nconst CLIArgs = process.argv.slice(2)\nconst isWatching = CLIArgs.includes('--watch')\n\nconst esbuild = require('esbuild')\nconst logger = require('./logger.js')\n\nconst watchingConsoleLogger = [{\n  name: 'watching-console-logger',\n  setup (build) {\n    build.onEnd(result => {\n      if (result.errors.length) {\n        logger.error('watch build failed:')\n        logger.error(`  build failed with ${result.errors.length} errors`)\n\n        for (const error of result.errors) {\n          logger.error('  Error:')\n          logger.error(JSON.stringify(error, null, 2))\n        }\n      } else {\n        logger.info('watch build succeeded:')\n        logger.info(JSON.stringify(result, null, 2))\n      }\n    })\n  }\n}]\n\nasync function main () {\n  const context = await esbuild.context({\n    entryPoints: ['app/javascript/application.js', 'app/javascript/all_casa_admin.js'],\n    outdir: 'app/assets/builds',\n    bundle: true,\n    plugins: watchingConsoleLogger\n  })\n\n  if (isWatching) {\n    await context.watch()\n  } else {\n    await context.rebuild()\n    await context.dispose()\n  }\n}\n\nmain().catch((e) => {\n  console.error(e.message)\n  process.exit(1)\n})\n"
  },
  {
    "path": "bin/asset_bundling_scripts/logger.js",
    "content": "const defaultText = '\\x1b[0m'\n// const bright = '\\x1b[1m'\n// const dim = '\\x1b[2m'\n// const underscore = '\\x1b[4m'\n// const blink = '\\x1b[5m'\n// const reverse = '\\x1b[7m'\n// const hidden = '\\x1b[8m'\n\n// const textBlack = '\\x1b[30m'\nconst textRed = '\\x1b[31m'\n// const textGreen = '\\x1b[32m'\nconst textYellow = '\\x1b[33m'\n// const textBlue = '\\x1b[34m'\n// const textMagenta = '\\x1b[35m'\nconst textCyan = '\\x1b[36m'\n// const textWhite = '\\x1b[37m'\n\n// const highlightBlack = '\\x1b[40m'\n// const highlightRed = '\\x1b[41m'\n// const highlightGreen = '\\x1b[42m'\n// const highlightYellow = '\\x1b[43m'\n// const highlightBlue = '\\x1b[44m'\n// const highlightMagenta = '\\x1b[45m'\n// const highlightCyan = '\\x1b[46m'\n// const highlightWhite = '\\x1b[47m'\n\nmodule.exports = {\n  error: function (message) {\n    if (typeof message !== 'string') {\n      throw new TypeError('Param message must be a string')\n    }\n\n    console.error(textRed + message + defaultText)\n  },\n\n  info: function (message) {\n    if (typeof message !== 'string') {\n      throw new TypeError('Param message must be a string')\n    }\n\n    console.log(textCyan + message + defaultText)\n  },\n\n  warn: function (message) {\n    if (typeof message !== 'string') {\n      throw new TypeError('Param message must be a string')\n    }\n\n    console.warn(textYellow + message + defaultText)\n  }\n}\n"
  },
  {
    "path": "bin/brakeman",
    "content": "#!/usr/bin/env ruby\nrequire \"rubygems\"\nrequire \"bundler/setup\"\n\nARGV.unshift(\"--ensure-latest\")\n\nload Gem.bin_path(\"brakeman\", \"brakeman\")\n"
  },
  {
    "path": "bin/bundle",
    "content": "#!/usr/bin/env ruby\n# frozen_string_literal: true\n\n#\n# This file was generated by Bundler.\n#\n# The application 'bundle' is installed as part of a gem, and\n# this file is here to facilitate running it.\n#\n\nrequire 'rubygems'\n\nm = Module.new do\n  module_function\n\n  def invoked_as_script?\n    File.expand_path($PROGRAM_NAME) == File.expand_path(__FILE__)\n  end\n\n  def env_var_version\n    ENV['BUNDLER_VERSION']\n  end\n\n  def cli_arg_version\n    return unless invoked_as_script? # don't want to hijack other binstubs\n    unless 'update'.start_with?(ARGV.first || ' ')\n      return\n    end # must be running `bundle update`\n\n    bundler_version = nil\n    update_index = nil\n    ARGV.each_with_index do |a, i|\n      if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN\n        bundler_version = a\n      end\n      next unless a =~ /\\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\\z/\n\n      bundler_version = Regexp.last_match(1)\n      update_index = i\n    end\n    bundler_version\n  end\n\n  def gemfile\n    gemfile = ENV['BUNDLE_GEMFILE']\n    return gemfile if gemfile && !gemfile.empty?\n\n    File.expand_path('../Gemfile', __dir__)\n  end\n\n  def lockfile\n    lockfile =\n      case File.basename(gemfile)\n      when 'gems.rb' then gemfile.sub(/\\.rb$/, gemfile)\n      else \"#{gemfile}.lock\"\n      end\n    File.expand_path(lockfile)\n  end\n\n  def lockfile_version\n    return unless File.file?(lockfile)\n\n    lockfile_contents = File.read(lockfile)\n    unless lockfile_contents =~ /\\n\\nBUNDLED WITH\\n\\s{2,}(#{Gem::Version::VERSION_PATTERN})\\n/\n      return\n    end\n\n    Regexp.last_match(1)\n  end\n\n  def bundler_version\n    @bundler_version ||=\n      env_var_version || cli_arg_version ||\n      lockfile_version\n  end\n\n  def bundler_requirement\n    return \"#{Gem::Requirement.default}.a\" unless bundler_version\n\n    bundler_gem_version = Gem::Version.new(bundler_version)\n\n    requirement = bundler_gem_version.approximate_recommendation\n\n    return requirement unless Gem::Version.new(Gem::VERSION) < Gem::Version.new('2.7.0')\n\n    requirement += '.a' if bundler_gem_version.prerelease?\n\n    requirement\n  end\n\n  def load_bundler!\n    ENV['BUNDLE_GEMFILE'] ||= gemfile\n\n    activate_bundler\n  end\n\n  def activate_bundler\n    gem_error = activation_error_handling do\n      gem 'bundler', bundler_requirement\n    end\n    return if gem_error.nil?\n\n    require_error = activation_error_handling do\n      require 'bundler/version'\n    end\n    if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))\n      return\n    end\n\n    warn \"Activating bundler (#{bundler_requirement}) failed:\\n#{gem_error.message}\\n\\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`\"\n    exit 42\n  end\n\n  def activation_error_handling\n    yield\n    nil\n  rescue StandardError, LoadError => e\n    e\n  end\nend\n\nm.load_bundler!\n\nload Gem.bin_path('bundler', 'bundle') if m.invoked_as_script?\n"
  },
  {
    "path": "bin/delayed_job",
    "content": "#!/usr/bin/env ruby\n\nrequire File.expand_path(File.join(File.dirname(__FILE__), '..', 'config', 'environment'))\nrequire 'delayed/command'\nDelayed::Command.new(ARGV).daemonize\n"
  },
  {
    "path": "bin/dev",
    "content": "#!/usr/bin/env bash\n\nif ! command -v foreman &> /dev/null\nthen\n  echo \"Installing foreman...\"\n  gem install foreman\nfi\n\nforeman start -f Procfile.dev \"$@\"\n"
  },
  {
    "path": "bin/git_hooks/README.md",
    "content": "This folder contains helper scripts for [Git hooks](https://www.atlassian.com/git/tutorials/git-hooks).\n\nTo create a hook, make a file inside the directory `.git/hooks/` with the name of the hook you want to set up. The file names will **not** include an extension like `.md` or `.txt`.\nFor example, if you want to set up a pre-commit hook, the file should be called `pre-commit`, or if you want a pre-push hook, it should be called `pre-push`. Assuming you're on a unix based operating system, make sure the file is executable using `chmod +x <FILE NAME>`.  \n\nOnce you've created that file, put the appropriate she-bang on the first line:\n```bash\n#!/bin/sh\n```\nfollowed by the script you want to run and any arguments or flags for that script.\n\nSee [Example Hooks](#example-hooks) below for how you might want to set these up.\nYou can read more about Git hooks [here](https://git-scm.com/docs/githooks).\n\n## Hook Scripts\n\n### `build-assets`  \nCompiles js and css to be served by your local webserver  \nUsage: `./build-assets`\n\n### `lint`  \nLints files on the current branch  \nUsage: `./lint <diff policy>`  \n + `<diff policy>`(optional) can be one of the the following\n   - `--staged` lints the files staged for commit\n   - `--unpushed` lints files changed by commits not yet pushed to origin\n   - `--all` (default) lints all files in the repo  \n\n### `migrate-all`  \nRuns all migrations if any are found to be down  \nUsage: `./migrate-all`  \n\n### `update-dependences`  \nInstalls dependencies if any are missing  \nUsage: `./update-dependencies`  \n  \n### `update-branch`\nUpdates the `main` and current branch by rebasing your commits on top of changes from the official casa repo  \nThis script assumes no commits were made directly to main  \nUsage: `./update-branch <remote name>`  \n + `<remote name>` is the name of the [remote](https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes) pointing to the official casa repo\n   \n## Example Hooks\n### pre-push\n    #!/bin/sh\n  \n    ./bin/git_hooks/update-branch actual_casa\n    ./bin/git_hooks/lint --unpushed\n\n### post-checkout\n    #!/bin/sh\n\n    ./bin/git_hooks/update-dependencies\n    ./bin/git_hooks/build-assets\n\n### post-merge, post-rewrite  \n    #!/bin/sh\n\n    ./bin/git_hooks/update-dependencies\n    ./bin/git_hooks/build-assets\n    # migrate-all has to come after update-dependencies because it relies on bundle\n    ./bin/git_hooks/migrate-all\n    \n## Disabling Hook Scripts  \nIf you ever need to disable a certain script from running in a hook, simply comment it out using `#`.  \n  \nExample:  \n\n    #!/bin/sh\n  \n    ./bin/git_hooks/update-branch actual_casa\n    #./bin/git_hooks/lint --unpushed # <-- This hook is now disabled\n"
  },
  {
    "path": "bin/git_hooks/build-assets",
    "content": "#!/bin/sh\n# Builds assets(js and css)\n# Usage:\n#   ./build-assets\n\nrepo_root=\"$(git rev-parse --show-toplevel)\"\n. \"$repo_root/bin/git_hooks/logger\"\n\nlog info \"Building Assets\"\n\nif ! [ -x \"$(command -v npm)\" ]; then\n  log error \"Command npm could not be found\"\n  exit 1\nfi\n\nnpm run build\nnpm run build:css\n"
  },
  {
    "path": "bin/git_hooks/lint",
    "content": "#!/bin/sh\n# Lints files on the current branch\n# Usage:\n#   lint <options>\n#     --staged\n#       lints if the files staged for commit contain lintable files\n#     --unpushed\n#       lints files changed by commits not yet pushed to origin\n#     --all (default)\n#       lints all files in the repo\n\nrepo_root=\"$(git rev-parse --show-toplevel)\"\n. \"$repo_root/bin/git_hooks/logger\"\n\nlog info \"Attempting to lint\"\n\ncurrent_branch=\"$(git branch --show-current)\"\n\nif [ $# -lt 1 ]; then\n  diff_policy=\"--all\"\nelse\n  diff_policy=$1\nfi\n\ncase $diff_policy in\n  --all)\n    changed_file_list=\"app/models/a.rb\\nb.erb\\nc.js\"\n    ;;\n\n  --staged)\n    changed_file_list=$(git diff --name-only --cached)\n    ;;\n\n  --unpushed)\n    if [ -z \"$(git ls-remote --heads origin ${current_branch})\" ]; then\n      changed_file_list=$(git diff --name-only HEAD~$(git cherry -v main ${current_branch} | wc -l) HEAD)\n    else\n      changed_file_list=$(git diff --name-only origin/${current_branch}..HEAD)\n    fi\n    ;;\n\n  *)\n    log error \"unknown option $diff_policy\"\n    exit 1\n    ;;\nesac\n\nerb_changed_count=$(echo \"$changed_file_list\" | grep -c \"\\.erb$\")\nfactory_changed_count=$(echo \"$changed_file_list\" | grep -c -E \"(app\\/models|spec\\/factories)\\/.*\\.rb$\")\njs_changed_count=$(echo \"$changed_file_list\" | grep -c \"\\.js$\")\nrb_changed_count=$(echo \"$changed_file_list\" | grep -c \"\\.rb$\")\n\nlint_time=$(date +%s)\n\nif test $erb_changed_count -gt 0; then\n  log info \"Linting via erblint\"\n\n  cd $repo_root/app\n\n  if ! [ -x \"$(command -v bundle)\" ]; then\n    log error \"Command bundle could not be found\"\n    exit 1\n  else\n    if ! bundle exec erb_lint --lint-all --autocorrect ; then\n      log error \"ERB Lint linting failed, could not fix 1 or more issues\\n  See above output for more details\"\n      exit 1\n    fi\n  fi\nfi\n\nif test $factory_changed_count -gt 0; then\n  log info \"Linting via factory:lint\"\n\n  cd $repo_root/app\n\n  if ! [ -x \"$(command -v bundle)\" ]; then\n    log error \"Command bundle could not be found\"\n    exit 1\n  else\n    if ! bundle exec rake factory_bot:lint; then\n      log error \"Factory bot linting failed, could not fix 1 or more issues\\n  See above output for more details\"\n      exit 1\n    fi\n  fi\nfi\n\nif test $js_changed_count -gt 0; then\n  log info \"Linting javasript via standard\"\n\n  cd $repo_root/app\n\n  if ! [ -x \"$(command -v npm)\" ]; then\n    log error \"Command npm could not be found\"\n    exit 1\n  else\n    if ! npm run lint:fix ; then\n      log error \"npm linting failed, could not fix 1 or more issues\\n  See above output for more details\"\n      exit 1\n    fi\n  fi\nfi\n\nif test $rb_changed_count -gt 0; then\n  log info \"Linting via standardrb\"\n\n  cd $repo_root\n\n  if ! [ -x \"$(command -v bundle)\" ]; then\n    log error \"Command bundle could not be found\"\n    exit 1\n  else\n    if ! bundle exec standardrb --fix ; then\n      log error \"Standard linting failed, could not fix 1 or more issues\\n  See above output for more details\"\n      exit 1\n    fi\n  fi\nfi\n\ncd $repo_root\nfor file in $(git diff --name-only); do\n  last_modified_time=$(date -r $file +%s)\n  if [ $last_modified_time -ge $lint_time ] ; then\n    log warn \"Some files were linted. Please preserve the changes before continuing.\\n  Run git diff to view linter changes.\"\n    exit 1\n  fi\ndone\n\nlog success \"No files were linted\"\n"
  },
  {
    "path": "bin/git_hooks/logger",
    "content": "#!/bin/sh\n# Colorized output with logging levels\n\n# Set colors if available\nif test -t 1; then # if terminal\n    ncolors=$(which tput > /dev/null && tput colors) # supports color\n    if test -n \"$ncolors\" && test $ncolors -ge 8; then\n        normal=\"$(tput sgr0)\"\n        red=\"$(tput setaf 1)\"\n        green=\"$(tput setaf 2)\"\n        yellow=\"$(tput setaf 3)\"\n        cyan=\"$(tput setaf 6)\"\n    fi\nfi\n\n# Colorized output\n#   Param $1 string | The logging level: info, warning, or error\n#   Param $2 string | The message to be logged\nlog () {\n  if [ $# -lt 2 ]; then\n    echo \"${red}ERROR: function log was run with insufficient parameters ${normal}\"\n    return\n  fi\n  \n  case $1 in\n    success)\n      printf \"${green}SUCCESS: $2 ${normal}\\n\"\n    ;;\n    info)\n      printf \"${cyan}INFO: $2 ${normal}\\n\"\n    ;;\n    warn)\n      printf \"${yellow}WARNING: $2 ${normal}\\n\"\n    ;;\n    error)\n      printf \"${red}ERROR: $2 ${normal}\\n\"\n    ;;\n    *)\n      echo \"${red}ERROR: Unrecognized log level: $1 ${normal}\"\n    ;;\n  esac\n}\n"
  },
  {
    "path": "bin/git_hooks/migrate-all",
    "content": "#!/bin/sh\n# Runs migrations if any are found to be down\n# Usage:\n#   ./migrate-all\n\nrepo_root=\"$(git rev-parse --show-toplevel)\"\n. \"$repo_root/bin/git_hooks/logger\"\n\nlog info \"Checking for down migrations\"\n\nif ! [ -x \"$(command -v bundle)\" ]; then\n  log error \"Command bundle could not be found\"\n  exit 1\nfi\n\nif bundle exec rails db:migrate:status | grep \"  down\"; then\n  log info \"Down migrations found. Migrating...\"\n\n  bundle exec rails db:migrate\nelse\n  log success \"No down migrations found\"\nfi\n"
  },
  {
    "path": "bin/git_hooks/update-branch",
    "content": "#!/bin/sh\n# Updates the `main` and current branch by rebasing your commits on top of changes from the official casa repo\n# Usage:\n#   update-branch <remote name>\n#     <remote name>\n#       The name of the remote pointing to the official casa repo\n\nrepo_root=\"$(git rev-parse --show-toplevel)\"\n. \"$repo_root/bin/git_hooks/logger\"\n\nlog info \"Attempting to update local repo\"\n\nif [ $# -lt 1 ]; then\n  log error \"Missing required arg <remote name>\"\n  exit 1\nfi\n\nupstream_remote=$1\nbranch_to_update=\"$(git branch --show-current)\"\n\nif test -z \"$(git branch --list ${branch_to_update})\"; then\n  log error \"Could not find branch $branch_to_update\"\n  exit 1\nfi\n\nlog info \"Fetching updates from upsteam\"\ngit fetch $upstream_remote\n\nlog info \"Updating main\"\ngit checkout main\ngit merge --ff-only $upstream_remote/main\n\nif test $branch_to_update != \"main\"; then\n  log info \"Updating $branch_to_update\"\n  git checkout $branch_to_update\n  git rebase -r $upstream_remote/main\nfi\n"
  },
  {
    "path": "bin/git_hooks/update-dependencies",
    "content": "#!/bin/sh\n# Installs dependencies if any are missing\n# Usage:\n#   update-dependencies\n#     no arguments\n\nrepo_root=\"$(git rev-parse --show-toplevel)\"\n. \"$repo_root/bin/git_hooks/logger\"\n\nlog info \"Checking rails dependency status\"\n\nif ! [ -x \"$(command -v bundle)\" ]; then\n  log error \"Command bundle could not be found\"\n  exit 1\nfi\n\nif ! [ -x \"$(command -v npm)\" ]; then\n  log error \"Command npm could not be found\"\n  exit 1\nfi\n\nif ! bundle check; then\n  log info \"Updating rails dependencies\"\n  bundle install\nelse\n  log success \"Dependencies up to date\"\nfi\n\nlog info \"Checking javascript dependency status\"\n\nif [ $(git diff HEAD@{1}..HEAD@{0} -- \"package-lock.json\" | wc -l) -gt 0 ] || [ $(git diff HEAD@{1}..HEAD@{0} -- \"package.json\" | wc -l) -gt 0 ]; then\n  log info \"Updating JavaScript dependencies\"\n  npm install\nfi"
  },
  {
    "path": "bin/lint",
    "content": "#!/usr/bin/env bash\n\nbundle exec standardrb --fix --format progress\nbundle exec erb_lint --lint-all --autocorrect\nnpm run lint:fix\necho \"Linting Factories\"\nrails factory_bot:lint\n"
  },
  {
    "path": "bin/login",
    "content": "#!/usr/bin/env ruby\n\n# See HELP_TEXT below for description.\n\nrequire 'webdrivers'\nrequire 'capybara/dsl'\n\nclass LoginExecutor\n\n  include Capybara::DSL\n\n  Capybara.default_driver = :selenium\n\n  DEFAULT_LOGIN_URL        = ENV['CASA_DEFAULT_LOGIN_URL'] || 'http://localhost:3000'\n  ALL_CASA_ADMIN_LOGIN_URL = ENV['ALL_CASA_ADMIN_LOGIN_URL'] || 'http://localhost:3000/all_casa_admins/sign_in'\n\n  User = Struct.new(:email, :url)\n\n  USERS = [\n      User.new('volunteer1@example.com',       DEFAULT_LOGIN_URL),\n      User.new('supervisor1@example.com',      DEFAULT_LOGIN_URL),\n      User.new('casa_admin1@example.com',      DEFAULT_LOGIN_URL),\n      User.new('other_casa_admin@example.com', DEFAULT_LOGIN_URL),\n      User.new('other.supervisor@example.com', DEFAULT_LOGIN_URL),\n      User.new('other.volunteer@example.com',  DEFAULT_LOGIN_URL),\n      User.new('allcasaadmin@example.com',     ALL_CASA_ADMIN_LOGIN_URL),\n  ]\n\n  HELP_TEXT = <<~HEREDOC\n    \n    Usage: bin/login [user_number]\n\n    This script automates login for experimentation with the users added to the application when it is seeded\n    in development mode.\n                        \n    If executed without any arguments, it outputs the available users and accepts a numbered choice.\n    It then logs in as that choice, at the URL appropriate for that user.\n                                    \n    It can also be executed with the user list element number passed as an argument, to bypass interactive mode.\n\n    The browser window remains open as long as the script has not yet terminated (using Ctrl-C).\n    You can either keep a terminal with this script open, or you can send it to the background with Ctrl-Z.\n    If the latter, when you are finished using the browser, you can bring the script back to the foreground with `fg[Enter]`.\n\n  HEREDOC\n\n\n  def self.login\n    self.new.call\n  end\n\n\n  def call\n\n    if ARGV.first == '-h'\n      puts HELP_TEXT\n      exit 0\n    end\n\n    user = ARGV.empty? ? get_user_from_input : get_user_from_arg\n    puts \"\\nLogging in to #{user.url} as #{user.email}...\\n\\n\"\n    visit_and_log_in(user)\n    print_post_open_message_and_wait\n  end\n\n\n  private  # ----------------------------- all methods below are private ----------------\n\n  def visit_and_log_in(user)\n    visit user.url\n    fill_in \"Email\", with: user.email\n    fill_in \"Password\", with: \"12345678\"\n    click_on \"Log in\"\n  end\n\n\n  def get_user_from_arg\n    arg = ARGV.first.strip\n    user = USERS[arg.to_i - 1]\n    unless user\n      puts \"\\nInvalid option: #{arg}. Must be a number between 1 and #{USERS.size}\"\n      exit -1\n    end\n    user\n  end\n\n\n  def get_user_from_input\n    puts \"With which user would you like to log in to CASA?\\n\\n\"\n    USERS.each_with_index do |user, index|\n      puts \"#{index + 1}) #{user.email}\"\n    end\n    puts \"\\nInput a number, then [Enter]. An invalid entry will exit.\"\n\n    choice = $stdin.gets.chomp.to_i\n\n    exit unless (1..(USERS.size)).include?(choice)\n\n    USERS[choice - 1]\n  end\n\n\n  def print_post_open_message_and_wait\n    loop do\n      puts <<~HEREDOC\n\n      --------------------------------------------------------------------------------\n      Press Ctrl-C to exit and close browser.\n\n      To move this script to the background so you can continue using your terminal:\n        Press Ctrl-Z.\n        When you are done, type fg[Enter] and then Ctrl-C.\n      HEREDOC\n\n      if ARGV.empty?\n        puts \"\\nNext time you can pass the user number on the command line if you like.\\n\\n\"\n      end\n      $stdin.gets\n    end\n  end\nend\n\n\nLoginExecutor.login"
  },
  {
    "path": "bin/npm",
    "content": "#!/usr/bin/env ruby\nAPP_ROOT = File.expand_path('..', __dir__)\nDir.chdir(APP_ROOT) do\n  npm = ENV[\"PATH\"].split(File::PATH_SEPARATOR).\n    select { |dir| File.expand_path(dir) != __dir__ }.\n    product([\"npm\", \"npm.cmd\", \"npm.ps1\"]).\n    map { |dir, file| File.expand_path(file, dir) }.\n    find { |file| File.executable?(file) }\n\n  if npm\n    command = ARGV.empty? ? [\"install\"] : ARGV\n    exec npm, *command\n  else\n    $stderr.puts \"npm executable was not detected in the system.\"\n    $stderr.puts \"Download npm by installing Node.js from https://nodejs.org/\"\n    exit 1\n  end\nend\n"
  },
  {
    "path": "bin/rails",
    "content": "#!/usr/bin/env ruby\nAPP_PATH = File.expand_path(\"../config/application\", __dir__)\nrequire_relative \"../config/boot\"\nrequire \"rails/commands\"\n"
  },
  {
    "path": "bin/rake",
    "content": "#!/usr/bin/env ruby\nrequire_relative \"../config/boot\"\nrequire \"rake\"\nRake.application.run\n"
  },
  {
    "path": "bin/rspec",
    "content": "#!/usr/bin/env ruby\nbegin\n  load File.expand_path('../spring', __FILE__)\nrescue LoadError => e\n  raise unless e.message.include?('spring')\nend\nrequire 'bundler/setup'\nload Gem.bin_path('rspec-core', 'rspec')\n"
  },
  {
    "path": "bin/setup",
    "content": "#!/usr/bin/env ruby\nrequire 'fileutils'\n\n# path to your application root.\nAPP_ROOT = File.expand_path('..', __dir__)\n\ndef system!(*args)\n  system(*args, exception: true) || abort(\"\\n== Command #{args} failed ==\")\nend\n\nFileUtils.chdir APP_ROOT do\n  # This script is a way to set up or update your development environment automatically.\n  # This script is idempotent, so that you can run it at any time and get an expectable outcome.\n  # Add necessary setup steps to this file.\n  expected_ruby_version = `cat .ruby-version`.chomp\n  current_ruby_version = `ruby -v`.chomp\n  unless current_ruby_version.include?(expected_ruby_version)\n    puts \"Ruby version must be #{expected_ruby_version}. You are on #{current_ruby_version}\"\n    exit\n  end\n\n  puts \"\\n== Installing dependencies ==\"\n  system! 'gem install foreman'\n  system! 'gem install bundler --conservative'\n  system!('bundle update --bundler --verbose')\n  system!('bundle check') || system!('bundle install')\n\n  # puts '\\n== Copying sample files =='\n  # unless File.exist?('config/database.yml')\n  #   FileUtils.cp 'config/database.yml.sample', 'config/database.yml'\n  # end\n\n  unless File.exist?('.env')\n    puts \"\\n== Setup .env file from .env.example ==\"\n    system!('cp .env.example .env')\n  end\n\n  puts \"\\n== Preparing database ==\"\n  puts '⚠️  If you use docker to run postgres, make sure your database is running ⚠️'\n  system! 'bin/rails db:reset'\n  system! 'bin/rails db:test:prepare'\n\n  puts \"\\n== Removing old logs and tempfiles ==\"\n  system! 'bin/rails log:clear tmp:clear'\n\n  puts \"\\n== Restarting application server ==\"\n  system! 'bin/rails restart'\n\n  puts \"\\n== Running post-deployment tasks ==\"\n  system! 'bin/rake after_party:run'\n\n  puts \"\\n== Installing npm packages ==\"\n  system!('npm ci') || abort('install npm and try again')\n\n  puts \"\\n== Building assets ==\"\n  system!('npm run build')\n  system!('npm run build:css')\n\n  puts \"\\n== Done ==\"\nend\n"
  },
  {
    "path": "bin/spring",
    "content": "#!/usr/bin/env ruby\nif !defined?(Spring) && [nil, \"development\", \"test\"].include?(ENV[\"RAILS_ENV\"])\n  gem \"bundler\"\n  require \"bundler\"\n\n  # Load Spring without loading other gems in the Gemfile, for speed.\n  Bundler.locked_gems&.specs&.find { |spec| spec.name == \"spring\" }&.tap do |spring|\n    Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path\n    gem \"spring\", spring.version\n    require \"spring/binstub\"\n  rescue Gem::LoadError\n    # Ignore when Spring is not installed.\n  end\nend\n"
  },
  {
    "path": "bin/update",
    "content": "#!/usr/bin/env ruby\nrequire 'pathname'\nrequire 'fileutils'\ninclude FileUtils\n\n# path to your application root.\nAPP_ROOT = Pathname.new File.expand_path('../../', __FILE__)\n\ndef system!(*args)\n  system(*args) || abort(\"\\n== Command #{args} failed ==\")\nend\n\nchdir APP_ROOT do\n  # This script is a way to update your development environment automatically.\n  # Add necessary update steps to this file.\n\n  puts '== Installing dependencies =='\n  system! 'gem install bundler --conservative'\n  system('bundle check') || system!('bundle install')\n\n  puts 'Updating npm'\n  system('npm install') || abort(\"Install npm and try again\")\n\n\n  puts \"\\n== Updating database ==\"\n  system! 'bin/rails db:migrate'\n\n  puts \"\\n== Running post-deployment tasks ==\"\n  system! 'bin/rake after_party:run'\n\n  puts \"\\n== Removing old logs and tempfiles ==\"\n  system! 'bin/rails log:clear tmp:clear'\n\n  puts \"\\n== Restarting application server ==\"\n  system! 'bin/rails restart'\n\n  puts \"\\n== Building assets ==\"\n  system('npm run build') || abort(\"Failed to build assets. Ensure npm is installed and try again.\")\n  system('npm run build:css') || abort(\"Failed to build CSS assets. Ensure npm is installed and try again.\")\n\n  puts \"\\n== Done ==\"\nend\n"
  },
  {
    "path": "code-of-conduct.md",
    "content": "[View our Code of Conduct here!](./doc/code-of-conduct.md)\n"
  },
  {
    "path": "config/application.rb",
    "content": "require_relative \"boot\"\n\nrequire \"rails/all\"\n\n# Require the gems listed in Gemfile, including any gems\n# you've limited to :test, :development, or :production.\nBundler.require(*Rails.groups)\n\nmodule Casa\n  class Application < Rails::Application\n    # Initialize configuration defaults for originally generated Rails version.\n    config.load_defaults 7.2\n\n    # Please, add to the `ignore` list any other `lib` subdirectories that do\n    # not contain `.rb` files, or that should not be reloaded or eager loaded.\n    # Common ones are `templates`, `generators`, or `middleware`, for example.\n    config.autoload_lib(ignore: %w[assets generators tasks mailers])\n\n    # Configuration for the application, engines, and railties goes here.\n    #\n    # These settings can be overridden in specific environments using the files\n    # in config/environments, which are processed later.\n    #\n    # config.time_zone = \"Central Time (US & Canada)\"\n    # config.eager_load_paths << Rails.root.join(\"extras\")\n\n    config.action_mailer.preview_paths << (defined?(Rails.root) ? Rails.root.join(\"lib/mailers/previews\") : nil)\n\n    config.eager_load_paths << Rails.root.join(\"app/lib/importers\")\n    config.assets.paths << Rails.root.join(\"app/assets/webfonts\")\n    config.active_storage.variant_processor = :mini_magick\n    config.active_storage.content_types_to_serve_as_binary.delete(\"image/svg+xml\")\n    config.serve_static_assets = true\n\n    # to use ViewComponent previews\n    config.view_component.previews.paths << \"#{Rails.root.join(\"spec/components/previews\")}\"\n  end\nend\n"
  },
  {
    "path": "config/boot.rb",
    "content": "ENV[\"BUNDLE_GEMFILE\"] ||= File.expand_path(\"../Gemfile\", __dir__)\n\nrequire \"bundler/setup\" # Set up gems listed in the Gemfile.\n"
  },
  {
    "path": "config/brakeman.ignore",
    "content": "{\n  \"ignored_warnings\": [\n    {\n      \"warning_type\": \"Dynamic Render Path\",\n      \"warning_code\": 15,\n      \"fingerprint\": \"82ef033042422190ef49507207d51ed6ccd9593483630925baf0bf6c5e65033e\",\n      \"check_name\": \"Render\",\n      \"message\": \"Render path contains parameter value\",\n      \"file\": \"app/controllers/static_controller.rb\",\n      \"line\": 21,\n      \"link\": \"https://brakemanscanner.org/docs/warning_types/dynamic_render_path/\",\n      \"code\": \"render(template => \\\"static/#{params[:name]}\\\", {})\",\n      \"render_path\": null,\n      \"location\": {\n        \"type\": \"method\",\n        \"class\": \"StaticController\",\n        \"method\": \"page\"\n      },\n      \"user_input\": \"params[:name]\",\n      \"confidence\": \"Medium\",\n      \"cwe_id\": [\n        22\n      ],\n      \"note\": \"\"\n    },\n    {\n      \"warning_type\": \"SQL Injection\",\n      \"warning_code\": 0,\n      \"fingerprint\": \"b75b292df5ec0c3d4d4f307a8ff2a18caecd456a9b3c9c62bb59d7cf3b67a562\",\n      \"check_name\": \"SQL\",\n      \"message\": \"Possible SQL injection\",\n      \"file\": \"app/datatables/supervisor_datatable.rb\",\n      \"line\": 45,\n      \"link\": \"https://brakemanscanner.org/docs/warning_types/sql_injection/\",\n      \"code\": \"Arel.sql(\\\"COALESCE(users.display_name, users.email) #{order_direction}\\\")\",\n      \"render_path\": null,\n      \"location\": {\n        \"type\": \"method\",\n        \"class\": \"SupervisorDatatable\",\n        \"method\": \"order_clause\"\n      },\n      \"user_input\": \"order_direction\",\n      \"confidence\": \"Medium\",\n      \"cwe_id\": [\n        89\n      ],\n      \"note\": \"We have a filter for this variable here: app/datatables/application_datatable.rbapp/datatables/application_datatable.rb:72\"\n    }\n  ],\n  \"updated\": \"2023-01-17 23:08:51 -0500\",\n  \"brakeman_version\": \"5.4.0\"\n}\n"
  },
  {
    "path": "config/cable.yml",
    "content": "development:\n  adapter: async\n\ntest:\n  adapter: test\n\nproduction:\n  adapter: redis\n  url: <%= ENV.fetch(\"REDIS_URL\") { \"redis://localhost:6379/1\" } %>\n  channel_prefix: casa_production\n"
  },
  {
    "path": "config/credentials/development.key",
    "content": "f66bde702577ef605d3e69abc69798f8"
  },
  {
    "path": "config/credentials/development.yml.enc",
    "content": "aewvdbZoQz8v7s3UlJ/+XOIrxpj1/nP2/dA7FkLGvTgmu8lZrnyecC19sDE6bcZN4XsnIqDomjSg/CL8TefHKXOsaoNNKmW8YPVfoH8AmlqXxvJduiZNuXlOcf7SR01E7E0r1VIdRga6g9KtOHBbgtc6hQyOs/2ajSxbD3gY5IFWnWNHIqMEWMUMy/PXtSSxUr+FdNCgdod9Rx0EEiecfEz1tMBP/V69dRwSrM5yfTeogkUPpOqReFisTbn9f0yolmNhhxo7nPoPzyeEcGHl4+maS1GHa6uYQ2n2d2t34FmhcDttI+rV7ITU9LmuwVcjgCE9fPxMUZ9bX2UBUEHialBZ8S+izXyBAKGTvbQw+/Wk9KNT98Tl3Gg=--BRmMgMTOgyAZUyw4--2OyLty/a3xH0OjlI0sf9Yw=="
  },
  {
    "path": "config/credentials/production.yml.enc",
    "content": "Y85/mpXDwuasGg7odbPklWamaevc8nlJJExz5YTnGBR/S5wCGTkkH7hwUBPINNTkYXpe3N1QN2ILq+NuGqTJhgtpy6Glwnk743UlxsFUChUQmSlccJfML1YQWKx3R1VSAFWjOoHiV9vnA0RcKf9tIQ5FWY5S8/IMn4SlpRbhU0zJfkHAb3Hbx5juTWmOWHh4BD+AmKYqu9lpVCCwQkitRJX/8AwLUBprbcaHm0nh9u4PL/Q/Fj0/UadHF2RCWDnpRxUAygqgMpCBxqZwpHzN3cRrATL3aDYFZt0CZzpkpnHBKQcuSs1K/oki06Jas+Z585iO00S6r4NRJtkPVkznFqu0DJ8JkUKriLDXOwbvcEHOHtCS0t2hCxYLPZvSg51qBsZbgS5No3+cfXPeGjIecq+h1c7zpg70PNieO6290IM/jpNYA9ZEt9nPXwUodAauMkvcocXlOxX4mOaYsL7fMiXhgu/JzqwK64maSXpCBaEEPcOwIkaKhTwn+w+iABnTYnhZeD2t3TMoFpzkI83w7cg9Mbetm1N9faX43dsYXRW93tFwSkJtF4b//CIxOyG9xFxruGNwnBqy8+IuoaSyBVF3WecnWjj+7TGSgmgl7FmiaUSjDDJkh27K8QDHbpevQM/Rc5U=--8GtVk0JHu3Dkz4jI--/FAbx4kRZsAKWLg9/+IWbw=="
  },
  {
    "path": "config/credentials/qa.key",
    "content": "f66bde702577ef605d3e69abc69798f8"
  },
  {
    "path": "config/credentials/qa.yml.enc",
    "content": "kCD5s9jBDZZfXdKirYJD95LfwF3xeWi2E+OGS9x9/8EsI6WMzl+jNUjuBQt/fUdO5R5pCHXXPCYPzsGnDyTKJJczqszDXDsG2xS5eSImw9O7BGmNDh7W1sQ5kMmjZPUUu9GaCcAjmnvh0ImXM2X+Lo/5X3NeAZ7FllFmUd3CaBD5J3N3mbXlJgVGRylabaKABqrZqbRnZK5QDOcjwxYspsnu3m9M2qx5B7m2RnpKdytDpxbRtGSj34cZmH0C+rGOCQUm9rSR5wrQVmmJ5DdElYAnM7lo6bUTf2W5wbd51TsrEATPCHx1iK89ot/alUIjmHcoMlUstLnSX8VQzQeIluqa7a7o2IN/p++vknWELVlYROn67wiRTrLPXeDy+NkhiYkhQCBl3AIL8qs92tKX9xwvM75q5pGwsxQneGM5IjOSHUnEmw6j0f6B0R1oksOGt/xg9VP73ANP3zxqNTnbzRNoqO9wVVju0jJVt8KGoXVzdQx2qQSU5ZqUulJ7v88Xjro7Ew6mSY5gh4itzzsVIF7kAEJM20eh2WWxNEDMZyzvKDD34XWxOb8nks9MF0yCJzWBIog=--m9KJMwI1N3qSf0ry--d64JLH6L2BJNNIW2DdMR6w=="
  },
  {
    "path": "config/credentials/test.key",
    "content": "5dfb1ed8adf6c5264039101150e400ec"
  },
  {
    "path": "config/credentials/test.yml.enc",
    "content": "zsvjeQzFV0vM7h3jsx7RUyA3WTQsEYWvEMreEvVbi4L0HN7sDNrP7kay2FZS5VEwrW5mJZdnu63BXa8fK/h1agvaaiOO9FhDXfyK+VT86TAfsLa1gsBK9mHjWSdXCJE9TZj9OjOtR3R/qHOI+2uPUnysh7Om4a0ckiu4Jwex3OcbgCYj2+G2JQtwHkWhlyBthGxLjuDDFfx+qxkJWkN7V9FhN0FkkPaflyj4FjR9BUf3/CB8pvHXqJ1lmxVScYsyhh50mc+CKyVptpqbLi9Jou3SiUePREX03ynV0KPR+7mT3FH9gCj4QyzzS1t3JOUfrgqeVFAzdV1TW01olinOyG2aMrZn1aA7GWfDeIr/GwnaPfUMmZNj4RQ=--KmPWCR7xHr6jUSx5--1L2S0bUzD2Bc+JFAHX7xKg=="
  },
  {
    "path": "config/database.yml",
    "content": "default: &default\n  adapter: postgresql\n  encoding: unicode\n  username: <%= ENV.fetch(\"POSTGRES_USER\") { } %>\n  pool: <%= ENV.fetch(\"RAILS_MAX_THREADS\") { 3 } %>\n  port: <%= ENV.fetch(\"POSTGRES_PORT\", \"5432\") %>\n  password: <%= ENV.fetch(\"POSTGRES_PASSWORD\") { 'password' } %>\n\n\ndevelopment:\n  <<: *default\n  database: casa_development\n  host: <%= ENV.fetch(\"DATABASE_HOST\") { } %>\n\ntest:\n  <<: *default\n  database: casa_test<%= ENV[\"TEST_ENV_NUMBER\"] %>\n  host: <%= ENV.fetch(\"DATABASE_HOST\") { } %>\n\nproduction:\n  <<: *default\n  database: casa_production\n  username: casa\n  password: <%= ENV['CASA_DATABASE_PASSWORD'] %>\n"
  },
  {
    "path": "config/docker.env",
    "content": "DOCKER=true\nDATABASE_HOST=database\n\nPOSTGRES_USER=postgres\nPOSTGRES_HOST_AUTH_METHOD=trust\n"
  },
  {
    "path": "config/environment.rb",
    "content": "# Load the Rails application.\nrequire_relative \"application\"\n\n# Initialize the Rails application.\nRails.application.initialize!\n"
  },
  {
    "path": "config/environments/development.rb",
    "content": "require \"active_support/core_ext/integer/time\"\n\nRails.application.configure do\n  # Settings specified here will take precedence over those in config/application.rb.\n\n  # In the development environment your application's code is reloaded any time\n  # it changes. This slows down response time but is perfect for development\n  # since you don't have to restart the web server when you make code changes.\n  config.enable_reloading = true\n\n  # Do not eager load code on boot.\n  config.eager_load = false\n\n  # Show full error reports.\n  config.consider_all_requests_local = true\n\n  # Enable server timing\n  config.server_timing = true\n\n  # Enable/disable caching. By default caching is disabled.\n  # Run rails dev:cache to toggle caching.\n  if Rails.root.join(\"tmp/caching-dev.txt\").exist?\n    config.action_controller.perform_caching = true\n    config.action_controller.enable_fragment_cache_logging = true\n\n    config.cache_store = :memory_store\n    config.public_file_server.headers = {\n      \"Cache-Control\" => \"public, max-age=#{2.days.to_i}\"\n    }\n  else\n    config.action_controller.perform_caching = false\n\n    config.cache_store = :null_store\n  end\n\n  # Store uploaded files on the local file system (see config/storage.yml for options).\n  config.active_storage.service = :local\n\n  # Don't care if the mailer can't send.\n  config.action_mailer.default_url_options = {host: \"localhost\", port: 3000}\n  config.action_mailer.delivery_method = :letter_opener\n  config.action_mailer.perform_deliveries = true\n  config.action_mailer.perform_caching = false\n  config.action_mailer.raise_delivery_errors = false\n\n  # Print deprecation notices to the Rails logger.\n  config.active_support.deprecation = :log\n\n  # Raise exceptions for disallowed deprecations.\n  config.active_support.disallowed_deprecation = :raise\n\n  # Tell Active Support which deprecation messages to disallow.\n  config.active_support.disallowed_deprecation_warnings = []\n\n  # Raise an error on page load if there are pending migrations.\n  config.active_record.migration_error = :page_load\n\n  # Highlight code that triggered database queries in logs.\n  config.active_record.verbose_query_logs = true\n\n  # Highlight code that enqueued background job in logs.\n  config.active_job.verbose_enqueue_logs = true\n\n  # Suppress logger output for asset requests.\n  config.assets.quiet = true\n  config.assets.digest = false\n\n  # Raises error for missing translations.\n  # config.i18n.raise_on_missing_translations = true\n\n  # Prosopite N+1 query detection\n  config.prosopite_enabled = true\n  config.prosopite_min_n_queries = 5  # More lenient for development\n\n  # Annotate rendered view with file names.\n  config.action_view.annotate_rendered_view_with_filenames = true\n\n  # Uncomment if you wish to allow Action Cable access from any origin.\n  # config.action_cable.disable_request_forgery_protection = true\n\n  config.hosts << ENV[\"DEV_HOSTS\"]\n  config.hosts << \".app.github.dev\"\n\n  if ENV[\"CODESPACES\"] === \"true\"\n    config.action_controller.forgery_protection_origin_check = false\n  end\n\n  # Raise error when a before_action's only/except options reference missing actions\n  config.action_controller.raise_on_missing_callback_actions = false\nend\n"
  },
  {
    "path": "config/environments/production.rb",
    "content": "require \"active_support/core_ext/integer/time\"\n\nRails.application.configure do\n  # Settings specified here will take precedence over those in config/application.rb.\n\n  config.action_mailer.default_url_options = {host: ENV[\"DOMAIN\"]}\n  config.action_mailer.raise_delivery_errors = true\n  config.action_mailer.show_previews = ENV[\"APP_ENVIRONMENT\"] != \"production\"\n  # Do not send emails in staging or qa\n  config.action_mailer.perform_deliveries = ENV[\"APP_ENVIRONMENT\"] == \"production\"\n  config.action_mailer.delivery_method = :smtp\n  config.action_mailer.smtp_settings = {\n    address: \"smtp-relay.sendinblue.com\",\n    port: 587,\n    user_name: ENV[\"SENDINBLUE_EMAIL\"],\n    password: ENV[\"SENDINBLUE_PASSWORD\"],\n    authentication: \"login\",\n    enable_starttls_auto: true\n  }\n  # Code is not reloaded between requests.\n  config.enable_reloading = false\n\n  # Eager load code on boot. This eager loads most of Rails and\n  # your application in memory, allowing both threaded web servers\n  # and those relying on copy on write to perform better.\n  # Rake tasks automatically ignore this option for performance.\n  config.eager_load = true\n\n  # Full error reports are disabled and caching is turned on.\n  config.consider_all_requests_local = false\n  config.action_controller.perform_caching = true\n\n  # Ensures that a master key has been made available in ENV[\"RAILS_MASTER_KEY\"], config/master.key, or an environment\n  # key such as config/credentials/production.key. This key is used to decrypt credentials (and other encrypted files).\n  config.require_master_key = true\n\n  # Enable static file serving from the `/public` folder (turn off if using NGINX/Apache for it).\n  config.public_file_server.enabled = ENV[\"RAILS_SERVE_STATIC_FILES\"].present?\n\n  # Compress CSS using a preprocessor.\n  # config.assets.css_compressor = :sass\n\n  # Do not fallback to assets pipeline if a precompiled asset is missed.\n  # config.assets.compile = false\n\n  # Enable serving of images, stylesheets, and JavaScripts from an asset server.\n  # config.asset_host = \"http://assets.example.com\"\n\n  # Specifies the header that your server uses for sending files.\n  # config.action_dispatch.x_sendfile_header = \"X-Sendfile\" # for Apache\n  # config.action_dispatch.x_sendfile_header = \"X-Accel-Redirect\" # for NGINX\n\n  # Store uploaded files on the local file system (see config/storage.yml for options).\n  config.active_storage.service = :microsoft\n\n  # Mount Action Cable outside main process or domain.\n  # config.action_cable.mount_path = nil\n  # config.action_cable.url = \"wss://example.com/cable\"\n  # config.action_cable.allowed_request_origins = [ \"http://example.com\", /http:\\/\\/example.*/ ]\n\n  # Assume all access to the app is happening through a SSL-terminating reverse proxy.\n  # Can be used together with config.force_ssl for Strict-Transport-Security and secure cookies.\n  # config.assume_ssl = true\n\n  # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.\n  config.force_ssl = true\n\n  # Log to STDOUT by default\n  config.logger = ActiveSupport::Logger.new($stdout)\n    .tap { |logger| logger.formatter = ::Logger::Formatter.new }\n    .then { |logger| ActiveSupport::TaggedLogging.new(logger) }\n\n  # Prepend all log lines with the following tags.\n  config.log_tags = [:request_id]\n\n  # Info include generic and useful information about system operation, but avoids logging too much\n  # information to avoid inadvertent exposure of personally identifiable information (PII). If you\n  # want to log everything, set the level to \"debug\".\n  config.log_level = :info\n\n  # Use a different cache store in production.\n  # config.cache_store = :mem_cache_store\n\n  # Use a real queuing backend for Active Job (and separate queues per environment).\n  config.active_job.queue_adapter = :delayed_job # production.rb\n  # config.active_job.queue_name_prefix = \"casa_production\"\n\n  config.action_mailer.perform_caching = false\n\n  # Ignore bad email addresses and do not raise email delivery errors.\n  # Set this to true and configure the email server for immediate delivery to raise delivery errors.\n  # config.action_mailer.raise_delivery_errors = false\n\n  # Enable locale fallbacks for I18n (makes lookups for any locale fall back to\n  # the I18n.default_locale when a translation cannot be found).\n  config.i18n.fallbacks = true\n\n  # Don't log any deprecations.\n  config.active_support.report_deprecations = false\n\n  # Do not dump schema after migrations.\n  config.active_record.dump_schema_after_migration = false\n\n  # Enable DNS rebinding protection and other `Host` header attacks.\n  # config.hosts = [\n  #   \"example.com\",     # Allow requests from example.com\n  #   /.*\\.example\\.com/ # Allow requests from subdomains like `www.example.com`\n  # ]\n  # Skip DNS rebinding protection for the default health check endpoint.\n  # config.host_authorization = { exclude: ->(request) { request.path == \"/up\" } }\nend\n"
  },
  {
    "path": "config/environments/test.rb",
    "content": "require \"active_support/core_ext/integer/time\"\n\n# The test environment is used exclusively to run your application's\n# test suite. You never need to work with it otherwise. Remember that\n# your test database is \"scratch space\" for the test suite and is wiped\n# and recreated between test runs. Don't rely on the data there!\n\nRails.application.configure do\n  # Settings specified here will take precedence over those in config/application.rb.\n  config.action_mailer.default_url_options = {host: \"localhost\", port: 3000} # for devise authentication\n  # While tests run files are not watched, reloading is not necessary.\n  # Turn false under Spring and add config.action_view.cache_template_loading = true.\n  config.action_view.cache_template_loading = true\n\n  # Eager loading loads your entire application. When running a single test locally,\n  # this is usually not necessary, and can slow down your test suite. However, it's\n  # recommended that you enable it in continuous integration systems to ensure eager\n  # loading is working properly before deploying your code.\n  config.eager_load = ENV[\"CI\"].present?\n  # cache classes on CI, but enable reloading for local work (bin/rspec)\n  config.enable_reloading = ENV[\"CI\"].blank?\n  # Configure public file server for tests with Cache-Control for performance.\n  config.public_file_server.enabled = true\n  config.public_file_server.headers = {\n    \"Cache-Control\" => \"public, max-age=#{1.hour.to_i}\"\n  }\n\n  # Show full error reports and disable caching.\n  config.consider_all_requests_local = true\n  config.action_controller.perform_caching = false\n  # config.cache_store = :null_store\n\n  # Raise exceptions instead of rendering exception templates.\n  config.action_dispatch.show_exceptions = :none\n\n  # Disable request forgery protection in test environment.\n  config.action_controller.allow_forgery_protection = false\n\n  # Store uploaded files on the local file system in a temporary directory.\n  config.active_storage.service = :test\n\n  config.action_mailer.perform_caching = false\n\n  # Tell Action Mailer not to deliver emails to the real world.\n  # The :test delivery method accumulates sent emails in the\n  # ActionMailer::Base.deliveries array.\n  config.action_mailer.delivery_method = :test\n\n  # Print deprecation notices to the stderr.\n  config.active_support.deprecation = :stderr\n\n  # Rack Attack configuration\n  # Set IP_BLOCKLIST for testing. Can't stub in spec since environment variable\n  # gets read during application initialization.\n  ENV[\"IP_BLOCKLIST\"] = \"4.5.6.7, 9.8.7.6,100.101.102.103\"\n\n  # Raise exceptions for disallowed deprecations.\n  config.active_support.disallowed_deprecation = :raise\n\n  # Tell Active Support which deprecation messages to disallow.\n  config.active_support.disallowed_deprecation_warnings = []\n\n  # Raises error for missing translations.\n  config.i18n.raise_on_missing_translations = true\n\n  # Annotate rendered view with file names.\n  # config.action_view.annotate_rendered_view_with_filenames = true\n\n  # Raise error when a before_action's only/except options reference missing actions\n  config.action_controller.raise_on_missing_callback_actions = false\n\n  # https://github.com/rails/rails/issues/48468\n  config.active_job.queue_adapter = :test\n\n  config.secret_key_base = ENV[\"SECRET_KEY_BASE\"] || \"dummy_test_secret_key\"\nend\n"
  },
  {
    "path": "config/initializers/after_party.rb",
    "content": "AfterParty.setup do |config|\n  # ==> ORM configuration\n  # Load and configure the ORM. Supports :active_record (default) and\n  # :mongoid (bson_ext recommended) by default. Other ORMs may be\n  # available as additional gems.\n  require \"after_party/active_record\"\nend\n"
  },
  {
    "path": "config/initializers/all_casa_admin_access.rb",
    "content": "class CanAccessFlipperUI\n  def self.matches?(request)\n    session = request.env[\"warden\"].raw_session.to_h\n    session[\"warden.user.all_casa_admin.session\"].present?\n  end\nend\n"
  },
  {
    "path": "config/initializers/application_controller_renderer.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# ActiveSupport::Reloader.to_prepare do\n#   ApplicationController.renderer.defaults.merge!(\n#     http_host: 'example.org',\n#     https: false\n#   )\n# end\n"
  },
  {
    "path": "config/initializers/assets.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Version of your assets, change this if you want to expire all your assets.\nRails.application.config.assets.version = \"1.0\"\n\n# Add additional assets to the asset load path.\n# Rails.application.config.assets.paths << Emoji.images_path\n\n# Precompile additional assets.\n# application.js, application.css, and all non-JS/CSS in the app/assets\n# folder are already added.\n# Rails.application.config.assets.precompile += %w( admin.js admin.css )\n"
  },
  {
    "path": "config/initializers/authtrail.rb",
    "content": "# set to true for geocoding (and add the geocoder gem to your Gemfile)\n# we recommend configuring local geocoding as well\n# see https://github.com/ankane/authtrail#geocoding\nAuthTrail.geocode = false\n\n# add or modify data\n# AuthTrail.transform_method = lambda do |data, request|\n#   data[:request_id] = request.request_id\n# end\n\n# exclude certain attempts from tracking\n# AuthTrail.exclude_method = lambda do |data|\n#   data[:identity] == \"capybara@example.org\"\n# end\n"
  },
  {
    "path": "config/initializers/backtrace_silencers.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.\n# Rails.backtrace_cleaner.add_silencer { |line| /my_noisy_library/.match?(line) }\n\n# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code\n# by setting BACKTRACE=1 before calling your invocation, like \"BACKTRACE=1 ./bin/rails runner 'MyClass.perform'\".\n# Rails.backtrace_cleaner.remove_silencers! if ENV[\"BACKTRACE\"]\n"
  },
  {
    "path": "config/initializers/blueprinter.rb",
    "content": "require \"oj\" # you can skip this if OJ has already been required.\n\nBlueprinter.configure do |config|\n  config.generator = Oj # default is JSON\nend\n"
  },
  {
    "path": "config/initializers/bugsnag.rb",
    "content": "if ENV[\"BUGSNAG_API_KEY\"].present?\n  Bugsnag.configure do |config|\n    config.api_key = ENV[\"BUGSNAG_API_KEY\"]\n    config.ignore_classes << ActiveRecord::RecordNotFound\n    config.release_stage = ENV[\"HEROKU_APP_NAME\"] || ENV[\"APP_ENVIRONMENT\"]\n\n    callback = proc do |event|\n      event.set_user(current_user&.id, current_user&.email) if defined?(current_user)\n    end\n\n    config.add_on_error(callback)\n  end\nelse\n  Bugsnag.configuration.logger = ::Logger.new(\"/dev/null\")\nend\n"
  },
  {
    "path": "config/initializers/content_security_policy.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Define an application-wide content security policy.\n# See the Securing Rails Applications Guide for more information:\n# https://guides.rubyonrails.org/security.html#content-security-policy-header\n\n# Rails.application.configure do\n#   config.content_security_policy do |policy|\n#     policy.default_src :self, :https\n#     policy.font_src    :self, :https, :data\n#     policy.img_src     :self, :https, :data\n#     policy.object_src  :none\n#     policy.script_src  :self, :https\n#     policy.style_src   :self, :https\n#     # Specify URI for violation reports\n#     # policy.report_uri \"/csp-violation-report-endpoint\"\n#   end\n#\n#   # Generate session nonces for permitted importmap, inline scripts, and inline styles.\n#   config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s }\n#   config.content_security_policy_nonce_directives = %w(script-src style-src)\n#\n#   # Report violations without enforcing the policy.\n#   # config.content_security_policy_report_only = true\n# end\n"
  },
  {
    "path": "config/initializers/cookies_serializer.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Specify a serializer for the signed and encrypted cookie jars.\n# Valid options are :json, :marshal, and :hybrid.\nRails.application.config.action_dispatch.cookies_serializer = :json\n"
  },
  {
    "path": "config/initializers/cors.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Avoid CORS issues when API is called from the frontend app.\n# Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin Ajax requests.\n\n# Read more: https://github.com/cyu/rack-cors\n\nRails.application.config.middleware.insert_before 0, Rack::Cors do\n  allow do\n    origins \"*\" # make sure to change to domain name of frontend\n    resource \"/api/v1/*\", headers: :any, methods: [:get, :post, :patch, :put, :delete, :options, :head]\n  end\nend\n"
  },
  {
    "path": "config/initializers/date_formats.rb",
    "content": "Date::DATE_FORMATS[:short_ordinal] = ->(date) { date.strftime(\"%B #{date.day.ordinalize}\") }\nDate::DATE_FORMATS[:slashes] = \"%Y/%m/%d\"\n"
  },
  {
    "path": "config/initializers/devise.rb",
    "content": "# frozen_string_literal: true\n\n# Use this hook to configure devise mailer, warden hooks and so forth.\n# Many of these configuration options can be set straight in your model.\nDevise.setup do |config|\n  # The secret key used by Devise. Devise uses this key to generate\n  # random tokens. Changing this key will render invalid all existing\n  # confirmation, reset password and unlock tokens in the database.\n  # Devise will use the `secret_key_base` as its `secret_key`\n  # by default. You can change it below and use your own secret key.\n  # config.secret_key = 'a42b150b56c158d6e064aa257c8434390c921811007f42c5aa16c7455b78e3777087c5a4bcf18bfb0886fdd9296dec34eaa02fc0b47bed58417347eba971ee2d'\n\n  # Fixes rspec triggering Rails.application.secrets deprecation warning for Rails 7.1.0 upgrade\n  config.secret_key = Rails.application.secret_key_base\n\n  # ==> Controller configuration\n  # Configure the parent class to the devise controllers.\n  # config.parent_controller = 'DeviseController'\n\n  # ==> Mailer Configuration\n  # Configure the e-mail address which will be shown in Devise::Mailer,\n  # note that it will be overwritten if you use your own mailer class\n  # with default \"from\" parameter.\n  config.mailer_sender = \"no-reply@#{ENV[\"DOMAIN\"]}\"\n\n  # Configure the class responsible to send e-mails.\n  # config.mailer = 'Devise::Mailer'\n\n  # Configure the parent class responsible to send e-mails.\n  # config.parent_mailer = 'ActionMailer::Base'\n\n  # ==> ORM configuration\n  # Load and configure the ORM. Supports :active_record (default) and\n  # :mongoid (bson_ext recommended) by default. Other ORMs may be\n  # available as additional gems.\n  require \"devise/orm/active_record\"\n\n  # ==> Configuration for any authentication mechanism\n  # Configure which keys are used when authenticating a user. The default is\n  # just :email. You can configure it to use [:username, :subdomain], so for\n  # authenticating a user, both parameters are required. Remember that those\n  # parameters are used only when authenticating and not when retrieving from\n  # session. If you need permissions, you should implement that in a before filter.\n  # You can also supply a hash where the value is a boolean determining whether\n  # or not authentication should be aborted when the value is not present.\n  # config.authentication_keys = [:email]\n\n  # Configure parameters from the request object used for authentication. Each entry\n  # given should be a request method and it will automatically be passed to the\n  # find_for_authentication method and considered in your model lookup. For instance,\n  # if you set :request_keys to [:subdomain], :subdomain will be used on authentication.\n  # The same considerations mentioned for authentication_keys also apply to request_keys.\n  # config.request_keys = []\n\n  # Configure which authentication keys should be case-insensitive.\n  # These keys will be downcased upon creating or modifying a user and when used\n  # to authenticate or find a user. Default is :email.\n  config.case_insensitive_keys = [:email]\n\n  # Configure which authentication keys should have whitespace stripped.\n  # These keys will have whitespace before and after removed upon creating or\n  # modifying a user and when used to authenticate or find a user. Default is :email.\n  config.strip_whitespace_keys = [:email]\n\n  # Tell if authentication through request.params is enabled. True by default.\n  # It can be set to an array that will enable params authentication only for the\n  # given strategies, for example, `config.params_authenticatable = [:database]` will\n  # enable it only for database (email + password) authentication.\n  # config.params_authenticatable = true\n\n  # Tell if authentication through HTTP Auth is enabled. False by default.\n  # It can be set to an array that will enable http authentication only for the\n  # given strategies, for example, `config.http_authenticatable = [:database]` will\n  # enable it only for database authentication. The supported strategies are:\n  # :database      = Support basic authentication with authentication key + password\n  # config.http_authenticatable = false\n\n  # If 401 status code should be returned for AJAX requests. True by default.\n  # config.http_authenticatable_on_xhr = true\n\n  # The realm used in Http Basic Authentication. 'Application' by default.\n  # config.http_authentication_realm = 'Application'\n\n  # It will change confirmation, password recovery and other workflows\n  # to behave the same regardless if the e-mail provided was right or wrong.\n  # Does not affect registerable.\n  # config.paranoid = true\n\n  # By default Devise will store the user in session. You can skip storage for\n  # particular strategies by setting this option.\n  # Notice that if you are skipping storage for all authentication paths, you\n  # may want to disable generating routes to Devise's sessions controller by\n  # passing skip: :sessions to `devise_for` in your config/routes.rb\n  config.skip_session_storage = [:http_auth]\n\n  # By default, Devise cleans up the CSRF token on authentication to\n  # avoid CSRF token fixation attacks. This means that, when using AJAX\n  # requests for sign in and sign up, you need to get a new CSRF token\n  # from the server. You can disable this option at your own risk.\n  # config.clean_up_csrf_token_on_authentication = true\n\n  # When false, Devise will not attempt to reload routes on eager load.\n  # This can reduce the time taken to boot the app but if your application\n  # requires the Devise mappings to be loaded during boot time the application\n  # won't boot properly.\n  # config.reload_routes = true\n\n  # ==> Configuration for :database_authenticatable\n  # For bcrypt, this is the cost for hashing the password and defaults to 11. If\n  # using other algorithms, it sets how many times you want the password to be hashed.\n  #\n  # Limiting the stretches to just one in testing will increase the performance of\n  # your test suite dramatically. However, it is STRONGLY RECOMMENDED to not use\n  # a value less than 10 in other environments. Note that, for bcrypt (the default\n  # algorithm), the cost increases exponentially with the number of stretches (e.g.\n  # a value of 20 is already extremely slow: approx. 60 seconds for 1 calculation).\n  config.stretches = Rails.env.test? ? 1 : 11\n\n  # Set up a pepper to generate the hashed password.\n  # config.pepper = 'cd6eaac91bb902fefeb87724dc41ea5023881a0b3044bbf4c23178147d2a8d883265a58105ab7c549a7bb5cf3a732f253ab4a17f26e44eefaeacb37f2ee13278'\n\n  # Send a notification to the original email when the user's email is changed.\n  config.send_email_changed_notification = true\n\n  # Send a notification email when the user's password is changed.\n  # config.send_password_change_notification = false\n\n  # ==> Configuration for :invitable\n  # The period the generated invitation token is valid.\n  # After this period, the invited resource won't be able to accept the invitation.\n  # When invite_for is 0 (the default), the invitation won't expire.\n  # config.invite_for = 2.weeks\n\n  # Number of invitations users can send.\n  # - If invitation_limit is nil, there is no limit for invitations, users can\n  # send unlimited invitations, invitation_limit column is not used.\n  # - If invitation_limit is 0, users can't send invitations by default.\n  # - If invitation_limit n > 0, users can send n invitations.\n  # You can change invitation_limit column for some users so they can send more\n  # or less invitations, even with global invitation_limit = 0\n  # Default: nil\n  # config.invitation_limit = 5\n\n  # The key to be used to check existing users when sending an invitation\n  # and the regexp used to test it when validate_on_invite is not set.\n  # config.invite_key = { email: /\\A[^@]+@[^@]+\\z/ }\n  # config.invite_key = { email: /\\A[^@]+@[^@]+\\z/, username: nil }\n\n  # Ensure that invited record is valid.\n  # The invitation won't be sent if this check fails.\n  # Default: false\n  # config.validate_on_invite = true\n\n  # Resend invitation if user with invited status is invited again\n  # Default: true\n  # config.resend_invitation = false\n\n  # The class name of the inviting model. If this is nil,\n  # the #invited_by association is declared to be polymorphic.\n  # Default: nil\n  # config.invited_by_class_name = 'User'\n\n  # The foreign key to the inviting model (if invited_by_class_name is set)\n  # Default: :invited_by_id\n  # config.invited_by_foreign_key = :invited_by_id\n\n  # The column name used for counter_cache column. If this is nil,\n  # the #invited_by association is declared without counter_cache.\n  # Default: nil\n  # config.invited_by_counter_cache = :invitations_count\n\n  # Auto-login after the user accepts the invite. If this is false,\n  # the user will need to manually log in after accepting the invite.\n  # Default: true\n  # config.allow_insecure_sign_in_after_accept = false\n\n  # ==> Configuration for :confirmable\n  # A period that the user is allowed to access the website even without\n  # confirming their account. For instance, if set to 2.days, the user will be\n  # able to access the website for two days without confirming their account,\n  # access will be blocked just in the third day.\n  # You can also set it to nil, which will allow the user to access the website\n  # without confirming their account.\n  # Default is 0.days, meaning the user cannot access the website without\n  # confirming their account.\n  # config.allow_unconfirmed_access_for = 2.days\n\n  # A period that the user is allowed to confirm their account before their\n  # token becomes invalid. For example, if set to 3.days, the user can confirm\n  # their account within 3 days after the mail was sent, but on the fourth day\n  # their account can't be confirmed with the token any more.\n  # Default is nil, meaning there is no restriction on how long a user can take\n  # before confirming their account.\n  # config.confirm_within = 3.days\n\n  # If true, requires any email changes to be confirmed (exactly the same way as\n  # initial account confirmation) to be applied. Requires additional unconfirmed_email\n  # db field (see migrations). Until confirmed, new email is stored in\n  # unconfirmed_email column, and copied to email column on successful confirmation.\n  config.reconfirmable = true\n\n  # Defines which key will be used when confirming an account\n  # config.confirmation_keys = [:email]\n\n  # ==> Configuration for :rememberable\n  # The time the user will be remembered without asking for credentials again.\n  # config.remember_for = 2.weeks\n\n  # Invalidates all the remember me tokens when the user signs out.\n  # config.expire_all_remember_me_on_sign_out = true\n\n  # If true, extends the user's remember period when remembered via cookie.\n  # config.extend_remember_period = false\n\n  # Options to be passed to the created cookie. For instance, you can set\n  # secure: true in order to force SSL only cookies.\n  # config.rememberable_options = {}\n\n  # ==> Configuration for :validatable\n  # Range for password length.\n  config.password_length = 8..128\n\n  # Email regex used to validate email formats. It simply asserts that\n  # one (and only one) @ exists in the given string. This is mainly\n  # to give user feedback and not to assert the e-mail validity.\n  config.email_regexp = /\\A[^@\\s]+@[^@\\s]+\\z/\n\n  # ==> Configuration for :timeoutable\n  # The time you want to timeout the user session without activity. After this\n  # time the user will be asked for credentials again. Default is 30 minutes.\n  config.timeout_in = 3.hours\n\n  # ==> Configuration for :lockable\n  # Defines which strategy will be used to lock an account.\n  # :failed_attempts = Locks an account after a number of failed attempts to sign in.\n  # :none            = No lock strategy. You should handle locking by yourself.\n  # config.lock_strategy = :failed_attempts\n\n  # Defines which key will be used when locking and unlocking an account\n  # config.unlock_keys = [:email]\n\n  # Defines which strategy will be used to unlock an account.\n  # :email = Sends an unlock link to the user email\n  # :time  = Re-enables login after a certain amount of time (see :unlock_in below)\n  # :both  = Enables both strategies\n  # :none  = No unlock strategy. You should handle unlocking by yourself.\n  # config.unlock_strategy = :both\n\n  # Number of authentication tries before locking an account if lock_strategy\n  # is failed attempts.\n  # config.maximum_attempts = 20\n\n  # Time interval to unlock the account if :time is enabled as unlock_strategy.\n  # config.unlock_in = 1.hour\n\n  # Warn on the last attempt before the account is locked.\n  # config.last_attempt_warning = true\n\n  # ==> Configuration for :recoverable\n  #\n  # Defines which key will be used when recovering the password for an account\n  # config.reset_password_keys = [:email]\n\n  # Time interval you can reset your password with a reset password key.\n  # Don't put a too small interval or your users won't have the time to\n  # change their passwords.\n  config.reset_password_within = 6.hours\n\n  # When set to false, does not sign a user in automatically after their password is\n  # reset. Defaults to true, so a user is signed in automatically after a reset.\n  # config.sign_in_after_reset_password = true\n\n  # ==> Configuration for :encryptable\n  # Allow you to use another hashing or encryption algorithm besides bcrypt (default).\n  # You can use :sha1, :sha512 or algorithms from others authentication tools as\n  # :clearance_sha1, :authlogic_sha512 (then you should set stretches above to 20\n  # for default behavior) and :restful_authentication_sha1 (then you should set\n  # stretches to 10, and copy REST_AUTH_SITE_KEY to pepper).\n  #\n  # Require the `devise-encryptable` gem when using anything other than bcrypt\n  # config.encryptor = :sha512\n\n  # ==> Scopes configuration\n  # Turn scoped views on. Before rendering \"sessions/new\", it will first check for\n  # \"users/sessions/new\". It's turned off by default because it's slower if you\n  # are using only default views.\n  config.scoped_views = true\n\n  # Configure the default scope given to Warden. By default it's the first\n  # devise role declared in your routes (usually :user).\n  config.default_scope = :user\n\n  # Set this configuration to false if you want /users/sign_out to sign out\n  # only the current scope. By default, Devise signs out all scopes.\n  # config.sign_out_all_scopes = true\n\n  # ==> Navigation configuration\n  # Lists the formats that should be treated as navigational. Formats like\n  # :html, should redirect to the sign in page when the user does not have\n  # access, but formats like :xml or :json, should return 401.\n  #\n  # If you have any extra navigational formats, like :iphone or :mobile, you\n  # should add them to the navigational formats lists.\n  #\n  # The \"*/*\" below is required to match Internet Explorer requests.\n  config.navigational_formats = [\"*/*\", :html]\n\n  # The default HTTP method used to sign out a resource. Default is :delete.\n  config.sign_out_via = :get\n\n  # ==> OmniAuth\n  # Add a new OmniAuth provider. Check the wiki for more information on setting\n  # up on your models and hooks.\n  # config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo'\n\n  # ==> Warden configuration\n  # If you want to use other strategies, that are not supported by Devise, or\n  # change the failure app, you can configure them inside the config.warden block.\n  #\n  # config.warden do |manager|\n  #   manager.intercept_401 = false\n  #   manager.default_strategies(scope: :user).unshift :some_external_strategy\n  # end\n\n  # ==> Mountable engine configurations\n  # When using Devise inside an engine, let's call it `MyEngine`, and this engine\n  # is mountable, there are some extra configurations to be taken into account.\n  # The following options are available, assuming the engine is mounted as:\n  #\n  #     mount MyEngine, at: '/my_engine'\n  #\n  # The router that invoked `devise_for`, in the example above, would be:\n  # config.router_name = :my_engine\n  #\n  # When using OmniAuth, Devise cannot automatically set OmniAuth path,\n  # so you need to do it manually. For the users scope, it would be:\n  # config.omniauth_path_prefix = '/my_engine/users/auth'\n\n  # ==> Turbolinks configuration\n  # If your app is using Turbolinks, Turbolinks::Controller needs to be included to make redirection work correctly:\n  #\n  # ActiveSupport.on_load(:devise_failure_app) do\n  #   include Turbolinks::Controller\n  # end\n\n  # ==> Configuration for :registerable\n\n  # When set to false, does not sign a user in automatically after their password is\n  # changed. Defaults to true, so a user is signed in automatically after changing a password.\n  # config.sign_in_after_change_password = true\nend\n"
  },
  {
    "path": "config/initializers/extensions.rb",
    "content": "require \"pdf_forms\"\nrequire \"ext/pdf_forms\"\n"
  },
  {
    "path": "config/initializers/filter_parameter_logging.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file.\n# Use this to limit dissemination of sensitive information.\n# See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors.\nRails.application.config.filter_parameters += [\n  :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn\n]\n"
  },
  {
    "path": "config/initializers/flipper.rb",
    "content": "ActiveSupport.on_load(:active_record) do\n  require \"flipper/adapters/active_record\"\n\n  Flipper.configure do |config|\n    config.default do\n      adapter = Flipper::Adapters::ActiveRecord.new\n      Flipper.new(adapter)\n    end\n  end\nend\n"
  },
  {
    "path": "config/initializers/generators.rb",
    "content": "# Allows rails generators (scaffold/controller) to use custom policy generator.\n# Arguments for rails generators will be passed to the policy generator.\n# Options will be shown in the help text for the rails generators,\n# including the option to skip the policy generator (--skip-policy).\nmodule PolicyGenerator\n  module ControllerGenerator\n    extend ActiveSupport::Concern\n\n    included do\n      hook_for :policy, in: nil, default: true, type: :boolean do |generator|\n        # use actions from controller invocation\n        invoke generator, [name.singularize, *actions]\n      end\n    end\n  end\n\n  module ScaffoldControllerGenerator\n    extend ActiveSupport::Concern\n\n    included do\n      hook_for :policy, in: nil, default: true, type: :boolean do |generator|\n        # prevent attribute arguments (name:string) being confused with actions\n        scaffold_actions = %w[index new create show edit update destroy]\n        invoke generator, [name.singularize, *scaffold_actions]\n      end\n    end\n  end\nend\n\nmodule ActiveModel\n  class Railtie < Rails::Railtie\n    generators do |app|\n      Rails::Generators.configure! app.config.generators\n      Rails::Generators::ControllerGenerator.include PolicyGenerator::ControllerGenerator\n      Rails::Generators::ScaffoldControllerGenerator.include PolicyGenerator::ScaffoldControllerGenerator\n    end\n  end\nend\n"
  },
  {
    "path": "config/initializers/inflections.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Add new inflection rules using the following format. Inflections\n# are locale specific, and you may define rules for as many different\n# locales as you wish. All of these examples are active by default:\n# ActiveSupport::Inflector.inflections(:en) do |inflect|\n#   inflect.plural /^(ox)$/i, \"\\\\1en\"\n#   inflect.singular /^(ox)en/i, \"\\\\1\"\n#   inflect.irregular \"person\", \"people\"\n#   inflect.uncountable %w( fish sheep )\n# end\n\n# These inflection rules are supported but not enabled by default:\n# ActiveSupport::Inflector.inflections(:en) do |inflect|\n#   inflect.acronym \"RESTful\"\n# end\nActiveSupport::Inflector.inflections(:en) do |inflect|\n  inflect.acronym \"DSS\" # TODO what is this for?\nend\n"
  },
  {
    "path": "config/initializers/lograge.rb",
    "content": "Rails.application.configure do\n  config.lograge.enabled = true\nend\n"
  },
  {
    "path": "config/initializers/mime_types.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Add new mime types for use in respond_to blocks:\n# Mime::Type.register \"text/richtext\", :rtf\nMime::Type.register \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\", :docx\n"
  },
  {
    "path": "config/initializers/pagy.rb",
    "content": "require \"pagy/extras/bootstrap\"\nrequire \"pagy/extras/size\"\n\nPagy::DEFAULT[:size] = [1, 3, 3, 1]\nPagy::DEFAULT[:nav_class] = \"pagy-bootstrap-nav\"\n"
  },
  {
    "path": "config/initializers/permissions_policy.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Define an application-wide HTTP permissions policy. For further\n# information see: https://developers.google.com/web/updates/2018/06/feature-policy\n\n# Rails.application.config.permissions_policy do |policy|\n#   policy.camera      :none\n#   policy.gyroscope   :none\n#   policy.microphone  :none\n#   policy.usb         :none\n#   policy.fullscreen  :self\n#   policy.payment     :self, \"https://secure.example.com\"\n# end\n"
  },
  {
    "path": "config/initializers/prosopite.rb",
    "content": "# frozen_string_literal: true\n\n# Rack middleware for development only — in test, scanning is handled by RSpec hooks\nif Rails.env.development? &&\n    Rails.configuration.respond_to?(:prosopite_enabled) &&\n    Rails.configuration.prosopite_enabled\n  require \"prosopite/middleware/rack\"\n  Rails.configuration.middleware.use(Prosopite::Middleware::Rack)\nend\n\n# Development configuration — test config lives in spec/support/prosopite.rb\nRails.application.config.after_initialize do\n  next unless Rails.env.development?\n\n  Prosopite.enabled = Rails.configuration.respond_to?(:prosopite_enabled) &&\n    Rails.configuration.prosopite_enabled\n\n  Prosopite.min_n_queries = Rails.configuration.respond_to?(:prosopite_min_n_queries) ?\n                            Rails.configuration.prosopite_min_n_queries : 2\n\n  Prosopite.rails_logger = true\n  Prosopite.prosopite_logger = true\nend\n"
  },
  {
    "path": "config/initializers/rack_attack.rb",
    "content": "class Rack::Attack\n  ### Configure Cache ###\n\n  # If you don't want to use Rails.cache (Rack::Attack's default), then\n  # configure it here.\n  #\n  # Note: The store is only used for throttling (not blocklisting and\n  # safelisting). It must implement .increment and .write like\n  # ActiveSupport::Cache::Store\n\n  # Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new\n\n  Rack::Attack.safelist(\"allow from localhost\") do |req|\n    # Requests are allowed if the return value is truthy\n    req.ip == \"127.0.0.1\" || req.ip == \"::1\"\n  end\n\n  ### Throttle Spammy Clients ###\n\n  # If any single client IP is making tons of requests, then they're\n  # probably malicious or a poorly-configured scraper. Either way, they\n  # don't deserve to hog all of the app server's CPU. Cut them off!\n  #\n  # Note: If you're serving assets through rack, those requests may be\n  # counted by rack-attack and this throttle may be activated too\n  # quickly. If so, enable the condition to exclude them from tracking.\n\n  # Throttle all requests by IP (60rpm)\n  #\n  # Key: \"rack::attack:#{Time.now.to_i/:period}:req/ip:#{req.ip}\"\n  throttle(\"req/ip\", limit: 300, period: 5.minutes) do |req|\n    req.ip unless req.path.start_with?(\"/packs\")\n  end\n\n  ### Prevent Brute-Force Login Attacks ###\n\n  # The most common brute-force login attack is a brute-force password\n  # attack where an attacker simply tries a large number of emails and\n  # passwords to see if any credentials match.\n  #\n  # Another common method of attack is to use a swarm of computers with\n  # different IPs to try brute-forcing a password for a specific account.\n\n  # Throttle POST requests to /xxxx/sign_in by IP address\n  #\n  # Key: \"rack::attack:#{Time.now.to_i/:period}:logins/ip:#{req.ip}\"\n  throttle(\"logins/ip\", limit: 5, period: 20.seconds) do |req|\n    if req.path =~ /sign_in/ && req.post?\n      req.ip\n    end\n  end\n\n  throttle(\"reg/ip\", limit: 5, period: 20.seconds) do |req|\n    req.ip if req.path.starts_with?(\"/api/v1\")\n  end\n\n  # Throttle POST requests to /xxxx/sign_in by email param\n  #\n  # Key: \"rack::attack:#{Time.now.to_i/:period}:logins/email:#{req.email}\"\n  #\n  # Note: This creates a problem where a malicious user could intentionally\n  # throttle logins for another user and force their login requests to be\n  # denied, but that's not very common and shouldn't happen to you. (Knock\n  # on wood!)\n  throttle(\"logins/email\", limit: 5, period: 20.seconds) do |req|\n    if req.path =~ /sign_in/ && req.post?\n      # return the email if present, nil otherwise\n      req.params.dig(\"user\", \"email\").presence ||\n        req.params.dig(\"all_casa_admin\", \"email\").presence\n    end\n  end\n\n  Rack::Attack.blocklist(\"fail2ban pentesters\") do |req|\n    # `filter` returns truthy value if request fails, or if it's from a\n    # previously banned IP so the request is blocked\n    Rack::Attack::Fail2Ban.filter(\"pentesters-#{req.ip}\", maxretry: 3, findtime: 10.minutes, bantime: 1.day) do\n      # The count for the IP is incremented if the return value is truthy\n      CGI.unescape(req.query_string) =~ %r{/etc/passwd} ||\n        req.path.match?(/etc\\/passwd/) ||\n        req.path.match(/wp-admin/i) ||\n        req.path.match(/wp-login/i) ||\n        req.path.match(/php/i) ||\n        req.path.match(/sql/i) ||\n        req.path.match(/PMA\\d+/i) ||\n        req.path.match(/serverstatus/i) ||\n        req.path.match(/config\\/server/i) ||\n        req.path.match(/xmlrpc/i) ||\n        req.path.match(/a2billing/i) ||\n        req.path.match(/testproxy/i) ||\n        req.path.match(/shopdb/i) ||\n        req.path.match(/index.action/i) ||\n        req.path.match(/etc\\/services/i)\n    end\n  end\n\n  bad_ips = ENV[\"IP_BLOCKLIST\"]\n  if bad_ips.present?\n    spammers = bad_ips.split(/\\s*,\\s*/)\n    spammer_regexp = Regexp.union(spammers)\n    blocklist(\"block bad ips\") do |request|\n      request.ip =~ spammer_regexp\n    end\n  end\n\n  ### Custom Throttle Response ###\n\n  # By default, Rack::Attack returns an HTTP 429 for throttled responses,\n  # which is just fine.\n  #\n  # If you want to return 503 so that the attacker might be fooled into\n  # believing that they've successfully broken your app (or you just want to\n  # customize the response), then uncomment these lines.\n  # self.throttled_response = lambda do |env|\n  #  [ 503,  # status\n  #    {},   # headers\n  #    ['']] # body\n  # end\nend\n"
  },
  {
    "path": "config/initializers/rswag_api.rb",
    "content": "Rswag::Api.configure do |c|\n  # Specify a root folder where Swagger JSON files are located\n  # This is used by the Swagger middleware to serve requests for API descriptions\n  # NOTE: If you're using rswag-specs to generate Swagger, you'll need to ensure\n  # that it's configured to generate files in the same folder\n  c.openapi_root = Rails.root.to_s + \"/swagger\"\n\n  # Inject a lambda function to alter the returned Swagger prior to serialization\n  # The function will have access to the rack env for the current request\n  # For example, you could leverage this to dynamically assign the \"host\" property\n  #\n  # c.swagger_filter = lambda { |swagger, env| swagger['host'] = env['HTTP_HOST'] }\nend\n"
  },
  {
    "path": "config/initializers/rswag_ui.rb",
    "content": "Rswag::Ui.configure do |c|\n  # List the Swagger endpoints that you want to be documented through the\n  # swagger-ui. The first parameter is the path (absolute or relative to the UI\n  # host) to the corresponding endpoint and the second is a title that will be\n  # displayed in the document selector.\n  # NOTE: If you're using rspec-api to expose Swagger files\n  # (under swagger_root) as JSON or YAML endpoints, then the list below should\n  # correspond to the relative paths for those endpoints.\n\n  c.openapi_endpoint \"/api-docs/v1/swagger.yaml\", \"API V1 Docs\"\n\n  # Add Basic Auth in case your API is private\n  # c.basic_auth_enabled = true\n  # c.basic_auth_credentials 'username', 'password'\nend\n"
  },
  {
    "path": "config/initializers/sent_email_event.rb",
    "content": "ActiveSupport::Notifications.subscribe \"process.action_mailer\" do |*args|\n  data = args.extract_options!\n  next if data[:mailer] == \"DebugPreviewMailer\"\n\n  user = data[:args][0]\n  if user&.role != \"All Casa Admin\"\n    SentEmail.create(\n      casa_org_id: user&.casa_org_id,\n      user_id: user&.id,\n      sent_address: user&.email,\n      mailer_type: data[:mailer],\n      category: data[:action].to_s.humanize\n    )\n    Rails.logger.info \"#{data[:action]} email saved!\"\n  end\nend\n"
  },
  {
    "path": "config/initializers/strong_migrations.rb",
    "content": "# Mark existing migrations as safe\nStrongMigrations.start_after = 20220117181508\n\n# Set timeouts for migrations\n# If you use PgBouncer in transaction mode, delete these lines and set timeouts on the database user\nStrongMigrations.lock_timeout = 10.seconds\nStrongMigrations.statement_timeout = 1.hour\n\n# Analyze tables after indexes are added\n# Outdated statistics can sometimes hurt performance\nStrongMigrations.auto_analyze = true\n\n# Set the version of the production database\n# so the right checks are run in development\n# StrongMigrations.target_version = 10\n\n# Add custom checks\n# StrongMigrations.add_check do |method, args|\n#   if method == :add_index && args[0].to_s == \"users\"\n#     stop! \"No more indexes on the users table\"\n#   end\n# end\n\n# Make some operations safe by default\n# See https://github.com/ankane/strong_migrations#safe-by-default\n# StrongMigrations.safe_by_default = true\n"
  },
  {
    "path": "config/initializers/wrap_parameters.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# This file contains settings for ActionController::ParamsWrapper which\n# is enabled by default.\n\n# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.\nActiveSupport.on_load(:action_controller) do\n  wrap_parameters format: [:json]\nend\n\n# To enable root element in JSON for ActiveRecord objects.\n# ActiveSupport.on_load(:active_record) do\n#   self.include_root_in_json = true\n# end\n"
  },
  {
    "path": "config/locales/devise.en.yml",
    "content": "# Additional translations at https://github.com/plataformatec/devise/wiki/I18n\n\nen:\n  devise:\n    confirmations:\n      confirmed: \"Your email address has been successfully confirmed.\"\n      send_instructions: \"You will receive an email with instructions for how to confirm your email address in a few minutes.\"\n      send_paranoid_instructions: \"If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes.\"\n    failure:\n      already_authenticated: \"You are already signed in.\"\n      inactive: \"Your account is currently inactive. Please contact your supervisor for more details.\"\n      invalid: \"Invalid %{authentication_keys} or password.\"\n      locked: \"Your account is locked.\"\n      last_attempt: \"You have one more attempt before your account is locked.\"\n      not_found_in_database: \"Invalid %{authentication_keys} or password.\"\n      timeout: \"Your session expired. Please sign in again to continue.\"\n      unauthenticated: \"You need to sign in before continuing.\"\n      unconfirmed: \"You have to confirm your email address before continuing.\"\n    mailer:\n      confirmation_instructions:\n        subject: \"Confirmation instructions\"\n      reset_password_instructions:\n        subject: \"Reset password instructions\"\n      unlock_instructions:\n        subject: \"Unlock instructions\"\n      email_changed:\n        subject: \"Email Changed\"\n      password_change:\n        subject: \"Password Changed\"\n    omniauth_callbacks:\n      failure: \"Could not authenticate you from %{kind} because \\\"%{reason}\\\".\"\n      success: \"Successfully authenticated from %{kind} account.\"\n    passwords:\n      no_token: \"You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided.\"\n      send_instructions: \"You will receive an email with instructions on how to reset your password in a few minutes.\"\n      send_paranoid_instructions: \"If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes.\"\n      updated: \"Your password has been changed successfully. You are now signed in.\"\n      updated_not_active: \"Your password has been changed successfully.\"\n    registrations:\n      destroyed: \"Bye! Your account has been successfully cancelled. We hope to see you again soon.\"\n      signed_up: \"Welcome! You have signed up successfully.\"\n      signed_up_but_inactive: \"You have signed up successfully. However, we could not sign you in because your account is not yet activated.\"\n      signed_up_but_locked: \"You have signed up successfully. However, we could not sign you in because your account is locked.\"\n      signed_up_but_unconfirmed: \"A message with a confirmation link has been sent to your email address. Please follow the link to activate your account.\"\n      update_needs_confirmation: \"You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirmation link to confirm your new email address.\"\n      updated: \"Your account has been updated successfully.\"\n      updated_but_not_signed_in: \"Your account has been updated successfully, but since your password was changed, you need to sign in again\"\n    sessions:\n      signed_in: \"Signed in successfully.\"\n      signed_out: \"Signed out successfully.\"\n      already_signed_out: \"Signed out successfully.\"\n    unlocks:\n      send_instructions: \"You will receive an email with instructions for how to unlock your account in a few minutes.\"\n      send_paranoid_instructions: \"If your account exists, you will receive an email with instructions for how to unlock it in a few minutes.\"\n      unlocked: \"Your account has been unlocked successfully. Please sign in to continue.\"\n  errors:\n    messages:\n      already_confirmed: \"was already confirmed, please try signing in\"\n      confirmation_period_expired: \"needs to be confirmed within %{period}, please request a new one\"\n      expired: \"has expired, please request a new one\"\n      not_found: \"not found\"\n      not_locked: \"was not locked\"\n      not_saved:\n        password: \"%{count} error(s) prohibited this password change from being saved:\"\n        one: \"1 error prohibited this %{resource} from being saved:\"\n        other: \"%{count} errors prohibited this %{resource} from being saved:\"\n"
  },
  {
    "path": "config/locales/devise_invitable.en.yml",
    "content": "en:\n  devise:\n    failure:\n      invited: >\n       \"You have a pending invitation, accept it to finish creating your\n       account.\"\n    invitations:\n      send_instructions: \"An invitation email has been sent to %{email}.\"\n      invitation_token_invalid: \"The invitation token provided is not valid!\"\n      updated: \"Your password was set successfully. You are now signed in.\"\n      updated_not_active: \"Your password was set successfully.\"\n      no_invitations_remaining: \"No invitations remaining\"\n      invitation_removed: \"Your invitation was removed.\"\n    mailer:\n      invitation_instructions:\n        subject: \"CASA Console invitation instructions\"\n  time:\n    formats:\n      devise:\n        mailer:\n          invitation_instructions:\n            accept_until_format: \"%B %d, %Y %I:%M %p\"\n"
  },
  {
    "path": "config/locales/en.yml",
    "content": "# Files in the config/locales directory are used for internationalization\n# and are automatically loaded by Rails. If you want to use locales other\n# than English, add the necessary files in this directory.\n#\n# To use the locales, use `I18n.t`:\n#\n#     I18n.t 'hello'\n#\n# In views, this is aliased to just `t`:\n#\n#     <%= t('hello') %>\n#\n# To use a different locale, set it with `I18n.locale`:\n#\n#     I18n.locale = :es\n#\n# This would use the information in config/locales/es.yml.\n#\n# The following keys must be escaped otherwise they will not be retrieved by\n# the default I18n backend:\n#\n# true, false, on, off, yes, no\n#\n# Instead, surround them with single quotes.\n#\n# en:\n#   'true': 'foo'\n#\n# To learn more, please read the Rails Internationalization guide\n# available at https://guides.rubyonrails.org/i18n.html.\n---\nen:\n  activerecord:\n    attributes:\n      additional_expense:\n        other_expenses_amount: Amount\n        other_expenses_describe: Description\n      case_contact:\n        case_contact_contact_types:\n          one: Contact Type\n          other: Contact Types\n        contact_types:\n          one: Contact Type\n          other: Contact Types\n        contact_topic_answers:\n          one: Discussion Topic\n          other: Discussion Topics\n        draft_case_ids:\n          one: CASA Case\n          other: CASA Cases\n        occurred_at: Date\n      case_contact/additional_expenses:\n        other_expense_amount: Other Expense Amount\n        other_expenses_describe: Other Expense Details\n      case_contact/contact_topic_answers:\n        contact_topic: Discussion Topic\n        value: Discussion Notes\n    errors:\n      messages:\n        cant_be_future: can't be in the future\n        must_be_selected: must be selected\n        must_be_true_or_false: must be true or false\n        email_uniqueness: This email is already in use. If it does not appear on your roster, it may be associated with another CASA organization. Please use a different email address.\n  time:\n    formats:\n      day_and_date: \"%A, %b %d, %Y\"\n      standard: \"%m-%d-%Y\"\n      contact_occurred_at: \"%Y%m%d%H%M%s\"\n      full: \"%B %-d, %Y\"\n      youth_date_of_birth: \"%B %Y\"\n      short_date: \"%-m/%d\"\n      long_date: \"%m/%d/%y\"\n      edit_profile: \"%B %d, %Y at %I:%M %p %Z\"\n      time_on_date: \"%-I:%-M %p on %m-%e-%Y\"\n  date:\n    formats:\n      default: \"%m/%d/%Y\"\n      full: \"%B %-d, %Y\"\n      long: \"%B %d, %Y\"\n      short: \"%b %d\"\n      year_first: \"%Y-%m-%d\"\n  imports:\n    labels:\n      casa_case: \"CASA Case\"\n      volunteer: \"Volunteer\"\n      supervisor: \"Supervisor\"\n"
  },
  {
    "path": "config/puma.rb",
    "content": "# This configuration file will be evaluated by Puma. The top-level methods that\n# are invoked here are part of Puma's configuration DSL. For more information\n# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html.\n\n# Puma starts a configurable number of processes (workers) and each process\n# serves each request in a thread from an internal thread pool.\n#\n# The ideal number of threads per worker depends both on how much time the\n# application spends waiting for IO operations and on how much you wish to\n# to prioritize throughput over latency.\n#\n# As a rule of thumb, increasing the number of threads will increase how much\n# traffic a given process can handle (throughput), but due to CRuby's\n# Global VM Lock (GVL) it has diminishing returns and will degrade the\n# response time (latency) of the application.\n#\n# The default is set to 3 threads as it's deemed a decent compromise between\n# throughput and latency for the average Rails application.\n#\n# Any libraries that use a connection pool or another resource pool should\n# be configured to provide at least as many connections as the number of\n# threads. This includes Active Record's `pool` parameter in `database.yml`.\nthreads_count = ENV.fetch(\"RAILS_MAX_THREADS\", 3)\nthreads threads_count, threads_count\n\n# Specifies the `port` that Puma will listen on to receive requests; default is 3000.\nport ENV.fetch(\"PORT\", 3000)\n\n# Allow puma to be restarted by `bin/rails restart` command.\nplugin :tmp_restart\n\n# Specify the PID file. Defaults to tmp/pids/server.pid in development.\n# In other environments, only set the PID file if requested.\npidfile ENV[\"PIDFILE\"] if ENV[\"PIDFILE\"]\n"
  },
  {
    "path": "config/routes.rb",
    "content": "# frozen_string_literal: true\n\nRails.application.routes.draw do\n  mount Rswag::Ui::Engine => \"/api-docs\"\n  mount Rswag::Api::Engine => \"/api-docs\"\n\n  devise_for :all_casa_admins, path: \"all_casa_admins\", controllers: {sessions: \"all_casa_admins/sessions\"}\n  devise_for :users, controllers: {sessions: \"users/sessions\", passwords: \"users/passwords\", invitations: \"users/invitations\"}\n  authenticate :all_casa_admins do\n    mount PgHero::Engine, at: \"pg_dashboard\", constraints: lambda { |request|\n      admin = request.env[\"warden\"].user(:all_casa_admin)\n      admin.present? && (admin.role == \"All Casa Admin\" || admin.casa_admin?)\n    }\n  end\n\n  concern :with_datatable do\n    post \"datatable\", on: :collection\n  end\n\n  authenticated :all_casa_admin do\n    root to: \"all_casa_admins/dashboard#show\", as: :authenticated_all_casa_admin_root\n  end\n\n  authenticated :user do\n    root to: \"dashboard#show\", as: :authenticated_user_root\n  end\n\n  devise_scope :user do\n    root to: \"static#index\"\n  end\n\n  devise_scope :all_casa_admins do\n    root to: \"all_casa_admins/sessions#new\", as: :unauthenticated_all_casa_root\n  end\n\n  resources :preference_sets, only: [] do\n    collection do\n      post \"/table_state_update/:table_name\", to: \"preference_sets#table_state_update\", as: :table_state_update\n      get \"/table_state/:table_name\", to: \"preference_sets#table_state\", as: :table_state\n    end\n  end\n\n  resources :health, only: %i[index] do\n    collection do\n      get :case_contacts_creation_times_in_last_week\n      get :gc\n      get :monthly_line_graph_data\n      get :monthly_unique_users_graph_data\n    end\n  end\n\n  get \"/.well-known/assetlinks.json\", to: \"android_app_associations#index\"\n  resources :casa_cases, except: %i[destroy] do\n    resource :emancipation, only: %i[show] do\n      member do\n        post \"save\"\n      end\n    end\n\n    resource :fund_request, only: %i[new create]\n\n    resources :court_dates, only: %i[create edit new show update destroy]\n\n    resources :placements\n\n    member do\n      patch :deactivate\n      patch :reactivate\n      patch :copy_court_orders\n    end\n  end\n\n  resources :casa_admins, except: %i[destroy show] do\n    member do\n      patch :deactivate\n      patch :activate\n      patch :resend_invitation\n      post :send_reactivation_alert\n      patch :change_to_supervisor\n    end\n  end\n\n  get \"case_contacts/leave\", to: \"case_contacts#leave\", as: \"leave_case_contacts_form\"\n  get \"case_contacts/drafts\", to: \"case_contacts#drafts\"\n\n  # Feature flag for new case contact table design\n  get \"case_contacts/new_design\", to: \"case_contacts/case_contacts_new_design#index\"\n  post \"case_contacts/new_design/datatable\", to: \"case_contacts/case_contacts_new_design#datatable\", as: \"datatable_case_contacts_new_design\"\n  resources :case_contacts, except: %i[create update show], concerns: %i[with_datatable] do\n    member do\n      post :restore\n    end\n    resources :form, controller: \"case_contacts/form\", only: %i[show update]\n    resources :followups, only: %i[create], controller: \"case_contacts/followups\", shallow: true do\n      patch :resolve, on: :member\n    end\n  end\n\n  resources :contact_topic_answers, only: %i[create destroy]\n\n  resources :reports, only: %i[index]\n  get :export_emails, to: \"reports#export_emails\"\n\n  resources :case_court_reports, only: %i[index show] do\n    collection do\n      post :generate\n    end\n  end\n  resources :reimbursements, only: %i[index change_complete_status], concerns: %i[with_datatable] do\n    patch :mark_as_complete, to: \"reimbursements#change_complete_status\"\n    patch :mark_as_needs_review, to: \"reimbursements#change_complete_status\"\n  end\n  resources :imports, only: %i[index create] do\n    collection do\n      get :download_failed\n    end\n  end\n  resources :case_contact_reports, only: %i[index]\n  resources :mileage_reports, only: %i[index]\n  resources :mileage_rates, only: %i[index new create edit update]\n  resources :casa_org, only: %i[edit update]\n  resources :contact_type_groups, only: %i[new create edit update]\n  resources :contact_types, only: %i[new create edit update]\n  resources :hearing_types, only: %i[new create edit update] do\n    resources :checklist_items, only: %i[new create edit update destroy]\n  end\n  resources :emancipation_checklists, only: %i[index]\n  resources :judges, only: %i[new create edit update]\n  resources :notifications, only: [:index] do\n    member do\n      post \"mark_as_read\"\n    end\n  end\n  resources :other_duties, only: %i[new create edit index update]\n  resources :missing_data_reports, only: %i[index]\n  resources :learning_hours_reports, only: %i[index]\n  resources :learning_hour_types, only: %i[new create edit update]\n  resources :learning_hour_topics, only: %i[new create edit update]\n  resources :placement_types, only: %i[new create edit update]\n\n  resources :contact_topics, except: %i[index show delete] do\n    delete \"soft_delete\", on: :member\n  end\n\n  resources :followup_reports, only: :index\n  resources :placement_reports, only: :index\n  resources :banners, except: %i[show] do\n    member do\n      get :dismiss\n    end\n  end\n  resources :bulk_court_dates, only: %i[new create]\n  resources :case_groups, only: %i[index new edit create update destroy]\n  resources :learning_hours, only: %i[index show new create edit update destroy]\n  namespace :learning_hours do\n    resources :volunteers, only: :show\n  end\n\n  resources :supervisors, except: %i[destroy show], concerns: %i[with_datatable] do\n    member do\n      patch :activate\n      patch :deactivate\n      patch :resend_invitation\n      patch :change_to_admin\n    end\n  end\n  resources :supervisor_volunteers, only: %i[create] do\n    collection do\n      post :bulk_assignment\n    end\n    member do\n      patch :unassign\n    end\n  end\n  resources :volunteers, except: %i[destroy], concerns: %i[with_datatable] do\n    post :stop_impersonating, on: :collection\n    member do\n      patch :activate\n      patch :deactivate\n      get :resend_invitation\n      get :send_reactivation_alert\n      patch :reminder\n      get :impersonate\n    end\n    resources :notes, only: %i[create edit update destroy]\n  end\n  resources :case_assignments, only: %i[create destroy] do\n    member do\n      get :unassign\n      patch :unassign\n      patch :show_hide_contacts\n      patch :reimbursement\n    end\n  end\n  resources :case_court_orders, only: %i[destroy]\n\n  resources :additional_expenses, only: %i[create destroy]\n\n  namespace :all_casa_admins do\n    resources :casa_orgs, only: [:new, :create, :show] do\n      resources :casa_admins, only: [:new, :create, :edit, :update] do\n        member do\n          patch :deactivate\n          patch :activate\n        end\n      end\n    end\n\n    resources :patch_notes, only: %i[create destroy index update]\n  end\n\n  resources :all_casa_admins, only: [:new, :create] do\n    collection do\n      get :edit\n      patch :update\n      patch \"update_password\"\n    end\n  end\n\n  resources :users, only: [] do\n    collection do\n      get :edit\n      patch :update\n      patch \"update_password\"\n      patch \"update_email\"\n      patch :add_language\n      delete :remove_language\n    end\n  end\n  resources :languages, only: %i[new create edit update] do\n    delete :remove_from_volunteer\n  end\n  resources :custom_org_links, only: %i[new create edit update destroy]\n\n  direct :help_admins_supervisors do\n    \"https://thunder-flower-8c2.notion.site/Casa-Volunteer-Tracking-App-HelpSite-3b95705e80c742ffa729ccce7beeabfa\"\n  end\n\n  direct :help_volunteers do\n    \"https://thunder-flower-8c2.notion.site/Casa-Volunteer-Tracking-App-HelpSite-Volunteers-c24d9d2ef8b249bbbda8192191365039?pvs=4\"\n  end\n\n  get \"/error\", to: \"error#index\"\n  post \"/error\", to: \"error#create\"\n\n  namespace :api do\n    namespace :v1 do\n      namespace :users do\n        post \"sign_in\", to: \"sessions#create\"\n        delete \"sign_out\", to: \"sessions#destroy\"\n      end\n    end\n  end\n\n  constraints CanAccessFlipperUI do\n    mount Flipper::UI.app(Flipper) => \"/flipper\"\n  end\nend\n"
  },
  {
    "path": "config/scout_apm.yml",
    "content": "# This configuration file is used for Scout APM.\n# https://scoutapm.com/docs/ruby/configuration\ncommon: &defaults\n\n  # key: Your Organization key for Scout APM. Found on the settings screen.\n  # - Default: none\n  key: <%= ENV.fetch(\"SCOUT_APM_KEY\", \"local\") %>\n\n  # log_level: Verboseness of logs.\n  # - Default: 'info'\n  # - Valid Options: debug, info, warn, error\n  # log_level: debug\n\n  # name: Application name in APM Web UI\n  # - Default: the application names comes from the Rails or Sinatra class name\n  # name:\n\n  # monitor: Enable Scout APM or not\n  # - Default: none\n  # - Valid Options: true, false\n  monitor: true\n\nproduction:\n  <<: *defaults\n  errors_enabled: true\n\ndevelopment:\n  <<: *defaults\n  monitor: false\n  dev_trace: true\n\ntest:\n  <<: *defaults\n  monitor: false\n\nstaging:\n  <<: *defaults\n  dev_trace: true\n"
  },
  {
    "path": "config/spring.rb",
    "content": "Spring.watch(\n  \".ruby-version\",\n  \".rbenv-vars\",\n  \"tmp/restart.txt\",\n  \"tmp/caching-dev.txt\"\n)\n"
  },
  {
    "path": "config/storage.yml",
    "content": "test:\n  service: Disk\n  root: <%= Rails.root.join(\"tmp/storage#{ENV['TEST_ENV_NUMBER']}\") %>\n\nlocal:\n  service: Disk\n  root: <%= Rails.root.join(\"storage\") %>\n\n# Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)\n# amazon:\n#   service: S3\n#   access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>\n#   secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>\n#   region: us-east-1\n#   bucket: your_own_bucket\n\n# Remember not to checkin your GCS keyfile to a repository\n# google:\n#   service: GCS\n#   project: your_project\n#   credentials: <%= Rails.root.join(\"path/to/gcs.keyfile\") %>\n#   bucket: your_own_bucket\n\n# Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)\nmicrosoft:\n  service: AzureStorage\n  storage_account_name: <%= ENV['STORAGE_ACCOUNT_NAME'] %>\n  storage_access_key: <%= ENV['STORAGE_ACCESS_KEY'] %>\n  container: <%= ENV['STORAGE_CONTAINER'] %>\n\n# mirror:\n#   service: Mirror\n#   primary: local\n#   mirrors: [ amazon, google, microsoft ]\n"
  },
  {
    "path": "config.ru",
    "content": "# This file is used by Rack-based servers to start the application.\n\nrequire_relative \"config/environment\"\n\nrun Rails.application\nRails.application.load_server\n"
  },
  {
    "path": "data/inputs_fdf.erb",
    "content": "%FDF-1.2\n%âãÏÓ\n1 0 obj\n\n<</FDF\n<</Fields[\n<% @inputs.as_json.each do |name, value| %>\n  << /V (<%= FdfInputsService.clean(value) %>) /T (<%= name %>) >>\n<% end %>\n]\n>>\n>>\nendobj\ntrailer\n\n<</Root 1 0 R>>\n%%EOF\n"
  },
  {
    "path": "db/migrate/20200329050100_create_casa_cases.rb",
    "content": "class CreateCasaCases < ActiveRecord::Migration[6.0]\n  def change\n    create_table :casa_cases do |t|\n      t.string :case_number\n      t.boolean :teen_program_eligible\n\n      t.timestamps\n    end\n  end\nend\n# rubocop:enable Style/Documentation\n"
  },
  {
    "path": "db/migrate/20200329062155_devise_create_users.rb",
    "content": "# frozen_string_literal: true\n\nclass DeviseCreateUsers < ActiveRecord::Migration[6.0]\n  def change\n    create_table :users do |t|\n      ## Database authenticatable\n      t.string :email, null: false, default: \"\"\n      t.string :encrypted_password, null: false, default: \"\"\n\n      ## Recoverable\n      t.string :reset_password_token\n      t.datetime :reset_password_sent_at\n\n      ## Rememberable\n      t.datetime :remember_created_at\n\n      ## Trackable\n      # t.integer  :sign_in_count, default: 0, null: false\n      # t.datetime :current_sign_in_at\n      # t.datetime :last_sign_in_at\n      # t.inet     :current_sign_in_ip\n      # t.inet     :last_sign_in_ip\n\n      ## Confirmable\n      # t.string   :confirmation_token\n      # t.datetime :confirmed_at\n      # t.datetime :confirmation_sent_at\n      # t.string   :unconfirmed_email # Only if using reconfirmable\n\n      ## Lockable\n      # t.integer  :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts\n      # t.string   :unlock_token # Only if unlock strategy is :email or :both\n      # t.datetime :locked_at\n\n      t.timestamps null: false\n    end\n\n    add_index :users, :email, unique: true\n    add_index :users, :reset_password_token, unique: true\n    # add_index :users, :confirmation_token,   unique: true\n    # add_index :users, :unlock_token,         unique: true\n  end\nend\n# rubocop:enable Style/Documentation\n"
  },
  {
    "path": "db/migrate/20200329064203_add_role_to_user.rb",
    "content": "class AddRoleToUser < ActiveRecord::Migration[6.0]\n  def change\n    add_column :users, :role, :string, null: false, default: \"volunteer\"\n  end\nend\n# rubocop:enable Style/Documentation\n"
  },
  {
    "path": "db/migrate/20200329071025_change_casa_case_teen_to_required.rb",
    "content": "class ChangeCasaCaseTeenToRequired < ActiveRecord::Migration[6.0]\n  def change\n    change_column :casa_cases, :teen_program_eligible, :boolean, null: false, default: false\n  end\nend\n# rubocop:enable Style/Documentation\n"
  },
  {
    "path": "db/migrate/20200329071327_change_casa_case_number_to_required.rb",
    "content": "class ChangeCasaCaseNumberToRequired < ActiveRecord::Migration[6.0]\n  def change\n    change_column :casa_cases, :case_number, :string, null: false\n  end\nend\n# rubocop:enable Style/Documentation\n"
  },
  {
    "path": "db/migrate/20200329071626_add_unique_index_on_case_case_number.rb",
    "content": "class AddUniqueIndexOnCaseCaseNumber < ActiveRecord::Migration[6.0]\n  def change\n    add_index :casa_cases, :case_number, unique: true\n  end\nend\n# rubocop:enable Style/Documentation\n"
  },
  {
    "path": "db/migrate/20200329074655_create_supervisor_volunteers.rb",
    "content": "class CreateSupervisorVolunteers < ActiveRecord::Migration[6.0]\n  def change\n    create_table :supervisor_volunteers do |t|\n      t.references :supervisor, foreign_key: {to_table: :users}, null: false\n      t.references :volunteer, foreign_key: {to_table: :users}, null: false\n\n      t.timestamps\n    end\n  end\nend\n# rubocop:enable Style/Documentation\n"
  },
  {
    "path": "db/migrate/20200329081206_create_case_assignments.rb",
    "content": "class CreateCaseAssignments < ActiveRecord::Migration[6.0]\n  def change\n    create_table :case_assignments do |t|\n      t.references :casa_case, foreign_key: {to_table: :casa_cases}, null: false\n      t.references :volunteer, foreign_key: {to_table: :users}, null: false\n      t.boolean :is_active, null: false, default: true\n\n      t.timestamps\n    end\n  end\nend\n# rubocop:enable Style/Documentation\n"
  },
  {
    "path": "db/migrate/20200329085225_create_versions.rb",
    "content": "# This migration creates the `versions` table, the only schema PT requires.\n# All other migrations PT provides are optional.\nclass CreateVersions < ActiveRecord::Migration[6.0]\n  # The largest text column available in all supported RDBMS is\n  # 1024^3 - 1 bytes, roughly one gibibyte.  We specify a size\n  # so that MySQL will use `longtext` instead of `text`.  Otherwise,\n  # when serializing very large objects, `text` might not be big enough.\n  TEXT_BYTES = 1_073_741_823\n\n  def change\n    create_table :versions do |t|\n      t.string :item_type, {null: false}\n      t.integer :item_id, null: false, limit: 8\n      t.string :event, null: false\n      t.string :whodunnit\n      t.text :object, limit: TEXT_BYTES\n\n      # Known issue in MySQL: fractional second precision\n      # -------------------------------------------------\n      #\n      # MySQL timestamp columns do not support fractional seconds unless\n      # defined with \"fractional seconds precision\". MySQL users should manually\n      # add fractional seconds precision to this migration, specifically, to\n      # the `created_at` column.\n      # (https://dev.mysql.com/doc/refman/5.6/en/fractional-seconds.html)\n      #\n      # MySQL users should also upgrade to at least rails 4.2, which is the first\n      # version of ActiveRecord with support for fractional seconds in MySQL.\n      # (https://github.com/rails/rails/pull/14359)\n      #\n      t.datetime :created_at\n    end\n    add_index :versions, %i[item_type item_id]\n  end\nend\n"
  },
  {
    "path": "db/migrate/20200329095031_create_casa_orgs.rb",
    "content": "class CreateCasaOrgs < ActiveRecord::Migration[6.0]\n  def change\n    create_table :casa_orgs do |t|\n      t.string :name, null: false\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20200329095154_add_casa_org_to_user.rb",
    "content": "class AddCasaOrgToUser < ActiveRecord::Migration[6.0]\n  def change\n    add_reference :users, :casa_org, foreign_key: true, null: false\n  end\nend\n"
  },
  {
    "path": "db/migrate/20200329102102_devise_create_all_casa_admins.rb",
    "content": "# frozen_string_literal: true\n\nclass DeviseCreateAllCasaAdmins < ActiveRecord::Migration[6.0]\n  def change\n    create_table :all_casa_admins do |t|\n      ## Database authenticatable\n      t.string :email, null: false, default: \"\"\n      t.string :encrypted_password, null: false, default: \"\"\n\n      ## Recoverable\n      t.string :reset_password_token\n      t.datetime :reset_password_sent_at\n\n      ## Rememberable\n      t.datetime :remember_created_at\n\n      ## Trackable\n      # t.integer  :sign_in_count, default: 0, null: false\n      # t.datetime :current_sign_in_at\n      # t.datetime :last_sign_in_at\n      # t.inet     :current_sign_in_ip\n      # t.inet     :last_sign_in_ip\n\n      ## Confirmable\n      # t.string   :confirmation_token\n      # t.datetime :confirmed_at\n      # t.datetime :confirmation_sent_at\n      # t.string   :unconfirmed_email # Only if using reconfirmable\n\n      ## Lockable\n      # t.integer  :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts\n      # t.string   :unlock_token # Only if unlock strategy is :email or :both\n      # t.datetime :locked_at\n\n      t.timestamps null: false\n    end\n\n    add_index :all_casa_admins, :email, unique: true\n    add_index :all_casa_admins, :reset_password_token, unique: true\n    # add_index :all_casa_admins, :confirmation_token,   unique: true\n    # add_index :all_casa_admins, :unlock_token,         unique: true\n  end\nend\n"
  },
  {
    "path": "db/migrate/20200329175337_create_case_contacts.rb",
    "content": "class CreateCaseContacts < ActiveRecord::Migration[6.0]\n  def change\n    create_table :case_contacts do |t|\n      t.references :creator, foreign_key: {to_table: :users}, null: false\n      t.references :casa_case, null: false, foreign_key: true\n      t.string :contact_type, null: false\n      t.string :other_type_text, null: true\n      t.integer :duration_minutes, null: false\n      t.datetime :occurred_at, null: false\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20200330231711_add_volunteer_reference_to_casa_cases.rb",
    "content": "class AddVolunteerReferenceToCasaCases < ActiveRecord::Migration[6.0]\n  def change\n    add_reference :casa_cases, :volunteer\n  end\nend\n"
  },
  {
    "path": "db/migrate/20200405112910_remove_volunteer_id_from_casa_case.rb",
    "content": "class RemoveVolunteerIdFromCasaCase < ActiveRecord::Migration[6.0]\n  def change\n    remove_reference :casa_cases, :volunteer\n  end\nend\n"
  },
  {
    "path": "db/migrate/20200420004403_add_medium_type_and_contact_made_to_case_contacts.rb",
    "content": "class AddMediumTypeAndContactMadeToCaseContacts < ActiveRecord::Migration[6.0]\n  def change\n    add_column :case_contacts, :contact_made, :boolean, default: false\n    add_column :case_contacts, :medium_type, :string\n  end\nend\n"
  },
  {
    "path": "db/migrate/20200422180727_replace_contact_type_with_contact_types_on_case_contact.rb",
    "content": "class ReplaceContactTypeWithContactTypesOnCaseContact < ActiveRecord::Migration[6.0]\n  def change\n    # NOTE: This is a destructive migration that we would normally avoid\n    #       if we were working on production data, but because there is\n    #       no production data we are comfortable being destructive and\n    #       losting whatever data is in the `contact_type` column.\n    remove_column :case_contacts, :contact_type, :string\n    add_column :case_contacts, :contact_types, :string, array: true\n    # By default, indexes in postgresql are full-value indexes.\n    # However, when you have fields that hold multiple values, such as enums\n    # or jsonb, you want to rely on a full-text search index type.\n    # gin indexes are a full-text index type that works well in this context.\n    # You can read more at the official PostgreSQL docs:\n    # https://www.postgresql.org/docs/current/textsearch-indexes.html\n    add_index :case_contacts, :contact_types, using: :gin\n  end\nend\n"
  },
  {
    "path": "db/migrate/20200423154018_change_teen_program_to_transition_aged_youth.rb",
    "content": "class ChangeTeenProgramToTransitionAgedYouth < ActiveRecord::Migration[6.0]\n  def change\n    rename_column :casa_cases, :teen_program_eligible, :transition_aged_youth\n  end\nend\n"
  },
  {
    "path": "db/migrate/20200423204147_add_name_to_user.rb",
    "content": "class AddNameToUser < ActiveRecord::Migration[6.0]\n  def change\n    add_column :users, :display_name, :string, default: \"\"\n  end\nend\n"
  },
  {
    "path": "db/migrate/20200525220759_add_driving_fields_to_case_contact.rb",
    "content": "class AddDrivingFieldsToCaseContact < ActiveRecord::Migration[6.0]\n  def up\n    add_column :case_contacts, :miles_driven, :integer, null: true\n    add_column :case_contacts, :want_driving_reimbursement, :boolean, default: false\n    execute <<-SQL\n      ALTER TABLE case_contacts\n        ADD CONSTRAINT want_driving_reimbursement_only_when_miles_driven\n        CHECK ((miles_driven IS NOT NULL) OR (NOT want_driving_reimbursement));\n    SQL\n  end\n\n  def down\n    execute <<-SQL\n      ALTER TABLE case_contacts\n        DROP CONSTRAINT want_driving_reimbursement_only_when_miles_driven\n    SQL\n    remove_column :case_contacts, :miles_driven, :integer\n    remove_column :case_contacts, :want_driving_reimbursement, :boolean\n  end\nend\n"
  },
  {
    "path": "db/migrate/20200726185103_devise_invitable_add_to_users.rb",
    "content": "class DeviseInvitableAddToUsers < ActiveRecord::Migration[6.0]\n  def up\n    change_table :users do |t|\n      t.string :invitation_token\n      t.datetime :invitation_created_at\n      t.datetime :invitation_sent_at\n      t.datetime :invitation_accepted_at\n      t.integer :invitation_limit\n      t.references :invited_by, polymorphic: true\n      t.integer :invitations_count, default: 0\n      t.index :invitations_count\n      t.index :invitation_token, unique: true # for invitable\n      t.index :invited_by_id\n    end\n  end\n\n  def down\n    change_table :users do |t|\n      t.remove_references :invited_by, polymorphic: true\n      t.remove :invitations_count, :invitation_limit, :invitation_sent_at, :invitation_accepted_at, :invitation_token, :invitation_created_at\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20200729002247_add_casa_org_to_casa_case.rb",
    "content": "class AddCasaOrgToCasaCase < ActiveRecord::Migration[6.0]\n  def change\n    add_reference :casa_cases, :casa_org, null: false, foreign_key: true, index: true\n  end\nend\n"
  },
  {
    "path": "db/migrate/20200801170524_add_type_to_user.rb",
    "content": "class User < ApplicationRecord\n  self.table_name = \"users\"\nend\n\nclass AddTypeToUser < ActiveRecord::Migration[6.0]\n  def up\n    add_column :users, :type, :string\n    add_column :users, :active, :boolean, default: true\n\n    # Set supervisor\n    User.where(role: \"supervisor\").update_all(type: \"Supervisor\")\n\n    # Set volunteers\n    User.where(role: \"volunteer\").update_all(type: \"Volunteer\")\n    User.where(role: \"inactive\").update_all(type: \"Volunteer\", active: false)\n\n    # Set casa_admins\n    User.where(role: \"casa_admin\").update_all(type: \"CasaAdmin\")\n  end\n\n  def down\n    drop_column :users, :type\n  end\nend\n"
  },
  {
    "path": "db/migrate/20200801192923_remove_role_from_user.rb",
    "content": "class RemoveRoleFromUser < ActiveRecord::Migration[6.0]\n  def change\n    remove_column :users, :role, :string, null: false, default: \"volunteer\"\n  end\nend\n"
  },
  {
    "path": "db/migrate/20200818220659_add_is_active_to_supervisor_volunteers.rb",
    "content": "class AddIsActiveToSupervisorVolunteers < ActiveRecord::Migration[6.0]\n  def change\n    add_column :supervisor_volunteers, :is_active, :boolean, default: true\n  end\nend\n"
  },
  {
    "path": "db/migrate/20200830011647_add_notes_to_case_contacts.rb",
    "content": "class AddNotesToCaseContacts < ActiveRecord::Migration[6.0]\n  def change\n    add_column :case_contacts, :notes, :string\n  end\nend\n"
  },
  {
    "path": "db/migrate/20200905192934_remove_other_type_text_from_case_contacts.rb",
    "content": "class RemoveOtherTypeTextFromCaseContacts < ActiveRecord::Migration[6.0]\n  def change\n    remove_column :case_contacts, :other_type_text, :string\n  end\nend\n"
  },
  {
    "path": "db/migrate/20200906145045_add_display_name_to_casa_orgs.rb",
    "content": "class AddDisplayNameToCasaOrgs < ActiveRecord::Migration[6.0]\n  def change\n    add_column :casa_orgs, :display_name, :string\n  end\nend\n"
  },
  {
    "path": "db/migrate/20200906145725_add_address_to_casa_orgs.rb",
    "content": "class AddAddressToCasaOrgs < ActiveRecord::Migration[6.0]\n  def change\n    add_column :casa_orgs, :address, :string\n  end\nend\n"
  },
  {
    "path": "db/migrate/20200906145830_add_footer_links_to_casa_orgs.rb",
    "content": "class AddFooterLinksToCasaOrgs < ActiveRecord::Migration[6.0]\n  def change\n    add_column :casa_orgs, :footer_links, :string, array: true, default: []\n  end\nend\n"
  },
  {
    "path": "db/migrate/20200906150641_create_casa_org_logos.rb",
    "content": "class CreateCasaOrgLogos < ActiveRecord::Migration[6.0]\n  def change\n    create_table :casa_org_logos do |t|\n      t.references :casa_org, null: false, foreign_key: true\n      t.string :url\n      t.string :alt_text\n      t.string :size\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20200906184455_add_banner_color_to_casa_org_logos.rb",
    "content": "class AddBannerColorToCasaOrgLogos < ActiveRecord::Migration[6.0]\n  def change\n    add_column :casa_org_logos, :banner_color, :string\n  end\nend\n"
  },
  {
    "path": "db/migrate/20200907142411_create_task_records.rb",
    "content": "class CreateTaskRecords < ActiveRecord::Migration[6.0]\n  def change\n    create_table :task_records, id: false do |t|\n      t.string :version, null: false\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20200917175655_add_default_value_to_case_contact_miles_driven.rb",
    "content": "class AddDefaultValueToCaseContactMilesDriven < ActiveRecord::Migration[6.0]\n  def up\n    change_column :case_contacts, :miles_driven, :integer, default: 0\n  end\n\n  def down\n    change_column :case_contacts, :miles_driven, :integer, default: nil\n  end\nend\n"
  },
  {
    "path": "db/migrate/20200918115741_set_miles_driven_as_not_nullable.rb",
    "content": "class SetMilesDrivenAsNotNullable < ActiveRecord::Migration[6.0]\n  def up\n    change_column :case_contacts, :miles_driven, :integer, null: false\n  end\n\n  def down\n    change_column :case_contacts, :miles_driven, :integer, null: true\n  end\nend\n"
  },
  {
    "path": "db/migrate/20200922144730_add_birth_month_year_youth_to_casa_cases.rb",
    "content": "class AddBirthMonthYearYouthToCasaCases < ActiveRecord::Migration[6.0]\n  def change\n    add_column :casa_cases, :birth_month_year_youth, :datetime\n  end\nend\n"
  },
  {
    "path": "db/migrate/20200922150754_create_contact_type.rb",
    "content": "class CreateContactType < ActiveRecord::Migration[6.0]\n  def change\n    create_table :contact_type_groups do |t|\n      t.references :casa_org, null: false\n      t.string :name, null: false\n      t.timestamps\n    end\n\n    create_table :contact_types do |t|\n      t.references :contact_type_group, null: false\n      t.string :name, null: false\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20200922170308_link_case_contacts_to_contact_types.rb",
    "content": "class LinkCaseContactsToContactTypes < ActiveRecord::Migration[6.0]\n  def change\n    create_table :case_contact_contact_types do |t|\n      t.references :case_contact, null: false\n      t.references :contact_type, null: false\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20200924192310_create_casa_case_contact_type.rb",
    "content": "class CreateCasaCaseContactType < ActiveRecord::Migration[6.0]\n  def change\n    create_table :casa_case_contact_types do |t|\n      t.references :contact_type, null: false\n      t.references :casa_case, null: false\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20200925180941_add_active_to_contact_type_groups.rb",
    "content": "class AddActiveToContactTypeGroups < ActiveRecord::Migration[6.0]\n  def change\n    add_column :contact_type_groups, :active, :boolean, default: true\n  end\nend\n"
  },
  {
    "path": "db/migrate/20200925181042_add_active_to_contact_types.rb",
    "content": "class AddActiveToContactTypes < ActiveRecord::Migration[6.0]\n  def change\n    add_column :contact_types, :active, :boolean, default: true\n  end\nend\n"
  },
  {
    "path": "db/migrate/20200928233606_add_court_report_submitted_to_casa_cases.rb",
    "content": "class AddCourtReportSubmittedToCasaCases < ActiveRecord::Migration[6.0]\n  def change\n    add_column :casa_cases, :court_report_submitted, :boolean, default: false, null: false\n  end\nend\n"
  },
  {
    "path": "db/migrate/20201002192636_add_court_date_to_casa_cases.rb",
    "content": "class AddCourtDateToCasaCases < ActiveRecord::Migration[6.0]\n  def change\n    add_column :casa_cases, :court_date, :datetime\n  end\nend\n"
  },
  {
    "path": "db/migrate/20201004165322_add_court_report_due_date_to_casa_cases.rb",
    "content": "class AddCourtReportDueDateToCasaCases < ActiveRecord::Migration[6.0]\n  def change\n    add_column :casa_cases, :court_report_due_date, :datetime\n  end\nend\n"
  },
  {
    "path": "db/migrate/20201005191326_create_hearing_types.rb",
    "content": "class CreateHearingTypes < ActiveRecord::Migration[6.0]\n  def change\n    create_table :hearing_types do |t|\n      t.references :casa_org, null: false\n      t.string :name, null: false\n      t.boolean :active, null: false, default: true\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20201013171632_remove_contact_types_from_case_contacts.rb",
    "content": "class RemoveContactTypesFromCaseContacts < ActiveRecord::Migration[6.0]\n  def change\n    # case_contacts.contact_types has been deprecated in favor of case_contact.case_contact_contact_type\n    remove_column :case_contacts, :contact_types, :string, array: true, default: []\n  end\nend\n"
  },
  {
    "path": "db/migrate/20201019120548_create_active_storage_tables.active_storage.rb",
    "content": "# This migration comes from active_storage (originally 20170806125915)\nclass CreateActiveStorageTables < ActiveRecord::Migration[5.2]\n  def change\n    create_table :active_storage_blobs do |t|\n      t.string :key, null: false\n      t.string :filename, null: false\n      t.string :content_type\n      t.text :metadata\n      t.bigint :byte_size, null: false\n      t.string :checksum, null: false\n      t.datetime :created_at, null: false\n\n      t.index [:key], unique: true\n    end\n\n    create_table :active_storage_attachments do |t|\n      t.string :name, null: false\n      t.references :record, null: false, polymorphic: true, index: false\n      t.references :blob, null: false\n\n      t.datetime :created_at, null: false\n\n      t.index [:record_type, :record_id, :name, :blob_id], name: \"index_active_storage_attachments_uniqueness\", unique: true\n      t.foreign_key :active_storage_blobs, column: :blob_id\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20201020095451_add_hearing_type_to_court_cases.rb",
    "content": "class AddHearingTypeToCourtCases < ActiveRecord::Migration[6.0]\n  def change\n    add_reference :casa_cases, :hearing_type, null: true\n  end\nend\n"
  },
  {
    "path": "db/migrate/20201020220412_create_emancipation_categories.rb",
    "content": "class CreateEmancipationCategories < ActiveRecord::Migration[6.0]\n  def change\n    create_table :emancipation_categories do |t|\n      t.string :name, null: false, index: {unique: true}\n      t.boolean :mutually_exclusive, null: false\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20201021024459_create_emancipation_options.rb",
    "content": "class CreateEmancipationOptions < ActiveRecord::Migration[6.0]\n  def change\n    create_table :emancipation_options do |t|\n      t.references :emancipation_category, null: false, foreign_key: true\n      t.string :name, null: false\n      t.index [:emancipation_category_id, :name], unique: true\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20201021034012_create_join_table_casa_cases_emancipaton_options.rb",
    "content": "class CreateJoinTableCasaCasesEmancipatonOptions < ActiveRecord::Migration[6.0]\n  def change\n    create_join_table :casa_cases, :emancipation_options do |t|\n      # Manually naming the index here because the autogenerated name has an illegal length\n      t.index [:casa_case_id, :emancipation_option_id], name: \"index_cases_options_on_case_id_and_option_id\", unique: true\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20201021143642_add_active_column_to_casa_cases_table.rb",
    "content": "class AddActiveColumnToCasaCasesTable < ActiveRecord::Migration[6.0]\n  def change\n    add_column :casa_cases, :active, :boolean, default: true, null: false\n  end\nend\n"
  },
  {
    "path": "db/migrate/20201022034445_add_foreign_key_constraints_to_case_emancipation_join_table.rb",
    "content": "class AddForeignKeyConstraintsToCaseEmancipationJoinTable < ActiveRecord::Migration[6.0]\n  def change\n    add_foreign_key :casa_cases_emancipation_options, :casa_cases\n    add_foreign_key :casa_cases_emancipation_options, :emancipation_options\n  end\nend\n"
  },
  {
    "path": "db/migrate/20201023233638_create_judges.rb",
    "content": "class CreateJudges < ActiveRecord::Migration[6.0]\n  def change\n    create_table :judges do |t|\n      t.references :casa_org, null: false, foreign_key: true\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20201023234325_add_active_to_judge.rb",
    "content": "class AddActiveToJudge < ActiveRecord::Migration[6.0]\n  def change\n    add_column :judges, :active, :boolean, default: true\n  end\nend\n"
  },
  {
    "path": "db/migrate/20201024003821_add_name_to_judge.rb",
    "content": "class AddNameToJudge < ActiveRecord::Migration[6.0]\n  def change\n    add_column :judges, :name, :string\n  end\nend\n"
  },
  {
    "path": "db/migrate/20201024113046_create_past_court_dates.rb",
    "content": "class CreatePastCourtDates < ActiveRecord::Migration[6.0]\n  def change\n    create_table :past_court_dates do |t|\n      t.datetime :date, null: false\n      t.references :casa_case, null: false, foreign_key: true\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20201025162142_add_judge_to_court_cases.rb",
    "content": "class AddJudgeToCourtCases < ActiveRecord::Migration[6.0]\n  def change\n    add_reference :casa_cases, :judge, null: true\n  end\nend\n"
  },
  {
    "path": "db/migrate/20201108142333_add_court_report_status_to_casa_cases.rb",
    "content": "class AddCourtReportStatusToCasaCases < ActiveRecord::Migration[6.0]\n  def change\n    add_column :casa_cases, :court_report_submitted_at, :datetime\n    add_column :casa_cases, :court_report_status, :integer, default: 0, not_null: true\n  end\nend\n"
  },
  {
    "path": "db/migrate/20201120103041_remove_remember_created_at_from_users.rb",
    "content": "class RemoveRememberCreatedAtFromUsers < ActiveRecord::Migration[6.0]\n  def change\n    remove_column :users, :remember_created_at, :datetime\n  end\nend\n"
  },
  {
    "path": "db/migrate/20201120103146_remove_remember_created_at_from_all_casa_admins.rb",
    "content": "class RemoveRememberCreatedAtFromAllCasaAdmins < ActiveRecord::Migration[6.0]\n  def change\n    remove_column :all_casa_admins, :remember_created_at, :datetime\n  end\nend\n"
  },
  {
    "path": "db/migrate/20201120215756_remove_court_report_submitted_from_casa_cases.rb",
    "content": "class RemoveCourtReportSubmittedFromCasaCases < ActiveRecord::Migration[6.0]\n  def change\n    remove_column :casa_cases, :court_report_submitted, :boolean\n  end\nend\n"
  },
  {
    "path": "db/migrate/20201123100716_remove_case_number_index_from_casa_cases.rb",
    "content": "class RemoveCaseNumberIndexFromCasaCases < ActiveRecord::Migration[6.0]\n  def change\n    remove_index :casa_cases, column: :case_number, unique: true\n  end\nend\n"
  },
  {
    "path": "db/migrate/20201123112651_add_case_number_index_scoped_by_casa_org_to_casa_cases.rb",
    "content": "class AddCaseNumberIndexScopedByCasaOrgToCasaCases < ActiveRecord::Migration[6.0]\n  def change\n    add_index :casa_cases, [:case_number, :casa_org_id], unique: true\n  end\nend\n"
  },
  {
    "path": "db/migrate/20201222125441_add_service_name_to_active_storage_blobs.active_storage.rb",
    "content": "# This migration comes from active_storage (originally 20190112182829)\nclass AddServiceNameToActiveStorageBlobs < ActiveRecord::Migration[6.0]\n  def up\n    unless column_exists?(:active_storage_blobs, :service_name)\n      add_column :active_storage_blobs, :service_name, :string\n\n      if (configured_service = ActiveStorage::Blob.service.name)\n        ActiveStorage::Blob.unscoped.update_all(service_name: configured_service)\n      end\n\n      change_column :active_storage_blobs, :service_name, :string, null: false\n    end\n  end\n\n  def down\n    remove_column :active_storage_blobs, :service_name\n  end\nend\n"
  },
  {
    "path": "db/migrate/20201222125442_create_active_storage_variant_records.active_storage.rb",
    "content": "# This migration comes from active_storage (originally 20191206030411)\nclass CreateActiveStorageVariantRecords < ActiveRecord::Migration[6.0]\n  def change\n    create_table :active_storage_variant_records do |t|\n      t.belongs_to :blob, null: false, index: false\n      t.string :variation_digest, null: false\n\n      t.index %i[blob_id variation_digest], name: \"index_active_storage_variant_records_uniqueness\", unique: true\n      t.foreign_key :active_storage_blobs, column: :blob_id\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20201226024029_create_casa_case_emancipation_categories.rb",
    "content": "class CreateCasaCaseEmancipationCategories < ActiveRecord::Migration[6.1]\n  def change\n    create_table :casa_case_emancipation_categories do |t|\n      t.references :casa_case, null: false, foreign_key: true\n      t.references :emancipation_category, null: false, foreign_key: true, index: {name: \"index_case_emancipation_categories_on_emancipation_category_id\"}\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20210105155534_create_followups.rb",
    "content": "class CreateFollowups < ActiveRecord::Migration[6.1]\n  def change\n    create_table :followups do |t|\n      t.belongs_to :case_contact\n      t.belongs_to :creator, foreign_key: {to_table: :users}\n      t.integer :status, default: 0, not_null: true\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20210107181908_add_id_and_timestamps_to_case_emancipation_options_table.rb",
    "content": "class AddIdAndTimestampsToCaseEmancipationOptionsTable < ActiveRecord::Migration[6.1]\n  def change\n    add_column :casa_cases_emancipation_options, :id, :primary_key\n    add_timestamps :casa_cases_emancipation_options, default: Time.zone.now\n    change_column_default :casa_cases_emancipation_options, :created_at, nil\n    change_column_default :casa_cases_emancipation_options, :updated_at, nil\n  end\nend\n"
  },
  {
    "path": "db/migrate/20210109231411_drop_casa_org_logos_table.rb",
    "content": "class DropCasaOrgLogosTable < ActiveRecord::Migration[6.1]\n  def up\n    drop_table :casa_org_logos\n  end\n\n  def down\n    fail ActiveRecord::IrreversibleMigration\n  end\nend\n"
  },
  {
    "path": "db/migrate/20210117185614_create_notifications.rb",
    "content": "class CreateNotifications < ActiveRecord::Migration[6.1]\n  def change\n    create_table :notifications do |t|\n      t.references :recipient, polymorphic: true, null: false\n      t.string :type, null: false\n      t.jsonb :params\n      t.datetime :read_at\n\n      t.timestamps\n    end\n    add_index :notifications, :read_at\n  end\nend\n"
  },
  {
    "path": "db/migrate/20210223133248_create_case_court_mandates.rb",
    "content": "class CreateCaseCourtMandates < ActiveRecord::Migration[6.1]\n  def change\n    create_table :case_court_mandates do |t|\n      t.string :mandate_text\n      t.references :casa_case, foreign_key: true, null: false\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20210308195135_change_is_active_name.rb",
    "content": "class ChangeIsActiveName < ActiveRecord::Migration[6.1]\n  def change\n    rename_column :case_assignments, :is_active, :active\n  end\nend\n"
  },
  {
    "path": "db/migrate/20210330182538_add_devise_trackable_columns_to_users.rb",
    "content": "class AddDeviseTrackableColumnsToUsers < ActiveRecord::Migration[6.1]\n  def change\n    add_column :users, :sign_in_count, :integer, default: 0, null: false\n    add_column :users, :current_sign_in_at, :datetime\n    add_column :users, :last_sign_in_at, :datetime\n    add_column :users, :current_sign_in_ip, :string\n    add_column :users, :last_sign_in_ip, :string\n  end\nend\n"
  },
  {
    "path": "db/migrate/20210401161710_add_deleted_at_to_case_contacts.rb",
    "content": "class AddDeletedAtToCaseContacts < ActiveRecord::Migration[6.1]\n  def change\n    add_column :case_contacts, :deleted_at, :datetime\n    add_index :case_contacts, :deleted_at\n  end\nend\n"
  },
  {
    "path": "db/migrate/20210401182359_add_implementation_status_to_edit_case_court_mandates.rb",
    "content": "class AddImplementationStatusToEditCaseCourtMandates < ActiveRecord::Migration[6.1]\n  def change\n    add_column :case_court_mandates, :implementation_status, :integer\n  end\nend\n"
  },
  {
    "path": "db/migrate/20210502172706_add_devise_invitable_to_all_casa_admins.rb",
    "content": "class AddDeviseInvitableToAllCasaAdmins < ActiveRecord::Migration[6.1]\n  def change\n    add_column :all_casa_admins, :invitation_token, :string\n    add_column :all_casa_admins, :invitation_created_at, :datetime\n    add_column :all_casa_admins, :invitation_sent_at, :datetime\n    add_column :all_casa_admins, :invitation_accepted_at, :datetime\n    add_column :all_casa_admins, :invitation_limit, :integer\n    add_column :all_casa_admins, :invited_by_id, :integer\n    add_column :all_casa_admins, :invited_by_type, :string\n    add_index :all_casa_admins, :invitation_token, unique: true\n  end\nend\n"
  },
  {
    "path": "db/migrate/20210521151549_add_casa_case_details_to_past_court_dates.rb",
    "content": "class AddCasaCaseDetailsToPastCourtDates < ActiveRecord::Migration[6.1]\n  def change\n    add_reference :past_court_dates, :hearing_type, null: true\n    add_reference :past_court_dates, :judge, null: true\n  end\nend\n"
  },
  {
    "path": "db/migrate/20210521194321_add_past_court_date_to_case_court_mandates.rb",
    "content": "class AddPastCourtDateToCaseCourtMandates < ActiveRecord::Migration[6.1]\n  def change\n    add_reference :case_court_mandates, :past_court_date, null: true\n  end\nend\n"
  },
  {
    "path": "db/migrate/20210526233058_create_sent_emails.rb",
    "content": "class CreateSentEmails < ActiveRecord::Migration[6.1]\n  def change\n    create_table :sent_emails do |t|\n      t.belongs_to :user, index: true, foreign_key: true\n      t.references :casa_org, null: false, foreign_key: true\n      t.string :mailer_type\n      t.string :category\n      t.string :sent_address\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20210624125750_add_note_to_followups.rb",
    "content": "class AddNoteToFollowups < ActiveRecord::Migration[6.1]\n  def change\n    add_column :followups, :note, :text\n  end\nend\n"
  },
  {
    "path": "db/migrate/20210913142024_set_display_name_as_not_nullable.rb",
    "content": "class SetDisplayNameAsNotNullable < ActiveRecord::Migration[6.1]\n  def up\n    change_column :users, :display_name, :string, null: false\n  end\n\n  def down\n    change_column :users, :display_name, :string, null: true\n  end\nend\n"
  },
  {
    "path": "db/migrate/20210925140028_add_slug_to_casa_orgs_and_casa_cases.rb",
    "content": "class AddSlugToCasaOrgsAndCasaCases < ActiveRecord::Migration[6.1]\n  def change\n    add_column :casa_cases, :slug, :string\n    add_column :casa_orgs, :slug, :string\n    add_index :casa_cases, :slug\n    add_index :casa_orgs, :slug, unique: true\n  end\nend\n"
  },
  {
    "path": "db/migrate/20211001204053_rename_court_mandates_to_court_orders.rb",
    "content": "class RenameCourtMandatesToCourtOrders < ActiveRecord::Migration[6.1]\n  def change\n    rename_table :case_court_mandates, :case_court_orders\n  end\nend\n"
  },
  {
    "path": "db/migrate/20211007144114_add_show_driving_reimbursement_to_casa_orgs.rb",
    "content": "class AddShowDrivingReimbursementToCasaOrgs < ActiveRecord::Migration[6.1]\n  def change\n    add_column :casa_orgs, :show_driving_reimbursement, :boolean, default: true\n  end\nend\n"
  },
  {
    "path": "db/migrate/20211008170357_add_text_to_case_court_orders.rb",
    "content": "class AddTextToCaseCourtOrders < ActiveRecord::Migration[6.1]\n  def change\n    add_column :case_court_orders, :text, :string\n  end\nend\n"
  },
  {
    "path": "db/migrate/20211008170724_migrate_case_court_orders_mandate_text_to_text.rb",
    "content": "class MigrateCaseCourtOrdersMandateTextToText < ActiveRecord::Migration[6.1]\n  def up\n    execute \"update case_court_orders set text=mandate_text\"\n  end\nend\n"
  },
  {
    "path": "db/migrate/20211008174527_remove_mandate_text_from_case_court_orders.rb",
    "content": "class RemoveMandateTextFromCaseCourtOrders < ActiveRecord::Migration[6.1]\n  def change\n    remove_column :case_court_orders, :mandate_text, :string\n  end\nend\n"
  },
  {
    "path": "db/migrate/20211011195857_rename_past_court_date_to_court_date.rb",
    "content": "class RenamePastCourtDateToCourtDate < ActiveRecord::Migration[6.1]\n  def change\n    rename_table :past_court_dates, :court_dates\n    rename_column :case_court_orders, :past_court_date_id, :court_date_id\n  end\nend\n"
  },
  {
    "path": "db/migrate/20211012180102_change_casa_cases_court_date_to_reference.rb",
    "content": "class ChangeCasaCasesCourtDateToReference < ActiveRecord::Migration[6.1]\n  def up\n    CasaCase.find_each do |casa_case|\n      CourtDate.create(\n        date: casa_case.court_date,\n        casa_case: casa_case,\n        hearing_type: casa_case.hearing_type,\n        judge: casa_case.judge,\n        case_court_orders: casa_case.case_court_orders\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20211023165907_add_reimbursement_complete_to_case_contacts.rb",
    "content": "class AddReimbursementCompleteToCaseContacts < ActiveRecord::Migration[6.1]\n  def change\n    add_column :case_contacts, :reimbursement_complete, :boolean, default: false\n  end\nend\n"
  },
  {
    "path": "db/migrate/20211024011923_create_mileage_rates.rb",
    "content": "class CreateMileageRates < ActiveRecord::Migration[6.1]\n  def change\n    create_table :mileage_rates do |t|\n      t.decimal :amount\n      t.date :effective_date\n      t.boolean :is_active, default: true\n      t.references :user, null: false, foreign_key: true\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20211024060815_create_preference_sets.rb",
    "content": "class CreatePreferenceSets < ActiveRecord::Migration[6.1]\n  def change\n    create_table :preference_sets do |t|\n      t.references :user, index: true, foreign_key: true\n      t.jsonb :case_volunteer_columns, null: false, default: \"{}\"\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20211025143709_create_healths.rb",
    "content": "class CreateHealths < ActiveRecord::Migration[6.1]\n  def change\n    create_table :healths do |t|\n      t.timestamp :latest_deploy_time\n      t.integer :singleton_guard\n\n      t.timestamps\n    end\n\n    add_index(:healths, :singleton_guard, unique: true)\n  end\nend\n"
  },
  {
    "path": "db/migrate/20211029032305_add_casa_org_to_mileage_rate.rb",
    "content": "class AddCasaOrgToMileageRate < ActiveRecord::Migration[6.1]\n  def change\n    # null false is dangerous if there are any in the db already! There aren't tho\n    add_column :mileage_rates, :casa_org_id, :bigint, null: false\n    add_index :mileage_rates, :casa_org_id\n  end\nend\n"
  },
  {
    "path": "db/migrate/20211029033530_remove_user_required_from_mileage_rate.rb",
    "content": "class RemoveUserRequiredFromMileageRate < ActiveRecord::Migration[6.1]\n  def change\n    change_column_null :mileage_rates, :user_id, true\n  end\nend\n"
  },
  {
    "path": "db/migrate/20211203181342_create_additional_expenses.rb",
    "content": "class CreateAdditionalExpenses < ActiveRecord::Migration[6.1]\n  def change\n    create_table :additional_expenses do |t|\n      t.references :case_contact, null: false, foreign_key: true\n      t.decimal \"other_expense_amount\"\n      t.string \"other_expenses_describe\"\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20211230033457_create_delayed_jobs.rb",
    "content": "class CreateDelayedJobs < ActiveRecord::Migration[6.1]\n  def self.up\n    create_table :delayed_jobs do |table|\n      table.integer :priority, default: 0, null: false # Allows some jobs to jump to the front of the queue\n      table.integer :attempts, default: 0, null: false # Provides for retries, but still fail eventually.\n      table.text :handler, null: false # YAML-encoded string of the object that will do work\n      table.text :last_error # reason for last failure (See Note below)\n      table.datetime :run_at # When to run. Could be Time.zone.now for immediately, or sometime in the future.\n      table.datetime :locked_at # Set when a client is working on this object\n      table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead)\n      table.string :locked_by # Who is working on this object (if locked)\n      table.string :queue # The name of the queue this job is in\n      table.timestamps null: true\n    end\n\n    add_index :delayed_jobs, [:priority, :run_at], name: \"delayed_jobs_priority\"\n  end\n\n  def self.down\n    drop_table :delayed_jobs\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220105030922_create_feature_flags.rb",
    "content": "class CreateFeatureFlags < ActiveRecord::Migration[6.1]\n  def change\n    create_table :feature_flags do |t|\n      t.string :name, null: false\n      t.boolean :enabled, null: false, default: false\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220127055733_create_notes.rb",
    "content": "class CreateNotes < ActiveRecord::Migration[6.1]\n  def change\n    create_table :notes do |t|\n      t.string :content\n      t.bigint :creator_id\n      t.references :notable, polymorphic: true\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220223035901_remove_null_check_deprecated_field.rb",
    "content": "class RemoveNullCheckDeprecatedField < ActiveRecord::Migration[6.1]\n  def change\n    change_column_null :casa_cases, :transition_aged_youth, true\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220226040507_create_fund_request.rb",
    "content": "class CreateFundRequest < ActiveRecord::Migration[6.1]\n  def change\n    create_table :fund_requests do |t|\n      t.text :submitter_email\n      t.text :youth_name\n      t.text :payment_amount\n      t.text :deadline\n      t.text :request_purpose\n      t.text :payee_name\n      t.text :requested_by_and_relationship\n      t.text :other_funding_source_sought\n      t.text :impact\n      t.text :extra_information\n      t.text :timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220303183053_create_other_duty.rb",
    "content": "class CreateOtherDuty < ActiveRecord::Migration[6.1]\n  def change\n    create_table :other_duties do |t|\n      t.bigint :creator_id, null: false\n      t.string :creator_type\n      t.datetime :occurred_at\n      t.bigint :duration_minutes\n      t.text :notes\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220323145733_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb",
    "content": "# This migration comes from active_storage (originally 20211119233751)\nclass RemoveNotNullOnActiveStorageBlobsChecksum < ActiveRecord::Migration[6.0]\n  def change\n    change_column_null(:active_storage_blobs, :checksum, true)\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220324141758_create_learning_hours.rb",
    "content": "class CreateLearningHours < ActiveRecord::Migration[6.1]\n  def change\n    create_table :learning_hours do |t|\n      t.references :user, null: false, foreign_key: true\n      t.integer :learning_type, default: 5, not_null: true\n      t.string :name, null: false\n      t.integer :duration_minutes, null: false\n      t.integer :duration_hours, null: false\n      t.datetime :occurred_at, null: false\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220402201247_add_phone_number_to_users.rb",
    "content": "class AddPhoneNumberToUsers < ActiveRecord::Migration[7.0]\n  def change\n    add_column :users, :phone_number, :string, default: \"\"\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220406011016_add_sms_notification_preferences_to_users.rb",
    "content": "class AddSmsNotificationPreferencesToUsers < ActiveRecord::Migration[7.0]\n  def change\n    add_column :users, :receive_sms_notifications, :boolean, null: false, default: false\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220406011144_add_email_notification_preferences_to_users.rb",
    "content": "class AddEmailNotificationPreferencesToUsers < ActiveRecord::Migration[7.0]\n  def change\n    add_column :users, :receive_email_notifications, :boolean, default: true\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220409184741_add_fund_request.rb",
    "content": "class AddFundRequest < ActiveRecord::Migration[7.0]\n  def change\n    add_column :casa_orgs, :show_fund_request, :boolean, default: false\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220411180242_add_uniqueness_constraint_to_feature_flag_name.rb",
    "content": "class AddUniquenessConstraintToFeatureFlagName < ActiveRecord::Migration[7.0]\n  disable_ddl_transaction!\n\n  def change\n    add_index :feature_flags, :name, unique: true, algorithm: :concurrently\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220509224425_add_columns_to_casa_orgs.rb",
    "content": "class AddColumnsToCasaOrgs < ActiveRecord::Migration[7.0]\n  def change\n    add_column :casa_orgs, :twilio_phone_number, :string\n    add_column :casa_orgs, :twilio_account_sid, :string\n    add_column :casa_orgs, :twilio_api_key_sid, :string\n    add_column :casa_orgs, :twilio_api_key_secret, :string\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220513084954_add_date_in_care_to_casa_cases.rb",
    "content": "class AddDateInCareToCasaCases < ActiveRecord::Migration[7.0]\n  def change\n    add_column :casa_cases, :date_in_care, :datetime\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220513111133_delete_versions.rb",
    "content": "class DeleteVersions < ActiveRecord::Migration[7.0]\n  def up\n    drop_table :versions\n  end\n\n  # copied from db/migrate/20200329085225_create_versions.rb\n  TEXT_BYTES = 1_073_741_823\n\n  def down\n    create_table :versions do |t|\n      t.string :item_type, {null: false}\n      t.integer :item_id, null: false, limit: 8\n      t.string :event, null: false\n      t.string :whodunnit\n      t.text :object, limit: TEXT_BYTES\n\n      # Known issue in MySQL: fractional second precision\n      # -------------------------------------------------\n      #\n      # MySQL timestamp columns do not support fractional seconds unless\n      # defined with \"fractional seconds precision\". MySQL users should manually\n      # add fractional seconds precision to this migration, specifically, to\n      # the `created_at` column.\n      # (https://dev.mysql.com/doc/refman/5.6/en/fractional-seconds.html)\n      #\n      # MySQL users should also upgrade to at least rails 4.2, which is the first\n      # version of ActiveRecord with support for fractional seconds in MySQL.\n      # (https://github.com/rails/rails/pull/14359)\n      #\n      t.datetime :created_at\n    end\n    add_index :versions, %i[item_type item_id]\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220519210423_create_sms_notification_events.rb",
    "content": "class CreateSmsNotificationEvents < ActiveRecord::Migration[7.0]\n  def change\n    create_table :sms_notification_events do |t|\n      t.string :name\n      t.string :user_type\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220519233803_create_user_sms_notification_events.rb",
    "content": "class CreateUserSmsNotificationEvents < ActiveRecord::Migration[7.0]\n  def change\n    create_table :user_sms_notification_events do |t|\n      t.references :user, null: false, foreign_key: true\n      t.references :sms_notification_event, null: false, foreign_key: true\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220526011848_create_user_reminder_times.rb",
    "content": "class CreateUserReminderTimes < ActiveRecord::Migration[7.0]\n  def change\n    create_table :user_reminder_times do |t|\n      t.belongs_to :user, null: false, foreign_key: true\n      t.datetime :case_contact_types\n      t.datetime :no_contact_made\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220602215632_create_addresses.rb",
    "content": "class CreateAddresses < ActiveRecord::Migration[7.0]\n  def change\n    create_table :addresses do |t|\n      t.string :content\n      t.belongs_to :user, null: false, foreign_key: true\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220607184910_add_hide_old_contacts_to_case_assignment.rb",
    "content": "class AddHideOldContactsToCaseAssignment < ActiveRecord::Migration[7.0]\n  def change\n    add_column :case_assignments, :hide_old_contacts, :boolean, default: false\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220610221701_create_checklist_items.rb",
    "content": "class CreateChecklistItems < ActiveRecord::Migration[7.0]\n  def change\n    create_table :checklist_items do |t|\n      t.integer :hearing_type_id\n      t.text :description, null: false\n      t.string :category, null: false\n      t.boolean :mandatory, default: false, null: false\n      t.timestamps\n    end\n    add_index :checklist_items, :hearing_type_id\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220615015056_create_patch_note_types.rb",
    "content": "class CreatePatchNoteTypes < ActiveRecord::Migration[7.0]\n  def change\n    create_table :patch_note_types do |t|\n      t.string :name, null: false\n      t.index :name, unique: true\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220616021404_add_checklist_updated_date_to_hearing_types.rb",
    "content": "class AddChecklistUpdatedDateToHearingTypes < ActiveRecord::Migration[7.0]\n  def change\n    add_column :hearing_types, :checklist_updated_date, :string, default: \"None\", null: false\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220618042137_create_patch_note_groups.rb",
    "content": "class CreatePatchNoteGroups < ActiveRecord::Migration[7.0]\n  def change\n    create_table :patch_note_groups do |t|\n      t.string :value, null: false\n      t.index :value, unique: true\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220622022147_create_patch_notes.rb",
    "content": "class CreatePatchNotes < ActiveRecord::Migration[7.0]\n  def change\n    create_table :patch_notes do |t|\n      t.text :note, null: false\n      t.references :patch_note_type, null: false, foreign_key: true\n      t.references :patch_note_group, null: false, foreign_key: true\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220820231119_create_languages.rb",
    "content": "class CreateLanguages < ActiveRecord::Migration[7.0]\n  def change\n    create_table :languages do |t|\n      t.string :name\n      t.references :casa_org, null: false, foreign_key: true\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220826130829_create_languages_users_join_table.rb",
    "content": "class CreateLanguagesUsersJoinTable < ActiveRecord::Migration[7.0]\n  def change\n    create_join_table :languages, :users do |t|\n      t.index :language_id\n      t.index :user_id\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20220924181447_remove_all_empty_languages.rb",
    "content": "class RemoveAllEmptyLanguages < ActiveRecord::Migration[7.0]\n  def up\n    safety_assured { execute \"DELETE from languages WHERE name IS NULL or trim(name) = ''\" }\n  end\nend\n"
  },
  {
    "path": "db/migrate/20221002103627_add_user_foreign_key_to_other_duties.rb",
    "content": "class AddUserForeignKeyToOtherDuties < ActiveRecord::Migration[7.0]\n  def change\n    add_foreign_key :other_duties, :users, column: :creator_id, validate: false\n  end\nend\n"
  },
  {
    "path": "db/migrate/20221002103754_validate_add_user_foreign_key_to_other_duties.rb",
    "content": "class ValidateAddUserForeignKeyToOtherDuties < ActiveRecord::Migration[7.0]\n  def change\n    validate_foreign_key :other_duties, :users\n  end\nend\n"
  },
  {
    "path": "db/migrate/20221003202112_add_court_report_due_date_to_court_dates.rb",
    "content": "class AddCourtReportDueDateToCourtDates < ActiveRecord::Migration[7.0]\n  def change\n    add_column :court_dates, :court_report_due_date, :datetime, precision: nil\n  end\nend\n"
  },
  {
    "path": "db/migrate/20221011044911_add_user_languages.rb",
    "content": "class AddUserLanguages < ActiveRecord::Migration[7.0]\n  disable_ddl_transaction!\n\n  def change\n    create_table :user_languages do |t|\n      t.references :user\n      t.references :language\n\n      t.timestamps\n    end\n\n    add_index :user_languages, [:language_id, :user_id], unique: true, algorithm: :concurrently\n  end\nend\n"
  },
  {
    "path": "db/migrate/20221012203806_populate_user_languages_from_languages_users.rb",
    "content": "class PopulateUserLanguagesFromLanguagesUsers < ActiveRecord::Migration[7.0]\n  def change\n    query = Arel.sql(\"select language_id, user_id from languages_users\")\n    old_join_table_entries = ActiveRecord::Base.connection.execute(query).to_a\n\n    old_join_table_entries.each do |entry|\n      UserLanguage.create(user_id: entry[\"user_id\"], language_id: entry[\"language_id\"])\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230121174227_remove_court_data_from_casa_cases.rb",
    "content": "class RemoveCourtDataFromCasaCases < ActiveRecord::Migration[7.0]\n  def change\n    safety_assured { remove_column :casa_cases, :court_date }\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230220210146_add_phone_number_to_all_casa_admin_resource.rb",
    "content": "class AddPhoneNumberToAllCasaAdminResource < ActiveRecord::Migration[7.0]\n  def change\n    add_column :all_casa_admins, :phone_number, :string, default: \"\"\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230316152808_remove_phone_number_from_all_casa_admin_resource.rb",
    "content": "class RemovePhoneNumberFromAllCasaAdminResource < ActiveRecord::Migration[7.0]\n  def change\n    safety_assured { remove_column :all_casa_admins, :phone_number }\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230326225216_create_placement_types.rb",
    "content": "class CreatePlacementTypes < ActiveRecord::Migration[7.0]\n  def change\n    create_table :placement_types do |t|\n      t.string :name, null: false\n      t.references :casa_org, null: false, foreign_key: true\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230326225230_create_placements.rb",
    "content": "class CreatePlacements < ActiveRecord::Migration[7.0]\n  def change\n    create_table :placements do |t|\n      # Add table placement_type: name, casa_org_id. Add table placement: placement_id, casa_case_id, started_at, created_by_id (links to user table)\n      t.datetime :placement_started_at, null: false\n      t.references :placement_type, null: false, foreign_key: true\n      t.references :creator, foreign_key: {to_table: :users}, null: false\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230327154626_add_confirmable_to_users.rb",
    "content": "class AddConfirmableToUsers < ActiveRecord::Migration[7.0]\n  disable_ddl_transaction!\n  # Note: You can't use change, as User.update_all will fail in the down migration\n  def up\n    add_column :users, :confirmation_token, :string\n    add_column :users, :confirmed_at, :datetime\n    add_column :users, :confirmation_sent_at, :datetime\n    add_column :users, :unconfirmed_email, :string # Only if using reconfirmable\n    add_index :users, :confirmation_token, unique: true, algorithm: :concurrently\n    # User.reset_column_information # Need for some types of updates, but not for update_all.\n    # To avoid a short time window between running the migration and updating all existing\n    # users as confirmed, do the following\n    # User.update_all confirmed_at: DateTime.now\n    # All existing user accounts should be able to log in after this.\n  end\n\n  def down\n    remove_index :users, :confirmation_token\n    remove_columns :users, :confirmation_token, :confirmed_at, :confirmation_sent_at\n    remove_columns :users, :unconfirmed_email # Only if using reconfirmable\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230327155053_add_email_confirmation_and_old_emails_to_users.rb",
    "content": "class AddEmailConfirmationAndOldEmailsToUsers < ActiveRecord::Migration[7.0]\n  def change\n    add_column :users, :old_emails, :string, array: true, default: []\n    add_column :users, :email_confirmation, :string\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230405202939_remove_email_confirmation_from_users.rb",
    "content": "class RemoveEmailConfirmationFromUsers < ActiveRecord::Migration[7.0]\n  def change\n    safety_assured { remove_column :users, :email_confirmation, :string }\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230412103356_add_casa_case_to_placements.rb",
    "content": "class AddCasaCaseToPlacements < ActiveRecord::Migration[7.0]\n  disable_ddl_transaction!\n\n  def change\n    add_reference :placements, :casa_case, null: false, index: {algorithm: :concurrently}\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230420212437_add_table_columns_display_to_preference_set.rb",
    "content": "class AddTableColumnsDisplayToPreferenceSet < ActiveRecord::Migration[7.0]\n  def change\n    add_column :preference_sets, :table_state, :jsonb, default: {}\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230610153139_add_receive_reimbursement_email_to_users.rb",
    "content": "class AddReceiveReimbursementEmailToUsers < ActiveRecord::Migration[7.0]\n  def change\n    return if column_exists?(:users, :receive_reimbursement_email)\n    add_column :users, :receive_reimbursement_email, :boolean, default: false\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230615155223_add_twilio_enabled_to_casa_orgs.rb",
    "content": "class AddTwilioEnabledToCasaOrgs < ActiveRecord::Migration[7.0]\n  def change\n    add_column :casa_orgs, :twilio_enabled, :boolean, default: false\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230621161252_add_additional_expenses_to_casa_orgs.rb",
    "content": "class AddAdditionalExpensesToCasaOrgs < ActiveRecord::Migration[7.0]\n  def change\n    add_column :casa_orgs, :additional_expenses_enabled, :boolean, default: false\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230627210040_add_allow_reimbursement_to_case_assignments.rb",
    "content": "class AddAllowReimbursementToCaseAssignments < ActiveRecord::Migration[7.0]\n  def change\n    add_column :case_assignments, :allow_reimbursement, :boolean, default: true\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230704123327_add_foreign_key_constraints_to_mileage_rates.rb",
    "content": "class AddForeignKeyConstraintsToMileageRates < ActiveRecord::Migration[7.0]\n  def change\n    add_foreign_key :mileage_rates, :casa_orgs, column: :casa_org_id, validate: false\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230710025852_add_token_to_users.rb",
    "content": "class AddTokenToUsers < ActiveRecord::Migration[7.0]\n  def up\n    add_column :users, :token, :string\n  end\n\n  def down\n    remove_column :users, :token, :string\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230712080040_add_foreign_key_creator_id_to_note.rb",
    "content": "class AddForeignKeyCreatorIdToNote < ActiveRecord::Migration[7.0]\n  def up\n    add_foreign_key :notes, :users, column: :creator_id, validate: false\n  end\n\n  def down\n    remove_foreign_key :notes, :creator_id\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230728135743_create_action_text_tables.action_text.rb",
    "content": "# This migration comes from action_text (originally 20180528164100)\nclass CreateActionTextTables < ActiveRecord::Migration[6.0]\n  def change\n    # Use Active Record's configured type for primary and foreign keys\n    primary_key_type, foreign_key_type = primary_and_foreign_key_types\n\n    create_table :action_text_rich_texts, id: primary_key_type do |t|\n      t.string :name, null: false\n      t.text :body, size: :long\n      t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type\n\n      t.timestamps\n\n      t.index [:record_type, :record_id, :name], name: \"index_action_text_rich_texts_uniqueness\", unique: true\n    end\n  end\n\n  private\n\n  def primary_and_foreign_key_types\n    config = Rails.configuration.generators\n    setting = config.options[config.orm][:primary_key_type]\n    primary_key_type = setting || :primary_key\n    foreign_key_type = setting || :bigint\n    [primary_key_type, foreign_key_type]\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230728140249_create_banners.rb",
    "content": "class CreateBanners < ActiveRecord::Migration[7.0]\n  def change\n    create_table :banners do |t|\n      t.references :casa_org, null: false, foreign_key: true\n      t.references :user, null: false, foreign_key: true\n      t.string :name\n      t.boolean :active, default: false\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230729143126_create_learning_hour_types.rb",
    "content": "class CreateLearningHourTypes < ActiveRecord::Migration[7.0]\n  def change\n    create_table :learning_hour_types do |t|\n      t.references :casa_org, null: false, foreign_key: true\n      t.string :name\n      t.boolean :active, default: true\n      t.integer :position, default: 1\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230729145310_add_reference_learning_hour_types.rb",
    "content": "class AddReferenceLearningHourTypes < ActiveRecord::Migration[7.0]\n  disable_ddl_transaction!\n\n  def change\n    add_reference :learning_hours, :learning_hour_type, validate: false, index: {algorithm: :concurrently}\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230729145351_add_foreign_key_learning_hour_types.rb",
    "content": "class AddForeignKeyLearningHourTypes < ActiveRecord::Migration[7.0]\n  def change\n    add_foreign_key :learning_hours, :learning_hour_types, validate: false\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230729145419_validate_foreign_key_learning_hour_types.rb",
    "content": "class ValidateForeignKeyLearningHourTypes < ActiveRecord::Migration[7.0]\n  def change\n    validate_foreign_key :learning_hours, :learning_hour_types\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230729154529_create_case_groups.rb",
    "content": "class CreateCaseGroups < ActiveRecord::Migration[7.0]\n  def change\n    create_table :case_groups do |t|\n      t.references :casa_org, null: false, foreign_key: true\n      t.string :name\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230729154545_create_case_group_memberships.rb",
    "content": "class CreateCaseGroupMemberships < ActiveRecord::Migration[7.0]\n  def change\n    create_table :case_group_memberships do |t|\n      t.references :case_group, null: false, foreign_key: true\n      t.references :casa_case, null: false, foreign_key: true\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230729213608_remove_hearing_type_id_and_judge_id_from_casa_cases.rb",
    "content": "class RemoveHearingTypeIdAndJudgeIdFromCasaCases < ActiveRecord::Migration[7.0]\n  def change\n    safety_assured { remove_column :casa_cases, :hearing_type_id }\n    safety_assured { remove_column :casa_cases, :judge_id }\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230730103110_remove_learning_type.rb",
    "content": "class RemoveLearningType < ActiveRecord::Migration[7.0]\n  def change\n    safety_assured { remove_column :learning_hours, :learning_type, :integer, default: 5, not_null: true }\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230809002819_drop_languages_users.rb",
    "content": "class DropLanguagesUsers < ActiveRecord::Migration[7.0]\n  def up\n    drop_table :languages_users\n  end\n\n  def down\n    fail ActiveRecord::IrreversibleMigration\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230817144910_create_learning_hour_topics.rb",
    "content": "class CreateLearningHourTopics < ActiveRecord::Migration[7.0]\n  def change\n    create_table :learning_hour_topics do |t|\n      t.string :name, null: false\n      t.references :casa_org, null: false, foreign_key: true\n      t.integer :position, default: 1\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230819124840_add_learning_hour_topic_id_to_learning_hour.rb",
    "content": "class AddLearningHourTopicIdToLearningHour < ActiveRecord::Migration[7.0]\n  disable_ddl_transaction!\n\n  def change\n    add_reference :learning_hours, :learning_hour_topic, index: {algorithm: :concurrently}\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230819132316_add_learning_topic_active_to_casa_org.rb",
    "content": "class AddLearningTopicActiveToCasaOrg < ActiveRecord::Migration[7.0]\n  def change\n    add_column :casa_orgs, :learning_topic_active, :boolean, default: false\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230822152341_drop_jwt_denylist_table.rb",
    "content": "class DropJwtDenylistTable < ActiveRecord::Migration[7.0]\n  def change\n    drop_table :jwt_denylist, if_exists: true\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230902021531_add_monthly_learning_hours_report_to_user.rb",
    "content": "class AddMonthlyLearningHoursReportToUser < ActiveRecord::Migration[7.0]\n  def change\n    add_column :users, :monthly_learning_hours_report, :boolean, default: false, null: false\n  end\nend\n"
  },
  {
    "path": "db/migrate/20230903182657_add_foreign_key_casa_case_to_placement.rb",
    "content": "class AddForeignKeyCasaCaseToPlacement < ActiveRecord::Migration[7.0]\n  def change\n    add_foreign_key :placements, :casa_cases, column: :casa_case_id, validate: false\n  end\nend\n"
  },
  {
    "path": "db/migrate/20231102181027_add_birthdays_to_users.rb",
    "content": "class AddBirthdaysToUsers < ActiveRecord::Migration[7.0]\n  def change\n    add_column :users, :date_of_birth, :datetime\n  end\nend\n"
  },
  {
    "path": "db/migrate/20231125150721_status_for_case_contacts.rb",
    "content": "class StatusForCaseContacts < ActiveRecord::Migration[7.0]\n  def change\n    add_column :case_contacts, :status, :string, default: \"started\"\n    add_column :case_contacts, :draft_case_ids, :integer, array: true, default: []\n    add_column :case_contacts, :volunteer_address, :string\n\n    # We need these columns to be nil if the case_contact is in_progress (ie. DRAFT mode)\n    change_column_null :case_contacts, :casa_case_id, true\n    change_column_null :case_contacts, :duration_minutes, true\n    change_column_null :case_contacts, :occurred_at, true\n  end\nend\n"
  },
  {
    "path": "db/migrate/20240216013254_add_contact_topics.rb",
    "content": "class AddContactTopics < ActiveRecord::Migration[7.1]\n  def change\n    create_table :contact_topics do |t|\n      t.references :casa_org, null: false, foreign_key: true\n      t.boolean :active, null: false, default: true\n      t.boolean :soft_delete, null: false, default: false\n      t.text :details\n      t.string :question\n\n      t.timestamps\n    end\n\n    create_table :contact_topic_answers do |t|\n      t.text :value\n      t.references :case_contact, null: false, foreign_key: true\n      t.references :contact_topic, null: false, foreign_key: true\n      t.boolean :selected, null: false, default: false\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20240415160842_add_followupable_to_followups.rb",
    "content": "class AddFollowupableToFollowups < ActiveRecord::Migration[7.1]\n  disable_ddl_transaction!\n  # To handle Dangerous operation detected by #strong_migrations in converting followups to polymorphic we will:\n  # 1. Create a new column\n  # 2. Write to both columns\n  # 3. Backfill data from the old column to the new column\n  # 4. Move reads from the old column to the new column\n  # 5. Stop writing to the old column\n  # 6. Drop the old column\n  #\n  def change\n    add_column :followups, :followupable_id, :bigint\n    add_column :followups, :followupable_type, :string\n\n    add_index :followups, [:followupable_type, :followupable_id], algorithm: :concurrently\n  end\nend\n"
  },
  {
    "path": "db/migrate/20240507022441_create_login_activities.rb",
    "content": "class CreateLoginActivities < ActiveRecord::Migration[7.1]\n  def change\n    create_table :login_activities do |t|\n      t.string :scope\n      t.string :strategy\n      t.string :identity, index: true\n      t.boolean :success\n      t.string :failure_reason\n      t.references :user, polymorphic: true\n      t.string :context\n      t.string :ip, index: true\n      t.text :user_agent\n      t.text :referrer\n      t.string :city\n      t.string :region\n      t.string :country\n      t.float :latitude\n      t.float :longitude\n      t.datetime :created_at\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20240509104733_create_noticed_tables.noticed.rb",
    "content": "# This migration comes from noticed (originally 20231215190233)\nclass CreateNoticedTables < ActiveRecord::Migration[6.1]\n  def change\n    primary_key_type, foreign_key_type = primary_and_foreign_key_types\n    create_table :noticed_events, id: primary_key_type do |t|\n      t.string :type\n      t.belongs_to :record, polymorphic: true, type: foreign_key_type\n      if t.respond_to?(:jsonb)\n        t.jsonb :params\n      else\n        t.json :params\n      end\n\n      t.timestamps\n    end\n\n    create_table :noticed_notifications, id: primary_key_type do |t|\n      t.string :type\n      t.belongs_to :event, null: false, type: foreign_key_type\n      t.belongs_to :recipient, polymorphic: true, null: false, type: foreign_key_type\n      t.datetime :read_at\n      t.datetime :seen_at\n\n      t.timestamps\n    end\n  end\n\n  private\n\n  def primary_and_foreign_key_types\n    config = Rails.configuration.generators\n    setting = config.options[config.orm][:primary_key_type]\n    primary_key_type = setting || :primary_key\n    foreign_key_type = setting || :bigint\n    [primary_key_type, foreign_key_type]\n  end\nend\n"
  },
  {
    "path": "db/migrate/20240509104734_add_notifications_count_to_noticed_event.noticed.rb",
    "content": "# This migration comes from noticed (originally 20240129184740)\nclass AddNotificationsCountToNoticedEvent < ActiveRecord::Migration[6.1]\n  def change\n    add_column :noticed_events, :notifications_count, :integer\n  end\nend\n"
  },
  {
    "path": "db/migrate/20240531172823_add_expires_at_to_banner.rb",
    "content": "class AddExpiresAtToBanner < ActiveRecord::Migration[7.1]\n  def change\n    add_column :banners, :expires_at, :datetime, null: true\n  end\nend\n"
  },
  {
    "path": "db/migrate/20240610071054_add_other_duties_enabled_to_casa_org.rb",
    "content": "class AddOtherDutiesEnabledToCasaOrg < ActiveRecord::Migration[7.1]\n  def change\n    add_column :casa_orgs, :other_duties_enabled, :boolean, default: true\n  end\nend\n"
  },
  {
    "path": "db/migrate/20240621165358_create_flipper_tables.rb",
    "content": "class CreateFlipperTables < ActiveRecord::Migration[7.1]\n  def up\n    create_table :flipper_features do |t|\n      t.string :key, null: false\n      t.timestamps null: false\n    end\n    add_index :flipper_features, :key, unique: true\n\n    create_table :flipper_gates do |t|\n      t.string :feature_key, null: false\n      t.string :key, null: false\n      t.text :value\n      t.timestamps null: false\n    end\n    add_index :flipper_gates, [:feature_key, :key, :value], unique: true, length: {value: 255}\n  end\n\n  def down\n    drop_table :flipper_gates\n    drop_table :flipper_features\n  end\nend\n"
  },
  {
    "path": "db/migrate/20240622020203_drop_feature_flags.rb",
    "content": "class DropFeatureFlags < ActiveRecord::Migration[7.1]\n  def up\n    drop_table :feature_flags\n  end\n\n  def down\n    create_table :feature_flags do |t|\n      t.string :name, null: false\n      t.boolean :enabled, null: false, default: false\n      t.timestamps\n    end\n\n    add_index :feature_flags, :name, unique: true, algorithm: :concurrently\n  end\nend\n"
  },
  {
    "path": "db/migrate/20240716194609_add_metadata_to_case_contacts.rb",
    "content": "class AddMetadataToCaseContacts < ActiveRecord::Migration[7.1]\n  def change\n    add_column :case_contacts, :metadata, :jsonb, default: {}\n  end\nend\n"
  },
  {
    "path": "db/migrate/20241017050129_remove_contact_topic_answer_contact_topic_id_null_constraint.rb",
    "content": "class RemoveContactTopicAnswerContactTopicIdNullConstraint < ActiveRecord::Migration[7.2]\n  def change\n    change_column_null(:contact_topic_answers, :contact_topic_id, true)\n  end\nend\n"
  },
  {
    "path": "db/migrate/20250207080433_remove_token_from_users.rb",
    "content": "class RemoveTokenFromUsers < ActiveRecord::Migration[7.2]\n  def change\n    safety_assured { remove_column :users, :token, :string }\n  end\nend\n"
  },
  {
    "path": "db/migrate/20250207080511_create_api_credentials.rb",
    "content": "class CreateApiCredentials < ActiveRecord::Migration[7.2]\n  def change\n    create_table :api_credentials do |t|\n      t.references :user, null: false, foreign_key: true\n      t.string :api_token\n      t.string :refresh_token\n      t.datetime :token_expires_at, default: -> { \"NOW() + INTERVAL '7 hours'\" }\n      t.datetime :refresh_token_expires_at, default: -> { \"NOW() + INTERVAL '30 days'\" }\n\n      t.string :api_token_digest\n      t.string :refresh_token_digest\n\n      t.timestamps\n    end\n\n    add_index :api_credentials, :api_token_digest, unique: true, where: \"api_token_digest IS NOT NULL\"\n    add_index :api_credentials, :refresh_token_digest, unique: true, where: \"refresh_token_digest IS NOT NULL\"\n  end\nend\n"
  },
  {
    "path": "db/migrate/20250208160513_remove_plain_text_tokens_from_api_credentials.rb",
    "content": "class RemovePlainTextTokensFromApiCredentials < ActiveRecord::Migration[7.2]\n  def change\n    safety_assured { remove_column :api_credentials, :api_token, :string }\n    safety_assured { remove_column :api_credentials, :refresh_token, :string }\n  end\nend\n"
  },
  {
    "path": "db/migrate/20250331032424_validate_constraint_mileage_rates.rb",
    "content": "class ValidateConstraintMileageRates < ActiveRecord::Migration[7.2]\n  def up\n    ActiveRecord::Base.connection.execute(Arel.sql(\"ALTER TABLE mileage_rates VALIDATE CONSTRAINT fk_rails_3dad81992f;\"))\n  end\n\n  def down\n    # cannot un-validate a constraint\n  end\nend\n"
  },
  {
    "path": "db/migrate/20250331033339_validate_constraint_notes.rb",
    "content": "class ValidateConstraintNotes < ActiveRecord::Migration[7.2]\n  def up\n    ActiveRecord::Base.connection.execute(Arel.sql(\"ALTER TABLE notes VALIDATE CONSTRAINT fk_rails_5d4a723a34;\"))\n  end\n\n  def down\n    # cannot un-validate a constraint\n  end\nend\n"
  },
  {
    "path": "db/migrate/20250331033350_validate_constraint_placements.rb",
    "content": "class ValidateConstraintPlacements < ActiveRecord::Migration[7.2]\n  def up\n    ActiveRecord::Base.connection.execute(Arel.sql(\"ALTER TABLE placements VALIDATE CONSTRAINT fk_rails_65aeeb5669;\"))\n  end\n\n  def down\n    # cannot un-validate a constraint\n  end\nend\n"
  },
  {
    "path": "db/migrate/20250331033418_remove_duplicate_indexindex_emancipation_options_on_emancipation_category_id.rb",
    "content": "class RemoveDuplicateIndexindexEmancipationOptionsOnEmancipationCategoryId < ActiveRecord::Migration[7.2]\n  def change\n    remove_index :emancipation_options, :emancipation_category_id\n  end\nend\n"
  },
  {
    "path": "db/migrate/20250331033441_remove_duplicate_indexindex_user_languages_on_language_id.rb",
    "content": "class RemoveDuplicateIndexindexUserLanguagesOnLanguageId < ActiveRecord::Migration[7.2]\n  def change\n    remove_index :user_languages, :language_id\n  end\nend\n"
  },
  {
    "path": "db/migrate/20250404200715_create_custom_org_links.rb",
    "content": "class CreateCustomOrgLinks < ActiveRecord::Migration[7.2]\n  def change\n    create_table :custom_org_links do |t|\n      t.references :casa_org, null: false, foreign_key: true\n      t.string :text, null: false\n      t.string :url, null: false\n      t.boolean :active, null: false, default: true\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20250507011754_remove_unused_indexes.rb",
    "content": "class RemoveUnusedIndexes < ActiveRecord::Migration[7.2]\n  def change\n    remove_index :case_contacts, name: \"index_case_contacts_on_deleted_at\", if_exists: true\n    remove_index :noticed_notifications, name: \"index_noticed_notifications_on_event_id\", if_exists: true\n    remove_index :contact_topic_answers, name: \"index_contact_topic_answers_on_contact_topic_id\", if_exists: true\n    remove_index :login_activities, name: \"index_login_activities_on_ip\", if_exists: true\n    remove_index :login_activities, name: \"index_login_activities_on_identity\", if_exists: true\n    remove_index :delayed_jobs, name: \"delayed_jobs_priority\", if_exists: true\n    remove_index :login_activities, name: \"index_login_activities_on_user\", if_exists: true\n    remove_index :sent_emails, name: \"index_sent_emails_on_user_id\", if_exists: true\n    remove_index :sent_emails, name: \"index_sent_emails_on_casa_org_id\", if_exists: true\n    remove_index :noticed_events, name: \"index_noticed_events_on_record\", if_exists: true\n    remove_index :notifications, name: \"index_notifications_on_read_at\", if_exists: true\n    remove_index :notifications, name: \"index_notifications_on_recipient\", if_exists: true\n    remove_index :api_credentials, name: \"index_api_credentials_on_user_id\", if_exists: true\n    remove_index :court_dates, name: \"index_court_dates_on_hearing_type_id\", if_exists: true\n    remove_index :court_dates, name: \"index_court_dates_on_judge_id\", if_exists: true\n    remove_index :followups, name: \"index_followups_on_followupable_type_and_followupable_id\", if_exists: true\n    remove_index :banners, name: \"index_banners_on_casa_org_id\", if_exists: true\n    remove_index :banners, name: \"index_banners_on_user_id\", if_exists: true\n    remove_index :casa_case_emancipation_categories, name: \"index_case_emancipation_categories_on_emancipation_category_id\", if_exists: true\n    remove_index :case_group_memberships, name: \"index_case_group_memberships_on_casa_case_id\", if_exists: true\n    remove_index :case_group_memberships, name: \"index_case_group_memberships_on_case_group_id\", if_exists: true\n    remove_index :case_groups, name: \"index_case_groups_on_casa_org_id\", if_exists: true\n    remove_index :contact_topics, name: \"index_contact_topics_on_casa_org_id\", if_exists: true\n    remove_index :contact_type_groups, name: \"index_contact_type_groups_on_casa_org_id\", if_exists: true\n    remove_index :followups, name: \"index_followups_on_creator_id\", if_exists: true\n    remove_index :hearing_types, name: \"index_hearing_types_on_casa_org_id\", if_exists: true\n    remove_index :judges, name: \"index_judges_on_casa_org_id\", if_exists: true\n    remove_index :languages, name: \"index_languages_on_casa_org_id\", if_exists: true\n    remove_index :learning_hour_topics, name: \"index_learning_hour_topics_on_casa_org_id\", if_exists: true\n    remove_index :learning_hour_types, name: \"index_learning_hour_types_on_casa_org_id\", if_exists: true\n    remove_index :learning_hours, name: \"index_learning_hours_on_learning_hour_topic_id\", if_exists: true\n    remove_index :learning_hours, name: \"index_learning_hours_on_learning_hour_type_id\", if_exists: true\n    remove_index :mileage_rates, name: \"index_mileage_rates_on_casa_org_id\", if_exists: true\n    remove_index :mileage_rates, name: \"index_mileage_rates_on_user_id\", if_exists: true\n    remove_index :notes, name: \"index_notes_on_notable\", if_exists: true\n    remove_index :patch_notes, name: \"index_patch_notes_on_patch_note_group_id\", if_exists: true\n    remove_index :patch_notes, name: \"index_patch_notes_on_patch_note_type_id\", if_exists: true\n    remove_index :user_languages, name: \"index_user_languages_on_user_id\", if_exists: true\n    remove_index :user_sms_notification_events, name: \"index_user_sms_notification_events_on_sms_notification_event_id\", if_exists: true\n    remove_index :user_sms_notification_events, name: \"index_user_sms_notification_events_on_user_id\", if_exists: true\n    remove_index :users, name: \"index_users_on_invitations_count\", if_exists: true\n    remove_index :users, name: \"index_users_on_invited_by_type_and_invited_by_id\", if_exists: true\n    remove_index :checklist_items, name: \"index_checklist_items_on_hearing_type_id\", if_exists: true\n    remove_index :placement_types, name: \"index_placement_types_on_casa_org_id\", if_exists: true\n    remove_index :placements, name: \"index_placements_on_casa_case_id\", if_exists: true\n    remove_index :placements, name: \"index_placements_on_creator_id\", if_exists: true\n    remove_index :placements, name: \"index_placements_on_placement_type_id\", if_exists: true\n    remove_index :user_case_contact_types_reminders, name: \"index_user_case_contact_types_reminders_on_user_id\", if_exists: true\n  end\nend\n"
  },
  {
    "path": "db/migrate/20250528092341_trim_whitespace_from_custom_org_links.rb",
    "content": "class TrimWhitespaceFromCustomOrgLinks < ActiveRecord::Migration[7.2]\n  def up\n    CustomOrgLink.find_each do |link|\n      trimmed_text = link.text.strip\n      link.update_columns(text: trimmed_text) if trimmed_text.present?\n    rescue => e\n      Rails.logger.error(\"Failed to update CustomOrgLink ##{link.id}: #{e.message}\")\n    end\n  end\n\n  def down\n    Rails.logger.info(\"Rollback not implemented for TrimWhitespaceFromCustomOrgLinks as it is a data migration\")\n  end\nend\n"
  },
  {
    "path": "db/migrate/20250702142004_add_exclude_from_court_report_to_contact_topics.rb",
    "content": "class AddExcludeFromCourtReportToContactTopics < ActiveRecord::Migration[7.2]\n  def change\n    add_column :contact_topics, :exclude_from_court_report, :boolean, default: false, null: false\n  end\nend\n"
  },
  {
    "path": "db/migrate/20260210233737_rename_casa_cases_emancipaton_options_to_casa_case_emancipaton_options.rb",
    "content": "class RenameCasaCasesEmancipatonOptionsToCasaCaseEmancipatonOptions < ActiveRecord::Migration[7.2]\n  def change\n    reversible do |dir|\n      dir.up do\n        create_table \"casa_case_emancipation_options\" do |t|\n          t.bigint \"casa_case_id\", null: false\n          t.bigint \"emancipation_option_id\", null: false\n          t.datetime \"created_at\", null: false\n          t.datetime \"updated_at\", null: false\n          t.index [\"casa_case_id\", \"emancipation_option_id\"], name: \"index_case_options_on_case_id_and_option_id\", unique: true\n        end\n\n        add_foreign_key \"casa_case_emancipation_options\", \"casa_cases\", validate: false\n        add_foreign_key \"casa_case_emancipation_options\", \"emancipation_options\", validate: false\n\n        safety_assured do\n          execute <<-SQL\n            INSERT INTO casa_case_emancipation_options (casa_case_id, emancipation_option_id, created_at, updated_at) SELECT casa_case_id, emancipation_option_id, created_at, updated_at FROM casa_cases_emancipation_options;\n          SQL\n        end\n      end\n\n      dir.down do\n        remove_foreign_key \"casa_case_emancipation_options\", \"casa_cases\", validate: false\n        remove_foreign_key \"casa_case_emancipation_options\", \"emancipation_options\", validate: false\n\n        drop_table :casa_case_emancipation_options\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20260211001655_rename_casa_cases_emancipaton_options_to_casa_case_emancipaton_options_follow_up.rb",
    "content": "class RenameCasaCasesEmancipatonOptionsToCasaCaseEmancipatonOptionsFollowUp < ActiveRecord::Migration[7.2]\n  def up\n    validate_foreign_key \"casa_case_emancipation_options\", \"casa_cases\"\n    validate_foreign_key \"casa_case_emancipation_options\", \"emancipation_options\"\n\n    drop_table :casa_cases_emancipation_options\n  end\n\n  def down\n    fail ActiveRecord::IrreversibleMigration\n  end\nend\n"
  },
  {
    "path": "db/migrate/20260414132818_add_deleted_at_to_contact_topic_answers.rb",
    "content": "class AddDeletedAtToContactTopicAnswers < ActiveRecord::Migration[7.2]\n  def change\n    add_column :contact_topic_answers, :deleted_at, :datetime\n  end\nend\n"
  },
  {
    "path": "db/schema.rb",
    "content": "# This file is auto-generated from the current state of the database. Instead\n# of editing this file, please use the migrations feature of Active Record to\n# incrementally modify your database, and then regenerate this schema definition.\n#\n# This file is the source Rails uses to define your schema when running `bin/rails\n# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to\n# be faster and is potentially less error prone than running all of your\n# migrations from scratch. Old migrations may fail to apply correctly if those\n# migrations use external dependencies or application code.\n#\n# It's strongly recommended that you check this file into your version control system.\n\nActiveRecord::Schema[7.2].define(version: 2026_04_14_132818) do\n  # These are extensions that must be enabled in order to support this database\n  enable_extension \"plpgsql\"\n\n  create_table \"action_text_rich_texts\", force: :cascade do |t|\n    t.string \"name\", null: false\n    t.text \"body\"\n    t.string \"record_type\", null: false\n    t.bigint \"record_id\", null: false\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.index [\"record_type\", \"record_id\", \"name\"], name: \"index_action_text_rich_texts_uniqueness\", unique: true\n  end\n\n  create_table \"active_storage_attachments\", force: :cascade do |t|\n    t.string \"name\", null: false\n    t.string \"record_type\", null: false\n    t.bigint \"record_id\", null: false\n    t.bigint \"blob_id\", null: false\n    t.datetime \"created_at\", precision: nil, null: false\n    t.index [\"blob_id\"], name: \"index_active_storage_attachments_on_blob_id\"\n    t.index [\"record_type\", \"record_id\", \"name\", \"blob_id\"], name: \"index_active_storage_attachments_uniqueness\", unique: true\n  end\n\n  create_table \"active_storage_blobs\", force: :cascade do |t|\n    t.string \"key\", null: false\n    t.string \"filename\", null: false\n    t.string \"content_type\"\n    t.text \"metadata\"\n    t.bigint \"byte_size\", null: false\n    t.string \"checksum\"\n    t.datetime \"created_at\", precision: nil, null: false\n    t.string \"service_name\", null: false\n    t.index [\"key\"], name: \"index_active_storage_blobs_on_key\", unique: true\n  end\n\n  create_table \"active_storage_variant_records\", force: :cascade do |t|\n    t.bigint \"blob_id\", null: false\n    t.string \"variation_digest\", null: false\n    t.index [\"blob_id\", \"variation_digest\"], name: \"index_active_storage_variant_records_uniqueness\", unique: true\n  end\n\n  create_table \"additional_expenses\", force: :cascade do |t|\n    t.bigint \"case_contact_id\", null: false\n    t.decimal \"other_expense_amount\"\n    t.string \"other_expenses_describe\"\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.index [\"case_contact_id\"], name: \"index_additional_expenses_on_case_contact_id\"\n  end\n\n  create_table \"addresses\", force: :cascade do |t|\n    t.string \"content\"\n    t.bigint \"user_id\", null: false\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.index [\"user_id\"], name: \"index_addresses_on_user_id\"\n  end\n\n  create_table \"all_casa_admins\", force: :cascade do |t|\n    t.string \"email\", default: \"\", null: false\n    t.string \"encrypted_password\", default: \"\", null: false\n    t.string \"reset_password_token\"\n    t.datetime \"reset_password_sent_at\", precision: nil\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.string \"invitation_token\"\n    t.datetime \"invitation_created_at\", precision: nil\n    t.datetime \"invitation_sent_at\", precision: nil\n    t.datetime \"invitation_accepted_at\", precision: nil\n    t.integer \"invitation_limit\"\n    t.integer \"invited_by_id\"\n    t.string \"invited_by_type\"\n    t.index [\"email\"], name: \"index_all_casa_admins_on_email\", unique: true\n    t.index [\"invitation_token\"], name: \"index_all_casa_admins_on_invitation_token\", unique: true\n    t.index [\"reset_password_token\"], name: \"index_all_casa_admins_on_reset_password_token\", unique: true\n  end\n\n  create_table \"api_credentials\", force: :cascade do |t|\n    t.bigint \"user_id\", null: false\n    t.datetime \"token_expires_at\", default: -> { \"(now() + 'PT7H'::interval)\" }\n    t.datetime \"refresh_token_expires_at\", default: -> { \"(now() + 'P30D'::interval)\" }\n    t.string \"api_token_digest\"\n    t.string \"refresh_token_digest\"\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.index [\"api_token_digest\"], name: \"index_api_credentials_on_api_token_digest\", unique: true, where: \"(api_token_digest IS NOT NULL)\"\n    t.index [\"refresh_token_digest\"], name: \"index_api_credentials_on_refresh_token_digest\", unique: true, where: \"(refresh_token_digest IS NOT NULL)\"\n  end\n\n  create_table \"banners\", force: :cascade do |t|\n    t.bigint \"casa_org_id\", null: false\n    t.bigint \"user_id\", null: false\n    t.string \"name\"\n    t.boolean \"active\", default: false\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.datetime \"expires_at\"\n  end\n\n  create_table \"casa_case_contact_types\", force: :cascade do |t|\n    t.bigint \"contact_type_id\", null: false\n    t.bigint \"casa_case_id\", null: false\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.index [\"casa_case_id\"], name: \"index_casa_case_contact_types_on_casa_case_id\"\n    t.index [\"contact_type_id\"], name: \"index_casa_case_contact_types_on_contact_type_id\"\n  end\n\n  create_table \"casa_case_emancipation_categories\", force: :cascade do |t|\n    t.bigint \"casa_case_id\", null: false\n    t.bigint \"emancipation_category_id\", null: false\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.index [\"casa_case_id\"], name: \"index_casa_case_emancipation_categories_on_casa_case_id\"\n  end\n\n  create_table \"casa_case_emancipation_options\", force: :cascade do |t|\n    t.bigint \"casa_case_id\", null: false\n    t.bigint \"emancipation_option_id\", null: false\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.index [\"casa_case_id\", \"emancipation_option_id\"], name: \"index_case_options_on_case_id_and_option_id\", unique: true\n  end\n\n  create_table \"casa_cases\", force: :cascade do |t|\n    t.string \"case_number\", null: false\n    t.boolean \"transition_aged_youth\", default: false\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.bigint \"casa_org_id\", null: false\n    t.datetime \"birth_month_year_youth\", precision: nil\n    t.datetime \"court_report_due_date\", precision: nil\n    t.boolean \"active\", default: true, null: false\n    t.datetime \"court_report_submitted_at\", precision: nil\n    t.integer \"court_report_status\", default: 0\n    t.string \"slug\"\n    t.datetime \"date_in_care\"\n    t.index [\"casa_org_id\"], name: \"index_casa_cases_on_casa_org_id\"\n    t.index [\"case_number\", \"casa_org_id\"], name: \"index_casa_cases_on_case_number_and_casa_org_id\", unique: true\n    t.index [\"slug\"], name: \"index_casa_cases_on_slug\"\n  end\n\n  create_table \"casa_orgs\", force: :cascade do |t|\n    t.string \"name\", null: false\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.string \"display_name\"\n    t.string \"address\"\n    t.string \"footer_links\", default: [], array: true\n    t.string \"slug\"\n    t.boolean \"show_driving_reimbursement\", default: true\n    t.boolean \"show_fund_request\", default: false\n    t.string \"twilio_phone_number\"\n    t.string \"twilio_account_sid\"\n    t.string \"twilio_api_key_sid\"\n    t.string \"twilio_api_key_secret\"\n    t.boolean \"twilio_enabled\", default: false\n    t.boolean \"additional_expenses_enabled\", default: false\n    t.boolean \"learning_topic_active\", default: false\n    t.boolean \"other_duties_enabled\", default: true\n    t.index [\"slug\"], name: \"index_casa_orgs_on_slug\", unique: true\n  end\n\n  create_table \"case_assignments\", force: :cascade do |t|\n    t.bigint \"casa_case_id\", null: false\n    t.bigint \"volunteer_id\", null: false\n    t.boolean \"active\", default: true, null: false\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.boolean \"hide_old_contacts\", default: false\n    t.boolean \"allow_reimbursement\", default: true\n    t.index [\"casa_case_id\"], name: \"index_case_assignments_on_casa_case_id\"\n    t.index [\"volunteer_id\"], name: \"index_case_assignments_on_volunteer_id\"\n  end\n\n  create_table \"case_contact_contact_types\", force: :cascade do |t|\n    t.bigint \"case_contact_id\", null: false\n    t.bigint \"contact_type_id\", null: false\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.index [\"case_contact_id\"], name: \"index_case_contact_contact_types_on_case_contact_id\"\n    t.index [\"contact_type_id\"], name: \"index_case_contact_contact_types_on_contact_type_id\"\n  end\n\n  create_table \"case_contacts\", force: :cascade do |t|\n    t.bigint \"creator_id\", null: false\n    t.bigint \"casa_case_id\"\n    t.integer \"duration_minutes\"\n    t.datetime \"occurred_at\", precision: nil\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.boolean \"contact_made\", default: false\n    t.string \"medium_type\"\n    t.integer \"miles_driven\", default: 0, null: false\n    t.boolean \"want_driving_reimbursement\", default: false\n    t.string \"notes\"\n    t.datetime \"deleted_at\", precision: nil\n    t.boolean \"reimbursement_complete\", default: false\n    t.string \"status\", default: \"started\"\n    t.integer \"draft_case_ids\", default: [], array: true\n    t.string \"volunteer_address\"\n    t.jsonb \"metadata\", default: {}\n    t.index [\"casa_case_id\"], name: \"index_case_contacts_on_casa_case_id\"\n    t.index [\"creator_id\"], name: \"index_case_contacts_on_creator_id\"\n    t.check_constraint \"miles_driven IS NOT NULL OR NOT want_driving_reimbursement\", name: \"want_driving_reimbursement_only_when_miles_driven\"\n  end\n\n  create_table \"case_court_orders\", force: :cascade do |t|\n    t.bigint \"casa_case_id\", null: false\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.integer \"implementation_status\"\n    t.bigint \"court_date_id\"\n    t.string \"text\"\n    t.index [\"casa_case_id\"], name: \"index_case_court_orders_on_casa_case_id\"\n    t.index [\"court_date_id\"], name: \"index_case_court_orders_on_court_date_id\"\n  end\n\n  create_table \"case_group_memberships\", force: :cascade do |t|\n    t.bigint \"case_group_id\", null: false\n    t.bigint \"casa_case_id\", null: false\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n  end\n\n  create_table \"case_groups\", force: :cascade do |t|\n    t.bigint \"casa_org_id\", null: false\n    t.string \"name\"\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n  end\n\n  create_table \"checklist_items\", force: :cascade do |t|\n    t.integer \"hearing_type_id\"\n    t.text \"description\", null: false\n    t.string \"category\", null: false\n    t.boolean \"mandatory\", default: false, null: false\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n  end\n\n  create_table \"contact_topic_answers\", force: :cascade do |t|\n    t.text \"value\"\n    t.bigint \"case_contact_id\", null: false\n    t.bigint \"contact_topic_id\"\n    t.boolean \"selected\", default: false, null: false\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.datetime \"deleted_at\"\n    t.index [\"case_contact_id\"], name: \"index_contact_topic_answers_on_case_contact_id\"\n  end\n\n  create_table \"contact_topics\", force: :cascade do |t|\n    t.bigint \"casa_org_id\", null: false\n    t.boolean \"active\", default: true, null: false\n    t.boolean \"soft_delete\", default: false, null: false\n    t.text \"details\"\n    t.string \"question\"\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.boolean \"exclude_from_court_report\", default: false, null: false\n  end\n\n  create_table \"contact_type_groups\", force: :cascade do |t|\n    t.bigint \"casa_org_id\", null: false\n    t.string \"name\", null: false\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.boolean \"active\", default: true\n  end\n\n  create_table \"contact_types\", force: :cascade do |t|\n    t.bigint \"contact_type_group_id\", null: false\n    t.string \"name\", null: false\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.boolean \"active\", default: true\n    t.index [\"contact_type_group_id\"], name: \"index_contact_types_on_contact_type_group_id\"\n  end\n\n  create_table \"court_dates\", force: :cascade do |t|\n    t.datetime \"date\", precision: nil, null: false\n    t.bigint \"casa_case_id\", null: false\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.bigint \"hearing_type_id\"\n    t.bigint \"judge_id\"\n    t.datetime \"court_report_due_date\", precision: nil\n    t.index [\"casa_case_id\"], name: \"index_court_dates_on_casa_case_id\"\n  end\n\n  create_table \"custom_org_links\", force: :cascade do |t|\n    t.bigint \"casa_org_id\", null: false\n    t.string \"text\", null: false\n    t.string \"url\", null: false\n    t.boolean \"active\", default: true, null: false\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.index [\"casa_org_id\"], name: \"index_custom_org_links_on_casa_org_id\"\n  end\n\n  create_table \"delayed_jobs\", force: :cascade do |t|\n    t.integer \"priority\", default: 0, null: false\n    t.integer \"attempts\", default: 0, null: false\n    t.text \"handler\", null: false\n    t.text \"last_error\"\n    t.datetime \"run_at\", precision: nil\n    t.datetime \"locked_at\", precision: nil\n    t.datetime \"failed_at\", precision: nil\n    t.string \"locked_by\"\n    t.string \"queue\"\n    t.datetime \"created_at\"\n    t.datetime \"updated_at\"\n  end\n\n  create_table \"emancipation_categories\", force: :cascade do |t|\n    t.string \"name\", null: false\n    t.boolean \"mutually_exclusive\", null: false\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.index [\"name\"], name: \"index_emancipation_categories_on_name\", unique: true\n  end\n\n  create_table \"emancipation_options\", force: :cascade do |t|\n    t.bigint \"emancipation_category_id\", null: false\n    t.string \"name\", null: false\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.index [\"emancipation_category_id\", \"name\"], name: \"index_emancipation_options_on_emancipation_category_id_and_name\", unique: true\n  end\n\n  create_table \"flipper_features\", force: :cascade do |t|\n    t.string \"key\", null: false\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.index [\"key\"], name: \"index_flipper_features_on_key\", unique: true\n  end\n\n  create_table \"flipper_gates\", force: :cascade do |t|\n    t.string \"feature_key\", null: false\n    t.string \"key\", null: false\n    t.text \"value\"\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.index [\"feature_key\", \"key\", \"value\"], name: \"index_flipper_gates_on_feature_key_and_key_and_value\", unique: true\n  end\n\n  create_table \"followups\", force: :cascade do |t|\n    t.bigint \"case_contact_id\"\n    t.bigint \"creator_id\"\n    t.integer \"status\", default: 0\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.text \"note\"\n    t.bigint \"followupable_id\"\n    t.string \"followupable_type\"\n    t.index [\"case_contact_id\"], name: \"index_followups_on_case_contact_id\"\n  end\n\n  create_table \"fund_requests\", force: :cascade do |t|\n    t.text \"submitter_email\"\n    t.text \"youth_name\"\n    t.text \"payment_amount\"\n    t.text \"deadline\"\n    t.text \"request_purpose\"\n    t.text \"payee_name\"\n    t.text \"requested_by_and_relationship\"\n    t.text \"other_funding_source_sought\"\n    t.text \"impact\"\n    t.text \"extra_information\"\n    t.text \"timestamps\"\n  end\n\n  create_table \"healths\", force: :cascade do |t|\n    t.datetime \"latest_deploy_time\", precision: nil\n    t.integer \"singleton_guard\"\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.index [\"singleton_guard\"], name: \"index_healths_on_singleton_guard\", unique: true\n  end\n\n  create_table \"hearing_types\", force: :cascade do |t|\n    t.bigint \"casa_org_id\", null: false\n    t.string \"name\", null: false\n    t.boolean \"active\", default: true, null: false\n    t.string \"checklist_updated_date\", default: \"None\", null: false\n  end\n\n  create_table \"judges\", force: :cascade do |t|\n    t.bigint \"casa_org_id\", null: false\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.boolean \"active\", default: true\n    t.string \"name\"\n  end\n\n  create_table \"languages\", force: :cascade do |t|\n    t.string \"name\"\n    t.bigint \"casa_org_id\", null: false\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n  end\n\n  create_table \"learning_hour_topics\", force: :cascade do |t|\n    t.string \"name\", null: false\n    t.bigint \"casa_org_id\", null: false\n    t.integer \"position\", default: 1\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n  end\n\n  create_table \"learning_hour_types\", force: :cascade do |t|\n    t.bigint \"casa_org_id\", null: false\n    t.string \"name\"\n    t.boolean \"active\", default: true\n    t.integer \"position\", default: 1\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n  end\n\n  create_table \"learning_hours\", force: :cascade do |t|\n    t.bigint \"user_id\", null: false\n    t.string \"name\", null: false\n    t.integer \"duration_minutes\", null: false\n    t.integer \"duration_hours\", null: false\n    t.datetime \"occurred_at\", precision: nil, null: false\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.bigint \"learning_hour_type_id\"\n    t.bigint \"learning_hour_topic_id\"\n    t.index [\"user_id\"], name: \"index_learning_hours_on_user_id\"\n  end\n\n  create_table \"login_activities\", force: :cascade do |t|\n    t.string \"scope\"\n    t.string \"strategy\"\n    t.string \"identity\"\n    t.boolean \"success\"\n    t.string \"failure_reason\"\n    t.string \"user_type\"\n    t.bigint \"user_id\"\n    t.string \"context\"\n    t.string \"ip\"\n    t.text \"user_agent\"\n    t.text \"referrer\"\n    t.string \"city\"\n    t.string \"region\"\n    t.string \"country\"\n    t.float \"latitude\"\n    t.float \"longitude\"\n    t.datetime \"created_at\"\n  end\n\n  create_table \"mileage_rates\", force: :cascade do |t|\n    t.decimal \"amount\"\n    t.date \"effective_date\"\n    t.boolean \"is_active\", default: true\n    t.bigint \"user_id\"\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.bigint \"casa_org_id\", null: false\n  end\n\n  create_table \"notes\", force: :cascade do |t|\n    t.string \"content\"\n    t.bigint \"creator_id\"\n    t.string \"notable_type\"\n    t.bigint \"notable_id\"\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n  end\n\n  create_table \"noticed_events\", force: :cascade do |t|\n    t.string \"type\"\n    t.string \"record_type\"\n    t.bigint \"record_id\"\n    t.jsonb \"params\"\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.integer \"notifications_count\"\n  end\n\n  create_table \"noticed_notifications\", force: :cascade do |t|\n    t.string \"type\"\n    t.bigint \"event_id\", null: false\n    t.string \"recipient_type\", null: false\n    t.bigint \"recipient_id\", null: false\n    t.datetime \"read_at\", precision: nil\n    t.datetime \"seen_at\", precision: nil\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.index [\"recipient_type\", \"recipient_id\"], name: \"index_noticed_notifications_on_recipient\"\n  end\n\n  create_table \"notifications\", force: :cascade do |t|\n    t.string \"recipient_type\", null: false\n    t.bigint \"recipient_id\", null: false\n    t.string \"type\", null: false\n    t.jsonb \"params\"\n    t.datetime \"read_at\", precision: nil\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n  end\n\n  create_table \"other_duties\", force: :cascade do |t|\n    t.bigint \"creator_id\", null: false\n    t.string \"creator_type\"\n    t.datetime \"occurred_at\", precision: nil\n    t.bigint \"duration_minutes\"\n    t.text \"notes\"\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n  end\n\n  create_table \"patch_note_groups\", force: :cascade do |t|\n    t.string \"value\", null: false\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.index [\"value\"], name: \"index_patch_note_groups_on_value\", unique: true\n  end\n\n  create_table \"patch_note_types\", force: :cascade do |t|\n    t.string \"name\", null: false\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.index [\"name\"], name: \"index_patch_note_types_on_name\", unique: true\n  end\n\n  create_table \"patch_notes\", force: :cascade do |t|\n    t.text \"note\", null: false\n    t.bigint \"patch_note_type_id\", null: false\n    t.bigint \"patch_note_group_id\", null: false\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n  end\n\n  create_table \"placement_types\", force: :cascade do |t|\n    t.string \"name\", null: false\n    t.bigint \"casa_org_id\", null: false\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n  end\n\n  create_table \"placements\", force: :cascade do |t|\n    t.datetime \"placement_started_at\", null: false\n    t.bigint \"placement_type_id\", null: false\n    t.bigint \"creator_id\", null: false\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.bigint \"casa_case_id\", null: false\n  end\n\n  create_table \"preference_sets\", force: :cascade do |t|\n    t.bigint \"user_id\"\n    t.jsonb \"case_volunteer_columns\", default: \"{}\", null: false\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.jsonb \"table_state\", default: {}\n    t.index [\"user_id\"], name: \"index_preference_sets_on_user_id\"\n  end\n\n  create_table \"sent_emails\", force: :cascade do |t|\n    t.bigint \"user_id\"\n    t.bigint \"casa_org_id\", null: false\n    t.string \"mailer_type\"\n    t.string \"category\"\n    t.string \"sent_address\"\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n  end\n\n  create_table \"sms_notification_events\", force: :cascade do |t|\n    t.string \"name\"\n    t.string \"user_type\"\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n  end\n\n  create_table \"supervisor_volunteers\", force: :cascade do |t|\n    t.bigint \"supervisor_id\", null: false\n    t.bigint \"volunteer_id\", null: false\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.boolean \"is_active\", default: true\n    t.index [\"supervisor_id\"], name: \"index_supervisor_volunteers_on_supervisor_id\"\n    t.index [\"volunteer_id\"], name: \"index_supervisor_volunteers_on_volunteer_id\"\n  end\n\n  create_table \"task_records\", id: false, force: :cascade do |t|\n    t.string \"version\", null: false\n  end\n\n  create_table \"user_languages\", force: :cascade do |t|\n    t.bigint \"user_id\"\n    t.bigint \"language_id\"\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.index [\"language_id\", \"user_id\"], name: \"index_user_languages_on_language_id_and_user_id\", unique: true\n  end\n\n  create_table \"user_reminder_times\", force: :cascade do |t|\n    t.bigint \"user_id\", null: false\n    t.datetime \"case_contact_types\"\n    t.datetime \"no_contact_made\"\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.index [\"user_id\"], name: \"index_user_reminder_times_on_user_id\"\n  end\n\n  create_table \"user_sms_notification_events\", force: :cascade do |t|\n    t.bigint \"user_id\", null: false\n    t.bigint \"sms_notification_event_id\", null: false\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n  end\n\n  create_table \"users\", force: :cascade do |t|\n    t.string \"email\", default: \"\", null: false\n    t.string \"encrypted_password\", default: \"\", null: false\n    t.string \"reset_password_token\"\n    t.datetime \"reset_password_sent_at\", precision: nil\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.bigint \"casa_org_id\", null: false\n    t.string \"display_name\", default: \"\", null: false\n    t.string \"invitation_token\"\n    t.datetime \"invitation_created_at\", precision: nil\n    t.datetime \"invitation_sent_at\", precision: nil\n    t.datetime \"invitation_accepted_at\", precision: nil\n    t.integer \"invitation_limit\"\n    t.string \"invited_by_type\"\n    t.bigint \"invited_by_id\"\n    t.integer \"invitations_count\", default: 0\n    t.string \"type\"\n    t.boolean \"active\", default: true\n    t.integer \"sign_in_count\", default: 0, null: false\n    t.datetime \"current_sign_in_at\", precision: nil\n    t.datetime \"last_sign_in_at\", precision: nil\n    t.string \"current_sign_in_ip\"\n    t.string \"last_sign_in_ip\"\n    t.string \"phone_number\", default: \"\"\n    t.boolean \"receive_sms_notifications\", default: false, null: false\n    t.boolean \"receive_email_notifications\", default: true\n    t.string \"confirmation_token\"\n    t.datetime \"confirmed_at\"\n    t.datetime \"confirmation_sent_at\"\n    t.string \"unconfirmed_email\"\n    t.string \"old_emails\", default: [], array: true\n    t.boolean \"receive_reimbursement_email\", default: false\n    t.boolean \"monthly_learning_hours_report\", default: false, null: false\n    t.datetime \"date_of_birth\"\n    t.index [\"casa_org_id\"], name: \"index_users_on_casa_org_id\"\n    t.index [\"confirmation_token\"], name: \"index_users_on_confirmation_token\", unique: true\n    t.index [\"email\"], name: \"index_users_on_email\", unique: true\n    t.index [\"invitation_token\"], name: \"index_users_on_invitation_token\", unique: true\n    t.index [\"invited_by_id\"], name: \"index_users_on_invited_by_id\"\n    t.index [\"reset_password_token\"], name: \"index_users_on_reset_password_token\", unique: true\n  end\n\n  add_foreign_key \"active_storage_attachments\", \"active_storage_blobs\", column: \"blob_id\"\n  add_foreign_key \"active_storage_variant_records\", \"active_storage_blobs\", column: \"blob_id\"\n  add_foreign_key \"additional_expenses\", \"case_contacts\"\n  add_foreign_key \"addresses\", \"users\"\n  add_foreign_key \"api_credentials\", \"users\"\n  add_foreign_key \"banners\", \"casa_orgs\"\n  add_foreign_key \"banners\", \"users\"\n  add_foreign_key \"casa_case_emancipation_categories\", \"casa_cases\"\n  add_foreign_key \"casa_case_emancipation_categories\", \"emancipation_categories\"\n  add_foreign_key \"casa_case_emancipation_options\", \"casa_cases\"\n  add_foreign_key \"casa_case_emancipation_options\", \"emancipation_options\"\n  add_foreign_key \"casa_cases\", \"casa_orgs\"\n  add_foreign_key \"case_assignments\", \"casa_cases\"\n  add_foreign_key \"case_assignments\", \"users\", column: \"volunteer_id\"\n  add_foreign_key \"case_contacts\", \"casa_cases\"\n  add_foreign_key \"case_contacts\", \"users\", column: \"creator_id\"\n  add_foreign_key \"case_court_orders\", \"casa_cases\"\n  add_foreign_key \"case_group_memberships\", \"casa_cases\"\n  add_foreign_key \"case_group_memberships\", \"case_groups\"\n  add_foreign_key \"case_groups\", \"casa_orgs\"\n  add_foreign_key \"contact_topic_answers\", \"case_contacts\"\n  add_foreign_key \"contact_topic_answers\", \"contact_topics\"\n  add_foreign_key \"contact_topics\", \"casa_orgs\"\n  add_foreign_key \"court_dates\", \"casa_cases\"\n  add_foreign_key \"custom_org_links\", \"casa_orgs\"\n  add_foreign_key \"emancipation_options\", \"emancipation_categories\"\n  add_foreign_key \"followups\", \"users\", column: \"creator_id\"\n  add_foreign_key \"judges\", \"casa_orgs\"\n  add_foreign_key \"languages\", \"casa_orgs\"\n  add_foreign_key \"learning_hour_topics\", \"casa_orgs\"\n  add_foreign_key \"learning_hour_types\", \"casa_orgs\"\n  add_foreign_key \"learning_hours\", \"learning_hour_types\"\n  add_foreign_key \"learning_hours\", \"users\"\n  add_foreign_key \"mileage_rates\", \"casa_orgs\"\n  add_foreign_key \"mileage_rates\", \"users\"\n  add_foreign_key \"notes\", \"users\", column: \"creator_id\"\n  add_foreign_key \"other_duties\", \"users\", column: \"creator_id\"\n  add_foreign_key \"patch_notes\", \"patch_note_groups\"\n  add_foreign_key \"patch_notes\", \"patch_note_types\"\n  add_foreign_key \"placement_types\", \"casa_orgs\"\n  add_foreign_key \"placements\", \"casa_cases\"\n  add_foreign_key \"placements\", \"placement_types\"\n  add_foreign_key \"placements\", \"users\", column: \"creator_id\"\n  add_foreign_key \"preference_sets\", \"users\"\n  add_foreign_key \"sent_emails\", \"casa_orgs\"\n  add_foreign_key \"sent_emails\", \"users\"\n  add_foreign_key \"supervisor_volunteers\", \"users\", column: \"supervisor_id\"\n  add_foreign_key \"supervisor_volunteers\", \"users\", column: \"volunteer_id\"\n  add_foreign_key \"user_reminder_times\", \"users\"\n  add_foreign_key \"user_sms_notification_events\", \"sms_notification_events\"\n  add_foreign_key \"user_sms_notification_events\", \"users\"\n  add_foreign_key \"users\", \"casa_orgs\"\nend\n"
  },
  {
    "path": "db/seeds/api_credential_data.rb",
    "content": "ApiCredential.destroy_all\nusers = User.all\n\nusers.each do |user|\n  ApiCredential.create!(user: user, api_token_digest: Digest::SHA256.hexdigest(SecureRandom.hex(18)), refresh_token_digest: Digest::SHA256.hexdigest(SecureRandom.hex(18)))\nend\n"
  },
  {
    "path": "db/seeds/casa_org_populator_presets.rb",
    "content": "# Preset option sets for various sizes and for the Rails environments.\n# These do not include `org_name`, which can be provided separately by the caller to override default name.\nmodule CasaOrgPopulatorPresets\n  module_function\n\n  def minimal_dataset_options\n    {\n      case_count: 1,\n      volunteer_count: 1,\n      supervisor_count: 1,\n      casa_admin_count: 1\n    }\n  end\n\n  def small_dataset_options\n    {\n      case_count: 8,\n      volunteer_count: 5,\n      supervisor_count: 2,\n      casa_admin_count: 1\n    }\n  end\n\n  def medium_dataset_options\n    {\n      case_count: 75,\n      volunteer_count: 50,\n      supervisor_count: 4,\n      casa_admin_count: 2\n    }\n  end\n\n  def large_dataset_options\n    {\n      case_count: 160,\n      volunteer_count: 100,\n      supervisor_count: 10,\n      casa_admin_count: 3\n    }\n  end\n\n  def for_environment\n    {\n      \"development\" => CasaOrgPopulatorPresets.small_dataset_options,\n      \"qa\" => CasaOrgPopulatorPresets.large_dataset_options,\n      \"staging\" => CasaOrgPopulatorPresets.large_dataset_options,\n      \"test\" => CasaOrgPopulatorPresets.small_dataset_options\n    }[ENV[\"APP_ENVIRONMENT\"] || Rails.env]\n  end\nend\n"
  },
  {
    "path": "db/seeds/db_populator.rb",
    "content": "# Called by the seeding process to create data with a specified random number generator.\n# There is a 1 in 30 probability that a Volunteer will be inactive when created.\n# There is no instance of a volunteer who was previously assigned a case being inactivated.\n# Email addresses generated will be globally unique across all orgs.\n\nclass DbPopulator\n  SEED_PASSWORD = \"12345678\"\n  WORD_LENGTH_TUNING = 10\n  LINE_BREAK_TUNING = 5\n  PREFIX_OPTIONS = (\"A\".ord..\"Z\".ord).to_a.map(&:chr)\n\n  attr_reader :rng\n\n  # Public Methods\n\n  # Pass an instance of Random for Faker and Ruby `rand` and sample` calls.\n  def initialize(random_instance, case_fourteen_years_old: false)\n    @rng = random_instance\n    @casa_org_counter = 0\n    @case_number_sequence = 1000\n    @case_fourteen_years_old = case_fourteen_years_old\n  end\n\n  def create_all_casa_admin(email)\n    return if AllCasaAdmin.find_by(email: email)\n    AllCasaAdmin.create!(email: email, password: SEED_PASSWORD, password_confirmation: SEED_PASSWORD)\n  end\n\n  def create_org(options)\n    @casa_org_counter += 1\n\n    options.org_name ||= \"CASA Organization ##{@casa_org_counter}\"\n    CasaOrg.find_or_create_by!(name: options.org_name) { |org|\n      org.name = options.org_name\n      org.display_name = options.org_name\n      org.address = Faker::Address.full_address\n      org.footer_links = [\n        [\"https://example.org/contact/\", \"Contact Us\"],\n        [\"https://example.org/subscribe-to-newsletter/\", \"Subscribe to newsletter\"],\n        [\"https://www.example.org/give/givefrm.asp?CID=4450\", \"Donate\"]\n      ]\n      org.logo.attach(io: File.open(CasaOrg::CASA_DEFAULT_LOGO), filename: CasaOrg::CASA_DEFAULT_LOGO.basename.to_s)\n    }\n  end\n\n  # Create 2 judges for each casa_org.\n  def create_judges(casa_org)\n    2.times { Judge.create(name: Faker::Name.name, casa_org: casa_org) }\n  end\n\n  # Creates 3 users, 1 each for [Volunteer, Supervisor, CasaAdmin].\n  # For org's after the first one created, adds an org number to the email address so that they will be globally unique\n  def create_users(casa_org, options)\n    # Generate email address; for orgs only after first org, and org number would be added, e.g.:\n    # Org #1: volunteer1@example.com\n    # Org #2: volunteer2-1@example.com\n    email = ->(klass, n) do\n      org_fragment = (@casa_org_counter > 1) ? \"#{@casa_org_counter}-\" : \"\"\n      klass.name.underscore + org_fragment + n.to_s + \"@example.com\"\n    end\n\n    create_users_of_type = ->(klass, count) do\n      (1..count).each do |n|\n        current_email = email.call(klass, n)\n        attributes = {\n          casa_org: casa_org,\n          email: current_email,\n          password: SEED_PASSWORD,\n          password_confirmation: SEED_PASSWORD,\n          display_name: Faker::Name.name,\n          phone_number: Faker::PhoneNumber.cell_phone_in_e164,\n          active: true,\n          confirmed_at: Time.now\n        }\n        # Approximately 1 out of 30 volunteers should be set to inactive.\n        if klass == Volunteer && rng.rand(30) == 0\n          attributes[:active] = false\n        end\n        unless klass.find_by(email: current_email)\n          klass.create!(attributes)\n        end\n      end\n    end\n\n    create_users_of_type.call(CasaAdmin, options.casa_admin_count)\n    create_users_of_type.call(Supervisor, options.supervisor_count)\n    create_users_of_type.call(Volunteer, options.volunteer_count)\n    supervisors = Supervisor.all.to_a\n    Volunteer.all.each { |v| v.supervisor = supervisors.sample(random: rng) }\n  end\n\n  # Create other duties (Volunteer only)\n  # Increment other_duties_counter by 1 each time other duty is created\n  # Print out statement that indicates number of other duties created\n\n  def create_other_duties\n    Volunteer.find_each do |v|\n      2.times {\n        OtherDuty.create!(\n          creator_id: v.id,\n          creator_type: \"Volunteer\",\n          occurred_at: Faker::Date.between(from: 2.days.ago, to: Date.today),\n          duration_minutes: rand(5..180),\n          notes: Faker::Lorem.sentence\n        )\n      }\n    end\n  end\n\n  def create_case_contacts(casa_case)\n    draft_statuses = %w[started details notes expenses]\n\n    case_contact_statuses = Array.new(random_zero_one_count, draft_statuses.sample(random: rng)) +\n      Array.new(random_case_contact_count, \"active\")\n\n    case_contact_statuses.shuffle(random: rng).each do |status|\n      create_case_contact(casa_case, status:)\n    end\n  end\n\n  def create_case_contact(casa_case, status: \"active\")\n    params = base_case_contact_params(casa_case, status)\n\n    status_modifications = {\n      \"started\" => {want_driving_reimbursement: false, draft_case_ids: [], medium_type: nil, occurred_at: nil, duration_minutes: nil, notes: nil, miles_driven: 0},\n      \"details\" => {want_driving_reimbursement: false, notes: nil, miles_driven: 0},\n      \"notes\" => {want_driving_reimbursement: false, miles_driven: 0},\n      \"expenses\" => {want_driving_reimbursement: true}\n    }\n\n    params.merge!(status_modifications[status]) unless status == \"active\"\n    CaseContact.create!(params)\n  end\n\n  def create_cases(casa_org, options)\n    ContactTypePopulator.populate\n    options.case_count.times do |index|\n      case_number = generate_case_number\n      court_date = generate_court_date\n      court_report_submitted = index.even?\n\n      new_casa_case = CasaCase.find_by(case_number: case_number)\n      birth_month_year_youth = @case_fourteen_years_old ? ((Date.today - 18.year)..(Date.today - CasaCase::TRANSITION_AGE.year)).to_a.sample : ((Date.today - 18.year)..(Date.today - 1.year)).to_a.sample\n      new_casa_case ||= CasaCase.find_or_create_by!(\n        casa_org_id: casa_org.id,\n        case_number: case_number,\n        court_report_submitted_at: court_report_submitted ? Date.today : nil,\n        court_report_status: court_report_submitted ? :submitted : :not_submitted,\n        birth_month_year_youth: birth_month_year_youth,\n        date_in_care: Date.today - (rand * 1500)\n      )\n      new_court_date = CourtDate.find_or_create_by!(\n        casa_case: new_casa_case,\n        court_report_due_date: court_date + 1.month,\n        date: court_date\n      )\n\n      volunteer = new_casa_case.casa_org.volunteers.active.sample(random: rng) ||\n        new_casa_case.casa_org.volunteers.active.first ||\n        Volunteer.create!(\n          casa_org: new_casa_case.casa_org,\n          email: \"#{SecureRandom.hex(10)}@example.com\",\n          password: SEED_PASSWORD,\n          display_name: \"active volunteer\"\n        )\n      CaseAssignment.find_or_create_by!(casa_case: new_casa_case, volunteer: volunteer)\n\n      random_court_order_count.times do\n        CaseCourtOrder.create!(\n          casa_case_id: new_casa_case.id,\n          court_date: new_court_date,\n          text: order_choices.sample(random: rng),\n          implementation_status: CaseCourtOrder::IMPLEMENTATION_STATUSES.values.sample(random: rng)\n        )\n      end\n\n      random_zero_one_count.times do |index|\n        CourtDate.create!(\n          casa_case_id: new_casa_case.id,\n          date: Date.today + 5.weeks\n        )\n      end\n\n      random_past_court_date_count.times do |index|\n        CourtDate.create!(\n          casa_case_id: new_casa_case.id,\n          date: Date.today - (index + 1).weeks\n        )\n      end\n\n      create_case_contacts(new_casa_case)\n\n      # guarantee at least one case contact before and after most recent past court date\n      if most_recent_past_court_date(new_casa_case.id)\n        if !case_contact_before_last_court_date?(new_casa_case.id, most_recent_past_court_date(new_casa_case.id))\n          new_case_contact = create_case_contact(new_casa_case)\n          new_case_contact.occurred_at = most_recent_past_court_date(new_casa_case.id) - 24.hours\n          new_case_contact.save!\n        end\n\n        if !case_contact_after_last_court_date?(new_casa_case.id, most_recent_past_court_date(new_casa_case.id))\n          new_case_contact = create_case_contact(new_casa_case)\n          new_case_contact.occurred_at = most_recent_past_court_date(new_casa_case.id) + 24.hours\n          new_case_contact.save!\n        end\n      end\n\n      # guarantee at least one transition aged youth case to \"volunteer1\"\n      volunteer1 = Volunteer.find_by(email: \"volunteer1@example.com\")\n      if volunteer1.casa_cases.where(birth_month_year_youth: ..CasaCase::TRANSITION_AGE.years.ago).blank?\n        rand(1..3).times do\n          birth_month_year_youth = ((Date.today - 18.year)..(Date.today - CasaCase::TRANSITION_AGE.year)).to_a.sample\n          new_casa_case = volunteer1.casa_cases.find_or_create_by!(\n            casa_org_id: volunteer1.casa_org.id,\n            case_number: generate_case_number,\n            court_report_submitted_at: court_report_submitted ? Date.today : nil,\n            court_report_status: court_report_submitted ? :submitted : :not_submitted,\n            birth_month_year_youth: birth_month_year_youth\n          )\n          CourtDate.find_or_create_by!(\n            casa_case: new_casa_case,\n            court_report_due_date: court_date + 1.month,\n            date: court_date\n          )\n        end\n      end\n    end\n  end\n\n  def create_hearing_types(casa_org)\n    active_hearing_type_names = [\n      \"emergency hearing\",\n      \"trial on the merits\",\n      \"scheduling conference\",\n      \"uncontested hearing\",\n      \"pendente lite hearing\",\n      \"pretrial conference\"\n    ]\n    inactive_hearing_type_names = [\n      \"deprecated hearing\"\n    ]\n    active_hearing_type_names.each do |hearing_type_name|\n      HearingType.find_or_create_by!(\n        casa_org_id: casa_org.id,\n        name: hearing_type_name,\n        active: true\n      )\n    end\n    inactive_hearing_type_names.each do |hearing_type_name|\n      HearingType.find_or_create_by!(\n        casa_org_id: casa_org.id,\n        name: hearing_type_name,\n        active: false\n      )\n    end\n  end\n\n  def create_checklist_items\n    checklist_item_categories = [\n      \"Education/Vocation\",\n      \"Placement\",\n      \"Category 3\"\n    ]\n    checklist_item_descriptions = [\n      \"checklist item description 1\",\n      \"checklist item description 2\",\n      \"checklist item description 3\"\n    ]\n    mandatory_options = [true, false]\n    half_of_the_hearing_types = HearingType.all.slice(0, HearingType.all.length / 2)\n    half_of_the_hearing_types.each do |hearing_type|\n      ChecklistItem.create(\n        hearing_type_id: hearing_type.id,\n        description: checklist_item_descriptions.sample,\n        category: checklist_item_categories.sample,\n        mandatory: mandatory_options.sample\n      )\n      hearing_type.update_attribute(:checklist_updated_date, \"Updated #{Time.new.strftime(\"%m/%d/%Y\")}\")\n    end\n  end\n\n  def create_languages(casa_org)\n    create_language(\"Spanish\", casa_org)\n    create_language(\"Vietnamese\", casa_org)\n    create_language(\"French\", casa_org)\n    create_language(\"Chinese Cantonese\", casa_org)\n    create_language(\"ASL\", casa_org)\n    create_language(\"Other\", casa_org)\n  end\n\n  def create_language(name, casa_org)\n    Language.find_or_create_by!(name: name, casa_org: casa_org)\n  end\n\n  def create_mileage_rates(casa_org)\n    attempt_count = 5\n    i = 0\n\n    while i < attempt_count\n      begin\n        MileageRate.create!({\n          amount: Faker::Number.between(from: 0.0, to: 1.0).round(2),\n          effective_date: Faker::Date.backward(days: 700),\n          is_active: true,\n          casa_org_id: casa_org.id\n        })\n      rescue ActiveRecord::RecordInvalid\n        attempt_count += 1\n      end\n\n      i += 1\n    end\n  end\n\n  def create_learning_hour_types(casa_org)\n    learning_types = %w[book movie webinar conference other]\n\n    learning_types.each do |learning_type|\n      learning_hour_type = casa_org.learning_hour_types.new(name: learning_type.capitalize)\n      learning_hour_type.position = 99 if learning_type == \"other\"\n      learning_hour_type.save\n    end\n  end\n\n  def create_learning_hour_topics(casa_org)\n    learning_topics = %w[cases reimbursements court_reports]\n    learning_topics.each do |learning_topic|\n      learning_hour_topic = casa_org.learning_hour_topics.new(name: learning_topic.humanize.capitalize)\n      learning_hour_topic.save\n    end\n  end\n\n  def create_learning_hours(casa_org)\n    casa_org.volunteers.each do |user|\n      [1, 2, 3].sample.times do\n        learning_hour_topic = casa_org.learning_hour_topics.sample\n        learning_hour_type = casa_org.learning_hour_types.sample\n        # randomize between 30 to 180 minutes\n        duration_minutes = (2..12).to_a.sample * 15\n        duration_hours = duration_minutes / 60\n        duration_minutes %= 60\n        occurred_at = Time.current - (1..7).to_a.sample.days\n        LearningHour.create(\n          user:,\n          learning_hour_type:,\n          name: \"#{learning_hour_type.name} on #{learning_hour_topic.name}\",\n          duration_hours:,\n          duration_minutes:,\n          occurred_at:,\n          learning_hour_topic:\n        )\n      end\n    end\n  end\n\n  private # -------------------------------------------------------------------------------------------------------\n\n  def most_recent_past_court_date(casa_case_id)\n    CourtDate.where(\n      \"date < ? AND casa_case_id = ?\",\n      Date.today,\n      casa_case_id\n    ).order(date: :desc).first&.date\n  end\n\n  def case_contact_before_last_court_date?(casa_case_id, date)\n    CaseContact.where(\n      \"occurred_at < ? AND casa_case_id = ?\",\n      date,\n      casa_case_id\n    ).any?\n  end\n\n  def case_contact_after_last_court_date?(case_case_id, date)\n    CaseContact.where(\n      \"occurred_at > ? AND casa_case_id = ?\",\n      date,\n      case_case_id\n    ).any?\n  end\n\n  def order_choices\n    [\n      \"Limited guardianship of the children for medical and educational purposes to [name] shall be rescinded;\",\n      \"The children shall remain children in need of assistance (cina), under the jurisdiction of the juvenile court, and shall remain committed to the department of health and human services/child welfare services, for continued placement on a trial home visit with [NAME]\",\n      \"The youth shall continue to participate in educational tutoring, under the direction of the department;\",\n      \"The youth shall continue to participate in family therapy with [name], under the direction of the department;\",\n      \"The permanency plan for all the children of reunification is reaffirmed;\",\n      \"Visitation between the youth and the father shall be unsupervised, minimum once weekly, in the community or at his home, and may include overnights when he has the appropriate space for the children to sleep, under the direction of the department;\",\n      \"Youth shall continue to participate in individual therapy, under the direction of the department;\",\n      \"The youth shall continue to maintain stable employment;\",\n      \"The youth shall maintain appropriate housing while working towards obtaining housing that can accommodate all of the children being reunified, and make home available for inspection, under the direction of the department;\",\n      \"The youth shall participate in case management services, under the direction of the department;\",\n      \"The youth shall participate in mental health treatment and medication management, under the direction of the department;\"\n    ]\n  end\n\n  def transition_aged_youth?(birth_month_year_youth)\n    (Date.today - birth_month_year_youth).days.in_years > CasaCase::TRANSITION_AGE\n  end\n\n  def base_case_contact_params(casa_case, status)\n    {\n      casa_case: casa_case,\n      creator: casa_case.volunteers.sample(random: rng),\n      duration_minutes: likely_contact_durations.sample(random: rng),\n      occurred_at: rng.rand(0..6).months.ago,\n      contact_types: ContactType.all.sample(2, random: rng),\n      medium_type: CaseContact::CONTACT_MEDIUMS.sample(random: rng),\n      miles_driven: rng.rand(5..40),\n      want_driving_reimbursement: random_true_false,\n      contact_made: random_true_false,\n      notes: note_generator,\n      status: status,\n      draft_case_ids: [casa_case&.id]\n    }\n  end\n\n  def random_case_contact_count\n    @random_case_contact_counts ||= [0, 1, 2, 2, 2, 3, 3, 3, 11, 11, 11]\n    @random_case_contact_counts.sample(random: rng)\n  end\n\n  def random_past_court_date_count\n    @random_past_court_date_counts ||= [0, 2, 3, 4, 5]\n    @random_past_court_date_counts.sample(random: rng)\n  end\n\n  def random_zero_one_count\n    @random_zero_one_count ||= [0, 1]\n    @random_zero_one_count.sample(random: rng)\n  end\n\n  def random_court_order_count\n    @random_court_order_counts ||= [0, 3, 5, 10]\n    @random_court_order_counts.sample(random: rng)\n  end\n\n  def likely_contact_durations\n    @likely_contact_durations ||= [15, 30, 60, 75, 4 * 60, 6 * 60]\n  end\n\n  def note_generator\n    paragraph_count = Random.rand(6)\n    (0..paragraph_count).map { |index|\n      Faker::Lorem.paragraph(sentence_count: 5, supplemental: true, random_sentences_to_add: 20)\n    }.join(\"\\n\\n\")\n  end\n\n  def generate_case_number\n    # CINA-YY-XXXX\n    years = ((DateTime.now.year - 20)..DateTime.now.year).to_a\n    yy = years.sample(random: rng).to_s[2..3]\n    @case_number_sequence += 1\n    \"CINA-#{yy}-#{@case_number_sequence}\"\n  end\n\n  def generate_court_date\n    ((Date.today + 1.month)..(Date.today + 5.months)).to_a.sample\n  end\n\n  def random_true_false\n    @true_false_array ||= [true, false]\n    @true_false_array.sample(random: rng)\n  end\nend\n"
  },
  {
    "path": "db/seeds/default_contact_topics.yml",
    "content": "- question: \"Background information\"\n  details: |-\n    a) When did the family first come into contact with the Department of Social Services or Department of Juvenile Justice – how many times?\n    b) Tell the history of their involvement with the department and any facts about their life that could help determine the need for placement and/or services.\n    c) Discuss the child’s history – behavior problems, educational history, medical history, psychological history (any hospitalizations, previous counseling, etc.)\n    d) If child has been placed previously give a history of the child’s placements (placed with different parents, relatives, DSS, etc).\n- question: \"Current situation\"\n  details: |-\n    a) Where is the child placed?\n    b) How is the child adjusting to the placement?\n    c) Are there any issues or concerns about the placement? If so, describe these concerns and specify the actions being taken to address them.\n- question: \"Education, vocation, or daycare\"\n  details: |-\n    a) Where is the child placed for education (daycare, public school, non-public school, GED, Job Corps, etc)?\n    b) How is the child adjusting to the educational placement? Are there any education-related concerns at this point? If yes, detail them and mention the steps taken to address them.\n    c) Does the child have an IEP? If not, is there a need for one?\n    d) Is the child employed? If not, are they looking for a job?\n    e) Does the child have vocational/life skills? Are they attending life skill classes?\n    f) Are there any other life skill needs? (Driver’s education, state ID, transportation assistance, etc.)\n    g) What is the feedback from professionals providing these services about the child's progress? Include strengths and not just needs.\n- question: \"Health and mental health\"\n  details: |-\n    a) Is the child up to date with medical exams?\n    b) Are there any other medical concerns?\n    c) Is the child receiving therapy, medication monitoring, mentoring, or other services? If so, specify with whom these services are being received.\n- question: \"Family and community connections\"\n  details: |-\n    a) Is this child seeing parents, siblings, other relatives? If so, who is the child visiting, and how often? Does the child desire a different arrangement?\n    b) Detail the steps parents have taken to address court orders. Address any barriers and highlight positive steps.\n- question: \"Child’s strengths\"\n  details: |-\n    a) Describe the child’s strengths, interests, and hobbies to provide a well-rounded perspective.\n"
  },
  {
    "path": "db/seeds/emancipation_data.rb",
    "content": "# Emancipation Checklist Form Data\ncategory_housing = EmancipationCategory.where(name: \"Youth has housing.\").first_or_create(mutually_exclusive: false)\ncategory_housing.add_option(\"With friend\")\ncategory_housing.add_option(\"With relative\")\ncategory_housing.add_option(\"With former foster parent\")\ncategory_housing.add_option(\"Subsidized (e.g., FUP, Future Bridges, adult services)\")\ncategory_housing.add_option(\"Independently (e.g., renting own apartment or room)\")\n\ncategory_income = EmancipationCategory.where(name: \"Youth has income to achieve self-sufficiency.\").first_or_create(mutually_exclusive: false)\ncategory_income.add_option(\"Employment\")\ncategory_income.add_option(\"Public benefits/TCA\")\ncategory_income.add_option(\"SSI\")\ncategory_income.add_option(\"SSDI\")\ncategory_income.add_option(\"Inheritance/survivors benefits\")\n\nEmancipationCategory.where(name: \"Youth has completed a budget.\").first_or_create(mutually_exclusive: false)\n\ncategory_employment = EmancipationCategory.where(name: \"Youth is employed.\").first_or_create(mutually_exclusive: true)\ncategory_employment.add_option(\"Part-time job\")\ncategory_employment.add_option(\"Full-time job\")\ncategory_employment.add_option(\"Apprenticeship or paid internship\")\ncategory_employment.add_option(\"Self-employed\")\n\ncategory_continuing_education = EmancipationCategory.where(name: \"Youth is attending an educational or vocational program.\").first_or_create(mutually_exclusive: true)\ncategory_continuing_education.add_option(\"High school\")\ncategory_continuing_education.add_option(\"Post-secondary/college\")\ncategory_continuing_education.add_option(\"Vocational\")\ncategory_continuing_education.add_option(\"GED program\")\n\ncategory_high_school_diploma = EmancipationCategory.where(name: \"Youth has a high school diploma or equivalency.\").first_or_create(mutually_exclusive: true)\ncategory_high_school_diploma.add_option(\"Traditional\")\ncategory_high_school_diploma.add_option(\"Out of school program\")\ncategory_high_school_diploma.add_option(\"GED\")\n\ncategory_medical_insurance = EmancipationCategory.where(name: \"Youth has medical insurance.\").first_or_create(mutually_exclusive: false)\ncategory_medical_insurance.add_option(\"Has medical insurance card\")\ncategory_medical_insurance.add_option(\"Knows his/her/their primary care entity\")\ncategory_medical_insurance.add_option(\"Knows how to continue insurance coverage\")\ncategory_medical_insurance.add_option(\"Knows that dental insurance ends at age 21\")\ncategory_medical_insurance.add_option(\"Has plan for dental care\")\n\nEmancipationCategory.where(name: \"Youth can identify permanent family and/or adult connections.\").first_or_create(mutually_exclusive: false)\n\ncategory_community = EmancipationCategory.where(name: \"Youth is accessing community activities.\").first_or_create(mutually_exclusive: false)\ncategory_community.add_option(\"Arts activities (e.g., singing, dancing, theater)\")\ncategory_community.add_option(\"Religious affiliations (e.g., church, mosque)\")\ncategory_community.add_option(\"Athletics/team sports\")\ncategory_community.add_option(\"Other\")\n\ncategory_documents = EmancipationCategory.where(name: \"Youth has all identifying documents.\").first_or_create(mutually_exclusive: false)\ncategory_documents.add_option(\"Birth certificate (original or certified copy)\")\ncategory_documents.add_option(\"Social security card\")\ncategory_documents.add_option(\"Learner's permit\")\ncategory_documents.add_option(\"Driver's license\")\ncategory_documents.add_option(\"Immigration documents\")\ncategory_documents.add_option(\"State identification card\")\n\ncategory_transportation = EmancipationCategory.where(name: \"Youth has access to transportation.\").first_or_create(mutually_exclusive: false)\ncategory_transportation.add_option(\"Vehicle\")\ncategory_transportation.add_option(\"Public transportation\")\n\ncategory_juvenile_criminal_cases = EmancipationCategory.where(name: \"Youth has been or is involved in past or current juvenile cases.\").first_or_create(mutually_exclusive: false)\ncategory_juvenile_criminal_cases.add_option(\"All juvenile issues have been resolved.\")\ncategory_juvenile_criminal_cases.add_option(\"All eligible juvenile records have been expunged.\")\n\ncategory_adult_criminal_cases = EmancipationCategory.where(name: \"Youth has been or is involved in past or current adult criminal cases.\").first_or_create(mutually_exclusive: false)\ncategory_adult_criminal_cases.add_option(\"All adult criminal cases have been resolved.\")\ncategory_adult_criminal_cases.add_option(\"All eligible adult criminal records have been expunged.\")\n\nEmancipationCategory.where(name: \"Youth has been or is involved in civil or family cases.\").first_or_create(mutually_exclusive: false)\n\ncategory_bank_account = EmancipationCategory.where(name: \"Youth has a bank account in good standing.\").first_or_create(mutually_exclusive: false)\ncategory_bank_account.add_option(\"Checking\")\ncategory_bank_account.add_option(\"Savings\")\n\ncategory_credit = EmancipationCategory.where(name: \"Youth has obtained a copy of his/her/their credit report.\").first_or_create(mutually_exclusive: false)\ncategory_credit.add_option(\"An adult has reviewed the credit report with Youth.\")\ncategory_credit.add_option(\"Issues or concerns\")\n\nEmancipationCategory.where(name: \"Youth can identify his/her/their core values.\").first_or_create(mutually_exclusive: false)\nEmancipationCategory.where(name: \"Youth has completed the Ansell Casey Assessment.\").first_or_create(mutually_exclusive: false)\n"
  },
  {
    "path": "db/seeds/emancipation_options_prune.rb",
    "content": "def get_category_by_name(category_name)\n  EmancipationCategory.where(name: category_name)&.first\nend\n\nget_category_by_name(\"Youth has completed a budget.\")&.delete_option(\"Completed budget\")\nget_category_by_name(\"Youth is employed.\")&.delete_option(\"Not employed\")\nget_category_by_name(\"Youth is attending an educational or vocational program.\")&.delete_option(\"Not attending\")\nget_category_by_name(\"Youth has a high school diploma or equivalency.\")&.delete_option(\"No\")\nget_category_by_name(\"Youth can identify permanent family and/or adult connections.\")&.delete_option(\"Has connections\")\nget_category_by_name(\"Youth has been or is involved in civil or family cases.\")&.delete_option(\"All civil or family cases have been resolved.\")\nget_category_by_name(\"Youth can identify his/her/their core values.\")&.delete_option(\"Identified values\")\nget_category_by_name(\"Youth has completed the Ansell Casey Assessment.\")&.delete_option(\"Threshold for self-sufficiency was met.\")\nget_category_by_name(\"Youth has completed the Ansell Casey Assessment.\")&.update_attribute(:name, \"Youth has completed the Ansell Casey Assessment and threshold for self-sufficiency was met.\")\n"
  },
  {
    "path": "db/seeds/patch_note_group_data.rb",
    "content": "# PatchNote Section Headers\n\nPatchNoteGroup.where(value: \"CasaAdmin+Supervisor\").first_or_create\nPatchNoteGroup.where(value: \"CasaAdmin+Supervisor+Volunteer\").first_or_create\n"
  },
  {
    "path": "db/seeds/patch_note_type_data.rb",
    "content": "# PatchNote Section Headers\n\nPatchNoteType.where(name: \"Coming Up\").first_or_create\nPatchNoteType.where(name: \"Fixes\").first_or_create\nPatchNoteType.where(name: \"What's New?\").first_or_create\n"
  },
  {
    "path": "db/seeds/placement_data.rb",
    "content": "casa_orgs = CasaOrg.all\n\nplacement_types = [\n  \"Reunification\",\n  \"Custody/Guardianship by a relative\",\n  \"Custody/Guardianship by a non-relative\",\n  \"Adoption by relative\",\n  \"Adoption by a non-relative\",\n  \"APPLA\"\n]\n\ncasa_orgs.each do |org|\n  placement_types.each do |label|\n    PlacementType.where(name: label, casa_org: org).first_or_create\n  end\nend\n"
  },
  {
    "path": "db/seeds.rb",
    "content": "# This seed script populates the development DB with a data set whose size is dependent on the Rails environment.\n\n# You can control the randomness of the data provided by FAKER and the Rails libraries via the DB_SEEDS_RANDOM_SEED environment variable.\n# If you specify a number, that number will be used as the seed, so you can enforce consistent data across runs\n#   with nondefault content.\n# If you specify the string 'random' (e.g. `export DB_SEEDS_RANDOM_SEED=random`), a random seed will be assigned for you.\n# If you don't specify anything, 0 will be used as the seed, ensuring consistent data across hosts and runs.\n\nrequire_relative \"seeds/casa_org_populator_presets\"\nrequire_relative \"seeds/db_populator\"\nrequire_relative \"../lib/tasks/data_post_processors/case_contact_populator\"\nrequire_relative \"../lib/tasks/data_post_processors/contact_type_populator\"\nrequire_relative \"../lib/tasks/data_post_processors/sms_notification_event_populator\"\nrequire_relative \"../lib/tasks/data_post_processors/contact_topic_populator\"\n\nclass SeederMain\n  attr_reader :db_populator, :rng\n\n  def initialize\n    random_seed = get_seed_specification\n    @rng = Random.new(random_seed) # rng = random number generator\n    @db_populator = DbPopulator.new(rng)\n    Faker::Config.random = rng\n    Faker::Config.locale = \"en-US\" # only allow US phone numbers\n  end\n\n  def seed\n    log \"NOTE: CASA seed does not delete anything anymore! Run rake db:seed:replant to delete everything and re-seed\"\n    log \"Creating the objects in the database...\"\n    db_populator.create_all_casa_admin(\"allcasaadmin@example.com\")\n    db_populator.create_all_casa_admin(\"all_casa_admin1@example.com\")\n    db_populator.create_all_casa_admin(\"admin1@example.com\")\n\n    options1 = OpenStruct.new(CasaOrgPopulatorPresets.for_environment.merge({org_name: \"Prince George CASA\"}))\n    org1 = db_populator.create_org(options1)\n    create_org_related_data(db_populator, org1, options1)\n\n    options2 = OpenStruct.new(CasaOrgPopulatorPresets.minimal_dataset_options)\n    org2 = db_populator.create_org(options2)\n    create_org_related_data(db_populator, org2, options2)\n\n    SmsNotificationEventPopulator.populate\n    2.times do\n      options3 = OpenStruct.new(CasaOrgPopulatorPresets.minimal_dataset_options)\n      org3 = DbPopulator.new(rng, case_fourteen_years_old: true)\n        .create_org(options3)\n      create_org_related_data(db_populator, org3, options3)\n    end\n\n    post_process_data\n    report_object_counts\n    log \"\\nDone.\\n\\n\"\n  end\n\n  private # -------------------------------------------------------------------------------------------------------\n\n  # Used for reporting record counts after completion:\n  def active_record_classes\n    @active_record_classes ||= [\n      AllCasaAdmin,\n      CasaAdmin,\n      CasaOrg,\n      CasaCase,\n      CaseContact,\n      ContactTopic,\n      ContactTopicAnswer,\n      CaseCourtOrder,\n      CaseAssignment,\n      ChecklistItem,\n      CourtDate,\n      ContactType,\n      ContactTypeGroup,\n      HearingType,\n      Judge,\n      Language,\n      LearningHourType,\n      LearningHourTopic,\n      MileageRate,\n      OtherDuty,\n      Supervisor,\n      SupervisorVolunteer,\n      User,\n      LearningHour,\n      Volunteer,\n      PlacementType\n    ]\n  end\n\n  def post_process_data\n    ContactTypePopulator.populate\n    CaseContactPopulator.populate\n    ContactTopicPopulator.populate\n  end\n\n  def get_seed_specification\n    seed_environment_value = ENV[\"DB_SEEDS_RANDOM_SEED\"]\n\n    if seed_environment_value.blank?\n      seed = 0\n      log \"\\nENV['DB_SEEDS_RANDOM_SEED'] not set to 'random' or a number; setting seed to 0.\\n\\n\"\n    elsif seed_environment_value.casecmp(\"random\") == 0\n      seed = Random.new_seed\n      log \"\\n'random' specified in ENV['DB_SEEDS_RANDOM_SEED']; setting seed to randomly generated value #{seed}.\\n\\n\"\n    else\n      seed = seed_environment_value.to_i\n      log \"\\nUsing random seed #{seed} specified in ENV['DB_SEEDS_RANDOM_SEED'].\\n\\n\"\n    end\n    seed\n  end\n\n  def report_object_counts\n    log \"\\nRecords written to the DB:\\n\\nCount  Class Name\\n-----  ----------\\n\\n\"\n    active_record_classes.each do |klass|\n      log format(\"%5d  %s\", klass.count, klass.name)\n    end\n    log \"\\n\\nVolunteers, Supervisors and CasaAdmins are types of Users\"\n  end\n\n  def log(message)\n    return if Rails.env.test?\n\n    Rails.logger.debug { message }\n  end\n\n  def create_org_related_data(db_populator, casa_org, options)\n    db_populator.create_users(casa_org, options)\n    db_populator.create_cases(casa_org, options)\n    db_populator.create_hearing_types(casa_org)\n    db_populator.create_checklist_items\n    db_populator.create_judges(casa_org)\n    db_populator.create_languages(casa_org)\n    db_populator.create_mileage_rates(casa_org)\n    db_populator.create_learning_hour_types(casa_org)\n    db_populator.create_learning_hour_topics(casa_org)\n    db_populator.create_learning_hours(casa_org)\n    db_populator.create_other_duties\n  end\nend\n\nSeederMain.new.seed\n\nload(Rails.root.join(\"db/seeds/emancipation_data.rb\"))\nbegin\n  load(Rails.root.join(\"db/seeds/emancipation_options_prune.rb\"))\nrescue => e\n  Rails.logger.error { \"Caught error during db seed emancipation_options_prune, continuing. Message: #{e}\" }\nend\nload(Rails.root.join(\"db/seeds/placement_data.rb\"))\nload(Rails.root.join(\"db/seeds/api_credential_data.rb\"))\n"
  },
  {
    "path": "doc/API.md",
    "content": "### POST `/casa_cases.json`  \nCreates a casa case\n### Params:  \n - **casa_case**  \n  Required. Contains all the object containing all the casa case params\n   - **case_number**: \"CINA-123-ABC\",  \n     Required. A unique string to identify the casa case\n   - **transition_aged_youth**: true,  \n     A boolean marking the case as transitioning or not. Currently in the process of deprecating this field\n   - **birth_month_year_youth**: \"2007-10-21\",  \n     Required. A date in the format YYYY-MM-DD determining if the case as transitioning or not.\n   - **casa_org_id**: 1,  \n     Required. The id of the casa org of the case. \n   - **hearing_type_id**: 1,  \n     The id of the hearing type for the next court date.\n   - **judge_id**: 1  \n     The id of the case judge\n\n### POST `/case_assignments.json`\nCreates a case_assignment\n- **Params:**\n   - **casa_case_id**: 1,  \n     Required. The id of the casa case the volunteer is being assigned to.\n   - **volunteer_id**: 1,  \n     Required. The id of the volunteer being assigned to the casa case.\n\n### PATCH   `/case_assignments/:id/unassign.json`\nUnassigns a case_assignment\n- **Params:**\n   - **id**: 1,  \n     Required. The id of the case_assignment to be unassigned.\n"
  },
  {
    "path": "doc/CONTRIBUTING.md",
    "content": "# Contributing\n\nWe ♥ contributors! By participating in this project, you agree to abide by the Ruby for Good [code of conduct](https://github.com/rubyforgood/code-of-conduct).\n\nIf you have any questions about an issue, comment on the issue, open a new issue or ask in [the RubyForGood slack](https://join.slack.com/t/rubyforgood/shared_invite/zt-35218k86r-vlIiWqig54c9t~_LkGpQ7Q). CASA has a `#casa` channel in the Slack. Our channel in slack also contains a zoom link for office hours every day office hours are held.\n\nYou won't be yelled at for giving your best effort. The worst that can happen is that you'll be politely asked to change something. We appreciate any sort of contributions, and don't want a wall of rules to get in the way of that.\n\n## Contributing Steps\n\n### Issues\n\nAll work is organized by issues.\n[Find issues here.](https://github.com/rubyforgood/projects/9)\n\nIf you would like to contribute, please ask for an issue to be assigned to you.\n\nIf you would like to contribute something that is not represented by an issue, please make an issue and assign yourself.\n\nOnly take multiple issues if they are related and you can solve all of them at the same time with the same pull request.\n\n### Pull Requests\n\nIf you are so inclined, you can open a draft PR as you continue to work on it.\n\n1. Follow [the setup guide](https://github.com/rubyforgood/casa#installation) to get the project working locally.\n\n1. We only accept pull requests with passing tests. To ensure your setup is working before you get started it's great to run the tests first.\n\n   - To run the tests use: `bundle exec rspec`\n\n1. Add a test for your change. If you are adding functionality or fixing a bug, you should add a test!\n\n1. Run linters and fix any linting errors that come up.\n\n   - From the repo root run: `./bin/git_hooks/lint`\n\n1. Push to your branch/fork and submit a pull request. Include the issue number (ex. `Resolves #1`) in the PR description. This will ensure the issue gets closed automatically when the pull request gets merged.\n\n#### Pull Request Checks\n\nWe will try to respond to your PR quickly.\n\nThere are scripts that check the code to ensure the code is working. Most of them need to pass. You can see the scripts run at the bottom of your pull request webpage (learn more about the scripts [here](https://github.com/rubyforgood/casa/wiki/Pull-Request-Checks)). You should attempt to fix errors found in the automated testing.\n\nPull requests are also manually reviewed. We may request changes after a manual review.\n\nSome qualities of good pull requests:\n\n- Small line diff count. Several small pull requests for a large issue are preferred over one big pull request.\n- Include tests that fail without your code, and pass with it.\n- For pull requests changing UI, make sure the UI matches the rest of the site. Some of our users aren't great with computers and we don't want to make them learn new things if we don't need to.\n- Update the documentation, for things like new rails/bash commands. Please include a guide if modifying the code in the future is difficult. For example [editing .docx templates](https://github.com/rubyforgood/casa/wiki/How-to-edit-docx-templates---word-document-court-report) is difficult because the documentation is hard to find and it requires microsoft word.\n- If your pull request involves user permissions, use [policy files](https://github.com/varvet/pundit#policies).\n- If your pull request has an erb file with complex rails logic inside of it, please use a [decorator](https://medium.com/@kosovacsedad/ruby-on-rails-decorator-design-pattern-b54a1afd03c8).\n"
  },
  {
    "path": "doc/DOCKER.md",
    "content": "# Development setup using Docker\n\nAfter you install Docker, please follow either the automatic setup or the manual\nsetup. If you are new to Docker, it is recommended that you follow the manual\nsetup.\n\n## Installing Docker\nInstall [Docker Community Edition](https://docs.docker.com/install/) if it is not already installed.\n\n## Automatic Setup\nThe automatic setup explained here relies on Bash scripts in the docker directory to execute the most basic and frequent tasks in Docker.  There is substantially less typing to do under the automatic setup than under the manual setup.\n\n### Initial setup\n1. Clone the repository to your local machine: `git clone https://github.com/rubyforgood/casa.git` or create a fork in GitHub if you don't have permission to commit directly to this repo.\n2. Change into the application directory: `cd casa`\n3. Run `docker/build` to build the app, seed the database, run the local web server (in a detached state), run the test suite, and log the screen outputs of these processes in the log directory.  The web application will be available at http://localhost:3000.\n4. Run `docker/test` to run the test suite and log the screen output in the log directory.\n5. If you reboot the machine, restart Docker, or stop any services, the tests and many other functions will not work.  Please run `docker/server` to restart the app and allow the tests and other functions to work.\n\n### Other Automated Scripts\n* Run `docker/seed` to reseed the database.\n* Run `docker/server` to restart the local web server (in a detached state).\n* Run `docker/nukec` to delete all of the Docker containers.\n* Run `docker/nuke` to delete all Docker containers, Docker networks, and Docker images.\n* Run `docker/console` to start the Rails Console.\n* Run `docker/sandbox` to start the Rails Sandbox.\n* Run `docker/brakeman` to run the Brakeman security tool, which checks for security vulnerabilities.\n* Use the `docker/run` script to run any command within the Rails Docker container.  For example, entering `docker/run cat /etc/os-release` executes the command `cat /etc/os-release` within the Rails Docker container.\n\n## Manual Setup\nThe manual setup instructions walk you through building the images and starting\nthe containers using Docker Compose commands directly. This setup method is particularly\nrecommended if you are new to Docker.\n\n### Initial setup\nThe following commands should just be run for the initial setup only. Rebuilding the docker images is only necessary when upgrading, if there are changes to the Dockerfile, or if gems have been added or updated.\n1. Clone the respository to your local machine: `git clone https://github.com/rubyforgood/casa.git` or create a fork in GitHub if you don't have permission to commit directly to this repo.\n2. Change into the application directory: `cd casa`\n3. Run `docker compose build` to build images for all services.\n4. Run `docker compose run --rm web bundle install` to install ruby dependencies\n5. Run `docker compose run --rm web rails db:reset` to create the dev and test databases, load the schema, and run the seeds file.\n6. Run `docker compose run --rm web npm install` to install javascript dependencies\n7. Run `docker compose run --rm web npm run build` to bundle javascript assets\n8. Run `docker compose run --rm web npm run build:css` to bundle the css\n9. Run `docker compose up` to start all the remaining services. Or use `docker compose up -d` to start containers in the background.\n10. Run `docker compose ps` to view status of the containers. All should have state \"Up\". Check the [logs](#viewing-logs) if there are any containers that did not start.\n11. The web application will be available at http://localhost:3000\n\n### For ongoing development:\n* Run `docker compose up -d` to start all services.\n* Run `docker compose ps` to view status of containers.\n* Run `docker compose stop` to stop all services.\n* Run `docker compose restart web` to restart the web server.\n* Run `docker compose rm <service>` to remove a stopped container.\n* Run `docker compose rm -f <service>` to force remove a stopped container.\n* Run `docker compose up -d --force-recreate` to start services with new\n   containers.\n* Run `docker compose build web` to build a new image for the web service.\n   After re-building an image, run `docker compose up -d --force-recreate web`\n   to start a container running the new image.\n* Run `docker compose down -v` to stop and remove all containers, as well as\n   volumes and networks. This command is helpful if you want to start with a\n   clean slate.  However, it will completely remove the database and you will\n   need to go through the database setup steps again above.\n\n#### Running commands\nIn order to run rake tasks, rails generators, bundle commands, etc., they need to be run inside the container:\n```\n$ docker compose exec web rails db:migrate\n```\n\nIf you do not have the web container running, you can run a command in a one-off container:\n\n```\n$ docker compose run --rm web bundle install\n```\n\nHowever, when using a one-off container, make sure the image is up-to-date by\nrunning `docker compose build web` first.  If you have been making gem updates\nto your container without rebuilding the image, then the one-off container will\nbe out of date.\n\n#### Running webpack dev server\nTo speed compiling of assets, run the webpack dev server in a separate terminal\nwindow:\n\n```\n$ docker compose exec web bin/webpack-dev-server\n```\n\n\n#### Viewing logs\nTo view the logs, run:\n```\n$ docker compose logs -f <service>\n```\n\nFor example:\n```\n$ docker compose logs -f web\n```\n\n#### Accessing services\n##### Postgres database\n```\n$ docker compose exec database psql -h database -Upostgres casa_development\n```\n\n##### Rails console\n```\n$ docker compose exec web rails c\n```\n\n### Testing Suite\nRun the testing suite from within the container:\n\n```\n$ docker compose exec web rspec spec -fd\n```\n\nFor a shorter screen output from running the testing suite from within the container:\n\n```\n$ docker compose exec web rspec spec\n```\n\nSystem tests will generate a screenshot upon failure. The screenshots can be\nfound in the local `tmp/screenshots` directory which maps to the\n`/usr/src/app/tmp/screenshots` directory inside the container.\n\n#### Watching tests run\n\nYou can view the tests in real time by using a VNC client and temporarily\nswitching to the `selenium_chrome_in_container` driver set in\n[spec/spec_helper.rb](https://github.com/rubyforgood/casa/blob/master/spec/spec_helper.rb).\nFor example, you can change this:\n\n```\n    if ENV[\"DOCKER\"]\n      driven_by :selenium_chrome_headless_in_container\n```\n\nto this:\n\n```\n    if ENV[\"DOCKER\"]\n      # driven_by :selenium_chrome_headless_in_container\n `    driven_by :selenium_chrome_in_container\n```\n\nMac OS comes with a built-in screen sharing application, \"Screen Sharing\".\nOn Ubuntu-based Linux, the VNC client application \"Vinagre\" (aka \"Remote Desktop Viewer\")\nis commonly used, and can be installed with `sudo apt install vinagre`.\n\nYou can open the VNC client application and configure it directly, but in both operating systems\nit's probably easier to click on [vnc://localhost:5900](vnc://localhost:5900)\n(or paste that into your browser's address bar) and let the browser launch the VNC client with\n the appropriate parameters for you.\n\nThe VNC password is `secret`.\n\nRun the spec(s) from the command line and you can see the test running in the browser through the VNC client.\n\n## Troubleshooting\n### Nokogiri not found on some macs\nhttps://stackoverflow.com/questions/70963924/unable-to-load-nokogiri-in-docker-container-on-m1-mac\n"
  },
  {
    "path": "doc/LINUX_SETUP.md",
    "content": "# Linux Development Environment Installation\n\nThe commands below can be run all at once by copying and pasting them all into a file and running the file as a script\n(e.g. `bash -x script_name`).\n\nIf you copy and paste directly from this page to your command line, we recommend you do so one section (or even one line) at a time.\n\n```\n# Install Linux Packages\nsudo apt update                    # Check internet for updates\nsudo apt upgrade -y                # Install updates\nsudo apt install -y git            # In case you don't have it already\nsudo apt install -y libvips42      # Render images for your local web server\nsudo apt install -y libpq-dev      # Helps compile C programs to be able to communicate with postgres\n\n# Optional\nsudo apt install -y curl           # A command to help fetching and sending data to urls\nsudo apt install -y vim            # A text editor accessible from the command line\n```\n\n```\n# Install Postgres\n#   Add the postgres repo\n#     Create the file repository configuration:\nsudo sh -c 'echo \"deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main\" > /etc/apt/sources.list.d/pgdg.list'\n\n#     Add the repo key to your keyring:\nwget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | sudo tee /usr/share/keyrings/postgres-archive-keyring.gpg\n\n# This next step is done to limit the key to only the postgres repo\n# Otherwise the signing key is considered valid for all your enabled Debian repositories\n#     Open /etc/apt/sources.list.d/pgdg.list with super user permissions so you are allowed to write to the file\n#       Example using vim:\n#         sudo vim /etc/apt/sources.list.d/pgdg.list\n#       Paste \"[signed-by=/usr/share/keyrings/postgres-archive-keyring.gpg]\" between \"deb\" and \"http://apt.postgresql...\"\n#         Example: deb [signed-by=/usr/share/keyrings/postgres-archive-keyring.gpg] http://apt.postgresql.org/pub/repos/apt noble-pgdg main\n#       Save the file\n\n#     Update the package lists:\nsudo apt update\n\n#   Install Postgres 12\nsudo apt install -y postgresql-12\n\n#   Turn the server on\nsudo systemctl start postgresql@12-main\n\n#   Add user to Postgres:\nsudo -u postgres psql -c \"CREATE USER $USER WITH CREATEDB\"\n\n# See https://www.postgresql.org/download/linux/ubuntu/ for more details\n```\n\n```\n# Install NVM and Node JS\n#   you can use curl\ncurl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | bash\n#   or wget\nwget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | bash\n\n#   Restart your terminal\n\n# List all available LTS versions\nnvm ls-remote | grep -i 'Latest LTS'\n\n# Install version from .nvmrc file:\nnvm install\n# Update npm\nnpm i -g npm@latest\n```\n\n```\n# add node and node tools to the path\nnvm alias default lts/krypton\n```\n\n```\n# Install and configure rbenv\nsudo apt install libyaml-dev\ngit clone https://github.com/rbenv/rbenv.git ~/.rbenv\n~/.rbenv/bin/rbenv init\n#   Restart your terminal\n\n#   fetch list of ruby versions\ngit clone https://github.com/rbenv/ruby-build.git \"$(rbenv root)\"/plugins/ruby-build\n\nrbenv install 4.0.2\n```\n\nIf you would like RVM instead of rbenv\n```\n# Install RVM (Part 1)\ngpg --keyserver hkp://pool.sks-keyservers.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB\n\n\\curl -sSL https://get.rvm.io | bash\n. ./.bashrc\nrvm get head\nrvm install 4.0.2\nrvm alias create ruby 4.0.2\nrvm alias create default ruby-4.0.2\n```\n\n```# Download the Chrome browser (for RSpec testing):\nsudo curl -sS -o - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add -\nsudo echo \"deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main\" | sudo tee /etc/apt/sources.list.d/google-chrome.list\nsudo apt -y update\nsudo apt -y install google-chrome-stable\n```\n\n## Connecting to Github via ssh\nConnecting to Gihub via ssh prevents being required to login very often when using git commands.\n\n### Creating an SSH Key Pair\n - Open Terminal.\n - Paste the text below, substituting in your GitHub email address.\n`ssh-keygen -t ed25519 -C \"your_email@example.com\"`\n - For all prompts simply press enter to set default values.\n\n#### Adding your SSH key to the ssh-agent\n - Run `eval \"$(ssh-agent -s)\"` in your terminal to start the ssh-agent in the background. It will use very few resources.\n - Run `ssh-add ~/.ssh/id_ed25519` to add your private key to the ssh agent.\n\nSee [github's article](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent) for more details/updates.\n\n### Add your ssh key to your github account.\n[See github's guide](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/adding-a-new-ssh-key-to-your-github-account)\n\n### Test Your ssh Connection\n - Run `ssh -T git@github.com`\n - If you see `Are you sure you want to continue connecting (yes/no)?`, enter `yes`\n - If you see `Hi username! You've successfully authenticated, but GitHub does not provide shell access.`, the connection is set up correctly.\n\n## Project Installation\n\n`cd` to the directory where you would like to install CASA\n\nRun this series of commands to install the project.\n```\ngit clone git@github.com:rubyforgood/casa.git # Download a copy of the repository locally\n# The URI will be different when cloning a fork\ncd casa                                       # Go into the folder containing casa\nbundle install                                # Install ruby dependencies\nbundle exec rails db:setup                    # Create your local test database\nbundle exec rails db:migrate                  # Update the database if it's out of date\nbundle exec rake after_party:run              # Run post deployment tasks\n\nnpm install                                   # install javascript dependencies\nnpm run build                                 # compile javascript\nnpm run build:css                             # compile css\n```\n[Back to the main readme for steps to test your installation.](https://github.com/rubyforgood/casa#running-the-app--verifying-installation)\n"
  },
  {
    "path": "doc/MAC_SETUP.md",
    "content": "# Install Needed Dependencies\n\n## Homebrew\n\nIf you haven't already, install the [homebrew](https://brew.sh/) package manager.\n\n## Postgres\n\nUse homebrew to install and run postgresql:\n\n```bash\nbrew install postgresql\n```\n\n```bash\nbrew services start postgresql\n```\n\nIf you have an older version of postgres, `brew postgresql-upgrade-database`\n\nFor a more GUI focused postgres experience, try [Postgres.app](https://postgresapp.com/) an alternative to the CLI focused default postgres\n\nIf you are having trouble connecting to your local postgres database using pgAdmin or another local tool, try the following configuration:  \n\n```\nHost Name: localhost\nPort: 5432\nMaintenance Database: postgres\nUsername: you_mac_login_username (Can be found by calling whoami in a terminal)\nPassword: password\n```\n\n## Ruby\n\n### Rbenv\n\nIt is often useful to install Ruby with a ruby version manager. The version of Ruby that comes with Mac is not sufficient\nfor this project. You can install [rbenv](https://github.com/rbenv/rbenv) with:\n\n```bash\nbrew install rbenv ruby-build\n```\n\nThen, setup rbenv:\n\n```bash\nrbenv init\n```\n\nAnd finally, follow the setup instructions that are outputted to your terminal after running that.\n\n### Actually installing Ruby\n\nNext, install the version of Ruby that this project uses. This can be found by checking the file in this repo, `.ruby-version`.\n\nTo install the appropriate ruby version, run:\n\n```bash\nrbenv install 4.0.2\n```\n\n(Do not forget to switch 4.0.2 to the appropriate version)\n\nFinally, run:\n\n```bash\nrbenv local 4.0.2\n```\n(Do not forget to switch 4.0.2 to the appropriate version)\n\n## Nodejs\n\nThe Casa package frontend leverages several javascript packages managed through `npm`.\n\n```bash\nbrew install node\n```\n\n## Chrome\nMany of the frontend tests are run using Google Chrome, so if you don't already have that installed you may wish to include it:\n\n```bash\nbrew install google-chrome\n```\n\n## Project setup\n\nInstall gem dependencies with:\n\n```bash\nbundle install\n```\n\nSetup the database with:\n\n```bash\nbin/rails db:setup\n```\n\nInstall javascript dependencies with:\n```bash\nnpm install\n```\n\nCompile assets with:\n\n```bash\nnpm run build\n```\n\nand then:\n\n```bash\nnpm run build:css\n```\n\nAnd lastly, run the app with:\n\n```bash\nbin/rails server\n```\n\nSee the README for login information.\n"
  },
  {
    "path": "doc/NIX_SETUP.md",
    "content": "**Nix** \n\nIf you have [Nix](https://nixos.org) installed you can use the [flake.nix](flake.nix)\nconfiguration file located at the root of the project to build and develop within an environment\nwithout needing to install `rvm`, `nodejs`, `postgresql` or other tools separately.\nThe environment also uses the `gemset.nix` file to automatically download and install all the gems\nnecessary to get the server up and running:\n\n1. Install [Nix](https://zero-to-nix.com/concepts/nix-installer)\n2. Add the following to `~/.config/nix/nix.conf` or `/etc/nix/nix.conf`:\n```\n    experimental-features = nix-command flakes\n```\n3. `cd` into casa\n4. `nix-shell -p bundix --run \"bundix -l\"` to update the `gemset.nix` file\n5. `nix develop` and wait for the packages to be downloaded and the environment to be built\n\nThen you can setup the database and run the server.\nThis will run on Linux and macOS."
  },
  {
    "path": "doc/SECURITY.md",
    "content": "# Security Policy\n\n## Supported Versions\n\nOnly the latest version [main branch](https://github.com/rubyforgood/casa) and currently deployed version of this project is in scope for security issues. Also, only the production environment is in scope, although it's ok and normal to test the staging environment.\n\n## Reporting a Vulnerability\n\nPlease report a vulnerability by emailing casa@rubyforgood.org\n\nYou can also open a github issue (do NOT provide vulnerability details on github) to notify us that you need to report an issue. \n\nWe will reply to all reported issues within a week and update at least every two days. \n\nWe currently do not have any bug bounty program but we will be happy to list your name in our contributors list! :)\n"
  },
  {
    "path": "doc/WSL_SETUP.md",
    "content": "This guide will walk you through setting up the neccessary environment using  [WSL](https://docs.microsoft.com/en-us/windows/wsl/install) (Windows Subsystem for Linux), which will allow you to run Ubuntu on your Windows machine.\n\nYou will need the following local tools installed:\n\n1. WSL\n2. Ruby\n3. NodeJs (optional)\n4. Postgres\n5. Google Chrome\n\n### WSL (Windows Subsystem for Linux)\n\n1. **Install [WSL](https://docs.microsoft.com/en-us/windows/wsl/install)**.\n\n   `wsl --install`\n\n   The above command only works if WSL is not installed at all, if you run `wsl --install `and see the WSL help text, do `--install -d Ubuntu`\n\n2. **Run Ubuntu on Windows**\n\n   You can run Ubuntu on Windows [several different ways](https://docs.microsoft.com/en-us/windows/wsl/install#ways-to-run-multiple-linux-distributions-with-wsl), but we suggest using [Windows Terminal](https://docs.microsoft.com/en-us/windows/terminal/install).\n\n   To open an Ubuntu tab in Terminal, click the downward arrow and choose 'Ubuntu'.\n\n   The following commands should all be run in an Ubuntu window.\n\n### Ruby\n\nInstall a ruby version manager like [rbenv](https://github.com/rbenv/rbenv#installation)\n\n  **Be sure to install the ruby version in `.ruby-version`. Right now that's Ruby 4.0.2.**\n\nInstructions for rbenv:\n\n1. **Install rbenv**\n\n   `sudo apt install rbenv`\n\n2. **Set up rbenv in your shell**\n\n   `rbenv init`\n\n3. **Close your Terminal window and open a new one so your changes take effect.**\n\n4. **Verify that rbenv is properly set up**\n\n   `curl -fsSL https://github.com/rbenv/rbenv-installer/raw/main/bin/rbenv-doctor | bash`\n\n5.  **[Install Ruby](https://github.com/rbenv/rbenv#installing-ruby-versions)**\n\n      **Be sure to install the ruby version in `.ruby-version`. Right now that's Ruby 4.0.2.**\n\n      `rbenv install 4.0.2`\n\n6. **Set a Ruby version to finish installation and start**\n\n    `rbenv global 4.0.2` OR `rbenv local 4.0.2`\n\n#### Troubleshooting\n    If you are on Ubuntu in Windows Subsystem for Linux (WSL) and `rbenv install` indicates that the Ruby version is unavailable, you might be using Ubuntu's default install of `ruby-build`, which only comes with old installs of Ruby (ending before 2.6.) You should uninstall rvm and ruby-build's apt packages (`apt remove rvm ruby-build`) and install them with Git like this:\n\n    - `git clone https://github.com/rbenv/rbenv.git ~/.rbenv`\n    - `echo 'export PATH=\"$HOME/.rbenv/bin:$PATH\"' >> ~/.bashrc`\n    - `echo 'eval \"$(rbenv init -)\"' >> ~/.bashrc`\n    - `exec $SHELL`\n    - `git clone https://github.com/rbenv/ruby-build.git \"$(rbenv root)\"/plugins/ruby-build`\n\n    You'll probably hit a problem where ruby-version reads `ruby-2.7.2` but the install available to you is called `2.7.2`. If you do, install [rbenv-alias](https://github.com/tpope/rbenv-aliases) and create an alias between the two.\n\n### NodeJS\n\nThe Casa package frontend leverages several javascript packages managed through `npm`, so if you are working on those elements you will want to have node, npm.\n\n1. **(Recommended) [Install nvm](https://github.com/nvm-sh/nvm#installing-and-updating)**\n\n   NVM is a node version manager.\n\n### Postgres\n\n1. **[Install PostgresSQL](https://docs.microsoft.com/en-us/windows/wsl/tutorials/wsl-database#install-postgresql) for WSL**\n\n   `sudo apt install postgresql postgresql-contrib` - install\n\n   `psql --version` - confirm installation and see version number\n\n 2. **Install libpq-dev library**\n\n      `sudo apt-get install libpq-dev`\n\n3. **Start your postgresql service**\n\n   `sudo service postgresql start`\n\n\n### Google Chrome\n\nMany of the frontend tests are run using Google Chrome, so if you don't already have that installed you may wish to install it.\n\nFor some linux distributions, installing `chromium-browser` may be enough on WSL. However, some versions of Ubuntu may require the chromium snap to be installed in order to use chromium.\nIf you receive errors about needing the chromium snap while running the test suite, you can install Chrome and chromedriver instead:\n\n1. Download and Install Chrome on WSL Ubuntu\n  - Update your packages:\n\n    `sudo apt update`\n\n  - Download Chrome:\n\n    `wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb`\n\n  - Install chrome from the downloaded file\n\n    `sudo dpkg -i google-chrome-stable_current_amd64.deb`\n\n    `sudo apt-get install -f`\n\n  - Check that Chrome is installed correctly\n\n    `google-chrome --version`\n\n2. Install the appropriate Chromedriver\n  - Depending on the version of google-chrome you installed, you will need a specific chromedriver. You can see which version you need on the [chromedriver dowload page](https://chromedriver.chromium.org/downloads).\n    - For example, if `google-chrome --version` returns 105.x.xxxx.xxx you will want to download the version of chromedriver recommended on the page for version 105.\n    - As of this writing, the download page says the following for Chrome version 105:\n      > If you are using Chrome version 105, please download ChromeDriver 105.0.5195.52\n  - To download chromedriver, run the following command, replacing `{CHROMEDRIVER-VERSION}` with the version of chromedriver you need (e.g., 105.0.5195.52)\n\n    `wget https://chromedriver.storage.googleapis.com/{CHROMEDRIVER-VERSION}/chromedriver_linux64.zip`\n\n  - Next, unzip the file you downloaded\n\n    `unzip chromedriver_linux64.zip`\n\n  - Finally, move chromedriver to the correct location and enable it for use:\n\n    `sudo mv chromedriver /usr/bin/chromedriver`\n\n    `sudo chown root:root /usr/bin/chromedriver`\n\n    `sudo chmod +x /usr/bin/chromedriver`\n\n3. Run the test suite\n  - Assuming the rest of the application is already set up, you can run the test suite to verify that you no longer receive error regarding chromium snap:\n    `bin/rails spec`\n\n### Casa & Rails\n\nCasa's install will also install the correct version of Rails.\n\n1. **Download the project**\n\n   **You should create a fork in GitHub if you don't have permission to directly commit to this repo. See our [contributing guide](https://github.com/rubyforgood/casa/blob/main/doc/CONTRIBUTING.md) for more detailed instructions.**\n\n   `git clone <git address>` - use your fork's address if you have one\n\n   ie\n\n   `git clone https://github.com/rubyforgood/casa.git`\n\n2. **Installing Packages**\n\n   `cd casa/`\n\n   `bundle install` -  install ruby dependencies.\n\n   `npm install` - install javascript dependencies.\n\n3. **Database Setup**\n\n   Be sure your postgres service is running (`sudo service postgresql start`).\n\n   Create a postgres user that matches your Ubuntu user:\n\n   `sudo -u postgres createuser <username>` - create user\n\n   `sudo -u postgres psql` - logs in as the postgres user\n\n   `psql=# alter user <username> with encrypted password '<password>';` - add password\n\n   `psql=# alter user <username> CREATEDB;` - give permission to your user to create databases\n\n   Set up the Casa DB\n\n    `bin/rails db:setup`  - sets up the db\n\n    `bin/rails db:seed:replant` - generates test data (can be rerun to regenerate test data)\n4. **Compile Assets**\n-  `npm run build` compile javascript\n&ensp;&ensp;`npm run build:dev` to auto recompile for when you edit js files\n-  `npm run build:css` compile css\n&ensp;&ensp;`npm run build:css:dev` to auto recompile for when you edit sass files\n\n### Getting Started\n\nSee [Running the App / Verifying Installation](https://github.com/rubyforgood/casa#running-the-app--verifying-installation).\n\nA good option for editing files in WSL is [Visual Studio Code Remote- WSL](https://code.visualstudio.com/docs/remote/wsl)\n"
  },
  {
    "path": "doc/architecture-decisions/0001-record-architecture-decisions.md",
    "content": "# 1. Record architecture decisions\n\nDate: 2020-04-04\n\n## Status\n\nAccepted\n\n## Context\n\nWe need to record the architectural decisions made on this project.\n\n## Decision\n\nWe will use Architecture Decision Records, as [described by Michael Nygard](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions).\n\n## Consequences\n\nSee Michael Nygard's article, linked above. For a lightweight ADR toolset, see Nat Pryce's [adr-tools](https://github.com/npryce/adr-tools)."
  },
  {
    "path": "doc/architecture-decisions/0002-disallow-ui-sign-ups.md",
    "content": "# 2. Disallow user sign-ups from the UI\n\nDate: 2020-04-04\n\n## Status\n\nAccepted\n\n## Context\n\nWe want it to be easy for people to join the organization, however we don't want random people signing up and spamming us. We want admin users to have control over who has accounts on the system. We don't have the capacity to handle this properly through the user interface right now.\n\n## Decision\n\nWe are going to disable Devise 'registerable' for the user model so that there will no longer be a public sign up option on the site. Creation of new accounts will be done on the backend.\n\n## Consequences\n\nAdmins have to do more work to sign up users, but this gives them more control over who can access the site."
  },
  {
    "path": "doc/architecture-decisions/0003-multiple-user-tables.md",
    "content": "# 3. Having 2 user tables\n\nDate: 2020-04-05\n\n## Status\n\nAccepted\n\n## Context\n\nThis is planned to be a multi-tenant system. There will be multiple CASA orgs in the system, so every case, case_contact, volunteer, supervisor, casa_admin etc must have a casa_org_id, because no one is allowed to belong to multiple CASAs. Volunteer, supervisor, and casa_admin are all roles for a \"User\" db object. In addition to those existing roles, we want to create a new kind of user: all_casa_admin. We need to handle the case of super users who have access to multiple casa_orgs, so they would be difficult to handle in the existing User table--with null handling around their casa_org_id field. We have used the built-in Devise ability to have multiple user tables, as recommended to us by our Rails expert Betsy. This is to prevent needing null handling around casa_id for User records since all_casa_admin users will not have casa_id populated.\n\nAdditionally, all_casa_admin users are currently intended to be allowed to create casa_admin users, but NOT to be able to see or edit any CASA data like volunteer assignments, cases, case_updates etc.\n\n## Decision\n\nWe are using two tables for users: \"user\" table for volunteers,supervisors, and casa_admin (all of which must have a casa_id). \"all_casa_admin\" for all_casa_admins, which will have no casa_id.\n\n## Consequences\n\nThe login behavior and dashboard page for all_casa_admin will need to be created and handled separately from the regular user login and dashboard\n"
  },
  {
    "path": "doc/architecture-decisions/0004-use-bootstrap.md",
    "content": "# 1. Use Bootstrap for styling\n\nDate: 2020-04-05\n\n## Status\n\nProposed\n\n## Context\n\nWe would like to have an easy-to-use system for consistent styles that doesn't\ntake much tinkering. We propose using the `bootstrap` gem.\n\n## Decision\n\nPending\n\n## Consequences\n\nSome familiarity with\n[Bootstrap](https://getbootstrap.com/docs/4.4/getting-started/introduction/)\nwill likely be necessary but we hope the medium-to-long term benefits will\noutweigh the cost to learn up front.\n"
  },
  {
    "path": "doc/architecture-decisions/0005-android-app-as-a-pwa.md",
    "content": "# 1. The android app is a [PWA](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps) running in a [TWA](https://developer.chrome.com/docs/android/trusted-web-activity/overview/)\n\nDate: 2021-07-07\n\n## Context\n\nBuilding a [progressive web app(PWA)](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps) is a very quick way to convert a website into a mobile app for android.\nPWAs can support offline mode and push notifications.\nOur app runs in a [trusted web activity(TWA)](https://developer.chrome.com/docs/android/trusted-web-activity/overview/) which is very similar to having the web page load in a mobile browser. The trusted web activity offers browser like support for the PWA.\n\n## Consequences\nMore javascript support for [service workers](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Offline_Service_workers) to support offline mode.  \n[Maintaining a key for app signing](https://github.com/rubyforgood/casa-android/wiki/How-to-manage-app-signing)\n"
  },
  {
    "path": "doc/architecture-decisions/0006-few-controller-tests.md",
    "content": "# 1. System tests are preferred over controller tests\nDate: 2021-07-14\n\n\n\"system\" tests test both the erb and the controller at the same time. They are slower. They use capybara. Having some of these (one per rendered page) is very important because it is possible for a controller to define a variable `@a` and an erb to require a variable `@b` and the tests for the controller and erb to both pass separately, but for the page loading to fail. We need system tests to make sure that our codebase is working properly. \n\nIn general, we don't write many controller tests because they tend to rely overly on mocking and are fully duplicitive with the system tests. \n\n\n"
  },
  {
    "path": "doc/architecture-decisions/0007-inline-css-for-email-views.md",
    "content": "# 1. Inline CSS for email views\n\nDate: 2021-08-18\n\nCSS is inline for email views becuase other forms of CSS lack full support in some email platforms including gmail and outlook.  \nSee https://www.campaignmonitor.com/css/ to see the current status of support for forms of CSS such as `<style>` and `<link>` tags\n\nSee https://content.myemma.com/blog/css-in-html-emails-what-you-need-to-know-to-get-started\n"
  },
  {
    "path": "doc/architecture-decisions/0008-controller-specs.txt",
    "content": "# 1. Testing controllers\n\nDate: 2022-10-18\n\n1.\n\"controller tests\" have filenames like `spec/controllers/casa_admins_controller_spec.rb` and syntax like\n```\nallow(controller).to receive(:authenticate_user!).and_return(:supervisor)\nlog_in_as_admin\nget :index\nexpect(response).to be_successful\n```\n2.\n\"request tests\" have filenames like `spec/requests/contact_types_spec.rb` and syntax like\n```\nsign_in admin\nget new_contact_type_path\nexpect(response).to be_successful\n```\n\n3.\n\"system tests\" have filenames like `spec/system/users/edit_spec.rb` and syntax like\n```\nsign_in volunteer\nvisit edit_users_path\nclick_on \"Change Password\"\nfill_in \"Current Password\", with: \"12345\"\nfill_in \"New Password\", with: \"123456789\"\nfill_in \"New Password Confirmation\", with: \"123456789\"\nclick_on \"Update Password\"\nexpect(page).to have_content \"1 error prohibited this password change from being saved:\"\n```\n\nIf only controller tests for a file exist, add to the controller tests\n\nIf both controller and request tests for a file exist, add to the request tests, and consider moving over the controller tests to the request test.\n\nYou should generally write request tests rather than controller tests.\n\nYou should write system tests *in addition* to request specs."
  },
  {
    "path": "doc/code-of-conduct.md",
    "content": "# Code of conduct\n\n**All people who choose to be a part of our community are required to agree with the following code of conduct.**\n\nWe expect everyone to help create a safe, positive, and welcoming environment for everyone.\n\nOur community is dedicated to providing a harassment-free experience for everyone, regardless of gender, gender identity and expression, age, sexual orientation, disability, physical appearance, body size, race, religion (or lack thereof), or any other intrinsic characteristic that makes us who we are.\n\nHarassment is the expression of ideas, through actions or words, intended or known to shame, intimidate, \nor harm another person. Harassment hurts people, and by extension destroys communities.\n\nHarassment may include, but is not limited to, offensive verbal comments related to any of those intrinsic characteristics by which we define our humanity, the display of sexually explicit images, deliberate physical intimidation, stalking,  photography or recording without consent, sustained disruption of talks or other events, inappropriate physical contact, and unwelcome sexual attention.\n\nOur community does not tolerate harassment in any form.\n\nSexual, violent, and/or hateful language and imagery is simply not appropriate for any of our events, or venues. That includes conferences, meetups, talks, workshops, parties and soirees, Twitter, IRC, email lists, ham radio conversations, etc.\n\nCommunity members asked to stop any harassing behavior are expected to comply immediately. This includes longtime members, newbies, friends who come along to events, sponsors, and organizers.\n\nPeople that violate our code may be sanctioned or expelled from our community events, without a refund, at the discretion of the community organizers.\n\n## Need Help?\nIf you have questions, comments, or concerns related to violations of our code, please be in touch with our community's organizers.\n\nIf you would like to contribute to our code, open up an issue, or submit a pull request!\n\n---\n\nCopied left from: [@leftlogic on GitHub](http://github.com/leftlogic/confcodeofconduct.com)\n\n*Licensed under the Creative Commons Attribution 3.0 Unported License*"
  },
  {
    "path": "doc/db_diagram_schema_code/part_1.txt",
    "content": "# Modify this code to update the DB schema diagram.\n# To reset the sample schema, replace everything with\n# two dots ('..' - without quotes).\n#################\n# Examples I'm not yet willing to delete\n#################\n# Table documentation comment 1 (try the PDF/RTF export)\n#Product as p # Table documentation comment 2\n#------------\n#ProductID PK int\n# Field documentation comment 1\n# Field documentation comment 2 \n#Name varchar(200) UNIQUE\n#Price money\n\n#OrderStatus as os\n#----\n#OrderStatusID PK int\n#Name UNIQUE string\n\ncasa_cases\n-\nid PK bigint\ncase_number string \ntransition_aged_youth boolean\ncreated_at datetime\nupdated_at datetime\ncasa_org_id bigint\nbirth_month_year_youth datetime NULL\ncourt_date datetime\ncourt_report_due_date datetime\nhearing_type_id bigint\nactive boolean\njudge_id bigint\ncourt_report_submitted_at datetime\ncourt_report_status integer\nslug string\ndate_in_care datetime\n\ncasa_cases_emancipation_options\n-\nid PK int\ncasa_case_id int FK >- casa_cases.id\nemancipation_option_id bigint FK >- emancipation_options.id\n\nemancipation_options\n-\nid PK bigint\nemancipation_category_id bigint \nname UNIQUE string\ncreated_at datetime\nupdated_at datetime\n\nadditional_expenses\n-\ncase_contact_id bigint\nother_expense_amount decimal\nother_expenses_describe string\ncreated_at datetime\nupdated_at datetime\n\naddresses\n-\ncontent string\nuser_id bigint\ncreated_at datetime\nupdated_at datetime\n\nall_casa_admins\n-\nemail string\nencrypted_password string\nreset_password_token string\nreset_password_sent_at datetime\ncreated_at datetime\nupdated_at datetime\ninvitation_token string\ninvitation_created_at datetime\ninvitation_sent_at datetime\ninvitation_accepted_at datetime\ninvitation_limit integer\ninvited_by_id integer\ninvited_by_type string\n\ncasa_case_contact_types\n-\ncontact_type_id bigint\ncasa_case_id bigint\ncreated_at datetime\nupdated_at datetime\n\ncasa_case_emancipation_categories\n-\ncasa_case_id bigint\nemancipation_category_id bigint\ncreated_at datetime\nupdated_at datetime\n\ncasa_orgs\n-\nname string\ncreated_at datetime\nupdated_at datetime\ndisplay_name string\naddress string\nfooter_links string\nslug string\nshow_driving_reimbursement boolean\nshow_fund_request boolean\ntwilio_phone_number string\ntwilio_account_sid string\ntwilio_api_key_sid string\ntwilio_api_key_secret string\n\ncase_assignments\n-\ncasa_case_id bigint\nvolunteer_id bigint\nactive boolean\ncreated_at datetime\nupdated_at datetime\nhide_old_contacts boolean\n\ncase_contact_contact_types\n-\ncase_contact_id bigint\ncontact_type_id bigint\ncreated_at datetime\nupdated_at datetime\n\ncase_contacts\n-\ncreator_id bigint\ncasa_case_id bigint\nduration_minutes integer\noccurred_at datetime\ncreated_at datetime\nupdated_at datetime\ncontact_made boolean\nmedium_type string\nmiles_driven integer\nwant_driving_reimbursement boolean\nnotes string\ndeleted_at datetime\nreimbursement_complete boolean\n\ncase_court_orders\n-\ncasa_case_id bigint\ncreated_at datetime\nupdated_at datetime\nimplementation_status integer\ncourt_date_id bigint\ntext string\n\nchecklist_items\n-\nhearing_type_id integer\ndescription text\ncategory string\nmandatory boolean\ncreated_at datetime\nupdated_at datetime\n\ncontact_type_groups\n-\ncasa_org_id bigint\nname string\ncreated_at datetime\nupdated_at datetime\nactive boolean\n\ncontact_types\n-\ncontact_type_group_id bigint\nname string\ncreated_at datetime\nupdated_at datetime\nactive boolean\n\ncourt_dates\n-\ndate datetime\ncasa_case_id bigint\ncreated_at datetime\nupdated_at datetime\nhearing_type_id bigint\njudge_id bigint\n\nemancipation_categories\n-\nname string\nmutually_exclusive boolean\ncreated_at datetime\nupdated_at datetime\n\nfeature_flags\n-\nname string\nenabled boolean\ncreated_at datetime\nupdated_at datetime\n\nfollowups\n-\ncase_contact_id bigint\ncreator_id bigint\nstatus integer\ncreated_at datetime\nupdated_at datetime\nnote text\n\nfund_requests\n-\nsubmitter_email text\nyouth_name text\npayment_amount text\ndeadline text\nrequest_purpose text\npayee_name text\nrequested_by_and_relationship text\nother_funding_source_sought text\nimpact text\nextra_information text\ntimestamps text\n\nhearing_types\n-\ncasa_org_id bigint\nname string\nactive boolean\nchecklist_updated_date string\n\njudges\n-\ncasa_org_id bigint\ncreated_at datetime\nupdated_at datetime\nactive boolean\nname string\n\nlanguages\n-\nname string\ncasa_org_id bigint\ncreated_at datetime\nupdated_at datetime\n\nlanguages_users\n-\nlanguage_id bigint\nuser_id bigint\n\nlearning_hours\n-\nuser_id bigint\nlearning_type integer\nname string\nduration_minutes integer\nduration_hours integer\noccurred_at datetime\ncreated_at datetime\nupdated_at datetime\n\nmileage_rates\n-\namount decimal\neffective_date date\nis_active boolean\nuser_id bigint\ncreated_at datetime\nupdated_at datetime\ncasa_org_id bigint\n\nnotes\n-\ncontent string\ncreator_id bigint\nnotable_type string\nnotable_id bigint\ncreated_at datetime\nupdated_at datetime\n\nnotifications\n-\nrecipient_type string\nrecipient_id bigint\ntype string\nparams jsonb\nread_at datetime\ncreated_at datetime\nupdated_at datetime\n\nother_duties\n-\ncreator_id bigint\ncreator_type string\noccurred_at datetime\nduration_minutes bigint\nnotes text\ncreated_at datetime\nupdated_at datetime\n\npatch_note_groups\n-\nvalue string\ncreated_at datetime\nupdated_at datetime\n\npatch_note_types\n-\nname string\ncreated_at datetime\nupdated_at datetime\n\npatch_notes\n-\nnote text\npatch_note_type_id bigint\npatch_note_group_id bigint\ncreated_at datetime\nupdated_at datetime\n\npreference_sets\n-\nuser_id bigint\ncase_volunteer_columns jsonb\ncreated_at datetime\nupdated_at datetime\n\nsent_emails\n-\nuser_id bigint\ncasa_org_id bigint\nmailer_type string\ncategory string\nsent_address string\ncreated_at datetime\nupdated_at datetime\n\nsms_notification_events\n-\nname string\nuser_type string\ncreated_at datetime\nupdated_at datetime\n\nsupervisor_volunteers\n-\nsupervisor_id bigint\nvolunteer_id bigint\ncreated_at datetime\nupdated_at datetime\nis_active boolean\n\nuser_reminder_times\n-\nuser_id bigint\nreminder_sent datetime\ncreated_at datetime\nupdated_at datetime\ncase_contact_types datetime\n\nuser_sms_notification_events\n-\nuser_id bigint\nsms_notification_event_id bigint\ncreated_at datetime\nupdated_at datetime\n\nusers\n-\nemail string\nencrypted_password string\nreset_password_token string\nreset_password_sent_at datetime\ncreated_at datetime\nupdated_at datetime\ncasa_org_id bigint\ndisplay_name string\ninvitation_token string\ninvitation_created_at datetime\ninvitation_sent_at datetime\ninvitation_accepted_at datetime\ninvitation_limit integer\ninvited_by_type string\ninvited_by_id bigint\ninvitations_count integer\ntype string\nactive boolean\nsign_in_count integer\ncurrent_sign_in_at datetime\nlast_sign_in_at datetime\ncurrent_sign_in_ip string\nlast_sign_in_ip string\nphone_number string\nreceive_sms_notifications boolean\nreceive_email_notifications boolean\n"
  },
  {
    "path": "doc/db_diagram_schema_code/part_2.txt",
    "content": "\nactive_storage_attachments\n-\nid bigint PK\nname string\nrecord_type string\nrecord_id bigint\nblob_id bigint FK - active_storage_blobs.id\ncreated_at datetime\n\nactive_storage_blobs\n-\nid bigint PK\nkey string UNIQUE\nfilename string\ncontent_type string NULL\nmetadata text NULL\nbyte_size bigint\nchecksum string NULL\ncreated_at datetime\nservice_name string\n\nactive_storage_variant_records\n-\nid bigint PK\nblob_id bigint FK - active_storage_blobs.id\nvariation_digest string\n\ndelayed_jobs\n-\nid bigint PK\npriority integer\nattempts integer\nhandler text\nlast_error text\nrun_at datetime\nlocked_at datetime\nfailed_at datetime\nlocked_by string\nqueue string\ncreated_at datetime\nupdated_at datetime\n\nhealths\n-\nid bigint PK\nlatest_deploy_time datetime\nsingleton_guard integer\ncreated_at datetime\nupdated_at datetime\n\ntask_records\n-\nid bigint PK\nversion string\n\n"
  },
  {
    "path": "doc/productsense.md",
    "content": "# CASA Product Sense\nThis will help you understand the high level thought process that has driven the ideation and development of casavolunteertracking.org. \n## Who is this for?\nThis is essential reading for anyone stepping into a **lead product role** on this project – particularly for team leads managing stakeholder relationships. It is recommended reading for contributing product managers, and would provide helpful but not essential context to contributing developers. \n### Historical Context\nCasavolunteertracking.org was initially developed to help a <a href=\"https://pgcasa.org/\">CASA organization in Prince George's County, Maryland</a> track and manage volunteer activity. PG / CASA was the first official pilot organization for this platform, and served as the primary stakeholders consulted throughout development until <a href=\"https://voicesforchildrenmontgomery.org/\">a second CASA organization in Montgomery County, Maryland</a> joined in early 2021. \n### Early Approach\nThe MVP requirements for this application were exclusively focused on solving the immediate needs of PG / CASA. Their volunteers at the time had virtually no system for logging time spent with foster youth, and this was the immediate gap we sought to fill. Making the application usable for **volunteers** to log `case_contacts` was the top priority that informed the order of our backlog.\n\nAfter we had a working volunteer user experience as defined by that topline requirement, we focused on giving PG / CASA staff **supervisors** and **admins** a window into that activity in accordance with their user permissions (admins can see/do everything, supervisors can see/do almost everything that relates to volunteers and cases but not staff users or organization settings).\n### Post MVP Approach and Shift to Multitenancy\nWhile we continued to work closely with PG / CASA to improve the MVP experience post production launch, it was around this time that we shifted gears and started focusing on multitenancy.\n\nCASA is an organization founded in Seattle, WA with a nationwide presence. County level chapters operate relatively independent of each other and of the national organization (the national org mostly focuses on policy advocacy, with the county level orgs providing services to foster youth in the form of community volunteers). The baseline operations of each CASA chapter are more or less the same, but the terminology they use and day-to-day processes vary slightly. \n\nIn order for casavolunteetracking.org to be a successful multitenant application, it needs to be clearly tailored for the specific needs of CASA staff and volunteers, yet customizable enough to serve CASA chapters all over the country – without becoming so complicated that it's difficult to use. (Salesforce could be a great solution for CASA chapters if they had the resources to hire a developer who can help set up, customize, and maintain a Salesforce instance. The overwhelming majority of CASA chapters do not have those resources at their disposal.) \n### Questions to Help Guide You\nThe stakeholders on this project are enthusiastic and filled with ideas! Most of them have been serving their respective CASA organizations for _decades_ and have lots of experience. When a stakeholder from one CASA organization suggests a new feature or improvement, it's important to also solicit feedback on this idea from stakeholders representing a _different_ CASA organization. This will help you begin to understand how a single feature may be used (or not used) at scale.\n\nPresently, this project only has stakeholders representing two CASA organizations from the same state. That means the product and team leads will have to flex their product sense when translating stakeholder feedback into platform changes.\n#### Do all CASA chapters do it that way, or just this one? \nThis is the top question that should be asked before ticketing out any feature changes. The answer to this is often,\n- Yes, but with slight variations.\n\nIt is rare that a feature change or idea is suggested that wouldn't be used by at least some CASA chapters. The key is to develop features that benefit the users it's useful to, but don't hinder those who don't require it. \n\n_Here is a real example that demonstrates this:_\n\nIn some counties, the judge overseeing a court hearing varies and is very important information for a volunteer to have ahead of a court date. Yet in other counties, the same judge will always oversee the court appearances for a particular case, so isn't something a volunteer considers when preparing for court. \n\nInstead of _always_ showing `judge_name` with court details on a `casa_case`, `judge_name` only appears if an `admin` has added it to their `casa_org`.\n#### Is the problem this solves a problem every CASA faces?\n\nThe answer to this question should be yes as frequently as possible. The more work in development that answers \"yes\" to this question, the more ways this platform can attract (and help) more CASA chapters. The bigger the problem and the more CASAs that face it, the higher up in the backlog it should go. \n\nIn order to answer this question, it's important to understand the core operations of a CASA chapter:\n\n- volunteer logs `case_contacts` representing time spent with youth (a.k.a. `casa_case`)\n- volunteer provides detailed reports on a `casa_case` to the court\n- volunteers help prepare eligible `casa_cases` for emancipation\n- volunteers help establish permanency plans for their assigned `casa_case`\n- supervisors coach volunteers and hold them accountable\n- admins maintain a birds eye view that allows them to measure impact, generate stats for fundraising, and provide strategic leadership\n\nThe fewer resources that are required to perform those core operations, the better. If a solution being developed improves the efficiency of conducting these core operations, then it probably solves a problem every CASA faces – and henceforth, delivers a big value-add to current and prospective tenants.\n\n#### Would this be “nice to have” for most CASA chapters? Or would this offer a big enough value-add to attract new tenants?\n\nThis application currently only has two tenant organizations. In order to scale the platform’s impact, it must serve more tenants. The answer to the previous question can help inform the answer to this one, but it's not the only factor to consider. \n\nThere are currently two market SaaS applications that solve many of the same problems casavolunteertracking.org seeks to, but at a (literal) cost. Apart from being too pricey for many CASA chapters to use, the CASAs that _do_ use one of those market solutions don't find them user friendly and aren't happy with the scope of solutions they offer.\n\nIf a solution..\n- solves a problem every CASA faces\n- does something a market solution doesn't do\n- does something a market solution _does_ do, but way better\n\nthen it is a winner! (and should be prioritized)\n"
  },
  {
    "path": "docker/brakeman",
    "content": "#!/bin/bash\nset -e\n\necho '*******************************'\necho 'docker compose run web brakeman'\ndocker compose run web brakeman\n"
  },
  {
    "path": "docker/build",
    "content": "#!/bin/bash\nset -e\n\n# This script builds the Docker container, seeds the app with sample data, starts the server, runs the tests, and logs the screen output.\n\nDATE=`date +%Y%m%d-%H%M%S-%3N`\nmkdir log/$DATE\ndocker/build-log 2>&1 | tee log/$DATE/build.log\nwait\ndocker/seed 2>&1 | tee log/$DATE/seed.log\nwait\ndocker/build-assets 2>&1 | tee log/$DATE/build-assets.log\nwait\ndocker/server-log 2>&1 | tee log/$DATE/server.log\nwait\ndocker/test-log 2>&1 | tee log/$DATE/test.log\nwait\necho '******************************'\necho 'The build process is complete!'\necho ''\necho \"The logs are in the log/$DATE directory.\"\necho ''\necho 'You should be able to view this app at http://localhost:3000/'\necho ''\necho 'If all went well, the local app has been seeded,'\necho 'and all the tests passed.'\necho ''\n"
  },
  {
    "path": "docker/build-assets",
    "content": "#!/bin/bash\nset +e\n\n# Bundle JS and CSS assets\n\necho 'Bundling js and css assets...'\ndocker compose run --rm web bundle install # usually will be a no-op\ndocker compose run --rm web npm install   # usually will be a no-op\n\ndocker compose run --rm web npm run build\ndocker compose run --rm web npm run build:css\necho 'Done bundling js and css assets.'\n"
  },
  {
    "path": "docker/build-log",
    "content": "#!/bin/bash\nset +e\n\ndocker compose down -v --remove-orphans\nwait\n\necho 'Building docker containers...'\ndocker compose build # Set up the Docker containers\necho 'Done building docker containers.'\n"
  },
  {
    "path": "docker/console",
    "content": "#!/bin/bash\nset -e\n\necho '-------------------------------------'\necho 'docker compose exec web rails console'\ndocker compose exec web rails console\n"
  },
  {
    "path": "docker/nuke",
    "content": "#!/bin/bash\n\n# This script destroys all Docker containers, images, and networks.\n# SOURCE: https://gist.github.com/JeffBelback/5687bb02f3618965ca8f\n\ndocker/nukec\n\necho '-----------------------'\necho 'docker network prune -f'\ndocker network prune -f\n\necho '--------------------------------------'\necho 'Killing and removing all Docker images'\nfor i in $(docker images -a -q)\ndo\n  docker kill $i; wait;\n  docker rmi -f $i; wait;\ndone;\n\necho '------------'\necho 'docker ps -a'\ndocker ps -a\n\necho '----------------'\necho 'docker images -a'\ndocker images -a\n\nwait\n"
  },
  {
    "path": "docker/nukec",
    "content": "#!/bin/bash\n\n# This script destroys all Docker containers but leaves the Docker images alone.\n\necho '------------------------------------------'\necho 'Killing and removing all Docker containers'\nfor i in $(docker ps -a -q)\ndo\n  docker kill $i; wait;\n  docker rm -f $i; wait;\ndone;\n\necho '------------'\necho 'docker ps -a'\ndocker ps -a\n"
  },
  {
    "path": "docker/run",
    "content": "#!/bin/bash\n\nset -e\n\n# Use this script to execute commaands in the Docker container.\n# Example: To run the \"ls -l\" command in the Docker container, enter the command \"docker/run ls -l\".\n\nfunction cleanup {\n  # capture exit code\n  code=$?\n  echo \"cleaning up\"\n\n  # ignore errors\n  set +e\n  docker compose down\n\n  exit $code\n}\n\ntrap cleanup EXIT\n\ndocker compose run web $@\n"
  },
  {
    "path": "docker/sandbox",
    "content": "#!/bin/bash\nset -e\n\necho '-----------------------------------------------'\necho 'docker compose exec web rails console --sandbox'\ndocker compose exec web rails console --sandbox\n"
  },
  {
    "path": "docker/seed",
    "content": "#!/bin/bash\nset +e\n# This is the data seeding script.\n\n# If the local web server is running, it must be stopped to allow the database to be reseeded.\necho 'Stopping existing containers...'\ndocker compose stop\necho 'Done stopping existing containers.'\n\n# NOTE: \"rails db:reset\" creates the development and test databases AND seeds the database.\n# This app will not work until you run this command.\necho 'Resetting rails db...'\ndocker compose run --rm web rails db:reset\necho 'Done resetting rails db.'\n\necho ''\necho 'The database seeding process is complete!'\necho 'At this point, the local web server is NOT running.'\necho 'You must enter \"docker/server\" to start it.'\n"
  },
  {
    "path": "docker/server",
    "content": "#!/bin/bash\nset -e\n\n# This script runs the Rails server and logs the screen output.\n\nDATE=`date +%Y%m%d-%H%M%S-%3N`\ndocker/server-log 2>&1 | tee log/server-$DATE.log\n"
  },
  {
    "path": "docker/server-log",
    "content": "#!/bin/bash\nset +e\n\necho 'Starting web server...'\necho 'View this app in your web browser at'\necho 'http://localhost:3000/'\ndocker compose up -d\n"
  },
  {
    "path": "docker/test",
    "content": "#!/bin/bash\nset -e\n\n# This script runs the entire test suite AND logs the screen output.\n\nDATE=`date +%Y%m%d-%H%M%S-%3N`\ndocker/test-log 2>&1 | tee log/test-$DATE.log\n"
  },
  {
    "path": "docker/test-log",
    "content": "#!/bin/bash\nset +e\n\n# Update db and run tests\n# All containers must be already running\n\necho 'Running migrations...'\ndocker compose exec web rails db:migrate\necho 'Done running migrations.'\n\necho 'Running rspec...'\ndocker compose exec web rspec spec\necho 'Done running rspec.'\n"
  },
  {
    "path": "docker-compose.yml",
    "content": "services:\n  web:\n    build: .\n    command: bin/dev\n    ports:\n      - \"3000:3000\"\n      - \"4000:4000\"\n    volumes:\n      - .:/usr/src/app\n      - node_modules:/usr/src/app/node_modules\n      - bundle_data:/usr/local/bundle/gems\n    env_file:\n      - config/docker.env\n    depends_on:\n      - database\n    healthcheck:\n      test: [\"CMD\", \"curl\", \"-f\", \"http://localhost:3000\"]\n      interval: 30s\n      timeout: 10s\n      retries: 5\n    tty: true\n    platform: linux/x86_64\n\n  database:\n    image: postgres:12.5\n    env_file:\n      - config/docker.env\n    volumes:\n      - db_data:/var/lib/postgresql/data\n    ports:\n      - \"5435:5432\"\n\n  selenium_chrome:\n    image: selenium/standalone-chrome-debug\n    logging:\n      driver: none\n    ports:\n      - \"5900:5900\"\n    volumes:\n      - ./tmp/downloads:/home/seluser/Downloads\n\nvolumes:\n  db_data:\n  bundle_data:\n  node_modules:\n"
  },
  {
    "path": "docker-entrypoint.sh",
    "content": "#!/bin/sh\nset -e\n\nif [ -f tmp/pids/server.pid ]; then\n  rm tmp/pids/server.pid\nfi\n\nexec \"$@\""
  },
  {
    "path": "flake.nix",
    "content": "{\n  description = \"A Ruby dev environment for Casa Development\";\n\n  nixConfig = {\n    extra-substituters = \"https://nixpkgs-ruby.cachix.org\";\n    extra-trusted-public-keys =\n      \"nixpkgs-ruby.cachix.org-1:vrcdi50fTolOxWCZZkw0jakOnUI1T19oYJ+PRYdK4SM=\";\n  };\n\n  inputs = {\n    nixpkgs.url = \"nixpkgs\";\n    ruby-nix = {\n      url = \"github:inscapist/ruby-nix\";\n      inputs.nixpkgs.follows = \"nixpkgs\";\n    };\n    bundix = {\n      url = \"github:inscapist/bundix/main\";\n      inputs.nixpkgs.follows = \"nixpkgs\";\n    };\n    fu.url = \"github:numtide/flake-utils\";\n    bob-ruby = {\n      url = \"github:bobvanderlinden/nixpkgs-ruby\";\n      inputs.nixpkgs.follows = \"nixpkgs\";\n    };\n  };\n\n  outputs = { self, nixpkgs, fu, ruby-nix, bundix, bob-ruby }:\n    with fu.lib;\n    eachDefaultSystem (system:\n      let\n        pkgs = import nixpkgs {\n          inherit system;\n          overlays = [ bob-ruby.overlays.default ];\n        };\n        rubyNix = ruby-nix.lib pkgs;\n\n        gemset =\n          if builtins.pathExists ./gemset.nix then import ./gemset.nix else { };\n\n        gemConfig = { };\n        # See available versions here: https://github.com/bobvanderlinden/nixpkgs-ruby/blob/master/ruby/versions.json\n        ruby = pkgs.\"ruby-4.0.2\";\n\n        bundixcli = bundix.packages.${system}.default;\n      in rec {\n        inherit (rubyNix {\n          inherit gemset ruby;\n          name = \"ruby-env-casa\";\n          gemConfig = pkgs.defaultGemConfig // gemConfig;\n        })\n          env;\n\n        devShells = rec {\n          default = dev;\n          dev = pkgs.mkShell {\n            BUNDLE_FORCE_RUBY_PLATFORM = \"true\";\n            shellHook = ''\n              export PS1='\\n\\[\\033[1;34m\\][💎:\\w]\\$\\[\\033[0m\\] '\n\n              # Setup postgres database\n              export PGHOST=$HOME/postgres\n              export PGDATA=$PGHOST/data\n              export PGDATABASE=postgres\n              export PGLOG=$PGHOST/postgres.log\n\n              mkdir -p $PGHOST\n\n              if [ ! -d $PGDATA ]; then\n                initdb --auth=trust --no-locale --encoding=UTF8\n              fi\n\n              if ! pg_ctl status\n              then\n                pg_ctl start -l $PGLOG -o \"--unix_socket_directories='$PGHOST'\"\n              fi\n\n              trap 'pg_ctl stop -D \"$PGDATA\" -s -m fast' EXIT\n            '';\n\n            buildInputs = [\n              env\n              bundixcli\n              pkgs.bundix\n              pkgs.bundler-audit\n              pkgs.direnv\n              pkgs.git\n              pkgs.gnumake\n              pkgs.libpcap\n              pkgs.libpqxx\n              pkgs.libxml2\n              pkgs.libxslt\n              pkgs.nodejs-18_x\n              pkgs.pkg-config\n              pkgs.postgresql\n              pkgs.sqlite\n              pkgs.nodePackages.npm\n            ];\n          };\n        };\n      });\n}\n"
  },
  {
    "path": "gemset.nix",
    "content": "{\n  actioncable = {\n    dependencies = [\"actionpack\" \"activesupport\" \"nio4r\" \"websocket-driver\" \"zeitwerk\"];\n    groups = [\"default\" \"development\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1kxdc5d7iyh0fjqqxr7x2l37xp48lcdpafkvsqc0p5bc4vd3qcjp\";\n      type = \"gem\";\n    };\n    version = \"7.1.3.3\";\n  };\n  actionmailbox = {\n    dependencies = [\"actionpack\" \"activejob\" \"activerecord\" \"activestorage\" \"activesupport\" \"mail\" \"net-imap\" \"net-pop\" \"net-smtp\"];\n    groups = [\"default\" \"development\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0qx6435q80bzk9h8hvii2vf6hq9nfb50ggm58ps8vy8jci3xh9bm\";\n      type = \"gem\";\n    };\n    version = \"7.1.3.3\";\n  };\n  actionmailer = {\n    dependencies = [\"actionpack\" \"actionview\" \"activejob\" \"activesupport\" \"mail\" \"net-imap\" \"net-pop\" \"net-smtp\" \"rails-dom-testing\"];\n    groups = [\"default\" \"development\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"05jjaw7m6xc4lpwidpnq9pnzg4rb5ild1ivp82shr7qq7wqlixbb\";\n      type = \"gem\";\n    };\n    version = \"7.1.3.3\";\n  };\n  actionpack = {\n    dependencies = [\"actionview\" \"activesupport\" \"nokogiri\" \"racc\" \"rack\" \"rack-session\" \"rack-test\" \"rails-dom-testing\" \"rails-html-sanitizer\"];\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"193svay7hhjxx5p42lxjwmk22hlp2bbf6b2gjb802108h0gzs29x\";\n      type = \"gem\";\n    };\n    version = \"7.1.3.3\";\n  };\n  actiontext = {\n    dependencies = [\"actionpack\" \"activerecord\" \"activestorage\" \"activesupport\" \"globalid\" \"nokogiri\"];\n    groups = [\"default\" \"development\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1vg85ff59hqndwk61kd9b5y5y7vnvm85cddrbg7la4ibkwfxa87d\";\n      type = \"gem\";\n    };\n    version = \"7.1.3.3\";\n  };\n  actionview = {\n    dependencies = [\"activesupport\" \"builder\" \"erubi\" \"rails-dom-testing\" \"rails-html-sanitizer\"];\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1zfmqqxaj1qnwr2ic6z6axsg332p8msikn9phr1i9vy1lpia31fs\";\n      type = \"gem\";\n    };\n    version = \"7.1.3.3\";\n  };\n  activejob = {\n    dependencies = [\"activesupport\" \"globalid\"];\n    groups = [\"default\" \"development\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0sd2h1l3dfl66sdvwg6xm6f7hx0pyj6wsv1mdavz0gkl104r2qgg\";\n      type = \"gem\";\n    };\n    version = \"7.1.3.3\";\n  };\n  activemodel = {\n    dependencies = [\"activesupport\"];\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0klvgx1bvr3f9l1hg5n7cdbsqzkp42dz7if82wgw9l77rhlj1j8h\";\n      type = \"gem\";\n    };\n    version = \"7.1.3.3\";\n  };\n  activemodel-serializers-xml = {\n    dependencies = [\"activemodel\" \"activesupport\" \"builder\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1pk5qrxxhgxlihim8qkdk805nq584ms71hmcg1766iwhx0v2x3r2\";\n      type = \"gem\";\n    };\n    version = \"1.0.2\";\n  };\n  activerecord = {\n    dependencies = [\"activemodel\" \"activesupport\" \"timeout\"];\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1j9m8m5y035lx2kyx04wzpspsvhadqggf4nyjlwg1xw4kpa37qrx\";\n      type = \"gem\";\n    };\n    version = \"7.1.3.3\";\n  };\n  activestorage = {\n    dependencies = [\"actionpack\" \"activejob\" \"activerecord\" \"activesupport\" \"marcel\"];\n    groups = [\"default\" \"development\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0hm10apwx80xp8qgcsfrpx2qmsvg707vpqvdvrr0rax0zq8zs25s\";\n      type = \"gem\";\n    };\n    version = \"7.1.3.3\";\n  };\n  activesupport = {\n    dependencies = [\"base64\" \"bigdecimal\" \"concurrent-ruby\" \"connection_pool\" \"drb\" \"i18n\" \"minitest\" \"mutex_m\" \"tzinfo\"];\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0z8kygxmz99krz9pwp947znkzf0jr64sml28df0vf1gzxlg7y57i\";\n      type = \"gem\";\n    };\n    version = \"7.1.3.3\";\n  };\n  addressable = {\n    dependencies = [\"public_suffix\"];\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0irbdwkkjwzajq1ip6ba46q49sxnrl2cw7ddkdhsfhb6aprnm3vr\";\n      type = \"gem\";\n    };\n    version = \"2.8.6\";\n  };\n  after_party = {\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"11g8w209w1fzg9058j8gmfgsn26zp6zwaq4liwxyg021lpc8fmcl\";\n      type = \"gem\";\n    };\n    version = \"1.11.2\";\n  };\n  amazing_print = {\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"15vklc53pgf2zr1rvncy4yz7ikm63k5ghfa2hlf425fwm8yyj0wr\";\n      type = \"gem\";\n    };\n    version = \"1.6.0\";\n  };\n  annotate = {\n    dependencies = [\"activerecord\" \"rake\"];\n    groups = [\"development\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1lw0fxb5mirsdp3bp20gjyvs7clvi19jbxnrm2ihm20kzfhvlqcs\";\n      type = \"gem\";\n    };\n    version = \"3.2.0\";\n  };\n  ast = {\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"04nc8x27hlzlrr5c2gn7mar4vdr0apw5xg22wp6m8dx3wqr04a0y\";\n      type = \"gem\";\n    };\n    version = \"2.4.2\";\n  };\n  authtrail = {\n    dependencies = [\"railties\" \"warden\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"12vnkajvszkvfg3rfbf4lkvrnml8q16jv59fagkzyaqg93kbbsmd\";\n      type = \"gem\";\n    };\n    version = \"0.5.0\";\n  };\n  azure-storage-blob = {\n    dependencies = [\"azure-storage-common\" \"nokogiri\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0qq3knsy7nj7a0r8m19spg2bgzns9b3j5vjbs9mpg49whhc63dv1\";\n      type = \"gem\";\n    };\n    version = \"2.0.3\";\n  };\n  azure-storage-common = {\n    dependencies = [\"faraday\" \"faraday_middleware\" \"net-http-persistent\" \"nokogiri\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0swmsvvpmy8cdcl305p3dl2pi7m3dqjd7zywfcxmhsz0n2m4v3v0\";\n      type = \"gem\";\n    };\n    version = \"2.0.4\";\n  };\n  base64 = {\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"01qml0yilb9basf7is2614skjp8384h2pycfx86cr8023arfj98g\";\n      type = \"gem\";\n    };\n    version = \"0.2.0\";\n  };\n  bcrypt = {\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"16a0g2q40biv93i1hch3gw8rbmhp77qnnifj1k0a6m7dng3zh444\";\n      type = \"gem\";\n    };\n    version = \"3.1.20\";\n  };\n  better_html = {\n    dependencies = [\"actionview\" \"activesupport\" \"ast\" \"erubi\" \"parser\" \"smart_properties\"];\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1sk5s5lpwbd53s4a1xzm02nys3kfqdw5mh9i2qfn04hjsk8wk3gc\";\n      type = \"gem\";\n    };\n    version = \"2.0.2\";\n  };\n  bigdecimal = {\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1gi7zqgmqwi5lizggs1jhc3zlwaqayy9rx2ah80sxy24bbnng558\";\n      type = \"gem\";\n    };\n    version = \"3.1.8\";\n  };\n  bindex = {\n    groups = [\"default\" \"development\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0zmirr3m02p52bzq4xgksq4pn8j641rx5d4czk68pv9rqnfwq7kv\";\n      type = \"gem\";\n    };\n    version = \"0.8.1\";\n  };\n  blueprinter = {\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1fz2mc5wb2fgxc6bxjqj019aiyvqqdmlp3xfrfs23c7qid733ak1\";\n      type = \"gem\";\n    };\n    version = \"1.0.2\";\n  };\n  brakeman = {\n    dependencies = [\"racc\"];\n    groups = [\"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1lylig4vgnw9l1ybwgxdi9nw9q2bc5dcplklg8nsbi7j32f7c5kp\";\n      type = \"gem\";\n    };\n    version = \"6.1.2\";\n  };\n  bugsnag = {\n    dependencies = [\"concurrent-ruby\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"149sb4fx6sq8cl6w9zv88kvl0x90cxj0hljz40226v4npyz6fmm7\";\n      type = \"gem\";\n    };\n    version = \"6.26.4\";\n  };\n  builder = {\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"045wzckxpwcqzrjr353cxnyaxgf0qg22jh00dcx7z38cys5g1jlr\";\n      type = \"gem\";\n    };\n    version = \"3.3.6\";\n  };\n  bullet = {\n    dependencies = [\"activesupport\" \"uniform_notifier\"];\n    groups = [\"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1zs6dwspb0m9ygl7lwdmilhdn9ka85a0qfiip49m9rfpfj46lps0\";\n      type = \"gem\";\n    };\n    version = \"7.1.6\";\n  };\n  bundler-audit = {\n    dependencies = [\"thor\"];\n    groups = [\"development\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0gdx0019vj04n1512shhdx7hwphzqmdpw4vva2k551nd47y1dixx\";\n      type = \"gem\";\n    };\n    version = \"0.9.1\";\n  };\n  byebug = {\n    groups = [\"development\" \"test\"];\n    platforms = [{\n      engine = \"maglev\";\n    } {\n      engine = \"mingw\";\n    } {\n      engine = \"mingw\";\n    } {\n      engine = \"ruby\";\n    }];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0nx3yjf4xzdgb8jkmk2344081gqr22pgjqnmjg2q64mj5d6r9194\";\n      type = \"gem\";\n    };\n    version = \"11.1.3\";\n  };\n  capybara = {\n    dependencies = [\"addressable\" \"matrix\" \"mini_mime\" \"nokogiri\" \"rack\" \"rack-test\" \"regexp_parser\" \"xpath\"];\n    groups = [\"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1vxfah83j6zpw3v5hic0j70h519nvmix2hbszmjwm8cfawhagns2\";\n      type = \"gem\";\n    };\n    version = \"3.40.0\";\n  };\n  capybara-screenshot = {\n    dependencies = [\"capybara\" \"launchy\"];\n    groups = [\"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0xqc7hdiw1ql42mklpfvqd2pyfsxmy55cpx0h9y0jlkpl1q96sw1\";\n      type = \"gem\";\n    };\n    version = \"1.0.26\";\n  };\n  caxlsx = {\n    dependencies = [\"htmlentities\" \"marcel\" \"nokogiri\" \"rubyzip\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"06lg57d6r7dz0phj1l02nbrhnhkssisdvh65pc10qmrcszcgdl27\";\n      type = \"gem\";\n    };\n    version = \"4.1.0\";\n  };\n  caxlsx_rails = {\n    dependencies = [\"actionpack\" \"caxlsx\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0v5kvrk93rh58sj99hil8gkb6xbhhwcm5pp9zpffcy75wrlzr17y\";\n      type = \"gem\";\n    };\n    version = \"0.6.3\";\n  };\n  cliver = {\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"096f4rj7virwvqxhkavy0v55rax10r4jqf8cymbvn4n631948xc7\";\n      type = \"gem\";\n    };\n    version = \"0.3.2\";\n  };\n  coderay = {\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0jvxqxzply1lwp7ysn94zjhh57vc14mcshw1ygw14ib8lhc00lyw\";\n      type = \"gem\";\n    };\n    version = \"1.1.3\";\n  };\n  concurrent-ruby = {\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1kmhr3pz2nmhnq0nqlicqfwfmkzkcl835g7sw1gjjhjvhz8g2sf3\";\n      type = \"gem\";\n    };\n    version = \"1.3.1\";\n  };\n  connection_pool = {\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1x32mcpm2cl5492kd6lbjbaf17qsssmpx9kdyr7z1wcif2cwyh0g\";\n      type = \"gem\";\n    };\n    version = \"2.4.1\";\n  };\n  crack = {\n    dependencies = [\"rexml\"];\n    groups = [\"default\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1cr1kfpw3vkhysvkk3wg7c54m75kd68mbm9rs5azdjdq57xid13r\";\n      type = \"gem\";\n    };\n    version = \"0.4.5\";\n  };\n  crass = {\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0pfl5c0pyqaparxaqxi6s4gfl21bdldwiawrc0aknyvflli60lfw\";\n      type = \"gem\";\n    };\n    version = \"1.0.6\";\n  };\n  cssbundling-rails = {\n    dependencies = [\"railties\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1nzp4j1ll6llgbrvqk81gkz6fqgk33sx4k1fcvbm7v7h79jk8808\";\n      type = \"gem\";\n    };\n    version = \"1.4.0\";\n  };\n  csv = {\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0zfn40dvgjk1xv1z8l11hr9jfg3jncwsc9yhzsz4l4rivkpivg8b\";\n      type = \"gem\";\n    };\n    version = \"3.3.0\";\n  };\n  database_cleaner-active_record = {\n    dependencies = [\"activerecord\" \"database_cleaner-core\"];\n    groups = [\"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"12hdsqnws9gyc9sxiyc8pjiwr0xa7136m1qbhmd1pk3vsrrvk13k\";\n      type = \"gem\";\n    };\n    version = \"2.1.0\";\n  };\n  database_cleaner-core = {\n    groups = [\"default\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0v44bn386ipjjh4m2kl53dal8g4d41xajn2jggnmjbhn6965fil6\";\n      type = \"gem\";\n    };\n    version = \"2.0.1\";\n  };\n  date = {\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"149jknsq999gnhy865n33fkk22s0r447k76x9pmcnnwldfv2q7wp\";\n      type = \"gem\";\n    };\n    version = \"3.3.4\";\n  };\n  delayed_job = {\n    dependencies = [\"activesupport\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0s2xg72ljg4cwmr05zi67vcyz8zib46gvvf7rmrdhsyq387m2qcq\";\n      type = \"gem\";\n    };\n    version = \"4.1.11\";\n  };\n  delayed_job_active_record = {\n    dependencies = [\"activerecord\" \"delayed_job\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1b80s5b6dihazdd8kcfrd7z3qv8kijxpxq5027prazdha3pgzadf\";\n      type = \"gem\";\n    };\n    version = \"4.1.8\";\n  };\n  devise = {\n    dependencies = [\"bcrypt\" \"orm_adapter\" \"railties\" \"responders\" \"warden\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1y57fpcvy1kjd4nb7zk7mvzq62wqcpfynrgblj558k3hbvz4404j\";\n      type = \"gem\";\n    };\n    version = \"4.9.4\";\n  };\n  devise_invitable = {\n    dependencies = [\"actionmailer\" \"devise\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0blhar8b3rgck4w0pvz9gw60i3h8418fslipyw8i4j63xhjcf9np\";\n      type = \"gem\";\n    };\n    version = \"2.0.9\";\n  };\n  diff-lcs = {\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1znxccz83m4xgpd239nyqxlifdb7m8rlfayk6s259186nkgj6ci7\";\n      type = \"gem\";\n    };\n    version = \"1.5.1\";\n  };\n  docile = {\n    groups = [\"default\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1lxqxgq71rqwj1lpl9q1mbhhhhhhdkkj7my341f2889pwayk85sz\";\n      type = \"gem\";\n    };\n    version = \"1.4.0\";\n  };\n  docx = {\n    dependencies = [\"nokogiri\" \"rubyzip\"];\n    groups = [\"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0mwl9w70w7brizggjl01flhmhcsglzadch9j1dg9835v6w962h9v\";\n      type = \"gem\";\n    };\n    version = \"0.8.0\";\n  };\n  domain_name = {\n    dependencies = [\"unf\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0lcqjsmixjp52bnlgzh4lg9ppsk52x9hpwdjd53k8jnbah2602h0\";\n      type = \"gem\";\n    };\n    version = \"0.5.20190701\";\n  };\n  dotenv = {\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1n0pi8x8ql5h1mijvm8lgn6bhq4xjb5a500p5r1krq4s6j9lg565\";\n      type = \"gem\";\n    };\n    version = \"2.8.1\";\n  };\n  dotenv-rails = {\n    dependencies = [\"dotenv\" \"railties\"];\n    groups = [\"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0v0gcbxzypcvy6fqq4gp80jb310xvdwj5n8qw9ci67g5yjvq2nxh\";\n      type = \"gem\";\n    };\n    version = \"2.8.1\";\n  };\n  draper = {\n    dependencies = [\"actionpack\" \"activemodel\" \"activemodel-serializers-xml\" \"activesupport\" \"request_store\" \"ruby2_keywords\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1bz1x23lj7zxpijwidbn86c30rzk3fwdpyvbzifaa20qwcgf3qsw\";\n      type = \"gem\";\n    };\n    version = \"4.0.2\";\n  };\n  drb = {\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0h5kbj9hvg5hb3c7l425zpds0vb42phvln2knab8nmazg2zp5m79\";\n      type = \"gem\";\n    };\n    version = \"2.2.1\";\n  };\n  email_spec = {\n    dependencies = [\"htmlentities\" \"launchy\" \"mail\"];\n    groups = [\"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1yhn60bmbyv10ikypp5zqrwyg0jfswyvnfqngy18r0dj5p458g7m\";\n      type = \"gem\";\n    };\n    version = \"2.2.2\";\n  };\n  erb_lint = {\n    dependencies = [\"activesupport\" \"better_html\" \"parser\" \"rainbow\" \"rubocop\" \"smart_properties\"];\n    groups = [\"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1h4rpid0d50hikb1yx7apk0vp53qsqgj1cn6rrfqnk580ln4zm5c\";\n      type = \"gem\";\n    };\n    version = \"0.5.0\";\n  };\n  erubi = {\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"08s75vs9cxlc4r1q2bjg4br8g9wc5lc5x5vl0vv4zq5ivxsdpgi7\";\n      type = \"gem\";\n    };\n    version = \"1.12.0\";\n  };\n  factory_bot = {\n    dependencies = [\"activesupport\"];\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1glq677vmd3xrdilcx6ar8sdaysm9ldrppg34yzw43jzr6dx47fp\";\n      type = \"gem\";\n    };\n    version = \"6.4.5\";\n  };\n  factory_bot_rails = {\n    dependencies = [\"factory_bot\" \"railties\"];\n    groups = [\"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1j6w4rr2cb5wng9yrn2ya9k40q52m0pbz47kzw8xrwqg3jncwwza\";\n      type = \"gem\";\n    };\n    version = \"6.4.3\";\n  };\n  faker = {\n    dependencies = [\"i18n\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1gc6li2fcvl9g752sz7nradi46jw7q2kjmmx017b0wfc8v7ayvp3\";\n      type = \"gem\";\n    };\n    version = \"3.4.1\";\n  };\n  faraday = {\n    dependencies = [\"faraday-em_http\" \"faraday-em_synchrony\" \"faraday-excon\" \"faraday-httpclient\" \"faraday-multipart\" \"faraday-net_http\" \"faraday-net_http_persistent\" \"faraday-patron\" \"faraday-rack\" \"faraday-retry\" \"ruby2_keywords\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1c760q0ks4vj4wmaa7nh1dgvgqiwaw0mjr7v8cymy7i3ffgjxx90\";\n      type = \"gem\";\n    };\n    version = \"1.10.3\";\n  };\n  faraday-em_http = {\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"12cnqpbak4vhikrh2cdn94assh3yxza8rq2p9w2j34bqg5q4qgbs\";\n      type = \"gem\";\n    };\n    version = \"1.0.0\";\n  };\n  faraday-em_synchrony = {\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1vgrbhkp83sngv6k4mii9f2s9v5lmp693hylfxp2ssfc60fas3a6\";\n      type = \"gem\";\n    };\n    version = \"1.0.0\";\n  };\n  faraday-excon = {\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0h09wkb0k0bhm6dqsd47ac601qiaah8qdzjh8gvxfd376x1chmdh\";\n      type = \"gem\";\n    };\n    version = \"1.1.0\";\n  };\n  faraday-httpclient = {\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0fyk0jd3ks7fdn8nv3spnwjpzx2lmxmg2gh4inz3by1zjzqg33sc\";\n      type = \"gem\";\n    };\n    version = \"1.0.1\";\n  };\n  faraday-multipart = {\n    dependencies = [\"multipart-post\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"09871c4hd7s5ws1wl4gs7js1k2wlby6v947m2bbzg43pnld044lh\";\n      type = \"gem\";\n    };\n    version = \"1.0.4\";\n  };\n  faraday-net_http = {\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1fi8sda5hc54v1w3mqfl5yz09nhx35kglyx72w7b8xxvdr0cwi9j\";\n      type = \"gem\";\n    };\n    version = \"1.0.1\";\n  };\n  faraday-net_http_persistent = {\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0dc36ih95qw3rlccffcb0vgxjhmipsvxhn6cw71l7ffs0f7vq30b\";\n      type = \"gem\";\n    };\n    version = \"1.2.0\";\n  };\n  faraday-patron = {\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"19wgsgfq0xkski1g7m96snv39la3zxz6x7nbdgiwhg5v82rxfb6w\";\n      type = \"gem\";\n    };\n    version = \"1.0.0\";\n  };\n  faraday-rack = {\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1h184g4vqql5jv9s9im6igy00jp6mrah2h14py6mpf9bkabfqq7g\";\n      type = \"gem\";\n    };\n    version = \"1.0.0\";\n  };\n  faraday-retry = {\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"153i967yrwnswqgvnnajgwp981k9p50ys1h80yz3q94rygs59ldd\";\n      type = \"gem\";\n    };\n    version = \"1.0.3\";\n  };\n  faraday_middleware = {\n    dependencies = [\"faraday\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1bw8mfh4yin2xk7138rg3fhb2p5g2dlmdma88k82psah9mbmvlfy\";\n      type = \"gem\";\n    };\n    version = \"1.2.0\";\n  };\n  ffi = {\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1862ydmclzy1a0cjbvm8dz7847d9rch495ib0zb64y84d3xd4bkg\";\n      type = \"gem\";\n    };\n    version = \"1.15.5\";\n  };\n  ffi-compiler = {\n    dependencies = [\"ffi\" \"rake\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0c2caqm9wqnbidcb8dj4wd3s902z15qmgxplwyfyqbwa0ydki7q1\";\n      type = \"gem\";\n    };\n    version = \"1.0.1\";\n  };\n  filterrific = {\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0glvw1ksr65s2wh38x5rqfcg04g25ip4as2y6l1pb6mm3b92wkrv\";\n      type = \"gem\";\n    };\n    version = \"5.2.5\";\n  };\n  friendly_id = {\n    dependencies = [\"activerecord\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"01v2q0cyqn8za374ygfxzpa5qf4j8yk7ilz6zrv3457wkfwg4670\";\n      type = \"gem\";\n    };\n    version = \"5.5.1\";\n  };\n  globalid = {\n    dependencies = [\"activesupport\"];\n    groups = [\"default\" \"development\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1sbw6b66r7cwdx3jhs46s4lr991969hvigkjpbdl7y3i31qpdgvh\";\n      type = \"gem\";\n    };\n    version = \"1.2.1\";\n  };\n  groupdate = {\n    dependencies = [\"activesupport\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0m7cbahlhd7akkizrn2rjp4g6y6wkg8s3dqas6rgjj1apx2hd535\";\n      type = \"gem\";\n    };\n    version = \"6.4.0\";\n  };\n  hashdiff = {\n    groups = [\"default\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1nynpl0xbj0nphqx1qlmyggq58ms1phf5i03hk64wcc0a17x1m1c\";\n      type = \"gem\";\n    };\n    version = \"1.0.1\";\n  };\n  htmlentities = {\n    groups = [\"default\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1nkklqsn8ir8wizzlakncfv42i32wc0w9hxp00hvdlgjr7376nhj\";\n      type = \"gem\";\n    };\n    version = \"4.3.4\";\n  };\n  http = {\n    dependencies = [\"addressable\" \"http-cookie\" \"http-form_data\" \"llhttp-ffi\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1bzb8p31kzv6q5p4z5xq88mnqk414rrw0y5rkhpnvpl29x5c3bpw\";\n      type = \"gem\";\n    };\n    version = \"5.1.1\";\n  };\n  http-cookie = {\n    dependencies = [\"domain_name\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"13rilvlv8kwbzqfb644qp6hrbsj82cbqmnzcvqip1p6vqx36sxbk\";\n      type = \"gem\";\n    };\n    version = \"1.0.5\";\n  };\n  http-form_data = {\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1wx591jdhy84901pklh1n9sgh74gnvq1qyqxwchni1yrc49ynknc\";\n      type = \"gem\";\n    };\n    version = \"2.3.0\";\n  };\n  httparty = {\n    dependencies = [\"csv\" \"mini_mime\" \"multi_xml\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0v7s60abgwkahkqi4lf68hmz3j69jli3q25jsf9h1kvijif2lrbq\";\n      type = \"gem\";\n    };\n    version = \"0.22.0\";\n  };\n  i18n = {\n    dependencies = [\"concurrent-ruby\"];\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1ffix518y7976qih9k1lgnc17i3v6yrlh0a3mckpxdb4wc2vrp16\";\n      type = \"gem\";\n    };\n    version = \"1.14.5\";\n  };\n  image_processing = {\n    dependencies = [\"mini_magick\" \"ruby-vips\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1f32dzj77p9mfp4q95930vfkp80psf88phjc46jhf9ncl72ykffk\";\n      type = \"gem\";\n    };\n    version = \"1.12.2\";\n  };\n  io-console = {\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"08d2lx42pa8jjav0lcjbzfzmw61b8imxr9041pva8xzqabrczp7h\";\n      type = \"gem\";\n    };\n    version = \"0.7.2\";\n  };\n  irb = {\n    dependencies = [\"rdoc\" \"reline\"];\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"14pji5w708d6v63m3yvyfx1d9gg0mi5x1a2czxf6259zncq2ymda\";\n      type = \"gem\";\n    };\n    version = \"1.13.1\";\n  };\n  jbuilder = {\n    dependencies = [\"actionview\" \"activesupport\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1li6hwkcz1z7g6wvhvw160bb1jkzm2i928gspfmir45q80rbxsa7\";\n      type = \"gem\";\n    };\n    version = \"2.12.0\";\n  };\n  jsbundling-rails = {\n    dependencies = [\"railties\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0bfh1mwkpq53bd3k0nsnqwmsc76hrygfgngqd5hl7sy06wmp2h11\";\n      type = \"gem\";\n    };\n    version = \"1.3.0\";\n  };\n  json = {\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0b4qsi8gay7ncmigr0pnbxyb17y3h8kavdyhsh7nrlqwr35vb60q\";\n      type = \"gem\";\n    };\n    version = \"2.7.2\";\n  };\n  json-schema = {\n    dependencies = [\"addressable\"];\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0j9dz9sf7swwmfahlngph8n9ibm0cx7mdy9zpv3w44578nbkka49\";\n      type = \"gem\";\n    };\n    version = \"4.1.1\";\n  };\n  jwt = {\n    dependencies = [\"base64\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"02m3vza49pb9dirwpn8vmzbcypi3fc6l3a9dh253jwm1121g7ajb\";\n      type = \"gem\";\n    };\n    version = \"2.8.1\";\n  };\n  language_server-protocol = {\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0gvb1j8xsqxms9mww01rmdl78zkd72zgxaap56bhv8j45z05hp1x\";\n      type = \"gem\";\n    };\n    version = \"3.17.0.3\";\n  };\n  launchy = {\n    dependencies = [\"addressable\"];\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"06r43899384das2bkbrpsdxsafyyqa94il7111053idfalb4984a\";\n      type = \"gem\";\n    };\n    version = \"2.5.2\";\n  };\n  letter_opener = {\n    dependencies = [\"launchy\"];\n    groups = [\"development\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1cnv3ggnzyagl50vzs1693aacv08bhwlprcvjp8jcg2w7cp3zwrg\";\n      type = \"gem\";\n    };\n    version = \"1.10.0\";\n  };\n  lint_roller = {\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"11yc0d84hsnlvx8cpk4cbj6a4dz9pk0r1k29p0n1fz9acddq831c\";\n      type = \"gem\";\n    };\n    version = \"1.1.0\";\n  };\n  llhttp-ffi = {\n    dependencies = [\"ffi-compiler\" \"rake\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"00dh6zmqdj59rhcya0l4b9aaxq6n8xizfbil93k0g06gndyk5xz5\";\n      type = \"gem\";\n    };\n    version = \"0.4.0\";\n  };\n  lograge = {\n    dependencies = [\"actionpack\" \"activesupport\" \"railties\" \"request_store\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1qcsvh9k4c0cp6agqm9a8m4x2gg7vifryqr7yxkg2x9ph9silds2\";\n      type = \"gem\";\n    };\n    version = \"0.14.0\";\n  };\n  loofah = {\n    dependencies = [\"crass\" \"nokogiri\"];\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1zkjqf37v2d7s11176cb35cl83wls5gm3adnfkn2zcc61h3nxmqh\";\n      type = \"gem\";\n    };\n    version = \"2.22.0\";\n  };\n  mail = {\n    dependencies = [\"mini_mime\" \"net-imap\" \"net-pop\" \"net-smtp\"];\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1bf9pysw1jfgynv692hhaycfxa8ckay1gjw5hz3madrbrynryfzc\";\n      type = \"gem\";\n    };\n    version = \"2.8.1\";\n  };\n  marcel = {\n    groups = [\"default\" \"development\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"190n2mk8m1l708kr88fh6mip9sdsh339d2s6sgrik3sbnvz4jmhd\";\n      type = \"gem\";\n    };\n    version = \"1.0.4\";\n  };\n  matrix = {\n    groups = [\"default\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1h2cgkpzkh3dd0flnnwfq6f3nl2b1zff9lvqz8xs853ssv5kq23i\";\n      type = \"gem\";\n    };\n    version = \"0.4.2\";\n  };\n  method_source = {\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1pnyh44qycnf9mzi1j6fywd5fkskv3x7nmsqrrws0rjn5dd4ayfp\";\n      type = \"gem\";\n    };\n    version = \"1.0.0\";\n  };\n  mini_magick = {\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1aj604x11d9pksbljh0l38f70b558rhdgji1s9i763hiagvvx2hs\";\n      type = \"gem\";\n    };\n    version = \"4.11.0\";\n  };\n  mini_mime = {\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1vycif7pjzkr29mfk4dlqv3disc5dn0va04lkwajlpr1wkibg0c6\";\n      type = \"gem\";\n    };\n    version = \"1.1.5\";\n  };\n  mini_portile2 = {\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"149r94xi6b3jbp6bv72f8383b95ndn0p5sxnq11gs1j9jadv0ajf\";\n      type = \"gem\";\n    };\n    version = \"2.8.6\";\n  };\n  minitest = {\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"181rfs9drd34b3akbfbfg0ynz07v74pfkzbb977bxa50nrlqwj2c\";\n      type = \"gem\";\n    };\n    version = \"5.23.0\";\n  };\n  multi_xml = {\n    dependencies = [\"bigdecimal\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"06x61ca5j84nyhr1mwh9r436yiphnc5hmacb3gwqyn5gd0611kjg\";\n      type = \"gem\";\n    };\n    version = \"0.7.1\";\n  };\n  multipart-post = {\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1033p35166d9p97y4vajbbvr13pmkk9zwn7sylxpmk9jrpk8ri67\";\n      type = \"gem\";\n    };\n    version = \"2.4.0\";\n  };\n  mutex_m = {\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1ma093ayps1m92q845hmpk0dmadicvifkbf05rpq9pifhin0rvxn\";\n      type = \"gem\";\n    };\n    version = \"0.2.0\";\n  };\n  net-http-persistent = {\n    dependencies = [\"connection_pool\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1yfypmfg1maf20yfd22zzng8k955iylz7iip0mgc9lazw36g8li7\";\n      type = \"gem\";\n    };\n    version = \"4.0.1\";\n  };\n  net-imap = {\n    dependencies = [\"date\" \"net-protocol\"];\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"18yclv06n0cy8jqmi11sd1dl8nasc5n5r1mhan2v51j7jd3z58v3\";\n      type = \"gem\";\n    };\n    version = \"0.4.12\";\n  };\n  net-pop = {\n    dependencies = [\"net-protocol\"];\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1wyz41jd4zpjn0v1xsf9j778qx1vfrl24yc20cpmph8k42c4x2w4\";\n      type = \"gem\";\n    };\n    version = \"0.1.2\";\n  };\n  net-protocol = {\n    dependencies = [\"timeout\"];\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1a32l4x73hz200cm587bc29q8q9az278syw3x6fkc9d1lv5y0wxa\";\n      type = \"gem\";\n    };\n    version = \"0.2.2\";\n  };\n  net-smtp = {\n    dependencies = [\"net-protocol\"];\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0amlhz8fhnjfmsiqcjajip57ici2xhw089x7zqyhpk51drg43h2z\";\n      type = \"gem\";\n    };\n    version = \"0.5.0\";\n  };\n  nio4r = {\n    groups = [\"default\" \"development\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"017nbw87dpr4wyk81cgj8kxkxqgsgblrkxnmmadc77cg9gflrfal\";\n      type = \"gem\";\n    };\n    version = \"2.7.3\";\n  };\n  nokogiri = {\n    dependencies = [\"mini_portile2\" \"racc\"];\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1lla2macphrlbzkirk0nwwwhcijrfymyfjjw1als0kwqd0n1cdpc\";\n      type = \"gem\";\n    };\n    version = \"1.16.5\";\n  };\n  noticed = {\n    dependencies = [\"http\" \"rails\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0v77w7yjam34hfwjpkmlbx8njvz5wicpn91q9fczwcqa95zsqqg0\";\n      type = \"gem\";\n    };\n    version = \"1.6.3\";\n  };\n  oj = {\n    dependencies = [\"bigdecimal\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0g5nx99lrwmk6ynfaacqkyijnhvi4mckm77bmvpa0jmfg068l26h\";\n      type = \"gem\";\n    };\n    version = \"3.16.3\";\n  };\n  orm_adapter = {\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1fg9jpjlzf5y49qs9mlpdrgs5rpcyihq1s4k79nv9js0spjhnpda\";\n      type = \"gem\";\n    };\n    version = \"0.5.0\";\n  };\n  parallel = {\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"15wkxrg1sj3n1h2g8jcrn7gcapwcgxr659ypjf75z1ipkgxqxwsv\";\n      type = \"gem\";\n    };\n    version = \"1.24.0\";\n  };\n  parallel_tests = {\n    dependencies = [\"parallel\"];\n    groups = [\"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0q5q38sfrpwgcqwf5sl6xals5w11xayh8i9nq1vxya2sbrzrgbcq\";\n      type = \"gem\";\n    };\n    version = \"4.7.1\";\n  };\n  paranoia = {\n    dependencies = [\"activerecord\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"18d3x7d1k804d4pm5lxgnkwi5farbxhk8ndvsh8j6i03lrl6hqp7\";\n      type = \"gem\";\n    };\n    version = \"2.6.3\";\n  };\n  parser = {\n    dependencies = [\"ast\" \"racc\"];\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0i0255l4pw6c1bc0ny98wx5qck25irinq062ijg4002mj8mydwvq\";\n      type = \"gem\";\n    };\n    version = \"3.3.1.0\";\n  };\n  pdf-forms = {\n    dependencies = [\"cliver\" \"rexml\" \"safe_shell\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0fha4fs2hdapixm5d1613ds0vlfs07bipskm6ihrkfh7lx6dw00p\";\n      type = \"gem\";\n    };\n    version = \"1.5.0\";\n  };\n  pg = {\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"071b55bhsz7mivlnp2kv0a11msnl7xg5awvk8mlflpl270javhsb\";\n      type = \"gem\";\n    };\n    version = \"1.5.6\";\n  };\n  pretender = {\n    dependencies = [\"actionpack\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0xxdizzp7l0cbccmh499rifr6q0khjgfqv0kir39haqwphjhy4h2\";\n      type = \"gem\";\n    };\n    version = \"0.5.0\";\n  };\n  pry = {\n    dependencies = [\"coderay\" \"method_source\"];\n    groups = [\"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0k9kqkd9nps1w1r1rb7wjr31hqzkka2bhi8b518x78dcxppm9zn4\";\n      type = \"gem\";\n    };\n    version = \"0.14.2\";\n  };\n  pry-byebug = {\n    dependencies = [\"byebug\" \"pry\"];\n    groups = [\"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1y41al94ks07166qbp2200yzyr5y60hm7xaiw4lxpgsm4b1pbyf8\";\n      type = \"gem\";\n    };\n    version = \"3.10.1\";\n  };\n  psych = {\n    dependencies = [\"stringio\"];\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0s5383m6004q76xm3lb732bp4sjzb6mxb6rbgn129gy2izsj4wrk\";\n      type = \"gem\";\n    };\n    version = \"5.1.2\";\n  };\n  public_suffix = {\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"14y4vzjwf5gp0mqgs880kis0k7n2biq8i6ci6q2n315kichl1hvj\";\n      type = \"gem\";\n    };\n    version = \"5.0.5\";\n  };\n  puma = {\n    dependencies = [\"nio4r\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0i2vaww6qcazj0ywva1plmjnj6rk23b01szswc5jhcq7s2cikd1y\";\n      type = \"gem\";\n    };\n    version = \"6.4.2\";\n  };\n  pundit = {\n    dependencies = [\"activesupport\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"18vz32n5ca5j5h971axnnfa8rjrfqnqv0zkgjv8xmbpb05c9m83w\";\n      type = \"gem\";\n    };\n    version = \"2.3.2\";\n  };\n  racc = {\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"021s7maw0c4d9a6s07vbmllrzqsj2sgmrwimlh8ffkvwqdjrld09\";\n      type = \"gem\";\n    };\n    version = \"1.8.0\";\n  };\n  rack = {\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0hj0rkw2z9r1lcg2wlrcld2n3phwrcgqcp7qd1g9a7hwgalh2qzx\";\n      type = \"gem\";\n    };\n    version = \"2.2.9\";\n  };\n  rack-attack = {\n    dependencies = [\"rack\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0z6pj5vjgl6swq7a33gssf795k958mss8gpmdb4v4cydcs7px91w\";\n      type = \"gem\";\n    };\n    version = \"6.7.0\";\n  };\n  rack-cors = {\n    dependencies = [\"rack\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"02lvkg1nb4z3zc2nry545dap7a64bb9h2k8waxfz0jkabkgnpimw\";\n      type = \"gem\";\n    };\n    version = \"2.0.1\";\n  };\n  rack-session = {\n    dependencies = [\"rack\"];\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0xhxhlsz6shh8nm44jsmd9276zcnyzii364vhcvf0k8b8bjia8d0\";\n      type = \"gem\";\n    };\n    version = \"1.0.2\";\n  };\n  rack-test = {\n    dependencies = [\"rack\"];\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1ysx29gk9k14a14zsp5a8czys140wacvp91fja8xcja0j1hzqq8c\";\n      type = \"gem\";\n    };\n    version = \"2.1.0\";\n  };\n  rackup = {\n    dependencies = [\"rack\" \"webrick\"];\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1wbr03334ba9ilcq25wh9913xciwj0j117zs60vsqm0zgwdkwpp9\";\n      type = \"gem\";\n    };\n    version = \"1.0.0\";\n  };\n  rails = {\n    dependencies = [\"actioncable\" \"actionmailbox\" \"actionmailer\" \"actionpack\" \"actiontext\" \"actionview\" \"activejob\" \"activemodel\" \"activerecord\" \"activestorage\" \"activesupport\" \"railties\"];\n    groups = [\"default\" \"development\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1pl2jgnbm9p031jvbihpbnpwn005107xb1794ps0ayairb6qhldn\";\n      type = \"gem\";\n    };\n    version = \"7.1.3.3\";\n  };\n  rails-controller-testing = {\n    dependencies = [\"actionpack\" \"actionview\" \"activesupport\"];\n    groups = [\"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"151f303jcvs8s149mhx2g5mn67487x0blrf9dzl76q1nb7dlh53l\";\n      type = \"gem\";\n    };\n    version = \"1.0.5\";\n  };\n  rails-dom-testing = {\n    dependencies = [\"activesupport\" \"minitest\" \"nokogiri\"];\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0fx9dx1ag0s1lr6lfr34lbx5i1bvn3bhyf3w3mx6h7yz90p725g5\";\n      type = \"gem\";\n    };\n    version = \"2.2.0\";\n  };\n  rails-html-sanitizer = {\n    dependencies = [\"loofah\" \"nokogiri\"];\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1pm4z853nyz1bhhqr7fzl44alnx4bjachcr6rh6qjj375sfz3sc6\";\n      type = \"gem\";\n    };\n    version = \"1.6.0\";\n  };\n  railties = {\n    dependencies = [\"actionpack\" \"activesupport\" \"irb\" \"rackup\" \"rake\" \"thor\" \"zeitwerk\"];\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"02xic4iyvqxj4hs5xzywg4zrff8s77c7xb1jchjfmdrzclnz51zx\";\n      type = \"gem\";\n    };\n    version = \"7.1.3.3\";\n  };\n  rainbow = {\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0smwg4mii0fm38pyb5fddbmrdpifwv22zv3d3px2xx497am93503\";\n      type = \"gem\";\n    };\n    version = \"3.1.1\";\n  };\n  rake = {\n    groups = [\"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"17850wcwkgi30p7yqh60960ypn7yibacjjha0av78zaxwvd3ijs6\";\n      type = \"gem\";\n    };\n    version = \"13.2.1\";\n  };\n  rdoc = {\n    dependencies = [\"psych\"];\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0ib3cnf4yllvw070gr4bz94sbmqx3haqc5f846fsvdcs494vgxrr\";\n      type = \"gem\";\n    };\n    version = \"6.6.3.1\";\n  };\n  regexp_parser = {\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0ik40vcv7mqigsfpqpca36hpmnx0536xa825ai5qlkv3mmkyf9ss\";\n      type = \"gem\";\n    };\n    version = \"2.9.2\";\n  };\n  reline = {\n    dependencies = [\"io-console\"];\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"06rlp3wjcbwbgw3xlawclzzmj6ryn6ap65nh54x5yzgx0c3jlqqz\";\n      type = \"gem\";\n    };\n    version = \"0.5.7\";\n  };\n  request_store = {\n    dependencies = [\"rack\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1jw89j9s5p5cq2k7ffj5p4av4j4fxwvwjs1a4i9g85d38r9mvdz1\";\n      type = \"gem\";\n    };\n    version = \"1.7.0\";\n  };\n  responders = {\n    dependencies = [\"actionpack\" \"railties\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"06ilkbbwvc8d0vppf8ywn1f79ypyymlb9krrhqv4g0q215zaiwlj\";\n      type = \"gem\";\n    };\n    version = \"3.1.1\";\n  };\n  rexml = {\n    dependencies = [\"strscan\"];\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0d8ivcirrrxpkpjc1c835wknc9s2fl54xpw08s177yfrh5ish209\";\n      type = \"gem\";\n    };\n    version = \"3.2.8\";\n  };\n  rspec-core = {\n    dependencies = [\"rspec-support\"];\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0k252n7s80bvjvpskgfm285a3djjjqyjcarlh3aq7a4dx2s94xsm\";\n      type = \"gem\";\n    };\n    version = \"3.13.0\";\n  };\n  rspec-expectations = {\n    dependencies = [\"diff-lcs\" \"rspec-support\"];\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0bhhjzwdk96vf3gq3rs7mln80q27fhq82hda3r15byb24b34h7b2\";\n      type = \"gem\";\n    };\n    version = \"3.13.0\";\n  };\n  rspec-mocks = {\n    dependencies = [\"diff-lcs\" \"rspec-support\"];\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0f3vgp43hajw716vmgjv6f4ar6f97zf50snny6y3fy9kkj4qjw88\";\n      type = \"gem\";\n    };\n    version = \"3.13.1\";\n  };\n  rspec-rails = {\n    dependencies = [\"actionpack\" \"activesupport\" \"railties\" \"rspec-core\" \"rspec-expectations\" \"rspec-mocks\" \"rspec-support\"];\n    groups = [\"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"02wr7fl189p1lnpaylz48dlp1n5y763w92gk59s0345hwfr4m1q2\";\n      type = \"gem\";\n    };\n    version = \"6.1.2\";\n  };\n  rspec-support = {\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"03z7gpqz5xkw9rf53835pa8a9vgj4lic54rnix9vfwmp2m7pv1s8\";\n      type = \"gem\";\n    };\n    version = \"3.13.1\";\n  };\n  rspec_junit_formatter = {\n    dependencies = [\"rspec-core\"];\n    groups = [\"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"059bnq1gcwl9g93cqf13zpz38zk7jxaa43anzz06qkmfwrsfdpa0\";\n      type = \"gem\";\n    };\n    version = \"0.6.0\";\n  };\n  rswag-api = {\n    dependencies = [\"activesupport\" \"railties\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0prlhpiqvs6lvnmlvc9mjbfh7411z8zhc61cn0fnxx1iwp38fsin\";\n      type = \"gem\";\n    };\n    version = \"2.13.0\";\n  };\n  rswag-specs = {\n    dependencies = [\"activesupport\" \"json-schema\" \"railties\" \"rspec-core\"];\n    groups = [\"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1iyqd95l8r8m6jna451xb20lsp0jiajk1gbw845qlyqf6d69xyx2\";\n      type = \"gem\";\n    };\n    version = \"2.13.0\";\n  };\n  rswag-ui = {\n    dependencies = [\"actionpack\" \"railties\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1bsc1wirnql1h0xxmi6afa8gpwqi5j778qc1l4kx8x3lsgghfi0m\";\n      type = \"gem\";\n    };\n    version = \"2.13.0\";\n  };\n  rubocop = {\n    dependencies = [\"json\" \"language_server-protocol\" \"parallel\" \"parser\" \"rainbow\" \"regexp_parser\" \"rexml\" \"rubocop-ast\" \"ruby-progressbar\" \"unicode-display_width\"];\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1zkdxkhvhmxap5w2rncpmrq40dw0pbz81sa1ivsr7kh0p8j0a9x3\";\n      type = \"gem\";\n    };\n    version = \"1.63.5\";\n  };\n  rubocop-ast = {\n    dependencies = [\"parser\"];\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"063qgvqbyv354icl2sgx758z22wzq38hd9skc3n96sbpv0cdc1qv\";\n      type = \"gem\";\n    };\n    version = \"1.31.3\";\n  };\n  rubocop-performance = {\n    dependencies = [\"rubocop\" \"rubocop-ast\"];\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"16jayzjaaglhx69s6b83acpdzcxxccfkcn69gfpkimf2j64zlm7c\";\n      type = \"gem\";\n    };\n    version = \"1.21.0\";\n  };\n  ruby-progressbar = {\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0cwvyb7j47m7wihpfaq7rc47zwwx9k4v7iqd9s1xch5nm53rrz40\";\n      type = \"gem\";\n    };\n    version = \"1.13.0\";\n  };\n  ruby-vips = {\n    dependencies = [\"ffi\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"19pzpx406rr9s3qk527rn9y3b76sjq5pi7y0xzqiy50q3k0hhg7g\";\n      type = \"gem\";\n    };\n    version = \"2.1.4\";\n  };\n  ruby2_keywords = {\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1vz322p8n39hz3b4a9gkmz9y7a5jaz41zrm2ywf31dvkqm03glgz\";\n      type = \"gem\";\n    };\n    version = \"0.0.5\";\n  };\n  rubyzip = {\n    groups = [\"default\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0grps9197qyxakbpw02pda59v45lfgbgiyw48i0mq9f2bn9y6mrz\";\n      type = \"gem\";\n    };\n    version = \"2.3.2\";\n  };\n  sablon = {\n    dependencies = [\"nokogiri\" \"rubyzip\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"010r39zasaabd1kr34qbdhir2ialbg1p68rziinxz6iwckvgbzn9\";\n      type = \"gem\";\n    };\n    version = \"0.3.2\";\n  };\n  safe_shell = {\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"02hvmik4amhcwpnzbnxs61gyq6p6vjpgn2d4pm8gh96rqxjkw7g4\";\n      type = \"gem\";\n    };\n    version = \"1.1.0\";\n  };\n  scout_apm = {\n    dependencies = [\"parser\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1wyih4c8fyb83cp21b1igbrf8nvj0q8ivgkmd1zwgcywmbvmw9lj\";\n      type = \"gem\";\n    };\n    version = \"5.3.8\";\n  };\n  selenium-webdriver = {\n    dependencies = [\"base64\" \"rexml\" \"rubyzip\" \"websocket\"];\n    groups = [\"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0qrjr30qs01b27km6ipzc2zasdlzhdgri5q7qrb53z1j8l0n82y3\";\n      type = \"gem\";\n    };\n    version = \"4.21.1\";\n  };\n  shoulda-matchers = {\n    dependencies = [\"activesupport\"];\n    groups = [\"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1pfq0w167v4055k0km64sxik1qslhsi32wl2jlidmfzkqmcw00m7\";\n      type = \"gem\";\n    };\n    version = \"6.2.0\";\n  };\n  simplecov = {\n    dependencies = [\"docile\" \"simplecov-html\" \"simplecov_json_formatter\"];\n    groups = [\"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"198kcbrjxhhzca19yrdcd6jjj9sb51aaic3b0sc3pwjghg3j49py\";\n      type = \"gem\";\n    };\n    version = \"0.22.0\";\n  };\n  simplecov-html = {\n    groups = [\"default\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0yx01bxa8pbf9ip4hagqkp5m0mqfnwnw2xk8kjraiywz4lrss6jb\";\n      type = \"gem\";\n    };\n    version = \"0.12.3\";\n  };\n  simplecov_json_formatter = {\n    groups = [\"default\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0a5l0733hj7sk51j81ykfmlk2vd5vaijlq9d5fn165yyx3xii52j\";\n      type = \"gem\";\n    };\n    version = \"0.1.4\";\n  };\n  smart_properties = {\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0jrqssk9qhwrpq41arm712226vpcr458xv6xaqbk8cp94a0kycpr\";\n      type = \"gem\";\n    };\n    version = \"1.17.0\";\n  };\n  spring = {\n    groups = [\"development\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1bm5w3mp597vy0cjwx609k3jdh5zik36ffmna7hchrn9g96s45w5\";\n      type = \"gem\";\n    };\n    version = \"4.2.1\";\n  };\n  spring-commands-rspec = {\n    dependencies = [\"spring\"];\n    groups = [\"development\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0b0svpq3md1pjz5drpa5pxwg8nk48wrshq8lckim4x3nli7ya0k2\";\n      type = \"gem\";\n    };\n    version = \"1.0.4\";\n  };\n  sprockets = {\n    dependencies = [\"concurrent-ruby\" \"rack\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1qj82dcfkk6c4zw357k5r05s5iwvyddh57bpwj0a1hjgaw70pcb8\";\n      type = \"gem\";\n    };\n    version = \"4.1.1\";\n  };\n  sprockets-rails = {\n    dependencies = [\"actionpack\" \"activesupport\" \"sprockets\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1b9i14qb27zs56hlcc2hf139l0ghbqnjpmfi0054dxycaxvk5min\";\n      type = \"gem\";\n    };\n    version = \"3.4.2\";\n  };\n  standard = {\n    dependencies = [\"language_server-protocol\" \"lint_roller\" \"rubocop\" \"standard-custom\" \"standard-performance\"];\n    groups = [\"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1135p2imjcx1a9qd1pj4kw09g6ly151kscc6n08iw0zfdxy09vmp\";\n      type = \"gem\";\n    };\n    version = \"1.36.0\";\n  };\n  standard-custom = {\n    dependencies = [\"lint_roller\" \"rubocop\"];\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0av55ai0nv23z5mhrwj1clmxpgyngk7vk6rh58d4y1ws2y2dqjj2\";\n      type = \"gem\";\n    };\n    version = \"1.0.2\";\n  };\n  standard-performance = {\n    dependencies = [\"lint_roller\" \"rubocop-performance\"];\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1551dwjwyqy7rckjgcb25k51k6wndn8m37mmbpanr0k4b6v757yy\";\n      type = \"gem\";\n    };\n    version = \"1.4.0\";\n  };\n  stimulus-rails = {\n    dependencies = [\"railties\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0b2nbj25cqhhklj04bfk0q3532skm4qcak6xki65yq2fssqrl7sd\";\n      type = \"gem\";\n    };\n    version = \"1.3.3\";\n  };\n  stringio = {\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"063psvsn1aq6digpznxfranhcpmi0sdv2jhra5g0459sw0x2dxn1\";\n      type = \"gem\";\n    };\n    version = \"3.1.0\";\n  };\n  strong_migrations = {\n    dependencies = [\"activerecord\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0p9g8jqcakpwmbs6f77ydmbiwbgx9c5nr6jgwxh4xx6xpig1bphq\";\n      type = \"gem\";\n    };\n    version = \"1.8.0\";\n  };\n  strscan = {\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0mamrl7pxacbc79ny5hzmakc9grbjysm3yy6119ppgsg44fsif01\";\n      type = \"gem\";\n    };\n    version = \"3.1.0\";\n  };\n  thor = {\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1vq1fjp45az9hfp6fxljhdrkv75cvbab1jfrwcw738pnsiqk8zps\";\n      type = \"gem\";\n    };\n    version = \"1.3.1\";\n  };\n  timeout = {\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"16mvvsmx90023wrhf8dxc1lpqh0m8alk65shb7xcya6a9gflw7vg\";\n      type = \"gem\";\n    };\n    version = \"0.4.1\";\n  };\n  traceroute = {\n    dependencies = [\"rails\"];\n    groups = [\"development\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1s06m08jqzv0idk39xknqmnacg684y48cinszc89bvg73i9knjjq\";\n      type = \"gem\";\n    };\n    version = \"0.8.1\";\n  };\n  twilio-ruby = {\n    dependencies = [\"faraday\" \"jwt\" \"nokogiri\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"19qr60lmycbnfb55drnhszmbs991y2ijxx1jav82cyvmzl53s883\";\n      type = \"gem\";\n    };\n    version = \"7.0.2\";\n  };\n  tzinfo = {\n    dependencies = [\"concurrent-ruby\"];\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"16w2g84dzaf3z13gxyzlzbf748kylk5bdgg3n1ipvkvvqy685bwd\";\n      type = \"gem\";\n    };\n    version = \"2.0.6\";\n  };\n  unf = {\n    dependencies = [\"unf_ext\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0bh2cf73i2ffh4fcpdn9ir4mhq8zi50ik0zqa1braahzadx536a9\";\n      type = \"gem\";\n    };\n    version = \"0.1.4\";\n  };\n  unf_ext = {\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1yj2nz2l101vr1x9w2k83a0fag1xgnmjwp8w8rw4ik2rwcz65fch\";\n      type = \"gem\";\n    };\n    version = \"0.0.8.2\";\n  };\n  unicode-display_width = {\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1d0azx233nags5jx3fqyr23qa2rhgzbhv8pxp46dgbg1mpf82xky\";\n      type = \"gem\";\n    };\n    version = \"2.5.0\";\n  };\n  uniform_notifier = {\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1dfvqixshwvm82b9qwdidvnkavdj7s0fbdbmyd4knkl6l3j9xcwr\";\n      type = \"gem\";\n    };\n    version = \"1.16.0\";\n  };\n  view_component = {\n    dependencies = [\"activesupport\" \"concurrent-ruby\" \"method_source\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1px8bcmj43xzcnzs7a8sh2qvhr07afbk0b2qjdjns4zjprsqksml\";\n      type = \"gem\";\n    };\n    version = \"3.9.0\";\n  };\n  warden = {\n    dependencies = [\"rack\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1l7gl7vms023w4clg02pm4ky9j12la2vzsixi2xrv9imbn44ys26\";\n      type = \"gem\";\n    };\n    version = \"1.2.9\";\n  };\n  web-console = {\n    dependencies = [\"actionview\" \"activemodel\" \"bindex\" \"railties\"];\n    groups = [\"development\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"087y4byl2s3fg0nfhix4s0r25cv1wk7d2j8n5324waza21xg7g77\";\n      type = \"gem\";\n    };\n    version = \"4.2.1\";\n  };\n  webmock = {\n    dependencies = [\"addressable\" \"crack\" \"hashdiff\"];\n    groups = [\"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"07zk8ljq5kyd1mm9qw3452fcnf7frg3irh9ql8ln2m8zbi1qf1qh\";\n      type = \"gem\";\n    };\n    version = \"3.23.0\";\n  };\n  webrick = {\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"13qm7s0gr2pmfcl7dxrmq38asaza4w0i2n9my4yzs499j731wh8r\";\n      type = \"gem\";\n    };\n    version = \"1.8.1\";\n  };\n  websocket = {\n    groups = [\"default\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1a4zc8d0d91c3xqwapda3j3zgpfwdbj76hkb69xn6qvfkfks9h9c\";\n      type = \"gem\";\n    };\n    version = \"1.2.10\";\n  };\n  websocket-driver = {\n    dependencies = [\"websocket-extensions\"];\n    groups = [\"default\" \"development\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"1nyh873w4lvahcl8kzbjfca26656d5c6z3md4sbqg5y1gfz0157n\";\n      type = \"gem\";\n    };\n    version = \"0.7.6\";\n  };\n  websocket-extensions = {\n    groups = [\"default\" \"development\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0hc2g9qps8lmhibl5baa91b4qx8wqw872rgwagml78ydj8qacsqw\";\n      type = \"gem\";\n    };\n    version = \"0.1.5\";\n  };\n  wicked = {\n    dependencies = [\"railties\"];\n    groups = [\"default\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"07ck2z3dkgc2yp4w20dav77w754r55wdz2gm7agnslmqn1n2bbvw\";\n      type = \"gem\";\n    };\n    version = \"2.0.0\";\n  };\n  xpath = {\n    dependencies = [\"nokogiri\"];\n    groups = [\"default\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0bh8lk9hvlpn7vmi6h4hkcwjzvs2y0cmkk3yjjdr8fxvj6fsgzbd\";\n      type = \"gem\";\n    };\n    version = \"3.2.0\";\n  };\n  zeitwerk = {\n    groups = [\"default\" \"development\" \"test\"];\n    platforms = [];\n    source = {\n      remotes = [\"https://rubygems.org\"];\n      sha256 = \"0ayraiqfhhjzpyr4yxp035002lq78ip1zhr0ix87rn3rqpnsrn3h\";\n      type = \"gem\";\n    };\n    version = \"2.6.14\";\n  };\n}\n"
  },
  {
    "path": "jest.config.js",
    "content": "// For a detailed explanation regarding each configuration property, visit:\n// https://jestjs.io/docs/en/configuration.html\n\nmodule.exports = {\n  // All imported modules in your tests should be mocked automatically\n  // automock: false,\n\n  // Stop running tests after `n` failures\n  // bail: 0,\n\n  // The directory where Jest should store its cached dependency information\n  // cacheDirectory: \"/private/var/folders/k5/qkc2qsxx5cd_m53hy8whr4b40000gn/T/jest_dx\",\n\n  // Automatically clear mock calls and instances between every test\n  // clearMocks: false,\n\n  // Indicates whether the coverage information should be collected while executing the test\n  // collectCoverage: false,\n\n  // An array of glob patterns indicating a set of files for which coverage information should be collected\n  // collectCoverageFrom: undefined,\n\n  // The directory where Jest should output its coverage files\n  // coverageDirectory: undefined,\n\n  // An array of regexp pattern strings used to skip coverage collection\n  testPathIgnorePatterns: [\n    '<rootDir>/app/javascript/__tests__/setup-jest.js'\n  ],\n\n  // Indicates which provider should be used to instrument code for coverage\n  // coverageProvider: \"v8\",\n\n  // A list of reporter names that Jest uses when writing coverage reports\n  // coverageReporters: [\n  //   \"json\",\n  //   \"text\",\n  //   \"lcov\",\n  //   \"clover\"\n  // ],\n\n  // An object that configures minimum threshold enforcement for coverage results\n  // coverageThreshold: undefined,\n\n  // A path to a custom dependency extractor\n  // dependencyExtractor: undefined,\n\n  // Make calling deprecated APIs throw helpful error messages\n  // errorOnDeprecated: false,\n\n  // Force coverage collection from ignored files using an array of glob patterns\n  // forceCoverageMatch: [],\n\n  // A path to a module which exports an async function that is triggered once before all test suites\n  // globalSetup: undefined,\n\n  // A path to a module which exports an async function that is triggered once after all test suites\n  // globalTeardown: undefined,\n\n  // A set of global variables that need to be available in all test environments\n  // globals: {},\n\n  // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.\n  // maxWorkers: \"50%\",\n\n  // An array of directory names to be searched recursively up from the requiring module's location\n  // moduleDirectories: [\n  //   \"node_modules\"\n  // ],\n\n  // An array of file extensions your modules use\n  // moduleFileExtensions: [\n  //   \"js\",\n  //   \"json\",\n  //   \"jsx\",\n  //   \"ts\",\n  //   \"tsx\",\n  //   \"node\"\n  // ],\n\n  // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module\n  // moduleNameMapper: {},\n  moduleNameMapper: {\n    '\\\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/__mocks__/fileMock.js',\n    '\\\\.(css|less)$': '<rootDir>/__mocks__/styleMock.js'\n  },\n\n  // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader\n  // modulePathIgnorePatterns: [],\n\n  // Activates notifications for test results\n  // notify: false,\n\n  // An enum that specifies notification mode. Requires { notify: true }\n  // notifyMode: \"failure-change\",\n\n  // A preset that is used as a base for Jest's configuration\n  // preset: undefined,\n\n  // Run tests from one or more projects\n  // projects: undefined,\n\n  // Use this configuration option to add custom reporters to Jest\n  // reporters: undefined,\n\n  // Automatically reset mock state between every test\n  // resetMocks: false,\n\n  // Reset the module registry before running each individual test\n  // resetModules: false,\n\n  // A path to a custom resolver\n  // resolver: undefined,\n\n  // Automatically restore mock state between every test\n  // restoreMocks: false,\n\n  // The root directory that Jest should scan for tests and modules within\n  // rootDir: undefined,\n\n  // A list of paths to directories that Jest should use to search for files in\n  // roots: [\n  //   \"<rootDir>\"\n  // ],\n\n  // Allows you to use a custom runner instead of Jest's default test runner\n  // runner: \"jest-runner\",\n\n  // The paths to modules that run some code to configure or set up the testing environment before each test\n  setupFiles: ['<rootDir>/app/javascript/__tests__/setup-jest.js'],\n  // A list of paths to modules that run some code to configure or set up the testing framework before each test\n  // setupFilesAfterEnv: [],\n\n  // The number of seconds after which a test is considered as slow and reported as such in the results.\n  // slowTestThreshold: 5,\n\n  // A list of paths to snapshot serializer modules Jest should use for snapshot testing\n  // snapshotSerializers: [],\n\n  // The test environment that will be used for testing\n  testEnvironment: 'jest-environment-jsdom',\n\n  // Options that will be passed to the testEnvironment\n  // testEnvironmentOptions: {},\n\n  // Adds a location field to test results\n  // testLocationInResults: false,\n\n  // The glob patterns Jest uses to detect test files\n  // testMatch: [\n  //   \"**/__tests__/**/*.[jt]s?(x)\",\n  //   \"**/?(*.)+(spec|test).[tj]s?(x)\"\n  // ],\n\n  // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped\n  // testPathIgnorePatterns: [\n  //   \"/node_modules/\"\n  // ],\n\n  // The regexp pattern or array of patterns that Jest uses to detect test files\n  testRegex: ['app/javascript/__tests__']\n\n  // This option allows the use of a custom results processor\n  // testResultsProcessor: undefined,\n\n  // This option allows use of a custom test runner\n  // testRunner: \"jasmine2\",\n\n  // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href\n  // testURL: \"http://localhost\",\n\n  // Setting this value to \"fake\" allows the use of fake timers for functions such as \"setTimeout\"\n  // timers: \"real\",\n\n  // A map from regular expressions to paths to transformers\n  // transform: undefined,\n\n  // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation\n  // transformIgnorePatterns: [\n  //   \"/node_modules/\",\n  //   \"\\\\.pnp\\\\.[^\\\\/]+$\"\n  // ],\n\n  // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them\n  // unmockedModulePathPatterns: undefined,\n\n  // Indicates whether each individual test should be reported during the run\n  // verbose: undefined,\n\n  // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode\n  // watchPathIgnorePatterns: [],\n\n  // Whether to use watchman for file crawling\n  // watchman: true,\n}\n"
  },
  {
    "path": "lib/assets/.keep",
    "content": ""
  },
  {
    "path": "lib/ext/pdf_forms.rb",
    "content": "module Ext\n  module PdfForms\n    #\n    # Fill form using FDF file directly\n    #\n    def fill_form_with_fdf(template, destination, fdf_path, fill_options = {}, flatten:)\n      args = pdftk_arguments(template, destination, fdf_path, flatten: flatten)\n\n      result = call_pdftk(*append_options(args, fill_options))\n\n      if !File.readable?(destination) || File.size(destination).zero?\n        raise ::PdfForms::PdftkError, \"failed to fill form with command\\n#{pdftk} #{args.flatten.compact.join \" \"}\\ncommand output was:\\n#{result}\"\n      end\n    end\n\n    private\n\n    def pdftk_arguments(template, destination, fdf_path, flatten:)\n      q_template = normalize_path(template)\n      q_destination = normalize_path(destination)\n      q_form_data = normalize_path(fdf_path)\n\n      args = [q_template, \"fill_form\", q_form_data, \"output\", q_destination]\n      args << \"flatten\" if flatten\n      args\n    end\n  end\nend\n\nPdfForms::PdftkWrapper.include(Ext::PdfForms)\n"
  },
  {
    "path": "lib/generators/rails/policy/USAGE",
    "content": "Description:\n    Generates a pundit policy and corresponding spec.\n\nExample:\n    bin/rails generate policy CasaThing\n\n    This will create:\n        - app/policies/casa_thing_policy.rb\n        - spec/policies/casa_thing_policy_spec.rb\n"
  },
  {
    "path": "lib/generators/rails/policy/policy_generator.rb",
    "content": "# NOTE: Rails namespace in order to be able to called from rails generators (see initializers/generators.rb)\nclass Rails::PolicyGenerator < Rails::Generators::NamedBase\n  source_root File.expand_path(\"templates\", __dir__)\n\n  remove_class_option :skip_namespace\n  remove_class_option :skip_collision_check\n\n  argument :actions, type: :array, banner: \"action action\", default: []\n\n  class_option :headless, type: :boolean, default: false,\n    desc: \"Policy for non-model routes (dashboard, collection, etc)\"\n\n  def create_policy\n    template \"policy.rb\", File.join(\"app/policies\", class_path, \"#{file_name}_policy.rb\")\n  end\n\n  def create_policy_spec\n    template \"policy_spec.rb\", File.join(\"spec/policies\", class_path, \"#{file_name}_policy_spec.rb\")\n  end\nend\n"
  },
  {
    "path": "lib/generators/rails/policy/templates/policy.rb.tt",
    "content": "<%- module_namespacing do -%>\nclass <%= class_name %>Policy < ApplicationPolicy\n<%- if options[:headless] -%>\n  # Headless policy (used when no corresponding ActiveRecord/Model):\n  # - use `authorize(:<%= singular_name %>, :action?)` in controller actions\n  # - see https://github.com/varvet/pundit#headless-policies\n  def initialize(user, _record)\n    @user = user\n  end\n<%- else -%>\n  class Scope < ApplicationPolicy::Scope\n    def resolve\n      case user\n      when CasaAdmin, Supervisor\n        scope.where(casa_org: @user.casa_org)\n      when Volunteer\n        # REMOVE IF NOT APPLICABLE (just an example, doesn't work for all cases)\n        # scope.assigned_to_user(@user)\n        scope.none\n      else\n        scope.none\n      end\n    end\n  end\n<%- end -%>\n<%- user_example = \"is_admin? || is_supervisor?\" -%>\n<%- record_example = \"admin_or_supervisor_same_org?\" -%>\n<%- if actions.empty? -%>\n\n  # No actions specified, Example usage:\n  # def index?\n  #   <%= user_example %>\n  # end\n  #\n  # def show?\n  #   <%= record_example %>\n  # end\n<%- end -%>\n<%- actions.each do |action| -%>\n\n  <%- if action == \"index\" -%>\n  def index?\n    <%= user_example %>\n  end\n  <%- else -%>\n  def <%= action %>?\n    <%= options[:headless] ? user_example :  record_example %>\n  end\n  <%- end -%>\n<%- end -%>\nend\n<% end -%>\n"
  },
  {
    "path": "lib/generators/rails/policy/templates/policy_spec.rb.tt",
    "content": "require \"<%= File.exist?(\"spec/rails_helper.rb\") ? \"rails_helper\" : \"spec_helper\" %>\"\n\n<%- headless_actions = options[:headless] ? actions : actions.select { |action| action.to_s.match?(/index/) } -%>\n<%- headless_methods = headless_actions.map { |action| \":#{action}?\" }.join(\", \") -%>\n<%- headless_methods = \":index?\" if actions.empty? -%>\n<%- record_actions = actions - headless_actions -%>\n<%- record_methods = record_actions.map { |action| \":#{action}?\" }.join(\", \") -%>\n<%- record_methods = \":show?\" if actions.empty? -%>\nRSpec.describe <%= class_name %>Policy, type: :policy do\n  let(:casa_org) { create :casa_org }\n  let(:volunteer) { create :volunteer, casa_org: }\n  let(:supervisor) { create :supervisor, casa_org: }\n  let(:casa_admin) { create :casa_admin, casa_org: }\n  let(:all_casa_admin) { create :all_casa_admin }\n<%- unless options[:headless] -%>\n\n  # may need to create other records/modify factory to assign org\n  let(:<%= singular_name %>) { create :<%= singular_name %>, casa_org: }\n  # modify to assign to volunteer user or remove if not applicable\n  let(:volunteer_<%= singular_name %>) { create :<%= singular_name %>, casa_org:, volunteer: }\n<%- end -%>\n\n  subject { described_class }\n\n  # NOTE: `permissions :action_one?, :action_two?, :action_three? do` for same behavior per method\n<%- unless options[:headless] -%>\n  # - may need to move collection methods to the other permissions block, this generator only checks for 'index',\n<%- end -%>\n\n<%- if actions.empty? -%>\n  # TODO: No actions were specified, replace show/index examples with actual policy methods & remove unused\n\n<%- end -%>\n<%- unless record_methods.empty? -%>\n  permissions <%= record_methods %> do\n    # Usage for action(s) on a single record (check user and record info to authorize)\n    it \"does not permit a nil user\" do\n      expect(described_class).not_to permit(nil, <%= singular_name %>)\n    end\n\n    it \"does not permit a volunteer\" do\n      expect(described_class).not_to permit(volunteer, <%= singular_name %>)\n    end\n\n    it \"permits a volunteer assigned to the <%= human_name.downcase %>\" do\n      expect(described_class).to permit(volunteer, volunteer_<%= singular_name %>)\n    end\n\n    it \"permits a supervisor\" do\n      expect(described_class).to permit(supervisor, <%= singular_name %>)\n    end\n\n    it \"does not permit a supervisor for a different casa org\" do\n      other_org_supervisor = create :supervisor, casa_org: create(:casa_org)\n      expect(described_class).not_to permit(other_org_supervisor, <%= singular_name %>)\n    end\n\n    it \"permits a casa admin\" do\n      expect(described_class).to permit(casa_admin, <%= singular_name %>)\n    end\n\n    it \"does not permit a casa admin for a different casa org\" do\n      other_org_casa_admin = create :casa_admin, casa_org: create(:casa_org)\n      expect(described_class).not_to permit(other_org_casa_admin, <%= singular_name %>)\n    end\n\n    it \"does not permit an all casa admin\" do\n      expect(described_class).not_to permit(all_casa_admin, <%= singular_name %>)\n    end\n  end\n<%- end -%>\n<%- unless headless_methods.empty? -%>\n\n  permissions <%= headless_methods %> do\n    # Usage for action(s) on a collection of records (no single record to authorize, check user only)\n    it \"does not permit a nil user\" do\n      expect(described_class).not_to permit(nil, :<%= singular_name %>)\n    end\n\n    it \"does not permit a volunteer\" do\n      expect(described_class).not_to permit(volunteer, :<%= singular_name %>)\n    end\n\n    it \"permits a supervisor\" do\n      expect(described_class).to permit(supervisor, :<%= singular_name %>)\n    end\n\n    it \"permits a casa admin\" do\n      expect(described_class).to permit(casa_admin, :<%= singular_name %>)\n    end\n\n    it \"does not permit an all casa admin\" do\n      expect(described_class).not_to permit(all_casa_admin, :<%= singular_name %>)\n    end\n  end\n<%- end -%>\n<%- unless options[:headless] -%>\n\n  describe \"Scope#resolve\" do\n    let!(:casa_org_<%= singular_name %>) { create :<%= singular_name %>, casa_org: }\n    let!(:other_casa_org_<%= singular_name %>) { create :<%= singular_name %>, casa_org: create(:casa_org) }\n\n    subject { described_class::Scope.new(user, <%= class_name %>.all).resolve }\n\n    context \"when user is a visitor\" do\n      let(:user) { nil }\n\n      it { is_expected.not_to include(casa_org_<%= singular_name %>) }\n      it { is_expected.not_to include(other_casa_org_<%= singular_name %>) }\n    end\n\n    context \"when user is a volunteer\" do\n      let(:user) { volunteer }\n      let!(:user_<%= singular_name %>) { volunteer_<%= singular_name %> }\n\n      it { is_expected.to include(user_<%= singular_name %>) }\n      it { is_expected.not_to include(casa_org_<%= singular_name %>) }\n      it { is_expected.not_to include(other_casa_org_<%= singular_name %>) }\n    end\n\n    context \"when user is a supervisor\" do\n      let(:user) { supervisor }\n\n      it { is_expected.to include(casa_org_<%= singular_name %>) }\n      it { is_expected.not_to include(other_casa_org_<%= singular_name %>) }\n    end\n\n    context \"when user is a casa_admin\" do\n      let(:user) { casa_admin }\n\n      it { is_expected.to include(casa_org_<%= singular_name %>) }\n      it { is_expected.not_to include(other_casa_org_<%= singular_name %>) }\n    end\n\n    context \"when user is an all_casa_admin\" do\n      let(:user) { all_casa_admin }\n\n      it { is_expected.not_to include(casa_org_<%= singular_name %>) }\n      it { is_expected.not_to include(other_casa_org_<%= singular_name %>) }\n    end\n  end\n<%- end -%>\nend\n"
  },
  {
    "path": "lib/mailers/debug_preview_mailer.rb",
    "content": "class DebugPreviewMailer < ActionMailer::Base\n  def invalid_user(role)\n    mail(\n      from: \"reply@example.com\",\n      to: \"missing_#{role}@example.com\",\n      subject: \"invalid_user_id\",\n      body: \"User does not exist or is not a #{role}\"\n    )\n  end\nend\n"
  },
  {
    "path": "lib/mailers/previews/casa_admin_mailer_preview.rb",
    "content": "require_relative \"../debug_preview_mailer\"\nclass CasaAdminMailerPreview < ActionMailer::Preview\n  def account_setup\n    casa_admin = params.has_key?(:id) ? CasaAdmin.find_by(id: params[:id]) : CasaAdmin.last\n    if casa_admin.nil?\n      DebugPreviewMailer.invalid_user(\"casa_admin\")\n    else\n      CasaAdminMailer.account_setup(casa_admin)\n    end\n  end\n\n  def deactivation\n    casa_admin = params.has_key?(:id) ? CasaAdmin.find_by(id: params[:id]) : CasaAdmin.last\n    if casa_admin.nil?\n      DebugPreviewMailer.invalid_user(\"casa_admin\")\n    else\n      CasaAdminMailer.deactivation(casa_admin)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/mailers/previews/concerns/mailer_preview.rb",
    "content": "module MailerPreview\n  private\n\n  def preview_linkable(record)\n    @id ||= 1\n    record.tap do |r|\n      r.id = @id\n      @id += 1\n      def r.persisted?\n        true\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/mailers/previews/devise_mailer_preview.rb",
    "content": "# Preview all emails at http://localhost:3000/rails/mailers/devise_mailer\n# :nocov:\nrequire_relative \"../debug_preview_mailer\"\nclass DeviseMailerPreview < ActionMailer::Preview\n  def reset_password_instructions\n    user = params.has_key?(:id) ? User.find_by(id: params[:id]) : User.last\n    if user.nil?\n      DebugPreviewMailer.invalid_user(\"user\")\n    else\n      Devise::Mailer.reset_password_instructions(user, \"faketoken\")\n    end\n  end\n\n  def invitation_instructions_as_all_casa_admin\n    all_casa_admin = AllCasaAdmin.first\n    update_invitation_sent_at(all_casa_admin)\n    preview(all_casa_admin)\n  end\n\n  def invitation_instructions_as_casa_admin\n    casa_admin = CasaAdmin.first\n    update_invitation_sent_at(casa_admin)\n    preview(casa_admin)\n  end\n\n  def invitation_instructions_as_supervisor\n    supervisor = Supervisor.first\n    update_invitation_sent_at(supervisor)\n    preview(supervisor)\n  end\n\n  def invitation_instructions_as_volunteer\n    volunteer = Volunteer.first\n    update_invitation_sent_at(volunteer)\n    preview(volunteer)\n  end\n\n  private\n\n  # Unused email types\n\n  def update_invitation_sent_at(model)\n    # Set :invitation_sent_at to guarantee the preview works\n    model.update_attribute(:invitation_sent_at, Date.today)\n  end\n\n  def preview(model)\n    Devise::Mailer.invitation_instructions(model, \"faketoken\")\n  end\n\n  def email_changed\n    user = params.has_key?(:id) ? User.find_by(id: params[:id]) : User.last\n    Devise::Mailer.email_changed(user)\n  end\n\n  def password_change\n    user = params.has_key?(:id) ? User.find_by(id: params[:id]) : User.last\n    Devise::Mailer.password_change(user)\n  end\nend\n# :nocov:\n"
  },
  {
    "path": "lib/mailers/previews/fund_request_mailer_preview.rb",
    "content": "# Preview all emails at http://localhost:3000/rails/mailers/fund_request_mailer\n# :nocov:\nrequire_relative \"../debug_preview_mailer\"\n\nclass FundRequestMailerPreview < ActionMailer::Preview\n  def send_request\n    # Set the FUND_REQUEST_RECIPIENT_EMAIL environment variable for testing\n    ENV[\"FUND_REQUEST_RECIPIENT_EMAIL\"] = \"recipient@example.com\"\n\n    fund_request = FundRequest.new(\n      submitter_email: \"casa@example.cmo\",\n      youth_name: \"The youth Name\",\n      payment_amount: \"$123.45\",\n      deadline: Date.today + 7.days,\n      request_purpose: \"shoes\",\n      payee_name: \"payee_name\",\n      requested_by_and_relationship: \"Sample Requester\",\n      other_funding_source_sought: \"Sample Funding Source\",\n      impact: \"Sample Impact\",\n      extra_information: \"Sample Extra Information\"\n    )\n\n    FundRequestMailer.send_request(nil, fund_request, false)\n  end\nend\n"
  },
  {
    "path": "lib/mailers/previews/learning_hours_mailer_preview.rb",
    "content": "require_relative \"../debug_preview_mailer\"\n\nclass LearningHoursMailerPreview < ActionMailer::Preview\n  def learning_hours_report_email\n    current_user = params.has_key?(:id) ? User.find_by(id: params[:id]) : User.first\n\n    if current_user.nil?\n      DebugPreviewMailer.invalid_user(\"user\")\n    else\n      LearningHoursMailer.learning_hours_report_email(current_user)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/mailers/previews/supervisor_mailer_preview.rb",
    "content": "# Preview all emails at http://localhost:3000/rails/mailers/supervisor_mailer\n# :nocov:\nrequire_relative \"../debug_preview_mailer\"\n\nclass SupervisorMailerPreview < ActionMailer::Preview\n  def account_setup\n    supervisor = params.has_key?(:id) ? Supervisor.find_by(id: params[:id]) : Supervisor.last\n    if supervisor.nil?\n      DebugPreviewMailer.invalid_user(\"supervisor\")\n    else\n      SupervisorMailer.account_setup(supervisor)\n    end\n  end\n\n  def weekly_digest_no_volunteers\n    supervisor = Supervisor.new(\n      display_name: \"Jane Smith\",\n      casa_org: CasaOrg.new(name: \"CASA of Awesome County\"),\n      volunteers: []\n    )\n    SupervisorMailer.weekly_digest(supervisor)\n  end\n\n  def weekly_digest_more_data\n    supervisor = Supervisor.new(\n      display_name: \"Jane Smith\",\n      casa_org: CasaOrg.new(name: \"CASA of Awesome County\"),\n      volunteers: [Volunteer.new(display_name: \"Anne Volunteerson\"), Volunteer.new(display_name: \"Betty McVolunteer\")]\n    )\n    SupervisorMailer.weekly_digest(supervisor)\n  end\n\n  def weekly_digest\n    supervisor = params.has_key?(:id) ? Supervisor.find_by(id: params[:id]) : Supervisor.last\n    if supervisor.nil?\n      DebugPreviewMailer.invalid_user(\"supervisor\")\n    else\n      SupervisorMailer.weekly_digest(supervisor)\n    end\n  end\n\n  def reimbursement_request_email\n    volunteer = params.has_key?(:volunteer_id) ? Volunteer.find_by(id: params[:volunteer_id]) : Volunteer.last\n    supervisor = params.has_key?(:supervisor_id) ? Supervisor.find_by(id: params[:supervisor_id]) : Supervisor.last\n    supervisor.receive_reimbursement_email = true\n    SupervisorMailer.reimbursement_request_email(volunteer, supervisor)\n  end\nend\n\n# :nocov:\n"
  },
  {
    "path": "lib/mailers/previews/volunteer_mailer_preview.rb",
    "content": "# Preview all emails at http://localhost:3000/rails/mailers/volunteer_mailer\n# :nocov:\nrequire_relative \"../debug_preview_mailer\"\nclass VolunteerMailerPreview < ActionMailer::Preview\n  def account_setup\n    volunteer = params.has_key?(:id) ? Volunteer.find_by(id: params[:id]) : Volunteer.last\n    if volunteer.nil?\n      DebugPreviewMailer.invalid_user(\"volunteer\")\n    else\n      VolunteerMailer.account_setup(volunteer)\n    end\n  end\n\n  def court_report_reminder\n    volunteer = params.has_key?(:id) ? Volunteer.find_by(id: params[:id]) : Volunteer.last\n    if volunteer.nil?\n      DebugPreviewMailer.invalid_user(\"volunteer\")\n    else\n      VolunteerMailer.court_report_reminder(volunteer, Date.today)\n    end\n  end\n\n  def case_contacts_reminder\n    volunteer = params.has_key?(:id) ? Volunteer.find_by(id: params[:id]) : Volunteer.last\n    if volunteer.nil?\n      DebugPreviewMailer.invalid_user(\"volunteer\")\n    else\n      VolunteerMailer.court_report_reminder(volunteer, true)\n    end\n  end\nend\n# :nocov:\n"
  },
  {
    "path": "lib/tasks/auto_annotate_models.rake",
    "content": "# NOTE: only doing this in development as some production environments (Heroku)\n# NOTE: are sensitive to local FS writes, and besides -- it's just not proper\n# NOTE: to have a dev-mode tool do its thing in production.\nif Rails.env.development?\n  require \"annotate\"\n  task :set_annotation_options do\n    # You can override any of these by setting an environment variable of the\n    # same name.\n    Annotate.set_defaults(\n      \"active_admin\" => \"false\",\n      \"additional_file_patterns\" => [],\n      \"routes\" => \"false\",\n      \"models\" => \"true\",\n      \"position_in_routes\" => \"bottom\",\n      \"position_in_class\" => \"bottom\",\n      \"position_in_test\" => \"bottom\",\n      \"position_in_fixture\" => \"bottom\",\n      \"position_in_factory\" => \"bottom\",\n      \"position_in_serializer\" => \"bottom\",\n      \"show_foreign_keys\" => \"true\",\n      \"show_complete_foreign_keys\" => \"false\",\n      \"show_indexes\" => \"true\",\n      \"simple_indexes\" => \"false\",\n      \"model_dir\" => \"app/models\",\n      \"root_dir\" => \"\",\n      \"include_version\" => \"false\",\n      \"require\" => \"\",\n      \"exclude_tests\" => \"true\",\n      \"exclude_fixtures\" => \"true\",\n      \"exclude_factories\" => \"true\",\n      \"exclude_serializers\" => \"true\",\n      \"exclude_scaffolds\" => \"true\",\n      \"exclude_controllers\" => \"true\",\n      \"exclude_helpers\" => \"true\",\n      \"exclude_sti_subclasses\" => \"false\",\n      \"ignore_model_sub_dir\" => \"false\",\n      \"ignore_columns\" => nil,\n      \"ignore_routes\" => nil,\n      \"ignore_unknown_models\" => \"false\",\n      \"hide_limit_column_types\" => \"integer,bigint,boolean\",\n      \"hide_default_column_types\" => \"json,jsonb,hstore\",\n      \"skip_on_db_migrate\" => \"false\",\n      \"format_bare\" => \"true\",\n      \"format_rdoc\" => \"false\",\n      \"format_yard\" => \"false\",\n      \"format_markdown\" => \"false\",\n      \"sort\" => \"false\",\n      \"force\" => \"false\",\n      \"frozen\" => \"false\",\n      \"classified_sort\" => \"true\",\n      \"trace\" => \"false\",\n      \"wrapper_open\" => nil,\n      \"wrapper_close\" => nil,\n      \"with_comment\" => \"true\"\n    )\n  end\n\n  Annotate.load_tasks\nend\n"
  },
  {
    "path": "lib/tasks/case_contact_types_reminder.rb",
    "content": "class CaseContactTypesReminder\n  NEW_CASE_CONTACT_PAGE_PATH = Rails.application.credentials[:BASE_URL]\n  FIRST_MESSAGE = \"It's been 60 days or more since you've reached out to these members of your youth's network:\\n\"\n  THIRD_MESSAGE = \"If you have made contact with them in the past 60 days, remember to log it: \"\n\n  def send!\n    if NEW_CASE_CONTACT_PAGE_PATH.blank?\n      raise \"NEW_CASE_CONTACT_PAGE_PATH environment variable not defined\"\n    end\n    responses = []\n    eligible_volunteers = Volunteer.where(receive_sms_notifications: true)\n      .where.not(phone_number: nil)\n      .select { |v| !last_reminder_within_quarter(v) }\n\n    eligible_volunteers.each do |volunteer|\n      uncontacted_case_contact_type_names = uncontacted_case_contact_types(volunteer)\n      if uncontacted_case_contact_type_names.count > 0\n        responses.push(\n          {\n            volunteer: volunteer,\n            messages: send_sms_messages(volunteer, uncontacted_case_contact_type_names)\n          }\n        )\n        UserReminderTime.find_by(user_id: volunteer.id)&.update_attributes(case_contact_types: DateTime.now)\n      end\n    end\n\n    responses\n  end\n\n  private\n\n  def uncontacted_case_contact_types(volunteer)\n    contacted_types = volunteer.case_contacts.where(\"occurred_at > ?\", 2.months.ago).joins(:contact_types).pluck(:name)\n    ContactType.all.pluck(:name).uniq - contacted_types\n  end\n\n  def send_sms_messages(volunteer, uncontacted_case_contact_type_names)\n    volunteer_casa_org = volunteer.casa_org\n    if !valid_casa_twilio_creds(volunteer_casa_org)\n      return\n    end\n    twilio_service = TwilioService.new(volunteer_casa_org)\n    sms_params = {\n      From: volunteer_casa_org.twilio_phone_number,\n      Body: nil,\n      To: volunteer.phone_number\n    }\n\n    messages = [\n      FIRST_MESSAGE,\n      uncontacted_case_contact_type_names.map { |name| \"• #{name}\" }.join(\"\\n\"),\n      THIRD_MESSAGE + new_case_contact_page_short_link\n    ]\n\n    responses = []\n    messages.each do |content|\n      sms_params[:Body] = content\n      responses.push(twilio_service.send_sms(sms_params))\n    end\n\n    responses\n  end\n\n  def valid_casa_twilio_creds(casa_org)\n    casa_org.twilio_enabled? && casa_org.twilio_phone_number? && casa_org.twilio_account_sid? && casa_org.twilio_api_key_sid? && casa_org.twilio_api_key_secret?\n  end\n\n  def last_reminder_within_quarter(volunteer)\n    reminder = UserReminderTime.find_by(user_id: volunteer.id)\n\n    if reminder&.case_contact_types\n      return reminder.case_contact_types > 3.months.ago\n    end\n\n    false\n  end\n\n  def new_case_contact_page_short_link\n    short_url_service = ShortUrlService.new\n    short_url_service.create_short_url(NEW_CASE_CONTACT_PAGE_PATH + \"/case_contacts/new\")\n    short_url_service.short_url\n  end\nend\n"
  },
  {
    "path": "lib/tasks/check_controller_tests.rake",
    "content": "namespace :test do\n  desc \"Check for controller tests in spec/controllers and fail if any are found\"\n  task check_controller_tests: :environment do\n    controller_tests = Dir.glob(\"spec/controllers/**/*_spec.rb\")\n    if controller_tests.any?\n      puts \"controller tests should be in spec/requests\"\n      exit 1\n    else\n      puts \"No controller tests found in spec/controllers\"\n    end\n  end\nend\n"
  },
  {
    "path": "lib/tasks/court_report_due_reminder.rake",
    "content": "desc \"Send an email to volunteers when their court report is due in 1 week, run by heroku scheduler.\"\ntask court_report_due_reminder: :environment do\n  Volunteer.send_court_report_reminder\nend\n"
  },
  {
    "path": "lib/tasks/data_post_processors/case_contact_populator.rb",
    "content": "module CaseContactPopulator\n  def self.populate\n    CaseContact.find_each do |case_contact|\n      # Get rid of drafts\n      unless case_contact.casa_case\n        case_contact.destroy\n      end\n      casa_org = case_contact.casa_case.casa_org\n      case_contact.contact_types&.each do |contact_type|\n        ct_name = contact_type.name\n        cts_by_name = ContactType.where(name: ct_name)\n        ct = cts_by_name.find { |ct| ct.contact_type_group.casa_org == casa_org }\n        unless ct\n          if cts_by_name.any?\n            ctg_name = cts_by_name.first.contact_type_group.name\n            org_ctg = ContactTypeGroup.find_by(casa_org: casa_org, name: ctg_name)\n            if org_ctg\n              ContactType.find_or_create_by!(contact_type_group: org_ctg, name: ct_name)\n            else\n              ContactTypeGroup.find_or_create_by!(casa_org: casa_org, name: ctg_name)\n            end\n          else\n            new_ctg = ContactTypeGroup.find_or_create_by!(casa_org: casa_org, name: \"#{ctg_name} Group\")\n            ContactType.find_or_create_by!(contact_type_group: new_ctg, name: ct_name)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/tasks/data_post_processors/contact_topic_populator.rb",
    "content": "module ContactTopicPopulator\n  def self.populate\n    CasaOrg.all.each do |casa_org|\n      ContactTopic.generate_for_org!(casa_org)\n\n      casa_org.contact_topics.each do |topic|\n        org_case_contacts = CaseContact.joins(:casa_case).where(\"casa_case.casa_org_id\": casa_org.id)\n        org_case_contacts.each do |contact|\n          FactoryBot.create(:contact_topic_answer, case_contact: contact, contact_topic: topic)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/tasks/data_post_processors/contact_type_populator.rb",
    "content": "module ContactTypePopulator\n  def self.populate\n    CasaOrg.all.each do |casa_org|\n      ContactTypeGroup.generate_for_org!(casa_org)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/tasks/data_post_processors/sms_notification_event_populator.rb",
    "content": "module SmsNotificationEventPopulator\n  SMS_NOTIFICATION_EVENTS = {\n    Volunteer: [\"CASA case youth has birthday\"],\n    Supervisor: [\"Volunteer made case contact\", \"Volunteer edited case (case details, court order, court dates)\"],\n    CasaAdmin: [\"New entry in reimbursement queue\"]\n  }.freeze\n\n  def self.populate\n    SMS_NOTIFICATION_EVENTS.each do |user_type, event_names|\n      event_names.each do |event_name|\n        SmsNotificationEvent.find_or_create_by!(name: event_name, user_type: user_type)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/tasks/deployment/20200913155303_populate_missing_display_names.rake",
    "content": "namespace :after_party do\n  desc \"Deployment task: populate_missing_display_names\"\n  task populate_missing_display_names: :environment do\n    puts \"Running deploy task 'populate_missing_display_names'\" unless Rails.env.test?\n\n    User.find_each do |user|\n      if user.display_name.blank?\n        user.display_name = Faker::Name.name\n        user.save(validate: false)\n      end\n    end\n    # Update task as completed.  If you remove the line below, the task will\n    # run with every deploy (or every time you call after_party:run).\n    AfterParty::TaskRecord\n      .create version: AfterParty::TaskRecorder.new(__FILE__).timestamp\n  end\nend\n"
  },
  {
    "path": "lib/tasks/deployment/20201005203140_populate_contact_type_groups_and_contact_types.rake",
    "content": "require_relative \"../data_post_processors/contact_type_populator\"\n\nnamespace :after_party do\n  desc \"Deployment task: populate_contact_type_groups_and_contact_types\"\n  task populate_contact_type_groups_and_contact_types: :environment do\n    puts \"Running deploy task 'populate_contact_type_groups_and_contact_types'\" unless Rails.env.test?\n    ContactTypePopulator.populate\n    # Update task as completed.  If you remove the line below, the task will\n    # run with every deploy (or every time you call after_party:run).\n    AfterParty::TaskRecord\n      .create version: AfterParty::TaskRecorder.new(__FILE__).timestamp\n  end\nend\n"
  },
  {
    "path": "lib/tasks/deployment/20201005203405_populate_case_contact_contact_type.rake",
    "content": "require_relative '../data_post_processors/case_contact_populator'\n\nnamespace :after_party do\n  desc \"Deployment task: populate_case_contact_contact_type\"\n  task populate_case_contact_contact_type: :environment do\n    puts \"Running deploy task 'populate_case_contact_contact_type'\" unless Rails.env.test?\n\n    CaseContactPopulator.populate\n\n    # Update task as completed.  If you remove the line below, the task will\n    # run with every deploy (or every time you call after_party:run).\n    AfterParty::TaskRecord\n      .create version: AfterParty::TaskRecorder.new(__FILE__).timestamp\n  end\nend\n"
  },
  {
    "path": "lib/tasks/deployment/20201210004047_populate_emancipation_data.rake",
    "content": "namespace :after_party do\n  desc \"Deployment task: populate_emancipation_data\"\n  task populate_emancipation_data: :environment do\n    puts \"Running deploy task 'populate_emancipation_data'\"\n\n    load(Rails.root.join(\"db\", \"seeds\", \"emancipation_data.rb\"))\n\n    # Update task as completed.  If you remove the line below, the task will\n    # run with every deploy (or every time you call after_party:run).\n    AfterParty::TaskRecord\n      .create version: AfterParty::TaskRecorder.new(__FILE__).timestamp\n  end\nend\n"
  },
  {
    "path": "lib/tasks/deployment/20201215183231_emancipation_category_options.rake",
    "content": "namespace :after_party do\n  desc \"Deployment task: adds_new_emancipation_category_options\"\n  task emancipation_category_options: :environment do\n    puts \"Running deploy task 'emancipation_category_options'\"\n\n    category_employment = EmancipationCategory.where(name: \"Youth is employed.\")\n      .first_or_create(mutually_exclusive: true)\n    category_employment.add_option(\"Not employed\")\n\n    category_continuing_education = EmancipationCategory.where(name: \"Youth is attending an educational or vocational program.\")\n      .first_or_create(mutually_exclusive: true)\n    category_continuing_education.add_option(\"Not attending\")\n\n    category_high_school_diploma = EmancipationCategory.where(name: \"Youth has a high school diploma or equivalency.\")\n      .first_or_create(mutually_exclusive: true)\n    category_high_school_diploma.add_option(\"No\")\n\n    # Update task as completed.  If you remove the line below, the task will\n    # run with every deploy (or every time you call after_party:run).\n    AfterParty::TaskRecord\n      .create version: AfterParty::TaskRecorder.new(__FILE__).timestamp\n  end\nend\n"
  },
  {
    "path": "lib/tasks/deployment/20210415012736_task_name.rake",
    "content": "namespace :after_party do\n  desc \"Deployment task: set_custom_court_docs_for_orgs\"\n  task task_name: :environment do\n    CasaOrg.where(name: \"Prince George CASA\").map do |casa_org|\n      casa_org.court_report_template.attach(io: File.new(Rails.root.join(\"app\", \"documents\", \"templates\", \"prince_george_report_template.docx\")), filename: \"prince_george_report_template.docx\")\n    end\n\n    CasaOrg.where(name: \"Voices for Children Montgomery\").map do |casa_org|\n      casa_org.court_report_template.attach(io: File.new(Rails.root.join(\"app\", \"documents\", \"templates\", \"montgomery_report_template.docx\")), filename: \"montgomery_report_template.docx\")\n    end\n\n    AfterParty::TaskRecord\n      .create version: AfterParty::TaskRecorder.new(__FILE__).timestamp\n  end\nend\n"
  },
  {
    "path": "lib/tasks/deployment/20210507200644_fix_transition_aged_youth_for_nil_birth_month_year_youth.rake",
    "content": "namespace :after_party do\n  desc \"Deployment task: fix_transition_aged_youth_for_nil_birth_month_year_youth\"\n  task fix_transition_aged_youth_for_nil_birth_month_year_youth: :environment do\n    unless Rails.env.production?\n      puts \"Running deploy task 'fix_transition_aged_youth_for_nil_birth_month_year_youth'\"\n\n      CasaCase\n        .where(transition_aged_youth: true)\n        .where(birth_month_year_youth: nil)\n        .update(transition_aged_youth: false)\n    end\n    # Update task as completed.  If you remove the line below, the task will\n    # run with every deploy (or every time you call after_party:run).\n    AfterParty::TaskRecord\n      .create version: AfterParty::TaskRecorder.new(__FILE__).timestamp\n  end\nend\n"
  },
  {
    "path": "lib/tasks/deployment/20210811052058_update_montgomery_court_report_template.rake",
    "content": "namespace :after_party do\n  desc \"Deployment task: update_montgomery_court_report_template\"\n  task update_montgomery_court_report_template: :environment do\n    CasaOrg.where(name: \"Voices for Children Montgomery\").map do |casa_org|\n      casa_org.court_report_template.attach(io: File.new(Rails.root.join(\"app\", \"documents\", \"templates\", \"montgomery_report_template.docx\")), filename: \"montgomery_report_template.docx\")\n    end\n\n    AfterParty::TaskRecord\n      .create version: AfterParty::TaskRecorder.new(__FILE__).timestamp\n  end\nend\n"
  },
  {
    "path": "lib/tasks/deployment/20210925143244_populate_slugs_for_orgs_and_cases.rake",
    "content": "namespace :after_party do\n  desc \"Deployment task: Generate slugs for existing records so they can be accessed with their new slug based routes\"\n  task populate_slugs_for_orgs_and_cases: :environment do\n    puts \"Running deploy task 'populate_slugs_for_orgs_and_cases'\"\n    puts \"task deleted because it uses the now-uncallable method set_slug\"\n\n    # Put your task implementation HERE.\n    # CasaOrg.all.each do |org|\n    #   org.set_slug\n    #   org.save\n    # end\n    #\n    # CasaCase.all.each do |casa_case|\n    #   casa_case.set_slug\n    #   casa_case.save\n    # end\n\n    # Update task as completed.  If you remove the line below, the task will\n    # run with every deploy (or every time you call after_party:run).\n    AfterParty::TaskRecord\n      .create version: AfterParty::TaskRecorder.new(__FILE__).timestamp\n  end\nend\n"
  },
  {
    "path": "lib/tasks/deployment/20220409174149_enable_feature_flag_prince_george_fund_request.rake",
    "content": "# frozen_string_literal: true\n\nnamespace :after_party do\n  desc \"Deployment task: enable_feature_flag_prince_george_fund_request.rake\"\n  task enable_feature_flag_prince_george_fund_request: :environment do\n    puts \"Running deploy task 'enable_feature_flag_prince_george_fund_request'\"\n\n    casa_org = CasaOrg.find_by(name: \"Prince George CASA\")\n    casa_org&.update!(show_fund_request: true)\n\n    # Update task as completed.  If you remove the line below, the task will\n    # run with every deploy (or every time you call after_party:run).\n    AfterParty::TaskRecord\n      .create version: AfterParty::TaskRecorder.new(__FILE__).timestamp\n  end\nend\n"
  },
  {
    "path": "lib/tasks/deployment/20220521015108_populate_sms_notification_events.rake",
    "content": "require_relative \"../data_post_processors/sms_notification_event_populator\"\n\nnamespace :after_party do\n  desc \"Deployment task: populate_sms_notification_events\"\n  task populate_sms_notification_events: :environment do\n    puts \"Running deploy task 'populate_sms_notification_events'\"\n\n    SmsNotificationEventPopulator.populate\n\n    # Update task as completed.  If you remove the line below, the task will\n    # run with every deploy (or every time you call after_party:run).\n    AfterParty::TaskRecord\n      .create version: AfterParty::TaskRecorder.new(__FILE__).timestamp\n  end\nend\n"
  },
  {
    "path": "lib/tasks/deployment/20220615020226_create_initial_patch_note_types.rake",
    "content": "namespace :after_party do\n  desc \"Deployment task: create_initial_patch_note_types\"\n  task create_initial_patch_note_types: :environment do\n    puts \"Running deploy task 'create_initial_patch_note_types'\"\n\n    load(Rails.root.join(\"db\", \"seeds\", \"patch_note_type_data.rb\"))\n\n    # Update task as completed.  If you remove the line below, the task will\n    # run with every deploy (or every time you call after_party:run).\n    AfterParty::TaskRecord\n      .create version: AfterParty::TaskRecorder.new(__FILE__).timestamp\n  end\nend\n"
  },
  {
    "path": "lib/tasks/deployment/20220629015841_update_montgomery_court_report_template.rake",
    "content": "namespace :after_party do\n  desc \"Deployment task: update_montgomery_court_report_template\"\n  task update_montgomery_court_report_template: :environment do\n    CasaOrg.where(name: \"Voices for Children Montgomery\").map do |casa_org|\n      casa_org.court_report_template.attach(io: File.new(Rails.root.join(\"app\", \"documents\", \"templates\", \"montgomery_report_template_062022.docx\")), filename: \"montgomery_report_template_062022.docx\")\n    end\n    AfterParty::TaskRecord\n      .create version: AfterParty::TaskRecorder.new(__FILE__).timestamp\n  end\nend\n"
  },
  {
    "path": "lib/tasks/deployment/20220902180609_populate_languages.rake",
    "content": "namespace :after_party do\n  desc \"Deployment task: Populate languages table\"\n  task populate_languages: :environment do\n    puts \"Running deploy task 'populate_languages'\"\n\n    # Put your task implementation HERE.\n    CasaOrg.all.each do |casa_org|\n      create_languages(casa_org)\n    end\n    # Update task as completed.  If you remove the line below, the task will\n    # run with every deploy (or every time you call after_party:run).\n    AfterParty::TaskRecord\n      .create version: AfterParty::TaskRecorder.new(__FILE__).timestamp\n  end\n\n  def create_languages(casa_org)\n    create_language(\"Spanish\", casa_org)\n    create_language(\"Vietnamese\", casa_org)\n    create_language(\"French\", casa_org)\n    create_language(\"Chinese Cantonese\", casa_org)\n    create_language(\"ASL\", casa_org)\n    create_language(\"Other\", casa_org)\n  end\n\n  def create_language(name, casa_org)\n    Language.find_or_create_by!(name: name, casa_org: casa_org)\n  end\nend\n"
  },
  {
    "path": "lib/tasks/deployment/20221003224029_migrate_court_report_due_date_to_court_dates.rake",
    "content": "# frozen_string_literal: true\n\nnamespace :after_party do\n  desc \"Deployment task: migrate_court_report_due_date_to_court_dates\"\n  task migrate_court_report_due_date_to_court_dates: :environment do\n    puts \"task deleted because it uses the removed field casa_case.court_report_due_date\"\n    AfterParty::TaskRecord\n      .create version: AfterParty::TaskRecorder.new(__FILE__).timestamp\n  end\nend\n"
  },
  {
    "path": "lib/tasks/deployment/20221009032756_add_medium_type_to_howard_court_report_template.rake",
    "content": "namespace :after_party do\n  desc \"Deployment task: add_medium_type_to_howard_court_report_template\"\n  task add_medium_type_to_howard_court_report_template: :environment do\n    puts \"Running deploy task 'add_medium_type_to_howard_court_report_template'\"\n\n    CasaOrg.where(name: \"Howard County CASA\").map do |casa_org|\n      casa_org.court_report_template.attach(io: File.new(Rails.root.join(\"app\", \"documents\", \"templates\", \"howard_county_report_template.docx\")), filename: \"howard_county_report_template.docx\")\n    end\n\n    AfterParty::TaskRecord\n      .create version: AfterParty::TaskRecorder.new(__FILE__).timestamp\n  end\nend\n"
  },
  {
    "path": "lib/tasks/deployment/20221104005957_create_initial_patch_note_groups.rake",
    "content": "namespace :after_party do\n  desc \"Deployment task: create_initial_patch_note_groups\"\n  task create_initial_patch_note_groups: :environment do\n    puts \"Running deploy task 'create_initial_patch_note_groups'\"\n\n    PatchNote.destroy_all\n    PatchNoteGroup.destroy_all\n    load(Rails.root.join(\"db\", \"seeds\", \"patch_note_group_data.rb\"))\n\n    # Update task as completed.  If you remove the line below, the task will\n    # run with every deploy (or every time you call after_party:run).\n    AfterParty::TaskRecord\n      .create version: AfterParty::TaskRecorder.new(__FILE__).timestamp\n  end\nend\n"
  },
  {
    "path": "lib/tasks/deployment/20230110000515_update_howard_court_report_template.rake",
    "content": "namespace :after_party do\n  desc \"Deployment task: update_howard_court_report_template\"\n  task update_howard_court_report_template: :environment do\n    puts \"Running deploy task 'update_howard_court_report_template'\"\n\n    casa_org = CasaOrg.find_by(name: \"Howard County CASA\")\n    if casa_org\n      casa_org.court_report_template.attach(io: File.new(Rails.root.join(\"app\", \"documents\", \"templates\", \"howard_county_report_template.docx\")), filename: \"howard_county_report_template.docx\")\n    else\n      Bugsnag.notify(\"No Howard County CASA found for rake task update_howard_court_report_template\")\n    end\n\n    # Update task as completed.  If you remove the line below, the task will\n    # run with every deploy (or every time you call after_party:run).\n    AfterParty::TaskRecord\n      .create version: AfterParty::TaskRecorder.new(__FILE__).timestamp\n  end\nend\n"
  },
  {
    "path": "lib/tasks/deployment/20230114184424_update_howard_court_report_temp2.rake",
    "content": "namespace :after_party do\n  desc \"Deployment task: update_howard_court_report_temp2\"\n  task update_howard_court_report_temp2: :environment do\n    puts \"Running deploy task 'update_howard_court_report_template'\"\n\n    casa_org = CasaOrg.find_by(name: \"Howard County CASA\")\n    if casa_org\n      casa_org.court_report_template.attach(io: File.new(Rails.root.join(\"app\", \"documents\", \"templates\", \"howard_county_report_template.docx\")), filename: \"howard_county_report_template.docx\")\n    else\n      Bugsnag.notify(\"No Howard County CASA found for rake task update_howard_court_report_template\")\n    end\n\n    # Update task as completed.  If you remove the line below, the task will\n    # run with every deploy (or every time you call after_party:run).\n    AfterParty::TaskRecord\n      .create version: AfterParty::TaskRecorder.new(__FILE__).timestamp\n  end\nend\n"
  },
  {
    "path": "lib/tasks/deployment/20230114184852_update_howard_court_report_temp3.rake",
    "content": "namespace :after_party do\n  desc \"Deployment task: update_howard_court_report_temp3\"\n  task update_howard_court_report_temp3: :environment do\n    puts \"Running deploy task 'update_howard_court_report_template' 3\"\n\n    casa_org = CasaOrg.find_by(name: \"Howard County CASA\")\n    if casa_org\n      casa_org.court_report_template.attach(io: File.new(Rails.root.join(\"app\", \"documents\", \"templates\", \"howard_county_report_template.docx\")), filename: \"howard_county_report_template.docx\")\n    else\n      Bugsnag.notify(\"No Howard County CASA found for rake task update_howard_court_report_template\")\n    end\n\n    # Update task as completed.  If you remove the line below, the task will\n    # run with every deploy (or every time you call after_party:run).\n    AfterParty::TaskRecord\n      .create version: AfterParty::TaskRecorder.new(__FILE__).timestamp\n  end\nend\n"
  },
  {
    "path": "lib/tasks/deployment/20230208031806_update_howard_court_report_temp4.rake",
    "content": "namespace :after_party do\n  desc \"Deployment task: update_howard_court_report_temp4\"\n  task update_howard_court_report_temp4: :environment do\n    puts \"Running deploy task 'update_howard_court_report_template' 3\"\n\n    casa_org = CasaOrg.find_by(name: \"Howard County CASA\")\n    if casa_org\n      casa_org.court_report_template.attach(io: File.new(Rails.root.join(\"app\", \"documents\", \"templates\", \"howard_county_report_template.docx\")), filename: \"howard_county_report_template.docx\")\n    else\n      Bugsnag.notify(\"No Howard County CASA found for rake task update_howard_court_report_template\")\n    end\n\n    # Update task as completed.  If you remove the line below, the task will\n    # run with every deploy (or every time you call after_party:run).\n    AfterParty::TaskRecord\n      .create version: AfterParty::TaskRecorder.new(__FILE__).timestamp\n  end\nend\n"
  },
  {
    "path": "lib/tasks/deployment/20230409222823_add_confrimation_to_existing_users.rake",
    "content": "namespace :after_party do\n  desc \"Deployment task: add_confrimation_to_existing_users\"\n  task add_confrimation_to_existing_users: :environment do\n    puts \"Running deploy task 'add_confrimation_to_existing_users'\"\n\n    User.update_all confirmed_at: DateTime.now\n\n    # Update task as completed.  If you remove the line below, the task will\n    # run with every deploy (or every time you call after_party:run).\n    AfterParty::TaskRecord\n      .create version: AfterParty::TaskRecorder.new(__FILE__).timestamp\n  end\nend\n"
  },
  {
    "path": "lib/tasks/deployment/20230419224330_backfill_user_prefernce_sets.rake",
    "content": "namespace :after_party do\n  desc \"Deployment task: This will create a PreferenceSet for all users that are missing one\"\n  task backfill_user_prefernce_sets: :environment do\n    puts \"Running deploy task 'backfill_user_prefernce_sets'\"\n\n    # Put your task implementation HERE.\n    User.includes(:preference_set).where(preference_sets: {id: nil}).find_in_batches(batch_size: 500) do |users|\n      # NOTE: This should ideally be run in a background job.\n      users.each(&:create_preference_set)\n    end\n\n    # Update task as completed.  If you remove the line below, the task will\n    # run with every deploy (or every time you call after_party:run).\n    AfterParty::TaskRecord\n      .create version: AfterParty::TaskRecorder.new(__FILE__).timestamp\n  end\nend\n"
  },
  {
    "path": "lib/tasks/deployment/20230720000759_update_howard_court_report_fix.rake",
    "content": "namespace :after_party do\n  desc \"Deployment task: update_howard_court_report_fix\"\n  task update_howard_court_report_fix: :environment do\n    puts \"Running deploy task 'update_howard_court_report_fix'\"\n\n    casa_org = CasaOrg.find_by(name: \"Howard County CASA\")\n    if casa_org\n      casa_org.court_report_template.attach(io: File.new(Rails.root.join(\"app\", \"documents\", \"templates\", \"howard_county_report_template.docx\")), filename: \"howard_county_report_template.docx\")\n    else\n      Bugsnag.notify(\"No Howard County CASA found for rake task update_howard_court_report_template\")\n    end\n\n    # Update task as completed.  If you remove the line below, the task will\n    # run with every deploy (or every time you call after_party:run).\n    AfterParty::TaskRecord\n      .create version: AfterParty::TaskRecorder.new(__FILE__).timestamp\n  end\nend\n"
  },
  {
    "path": "lib/tasks/deployment/20231125151610_set_case_contacts_as_active.rake",
    "content": "namespace :after_party do\n  desc \"Deployment task: set_case_contacts_as_active\"\n  task set_case_contacts_as_active: :environment do\n    puts \"Running deploy task 'set_case_contacts_as_active'\"\n\n    CaseContact.where.not(casa_case_id: nil).each do |cc|\n      cc.additional_expenses.each do |additional_expense|\n        additional_expense.update(other_expenses_describe: \"No description given\") unless additional_expense.other_expenses_describe\n      end\n      cc.update(status: \"active\", draft_case_ids: [cc.casa_case_id])\n    end\n\n    # Update task as completed.  If you remove the line below, the task will\n    # run with every deploy (or every time you call after_party:run).\n    AfterParty::TaskRecord\n      .create version: AfterParty::TaskRecorder.new(__FILE__).timestamp\n  end\nend\n"
  },
  {
    "path": "lib/tasks/deployment/20240416171009_backfill_followup_followupable_id_and_type_from_case_contact_id.rake",
    "content": "namespace :after_party do\n  desc \"Deployment task: Deployment task: backfill Followup polymorephic columns (followupable_id and followupable_type) from case_contact_id\"\n  task backfill_followup_followupable_id_and_type_from_case_contact_id: :environment do\n    puts \"Running deploy task 'backfill_followup_followupable_id_and_type_from_case_contact_id'\"\n    BackfillFollowupableService.new.fill_followup_id_and_type\n\n    # Update task as completed.  If you remove the line below, the task will\n    # run with every deploy (or every time you call after_party:run).\n    AfterParty::TaskRecord\n      .create version: AfterParty::TaskRecorder.new(__FILE__).timestamp\n  end\nend\n"
  },
  {
    "path": "lib/tasks/deployment/20240420230126_update_org_templates.rake",
    "content": "namespace :after_party do\n  desc \"Deployment task: Updates_production_casa_orgs_with_new_templates\"\n  task update_org_templates: :environment do\n    puts \"Running deploy task 'update_org_templates'\"\n\n    mapping = {\n      \"Howard County CASA\" => \"howard_county_report_template.docx\",\n      \"Voices for Children Montgomery\" => \"montgomery_report_template.docx\",\n      \"Prince George CASA\" => \"prince_george_report_template.docx\"\n    }\n\n    mapping.each do |casa_org_name, template_file_name|\n      casa_org = CasaOrg.find_by(name: casa_org_name)\n      if casa_org\n        casa_org.court_report_template.attach(\n          io: File.new(Rails.root.join(\"app\", \"documents\", \"templates\", template_file_name)),\n          filename: template_file_name\n        )\n      else\n        Bugsnag.notify(\"No #{casa_org_name} found for rake task update_org_templates\")\n      end\n    end\n\n    # Update task as completed.  If you remove the line below, the task will\n    # run with every deploy (or every time you call after_party:run).\n    AfterParty::TaskRecord\n      .create version: AfterParty::TaskRecorder.new(__FILE__).timestamp\n  end\nend\n"
  },
  {
    "path": "lib/tasks/deployment/20240604121427_migrate_notifications.rake",
    "content": "# Temporarily define the Notification model to access the old table\nclass Notification < ActiveRecord::Base\n  self.inheritance_column = nil\nend\n\nnamespace :after_party do\n  desc \"Deployment task: Migrates existing notifications to new tables for noticed gem v2.x\"\n  task migrate_notifications: :environment do\n    puts \"Running deploy task 'migrate_notifications'\"\n\n    # Migrate each record to the new tables\n    Notification.find_each do |notification|\n      attributes = notification.attributes.slice(\"type\", \"created_at\", \"updated_at\").with_indifferent_access\n\n      attributes[:type] = attributes[:type].sub(\"Notification\", \"Notifier\")\n\n      attributes[:params] = Noticed::Coder.load(notification.params)\n      attributes[:params] = {} if attributes[:params].try(:has_key?, \"noticed_error\") # Skip invalid records\n\n      attributes[:notifications_attributes] = [{\n        type: \"#{attributes[:type]}::Notification\",\n        recipient_type: notification.recipient_type,\n        recipient_id: notification.recipient_id,\n        read_at: notification.read_at,\n        seen_at: notification.read_at,\n        created_at: notification.created_at,\n        updated_at: notification.updated_at\n      }]\n      Noticed::Event.create!(attributes)\n    end\n\n    # Update task as completed.  If you remove the line below, the task will\n    # run with every deploy (or every time you call after_party:run).\n    AfterParty::TaskRecord\n      .create version: AfterParty::TaskRecorder.new(__FILE__).timestamp\n  end\nend\n"
  },
  {
    "path": "lib/tasks/deployment/20240720232939_backfill_case_contact_started_metadata.rake",
    "content": "namespace :after_party do\n  desc \"Deployment task: set metadata for status started to every CaseContact that had status metadata\"\n  task backfill_case_contact_started_metadata: :environment do\n    puts \"Running deploy task 'backfill_case_contact_started_metadata'\"\n\n    # Put your task implementation HERE.\n    Deployment::BackfillCaseContactStartedMetadataService.new.backfill_metadata\n\n    # Update task as completed.  If you remove the line below, the task will\n    # run with every deploy (or every time you call after_party:run).\n    AfterParty::TaskRecord\n      .create version: AfterParty::TaskRecorder.new(__FILE__).timestamp\n  end\nend\n"
  },
  {
    "path": "lib/tasks/deployment/20250226015042_populate_new_api_and_refresh_token.rake",
    "content": "namespace :after_party do\n  desc \"Deployment task: populate_new_api_and_refresh_token\"\n  task populate_new_api_and_refresh_token: :environment do\n    puts \"Running deploy task 'populate_new_api_and_refresh_token'\"\n\n    # Put your task implementation HERE.\n    User.find_each do |user|\n      user.update(receive_sms_notifications: false) if user.phone_number.blank?\n      user.api_credential || user.create_api_credential\n    end\n\n    # Update task as completed.  If you remove the line below, the task will\n    # run with every deploy (or every time you call after_party:run).\n    AfterParty::TaskRecord\n      .create version: AfterParty::TaskRecorder.new(__FILE__).timestamp\n  end\nend\n"
  },
  {
    "path": "lib/tasks/deployment/99991023145114_store_deploy_time.rake",
    "content": "namespace :after_party do\n  desc \"Deployment task: stores_the_time_of_the_latest_deploy_as_a_file\"\n  task store_deploy_time: :environment do\n    puts \"Running deploy task 'store_deploy_time'\" unless Rails.env.test?\n    pending_files = AfterParty::TaskRecorder.pending_files\n\n    down_tasks = pending_files.reject { |item| item.task_name == \"store_deploy_time\" }\n    if down_tasks.empty?\n      Health.instance.update_attribute(:latest_deploy_time, Time.now)\n    else\n      puts(\"failed tasks found, latest_deploy_time will not be updated!\")\n    end\n  end\nend\n"
  },
  {
    "path": "lib/tasks/development/notifications.rake",
    "content": "desc \"Create a emancipation_checklist_reminder_notifier notification\"\ntask emancipation_checklist_reminder_notifier: :environment do\n  CasaCase.birthday_next_month.each do |casa_case|\n    EmancipationChecklistReminderNotifier\n      .with(casa_case: casa_case)\n      .deliver(Volunteer.find_by(id: casa_case.case_assignments.first.volunteer_id))\n  end\nend\n\ndesc \"Create a followup_notifier notification\"\ntask followup_notifier: :environment do\n  followup = Followup.all.first\n  deliver_from = User.all.to_a.last\n\n  FollowupNotifier\n    .with(followup: followup, created_by: deliver_from)\n    .deliver(User.find_by(id: 1))\nend\n\ndesc \"Create a reimbursement_complete_notifier notification\"\ntask reimbursement_complete_notifier: :environment do\n  ReimbursementCompleteNotifier\n    .with(case_contact: CaseContact.all.first)\n    .deliver(User.find_by(id: 1))\nend\n"
  },
  {
    "path": "lib/tasks/emancipation_checklist_reminder.rake",
    "content": "desc \"Create a notification for volunteers with transition aged youth to utilize the Emancipation Checklist, scheduled once per month in Heroku Scheduler\"\ntask emancipation_checklist_reminder: :environment do\n  if Time.now.utc.to_date.day == 1\n    EmancipationChecklistReminderService.new.send_reminders\n  end\nend\n"
  },
  {
    "path": "lib/tasks/example_recurring_task.rb",
    "content": "class ExampleRecurringTask\n  include Delayed::RecurringJob\n  run_every 1.day\n  run_at \"11:00am\"\n  timezone \"US/Pacific\"\n\n  def perform\n    Bugsnag.notify(\"This is just ExampleRecurringTask saying hi in #{Rails.env}\")\n  end\nend\n"
  },
  {
    "path": "lib/tasks/lint_factory_bot.rake",
    "content": "namespace :factory_bot do\n  desc \"Verify that all FactoryBot factories are valid\"\n  task lint: :environment do\n    if Rails.env.test?\n      puts \"linting factory_bot factories for being rails valid objects\"\n      conn = ActiveRecord::Base.connection\n      invalid_factories = [\n        # * casa_admin+with_casa_cases - Validation failed: Volunteer Case assignee must be an active volunteer (ActiveRecord::RecordInvalid)\n        # * casa_admin+with_case_contact - Validation failed: Volunteer Case assignee must be an active volunteer (ActiveRecord::RecordInvalid)\n        # * casa_admin+with_case_contact_wants_driving_reimbursement - Validation failed: Volunteer Case assignee must be an active volunteer (ActiveRecord::RecordInvalid)\n        # * casa_admin+with_casa_cases - Validation failed: Volunteer Case assignee must be an active volunteer (ActiveRecord::RecordInvalid)\n        # * casa_admin+with_single_case - Validation failed: Volunteer Case assignee must be an active volunteer (ActiveRecord::RecordInvalid)\n        # * casa_admin+with_case_contact - Validation failed: Volunteer Case assignee must be an active volunteer (ActiveRecord::RecordInvalid)\n        # * casa_admin+with_case_contact_wants_driving_reimbursement - Validation failed: Volunteer Case assignee must be an active volunteer (ActiveRecord::RecordInvalid)\n        :casa_admin,\n\n        # * supervisor+with_casa_cases - Validation failed: Volunteer Case assignee must be an active volunteer (ActiveRecord::RecordInvalid)\n        # * supervisor+with_case_contact - Validation failed: Volunteer Case assignee must be an active volunteer (ActiveRecord::RecordInvalid)\n        # * supervisor+with_case_contact_wants_driving_reimbursement - Validation failed: Volunteer Case assignee must be an active volunteer (ActiveRecord::RecordInvalid)\n        # * supervisor+with_casa_cases - Validation failed: Volunteer Case assignee must be an active volunteer (ActiveRecord::RecordInvalid)\n        # * supervisor+with_single_case - Validation failed: Volunteer Case assignee must be an active volunteer (ActiveRecord::RecordInvalid)\n        # * supervisor+with_case_contact - Validation failed: Volunteer Case assignee must be an active volunteer (ActiveRecord::RecordInvalid)\n        # * supervisor+with_case_contact_wants_driving_reimbursement - Validation failed: Volunteer Case assignee must be an active volunteer (ActiveRecord::RecordInvalid)\n        :supervisor,\n\n        # * user+with_casa_cases - Validation failed: Volunteer Case assignee must be an active volunteer (ActiveRecord::RecordInvalid)\n        # * user+with_single_case - Validation failed: Volunteer Case assignee must be an active volunteer (ActiveRecord::RecordInvalid)\n        # * user+with_case_contact - Validation failed: Volunteer Case assignee must be an active volunteer (ActiveRecord::RecordInvalid)\n        # * user+with_case_contact_wants_driving_reimbursement - Validation failed: Volunteer Case assignee must be an active volunteer (ActiveRecord::RecordInvalid)\n        :user\n      ]\n      raise \"a suspiciously low number of FactoryBot factories\" if FactoryBot.factories.count < 50\n      factories_to_lint = FactoryBot.factories.reject do |factory|\n        invalid_factories.include?(factory.name)\n      end\n      conn.transaction do\n        FactoryBot.lint(factories_to_lint, {traits: true})\n        raise ActiveRecord::Rollback\n      end\n    else\n      system(\"bundle exec rake factory_bot:lint RAILS_ENV='test'\")\n      raise if $?.exitstatus.nonzero?\n    end\n  end\nend\n"
  },
  {
    "path": "lib/tasks/monthly_learning_hours_report.rake",
    "content": "desc \"Scheduled once per month in Heroku Scheduler, this task will send a learning hours report to all Casa Admins and Supervisors\"\ntask send_learning_hour_reports: :environment do\n  admins = CasaAdmin.active.where(monthly_learning_hours_report: true)\n  supervisors = Supervisor.active.where(monthly_learning_hours_report: true)\n\n  admins.each do |admin|\n    LearningHoursMailer.learning_hours_report_email(admin).deliver_later\n  end\n\n  supervisors.each do |supervisor|\n    LearningHoursMailer.learning_hours_report_email(supervisor).deliver_later\n  end\nend\n"
  },
  {
    "path": "lib/tasks/no_contact_made_reminder.rb",
    "content": "class NoContactMadeReminder\n  def send!\n    responses = []\n\n    eligible_volunteers = Volunteer.where(receive_sms_notifications: true)\n      .where.not(phone_number: nil)\n      .select { |v| valid_past_reminders(v) }\n\n    eligible_volunteers.each do |volunteer|\n      responses += send_reminders(volunteer)\n    end\n\n    responses\n  end\n\n  private\n\n  def send_reminders(volunteer)\n    responses = []\n    contact_types = get_contact_types_in_past_2_weeks(volunteer, false) - get_contact_types_in_past_2_weeks(volunteer, true)\n    return responses unless contact_types.count > 0\n\n    contact_types.each do |type|\n      responses.push(\n        {\n          volunteer: volunteer,\n          message: NoContactMadeSmsReminderService.no_contact_made_reminder(volunteer, type)\n        }\n      )\n      UserReminderTime.find_by(user_id: volunteer.id)&.update_attributes(no_contact_made: DateTime.now)\n    end\n\n    responses\n  end\n\n  def get_contact_types_in_past_2_weeks(volunteer, contact_made)\n    volunteer.case_contacts.where(\"occurred_at > ?\", 2.weeks.ago).where(contact_made: contact_made).joins(:contact_types).pluck(:name)\n  end\n\n  def valid_past_reminders(volunteer)\n    reminder = UserReminderTime.find_by(user_id: volunteer.id)\n\n    if reminder&.case_contact_types&.today?\n      return false\n    end\n\n    if reminder&.no_contact_made && reminder.no_contact_made >= 1.months.ago\n      return false\n    end\n\n    true\n  end\nend\n"
  },
  {
    "path": "lib/tasks/post_gc_stat_to_discord.rake",
    "content": "desc \"Post gc stats to discord channel\"\n\ntask post_gc_stat_to_discord: :environment do\n  require \"net/http\"\n\n  url = URI(\"https://casavolunteertracking.org/health/gc?token=#{ENV[\"GC_ACCESS_TOKEN\"]}\")\n  response = Net::HTTP.get_response(url)\n\n  unless response.is_a?(Net::HTTPSuccess)\n    raise \"Failed to fetch GC stats. HTTP status code:#{response.code}\"\n  end\n\n  stats = response.body\n\n  unless ENV[\"DISCORD_WEBHOOK_URL\"].nil?\n    discord_message = <<~MULTILINE\n      ```json\n      #{stats}\n      ```\n    MULTILINE\n\n    payload = {content: discord_message}.to_json\n\n    uri = URI.parse(ENV[\"DISCORD_WEBHOOK_URL\"])\n    http = Net::HTTP.new(uri.host, uri.port)\n    http.use_ssl = (uri.scheme == \"https\")  # Use SSL for HTTPS\n\n    request = Net::HTTP::Post.new(uri.path, {\"Content-Type\" => \"application/json\"})\n    request.body = payload\n\n    http.request(request)\n  end\nend\n"
  },
  {
    "path": "lib/tasks/recurring_jobs.rake",
    "content": "namespace :recurring do\n  task init: :environment do\n    ExampleRecurringTask.schedule!\n  end\nend\n"
  },
  {
    "path": "lib/tasks/scheduler.rake",
    "content": "desc \"Clear court dates and report information when date has passed, run by heroku scheduler\"\ntask clear_passed_dates: :environment do\n  puts \"Checking case due dates...\"\n\n  CasaCase.due_date_passed.each do |cc|\n    cc.clear_court_dates\n  end\n\n  puts \"done.\"\nend\n"
  },
  {
    "path": "lib/tasks/send_case_contact_types_reminder.rake",
    "content": "desc \"Send an SMS to volunteers reminding them to connect with the contact types they have not connected with in the past 60 or more days\"\nrequire_relative \"case_contact_types_reminder\"\ntask send_case_contact_types_reminder: :environment do\n  every 1.weeks do\n    CaseContactTypesReminder.new.send!\n  end\nend\n"
  },
  {
    "path": "lib/tasks/send_no_contact_made_reminder.rake",
    "content": "desc \"Send an SMS to volunteers reminding them to make contact if two weeks have passed since they logged a case contact but contact was not made\"\nrequire_relative \"case_contact_types_reminder\"\ntask send_case_contact_types_reminder: :environment do\n  every 1.days do\n    CaseContactTypesReminder.new.send!\n  end\nend\n"
  },
  {
    "path": "lib/tasks/send_supervisor_digest.rake",
    "content": "desc \"Send an email to supervisors each week to share an overview of their volunteers' activities\"\nrequire_relative \"supervisor_weekly_digest\"\ntask send_supervisor_digest: :environment do\n  SupervisorWeeklyDigest.new.send!\nend\n"
  },
  {
    "path": "lib/tasks/supervisor_weekly_digest.rb",
    "content": "class SupervisorWeeklyDigest\n  def send!\n    if Time.now.monday?\n      Supervisor.active.find_each do |supervisor|\n        SupervisorMailer.weekly_digest(supervisor).deliver_now\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/tasks/test_checker.rake",
    "content": "require \"amazing_print\"\n\ndesc \"Check app rb files to verify that there are corresponding spec files.\"\ntask test_checker: :environment do\n  # File containing app filespecs that should not be flagged as errors for not having spec files.\n  def deny_filespec\n    Rails.root.join(\".allow_skipping_tests\").to_s\n  end\n\n  def dashed_line\n    \"-\" * 80\n  end\n  # Lines beginning with '#' are ignored.\n\n  # Transform the object into the object's AmazingPrint representation.\n  # `ai` alone will included color ANSI sequences in the output.\n  # If those color sequences need to be omitted, pass `plain: true` to `ai`.\n  def amazing_printize(object)\n    object.ai\n  end\n\n  # @return absolute filespec of a Rails project's top level directory.\n  def top_level_dir(name)\n    File.absolute_path(Rails.root.join(name).to_s)\n  end\n\n  # @return .rb files in a directory tree, relative to the passed directory\n  def ruby_files(dir)\n    absolutes = Dir[File.join(dir, \"**\", \"*.rb\")]\n    absolutes.map { |fspec| fspec.sub(dir + \"/\", \"\") }.sort\n  end\n\n  # @return the absolute path of the project's app directory.\n  def app_dir\n    @app_dir ||= top_level_dir(\"app\")\n  end\n\n  # @return the absolute path of the project's spec directory.\n  def spec_dir\n    @spec_dir ||= top_level_dir(\"spec\")\n  end\n\n  # @return the app Ruby filespecs\n  def app_files\n    @app_files ||= ruby_files(app_dir)\n  end\n\n  # @return the spec Ruby filespecs, with 'spec_' removed\n  def spec_files\n    @spec_files ||= ruby_files(spec_dir).map do |fspec|\n      if fspec.include?(\"requests/\")\n        fspec.sub(\"requests\", \"controllers\")\n          .sub(\"spec.rb\", \"controller.rb\")\n      else\n        fspec.sub(\"_spec.rb\", \".rb\")\n      end\n    end.uniq\n  end\n\n  def ignore_files\n    return @ignore_files if @ignore_files\n    file_lines = File.readlines(deny_filespec).map(&:chomp)\n    @ignore_files = file_lines.reject { |line| /\\s*#/.match(line) } # exclude comment lines\n  end\n\n  # puts \"Ignore files: \\n #{ignore_files.join(\"\\n\")}\"\n  def missing_spec_files\n    @missing_spec_files ||= app_files - spec_files\n  end\n\n  def missing_and_not_denied_spec_files\n    @missing_and_not_denied_spec_files ||= missing_spec_files - ignore_files\n  end\n\n  def missing_but_denied_files\n    @missing_but_denied_files ||= missing_spec_files & ignore_files\n  end\n\n  def output_missing_but_denied\n    percent = (100 * missing_but_denied_files.size.to_f / app_files.size)\n    puts <<~TEXT\n\n      #{dashed_line}\n      #{missing_but_denied_files.size} of #{app_files.size} app files (#{percent.round(1)}%) did not have a corresponding spec file\n      but were listed in the deny file (#{deny_filespec}):\n\n      #{amazing_printize(missing_but_denied_files)}\n    TEXT\n  end\n\n  def output_missing_and_not_denied\n    percent = (100 * missing_and_not_denied_spec_files.size.to_f / app_files.size)\n    puts <<~ERROR_TXT\n\n      #{dashed_line}\n      #{missing_and_not_denied_spec_files.size} of #{app_files.size} app files (#{percent.round(1)}%) did not have a corresponding spec file\n      and are not in the deny list:\n\n      #{amazing_printize(missing_and_not_denied_spec_files)}\n\n    ERROR_TXT\n  end\n\n  if missing_and_not_denied_spec_files.any?\n    output_missing_and_not_denied\n    output_missing_but_denied\n    abort\n  end\n\n  if missing_but_denied_files.any?\n    output_missing_but_denied\n  end\nend\n"
  },
  {
    "path": "lib/tasks/volunteer_birthday_reminder.rake",
    "content": "desc \"Create a notification for supervisors when a volunteer has a birthday coming in the next month, scheduled for the 15th of each month in Heroku Scheduler\"\ntask volunteer_birthday_reminder: :environment do\n  VolunteerBirthdayReminderService.new.send_reminders\nend\n"
  },
  {
    "path": "lib/tasks/youth_birthday_reminder.rake",
    "content": "desc \"Create a notification for volunteers when a youth has a birthday coming in the next month, scheduled for the 15th of each month in Heroku Scheduler\"\ntask youth_birthday_reminder: :environment do\n  CasaCase.birthday_next_month.each do |casa_case|\n    YouthBirthdayNotifier\n      .with(casa_case: casa_case)\n      .deliver(Volunteer.find_by(id: casa_case.case_assignments.first.volunteer_id))\n  end\nend\n"
  },
  {
    "path": "log/.keep",
    "content": ""
  },
  {
    "path": "noop",
    "content": "Thu Mar 20 09:44:23 PDT 2025\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"casa\",\n  \"private\": true,\n  \"scripts\": {\n    \"test\": \"jest\",\n    \"lint\": \"standard\",\n    \"lint:fix\": \"standard --fix\",\n    \"toc\": \"markdown-toc -i README.md\",\n    \"build\": \"./bin/asset_bundling_scripts/build_js.js\",\n    \"build:dev\": \"./bin/asset_bundling_scripts/build_js.js --watch\",\n    \"build:css\": \"sass app/assets/stylesheets/application.scss app/assets/builds/application.css --load-path=node_modules\",\n    \"build:css:dev\": \"sass app/assets/stylesheets/application.scss app/assets/builds/application.css --load-path=node_modules --watch\"\n  },\n  \"dependencies\": {\n    \"@babel/core\": \"^7.29.0\",\n    \"@fortawesome/fontawesome-free\": \"^7.2.0\",\n    \"@hotwired/stimulus\": \"^3.2.2\",\n    \"@hotwired/turbo-rails\": \"^8.0.23\",\n    \"@popperjs/core\": \"^2.11.8\",\n    \"@rails/actioncable\": \"^8.1.300\",\n    \"@rails/actiontext\": \"^8.1.300\",\n    \"@rails/activestorage\": \"^8.1.300\",\n    \"@rails/ujs\": \"^7.1.600\",\n    \"@stimulus-components/rails-nested-form\": \"^5.0.0\",\n    \"add-to-calendar-button\": \"^2.13.8\",\n    \"bootstrap\": \"5.3.6\",\n    \"bootstrap-datepicker\": \"^1.10.1\",\n    \"bootstrap-scss\": \"^5.3.8\",\n    \"bootstrap-select\": \"^1.13.18\",\n    \"chart.js\": \"^4.5.1\",\n    \"chartjs-adapter-luxon\": \"^1.3.1\",\n    \"datatables.net-dt\": \"^1.13.11\",\n    \"esbuild\": \"^0.28.0\",\n    \"jquery\": \"^3.7.1\",\n    \"js-cookie\": \"^3.0.5\",\n    \"jstz\": \"^2.1.1\",\n    \"lodash\": \"^4.18.1\",\n    \"luxon\": \"^3.6.1\",\n    \"popper.js\": \"^1.16.1\",\n    \"sass\": \"1.77.6\",\n    \"select2\": \"^4.0.13\",\n    \"select2-bootstrap-5-theme\": \"^1.3.0\",\n    \"strftime\": \"^0.10.3\",\n    \"sweetalert2\": \"^11.3.5\",\n    \"tom-select\": \"^2.5.2\",\n    \"trix\": \"^2.1.18\"\n  },\n  \"version\": \"0.1.0\",\n  \"devDependencies\": {\n    \"@babel/preset-env\": \"^7.29.2\",\n    \"faker\": \"^5.5.3\",\n    \"jest\": \"^30.3.0\",\n    \"jest-environment-jsdom\": \"^30.3.0\",\n    \"markdown-toc\": \"^1.2.0\",\n    \"standard\": \"^17.1.2\",\n    \"start-server-and-test\": \"^2.1.5\"\n  },\n  \"standard\": {\n    \"globals\": [\n      \"$\",\n      \"jQuery\",\n      \"Turbo\"\n    ],\n    \"ignore\": [\n      \"app/assets/builds/**/*\"\n    ]\n  },\n  \"engines\": {\n    \"node\": \"24.x\"\n  }\n}\n"
  },
  {
    "path": "postcss.config.js",
    "content": "module.exports = {\n  plugins: [\n    require('postcss-import'),\n    require('postcss-flexbugs-fixes'),\n    require('postcss-preset-env')({\n      autoprefixer: {\n        flexbox: 'no-2009'\n      },\n      stage: 3\n    })\n  ]\n}\n"
  },
  {
    "path": "public/403.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <title>We're sorry, but something went wrong (403)</title>\n  <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n  <style>\n  .rails-default-error-page {\n    background-color: #EFEFEF;\n    color: #2E2F30;\n    text-align: center;\n    font-family: arial, sans-serif;\n    margin: 0;\n  }\n\n  .rails-default-error-page div.dialog {\n    width: 95%;\n    max-width: 33em;\n    margin: 4em auto 0;\n  }\n\n  .rails-default-error-page div.dialog > div {\n    border: 1px solid #CCC;\n    border-right-color: #999;\n    border-left-color: #999;\n    border-bottom-color: #BBB;\n    border-top: #B00100 solid 4px;\n    border-top-left-radius: 9px;\n    border-top-right-radius: 9px;\n    background-color: white;\n    padding: 7px 12% 0;\n    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);\n  }\n\n  .rails-default-error-page h1 {\n    font-size: 100%;\n    color: #730E15;\n    line-height: 1.5em;\n  }\n\n  .rails-default-error-page div.dialog > p {\n    margin: 0 0 1em;\n    padding: 1em;\n    background-color: #F7F7F7;\n    border: 1px solid #CCC;\n    border-right-color: #999;\n    border-left-color: #999;\n    border-bottom-color: #999;\n    border-bottom-left-radius: 4px;\n    border-bottom-right-radius: 4px;\n    border-top-color: #DADADA;\n    color: #666;\n    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);\n  }\n  </style>\n</head>\n\n<body class=\"rails-default-error-page\">\n  <!-- This file lives in public/500.html -->\n  <div class=\"dialog\">\n    <div>\n      <h1>Sorry, you are not authorized to perform this action.</h1>\n    </div>\n  </div>\n</body>\n</html>\n"
  },
  {
    "path": "public/404.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <title>The page you were looking for doesn't exist (404)</title>\n  <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n  <style>\n    .rails-default-error-page {\n      background-color: #EFEFEF;\n      color: #2E2F30;\n      text-align: center;\n      font-family: arial, sans-serif;\n      margin: 0;\n    }\n\n    .rails-default-error-page .header {\n      color: #fff;\n      background: #00447c;\n      background-color: rgb(0, 68, 124);\n      box-shadow: 0 0 4px 4px rgb(0 0 0 / 28%);\n      height: 80px;\n      justify-content: flex-start;\n      padding: 16px;\n      display: flex;\n      flex-direction: row;\n      font-size: 48px;\n      align-items: center;\n      font-weight: bolder;\n    }\n\n    .rails-default-error-page div.dialog {\n      width: 95%;\n      max-width: 33em;\n      margin: 15em auto;\n    }\n\n    .rails-default-error-page div.dialog > div {\n      border: 1px solid #CCC;\n      border-right-color: #999;\n      border-left-color: #999;\n      border-bottom-color: #BBB;\n      border-top: #103862 solid 4px;\n      border-radius: 9px;\n      background-color: white;\n      padding: 7px 12% 0;\n      box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);\n      height: 150px;\n      display: flex;\n      flex-direction: column;\n      justify-content: center;\n    }\n\n    .rails-default-error-page h1 {\n      font-size: 100%;\n      color: #103862;\n      line-height: 1.5em;\n    }\n\n    .rails-default-error-page .footer {\n      color: #fff;\n      padding: 2rem 0;\n      text-align: center;\n      background-color: #00447c;\n      bottom: 0;\n      position: fixed;\n      width: 100vw;\n    }\n  </style>\n</head>\n\n<body class=\"rails-default-error-page\">\n  <!-- This file lives in public/404.html -->\n  <div class=\"header\">\n    CASA/Volunteer Tracking\n  </div>\n\n  <div class=\"dialog\">\n    <div>\n      <h1>The page you were looking for doesn't exist.</h1>\n      <p>You may have mistyped the address or the page may have moved.</p>\n    </div>\n  </div>\n\n  <div class=\"footer\">\n    <p class=\"copyright\">\n      &#169 CASA / Volunteer Tracking\n    </p>\n  </div>\n</body>\n\n</html>\n"
  },
  {
    "path": "public/406-unsupported-browser.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <title>Your browser is not supported (406)</title>\n  <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n  <style>\n  .rails-default-error-page {\n    background-color: #EFEFEF;\n    color: #2E2F30;\n    text-align: center;\n    font-family: arial, sans-serif;\n    margin: 0;\n  }\n\n  .rails-default-error-page div.dialog {\n    width: 95%;\n    max-width: 33em;\n    margin: 4em auto 0;\n  }\n\n  .rails-default-error-page div.dialog > div {\n    border: 1px solid #CCC;\n    border-right-color: #999;\n    border-left-color: #999;\n    border-bottom-color: #BBB;\n    border-top: #B00100 solid 4px;\n    border-top-left-radius: 9px;\n    border-top-right-radius: 9px;\n    background-color: white;\n    padding: 7px 12% 0;\n    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);\n  }\n\n  .rails-default-error-page h1 {\n    font-size: 100%;\n    color: #730E15;\n    line-height: 1.5em;\n  }\n\n  .rails-default-error-page div.dialog > p {\n    margin: 0 0 1em;\n    padding: 1em;\n    background-color: #F7F7F7;\n    border: 1px solid #CCC;\n    border-right-color: #999;\n    border-left-color: #999;\n    border-bottom-color: #999;\n    border-bottom-left-radius: 4px;\n    border-bottom-right-radius: 4px;\n    border-top-color: #DADADA;\n    color: #666;\n    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);\n  }\n  </style>\n</head>\n\n<body class=\"rails-default-error-page\">\n  <!-- This file lives in public/406-unsupported-browser.html -->\n  <div class=\"dialog\">\n    <div>\n      <h1>Your browser is not supported.</h1>\n      <p>Please upgrade your browser to continue.</p>\n    </div>\n  </div>\n</body>\n</html>\n"
  },
  {
    "path": "public/422.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <title>The change you wanted was rejected (422)</title>\n  <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n  <style>\n  .rails-default-error-page {\n    background-color: #EFEFEF;\n    color: #2E2F30;\n    text-align: center;\n    font-family: arial, sans-serif;\n    margin: 0;\n  }\n\n  .rails-default-error-page div.dialog {\n    width: 95%;\n    max-width: 33em;\n    margin: 4em auto 0;\n  }\n\n  .rails-default-error-page div.dialog > div {\n    border: 1px solid #CCC;\n    border-right-color: #999;\n    border-left-color: #999;\n    border-bottom-color: #BBB;\n    border-top: #B00100 solid 4px;\n    border-top-left-radius: 9px;\n    border-top-right-radius: 9px;\n    background-color: white;\n    padding: 7px 12% 0;\n    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);\n  }\n\n  .rails-default-error-page h1 {\n    font-size: 100%;\n    color: #730E15;\n    line-height: 1.5em;\n  }\n\n  .rails-default-error-page div.dialog > p {\n    margin: 0 0 1em;\n    padding: 1em;\n    background-color: #F7F7F7;\n    border: 1px solid #CCC;\n    border-right-color: #999;\n    border-left-color: #999;\n    border-bottom-color: #999;\n    border-bottom-left-radius: 4px;\n    border-bottom-right-radius: 4px;\n    border-top-color: #DADADA;\n    color: #666;\n    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);\n  }\n  </style>\n</head>\n\n<body class=\"rails-default-error-page\">\n  <!-- This file lives in public/422.html -->\n  <div class=\"dialog\">\n    <div>\n      <h1>The change you wanted was rejected.</h1>\n      <p>Maybe you tried to change something you didn't have access to.</p>\n    </div>\n    <p>If you are the application owner check the logs for more information.</p>\n  </div>\n</body>\n</html>\n"
  },
  {
    "path": "public/500.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <title>We're sorry, but something went wrong (500)</title>\n  <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n  <style>\n    .rails-default-error-page {\n      background-color: #EFEFEF;\n      color: #2E2F30;\n      text-align: center;\n      font-family: arial, sans-serif;\n      margin: 0;\n    }\n\n    .rails-default-error-page .header {\n      color: #fff;\n      background: #00447c;\n      background-color: rgb(0, 68, 124);\n      box-shadow: 0 0 4px 4px rgb(0 0 0 / 28%);\n      height: 80px;\n      justify-content: flex-start;\n      padding: 16px;\n      display: flex;\n      flex-direction: row;\n      font-size: 48px;\n      align-items: center;\n      font-weight: bolder;\n    }\n\n    .rails-default-error-page div.dialog {\n      width: 95%;\n      max-width: 33em;\n      margin: 15em auto;\n    }\n\n    .rails-default-error-page div.dialog > div {\n      border: 1px solid #CCC;\n      border-right-color: #999;\n      border-left-color: #999;\n      border-bottom-color: #BBB;\n      border-top: #103862 solid 4px;\n      border-radius: 9px;\n      background-color: white;\n      padding: 7px 12% 0;\n      box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);\n      height: 150px;\n      display: flex;\n      flex-direction: column;\n      justify-content: center;\n    }\n\n    .rails-default-error-page h1 {\n      font-size: 100%;\n      color: #103862;\n      line-height: 1.5em;\n    }\n\n    .rails-default-error-page .footer {\n      color: #fff;\n      padding: 2rem 0;\n      text-align: center;\n      background-color: #00447c;\n      bottom: 0;\n      position: fixed;\n      width: 100vw;\n    }\n  </style>\n</head>\n\n<body class=\"rails-default-error-page\">\n  <!-- This file lives in public/500.html -->\n   <div class=\"header\">\n    CASA/Volunteer Tracking\n  </div>\n  \n  <div class=\"dialog\">\n    <div>\n      <h1>We're sorry, but something went wrong.</h1>\n      <p>If you are the application owner check the logs for more information.</p>\n    </div>\n  </div>\n\n  <div class=\"footer\">\n    <p class=\"copyright\">\n      &#169 CASA / Volunteer Tracking\n    </p>\n  </div>\n</body>\n</html>\n"
  },
  {
    "path": "public/assets/css/lineicons.css",
    "content": "/*--------------------------------\n\nLineIcons Web Font\nAuthor: lineicons.com\n\n-------------------------------- */\n@font-face {\n  font-family: \"LineIcons\";\n  src: url(\"../fonts/LineIcons.eot\");\n  src: url(\"../fonts/LineIcons.eot\") format(\"embedded-opentype\"),\n    url(\"../fonts/LineIcons.woff2\") format(\"woff2\"),\n    url(\"../fonts/LineIcons.woff\") format(\"woff\"),\n    url(\"../fonts/LineIcons.ttf\") format(\"truetype\"),\n    url(\"../fonts/LineIcons.svg\") format(\"svg\");\n  font-weight: normal;\n  font-style: normal;\n}\n/*------------------------\n\tbase class definition\n-------------------------*/\n.lni {\n  display: inline-block;\n  font: normal normal normal 1em/1 \"LineIcons\";\n  color: inherit;\n  flex-shrink: 0;\n  speak: none;\n  text-transform: none;\n  line-height: 1;\n  vertical-align: -0.125em;\n  /* Better Font Rendering */\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n/*------------------------\n  change icon size\n-------------------------*/\n/* relative units */\n.lni-sm {\n  font-size: 0.8em;\n}\n.lni-lg {\n  font-size: 1.2em;\n}\n/* absolute units */\n.lni-16 {\n  font-size: 16px;\n}\n.lni-32 {\n  font-size: 32px;\n}\n\n/*------------------------\n  spinning icons\n-------------------------*/\n.lni-is-spinning {\n  animation: lni-spin 1s infinite linear;\n}\n@keyframes lni-spin {\n  0% {\n    transform: rotate(0deg);\n  }\n  100% {\n    transform: rotate(360deg);\n  }\n}\n/*------------------------\n  rotated/flipped icons\n-------------------------*/\n.lni-rotate-90 {\n  transform: rotate(90deg);\n}\n.lni-rotate-180 {\n  transform: rotate(180deg);\n}\n.lni-rotate-270 {\n  transform: rotate(270deg);\n}\n.lni-flip-y {\n  transform: scaleY(-1);\n}\n.lni-flip-x {\n  transform: scaleX(-1);\n}\n/*------------------------\n\ticons\n-------------------------*/\n\n.lni-500px::before {\n  content: \"\\ea03\";\n}\n\n.lni-add-files::before {\n  content: \"\\ea01\";\n}\n\n.lni-adobe::before {\n  content: \"\\ea06\";\n}\n\n.lni-agenda::before {\n  content: \"\\ea02\";\n}\n\n.lni-airbnb::before {\n  content: \"\\ea07\";\n}\n\n.lni-alarm-clock::before {\n  content: \"\\ea08\";\n}\n\n.lni-alarm::before {\n  content: \"\\ea04\";\n}\n\n.lni-amazon-original::before {\n  content: \"\\ea05\";\n}\n\n.lni-amazon-pay::before {\n  content: \"\\ea09\";\n}\n\n.lni-amazon::before {\n  content: \"\\ea0a\";\n}\n\n.lni-ambulance::before {\n  content: \"\\ea0b\";\n}\n\n.lni-amex::before {\n  content: \"\\ea0c\";\n}\n\n.lni-anchor::before {\n  content: \"\\ea0d\";\n}\n\n.lni-android-original::before {\n  content: \"\\ea0e\";\n}\n\n.lni-android::before {\n  content: \"\\ea0f\";\n}\n\n.lni-angellist::before {\n  content: \"\\ea10\";\n}\n\n.lni-angle-double-down::before {\n  content: \"\\ea11\";\n}\n\n.lni-angle-double-left::before {\n  content: \"\\ea12\";\n}\n\n.lni-angle-double-right::before {\n  content: \"\\ea13\";\n}\n\n.lni-angle-double-up::before {\n  content: \"\\ea14\";\n}\n\n.lni-angular::before {\n  content: \"\\ea15\";\n}\n\n.lni-apartment::before {\n  content: \"\\ea16\";\n}\n\n.lni-app-store::before {\n  content: \"\\ea17\";\n}\n\n.lni-apple-music::before {\n  content: \"\\ea18\";\n}\n\n.lni-apple-pay::before {\n  content: \"\\ea19\";\n}\n\n.lni-apple::before {\n  content: \"\\ea1a\";\n}\n\n.lni-archive::before {\n  content: \"\\ea1f\";\n}\n\n.lni-arrow-down-circle::before {\n  content: \"\\ea1b\";\n}\n\n.lni-arrow-down::before {\n  content: \"\\ea1c\";\n}\n\n.lni-arrow-left-circle::before {\n  content: \"\\ea1d\";\n}\n\n.lni-arrow-left::before {\n  content: \"\\ea1e\";\n}\n\n.lni-arrow-right-circle::before {\n  content: \"\\ea20\";\n}\n\n.lni-arrow-right::before {\n  content: \"\\ea21\";\n}\n\n.lni-arrow-top-left::before {\n  content: \"\\ea22\";\n}\n\n.lni-arrow-top-right::before {\n  content: \"\\ea23\";\n}\n\n.lni-arrow-up-circle::before {\n  content: \"\\ea24\";\n}\n\n.lni-arrow-up::before {\n  content: \"\\ea25\";\n}\n\n.lni-arrows-horizontal::before {\n  content: \"\\ea26\";\n}\n\n.lni-arrows-vertical::before {\n  content: \"\\ea27\";\n}\n\n.lni-atlassian::before {\n  content: \"\\ea28\";\n}\n\n.lni-aws::before {\n  content: \"\\ea29\";\n}\n\n.lni-azure::before {\n  content: \"\\ea2a\";\n}\n\n.lni-backward::before {\n  content: \"\\ea2b\";\n}\n\n.lni-baloon::before {\n  content: \"\\ea2c\";\n}\n\n.lni-ban::before {\n  content: \"\\ea2d\";\n}\n\n.lni-bar-chart::before {\n  content: \"\\ea2e\";\n}\n\n.lni-basketball::before {\n  content: \"\\ea2f\";\n}\n\n.lni-behance-original::before {\n  content: \"\\ea30\";\n}\n\n.lni-behance::before {\n  content: \"\\ea31\";\n}\n\n.lni-bi-cycle::before {\n  content: \"\\ea32\";\n}\n\n.lni-bitbucket::before {\n  content: \"\\ea33\";\n}\n\n.lni-bitcoin::before {\n  content: \"\\ea34\";\n}\n\n.lni-blackboard::before {\n  content: \"\\ea35\";\n}\n\n.lni-blogger::before {\n  content: \"\\ea36\";\n}\n\n.lni-bluetooth-original::before {\n  content: \"\\ea37\";\n}\n\n.lni-bluetooth::before {\n  content: \"\\ea38\";\n}\n\n.lni-bold::before {\n  content: \"\\ea39\";\n}\n\n.lni-bolt-alt::before {\n  content: \"\\ea3a\";\n}\n\n.lni-bolt::before {\n  content: \"\\ea40\";\n}\n\n.lni-book::before {\n  content: \"\\ea3b\";\n}\n\n.lni-bookmark-alt::before {\n  content: \"\\ea3c\";\n}\n\n.lni-bookmark::before {\n  content: \"\\ea3d\";\n}\n\n.lni-bootstrap::before {\n  content: \"\\ea3e\";\n}\n\n.lni-bricks::before {\n  content: \"\\ea3f\";\n}\n\n.lni-bridge::before {\n  content: \"\\ea41\";\n}\n\n.lni-briefcase::before {\n  content: \"\\ea42\";\n}\n\n.lni-brush-alt::before {\n  content: \"\\ea43\";\n}\n\n.lni-brush::before {\n  content: \"\\ea44\";\n}\n\n.lni-btc::before {\n  content: \"\\ea45\";\n}\n\n.lni-bubble::before {\n  content: \"\\ea46\";\n}\n\n.lni-bug::before {\n  content: \"\\ea47\";\n}\n\n.lni-bulb::before {\n  content: \"\\ea48\";\n}\n\n.lni-bullhorn::before {\n  content: \"\\ea49\";\n}\n\n.lni-burger::before {\n  content: \"\\ea4a\";\n}\n\n.lni-bus::before {\n  content: \"\\ea4b\";\n}\n\n.lni-cake::before {\n  content: \"\\ea4c\";\n}\n\n.lni-calculator::before {\n  content: \"\\ea4d\";\n}\n\n.lni-calendar::before {\n  content: \"\\ea4e\";\n}\n\n.lni-camera::before {\n  content: \"\\ea4f\";\n}\n\n.lni-candy-cane::before {\n  content: \"\\ea50\";\n}\n\n.lni-candy::before {\n  content: \"\\ea51\";\n}\n\n.lni-capsule::before {\n  content: \"\\ea52\";\n}\n\n.lni-car-alt::before {\n  content: \"\\ea53\";\n}\n\n.lni-car::before {\n  content: \"\\ea54\";\n}\n\n.lni-caravan::before {\n  content: \"\\ea55\";\n}\n\n.lni-cart-full::before {\n  content: \"\\ea56\";\n}\n\n.lni-cart::before {\n  content: \"\\ea57\";\n}\n\n.lni-certificate::before {\n  content: \"\\ea58\";\n}\n\n.lni-check-box::before {\n  content: \"\\ea59\";\n}\n\n.lni-checkmark-circle::before {\n  content: \"\\ea5a\";\n}\n\n.lni-checkmark::before {\n  content: \"\\ea5b\";\n}\n\n.lni-chef-hat::before {\n  content: \"\\ea5c\";\n}\n\n.lni-chevron-down-circle::before {\n  content: \"\\ea5d\";\n}\n\n.lni-chevron-down::before {\n  content: \"\\ea5e\";\n}\n\n.lni-chevron-left-circle::before {\n  content: \"\\ea5f\";\n}\n\n.lni-chevron-left::before {\n  content: \"\\ea60\";\n}\n\n.lni-chevron-right-circle::before {\n  content: \"\\ea61\";\n}\n\n.lni-chevron-right::before {\n  content: \"\\ea62\";\n}\n\n.lni-chevron-up-circle::before {\n  content: \"\\ea63\";\n}\n\n.lni-chevron-up::before {\n  content: \"\\ea64\";\n}\n\n.lni-chrome::before {\n  content: \"\\ea65\";\n}\n\n.lni-chromecast::before {\n  content: \"\\ea66\";\n}\n\n.lni-circle-minus::before {\n  content: \"\\ea67\";\n}\n\n.lni-circle-plus::before {\n  content: \"\\ea68\";\n}\n\n.lni-clipboard::before {\n  content: \"\\ea69\";\n}\n\n.lni-close::before {\n  content: \"\\ea6a\";\n}\n\n.lni-cloud-check::before {\n  content: \"\\ea6b\";\n}\n\n.lni-cloud-download::before {\n  content: \"\\ea6c\";\n}\n\n.lni-cloud-network::before {\n  content: \"\\ea6d\";\n}\n\n.lni-cloud-sync::before {\n  content: \"\\ea6e\";\n}\n\n.lni-cloud-upload::before {\n  content: \"\\ea6f\";\n}\n\n.lni-cloud::before {\n  content: \"\\ea70\";\n}\n\n.lni-cloudflare::before {\n  content: \"\\ea71\";\n}\n\n.lni-cloudy-sun::before {\n  content: \"\\ea72\";\n}\n\n.lni-code-alt::before {\n  content: \"\\ea73\";\n}\n\n.lni-code::before {\n  content: \"\\ea74\";\n}\n\n.lni-codepen::before {\n  content: \"\\ea75\";\n}\n\n.lni-coffee-cup::before {\n  content: \"\\ea76\";\n}\n\n.lni-cog::before {\n  content: \"\\ea77\";\n}\n\n.lni-cogs::before {\n  content: \"\\ea78\";\n}\n\n.lni-coin::before {\n  content: \"\\ea79\";\n}\n\n.lni-comments-alt::before {\n  content: \"\\ea7a\";\n}\n\n.lni-comments-reply::before {\n  content: \"\\ea7b\";\n}\n\n.lni-comments::before {\n  content: \"\\ea7c\";\n}\n\n.lni-compass::before {\n  content: \"\\ea7d\";\n}\n\n.lni-connectdevelop::before {\n  content: \"\\ea7e\";\n}\n\n.lni-construction-hammer::before {\n  content: \"\\ea7f\";\n}\n\n.lni-construction::before {\n  content: \"\\ea80\";\n}\n\n.lni-consulting::before {\n  content: \"\\ea81\";\n}\n\n.lni-control-panel::before {\n  content: \"\\ea82\";\n}\n\n.lni-cool::before {\n  content: \"\\ea83\";\n}\n\n.lni-cpanel::before {\n  content: \"\\ea84\";\n}\n\n.lni-creative-commons::before {\n  content: \"\\ea85\";\n}\n\n.lni-credit-cards::before {\n  content: \"\\ea86\";\n}\n\n.lni-crop::before {\n  content: \"\\ea87\";\n}\n\n.lni-cross-circle::before {\n  content: \"\\ea88\";\n}\n\n.lni-crown::before {\n  content: \"\\ea89\";\n}\n\n.lni-css3::before {\n  content: \"\\ea8a\";\n}\n\n.lni-cup::before {\n  content: \"\\ea8b\";\n}\n\n.lni-customer::before {\n  content: \"\\ea8c\";\n}\n\n.lni-cut::before {\n  content: \"\\ea8d\";\n}\n\n.lni-dashboard::before {\n  content: \"\\ea8e\";\n}\n\n.lni-database::before {\n  content: \"\\ea8f\";\n}\n\n.lni-delivery::before {\n  content: \"\\ea90\";\n}\n\n.lni-dev::before {\n  content: \"\\ea91\";\n}\n\n.lni-diamond-alt::before {\n  content: \"\\ea92\";\n}\n\n.lni-diamond::before {\n  content: \"\\ea93\";\n}\n\n.lni-digitalocean::before {\n  content: \"\\ea94\";\n}\n\n.lni-diners-club::before {\n  content: \"\\ea95\";\n}\n\n.lni-dinner::before {\n  content: \"\\ea96\";\n}\n\n.lni-direction-alt::before {\n  content: \"\\ea97\";\n}\n\n.lni-direction-ltr::before {\n  content: \"\\ea98\";\n}\n\n.lni-direction-rtl::before {\n  content: \"\\ea99\";\n}\n\n.lni-direction::before {\n  content: \"\\ea9a\";\n}\n\n.lni-discord::before {\n  content: \"\\ea9b\";\n}\n\n.lni-discover::before {\n  content: \"\\ea9c\";\n}\n\n.lni-display-alt::before {\n  content: \"\\ea9d\";\n}\n\n.lni-display::before {\n  content: \"\\ea9e\";\n}\n\n.lni-docker::before {\n  content: \"\\ea9f\";\n}\n\n.lni-dollar::before {\n  content: \"\\eaa0\";\n}\n\n.lni-domain::before {\n  content: \"\\eaa1\";\n}\n\n.lni-download::before {\n  content: \"\\eaa2\";\n}\n\n.lni-dribbble::before {\n  content: \"\\eaa3\";\n}\n\n.lni-drop::before {\n  content: \"\\eaa4\";\n}\n\n.lni-dropbox-original::before {\n  content: \"\\eaa5\";\n}\n\n.lni-dropbox::before {\n  content: \"\\eaa6\";\n}\n\n.lni-drupal-original::before {\n  content: \"\\eaa7\";\n}\n\n.lni-drupal::before {\n  content: \"\\eaa8\";\n}\n\n.lni-dumbbell::before {\n  content: \"\\eaa9\";\n}\n\n.lni-edge::before {\n  content: \"\\eaaa\";\n}\n\n.lni-empty-file::before {\n  content: \"\\eaab\";\n}\n\n.lni-enter::before {\n  content: \"\\eaac\";\n}\n\n.lni-envato::before {\n  content: \"\\eaad\";\n}\n\n.lni-envelope::before {\n  content: \"\\eaae\";\n}\n\n.lni-eraser::before {\n  content: \"\\eaaf\";\n}\n\n.lni-euro::before {\n  content: \"\\eab0\";\n}\n\n.lni-exit-down::before {\n  content: \"\\eab1\";\n}\n\n.lni-exit-up::before {\n  content: \"\\eab2\";\n}\n\n.lni-exit::before {\n  content: \"\\eab3\";\n}\n\n.lni-eye::before {\n  content: \"\\eab4\";\n}\n\n.lni-facebook-filled::before {\n  content: \"\\eab5\";\n}\n\n.lni-facebook-messenger::before {\n  content: \"\\eab6\";\n}\n\n.lni-facebook-original::before {\n  content: \"\\eab7\";\n}\n\n.lni-facebook-oval::before {\n  content: \"\\eab8\";\n}\n\n.lni-facebook::before {\n  content: \"\\eab9\";\n}\n\n.lni-figma::before {\n  content: \"\\eaba\";\n}\n\n.lni-files::before {\n  content: \"\\eabb\";\n}\n\n.lni-firefox-original::before {\n  content: \"\\eabc\";\n}\n\n.lni-firefox::before {\n  content: \"\\eabd\";\n}\n\n.lni-fireworks::before {\n  content: \"\\eabe\";\n}\n\n.lni-first-aid::before {\n  content: \"\\eabf\";\n}\n\n.lni-flag-alt::before {\n  content: \"\\eac0\";\n}\n\n.lni-flag::before {\n  content: \"\\eac1\";\n}\n\n.lni-flags::before {\n  content: \"\\eac2\";\n}\n\n.lni-flickr::before {\n  content: \"\\eac3\";\n}\n\n.lni-flower::before {\n  content: \"\\eac4\";\n}\n\n.lni-folder::before {\n  content: \"\\eac5\";\n}\n\n.lni-forward::before {\n  content: \"\\eac6\";\n}\n\n.lni-frame-expand::before {\n  content: \"\\eac7\";\n}\n\n.lni-fresh-juice::before {\n  content: \"\\eac8\";\n}\n\n.lni-friendly::before {\n  content: \"\\eac9\";\n}\n\n.lni-full-screen::before {\n  content: \"\\eaca\";\n}\n\n.lni-funnel::before {\n  content: \"\\eacb\";\n}\n\n.lni-gallery::before {\n  content: \"\\eacc\";\n}\n\n.lni-game::before {\n  content: \"\\eacd\";\n}\n\n.lni-gatsby::before {\n  content: \"\\eace\";\n}\n\n.lni-gift::before {\n  content: \"\\eacf\";\n}\n\n.lni-git::before {\n  content: \"\\ead0\";\n}\n\n.lni-github-original::before {\n  content: \"\\ead1\";\n}\n\n.lni-github::before {\n  content: \"\\ead2\";\n}\n\n.lni-goodreads::before {\n  content: \"\\ead3\";\n}\n\n.lni-google-drive::before {\n  content: \"\\ead4\";\n}\n\n.lni-google-pay::before {\n  content: \"\\ead5\";\n}\n\n.lni-google-wallet::before {\n  content: \"\\ead6\";\n}\n\n.lni-google::before {\n  content: \"\\ead7\";\n}\n\n.lni-graduation::before {\n  content: \"\\ead8\";\n}\n\n.lni-graph::before {\n  content: \"\\ead9\";\n}\n\n.lni-grid-alt::before {\n  content: \"\\eada\";\n}\n\n.lni-grid::before {\n  content: \"\\eadb\";\n}\n\n.lni-grow::before {\n  content: \"\\eadc\";\n}\n\n.lni-hacker-news::before {\n  content: \"\\eadd\";\n}\n\n.lni-hammer::before {\n  content: \"\\eade\";\n}\n\n.lni-hand::before {\n  content: \"\\eadf\";\n}\n\n.lni-handshake::before {\n  content: \"\\eae0\";\n}\n\n.lni-happy::before {\n  content: \"\\eae1\";\n}\n\n.lni-harddrive::before {\n  content: \"\\eae2\";\n}\n\n.lni-headphone-alt::before {\n  content: \"\\eae3\";\n}\n\n.lni-headphone::before {\n  content: \"\\eae4\";\n}\n\n.lni-heart-filled::before {\n  content: \"\\eae5\";\n}\n\n.lni-heart-monitor::before {\n  content: \"\\eae6\";\n}\n\n.lni-heart::before {\n  content: \"\\eae7\";\n}\n\n.lni-helicopter::before {\n  content: \"\\eae8\";\n}\n\n.lni-helmet::before {\n  content: \"\\eae9\";\n}\n\n.lni-help::before {\n  content: \"\\eaea\";\n}\n\n.lni-highlight-alt::before {\n  content: \"\\eaeb\";\n}\n\n.lni-highlight::before {\n  content: \"\\eaec\";\n}\n\n.lni-home::before {\n  content: \"\\eaed\";\n}\n\n.lni-hospital::before {\n  content: \"\\eaee\";\n}\n\n.lni-hourglass::before {\n  content: \"\\eaef\";\n}\n\n.lni-html5::before {\n  content: \"\\eaf0\";\n}\n\n.lni-image::before {\n  content: \"\\eaf1\";\n}\n\n.lni-imdb::before {\n  content: \"\\eaf2\";\n}\n\n.lni-inbox::before {\n  content: \"\\eaf3\";\n}\n\n.lni-indent-decrease::before {\n  content: \"\\eaf4\";\n}\n\n.lni-indent-increase::before {\n  content: \"\\eaf5\";\n}\n\n.lni-infinite::before {\n  content: \"\\eaf6\";\n}\n\n.lni-information::before {\n  content: \"\\eaf7\";\n}\n\n.lni-instagram-filled::before {\n  content: \"\\eaf8\";\n}\n\n.lni-instagram-original::before {\n  content: \"\\eaf9\";\n}\n\n.lni-instagram::before {\n  content: \"\\eafa\";\n}\n\n.lni-invention::before {\n  content: \"\\eafb\";\n}\n\n.lni-invest-monitor::before {\n  content: \"\\eafc\";\n}\n\n.lni-investment::before {\n  content: \"\\eafd\";\n}\n\n.lni-island::before {\n  content: \"\\eafe\";\n}\n\n.lni-italic::before {\n  content: \"\\eaff\";\n}\n\n.lni-java::before {\n  content: \"\\eb00\";\n}\n\n.lni-javascript::before {\n  content: \"\\eb01\";\n}\n\n.lni-jcb::before {\n  content: \"\\eb02\";\n}\n\n.lni-joomla-original::before {\n  content: \"\\eb03\";\n}\n\n.lni-joomla::before {\n  content: \"\\eb04\";\n}\n\n.lni-jsfiddle::before {\n  content: \"\\eb05\";\n}\n\n.lni-juice::before {\n  content: \"\\eb06\";\n}\n\n.lni-key::before {\n  content: \"\\eb07\";\n}\n\n.lni-keyboard::before {\n  content: \"\\eb08\";\n}\n\n.lni-keyword-research::before {\n  content: \"\\eb09\";\n}\n\n.lni-laptop-phone::before {\n  content: \"\\eb0a\";\n}\n\n.lni-laptop::before {\n  content: \"\\eb0b\";\n}\n\n.lni-laravel::before {\n  content: \"\\eb0c\";\n}\n\n.lni-layers::before {\n  content: \"\\eb0d\";\n}\n\n.lni-layout::before {\n  content: \"\\eb0e\";\n}\n\n.lni-leaf::before {\n  content: \"\\eb0f\";\n}\n\n.lni-library::before {\n  content: \"\\eb10\";\n}\n\n.lni-license::before {\n  content: \"\\eb11\";\n}\n\n.lni-lifering::before {\n  content: \"\\eb12\";\n}\n\n.lni-line-dashed::before {\n  content: \"\\eb13\";\n}\n\n.lni-line-dotted::before {\n  content: \"\\eb14\";\n}\n\n.lni-line-double::before {\n  content: \"\\eb15\";\n}\n\n.lni-line-spacing::before {\n  content: \"\\eb16\";\n}\n\n.lni-line::before {\n  content: \"\\eb17\";\n}\n\n.lni-lineicons-alt::before {\n  content: \"\\eb18\";\n}\n\n.lni-lineicons::before {\n  content: \"\\eb19\";\n}\n\n.lni-link::before {\n  content: \"\\eb1a\";\n}\n\n.lni-linkedin-original::before {\n  content: \"\\eb1b\";\n}\n\n.lni-linkedin::before {\n  content: \"\\eb1c\";\n}\n\n.lni-list::before {\n  content: \"\\eb1d\";\n}\n\n.lni-lock-alt::before {\n  content: \"\\eb1e\";\n}\n\n.lni-lock::before {\n  content: \"\\eb1f\";\n}\n\n.lni-magento::before {\n  content: \"\\eb20\";\n}\n\n.lni-magnet::before {\n  content: \"\\eb21\";\n}\n\n.lni-magnifier::before {\n  content: \"\\eb22\";\n}\n\n.lni-mailchimp::before {\n  content: \"\\eb23\";\n}\n\n.lni-map-marker::before {\n  content: \"\\eb24\";\n}\n\n.lni-map::before {\n  content: \"\\eb25\";\n}\n\n.lni-markdown::before {\n  content: \"\\eb26\";\n}\n\n.lni-mashroom::before {\n  content: \"\\eb27\";\n}\n\n.lni-mastercard::before {\n  content: \"\\eb28\";\n}\n\n.lni-medium::before {\n  content: \"\\eb29\";\n}\n\n.lni-menu::before {\n  content: \"\\eb2a\";\n}\n\n.lni-mic::before {\n  content: \"\\eb2b\";\n}\n\n.lni-microphone::before {\n  content: \"\\eb2c\";\n}\n\n.lni-microscope::before {\n  content: \"\\eb2d\";\n}\n\n.lni-microsoft-edge::before {\n  content: \"\\eb2e\";\n}\n\n.lni-microsoft::before {\n  content: \"\\eb2f\";\n}\n\n.lni-minus::before {\n  content: \"\\eb30\";\n}\n\n.lni-mobile::before {\n  content: \"\\eb31\";\n}\n\n.lni-money-location::before {\n  content: \"\\eb32\";\n}\n\n.lni-money-protection::before {\n  content: \"\\eb33\";\n}\n\n.lni-more-alt::before {\n  content: \"\\eb34\";\n}\n\n.lni-more::before {\n  content: \"\\eb35\";\n}\n\n.lni-mouse::before {\n  content: \"\\eb36\";\n}\n\n.lni-move::before {\n  content: \"\\eb37\";\n}\n\n.lni-music::before {\n  content: \"\\eb38\";\n}\n\n.lni-netlify::before {\n  content: \"\\eb39\";\n}\n\n.lni-network::before {\n  content: \"\\eb3a\";\n}\n\n.lni-night::before {\n  content: \"\\eb3b\";\n}\n\n.lni-nodejs-alt::before {\n  content: \"\\eb3c\";\n}\n\n.lni-nodejs::before {\n  content: \"\\eb3d\";\n}\n\n.lni-notepad::before {\n  content: \"\\eb3e\";\n}\n\n.lni-npm::before {\n  content: \"\\eb3f\";\n}\n\n.lni-offer::before {\n  content: \"\\eb40\";\n}\n\n.lni-opera::before {\n  content: \"\\eb41\";\n}\n\n.lni-package::before {\n  content: \"\\eb42\";\n}\n\n.lni-page-break::before {\n  content: \"\\eb43\";\n}\n\n.lni-pagination::before {\n  content: \"\\eb44\";\n}\n\n.lni-paint-bucket::before {\n  content: \"\\eb45\";\n}\n\n.lni-paint-roller::before {\n  content: \"\\eb46\";\n}\n\n.lni-pallet::before {\n  content: \"\\eb47\";\n}\n\n.lni-paperclip::before {\n  content: \"\\eb48\";\n}\n\n.lni-patreon::before {\n  content: \"\\eb49\";\n}\n\n.lni-pause::before {\n  content: \"\\eb4a\";\n}\n\n.lni-paypal-original::before {\n  content: \"\\eb4b\";\n}\n\n.lni-paypal::before {\n  content: \"\\eb4c\";\n}\n\n.lni-pencil-alt::before {\n  content: \"\\eb4d\";\n}\n\n.lni-pencil::before {\n  content: \"\\eb4e\";\n}\n\n.lni-phone-set::before {\n  content: \"\\eb4f\";\n}\n\n.lni-phone::before {\n  content: \"\\eb50\";\n}\n\n.lni-php::before {\n  content: \"\\eb51\";\n}\n\n.lni-pie-chart::before {\n  content: \"\\eb52\";\n}\n\n.lni-pilcrow::before {\n  content: \"\\eb53\";\n}\n\n.lni-pin::before {\n  content: \"\\eb54\";\n}\n\n.lni-pinterest::before {\n  content: \"\\eb55\";\n}\n\n.lni-pizza::before {\n  content: \"\\eb56\";\n}\n\n.lni-plane::before {\n  content: \"\\eb57\";\n}\n\n.lni-play-store::before {\n  content: \"\\eb58\";\n}\n\n.lni-play::before {\n  content: \"\\eb59\";\n}\n\n.lni-playstation::before {\n  content: \"\\eb5a\";\n}\n\n.lni-plug::before {\n  content: \"\\eb5b\";\n}\n\n.lni-plus::before {\n  content: \"\\eb5c\";\n}\n\n.lni-pointer-down::before {\n  content: \"\\eb5d\";\n}\n\n.lni-pointer-left::before {\n  content: \"\\eb5e\";\n}\n\n.lni-pointer-right::before {\n  content: \"\\eb5f\";\n}\n\n.lni-pointer-top::before {\n  content: \"\\eb60\";\n}\n\n.lni-pointer::before {\n  content: \"\\eb61\";\n}\n\n.lni-popup::before {\n  content: \"\\eb62\";\n}\n\n.lni-postcard::before {\n  content: \"\\eb63\";\n}\n\n.lni-pound::before {\n  content: \"\\eb64\";\n}\n\n.lni-power-switch::before {\n  content: \"\\eb65\";\n}\n\n.lni-printer::before {\n  content: \"\\eb66\";\n}\n\n.lni-producthunt::before {\n  content: \"\\eb67\";\n}\n\n.lni-protection::before {\n  content: \"\\eb68\";\n}\n\n.lni-pulse::before {\n  content: \"\\eb69\";\n}\n\n.lni-pyramids::before {\n  content: \"\\eb6a\";\n}\n\n.lni-python::before {\n  content: \"\\eb6b\";\n}\n\n.lni-question-circle::before {\n  content: \"\\eb6c\";\n}\n\n.lni-quora::before {\n  content: \"\\eb6d\";\n}\n\n.lni-quotation::before {\n  content: \"\\eb6e\";\n}\n\n.lni-radio-button::before {\n  content: \"\\eb6f\";\n}\n\n.lni-rain::before {\n  content: \"\\eb70\";\n}\n\n.lni-react::before {\n  content: \"\\eb73\";\n}\n\n.lni-reddit::before {\n  content: \"\\eb71\";\n}\n\n.lni-reload::before {\n  content: \"\\eb72\";\n}\n\n.lni-remove-file::before {\n  content: \"\\eb74\";\n}\n\n.lni-reply::before {\n  content: \"\\eb75\";\n}\n\n.lni-restaurant::before {\n  content: \"\\eb76\";\n}\n\n.lni-revenue::before {\n  content: \"\\eb77\";\n}\n\n.lni-road::before {\n  content: \"\\eb78\";\n}\n\n.lni-rocket::before {\n  content: \"\\eb79\";\n}\n\n.lni-rss-feed::before {\n  content: \"\\eb7a\";\n}\n\n.lni-ruler-alt::before {\n  content: \"\\eb7b\";\n}\n\n.lni-ruler-pencil::before {\n  content: \"\\eb7c\";\n}\n\n.lni-ruler::before {\n  content: \"\\eb7d\";\n}\n\n.lni-rupee::before {\n  content: \"\\eb7e\";\n}\n\n.lni-sad::before {\n  content: \"\\eb7f\";\n}\n\n.lni-save::before {\n  content: \"\\eb80\";\n}\n\n.lni-school-bench-alt::before {\n  content: \"\\eb81\";\n}\n\n.lni-school-bench::before {\n  content: \"\\eb82\";\n}\n\n.lni-scooter::before {\n  content: \"\\eb83\";\n}\n\n.lni-scroll-down::before {\n  content: \"\\eb84\";\n}\n\n.lni-search-alt::before {\n  content: \"\\eb85\";\n}\n\n.lni-search::before {\n  content: \"\\eb86\";\n}\n\n.lni-select::before {\n  content: \"\\eb87\";\n}\n\n.lni-seo::before {\n  content: \"\\eb88\";\n}\n\n.lni-service::before {\n  content: \"\\eb89\";\n}\n\n.lni-share-alt-1::before {\n  content: \"\\eb8a\";\n}\n\n.lni-share-alt::before {\n  content: \"\\eb8b\";\n}\n\n.lni-share::before {\n  content: \"\\eb8c\";\n}\n\n.lni-shield::before {\n  content: \"\\eb8d\";\n}\n\n.lni-shift-left::before {\n  content: \"\\eb8e\";\n}\n\n.lni-shift-right::before {\n  content: \"\\eb8f\";\n}\n\n.lni-ship::before {\n  content: \"\\eb90\";\n}\n\n.lni-shopify::before {\n  content: \"\\eb91\";\n}\n\n.lni-shopping-basket::before {\n  content: \"\\eb92\";\n}\n\n.lni-shortcode::before {\n  content: \"\\eb93\";\n}\n\n.lni-shovel::before {\n  content: \"\\eb94\";\n}\n\n.lni-shuffle::before {\n  content: \"\\eb95\";\n}\n\n.lni-signal::before {\n  content: \"\\eb96\";\n}\n\n.lni-sketch::before {\n  content: \"\\eb97\";\n}\n\n.lni-skipping-rope::before {\n  content: \"\\eb98\";\n}\n\n.lni-skype::before {\n  content: \"\\eb99\";\n}\n\n.lni-slack-line::before {\n  content: \"\\eb9a\";\n}\n\n.lni-slack::before {\n  content: \"\\eb9b\";\n}\n\n.lni-slice::before {\n  content: \"\\eb9c\";\n}\n\n.lni-slideshare::before {\n  content: \"\\eb9d\";\n}\n\n.lni-slim::before {\n  content: \"\\eb9e\";\n}\n\n.lni-smile::before {\n  content: \"\\eb9f\";\n}\n\n.lni-snapchat::before {\n  content: \"\\eba0\";\n}\n\n.lni-sort-alpha-asc::before {\n  content: \"\\eba1\";\n}\n\n.lni-sort-amount-asc::before {\n  content: \"\\eba2\";\n}\n\n.lni-sort-amount-dsc::before {\n  content: \"\\eba3\";\n}\n\n.lni-soundcloud-original::before {\n  content: \"\\eba4\";\n}\n\n.lni-soundcloud::before {\n  content: \"\\eba5\";\n}\n\n.lni-speechless::before {\n  content: \"\\eba6\";\n}\n\n.lni-spellcheck::before {\n  content: \"\\eba7\";\n}\n\n.lni-spinner-arrow::before {\n  content: \"\\eba8\";\n}\n\n.lni-spinner-solid::before {\n  content: \"\\eba9\";\n}\n\n.lni-spinner::before {\n  content: \"\\ebaa\";\n}\n\n.lni-spotify-original::before {\n  content: \"\\ebab\";\n}\n\n.lni-spotify::before {\n  content: \"\\ebac\";\n}\n\n.lni-spray::before {\n  content: \"\\ebad\";\n}\n\n.lni-sprout::before {\n  content: \"\\ebae\";\n}\n\n.lni-squarespace::before {\n  content: \"\\ebaf\";\n}\n\n.lni-stackoverflow::before {\n  content: \"\\ebb0\";\n}\n\n.lni-stamp::before {\n  content: \"\\ebb1\";\n}\n\n.lni-star-empty::before {\n  content: \"\\ebb2\";\n}\n\n.lni-star-filled::before {\n  content: \"\\ebb3\";\n}\n\n.lni-star-half::before {\n  content: \"\\ebb4\";\n}\n\n.lni-star::before {\n  content: \"\\ebb5\";\n}\n\n.lni-stats-down::before {\n  content: \"\\ebb6\";\n}\n\n.lni-stats-up::before {\n  content: \"\\ebb7\";\n}\n\n.lni-steam::before {\n  content: \"\\ebb8\";\n}\n\n.lni-sthethoscope::before {\n  content: \"\\ebb9\";\n}\n\n.lni-stop::before {\n  content: \"\\ebba\";\n}\n\n.lni-strikethrough::before {\n  content: \"\\ebbb\";\n}\n\n.lni-stripe::before {\n  content: \"\\ebbc\";\n}\n\n.lni-stumbleupon::before {\n  content: \"\\ebbd\";\n}\n\n.lni-sun::before {\n  content: \"\\ebbe\";\n}\n\n.lni-support::before {\n  content: \"\\ebbf\";\n}\n\n.lni-surf-board::before {\n  content: \"\\ebc0\";\n}\n\n.lni-suspect::before {\n  content: \"\\ebc1\";\n}\n\n.lni-swift::before {\n  content: \"\\ebc2\";\n}\n\n.lni-syringe::before {\n  content: \"\\ebc3\";\n}\n\n.lni-tab::before {\n  content: \"\\ebc4\";\n}\n\n.lni-tag::before {\n  content: \"\\ebc5\";\n}\n\n.lni-target-customer::before {\n  content: \"\\ebc6\";\n}\n\n.lni-target-revenue::before {\n  content: \"\\ebc7\";\n}\n\n.lni-target::before {\n  content: \"\\ebc8\";\n}\n\n.lni-taxi::before {\n  content: \"\\ebc9\";\n}\n\n.lni-teabag::before {\n  content: \"\\ebca\";\n}\n\n.lni-telegram-original::before {\n  content: \"\\ebcb\";\n}\n\n.lni-telegram::before {\n  content: \"\\ebcc\";\n}\n\n.lni-text-align-center::before {\n  content: \"\\ebcd\";\n}\n\n.lni-text-align-justify::before {\n  content: \"\\ebce\";\n}\n\n.lni-text-align-left::before {\n  content: \"\\ebcf\";\n}\n\n.lni-text-align-right::before {\n  content: \"\\ebd0\";\n}\n\n.lni-text-format-remove::before {\n  content: \"\\ebd4\";\n}\n\n.lni-text-format::before {\n  content: \"\\ebd1\";\n}\n\n.lni-thought::before {\n  content: \"\\ebd2\";\n}\n\n.lni-thumbs-down::before {\n  content: \"\\ebd3\";\n}\n\n.lni-thumbs-up::before {\n  content: \"\\ebd5\";\n}\n\n.lni-thunder-alt::before {\n  content: \"\\ebd6\";\n}\n\n.lni-thunder::before {\n  content: \"\\ebd7\";\n}\n\n.lni-ticket-alt::before {\n  content: \"\\ebd8\";\n}\n\n.lni-ticket::before {\n  content: \"\\ebd9\";\n}\n\n.lni-tiktok::before {\n  content: \"\\ebda\";\n}\n\n.lni-timer::before {\n  content: \"\\ebdb\";\n}\n\n.lni-tounge::before {\n  content: \"\\ebdc\";\n}\n\n.lni-train-alt::before {\n  content: \"\\ebdd\";\n}\n\n.lni-train::before {\n  content: \"\\ebde\";\n}\n\n.lni-trash-can::before {\n  content: \"\\ebdf\";\n}\n\n.lni-travel::before {\n  content: \"\\ebe0\";\n}\n\n.lni-tree::before {\n  content: \"\\ebe1\";\n}\n\n.lni-trees::before {\n  content: \"\\ebe2\";\n}\n\n.lni-trello::before {\n  content: \"\\ebe3\";\n}\n\n.lni-trowel::before {\n  content: \"\\ebe4\";\n}\n\n.lni-tshirt::before {\n  content: \"\\ebe5\";\n}\n\n.lni-tumblr::before {\n  content: \"\\ebe6\";\n}\n\n.lni-twitch::before {\n  content: \"\\ebe7\";\n}\n\n.lni-twitter-filled::before {\n  content: \"\\ebe8\";\n}\n\n.lni-twitter-original::before {\n  content: \"\\ebe9\";\n}\n\n.lni-twitter::before {\n  content: \"\\ebea\";\n}\n\n.lni-ubuntu::before {\n  content: \"\\ebeb\";\n}\n\n.lni-underline::before {\n  content: \"\\ebec\";\n}\n\n.lni-unlink::before {\n  content: \"\\ebed\";\n}\n\n.lni-unlock::before {\n  content: \"\\ebee\";\n}\n\n.lni-unsplash::before {\n  content: \"\\ebef\";\n}\n\n.lni-upload::before {\n  content: \"\\ebf0\";\n}\n\n.lni-user::before {\n  content: \"\\ebf1\";\n}\n\n.lni-users::before {\n  content: \"\\ebf6\";\n}\n\n.lni-ux::before {\n  content: \"\\ebf2\";\n}\n\n.lni-vector::before {\n  content: \"\\ebf3\";\n}\n\n.lni-video::before {\n  content: \"\\ebf4\";\n}\n\n.lni-vimeo::before {\n  content: \"\\ebf5\";\n}\n\n.lni-visa::before {\n  content: \"\\ebf7\";\n}\n\n.lni-vk::before {\n  content: \"\\ebf8\";\n}\n\n.lni-volume-high::before {\n  content: \"\\ebf9\";\n}\n\n.lni-volume-low::before {\n  content: \"\\ebfa\";\n}\n\n.lni-volume-medium::before {\n  content: \"\\ebfb\";\n}\n\n.lni-volume-mute::before {\n  content: \"\\ebfc\";\n}\n\n.lni-volume::before {\n  content: \"\\ebfd\";\n}\n\n.lni-wallet::before {\n  content: \"\\ebfe\";\n}\n\n.lni-warning::before {\n  content: \"\\ebff\";\n}\n\n.lni-website-alt::before {\n  content: \"\\ec00\";\n}\n\n.lni-website::before {\n  content: \"\\ec01\";\n}\n\n.lni-wechat::before {\n  content: \"\\ec02\";\n}\n\n.lni-weight::before {\n  content: \"\\ec03\";\n}\n\n.lni-whatsapp::before {\n  content: \"\\ec04\";\n}\n\n.lni-wheelbarrow::before {\n  content: \"\\ec05\";\n}\n\n.lni-wheelchair::before {\n  content: \"\\ec06\";\n}\n\n.lni-windows::before {\n  content: \"\\ec07\";\n}\n\n.lni-wordpress-filled::before {\n  content: \"\\ec08\";\n}\n\n.lni-wordpress::before {\n  content: \"\\ec09\";\n}\n\n.lni-world-alt::before {\n  content: \"\\ec0a\";\n}\n\n.lni-world::before {\n  content: \"\\ec0c\";\n}\n\n.lni-write::before {\n  content: \"\\ec0b\";\n}\n\n.lni-xbox::before {\n  content: \"\\ec0d\";\n}\n\n.lni-yahoo::before {\n  content: \"\\ec0e\";\n}\n\n.lni-ycombinator::before {\n  content: \"\\ec0f\";\n}\n\n.lni-yen::before {\n  content: \"\\ec10\";\n}\n\n.lni-youtube::before {\n  content: \"\\ec13\";\n}\n\n.lni-zip::before {\n  content: \"\\ec11\";\n}\n\n.lni-zoom-in::before {\n  content: \"\\ec12\";\n}\n\n.lni-zoom-out::before {\n  content: \"\\ec14\";\n}\n"
  },
  {
    "path": "public/assets/css/main.css",
    "content": "/*===========================\n\t\tCOMMON css\n===========================*/\n@import url(\"https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap\");\nhtml {\n  scroll-behavior: smooth; }\n\nbody {\n  font-family: \"Inter\", sans-serif !important;\n  font-weight: normal;\n  font-style: normal;\n  color: #5d657b;\n  overflow-x: hidden;\n  background: #f1f5f9; }\n\n* {\n  margin: 0;\n  padding: 0;\n  -webkit-box-sizing: border-box;\n  -moz-box-sizing: border-box;\n  box-sizing: border-box; }\n\na:focus,\ninput:focus,\ntextarea:focus,\nbutton:focus,\n.btn:focus,\n.btn.focus,\n.btn:not(:disabled):not(.disabled).active,\n.btn:not(:disabled):not(.disabled):active {\n  text-decoration: none;\n  outline: none;\n  -webkit-box-shadow: none;\n  -moz-box-shadow: none;\n  box-shadow: none; }\n\na:hover {\n  color: #4a6cf7; }\n\nbutton,\na {\n  -webkit-transition: all 0.3s ease-out 0s;\n  -moz-transition: all 0.3s ease-out 0s;\n  -ms-transition: all 0.3s ease-out 0s;\n  -o-transition: all 0.3s ease-out 0s;\n  transition: all 0.3s ease-out 0s; }\n\na,\na:focus,\na:hover {\n  text-decoration: none; }\n\ni,\na {\n  display: inline-block; }\n\naudio,\ncanvas,\niframe,\nimg,\nsvg,\nvideo {\n  vertical-align: middle; }\n\nh1 a,\nh2 a,\nh3 a,\nh4 a,\nh5 a,\nh6 a {\n  color: inherit; }\n\nul,\nol {\n  margin: 0px;\n  padding: 0px;\n  list-style-type: none; }\n\np {\n  font-size: 16px;\n  font-weight: 400;\n  line-height: 25px;\n  margin: 0px; }\n\n.img-bg {\n  background-position: center center;\n  background-size: cover;\n  background-repeat: no-repeat;\n  width: 100%;\n  height: 100%; }\n\n.para-width-500 {\n  max-width: 500px;\n  width: 100%; }\n\n@media (max-width: 767px) {\n  .container {\n    padding: 0 30px; } }\n\n/* ========== cart style ========== */\n.card-style {\n  background: #fff;\n  box-sizing: border-box;\n  padding: 25px 30px;\n  position: relative;\n  border: 1px solid #e2e8f0;\n  box-shadow: 0px 10px 20px rgba(200, 208, 216, 0.3);\n  border-radius: 10px; }\n  @media (max-width: 767px) {\n    .card-style {\n      padding: 20px; } }\n  .card-style .jvm-zoom-btn {\n    position: absolute;\n    display: inline-flex;\n    justify-content: center;\n    align-items: center;\n    width: 30px;\n    height: 30px;\n    border: 1px solid rgba(0, 0, 0, 0.1);\n    right: 30px;\n    bottom: 30px;\n    cursor: pointer; }\n    .card-style .jvm-zoom-btn.jvm-zoomin {\n      bottom: 70px; }\n  .card-style .dropdown-toggle {\n    border: none;\n    background: none; }\n    .card-style .dropdown-toggle::after {\n      display: none; }\n  .card-style .dropdown-menu {\n    -webkit-box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.07);\n    -moz-box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.07);\n    box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.07); }\n    .card-style .dropdown-menu li:hover a {\n      color: #4a6cf7 !important; }\n    .card-style .dropdown-menu li a {\n      display: block;\n      font-size: 14px; }\n\n/* ======= Border Radius ========= */\n.radius-4 {\n  border-radius: 4px; }\n\n.radius-10 {\n  border-radius: 10px; }\n\n.radius-30 {\n  border-radius: 30px; }\n\n.radius-50 {\n  border-radius: 50px; }\n\n.radius-full {\n  border-radius: 50%; }\n\n.scroll-top {\n  width: 45px;\n  height: 45px;\n  background: #4a6cf7;\n  display: none;\n  justify-content: center;\n  align-items: center;\n  font-size: 18px;\n  color: #fff;\n  border-radius: 5px;\n  position: fixed;\n  bottom: 30px;\n  right: 30px;\n  z-index: 9;\n  cursor: pointer;\n  -webkit-transition: all 0.3s ease-out 0s;\n  -moz-transition: all 0.3s ease-out 0s;\n  -ms-transition: all 0.3s ease-out 0s;\n  -o-transition: all 0.3s ease-out 0s;\n  transition: all 0.3s ease-out 0s; }\n  .scroll-top:hover {\n    color: #fff;\n    background: rgba(74, 108, 247, 0.8); }\n\n.form-control:focus {\n  box-shadow: none; }\n\n.form-control.is-valid:focus,\n.was-validated .form-control:valid:focus,\n.form-control.is-invalid:focus,\n.was-validated .form-control:invalid:focus,\n.form-check-input.is-valid:focus,\n.was-validated .form-check-input:valid:focus,\n.form-check-input.is-invalid:focus,\n.was-validated .form-check-input:invalid:focus,\n.form-check-input:focus,\n.radio-style.radio-success .form-check-input:focus,\n.radio-style.radio-warning .form-check-input:focus,\n.radio-style.radio-danger .form-check-input:focus {\n  box-shadow: none; }\n\n.hover-underline:hover {\n  text-decoration: underline; }\n\n/* ============= typography css ============= */\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\n.h1,\n.h2,\n.h3,\n.h4,\n.h5,\n.h6 {\n  color: #262d3f;\n  margin: 0; }\n\nh1,\n.h1 {\n  font-size: 32px;\n  font-weight: 700; }\n\nh2,\n.h2 {\n  font-size: 28px;\n  font-weight: 600; }\n\nh3,\n.h3 {\n  font-size: 24px;\n  font-weight: 500; }\n\nh4,\n.h4 {\n  font-size: 20px;\n  font-weight: 600; }\n\nh5,\n.h5 {\n  font-size: 16px;\n  font-weight: 700; }\n\nh6,\n.h6 {\n  font-size: 16px;\n  font-weight: 600; }\n\n.text-bold {\n  font-weight: 700; }\n\n.text-semi-bold {\n  font-weight: 600; }\n\n.text-medium {\n  font-weight: 500; }\n\n.text-regular {\n  font-weight: 400; }\n\n.text-light {\n  font-weight: 300; }\n\n.text-sm {\n  font-size: 14px;\n  line-height: 22px; }\n\n/* ========== breadcrumb ============ */\n.breadcrumb-wrapper {\n  display: flex;\n  justify-content: flex-end; }\n  @media (max-width: 767px) {\n    .breadcrumb-wrapper {\n      justify-content: flex-start; } }\n  .breadcrumb-wrapper .breadcrumb li {\n    font-size: 14px;\n    color: #4a6cf7; }\n    .breadcrumb-wrapper .breadcrumb li a {\n      color: #5d657b; }\n      .breadcrumb-wrapper .breadcrumb li a:hover {\n        color: #4a6cf7; }\n\n/* ========== Buttons css ========== */\n/* buttons base styles */\n.main-btn {\n  display: inline-block;\n  text-align: center;\n  white-space: nowrap;\n  vertical-align: middle;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  -ms-user-select: none;\n  user-select: none;\n  padding: 15px 45px;\n  font-weight: 500;\n  font-size: 14px;\n  line-height: 24px;\n  border-radius: 4px;\n  cursor: pointer;\n  z-index: 5;\n  transition: all 0.4s ease-in-out;\n  border: 1px solid transparent;\n  overflow: hidden; }\n  .main-btn:hover {\n    color: inherit; }\n\n.btn-sm {\n  padding: 10px 20px;\n  font-weight: 400; }\n\n/* buttons hover effect */\n.btn-hover {\n  position: relative;\n  overflow: hidden; }\n  .btn-hover::after {\n    content: \"\";\n    position: absolute;\n    width: 0%;\n    height: 0%;\n    border-radius: 50%;\n    background: rgba(255, 255, 255, 0.05);\n    top: 50%;\n    left: 50%;\n    padding: 50%;\n    z-index: -1;\n    -webkit-transition: all 0.3s ease-out 0s;\n    -moz-transition: all 0.3s ease-out 0s;\n    -ms-transition: all 0.3s ease-out 0s;\n    -o-transition: all 0.3s ease-out 0s;\n    transition: all 0.3s ease-out 0s;\n    -webkit-transform: translate3d(-50%, -50%, 0) scale(0);\n    -moz-transform: translate3d(-50%, -50%, 0) scale(0);\n    -ms-transform: translate3d(-50%, -50%, 0) scale(0);\n    -o-transform: translate3d(-50%, -50%, 0) scale(0);\n    transform: translate3d(-50%, -50%, 0) scale(0); }\n  .btn-hover:hover::after {\n    -webkit-transform: translate3d(-50%, -50%, 0) scale(1.3);\n    -moz-transform: translate3d(-50%, -50%, 0) scale(1.3);\n    -ms-transform: translate3d(-50%, -50%, 0) scale(1.3);\n    -o-transform: translate3d(-50%, -50%, 0) scale(1.3);\n    transform: translate3d(-50%, -50%, 0) scale(1.3); }\n\n/* primary buttons */\n.primary-btn {\n  background: #4a6cf7;\n  color: #fff; }\n  .primary-btn:hover {\n    color: #fff; }\n\n.primary-btn-outline {\n  background: transparent;\n  color: #4a6cf7;\n  border-color: #4a6cf7; }\n  .primary-btn-outline:hover {\n    color: #fff;\n    background: #4a6cf7; }\n\n/* secondary buttons */\n.secondary-btn {\n  background: #00c1f8;\n  color: #fff; }\n  .secondary-btn:hover {\n    color: #fff; }\n\n.secondary-btn-outline {\n  background: transparent;\n  color: #00c1f8;\n  border-color: #00c1f8; }\n  .secondary-btn-outline:hover {\n    color: #fff;\n    background: #00c1f8; }\n\n/* success buttons */\n.success-btn {\n  background: #219653;\n  color: #fff; }\n  .success-btn:hover {\n    color: #fff; }\n\n.success-btn-outline {\n  background: transparent;\n  color: #219653;\n  border-color: #219653; }\n  .success-btn-outline:hover {\n    color: #fff;\n    background: #219653; }\n\n/* danger buttons */\n.danger-btn {\n  background: #d50100;\n  color: #fff; }\n  .danger-btn:hover {\n    color: #fff; }\n\n.danger-btn-outline {\n  background: transparent;\n  color: #d50100;\n  border-color: #d50100; }\n  .danger-btn-outline:hover {\n    color: #fff;\n    background: #d50100; }\n\n/* warning buttons */\n.warning-btn {\n  background: #f7c800;\n  color: #fff; }\n  .warning-btn:hover {\n    color: #fff; }\n\n.warning-btn-outline {\n  background: transparent;\n  color: #f7c800;\n  border-color: #f7c800; }\n  .warning-btn-outline:hover {\n    color: #fff;\n    background: #f7c800; }\n\n/* info buttons */\n.info-btn {\n  background: #97ca31;\n  color: #fff; }\n  .info-btn:hover {\n    color: #fff; }\n\n.info-btn-outline {\n  background: transparent;\n  color: #97ca31;\n  border-color: #97ca31; }\n  .info-btn-outline:hover {\n    color: #fff;\n    background: #97ca31; }\n\n/* dark buttons */\n.dark-btn {\n  background: #262d3f;\n  color: #fff; }\n  .dark-btn:hover {\n    color: #fff; }\n\n.dark-btn-outline {\n  background: transparent;\n  color: #262d3f;\n  border-color: #262d3f; }\n  .dark-btn-outline:hover {\n    color: #fff;\n    background: #262d3f; }\n\n/* light buttons */\n.light-btn {\n  background: #efefef;\n  color: #262d3f; }\n  .light-btn:hover {\n    color: #262d3f; }\n\n.light-btn-outline {\n  background: transparent;\n  color: #262d3f;\n  border-color: #efefef; }\n  .light-btn-outline:hover {\n    color: #262d3f;\n    background: #efefef; }\n\n/* active buttons */\n.active-btn {\n  background: #4a6cf7;\n  color: #fff; }\n  .active-btn:hover {\n    color: #fff; }\n\n.active-btn-outline {\n  background: transparent;\n  color: #4a6cf7;\n  border-color: #4a6cf7; }\n  .active-btn-outline:hover {\n    color: #fff;\n    background: #4a6cf7; }\n\n/* deactive buttons */\n.deactive-btn {\n  background: #cbe1ff;\n  color: #4a6cf7; }\n  .deactive-btn:hover {\n    color: #4a6cf7; }\n\n.deactive-btn-outline {\n  background: transparent;\n  color: #4a6cf7;\n  border-color: #cbe1ff; }\n  .deactive-btn-outline:hover {\n    color: #4a6cf7;\n    background: #cbe1ff; }\n\n/* =========  square-btn ========= */\n.square-btn {\n  border-radius: 0px; }\n\n/* =========  rounded-md ========= */\n.rounded-md {\n  border-radius: 10px; }\n\n/* =========  rounded-full ========= */\n.rounded-full {\n  border-radius: 30px; }\n\n/* ========== buttons group css ========= */\n.buttons-group {\n  display: flex;\n  flex-wrap: wrap;\n  margin: 0 -10px; }\n  .buttons-group li {\n    margin: 10px; }\n\n/* ====== Status Button ====== */\n.status-btn {\n  padding: 7px 15px;\n  border-radius: 30px;\n  font-size: 14px;\n  font-weight: 400; }\n  .status-btn.primary-btn {\n    color: #fff;\n    background: #4a6cf7; }\n  .status-btn.active-btn {\n    color: #4a6cf7;\n    background: rgba(74, 108, 247, 0.1); }\n  .status-btn.close-btn {\n    color: #d50100;\n    background: rgba(213, 1, 0, 0.1); }\n  .status-btn.warning-btn {\n    color: #f7c800;\n    background: rgba(247, 200, 0, 0.1); }\n  .status-btn.info-btn {\n    color: #97ca31;\n    background: rgba(151, 202, 49, 0.1); }\n  .status-btn.success-btn {\n    color: #219653;\n    background: rgba(33, 150, 83, 0.1); }\n  .status-btn.secondary-btn {\n    color: #00c1f8;\n    background: rgba(0, 193, 248, 0.1); }\n  .status-btn.dark-btn {\n    color: #262d3f;\n    background: rgba(38, 45, 63, 0.1); }\n  .status-btn.orange-btn {\n    color: #f2994a;\n    background: rgba(242, 153, 74, 0.1); }\n\n/* ============ alerts css ============ */\n.alert-box {\n  display: flex;\n  position: relative;\n  margin-bottom: 20px; }\n  @media (max-width: 767px) {\n    .alert-box {\n      padding-left: 0px !important; } }\n  .alert-box .left {\n    max-width: 75px;\n    width: 100%;\n    height: 100%;\n    border-radius: 4px;\n    background: #d50100;\n    position: absolute;\n    left: 0;\n    top: 0;\n    display: flex;\n    justify-content: center;\n    align-items: center; }\n    @media (max-width: 767px) {\n      .alert-box .left {\n        display: none; } }\n    .alert-box .left h5 {\n      -webkit-transform: rotate(-90deg);\n      -moz-transform: rotate(-90deg);\n      -ms-transform: rotate(-90deg);\n      -o-transform: rotate(-90deg);\n      transform: rotate(-90deg);\n      color: #fff; }\n  .alert-box .alert {\n    margin-bottom: 0px;\n    padding: 25px 40px; }\n    @media (max-width: 767px) {\n      .alert-box .alert {\n        padding: 20px; } }\n\n/* Alert Primary */\n.primary-alert .left {\n  background: #4a6cf7; }\n\n.primary-alert .alert {\n  color: #4a6cf7;\n  border: 1px solid #4a6cf7;\n  background: rgba(74, 108, 247, 0.2);\n  width: 100%; }\n  .primary-alert .alert .alert-heading {\n    color: #4a6cf7;\n    margin-bottom: 15px; }\n\n/* Alert Danger */\n.danger-alert .left {\n  background: #d50100; }\n\n.danger-alert .alert {\n  color: #d50100;\n  border: 1px solid #d50100;\n  background: rgba(213, 1, 0, 0.2);\n  width: 100%; }\n  .danger-alert .alert .alert-heading {\n    color: #d50100;\n    margin-bottom: 15px; }\n\n/* Alert warning */\n.warning-alert .left {\n  background: #f7c800; }\n\n.warning-alert .alert {\n  color: #f7c800;\n  border: 1px solid #f7c800;\n  background: rgba(247, 200, 0, 0.2);\n  width: 100%; }\n  .warning-alert .alert .alert-heading {\n    color: #f7c800;\n    margin-bottom: 15px; }\n\n/* Alert warning */\n.warning-alert .left {\n  background: #f7c800; }\n\n.warning-alert .alert {\n  color: #f7c800;\n  border: 1px solid #f7c800;\n  background: rgba(247, 200, 0, 0.2);\n  width: 100%; }\n  .warning-alert .alert .alert-heading {\n    color: #f7c800;\n    margin-bottom: 15px; }\n\n/* Alert info */\n.info-alert .left {\n  background: #97ca31; }\n\n.info-alert .alert {\n  color: #97ca31;\n  border: 1px solid #97ca31;\n  background: rgba(151, 202, 49, 0.2);\n  width: 100%; }\n  .info-alert .alert .alert-heading {\n    color: #97ca31;\n    margin-bottom: 15px; }\n\n/* Alert success */\n.success-alert .left {\n  background: #219653; }\n\n.success-alert .alert {\n  color: #219653;\n  border: 1px solid #219653;\n  background: rgba(33, 150, 83, 0.2);\n  width: 100%; }\n  .success-alert .alert .alert-heading {\n    color: #219653;\n    margin-bottom: 15px; }\n\n/* Alert secondary */\n.secondary-alert .left {\n  background: #00c1f8; }\n\n.secondary-alert .alert {\n  color: #00c1f8;\n  border: 1px solid #00c1f8;\n  background: rgba(0, 193, 248, 0.2);\n  width: 100%; }\n  .secondary-alert .alert .alert-heading {\n    color: #00c1f8;\n    margin-bottom: 15px; }\n\n/* Alert gray */\n.gray-alert .left {\n  background: #5d657b; }\n\n.gray-alert .alert {\n  color: #5d657b;\n  border: 1px solid #5d657b;\n  background: rgba(93, 101, 123, 0.2);\n  width: 100%; }\n  .gray-alert .alert .alert-heading {\n    color: #5d657b;\n    margin-bottom: 15px; }\n\n/* Alert black */\n.black-alert .left {\n  background: #000; }\n\n.black-alert .alert {\n  color: #000;\n  border: 1px solid #000;\n  background: rgba(0, 0, 0, 0.2);\n  width: 100%; }\n  .black-alert .alert .alert-heading {\n    color: #000;\n    margin-bottom: 15px; }\n\n/* Alert orange */\n.orange-alert .left {\n  background: #f2994a; }\n\n.orange-alert .alert {\n  color: #f2994a;\n  border: 1px solid #f2994a;\n  background: rgba(242, 153, 74, 0.2);\n  width: 100%; }\n  .orange-alert .alert .alert-heading {\n    color: #f2994a;\n    margin-bottom: 15px; }\n\n/* ========== cards css =========== */\n/* card-style-1 */\n.card-style-1 {\n  background: #fff;\n  border: 1px solid #efefef;\n  border-radius: 10px;\n  padding: 25px 0;\n  position: relative;\n  -webkit-transition: all 0.3s ease-out 0s;\n  -moz-transition: all 0.3s ease-out 0s;\n  -ms-transition: all 0.3s ease-out 0s;\n  -o-transition: all 0.3s ease-out 0s;\n  transition: all 0.3s ease-out 0s; }\n  .card-style-1:hover {\n    -webkit-box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1);\n    -moz-box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1);\n    box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1); }\n  .card-style-1 .card-meta {\n    display: flex;\n    align-items: center;\n    margin-bottom: 15px;\n    padding: 0 30px; }\n    @media (max-width: 767px) {\n      .card-style-1 .card-meta {\n        padding: 0 20px; } }\n    .card-style-1 .card-meta .image {\n      max-width: 40px;\n      width: 100%;\n      border-radius: 50%;\n      overflow: hidden;\n      margin-right: 12px; }\n      .card-style-1 .card-meta .image img {\n        width: 100%; }\n    .card-style-1 .card-meta .text p {\n      color: #262d3f; }\n      .card-style-1 .card-meta .text p a {\n        color: inherit; }\n        .card-style-1 .card-meta .text p a:hover {\n          color: #4a6cf7; }\n  .card-style-1 .card-image {\n    border-radius: 10px;\n    margin-bottom: 25px;\n    overflow: hidden; }\n    .card-style-1 .card-image a {\n      display: block; }\n    .card-style-1 .card-image img {\n      width: 100%; }\n  .card-style-1 .card-content {\n    padding: 0px 30px; }\n    @media (max-width: 767px) {\n      .card-style-1 .card-content {\n        padding: 0px 20px; } }\n    .card-style-1 .card-content h4 a {\n      color: inherit;\n      margin-bottom: 15px;\n      display: block; }\n      .card-style-1 .card-content h4 a:hover {\n        color: #4a6cf7; }\n\n/* card-style-2 */\n.card-style-2 {\n  background: #fff;\n  border: 1px solid #efefef;\n  border-radius: 4px;\n  padding: 20px;\n  -webkit-transition: all 0.3s ease-out 0s;\n  -moz-transition: all 0.3s ease-out 0s;\n  -ms-transition: all 0.3s ease-out 0s;\n  -o-transition: all 0.3s ease-out 0s;\n  transition: all 0.3s ease-out 0s; }\n  .card-style-2:hover {\n    -webkit-box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1);\n    -moz-box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1);\n    box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1); }\n  .card-style-2 .card-image {\n    border-radius: 4px;\n    margin-bottom: 30px;\n    overflow: hidden; }\n    .card-style-2 .card-image a {\n      display: block; }\n    .card-style-2 .card-image img {\n      width: 100%; }\n  .card-style-2 .card-content {\n    padding: 0px 10px; }\n    @media (max-width: 767px) {\n      .card-style-2 .card-content {\n        padding: 0px; } }\n    .card-style-2 .card-content h4 a {\n      color: inherit;\n      margin-bottom: 15px;\n      display: block; }\n      .card-style-2 .card-content h4 a:hover {\n        color: #4a6cf7; }\n\n/* card-style-3 */\n.card-style-3 {\n  background: #fff;\n  border: 1px solid #efefef;\n  border-radius: 4px;\n  padding: 25px 30px;\n  -webkit-transition: all 0.3s ease-out 0s;\n  -moz-transition: all 0.3s ease-out 0s;\n  -ms-transition: all 0.3s ease-out 0s;\n  -o-transition: all 0.3s ease-out 0s;\n  transition: all 0.3s ease-out 0s; }\n  .card-style-3:hover {\n    -webkit-box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1);\n    -moz-box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1);\n    box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1); }\n  .card-style-3 .card-content h4 a {\n    color: inherit;\n    margin-bottom: 15px;\n    display: block; }\n    .card-style-3 .card-content h4 a:hover {\n      color: #4a6cf7; }\n  .card-style-3 .card-content a.read-more {\n    font-weight: 500;\n    color: #262d3f;\n    margin-top: 20px; }\n    .card-style-3 .card-content a.read-more:hover {\n      color: #4a6cf7;\n      letter-spacing: 2px; }\n\n/* ======= icon-card ======== */\n.icon-card {\n  display: flex;\n  align-items: center;\n  background: #fff;\n  padding: 30px 20px;\n  border: 1px solid #e2e8f0;\n  box-shadow: 0px 10px 20px rgba(200, 208, 216, 0.3);\n  border-radius: 10px; }\n  .icon-card.icon-card-3 {\n    display: block;\n    padding: 0px; }\n    .icon-card.icon-card-3 .card-content {\n      display: flex;\n      padding: 20px;\n      padding-bottom: 0; }\n  @media only screen and (min-width: 1200px) and (max-width: 1399px) {\n    .icon-card h6 {\n      font-size: 15px; } }\n  @media only screen and (min-width: 1200px) and (max-width: 1399px) {\n    .icon-card h3 {\n      font-size: 20px; } }\n  .icon-card.icon-card-2 {\n    display: block; }\n    .icon-card.icon-card-2 .progress {\n      height: 7px; }\n      .icon-card.icon-card-2 .progress .progress-bar {\n        border-radius: 4px; }\n  .icon-card .icon {\n    max-width: 46px;\n    width: 100%;\n    height: 46px;\n    border-radius: 10px;\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    font-size: 24px;\n    margin-right: 20px;\n    background: rgba(74, 108, 247, 0.1);\n    color: #4a6cf7;\n    font-weight: 700; }\n    @media only screen and (min-width: 1200px) and (max-width: 1399px) {\n      .icon-card .icon {\n        margin-right: 10px; } }\n    .icon-card .icon.purple {\n      background: rgba(155, 81, 224, 0.1);\n      color: #9b51e0; }\n    .icon-card .icon.success {\n      background: rgba(33, 150, 83, 0.1);\n      color: #219653; }\n    .icon-card .icon.primary {\n      background: rgba(74, 108, 247, 0.1);\n      color: #4a6cf7; }\n    .icon-card .icon.orange {\n      background: rgba(242, 153, 74, 0.1);\n      color: #f2994a; }\n    .icon-card .icon.opacity-100.purple {\n      background: #9b51e0;\n      color: #fff; }\n    .icon-card .icon.opacity-100.success {\n      background: #219653;\n      color: #fff; }\n    .icon-card .icon.opacity-100.primary {\n      background: #4a6cf7;\n      color: #fff; }\n    .icon-card .icon.opacity-100.orange {\n      background: #f2994a;\n      color: #fff; }\n    .icon-card .icon.opacity-100.deep-blue {\n      background: #345d9d;\n      color: #fff; }\n\n/* =========== tables css =========== */\n.table {\n  border-collapse: inherit;\n  border-spacing: 0px; }\n  .table > :not(caption) > * > * {\n    padding: 15px 0;\n    border-bottom-color: #efefef;\n    vertical-align: middle; }\n  .table > :not(:last-child) > :last-child > * {\n    border-bottom-color: #efefef; }\n  .table tbody tr:first-child > * {\n    padding-top: 20px; }\n  .table tbody tr:last-child > * {\n    border-bottom-color: transparent;\n    padding-bottom: 0px; }\n  .table th h6 {\n    font-weight: 500;\n    color: #262d3f;\n    font-size: 14px; }\n  .table td.min-width {\n    padding: 5px; }\n    @media (max-width: 767px) {\n      .table td.min-width {\n        min-width: 150px; } }\n  .table td p {\n    font-size: 14px;\n    line-height: 1.5;\n    color: #5d657b; }\n    .table td p a {\n      color: inherit; }\n      .table td p a:hover {\n        color: #4a6cf7; }\n  .table .lead-info {\n    min-width: 200px; }\n  .table .lead-email {\n    min-width: 150px;\n    white-space: nowrap; }\n  .table .lead-phone {\n    min-width: 160px; }\n  .table .lead-company {\n    min-width: 180px; }\n  .table .referrals-image {\n    min-width: 150px; }\n    .table .referrals-image .image {\n      width: 55px;\n      max-width: 100%;\n      height: 55px;\n      border-radius: 4px;\n      overflow: hidden; }\n      .table .referrals-image .image img {\n        width: 100%; }\n  .table .lead {\n    display: flex;\n    align-items: center; }\n    .table .lead .lead-image {\n      max-width: 50px;\n      width: 100%;\n      height: 50px;\n      border-radius: 50%;\n      overflow: hidden;\n      margin-right: 15px; }\n      .table .lead .lead-image img {\n        width: 100%; }\n    .table .lead .lead-text {\n      width: 100%; }\n  .table .employee-image {\n    width: 50px;\n    max-width: 100%;\n    height: 50px;\n    border-radius: 50%;\n    overflow: hidden;\n    margin-right: 15px; }\n    .table .employee-image img {\n      width: 100%; }\n  .table .action {\n    display: flex;\n    align-items: center; }\n    .table .action button {\n      border: none;\n      background: transparent;\n      padding: 0px 6px;\n      font-size: 18px; }\n      .table .action button.edit:hover {\n        color: #4a6cf7; }\n      .table .action button::after {\n        display: none; }\n    .table .action .dropdown-menu {\n      -webkit-box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.07);\n      -moz-box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.07);\n      box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.07); }\n      .table .action .dropdown-menu li:hover a {\n        color: #4a6cf7 !important; }\n      .table .action .dropdown-menu li a {\n        display: block; }\n\n.top-selling-table tr th,\n.top-selling-table tr td {\n  vertical-align: middle;\n  padding: 10px 5px; }\n\n.top-selling-table tr .min-width {\n  min-width: 80px;\n  white-space: nowrap; }\n\n.top-selling-table .form-check-input[type=\"checkbox\"] {\n  margin-left: 5px; }\n\n.top-selling-table .product {\n  display: flex;\n  align-items: center;\n  min-width: 150px; }\n  .top-selling-table .product .image {\n    border-radius: 4px;\n    overflow: hidden;\n    margin-right: 15px;\n    max-width: 50px;\n    width: 100%;\n    height: 50px; }\n    .top-selling-table .product .image img {\n      width: 100%; }\n  .top-selling-table .product p {\n    width: 100%; }\n\n@media (max-width: 767px) {\n  .referrals-table-card .title .right {\n    width: 100%; } }\n\n@media only screen and (min-width: 550px) and (max-width: 767px) {\n  .referrals-table-card .title .right {\n    width: auto; } }\n\n.referrals-table-card .referrals-table td {\n  padding: 10px; }\n\n/* ===== lead-table ===== */\n.lead-table th,\n.lead-table td {\n  padding: 10px 5px; }\n\n.lead-table .name {\n  min-width: 120px; }\n\n.lead-table .email {\n  min-width: 130px; }\n\n.lead-table .project {\n  min-width: 150px; }\n\n.lead-table .status {\n  min-width: 120px;\n  text-align: center; }\n\n.lead-table .action {\n  min-width: 60px; }\n\n.clients-table-card .table .employee-info {\n  min-width: 150px; }\n\n.clients-table th,\n.clients-table td {\n  padding: 5px; }\n  .clients-table th.min-width,\n  .clients-table td.min-width {\n    min-width: 150px; }\n\n.clients-table .employee-image {\n  margin-right: 0px; }\n\n/* =========== form elements css ========== */\n/* ===== input style ===== */\n.input-style-1 {\n  position: relative;\n  margin-bottom: 30px; }\n  .input-style-1 label {\n    font-size: 14px;\n    font-weight: 500;\n    color: #262d3f;\n    display: block;\n    margin-bottom: 10px; }\n  .input-style-1 input,\n  .input-style-1 textarea {\n    width: 100%;\n    background: rgba(239, 239, 239, 0.5);\n    border: 1px solid #e5e5e5;\n    border-radius: 4px;\n    padding: 16px;\n    color: #5d657b;\n    resize: none;\n    transition: all 0.3s; }\n    .input-style-1 input:focus,\n    .input-style-1 textarea:focus {\n      border-color: #4a6cf7;\n      background: #fff; }\n    .input-style-1 input[type=\"date\"], .input-style-1 input[type=\"time\"],\n    .input-style-1 textarea[type=\"date\"],\n    .input-style-1 textarea[type=\"time\"] {\n      background: transparent; }\n\n.input-style-2 {\n  position: relative;\n  margin-bottom: 30px;\n  z-index: 1; }\n  .input-style-2 label {\n    font-size: 14px;\n    font-weight: 500;\n    color: #262d3f;\n    display: block;\n    margin-bottom: 10px; }\n  .input-style-2 input,\n  .input-style-2 textarea {\n    width: 100%;\n    background: rgba(239, 239, 239, 0.5);\n    border: 1px solid #e5e5e5;\n    border-radius: 4px;\n    padding: 16px;\n    color: #5d657b;\n    resize: none;\n    transition: all 0.3s; }\n    .input-style-2 input:focus,\n    .input-style-2 textarea:focus {\n      border-color: #4a6cf7;\n      background: #fff; }\n    .input-style-2 input[type=\"date\"], .input-style-2 input[type=\"time\"],\n    .input-style-2 textarea[type=\"date\"],\n    .input-style-2 textarea[type=\"time\"] {\n      background: transparent; }\n  .input-style-2 input[type=\"date\"]::-webkit-inner-spin-button,\n  .input-style-2 input[type=\"date\"]::-webkit-calendar-picker-indicator {\n    opacity: 0; }\n  .input-style-2 input[type=\"date\"] ~ .icon {\n    z-index: -1; }\n  .input-style-2 .icon {\n    position: absolute;\n    right: 0;\n    bottom: 0;\n    padding: 17px; }\n\n.input-style-3 {\n  position: relative;\n  margin-bottom: 30px; }\n  .input-style-3 label {\n    font-size: 14px;\n    font-weight: 500;\n    color: #262d3f;\n    display: block;\n    margin-bottom: 10px; }\n  .input-style-3 input,\n  .input-style-3 textarea {\n    width: 100%;\n    background: rgba(239, 239, 239, 0.5);\n    border: 1px solid #e5e5e5;\n    border-radius: 4px;\n    padding: 16px;\n    padding-left: 45px;\n    color: #5d657b;\n    resize: none;\n    transition: all 0.3s; }\n    .input-style-3 input:focus,\n    .input-style-3 textarea:focus {\n      border-color: #4a6cf7;\n      background: #fff; }\n  .input-style-3 .icon {\n    position: absolute;\n    left: 0;\n    top: 0;\n    height: 100%;\n    padding: 16px; }\n\n/* ========= select style ========== */\n.select-style-1 {\n  margin-bottom: 30px; }\n  .select-style-1 label {\n    font-size: 14px;\n    font-weight: 500;\n    color: #262d3f;\n    display: block;\n    margin-bottom: 10px; }\n  .select-style-1 .select-position {\n    position: relative; }\n    .select-style-1 .select-position::after {\n      border-bottom: 2px solid #5d657b;\n      border-right: 2px solid #5d657b;\n      content: \"\";\n      display: block;\n      height: 10px;\n      width: 10px;\n      margin-top: -5px;\n      pointer-events: none;\n      position: absolute;\n      right: 16px;\n      top: 50%;\n      -webkit-transform: rotate(45deg);\n      -ms-transform: rotate(45deg);\n      transform: rotate(45deg);\n      -webkit-transition: all 0.15s ease-in-out;\n      transition: all 0.15s ease-in-out; }\n    .select-style-1 .select-position.select-sm::after {\n      margin-top: -8px; }\n    .select-style-1 .select-position.select-sm select {\n      padding-top: 8px;\n      padding-bottom: 8px;\n      font-size: 14px; }\n    .select-style-1 .select-position select {\n      width: 100%;\n      background: transparent;\n      border: 1px solid #e5e5e5;\n      border-radius: 10px;\n      padding: 16px;\n      padding-right: 38px;\n      color: #5d657b;\n      appearance: none;\n      -webkit-appearance: none;\n      -moz-appearance: none;\n      -webkit-transition: all 0.3s ease-out 0s;\n      -moz-transition: all 0.3s ease-out 0s;\n      -ms-transition: all 0.3s ease-out 0s;\n      -o-transition: all 0.3s ease-out 0s;\n      transition: all 0.3s ease-out 0s; }\n      .select-style-1 .select-position select:focus {\n        border-color: #4a6cf7;\n        outline: none; }\n      .select-style-1 .select-position select.light-bg {\n        background: rgba(239, 239, 239, 0.5); }\n      .select-style-1 .select-position select.light-bg:focus {\n        background: #fff; }\n      .select-style-1 .select-position select.radius-30 {\n        border-radius: 30px; }\n\n.select-style-2 {\n  margin-bottom: 30px; }\n  .select-style-2 .select-position {\n    position: relative; }\n    .select-style-2 .select-position.select-sm::after {\n      margin-top: -8px; }\n    .select-style-2 .select-position.select-sm::before {\n      margin-top: 0; }\n    .select-style-2 .select-position.select-sm select {\n      padding-top: 8px;\n      padding-bottom: 8px;\n      font-size: 14px; }\n    .select-style-2 .select-position::before, .select-style-2 .select-position::after {\n      content: \"\";\n      display: block;\n      height: 8px;\n      width: 8px;\n      pointer-events: none;\n      position: absolute;\n      right: 16px;\n      top: 50%;\n      -webkit-transform: rotate(45deg);\n      -ms-transform: rotate(45deg);\n      transform: rotate(45deg);\n      -webkit-transition: all 0.15s ease-in-out;\n      transition: all 0.15s ease-in-out; }\n    .select-style-2 .select-position::before {\n      margin-top: 0px;\n      border-bottom: 1px solid #5d657b;\n      border-right: 1px solid #5d657b; }\n    .select-style-2 .select-position::after {\n      margin-top: -8px;\n      border-top: 1px solid #5d657b;\n      border-left: 1px solid #5d657b; }\n    .select-style-2 .select-position select {\n      width: 100%;\n      background: transparent;\n      border: 1px solid #e5e5e5;\n      border-radius: 10px;\n      padding: 16px;\n      padding-right: 38px;\n      color: #5d657b;\n      appearance: none;\n      -webkit-appearance: none;\n      -moz-appearance: none;\n      -webkit-transition: all 0.3s ease-out 0s;\n      -moz-transition: all 0.3s ease-out 0s;\n      -ms-transition: all 0.3s ease-out 0s;\n      -o-transition: all 0.3s ease-out 0s;\n      transition: all 0.3s ease-out 0s; }\n      .select-style-2 .select-position select:focus {\n        border-color: #4a6cf7;\n        outline: none; }\n      .select-style-2 .select-position select.light-bg {\n        background: rgba(239, 239, 239, 0.5); }\n      .select-style-2 .select-position select.light-bg:focus {\n        background: #fff; }\n      .select-style-2 .select-position select.select-sm {\n        padding-top: 8px;\n        padding-bottom: 8px;\n        font-size: 14px; }\n\n.select-style-3 {\n  margin-bottom: 30px; }\n  .select-style-3 .select-position {\n    position: relative; }\n    .select-style-3 .select-position::after {\n      border-bottom: 2px solid #5d657b;\n      border-right: 2px solid #5d657b;\n      content: \"\";\n      display: block;\n      height: 10px;\n      width: 10px;\n      margin-top: -7px;\n      pointer-events: none;\n      position: absolute;\n      right: 0px;\n      top: 50%;\n      -webkit-transform: rotate(45deg);\n      -ms-transform: rotate(45deg);\n      transform: rotate(45deg);\n      -webkit-transition: all 0.15s ease-in-out;\n      transition: all 0.15s ease-in-out; }\n    .select-style-3 .select-position.select-sm::after {\n      margin-top: -8px; }\n    .select-style-3 .select-position.select-sm select {\n      padding-top: 8px;\n      padding-bottom: 8px;\n      font-size: 14px; }\n    .select-style-3 .select-position select {\n      width: 100%;\n      background: transparent;\n      border: transparent;\n      border-radius: 10px;\n      padding-right: 38px;\n      color: #000;\n      appearance: none;\n      -webkit-appearance: none;\n      -moz-appearance: none;\n      -webkit-transition: all 0.3s ease-out 0s;\n      -moz-transition: all 0.3s ease-out 0s;\n      -ms-transition: all 0.3s ease-out 0s;\n      -o-transition: all 0.3s ease-out 0s;\n      transition: all 0.3s ease-out 0s; }\n      .select-style-3 .select-position select:focus {\n        border-color: #4a6cf7;\n        outline: none; }\n      .select-style-3 .select-position select.light-bg {\n        background: rgba(239, 239, 239, 0.5); }\n\n.toggle-switch {\n  padding-left: 60px;\n  min-height: 30px; }\n  .toggle-switch .form-check-input {\n    width: 50px;\n    height: 28px;\n    margin-left: -60px;\n    cursor: pointer; }\n  .toggle-switch label {\n    margin-top: 6px;\n    font-size: 14px;\n    color: #262d3f;\n    cursor: pointer;\n    user-select: none; }\n\n.checkbox-style {\n  padding-left: 40px;\n  min-height: 28px; }\n  .checkbox-style .form-check-input {\n    width: 28px;\n    height: 28px;\n    border-radius: 4px;\n    margin-left: -40px;\n    cursor: pointer; }\n    .checkbox-style .form-check-input:disabled {\n      cursor: auto; }\n  .checkbox-style .form-check-input:disabled ~ label {\n    cursor: auto; }\n  .checkbox-style label {\n    margin-top: 6px;\n    cursor: pointer;\n    user-select: none; }\n  .checkbox-style.checkbox-success .form-check-input:checked {\n    background-color: #219653;\n    border-color: #219653; }\n  .checkbox-style.checkbox-warning .form-check-input:checked {\n    background-color: #f7c800;\n    border-color: #f7c800; }\n  .checkbox-style.checkbox-danger .form-check-input:checked {\n    background-color: #d50100;\n    border-color: #d50100; }\n\n.radio-style {\n  padding-left: 40px;\n  min-height: 28px; }\n  .radio-style .form-check-input {\n    width: 28px;\n    height: 28px;\n    border-radius: 50%;\n    margin-left: -40px;\n    cursor: pointer; }\n    .radio-style .form-check-input:disabled {\n      cursor: auto; }\n  .radio-style .form-check-input:disabled ~ label {\n    cursor: auto; }\n  .radio-style label {\n    margin-top: 6px;\n    cursor: pointer;\n    user-select: none; }\n  .radio-style.radio-success .form-check-input:checked {\n    background-color: #219653;\n    border-color: #219653; }\n  .radio-style.radio-warning .form-check-input:checked {\n    background-color: #f7c800;\n    border-color: #f7c800; }\n  .radio-style.radio-danger .form-check-input:checked {\n    background-color: #d50100;\n    border-color: #d50100; }\n\n@media (max-width: 767px) {\n  .button-group .main-btn {\n    width: 100%; } }\n\n.buy-sell-form .input-group {\n  display: flex; }\n  .buy-sell-form .input-group input {\n    width: 60%;\n    background: transparent;\n    border: 1px solid #e2e8f0;\n    border-radius: 4px;\n    padding: 8px 16px;\n    font-size: 14px;\n    color: #5d657b; }\n    .buy-sell-form .input-group input:focus {\n      border-color: #4a6cf7; }\n  .buy-sell-form .input-group .select-style-1 {\n    width: 40%; }\n    .buy-sell-form .input-group .select-style-1 .select-position::after {\n      width: 8px;\n      height: 8px; }\n  .buy-sell-form .input-group select {\n    border: 1px solid #e2e8f0;\n    border-radius: 0px 4px 4px 0px;\n    padding: 8px 16px;\n    padding-right: 24px;\n    font-size: 14px;\n    color: #5d657b; }\n\n.buy-sell-form .buy-sell-btn .main-btn {\n  display: block;\n  width: 100%;\n  font-weight: 500; }\n  .buy-sell-form .buy-sell-btn .main-btn:hover {\n    box-shadow: 0px 5px 20px rgba(0, 0, 0, 0.1); }\n  .buy-sell-form .buy-sell-btn .main-btn.success-btn {\n    background: #08c18d; }\n  .buy-sell-form .buy-sell-btn .main-btn.danger-btn {\n    background: #eb5757; }\n\n.buy-sell-form .field-group-2 label {\n  font-size: 12px; }\n\n.buy-sell-form .field-group-2 .input-group input {\n  font-size: 12px;\n  width: 70%; }\n\n.buy-sell-form .field-group-2 .input-group span {\n  font-size: 12px;\n  padding: 8px 16px;\n  width: 30%;\n  background: #e2e8f0;\n  text-align: center;\n  border-radius: 0px 4px 4px 0px;\n  border: 1px solid #e2e8f0; }\n\n.buy-sell-form .input-group-2 label {\n  font-size: 12px;\n  color: #5d657b;\n  margin-bottom: 8px;\n  display: block; }\n\n.buy-sell-form .input-group-2 .select-position::after {\n  width: 8px;\n  height: 8px; }\n\n.buy-sell-form .input-group-2 select {\n  padding: 8px 12px;\n  font-size: 12px;\n  color: #5d657b;\n  border: 1px solid #e2e8f0;\n  border-radius: 4px;\n  width: 100%; }\n\n/* ============= notification css ============= */\n.single-notification {\n  display: flex;\n  justify-content: space-between;\n  align-items: flex-start;\n  padding: 20px 0;\n  border-bottom: 1px solid #efefef; }\n  .single-notification.readed {\n    opacity: 0.7; }\n  .single-notification:first-child {\n    padding-top: 0px; }\n  .single-notification:last-child {\n    padding-bottom: 0px;\n    border-bottom: 0px; }\n  .single-notification .checkbox {\n    max-width: 50px;\n    width: 100%;\n    padding-top: 10px; }\n    @media (max-width: 767px) {\n      .single-notification .checkbox {\n        display: none; } }\n    .single-notification .checkbox input {\n      background-color: #efefef;\n      border-color: #e5e5e5; }\n      .single-notification .checkbox input:checked {\n        background-color: #4a6cf7;\n        border-color: #4a6cf7; }\n  .single-notification .notification {\n    display: flex;\n    width: 100%; }\n    @media (max-width: 767px) {\n      .single-notification .notification {\n        flex-direction: column; } }\n    .single-notification .notification .image {\n      max-width: 50px;\n      width: 100%;\n      height: 50px;\n      border-radius: 50%;\n      overflow: hidden;\n      color: #fff;\n      display: flex;\n      justify-content: center;\n      align-items: center;\n      font-weight: 600;\n      margin-right: 15px; }\n      @media (max-width: 767px) {\n        .single-notification .notification .image {\n          margin-bottom: 15px; } }\n      .single-notification .notification .image img {\n        width: 100%; }\n    .single-notification .notification .content {\n      display: block;\n      max-width: 800px; }\n      .single-notification .notification .content h6 {\n        margin-bottom: 15px; }\n      .single-notification .notification .content p {\n        margin-bottom: 10px; }\n  .single-notification .action {\n    display: inline-flex;\n    justify-content: flex-end;\n    padding-top: 10px; }\n    @media (max-width: 767px) {\n      .single-notification .action {\n        display: none; } }\n    .single-notification .action button {\n      border: none;\n      background: transparent;\n      color: #5d657b;\n      margin-left: 20px;\n      font-size: 18px; }\n      .single-notification .action button.delete-btn:hover {\n        color: #d50100; }\n    .single-notification .action .dropdown-toggle::after {\n      display: none; }\n\n/* ========== header css ========== */\n.header {\n  padding: 30px 0;\n  background: #fff; }\n  .header .header-left .menu-toggle-btn .main-btn {\n    padding: 0px 15px;\n    height: 46px;\n    line-height: 46px;\n    border-radius: 10px; }\n  .header .header-left .header-search form {\n    max-width: 270px;\n    position: relative; }\n    .header .header-left .header-search form input {\n      width: 100%;\n      border: 1px solid #efefef;\n      background: rgba(239, 239, 239, 0.5);\n      border-radius: 10px;\n      height: 46px;\n      padding-left: 44px;\n      -webkit-transition: all 0.3s ease-out 0s;\n      -moz-transition: all 0.3s ease-out 0s;\n      -ms-transition: all 0.3s ease-out 0s;\n      -o-transition: all 0.3s ease-out 0s;\n      transition: all 0.3s ease-out 0s; }\n      .header .header-left .header-search form input:focus {\n        border-color: #4a6cf7;\n        background: #fff; }\n    .header .header-left .header-search form button {\n      position: absolute;\n      border: none;\n      background: transparent;\n      left: 16px;\n      top: 0;\n      height: 46px;\n      color: #5d657b;\n      font-weight: 700; }\n  .header .header-right {\n    display: flex;\n    justify-content: flex-end; }\n    .header .header-right button {\n      border: 1px solid #efefef;\n      background: rgba(239, 239, 239, 0.5);\n      border-radius: 10px;\n      height: 46px;\n      width: 46px;\n      display: flex;\n      justify-content: center;\n      align-items: center;\n      position: relative; }\n      .header .header-right button::after {\n        display: none; }\n      .header .header-right button span {\n        position: absolute;\n        width: 20px;\n        height: 20px;\n        background: #4a6cf7;\n        color: #fff;\n        border-radius: 50%;\n        display: flex;\n        justify-content: center;\n        align-items: center;\n        top: -8px;\n        right: -6px;\n        font-size: 12px;\n        font-weight: 500; }\n    .header .header-right .dropdown-menu {\n      width: 350px;\n      border: 1px solid #efefef;\n      padding: 10px 10px;\n      -webkit-transition: all 0.3s ease-out 0s;\n      -moz-transition: all 0.3s ease-out 0s;\n      -ms-transition: all 0.3s ease-out 0s;\n      -o-transition: all 0.3s ease-out 0s;\n      transition: all 0.3s ease-out 0s;\n      top: 24px !important;\n      right: 0;\n      position: absolute;\n      transform: translate3d(0px, 60px, 0px);\n      border-radius: 10px; }\n      .header .header-right .dropdown-menu li {\n        padding: 3px 0px;\n        -webkit-transition: all 0.3s ease-out 0s;\n        -moz-transition: all 0.3s ease-out 0s;\n        -ms-transition: all 0.3s ease-out 0s;\n        -o-transition: all 0.3s ease-out 0s;\n        transition: all 0.3s ease-out 0s;\n        border-bottom: 1px solid #efefef;\n        position: relative;\n        z-index: 2; }\n        .header .header-right .dropdown-menu li:hover a {\n          color: #4a6cf7;\n          background: rgba(74, 108, 247, 0.05); }\n        .header .header-right .dropdown-menu li:last-child {\n          border-bottom: none; }\n        .header .header-right .dropdown-menu li a,\n        .header .header-right .dropdown-menu li span {\n          padding: 8px 12px;\n          display: flex;\n          color: rgba(0, 0, 0, 0.7);\n          border-radius: 6px; }\n        .header .header-right .dropdown-menu li span {\n          font-size: 14px;\n        }\n          .header .header-right .dropdown-menu li a .image {\n            max-width: 35px;\n            width: 100%;\n            height: 35px;\n            border-radius: 50%;\n            overflow: hidden;\n            margin-right: 12px; }\n            .header .header-right .dropdown-menu li a .image img {\n              width: 100%; }\n          .header .header-right .dropdown-menu li a .content {\n            width: 100%; }\n            .header .header-right .dropdown-menu li a .content h6 {\n              font-size: 14px;\n              margin-bottom: 5px;\n              font-weight: 600;\n              line-height: 1; }\n            .header .header-right .dropdown-menu li a .content p {\n              font-size: 14px;\n              color: rgba(0, 0, 0, 0.7);\n              margin-bottom: 0px;\n              line-height: 1.4; }\n            .header .header-right .dropdown-menu li a .content span {\n              font-size: 12px;\n              color: rgba(0, 0, 0, 0.5); }\n    .header .header-right .dropdown-box {\n      position: relative; }\n    .header .header-right .notification-box,\n    .header .header-right .header-message-box {\n      position: relative; }\n    .header .header-right .notification-box .dropdown-menu.dropdown-menu-end {\n      transform: translate3d(0px, 60px, 0px); }\n    .header .header-right .header-message-box .dropdown-menu.dropdown-menu-end {\n      transform: translate3d(0px, 60px, 0px); }\n    .header .header-right .profile-box {\n      display: flex;\n      position: relative; }\n      .header .header-right .profile-box button {\n        width: auto; }\n      .header .header-right .profile-box .dropdown-menu {\n        width: 230px; }\n        .header .header-right .profile-box .dropdown-menu.dropdown-menu-end {\n          transform: translate3d(0px, 60px, 0px); }\n        .header .header-right .profile-box .dropdown-menu li {\n          border-bottom: none; }\n          .header .header-right .profile-box .dropdown-menu li a {\n            font-size: 14px;\n            display: flex;\n            align-items: center; }\n            .header .header-right .profile-box .dropdown-menu li a i {\n              margin-right: 15px;\n              font-weight: 700; }\n      .header .header-right .profile-box .profile-info {\n        margin: 0 5px; }\n        .header .header-right .profile-box .profile-info .info {\n          display: flex;\n          align-items: center; }\n          .header .header-right .profile-box .profile-info .info .image {\n            border: 2px solid #f9f9f9;\n            -webkit-box-shadow: 0px 21px 25px rgba(218, 223, 227, 0.8);\n            -moz-box-shadow: 0px 21px 25px rgba(218, 223, 227, 0.8);\n            box-shadow: 0px 21px 25px rgba(218, 223, 227, 0.8);\n            width: 46px;\n            height: 46px;\n            border-radius: 50%;\n            margin-left: 16px;\n            position: relative; }\n            .header .header-right .profile-box .profile-info .info .image .status {\n              width: 16px;\n              height: 16px;\n              border-radius: 50%;\n              border: 2px solid #e5e5e5;\n              background: #219653;\n              position: absolute;\n              bottom: 0;\n              right: 0;\n              top: auto; }\n            .header .header-right .profile-box .profile-info .info .image img {\n              width: 100%;\n              border-radius: 50%; }\n\n/* ========== Dashboards css ================= */\n@media (max-width: 767px) {\n  #doughnutChart1 {\n    height: 250px !important; } }\n\n.legend3 li {\n  margin-right: 25px; }\n  .legend3 li div {\n    white-space: nowrap; }\n  .legend3 li .bg-color {\n    position: relative;\n    margin-left: 12px;\n    border-radius: 50%; }\n    .legend3 li .bg-color::after {\n      content: \"\";\n      position: absolute;\n      width: 12px;\n      height: 12px;\n      border-radius: 50%;\n      background: inherit;\n      left: -12px;\n      top: 5px; }\n  .legend3 li .text {\n    margin-left: 10px; }\n    .legend3 li .text p {\n      display: flex;\n      align-items: center;\n      width: 100%; }\n\n.todo-list-wrapper ul li.todo-list-item {\n  position: relative;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  padding-left: 20px;\n  margin-bottom: 25px; }\n  .todo-list-wrapper ul li.todo-list-item:last-child {\n    margin-bottom: 0px; }\n  .todo-list-wrapper ul li.todo-list-item::before {\n    content: \"\";\n    position: absolute;\n    left: 0;\n    top: 0;\n    width: 4px;\n    height: 100%; }\n  @media (max-width: 767px) {\n    .todo-list-wrapper ul li.todo-list-item {\n      display: block; }\n      .todo-list-wrapper ul li.todo-list-item .todo-status {\n        margin-top: 20px; } }\n  .todo-list-wrapper ul li.todo-list-item.success::before {\n    background: #219653; }\n  .todo-list-wrapper ul li.todo-list-item.primary::before {\n    background: #4a6cf7; }\n  .todo-list-wrapper ul li.todo-list-item.orange::before {\n    background: #f2994a; }\n  .todo-list-wrapper ul li.todo-list-item.danger::before {\n    background: #d50100; }\n\n/* ============ signin css ============= */\n.auth-row {\n  background: #fff;\n  border-radius: 4px;\n  overflow: hidden; }\n\n.auth-cover-wrapper {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  padding: 45px;\n  position: relative;\n  z-index: 1;\n  height: 100%; }\n  @media (max-width: 767px) {\n    .auth-cover-wrapper {\n      padding: 30px 20px; } }\n  .auth-cover-wrapper .auth-cover .title {\n    text-align: cover;\n    margin-bottom: 40px; }\n    @media (max-width: 767px) {\n      .auth-cover-wrapper .auth-cover .title h1 {\n        font-size: 24px; } }\n  .auth-cover-wrapper .auth-cover .cover-image {\n    max-width: 100%;\n    margin: auto; }\n    .auth-cover-wrapper .auth-cover .cover-image img {\n      width: 100%; }\n  .auth-cover-wrapper .auth-cover .shape-image {\n    position: absolute;\n    z-index: -1;\n    right: 0;\n    bottom: 5%; }\n\n.signin-wrapper {\n  background: #fff;\n  padding: 60px;\n  min-height: 600px;\n  display: flex;\n  align-items: center;\n  justify-content: center; }\n  @media only screen and (min-width: 992px) and (max-width: 1199px) {\n    .signin-wrapper {\n      padding: 40px; } }\n  @media (max-width: 767px) {\n    .signin-wrapper {\n      padding: 30px; } }\n  .signin-wrapper .form-wrapper {\n    width: 100%; }\n  .signin-wrapper .singin-option button {\n    font-size: 16px;\n    font-weight: 600; }\n    @media only screen and (min-width: 1200px) and (max-width: 1399px) {\n      .signin-wrapper .singin-option button {\n        padding-left: 25px;\n        padding-right: 25px; } }\n    @media only screen and (min-width: 992px) and (max-width: 1199px) {\n      .signin-wrapper .singin-option button {\n        padding-left: 30px;\n        padding-right: 30px; } }\n    @media (max-width: 767px) {\n      .signin-wrapper .singin-option button {\n        width: 100%; } }\n    @media only screen and (min-width: 550px) and (max-width: 767px) {\n      .signin-wrapper .singin-option button {\n        width: auto; } }\n  .signin-wrapper .singin-option a:hover {\n    text-decoration: underline; }\n\n/* ============ signup css ============= */\n.auth-row {\n  background: #fff;\n  border-radius: 4px;\n  overflow: hidden; }\n\n.auth-cover-wrapper {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  padding: 45px;\n  position: relative;\n  z-index: 1;\n  height: 100%; }\n  @media (max-width: 767px) {\n    .auth-cover-wrapper {\n      padding: 30px 20px; } }\n  .auth-cover-wrapper .auth-cover .title {\n    text-align: cover;\n    margin-bottom: 40px; }\n    @media (max-width: 767px) {\n      .auth-cover-wrapper .auth-cover .title h1 {\n        font-size: 24px; } }\n  .auth-cover-wrapper .auth-cover .cover-image {\n    max-width: 100%;\n    margin: auto; }\n    .auth-cover-wrapper .auth-cover .cover-image img {\n      width: 100%; }\n  .auth-cover-wrapper .auth-cover .shape-image {\n    position: absolute;\n    z-index: -1;\n    right: 0;\n    bottom: 5%; }\n\n.signup-wrapper {\n  background: #fff;\n  padding: 60px;\n  min-height: 600px;\n  display: flex;\n  align-items: center;\n  justify-content: center; }\n  @media only screen and (min-width: 992px) and (max-width: 1199px) {\n    .signup-wrapper {\n      padding: 40px; } }\n  @media (max-width: 767px) {\n    .signup-wrapper {\n      padding: 30px; } }\n  .signup-wrapper .form-wrapper {\n    width: 100%; }\n  .signup-wrapper .singup-option button {\n    font-size: 16px;\n    font-weight: 600; }\n    @media only screen and (min-width: 1200px) and (max-width: 1399px) {\n      .signup-wrapper .singup-option button {\n        padding-left: 25px;\n        padding-right: 25px; } }\n    @media only screen and (min-width: 992px) and (max-width: 1199px) {\n      .signup-wrapper .singup-option button {\n        padding-left: 30px;\n        padding-right: 30px; } }\n    @media (max-width: 767px) {\n      .signup-wrapper .singup-option button {\n        width: 100%; } }\n    @media only screen and (min-width: 550px) and (max-width: 767px) {\n      .signup-wrapper .singup-option button {\n        width: auto; } }\n  .signup-wrapper .singup-option a:hover {\n    text-decoration: underline; }\n\n/* =========== settings css ============== */\n.settings-card-1 .profile-info .profile-image {\n  max-width: 75px;\n  width: 100%;\n  height: 75px;\n  border-radius: 50%;\n  margin-right: 20px;\n  position: relative;\n  z-index: 1; }\n  .settings-card-1 .profile-info .profile-image img {\n    width: 100%;\n    border-radius: 50%; }\n  .settings-card-1 .profile-info .profile-image .update-image {\n    position: absolute;\n    bottom: 0;\n    right: 0;\n    width: 30px;\n    height: 30px;\n    background: #efefef;\n    border: 2px solid #fff;\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    border-radius: 50%;\n    cursor: pointer;\n    z-index: 99; }\n    .settings-card-1 .profile-info .profile-image .update-image:hover {\n      opacity: 0.9; }\n    .settings-card-1 .profile-info .profile-image .update-image input {\n      opacity: 0;\n      position: absolute;\n      width: 100%;\n      height: 100%;\n      cursor: pointer;\n      z-index: 99; }\n    .settings-card-1 .profile-info .profile-image .update-image label {\n      cursor: pointer;\n      z-index: 99; }\n\n/* =========== Invoice Css ============= */\n.invoice-card .invoice-header {\n  display: flex;\n  flex-wrap: wrap;\n  justify-content: space-between;\n  flex: 1;\n  padding-bottom: 30px;\n  border-bottom: 1px solid rgba(0, 0, 0, 0.1); }\n  @media (max-width: 767px) {\n    .invoice-card .invoice-header {\n      flex-direction: column; } }\n  .invoice-card .invoice-header .invoice-logo {\n    width: 110px;\n    height: 110px;\n    border-radius: 50%;\n    overflow: hidden; }\n    @media (max-width: 767px) {\n      .invoice-card .invoice-header .invoice-logo {\n        order: -1;\n        margin-bottom: 30px; } }\n    .invoice-card .invoice-header .invoice-logo img {\n      width: 100%; }\n  @media (max-width: 767px) {\n    .invoice-card .invoice-header .invoice-date {\n      margin-top: 30px; } }\n  .invoice-card .invoice-header .invoice-date p {\n    font-size: 14px;\n    font-weight: 400;\n    margin-bottom: 10px; }\n    .invoice-card .invoice-header .invoice-date p span {\n      font-weight: 500; }\n\n.invoice-card .invoice-address {\n  padding-top: 30px;\n  display: flex;\n  margin-bottom: 40px; }\n  @media (max-width: 767px) {\n    .invoice-card .invoice-address {\n      display: block; } }\n  .invoice-card .invoice-address .address-item {\n    margin-right: 30px;\n    min-width: 250px; }\n    .invoice-card .invoice-address .address-item h5 {\n      margin-bottom: 15px; }\n    .invoice-card .invoice-address .address-item h1 {\n      margin-bottom: 10px;\n      font-size: 24px; }\n    .invoice-card .invoice-address .address-item p {\n      margin-bottom: 10px; }\n\n@media (max-width: 767px) {\n  .invoice-card .invoice-action ul li {\n    flex: 1; } }\n\n@media (max-width: 767px) {\n  .invoice-card .invoice-action ul li a {\n    width: 100%; } }\n\n.invoice-table th,\n.invoice-table td {\n  padding: 10px 8px; }\n\n.invoice-table .service {\n  min-width: 150px; }\n\n.invoice-table .desc {\n  min-width: 150px; }\n\n.invoice-table .qty {\n  min-width: 150px; }\n\n.invoice-table .amount {\n  min-width: 100px; }\n\n/* ============== Icons Css ===========*/\n.icons-wrapper .icons,\n.icons-wrapper ul {\n  display: flex;\n  flex-wrap: wrap;\n  margin: 0 -10px; }\n  .icons-wrapper .icons > div,\n  .icons-wrapper .icons li,\n  .icons-wrapper ul > div,\n  .icons-wrapper ul li {\n    display: flex;\n    align-items: center;\n    margin: 10px;\n    flex-basis: 215px; }\n    @media (max-width: 400px) {\n      .icons-wrapper .icons > div,\n      .icons-wrapper .icons li,\n      .icons-wrapper ul > div,\n      .icons-wrapper ul li {\n        flex-basis: 100%; } }\n    .icons-wrapper .icons > div i,\n    .icons-wrapper .icons li i,\n    .icons-wrapper ul > div i,\n    .icons-wrapper ul li i {\n      max-width: 45px;\n      width: 100%;\n      height: 45px;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      border: 1px solid #efefef;\n      border-radius: 4px;\n      background: transparent;\n      color: #262d3f;\n      font-size: 20px;\n      margin-right: 10px; }\n    .icons-wrapper .icons > div span,\n    .icons-wrapper .icons li span,\n    .icons-wrapper ul > div span,\n    .icons-wrapper ul li span {\n      color: #262d3f;\n      user-select: all; }\n\n/* ============ Calendar Css ============= */\n.calendar-card .fc {\n  height: 450px; }\n  .calendar-card .fc#calendar-full {\n    height: 600px; }\n  .calendar-card .fc table {\n    border: none; }\n  .calendar-card .fc .fc-toolbar-title {\n    font-size: 16px;\n    font-weight: 500; }\n  .calendar-card .fc .fc-button {\n    background: transparent;\n    border: none;\n    color: #5d657b;\n    text-transform: capitalize; }\n    .calendar-card .fc .fc-button:focus {\n      -webkit-box-shadow: none;\n      -moz-box-shadow: none;\n      box-shadow: none;\n      color: #4a6cf7; }\n  .calendar-card .fc th {\n    text-align: left;\n    border-bottom: 1px solid rgba(0, 0, 0, 0.1) !important;\n    border-right: 0px; }\n    .calendar-card .fc th a {\n      color: #5d657b;\n      font-weight: 400; }\n  .calendar-card .fc .fc-day {\n    border-width: 4px;\n    background: #fff; }\n    .calendar-card .fc .fc-day.fc-day-today .fc-daygrid-day-frame {\n      background: rgba(74, 108, 247, 0.8); }\n      .calendar-card .fc .fc-day.fc-day-today .fc-daygrid-day-frame a {\n        color: #fff; }\n    .calendar-card .fc .fc-day .fc-daygrid-day-frame {\n      display: flex;\n      flex-direction: column;\n      align-items: flex-end;\n      background: #f9f9f9;\n      border-radius: 6px; }\n      .calendar-card .fc .fc-day .fc-daygrid-day-frame a {\n        color: #5d657b; }\n\n.calendar-card .fc-theme-standard td,\n.calendar-card .fc-theme-standard th {\n  border-color: transparent; }\n\n/* =========== Sidebar css =========== */\n.sidebar-nav-wrapper {\n  background: #fff;\n  width: 250px;\n  padding: 20px 0px;\n  height: 100vh;\n  position: fixed;\n  overflow-y: scroll;\n  overflow-x: hidden;\n  top: 0;\n  left: 0;\n  z-index: 99;\n  box-shadow: 0px 0px 30px rgba(200, 208, 216, 0.3);\n  -webkit-transition: all 0.3s ease-out 0s;\n  -moz-transition: all 0.3s ease-out 0s;\n  -ms-transition: all 0.3s ease-out 0s;\n  -o-transition: all 0.3s ease-out 0s;\n  transition: all 0.3s ease-out 0s;\n  -webkit-transform: translateX(0);\n  -moz-transform: translateX(0);\n  -ms-transform: translateX(0);\n  -o-transform: translateX(0);\n  transform: translateX(0); }\n  @media only screen and (min-width: 992px) and (max-width: 1199px), only screen and (min-width: 768px) and (max-width: 991px), (max-width: 767px) {\n    .sidebar-nav-wrapper {\n      -webkit-transform: translateX(-260px);\n      -moz-transform: translateX(-260px);\n      -ms-transform: translateX(-260px);\n      -o-transform: translateX(-260px);\n      transform: translateX(-260px); } }\n  .sidebar-nav-wrapper.active {\n    -webkit-transform: translateX(-260px);\n    -moz-transform: translateX(-260px);\n    -ms-transform: translateX(-260px);\n    -o-transform: translateX(-260px);\n    transform: translateX(-260px); }\n    @media only screen and (min-width: 992px) and (max-width: 1199px), only screen and (min-width: 768px) and (max-width: 991px), (max-width: 767px) {\n      .sidebar-nav-wrapper.active {\n        -webkit-transform: translateX(0px);\n        -moz-transform: translateX(0px);\n        -ms-transform: translateX(0px);\n        -o-transform: translateX(0px);\n        transform: translateX(0px); } }\n  .sidebar-nav-wrapper .navbar-logo {\n    text-align: center;\n    padding: 0 25px;\n    margin-bottom: 30px; }\n  .sidebar-nav-wrapper .sidebar-nav .divider {\n    padding: 5px 25px;\n    width: 100%; }\n    .sidebar-nav-wrapper .sidebar-nav .divider hr {\n      height: 1px;\n      background: #e2e2e2; }\n  .sidebar-nav-wrapper .sidebar-nav ul .nav-item {\n    position: relative;\n    margin: 5px 0px; }\n    .sidebar-nav-wrapper .sidebar-nav ul .nav-item.nav-item-has-children > a {\n      color: #262d3f; }\n      .sidebar-nav-wrapper .sidebar-nav ul .nav-item.nav-item-has-children > a::before {\n        opacity: 1;\n        visibility: visible; }\n      .sidebar-nav-wrapper .sidebar-nav ul .nav-item.nav-item-has-children > a::after {\n        content: \"\\ea5e\";\n        font: normal normal normal 1em/1 \"LineIcons\";\n        position: absolute;\n        right: 25px;\n        top: 16px;\n        font-size: 12px;\n        -webkit-transition: all 0.3s ease-out 0s;\n        -moz-transition: all 0.3s ease-out 0s;\n        -ms-transition: all 0.3s ease-out 0s;\n        -o-transition: all 0.3s ease-out 0s;\n        transition: all 0.3s ease-out 0s;\n        -webkit-transform: rotate(180deg);\n        -moz-transform: rotate(180deg);\n        -ms-transform: rotate(180deg);\n        -o-transform: rotate(180deg);\n        transform: rotate(180deg); }\n      .sidebar-nav-wrapper .sidebar-nav ul .nav-item.nav-item-has-children > a.collapsed {\n        color: #5d657b; }\n        .sidebar-nav-wrapper .sidebar-nav ul .nav-item.nav-item-has-children > a.collapsed::before {\n          opacity: 0;\n          visibility: hidden; }\n        .sidebar-nav-wrapper .sidebar-nav ul .nav-item.nav-item-has-children > a.collapsed::after {\n          -webkit-transform: rotate(0deg);\n          -moz-transform: rotate(0deg);\n          -ms-transform: rotate(0deg);\n          -o-transform: rotate(0deg);\n          transform: rotate(0deg); }\n    .sidebar-nav-wrapper .sidebar-nav ul .nav-item.nav-item-has-children ul {\n      padding: 0px 15px; }\n      .sidebar-nav-wrapper .sidebar-nav ul .nav-item.nav-item-has-children ul li {\n        margin-bottom: 10px; }\n        .sidebar-nav-wrapper .sidebar-nav ul .nav-item.nav-item-has-children ul li:last-child {\n          margin-bottom: 0px; }\n        .sidebar-nav-wrapper .sidebar-nav ul .nav-item.nav-item-has-children ul li a {\n          font-size: 14px;\n          font-weight: 400;\n          border-radius: 6px;\n          padding: 8px 15px;\n          display: flex;\n          align-items: center;\n          border: 1px solid transparent; }\n          .sidebar-nav-wrapper .sidebar-nav ul .nav-item.nav-item-has-children ul li a.active, .sidebar-nav-wrapper .sidebar-nav ul .nav-item.nav-item-has-children ul li a:hover {\n            color: #4a6cf7;\n            border-color: rgba(74, 108, 247, 0.15);\n            background: rgba(74, 108, 247, 0.1); }\n          .sidebar-nav-wrapper .sidebar-nav ul .nav-item.nav-item-has-children ul li a i {\n            font-size: 16px;\n            margin-right: 15px; }\n          .sidebar-nav-wrapper .sidebar-nav ul .nav-item.nav-item-has-children ul li a span.text {\n            display: flex;\n            align-items: center;\n            justify-content: space-between;\n            width: 100%; }\n          .sidebar-nav-wrapper .sidebar-nav ul .nav-item.nav-item-has-children ul li a span.pro-badge {\n            background: #4a6cf7;\n            color: #fff;\n            padding: 1px 6px;\n            border-radius: 4px;\n            font-size: 10px;\n            margin-left: 10px; }\n    .sidebar-nav-wrapper .sidebar-nav ul .nav-item a {\n      display: flex;\n      align-items: center;\n      color: #5d657b;\n      font-size: 16px;\n      font-weight: 500;\n      width: 100%;\n      position: relative;\n      z-index: 1;\n      padding: 10px 25px; }\n      .sidebar-nav-wrapper .sidebar-nav ul .nav-item a::before {\n        content: \"\";\n        position: absolute;\n        left: 0;\n        top: 0;\n        height: 100%;\n        width: 4px;\n        background: #4a6cf7;\n        border-radius: 0 3px 3px 0px;\n        opacity: 0;\n        visibility: hidden;\n        -webkit-transition: all 0.3s ease-out 0s;\n        -moz-transition: all 0.3s ease-out 0s;\n        -ms-transition: all 0.3s ease-out 0s;\n        -o-transition: all 0.3s ease-out 0s;\n        transition: all 0.3s ease-out 0s; }\n      .sidebar-nav-wrapper .sidebar-nav ul .nav-item a span.text {\n        display: flex;\n        align-items: center;\n        justify-content: space-between;\n        width: 100%; }\n      .sidebar-nav-wrapper .sidebar-nav ul .nav-item a span.pro-badge {\n        background: #4a6cf7;\n        color: #fff;\n        padding: 1px 6px;\n        border-radius: 4px;\n        font-size: 10px;\n        margin-left: 10px; }\n      .sidebar-nav-wrapper .sidebar-nav ul .nav-item a .icon {\n        margin-right: 12px;\n        font-size: 18px; }\n        .sidebar-nav-wrapper .sidebar-nav ul .nav-item a .icon svg {\n          fill: currentColor; }\n    .sidebar-nav-wrapper .sidebar-nav ul .nav-item.active > a,\n    .sidebar-nav-wrapper .sidebar-nav ul .nav-item.active > a.collapsed, .sidebar-nav-wrapper .sidebar-nav ul .nav-item:hover > a,\n    .sidebar-nav-wrapper .sidebar-nav ul .nav-item:hover > a.collapsed {\n      color: #262d3f; }\n      .sidebar-nav-wrapper .sidebar-nav ul .nav-item.active > a::before,\n      .sidebar-nav-wrapper .sidebar-nav ul .nav-item.active > a.collapsed::before, .sidebar-nav-wrapper .sidebar-nav ul .nav-item:hover > a::before,\n      .sidebar-nav-wrapper .sidebar-nav ul .nav-item:hover > a.collapsed::before {\n        opacity: 1;\n        visibility: visible; }\n\n.overlay {\n  position: fixed;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  background: rgba(0, 0, 0, 0.3);\n  z-index: 11;\n  -webkit-transform: translateX(-100%);\n  -moz-transform: translateX(-100%);\n  -ms-transform: translateX(-100%);\n  -o-transform: translateX(-100%);\n  transform: translateX(-100%);\n  opacity: 0;\n  visibility: hidden; }\n  @media only screen and (min-width: 1400px), only screen and (min-width: 1200px) and (max-width: 1399px) {\n    .overlay {\n      display: none; } }\n  @media only screen and (min-width: 992px) and (max-width: 1199px), only screen and (min-width: 768px) and (max-width: 991px), (max-width: 767px) {\n    .overlay.active {\n      opacity: 1;\n      visibility: visible;\n      -webkit-transform: translateX(0);\n      -moz-transform: translateX(0);\n      -ms-transform: translateX(0);\n      -o-transform: translateX(0);\n      transform: translateX(0); } }\n\n.main-wrapper {\n  -webkit-transition: all 0.3s ease-out 0s;\n  -moz-transition: all 0.3s ease-out 0s;\n  -ms-transition: all 0.3s ease-out 0s;\n  -o-transition: all 0.3s ease-out 0s;\n  transition: all 0.3s ease-out 0s;\n  margin-left: 250px;\n  min-height: 100vh;\n  padding-bottom: 85px;\n  position: relative; }\n  @media only screen and (min-width: 992px) and (max-width: 1199px), only screen and (min-width: 768px) and (max-width: 991px), (max-width: 767px) {\n    .main-wrapper {\n      margin-left: 0; } }\n  @media (max-width: 767px) {\n    .main-wrapper {\n      padding-bottom: 110px; } }\n  .main-wrapper.active {\n    margin-left: 0; }\n  .main-wrapper .container-fluid {\n    padding-left: 40px;\n    padding-right: 40px; }\n    @media (max-width: 767px) {\n      .main-wrapper .container-fluid {\n        padding-left: 20px;\n        padding-right: 20px; } }\n  .main-wrapper .footer {\n    padding: 25px 0;\n    justify-items: flex-end;\n    position: absolute;\n    bottom: 0;\n    width: 100%; }\n    .main-wrapper .footer .copyright p a {\n      color: inherit; }\n      .main-wrapper .footer .copyright p a:hover {\n        color: #4a6cf7; }\n    @media (max-width: 767px) {\n      .main-wrapper .footer .terms {\n        margin-bottom: 10px;\n        text-align: center; } }\n    .main-wrapper .footer .terms a {\n      color: #4a6cf7; }\n      .main-wrapper .footer .terms a:hover {\n        color: #4a6cf7; }\n\n.promo-box {\n  box-shadow: 0px 10px 20px rgba(200, 208, 216, 0.3);\n  padding: 24px 16px;\n  text-align: center;\n  max-width: 210px;\n  margin: 0 auto;\n  margin-top: 32px;\n  border-radius: 4px; }\n  .promo-box h3 {\n    font-size: 16px;\n    font-weight: 600;\n    margin-bottom: 4px; }\n  .promo-box p {\n    font-size: 12px;\n    line-height: 1.5;\n    margin-bottom: 16px; }\n  .promo-box .main-btn {\n    padding: 12px;\n    width: 100%; }\n\n/* ========== DEFAULT CSS ======== */\n/* ======= Margin Top ======= */\n.mt-5 {\n  margin-top: 5px; }\n\n.mt-10 {\n  margin-top: 10px; }\n\n.mt-15 {\n  margin-top: 15px; }\n\n.mt-20 {\n  margin-top: 20px; }\n\n.mt-25 {\n  margin-top: 25px; }\n\n.mt-30 {\n  margin-top: 30px; }\n\n.mt-35 {\n  margin-top: 35px; }\n\n.mt-40 {\n  margin-top: 40px; }\n\n.mt-45 {\n  margin-top: 45px; }\n\n.mt-50 {\n  margin-top: 50px; }\n\n.mt-55 {\n  margin-top: 55px; }\n\n.mt-60 {\n  margin-top: 60px; }\n\n.mt-65 {\n  margin-top: 65px; }\n\n.mt-70 {\n  margin-top: 70px; }\n\n.mt-75 {\n  margin-top: 75px; }\n\n.mt-80 {\n  margin-top: 80px; }\n\n.mt-85 {\n  margin-top: 85px; }\n\n.mt-90 {\n  margin-top: 90px; }\n\n.mt-95 {\n  margin-top: 95px; }\n\n.mt-100 {\n  margin-top: 100px; }\n\n.mt-105 {\n  margin-top: 105px; }\n\n.mt-110 {\n  margin-top: 110px; }\n\n.mt-115 {\n  margin-top: 115px; }\n\n.mt-120 {\n  margin-top: 120px; }\n\n.mt-125 {\n  margin-top: 125px; }\n\n.mt-130 {\n  margin-top: 130px; }\n\n.mt-135 {\n  margin-top: 135px; }\n\n.mt-140 {\n  margin-top: 140px; }\n\n.mt-145 {\n  margin-top: 145px; }\n\n.mt-150 {\n  margin-top: 150px; }\n\n.mt-155 {\n  margin-top: 155px; }\n\n.mt-160 {\n  margin-top: 160px; }\n\n.mt-165 {\n  margin-top: 165px; }\n\n.mt-170 {\n  margin-top: 170px; }\n\n.mt-175 {\n  margin-top: 175px; }\n\n.mt-180 {\n  margin-top: 180px; }\n\n.mt-185 {\n  margin-top: 185px; }\n\n.mt-190 {\n  margin-top: 190px; }\n\n.mt-195 {\n  margin-top: 195px; }\n\n.mt-200 {\n  margin-top: 200px; }\n\n.mt-205 {\n  margin-top: 205px; }\n\n.mt-210 {\n  margin-top: 210px; }\n\n.mt-215 {\n  margin-top: 215px; }\n\n.mt-220 {\n  margin-top: 220px; }\n\n.mt-225 {\n  margin-top: 225px; }\n\n/* ======= Margin Bottom ======= */\n.mb-5 {\n  margin-bottom: 5px; }\n\n.mb-10 {\n  margin-bottom: 10px; }\n\n.mb-15 {\n  margin-bottom: 15px; }\n\n.mb-20 {\n  margin-bottom: 20px; }\n\n.mb-25 {\n  margin-bottom: 25px; }\n\n.mb-30 {\n  margin-bottom: 30px; }\n\n.mb-35 {\n  margin-bottom: 35px; }\n\n.mb-40 {\n  margin-bottom: 40px; }\n\n.mb-45 {\n  margin-bottom: 45px; }\n\n.mb-50 {\n  margin-bottom: 50px; }\n\n.mb-55 {\n  margin-bottom: 55px; }\n\n.mb-60 {\n  margin-bottom: 60px; }\n\n.mb-65 {\n  margin-bottom: 65px; }\n\n.mb-70 {\n  margin-bottom: 70px; }\n\n.mb-75 {\n  margin-bottom: 75px; }\n\n.mb-80 {\n  margin-bottom: 80px; }\n\n.mb-85 {\n  margin-bottom: 85px; }\n\n.mb-90 {\n  margin-bottom: 90px; }\n\n.mb-95 {\n  margin-bottom: 95px; }\n\n.mb-100 {\n  margin-bottom: 100px; }\n\n.mb-105 {\n  margin-bottom: 105px; }\n\n.mb-110 {\n  margin-bottom: 110px; }\n\n.mb-115 {\n  margin-bottom: 115px; }\n\n.mb-120 {\n  margin-bottom: 120px; }\n\n.mb-125 {\n  margin-bottom: 125px; }\n\n.mb-130 {\n  margin-bottom: 130px; }\n\n.mb-135 {\n  margin-bottom: 135px; }\n\n.mb-140 {\n  margin-bottom: 140px; }\n\n.mb-145 {\n  margin-bottom: 145px; }\n\n.mb-150 {\n  margin-bottom: 150px; }\n\n.mb-155 {\n  margin-bottom: 155px; }\n\n.mb-160 {\n  margin-bottom: 160px; }\n\n.mb-165 {\n  margin-bottom: 165px; }\n\n.mb-170 {\n  margin-bottom: 170px; }\n\n.mb-175 {\n  margin-bottom: 175px; }\n\n.mb-180 {\n  margin-bottom: 180px; }\n\n.mb-185 {\n  margin-bottom: 185px; }\n\n.mb-190 {\n  margin-bottom: 190px; }\n\n.mb-195 {\n  margin-bottom: 195px; }\n\n.mb-200 {\n  margin-bottom: 200px; }\n\n.mb-205 {\n  margin-bottom: 205px; }\n\n.mb-210 {\n  margin-bottom: 210px; }\n\n.mb-215 {\n  margin-bottom: 215px; }\n\n.mb-220 {\n  margin-bottom: 220px; }\n\n.mb-225 {\n  margin-bottom: 225px; }\n\n/* ======= Margin Left ======= */\n.ml-5 {\n  margin-left: 5px; }\n\n.ml-10 {\n  margin-left: 10px; }\n\n.ml-15 {\n  margin-left: 15px; }\n\n.ml-20 {\n  margin-left: 20px; }\n\n.ml-25 {\n  margin-left: 25px; }\n\n.ml-30 {\n  margin-left: 30px; }\n\n.ml-35 {\n  margin-left: 35px; }\n\n.ml-40 {\n  margin-left: 40px; }\n\n.ml-45 {\n  margin-left: 45px; }\n\n.ml-50 {\n  margin-left: 50px; }\n\n.ml-55 {\n  margin-left: 55px; }\n\n.ml-60 {\n  margin-left: 60px; }\n\n.ml-65 {\n  margin-left: 65px; }\n\n.ml-70 {\n  margin-left: 70px; }\n\n.ml-75 {\n  margin-left: 75px; }\n\n.ml-80 {\n  margin-left: 80px; }\n\n.ml-85 {\n  margin-left: 85px; }\n\n.ml-90 {\n  margin-left: 90px; }\n\n.ml-95 {\n  margin-left: 95px; }\n\n.ml-100 {\n  margin-left: 100px; }\n\n.ml-105 {\n  margin-left: 105px; }\n\n.ml-110 {\n  margin-left: 110px; }\n\n.ml-115 {\n  margin-left: 115px; }\n\n.ml-120 {\n  margin-left: 120px; }\n\n.ml-125 {\n  margin-left: 125px; }\n\n.ml-130 {\n  margin-left: 130px; }\n\n.ml-135 {\n  margin-left: 135px; }\n\n.ml-140 {\n  margin-left: 140px; }\n\n.ml-145 {\n  margin-left: 145px; }\n\n.ml-150 {\n  margin-left: 150px; }\n\n.ml-155 {\n  margin-left: 155px; }\n\n.ml-160 {\n  margin-left: 160px; }\n\n.ml-165 {\n  margin-left: 165px; }\n\n.ml-170 {\n  margin-left: 170px; }\n\n.ml-175 {\n  margin-left: 175px; }\n\n.ml-180 {\n  margin-left: 180px; }\n\n.ml-185 {\n  margin-left: 185px; }\n\n.ml-190 {\n  margin-left: 190px; }\n\n.ml-195 {\n  margin-left: 195px; }\n\n.ml-200 {\n  margin-left: 200px; }\n\n.ml-205 {\n  margin-left: 205px; }\n\n.ml-210 {\n  margin-left: 210px; }\n\n.ml-215 {\n  margin-left: 215px; }\n\n.ml-220 {\n  margin-left: 220px; }\n\n.ml-225 {\n  margin-left: 225px; }\n\n/* ======= Margin Right ======= */\n.mr-5 {\n  margin-right: 5px; }\n\n.mr-10 {\n  margin-right: 10px; }\n\n.mr-15 {\n  margin-right: 15px; }\n\n.mr-20 {\n  margin-right: 20px; }\n\n.mr-25 {\n  margin-right: 25px; }\n\n.mr-30 {\n  margin-right: 30px; }\n\n.mr-35 {\n  margin-right: 35px; }\n\n.mr-40 {\n  margin-right: 40px; }\n\n.mr-45 {\n  margin-right: 45px; }\n\n.mr-50 {\n  margin-right: 50px; }\n\n.mr-55 {\n  margin-right: 55px; }\n\n.mr-60 {\n  margin-right: 60px; }\n\n.mr-65 {\n  margin-right: 65px; }\n\n.mr-70 {\n  margin-right: 70px; }\n\n.mr-75 {\n  margin-right: 75px; }\n\n.mr-80 {\n  margin-right: 80px; }\n\n.mr-85 {\n  margin-right: 85px; }\n\n.mr-90 {\n  margin-right: 90px; }\n\n.mr-95 {\n  margin-right: 95px; }\n\n.mr-100 {\n  margin-right: 100px; }\n\n.mr-105 {\n  margin-right: 105px; }\n\n.mr-110 {\n  margin-right: 110px; }\n\n.mr-115 {\n  margin-right: 115px; }\n\n.mr-120 {\n  margin-right: 120px; }\n\n.mr-125 {\n  margin-right: 125px; }\n\n.mr-130 {\n  margin-right: 130px; }\n\n.mr-135 {\n  margin-right: 135px; }\n\n.mr-140 {\n  margin-right: 140px; }\n\n.mr-145 {\n  margin-right: 145px; }\n\n.mr-150 {\n  margin-right: 150px; }\n\n.mr-155 {\n  margin-right: 155px; }\n\n.mr-160 {\n  margin-right: 160px; }\n\n.mr-165 {\n  margin-right: 165px; }\n\n.mr-170 {\n  margin-right: 170px; }\n\n.mr-175 {\n  margin-right: 175px; }\n\n.mr-180 {\n  margin-right: 180px; }\n\n.mr-185 {\n  margin-right: 185px; }\n\n.mr-190 {\n  margin-right: 190px; }\n\n.mr-195 {\n  margin-right: 195px; }\n\n.mr-200 {\n  margin-right: 200px; }\n\n.mr-205 {\n  margin-right: 205px; }\n\n.mr-210 {\n  margin-right: 210px; }\n\n.mr-215 {\n  margin-right: 215px; }\n\n.mr-220 {\n  margin-right: 220px; }\n\n.mr-225 {\n  margin-right: 225px; }\n\n/* ======= Padding Top ======= */\n.pt-5 {\n  padding-top: 5px; }\n\n.pt-10 {\n  padding-top: 10px; }\n\n.pt-15 {\n  padding-top: 15px; }\n\n.pt-20 {\n  padding-top: 20px; }\n\n.pt-25 {\n  padding-top: 25px; }\n\n.pt-30 {\n  padding-top: 30px; }\n\n.pt-35 {\n  padding-top: 35px; }\n\n.pt-40 {\n  padding-top: 40px; }\n\n.pt-45 {\n  padding-top: 45px; }\n\n.pt-50 {\n  padding-top: 50px; }\n\n.pt-55 {\n  padding-top: 55px; }\n\n.pt-60 {\n  padding-top: 60px; }\n\n.pt-65 {\n  padding-top: 65px; }\n\n.pt-70 {\n  padding-top: 70px; }\n\n.pt-75 {\n  padding-top: 75px; }\n\n.pt-80 {\n  padding-top: 80px; }\n\n.pt-85 {\n  padding-top: 85px; }\n\n.pt-90 {\n  padding-top: 90px; }\n\n.pt-95 {\n  padding-top: 95px; }\n\n.pt-100 {\n  padding-top: 100px; }\n\n.pt-105 {\n  padding-top: 105px; }\n\n.pt-110 {\n  padding-top: 110px; }\n\n.pt-115 {\n  padding-top: 115px; }\n\n.pt-120 {\n  padding-top: 120px; }\n\n.pt-125 {\n  padding-top: 125px; }\n\n.pt-130 {\n  padding-top: 130px; }\n\n.pt-135 {\n  padding-top: 135px; }\n\n.pt-140 {\n  padding-top: 140px; }\n\n.pt-145 {\n  padding-top: 145px; }\n\n.pt-150 {\n  padding-top: 150px; }\n\n.pt-155 {\n  padding-top: 155px; }\n\n.pt-160 {\n  padding-top: 160px; }\n\n.pt-165 {\n  padding-top: 165px; }\n\n.pt-170 {\n  padding-top: 170px; }\n\n.pt-175 {\n  padding-top: 175px; }\n\n.pt-180 {\n  padding-top: 180px; }\n\n.pt-185 {\n  padding-top: 185px; }\n\n.pt-190 {\n  padding-top: 190px; }\n\n.pt-195 {\n  padding-top: 195px; }\n\n.pt-200 {\n  padding-top: 200px; }\n\n.pt-205 {\n  padding-top: 205px; }\n\n.pt-210 {\n  padding-top: 210px; }\n\n.pt-215 {\n  padding-top: 215px; }\n\n.pt-220 {\n  padding-top: 220px; }\n\n.pt-225 {\n  padding-top: 225px; }\n\n/* ======= Padding Bottom ======= */\n.pb-5 {\n  padding-bottom: 5px; }\n\n.pb-10 {\n  padding-bottom: 10px; }\n\n.pb-15 {\n  padding-bottom: 15px; }\n\n.pb-20 {\n  padding-bottom: 20px; }\n\n.pb-25 {\n  padding-bottom: 25px; }\n\n.pb-30 {\n  padding-bottom: 30px; }\n\n.pb-35 {\n  padding-bottom: 35px; }\n\n.pb-40 {\n  padding-bottom: 40px; }\n\n.pb-45 {\n  padding-bottom: 45px; }\n\n.pb-50 {\n  padding-bottom: 50px; }\n\n.pb-55 {\n  padding-bottom: 55px; }\n\n.pb-60 {\n  padding-bottom: 60px; }\n\n.pb-65 {\n  padding-bottom: 65px; }\n\n.pb-70 {\n  padding-bottom: 70px; }\n\n.pb-75 {\n  padding-bottom: 75px; }\n\n.pb-80 {\n  padding-bottom: 80px; }\n\n.pb-85 {\n  padding-bottom: 85px; }\n\n.pb-90 {\n  padding-bottom: 90px; }\n\n.pb-95 {\n  padding-bottom: 95px; }\n\n.pb-100 {\n  padding-bottom: 100px; }\n\n.pb-105 {\n  padding-bottom: 105px; }\n\n.pb-110 {\n  padding-bottom: 110px; }\n\n.pb-115 {\n  padding-bottom: 115px; }\n\n.pb-120 {\n  padding-bottom: 120px; }\n\n.pb-125 {\n  padding-bottom: 125px; }\n\n.pb-130 {\n  padding-bottom: 130px; }\n\n.pb-135 {\n  padding-bottom: 135px; }\n\n.pb-140 {\n  padding-bottom: 140px; }\n\n.pb-145 {\n  padding-bottom: 145px; }\n\n.pb-150 {\n  padding-bottom: 150px; }\n\n.pb-155 {\n  padding-bottom: 155px; }\n\n.pb-160 {\n  padding-bottom: 160px; }\n\n.pb-165 {\n  padding-bottom: 165px; }\n\n.pb-170 {\n  padding-bottom: 170px; }\n\n.pb-175 {\n  padding-bottom: 175px; }\n\n.pb-180 {\n  padding-bottom: 180px; }\n\n.pb-185 {\n  padding-bottom: 185px; }\n\n.pb-190 {\n  padding-bottom: 190px; }\n\n.pb-195 {\n  padding-bottom: 195px; }\n\n.pb-200 {\n  padding-bottom: 200px; }\n\n.pb-205 {\n  padding-bottom: 205px; }\n\n.pb-210 {\n  padding-bottom: 210px; }\n\n.pb-215 {\n  padding-bottom: 215px; }\n\n.pb-220 {\n  padding-bottom: 220px; }\n\n.pb-225 {\n  padding-bottom: 225px; }\n\n/* ======= Padding Left ======= */\n.pl-5 {\n  padding-left: 5px; }\n\n.pl-10 {\n  padding-left: 10px; }\n\n.pl-15 {\n  padding-left: 15px; }\n\n.pl-20 {\n  padding-left: 20px; }\n\n.pl-25 {\n  padding-left: 25px; }\n\n.pl-30 {\n  padding-left: 30px; }\n\n.pl-35 {\n  padding-left: 35px; }\n\n.pl-40 {\n  padding-left: 40px; }\n\n.pl-45 {\n  padding-left: 45px; }\n\n.pl-50 {\n  padding-left: 50px; }\n\n.pl-55 {\n  padding-left: 55px; }\n\n.pl-60 {\n  padding-left: 60px; }\n\n.pl-65 {\n  padding-left: 65px; }\n\n.pl-70 {\n  padding-left: 70px; }\n\n.pl-75 {\n  padding-left: 75px; }\n\n.pl-80 {\n  padding-left: 80px; }\n\n.pl-85 {\n  padding-left: 85px; }\n\n.pl-90 {\n  padding-left: 90px; }\n\n.pl-95 {\n  padding-left: 95px; }\n\n.pl-100 {\n  padding-left: 100px; }\n\n.pl-105 {\n  padding-left: 105px; }\n\n.pl-110 {\n  padding-left: 110px; }\n\n.pl-115 {\n  padding-left: 115px; }\n\n.pl-120 {\n  padding-left: 120px; }\n\n.pl-125 {\n  padding-left: 125px; }\n\n.pl-130 {\n  padding-left: 130px; }\n\n.pl-135 {\n  padding-left: 135px; }\n\n.pl-140 {\n  padding-left: 140px; }\n\n.pl-145 {\n  padding-left: 145px; }\n\n.pl-150 {\n  padding-left: 150px; }\n\n.pl-155 {\n  padding-left: 155px; }\n\n.pl-160 {\n  padding-left: 160px; }\n\n.pl-165 {\n  padding-left: 165px; }\n\n.pl-170 {\n  padding-left: 170px; }\n\n.pl-175 {\n  padding-left: 175px; }\n\n.pl-180 {\n  padding-left: 180px; }\n\n.pl-185 {\n  padding-left: 185px; }\n\n.pl-190 {\n  padding-left: 190px; }\n\n.pl-195 {\n  padding-left: 195px; }\n\n.pl-200 {\n  padding-left: 200px; }\n\n.pl-205 {\n  padding-left: 205px; }\n\n.pl-210 {\n  padding-left: 210px; }\n\n.pl-215 {\n  padding-left: 215px; }\n\n.pl-220 {\n  padding-left: 220px; }\n\n.pl-225 {\n  padding-left: 225px; }\n\n/* ======= Padding Right ======= */\n.pr-5 {\n  padding-right: 5px; }\n\n.pr-10 {\n  padding-right: 10px; }\n\n.pr-15 {\n  padding-right: 15px; }\n\n.pr-20 {\n  padding-right: 20px; }\n\n.pr-25 {\n  padding-right: 25px; }\n\n.pr-30 {\n  padding-right: 30px; }\n\n.pr-35 {\n  padding-right: 35px; }\n\n.pr-40 {\n  padding-right: 40px; }\n\n.pr-45 {\n  padding-right: 45px; }\n\n.pr-50 {\n  padding-right: 50px; }\n\n.pr-55 {\n  padding-right: 55px; }\n\n.pr-60 {\n  padding-right: 60px; }\n\n.pr-65 {\n  padding-right: 65px; }\n\n.pr-70 {\n  padding-right: 70px; }\n\n.pr-75 {\n  padding-right: 75px; }\n\n.pr-80 {\n  padding-right: 80px; }\n\n.pr-85 {\n  padding-right: 85px; }\n\n.pr-90 {\n  padding-right: 90px; }\n\n.pr-95 {\n  padding-right: 95px; }\n\n.pr-100 {\n  padding-right: 100px; }\n\n.pr-105 {\n  padding-right: 105px; }\n\n.pr-110 {\n  padding-right: 110px; }\n\n.pr-115 {\n  padding-right: 115px; }\n\n.pr-120 {\n  padding-right: 120px; }\n\n.pr-125 {\n  padding-right: 125px; }\n\n.pr-130 {\n  padding-right: 130px; }\n\n.pr-135 {\n  padding-right: 135px; }\n\n.pr-140 {\n  padding-right: 140px; }\n\n.pr-145 {\n  padding-right: 145px; }\n\n.pr-150 {\n  padding-right: 150px; }\n\n.pr-155 {\n  padding-right: 155px; }\n\n.pr-160 {\n  padding-right: 160px; }\n\n.pr-165 {\n  padding-right: 165px; }\n\n.pr-170 {\n  padding-right: 170px; }\n\n.pr-175 {\n  padding-right: 175px; }\n\n.pr-180 {\n  padding-right: 180px; }\n\n.pr-185 {\n  padding-right: 185px; }\n\n.pr-190 {\n  padding-right: 190px; }\n\n.pr-195 {\n  padding-right: 195px; }\n\n.pr-200 {\n  padding-right: 200px; }\n\n.pr-205 {\n  padding-right: 205px; }\n\n.pr-210 {\n  padding-right: 210px; }\n\n.pr-215 {\n  padding-right: 215px; }\n\n.pr-220 {\n  padding-right: 220px; }\n\n.pr-225 {\n  padding-right: 225px; }\n\n/* ======= bg-primary shades ========= */\n.bg-primary-100 {\n  background: rgba(74, 108, 247, 0.1); }\n\n.bg-primary-200 {\n  background: rgba(74, 108, 247, 0.2); }\n\n.bg-primary-300 {\n  background: rgba(74, 108, 247, 0.3); }\n\n.bg-primary-400 {\n  background: rgba(74, 108, 247, 0.4); }\n\n.bg-primary-500 {\n  background: rgba(74, 108, 247, 0.5); }\n\n.bg-primary-600 {\n  background: rgba(74, 108, 247, 0.6); }\n\n.bg-primary-700 {\n  background: rgba(74, 108, 247, 0.7); }\n\n.bg-primary-800 {\n  background: rgba(74, 108, 247, 0.8); }\n\n.bg-primary-900 {\n  background: rgba(74, 108, 247, 0.9); }\n\n/* ======= bg-secondary shades ========= */\n.bg-secondary-100 {\n  background: rgba(0, 193, 248, 0.1); }\n\n.bg-secondary-200 {\n  background: rgba(0, 193, 248, 0.2); }\n\n.bg-secondary-300 {\n  background: rgba(0, 193, 248, 0.3); }\n\n.bg-secondary-400 {\n  background: rgba(0, 193, 248, 0.4); }\n\n.bg-secondary-500 {\n  background: rgba(0, 193, 248, 0.5); }\n\n.bg-secondary-600 {\n  background: rgba(0, 193, 248, 0.6); }\n\n.bg-secondary-700 {\n  background: rgba(0, 193, 248, 0.7); }\n\n.bg-secondary-800 {\n  background: rgba(0, 193, 248, 0.8); }\n\n.bg-secondary-900 {\n  background: rgba(0, 193, 248, 0.9); }\n\n/* ======= bg-success shades ========= */\n.bg-success-100 {\n  background: rgba(33, 150, 83, 0.1); }\n\n.bg-success-200 {\n  background: rgba(33, 150, 83, 0.2); }\n\n.bg-success-300 {\n  background: rgba(33, 150, 83, 0.3); }\n\n.bg-success-400 {\n  background: rgba(33, 150, 83, 0.4); }\n\n.bg-success-500 {\n  background: rgba(33, 150, 83, 0.5); }\n\n.bg-success-600 {\n  background: rgba(33, 150, 83, 0.6); }\n\n.bg-success-700 {\n  background: rgba(33, 150, 83, 0.7); }\n\n.bg-success-800 {\n  background: rgba(33, 150, 83, 0.8); }\n\n.bg-success-900 {\n  background: rgba(33, 150, 83, 0.9); }\n\n/* ======= bg-danger shades ========= */\n.bg-danger-100 {\n  background: rgba(213, 1, 0, 0.1); }\n\n.bg-danger-200 {\n  background: rgba(213, 1, 0, 0.2); }\n\n.bg-danger-300 {\n  background: rgba(213, 1, 0, 0.3); }\n\n.bg-danger-400 {\n  background: rgba(213, 1, 0, 0.4); }\n\n.bg-danger-500 {\n  background: rgba(213, 1, 0, 0.5); }\n\n.bg-danger-600 {\n  background: rgba(213, 1, 0, 0.6); }\n\n.bg-danger-700 {\n  background: rgba(213, 1, 0, 0.7); }\n\n.bg-danger-800 {\n  background: rgba(213, 1, 0, 0.8); }\n\n.bg-danger-900 {\n  background: rgba(213, 1, 0, 0.9); }\n\n/* ======= bg-warning shades ========= */\n.bg-warning-100 {\n  background: rgba(247, 200, 0, 0.1); }\n\n.bg-warning-200 {\n  background: rgba(247, 200, 0, 0.2); }\n\n.bg-warning-300 {\n  background: rgba(247, 200, 0, 0.3); }\n\n.bg-warning-400 {\n  background: rgba(247, 200, 0, 0.4); }\n\n.bg-warning-500 {\n  background: rgba(247, 200, 0, 0.5); }\n\n.bg-warning-600 {\n  background: rgba(247, 200, 0, 0.6); }\n\n.bg-warning-700 {\n  background: rgba(247, 200, 0, 0.7); }\n\n.bg-warning-800 {\n  background: rgba(247, 200, 0, 0.8); }\n\n.bg-warning-900 {\n  background: rgba(247, 200, 0, 0.9); }\n\n/* ======= bg-info shades ========= */\n.bg-info-100 {\n  background: rgba(151, 202, 49, 0.1); }\n\n.bg-info-200 {\n  background: rgba(151, 202, 49, 0.2); }\n\n.bg-info-300 {\n  background: rgba(151, 202, 49, 0.3); }\n\n.bg-info-400 {\n  background: rgba(151, 202, 49, 0.4); }\n\n.bg-info-500 {\n  background: rgba(151, 202, 49, 0.5); }\n\n.bg-info-600 {\n  background: rgba(151, 202, 49, 0.6); }\n\n.bg-info-700 {\n  background: rgba(151, 202, 49, 0.7); }\n\n.bg-info-800 {\n  background: rgba(151, 202, 49, 0.8); }\n\n.bg-info-900 {\n  background: rgba(151, 202, 49, 0.9); }\n\n/* ======= bg-dark shades ========= */\n.bg-dark-100 {\n  background: rgba(38, 45, 63, 0.1); }\n\n.bg-dark-200 {\n  background: rgba(38, 45, 63, 0.2); }\n\n.bg-dark-300 {\n  background: rgba(38, 45, 63, 0.3); }\n\n.bg-dark-400 {\n  background: rgba(38, 45, 63, 0.4); }\n\n.bg-dark-500 {\n  background: rgba(38, 45, 63, 0.5); }\n\n.bg-dark-600 {\n  background: rgba(38, 45, 63, 0.6); }\n\n.bg-dark-700 {\n  background: rgba(38, 45, 63, 0.7); }\n\n.bg-dark-800 {\n  background: rgba(38, 45, 63, 0.8); }\n\n.bg-dark-900 {\n  background: rgba(38, 45, 63, 0.9); }\n\n/* ======= bg-purple shades ========= */\n.bg-purple-100 {\n  background: rgba(155, 81, 224, 0.1); }\n\n.bg-purple-200 {\n  background: rgba(155, 81, 224, 0.2); }\n\n.bg-purple-300 {\n  background: rgba(155, 81, 224, 0.3); }\n\n.bg-purple-400 {\n  background: rgba(155, 81, 224, 0.4); }\n\n.bg-purple-500 {\n  background: rgba(155, 81, 224, 0.5); }\n\n.bg-purple-600 {\n  background: rgba(155, 81, 224, 0.6); }\n\n.bg-purple-700 {\n  background: rgba(155, 81, 224, 0.7); }\n\n.bg-purple-800 {\n  background: rgba(155, 81, 224, 0.8); }\n\n.bg-purple-900 {\n  background: rgba(155, 81, 224, 0.9); }\n\n/* ======= bg-orange shades ========= */\n.bg-orange-100 {\n  background: rgba(242, 153, 74, 0.1); }\n\n.bg-orange-200 {\n  background: rgba(242, 153, 74, 0.2); }\n\n.bg-orange-300 {\n  background: rgba(242, 153, 74, 0.3); }\n\n.bg-orange-400 {\n  background: rgba(242, 153, 74, 0.4); }\n\n.bg-orange-500 {\n  background: rgba(242, 153, 74, 0.5); }\n\n.bg-orange-600 {\n  background: rgba(242, 153, 74, 0.6); }\n\n.bg-orange-700 {\n  background: rgba(242, 153, 74, 0.7); }\n\n.bg-orange-800 {\n  background: rgba(242, 153, 74, 0.8); }\n\n.bg-orange-900 {\n  background: rgba(242, 153, 74, 0.9); }\n\n/* ======== Background Colors ========== */\n.primary-bg {\n  background-color: #4a6cf7; }\n\n.secondary-bg {\n  background-color: #00c1f8; }\n\n.success-bg {\n  background-color: #219653; }\n\n.danger-bg {\n  background-color: #d50100; }\n\n.warning-bg {\n  background-color: #f7c800; }\n\n.info-bg {\n  background-color: #97ca31; }\n\n.dark-bg {\n  background-color: #262d3f; }\n\n.light-bg {\n  background-color: #efefef; }\n\n.active-bg {\n  background-color: #4a6cf7; }\n\n.deactive-bg {\n  background-color: #cbe1ff; }\n\n.deactive-bg {\n  background-color: #cbe1ff; }\n\n.gray-bg {\n  background-color: #5d657b; }\n\n.purple-bg {\n  background-color: #9b51e0; }\n\n.orange-bg {\n  background-color: #f2994a; }\n\n.deep-blue-bg {\n  background-color: #345d9d; }\n\n/* ======== Text Colors ========== */\n.text-primary {\n  color: #4a6cf7 !important; }\n\n.text-secondary {\n  color: #00c1f8 !important; }\n\n.text-success {\n  color: #219653 !important; }\n\n.text-danger {\n  color: #d50100 !important; }\n\n.text-warning {\n  color: #f7c800 !important; }\n\n.text-info {\n  color: #97ca31 !important; }\n\n.text-dark {\n  color: #262d3f !important; }\n\n.text-light {\n  color: #efefef !important; }\n\n.text-active {\n  color: #4a6cf7 !important; }\n\n.text-deactive {\n  color: #cbe1ff !important; }\n\n.text-deactive {\n  color: #cbe1ff !important; }\n\n.text-gray {\n  color: #5d657b !important; }\n\n.text-orange {\n  color: #f2994a !important; }\n\n/* ========= Font Weight =========== */\n.fw-300 {\n  font-weight: 300; }\n\n.fw-400 {\n  font-weight: 400; }\n\n.fw-500 {\n  font-weight: 500; }\n\n.fw-600 {\n  font-weight: 600; }\n\n.fw-700 {\n  font-weight: 700; }\n\n.fw-800 {\n  font-weight: 800; }\n\n.fw-900 {\n  font-weight: 900; }\n\n/* ====== Supervisor Stats ====== */\n.supervisor-stat {\n  display: block;\n  padding: 7px 15px;\n  border-radius: 30px;\n  font-size: 14px;\n  font-weight: 400; }"
  },
  {
    "path": "public/assets/scss/_common.scss",
    "content": "/*===========================\r\n\t\tCOMMON css\r\n===========================*/\r\n\r\n@import url(\"https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap\");\r\n\r\nhtml {\r\n  scroll-behavior: smooth;\r\n}\r\n\r\nbody {\r\n  font-family: $font !important;\r\n  font-weight: normal;\r\n  font-style: normal;\r\n  color: $gray;\r\n  overflow-x: hidden;\r\n  background: #f1f5f9;\r\n}\r\n\r\n* {\r\n  margin: 0;\r\n  padding: 0;\r\n  @include box-sizing(border-box);\r\n}\r\n\r\na:focus,\r\ninput:focus,\r\ntextarea:focus,\r\nbutton:focus,\r\n.btn:focus,\r\n.btn.focus,\r\n.btn:not(:disabled):not(.disabled).active,\r\n.btn:not(:disabled):not(.disabled):active {\r\n  text-decoration: none;\r\n  outline: none;\r\n  @include box-shadow(none);\r\n}\r\n\r\na:hover {\r\n  color: $primary;\r\n}\r\nbutton,\r\na {\r\n  @include transition(0.3s);\r\n}\r\n\r\na,\r\na:focus,\r\na:hover {\r\n  text-decoration: none;\r\n}\r\n\r\ni,\r\nspan,\r\na {\r\n  display: inline-block;\r\n}\r\n\r\naudio,\r\ncanvas,\r\niframe,\r\nimg,\r\nsvg,\r\nvideo {\r\n  vertical-align: middle;\r\n}\r\n\r\nh1 a,\r\nh2 a,\r\nh3 a,\r\nh4 a,\r\nh5 a,\r\nh6 a {\r\n  color: inherit;\r\n}\r\n\r\nul,\r\nol {\r\n  margin: 0px;\r\n  padding: 0px;\r\n  list-style-type: none;\r\n}\r\n\r\np {\r\n  font-size: 16px;\r\n  font-weight: 400;\r\n  line-height: 25px;\r\n  margin: 0px;\r\n}\r\n\r\n.img-bg {\r\n  background-position: center center;\r\n  background-size: cover;\r\n  background-repeat: no-repeat;\r\n  width: 100%;\r\n  height: 100%;\r\n}\r\n\r\n.para-width-500 {\r\n  max-width: 500px;\r\n  width: 100%;\r\n}\r\n\r\n.container {\r\n  @media #{$xs} {\r\n    padding: 0 30px;\r\n  }\r\n}\r\n\r\n/* ========== cart style ========== */\r\n.card-style {\r\n  background: $white;\r\n  box-sizing: border-box;\r\n  padding: 25px 30px;\r\n  position: relative;\r\n  border: 1px solid #e2e8f0;\r\n  box-shadow: 0px 10px 20px rgba(200, 208, 216, 0.3);\r\n  border-radius: 10px;\r\n\r\n  @media #{$xs} {\r\n    padding: 20px;\r\n  }\r\n\r\n  .jvm-zoom-btn {\r\n    position: absolute;\r\n    display: inline-flex;\r\n    justify-content: center;\r\n    align-items: center;\r\n    width: 30px;\r\n    height: 30px;\r\n    border: 1px solid $black-10;\r\n    right: 30px;\r\n    bottom: 30px;\r\n    cursor: pointer;\r\n\r\n    &.jvm-zoomin {\r\n      bottom: 70px;\r\n    }\r\n  }\r\n\r\n  .dropdown-toggle {\r\n    border: none;\r\n    background: none;\r\n\r\n    &::after {\r\n      display: none;\r\n    }\r\n  }\r\n\r\n  .dropdown-menu {\r\n    @include box-shadow(0px 0px 5px rgba(0, 0, 0, 0.07));\r\n\r\n    li {\r\n      &:hover {\r\n        a {\r\n          color: $primary !important;\r\n        }\r\n      }\r\n\r\n      a {\r\n        display: block;\r\n        font-size: 14px;\r\n      }\r\n    }\r\n  }\r\n}\r\n\r\n/* ======= Border Radius ========= */\r\n.radius-4 {\r\n  border-radius: 4px;\r\n}\r\n.radius-10 {\r\n  border-radius: 10px;\r\n}\r\n.radius-30 {\r\n  border-radius: 30px;\r\n}\r\n.radius-50 {\r\n  border-radius: 50px;\r\n}\r\n.radius-full {\r\n  border-radius: 50%;\r\n}\r\n\r\n// scroll-top\r\n.scroll-top {\r\n  width: 45px;\r\n  height: 45px;\r\n  background: $primary;\r\n  display: none;\r\n  justify-content: center;\r\n  align-items: center;\r\n  font-size: 18px;\r\n  color: $white;\r\n  border-radius: 5px;\r\n  position: fixed;\r\n  bottom: 30px;\r\n  right: 30px;\r\n  z-index: 9;\r\n  cursor: pointer;\r\n  @include transition(0.3s);\r\n\r\n  &:hover {\r\n    color: $white;\r\n    background: rgba($primary, 0.8);\r\n  }\r\n}\r\n\r\n.form-control {\r\n  &:focus {\r\n    box-shadow: none;\r\n  }\r\n}\r\n\r\n.form-control.is-valid:focus,\r\n.was-validated .form-control:valid:focus,\r\n.form-control.is-invalid:focus,\r\n.was-validated .form-control:invalid:focus,\r\n.form-check-input.is-valid:focus,\r\n.was-validated .form-check-input:valid:focus,\r\n.form-check-input.is-invalid:focus,\r\n.was-validated .form-check-input:invalid:focus,\r\n.form-check-input:focus,\r\n.radio-style.radio-success .form-check-input:focus,\r\n.radio-style.radio-warning .form-check-input:focus,\r\n.radio-style.radio-danger .form-check-input:focus {\r\n  box-shadow: none;\r\n}\r\n\r\n.hover-underline:hover {\r\n  text-decoration: underline;\r\n}\r\n"
  },
  {
    "path": "public/assets/scss/_default.scss",
    "content": "/* ========== DEFAULT CSS ======== */\r\n\r\n/* ======= Margin Top ======= */\r\n@for $i from 1 through 45 {\r\n  .mt-#{5 * $i} {\r\n    margin-top: 5px * $i;\r\n  }\r\n}\r\n\r\n/* ======= Margin Bottom ======= */\r\n@for $i from 1 through 45 {\r\n  .mb-#{5 * $i} {\r\n    margin-bottom: 5px * $i;\r\n  }\r\n}\r\n\r\n/* ======= Margin Left ======= */\r\n@for $i from 1 through 45 {\r\n  .ml-#{5 * $i} {\r\n    margin-left: 5px * $i;\r\n  }\r\n}\r\n\r\n/* ======= Margin Right ======= */\r\n@for $i from 1 through 45 {\r\n  .mr-#{5 * $i} {\r\n    margin-right: 5px * $i;\r\n  }\r\n}\r\n\r\n/* ======= Padding Top ======= */\r\n@for $i from 1 through 45 {\r\n  .pt-#{5 * $i} {\r\n    padding-top: 5px * $i;\r\n  }\r\n}\r\n\r\n/* ======= Padding Bottom ======= */\r\n@for $i from 1 through 45 {\r\n  .pb-#{5 * $i} {\r\n    padding-bottom: 5px * $i;\r\n  }\r\n}\r\n\r\n/* ======= Padding Left ======= */\r\n@for $i from 1 through 45 {\r\n  .pl-#{5 * $i} {\r\n    padding-left: 5px * $i;\r\n  }\r\n}\r\n\r\n/* ======= Padding Right ======= */\r\n@for $i from 1 through 45 {\r\n  .pr-#{5 * $i} {\r\n    padding-right: 5px * $i;\r\n  }\r\n}\r\n\r\n/* ======= bg-primary shades ========= */\r\n@for $i from 1 through 9 {\r\n  .bg-primary-#{100 * $i} {\r\n    background: rgba($primary, 0.1 * $i);\r\n  }\r\n}\r\n\r\n/* ======= bg-secondary shades ========= */\r\n@for $i from 1 through 9 {\r\n  .bg-secondary-#{100 * $i} {\r\n    background: rgba($secondary, 0.1 * $i);\r\n  }\r\n}\r\n\r\n/* ======= bg-success shades ========= */\r\n@for $i from 1 through 9 {\r\n  .bg-success-#{100 * $i} {\r\n    background: rgba($success, 0.1 * $i);\r\n  }\r\n}\r\n\r\n/* ======= bg-danger shades ========= */\r\n@for $i from 1 through 9 {\r\n  .bg-danger-#{100 * $i} {\r\n    background: rgba($danger, 0.1 * $i);\r\n  }\r\n}\r\n\r\n/* ======= bg-warning shades ========= */\r\n@for $i from 1 through 9 {\r\n  .bg-warning-#{100 * $i} {\r\n    background: rgba($warning, 0.1 * $i);\r\n  }\r\n}\r\n\r\n/* ======= bg-info shades ========= */\r\n@for $i from 1 through 9 {\r\n  .bg-info-#{100 * $i} {\r\n    background: rgba($info, 0.1 * $i);\r\n  }\r\n}\r\n\r\n/* ======= bg-dark shades ========= */\r\n@for $i from 1 through 9 {\r\n  .bg-dark-#{100 * $i} {\r\n    background: rgba($dark, 0.1 * $i);\r\n  }\r\n}\r\n\r\n/* ======= bg-purple shades ========= */\r\n@for $i from 1 through 9 {\r\n  .bg-purple-#{100 * $i} {\r\n    background: rgba($purple, 0.1 * $i);\r\n  }\r\n}\r\n\r\n/* ======= bg-orange shades ========= */\r\n@for $i from 1 through 9 {\r\n  .bg-orange-#{100 * $i} {\r\n    background: rgba($orange, 0.1 * $i);\r\n  }\r\n}\r\n\r\n/* ======== Background Colors ========== */\r\n.primary-bg {\r\n  background-color: $primary;\r\n}\r\n.secondary-bg {\r\n  background-color: $secondary;\r\n}\r\n.success-bg {\r\n  background-color: $success;\r\n}\r\n.danger-bg {\r\n  background-color: $danger;\r\n}\r\n.warning-bg {\r\n  background-color: $warning;\r\n}\r\n.info-bg {\r\n  background-color: $info;\r\n}\r\n.dark-bg {\r\n  background-color: $dark;\r\n}\r\n.light-bg {\r\n  background-color: $light;\r\n}\r\n.active-bg {\r\n  background-color: $active;\r\n}\r\n.deactive-bg {\r\n  background-color: $deactive;\r\n}\r\n.deactive-bg {\r\n  background-color: $deactive;\r\n}\r\n.gray-bg {\r\n  background-color: $gray;\r\n}\r\n.purple-bg {\r\n  background-color: $purple;\r\n}\r\n.orange-bg {\r\n  background-color: $orange;\r\n}\r\n.deep-blue-bg {\r\n  background-color: $deep-blue;\r\n}\r\n\r\n/* ======== Text Colors ========== */\r\n.text-primary {\r\n  color: $primary !important;\r\n}\r\n.text-secondary {\r\n  color: $secondary !important;\r\n}\r\n.text-success {\r\n  color: $success !important;\r\n}\r\n.text-danger {\r\n  color: $danger !important;\r\n}\r\n.text-warning {\r\n  color: $warning !important;\r\n}\r\n.text-info {\r\n  color: $info !important;\r\n}\r\n.text-dark {\r\n  color: $dark !important;\r\n}\r\n.text-light {\r\n  color: $light !important;\r\n}\r\n.text-active {\r\n  color: $active !important;\r\n}\r\n.text-deactive {\r\n  color: $deactive !important;\r\n}\r\n.text-deactive {\r\n  color: $deactive !important;\r\n}\r\n.text-gray {\r\n  color: $gray !important;\r\n}\r\n.text-orange {\r\n  color: $orange !important;\r\n}\r\n\r\n/* ========= Font Weight =========== */\r\n.fw-300 {\r\n  font-weight: 300;\r\n}\r\n.fw-400 {\r\n  font-weight: 400;\r\n}\r\n.fw-500 {\r\n  font-weight: 500;\r\n}\r\n.fw-600 {\r\n  font-weight: 600;\r\n}\r\n.fw-700 {\r\n  font-weight: 700;\r\n}\r\n.fw-800 {\r\n  font-weight: 800;\r\n}\r\n.fw-900 {\r\n  font-weight: 900;\r\n}\r\n"
  },
  {
    "path": "public/assets/scss/_mixin.scss",
    "content": "@mixin transition($time) {\r\n  -webkit-transition: all $time ease-out 0s;\r\n  -moz-transition: all $time ease-out 0s;\r\n  -ms-transition: all $time ease-out 0s;\r\n  -o-transition: all $time ease-out 0s;\r\n  transition: all $time ease-out 0s;\r\n}\r\n\r\n@mixin transform($value) {\r\n  -webkit-transform: $value;\r\n  -moz-transform: $value;\r\n  -ms-transform: $value;\r\n  -o-transform: $value;\r\n  transform: $value;\r\n}\r\n\r\n@mixin user-select($value) {\r\n  -webkit-user-select: $value;\r\n  -moz-user-select: $value;\r\n  -ms-user-select: $value;\r\n  user-select: $value;\r\n}\r\n\r\n@mixin box-sizing($value) {\r\n  -webkit-box-sizing: $value;\r\n  -moz-box-sizing: $value;\r\n  box-sizing: $value;\r\n}\r\n\r\n@mixin animation($value) {\r\n  -webkit-animation: $value;\r\n  -moz-animation: $value;\r\n  -o-animation: $value;\r\n  animation: $value;\r\n}\r\n\r\n@mixin animation-delay($value) {\r\n  -webkit-animation-delay: $value;\r\n  -moz-animation-delay: $value;\r\n  -o-animation-delay: $value;\r\n  animation-delay: $value;\r\n}\r\n\r\n@mixin box-shadow($value) {\r\n  -webkit-box-shadow: $value;\r\n  -moz-box-shadow: $value;\r\n  box-shadow: $value;\r\n}\r\n\r\n// Placeholder Mixins\r\n@mixin placeholder {\r\n  &::placeholder {\r\n    @content;\r\n  }\r\n  &::-moz-placeholder {\r\n    @content;\r\n  }\r\n  &::-moz-placeholder {\r\n    @content;\r\n  }\r\n  &::-webkit-input-placeholder {\r\n    @content;\r\n  }\r\n}\r\n\r\n@mixin flex-center {\r\n  display: flex;\r\n  justify-content: center;\r\n  align-items: center;\r\n}\r\n"
  },
  {
    "path": "public/assets/scss/_sidebar.scss",
    "content": "/* =========== Sidebar css =========== */\n.sidebar-nav-wrapper {\n  background: $white;\n  width: 250px;\n  padding: 20px 0px;\n  height: 100vh;\n  position: fixed;\n  overflow-y: scroll;\n  overflow-x: hidden;\n  top: 0;\n  left: 0;\n  z-index: 99;\n  box-shadow: 0px 0px 30px rgba(200, 208, 216, 0.3);\n  @include transition(0.3s);\n  @include transform(translateX(0));\n\n  @media #{$lg, $md, $xs} {\n    @include transform(translateX(-260px));\n  }\n\n  &.active {\n    @include transform(translateX(-260px));\n\n    @media #{$lg, $md, $xs} {\n      @include transform(translateX(0px));\n    }\n  }\n\n  .navbar-logo {\n    text-align: center;\n    padding: 0 25px;\n    margin-bottom: 30px;\n  }\n\n  .sidebar-nav {\n    .divider {\n      padding: 5px 25px;\n      width: 100%;\n\n      hr {\n        height: 1px;\n        background: #e2e2e2;\n      }\n    }\n\n    ul {\n      .nav-item {\n        position: relative;\n        margin: 5px 0px;\n\n        &.nav-item-has-children {\n          & > a {\n            color: $dark;\n\n            &::before {\n              opacity: 1;\n              visibility: visible;\n            }\n\n            &::after {\n              content: \"\\ea5e\";\n              font: normal normal normal 1em/1 \"LineIcons\";\n              position: absolute;\n              right: 25px;\n              top: 16px;\n              font-size: 12px;\n              @include transition(0.3s);\n              @include transform(rotate(180deg));\n            }\n\n            &.collapsed {\n              color: $gray;\n\n              &::before {\n                opacity: 0;\n                visibility: hidden;\n              }\n\n              &::after {\n                @include transform(rotate(0deg));\n              }\n            }\n          }\n\n          ul {\n            padding: 0px 15px;\n            li {\n              margin-bottom: 10px;\n\n              &:last-child {\n                margin-bottom: 0px;\n              }\n\n              a {\n                font-size: 14px;\n                font-weight: 400;\n                border-radius: 6px;\n                padding: 8px 15px;\n                display: flex;\n                align-items: center;\n                border: 1px solid transparent;\n\n                &.active,\n                &:hover {\n                  color: $primary;\n                  border-color: rgba($primary, 0.15);\n                  background: rgba($primary, 0.1);\n                }\n\n                i {\n                  font-size: 16px;\n                  margin-right: 15px;\n                }\n\n                span.text {\n                  display: flex;\n                  align-items: center;\n                  justify-content: space-between;\n                  width: 100%;\n                }\n\n                span.pro-badge {\n                  background: $primary;\n                  color: $white;\n                  padding: 1px 6px;\n                  border-radius: 4px;\n                  font-size: 10px;\n                  margin-left: 10px;\n                }\n              }\n            }\n          }\n        }\n\n        a {\n          display: flex;\n          align-items: center;\n          color: $gray;\n          font-size: 16px;\n          font-weight: 500;\n          width: 100%;\n          position: relative;\n          z-index: 1;\n          padding: 10px 25px;\n\n          &::before {\n            content: \"\";\n            position: absolute;\n            left: 0;\n            top: 0;\n            height: 100%;\n            width: 4px;\n            background: $primary;\n            border-radius: 0 3px 3px 0px;\n            opacity: 0;\n            visibility: hidden;\n            @include transition(0.3s);\n          }\n\n          span.text {\n            display: flex;\n            align-items: center;\n            justify-content: space-between;\n            width: 100%;\n          }\n\n          span.pro-badge {\n            background: $primary;\n            color: $white;\n            padding: 1px 6px;\n            border-radius: 4px;\n            font-size: 10px;\n            margin-left: 10px;\n          }\n\n          .icon {\n            margin-right: 12px;\n            font-size: 18px;\n\n            svg {\n              fill: currentColor;\n            }\n          }\n        }\n        &.active,\n        &:hover {\n          & > a,\n          & > a.collapsed {\n            color: $dark;\n\n            &::before {\n              opacity: 1;\n              visibility: visible;\n            }\n          }\n        }\n      }\n    }\n  }\n}\n\n.overlay {\n  position: fixed;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  background: rgba($black, 0.3);\n  z-index: 11;\n  @include transform(translateX(-100%));\n  opacity: 0;\n  visibility: hidden;\n\n  @media #{$desktop, $laptop} {\n    display: none;\n  }\n\n  &.active {\n    @media #{$lg, $md, $xs} {\n      opacity: 1;\n      visibility: visible;\n      @include transform(translateX(0));\n    }\n  }\n}\n\n.main-wrapper {\n  @include transition(0.3s);\n  margin-left: 250px;\n  min-height: 100vh;\n  padding-bottom: 85px;\n  position: relative;\n\n  @media #{$lg, $md, $xs} {\n    margin-left: 0;\n  }\n\n  @media #{$xs} {\n    padding-bottom: 110px;\n  }\n\n  &.active {\n    margin-left: 0;\n  }\n\n  .container-fluid {\n    padding-left: 40px;\n    padding-right: 40px;\n\n    @media #{$xs} {\n      padding-left: 20px;\n      padding-right: 20px;\n    }\n  }\n\n  .footer {\n    padding: 25px 0;\n    justify-items: flex-end;\n    position: absolute;\n    bottom: 0;\n    width: 100%;\n\n    .copyright {\n      p {\n        a {\n          color: inherit;\n\n          &:hover {\n            color: $primary;\n          }\n        }\n      }\n    }\n\n    .terms {\n      @media #{$xs} {\n        margin-bottom: 10px;\n        text-align: center;\n      }\n\n      a {\n        color: $gray;\n\n        &:hover {\n          color: $primary;\n        }\n      }\n    }\n  }\n}\n\n.promo-box {\n  box-shadow: 0px 10px 20px rgba(200, 208, 216, 0.3);\n  padding: 24px 16px;\n  text-align: center;\n  max-width: 210px;\n  margin: 0 auto;\n  margin-top: 32px;\n  border-radius: 4px;\n\n  h3 {\n    font-size: 16px;\n    font-weight: 600;\n    margin-bottom: 4px;\n  }\n  p {\n    font-size: 12px;\n    line-height: 1.5;\n    margin-bottom: 16px;\n  }\n\n  .main-btn {\n    padding: 12px;\n    width: 100%;\n  }\n}\n"
  },
  {
    "path": "public/assets/scss/_variables.scss",
    "content": "// Font Family\r\n$font: \"Inter\", sans-serif;\r\n\r\n// ======== white colors variants\r\n$white: #fff;\r\n$white-90: rgba($white, 0.9);\r\n$white-80: rgba($white, 0.8);\r\n$white-70: rgba($white, 0.7);\r\n$white-60: rgba($white, 0.6);\r\n$white-50: rgba($white, 0.5);\r\n$white-40: rgba($white, 0.4);\r\n$white-30: rgba($white, 0.3);\r\n$white-20: rgba($white, 0.2);\r\n$white-10: rgba($white, 0.1);\r\n\r\n// ========== black colors variants\r\n$black: #000;\r\n$black-90: rgba($black, 0.9);\r\n$black-80: rgba($black, 0.8);\r\n$black-70: rgba($black, 0.7);\r\n$black-60: rgba($black, 0.6);\r\n$black-50: rgba($black, 0.5);\r\n$black-40: rgba($black, 0.4);\r\n$black-30: rgba($black, 0.3);\r\n$black-20: rgba($black, 0.2);\r\n$black-10: rgba($black, 0.1);\r\n\r\n// ======== dark color\r\n$dark: #262d3f;\r\n$dark-2: #2f3546;\r\n$dark-3: #090e34;\r\n\r\n// ======== gray color\r\n$gray: #5d657b;\r\n$gray-2: #c2cbd6;\r\n\r\n// ======== primary color\r\n$primary: #4a6cf7;\r\n\r\n// ======== secondary color\r\n$secondary: #00c1f8;\r\n\r\n// ======== success color\r\n$success: #219653;\r\n\r\n// ======== danger color\r\n$danger: #d50100;\r\n\r\n// ======== warning color\r\n$warning: #f7c800;\r\n\r\n// ======== info color\r\n$info: #97ca31;\r\n\r\n// ======== purple color\r\n$purple: #9b51e0;\r\n\r\n// ======== light color\r\n$light: #efefef;\r\n$light-2: #e5e5e5;\r\n\r\n// ======== active color\r\n$active: #4a6cf7;\r\n\r\n// ======== deactive color\r\n$deactive: #cbe1ff;\r\n\r\n// ======== orange color\r\n$orange: #f2994a;\r\n\r\n// ======== deep-blue color\r\n$deep-blue: #345d9d;\r\n\r\n// ======== Shadow\r\n$shadow-one: 0px 5px 20px rgba(0, 0, 0, 0.1);\r\n\r\n// ======== Responsive Variables\r\n$desktop: \"only screen and (min-width: 1400px)\";\r\n$laptop: \"only screen and (min-width: 1200px) and (max-width: 1399px)\";\r\n$lg: \"only screen and (min-width: 992px) and (max-width: 1199px)\";\r\n$md: \"only screen and (min-width: 768px) and (max-width: 991px)\";\r\n$xs: \"(max-width: 767px)\";\r\n$sm: \"only screen and (min-width: 550px) and (max-width: 767px)\";\r\n"
  },
  {
    "path": "public/assets/scss/alerts/_alerts.scss",
    "content": "/* ============ alerts css ============ */\n.alert-box {\n  display: flex;\n  position: relative;\n  margin-bottom: 20px;\n\n  @media #{$xs} {\n    padding-left: 0px !important;\n  }\n\n  .left {\n    max-width: 75px;\n    width: 100%;\n    height: 100%;\n    border-radius: 4px;\n    background: $danger;\n    position: absolute;\n    left: 0;\n    top: 0;\n    @include flex-center;\n\n    @media #{$xs} {\n      display: none;\n    }\n\n    h5 {\n      @include transform(rotate(-90deg));\n      color: $white;\n    }\n  }\n\n  .alert {\n    margin-bottom: 0px;\n    padding: 25px 40px;\n\n    @media #{$xs} {\n      padding: 20px;\n    }\n  }\n}\n/* Alert Primary */\n.primary-alert {\n  .left {\n    background: $primary;\n  }\n\n  .alert {\n    color: $primary;\n    border: 1px solid $primary;\n    background: rgba($primary, 0.2);\n    width: 100%;\n\n    .alert-heading {\n      color: $primary;\n      margin-bottom: 15px;\n    }\n  }\n}\n\n/* Alert Danger */\n.danger-alert {\n  .left {\n    background: $danger;\n  }\n\n  .alert {\n    color: $danger;\n    border: 1px solid $danger;\n    background: rgba($danger, 0.2);\n    width: 100%;\n\n    .alert-heading {\n      color: $danger;\n      margin-bottom: 15px;\n    }\n  }\n}\n\n/* Alert warning */\n.warning-alert {\n  .left {\n    background: $warning;\n  }\n  .alert {\n    color: $warning;\n    border: 1px solid $warning;\n    background: rgba($warning, 0.2);\n    width: 100%;\n\n    .alert-heading {\n      color: $warning;\n      margin-bottom: 15px;\n    }\n  }\n}\n\n/* Alert warning */\n.warning-alert {\n  .left {\n    background: $warning;\n  }\n  .alert {\n    color: $warning;\n    border: 1px solid $warning;\n    background: rgba($warning, 0.2);\n    width: 100%;\n\n    .alert-heading {\n      color: $warning;\n      margin-bottom: 15px;\n    }\n  }\n}\n/* Alert info */\n.info-alert {\n  .left {\n    background: $info;\n  }\n  .alert {\n    color: $info;\n    border: 1px solid $info;\n    background: rgba($info, 0.2);\n    width: 100%;\n\n    .alert-heading {\n      color: $info;\n      margin-bottom: 15px;\n    }\n  }\n}\n\n/* Alert success */\n.success-alert {\n  .left {\n    background: $success;\n  }\n  .alert {\n    color: $success;\n    border: 1px solid $success;\n    background: rgba($success, 0.2);\n    width: 100%;\n\n    .alert-heading {\n      color: $success;\n      margin-bottom: 15px;\n    }\n  }\n}\n\n/* Alert secondary */\n.secondary-alert {\n  .left {\n    background: $secondary;\n  }\n  .alert {\n    color: $secondary;\n    border: 1px solid $secondary;\n    background: rgba($secondary, 0.2);\n    width: 100%;\n\n    .alert-heading {\n      color: $secondary;\n      margin-bottom: 15px;\n    }\n  }\n}\n\n/* Alert gray */\n.gray-alert {\n  .left {\n    background: $gray;\n  }\n  .alert {\n    color: $gray;\n    border: 1px solid $gray;\n    background: rgba($gray, 0.2);\n    width: 100%;\n\n    .alert-heading {\n      color: $gray;\n      margin-bottom: 15px;\n    }\n  }\n}\n\n/* Alert black */\n.black-alert {\n  .left {\n    background: $black;\n  }\n  .alert {\n    color: $black;\n    border: 1px solid $black;\n    background: rgba($black, 0.2);\n    width: 100%;\n\n    .alert-heading {\n      color: $black;\n      margin-bottom: 15px;\n    }\n  }\n}\n/* Alert orange */\n.orange-alert {\n  .left {\n    background: $orange;\n  }\n  .alert {\n    color: $orange;\n    border: 1px solid $orange;\n    background: rgba($orange, 0.2);\n    width: 100%;\n\n    .alert-heading {\n      color: $orange;\n      margin-bottom: 15px;\n    }\n  }\n}\n"
  },
  {
    "path": "public/assets/scss/auth/_signin.scss",
    "content": "/* ============ signin css ============= */\n.auth-row {\n  background: $white;\n  border-radius: 4px;\n  overflow: hidden;\n}\n\n.auth-cover-wrapper {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  padding: 45px;\n  position: relative;\n  z-index: 1;\n  height: 100%;\n\n  @media #{$xs} {\n    padding: 30px 20px;\n  }\n\n  .auth-cover {\n    .title {\n      text-align: cover;\n      margin-bottom: 40px;\n\n      @media #{$xs} {\n        h1 {\n          font-size: 24px;\n        }\n      }\n    }\n\n    .cover-image {\n      max-width: 100%;\n      margin: auto;\n\n      img {\n        width: 100%;\n      }\n    }\n    .shape-image {\n      position: absolute;\n      z-index: -1;\n      right: 0;\n      bottom: 5%;\n    }\n  }\n}\n\n.signin-wrapper {\n  background: $white;\n  padding: 60px;\n  min-height: 600px;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n\n  @media #{$lg} {\n    padding: 40px;\n  }\n\n  @media #{$xs} {\n    padding: 30px;\n  }\n  .form-wrapper {\n    width: 100%;\n  }\n  .singin-option {\n    button {\n      font-size: 16px;\n      font-weight: 600;\n\n      @media #{$laptop} {\n        padding-left: 25px;\n        padding-right: 25px;\n      }\n\n      @media #{$lg} {\n        padding-left: 30px;\n        padding-right: 30px;\n      }\n\n      @media #{$xs} {\n        width: 100%;\n      }\n\n      @media #{$sm} {\n        width: auto;\n      }\n    }\n\n    a {\n      &:hover {\n        text-decoration: underline;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "public/assets/scss/auth/_signup.scss",
    "content": "/* ============ signup css ============= */\n.auth-row {\n  background: $white;\n  border-radius: 4px;\n  overflow: hidden;\n}\n\n.auth-cover-wrapper {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  padding: 45px;\n  position: relative;\n  z-index: 1;\n  height: 100%;\n\n  @media #{$xs} {\n    padding: 30px 20px;\n  }\n\n  .auth-cover {\n    .title {\n      text-align: cover;\n      margin-bottom: 40px;\n\n      @media #{$xs} {\n        h1 {\n          font-size: 24px;\n        }\n      }\n    }\n\n    .cover-image {\n      max-width: 100%;\n      margin: auto;\n\n      img {\n        width: 100%;\n      }\n    }\n    .shape-image {\n      position: absolute;\n      z-index: -1;\n      right: 0;\n      bottom: 5%;\n    }\n  }\n}\n\n.signup-wrapper {\n  background: $white;\n  padding: 60px;\n  min-height: 600px;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n\n  @media #{$lg} {\n    padding: 40px;\n  }\n\n  @media #{$xs} {\n    padding: 30px;\n  }\n  .form-wrapper {\n    width: 100%;\n  }\n  .singup-option {\n    button {\n      font-size: 16px;\n      font-weight: 600;\n\n      @media #{$laptop} {\n        padding-left: 25px;\n        padding-right: 25px;\n      }\n\n      @media #{$lg} {\n        padding-left: 30px;\n        padding-right: 30px;\n      }\n\n      @media #{$xs} {\n        width: 100%;\n      }\n\n      @media #{$sm} {\n        width: auto;\n      }\n    }\n\n    a {\n      &:hover {\n        text-decoration: underline;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "public/assets/scss/buttons/_buttons.scss",
    "content": "/* ========== Buttons css ========== */\n\n/* buttons base styles */\n.main-btn {\n  display: inline-block;\n  text-align: center;\n  white-space: nowrap;\n  vertical-align: middle;\n  @include user-select(none);\n  padding: 15px 45px;\n  font-weight: 500;\n  font-size: 14px;\n  line-height: 24px;\n  border-radius: 4px;\n  cursor: pointer;\n  z-index: 5;\n  transition: all 0.4s ease-in-out;\n  border: 1px solid transparent;\n  overflow: hidden;\n\n  &:hover {\n    color: inherit;\n  }\n}\n\n.btn-sm {\n  padding: 10px 20px;\n  font-weight: 400;\n}\n\n/* buttons hover effect */\n.btn-hover {\n  position: relative;\n  overflow: hidden;\n\n  &::after {\n    content: \"\";\n    position: absolute;\n    width: 0%;\n    height: 0%;\n    border-radius: 50%;\n    background: rgba(255, 255, 255, 0.05);\n    top: 50%;\n    left: 50%;\n    padding: 50%;\n    z-index: -1;\n    @include transition(0.3s);\n    @include transform(translate3d(-50%, -50%, 0) scale(0));\n  }\n\n  &:hover {\n    &::after {\n      @include transform(translate3d(-50%, -50%, 0) scale(1.3));\n    }\n  }\n}\n/* primary buttons */\n.primary-btn {\n  background: $primary;\n  color: $white;\n\n  &:hover {\n    color: $white;\n  }\n}\n.primary-btn-outline {\n  background: transparent;\n  color: $primary;\n  border-color: $primary;\n\n  &:hover {\n    color: $white;\n    background: $primary;\n  }\n}\n\n/* secondary buttons */\n.secondary-btn {\n  background: $secondary;\n  color: $white;\n\n  &:hover {\n    color: $white;\n  }\n}\n.secondary-btn-outline {\n  background: transparent;\n  color: $secondary;\n  border-color: $secondary;\n\n  &:hover {\n    color: $white;\n    background: $secondary;\n  }\n}\n\n/* success buttons */\n.success-btn {\n  background: $success;\n  color: $white;\n\n  &:hover {\n    color: $white;\n  }\n}\n.success-btn-outline {\n  background: transparent;\n  color: $success;\n  border-color: $success;\n\n  &:hover {\n    color: $white;\n    background: $success;\n  }\n}\n\n/* danger buttons */\n.danger-btn {\n  background: $danger;\n  color: $white;\n\n  &:hover {\n    color: $white;\n  }\n}\n.danger-btn-outline {\n  background: transparent;\n  color: $danger;\n  border-color: $danger;\n\n  &:hover {\n    color: $white;\n    background: $danger;\n  }\n}\n\n/* warning buttons */\n.warning-btn {\n  background: $warning;\n  color: $white;\n\n  &:hover {\n    color: $white;\n  }\n}\n.warning-btn-outline {\n  background: transparent;\n  color: $warning;\n  border-color: $warning;\n\n  &:hover {\n    color: $white;\n    background: $warning;\n  }\n}\n\n/* info buttons */\n.info-btn {\n  background: $info;\n  color: $white;\n\n  &:hover {\n    color: $white;\n  }\n}\n.info-btn-outline {\n  background: transparent;\n  color: $info;\n  border-color: $info;\n\n  &:hover {\n    color: $white;\n    background: $info;\n  }\n}\n\n/* dark buttons */\n.dark-btn {\n  background: $dark;\n  color: $white;\n\n  &:hover {\n    color: $white;\n  }\n}\n.dark-btn-outline {\n  background: transparent;\n  color: $dark;\n  border-color: $dark;\n\n  &:hover {\n    color: $white;\n    background: $dark;\n  }\n}\n\n/* light buttons */\n.light-btn {\n  background: $light;\n  color: $dark;\n\n  &:hover {\n    color: $dark;\n  }\n}\n.light-btn-outline {\n  background: transparent;\n  color: $dark;\n  border-color: $light;\n\n  &:hover {\n    color: $dark;\n    background: $light;\n  }\n}\n\n/* active buttons */\n.active-btn {\n  background: $active;\n  color: $white;\n\n  &:hover {\n    color: $white;\n  }\n}\n.active-btn-outline {\n  background: transparent;\n  color: $active;\n  border-color: $active;\n\n  &:hover {\n    color: $white;\n    background: $active;\n  }\n}\n\n/* deactive buttons */\n.deactive-btn {\n  background: $deactive;\n  color: $active;\n\n  &:hover {\n    color: $active;\n  }\n}\n.deactive-btn-outline {\n  background: transparent;\n  color: $active;\n  border-color: $deactive;\n\n  &:hover {\n    color: $active;\n    background: $deactive;\n  }\n}\n\n/* =========  square-btn ========= */\n.square-btn {\n  border-radius: 0px;\n}\n\n/* =========  rounded-md ========= */\n.rounded-md {\n  border-radius: 10px;\n}\n/* =========  rounded-full ========= */\n.rounded-full {\n  border-radius: 30px;\n}\n\n/* ========== buttons group css ========= */\n.buttons-group {\n  display: flex;\n  flex-wrap: wrap;\n  margin: 0 -10px;\n\n  li {\n    margin: 10px;\n  }\n}\n\n/* ====== Status Button ====== */\n.status-btn {\n  padding: 7px 15px;\n  border-radius: 30px;\n  font-size: 14px;\n  font-weight: 400;\n\n  &.primary-btn {\n    color: $white;\n    background: rgba($primary, 1);\n  }\n  &.active-btn {\n    color: $primary;\n    background: rgba($primary, 0.1);\n  }\n  &.close-btn {\n    color: $danger;\n    background: rgba($danger, 0.1);\n  }\n  &.warning-btn {\n    color: $warning;\n    background: rgba($warning, 0.1);\n  }\n  &.info-btn {\n    color: $info;\n    background: rgba($info, 0.1);\n  }\n  &.success-btn {\n    color: $success;\n    background: rgba($success, 0.1);\n  }\n  &.secondary-btn {\n    color: $secondary;\n    background: rgba($secondary, 0.1);\n  }\n  &.dark-btn {\n    color: $dark;\n    background: rgba($dark, 0.1);\n  }\n  &.orange-btn {\n    color: $orange;\n    background: rgba($orange, 0.1);\n  }\n}\n"
  },
  {
    "path": "public/assets/scss/calendar/_calendar.scss",
    "content": "/* ============ Calendar Css ============= */\n\n.calendar-card {\n  .fc {\n    height: 450px;\n\n    &#calendar-full {\n      height: 600px;\n    }\n\n    table {\n      border: none;\n    }\n\n    .fc-toolbar-title {\n      font-size: 16px;\n      font-weight: 500;\n    }\n\n    .fc-button {\n      background: transparent;\n      border: none;\n      color: $gray;\n      text-transform: capitalize;\n\n      &:focus {\n        @include box-shadow(none);\n        color: $primary;\n      }\n    }\n\n    th {\n      text-align: left;\n      border-bottom: 1px solid $black-10 !important;\n      border-right: 0px;\n\n      a {\n        color: $gray;\n        font-weight: 400;\n      }\n    }\n\n    .fc-day {\n      border-width: 4px;\n      background: $white;\n\n      &.fc-day-today {\n        .fc-daygrid-day-frame {\n          background: rgba($primary, 0.8);\n\n          a {\n            color: $white;\n          }\n        }\n      }\n\n      .fc-daygrid-day-frame {\n        display: flex;\n        flex-direction: column;\n        align-items: flex-end;\n        background: #f9f9f9;\n        border-radius: 6px;\n        // padding: 5px;\n\n        a {\n          color: $gray;\n        }\n      }\n    }\n  }\n\n  .fc-theme-standard td,\n  .fc-theme-standard th {\n    border-color: transparent;\n  }\n}\n"
  },
  {
    "path": "public/assets/scss/cards/_cards.scss",
    "content": "/* ========== cards css =========== */\n\n/* card-style-1 */\n.card-style-1 {\n  background: $white;\n  border: 1px solid $light;\n  border-radius: 10px;\n  padding: 25px 0;\n  position: relative;\n  @include transition(0.3s);\n\n  &:hover {\n    @include box-shadow(0px 0px 5px rgba(0, 0, 0, 0.1));\n  }\n\n  .card-meta {\n    display: flex;\n    align-items: center;\n    margin-bottom: 15px;\n    padding: 0 30px;\n\n    @media #{$xs} {\n      padding: 0 20px;\n    }\n\n    .image {\n      max-width: 40px;\n      width: 100%;\n      border-radius: 50%;\n      overflow: hidden;\n      margin-right: 12px;\n\n      img {\n        width: 100%;\n      }\n    }\n\n    .text {\n      p {\n        color: $dark;\n\n        a {\n          color: inherit;\n\n          &:hover {\n            color: $primary;\n          }\n        }\n      }\n    }\n  }\n  .card-image {\n    border-radius: 10px;\n    margin-bottom: 25px;\n    overflow: hidden;\n\n    a {\n      display: block;\n    }\n\n    img {\n      width: 100%;\n    }\n  }\n\n  .card-content {\n    padding: 0px 30px;\n\n    @media #{$xs} {\n      padding: 0px 20px;\n    }\n\n    h4 {\n      a {\n        color: inherit;\n        margin-bottom: 15px;\n        display: block;\n\n        &:hover {\n          color: $primary;\n        }\n      }\n    }\n  }\n}\n\n/* card-style-2 */\n.card-style-2 {\n  background: $white;\n  border: 1px solid $light;\n  border-radius: 4px;\n  padding: 20px;\n  @include transition(0.3s);\n\n  &:hover {\n    @include box-shadow(0px 0px 5px rgba(0, 0, 0, 0.1));\n  }\n\n  .card-image {\n    border-radius: 4px;\n    margin-bottom: 30px;\n    overflow: hidden;\n\n    a {\n      display: block;\n    }\n\n    img {\n      width: 100%;\n    }\n  }\n\n  .card-content {\n    padding: 0px 10px;\n\n    @media #{$xs} {\n      padding: 0px;\n    }\n\n    h4 {\n      a {\n        color: inherit;\n        margin-bottom: 15px;\n        display: block;\n\n        &:hover {\n          color: $primary;\n        }\n      }\n    }\n  }\n}\n\n/* card-style-3 */\n.card-style-3 {\n  background: $white;\n  border: 1px solid $light;\n  border-radius: 4px;\n  padding: 25px 30px;\n  @include transition(0.3s);\n\n  &:hover {\n    @include box-shadow(0px 0px 5px rgba(0, 0, 0, 0.1));\n  }\n\n  .card-content {\n    h4 {\n      a {\n        color: inherit;\n        margin-bottom: 15px;\n        display: block;\n\n        &:hover {\n          color: $primary;\n        }\n      }\n    }\n\n    a.read-more {\n      font-weight: 500;\n      color: $dark;\n      margin-top: 20px;\n\n      &:hover {\n        color: $primary;\n        letter-spacing: 2px;\n      }\n    }\n  }\n}\n\n/* ======= icon-card ======== */\n.icon-card {\n  display: flex;\n  align-items: center;\n  background: $white;\n  padding: 30px 20px;\n  border: 1px solid #e2e8f0;\n  box-shadow: 0px 10px 20px rgba(200, 208, 216, 0.3);\n  border-radius: 10px;\n\n  &.icon-card-3 {\n    display: block;\n    padding: 0px;\n\n    .card-content {\n      display: flex;\n      padding: 20px;\n      padding-bottom: 0;\n    }\n  }\n\n  h6 {\n    @media #{$laptop} {\n      font-size: 15px;\n    }\n  }\n\n  h3 {\n    @media #{$laptop} {\n      font-size: 20px;\n    }\n  }\n\n  &.icon-card-2 {\n    display: block;\n\n    .progress {\n      height: 7px;\n\n      .progress-bar {\n        border-radius: 4px;\n      }\n    }\n  }\n\n  .icon {\n    max-width: 46px;\n    width: 100%;\n    height: 46px;\n    border-radius: 10px;\n    @include flex-center;\n    font-size: 24px;\n    margin-right: 20px;\n    background: rgba($primary, 0.1);\n    color: $primary;\n    font-weight: 700;\n\n    @media #{$laptop} {\n      margin-right: 10px;\n    }\n\n    &.purple {\n      background: rgba($purple, 0.1);\n      color: $purple;\n    }\n    &.success {\n      background: rgba($success, 0.1);\n      color: $success;\n    }\n    &.primary {\n      background: rgba($primary, 0.1);\n      color: $primary;\n    }\n    &.orange {\n      background: rgba($orange, 0.1);\n      color: $orange;\n    }\n\n    &.opacity-100 {\n      &.purple {\n        background: $purple;\n        color: $white;\n      }\n      &.success {\n        background: $success;\n        color: $white;\n      }\n      &.primary {\n        background: $primary;\n        color: $white;\n      }\n      &.orange {\n        background: $orange;\n        color: $white;\n      }\n      &.deep-blue {\n        background: $deep-blue;\n        color: $white;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "public/assets/scss/dashboards/_dashboards.scss",
    "content": "/* ========== Dashboards css ================= */\n\n#doughnutChart1 {\n  @media #{$xs} {\n    height: 250px !important;\n  }\n}\n.legend3 {\n  li {\n    margin-right: 25px;\n\n    div {\n      white-space: nowrap;\n    }\n\n    .bg-color {\n      position: relative;\n      margin-left: 12px;\n      border-radius: 50%;\n\n      &::after {\n        content: \"\";\n        position: absolute;\n        width: 12px;\n        height: 12px;\n        border-radius: 50%;\n        background: inherit;\n        left: -12px;\n        top: 5px;\n      }\n    }\n    .text {\n      margin-left: 10px;\n\n      p {\n        display: flex;\n        align-items: center;\n        width: 100%;\n      }\n    }\n  }\n}\n\n.todo-list-wrapper {\n  ul {\n    li.todo-list-item {\n      position: relative;\n      display: flex;\n      align-items: center;\n      justify-content: space-between;\n      padding-left: 20px;\n      margin-bottom: 25px;\n\n      &:last-child {\n        margin-bottom: 0px;\n      }\n\n      &::before {\n        content: \"\";\n        position: absolute;\n        left: 0;\n        top: 0;\n        width: 4px;\n        height: 100%;\n      }\n\n      @media #{$xs} {\n        display: block;\n\n        .todo-status {\n          margin-top: 20px;\n        }\n      }\n\n      &.success {\n        &::before {\n          background: $success;\n        }\n      }\n      &.primary {\n        &::before {\n          background: $primary;\n        }\n      }\n      &.orange {\n        &::before {\n          background: $orange;\n        }\n      }\n      &.danger {\n        &::before {\n          background: $danger;\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "public/assets/scss/forms/_form-elements.scss",
    "content": "/* =========== form elements css ========== */\n\n/* ===== input style ===== */\n.input-style-1 {\n  position: relative;\n  margin-bottom: 30px;\n\n  label {\n    font-size: 14px;\n    font-weight: 500;\n    color: $dark;\n    display: block;\n    margin-bottom: 10px;\n  }\n\n  input,\n  textarea {\n    width: 100%;\n    background: rgba($light, 0.5);\n    border: 1px solid $light-2;\n    border-radius: 4px;\n    padding: 16px;\n    color: $gray;\n    resize: none;\n    transition: all 0.3s;\n\n    &:focus {\n      border-color: $primary;\n      background: $white;\n    }\n\n    &[type=\"date\"],\n    &[type=\"time\"] {\n      background: transparent;\n    }\n  }\n}\n\n.input-style-2 {\n  position: relative;\n  margin-bottom: 30px;\n  z-index: 1;\n\n  label {\n    font-size: 14px;\n    font-weight: 500;\n    color: $dark;\n    display: block;\n    margin-bottom: 10px;\n  }\n\n  input,\n  textarea {\n    width: 100%;\n    background: rgba($light, 0.5);\n    border: 1px solid $light-2;\n    border-radius: 4px;\n    padding: 16px;\n    color: $gray;\n    resize: none;\n    transition: all 0.3s;\n\n    &:focus {\n      border-color: $primary;\n      background: $white;\n    }\n\n    &[type=\"date\"],\n    &[type=\"time\"] {\n      background: transparent;\n    }\n  }\n\n  input[type=\"date\"]::-webkit-inner-spin-button,\n  input[type=\"date\"]::-webkit-calendar-picker-indicator {\n    opacity: 0;\n  }\n\n  input[type=\"date\"] ~ .icon {\n    z-index: -1;\n  }\n\n  .icon {\n    position: absolute;\n    right: 0;\n    bottom: 0;\n    padding: 17px;\n  }\n}\n\n.input-style-3 {\n  position: relative;\n  margin-bottom: 30px;\n\n  label {\n    font-size: 14px;\n    font-weight: 500;\n    color: $dark;\n    display: block;\n    margin-bottom: 10px;\n  }\n\n  input,\n  textarea {\n    width: 100%;\n    background: rgba($light, 0.5);\n    border: 1px solid $light-2;\n    border-radius: 4px;\n    padding: 16px;\n    padding-left: 45px;\n    color: $gray;\n    resize: none;\n    transition: all 0.3s;\n\n    &:focus {\n      border-color: $primary;\n      background: $white;\n    }\n  }\n\n  .icon {\n    position: absolute;\n    left: 0;\n    top: 0;\n    height: 100%;\n    padding: 16px;\n  }\n}\n\n/* ========= select style ========== */\n.select-style-1 {\n  margin-bottom: 30px;\n\n  label {\n    font-size: 14px;\n    font-weight: 500;\n    color: $dark;\n    display: block;\n    margin-bottom: 10px;\n  }\n\n  .select-position {\n    position: relative;\n\n    &::after {\n      border-bottom: 2px solid $gray;\n      border-right: 2px solid $gray;\n      content: \"\";\n      display: block;\n      height: 10px;\n      width: 10px;\n      margin-top: -5px;\n      pointer-events: none;\n      position: absolute;\n      right: 16px;\n      top: 50%;\n      -webkit-transform: rotate(45deg);\n      -ms-transform: rotate(45deg);\n      transform: rotate(45deg);\n      -webkit-transition: all 0.15s ease-in-out;\n      transition: all 0.15s ease-in-out;\n    }\n\n    &.select-sm {\n      &::after {\n        margin-top: -8px;\n      }\n\n      select {\n        padding-top: 8px;\n        padding-bottom: 8px;\n        font-size: 14px;\n      }\n    }\n\n    select {\n      width: 100%;\n      background: transparent;\n      border: 1px solid $light-2;\n      border-radius: 10px;\n      padding: 16px;\n      padding-right: 38px;\n      color: $gray;\n      appearance: none;\n      -webkit-appearance: none;\n      -moz-appearance: none;\n      @include transition(0.3s);\n\n      &:focus {\n        border-color: $primary;\n        outline: none;\n      }\n\n      &.light-bg {\n        background: rgba($light, 0.5);\n      }\n      &.light-bg:focus {\n        background: $white;\n      }\n\n      &.radius-30 {\n        border-radius: 30px;\n      }\n    }\n  }\n}\n\n.select-style-2 {\n  margin-bottom: 30px;\n\n  .select-position {\n    position: relative;\n\n    &.select-sm {\n      &::after {\n        margin-top: -8px;\n      }\n      &::before {\n        margin-top: 0;\n      }\n\n      select {\n        padding-top: 8px;\n        padding-bottom: 8px;\n        font-size: 14px;\n      }\n    }\n\n    &::before,\n    &::after {\n      content: \"\";\n      display: block;\n      height: 8px;\n      width: 8px;\n      pointer-events: none;\n      position: absolute;\n      right: 16px;\n      top: 50%;\n      -webkit-transform: rotate(45deg);\n      -ms-transform: rotate(45deg);\n      transform: rotate(45deg);\n      -webkit-transition: all 0.15s ease-in-out;\n      transition: all 0.15s ease-in-out;\n    }\n\n    &::before {\n      margin-top: 0px;\n      border-bottom: 1px solid $gray;\n      border-right: 1px solid $gray;\n    }\n\n    &::after {\n      margin-top: -8px;\n      border-top: 1px solid $gray;\n      border-left: 1px solid $gray;\n    }\n\n    select {\n      width: 100%;\n      background: transparent;\n      border: 1px solid $light-2;\n      border-radius: 10px;\n      padding: 16px;\n      padding-right: 38px;\n      color: $gray;\n      appearance: none;\n      -webkit-appearance: none;\n      -moz-appearance: none;\n      @include transition(0.3s);\n\n      &:focus {\n        border-color: $primary;\n        outline: none;\n      }\n\n      &.light-bg {\n        background: rgba($light, 0.5);\n      }\n      &.light-bg:focus {\n        background: $white;\n      }\n\n      &.select-sm {\n        padding-top: 8px;\n        padding-bottom: 8px;\n        font-size: 14px;\n      }\n    }\n  }\n}\n\n.select-style-3 {\n  margin-bottom: 30px;\n\n  .select-position {\n    position: relative;\n\n    &::after {\n      border-bottom: 2px solid $gray;\n      border-right: 2px solid $gray;\n      content: \"\";\n      display: block;\n      height: 10px;\n      width: 10px;\n      margin-top: -7px;\n      pointer-events: none;\n      position: absolute;\n      right: 0px;\n      top: 50%;\n      -webkit-transform: rotate(45deg);\n      -ms-transform: rotate(45deg);\n      transform: rotate(45deg);\n      -webkit-transition: all 0.15s ease-in-out;\n      transition: all 0.15s ease-in-out;\n    }\n\n    &.select-sm {\n      &::after {\n        margin-top: -8px;\n      }\n\n      select {\n        padding-top: 8px;\n        padding-bottom: 8px;\n        font-size: 14px;\n      }\n    }\n\n    select {\n      width: 100%;\n      background: transparent;\n      border: transparent;\n      border-radius: 10px;\n      padding-right: 38px;\n      color: $black;\n      appearance: none;\n      -webkit-appearance: none;\n      -moz-appearance: none;\n      @include transition(0.3s);\n\n      &:focus {\n        border-color: $primary;\n        outline: none;\n      }\n\n      &.light-bg {\n        background: rgba($light, 0.5);\n      }\n    }\n  }\n}\n\n.toggle-switch {\n  padding-left: 60px;\n  min-height: 30px;\n\n  .form-check-input {\n    width: 50px;\n    height: 28px;\n    margin-left: -60px;\n    cursor: pointer;\n  }\n\n  label {\n    margin-top: 6px;\n    font-size: 14px;\n    color: $dark;\n    cursor: pointer;\n    user-select: none;\n  }\n}\n\n//* ===== checkbox style  */\n.checkbox-style {\n  padding-left: 40px;\n  min-height: 28px;\n\n  .form-check-input {\n    width: 28px;\n    height: 28px;\n    border-radius: 4px;\n    margin-left: -40px;\n    cursor: pointer;\n\n    &:disabled {\n      cursor: auto;\n    }\n  }\n  .form-check-input:disabled ~ label {\n    cursor: auto;\n  }\n  label {\n    margin-top: 6px;\n    cursor: pointer;\n    user-select: none;\n  }\n\n  &.checkbox-success {\n    .form-check-input {\n      &:checked {\n        background-color: $success;\n        border-color: $success;\n      }\n    }\n  }\n  &.checkbox-warning {\n    .form-check-input {\n      &:checked {\n        background-color: $warning;\n        border-color: $warning;\n      }\n    }\n  }\n  &.checkbox-danger {\n    .form-check-input {\n      &:checked {\n        background-color: $danger;\n        border-color: $danger;\n      }\n    }\n  }\n}\n\n//* ===== radio style  */\n.radio-style {\n  padding-left: 40px;\n  min-height: 28px;\n\n  .form-check-input {\n    width: 28px;\n    height: 28px;\n    border-radius: 50%;\n    margin-left: -40px;\n    cursor: pointer;\n\n    &:disabled {\n      cursor: auto;\n    }\n  }\n  .form-check-input:disabled ~ label {\n    cursor: auto;\n  }\n  label {\n    margin-top: 6px;\n    cursor: pointer;\n    user-select: none;\n  }\n\n  &.radio-success {\n    .form-check-input {\n      &:checked {\n        background-color: $success;\n        border-color: $success;\n      }\n    }\n  }\n  &.radio-warning {\n    .form-check-input {\n      &:checked {\n        background-color: $warning;\n        border-color: $warning;\n      }\n    }\n  }\n  &.radio-danger {\n    .form-check-input {\n      &:checked {\n        background-color: $danger;\n        border-color: $danger;\n      }\n    }\n  }\n}\n\n.button-group {\n  @media #{$xs} {\n    .main-btn {\n      width: 100%;\n    }\n  }\n}\n\n.buy-sell-form {\n  .input-group {\n    display: flex;\n\n    input {\n      width: 60%;\n      background: transparent;\n      border: 1px solid #e2e8f0;\n      border-radius: 4px;\n      padding: 8px 16px;\n      font-size: 14px;\n      color: $gray;\n\n      &:focus {\n        border-color: $primary;\n      }\n    }\n\n    .select-style-1 {\n      width: 40%;\n\n      .select-position {\n        &::after {\n          width: 8px;\n          height: 8px;\n        }\n      }\n    }\n\n    select {\n      border: 1px solid #e2e8f0;\n      border-radius: 0px 4px 4px 0px;\n      padding: 8px 16px;\n      padding-right: 24px;\n      font-size: 14px;\n      color: $gray;\n    }\n  }\n\n  .buy-sell-btn {\n    .main-btn {\n      display: block;\n      width: 100%;\n      font-weight: 500;\n\n      &:hover {\n        box-shadow: $shadow-one;\n      }\n\n      &.success-btn {\n        background: #08c18d;\n      }\n\n      &.danger-btn {\n        background: #eb5757;\n      }\n    }\n  }\n\n  .field-group-2 {\n    label {\n      font-size: 12px;\n    }\n\n    .input-group {\n      input {\n        font-size: 12px;\n        width: 70%;\n      }\n\n      span {\n        font-size: 12px;\n        padding: 8px 16px;\n        width: 30%;\n        background: #e2e8f0;\n        text-align: center;\n        border-radius: 0px 4px 4px 0px;\n        border: 1px solid #e2e8f0;\n      }\n    }\n  }\n\n  .input-group-2 {\n    label {\n      font-size: 12px;\n      color: $gray;\n      margin-bottom: 8px;\n      display: block;\n    }\n\n    .select-position {\n      &::after {\n        width: 8px;\n        height: 8px;\n      }\n    }\n\n    select {\n      padding: 8px 12px;\n      font-size: 12px;\n      color: $gray;\n      border: 1px solid #e2e8f0;\n      border-radius: 4px;\n      width: 100%;\n    }\n  }\n}\n"
  },
  {
    "path": "public/assets/scss/header/_header.scss",
    "content": "/* ========== header css ========== */\n\n.header {\n  padding: 30px 0;\n  background: $white;\n\n  .header-left {\n    .menu-toggle-btn {\n      .main-btn {\n        padding: 0px 15px;\n        height: 46px;\n        line-height: 46px;\n        border-radius: 10px;\n      }\n    }\n\n    .header-search {\n      form {\n        max-width: 270px;\n        position: relative;\n\n        input {\n          width: 100%;\n          border: 1px solid $light;\n          background: rgba($light, 0.5);\n          border-radius: 10px;\n          height: 46px;\n          padding-left: 44px;\n          @include transition(0.3s);\n\n          &:focus {\n            border-color: $primary;\n            background: $white;\n          }\n        }\n        button {\n          position: absolute;\n          border: none;\n          background: transparent;\n          left: 16px;\n          top: 0;\n          height: 46px;\n          color: $gray;\n          font-weight: 700;\n        }\n      }\n    }\n  }\n\n  .header-right {\n    display: flex;\n    justify-content: flex-end;\n\n    button {\n      border: 1px solid $light;\n      background: rgba($light, 0.5);\n      border-radius: 10px;\n      height: 46px;\n      width: 46px;\n      @include flex-center;\n      position: relative;\n\n      &::after {\n        display: none;\n      }\n\n      span {\n        position: absolute;\n        width: 20px;\n        height: 20px;\n        background: $primary;\n        color: $white;\n        border-radius: 50%;\n        @include flex-center;\n        top: -8px;\n        right: -6px;\n        font-size: 12px;\n        font-weight: 500;\n      }\n    }\n\n    .dropdown-menu {\n      width: 350px;\n      border: 1px solid $light;\n      padding: 10px 10px;\n      @include transition(0.3s);\n      top: 24px !important;\n      right: 0;\n      position: absolute;\n      transform: translate3d(0px, 60px, 0px);\n      border-radius: 10px;\n\n      li {\n        padding: 3px 0px;\n        @include transition(0.3s);\n        border-bottom: 1px solid $light;\n        position: relative;\n        z-index: 2;\n\n        &:hover {\n          a {\n            color: $primary;\n            background: rgba($primary, 0.05);\n          }\n        }\n        &:last-child {\n          border-bottom: none;\n        }\n        a {\n          padding: 8px 12px;\n          display: flex;\n          color: $black-70;\n          border-radius: 6px;\n\n          .image {\n            max-width: 35px;\n            width: 100%;\n            height: 35px;\n            border-radius: 50%;\n            overflow: hidden;\n            margin-right: 12px;\n\n            img {\n              width: 100%;\n            }\n          }\n\n          .content {\n            width: 100%;\n\n            h6 {\n              font-size: 14px;\n              margin-bottom: 5px;\n              font-weight: 600;\n              line-height: 1;\n            }\n            p {\n              font-size: 14px;\n              color: $black-70;\n              margin-bottom: 0px;\n              line-height: 1.4;\n            }\n\n            span {\n              font-size: 12px;\n              color: $black-50;\n            }\n          }\n        }\n      }\n    }\n\n    .dropdown-box {\n      position: relative;\n    }\n\n    .notification-box,\n    .header-message-box {\n      position: relative;\n    }\n\n    .notification-box {\n      .dropdown-menu {\n        &.dropdown-menu-end {\n          transform: translate3d(0px, 60px, 0px);\n        }\n      }\n    }\n\n    .header-message-box {\n      .dropdown-menu {\n        &.dropdown-menu-end {\n          transform: translate3d(0px, 60px, 0px);\n        }\n      }\n    }\n\n    .profile-box {\n      display: flex;\n      position: relative;\n\n      button {\n        width: auto;\n      }\n\n      .dropdown-menu {\n        width: 230px;\n\n        &.dropdown-menu-end {\n          transform: translate3d(0px, 60px, 0px);\n        }\n\n        li {\n          border-bottom: none;\n\n          a {\n            font-size: 14px;\n            display: flex;\n            align-items: center;\n\n            i {\n              margin-right: 15px;\n              font-weight: 700;\n            }\n          }\n        }\n      }\n\n      .profile-info {\n        margin: 0 5px;\n        .info {\n          display: flex;\n          align-items: center;\n\n          .image {\n            border: 2px solid #f9f9f9;\n            @include box-shadow(0px 21px 25px rgba(218, 223, 227, 0.8));\n            width: 46px;\n            height: 46px;\n            border-radius: 50%;\n            margin-left: 16px;\n            position: relative;\n\n            .status {\n              width: 16px;\n              height: 16px;\n              border-radius: 50%;\n              border: 2px solid $light-2;\n              background: $success;\n              position: absolute;\n              bottom: 0;\n              right: 0;\n              top: auto;\n            }\n\n            img {\n              width: 100%;\n              border-radius: 50%;\n            }\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "public/assets/scss/icons/_icons.scss",
    "content": "/* ============== Icons Css ===========*/\n.icons-wrapper {\n  .icons,\n  ul {\n    display: flex;\n    flex-wrap: wrap;\n    margin: 0 -10px;\n\n    & > div,\n    li {\n      display: flex;\n      align-items: center;\n      margin: 10px;\n      flex-basis: 215px;\n\n      @media (max-width: 400px) {\n        flex-basis: 100%;\n      }\n\n      i {\n        max-width: 45px;\n        width: 100%;\n        height: 45px;\n        display: flex;\n        align-items: center;\n        justify-content: center;\n        border: 1px solid #efefef;\n        border-radius: 4px;\n        background: transparent;\n        color: $dark;\n        font-size: 20px;\n        margin-right: 10px;\n      }\n      span {\n        color: $dark;\n        user-select: all;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "public/assets/scss/invoice/_invoice.scss",
    "content": "/* =========== Invoice Css ============= */\n\n.invoice-card {\n  .invoice-header {\n    display: flex;\n    flex-wrap: wrap;\n    justify-content: space-between;\n    flex: 1;\n    padding-bottom: 30px;\n    border-bottom: 1px solid rgba($black, 0.1);\n\n    @media #{$xs} {\n      flex-direction: column;\n    }\n\n    .invoice-logo {\n      width: 110px;\n      height: 110px;\n      border-radius: 50%;\n      overflow: hidden;\n\n      @media #{$xs} {\n        order: -1;\n        margin-bottom: 30px;\n      }\n\n      img {\n        width: 100%;\n      }\n    }\n\n    .invoice-date {\n      @media #{$xs} {\n        margin-top: 30px;\n      }\n      p {\n        font-size: 14px;\n        font-weight: 400;\n        margin-bottom: 10px;\n\n        span {\n          font-weight: 500;\n        }\n      }\n    }\n  }\n\n  .invoice-address {\n    padding-top: 30px;\n    display: flex;\n    margin-bottom: 40px;\n\n    @media #{$xs} {\n      display: block;\n    }\n\n    .address-item {\n      margin-right: 30px;\n      min-width: 250px;\n\n      h5 {\n        margin-bottom: 15px;\n      }\n\n      h1 {\n        margin-bottom: 10px;\n        font-size: 24px;\n      }\n\n      p {\n        margin-bottom: 10px;\n      }\n    }\n  }\n\n  .invoice-action {\n    ul {\n      li {\n        @media #{$xs} {\n          flex: 1;\n        }\n\n        a {\n          @media #{$xs} {\n            width: 100%;\n          }\n        }\n      }\n    }\n  }\n}\n\n.invoice-table {\n  th,\n  td {\n    padding: 10px 8px;\n  }\n  .service {\n    min-width: 150px;\n  }\n  .desc {\n    min-width: 150px;\n  }\n  .qty {\n    min-width: 150px;\n  }\n  .amount {\n    min-width: 100px;\n  }\n}\n"
  },
  {
    "path": "public/assets/scss/main.scss",
    "content": "@import \"variables\";\r\n@import \"mixin\";\r\n@import \"common\";\r\n\r\n@import \"./typography/typography\";\r\n\r\n@import \"./buttons/buttons\";\r\n\r\n@import \"./alerts/alerts\";\r\n\r\n@import \"./cards/cards\";\r\n\r\n@import \"./tables/tables\";\r\n\r\n@import \"./forms/form-elements\";\r\n\r\n@import \"./notification/notification\";\r\n\r\n@import \"./header/header\";\r\n\r\n@import \"./dashboards/dashboards\";\r\n\r\n// ======= auth css\r\n@import \"./auth/signin\";\r\n@import \"./auth/signup\";\r\n\r\n@import \"./settings/settings\";\r\n\r\n@import \"./invoice/invoice\";\r\n\r\n@import \"./icons/icons\";\r\n\r\n@import \"./calendar/calendar\";\r\n\r\n@import \"sidebar\";\r\n\r\n@import \"default\";\r\n"
  },
  {
    "path": "public/assets/scss/notification/_notification.scss",
    "content": "/* ============= notification css ============= */\n.single-notification {\n  display: flex;\n  justify-content: space-between;\n  align-items: flex-start;\n  padding: 20px 0;\n  border-bottom: 1px solid $light;\n\n  &.readed {\n    opacity: 0.7;\n  }\n\n  &:first-child {\n    padding-top: 0px;\n  }\n  &:last-child {\n    padding-bottom: 0px;\n    border-bottom: 0px;\n  }\n\n  .checkbox {\n    max-width: 50px;\n    width: 100%;\n    padding-top: 10px;\n\n    @media #{$xs} {\n      display: none;\n    }\n\n    input {\n      background-color: $light;\n      border-color: $light-2;\n\n      &:checked {\n        background-color: $primary;\n        border-color: $primary;\n      }\n    }\n  }\n\n  .notification {\n    display: flex;\n    width: 100%;\n\n    @media #{$xs} {\n      flex-direction: column;\n    }\n\n    .image {\n      max-width: 50px;\n      width: 100%;\n      height: 50px;\n      border-radius: 50%;\n      overflow: hidden;\n      color: $white;\n      @include flex-center;\n      font-weight: 600;\n      margin-right: 15px;\n\n      @media #{$xs} {\n        margin-bottom: 15px;\n      }\n\n      img {\n        width: 100%;\n      }\n    }\n\n    .content {\n      display: block;\n      max-width: 800px;\n\n      h6 {\n        margin-bottom: 15px;\n      }\n      p {\n        margin-bottom: 10px;\n      }\n    }\n  }\n\n  .action {\n    display: inline-flex;\n    justify-content: flex-end;\n    padding-top: 10px;\n\n    @media #{$xs} {\n      display: none;\n    }\n\n    button {\n      border: none;\n      background: transparent;\n      color: $gray;\n      margin-left: 20px;\n      font-size: 18px;\n\n      &.delete-btn {\n        &:hover {\n          color: $danger;\n        }\n      }\n    }\n    .dropdown-toggle::after {\n      display: none;\n    }\n  }\n}\n"
  },
  {
    "path": "public/assets/scss/settings/_settings.scss",
    "content": "/* =========== settings css ============== */\n\n.settings-card-1 {\n  .profile-info {\n    .profile-image {\n      max-width: 75px;\n      width: 100%;\n      height: 75px;\n      border-radius: 50%;\n      margin-right: 20px;\n      position: relative;\n      z-index: 1;\n\n      img {\n        width: 100%;\n        border-radius: 50%;\n      }\n\n      .update-image {\n        position: absolute;\n        bottom: 0;\n        right: 0;\n        width: 30px;\n        height: 30px;\n        background: $light;\n        border: 2px solid $white;\n        @include flex-center;\n        border-radius: 50%;\n        cursor: pointer;\n        z-index: 99;\n\n        &:hover {\n          opacity: 0.9;\n        }\n\n        input {\n          opacity: 0;\n          position: absolute;\n          width: 100%;\n          height: 100%;\n          cursor: pointer;\n          z-index: 99;\n        }\n        label {\n          cursor: pointer;\n          z-index: 99;\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "public/assets/scss/tables/_tables.scss",
    "content": "/* =========== tables css =========== */\n\n.table {\n  border-collapse: inherit;\n  border-spacing: 0px;\n\n  & > :not(caption) > * > * {\n    padding: 15px 0;\n    border-bottom-color: $light;\n    vertical-align: middle;\n  }\n  & > :not(:last-child) > :last-child > * {\n    border-bottom-color: $light;\n  }\n  tbody {\n    tr {\n      &:first-child > * {\n        padding-top: 20px;\n      }\n      &:last-child > * {\n        border-bottom-color: transparent;\n        padding-bottom: 0px;\n      }\n    }\n  }\n\n  th {\n    h6 {\n      font-weight: 500;\n      color: $dark;\n      font-size: 14px;\n    }\n  }\n\n  td {\n    &.min-width {\n      padding: 5px;\n\n      @media #{$xs} {\n        min-width: 150px;\n      }\n    }\n\n    p {\n      font-size: 14px;\n      line-height: 1.5;\n      color: $gray;\n\n      a {\n        color: inherit;\n\n        &:hover {\n          color: $primary;\n        }\n      }\n    }\n  }\n\n  .lead-info {\n    min-width: 200px;\n  }\n  .lead-email {\n    min-width: 150px;\n    white-space: nowrap;\n  }\n  .lead-phone {\n    min-width: 160px;\n  }\n  .lead-company {\n    min-width: 180px;\n  }\n\n  .referrals-image {\n    min-width: 150px;\n\n    .image {\n      width: 55px;\n      max-width: 100%;\n      height: 55px;\n      border-radius: 4px;\n      overflow: hidden;\n\n      img {\n        width: 100%;\n      }\n    }\n  }\n\n  .lead {\n    display: flex;\n    align-items: center;\n\n    .lead-image {\n      max-width: 50px;\n      width: 100%;\n      height: 50px;\n      border-radius: 50%;\n      overflow: hidden;\n      margin-right: 15px;\n\n      img {\n        width: 100%;\n      }\n    }\n    .lead-text {\n      width: 100%;\n    }\n  }\n  .employee-image {\n    width: 50px;\n    max-width: 100%;\n    height: 50px;\n    border-radius: 50%;\n    overflow: hidden;\n    margin-right: 15px;\n\n    img {\n      width: 100%;\n    }\n  }\n\n  .action {\n    display: flex;\n    align-items: center;\n\n    button {\n      border: none;\n      background: transparent;\n      padding: 0px 6px;\n      font-size: 18px;\n\n      &.edit {\n        &:hover {\n          color: $primary;\n        }\n      }\n\n      &::after {\n        display: none;\n      }\n    }\n\n    .dropdown-menu {\n      @include box-shadow(0px 0px 5px rgba(0, 0, 0, 0.07));\n\n      li {\n        &:hover {\n          a {\n            color: $primary !important;\n          }\n        }\n        a {\n          display: block;\n        }\n      }\n    }\n  }\n}\n\n// ===== Top selling table\n.top-selling-table {\n  tr {\n    th,\n    td {\n      vertical-align: middle;\n      padding: 10px 5px;\n    }\n\n    .min-width {\n      min-width: 80px;\n      white-space: nowrap;\n    }\n  }\n\n  .form-check-input[type=\"checkbox\"] {\n    margin-left: 5px;\n  }\n  .product {\n    display: flex;\n    align-items: center;\n    min-width: 150px;\n\n    .image {\n      border-radius: 4px;\n      overflow: hidden;\n      margin-right: 15px;\n      max-width: 50px;\n      width: 100%;\n      height: 50px;\n\n      img {\n        width: 100%;\n      }\n    }\n    p {\n      width: 100%;\n    }\n  }\n}\n\n// ===== referrals-table\n.referrals-table-card {\n  .title {\n    @media #{$xs} {\n      .right {\n        width: 100%;\n      }\n    }\n    @media #{$sm} {\n      .right {\n        width: auto;\n      }\n    }\n  }\n  .referrals-table {\n    td {\n      padding: 10px;\n    }\n  }\n}\n\n/* ===== lead-table ===== */\n.lead-table {\n  th,\n  td {\n    padding: 10px 5px;\n  }\n\n  .name {\n    min-width: 120px;\n  }\n\n  .email {\n    min-width: 130px;\n  }\n  .project {\n    min-width: 150px;\n  }\n\n  .status {\n    min-width: 120px;\n    text-align: center;\n  }\n  .action {\n    min-width: 60px;\n  }\n}\n\n// ======== Clients Table\n.clients-table-card {\n  .table {\n    .employee-info {\n      min-width: 150px;\n    }\n  }\n}\n.clients-table {\n  th,\n  td {\n    padding: 5px;\n\n    &.min-width {\n      min-width: 150px;\n    }\n  }\n\n  .employee-image {\n    margin-right: 0px;\n  }\n}\n"
  },
  {
    "path": "public/assets/scss/typography/_typography.scss",
    "content": "/* ============= typography css ============= */\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\n.h1,\n.h2,\n.h3,\n.h4,\n.h5,\n.h6 {\n  color: $dark;\n  margin: 0;\n}\n\nh1,\n.h1 {\n  font-size: 32px;\n  font-weight: 700;\n}\n\nh2,\n.h2 {\n  font-size: 28px;\n  font-weight: 600;\n}\n\nh3,\n.h3 {\n  font-size: 24px;\n  font-weight: 500;\n}\n\nh4,\n.h4 {\n  font-size: 20px;\n  font-weight: 600;\n}\n\nh5,\n.h5 {\n  font-size: 16px;\n  font-weight: 700;\n}\n\nh6,\n.h6 {\n  font-size: 16px;\n  font-weight: 600;\n}\n\n.text-bold {\n  font-weight: 700;\n}\n.text-semi-bold {\n  font-weight: 600;\n}\n.text-medium {\n  font-weight: 500;\n}\n.text-regular {\n  font-weight: 400;\n}\n.text-light {\n  font-weight: 300;\n}\n\n.text-sm {\n  font-size: 14px;\n  line-height: 22px;\n}\n\n/* ========== breadcrumb ============ */\n.breadcrumb-wrapper {\n  display: flex;\n  justify-content: flex-end;\n\n  @media #{$xs} {\n    justify-content: flex-start;\n  }\n\n  .breadcrumb {\n    li {\n      font-size: 14px;\n      color: $primary;\n\n      a {\n        color: $gray;\n\n        &:hover {\n          color: $primary;\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "public/casa_cases.csv",
    "content": "﻿case_number,case_assignment,birth_month_year_youth,next_court_date\r\nCINA-01-4347,volunteer1@example.net,March 2010,September 16 2022\r\nCINA-01-4348,\"volunteer2@example.net, volunteer3@example.net\",September 2015,Jan 6 2023\r\n"
  },
  {
    "path": "public/robots.txt",
    "content": "# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file\nUser-agent: *\nDisallow: /\n"
  },
  {
    "path": "public/sms-terms-conditions.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n <title>SMS Terms & Conditions</title>\n <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n <style>\n .terms-conditions-page {\n   background-color: #325271;\n   color: #2E2F30;\n   font-family: arial, sans-serif;\n   margin: 0;\n }\n \n .terms-conditions-page div.dialog {\n   width: 95%;\n   max-width: 60em;\n   margin: 4em auto 0;\n }\n \n .terms-conditions-page div.dialog > div {\n   border: 1px solid #CCC;\n   border-right-color: #999;\n   border-left-color: #999;\n   border-bottom-color: #BBB;\n   border-top: #EF5652 solid 4px;\n   border-top-left-radius: 9px;\n   border-top-right-radius: 9px;\n   background-color: white;\n   padding: 7px 12% 0;\n   box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);\n }\n \n .terms-conditions-page h1 {\n   font-size: 100%;\n   color: #325271;\n   line-height: 1.5em;\n   text-align: center;\n  }\n .terms-conditions-page h2 {\n   font-size: 85%;\n   color: #325271;\n   line-height: 1.5em;\n   text-align: center;\n  }\n \n .terms-conditions-page div.dialog > p {\n   margin: 0 0 1em;\n   padding: 1em;\n   background-color: #F7F7F7;\n   color: #666;\n }\n \n .terms-conditions-page div.dialog > p.section {\n \n   text-indent: 5%;\n   margin: 0 0 0em;\n   padding: 1em;\n   background-color: #F7F7F7;\n   color: #666;\n }\n \n .center {\n   display: block;\n   margin-left: auto;\n   margin-right: auto;\n }\n  </style>\n</head>\n \n<body class=\"terms-conditions-page\">\n <!-- This file lives in public/sms-terms-conditions.html -->\n <div class=\"dialog\">\n   <img src=\"apple-icon.png\" style=\"width:150px;height:140px;\" class=\"center\">\n   <br>\n   <div>\n     <h1>SMS Terms & Conditions</h1>\n     <h2>Last updated: 05/21/2022</h2>\n   </div>\n   <p class=\"section\">The CASA mobile message service (the \"Service\") is operated by \"Court Appointed Special Advocate (CASA)/Prince George's County\" (\"CASA\", \"PGCasa\", \"we\", or \"us\"). Your use of the Service constitutes your agreement to these terms and conditions (\"Mobile Terms\"). We may modify or cancel the Service or any of its features without notice. To the extent permitted by applicable law, we may also modify these Mobile Terms at any time and your continued use of the Service following the effective date of any such changes shall constitute your acceptance of such changes.</p>\n   <p class=\"section\">By consenting to CASA's SMS/text messaging service, you agree to receive recurring SMS/text messages from and on behalf of CASA through your wireless provider to the mobile number you provided, even if your mobile number is registered on any state or federal Do Not Call list. Text messages may be sent using an automatic telephone dialing system or other technology. Service-related messages may include updates, alerts, and information (e.g., event reminders, account alerts, password resets, etc.).</p>\n   <p class=\"section\">You understand that you do not have to sign up for this program, and your consent is not a condition of any purchase with CASA. Your participation in this program is completely voluntary. We do not charge for the Service, but you are responsible for all charges and fees associated with text messaging imposed by your wireless provider. Message frequency varies. Message and data rates may apply. Check your mobile plan and contact your wireless provider for details. You are solely responsible for all charges related to SMS/text messages, including charges from your wireless provider.</p>\n   <p class=\"section\">You may opt-in/out of the Service at any time under your CASA profile page (https://casavolunteertracking.org/). No further messages will be sent to your mobile device unless initiated by you. If you have subscribed to other CASA mobile message programs and wish to cancel, except where applicable law requires otherwise, you will need to opt-out separately from those programs by following the instructions provided in their respective mobile terms. For Service support or assistance, email casa@rubyforgood.org.</p>\n   <p class=\"section\">We may change any short-code or telephone number we use to operate the Service at any time and will notify you of these changes. You acknowledge that any messages you send to a short-code or telephone number we have changed may not be received and we will not be responsible for honoring requests made in such messages. The wireless carriers supported by the Service are not liable for delayed or undelivered messages. You agree to provide us with a valid mobile number. If you get a new mobile number, you will need to update your profile with the new number.</p>\n   <p class=\"section\">To the extent permitted by applicable law, you agree that we will not be liable for failed, delayed, or misdirected delivery of any information sent through the Service, any errors in such information, and/or any action you may or may not take in reliance on the information or Service.</p>\n   <br>\n </div>\n</body>\n</html>"
  },
  {
    "path": "public/supervisors.csv",
    "content": "﻿email,display_name,supervisor_volunteers,phone_number\r\nsupervisor1@example.net,Supervisor One,volunteer1@example.net,11111111111\r\nsupervisor2@example.net,Supervisor Two,\"volunteer2@example.net, volunteer3@example.net\",11111111111"
  },
  {
    "path": "public/volunteers.csv",
    "content": "﻿display_name,email,phone_number\r\nVolunteer One,volunteer1@example.net,11234567890\r\nVolunteer Two,volunteer2@example.net,11234567891\r\nVolunteer Three,volunteer3@example.net,11234567892"
  },
  {
    "path": "scripts/generate_github_issues_for_missing_spec.rb",
    "content": "# for every xit test\nDir.glob(\"spec/**/*spec.rb\").each do |filename|\n  File.open(filename, \"r\").readlines.select { |line| line.include?(\"xit \\\"\") }.each do |xit_line|\n    line_number = $.\n    clean_test_name = xit_line.gsub(\"xit \", \"\").gsub(\" do\\n\", \"\").delete('\"').delete(\"\\n\").strip\n    # clean_test_name = xit_line.gsub('xit', '').gsub('\\\"do.*', '').gsub('\"', '').gsub(\"\\n\", '').strip\n    title = \"Fix or remove xit-ignored test in #{filename}:#{line_number} '#{clean_test_name}'\"\n    `gh issue create --title \"#{title}\" --body \"#{title}\"`\n  end\nend\nnil\n"
  },
  {
    "path": "scripts/import_casa_case_date_of_birth.rb",
    "content": "# This will be run by hand with real prod data in prod console,\n# because we don't want to check in the data\n# and we don't want to make Sarah from PG CASA do hundreds of these by hand in the UI\n# because import didn't have DOB when PG CASA onboarded.\n\n# data = \"\"\"\n# 1/21/2000,,,,CINA 11-1234,\n# 2/22/2000,,,,TPR 12-1234,\n# 3/23/2000,,,,CINA 13-1234,\n# \"\"\"\n\ndef update_casa_case_birth_month_year_youth(casa_case, new_date)\n  casa_case.update!(birth_month_year_youth: Date.new(new_date.year, new_date.month, 1))\nend\n\ndef dates_match(casa_case, new_date)\n  casa_case.birth_month_year_youth.year == new_date.year && casa_case.birth_month_year_youth.month == new_date.month\nend\n\ndef update_casa_case_dates_of_birth(data, case_not_found, already_has_nonmatching_date, no_edit_made, updated_casa_cases)\n  casa_org = CasaOrg.find_by(name: \"Prince George CASA\")\n  return \"Prince George CASA not found\" unless casa_org\n  data.split(\"\\n\").map(&:strip).reject(&:empty?).each do |row|\n    chunks = row.split(\",\").compact\n    d1 = chunks[0]\n    p d1\n    d2 = Date.strptime(d1, \"%m/%d/%Y\") # https://ruby-doc.org/stdlib-2.4.1/libdoc/date/rdoc/Date.html#method-i-strftime\n    p d2\n    case_number = chunks.last\n    cc = CasaCase.find_by(case_number: case_number, casa_org_id: casa_org.id)\n\n    process_casa_case_date(cc, import_date, case_number, already_has_nonmatching_date, no_edit_made, updated_casa_cases, case_not_found)\n  end\nend\n\ndef process_casa_case_date(cc, import_date, case_number, already_has_nonmatching_date, no_edit_made, updated_casa_cases, case_not_found)\n  if cc&.birth_month_year_youth\n    if !dates_match(cc, d2)\n      already_has_nonmatching_date << {case_number: case_number, prev_date: cc.birth_month_year_youth, import_date: d2}\n    else\n      no_edit_made << cc.case_number\n    end\n  elsif cc\n    update_casa_case_birth_month_year_youth(cc, d2)\n    updated_casa_cases << cc.case_number\n  else\n    case_not_found << case_number\n  end\n  {not_found: case_not_found, nonmatching: already_has_nonmatching_date, no_edit_made: no_edit_made, updated_casa_cases: updated_casa_cases}\nend\n\n# data = \"\"\"\n# 1/21/2000,,,,CINA 11-1234,\n# 2/22/2000,,,,TPR 12-1234,\n# 3/23/2000,,,,CINA 13-1234,\n# \"\"\"\n# case_not_found = []\n# already_has_nonmatching_date = []\n# no_edit_made = []\n# updated_casa_cases = []\n# r1 = update_casa_case_dates_of_birth(data, case_not_found, already_has_nonmatching_date, no_edit_made, updated_casa_cases)\n# r1\n# puts CasaCase.all.pluck(:case_number, :birth_month_year_youth).map {|i| i.join(\", \")}.sort\n"
  },
  {
    "path": "spec/.prosopite_ignore",
    "content": "# Directories excluded from Prosopite N+1 raise (will still log).\n# Remove paths as you fix the underlying N+1s.\n#\n# See PROSOPITE_TODO.md for the full list of known issues.\nspec/models\nspec/services\nspec/lib\nspec/system\nspec/requests\nspec/controllers\nspec/views\nspec/decorators\nspec/policies\nspec/datatables\nspec/helpers\nspec/seeds\nspec/mailers\nspec/presenters\nspec/config\nspec/notifications\n"
  },
  {
    "path": "spec/blueprints/api/v1/session_blueprint_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe Api::V1::SessionBlueprint do\n  # TODO: Add tests for Api::V1::SessionBlueprint\n\n  pending \"add some tests for Api::V1::SessionBlueprint\"\nend\n"
  },
  {
    "path": "spec/callbacks/case_contact_metadata_callback_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CaseContactMetadataCallback do\n  let(:past) { Date.new(2020, 1, 1).in_time_zone }\n  let(:parsed_past) { past.as_json }\n\n  let(:present) { Date.new(2024, 1, 1).in_time_zone }\n  let(:parsed_present) { present.as_json }\n\n  before { travel_to present }\n\n  # NOTE: as you might notice these tests are omitting quite a few cases\n  # ex: notes => details, expenses => notes. I don't think it is worth dealing\n  # with metadata surrounding cases that are not processed in the correct order.\n  # A user would not be able to replicate this behavior.\n  #\n  describe \"after_commit\" do\n    it \"sets started metadata when case contact is created\" do\n      cc = create(:case_contact)\n\n      expect(cc.metadata.dig(\"status\", \"active\")).to eq(parsed_present)\n    end\n\n    context \"case contact is in started status\" do\n      let(:case_contact) { create(:case_contact, :started, created_at: past) }\n\n      context \"updates to started status\" do\n        before { case_contact.update(status: \"started\") }\n\n        it \"does not update the metadata\" do\n          expect(case_contact.metadata.dig(\"status\", \"started\")).to eq(parsed_past)\n        end\n      end\n\n      context \"updates to details status\" do\n        before { case_contact.update(status: \"details\") }\n\n        it { expect(case_contact.metadata.dig(\"status\", \"details\")).to eq(parsed_present) }\n      end\n\n      context \"updates to notes status\" do\n        before { case_contact.update(status: \"notes\") }\n\n        it { expect(case_contact.metadata.dig(\"status\", \"notes\")).to eq(parsed_present) }\n      end\n\n      context \"updates to expenses status\" do\n        before { case_contact.update(status: \"expenses\") }\n\n        it { expect(case_contact.metadata.dig(\"status\", \"expenses\")).to eq(parsed_present) }\n      end\n\n      context \"updates to active status\" do\n        before { case_contact.update(status: \"active\") }\n\n        it { expect(case_contact.metadata.dig(\"status\", \"active\")).to eq(parsed_present) }\n      end\n    end\n\n    context \"case contact is in details status\" do\n      let(:case_contact) { create(:case_contact, :details, created_at: past) }\n\n      context \"updates to details status\" do\n        before { case_contact.update(status: \"details\") }\n\n        it \"does not update the metadata\" do\n          expect(case_contact.metadata.dig(\"status\", \"details\")).to eq(parsed_past)\n        end\n      end\n\n      context \"updates to notes status\" do\n        before { case_contact.update(status: \"notes\") }\n\n        it { expect(case_contact.metadata.dig(\"status\", \"notes\")).to eq(parsed_present) }\n      end\n\n      context \"updates to expenses status\" do\n        before { case_contact.update(status: \"expenses\") }\n\n        it { expect(case_contact.metadata.dig(\"status\", \"expenses\")).to eq(parsed_present) }\n      end\n\n      context \"updates to active status\" do\n        before { case_contact.update(status: \"active\") }\n\n        it { expect(case_contact.metadata.dig(\"status\", \"active\")).to eq(parsed_present) }\n      end\n    end\n\n    context \"case contact is in notes status\" do\n      let(:case_contact) { create(:case_contact, :notes, created_at: past) }\n\n      context \"updates to notes status\" do\n        before { case_contact.update(status: \"notes\") }\n\n        it { expect(case_contact.metadata.dig(\"status\", \"notes\")).to eq(parsed_past) }\n      end\n\n      context \"updates to expenses status\" do\n        before { case_contact.update(status: \"expenses\") }\n\n        it { expect(case_contact.metadata.dig(\"status\", \"expenses\")).to eq(parsed_present) }\n      end\n\n      context \"updates to active status\" do\n        before { case_contact.update(status: \"active\") }\n\n        it { expect(case_contact.metadata.dig(\"status\", \"active\")).to eq(parsed_present) }\n      end\n    end\n\n    context \"case contact is in expenses status\" do\n      let!(:case_contact) { create(:case_contact, :expenses, created_at: past) }\n\n      context \"updates to expenses status\" do\n        before { case_contact.update(status: \"expenses\") }\n\n        it \"does not update the metadata\" do\n          expect(case_contact.metadata.dig(\"status\", \"expenses\")).to eq(parsed_past)\n        end\n      end\n\n      context \"updates to active status\" do\n        before { case_contact.update(status: \"active\") }\n\n        it { expect(case_contact.metadata.dig(\"status\", \"active\")).to eq(parsed_present) }\n      end\n    end\n\n    context \"case contact is in active status\" do\n      let(:case_contact) { create(:case_contact, created_at: past) }\n\n      context \"updates to active status\" do\n        before { case_contact.update(status: \"active\") }\n\n        it \"does not update the metadata\" do\n          expect(case_contact.metadata.dig(\"status\", \"active\")).to eq(parsed_past)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/channels/application_cable/channel_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"Channel\", type: :channel do\n  # TODO: Add tests for Channel\n\n  pending \"add some tests for Channel\"\nend\n"
  },
  {
    "path": "spec/channels/application_cable/connection_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"Connection\", type: :channel do\n  # TODO: Add tests for Connection\n\n  pending \"add some tests for Connection\"\nend\n"
  },
  {
    "path": "spec/components/badge_component_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe BadgeComponent, type: :component do\n  it \"renders a success badge with only required parameters\" do\n    component = described_class.new(text: \"success\", type: :success)\n\n    render_inline(component)\n    expect(page).to have_css(\"span.bg-success\", text: \"success\")\n    expect(page).to have_css(\".text-uppercase\")\n    expect(page).not_to have_css(\".text-dark\")\n    expect(page).not_to have_css(\".rounded-pill\")\n    expect(page).to have_css(\".my-1\")\n  end\n\n  it \"renders a danger badge changing default parameters\" do\n    component = described_class.new(text: \"danger\", type: :danger, rounded: true, margin: false)\n\n    render_inline(component)\n    expect(page).to have_css(\"span.bg-danger\", text: \"danger\")\n    expect(page).to have_css(\".text-uppercase\")\n    expect(page).not_to have_css(\".text-dark\")\n    expect(page).to have_css(\".rounded-pill\")\n    expect(page).not_to have_css(\".my-1\")\n  end\n\n  it \"renders the dark text badges\" do\n    dark_text_badges = [\"warning\", \"light\"]\n    dark_text_badges.each do |badge|\n      component = described_class.new(text: badge, type: badge)\n\n      render_inline(component)\n      expect(page).to have_css(\"span.bg-#{badge}\", text: badge)\n      expect(page).to have_css(\".text-dark\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/components/dropdown_menu_component_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe DropdownMenuComponent, type: :component do\n  it \"renders the dropdown menu with an icon and label\" do\n    render_inline(DropdownMenuComponent.new(menu_title: \"Example\", icon_name: \"example-icon\")) { \"Example Content\" }\n\n    expect(page).to have_css(\"div.dropdown\")\n    expect(page).to have_css(\"button.btn.btn-secondary.dropdown-toggle span\", text: \"Example\")\n    expect(page).to have_css(\"button.btn.btn-secondary.dropdown-toggle i.lni.mr-10.lni-example-icon\")\n    expect(page).to have_css(\".dropdown-menu\", text: \"Example Content\")\n  end\n\n  it \"renders the dropdown menu with a hidden label\" do\n    render_inline(DropdownMenuComponent.new(menu_title: \"Example\", icon_name: \"example-icon\", hide_label: true)) { \"Example Content\" }\n\n    expect(page).to have_css(\"div.dropdown\")\n    expect(page).to have_css(\"button.btn.btn-secondary.dropdown-toggle span.sr-only\", text: \"Example\")\n    expect(page).to have_css(\"button.btn.btn-secondary.dropdown-toggle i.lni.mr-10.lni-example-icon\")\n    expect(page).to have_css(\".dropdown-menu\", text: \"Example Content\")\n  end\n\n  it \"renders the dropdown menu with only a label and content\" do\n    render_inline(DropdownMenuComponent.new(menu_title: \"Example Title\")) { \"Example Item\" }\n\n    expect(page).to have_css(\"div.dropdown\")\n    expect(page).to have_css(\"button.btn.btn-secondary.dropdown-toggle svg\")\n    expect(page).to have_css(\"svg title\", text: \"Example Title\")\n    expect(page).to have_css(\".dropdown-menu\", text: \"Example Item\")\n  end\n\n  it \"doesn't render anything if no content provided\" do\n    render_inline(DropdownMenuComponent.new(menu_title: nil))\n\n    expect(page).not_to have_css(\"div.dropdown\")\n  end\n\n  it \"renders the dropdown menu with additional classes\" do\n    render_inline(DropdownMenuComponent.new(menu_title: \"Example\", klass: \"example-class\")) { \"Example Content\" }\n\n    expect(page).to have_css(\"div.dropdown.example-class\")\n  end\n\n  it \"doesn't render if render_check is false\" do\n    render_inline(DropdownMenuComponent.new(menu_title: \"Example\", render_check: false))\n\n    expect(page).not_to have_css(\"div.dropdown\")\n  end\nend\n"
  },
  {
    "path": "spec/components/form/hour_minute_duration_component_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe Form::HourMinuteDurationComponent, type: :component do\n  let(:case_contact) { build(:case_contact) }\n  let(:form_builder) { ActionView::Helpers::FormBuilder.new(:object, double(\"object\"), ActionView::Base.new(ActionView::LookupContext.new(\"app/views\"), {}, ActionController::Base.new), {}) }\n\n  it \"has initial values set by the component\" do\n    minute_value = \"1112\"\n    hour_value = 256\n\n    component = described_class.new(form: form_builder, hour_value: hour_value, minute_value: minute_value)\n    render_inline(component)\n\n    expect(page.find_css(\"input[type=number][value=#{minute_value}]\").length).to eq(1)\n    expect(page.find_css(\"input[type=number][value=#{hour_value}]\").length).to eq(1)\n  end\n\n  it \"throws errors for incorrect parameters\" do\n    expect {\n      described_class.new(form: form_builder, hour_value: \"Not a number\", minute_value: 10)\n    }.to raise_error(ArgumentError)\n\n    expect {\n      described_class.new(form: form_builder, hour_value: 10, minute_value: -10)\n    }.to raise_error(RangeError)\n\n    expect {\n      described_class.new(form: form_builder, hour_value: 10, minute_value: \"-10\")\n    }.to raise_error(RangeError)\n\n    expect {\n      described_class.new(form: form_builder, hour_value: false, minute_value: \"10\")\n    }.to raise_error(TypeError)\n  end\nend\n"
  },
  {
    "path": "spec/components/form/multiple_select/item_component_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe Form::MultipleSelect::ItemComponent, type: :component do\n  pending \"add some examples to (or delete) #{__FILE__}\"\n\n  # it \"renders something useful\" do\n  #   expect(\n  #     render_inline(described_class.new(attr: \"value\")) { \"Hello, components!\" }.css(\"p\").to_html\n  #   ).to include(\n  #     \"Hello, components!\"\n  #   )\n  # end\nend\n"
  },
  {
    "path": "spec/components/form/multiple_select_component_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe Form::MultipleSelectComponent, type: :component do\n  pending \"add some examples to (or delete) #{__FILE__}\"\n\n  # it \"renders something useful\" do\n  #   expect(\n  #     render_inline(described_class.new(attr: \"value\")) { \"Hello, components!\" }.css(\"p\").to_html\n  #   ).to include(\n  #     \"Hello, components!\"\n  #   )\n  # end\nend\n"
  },
  {
    "path": "spec/components/local_time_component_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe LocalTimeComponent, type: :component do\n  it \"formats the date using strftime\" do\n    component = described_class.new(format: \"%b %d, %Y\", unix_timestamp: 1693825843, time_zone: ActiveSupport::TimeZone.new(\"Eastern Time (US & Canada)\"))\n    render_inline(component)\n    expect(page).to have_text(\"Sep 04, 2023\")\n  end\n\n  it \"uses the time zone passed to it to format the time\" do\n    component = described_class.new(format: \"%l:%M %p %Z\", unix_timestamp: 1693825843, time_zone: ActiveSupport::TimeZone.new(\"Eastern Time (US & Canada)\"))\n    render_inline(component)\n    expect(page).to have_text(\"7:10 AM EDT\")\n\n    component = described_class.new(format: \"%l:%M %p %Z\", unix_timestamp: 1693825843, time_zone: ActiveSupport::TimeZone.new(\"Central Time (US & Canada)\"))\n    render_inline(component)\n    expect(page).to have_text(\"6:10 AM CDT\")\n\n    component = described_class.new(format: \"%l:%M %p %Z\", unix_timestamp: 1693825843, time_zone: ActiveSupport::TimeZone.new(\"Mountain Time (US & Canada)\"))\n    render_inline(component)\n    expect(page).to have_text(\"5:10 AM MDT\")\n\n    component = described_class.new(format: \"%l:%M %p %Z\", unix_timestamp: 1693825843, time_zone: ActiveSupport::TimeZone.new(\"Pacific Time (US & Canada)\"))\n    render_inline(component)\n    expect(page).to have_text(\"4:10 AM PDT\")\n  end\n\n  it \"has an unambigous detailed date as the title of the element\" do\n    component = described_class.new(format: \"%l:%M %p %Z\", unix_timestamp: 1693825843, time_zone: ActiveSupport::TimeZone.new(\"Central Time (US & Canada)\"))\n    render_inline(component)\n    expect(page.find_css(\"span\").attr(\"title\").value).to have_text(\"Sep 04, 2023,  6:10 AM CDT\")\n  end\nend\n"
  },
  {
    "path": "spec/components/modal/body_component_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe Modal::BodyComponent, type: :component do\n  it \"renders the body with text\" do\n    render_inline(Modal::BodyComponent.new(text: \"Example Body\", klass: \"example-class\"))\n\n    expect(page).to have_css(\"div.modal-body.example-class\")\n    expect(page).to have_css(\"div.modal-body p\", text: \"Example Body\")\n  end\n\n  it \"renders the body with multiple paragraphs\" do\n    render_inline(Modal::BodyComponent.new(text: [\"Paragraph 1\", \"Paragraph 2\"]))\n\n    expect(page).to have_css(\"div.modal-body p\", text: \"Paragraph 1\")\n    expect(page).to have_css(\"div.modal-body p\", text: \"Paragraph 2\")\n  end\n\n  it \"renders the body with content\" do\n    render_inline(Modal::BodyComponent.new) do\n      \"Content Override\"\n    end\n\n    expect(page).to have_css(\"div.modal-body\", text: \"Content Override\")\n  end\n\n  it \"renders the body with content and overrides text\" do\n    render_inline(Modal::BodyComponent.new(text: \"Example Body\")) do\n      \"Content Override\"\n    end\n\n    expect(page).to have_css(\"div.modal-body\", text: \"Content Override\")\n    expect(page).not_to have_css(\"div.modal-body\", text: \"Example Body\")\n  end\n\n  it \"does not render if text and content missing\" do\n    render_inline(Modal::BodyComponent.new)\n\n    expect(page).not_to have_css(\"div.modal-body\")\n  end\n\n  it \"doesn't render if render_check is false\" do\n    render_inline(Modal::BodyComponent.new(text: \"Example Body\", render_check: false))\n\n    expect(page).not_to have_css(\"div.modal-body\")\n  end\nend\n"
  },
  {
    "path": "spec/components/modal/footer_component_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe Modal::FooterComponent, type: :component do\n  it \"renders the footer with content\" do\n    render_inline(Modal::FooterComponent.new(klass: \"example-class\")) do\n      \"Footer Content\"\n    end\n\n    expect(page).to have_css(\"div.modal-footer.example-class\")\n    expect(page).to have_css(\"div.modal-footer button.btn.btn-secondary\", text: \"Close\")\n    expect(page).to have_text(\"Footer Content\")\n  end\n\n  it \"does not render the footer if content missing\" do\n    render_inline(Modal::FooterComponent.new)\n\n    expect(page).not_to have_css(\"div.modal-footer\")\n  end\n\n  it \"doesn't render if render_check is false\" do\n    render_inline(Modal::FooterComponent.new(render_check: false)) do\n      \"Footer Content\"\n    end\n\n    expect(page).not_to have_css(\"div.modal-footer\")\n  end\nend\n"
  },
  {
    "path": "spec/components/modal/group_component_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe Modal::GroupComponent, type: :component do\n  before do\n    @component = Modal::GroupComponent.new(id: \"exampleModal\", klass: \"example-class\")\n  end\n\n  it \"renders the modal group with header, body, and footer\" do\n    @component.with_header(id: \"header-id\") { \"Example Header\" }\n    @component.with_body { \"Example Body\" }\n    @component.with_footer { \"Example Footer\" }\n    render_inline(@component)\n\n    expect(page).to have_css(\"div.modal.fade.example-class#exampleModal\")\n    expect(page).to have_css(\"div.modal-dialog.modal-dialog-centered\")\n    expect(page).to have_css(\"div.modal-content\")\n    expect(page).to have_text(\"Example Header\")\n    expect(page).to have_text(\"Example Body\")\n    expect(page).to have_text(\"Example Footer\")\n  end\n\n  it \"renders the modal group with only a header\" do\n    @component.with_header(id: \"header-id\") { \"Example Header\" }\n    render_inline(@component)\n\n    expect(page).to have_css(\"div.modal.fade.example-class#exampleModal\")\n    expect(page).to have_css(\"div.modal-dialog.modal-dialog-centered\")\n    expect(page).to have_css(\"div.modal-content\")\n    expect(page).to have_text(\"Example Header\")\n    expect(page).not_to have_css(\"div.modal-body\")\n    expect(page).not_to have_css(\"div.modal-footer\")\n  end\n\n  it \"renders the modal group with only a body\" do\n    @component.with_body { \"Example Body\" }\n    render_inline(@component)\n\n    expect(page).to have_css(\"div.modal.fade.example-class#exampleModal\")\n    expect(page).to have_css(\"div.modal-dialog.modal-dialog-centered\")\n    expect(page).to have_css(\"div.modal-content\")\n    expect(page).to have_text(\"Example Body\")\n    expect(page).not_to have_css(\"div.modal-header\")\n    expect(page).not_to have_css(\"div.modal-footer\")\n  end\n\n  it \"doesn't render anything if no content provided\" do\n    render_inline(@component)\n\n    expect(page).not_to have_css(\"div.modal.fade.example-class#exampleModal\")\n    expect(page).not_to have_css(\"div.modal-dialog.modal-dialog-centered\")\n    expect(page).not_to have_css(\"div.modal-content\")\n  end\n\n  it \"doesn't render if render_check is false\" do\n    @component = Modal::GroupComponent.new(id: \"exampleModal\", klass: \"example-class\", render_check: false)\n    @component.with_header(id: \"header-id\") { \"Example Header\" }\n    @component.with_body { \"Example Body\" }\n    @component.with_footer { \"Example Footer\" }\n    render_inline(@component)\n\n    expect(page).not_to have_css(\"div.modal.fade.example-class#exampleModal\")\n    expect(page).not_to have_css(\"div.modal-dialog.modal-dialog-centered\")\n    expect(page).not_to have_css(\"div.modal-content\")\n  end\nend\n"
  },
  {
    "path": "spec/components/modal/header_component_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe Modal::HeaderComponent, type: :component do\n  it \"renders the header with text and icon\" do\n    render_inline(Modal::HeaderComponent.new(text: \"Example Header\", id: \"modalHeader\", icon: \"example-icon\", klass: \"example-class\"))\n\n    expect(page).to have_css(\"div.modal-header.example-class\")\n    expect(page).to have_css(\"div.modal-header h1#modalHeader-label.modal-title.fs-5\")\n    expect(page).to have_css(\"div.modal-header h1#modalHeader-label i.lni.mr-10.lni-example-icon\")\n    expect(page).to have_text(\"Example Header\")\n    expect(page).to have_css(\"div.modal-header button.btn-close\")\n  end\n\n  it \"renders the header with only text\" do\n    render_inline(Modal::HeaderComponent.new(text: \"Example Header\", id: \"modalHeader\"))\n\n    expect(page).to have_css(\"div.modal-header\")\n    expect(page).to have_css(\"div.modal-header h1#modalHeader-label.modal-title.fs-5\")\n    expect(page).not_to have_css(\"div.modal-header i\")\n    expect(page).to have_text(\"Example Header\")\n    expect(page).to have_css(\"div.modal-header button.btn-close\")\n  end\n\n  it \"renders the header with content\" do\n    render_inline(Modal::HeaderComponent.new(id: \"modalHeader\")) do\n      \"Header Content\"\n    end\n\n    expect(page).to have_css(\"div.modal-header\")\n    expect(page).to have_text(\"Header Content\")\n    expect(page).to have_css(\"div.modal-header button.btn-close\")\n  end\n\n  it \"content overrides text\" do\n    render_inline(Modal::HeaderComponent.new(id: \"modalHeader\", text: \"Missing\")) do\n      \"Header Content\"\n    end\n\n    expect(page).to have_css(\"div.modal-header\")\n    expect(page).to have_text(\"Header Content\")\n    expect(page).not_to have_text(\"Missing\")\n    expect(page).to have_css(\"div.modal-header button.btn-close\")\n  end\n\n  it \"doesn't render anything if both text and content are absent\" do\n    render_inline(Modal::HeaderComponent.new(id: \"modalHeader\"))\n\n    expect(page).not_to have_css(\"div.modal-header\")\n  end\n\n  it \"doesn't render if render_check is false\" do\n    render_inline(Modal::HeaderComponent.new(text: \"Example Header\", id: \"modalHeader\", render_check: false))\n\n    expect(page).not_to have_css(\"div.modal-header\")\n  end\nend\n"
  },
  {
    "path": "spec/components/modal/open_button_component_spec.rb",
    "content": "# frozen_string_literal: true\n\n# spec/components/modal/open_button_component_spec.rb\n\nrequire \"rails_helper\"\n\nRSpec.describe Modal::OpenButtonComponent, type: :component do\n  it \"renders the button with text and icon\" do\n    render_inline(Modal::OpenButtonComponent.new(target: \"myModal\", text: \"Example Text\", icon: \"example-icon\", klass: \"example-class\"))\n\n    expect(page).to have_css(\"button[type='button'][class='example-class'][data-bs-toggle='modal'][data-bs-target='#myModal']\")\n    expect(page).to have_css(\"button i.lni.mr-10.lni-example-icon\")\n    expect(page).to have_text(\"Example Text\")\n  end\n\n  it \"renders the button with only text\" do\n    render_inline(Modal::OpenButtonComponent.new(target: \"myModal\", text: \"Example Text\"))\n\n    expect(page).to have_css(\"button[type='button'][class=''][data-bs-toggle='modal'][data-bs-target='#myModal']\")\n    expect(page).not_to have_css(\"button i\")\n    expect(page).to have_text(\"Example Text\")\n  end\n\n  it \"renders the button with content\" do\n    render_inline(Modal::OpenButtonComponent.new(target: \"myModal\")) do\n      \"Example Text\"\n    end\n\n    expect(page).to have_css(\"button[type='button'][class=''][data-bs-toggle='modal'][data-bs-target='#myModal']\")\n    expect(page).not_to have_css(\"button i\")\n    expect(page).to have_text(\"Example Text\")\n  end\n\n  it \"content overrides text\" do\n    render_inline(Modal::OpenButtonComponent.new(target: \"myModal\", text: \"Overwritten\")) do\n      \"Example Text\"\n    end\n\n    expect(page).to have_css(\"button[type='button'][class=''][data-bs-toggle='modal'][data-bs-target='#myModal']\")\n    expect(page).not_to have_css(\"button i\")\n    expect(page).to have_text(\"Example Text\")\n    expect(page).not_to have_text(\"Overwritten\")\n  end\n\n  it \"doesn't render anything if both text and content are absent\" do\n    render_inline(Modal::OpenButtonComponent.new(target: \"myModal\"))\n\n    expect(page).not_to have_css(\"button\")\n  end\n\n  it \"doesn't render if render_check is false\" do\n    render_inline(Modal::OpenButtonComponent.new(target: \"myModal\", text: \"Example Text\", render_check: false))\n\n    expect(page).not_to have_css(\"button\")\n  end\nend\n"
  },
  {
    "path": "spec/components/modal/open_link_component_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe Modal::OpenLinkComponent, type: :component do\n  it \"renders the link with text and icon\" do\n    render_inline(Modal::OpenLinkComponent.new(target: \"myModal\", text: \"Example Text\", icon: \"example-icon\", klass: \"example-class\"))\n\n    expect(page).to have_css(\"a[href='#'][role='button'][class='btn example-class'][data-bs-toggle='modal'][data-bs-target='#myModal']\")\n    expect(page).to have_css(\"a i.lni.mr-10.lni-example-icon\")\n    expect(page).to have_text(\"Example Text\")\n  end\n\n  it \"renders the link with only text\" do\n    render_inline(Modal::OpenLinkComponent.new(target: \"myModal\", text: \"Example Text\"))\n\n    expect(page).to have_css(\"a[href='#'][role='button'][class='btn '][data-bs-toggle='modal'][data-bs-target='#myModal']\")\n    expect(page).not_to have_css(\"a i\")\n    expect(page).to have_text(\"Example Text\")\n  end\n\n  it \"renders the link with content\" do\n    render_inline(Modal::OpenLinkComponent.new(target: \"myModal\")) do\n      \"Example Text\"\n    end\n\n    expect(page).to have_css(\"a[href='#'][role='button'][class='btn '][data-bs-toggle='modal'][data-bs-target='#myModal']\")\n    expect(page).not_to have_css(\"a i\")\n    expect(page).to have_text(\"Example Text\")\n  end\n\n  it \"content overrides text\" do\n    render_inline(Modal::OpenLinkComponent.new(target: \"myModal\", text: \"Override\")) do\n      \"Example Text\"\n    end\n\n    expect(page).to have_css(\"a[href='#'][role='button'][class='btn '][data-bs-toggle='modal'][data-bs-target='#myModal']\")\n    expect(page).not_to have_css(\"a i\")\n    expect(page).to have_text(\"Example Text\")\n    expect(page).not_to have_text(\"Override\")\n  end\n\n  it \"doesn't render anything if both text and content are absent\" do\n    render_inline(Modal::OpenLinkComponent.new(target: \"myModal\"))\n\n    expect(page).not_to have_css(\"a\")\n  end\n\n  it \"doesn't render if render_check is false\" do\n    render_inline(Modal::OpenLinkComponent.new(target: \"myModal\", text: \"Example Text\", render_check: false))\n\n    expect(page).not_to have_css(\"a\")\n  end\nend\n"
  },
  {
    "path": "spec/components/notification_component_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe NotificationComponent, type: :component do\n  let(:user) { create(:user, display_name: \"John Doe\") }\n  let(:casa_case) { create(:casa_case, case_number: \"CINA-1234\") }\n  let(:followup_with_note) { create(:notification, :followup_with_note, created_by: user) }\n  let(:followup_no_note) { create(:notification, :followup_without_note, created_by: user) }\n  let(:followup_read) { create(:notification, :followup_read, created_by: user) }\n  let(:emancipation_checklist_reminder) { create(:notification, :emancipation_checklist_reminder, casa_case: casa_case) }\n  let(:youth_birthday) { create(:notification, :youth_birthday, casa_case: casa_case) }\n\n  it \"renders a followup with note\" do\n    component = described_class.new(notification: followup_with_note)\n\n    render_inline(component)\n    expect(page).to have_text(\"New followup\")\n    expect(page).to have_text(\"Note: \")\n    expect(page).to have_text(\"#{user.display_name} has flagged a Case Contact that needs follow up.\")\n  end\n\n  it \"renders a followup without a note\" do\n    component = described_class.new(notification: followup_no_note)\n\n    render_inline(component)\n    expect(page).to have_text(\"New followup\")\n    expect(page).not_to have_text(\"Note: \")\n    expect(page).to have_text(\"#{user.display_name} has flagged a Case Contact that needs follow up. Click to see more.\")\n  end\n\n  it \"renders read followups with the correct styles\" do\n    component = described_class.new(notification: followup_read)\n\n    render_inline(component)\n    expect(page).to have_css(\"a.bg-light.text-muted\")\n    expect(page).not_to have_css(\"i.fas.fa-bell\")\n  end\n\n  it \"renders an emancipation checklist reminder\" do\n    component = described_class.new(notification: emancipation_checklist_reminder)\n\n    render_inline(component)\n    expect(page).to have_text(\"Emancipation Checklist Reminder\")\n    expect(page).to have_text(\"Your case #{casa_case.case_number} is a transition aged youth. We want to make sure that along the way, we’re preparing our youth for emancipation. Make sure to check the emancipation checklist.\")\n  end\n\n  it \"renders a youth birthday notification\" do\n    component = described_class.new(notification: youth_birthday)\n\n    render_inline(component)\n    expect(page).to have_text(\"Youth Birthday\")\n    expect(page).to have_text(\"Your youth, case number: #{casa_case.case_number} has a birthday next month.\")\n  end\nend\n"
  },
  {
    "path": "spec/components/previews/truncated_text_component_preview.rb",
    "content": "class TruncatedTextComponentPreview < ViewComponent::Preview\n  def default\n    render(TruncatedTextComponent.new(\n      Faker::Lorem.paragraph(sentence_count: 3),\n      label: \"Some Label\"\n    ))\n  end\nend\n"
  },
  {
    "path": "spec/components/sidebar/group_component_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe Sidebar::GroupComponent, type: :component do\n  before do\n    @component = described_class.new(title: \"Group Actions\", icon: \"list\")\n  end\n\n  it \"renders component when rendered links are added\" do\n    @component.with_link(title: \"Generate Court Reports\", icon: \"paperclip\", path: \"/case_court_reports\")\n    @component.with_link(title: \"Reimbursement Queue\", icon: \"money-location\", path: \"/reimbursements\")\n    render_inline(@component)\n\n    expect(page).to have_css \"li[class='nav-item nav-item-has-children group-item']\"\n    expect(page).to have_css \"a[class='group-actions collapsed']\"\n    expect(page).to have_css \"a[data-bs-target='#ddmenu_group-actions']\"\n    expect(page).to have_css \"a[aria-controls='ddmenu_group-actions']\"\n    expect(page).to have_css \"i[class='lni mr-10 lni-list']\"\n    expect(page).to have_css \"span[data-sidebar-target='linkTitle']\", text: \"Group Actions\"\n    expect(page).to have_css \"ul[id='ddmenu_group-actions']\"\n  end\n\n  it \"renders links\" do\n    @component.with_link(title: \"Generate Court Reports\", icon: \"paperclip\", path: \"/case_court_reports\")\n    @component.with_link(title: \"Reimbursement Queue\", icon: \"money-location\", path: \"/reimbursements\")\n    render_inline(@component)\n\n    expect(page).to have_css \"span[data-sidebar-target='linkTitle']\", text: \"Generate Court Reports\"\n    expect(page).to have_css \"span[data-sidebar-target='linkTitle']\", text: \"Reimbursement Queue\"\n  end\n\n  it \"does not render component if no links are added\" do\n    render_inline(@component)\n\n    expect(page).not_to have_css \"li[class='nav-item nav-item-has-children group-item']\"\n  end\n\n  it \"does not render component if all links are not rendered\" do\n    @component.with_link(title: \"Generate Court Reports\", icon: \"paperclip\", path: \"/case_court_reports\", render_check: false)\n    render_inline(@component)\n  end\nend\n"
  },
  {
    "path": "spec/components/sidebar/link_component_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe Sidebar::LinkComponent, type: :component do\n  context \"component render\" do\n    it \"is by default\" do\n      render_inline(described_class.new(title: \"Supervisors\", path: \"/supervisors\", icon: \"network\"))\n\n      expect(page).to have_css \"span[data-sidebar-target='linkTitle']\", text: \"Supervisors\"\n      expect(page).to have_css \"a[href='/supervisors']\"\n      expect(page).to have_css \"i[class='lni mr-10 lni-network']\"\n    end\n\n    it \"doesn't happen if render_check is false\" do\n      render_inline(described_class.new(title: \"Supervisors\", path: \"/supervisors\", icon: \"network\", render_check: false))\n\n      expect(page).not_to have_css \"span[data-sidebar-target='linkTitle']\", text: \"Supervisors\"\n    end\n  end\n\n  context \"icon render\" do\n    it \"doesn't happen if icon not set\" do\n      render_inline(described_class.new(title: \"Supervisors\", path: \"/supervisors\"))\n\n      expect(page).not_to have_css \"i\"\n    end\n  end\n\n  context \"active class\" do\n    it \"is rendered if request path matches link's path\" do\n      with_request_url \"/supervisors\" do\n        render_inline(described_class.new(title: \"Supervisors\", path: \"/supervisors\", icon: \"network\"))\n\n        expect(page).to have_css \"li[class='nav-item active']\"\n      end\n    end\n\n    it \"is not rendered if request path doesn't match\" do\n      with_request_url \"/volunteers\" do\n        render_inline(described_class.new(title: \"Supervisors\", path: \"/supervisors\", icon: \"network\"))\n\n        expect(page).to have_css \"li[class='nav-item ']\"\n        expect(page).to have_no_content \"li[class='nav-item active']\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/components/truncated_text_component_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe TruncatedTextComponent, type: :component do\n  let(:text) { \"This is some sample text.\" }\n  let(:label) { \"Label\" }\n\n  context \"when text and a label is provided\" do\n    it \"renders the component with the provided text\" do\n      render_inline(TruncatedTextComponent.new(text, label: label))\n\n      expect(page).to have_css(\".truncation-container\")\n      expect(page).to have_css(\"div.line-clamp-1\", text: text)\n      expect(page).to have_css(\"span.text-bold\", text: label)\n      expect(page).to have_css('a[data-truncated-text-target=\"moreButton\"]', text: \"[read more]\")\n      expect(page).to have_css('a[data-truncated-text-target=\"hideButton\"]', text: \"[hide]\")\n    end\n  end\n\n  context \"when text is provided but a label is not\" do\n    it \"renders the component with the provided content\" do\n      render_inline(TruncatedTextComponent.new(text))\n\n      expect(page).to have_css(\".truncation-container\")\n      expect(page).to have_css(\"div.line-clamp-1\", text: text)\n      expect(page).not_to have_css(\"span.text-bold\", text: label)\n      expect(page).to have_css('a[data-truncated-text-target=\"moreButton\"]', text: \"[read more]\")\n      expect(page).to have_css('a[data-truncated-text-target=\"hideButton\"]', text: \"[hide]\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/config/initializers/rack_attack_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe Rack::Attack do\n  include Rack::Test::Methods\n\n  # https://makandracards.com/makandra/46189-how-to-rails-cache-for-individual-rspec-tests\n  # memory store is per process and therefore no conflicts in parallel tests\n  let(:memory_store) { ActiveSupport::Cache.lookup_store(:memory_store) }\n  let(:header) { {\"REMOTE_ADDR\" => remote_ip} }\n  let(:params) { {} }\n  let(:limit) { 5 }\n  let(:cache) { Rails.cache }\n\n  before do\n    Rack::Attack.enabled = true\n    ActionController::Base.perform_caching = true\n    allow(Rails).to receive(:cache).and_return(memory_store)\n    Rails.cache.clear\n    freeze_time\n  end\n\n  after do\n    ActionController::Base.perform_caching = false\n  end\n\n  def app\n    Rails.application\n  end\n\n  describe \"throttle excessive requests by single IP address\" do\n    shared_examples \"correctly throttles\" do\n      it \"changes the request status to 429 if greater than limit\" do\n        (limit * 2).times do |i|\n          post path, params, header\n          expect(last_response.status).not_to eq 429 if i < limit\n          expect(last_response.status).to eq(429) if i >= limit\n        end\n      end\n    end\n\n    it_behaves_like \"correctly throttles\" do\n      let(:path) { \"/users/sign_in\" }\n      let(:remote_ip) { \"111.200.300.123\" }\n    end\n\n    it_behaves_like \"correctly throttles\" do\n      let(:path) { \"/all_casa_admins/sign_in\" }\n      let(:remote_ip) { \"111.200.300.456\" }\n    end\n  end\n\n  describe \"localhost is not throttled\" do\n    let(:remote_ip) { \"127.0.0.1\" }\n    let(:path) { \"/users/sign_in\" }\n\n    it \"does not change the request status to 429\" do\n      (limit * 2).times do |i|\n        post path, params, header\n        expect(last_response.status).not_to eq(429) if i > limit\n      end\n    end\n  end\n\n  describe \"throttle excessive requests for email login by variety of IP addresses\" do\n    shared_examples \"correctly throttles\" do\n      it \"changes the request status to 429 when greater than limit\" do\n        (limit * 2).times do |i|\n          header = {\"REMOTE_ADDR\" => \"#{remote_ip}#{i}\"}\n          post path, params, header\n          expect(last_response.status).not_to eq 429 if i < limit\n          expect(last_response.status).to eq(429) if i >= limit\n        end\n      end\n    end\n\n    it_behaves_like \"correctly throttles\" do\n      let(:user) { create(:user, email: \"foo@example.com\") }\n      let(:remote_ip) { \"189.23.45.1\" }\n      let(:path) { \"/users/sign_in\" }\n      let(:params) {\n        {\n          user: {\n            email: user.email,\n            password: \"badpassword\"\n          }\n        }\n      }\n    end\n\n    it_behaves_like \"correctly throttles\" do\n      let(:user) { create(:all_casa_admin, email: \"bar@example.com\") }\n      let(:remote_ip) { \"199.23.45.1\" }\n      let(:path) { \"/all_casa_admins/sign_in\" }\n      let(:first_block) { \"223\" }\n      let(:params) {\n        {\n          all_casa_admin: {\n            email: user.email,\n            password: \"badpassword\"\n          }\n        }\n      }\n    end\n  end\n\n  context \"blocklist\" do\n    let(:path) { \"/users/sign_in\" }\n\n    context \"good ip\" do\n      let(:remote_ip) { \"101.202.103.104\" }\n\n      it \"is not blocked\" do\n        post path, params, header\n        expect(last_response.status).not_to eq(403)\n      end\n    end\n\n    context \"bad ips\" do\n      # IP_BLOCKLIST environment variable set in config/environments/test.rb\n      shared_examples \"blocks request\" do\n        it \"changes the request status to 403\" do\n          post path, params, header\n          expect(last_response.status).to eq(403)\n        end\n      end\n\n      it_behaves_like \"blocks request\" do\n        let(:remote_ip) { \"4.5.6.7\" }\n      end\n\n      it_behaves_like \"blocks request\" do\n        let(:remote_ip) { \"9.8.7.6\" }\n      end\n\n      it_behaves_like \"blocks request\" do\n        let(:remote_ip) { \"100.101.102.103\" }\n      end\n    end\n  end\n\n  describe \"fail2ban\" do\n    shared_examples \"bans successfully\" do\n      it \"changes the request status to 403\" do\n        head path, params, header\n        expect(last_response.status).to eq(403)\n      end\n    end\n\n    context \"phpmyadmin\" do\n      it_behaves_like \"bans successfully\" do\n        let(:remote_ip) { \"1.2.33.4\" }\n        let(:path) { \"/phpMyAdmin/\" }\n      end\n    end\n\n    context \"phpmyadmin4\" do\n      it_behaves_like \"bans successfully\" do\n        let(:remote_ip) { \"55.66.77.88\" }\n        let(:path) { \"/phpMyAdmin4/\" }\n      end\n    end\n\n    context \"sql/phpmy-admin\" do\n      it_behaves_like \"bans successfully\" do\n        let(:remote_ip) { \"44.66.77.99\" }\n        let(:path) { \"/sql/phpmy-admin/\" }\n      end\n    end\n\n    context \"db/phpmyadmin-32\" do\n      it_behaves_like \"bans successfully\" do\n        let(:remote_ip) { \"44.96.77.99\" }\n        let(:path) { \"/db/phpMyAdmin-3/\" }\n      end\n    end\n\n    context \"sqlmanager\" do\n      it_behaves_like \"bans successfully\" do\n        let(:remote_ip) { \"44.95.77.99\" }\n        let(:path) { \"/mysql/mysqlmanager/\" }\n      end\n    end\n\n    context \"PMA year\" do\n      it_behaves_like \"bans successfully\" do\n        let(:remote_ip) { \"44.94.77.99\" }\n        let(:path) { \"/PMA2014\" }\n      end\n    end\n\n    context \"mysql\" do\n      it_behaves_like \"bans successfully\" do\n        let(:remote_ip) { \"44.93.77.99\" }\n        let(:path) { \"/mysql/dbadmin/\" }\n      end\n    end\n\n    context \"config/server\" do\n      it_behaves_like \"bans successfully\" do\n        let(:remote_ip) { \"44.92.77.99\" }\n        let(:path) { \"/config/server\" }\n      end\n    end\n\n    context \"config/server\" do\n      it_behaves_like \"bans successfully\" do\n        let(:remote_ip) { \"44.91.77.99\" }\n        let(:path) { \"/_ServerStatus\" }\n      end\n    end\n\n    context \"etc/services\" do\n      it_behaves_like \"bans successfully\" do\n        let(:remote_ip) { \"44.89.77.99\" }\n        let(:path) { \"/etc/services\" }\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/controllers/README.md",
    "content": "Tests for controllers should be in spec/requests"
  },
  {
    "path": "spec/controllers/application_controller_spec.rb",
    "content": "require \"rails_helper\"\nrequire \"support/stubbed_requests/webmock_helper\"\n\nRSpec.describe ApplicationController, type: :controller do\n  let(:volunteer) { create(:volunteer) }\n\n  controller do\n    def index\n      render plain: \"hello there...\"\n    end\n\n    # input => array of urls\n    # output => hash of valid short urls {id => short url/nil}\n    def handle_short_url(url_list)\n      super\n    end\n\n    def store_referring_location\n      super\n    end\n\n    def not_authorized_error\n      raise Pundit::NotAuthorizedError\n    end\n\n    def unknown_organization\n      raise Organizational::UnknownOrganization\n    end\n  end\n\n  before do\n    # authorize user\n    # sign in as an admin\n    allow(controller).to receive(:authenticate_user!).and_return(true)\n    allow(controller).to receive(:current_user).and_return(volunteer)\n    @short_io_stub = WebMockHelper.short_io_stub_sms\n    @short_io_error_stub = WebMockHelper.short_io_error_stub\n  end\n\n  describe \"#index\" do\n    it \"does not store URL path for POST\" do\n      path = \"/index\"\n      session_key = \"user_return_to\"\n      routes.draw { post \"index\" => \"anonymous#index\" }\n      post :index\n      expect(session[session_key]).not_to eq path\n      expect(session[session_key]).to be_nil\n    end\n  end\n\n  describe \"handle_short_url\" do\n    it \"returns a hash of shortened urls\" do\n      input_list = [\"www.clubpenguin.com\", \"www.miniclip.com\"]\n      output_hash = controller.handle_short_url(input_list)\n      expect(output_hash[0]).to eq(\"https://42ni.short.gy/jzTwdF\")\n      expect(output_hash[1]).to eq(\"https://42ni.short.gy/jzTwdF\")\n      expect(output_hash.length).to eq(2)\n      expect(@short_io_stub).to have_been_requested.times(2)\n    end\n\n    it \"returns a hash with a mix of valid/invalid short urls\" do\n      input_list = [\"www.clubpenguin.com\", \"www.badrequest.com\", \"www.miniclip.com\"]\n      output_hash = controller.handle_short_url(input_list)\n      expect(output_hash[1]).to eq(nil)\n      expect(output_hash.length).to eq(3)\n      expect(@short_io_stub).to have_been_requested.times(3)\n      expect(@short_io_error_stub).to have_been_requested.times(1)\n    end\n  end\n\n  describe \"Raise error:\" do\n    it \"redirects to root_url if rescued Pundit::NotAuthorizedError\" do\n      routes.draw { get :not_authorized_error, to: \"anonymous#not_authorized_error\" }\n      get :not_authorized_error\n      expect(response).to redirect_to(root_url)\n    end\n\n    it \"redirects to root_url if rescued Organizational::UnknownOrganization\" do\n      routes.draw { get :unknown_organization, to: \"anonymous#unknown_organization\" }\n      get :unknown_organization\n      expect(response).to redirect_to(root_url)\n    end\n  end\n\n  describe \"After signin path\" do\n    it \"is equal to initial path\" do\n      routes.draw { get :index, to: \"anonymous#index\" }\n      get :index\n      path = controller.after_sign_in_path_for(volunteer)\n      expect(path).to eq(\"/index\")\n    end\n  end\n\n  describe \"After signout path\" do\n    it \"is equal to new_all_casa_admin_session_path\" do\n      path = controller.after_sign_out_path_for(:all_casa_admin)\n      expect(path).to eq(new_all_casa_admin_session_path)\n    end\n\n    it \"is equal to root_path\" do\n      path = controller.after_sign_out_path_for(volunteer)\n      expect(path).to eq(root_path)\n    end\n  end\n\n  describe \"sms acct creation notice\" do\n    it \"sms status is blank\" do\n      expect(controller.send(:sms_acct_creation_notice, \"admin\", \"blank\")).to eq(\"New admin created successfully.\")\n    end\n\n    it \"sms status is error\" do\n      expect(controller.send(:sms_acct_creation_notice, \"admin\", \"error\")).to eq(\"New admin created successfully. SMS not sent. Error: .\")\n    end\n\n    it \"sms status is sent\" do\n      expect(controller.send(:sms_acct_creation_notice, \"admin\", \"sent\")).to eq(\"New admin created successfully. SMS has been sent!\")\n    end\n  end\n\n  describe \"#store_referring_location\" do\n    it \"stores referring location in session if referer is present and not sign in page\" do\n      request.env[\"HTTP_REFERER\"] = \"http://example.com\"\n      controller.store_referring_location\n      expect(session[:return_to]).to eq(\"http://example.com\")\n    end\n\n    it \"does not store referring location if referer is sign in page\" do\n      request.env[\"HTTP_REFERER\"] = \"http://example.com/users/sign_in\"\n      controller.store_referring_location\n      expect(session[:return_to]).to be_nil\n    end\n\n    it \"does not store referring location if referer is not present\" do\n      request.env[\"HTTP_REFERER\"] = nil\n      controller.store_referring_location\n      expect(session[:return_to]).to be_nil\n    end\n  end\nend\n"
  },
  {
    "path": "spec/controllers/concerns/accessible_spec.rb",
    "content": "require \"rails_helper\"\n\nclass MockController < ApplicationController\n  before_action :reset_session, only: :no_session_action\n  include Accessible\n  def action\n    render plain: \"controller action test...\"\n  end\n\n  def no_session_action\n    render plain: \"controller no session action test...\"\n  end\nend\n\nRSpec.describe MockController, type: :controller do\n  let(:admin) { create(:casa_admin) }\n  let(:volunteer) { create(:volunteer) }\n\n  context \"Authenticated user\" do\n    around do |example|\n      Rails.application.routes.draw do\n        get :action, to: \"mock#action\"\n        get :no_session_action, to: \"mock#no_session_action\"\n\n        # required routes to make Accessible concern work\n        get :mock_admin, to: \"admin#mock\", as: :authenticated_all_casa_admin_root\n        get :mock_user, to: \"user#mock\", as: :authenticated_user_root\n      end\n\n      example.run\n\n      Rails.application.reload_routes!\n    end\n\n    it \"redirects to authenticated casa admin root path\" do\n      allow(controller).to receive(:authenticate_user!).and_return(true)\n      allow(controller).to receive(:current_all_casa_admin).and_return(admin)\n      get :action\n      expect(response).to redirect_to authenticated_all_casa_admin_root_path\n    end\n\n    it \"redirects to authenticated user root path\" do\n      allow(controller).to receive(:authenticate_user!).and_return(true)\n      allow(controller).to receive(:current_user).and_return(volunteer)\n      get :no_session_action\n      expect(response).to redirect_to authenticated_user_root_path\n    end\n  end\nend\n"
  },
  {
    "path": "spec/controllers/concerns/court_date_params_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CourtDateParams, type: :controller do\n  let(:host) do\n    Class.new do\n      include CourtDateParams\n\n      attr_accessor :params\n\n      def initialize(params)\n        @params = params\n      end\n    end\n  end\n\n  it \"exists and defines private API\" do\n    expect(described_class).to be_a(Module)\n    expect(host.private_instance_methods)\n      .to include(:sanitized_court_date_params, :court_date_params)\n  end\n\n  describe \"#sanitized_court_date_params\" do\n    let(:casa_case) { create(:casa_case) }\n    let(:controller) { host.new(params) }\n\n    context \"when case_court_orders_attributes contains blank entries\" do\n      let(:params) do\n        ActionController::Parameters.new(\n          court_date: {\n            date: \"2025-10-15\",\n            case_court_orders_attributes: {\n              \"0\" => {text: \"Valid order\", implementation_status: \"not_implemented\"},\n              \"1\" => {text: \"\", implementation_status: \"\"},\n              \"2\" => {text: \"Another valid order\", implementation_status: \"partially_implemented\"}\n            }\n          }\n        )\n      end\n\n      it \"removes entries where both text and implementation_status are blank\" do\n        result = controller.send(:sanitized_court_date_params, casa_case)\n        expect(result[:case_court_orders_attributes].keys).to contain_exactly(\"0\", \"2\")\n      end\n\n      it \"sets casa_case_id for remaining entries\" do\n        result = controller.send(:sanitized_court_date_params, casa_case)\n        expect(result[:case_court_orders_attributes][\"0\"][:casa_case_id]).to eq(casa_case.id)\n        expect(result[:case_court_orders_attributes][\"2\"][:casa_case_id]).to eq(casa_case.id)\n      end\n    end\n\n    context \"when case_court_orders_attributes has text but blank implementation_status\" do\n      let(:params) do\n        ActionController::Parameters.new(\n          court_date: {\n            date: \"2025-10-15\",\n            case_court_orders_attributes: {\n              \"0\" => {text: \"Order with text only\", implementation_status: \"\"}\n            }\n          }\n        )\n      end\n\n      it \"keeps the entry\" do\n        result = controller.send(:sanitized_court_date_params, casa_case)\n        expect(result[:case_court_orders_attributes].keys).to include(\"0\")\n      end\n    end\n\n    context \"when case_court_orders_attributes has implementation_status but blank text\" do\n      let(:params) do\n        ActionController::Parameters.new(\n          court_date: {\n            date: \"2025-10-15\",\n            case_court_orders_attributes: {\n              \"0\" => {text: \"\", implementation_status: \"implemented\"}\n            }\n          }\n        )\n      end\n\n      it \"keeps the entry\" do\n        result = controller.send(:sanitized_court_date_params, casa_case)\n        expect(result[:case_court_orders_attributes].keys).to include(\"0\")\n      end\n    end\n\n    context \"when case_court_orders_attributes is nil\" do\n      let(:params) do\n        ActionController::Parameters.new(\n          court_date: {\n            date: \"2025-10-15\"\n          }\n        )\n      end\n\n      it \"does not raise an error\" do\n        expect { controller.send(:sanitized_court_date_params, casa_case) }.not_to raise_error\n      end\n    end\n\n    context \"when case_court_orders_attributes is present\" do\n      let(:params) do\n        ActionController::Parameters.new(\n          court_date: {\n            date: \"2025-10-15\",\n            case_court_orders_attributes: {\n              \"0\" => {text: \"Test order\", implementation_status: \"not_implemented\"}\n            }\n          }\n        )\n      end\n\n      it \"returns the court_date parameter\" do\n        result = controller.send(:sanitized_court_date_params, casa_case)\n        expect(result[:date]).to eq(\"2025-10-15\")\n      end\n    end\n  end\n\n  describe \"#court_date_params\" do\n    let(:casa_case) { create(:casa_case) }\n    let(:controller) { host.new(params) }\n\n    context \"with all permitted attributes\" do\n      let(:hearing_type) { create(:hearing_type, casa_org: casa_case.casa_org) }\n      let(:judge) { create(:judge, casa_org: casa_case.casa_org) }\n      let(:params) do\n        ActionController::Parameters.new(\n          court_date: {\n            date: \"2025-10-15\",\n            hearing_type_id: hearing_type.id,\n            judge_id: judge.id,\n            court_report_due_date: \"2025-10-10\",\n            case_court_orders_attributes: {\n              \"0\" => {\n                text: \"Test order\",\n                implementation_status: \"not_implemented\",\n                id: \"123\",\n                casa_case_id: casa_case.id,\n                _destroy: \"false\"\n              }\n            }\n          }\n        )\n      end\n\n      it \"permits all allowed attributes\" do\n        result = controller.send(:court_date_params, casa_case)\n        expect(result.permitted?).to be true\n        expect(result[:date]).to eq(\"2025-10-15\")\n        expect(result[:hearing_type_id]).to eq(hearing_type.id)\n        expect(result[:judge_id]).to eq(judge.id)\n        expect(result[:court_report_due_date]).to eq(\"2025-10-10\")\n      end\n\n      it \"permits nested case_court_orders_attributes\" do\n        result = controller.send(:court_date_params, casa_case)\n        order_attrs = result[:case_court_orders_attributes][\"0\"]\n        expect(order_attrs[:text]).to eq(\"Test order\")\n        expect(order_attrs[:implementation_status]).to eq(\"not_implemented\")\n        expect(order_attrs[:id]).to eq(\"123\")\n        expect(order_attrs[:casa_case_id]).to eq(casa_case.id)\n        expect(order_attrs[:_destroy]).to eq(\"false\")\n      end\n    end\n\n    context \"with unpermitted attributes\" do\n      let(:params) do\n        ActionController::Parameters.new(\n          court_date: {\n            date: \"2025-10-15\",\n            unauthorized_field: \"should not be permitted\",\n            case_court_orders_attributes: {\n              \"0\" => {\n                text: \"Test order\",\n                implementation_status: \"not_implemented\",\n                unauthorized_nested_field: \"should not be permitted\"\n              }\n            }\n          }\n        )\n      end\n\n      it \"filters out unpermitted attributes\" do\n        result = controller.send(:court_date_params, casa_case)\n        expect(result.to_h.keys).not_to include(\"unauthorized_field\")\n      end\n\n      it \"filters out unpermitted nested attributes\" do\n        result = controller.send(:court_date_params, casa_case)\n        order_attrs = result[:case_court_orders_attributes][\"0\"]\n        expect(order_attrs.to_h.keys).not_to include(\"unauthorized_nested_field\")\n      end\n    end\n\n    context \"when sanitized_court_date_params removes blank orders\" do\n      let(:params) do\n        ActionController::Parameters.new(\n          court_date: {\n            date: \"2025-10-15\",\n            case_court_orders_attributes: {\n              \"0\" => {text: \"Valid order\", implementation_status: \"not_implemented\"},\n              \"1\" => {text: \"\", implementation_status: \"\"}\n            }\n          }\n        )\n      end\n\n      it \"only includes non-blank orders in the result\" do\n        result = controller.send(:court_date_params, casa_case)\n        expect(result[:case_court_orders_attributes].keys).to eq([\"0\"])\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/controllers/concerns/loads_case_contacts_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe LoadsCaseContacts do\n  let(:host) do\n    Class.new do\n      include LoadsCaseContacts\n    end\n  end\n\n  it \"exists and defines private API\" do\n    expect(described_class).to be_a(Module)\n    expect(host.private_instance_methods)\n      .to include(:load_case_contacts, :current_organization_groups, :all_case_contacts)\n  end\n\n  describe \"integration with Flipper flags\", type: :request do\n    let(:organization) { create(:casa_org) }\n    let(:admin) { create(:casa_admin, casa_org: organization) }\n    let!(:casa_case) { create(:casa_case, casa_org: organization) }\n    let!(:case_contact) { create(:case_contact, :active, casa_case: casa_case) }\n\n    before { sign_in admin }\n\n    context \"when new_case_contact_table flag is enabled\" do\n      before do\n        allow(Flipper).to receive(:enabled?).with(:new_case_contact_table).and_return(true)\n      end\n\n      it \"loads case contacts successfully through the new design controller\" do\n        get case_contacts_new_design_path\n        expect(response).to have_http_status(:success)\n        expect(assigns(:filtered_case_contacts)).to be_present\n      end\n    end\n\n    context \"when new_case_contact_table flag is disabled\" do\n      before do\n        allow(Flipper).to receive(:enabled?).with(:new_case_contact_table).and_return(false)\n      end\n\n      it \"does not load case contacts and redirects instead\" do\n        get case_contacts_new_design_path\n        expect(response).to redirect_to(case_contacts_path)\n        expect(assigns(:filtered_case_contacts)).to be_nil\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/controllers/concerns/organizational_spec.rb",
    "content": "require \"rails_helper\"\n\nclass MockController < ApplicationController\n  include Organizational\nend\n\nRSpec.describe MockController, type: :controller do\n  it \"raises a UnknownOrganization error\" do\n    expect { controller.require_organization! }.to raise_error(Organizational::UnknownOrganization)\n  end\n\n  it \"does not raise a UnknownOrganization error\" do\n    current_user = create(:volunteer)\n\n    allow(controller).to receive(:current_user).and_return(current_user)\n\n    expect { controller.require_organization! }.not_to raise_error\n  end\n\n  it \"returns the user's current organization\" do\n    current_user = create(:volunteer)\n\n    allow(controller).to receive(:current_user).and_return(current_user)\n\n    expect(controller.current_organization).to eq(current_user.casa_org)\n  end\n\n  context \"when admin\" do\n    it \"returns the current user role\" do\n      current_user = create(:all_casa_admin)\n\n      allow(controller).to receive(:current_user).and_return(current_user)\n\n      expect(controller.current_role).to eq(current_user.role)\n    end\n  end\n\n  context \"when admin\" do\n    it \"returns the current user role\" do\n      current_user = create(:all_casa_admin)\n\n      allow(controller).to receive(:current_user).and_return(current_user)\n\n      expect(controller.current_role).to eq(current_user.role)\n    end\n  end\n\n  context \"when supervisor\" do\n    it \"returns the current user role\" do\n      current_user = create(:supervisor)\n\n      allow(controller).to receive(:current_user).and_return(current_user)\n\n      expect(controller.current_role).to eq(current_user.role)\n    end\n  end\n\n  context \"when volunteer\" do\n    it \"returns the current user role\" do\n      current_user = create(:volunteer)\n\n      allow(controller).to receive(:current_user).and_return(current_user)\n\n      expect(controller.current_role).to eq(current_user.role)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/controllers/concerns/users/time_zone_spec.rb",
    "content": "require \"rails_helper\"\n\nclass MockController < ApplicationController\n  include Users::TimeZone\nend\n\nRSpec.describe MockController, type: :controller do\n  let(:browser_time_zone) { \"America/Los_Angeles\" }\n  let(:default_time_zone) { \"Eastern Time (US & Canada)\" }\n  let(:time_date) { Time.zone.now }\n\n  before do\n    allow(controller).to receive(:cookies).and_return(browser_time_zone: browser_time_zone)\n  end\n\n  describe \"#browser_time_zone\" do\n    it \"returns the matching time zone\" do\n      browser_tz = ActiveSupport::TimeZone.find_tzinfo(browser_time_zone)\n      matching_zone = ActiveSupport::TimeZone.all.find { |zone| zone.tzinfo == browser_tz }\n      expect(controller.browser_time_zone).to eq(matching_zone || Time.zone)\n    end\n\n    context \"when browser_time_zone cookie is not set\" do\n      before do\n        allow(controller).to receive(:cookies).and_return({})\n      end\n\n      it \"returns the default time zone\" do\n        expect(controller.browser_time_zone).to eq(Time.zone)\n      end\n    end\n\n    context \"when browser_time_zone cookie contains an invalid value\" do\n      before do\n        allow(controller).to receive(:cookies).and_return(browser_time_zone: \"Invalid/Timezone\")\n      end\n\n      it \"returns the default time zone\" do\n        expect(controller.browser_time_zone).to eq(Time.zone)\n      end\n    end\n  end\n\n  describe \"#to_user_timezone\" do\n    it \"takes a time_date and converts it to user's time zone\" do\n      expected = controller.to_user_timezone(time_date)\n      returned = time_date.in_time_zone(browser_time_zone)\n      expect(expected.zone).to eq(returned.zone)\n      expect(expected.day).to eq(returned.day)\n      expect(expected.hour).to eq(returned.hour)\n    end\n\n    context \"when invalid param is sent\" do\n      it \"returns the empty string for nil param\" do\n        expect(controller.to_user_timezone(nil)).to eq(\"\")\n      end\n\n      it \"returns empty string if empty string param provided\" do\n        expect(controller.to_user_timezone(\"\")).to eq(\"\")\n      end\n\n      it \"returns nil for invalid date string\" do\n        expect(controller.to_user_timezone(\"invalid-date\")).to eq(nil)\n      end\n    end\n  end\n\n  describe \"#user_timezone\" do\n    context \"when browser time zone has an invalid value\" do\n      before do\n        allow(controller).to receive(:cookies).and_return(browser_time_zone: \"Invalid/Timezone\")\n      end\n\n      it \"returns the default time zone\" do\n        expect(controller.user_timezone).to eq(default_time_zone)\n      end\n    end\n\n    context \"when browser time zone is not set\" do\n      before do\n        allow(controller).to receive(:cookies).and_return({})\n      end\n\n      it \"returns the default time zone\" do\n        expect(controller.user_timezone).to eq(default_time_zone)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/controllers/emancipations_controller_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe EmancipationsController, type: :controller do\n  let(:organization) { create(:casa_org) }\n  let(:other_org) { create(:casa_org) }\n  let(:user) { create(:supervisor, casa_org: organization) }\n  let(:casa_case) { create(:casa_case, casa_org: organization, birth_month_year_youth: 20.years.ago) }\n  let(:non_transition_case) { create(:casa_case, :pre_transition, casa_org: organization) }\n  let(:emancipation_category) { create(:emancipation_category) }\n  let(:emancipation_option) { create(:emancipation_option, emancipation_category: emancipation_category) }\n\n  before { sign_in user }\n\n  describe \"GET #show\" do\n    context \"when authenticated and authorized\" do\n      it \"returns http success\" do\n        get :show, params: {casa_case_id: casa_case.friendly_id}\n        expect(response).to have_http_status(:success)\n      end\n\n      it \"assigns @current_case\" do\n        get :show, params: {casa_case_id: casa_case.friendly_id}\n        expect(assigns(:current_case)).to eq(casa_case)\n      end\n\n      it \"assigns @emancipation_form_data with all categories\" do\n        get :show, params: {casa_case_id: casa_case.friendly_id}\n        expect(assigns(:emancipation_form_data)).to match_array(EmancipationCategory.all)\n      end\n    end\n\n    context \"when case does not exist\" do\n      it \"raises a record not found error\" do\n        expect {\n          get :show, params: {casa_case_id: \"nonexistent-case\"}\n        }.to raise_error(ActiveRecord::RecordNotFound)\n      end\n    end\n\n    context \"when user belongs to a different org\" do\n      let(:user) { create(:supervisor, casa_org: other_org) }\n\n      it \"redirects to root with an authorization notice\" do\n        get :show, params: {casa_case_id: casa_case.friendly_id}\n        expect(response).to redirect_to(root_url)\n        expect(flash[:notice]).to match(/not authorized/)\n      end\n    end\n\n    context \"docx format\" do\n      it \"sends a docx file with the correct filename\" do\n        get :show, params: {casa_case_id: casa_case.friendly_id}, format: :docx\n        expect(response.headers[\"Content-Disposition\"]).to include(\n          \"#{casa_case.case_number} Emancipation Checklist.docx\"\n        )\n      end\n    end\n  end\n\n  describe \"POST #save\" do\n    def post_save(action, check_item_id, case_id: casa_case.friendly_id)\n      post :save, params: {\n        casa_case_id: case_id,\n        check_item_action: action,\n        check_item_id: check_item_id\n      }, format: :json\n    end\n\n    # Authorization\n    context \"when user belongs to a different org\" do\n      let(:user) { create(:supervisor, casa_org: other_org) }\n\n      it \"returns unauthorized with a json error message\" do\n        post_save(\"add_category\", emancipation_category.id)\n        expect(response).to have_http_status(:unauthorized)\n        expect(json_response[\"error\"]).to match(/not authorized/)\n      end\n    end\n\n    # Case not found\n    context \"when casa_case_id does not match any case\" do\n      it \"returns 404 with a descriptive error\" do\n        post_save(\"add_category\", emancipation_category.id, case_id: \"nonexistent-id\")\n        expect(response).to have_http_status(:not_found)\n        expect(json_response[\"error\"]).to match(/Could not find case/)\n      end\n    end\n\n    # Non-transitioning case\n    context \"when the case is not in transition age\" do\n      it \"returns bad_request\" do\n        post_save(\"add_category\", emancipation_category.id, case_id: non_transition_case.friendly_id)\n        expect(response).to have_http_status(:bad_request)\n        expect(json_response[\"error\"]).to match(/not marked as transitioning/)\n      end\n    end\n\n    # Unsupported action\n    context \"when check_item_action is not supported\" do\n      it \"returns bad_request with unsupported action message\" do\n        post_save(\"unsupported_action\", emancipation_category.id)\n        expect(response).to have_http_status(:bad_request)\n        expect(json_response[\"error\"]).to match(/not a supported action/)\n      end\n    end\n\n    # ADD_CATEGORY\n    context \"with action: add_category\" do\n      it \"adds the category to the case and returns success\" do\n        expect {\n          post_save(\"add_category\", emancipation_category.id)\n        }.to change { casa_case.emancipation_categories.count }.by(1)\n\n        expect(response).to have_http_status(:ok)\n        expect(json_response).to eq(\"success\")\n      end\n\n      it \"returns bad_request when category is already associated\" do\n        casa_case.add_emancipation_category(emancipation_category.id)\n        post_save(\"add_category\", emancipation_category.id)\n        expect(response).to have_http_status(:bad_request)\n        expect(json_response[\"error\"]).to match(/already exists/)\n      end\n\n      it \"returns bad_request when category id does not exist\" do\n        post_save(\"add_category\", -1)\n        expect(response).to have_http_status(:bad_request)\n      end\n    end\n\n    # ADD_OPTION\n    context \"with action: add_option\" do\n      it \"adds the option to the case and returns success\" do\n        expect {\n          post_save(\"add_option\", emancipation_option.id)\n        }.to change { casa_case.emancipation_options.count }.by(1)\n\n        expect(response).to have_http_status(:ok)\n        expect(json_response).to eq(\"success\")\n      end\n\n      it \"returns bad_request when option is already associated\" do\n        casa_case.add_emancipation_option(emancipation_option.id)\n        post_save(\"add_option\", emancipation_option.id)\n        expect(response).to have_http_status(:bad_request)\n        expect(json_response[\"error\"]).to match(/already exists/)\n      end\n\n      it \"returns bad_request when option id does not exist\" do\n        post_save(\"add_option\", -1)\n        expect(response).to have_http_status(:bad_request)\n      end\n    end\n\n    # DELETE_CATEGORY\n    context \"with action: delete_category\" do\n      before do\n        casa_case.add_emancipation_category(emancipation_category.id)\n        casa_case.add_emancipation_option(emancipation_option.id)\n      end\n\n      it \"removes the category and its associated options from the case\" do\n        post_save(\"delete_category\", emancipation_category.id)\n        expect(response).to have_http_status(:ok)\n        expect(json_response).to eq(\"success\")\n        expect(casa_case.reload.emancipation_categories).not_to include(emancipation_category)\n        expect(casa_case.reload.emancipation_options).not_to include(emancipation_option)\n      end\n\n      it \"returns bad_request when category is not associated with the case\" do\n        other_category = create(:emancipation_category)\n        post_save(\"delete_category\", other_category.id)\n        expect(response).to have_http_status(:bad_request)\n        expect(json_response[\"error\"]).to match(/does not exist/)\n      end\n    end\n\n    # DELETE_OPTION\n    context \"with action: delete_option\" do\n      before { casa_case.add_emancipation_option(emancipation_option.id) }\n\n      it \"removes the option from the case and returns success\" do\n        expect {\n          post_save(\"delete_option\", emancipation_option.id)\n        }.to change { casa_case.emancipation_options.count }.by(-1)\n\n        expect(response).to have_http_status(:ok)\n        expect(json_response).to eq(\"success\")\n      end\n\n      it \"returns bad_request when option is not associated with the case\" do\n        other_option = create(:emancipation_option, emancipation_category: emancipation_category)\n        post_save(\"delete_option\", other_option.id)\n        expect(response).to have_http_status(:bad_request)\n        expect(json_response[\"error\"]).to match(/does not exist/)\n      end\n    end\n\n    # SET_OPTION\n    context \"with action: set_option\" do\n      let(:other_option) { create(:emancipation_option, emancipation_category: emancipation_category) }\n\n      before { casa_case.add_emancipation_option(other_option.id) }\n\n      it \"replaces the existing option in the same category with the new one\" do\n        post_save(\"set_option\", emancipation_option.id)\n        expect(response).to have_http_status(:ok)\n        expect(json_response).to eq(\"success\")\n        expect(casa_case.reload.emancipation_options).to include(emancipation_option)\n        expect(casa_case.reload.emancipation_options).not_to include(other_option)\n      end\n\n      it \"returns bad_request when option id does not exist\" do\n        post_save(\"set_option\", -1)\n        expect(response).to have_http_status(:bad_request)\n      end\n    end\n  end\n\n  # JSON error handler for unauthorized access from save\n  describe \"#not_authorized\" do\n    let(:user) { create(:supervisor, casa_org: other_org) }\n\n    it \"renders a json unauthorized error when called from save\" do\n      post :save, params: {\n        casa_case_id: casa_case.friendly_id,\n        check_item_action: \"add_category\",\n        check_item_id: emancipation_category.id\n      }, format: :json\n\n      expect(response).to have_http_status(:unauthorized)\n      expect(json_response[\"error\"]).to match(/not authorized/)\n    end\n  end\n\n  # Helper to parse JSON responses\n  def json_response\n    JSON.parse(response.body)\n  end\nend\n"
  },
  {
    "path": "spec/controllers/learning_hours/volunteers_controller_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe LearningHours::VolunteersController, type: :controller do\n  # TODO: Add tests for LearningHours::VolunteersController\n\n  pending \"add some tests for LearningHours::VolunteersController\"\nend\n"
  },
  {
    "path": "spec/controllers/users/sessions_controller_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe Users::SessionsController, type: :controller do\n  # TODO: Add tests for Users::SessionsController\n\n  pending \"add some tests for Users::SessionsController\"\nend\n"
  },
  {
    "path": "spec/datatables/application_datatable_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe ApplicationDatatable do\n  # TODO: Add tests for ApplicationDatatable\n\n  pending \"add some tests for ApplicationDatatable\"\nend\n"
  },
  {
    "path": "spec/datatables/case_contact_datatable_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe CaseContactDatatable do\n  let(:organization) { create(:casa_org) }\n  let(:supervisor) { create(:supervisor, casa_org: organization) }\n  let(:volunteer) { create(:volunteer, casa_org: organization) }\n  let(:casa_case) { create(:casa_case, casa_org: organization) }\n  let(:contact_type) { create(:contact_type, casa_org: organization) }\n\n  let(:params) do\n    {\n      draw: \"1\",\n      start: \"0\",\n      length: \"10\",\n      search: {value: search_term},\n      order: {\"0\" => {column: order_column, dir: order_direction}},\n      columns: {\n        \"0\" => {name: \"occurred_at\", orderable: \"true\"},\n        \"1\" => {name: \"contact_made\", orderable: \"true\"},\n        \"2\" => {name: \"medium_type\", orderable: \"true\"},\n        \"3\" => {name: \"duration_minutes\", orderable: \"true\"}\n      }\n    }\n  end\n\n  let(:search_term) { \"\" }\n  let(:order_column) { \"0\" }\n  let(:order_direction) { \"desc\" }\n  let(:base_relation) { organization.case_contacts }\n\n  let(:current_user) { create(:casa_admin, casa_org: organization) }\n\n  subject(:datatable) { described_class.new(base_relation, params, current_user) }\n\n  describe \"#data\" do\n    let!(:case_contact) do\n      create(:case_contact,\n        casa_case: casa_case,\n        creator: volunteer,\n        occurred_at: 2.days.ago,\n        contact_made: true,\n        medium_type: \"in-person\",\n        duration_minutes: 60,\n        notes: \"Test notes\")\n    end\n\n    let!(:contact_topic) { create(:contact_topic, casa_org: organization) }\n\n    before do\n      case_contact.contact_types << contact_type\n      create(:contact_topic_answer, case_contact: case_contact, contact_topic: contact_topic)\n    end\n\n    it \"returns an array of case contact data\" do\n      expect(datatable.as_json[:data]).to be_an(Array)\n    end\n\n    it \"includes case contact attributes\" do\n      contact_data = datatable.as_json[:data].first\n\n      expect(contact_data[:id]).to eq(case_contact.id.to_s)\n      expect(contact_data[:contact_made]).to eq(\"true\")\n      expect(contact_data[:medium_type]).to eq(\"In Person\")\n      expect(contact_data[:duration_minutes]).to eq(\"60\")\n    end\n\n    it \"includes formatted occurred_at date\" do\n      contact_data = datatable.as_json[:data].first\n      expected_date = I18n.l(case_contact.occurred_at, format: :full, default: nil)\n\n      expect(contact_data[:occurred_at]).to eq(expected_date)\n    end\n\n    it \"includes casa_case data\" do\n      contact_data = datatable.as_json[:data].first\n\n      expect(contact_data[:casa_case][:id]).to eq(casa_case.id.to_s)\n      expect(contact_data[:casa_case][:case_number]).to eq(casa_case.case_number)\n    end\n\n    it \"includes contact_types as comma-separated string\" do\n      contact_data = datatable.as_json[:data].first\n\n      expect(contact_data[:contact_types]).to include(contact_type.name)\n    end\n\n    it \"includes creator data\" do\n      contact_data = datatable.as_json[:data].first\n\n      expect(contact_data[:creator][:id]).to eq(volunteer.id.to_s)\n      expect(contact_data[:creator][:display_name]).to eq(volunteer.display_name)\n      expect(contact_data[:creator][:email]).to eq(volunteer.email)\n      expect(contact_data[:creator][:role]).to eq(\"Volunteer\")\n    end\n\n    it \"includes contact_topics as pipe-separated string\" do\n      contact_data = datatable.as_json[:data].first\n\n      expect(contact_data[:contact_topics]).to include(contact_topic.question)\n    end\n\n    it \"includes is_draft status\" do\n      contact_data = datatable.as_json[:data].first\n\n      expect(contact_data[:is_draft]).to eq((!case_contact.active?).to_s)\n    end\n\n    context \"with action metadata\" do\n      it \"includes all expected action metadata keys\" do\n        contact_data = datatable.as_json[:data].first\n        expect(contact_data).to include(:can_edit, :can_destroy, :edit_path, :followup_id, :has_followup)\n      end\n\n      it \"includes edit_path for the contact\" do\n        contact_data = datatable.as_json[:data].first\n        expected_path = Rails.application.routes.url_helpers.edit_case_contact_path(case_contact)\n        expect(contact_data[:edit_path]).to eq(expected_path)\n      end\n\n      it \"includes followup_id as empty string when no requested followup exists\" do\n        contact_data = datatable.as_json[:data].first\n        expect(contact_data[:followup_id]).to eq(\"\")\n      end\n\n      it \"includes followup_id when a requested followup exists\" do\n        followup = create(:followup, case_contact: case_contact, status: \"requested\")\n        contact_data = datatable.as_json[:data].first\n        expect(contact_data[:followup_id]).to eq(followup.id.to_s)\n      end\n\n      it \"does not include followup_id for a resolved followup\" do\n        create(:followup, case_contact: case_contact, status: \"resolved\")\n        contact_data = datatable.as_json[:data].first\n        expect(contact_data[:followup_id]).to eq(\"\")\n      end\n    end\n\n    context \"with permission flags\" do\n      context \"when current_user is an admin\" do\n        it \"sets can_edit to true\" do\n          expect(datatable.as_json[:data].first[:can_edit]).to eq(\"true\")\n        end\n\n        it \"sets can_destroy to true\" do\n          expect(datatable.as_json[:data].first[:can_destroy]).to eq(\"true\")\n        end\n      end\n\n      context \"when current_user is the volunteer who created the contact\" do\n        let(:current_user) { volunteer }\n\n        it \"sets can_edit to true\" do\n          expect(datatable.as_json[:data].first[:can_edit]).to eq(\"true\")\n        end\n\n        it \"sets can_destroy to false for an active contact\" do\n          expect(datatable.as_json[:data].first[:can_destroy]).to eq(\"false\")\n        end\n      end\n\n      context \"when current_user is the volunteer who created a draft contact\" do\n        let(:current_user) { volunteer }\n        let!(:case_contact) do\n          create(:case_contact, casa_case: casa_case, creator: volunteer, status: \"started\")\n        end\n\n        it \"sets can_destroy to true for their own draft\" do\n          expect(datatable.as_json[:data].first[:can_destroy]).to eq(\"true\")\n        end\n      end\n    end\n\n    context \"when case_contact has no casa_case (draft)\" do\n      let!(:draft_contact) do\n        build(:case_contact,\n          casa_case: nil,\n          creator: volunteer,\n          occurred_at: 1.day.ago).tap do |cc|\n          cc.save(validate: false)\n        end\n      end\n\n      it \"handles nil casa_case gracefully\" do\n        draft_data = datatable.as_json[:data].find { |d| d[:id] == draft_contact.id.to_s }\n\n        # The sanitize method converts nil to empty string\n        expect(draft_data[:casa_case][:id]).to eq(\"\")\n        expect(draft_data[:casa_case][:case_number]).to eq(\"\")\n      end\n    end\n\n    context \"with followups\" do\n      it \"sets has_followup to true when requested followup exists\" do\n        create(:followup, case_contact: case_contact, status: \"requested\")\n\n        contact_data = datatable.as_json[:data].first\n        expect(contact_data[:has_followup]).to eq(\"true\")\n      end\n\n      it \"sets has_followup to false when no requested followup exists\" do\n        contact_data = datatable.as_json[:data].first\n        expect(contact_data[:has_followup]).to eq(\"false\")\n      end\n\n      it \"sets has_followup to false when followup is resolved\" do\n        create(:followup, case_contact: case_contact, status: \"resolved\")\n\n        contact_data = datatable.as_json[:data].first\n        expect(contact_data[:has_followup]).to eq(\"false\")\n      end\n    end\n  end\n\n  describe \"search functionality\" do\n    let!(:john_contact) do\n      create(:case_contact,\n        casa_case: casa_case,\n        creator: create(:volunteer, display_name: \"John Doe\", email: \"john@example.com\"),\n        notes: \"Meeting with youth\")\n    end\n\n    let!(:jane_contact) do\n      create(:case_contact,\n        casa_case: create(:casa_case, casa_org: organization, case_number: \"CASA-2024-001\"),\n        creator: create(:volunteer, display_name: \"Jane Smith\", email: \"jane@example.com\"),\n        notes: \"Phone call\")\n    end\n\n    let!(:family_contact_type) { create(:contact_type, name: \"Family\", casa_org: organization) }\n    let!(:school_contact_type) { create(:contact_type, name: \"School\", casa_org: organization) }\n\n    before do\n      john_contact.contact_types << family_contact_type\n      jane_contact.contact_types << school_contact_type\n    end\n\n    context \"searching by creator display_name\" do\n      let(:search_term) { \"John\" }\n\n      it \"returns matching case contacts\" do\n        expect(datatable.as_json[:data].pluck(:id)).to include(john_contact.id.to_s)\n        expect(datatable.as_json[:data].pluck(:id)).not_to include(jane_contact.id.to_s)\n      end\n    end\n\n    context \"searching by creator email\" do\n      let(:search_term) { \"jane@example.com\" }\n\n      it \"returns matching case contacts\" do\n        expect(datatable.as_json[:data].pluck(:id)).to include(jane_contact.id.to_s)\n        expect(datatable.as_json[:data].pluck(:id)).not_to include(john_contact.id.to_s)\n      end\n    end\n\n    context \"searching by case number\" do\n      let(:search_term) { \"2024-001\" }\n\n      it \"returns matching case contacts\" do\n        expect(datatable.as_json[:data].pluck(:id)).to include(jane_contact.id.to_s)\n        expect(datatable.as_json[:data].pluck(:id)).not_to include(john_contact.id.to_s)\n      end\n    end\n\n    context \"searching by notes\" do\n      let(:search_term) { \"Meeting\" }\n\n      it \"returns matching case contacts\" do\n        expect(datatable.as_json[:data].pluck(:id)).to include(john_contact.id.to_s)\n        expect(datatable.as_json[:data].pluck(:id)).not_to include(jane_contact.id.to_s)\n      end\n    end\n\n    context \"searching by contact_type name\" do\n      let(:search_term) { \"Family\" }\n\n      it \"returns matching case contacts\" do\n        expect(datatable.as_json[:data].pluck(:id)).to include(john_contact.id.to_s)\n        expect(datatable.as_json[:data].pluck(:id)).not_to include(jane_contact.id.to_s)\n      end\n    end\n\n    context \"with case-insensitive search\" do\n      let(:search_term) { \"JOHN\" }\n\n      it \"returns matching case contacts regardless of case\" do\n        expect(datatable.as_json[:data].pluck(:id)).to include(john_contact.id.to_s)\n      end\n    end\n\n    context \"with partial search term\" do\n      let(:search_term) { \"Smi\" }\n\n      it \"returns matching case contacts with partial match\" do\n        expect(datatable.as_json[:data].pluck(:id)).to include(jane_contact.id.to_s)\n      end\n    end\n\n    context \"with blank search term\" do\n      let(:search_term) { \"\" }\n\n      it \"returns all case contacts\" do\n        expect(datatable.as_json[:data].pluck(:id)).to include(john_contact.id.to_s, jane_contact.id.to_s)\n      end\n    end\n\n    context \"with no matching results\" do\n      let(:search_term) { \"NonexistentName\" }\n\n      it \"returns empty array\" do\n        expect(datatable.as_json[:data]).to be_empty\n      end\n    end\n  end\n\n  describe \"ordering\" do\n    let!(:old_contact) do\n      create(:case_contact,\n        casa_case: casa_case,\n        creator: volunteer,\n        occurred_at: 5.days.ago,\n        contact_made: false,\n        medium_type: \"text/email\",\n        duration_minutes: 30)\n    end\n\n    let!(:recent_contact) do\n      create(:case_contact,\n        casa_case: casa_case,\n        creator: volunteer,\n        occurred_at: 1.day.ago,\n        contact_made: true,\n        medium_type: \"in-person\",\n        duration_minutes: 90)\n    end\n\n    context \"ordering by occurred_at\" do\n      let(:order_column) { \"0\" }\n\n      context \"descending\" do\n        let(:order_direction) { \"desc\" }\n\n        it \"orders contacts by occurred_at descending\" do\n          ids = datatable.as_json[:data].pluck(:id)\n          expect(ids).to eq([recent_contact.id.to_s, old_contact.id.to_s])\n        end\n      end\n\n      context \"ascending\" do\n        let(:order_direction) { \"asc\" }\n\n        it \"orders contacts by occurred_at ascending\" do\n          ids = datatable.as_json[:data].pluck(:id)\n          expect(ids).to eq([old_contact.id.to_s, recent_contact.id.to_s])\n        end\n      end\n    end\n\n    context \"ordering by contact_made\" do\n      let(:order_column) { \"1\" }\n      let(:order_direction) { \"desc\" }\n\n      it \"orders contacts by contact_made\" do\n        ids = datatable.as_json[:data].pluck(:id)\n        expect(ids.first).to eq(recent_contact.id.to_s)\n      end\n    end\n\n    context \"ordering by medium_type\" do\n      let(:order_column) { \"2\" }\n      let(:order_direction) { \"asc\" }\n\n      it \"orders contacts by medium_type\" do\n        ids = datatable.as_json[:data].pluck(:id)\n        expect(ids.first).to eq(recent_contact.id.to_s)\n      end\n    end\n\n    context \"ordering by duration_minutes\" do\n      let(:order_column) { \"3\" }\n      let(:order_direction) { \"desc\" }\n\n      it \"orders contacts by duration_minutes\" do\n        ids = datatable.as_json[:data].pluck(:id)\n        expect(ids).to eq([recent_contact.id.to_s, old_contact.id.to_s])\n      end\n    end\n  end\n\n  describe \"pagination\" do\n    let!(:contacts) do\n      25.times.map do |i|\n        create(:case_contact,\n          casa_case: casa_case,\n          creator: volunteer,\n          occurred_at: i.days.ago)\n      end\n    end\n\n    context \"first page\" do\n      let(:params) do\n        super().merge(start: \"0\", length: \"10\")\n      end\n\n      it \"returns first 10 records\" do\n        expect(datatable.as_json[:data].length).to eq(10)\n      end\n\n      it \"returns correct recordsTotal\" do\n        expect(datatable.as_json[:recordsTotal]).to eq(25)\n      end\n\n      it \"returns correct recordsFiltered\" do\n        expect(datatable.as_json[:recordsFiltered]).to eq(25)\n      end\n    end\n\n    context \"second page\" do\n      let(:params) do\n        super().merge(start: \"10\", length: \"10\")\n      end\n\n      it \"returns next 10 records\" do\n        expect(datatable.as_json[:data].length).to eq(10)\n      end\n    end\n\n    context \"last page with partial results\" do\n      let(:params) do\n        super().merge(start: \"20\", length: \"10\")\n      end\n\n      it \"returns remaining 5 records\" do\n        expect(datatable.as_json[:data].length).to eq(5)\n      end\n    end\n\n    context \"with search filtering\" do\n      let!(:searchable_contact) do\n        create(:case_contact,\n          casa_case: casa_case,\n          creator: create(:volunteer, display_name: \"UniqueSearchName\", casa_org: organization),\n          occurred_at: 1.day.ago)\n      end\n\n      let(:search_term) { \"UniqueSearchName\" }\n\n      it \"paginates filtered results\" do\n        expect(datatable.as_json[:data].length).to eq(1)\n        expect(datatable.as_json[:recordsFiltered]).to eq(1)\n        expect(datatable.as_json[:recordsTotal]).to eq(26)\n      end\n    end\n  end\n\n  describe \"#as_json\" do\n    let!(:case_contact) do\n      create(:case_contact, casa_case: casa_case, creator: volunteer)\n    end\n\n    it \"returns hash with data, recordsFiltered, and recordsTotal\" do\n      json = datatable.as_json\n\n      expect(json).to have_key(:data)\n      expect(json).to have_key(:recordsFiltered)\n      expect(json).to have_key(:recordsTotal)\n    end\n\n    it \"sanitizes HTML in data\" do\n      contact_with_html = create(:case_contact,\n        casa_case: casa_case,\n        creator: volunteer,\n        notes: \"<script>alert('xss')</script>\")\n\n      json = datatable.as_json\n      contact_data = json[:data].find { |d| d[:id] == contact_with_html.id.to_s }\n\n      # Note: The sanitize method in ApplicationDatatable should escape HTML\n      expect(contact_data).to be_present\n    end\n  end\n\n  describe \"N+1 queries\" do\n    let!(:contacts) do\n      3.times.map do\n        create(:case_contact,\n          casa_case: create(:casa_case, casa_org: organization),\n          creator: create(:volunteer, casa_org: organization))\n      end\n    end\n\n    it \"does not trigger N+1 queries for casa_org when computing per-row policy permissions\" do\n      casa_org_queries = []\n      subscription = ActiveSupport::Notifications.subscribe(\"sql.active_record\") do |*, payload|\n        casa_org_queries << payload[:sql] if payload[:sql].match?(/SELECT.*casa_orgs/)\n      end\n\n      datatable.as_json\n\n      ActiveSupport::Notifications.unsubscribe(subscription)\n\n      # With proper eager loading, casa_orgs is fetched in at most 2 batch queries\n      # (one for :casa_org through casa_case, one for :creator_casa_org through creator).\n      # An N+1 would fire 1 query per record (3+ here).\n      expect(casa_org_queries.length).to be <= 2\n    end\n  end\n\n  describe \"associations loading\" do\n    let!(:contacts) do\n      10.times.map do |i|\n        contact = create(:case_contact,\n          casa_case: create(:casa_case, casa_org: organization),\n          creator: create(:volunteer, casa_org: organization),\n          occurred_at: i.days.ago)\n\n        contact_type = create(:contact_type, casa_org: organization)\n        contact.contact_types << contact_type\n\n        contact_topic = create(:contact_topic, casa_org: organization)\n        create(:contact_topic_answer, case_contact: contact, contact_topic: contact_topic)\n\n        contact\n      end\n    end\n\n    it \"loads all associations efficiently with includes\" do\n      # This test verifies that the datatable returns data successfully\n      # with proper includes to prevent N+1 queries\n      json = datatable.as_json\n\n      expect(json[:data].length).to eq(10)\n      expect(json[:data].first).to have_key(:contact_types)\n      expect(json[:data].first).to have_key(:contact_topics)\n      expect(json[:data].first).to have_key(:creator)\n      expect(json[:data].first).to have_key(:casa_case)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/datatables/reimbursement_datatable_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"ReimbursementDatatable\" do\n  let(:org) { CasaOrg.first }\n  let(:case_contacts) { CaseContact.joins(:casa_case) }\n  let(:instance) { described_class.new(case_contacts, params) }\n  let(:json_result) { instance.as_json }\n  let(:first_result) { json_result[:data].first }\n  let(:order_by) { nil }\n  let(:order_direction) { nil }\n  let(:page) { 1 }\n  let(:per_page) { 10 }\n  let(:params) do\n    datatable_params(\n      order_by: order_by,\n      order_direction: order_direction,\n      page: page,\n      per_page: per_page\n    )\n  end\n\n  # Requires the following to be defined:\n  # - `sorted_case_contacts` = array of reimbursement records ordered in the expected way\n  RSpec.shared_examples_for \"a sorted results set\" do\n    it \"orders ascending by default\" do\n      expect(first_result[:id]).to eq(sorted_case_contacts.first.id.to_s)\n    end\n\n    describe \"explicit ascending order\" do\n      let(:order_direction) { \"ASC\" }\n\n      it \"orders correctly\" do\n        expect(first_result[:id]).to eq(sorted_case_contacts.first.id.to_s)\n      end\n    end\n\n    describe \"descending order\" do\n      let(:order_direction) { \"DESC\" }\n\n      it \"orders correctly\" do\n        expect(first_result[:id]).to eq(sorted_case_contacts.last.id.to_s)\n      end\n    end\n  end\n\n  describe \"the data shape\" do\n    let(:first_contact) { case_contacts.first }\n\n    before do\n      create(:case_contact, casa_case: create(:casa_case))\n    end\n\n    describe \":casa_case\" do\n      subject(:casa_case) { first_result[:casa_case] }\n\n      it { is_expected.to include(id: first_contact.casa_case.id.to_s) }\n      it { is_expected.to include(case_number: first_contact.casa_case.case_number.to_s) }\n    end\n\n    describe \":volunteer\" do\n      subject(:volunteer) { first_result[:volunteer] }\n\n      it { is_expected.to include(id: first_contact.creator.id.to_s) }\n      it { is_expected.to include(display_name: first_contact.creator.display_name.to_s) }\n      it { is_expected.to include(email: first_contact.creator.email.to_s) }\n      it { is_expected.to include(address: first_contact.creator.address.to_s) }\n    end\n\n    describe \":contact_types\" do\n      subject(:contact_types) { first_result[:contact_types] }\n\n      let(:expected_contact_types) do\n        first_contact.contact_types.map do |ct|\n          {\n            name: ct.name,\n            group_name: ct.contact_type_group.name\n          }\n        end\n      end\n\n      it { is_expected.to eq(expected_contact_types) }\n    end\n\n    describe \":occurred_at\" do\n      subject(:occurred_at) { first_result[:occurred_at] }\n\n      it { is_expected.to eq(first_contact.occurred_at.to_s) }\n    end\n\n    describe \":miles_driven\" do\n      subject(:miles_driven) { first_result[:miles_driven] }\n\n      it { is_expected.to eq(first_contact.miles_driven.to_s) }\n    end\n\n    describe \":complete\" do\n      subject(:complete) { first_result[:complete] }\n\n      it { is_expected.to eq(first_contact.reimbursement_complete.to_s) }\n    end\n\n    describe \":mark_as_complete_path\" do\n      subject(:mark_as_complete_path) { first_result[:mark_as_complete_path] }\n\n      it { is_expected.to eq(\"/reimbursements/#{first_contact.id}/mark_as_complete\") }\n    end\n  end\n\n  describe \"multiple record handling\" do\n    before do\n      possible_miles_driven_values = (0..100).to_a.shuffle\n      possible_occurred_at_offsets = (0..100).to_a.shuffle\n\n      5.times.collect do\n        casa_case = create(:casa_case)\n        3.times.collect do\n          create(\n            :case_contact,\n            casa_case: casa_case,\n            occurred_at: Time.new - possible_occurred_at_offsets.pop,\n            miles_driven: possible_miles_driven_values.pop\n          )\n        end.reverse\n      end.flatten\n    end\n\n    it \"has the correct recordsFiltered\" do\n      expect(json_result[:recordsFiltered]).to eq(15)\n    end\n\n    it \"has the correct recordsTotal\" do\n      expect(json_result[:recordsTotal]).to eq(15)\n    end\n\n    it \"yields the correct number of records\" do\n      expect(json_result[:data].size).to eq 10\n    end\n\n    describe \"order by creator display name\" do\n      let(:order_by) { \"display_name\" }\n      let(:sorted_case_contacts) do\n        case_contacts.sort_by { |case_contact| case_contact.creator.display_name }\n      end\n\n      it_behaves_like \"a sorted results set\"\n    end\n\n    describe \"order by created at\" do\n      let(:order_by) { \"occurred_at\" }\n      let(:sorted_case_contacts) do\n        case_contacts.sort_by { |case_contact| case_contact.occurred_at }\n      end\n\n      it_behaves_like \"a sorted results set\"\n    end\n\n    describe \"order by miles driven\" do\n      let(:order_by) { \"miles_driven\" }\n      let(:sorted_case_contacts) do\n        case_contacts.sort_by { |case_contact| case_contact.miles_driven }\n      end\n\n      it_behaves_like \"a sorted results set\"\n    end\n\n    describe \"order by case number\" do\n      let(:order_by) { \"case_number\" }\n      let(:sorted_case_contacts) { case_contacts.sort_by { |case_contact| case_contact.casa_case.case_number } }\n      let(:first_case_number) { first_result[:casa_case][:case_number] }\n      let(:lowest_case_number) { sorted_case_contacts.first.casa_case.case_number }\n\n      it \"orders ascending by default\" do\n        expect(first_case_number).to eq(lowest_case_number)\n      end\n\n      describe \"explicit ascending order\" do\n        let(:order_direction) { \"ASC\" }\n\n        it \"orders correctly\" do\n          expect(first_case_number).to eq(lowest_case_number)\n        end\n      end\n\n      describe \"descending order\" do\n        let(:order_direction) { \"DESC\" }\n        let(:highest_case_number) { sorted_case_contacts.last.casa_case.case_number }\n\n        it \"orders correctly\" do\n          expect(first_case_number).to eq(highest_case_number)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/datatables/supervisor_datatable_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe SupervisorDatatable do\n  subject { described_class.new(org.supervisors, params).as_json }\n\n  let(:org) { create(:casa_org) }\n  let(:order_by) { \"display_name\" }\n  let(:order_direction) { \"asc\" }\n  let(:params) { datatable_params(order_by: nil, additional_filters: additional_filters) }\n\n  describe \"filter\" do\n    let!(:active_supervisor) { create(:supervisor, casa_org: org, active: true) }\n    let!(:inactive_supervisor) { create(:supervisor, casa_org: org, active: false) }\n\n    describe \"active\" do\n      context \"when active\" do\n        let(:additional_filters) { {active: %w[true]} }\n\n        it \"brings only active supervisors\", :aggregate_failures do\n          expect(subject[:recordsTotal]).to eq(2)\n          expect(subject[:recordsFiltered]).to eq(1)\n          expect(subject[:data].pluck(:display_name)).to include(CGI.escapeHTML(active_supervisor.display_name))\n          expect(subject[:data].pluck(:display_name)).not_to include(CGI.escapeHTML(inactive_supervisor.display_name))\n        end\n      end\n\n      context \"when inactive\" do\n        let(:additional_filters) { {active: %w[false]} }\n\n        it \"brings only inactive supervisors\", :aggregate_failures do\n          expect(subject[:recordsTotal]).to eq(2)\n          expect(subject[:recordsFiltered]).to eq(1)\n          expect(subject[:data].pluck(:display_name)).to include(CGI.escapeHTML(inactive_supervisor.display_name))\n          expect(subject[:data].pluck(:display_name)).not_to include(CGI.escapeHTML(active_supervisor.display_name))\n        end\n      end\n\n      context \"when both\" do\n        let(:additional_filters) { {active: %w[false true]} }\n        let!(:inactive_supervisor) { create(:supervisor, casa_org: org, active: true, display_name: \"Neil O'Reilly\") }\n\n        it \"brings only all supervisors\", :aggregate_failures do\n          expect(subject[:recordsTotal]).to eq(2)\n          expect(subject[:recordsFiltered]).to eq(2)\n          expect(subject[:data].pluck(:display_name)).to include(CGI.escapeHTML(active_supervisor.display_name))\n          expect(subject[:data].pluck(:display_name)).to include(CGI.escapeHTML(inactive_supervisor.display_name))\n        end\n      end\n\n      context \"when no selection\" do\n        let(:additional_filters) { {active: []} }\n\n        it \"brings nothing\", :aggregate_failures do\n          expect(subject[:recordsTotal]).to eq(2)\n          expect(subject[:recordsFiltered]).to eq(0)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/datatables/volunteer_datatable_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe VolunteerDatatable do\n  let(:org) { CasaOrg.first }\n  let(:supervisors) { Supervisor.all }\n  let(:assigned_volunteers) { Volunteer.joins(:supervisor) }\n  let(:subject) { described_class.new(org.volunteers, params).as_json }\n  let(:additional_filters) do\n    {\n      active: %w[false true],\n      supervisor: supervisors.pluck(:id),\n      transition_aged_youth: %w[false true]\n    }\n  end\n  let(:order_by) { \"display_name\" }\n  let(:order_direction) { \"asc\" }\n  let(:page) { 1 }\n  let(:per_page) { 10 }\n  let(:search_term) { nil }\n  let(:params) do\n    datatable_params(\n      additional_filters: additional_filters,\n      order_by: order_by,\n      order_direction: order_direction,\n      page: page,\n      per_page: per_page,\n      search_term: search_term\n    )\n  end\n\n  describe \":has_transition_aged_youth_cases\" do\n    let(:volunteer_has_transition_aged_youth) { subject[:data].first[:has_transition_aged_youth_cases] }\n    let(:non_transitional_birth) { 10.years.ago }\n    let(:transitional_birth) { 16.years.ago }\n    let(:org) { build :casa_org }\n    let(:active_assignment) { true }\n\n    context \"with a volunteer with a case assignment\" do\n      let(:casa_case) { build :casa_case, casa_org: org, birth_month_year_youth: youth_month_year }\n      let(:supervisor) { create :supervisor, casa_org: org }\n      let(:volunteer) { create :volunteer, casa_org: org, supervisor: supervisor }\n\n      before do\n        create :case_assignment, volunteer: volunteer, casa_case: casa_case, active: active_assignment\n      end\n\n      context \"which has a non-transition aged case\" do\n        let(:youth_month_year) { non_transitional_birth }\n\n        it \"is 'false'\" do\n          expect(volunteer_has_transition_aged_youth).to eq \"false\"\n        end\n      end\n\n      context \"which had (but no longer has) a transition aged case\" do\n        let(:youth_month_year) { transitional_birth }\n        let(:active_assignment) { false }\n\n        it \"is 'false'\" do\n          expect(volunteer_has_transition_aged_youth).to eq \"false\"\n        end\n      end\n\n      context \"which has a transition aged case\" do\n        let(:youth_month_year) { transitional_birth }\n\n        it \"is 'true'\" do\n          expect(volunteer_has_transition_aged_youth).to eq \"true\"\n        end\n      end\n    end\n  end\n\n  context \"with supervisors who have volunteers who have cases\" do\n    before :all do\n      DatabaseCleaner.strategy = :transaction\n      DatabaseCleaner.start\n\n      org = build(:casa_org)\n      supervisors = create_list :supervisor, 3, casa_org: org\n\n      supervisors.each do |supervisor|\n        volunteers = create_list :volunteer, 2, casa_org: org, supervisor: supervisor\n\n        volunteers.each_with_index do |volunteer, idx|\n          volunteer.casa_cases << build(:casa_case, casa_org: org, birth_month_year_youth: (idx == 1) ? 10.years.ago : 16.years.ago)\n        end\n      end\n\n      create_list :volunteer, 2, casa_org: org\n    end\n\n    after :all do\n      DatabaseCleaner.clean\n    end\n\n    describe \"order by\" do\n      let(:values) { subject[:data] }\n\n      let(:check_attr_equality) do\n        lambda { |model, idx|\n          expect(values[idx][:id]).to eq model.id.to_s\n        }\n      end\n\n      let(:check_asc_order) do\n        lambda {\n          sorted_models.each_with_index(&check_attr_equality)\n        }\n      end\n\n      let(:check_desc_order) do\n        lambda {\n          sorted_models.reverse.each_with_index(&check_attr_equality)\n        }\n      end\n\n      describe \"display_name\" do\n        let(:order_by) { \"display_name\" }\n        let(:sorted_models) { assigned_volunteers.order :display_name }\n\n        context \"when ascending\" do\n          it \"is successful\" do\n            check_asc_order.call\n          end\n        end\n\n        context \"when descending\" do\n          let(:order_direction) { \"desc\" }\n\n          it \"is succesful\" do\n            check_desc_order.call\n          end\n        end\n      end\n\n      describe \"email\" do\n        let(:order_by) { \"email\" }\n        let(:sorted_models) { assigned_volunteers.order :email }\n\n        context \"when ascending\" do\n          it \"is successful\" do\n            check_asc_order.call\n          end\n        end\n\n        context \"when descending\" do\n          let(:order_direction) { \"desc\" }\n\n          it \"is successful\" do\n            check_desc_order.call\n          end\n        end\n      end\n\n      describe \"supervisor_name\" do\n        let(:order_by) { \"supervisor_name\" }\n        let(:sorted_models) { assigned_volunteers.sort_by { |v| v.supervisor.display_name } }\n\n        context \"when ascending\" do\n          it \"is successful\" do\n            sorted_models.each_with_index do |model, idx|\n              expect(CGI.unescapeHTML(values[idx][:supervisor][:name])).to eq model.supervisor.display_name\n            end\n          end\n        end\n\n        context \"when descending\" do\n          let(:order_direction) { \"desc\" }\n          let(:sorted_models) { assigned_volunteers.sort_by { |v| v.supervisor.display_name } }\n\n          it \"is successful\" do\n            sorted_models.reverse.each_with_index do |model, idx|\n              expect(CGI.unescapeHTML(values[idx][:supervisor][:name])).to eq model.supervisor.display_name\n            end\n          end\n        end\n      end\n\n      describe \"active\" do\n        let(:order_by) { \"active\" }\n        let(:sorted_models) { assigned_volunteers.order :active, :id }\n\n        before do\n          supervisors.each { |s| s.volunteers.first.update active: false }\n        end\n\n        context \"when ascending\" do\n          it \"is successful\" do\n            check_asc_order.call\n          end\n        end\n\n        context \"when descending\" do\n          let(:order_direction) { \"desc\" }\n          let(:sorted_models) { assigned_volunteers.order :active, id: :desc }\n\n          it \"is successful\" do\n            check_desc_order.call\n          end\n        end\n      end\n\n      describe \"has_transition_aged_youth_cases\" do\n        let(:order_by) { \"has_transition_aged_youth_cases\" }\n        let(:transition_aged_youth_bool_to_int) do\n          lambda { |volunteer|\n            volunteer.casa_cases.exists?(birth_month_year_youth: ..CasaCase::TRANSITION_AGE.years.ago) ? 1 : 0\n          }\n        end\n        let(:sorted_models) { assigned_volunteers.sort_by(&transition_aged_youth_bool_to_int) }\n\n        context \"when ascending\" do\n          it \"is successful\" do\n            sorted_models.each_with_index do |model, idx|\n              expect(values[idx][:has_transition_aged_youth_cases]).to eq model.casa_cases.exists?(birth_month_year_youth: ..CasaCase::TRANSITION_AGE.years.ago).to_s\n            end\n          end\n        end\n\n        context \"when descending\" do\n          let(:order_direction) { \"desc\" }\n          let(:sorted_models) { assigned_volunteers.sort_by(&transition_aged_youth_bool_to_int) }\n\n          it \"is successful\" do\n            sorted_models.reverse.each_with_index do |model, idx|\n              expect(values[idx][:has_transition_aged_youth_cases]).to eq model.casa_cases.exists?(birth_month_year_youth: ..CasaCase::TRANSITION_AGE.years.ago).to_s\n            end\n          end\n        end\n      end\n\n      describe \"most_recent_attempt_occurred_at\" do\n        let(:order_by) { \"most_recent_attempt_occurred_at\" }\n        let(:sorted_models) do\n          assigned_volunteers.order(:id).sort_by { |v| v.case_contacts.maximum :occurred_at }\n        end\n\n        before do\n          CasaCase.all.each_with_index { |cc, idx| cc.case_contacts << build(:case_contact, contact_made: true, creator: cc.volunteers.first, occurred_at: idx.days.ago) }\n        end\n\n        context \"when ascending\" do\n          it \"is successful\" do\n            check_asc_order.call\n          end\n        end\n\n        context \"when descending\" do\n          let(:order_direction) { \"desc\" }\n\n          it \"is successful\" do\n            check_desc_order.call\n          end\n        end\n      end\n\n      describe \"contacts_made_in_past_days\" do\n        let(:order_by) { \"contacts_made_in_past_days\" }\n        let(:volunteer1) { assigned_volunteers.first }\n        let(:casa_case1) { volunteer1.casa_cases.first }\n        let(:volunteer2) { assigned_volunteers.second }\n        let(:casa_case2) { volunteer2.casa_cases.first }\n        let(:sorted_models) do\n          assigned_volunteers\n            .order(:id)\n            .sort_by { |v| v.case_contacts.where(occurred_at: 60.days.ago.to_date..).count }\n            .sort_by { |v| v.case_contacts.exists?(occurred_at: 60.days.ago.to_date..) ? 0 : 1 }\n        end\n\n        before do\n          4.times do |i|\n            create(:case_contact, contact_made: true, casa_case: casa_case1, creator: volunteer1, occurred_at: (19 * (i + 1)).days.ago)\n          end\n\n          3.times do |i|\n            create(:case_contact, contact_made: true, casa_case: casa_case2, creator: volunteer2, occurred_at: (29 * (i + 1)).days.ago)\n          end\n        end\n\n        context \"when ascending\" do\n          it \"is successful\" do\n            expect(values.pluck(:contacts_made_in_past_days)).to eq [\"2\", \"3\", \"\", \"\", \"\", \"\"]\n          end\n        end\n\n        context \"when descending\" do\n          let(:order_direction) { \"desc\" }\n          let(:sorted_models) do\n            assigned_volunteers\n              .order(id: :desc)\n              .sort_by { |v| v.case_contacts.where(occurred_at: 60.days.ago.to_date..).count }\n          end\n\n          it \"is successful\" do\n            expect(values.pluck(:contacts_made_in_past_days)).to eq [\"3\", \"2\", \"\", \"\", \"\", \"\"]\n          end\n\n          it \"moves blanks to the end\" do\n            expect(values[0][:contacts_made_in_past_days]).not_to be_blank\n          end\n        end\n      end\n    end\n\n    describe \"search\" do\n      let(:volunteer) { assigned_volunteers.first }\n      let(:search_term) { volunteer.display_name }\n\n      describe \"recordsTotal\" do\n        it \"includes all volunteers\" do\n          expect(subject[:recordsTotal]).to eq org.volunteers.count\n        end\n      end\n\n      describe \"recordsFiltered\" do\n        it \"includes filtered volunteers\" do\n          expect(subject[:recordsFiltered]).to eq 1\n        end\n      end\n\n      describe \"display_name\" do\n        it \"is successful\" do\n          expect(subject[:data].length).to eq 1\n          expect(subject[:data].first[:id]).to eq volunteer.id.to_s\n        end\n      end\n\n      describe \"email\" do\n        let(:search_term) { volunteer.email }\n\n        it \"is successful\" do\n          expect(subject[:data].length).to eq 1\n          expect(subject[:data].first[:id]).to eq volunteer.id.to_s\n        end\n      end\n\n      describe \"supervisor_name\" do\n        let(:supervisor) { volunteer.supervisor }\n        let(:search_term) { supervisor.display_name }\n        let(:volunteers) { supervisor.volunteers }\n\n        it \"is successful\" do\n          expect(subject[:data].length).to eq volunteers.count\n          expect(subject[:data].pluck(:id).sort).to eq volunteers.map { |v| v.id.to_s }.sort\n        end\n      end\n\n      describe \"case_numbers\" do\n        let(:casa_case) { volunteer.casa_cases.first }\n        let(:search_term) { casa_case.case_number }\n\n        # Sometimes the default case number is a substring of other case numbers\n        before { casa_case.update case_number: Random.hex }\n\n        it \"is successful\" do\n          expect(subject[:data].length).to eq 1\n          expect(subject[:data].first[:id]).to eq volunteer.id.to_s\n        end\n\n        context \"when search term case number matches unassigned case\" do\n          let(:new_supervisor) { create(:supervisor, casa_org: casa_case.casa_org) }\n          let(:new_casa_case) { create(:casa_case, :pre_transition, casa_org: casa_case.casa_org, case_number: \"ABC-123\") }\n          let(:volunteer_1) { create(:volunteer, display_name: \"Volunteer 1\", casa_org: casa_case.casa_org, supervisor: new_supervisor) }\n          let(:volunteer_2) { create(:volunteer, display_name: \"Volunteer 2\", casa_org: casa_case.casa_org, supervisor: new_supervisor) }\n          let(:search_term) { new_casa_case.case_number }\n\n          before do\n            create(:case_assignment, casa_case: new_casa_case, volunteer: volunteer_1)\n            create(:case_assignment, casa_case: new_casa_case, volunteer: volunteer_2, active: false)\n          end\n\n          it \"does not include unassigned case matches in search results\" do\n            expect(subject[:data].length).to eq 1\n            expect(subject[:data].first[:id]).to eq volunteer_1.id.to_s\n          end\n        end\n      end\n    end\n\n    describe \"filter\" do\n      describe \"supervisor\" do\n        context \"when unassigned excluded\" do\n          it \"is successful\" do\n            expect(subject[:recordsTotal]).to eq Volunteer.count\n            expect(subject[:recordsFiltered]).to eq assigned_volunteers.count\n          end\n        end\n\n        context \"when unassigned included\" do\n          before { additional_filters[:supervisor] << nil }\n\n          it \"is successful\" do\n            expect(subject[:recordsTotal]).to eq Volunteer.count\n            expect(subject[:recordsFiltered]).to eq Volunteer.count\n          end\n        end\n\n        context \"when no selection\" do\n          before { additional_filters[:supervisor] = [] }\n\n          it \"is successful\" do\n            expect(subject[:recordsTotal]).to eq Volunteer.count\n            expect(subject[:recordsFiltered]).to be_zero\n          end\n        end\n      end\n\n      describe \"active\" do\n        before { assigned_volunteers.limit(3).update_all active: \"false\" }\n\n        context \"when active\" do\n          before { additional_filters[:active] = %w[true] }\n\n          it \"is successful\" do\n            expect(subject[:recordsTotal]).to eq Volunteer.count\n            expect(subject[:recordsFiltered]).to eq assigned_volunteers.where(active: true).count\n          end\n        end\n\n        context \"when inactive\" do\n          before { additional_filters[:active] = %w[false] }\n\n          it \"is successful\" do\n            expect(subject[:recordsTotal]).to eq Volunteer.count\n            expect(subject[:recordsFiltered]).to eq assigned_volunteers.where(active: false).count\n          end\n        end\n\n        context \"when both\" do\n          before { additional_filters[:active] = %w[false true] }\n\n          it \"is successful\" do\n            expect(subject[:recordsTotal]).to eq Volunteer.count\n            expect(subject[:recordsFiltered]).to eq assigned_volunteers.count\n          end\n        end\n\n        context \"when no selection\" do\n          before { additional_filters[:active] = [] }\n\n          it \"is successful\" do\n            expect(subject[:recordsTotal]).to eq Volunteer.count\n            expect(subject[:recordsFiltered]).to be_zero\n          end\n        end\n      end\n\n      describe \"transition_aged_youth\" do\n        context \"when yes\" do\n          before { additional_filters[:transition_aged_youth] = %w[true] }\n\n          it \"is successful\" do\n            expect(subject[:recordsTotal]).to eq 8\n            expect(subject[:recordsFiltered]).to eq 3\n          end\n        end\n\n        context \"when no\" do\n          before { additional_filters[:transition_aged_youth] = %w[false] }\n\n          it \"is successful\" do\n            expect(subject[:recordsTotal]).to eq 8\n            expect(subject[:recordsFiltered]).to eq 3\n          end\n        end\n\n        context \"when both\" do\n          before { additional_filters[:transition_aged_youth] = %w[false true] }\n\n          it \"is successful\" do\n            expect(subject[:recordsTotal]).to eq 8\n            expect(subject[:recordsFiltered]).to eq 6\n          end\n        end\n\n        context \"when no selection\" do\n          before { additional_filters[:transition_aged_youth] = [] }\n\n          it \"is successful\" do\n            expect(subject[:recordsTotal]).to eq 8\n            expect(subject[:recordsFiltered]).to be_zero\n          end\n        end\n      end\n    end\n\n    describe \"pagination\" do\n      let(:page) { 2 }\n      let(:per_page) { 5 }\n\n      it \"is successful\" do\n        expect(subject[:data].length).to eq assigned_volunteers.count - 5\n      end\n\n      describe \"recordsTotal\" do\n        it \"includes all volunteers\" do\n          expect(subject[:recordsTotal]).to eq org.volunteers.count\n        end\n      end\n\n      describe \"recordsFiltered\" do\n        it \"includes all filtered volunteers\" do\n          expect(subject[:recordsFiltered]).to eq assigned_volunteers.count\n        end\n      end\n    end\n  end\n\n  describe \"extra_languages filter\" do\n    let(:org) { build :casa_org }\n    let(:supervisor) { create :supervisor, casa_org: org }\n    let(:volunteer_with_language) { create :volunteer, casa_org: org, supervisor: supervisor }\n    let(:volunteer_without_language) { create :volunteer, casa_org: org, supervisor: supervisor }\n    let(:language) { create :language, casa_org: org }\n\n    before do\n      create :user_language, user: volunteer_with_language, language: language\n    end\n\n    context \"when filtering for volunteers with extra languages with default ordering\" do\n      # Use an invalid order_by to trigger the default COALESCE ordering\n      # which causes PG::InvalidColumnReference when combined with DISTINCT\n      let(:order_by) { \"invalid_column\" }\n      let(:additional_filters) do\n        {\n          active: %w[false true],\n          supervisor: [supervisor.id],\n          transition_aged_youth: %w[false true],\n          extra_languages: %w[true]\n        }\n      end\n\n      it \"does not raise PG::InvalidColumnReference error\" do\n        # Bug: PG::InvalidColumnReference: ERROR: for SELECT DISTINCT, ORDER BY\n        # expressions must appear in select list\n        # This occurs because extra_languages filter adds .distinct but the default\n        # ORDER BY COALESCE(users.display_name, users.email) is not in the SELECT list\n        expect { subject }.not_to raise_error\n      end\n\n      it \"returns only volunteers with languages\" do\n        expect(subject[:data].map { |d| d[:id].to_i }).to contain_exactly(volunteer_with_language.id)\n      end\n    end\n\n    context \"when filtering for volunteers without extra languages with default ordering\" do\n      let(:order_by) { \"invalid_column\" }\n      let(:additional_filters) do\n        {\n          active: %w[false true],\n          supervisor: [supervisor.id],\n          transition_aged_youth: %w[false true],\n          extra_languages: %w[false]\n        }\n      end\n\n      it \"does not raise PG::InvalidColumnReference error\" do\n        expect { subject }.not_to raise_error\n      end\n    end\n\n    context \"when filtering with multiple extra_languages options with default ordering\" do\n      let(:order_by) { \"invalid_column\" }\n      let(:additional_filters) do\n        {\n          active: %w[false true],\n          supervisor: [supervisor.id],\n          transition_aged_youth: %w[false true],\n          extra_languages: %w[true false]\n        }\n      end\n\n      it \"does not raise PG::InvalidColumnReference error\" do\n        expect { subject }.not_to raise_error\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/decorators/android_app_association_decorator_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe AndroidAppAssociationDecorator do\nend\n"
  },
  {
    "path": "spec/decorators/application_decorator_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe ApplicationDecorator, type: :decorator do\n  # TODO: Add tests for ApplicationDecorator\n\n  pending \"add some tests for ApplicationDecorator\"\nend\n"
  },
  {
    "path": "spec/decorators/casa_case_decorator_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CasaCaseDecorator do\n  describe \"#court_report_submission\" do\n    subject { casa_case.decorate.court_report_submission }\n\n    let(:casa_case) { build(:casa_case, court_report_status: court_report_status) }\n\n    context \"when case_report_status is not_submitted\" do\n      let(:court_report_status) { :not_submitted }\n\n      it { is_expected.to eq(\"Not submitted\") }\n    end\n\n    context \"when case_report_status is submitted\" do\n      let(:court_report_status) { :submitted }\n\n      it { is_expected.to eq(\"Submitted\") }\n    end\n\n    context \"when case_report_status is in_review\" do\n      let(:court_report_status) { :in_review }\n\n      it { is_expected.to eq(\"In review\") }\n    end\n\n    context \"when case_report_status is completed\" do\n      let(:court_report_status) { :completed }\n\n      it { is_expected.to eq(\"Completed\") }\n    end\n  end\n\n  describe \"#court_report_submission\" do\n    subject { casa_case.decorate.court_report_submitted_date }\n\n    let(:submitted_time) { Time.parse(\"Sun Nov 08 11:06:20 2020\") }\n    let(:casa_case) { build(:casa_case, court_report_submitted_at: submitted_time) }\n\n    it { is_expected.to eq \"November 8, 2020\" }\n\n    context \"when report is not submitted\" do\n      let(:submitted_time) { nil }\n\n      it { is_expected.to be_nil }\n    end\n  end\n\n  describe \"#formatted_updated_at\" do\n    subject { casa_case.decorate.formatted_updated_at }\n\n    let(:updated_at_time) { Time.parse(\"Wed Dec 9 12:51:20 2020\") }\n    let(:casa_case) { build(:casa_case, updated_at: updated_at_time) }\n\n    it { is_expected.to eq \"12-09-2020\" }\n  end\n\n  describe \"#transition_age_youth\" do\n    it \"returns transition age youth status with icon if not transition age youth && birthday is nil\" do\n      casa_case = build(:casa_case, birth_month_year_youth: nil)\n      expect(casa_case.decorate.transition_aged_youth)\n        .to eq \"No #{CasaCase::NON_TRANSITION_AGE_YOUTH_ICON}\"\n    end\n\n    it \"returns transition age youth status with icon if over 14 years old\" do\n      casa_case = build_stubbed(:casa_case, birth_month_year_youth: CasaCase::TRANSITION_AGE.years.ago)\n      expect(casa_case.decorate.transition_aged_youth)\n        .to include \"Yes #{CasaCase::TRANSITION_AGE_YOUTH_ICON}\"\n      expect(casa_case.decorate.transition_aged_youth).to include \"Emancipation\"\n    end\n\n    it \"returns non-transition age youth status with icon if not over 14 years old\" do\n      casa_case = build(:casa_case, birth_month_year_youth: 13.years.ago)\n      expect(casa_case.decorate.transition_aged_youth)\n        .to eq \"No #{CasaCase::NON_TRANSITION_AGE_YOUTH_ICON}\"\n    end\n  end\n\n  describe \"#transition_age_youth_icon\" do\n    it \"returns transition age youth status with icon if not transition age youth && birthday is nil\" do\n      casa_case = build(:casa_case, birth_month_year_youth: nil)\n      expect(casa_case.decorate.transition_aged_youth_icon)\n        .to eq CasaCase::NON_TRANSITION_AGE_YOUTH_ICON\n    end\n\n    it \"returns transition age youth icon if over 14 years old\" do\n      casa_case = build(:casa_case, birth_month_year_youth: CasaCase::TRANSITION_AGE.years.ago)\n      expect(casa_case.decorate.transition_aged_youth_icon)\n        .to eq CasaCase::TRANSITION_AGE_YOUTH_ICON\n    end\n\n    it \"returns non-transition age youth icon if not over 14 years old\" do\n      casa_case = build(:casa_case, birth_month_year_youth: 13.years.ago)\n      expect(casa_case.decorate.transition_aged_youth_icon)\n        .to eq CasaCase::NON_TRANSITION_AGE_YOUTH_ICON\n    end\n  end\n\n  describe \"#emancipation_checklist_count\" do\n    it \"returns a fraction indicating how many emancipation categories have been fulfilled\" do\n      casa_case = build(:casa_case)\n\n      expect(casa_case).to(\n        receive(:casa_case_emancipation_categories).and_return(\n          double(:categories, count: 2)\n        )\n      )\n      expect(EmancipationCategory).to receive(:count).and_return(5)\n      expect(casa_case.decorate.emancipation_checklist_count).to eq \"2 / 5\"\n    end\n  end\nend\n"
  },
  {
    "path": "spec/decorators/case_assignment_decorator_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CaseAssignmentDecorator, type: :decorator do\n  # TODO: Add tests for CaseAssignmentDecorator\n\n  pending \"add some tests for CaseAssignmentDecorator\"\nend\n"
  },
  {
    "path": "spec/decorators/case_contact_decorator_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CaseContactDecorator do\n  let(:case_contact) { build(:case_contact) }\n\n  describe \"#duration_minutes\" do\n    context \"when duration_minutes is less than 60\" do\n      let(:case_contact) { build(:case_contact, duration_minutes: 30) }\n\n      it \"returns only minutes\" do\n        expect(case_contact.decorate.duration_minutes).to eq \"30 minutes\"\n      end\n    end\n\n    context \"when duration_minutes is greater than 60\" do\n      let(:case_contact) { build(:case_contact, duration_minutes: 135) }\n\n      it \"returns minutes and hours\" do\n        case_contact.update_attribute(:duration_minutes, 135)\n\n        expect(case_contact.decorate.duration_minutes).to eq \"2 hours 15 minutes\"\n      end\n\n      context \"when is exactly on hour\" do\n        let(:case_contact) { build(:case_contact, duration_minutes: 120) }\n\n        it \"returns only hours\" do\n          expect(case_contact.decorate.duration_minutes).to eq \"2 hours\"\n        end\n      end\n    end\n\n    context \"when minutes is nil\" do\n      let(:case_contact) { build(:case_contact, duration_minutes: nil) }\n\n      it \"returns not set\" do\n        expect(case_contact.decorate.duration_minutes).to eq \"Duration not set\"\n      end\n    end\n  end\n\n  describe \"#contact_made\" do\n    context \"when contact_made is false\" do\n      it \"returns No Contact Made\" do\n        case_contact.update_attribute(:contact_made, false)\n\n        expect(case_contact.decorate.contact_made).to eq \"No Contact Made\"\n      end\n    end\n\n    context \"when contact_made is true\" do\n      it \"returns Yes\" do\n        case_contact.update_attribute(:contact_made, true)\n\n        expect(case_contact.decorate.contact_made).to be_nil\n      end\n    end\n  end\n\n  describe \"#contact_types\" do\n    subject(:contact_types) { decorated_case_contact.contact_types }\n\n    let(:case_contact) { build(:case_contact, contact_types: contact_types) }\n    let(:decorated_case_contact) do\n      described_class.new(case_contact)\n    end\n\n    context \"when the contact_types is an empty array\" do\n      let(:contact_types) { [] }\n\n      it { is_expected.to eql(\"No contact type specified\") }\n    end\n\n    context \"when the contact_types is an array with three or more values\" do\n      let(:contact_types) do\n        [\n          build_stubbed(:contact_type, name: \"School\"),\n          build_stubbed(:contact_type, name: \"Therapist\"),\n          build_stubbed(:contact_type, name: \"Bio Parent\")\n        ]\n      end\n\n      it { is_expected.to eql(\"School, Therapist, and Bio Parent\") }\n    end\n\n    context \"when the contact types is an array with less than three values\" do\n      let(:contact_types) do\n        [\n          build_stubbed(:contact_type, name: \"School\"),\n          build_stubbed(:contact_type, name: \"Therapist\")\n        ]\n      end\n\n      it { is_expected.to eql(\"School and Therapist\") }\n    end\n  end\n\n  describe \"#medium_icon_classes\" do\n    context \"when medium type is in-person\" do\n      it \"returns the proper font-awesome classes\" do\n        case_contact.update_attribute(:medium_type, \"in-person\")\n\n        expect(case_contact.decorate.medium_icon_classes).to eql(\"lni lni-users\")\n      end\n    end\n\n    context \"when medium type is text/email\" do\n      it \"returns the proper font-awesome classes\" do\n        case_contact.update_attribute(:medium_type, \"text/email\")\n\n        expect(case_contact.decorate.medium_icon_classes).to eql(\"lni lni-envelope\")\n      end\n    end\n\n    context \"when medium type is video\" do\n      it \"returns the proper font-awesome classes\" do\n        case_contact.update_attribute(:medium_type, \"video\")\n\n        expect(case_contact.decorate.medium_icon_classes).to eql(\"lni lni-camera\")\n      end\n    end\n\n    context \"when medium type is voice-only\" do\n      it \"returns the proper font-awesome classes\" do\n        case_contact.update_attribute(:medium_type, \"voice-only\")\n\n        expect(case_contact.decorate.medium_icon_classes).to eql(\"lni lni-phone\")\n      end\n    end\n\n    context \"when medium type is letter\" do\n      it \"returns the proper font-awesome classes\" do\n        case_contact.update_attribute(:medium_type, \"letter\")\n\n        expect(case_contact.decorate.medium_icon_classes).to eql(\"lni lni-empty-file\")\n      end\n    end\n\n    context \"when medium type is anything else\" do\n      it \"returns the proper font-awesome classes\" do\n        case_contact.update_attribute(:medium_type, \"foo\")\n\n        expect(case_contact.decorate.medium_icon_classes).to eql(\"lni lni-question-circle\")\n      end\n    end\n  end\n\n  describe \"#subheading\" do\n    let(:contact_group) { build_stubbed(:contact_type_group, name: \"Group X\") }\n    let(:contact_type) { build_stubbed(:contact_type, contact_type_group: contact_group, name: \"Type X\") }\n\n    context \"when all information is available\" do\n      it \"returns a properly formatted string\" do\n        case_contact.update(occurred_at: \"2020-12-01\", duration_minutes: 99, contact_made: false, miles_driven: 100, want_driving_reimbursement: true)\n        case_contact.contact_types = [contact_type]\n\n        expect(case_contact.decorate.subheading).to eq(\n          \"December 1, 2020 | 1 hour 39 minutes | No Contact Made | 100 miles driven | Reimbursement\"\n        )\n      end\n    end\n\n    context \"when some information is missing\" do\n      it \"returns a properly formatted string without extra pipes\" do\n        case_contact.update(occurred_at: \"2020-12-01\", duration_minutes: 99, contact_made: true, miles_driven: 100, want_driving_reimbursement: true)\n        case_contact.contact_types = [contact_type]\n\n        expect(case_contact.decorate.subheading).to eq(\n          \"December 1, 2020 | 1 hour 39 minutes | 100 miles driven | Reimbursement\"\n        )\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/decorators/case_contacts/form_decorator_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CaseContacts::FormDecorator do\nend\n"
  },
  {
    "path": "spec/decorators/contact_type_decorator_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe ContactTypeDecorator do\n  let(:casa_org) { create(:casa_org) }\n  let(:contact_type_group) { create(:contact_type_group, casa_org: casa_org) }\n  let(:contact_type) { create(:contact_type, contact_type_group: contact_type_group) }\n\n  describe \"hash_for_multi_select_with_cases\" do\n    it \"returns hash\" do\n      hash = contact_type.decorate.hash_for_multi_select_with_cases([])\n      expect(hash[:value]).to eq contact_type.id\n      expect(hash[:text]).to eq contact_type.name\n      expect(hash[:group]).to eq contact_type_group.name\n      expect(hash[:subtext]).to eq \"never\"\n    end\n\n    context \"with nil array\" do\n      it { expect(contact_type.decorate.hash_for_multi_select_with_cases(nil).class).to eq Hash }\n    end\n  end\n\n  describe \"last_time_used_with_cases\" do\n    subject { contact_type.decorate.last_time_used_with_cases casa_case_ids }\n\n    let(:casa_case_ids) { [] }\n\n    context \"with empty array\" do\n      it { is_expected.to eq \"never\" }\n    end\n\n    context \"with cases\" do\n      let(:casa_case) { create(:casa_case, casa_org: casa_org) }\n      let(:casa_case_ids) { [casa_case.id] }\n\n      context \"with no case contacts\" do\n        it { expect(contact_type.decorate.last_time_used_with_cases([])).to eq \"never\" }\n      end\n\n      context \"with case contacts\" do\n        let(:case_contact1) { create(:case_contact, casa_case: casa_case, occurred_at: 4.days.ago) }\n        let(:case_contact2) { create(:case_contact, casa_case: casa_case, occurred_at: 3.days.ago) }\n\n        it \"is the most recent case contact\" do\n          case_contact1.contact_types << contact_type\n          expect(subject).to eq \"4 days ago\"\n\n          case_contact2.contact_types << contact_type\n          expect(contact_type.decorate.last_time_used_with_cases(casa_case_ids)).to eq \"3 days ago\"\n        end\n\n        context \"when case_contact occurred_at is nil\" do\n          let(:case_contact1) { build :case_contact, casa_case: casa_case, occurred_at: nil }\n\n          it \"returns 'never'\" do\n            case_contact1.contact_types << contact_type\n            expect(subject).to eq \"never\"\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/decorators/court_date_decorator_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CourtDateDecorator, type: :decorator do\n  # TODO: Add tests for CourtDateDecorator\n\n  pending \"add some tests for CourtDateDecorator\"\nend\n"
  },
  {
    "path": "spec/decorators/learning_hour_decorator_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe LearningHourDecorator, type: :decorator do\n  # TODO: Add tests for LearningHourDecorator\n\n  pending \"add some tests for LearningHourDecorator\"\nend\n"
  },
  {
    "path": "spec/decorators/learning_hour_topic_decorator_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe LearningHourTopicDecorator, type: :decorator do\n  # TODO: Add tests for LearningHourTopicDecorator\n\n  pending \"add some tests for LearningHourTopicDecorator\"\nend\n"
  },
  {
    "path": "spec/decorators/other_duty_decorator_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe OtherDutyDecorator do\n  let(:other_duty) { build(:other_duty) }\n\n  describe \"#duration_minutes\" do\n    context \"when duration_minutes is less than 60\" do\n      it \"returns only minutes\" do\n        other_duty.update_attribute(:duration_minutes, 45)\n\n        expect(other_duty.decorate.duration_in_minutes).to eq \"45 minutes\"\n      end\n    end\n\n    context \"when duration_minutes is greater than 60\" do\n      it \"returns minutes and hours\" do\n        other_duty.update_attribute(:duration_minutes, 182)\n\n        expect(other_duty.decorate.duration_in_minutes).to eq \"3 hours 2 minutes\"\n      end\n    end\n  end\n\n  describe \"#truncate_notes\" do\n    let(:truncated_od) {\n      build(:other_duty,\n        notes: \"I have no fear, for fear is the little death that kills me over and over. Without fear, I die but once.\")\n    }\n\n    context \"when notes length is shorter than limit\" do\n      it \"returns notes completely\" do\n        other_duty.update_attribute(:notes, \"Short note.\")\n\n        expect(other_duty.decorate.truncate_notes).to eq(\"<p>Short note.</p>\")\n      end\n    end\n\n    context \"when notes length is bigger than limit\" do\n      it \"returns a truncated string\" do\n        expect(truncated_od.decorate.truncate_notes).to eq(\n          \"<p>I have no fear, for fear is the little death...</p>\"\n        )\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/decorators/patch_note_decorator_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe PatchNoteDecorator do\nend\n"
  },
  {
    "path": "spec/decorators/placement_decorator_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CaseContactDecorator do\n  let(:placement) { build(:placement) }\n  let(:date_string) { \"April 14, 2023\" }\n\n  before do\n    placement.update_attribute(:placement_started_at, Date.new(2023, 4, 14))\n  end\n\n  describe \"#formatted_date\" do\n    it \"returns correctly formatted date\" do\n      expect(placement.decorate.formatted_date).to eq date_string\n    end\n  end\n\n  describe \"#placement_info\" do\n    it \"returns the correct placement info string\" do\n      expect(placement.decorate.placement_info).to eq \"Started At:  #{date_string} - Placement Type: #{placement.placement_type.name}\"\n    end\n  end\nend\n"
  },
  {
    "path": "spec/decorators/user_decorator_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe UserDecorator do\n  let(:decorated_user) { user.decorate }\n  let(:user) { create(:user) }\n\n  describe \"#status\" do\n    context \"when user role is inactive\" do\n      it \"returns Inactive\" do\n        volunteer = build(:volunteer, :inactive)\n\n        expect(volunteer.decorate.status).to eq \"Inactive\"\n      end\n    end\n\n    context \"when user role is volunteer\" do\n      it \"returns Active\" do\n        volunteer = build(:volunteer)\n\n        expect(volunteer.decorate.status).to eq \"Active\"\n      end\n    end\n  end\n\n  describe \"#formatted_created_at\" do\n    context \"when using the 'default'format string\"\n    it \"returns the correctly formatted date\" do\n      user.update(created_at: Time.new(2023, 5, 1, 12, 0, 0))\n      expected_date = I18n.l(user.created_at, format: :full, default: nil)\n      expect(decorated_user.formatted_created_at).to eq expected_date\n    end\n\n    context \"when passing in the custom :edit_profile format string\"\n    it \"returns the correctly formatted date\" do\n      user.update(created_at: Time.new(2023, 5, 1, 12, 0, 0))\n      expected_date = I18n.l(user.created_at, format: :edit_profile, default: nil)\n      decorated_user.context[:format] = :edit_profile\n      expect(decorated_user.formatted_created_at).to eq expected_date\n    end\n  end\n\n  describe \"#formatted_updated_at\" do\n    context \"when using the 'default'format string\"\n    it \"returns the correctly formatted date\" do\n      user.update(updated_at: Time.new(2023, 5, 1, 12, 0, 0))\n      expected_date = I18n.l(user.updated_at, format: :full, default: nil)\n      expect(decorated_user.formatted_updated_at).to eq expected_date\n    end\n\n    context \"when passing in the custom :edit_profile format string\"\n    it \"returns the correctly formatted date\" do\n      user.update(updated_at: Time.new(2023, 5, 1, 12, 0, 0))\n      expected_date = I18n.l(user.updated_at, format: :edit_profile, default: nil)\n      decorated_user.context[:format] = :edit_profile\n      expect(decorated_user.formatted_updated_at).to eq expected_date\n    end\n  end\n\n  describe \"#formatted_current_sign_in_at\" do\n    context \"when using the 'default'format string\"\n    it \"returns the correctly formatted date\" do\n      user.update(current_sign_in_at: Time.new(2023, 5, 1, 12, 0, 0))\n      expected_date = I18n.l(user.current_sign_in_at, format: :full, default: nil)\n      expect(decorated_user.formatted_current_sign_in_at).to eq expected_date\n    end\n\n    context \"when passing in the custom :edit_profile format string\"\n    it \"returns the correctly formatted date\" do\n      user.update(current_sign_in_at: Time.new(2023, 5, 1, 12, 0, 0))\n      expected_date = I18n.l(user.current_sign_in_at, format: :edit_profile, default: nil)\n      decorated_user.context[:format] = :edit_profile\n      expect(decorated_user.formatted_current_sign_in_at).to eq expected_date\n    end\n  end\n\n  describe \"#formatted_invitation_accepted_at\" do\n    context \"when using the 'default'format string\"\n    it \"returns the correctly formatted date\" do\n      user.update(invitation_accepted_at: Time.new(2023, 5, 1, 12, 0, 0))\n      expected_date = I18n.l(user.invitation_accepted_at, format: :full, default: nil)\n      expect(decorated_user.formatted_invitation_accepted_at).to eq expected_date\n    end\n\n    context \"when passing in the custom :edit_profile format string\"\n    it \"returns the correctly formatted date\" do\n      user.update(invitation_accepted_at: Time.new(2023, 5, 1, 12, 0, 0))\n      expected_date = I18n.l(user.invitation_accepted_at, format: :edit_profile, default: nil)\n      decorated_user.context[:format] = :edit_profile\n      expect(decorated_user.formatted_invitation_accepted_at).to eq expected_date\n    end\n  end\n\n  describe \"#formatted_reset_password_sent_at\" do\n    context \"when using the 'default'format string\"\n    it \"returns the correctly formatted date\" do\n      user.update(reset_password_sent_at: Time.new(2023, 5, 1, 12, 0, 0))\n      expected_date = I18n.l(user.reset_password_sent_at, format: :full, default: nil)\n      expect(decorated_user.formatted_reset_password_sent_at).to eq expected_date\n    end\n\n    context \"when passing in the custom :edit_profile format string\"\n    it \"returns the correctly formatted date\" do\n      user.update(reset_password_sent_at: Time.new(2023, 5, 1, 12, 0, 0))\n      expected_date = I18n.l(user.reset_password_sent_at, format: :edit_profile, default: nil)\n      decorated_user.context[:format] = :edit_profile\n      expect(decorated_user.formatted_reset_password_sent_at).to eq expected_date\n    end\n  end\n\n  describe \"#formatted_invitation_sent_at\" do\n    context \"when using the 'default'format string\"\n    it \"returns the correctly formatted date\" do\n      user.update(invitation_sent_at: Time.new(2023, 5, 1, 12, 0, 0))\n      expected_date = I18n.l(user.invitation_sent_at, format: :full, default: nil)\n      expect(decorated_user.formatted_invitation_sent_at).to eq expected_date\n    end\n\n    context \"when passing in the custom :edit_profile format string\"\n    it \"returns the correctly formatted date\" do\n      user.update(invitation_sent_at: Time.new(2023, 5, 1, 12, 0, 0))\n      expected_date = I18n.l(user.invitation_sent_at, format: :edit_profile, default: nil)\n      decorated_user.context[:format] = :edit_profile\n      expect(decorated_user.formatted_invitation_sent_at).to eq expected_date\n    end\n  end\n\n  describe \"#formatted_birthday\" do\n    context \"when a user has no date of birth set\"\n    it \"returns a blank string\" do\n      user.update(date_of_birth: nil)\n      expect(decorated_user.formatted_birthday).to eq \"\"\n    end\n\n    context \"when a user has a valid date of birth\"\n    it \"returns the month and ordinal of their birthday\" do\n      user.update(date_of_birth: Date.new(1991, 7, 8))\n      expect(decorated_user.formatted_birthday).to eq \"July 8th\"\n    end\n  end\n\n  describe \"#formatted_date_of_birth\" do\n    context \"when a user has no date of birth set\"\n    it \"returns a blank string\" do\n      user.update(date_of_birth: nil)\n      expect(decorated_user.formatted_date_of_birth).to eq \"\"\n    end\n\n    context \"when a user has a valid date of birth\"\n    it \"returns the YYYY/MM/DD of their date of birth\" do\n      user.update(date_of_birth: Date.new(1991, 7, 8))\n      expect(decorated_user.formatted_date_of_birth).to eq \"1991/07/08\"\n    end\n  end\nend\n"
  },
  {
    "path": "spec/decorators/volunteer_decorator_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe VolunteerDecorator do\n  let(:organization) { create(:casa_org) }\n  let(:admin) { create(:casa_admin, casa_org: organization) }\n  let(:supervisor) { create(:supervisor, casa_org: organization) }\n  let(:volunteer) { create(:volunteer, casa_org: organization) }\n\n  describe \"CC reminder text\" do\n    context \"when user is admin\" do\n      it \"includes both supervisor and admin in prompt\" do\n        sign_in admin\n\n        expect(volunteer.decorate.cc_reminder_text).to include \"Supervisor\"\n        expect(volunteer.decorate.cc_reminder_text).to include \"Admin\"\n      end\n    end\n\n    context \"when user is supervisor\" do\n      it \"includes only supervisor in prompt\" do\n        sign_in supervisor\n\n        expect(volunteer.decorate.cc_reminder_text).to include \"Supervisor\"\n        expect(volunteer.decorate.cc_reminder_text).not_to include \"Admin\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/documents/templates/prince_george_report_template_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\nrequire \"zip\"\nrequire \"rexml/document\"\n\nRSpec.describe \"Prince George report template\" do\n  let(:template_path) { Rails.root.join(\"app/documents/templates/prince_george_report_template.docx\").to_s }\n  let(:w_ns) { \"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" }\n\n  def extract_document_xml(docx_path)\n    Zip::File.open(docx_path) do |zip|\n      entry = zip.find_entry(\"word/document.xml\")\n      REXML::Document.new(entry.get_input_stream.read)\n    end\n  end\n\n  def find_contacts_table(doc)\n    tables = REXML::XPath.match(doc, \"//w:tbl\", \"w\" => w_ns)\n    tables.find do |tbl|\n      text = \"\"\n      REXML::XPath.each(tbl, \".//w:t\", \"w\" => w_ns) { |t| text += t.text.to_s }\n      text.include?(\"Contact Dates\")\n    end\n  end\n\n  describe \"contacts table column widths\" do\n    it \"allocates more width to the Contact Dates column than Name or Title columns\" do\n      doc = extract_document_xml(template_path)\n      table = find_contacts_table(doc)\n      expect(table).not_to be_nil, \"Could not find contacts table in template\"\n\n      grid_cols = REXML::XPath.match(table, \".//w:tblGrid/w:gridCol\", \"w\" => w_ns)\n      expect(grid_cols.length).to eq(3)\n\n      widths = grid_cols.map { |col| col.attributes[\"w:w\"].to_i }\n      name_width, title_width, dates_width = widths\n\n      expect(dates_width).to be > name_width, \"Contact Dates column (#{dates_width}) should be wider than Name column (#{name_width})\"\n      expect(dates_width).to be > title_width, \"Contact Dates column (#{dates_width}) should be wider than Title column (#{title_width})\"\n      expect(dates_width).to be >= 5760, \"Contact Dates column should be at least 4 inches (5760 twips), got #{dates_width}\"\n    end\n\n    it \"preserves the total table width\" do\n      doc = extract_document_xml(template_path)\n      table = find_contacts_table(doc)\n      expect(table).not_to be_nil, \"Could not find contacts table in template\"\n\n      grid_cols = REXML::XPath.match(table, \".//w:tblGrid/w:gridCol\", \"w\" => w_ns)\n      total = grid_cols.sum { |col| col.attributes[\"w:w\"].to_i }\n\n      expect(total).to eq(9606), \"Total table width should be 9606 twips (original width), got #{total}\"\n    end\n  end\nend\n"
  },
  {
    "path": "spec/factories/additional_expenses.rb",
    "content": "FactoryBot.define do\n  factory :additional_expense do\n    other_expense_amount { 20 }\n    other_expenses_describe { \"description of expense\" }\n    case_contact\n  end\nend\n"
  },
  {
    "path": "spec/factories/addresses.rb",
    "content": "FactoryBot.define do\n  factory :address do\n    content { Faker::Address.full_address }\n    association :user\n  end\nend\n"
  },
  {
    "path": "spec/factories/all_casa_admins.rb",
    "content": "FactoryBot.define do\n  factory :all_casa_admin, class: \"AllCasaAdmin\" do\n    sequence(:email) { |n| \"email#{n}@example.com\" }\n    password { \"12345678\" }\n    password_confirmation { \"12345678\" }\n  end\nend\n"
  },
  {
    "path": "spec/factories/api_credential.rb",
    "content": "FactoryBot.define do\n  factory :api_credential do\n    association :user\n    api_token_digest { Digest::SHA256.hexdigest(SecureRandom.hex(18)) }\n    refresh_token_digest { Digest::SHA256.hexdigest(SecureRandom.hex(18)) }\n    token_expires_at { 1.hour.from_now }\n    refresh_token_expires_at { 1.day.from_now }\n  end\nend\n"
  },
  {
    "path": "spec/factories/banners.rb",
    "content": "FactoryBot.define do\n  factory :banner do\n    casa_org\n    association :user, factory: :supervisor\n    name { \"Volunteer Survey\" }\n    active { true }\n    content { \"Please fill out this survey\" }\n  end\nend\n"
  },
  {
    "path": "spec/factories/casa_admins.rb",
    "content": "FactoryBot.define do\n  factory :casa_admin, class: \"CasaAdmin\", parent: :user do\n    trait :with_casa_cases do\n      after(:create) do |user, _|\n        create_list(:case_assignment, 2, volunteer: user)\n      end\n    end\n\n    trait :with_case_contact do\n      after(:create) do |user, _|\n        create(:case_assignment, volunteer: user)\n        create(:case_contact, creator: user, casa_case: user.casa_cases.first, contact_made: true)\n      end\n    end\n\n    trait :with_case_contact_wants_driving_reimbursement do\n      after(:create) do |user, _|\n        create(:case_assignment, volunteer: user)\n        create(:case_contact, :wants_reimbursement, creator: user, casa_case: user.casa_cases.first, contact_made: true)\n      end\n    end\n\n    trait :inactive do\n      active { false }\n    end\n  end\nend\n"
  },
  {
    "path": "spec/factories/casa_case_contact_types.rb",
    "content": "FactoryBot.define do\n  factory :casa_case_contact_type do\n    contact_type\n    casa_case\n  end\nend\n"
  },
  {
    "path": "spec/factories/casa_case_emancipation_categories.rb",
    "content": "FactoryBot.define do\n  factory :casa_case_emancipation_category do\n    casa_case do\n      create(:casa_case)\n    end\n\n    emancipation_category do\n      create(:emancipation_category)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/factories/casa_case_emancipation_options.rb",
    "content": "FactoryBot.define do\n  factory :casa_case_emancipation_option do\n    casa_case do\n      create(:casa_case)\n    end\n\n    emancipation_option do\n      create(:emancipation_option)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/factories/casa_cases.rb",
    "content": "FactoryBot.define do\n  factory :casa_case do\n    sequence(:case_number) { |n| \"CINA-#{n}\" }\n    birth_month_year_youth { 16.years.ago }\n    casa_org { CasaOrg.first || create(:casa_org) }\n    court_report_status { :not_submitted }\n    case_court_orders { [] }\n\n    transient do\n      volunteers { [] }\n    end\n\n    after(:create) do |casa_case, evaluator|\n      Array.wrap(evaluator.volunteers).each do |volunteer|\n        create(:case_assignment, casa_case:, volunteer:)\n      end\n    end\n\n    trait :pre_transition do\n      birth_month_year_youth { 13.years.ago }\n    end\n\n    trait :with_one_case_assignment do\n      after(:create) do |casa_case, _|\n        casa_org = casa_case.casa_org\n        volunteer = create(:volunteer, casa_org: casa_org)\n        create(:case_assignment, casa_case: casa_case, volunteer: volunteer)\n      end\n    end\n\n    trait :with_case_assignments do\n      after(:create) do |casa_case, _|\n        casa_org = casa_case.casa_org\n        2.times.map do\n          volunteer = create(:volunteer, casa_org: casa_org)\n          create(:case_assignment, casa_case: casa_case, volunteer: volunteer)\n        end\n      end\n    end\n\n    trait :with_one_court_order do\n      after(:create) do |casa_case|\n        casa_case.case_court_orders << build(:case_court_order)\n        casa_case.save\n      end\n    end\n\n    trait :active do\n      active { true }\n    end\n\n    trait :inactive do\n      active { false }\n    end\n  end\n\n  trait :with_case_contacts do\n    after(:create) do |casa_case|\n      3.times do\n        create(:case_contact, casa_case_id: casa_case.id)\n      end\n    end\n  end\n\n  trait :with_casa_case_contact_types do\n    after(:create) do |casa_case, _|\n      casa_org = casa_case.casa_org\n      2.times.map do\n        contact_type_group = create(:contact_type_group, casa_org: casa_org)\n        contact_type = create(:contact_type, contact_type_group: contact_type_group)\n        create(:casa_case_contact_type, casa_case: casa_case, contact_type: contact_type)\n      end\n    end\n  end\n\n  trait :with_upcoming_court_date do\n    after(:create) do |casa_case|\n      create(:court_date, casa_case: casa_case, date: Date.tomorrow)\n    end\n  end\n\n  trait :with_past_court_date do\n    after(:create) do |casa_case|\n      create(:court_date, casa_case: casa_case, date: Date.yesterday)\n    end\n  end\n\n  trait :with_placement do\n    after(:create) do |casa_case|\n      create(:placement, casa_case: casa_case, placement_started_at: Date.tomorrow)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/factories/casa_orgs.rb",
    "content": "FactoryBot.define do\n  factory :casa_org do\n    sequence(:name) { |n| \"CASA Org #{n}\" }\n    sequence(:display_name) { |n| \"CASA Org #{n}\" }\n    address { \"123 Main St\" }\n    footer_links { [[\"www.example.com\", \"First Link\"], [\"www.foobar.com\", \"Second Link\"]] }\n    twilio_account_sid { \"articuno34\" }\n    twilio_api_key_secret { \"open sesame\" }\n    twilio_api_key_sid { \"Aladdin\" }\n    twilio_phone_number { \"+15555555555\" }\n\n    trait :with_logo do\n      logo { Rack::Test::UploadedFile.new(Rails.root.join(\"spec/fixtures/files/org_logo.jpeg\")) }\n    end\n\n    trait :all_reimbursements_enabled do\n      additional_expenses_enabled { true }\n      show_driving_reimbursement { true }\n    end\n\n    trait :with_placement_types do\n      transient { placement_names { [\"Reunification\", \"Adoption\", \"Foster Care\", \"Kinship\"] } }\n\n      after(:create) do |org, evaluator|\n        evaluator.placement_names.each do |name|\n          org.placement_types.create!(name: name)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/factories/case_assignments.rb",
    "content": "FactoryBot.define do\n  factory :case_assignment do\n    transient do\n      casa_org { CasaOrg.first || create(:casa_org) }\n      pre_transition { false }\n    end\n\n    active { true }\n    allow_reimbursement { true }\n\n    casa_case do\n      if pre_transition\n        create(:casa_case, :pre_transition, casa_org: @overrides[:volunteer].try(:casa_org) || casa_org)\n      else\n        create(:casa_case, casa_org: @overrides[:volunteer].try(:casa_org) || casa_org)\n      end\n    end\n\n    volunteer do\n      create(:volunteer, casa_org: @overrides[:casa_case].try(:casa_org) || casa_org)\n    end\n\n    trait :disallow_reimbursement do\n      allow_reimbursement { false }\n    end\n\n    trait :inactive do\n      active { false }\n    end\n  end\nend\n"
  },
  {
    "path": "spec/factories/case_contact_contact_type.rb",
    "content": "FactoryBot.define do\n  factory :case_contact_contact_type do\n    contact_type\n    case_contact\n  end\nend\n"
  },
  {
    "path": "spec/factories/case_contacts.rb",
    "content": "FactoryBot.define do\n  # NOTE: FactoryBot automatically creates traits for a model's enum attributes\n  # https://github.com/thoughtbot/factory_bot/blob/main/GETTING_STARTED.md#enum-traits\n  # For example, CaseContact status enum includes `active: \"active\"` state, so following already defined:\n  # trait :active do\n  #   status { \"active\" }\n  # end\n  # ALSO, we can use any trait within other traits:\n  # https://github.com/thoughtbot/factory_bot/blob/main/GETTING_STARTED.md#traits-within-traits\n  # So, rather than `status { \"active\" }` - use enum trait like so:\n  factory :case_contact do\n    active # use the `:active` enum trait\n    association :creator, factory: :user\n    casa_case\n\n    contact_types { [association(:contact_type)] }\n    duration_minutes { 60 }\n    occurred_at { Time.zone.today }\n    contact_made { false }\n    medium_type { CaseContact::CONTACT_MEDIUMS.first }\n    want_driving_reimbursement { false }\n    deleted_at { nil }\n    draft_case_ids { [casa_case&.id] }\n\n    trait :multi_line_note do\n      notes { \"line1\\nline2\\nline3\" }\n    end\n\n    trait :long_note do\n      notes { \"1234567890 \" * 11 } # longer than NOTES_CHARACTER_LIMIT\n    end\n\n    trait :miles_driven_no_reimbursement do\n      miles_driven { 20 }\n      want_driving_reimbursement { false }\n    end\n\n    trait :wants_reimbursement do\n      miles_driven { 456 }\n      want_driving_reimbursement { true }\n      volunteer_address { \"123 Contact Factory St\" }\n    end\n\n    trait :started_status do\n      started # enum trait\n\n      casa_case { nil }\n      contact_types { [] }\n      draft_case_ids { [] }\n      medium_type { nil }\n      occurred_at { nil }\n      duration_minutes { nil }\n      notes { nil }\n      miles_driven { 0 }\n    end\n\n    trait :details_status do\n      details # enum trait\n\n      casa_case { nil }\n      draft_case_ids { [1] }\n      notes { nil }\n      miles_driven { 0 }\n    end\n\n    trait :notes_status do\n      notes # enum trait\n\n      casa_case { nil }\n      draft_case_ids { [1] }\n      miles_driven { 0 }\n    end\n\n    trait :expenses_status do\n      expenses # enum trait\n\n      draft_case_ids { [1] }\n    end\n\n    after(:create) do |case_contact, evaluator|\n      if evaluator.metadata\n        case_contact.update_columns(metadata: evaluator.metadata)\n      elsif case_contact.status\n        case_contact.update_columns(metadata: {\"status\" => {case_contact.status => case_contact.created_at}})\n      end\n    end\n\n    trait :with_org_topics do\n      after(:create) do |case_contact, _|\n        return if case_contact.casa_case.nil?\n\n        casa_org = case_contact.casa_case.casa_org\n        casa_org.contact_topics.active.each do |contact_topic|\n          case_contact.contact_topic_answers << build(:contact_topic_answer, contact_topic: contact_topic)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/factories/case_court_orders.rb",
    "content": "FactoryBot.define do\n  factory :case_court_order do\n    casa_case\n    text { Faker::Lorem.paragraph(sentence_count: 5, supplemental: true, random_sentences_to_add: 20) }\n    implementation_status { [:unimplemented, :partially_implemented, :implemented].sample }\n  end\nend\n"
  },
  {
    "path": "spec/factories/case_court_report_context.rb",
    "content": "FactoryBot.define do\n  factory :case_court_report_context do\n    skip_create # This model has no presence in the database\n\n    transient do\n      casa_case { nil }\n      court_date { nil }\n      volunteer { nil }\n      case_court_orders { nil }\n      path_to_report { Rails.root.join(\"tmp/test_report.docx\").to_s }\n      path_to_template { Rails.root.join(\"app/documents/templates/default_report_template.docx\").to_s }\n      start_date { nil }\n      end_date { nil }\n      time_zone { nil }\n    end\n\n    initialize_with {\n      volunteer_for_context = volunteer.nil? ? create(:volunteer) : volunteer\n      casa_case_for_context = casa_case.nil? ? create(:casa_case) : casa_case\n\n      if volunteer_for_context && volunteer_for_context.casa_cases.where(id: casa_case_for_context.id).none?\n        volunteer_for_context.casa_cases << casa_case_for_context\n      end\n\n      new(\n        case_id: casa_case_for_context.id,\n        volunteer_id: volunteer_for_context.try(:id),\n        path_to_report: path_to_report,\n        path_to_template: path_to_template,\n        court_date: court_date,\n        case_court_orders: case_court_orders,\n        start_date: start_date,\n        end_date: end_date,\n        time_zone: time_zone\n      )\n    }\n  end\nend\n"
  },
  {
    "path": "spec/factories/case_group_memberships.rb",
    "content": "FactoryBot.define do\n  factory :case_group_membership do\n    case_group { create(:case_group) }\n    casa_case { create(:casa_case) }\n  end\nend\n"
  },
  {
    "path": "spec/factories/case_groups.rb",
    "content": "FactoryBot.define do\n  factory :case_group do\n    transient do\n      case_count { 1 }\n      casa_cases { nil }\n    end\n    casa_org { CasaOrg.first || create(:casa_org) }\n    sequence(:name) { |n| \"Family #{n}\" }\n\n    after(:build) do |case_group, evaluator|\n      casa_cases = if evaluator.casa_cases.present?\n        evaluator.casa_cases\n      elsif case_group.case_group_memberships.empty?\n        build_list(:casa_case, evaluator.case_count, casa_org: case_group.casa_org)\n      else\n        []\n      end\n      casa_cases.each do |casa_case|\n        case_group.case_group_memberships.build(casa_case: casa_case)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/factories/checklist_items.rb",
    "content": "FactoryBot.define do\n  factory :checklist_item do\n    description { \"checklist item description\" }\n    category { \"checklist item category\" }\n    mandatory { false }\n    association :hearing_type\n  end\nend\n"
  },
  {
    "path": "spec/factories/contact_topic_answers.rb",
    "content": "FactoryBot.define do\n  factory :contact_topic_answer do\n    case_contact\n    contact_topic\n    selected { false }\n    value { Faker::Lorem.paragraph_by_chars(number: 300) }\n  end\nend\n"
  },
  {
    "path": "spec/factories/contact_topics.rb",
    "content": "FactoryBot.define do\n  factory :contact_topic do\n    casa_org\n    active { true }\n    question { Faker::Lorem.sentence }\n    details { Faker::Lorem.paragraph_by_chars(number: 300) }\n  end\nend\n"
  },
  {
    "path": "spec/factories/contact_type_group.rb",
    "content": "FactoryBot.define do\n  factory :contact_type_group do\n    casa_org { CasaOrg.first || create(:casa_org) }\n    sequence(:name) { |n| \"Group #{n}\" }\n  end\nend\n"
  },
  {
    "path": "spec/factories/contact_types.rb",
    "content": "FactoryBot.define do\n  factory :contact_type do\n    contact_type_group\n    sequence(:name) { |n| \"Type #{n}\" }\n  end\nend\n"
  },
  {
    "path": "spec/factories/court_dates.rb",
    "content": "FactoryBot.define do\n  factory :court_date, class: \"CourtDate\" do\n    casa_case\n    date { 1.week.ago }\n\n    trait :with_court_details do\n      with_judge\n      with_hearing_type\n      with_court_order\n    end\n\n    trait(:with_judge) { judge }\n    trait(:with_hearing_type) { hearing_type }\n\n    trait :with_court_order do\n      after(:create) do |court_date|\n        court_date.case_court_orders << build(:case_court_order, casa_case: court_date.casa_case)\n        court_date.save\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/factories/custom_org_links.rb",
    "content": "FactoryBot.define do\n  factory :custom_org_link do\n    casa_org\n    text { \"Custom Link Text\" }\n    url { \"https://custom.link\" }\n    active { true }\n  end\nend\n"
  },
  {
    "path": "spec/factories/emancipation_categories.rb",
    "content": "FactoryBot.define do\n  factory :emancipation_category do\n    sequence(:name) { |n| \"Emancipation category #{n}\" }\n    mutually_exclusive { false }\n  end\nend\n"
  },
  {
    "path": "spec/factories/emancipation_options.rb",
    "content": "FactoryBot.define do\n  factory :emancipation_option do\n    emancipation_category { build(:emancipation_category) }\n    sequence(:name) { |n| \"Emancipation option #{n}\" }\n  end\nend\n"
  },
  {
    "path": "spec/factories/followups.rb",
    "content": "FactoryBot.define do\n  factory :followup do\n    association :creator, factory: :user\n    status { :requested }\n    case_contact\n\n    # TODO polymorph Simulating the dual-writing setup during polymorphic migration\n    # remove after migration completed\n    after(:build) do |followup, evaluator|\n      unless evaluator.instance_variable_defined?(:@without_dual_writing)\n        followup.followupable = followup.case_contact\n      end\n    end\n\n    trait :without_dual_writing do\n      after(:build) do |followup|\n        followup.followupable_id = nil\n        followup.followupable_type = nil\n      end\n    end\n\n    trait :with_note do\n      note { Faker::Lorem.paragraph }\n    end\n\n    trait :without_note do\n      note { \"\" }\n    end\n  end\nend\n"
  },
  {
    "path": "spec/factories/fund_requests.rb",
    "content": "FactoryBot.define do\n  factory :fund_request do\n    deadline { \"tuesday the 12th of May\" }\n    extra_information { \"extra_information\" }\n    impact { \"impact\" }\n    other_funding_source_sought { \"other_funding_source_sought\" }\n    payee_name { \"payee_name\" }\n    payment_amount { \"$123.45\" }\n    request_purpose { \"shoes\" }\n    requested_by_and_relationship { \"me, the CASA\" }\n    submitter_email { \"casa@example.cmo\" }\n    youth_name { \"The youth Name\" }\n  end\nend\n"
  },
  {
    "path": "spec/factories/healths.rb",
    "content": "FactoryBot.define do\n  factory :health do\n    latest_deploy_time { Time.now }\n    singleton_guard { 0 }\n  end\nend\n"
  },
  {
    "path": "spec/factories/hearing_types.rb",
    "content": "FactoryBot.define do\n  factory :hearing_type do\n    casa_org { CasaOrg.first || create(:casa_org) }\n    sequence(:name) { |n| \"Emergency Hearing #{n}\" }\n    active { true }\n  end\nend\n"
  },
  {
    "path": "spec/factories/judges.rb",
    "content": "FactoryBot.define do\n  factory :judge do\n    casa_org { CasaOrg.first || create(:casa_org) }\n    name { Faker::Name.name }\n    active { true }\n  end\nend\n"
  },
  {
    "path": "spec/factories/languages.rb",
    "content": "FactoryBot.define do\n  factory :language do\n    sequence(:name) { |n| \"Language #{n} - #{Faker::Nation.language}\" }\n    casa_org { CasaOrg.first || create(:casa_org) }\n  end\nend\n"
  },
  {
    "path": "spec/factories/learning_hour_topics.rb",
    "content": "FactoryBot.define do\n  factory :learning_hour_topic do\n    casa_org { CasaOrg.first || create(:casa_org) }\n    sequence(:name) { |n| \"Learning Hour Type #{n}\" }\n    position { 1 }\n  end\nend\n"
  },
  {
    "path": "spec/factories/learning_hour_types.rb",
    "content": "FactoryBot.define do\n  factory :learning_hour_type do\n    casa_org { CasaOrg.first || create(:casa_org) }\n    sequence(:name) { |n| \"Learning Hour Type #{n}\" }\n    active { true }\n    position { 1 }\n  end\nend\n"
  },
  {
    "path": "spec/factories/learning_hours.rb",
    "content": "FactoryBot.define do\n  factory :learning_hour do\n    user { User.first || create(:user) }\n    name { Faker::Book.title }\n    duration_minutes { 25 }\n    duration_hours { 1 }\n    occurred_at { 2.days.ago }\n    learning_hour_type { LearningHourType.first || create(:learning_hour_type) }\n  end\nend\n"
  },
  {
    "path": "spec/factories/login_activities.rb",
    "content": "FactoryBot.define do\n  factory :login_activity do\n    association :user\n    scope { \"user\" }\n    strategy { \"database_authenticatable\" }\n    identity { user.email }\n    success { true }\n    context { \"session\" }\n\n    ip { \"127.0.0.1\" }\n    user_agent { \"Mozilla/5.0 (Macintosh; Intel Mac OS X)\" }\n  end\nend\n"
  },
  {
    "path": "spec/factories/mileage_rates.rb",
    "content": "FactoryBot.define do\n  factory :mileage_rate do\n    casa_org\n    amount { \"9.99\" }\n    effective_date { \"2021-10-23\" }\n    is_active { true }\n  end\nend\n"
  },
  {
    "path": "spec/factories/notes.rb",
    "content": "FactoryBot.define do\n  factory :note do\n    association :notable, factory: :user\n    association :creator, factory: :user\n    content { \"I am a note\" }\n  end\nend\n"
  },
  {
    "path": "spec/factories/notifications.rb",
    "content": "FactoryBot.define do\n  factory :notification, class: \"Noticed::Notification\" do\n    association :recipient, factory: :volunteer\n    association :event, factory: :followup_notifier\n    recipient_type { \"User\" }\n    type { \"FollowupNotifier::Notification\" }\n\n    transient do\n      created_by { nil }\n      casa_case { nil }\n    end\n\n    before(:create) do |notification, eval|\n      notification.params[:created_by] = eval.created_by if eval.created_by.present?\n      notification.params[:casa_case] = eval.casa_case if eval.casa_case.present?\n    end\n\n    trait :followup_with_note do\n      association :event, factory: [:followup_notifier, :with_note]\n    end\n\n    trait :followup_without_note do\n      association :event, factory: [:followup_notifier, :without_note]\n    end\n\n    trait :followup_read do\n      association :event, factory: [:followup_notifier, :read]\n      read_at { DateTime.current }\n      seen_at { DateTime.current }\n    end\n\n    trait :emancipation_checklist_reminder do\n      association :event, factory: :emancipation_checklist_reminder_notifier\n      type { \"EmancipationChecklistReminderNotifier::Notification\" }\n    end\n\n    trait :youth_birthday do\n      association :event, factory: :youth_birthday_notifier\n      type { \"YouthBirthdayNotifier::Notification\" }\n    end\n\n    trait :reimbursement_complete do\n      association :event, factory: :reimbursement_complete_notifier\n      type { \"ReimbursementCompleteNotifier::Notification\" }\n    end\n  end\nend\n"
  },
  {
    "path": "spec/factories/notifiers.rb",
    "content": "FactoryBot.define do\n  factory :followup_notifier do\n    type { \"FollowupNotifier\" }\n\n    trait :with_note do\n      params do\n        {\n          followup: create(:followup, :with_note),\n          created_by: create(:user)\n        }\n      end\n    end\n\n    trait :without_note do\n      params do\n        {\n          followup: create(:followup, :without_note, case_contact_id: create(:case_contact).id)\n        }\n      end\n    end\n\n    trait :read do\n      params do\n        {\n          followup: create(:followup, :without_note, case_contact_id: create(:case_contact).id)\n        }\n      end\n    end\n  end\n\n  factory :emancipation_checklist_reminder_notifier do\n    type { \"EmancipationChecklistReminderNotifier\" }\n    params do\n      {\n        casa_case: create(:casa_case)\n      }\n    end\n  end\n\n  factory :youth_birthday_notifier do\n    type { \"YouthBirthdayNotifier\" }\n    params do\n      {\n        casa_case: create(:casa_case)\n      }\n    end\n  end\n\n  factory :reimbursement_complete_notifier do\n    type { \"ReimbursementCompleteNotifier\" }\n    params do\n      {\n        case_contact: create(:case_contact)\n      }\n    end\n  end\nend\n"
  },
  {
    "path": "spec/factories/other_duty.rb",
    "content": "FactoryBot.define do\n  factory :other_duty do\n    creator { association :user }\n    creator_type { \"\" }\n    occurred_at { Date.current }\n    duration_minutes { rand(99) }\n    notes { Faker::Lorem.paragraph(sentence_count: 5, supplemental: true, random_sentences_to_add: 20) }\n  end\nend\n"
  },
  {
    "path": "spec/factories/patch_note_groups.rb",
    "content": "FactoryBot.define do\n  factory :patch_note_group do\n    sequence :value do |n| # Factory with default value includes no users\n      n.to_s\n    end\n\n    trait :all_users do\n      value { \"CasaAdmin+Supervisor+Volunteer\" }\n    end\n\n    trait :only_supervisors_and_admins do\n      value { \"CasaAdmin+Supervisor\" }\n    end\n  end\nend\n"
  },
  {
    "path": "spec/factories/patch_note_types.rb",
    "content": "FactoryBot.define do\n  factory :patch_note_type do\n    sequence(:name) { |n| \"Patch Note Type #{n}\" }\n  end\nend\n"
  },
  {
    "path": "spec/factories/patch_notes.rb",
    "content": "FactoryBot.define do\n  factory :patch_note do\n    sequence :note do |n|\n      n.to_s\n    end\n\n    patch_note_type { create(:patch_note_type) }\n    patch_note_group { create(:patch_note_group) }\n  end\nend\n"
  },
  {
    "path": "spec/factories/placement_types.rb",
    "content": "FactoryBot.define do\n  factory :placement_type do\n    sequence(:name) { |n| \"Placement Type #{n}\" }\n    casa_org\n  end\nend\n"
  },
  {
    "path": "spec/factories/placements.rb",
    "content": "FactoryBot.define do\n  factory :placement do\n    association :creator, factory: :user\n    casa_case\n    placement_type\n    placement_started_at { DateTime.now }\n  end\nend\n"
  },
  {
    "path": "spec/factories/preference_sets.rb",
    "content": "FactoryBot.define do\n  factory :preference_set do\n    user\n    case_volunteer_columns { {} }\n    table_state { {} }\n  end\nend\n"
  },
  {
    "path": "spec/factories/sent_emails.rb",
    "content": "FactoryBot.define do\n  factory :sent_email do\n    association :user, factory: :user\n    casa_org { CasaOrg.first || create(:casa_org) }\n    mailer_type { \"Mailer Type\" }\n    category { \"Mail Action Category\" }\n    sent_address { user.email }\n  end\nend\n"
  },
  {
    "path": "spec/factories/sms_notification_events.rb",
    "content": "FactoryBot.define do\n  factory :sms_notification_event do\n    name { \"name\" }\n    user_type { \"user type\" }\n  end\nend\n"
  },
  {
    "path": "spec/factories/supervisor_volunteer.rb",
    "content": "FactoryBot.define do\n  factory :supervisor_volunteer do\n    supervisor { create(:supervisor) }\n    volunteer { create(:volunteer) }\n\n    transient do\n      casa_org { CasaOrg.first || create(:casa_org) }\n    end\n\n    trait :inactive do\n      is_active { false }\n    end\n  end\nend\n"
  },
  {
    "path": "spec/factories/supervisors.rb",
    "content": "FactoryBot.define do\n  factory :supervisor, class: \"Supervisor\", parent: :user do\n    display_name { Faker::Name.unique.name }\n    active { true }\n\n    transient do\n      volunteers { [] }\n    end\n\n    after(:create) do |supervisor, evaluator|\n      Array.wrap(evaluator.volunteers).each do |volunteer|\n        create(:supervisor_volunteer, supervisor:, volunteer:)\n      end\n    end\n\n    trait :with_casa_cases do\n      after(:create) do |user, _|\n        volunteer = create(:volunteer)\n        create_list(:case_assignment, 2, volunteer: volunteer)\n      end\n    end\n\n    trait :with_case_contact do\n      after(:create) do |user, _|\n        create(:case_assignment, volunteer: user)\n        create(:case_contact, creator: user, casa_case: user.casa_cases.first, contact_made: true)\n      end\n    end\n\n    trait :with_case_contact_wants_driving_reimbursement do\n      after(:create) do |user, _|\n        create(:case_assignment, volunteer: user)\n        create(:case_contact, :wants_reimbursement, creator: user, casa_case: user.casa_cases.first, contact_made: true)\n      end\n    end\n\n    trait :with_volunteers do\n      after(:create) do |user, _|\n        create_list(:supervisor_volunteer, 2, supervisor: user)\n      end\n    end\n\n    trait :inactive do\n      active { false }\n    end\n\n    trait :receive_reimbursement_attachment do\n      receive_reimbursement_email { true }\n    end\n  end\nend\n"
  },
  {
    "path": "spec/factories/user_languages.rb",
    "content": "FactoryBot.define do\n  factory :user_language do\n    user\n    language\n  end\nend\n"
  },
  {
    "path": "spec/factories/user_reminder_time.rb",
    "content": "FactoryBot.define do\n  factory :user_reminder_time do\n    user { Volunteer.first }\n  end\n\n  trait :case_contact_types do\n    case_contact_types { DateTime.now }\n  end\n\n  trait :quarterly_reminder do\n    case_contact_types { DateTime.now }\n  end\nend\n"
  },
  {
    "path": "spec/factories/user_sms_notification_events.rb",
    "content": "FactoryBot.define do\n  factory :user_sms_notification_event do\n    user\n    sms_notification_event\n  end\nend\n"
  },
  {
    "path": "spec/factories/users.rb",
    "content": "FactoryBot.define do\n  factory :user do\n    casa_org { CasaOrg.first || create(:casa_org) }\n    sequence(:email) { |n| \"email#{n}@example.com\" }\n    sequence(:display_name) { |n| \"User #{n}\" }\n    password { \"12345678\" }\n    password_confirmation { \"12345678\" }\n    date_of_birth { nil }\n    case_assignments { [] }\n    phone_number { \"\" }\n    confirmed_at { Time.now }\n\n    after(:create) do |user|\n      create(:api_credential, user: user)\n    end\n\n    trait :inactive do\n      type { \"Volunteer\" }\n      active { false }\n      role { :inactive }\n    end\n\n    trait :with_casa_cases do\n      after(:create) do |user, _|\n        create_list(:case_assignment, 2, volunteer: user)\n      end\n    end\n\n    trait :with_single_case do\n      after(:create) do |user, _|\n        create_list(:case_assignment, 1, volunteer: user)\n      end\n    end\n\n    trait :with_case_contact do\n      after(:create) do |user, _|\n        create(:case_assignment, volunteer: user)\n        create(:case_contact, creator: user, casa_case: user.casa_cases.first, contact_made: true)\n      end\n    end\n\n    trait :with_case_contact_wants_driving_reimbursement do\n      after(:create) do |user, _|\n        create(:case_assignment, volunteer: user)\n        create(:case_contact, :wants_reimbursement, creator: user, casa_case: user.casa_cases.first, contact_made: true)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/factories/volunteers.rb",
    "content": "FactoryBot.define do\n  factory :volunteer, class: \"Volunteer\", parent: :user do\n    trait :inactive do\n      active { false }\n    end\n\n    trait :with_casa_cases do\n      after(:create) do |user, _|\n        create(:case_assignment, casa_case: create(:casa_case, casa_org: user.casa_org), volunteer: user)\n        create(:case_assignment, casa_case: create(:casa_case, casa_org: user.casa_org), volunteer: user)\n      end\n    end\n\n    trait :with_pretransition_age_case do\n      after(:create) do |user, _|\n        create(:case_assignment, casa_case: create(:casa_case, :pre_transition, casa_org: user.casa_org), volunteer: user)\n      end\n    end\n\n    trait :with_cases_and_contacts do\n      after(:create) do |user, _|\n        assignment1 = create :case_assignment, casa_case: create(:casa_case, :pre_transition, casa_org: user.casa_org), volunteer: user\n        create :case_assignment, casa_case: create(:casa_case, casa_org: user.casa_org, birth_month_year_youth: 10.years.ago), volunteer: user\n        create :case_assignment, casa_case: create(:casa_case, casa_org: user.casa_org, birth_month_year_youth: 15.years.ago), volunteer: user\n        contact = create :case_contact, creator: user, casa_case: assignment1.casa_case\n        contact_types = create_list :contact_type, 3, contact_type_group: create(:contact_type_group, casa_org: user.casa_org)\n        3.times do\n          CaseContactContactType.create(case_contact: contact, contact_type: contact_types.pop)\n        end\n      end\n    end\n\n    trait :with_assigned_supervisor do\n      transient { supervisor { nil } }\n\n      after(:create) do |volunteer, evaluator|\n        supervisor = evaluator.supervisor || create(:supervisor, casa_org: volunteer.casa_org)\n        create(:supervisor_volunteer, volunteer:, supervisor:)\n      end\n    end\n\n    trait :with_inactive_supervisor do\n      transient { supervisor { create(:supervisor) } }\n\n      after(:create) do |user, evaluator|\n        create(:supervisor_volunteer, :inactive, volunteer: user, supervisor: evaluator.supervisor)\n      end\n    end\n\n    trait :with_disallow_reimbursement do\n      after(:create) do |user, _|\n        create(:case_assignment, :disallow_reimbursement, casa_case: create(:casa_case, casa_org: user.casa_org), volunteer: user)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/fixtures/files/casa_cases.csv",
    "content": "﻿case_number,case_assignment,birth_month_year_youth,next_court_date\nCINA-01-4347,volunteer1@example.net, March 2011,Sept 16 2022\nCINA-01-4348,\"volunteer2@example.net, volunteer3@example.net\",February 2000,Jan 1 2023\nCINA-01-4349,,December 2016,\n"
  },
  {
    "path": "spec/fixtures/files/casa_cases_without_case_number.csv",
    "content": "case_number,case_assignment,birth_month_year_youth\n,volunteer1@example.net,\nCINA-01-4348,\"volunteer2@example.net, volunteer3@example.net\",February 2000\n"
  },
  {
    "path": "spec/fixtures/files/existing_casa_case.csv",
    "content": "case_number,case_assignment,birth_month_year_youth,next_court_date\nCINA-00-0000,volunteer1@example.net,\n"
  },
  {
    "path": "spec/fixtures/files/generic.csv",
    "content": "Name,Age\nAllie,30\nBob,40"
  },
  {
    "path": "spec/fixtures/files/no_rows.csv",
    "content": "Name,Age"
  },
  {
    "path": "spec/fixtures/files/supervisor_volunteers.csv",
    "content": "﻿email,display_name,supervisor_volunteers,phone_number\ns5@example.com,s5,volunteer1@example.net,11111111111\ns6@example.com,s6,volunteer1@example.net,11111111111\n"
  },
  {
    "path": "spec/fixtures/files/supervisors.csv",
    "content": "﻿email,display_name,supervisor_volunteers,phone_number\nsupervisor1@example.net,Supervisor One,volunteer1@example.net,11111111111\nsupervisor2@example.net,Supervisor Two,\"volunteer2@example.net, volunteer3@example.net\",11111111111\nsupervisor3@example.net,Supervisor Three,,11111111111\n"
  },
  {
    "path": "spec/fixtures/files/supervisors_invalid_phone_numbers.csv",
    "content": "email,display_name,supervisor_volunteers,phone_number\nsupervisor1@example.net,Supervisor One,volunteer1@example.net,12345678\nsupervisor2@example.net,Supervisor Two,\"volunteer2@example.net, volunteer3@example.net\",+++++++++++\nsupervisor3@example.net,Supervisor Three,,111111111111111111\nsupervisor4@example.net,Supervisor Four,,1.111.111.11112"
  },
  {
    "path": "spec/fixtures/files/supervisors_without_display_names.csv",
    "content": "email,display_name,supervisor_volunteers,phone_number\nsupervisor1@example.net,,volunteer1@example.net,11111111111\nsupervisor2@example.net,,\"volunteer2@example.net, volunteer3@example.net\",22222222222\nsupervisor3@example.net,,,33333333333\n"
  },
  {
    "path": "spec/fixtures/files/supervisors_without_email.csv",
    "content": "﻿email,display_name,supervisor_volunteers,phone_number\nsupervisor1@example.net,Supervisor One,volunteer1@example.net,11111111111\n,Supervisor Two,\"volunteer2@example.net, volunteer3@example.net\",11111111111\n"
  },
  {
    "path": "spec/fixtures/files/supervisors_without_phone_numbers.csv",
    "content": "email,display_name,supervisor_volunteers,phone_number\nsupervisor1@example.net,Supervisor One,volunteer1@example.net,\nsupervisor2@example.net,Supervisor Two,\"volunteer2@example.net, volunteer3@example.net\",\nsupervisor3@example.net,Supervisor Three,,"
  },
  {
    "path": "spec/fixtures/files/volunteers.csv",
    "content": "﻿display_name,email,phone_number\nVolunteer One,volunteer1@example.net,11234567890\nVolunteer Two,volunteer2@example.net,11234567891\nVolunteer Three,volunteer3@example.net,11234567892\n"
  },
  {
    "path": "spec/fixtures/files/volunteers_invalid_phone_numbers.csv",
    "content": "display_name,email,phone_number\nVolunteer One,volunteer1@example.net,22222222222\nVolunteer Two,volunteer2@example.net,1bc12345678\nVolunteer Three,volunteer3@example.net,111111111"
  },
  {
    "path": "spec/fixtures/files/volunteers_without_display_names.csv",
    "content": "display_name,email,phone_number\n,volunteer1@example.net,111111111\n,volunteer2@example.net,22222222222\n,volunteer3@example.net,33333333333\n"
  },
  {
    "path": "spec/fixtures/files/volunteers_without_email.csv",
    "content": "﻿display_name,email,phone_number\nVolunteer One,volunteer1@example.net,11111111111\nVolunteer Two,11111111111\nVolunteer Three,,11111111111\n"
  },
  {
    "path": "spec/fixtures/files/volunteers_without_phone_numbers.csv",
    "content": "display_name,email,phone_number\nVolunteer One,volunteer1@example.net,\nVolunteer Two,volunteer2@example.net,"
  },
  {
    "path": "spec/helpers/all_casa_admins/casa_orgs_helper_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe AllCasaAdmins::CasaOrgsHelper, type: :helper do\n  # TODO: Add tests for AllCasaAdmins::CasaOrgsHelper\n\n  pending \"add some tests for AllCasaAdmins::CasaOrgsHelper\"\nend\n"
  },
  {
    "path": "spec/helpers/api_base_helper_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe ApiBaseHelper, type: :helper do\n  # TODO: Add tests for ApiBaseHelper\n\n  pending \"add some tests for ApiBaseHelper\"\nend\n"
  },
  {
    "path": "spec/helpers/application_helper_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe ApplicationHelper, type: :helper do\n  describe \"#page_header\" do\n    it \"displays the header when user is logged in\" do\n      current_organization = build_stubbed(:casa_org)\n      user = build_stubbed(:user, casa_org: current_organization)\n\n      allow(helper).to receive(:user_signed_in?).and_return(true)\n      allow(helper).to receive(:current_user).and_return(user)\n      allow(helper).to receive(:current_organization).and_return(current_organization)\n\n      expect(helper.page_header).to eq(current_organization.display_name)\n    end\n\n    it \"displays the header when user is not logged in\" do\n      allow(helper).to receive(:user_signed_in?).and_return(false)\n\n      expect(helper.page_header).to eq(helper.default_page_header)\n    end\n  end\n\n  describe \"#session_link\" do\n    it \"links to the sign_out page when user is signed in\" do\n      allow(helper).to receive(:user_signed_in?).and_return(true)\n\n      expect(helper.session_link).to match(destroy_user_session_path)\n    end\n\n    it \"links to the sign_out page when all_casa_admin is signed in\" do\n      allow(helper).to receive(:user_signed_in?).and_return(false)\n      allow(helper).to receive(:all_casa_admin_signed_in?).and_return(true)\n\n      expect(helper.session_link).to match(destroy_all_casa_admin_session_path)\n    end\n\n    it \"links to the sign_in page when user is not signed in\" do\n      allow(helper).to receive(:user_signed_in?).and_return(false)\n      allow(helper).to receive(:all_casa_admin_signed_in?).and_return(false)\n      expect(helper.session_link).to match(new_user_session_path)\n    end\n  end\n\n  describe \"#og_tag\" do\n    subject { helper.og_tag(:title, content: \"Website Title\") }\n\n    it { is_expected.to eql('<meta property=\"og:title\" content=\"Website Title\">') }\n  end\nend\n"
  },
  {
    "path": "spec/helpers/banner_helper_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe BannerHelper, type: :helper do\n  describe \"#conditionally_add_hidden_class\" do\n    it \"returns d-none if current banner is inactive\" do\n      current_organization = double\n      allow(helper).to receive(:current_organization).and_return(current_organization)\n      banner = double(id: 1)\n      assign(:banner, banner)\n\n      allow(current_organization).to receive(:has_alternate_active_banner?).and_return(true)\n\n      expect(helper.conditionally_add_hidden_class(false)).to eq(\"d-none\")\n    end\n\n    it \"returns d-none if current banner is active and org does not have an alternate active banner\" do\n      current_organization = double\n      allow(helper).to receive(:current_organization).and_return(current_organization)\n      banner = double(id: 1)\n      assign(:banner, banner)\n\n      allow(current_organization).to receive(:has_alternate_active_banner?).and_return(false)\n\n      expect(helper.conditionally_add_hidden_class(true)).to eq(\"d-none\")\n    end\n\n    it \"returns nil if current banner is active and org has an alternate active banner\" do\n      current_organization = double\n      allow(helper).to receive(:current_organization).and_return(current_organization)\n      banner = double(id: 1)\n      assign(:banner, banner)\n\n      allow(current_organization).to receive(:has_alternate_active_banner?).and_return(true)\n\n      expect(helper.conditionally_add_hidden_class(true)).to eq(nil)\n    end\n  end\n\n  describe \"#banner_expiration_time_in_words\" do\n    let(:banner) { create(:banner, expires_at: expires_at) }\n\n    context \"when expires_at isn't set\" do\n      let(:expires_at) { nil }\n\n      it \"returns No Expiration\" do\n        expect(helper.banner_expiration_time_in_words(banner)).to eq(\"No Expiration\")\n      end\n    end\n\n    context \"when expires_at is in the future\" do\n      let(:expires_at) { 7.days.from_now }\n\n      it \"returns a word description of how far in the future\" do\n        expect(helper.banner_expiration_time_in_words(banner)).to eq(\"in 7 days\")\n      end\n    end\n\n    context \"when expires_at is in the past\" do\n      let(:expired_banner) do\n        banner = create(:banner, expires_at: nil)\n        banner.update_columns(expires_at: 2.days.ago)\n        banner\n      end\n\n      it \"returns Expired\" do\n        expect(helper.banner_expiration_time_in_words(expired_banner)).to eq(\"Expired\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/helpers/case_contacts_helper_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CaseContactsHelper, type: :helper do\n  describe \"#render_back_link\" do\n    it \"renders back link to home page when user is a volunteer\" do\n      current_user = create(:volunteer)\n      casa_case = create(:casa_case)\n      allow(helper).to receive(:current_user).and_return(current_user)\n\n      expect(helper.render_back_link(casa_case)).to eq(root_path)\n    end\n\n    it \"renders back link to home page when user does not exist\" do\n      casa_case = create(:casa_case)\n      allow(helper).to receive(:current_user).and_return(nil)\n\n      expect(helper.render_back_link(casa_case)).to eq(root_path)\n    end\n\n    it \"renders back link to home page when user is a supervisor\" do\n      current_user = create(:supervisor)\n      casa_case = create(:casa_case)\n      allow(helper).to receive(:current_user).and_return(current_user)\n\n      expect(helper.render_back_link(casa_case)).to eq(casa_case_path(casa_case))\n    end\n\n    it \"renders back link to home page when user is a administrator\" do\n      current_user = create(:casa_admin)\n      casa_case = create(:casa_case)\n      allow(helper).to receive(:current_user).and_return(current_user)\n\n      expect(helper.render_back_link(casa_case)).to eq(casa_case_path(casa_case))\n    end\n  end\n\n  describe \"#duration_minutes\" do\n    it \"returns remainder if duration_minutes is set\" do\n      case_contact = build(:case_contact, duration_minutes: 80)\n      expect(helper.duration_minutes(case_contact)).to eq(20)\n    end\n\n    it \"returns zero if duration_minutes is zero\" do\n      case_contact = build(:case_contact, duration_minutes: 0)\n      expect(helper.duration_minutes(case_contact)).to eq(0)\n    end\n\n    it \"returns zero if duration_minutes is nil\" do\n      case_contact = build(:case_contact, duration_minutes: nil)\n      expect(helper.duration_minutes(case_contact)).to eq(0)\n    end\n  end\n\n  describe \"#duration_hours\" do\n    it \"returns minutes if duration_minutes is set\" do\n      case_contact = build(:case_contact, duration_minutes: 80)\n      expect(helper.duration_hours(case_contact)).to eq(1)\n    end\n\n    it \"returns zero if duration_minutes is zero\" do\n      case_contact = build(:case_contact, duration_minutes: 0)\n      expect(helper.duration_hours(case_contact)).to eq(0)\n    end\n\n    it \"returns zero if duration_minutes is nil\" do\n      case_contact = build(:case_contact, duration_minutes: nil)\n      expect(helper.duration_hours(case_contact)).to eq(0)\n    end\n  end\n\n  describe \"#show_volunteer_reimbursement\" do\n    before do\n      @casa_cases = []\n      @casa_cases << create(:casa_case)\n      @casa_org = @casa_cases[0].casa_org\n      @current_user = create(:volunteer, casa_org: @casa_org)\n    end\n\n    it \"returns true if allow_reimbursement is true\" do\n      create(:case_assignment, casa_case: @casa_cases[0], volunteer: @current_user)\n      allow(helper).to receive(:current_user).and_return(@current_user)\n      expect(helper.show_volunteer_reimbursement(@casa_cases)).to eq(true)\n    end\n\n    it \"returns false if allow_reimbursement is false\" do\n      create(:case_assignment, :disallow_reimbursement, casa_case: @casa_cases[0], volunteer: @current_user)\n      allow(helper).to receive(:current_user).and_return(@current_user)\n      expect(helper.show_volunteer_reimbursement(@casa_cases)).to eq(false)\n    end\n\n    it \"returns false if no case_assigmnents are found\" do\n      allow(helper).to receive(:current_user).and_return(@current_user)\n      expect(helper.show_volunteer_reimbursement(@casa_cases)).to eq(false)\n    end\n  end\n\n  describe \"#expand_filters?\" do\n    it \"returns false if filterrific param does not exist\" do\n      allow(helper).to receive(:params)\n        .and_return({})\n\n      expect(helper.expand_filters?).to eq(false)\n    end\n\n    it \"returns false if filterrific contains only surfaced params\" do\n      allow(helper).to receive(:params)\n        .and_return({filterrific: {surfaced_param: \"true\"}})\n\n      expect(helper.expand_filters?([:surfaced_param])).to eq(false)\n    end\n\n    it \"returns true if filterrific contains any other key\" do\n      allow(helper).to receive(:params)\n        .and_return({filterrific: {surfaced_param: \"true\", other_key: \"value\"}})\n\n      expect(helper.expand_filters?([:surfaced_param])).to eq(true)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/helpers/contact_types_helper_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe ContactTypesHelper do\nend\n"
  },
  {
    "path": "spec/helpers/court_dates_helper_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CourtDatesHelper, type: :helper do\n  describe \"#when_do_we_have_court_dates\" do\n    subject { helper.when_do_we_have_court_dates(casa_case) }\n\n    describe \"when casa case has no court dates\" do\n      let(:casa_case) { create(:casa_case) }\n\n      it { expect(subject).to eq(\"none\") }\n    end\n\n    describe \"when casa case has only dates in the past\" do\n      let(:casa_case) { create(:casa_case, :with_past_court_date) }\n\n      it { expect(subject).to eq(\"past\") }\n    end\n\n    describe \"when casa case only has dates in the future\" do\n      let(:casa_case) { create(:casa_case, :with_upcoming_court_date) }\n\n      it { expect(subject).to eq(\"future\") }\n    end\n\n    describe \"when casa case has dates both in the past and future\" do\n      let(:casa_case) { create(:casa_case, :with_upcoming_court_date, :with_past_court_date) }\n\n      it { expect(subject).to be_nil }\n    end\n  end\nend\n"
  },
  {
    "path": "spec/helpers/court_orders_helper_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CourtDatesHelper, type: :helper do\n  describe \"#court_order_select_options\" do\n    context \"when no court orders\" do\n      it \"empty\" do\n        expect(helper.court_order_select_options).to eq([[\"Unimplemented\", \"unimplemented\"], [\"Partially implemented\", \"partially_implemented\"], [\"Implemented\", \"implemented\"]])\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/helpers/date_helper_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe DateHelper, type: :helper do\n  # TODO: Add tests for DateHelper\n\n  pending \"add some tests for DateHelper\"\nend\n"
  },
  {
    "path": "spec/helpers/emancipations_helper_spec.rb",
    "content": "require \"rails_helper\"\n\n# Specs in this file have access to a helper object that includes\n# the EmancipationsHelper. For example:\n#\n# describe EmancipationsHelper do\n#   describe \"string concat\" do\n#     it \"concats two strings with spaces\" do\n#       expect(helper.concat_strings(\"this\",\"that\")).to eq(\"this that\")\n#     end\n#   end\n# end\nRSpec.describe EmancipationsHelper, type: :helper do\n  let(:casa_case) { create(:casa_case) }\n\n  describe \"#emancipation_category_checkbox_checked\" do\n    let(:emancipation_category) { create(:emancipation_category, name: \"unique name\") }\n\n    it \"returns \\\"checked\\\" when passed an associated casa case and emancipation category\" do\n      create(:casa_case_emancipation_category, casa_case_id: casa_case.id, emancipation_category_id: emancipation_category.id)\n      expect(helper.emancipation_category_checkbox_checked(casa_case, emancipation_category)).to eq(\"checked\")\n    end\n\n    it \"returns nil when passed an unassociated casa case and emancipation category\" do\n      expect(helper.emancipation_category_checkbox_checked(casa_case, emancipation_category)).to eq(nil)\n    end\n  end\n\n  describe \"#emancipation_category_collapse_hidden\" do\n    let(:emancipation_category) { create(:emancipation_category, name: \"another unique name\") }\n\n    it \"returns nil when passed an associated casa case and emancipation category\" do\n      create(:casa_case_emancipation_category, casa_case_id: casa_case.id, emancipation_category_id: emancipation_category.id)\n      expect(helper.emancipation_category_collapse_hidden(casa_case, emancipation_category)).to eq(nil)\n    end\n\n    it \"returns \\\"display: none;\\\" when passed an unassociated casa case and emancipation category\" do\n      expect(helper.emancipation_category_collapse_hidden(casa_case, emancipation_category)).to eq(\"display: none;\")\n    end\n  end\n\n  describe \"#emancipation_category_collapse_icon\" do\n    let(:emancipation_category) { create(:emancipation_category, name: \"another unique name\") }\n\n    it \"returns nil when passed an associated casa case and emancipation category\" do\n      create(:casa_case_emancipation_category, casa_case_id: casa_case.id, emancipation_category_id: emancipation_category.id)\n      expect(helper.emancipation_category_collapse_icon(casa_case, emancipation_category)).to eq(\"−\")\n    end\n\n    it \"returns \\\"display: none;\\\" when passed an unassociated casa case and emancipation category\" do\n      expect(helper.emancipation_category_collapse_icon(casa_case, emancipation_category)).to eq(\"+\")\n    end\n  end\n\n  describe \"#emancipation_option_checkbox_checked\" do\n    let(:emancipation_option) { create(:emancipation_option) }\n\n    it \"returns \\\"checked\\\" when passed an associated casa case and emancipation option\" do\n      create(:casa_case_emancipation_option, casa_case_id: casa_case.id, emancipation_option_id: emancipation_option.id)\n      expect(helper.emancipation_option_checkbox_checked(casa_case, emancipation_option)).to eq(\"checked\")\n    end\n\n    it \"returns nil when passed an unassociated casa case and emancipation option id\" do\n      expect(helper.emancipation_option_checkbox_checked(casa_case, emancipation_option)).to eq(nil)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/helpers/followup_helper_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe FollowupHelper, type: :helper do\n  describe \"#followup_icon\" do\n    context \"volunteer created followup\" do\n      it \"is orange circle with an exclamation point\" do\n        creator = build_stubbed(:volunteer)\n        expect(helper.followup_icon(creator)).to include(\"exclamation-circle\")\n      end\n    end\n\n    context \"admin created followup\" do\n      it \"is orange circle with an exclamation point\" do\n        creator = build_stubbed(:casa_admin)\n        expect(helper.followup_icon(creator)).to include(\"exclamation-triangle\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/helpers/learning_hours_helper_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe LearningHoursHelper, type: :helper do\n  describe \"#format_time\" do\n    it \"formats time correctly for positive values\" do\n      expect(helper.format_time(120)).to eq(\"2 hours 0 minutes\")\n      expect(helper.format_time(90)).to eq(\"1 hours 30 minutes\")\n      expect(helper.format_time(75)).to eq(\"1 hours 15 minutes\")\n    end\n\n    it \"formats time correctly for zero minutes\" do\n      expect(helper.format_time(0)).to eq(\"0 hours 0 minutes\")\n    end\n\n    it \"formats time correctly for large values\" do\n      expect(helper.format_time(360)).to eq(\"6 hours 0 minutes\")\n      expect(helper.format_time(1800)).to eq(\"30 hours 0 minutes\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/helpers/mileage_rates_helper_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe MileageRatesHelper, type: :helper do\n  describe \"#effective_date_parser\" do\n    context \"with date\" do\n      let(:date) { DateTime.parse(\"01-01-2021\") }\n\n      it \"returns date formated\" do\n        expect(helper.effective_date_parser(date)).to eq \"January 01, 2021\"\n      end\n    end\n  end\n\n  context \"without date\" do\n    let(:date) { nil }\n\n    it \"returns current date formated\" do\n      expect(helper.effective_date_parser(date)).to eq DateTime.current.strftime(::DateHelper::RUBY_MONTH_DAY_YEAR_FORMAT)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/helpers/notifications_helper_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe NotificationsHelper, type: :helper do\n  context \"notifications with respect to deploy time\" do\n    let(:notification_created_after_deploy_a) { create(:notification) }\n    let(:notification_created_after_deploy_b) { create(:notification, created_at: 1.day.ago) }\n    let(:notification_created_at_deploy) { create(:notification, created_at: 2.days.ago) }\n    let(:notification_created_before_deploy_a) { create(:notification, created_at: 2.days.ago - 1.hour) }\n    let(:notification_created_before_deploy_b) { create(:notification, created_at: 3.days.ago) }\n\n    before do\n      travel_to Time.new(2022, 1, 1, 0, 0, 0)\n\n      notification_created_after_deploy_a.update_attribute(:created_at, 1.hour.ago)\n      notification_created_after_deploy_b.update_attribute(:created_at, 1.day.ago)\n\n      Health.instance.update_attribute(:latest_deploy_time, 2.days.ago)\n      notification_created_at_deploy.update_attribute(:created_at, 2.days.ago)\n\n      notification_created_before_deploy_a.update_attribute(:created_at, 2.days.ago - 1.hour)\n      notification_created_before_deploy_b.update_attribute(:created_at, 3.days.ago)\n    end\n\n    describe \"#notifications_after_and_including_deploy\" do\n      let(:notifications_after_and_including_deploy) { helper.notifications_after_and_including_deploy(Noticed::Notification.all) }\n\n      it \"returns all notifications from the given list after and including deploy time\" do\n        expect(notifications_after_and_including_deploy).to include(notification_created_after_deploy_a)\n        expect(notifications_after_and_including_deploy).to include(notification_created_after_deploy_b)\n        expect(notifications_after_and_including_deploy).to include(notification_created_at_deploy)\n      end\n\n      it \"does not contain notifications before the deploy time\" do\n        expect(notifications_after_and_including_deploy).not_to include(notification_created_before_deploy_a)\n        expect(notifications_after_and_including_deploy).not_to include(notification_created_before_deploy_b)\n      end\n    end\n\n    describe \"#notifications_before_deploy\" do\n      let(:notifications_before_deploy) { helper.notifications_before_deploy(Noticed::Notification.all) }\n\n      it \"returns all notifications from the given list before deploy time\" do\n        expect(notifications_before_deploy).to include(notification_created_before_deploy_a)\n        expect(notifications_before_deploy).to include(notification_created_before_deploy_b)\n      end\n\n      it \"does not contain notifications after and including the deploy time\" do\n        expect(notifications_before_deploy).not_to include(notification_created_after_deploy_a)\n        expect(notifications_before_deploy).not_to include(notification_created_after_deploy_b)\n        expect(notifications_before_deploy).not_to include(notification_created_at_deploy)\n      end\n    end\n  end\n\n  describe \"#patch_notes_as_hash_keyed_by_type_name\" do\n    it \"returns a hash where the keys are the names of the patch note type and the values are lists of patch note strings belonging to the type\" do\n      patch_note_type_a = create(:patch_note_type, name: \"patch_note_type_a\")\n      patch_note_type_b = create(:patch_note_type, name: \"patch_note_type_b\")\n      patch_note_1 = create(:patch_note, note: \"Patch Note 1\", patch_note_type: patch_note_type_a)\n      patch_note_2 = create(:patch_note, note: \"Patch Note 2\", patch_note_type: patch_note_type_b)\n      patch_note_3 = create(:patch_note, note: \"Patch Note 3\", patch_note_type: patch_note_type_b)\n\n      patch_notes_hash = helper.patch_notes_as_hash_keyed_by_type_name(PatchNote.all)\n\n      expect(patch_notes_hash).to have_key(patch_note_type_a.name)\n      expect(patch_notes_hash).to have_key(patch_note_type_b.name)\n      expect(patch_notes_hash[patch_note_type_a.name]).to contain_exactly(patch_note_1.note)\n      expect(patch_notes_hash[patch_note_type_b.name]).to contain_exactly(patch_note_2.note, patch_note_3.note)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/helpers/other_duties_helper_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe OtherDutiesHelper, type: :helper do\n  describe \"#duration_minutes\" do\n    it \"returns remainder if duration_minutes is set\" do\n      other_duty = build(:other_duty, duration_minutes: 80)\n      expect(helper.duration_minutes(other_duty)).to eq(20)\n    end\n\n    it \"returns zero if duration_minutes is zero\" do\n      other_duty = build(:other_duty, duration_minutes: 0)\n      expect(helper.duration_minutes(other_duty)).to eq(0)\n    end\n\n    it \"returns zero if duration_minutes is nil\" do\n      other_duty = build(:other_duty, duration_minutes: nil)\n      expect(helper.duration_minutes(other_duty)).to eq(0)\n    end\n  end\n\n  describe \"#duration_hours\" do\n    it \"returns minutes if duration_minutes is set\" do\n      other_duty = build(:other_duty, duration_minutes: 80)\n      expect(helper.duration_hours(other_duty)).to eq(1)\n    end\n\n    it \"returns zero if duration_minutes is zero\" do\n      other_duty = build(:other_duty, duration_minutes: 0)\n      expect(helper.duration_hours(other_duty)).to eq(0)\n    end\n\n    it \"returns zero if duration_minutes is nil\" do\n      other_duty = build(:other_duty, duration_minutes: nil)\n      expect(helper.duration_hours(other_duty)).to eq(0)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/helpers/phone_number_helper_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe PhoneNumberHelper, type: :helper do\n  context \"validates phone number\" do\n    it \"with empty string\" do\n      valid, error = valid_phone_number(\"\")\n      expect(valid).to be(true)\n      expect(error).to be_nil\n    end\n\n    it \"with 10 digit phone number prepended with US country code\" do\n      valid, error = valid_phone_number(\"+12223334444\")\n      expect(valid).to be(true)\n      expect(error).to be_nil\n    end\n\n    it \"with 10 digit phone number prepended with US country code without the plus sign\" do\n      valid, error = valid_phone_number(\"12223334444\")\n      expect(valid).to be(true)\n      expect(error).to be_nil\n    end\n\n    it \"with 10 phone number with spaces\" do\n      valid, error = valid_phone_number(\"222 333 4444\")\n      expect(valid).to be(true)\n      expect(error).to be_nil\n    end\n\n    it \"with 10 phone number with parentheses\" do\n      valid, error = valid_phone_number(\"(222)3334444\")\n      expect(valid).to be(true)\n      expect(error).to be_nil\n    end\n\n    it \"with 10 phone number with dashes\" do\n      valid, error = valid_phone_number(\"222-333-4444\")\n      expect(valid).to be(true)\n      expect(error).to be_nil\n    end\n\n    it \"with 10 phone number with dots\" do\n      valid, error = valid_phone_number(\"222.333.4444\")\n      expect(valid).to be(true)\n      expect(error).to be_nil\n    end\n  end\n\n  context \"invalidates phone number\" do\n    it \"with incorrect country code\" do\n      valid, error = valid_phone_number(\"+22223334444\")\n      expect(valid).to be(false)\n      expect(error).to have_text(\"must be 10 digits or 12 digits including country code (+1)\")\n    end\n\n    it \"with short phone number\" do\n      valid, error = valid_phone_number(\"+122\")\n      expect(valid).to be(false)\n      expect(error).to have_text(\"must be 10 digits or 12 digits including country code (+1)\")\n    end\n\n    it \"with long phone number\" do\n      valid, error = valid_phone_number(\"+12223334444555\")\n      expect(valid).to be(false)\n      expect(error).to have_text(\"must be 10 digits or 12 digits including country code (+1)\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/helpers/preference_sets_helper_spec.rb",
    "content": "require \"rails_helper\"\n\n# Specs in this file have access to a helper object that includes\n# the PreferenceSetsHelper. For example:\n#\n# describe PreferenceSetsHelper do\n#   describe \"string concat\" do\n#     it \"concats two strings with spaces\" do\n#       expect(helper.concat_strings(\"this\",\"that\")).to eq(\"this that\")\n#     end\n#   end\n# end\nRSpec.describe PreferenceSetsHelper, type: :helper do\n  pending \"add some examples to (or delete) #{__FILE__}\"\nend\n"
  },
  {
    "path": "spec/helpers/report_helper_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe ReportHelper, type: :helper do\n  describe \"#boolean_choices\" do\n    it \"returns array with correct options\" do\n      expect(helper.boolean_choices).to eq [[\"Both\", \"\"], [\"Yes\", true], [\"No\", false]]\n    end\n  end\nend\n"
  },
  {
    "path": "spec/helpers/request_header_helper_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe RequestHeaderHelper, type: :helper do\n  # TODO: Add tests for RequestHeaderHelper\n\n  pending \"add some tests for RequestHeaderHelper\"\nend\n"
  },
  {
    "path": "spec/helpers/sidebar_helper_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe SidebarHelper, type: :helper do\n  describe \"#menu_item\" do\n    it \"does not render sidebar menu item when not visible\" do\n      menu_item = helper.menu_item(label: \"Supervisors\", path: supervisors_path, visible: false)\n\n      expect(menu_item).to be_nil\n    end\n\n    it \"renders sidebar menu item label correctly\" do\n      allow(helper).to receive(:action_name).and_return(\"index\")\n      allow(helper).to receive(:current_page?).with({controller: \"supervisors\", action: \"index\"}).and_return(true)\n\n      menu_item = helper.menu_item(label: \"Supervisors\", path: supervisors_path, visible: true)\n\n      expect(menu_item).to match \">Supervisors</a>\"\n    end\n\n    describe \"menu item active state\" do\n      context \"when current page does not match the menu item path\" do\n        it \"renders sidebar menu item as an inactive link\" do\n          allow(helper).to receive(:action_name).and_return(\"index\")\n          allow(helper).to receive(:current_page?).with({controller: \"supervisors\", action: \"index\"}).and_return(false)\n\n          menu_item = helper.menu_item(label: \"Supervisors\", path: supervisors_path, visible: true)\n\n          expect(menu_item).to match \"class=\\\"list-group-item \\\"\"\n        end\n      end\n\n      context \"when accessing an index route\" do\n        it \"renders sidebar menu item as an active link\" do\n          helper.request.path = \"/supervisors\"\n\n          menu_item = helper.menu_item(label: \"Supervisors\", path: supervisors_path, visible: true)\n\n          expect(menu_item).to match \"class=\\\"list-group-item active\\\"\"\n        end\n      end\n\n      context \"when accessing an all casa admin menu item\" do\n        it \"renders the sidebar menu item as an active link\" do\n          helper.request.path = \"/all_casa_admins/patch_notes\"\n\n          menu_item = helper.menu_item(label: \"Patch Notes\", path: all_casa_admins_patch_notes_path, visible: true)\n\n          expect(menu_item).to match \"class=\\\"list-group-item active\\\"\"\n        end\n      end\n\n      context \"when accessing an volunteer emancipation checklist\" do\n        it \"renders the sidebar menu item as an active link with no redirect\" do\n          helper.request.path = \"/emancipation_checklists\"\n\n          menu_item = helper.menu_item(label: \"Emancipation Checklist(s)\", path: emancipation_checklists_path, visible: true)\n\n          expect(menu_item).to match \"class=\\\"list-group-item active\\\"\"\n        end\n\n        it \"renders the sidebar menu item as an active link with redirect\" do\n          helper.request.path = \"/casa_cases/some-case-slug/emancipation\"\n\n          menu_item = helper.menu_item(label: \"Emancipation Checklist(s)\", path: emancipation_checklists_path, visible: true)\n\n          expect(menu_item).to match \"class=\\\"list-group-item active\\\"\"\n        end\n      end\n    end\n  end\n\n  describe \"#cases_index_title\" do\n    it \"returns 'My Cases' when logged in as a volunteer\" do\n      volunteer = build :volunteer\n\n      allow(helper).to receive(:current_user).and_return(volunteer)\n\n      expect(helper.cases_index_title).to eq \"My Cases\"\n    end\n\n    it \"returns 'Cases' when logged in as a supervisor\" do\n      volunteer = build :volunteer\n\n      allow(helper).to receive(:current_user).and_return(volunteer)\n\n      expect(helper.cases_index_title).to eq \"My Cases\"\n    end\n  end\n\n  describe \"#inbox_label\" do\n    it \"returns 'Inbox' when there are no unread notifications\" do\n      volunteer = build :volunteer\n\n      allow(helper).to receive(:current_user).and_return(volunteer)\n\n      expect(helper.inbox_label).to eq \"Inbox\"\n    end\n  end\nend\n"
  },
  {
    "path": "spec/helpers/sms_body_helper_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe SmsBodyHelper, type: :helper do\n  describe \"#account_activation_msg\" do\n    it \"correct short links provided\" do\n      expected_response = account_activation_msg(\"primogems\", {0 => \"www.pasta.com\", 1 => \"www.yogurt.com\"})\n      expect(expected_response).to include(\"First, set your password here www.pasta.com. Then visit www.yogurt.com to change your text message settings.\")\n    end\n\n    it \"incorrect short links provided\" do\n      expected_response = account_activation_msg(\"primogems\", {0 => nil, 1 => nil})\n      expect(expected_response).to include(\"Please check your email to set up your password. Go to profile edit page to change SMS settings.\")\n    end\n\n    it \"set up password link invalid\" do\n      expected_response = account_activation_msg(\"primogems\", {0 => nil, 1 => \"www.carfax.com\"})\n      expect(expected_response).to include(\"Please check your email to set up your password. Then visit www.carfax.com to change your text message settings.\")\n    end\n\n    it \"link to users/edit invalid\" do\n      expected_response = account_activation_msg(\"primogems\", {0 => \"www.yummy.com\", 1 => nil})\n      expect(expected_response).to include(\"First, set your password here www.yummy.com. Go to profile edit page to change SMS settings.\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/helpers/template_helper_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe TemplateHelper, type: :helper do\n  # TODO: Add tests for TemplateHelper\n\n  pending \"add some tests for TemplateHelper\"\nend\n"
  },
  {
    "path": "spec/helpers/ui_helper_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe UiHelper, type: :helper do\n  describe \"#grouped_options_for_assigning_case\" do\n    before do\n      @casa_cases = create_list(:casa_case, 4)\n      @volunteer = create(:volunteer, casa_org: @casa_cases[0].casa_org)\n      current_user = create(:supervisor)\n      allow(helper).to receive(:current_user).and_return(current_user)\n    end\n\n    it \"does not render duplicate casa_case\" do\n      options = helper.grouped_options_for_assigning_case(@volunteer)\n\n      expect(options[0]).to eq(options[0].uniq { |option| option[0] })\n      expect(options[1]).to eq(options[1].uniq { |option| option[0] })\n    end\n  end\nend\n"
  },
  {
    "path": "spec/helpers/volunteer_helper_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe VolunteerHelper, type: :helper do\n  let(:casa_case) { create(:casa_case) }\n  let(:current_user) { create(:user) }\n\n  context \"when user is not a volunteer\" do\n    it \"returns the assigned volunteers' names\" do\n      volunteer = create(:volunteer)\n      casa_case.volunteers << volunteer\n\n      badge_html = helper.volunteer_badge(casa_case, current_user)\n\n      expect(badge_html).to include(\"badge\")\n      expect(badge_html).to include(volunteer.display_name)\n    end\n\n    it \"returns 'Unassigned' when no volunteers are present\" do\n      badge_html = helper.volunteer_badge(casa_case, current_user)\n\n      expect(badge_html).to include(\"badge\")\n      expect(badge_html).to include(\"Unassigned\")\n    end\n  end\n\n  context \"when user is a volunteer\" do\n    let(:current_user) { create(:volunteer) }\n\n    it \"returns an empty string\" do\n      badge_html = helper.volunteer_badge(casa_case, current_user)\n\n      expect(badge_html).to eq(\"\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/jobs/application_job_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe ApplicationJob, type: :job do\n  # TODO: Add tests for ApplicationJob\n\n  pending \"add some tests for ApplicationJob\"\nend\n"
  },
  {
    "path": "spec/lib/importers/case_importer_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CaseImporter do\n  subject(:case_importer) { CaseImporter.new(import_file_path, casa_org_id) }\n\n  let(:casa_org) { create :casa_org }\n  let(:casa_org_id) { casa_org.id }\n  let(:import_file_path) { file_fixture \"casa_cases.csv\" }\n\n  before do\n    allow(case_importer).to receive(:email_addresses_to_users) do |_clazz, comma_separated_emails|\n      create_list(:volunteer, comma_separated_emails.split(\",\").size, casa_org_id: casa_org_id)\n    end\n\n    # next_court_date in casa_cases.csv needs to be a future date\n    travel_to Date.parse(\"Sept 15 2022\")\n  end\n\n  describe \"#import_cases\" do\n    it \"imports cases and associates volunteers with them\" do\n      expect { case_importer.import_cases }.to change(CasaCase, :count).by(3)\n\n      # correctly imports birth_month_year_youth\n      expect(CasaCase.find_by(case_number: \"CINA-01-4347\").birth_month_year_youth&.strftime(\"%Y-%m-%d\")).to eql \"2011-03-01\"\n      expect(CasaCase.find_by(case_number: \"CINA-01-4348\").birth_month_year_youth&.strftime(\"%Y-%m-%d\")).to eql \"2000-02-01\"\n      expect(CasaCase.find_by(case_number: \"CINA-01-4349\").birth_month_year_youth&.strftime(\"%Y-%m-%d\")).to eql \"2016-12-01\"\n\n      # correctly adds volunteers\n      expect(CasaCase.find_by(case_number: \"CINA-01-4347\").volunteers.size).to eq(1)\n      expect(CasaCase.find_by(case_number: \"CINA-01-4348\").volunteers.size).to eq(2)\n      expect(CasaCase.find_by(case_number: \"CINA-01-4349\").volunteers.size).to eq(0)\n\n      # correctly adds next court date\n      expect(CasaCase.find_by(case_number: \"CINA-01-4348\").next_court_date.date.strftime(\"%Y-%m-%d\")).to eql \"2023-01-01\"\n      expect(CasaCase.find_by(case_number: \"CINA-01-4347\").next_court_date.date.strftime(\"%Y-%m-%d\")).to eql \"2022-09-16\"\n      expect(CasaCase.find_by(case_number: \"CINA-01-4349\").next_court_date).to be_nil\n    end\n\n    context \"when updating records\" do\n      let!(:existing_case) { create(:casa_case, case_number: \"CINA-01-4348\") }\n\n      it \"assigns new volunteers to the case\" do\n        expect { case_importer.import_cases }.to change(existing_case.volunteers, :count).by(2)\n      end\n\n      it \"updates outdated case fields\" do\n        expect {\n          case_importer.import_cases\n          existing_case.reload\n        }.to change(existing_case, :birth_month_year_youth).to(Date.new(2000, 2, 1))\n      end\n\n      it \"adds a next court date\" do\n        expect {\n          case_importer.import_cases\n          existing_case.reload\n        }.to change(existing_case, :court_dates).from([])\n      end\n    end\n\n    it \"returns a success message with the number of cases imported\" do\n      alert = case_importer.import_cases\n\n      expect(alert[:type]).to eq(:success)\n      expect(alert[:message]).to eq(\"You successfully imported 3 casa_cases.\")\n    end\n\n    specify \"static and instance methods have identical results\" do\n      CaseImporter.new(import_file_path, casa_org_id).import_cases\n      data_using_instance = CasaCase.pluck(:case_number).sort\n\n      CourtDate.delete_all\n      CasaCase.delete_all\n      CaseImporter.import_cases(import_file_path, casa_org_id)\n      data_using_static = CasaCase.pluck(:case_number).sort\n\n      expect(data_using_static).to eq(data_using_instance)\n      expect(data_using_static).not_to be_empty\n    end\n\n    context \"when the importer has already run once\" do\n      before { case_importer.import_cases }\n\n      it \"does not duplicate casa case files from csv files\" do\n        expect { case_importer.import_cases }.not_to change(CasaCase, :count)\n      end\n    end\n\n    context \"when there's no case number\" do\n      let(:import_file_path) { file_fixture \"casa_cases_without_case_number.csv\" }\n\n      it \"returns an error message if row does not contain a case number\" do\n        alert = case_importer.import_cases\n\n        expect(alert[:type]).to eq(:error)\n        expect(alert[:message]).to eq(\"You successfully imported 1 casa_cases. Not all rows were imported.\")\n        expect(alert[:exported_rows]).to include(\"Row does not contain a case number.\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/lib/importers/file_importer_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe FileImporter do\n  let!(:import_user) { build_stubbed(:casa_admin) }\n  let(:import_file_path) { file_fixture \"generic.csv\" }\n  let(:file_importer) { FileImporter.new(import_file_path, import_user.casa_org.id, \"something\", [\"header\"]) }\n\n  describe \"import\" do\n    it \"assumes headers\" do\n      file_importer.import { |_f| true }\n      expect(file_importer.number_imported).to eq(2)\n    end\n\n    it \"resets the count of how many have been imported, each time\" do\n      file_importer.import { |_f| true }\n      file_importer.import { |_f| true }\n      expect(file_importer.number_imported).to eq(2)\n    end\n\n    it \"yields to a block\" do\n      names = []\n      file_importer.import do |row|\n        names << row\n      end\n      expect(names.size).to eq(2)\n    end\n\n    it \"captures errors\" do\n      expect {\n        file_importer.import do |_row|\n          raise \"Something bad\"\n        end\n      }.not_to raise_error\n      expect(file_importer.failed_imports.size).to eq(2)\n    end\n\n    it \"returns hash with expected attributes\" do\n      result = file_importer.import { |_f| true }\n      expect(result.keys).to contain_exactly(:type, :message, :exported_rows)\n    end\n\n    it \"returns an error if file has no rows\" do\n      no_row_path = file_fixture \"no_rows.csv\"\n      no_row_importer = FileImporter.new(no_row_path, import_user.casa_org.id, \"something\", [\"header\"])\n      expect(no_row_importer.import[:message]).eql?(FileImporter::ERR_NO_ROWS)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/lib/importers/supervisor_importer_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe SupervisorImporter do\n  let!(:import_user) { build_stubbed(:casa_admin) }\n  let(:casa_org_id) { import_user.casa_org.id }\n\n  # Use of the static method SupervisorImporter.import_volunteers functions identically to SupervisorImporter.new(...).import_volunteers\n  # but is preferred.\n  let(:supervisor_import_data_path) { file_fixture \"supervisors.csv\" }\n\n  let(:supervisor_importer) do\n    importer = SupervisorImporter.new(supervisor_import_data_path, casa_org_id)\n\n    allow(importer).to receive(:email_addresses_to_users) do |_clazz, supervisor_volunteers|\n      create_list(:volunteer, supervisor_volunteers.split(\",\").size, casa_org: import_user.casa_org)\n    end\n\n    importer\n  end\n\n  it \"imports supervisors and associates volunteers with them\" do\n    expect { supervisor_importer.import_supervisors }.to change(Supervisor, :count).by(3)\n    expect(Supervisor.find_by(email: \"supervisor1@example.net\").volunteers.size).to eq(1)\n    expect(Supervisor.find_by(email: \"supervisor2@example.net\").volunteers.size).to eq(2)\n    expect(Supervisor.find_by(email: \"supervisor3@example.net\").volunteers.size).to eq(0)\n  end\n\n  it \"returns a success message with the number of supervisors imported\" do\n    alert = supervisor_importer.import_supervisors\n    expect(alert[:type]).to eq(:success)\n    expect(alert[:message]).to eq(\"You successfully imported 3 supervisors.\")\n  end\n\n  context \"when the supervisors have already been imported\" do\n    before { supervisor_importer.import_supervisors }\n\n    it \"does not import duplicate supervisors from csv files\" do\n      expect { supervisor_importer.import_supervisors }.not_to change(Supervisor, :count)\n    end\n\n    context \"when any volunteer could not be assigned to the supervisor during the import\" do\n      let!(:existing_volunteer) { build(:volunteer, email: \"volunteer1@example.net\") }\n      let(:supervisor_import_data_path) { file_fixture \"supervisor_volunteers.csv\" }\n\n      it \"returns an error message\" do\n        alert = SupervisorImporter.new(supervisor_import_data_path, casa_org_id).import_supervisors\n\n        expect(alert[:type]).to eq(:error)\n        expect(alert[:message]).to include(\"Not all rows were imported.\")\n      end\n\n      context \"because the volunteer has already been assigned to a supervisor\" do\n        let!(:supervisor_volunteer) { create(:supervisor_volunteer, volunteer: existing_volunteer) }\n\n        it \"returns an error message\" do\n          alert = SupervisorImporter.new(supervisor_import_data_path, casa_org_id).import_supervisors\n\n          expect(alert[:type]).to eq(:error)\n          expect(alert[:exported_rows]).to include(\"Volunteer #{existing_volunteer.email} already has a supervisor\")\n        end\n      end\n    end\n  end\n\n  context \"when updating supervisors\" do\n    let!(:existing_supervisor) { create(:supervisor, display_name: \"#\", email: \"supervisor2@example.net\") }\n\n    it \"assigns unassigned volunteers\" do\n      expect {\n        supervisor_importer.import_supervisors\n      }.to change(existing_supervisor.volunteers, :count).by(2)\n    end\n\n    it \"updates outdated supervisor fields\" do\n      expect {\n        supervisor_importer.import_supervisors\n        existing_supervisor.reload\n      }.to change(existing_supervisor, :display_name).to(\"Supervisor Two\")\n    end\n\n    it \"updates phone number to valid number and turns on sms notifications\" do\n      expect {\n        supervisor_importer.import_supervisors\n        existing_supervisor.reload\n      }.to change(existing_supervisor, :phone_number).to(\"+11111111111\")\n        .and change(existing_supervisor, :receive_sms_notifications).to(true)\n    end\n  end\n\n  context \"when row doesn't have e-mail address\" do\n    let(:supervisor_import_data_path) { file_fixture \"supervisors_without_email.csv\" }\n\n    it \"returns an error message\" do\n      alert = supervisor_importer.import_supervisors\n\n      expect(alert[:type]).to eq(:error)\n      expect(alert[:message]).to eq(\"You successfully imported 1 supervisors. Not all rows were imported.\")\n      expect(alert[:exported_rows]).to include(\"Row does not contain e-mail address.\")\n    end\n  end\n\n  context \"when row doesn't have phone number\" do\n    let(:supervisor_import_data_path) { file_fixture \"supervisors_without_phone_numbers.csv\" }\n\n    let!(:existing_supervisor_with_number) { create(:supervisor, display_name: \"#\", email: \"supervisor1@example.net\", phone_number: \"+11111111111\", receive_sms_notifications: true) }\n\n    it \"updates phone number to be deleted and turns off sms notifications\" do\n      expect {\n        supervisor_importer.import_supervisors\n        existing_supervisor_with_number.reload\n      }.to change(existing_supervisor_with_number, :phone_number).to(\"\")\n        .and change(existing_supervisor_with_number, :receive_sms_notifications).to(false)\n    end\n  end\n\n  context \"when phone number in row is invalid\" do\n    let(:supervisor_import_data_path) { file_fixture \"supervisors_invalid_phone_numbers.csv\" }\n\n    it \"returns an error message\" do\n      alert = supervisor_importer.import_supervisors\n\n      expect(alert[:type]).to eq(:error)\n      expect(alert[:message]).to eq(\"Not all rows were imported.\")\n      expect(alert[:exported_rows]).to include(\"Phone number must be 10 digits or 12 digits including country code (+1)\")\n    end\n  end\n\n  specify \"static and instance methods have identical results\" do\n    SupervisorImporter.new(supervisor_import_data_path, casa_org_id).import_supervisors\n    data_using_instance = Supervisor.pluck(:email).sort\n\n    SentEmail.destroy_all\n    Supervisor.destroy_all\n    SupervisorImporter.import_supervisors(supervisor_import_data_path, casa_org_id)\n    data_using_static = Supervisor.pluck(:email).sort\n\n    expect(data_using_static).to eq(data_using_instance)\n    expect(data_using_static).not_to be_empty\n  end\nend\n"
  },
  {
    "path": "spec/lib/importers/volunteer_importer_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe VolunteerImporter do\n  let!(:import_user) { build(:casa_admin) }\n  let(:casa_org_id) { import_user.casa_org.id }\n\n  # Use of the static method VolunteerImporter.import_volunteers functions identically to VolunteerImporter.new(...).import_volunteers\n  # but is preferred.\n  let(:import_file_path) { file_fixture \"volunteers.csv\" }\n  let(:volunteer_importer) { -> { VolunteerImporter.import_volunteers(import_file_path, casa_org_id) } }\n\n  it \"imports volunteers from a csv file\" do\n    expect { volunteer_importer.call }.to change(User, :count).by(3)\n  end\n\n  it \"returns a success message with the number of volunteers imported\" do\n    alert = volunteer_importer.call\n    expect(alert[:type]).to eq(:success)\n    expect(alert[:message]).to eq(\"You successfully imported 3 volunteers.\")\n  end\n\n  context \"when the volunteers have been imported already\" do\n    before { volunteer_importer.call }\n\n    it \"does not import duplicate volunteers from csv files\" do\n      expect { volunteer_importer.call }.not_to change(User, :count)\n    end\n\n    specify \"static and instance methods have identical results\" do\n      VolunteerImporter.new(import_file_path, casa_org_id).import_volunteers\n      data_using_instance = Volunteer.pluck(:email).sort\n\n      SentEmail.destroy_all\n      Volunteer.destroy_all\n      VolunteerImporter.import_volunteers(import_file_path, casa_org_id)\n      data_using_static = Volunteer.pluck(:email).sort\n\n      expect(data_using_static).to eq(data_using_instance)\n      expect(data_using_static).not_to be_empty\n    end\n  end\n\n  context \"when updating volunteers\" do\n    let!(:existing_volunteer) { create(:volunteer, display_name: \"&&&&&\", email: \"volunteer1@example.net\") }\n\n    it \"updates outdated volunteer fields\" do\n      expect {\n        volunteer_importer.call\n        existing_volunteer.reload\n      }.to change(existing_volunteer, :display_name).to(\"Volunteer One\")\n    end\n\n    it \"updates phone number to valid number and turns sms notifications on\" do\n      expect {\n        volunteer_importer.call\n        existing_volunteer.reload\n      }.to change(existing_volunteer, :phone_number).to(\"+11234567890\")\n        .and change(existing_volunteer, :receive_sms_notifications).to(true)\n    end\n  end\n\n  context \"when row doesn't have e-mail address\" do\n    let(:import_file_path) { file_fixture \"volunteers_without_email.csv\" }\n\n    it \"returns an error message\" do\n      alert = volunteer_importer.call\n\n      expect(alert[:type]).to eq(:error)\n      expect(alert[:message]).to eq(\"You successfully imported 1 volunteers. Not all rows were imported.\")\n      expect(alert[:exported_rows]).to include(\"Row does not contain an e-mail address.\")\n    end\n  end\n\n  context \"when row doesn't have phone number\" do\n    let(:import_file_path) { file_fixture \"volunteers_without_phone_numbers.csv\" }\n\n    let!(:existing_volunteer_with_number) { create(:volunteer, display_name: \"#\", email: \"volunteer2@example.net\", phone_number: \"+11111111111\", receive_sms_notifications: true) }\n\n    it \"updates phone number to be deleted and turns sms notifications off\" do\n      expect {\n        volunteer_importer.call\n        existing_volunteer_with_number.reload\n      }.to change(existing_volunteer_with_number, :phone_number).to(\"\")\n        .and change(existing_volunteer_with_number, :receive_sms_notifications).to(false)\n    end\n  end\n\n  context \"when phone number in row is invalid\" do\n    let(:import_file_path) { file_fixture \"volunteers_invalid_phone_numbers.csv\" }\n\n    it \"returns an error message\" do\n      alert = volunteer_importer.call\n\n      expect(alert[:type]).to eq(:error)\n      expect(alert[:message]).to eq(\"Not all rows were imported.\")\n      expect(alert[:exported_rows]).to include(\"Phone number must be 10 digits or 12 digits including country code (+1)\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/lib/tasks/case_contact_types_reminder_spec.rb",
    "content": "require \"rails_helper\"\nrequire_relative \"../../../lib/tasks/case_contact_types_reminder\"\nrequire \"support/stubbed_requests/webmock_helper\"\n\nRSpec.describe CaseContactTypesReminder do\n  let!(:casa_org) do\n    create(\n      :casa_org,\n      twilio_enabled: true,\n      twilio_phone_number: \"+15555555555\",\n      twilio_account_sid: \"articuno34\",\n      twilio_api_key_sid: \"Aladdin\",\n      twilio_api_key_secret: \"open sesame\"\n    )\n  end\n  let!(:volunteer) do\n    create(\n      :volunteer,\n      casa_org_id: casa_org.id,\n      phone_number: \"+12222222222\",\n      receive_sms_notifications: true\n    )\n  end\n  let!(:contact_type) { create(:contact_type, name: \"test\") }\n  let!(:case_contact) do\n    create(\n      :case_contact,\n      creator: volunteer,\n      contact_types: [contact_type],\n      occurred_at: 4.months.ago\n    )\n  end\n\n  before do\n    WebMockHelper.twilio_success_stub\n    WebMockHelper.twilio_success_stub_messages_60_days\n    WebMockHelper.short_io_stub_localhost\n  end\n\n  context \"volunteer with uncontacted contact types, sms notifications on, and no reminder in last quarter\" do\n    it \"sends sms reminder\" do\n      responses = CaseContactTypesReminder.new.send!\n      expect(responses.count).to eq 1\n      expect(responses[0][:messages][0].body).to include CaseContactTypesReminder::FIRST_MESSAGE.strip\n      expect(responses[0][:messages][1].body).to include contact_type.name\n      expect(responses[0][:messages][2].body).to match CaseContactTypesReminder::THIRD_MESSAGE + \"https://42ni.short.gy/jzTwdF\"\n    end\n  end\n\n  context \"volunteer with contacted contact types within last 60 days, sms notifications on, and no reminder in last quarter\" do\n    it \"does not send sms reminder\" do\n      CaseContact.update_all(occurred_at: 1.months.ago)\n      responses = CaseContactTypesReminder.new.send!\n      expect(responses.count).to match 0\n    end\n  end\n\n  context \"volunteer with uncontacted contact types, sms notifications off, and no reminder in last quarter\" do\n    it \"does not send sms reminder\" do\n      Volunteer.update_all(receive_sms_notifications: false)\n      responses = CaseContactTypesReminder.new.send!\n      expect(responses.count).to match 0\n    end\n  end\n\n  context \"volunteer with uncontacted contact types, sms notifications on, and reminder in last quarter\" do\n    it \"does not send sms reminder\" do\n      create(:user_reminder_time, :case_contact_types)\n      Volunteer.update_all(receive_sms_notifications: true)\n      responses = CaseContactTypesReminder.new.send!\n      expect(responses.count).to match 0\n    end\n  end\n\n  context \"volunteer with uncontacted contact types, sms notifications on, and reminder out of last quarter\" do\n    it \"sends sms reminder\" do\n      UserReminderTime.destroy_all\n      Volunteer.all do |v|\n        create(:user_case_contact_types_reminder, user_id: v.id, reminder_sent: 4.months.ago)\n      end\n      responses = CaseContactTypesReminder.new.send!\n      expect(responses.count).to match 1\n      expect(responses[0][:messages][0].body).to eq CaseContactTypesReminder::FIRST_MESSAGE.strip\n      expect(responses[0][:messages][1].body).to include contact_type.name\n      expect(responses[0][:messages][2].body).to match CaseContactTypesReminder::THIRD_MESSAGE + \"https://42ni.short.gy/jzTwdF\"\n    end\n  end\nend\n"
  },
  {
    "path": "spec/lib/tasks/data_post_processors/case_contact_populator_spec.rb",
    "content": "require \"rails_helper\"\nrequire \"./lib/tasks/data_post_processors/case_contact_populator\"\n\nRSpec.describe CaseContactPopulator do\n  before do\n    Rake::Task.clear\n    Casa::Application.load_tasks\n  end\n\n  it \"does nothing on an empty database\" do\n    described_class.populate\n\n    expect(ContactType.count).to eq(0)\n    expect(ContactTypeGroup.count).to eq(0)\n  end\n\n  it \"does nothing if there are no contact types\" do\n    case_contact = create(:case_contact, contact_types: [], status: \"started\")\n\n    described_class.populate\n\n    expect(case_contact.contact_types).to be_empty\n    expect(ContactType.count).to eq(0)\n    expect(ContactTypeGroup.count).to eq(0)\n  end\n\n  it \"creates a new contact type group with the org of the casa case\" do\n    contact_type = create(:contact_type)\n    casa_org1 = contact_type.contact_type_group.casa_org\n\n    casa_org2 = create(:casa_org)\n    casa_case = create(:casa_case, casa_org: casa_org2)\n    create(:case_contact, casa_case: casa_case, contact_types: [contact_type])\n\n    described_class.populate\n\n    expect(ContactTypeGroup.count).to eq(2)\n    expect(ContactTypeGroup.last.casa_org).to eq(casa_org2)\n\n    expect(ContactType.count).to eq(1)\n    expect(ContactType.first.contact_type_group.casa_org).to eq(casa_org1)\n  end\n\n  it \"creates a new contact type with the org of the casa case\" do\n    ctg1 = create(:contact_type_group, casa_org: create(:casa_org), name: \"Education\")\n    ctg2 = create(:contact_type_group, casa_org: create(:casa_org), name: \"Education\")\n\n    contact_type = create(:contact_type, contact_type_group: ctg1, name: \"School\")\n\n    casa_case = create(:casa_case, casa_org: ctg2.casa_org)\n    create(:case_contact, casa_case: casa_case, contact_types: [contact_type])\n\n    described_class.populate\n\n    expect(ContactTypeGroup.count).to eq(2)\n    expect(ContactType.count).to eq(2)\n\n    expect(ContactType.first.contact_type_group).to eq(ctg1)\n    expect(ContactType.last.contact_type_group).to eq(ctg2)\n    expect(contact_type.reload.contact_type_group).to eq(ctg1)\n  end\nend\n"
  },
  {
    "path": "spec/lib/tasks/data_post_processors/contact_topic_populator_spec.rb",
    "content": "require \"rails_helper\"\nrequire \"./lib/tasks/data_post_processors/contact_topic_populator\"\n\nRSpec.describe \"populates each existing organization with contact groups and types\" do\n  let(:fake_topics) { [1, 2, 3].map { |i| {\"question\" => \"Question #{i}\", \"details\" => \"Details #{i}\"} } }\n  let(:org_one) { create(:casa_org) }\n  let(:org_two) { create(:casa_org) }\n\n  before do\n    Rake::Task.clear\n    Casa::Application.load_tasks\n\n    allow(ContactTopic).to receive(:default_contact_topics).and_return(fake_topics)\n    ContactTopicPopulator.populate\n  end\n\n  it \"does nothing on an empty database\" do\n    ContactTopicPopulator.populate\n\n    expect(ContactTopic.count).to eq(0)\n    expect(ContactTopicAnswer.count).to eq(0)\n  end\n\n  context \"there are orgs\" do\n    before do\n      org_one\n      org_two\n    end\n\n    it \"creates 3 topics per each of two orgs totalling 6\" do\n      expect do\n        ContactTopicPopulator.populate\n      end.to change(ContactTopic, :count).from(0).to(6)\n    end\n\n    context \"there are case_contacts\" do\n      let(:case_one) { create(:casa_case, casa_org: org_one) }\n      let(:case_two) { create(:casa_case, casa_org: org_two) }\n\n      before do\n        create_list(:case_contact, 3, casa_case: case_one)\n        create_list(:case_contact, 3, casa_case: case_two)\n      end\n\n      it \"creates 3 topic answers for each case contact\" do\n        expect { ContactTopicPopulator.populate }.to change(ContactTopicAnswer, :count).from(0).to(18)\n        expect(case_one.case_contacts.map(&:contact_topic_answers).flatten.size).to eq(9)\n        expect(case_two.case_contacts.map(&:contact_topic_answers).flatten.size).to eq(9)\n\n        CaseContact.all.each do |case_contact|\n          expect(case_contact.contact_topic_answers.size).to eq(3)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/lib/tasks/data_post_processors/contact_type_populator_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"populates each existing organization with contact groups and types\" do\n  before do\n    Rake::Task.clear\n    Casa::Application.load_tasks\n  end\n\n  it \"creates the expected contact groups and contact types for each existing organization\" do\n    ContactTypePopulator.populate\n\n    CasaOrg.all.each do |org|\n      casa_group = org.contact_type_groups.find_by(name: \"CASA\")\n      expect(casa_group.contact_types.pluck(:name)).to contain_exactly(\"Youth\", \"Supervisor\")\n\n      family_group = org.contact_type_groups.find_by(name: \"Family\")\n      expect(family_group.contact_types.pluck(:name)).to contain_exactly(\"Parent\", \"Other Family\", \"Sibling\", \"Grandparent\", \"Aunt Uncle or Cousin\", \"Fictive Kin\")\n\n      placement_group = org.contact_type_groups.find_by(name: \"Placement\")\n      expect(placement_group.contact_types.pluck(:name)).to contain_exactly(\"Foster Parent\", \"Caregiver Family\", \"Therapeutic Agency Worker\")\n\n      social_services_group = org.contact_type_groups.find_by(name: \"Social Services\")\n      expect(social_services_group.contact_types.pluck(:name)).to contain_exactly(\"Social Worker\")\n\n      legal_group = org.contact_type_groups.find_by(name: \"Legal\")\n      expect(legal_group.contact_types.pluck(:name)).to contain_exactly(\"Court\", \"Attorney\")\n\n      health_group = org.contact_type_groups.find_by(name: \"Health\")\n      expect(health_group.contact_types.pluck(:name)).to contain_exactly(\"Medical Professional\", \"Mental Health Therapist\", \"Other Therapist\", \"Psychiatric Practitioner\")\n\n      education_group = org.contact_type_groups.find_by(name: \"Education\")\n      expect(education_group.contact_types.pluck(:name)).to contain_exactly(\"School\", \"Guidance Counselor\", \"Teacher\", \"IEP Team\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/lib/tasks/no_contact_made_reminder_spec.rb",
    "content": "require \"rails_helper\"\nrequire_relative \"../../../lib/tasks/no_contact_made_reminder\"\nrequire \"support/stubbed_requests/webmock_helper\"\n\nRSpec.describe NoContactMadeReminder do\n  let!(:casa_org) do\n    create(\n      :casa_org,\n      twilio_enabled: true,\n      twilio_phone_number: \"+15555555555\",\n      twilio_account_sid: \"articuno34\",\n      twilio_api_key_sid: \"Aladdin\",\n      twilio_api_key_secret: \"open sesame\"\n    )\n  end\n  let!(:volunteer) do\n    create(\n      :volunteer,\n      casa_org_id: casa_org.id,\n      phone_number: \"+12222222222\",\n      receive_sms_notifications: true\n    )\n  end\n  let!(:contact_type) { create(:contact_type, name: \"test\") }\n  let!(:case_contact) do\n    create(\n      :case_contact,\n      creator: volunteer,\n      contact_types: [contact_type],\n      occurred_at: 1.week.ago,\n      contact_made: false\n    )\n  end\n  let!(:expected_sms) { \"It's been two weeks since you've tried reaching 'test'. Try again! https://42ni.short.gy/jzTwdF\" }\n\n  before do\n    WebMockHelper.twilio_success_stub\n    WebMockHelper.twilio_no_contact_made_stub\n    WebMockHelper.short_io_stub_localhost\n  end\n\n  context \"volunteer with no contact made in past 2 weeks in case contact\" do\n    it \"sends sms reminder\" do\n      responses = NoContactMadeReminder.new.send!\n      expect(responses.count).to eq 1\n      expect(responses[0][:volunteer]).to eq(volunteer)\n      expect(responses[0][:message].body).to eq expected_sms\n    end\n  end\n\n  context \"volunteer with contact made after not making contact\" do\n    let(:case_contact) do\n      create(\n        :case_contact,\n        creator: volunteer,\n        contact_types: [contact_type],\n        occurred_at: 2.days.ago,\n        contact_made: true\n      )\n    end\n\n    it \"sends not sms reminder\" do\n      responses = NoContactMadeReminder.new.send!\n      expect(responses.count).to eq 0\n    end\n  end\n\n  context \"volunteer with contact made after not making contact but volunteer is no longer assigned to the case\" do\n    let(:case_contact) do\n      create(\n        :case_contact,\n        creator: create(:volunteer), # different volunteer assigned\n        contact_types: [contact_type],\n        occurred_at: 1.week.ago,\n        contact_made: false\n      )\n    end\n\n    it \"sends not sms reminder\" do\n      responses = NoContactMadeReminder.new.send!\n      expect(responses.count).to eq 0\n    end\n  end\n\n  context \"volunteer with no case contacts\" do\n    it \"sends not sms reminder\" do\n      CaseContact.destroy_all\n      responses = NoContactMadeReminder.new.send!\n      expect(responses.count).to eq 0\n    end\n  end\n\n  context \"volunteer with quarterly case contact type reminder sent on same day\" do\n    let(:quarterly_reminder) { create(:user_reminder_time, :quarterly_reminder) }\n\n    it \"sends not sms reminder\" do\n      CaseContact.destroy_all\n      responses = NoContactMadeReminder.new.send!\n      expect(responses.count).to eq 0\n    end\n  end\n\n  context \"volunteer with no contact made reminder sent within last 30 days\" do\n    let(:no_contact_made_reminder) { create(:user_reminder_time, no_contact_made: 1.weeks.ago) }\n\n    it \"sends not sms reminder\" do\n      CaseContact.destroy_all\n      responses = NoContactMadeReminder.new.send!\n      expect(responses.count).to eq 0\n    end\n  end\n\n  context \"volunteer with sms notification off\" do\n    let(:volunteer) {\n      create(\n        :volunteer,\n        casa_org_id: casa_org.id,\n        phone_number: \"+12222222222\",\n        receive_sms_notifications: false\n      )\n    }\n\n    it \"sends not sms reminder\" do\n      responses = NoContactMadeReminder.new.send!\n      expect(responses.count).to eq 0\n    end\n  end\nend\n"
  },
  {
    "path": "spec/lib/tasks/supervisor_weekly_digest_spec.rb",
    "content": "require \"rails_helper\"\nrequire_relative \"../../../lib/tasks/supervisor_weekly_digest\"\n\nRSpec.describe SupervisorWeeklyDigest do\n  describe \"#send!\" do\n    subject { described_class.new.send! }\n\n    context \"on monday\" do\n      context \"with active and deactivated supervisor\" do\n        before do\n          travel_to Time.zone.local(2021, 9, 27, 12, 0, 0) # monday noon\n          create(:supervisor, active: true)\n          create(:supervisor, active: false)\n        end\n\n        it \"only sends to active supervisor\" do\n          expect { subject }.to change { ActionMailer::Base.deliveries.count }.by(1)\n          expect(ActionMailer::Base.deliveries.last.subject).to eq(\"Weekly summary of volunteers' activities for the week of 2021-09-20\")\n        end\n      end\n    end\n\n    context \"not on monday\" do\n      before do\n        travel_to Time.zone.local(2021, 9, 29, 12, 0, 0) # not monday\n        create(:supervisor, active: true)\n      end\n\n      it \"does not send email\" do\n        expect { subject }.not_to change { ActionMailer::Base.deliveries.count }\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/mailers/application_mailer_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe ApplicationMailer, type: :mailer do\n  # TODO: Add tests for ApplicationMailer\n\n  pending \"add some tests for ApplicationMailer\"\nend\n"
  },
  {
    "path": "spec/mailers/casa_admin_mailer_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CasaAdminMailer, type: :mailer do\n  let(:casa_admin) { create(:casa_admin) }\n\n  describe \".account_setup for an admin\" do\n    let(:mail) { CasaAdminMailer.account_setup(casa_admin) }\n\n    it \"sends an email saying the account has been created\" do\n      expect(mail.body.encoded).to match(\"A #{casa_admin.casa_org.display_name}’s County Admin account\")\n      expect(mail.body.encoded).to match(\"has been created for you\")\n    end\n\n    it \"generates a password reset token and sends email\" do\n      expect(casa_admin.reset_password_token).to be_nil\n      expect(casa_admin.reset_password_sent_at).to be_nil\n      expect(mail.body.encoded.squish).to match(\"Set Your Password\")\n      expect(casa_admin.reset_password_token).not_to be_nil\n      expect(casa_admin.reset_password_sent_at).not_to be_nil\n    end\n  end\n\n  describe \".invitation_instructions for an all casa admin\" do\n    let!(:all_casa_admin) { create(:all_casa_admin) }\n    let!(:mail) { all_casa_admin.invite! }\n\n    it \"informs the correct expiration date\" do\n      expiration_date = I18n.l(all_casa_admin.invitation_due_at, format: :full, default: nil)\n\n      email_body = mail.html_part.body.to_s.squish\n      expect(email_body).to include(\"This invitation will expire on #{expiration_date} (one week).\")\n    end\n  end\n\n  describe \".invitation_instructions for a casa admin\" do\n    let!(:casa_admin) { create(:casa_admin) }\n    let!(:mail) { casa_admin.invite! }\n\n    it \"informs the correct expiration date\" do\n      expiration_date = I18n.l(casa_admin.invitation_due_at, format: :full, default: nil)\n\n      email_body = mail.html_part.body.to_s.squish\n      expect(email_body).to include(\"This invitation will expire on #{expiration_date} (two weeks).\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/mailers/fund_request_mailer_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe FundRequestMailer, type: :mailer do\n  let(:fund_request) { build(:fund_request) }\n  let(:mail) { described_class.send_request(nil, fund_request) }\n\n  it \"sends email\" do\n    email_body = mail.body.encoded.squish\n    expect(email_body).to include(\"Fund Request\")\n  end\nend\n"
  },
  {
    "path": "spec/mailers/learning_hours_mailer_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe LearningHoursMailer, type: :mailer do\n  describe \"#learning_hours_report_email\" do\n    let(:user) { create(:user) }\n    let(:casa_org) { create(:casa_org, users: [user]) }\n    let(:learning_hours) { [instance_double(LearningHour)] }\n    let(:csv_data) { \"dummy,csv,data\" }\n\n    before do\n      allow(LearningHour).to receive(:where).and_return(learning_hours)\n      allow(LearningHoursExportCsvService).to receive(:new).and_return(instance_double(LearningHoursExportCsvService, perform: csv_data))\n    end\n\n    it \"sends the email to the provided user with correct subject and attachment\" do\n      mail = LearningHoursMailer.learning_hours_report_email(user)\n\n      expect(mail.to).to eq([user.email])\n\n      end_date = Date.today.end_of_month\n      expected_subject = \"Learning Hours Report for #{end_date.strftime(\"%B, %Y\")}.\"\n      expect(mail.subject).to eq(expected_subject)\n\n      expect(mail.attachments.first.filename).to eq(\"learning-hours-report-#{Date.today}.csv\")\n      expect(mail.attachments.first.body.raw_source).to eq(csv_data)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/mailers/previews/casa_admin_mailer_preview_spec.rb",
    "content": "require \"rails_helper\"\nrequire Rails.root.join(\"lib/mailers/previews/casa_admin_mailer_preview\").to_s\n\nRSpec.describe CasaAdminMailerPreview do\n  let!(:casa_admin) { create(:casa_admin) }\n\n  describe \"#account_setup\" do\n    context \"When no ID is passed\" do\n      let(:preview) { described_class.new }\n      let(:email) { preview.account_setup }\n\n      it { expect(email.to).to eq [casa_admin.email] }\n    end\n\n    context \"When passed ID is valid\" do\n      let(:preview) { described_class.new(id: casa_admin.id) }\n      let(:email) { preview.account_setup }\n\n      it { expect(email.to).to eq [casa_admin.email] }\n    end\n\n    context \"When passed ID is invalid\" do\n      let(:preview) { described_class.new(id: -1) }\n      let(:email) { preview.account_setup }\n\n      it { expect(email.to).to eq [\"missing_casa_admin@example.com\"] }\n    end\n  end\n\n  describe \"#deactivation\" do\n    context \"When no ID is passed\" do\n      let(:preview) { described_class.new }\n      let(:email) { preview.deactivation }\n\n      it { expect(email.to).to eq [casa_admin.email] }\n    end\n\n    context \"When passed ID is valid\" do\n      let(:preview) { described_class.new(id: casa_admin.id) }\n      let(:email) { preview.deactivation }\n\n      it { expect(email.to).to eq [casa_admin.email] }\n    end\n\n    context \"When passed ID is invalid\" do\n      let(:preview) { described_class.new(id: -1) }\n      let(:email) { preview.deactivation }\n\n      it { expect(email.to).to eq [\"missing_casa_admin@example.com\"] }\n    end\n  end\nend\n"
  },
  {
    "path": "spec/mailers/previews/devise_mailer_preview_spec.rb",
    "content": "require \"rails_helper\"\nrequire Rails.root.join(\"lib/mailers/previews/devise_mailer_preview\").to_s\n\nRSpec.describe DeviseMailerPreview do\n  let(:subject) { described_class.new }\n  let!(:user) { create(:user) }\n\n  describe \"#reset_password_instructions\" do\n    context \"When no ID is passed\" do\n      let(:preview) { described_class.new }\n      let(:email) { preview.reset_password_instructions }\n\n      it { expect(email.to).to eq [user.email] }\n    end\n\n    context \"When passed ID is valid\" do\n      let(:preview) { described_class.new(id: user.id) }\n      let(:email) { preview.reset_password_instructions }\n\n      it { expect(email.to).to eq [user.email] }\n    end\n\n    context \"When passed ID is invalid\" do\n      let(:preview) { described_class.new(id: -1) }\n      let(:email) { preview.reset_password_instructions }\n\n      it { expect(email.to).to eq [\"missing_user@example.com\"] }\n    end\n  end\n\n  describe \"#invitation_instructions_as_all_casa_admin\" do\n    let!(:all_casa_admin) { create(:all_casa_admin) }\n    let(:message) { subject.invitation_instructions_as_all_casa_admin }\n\n    it { expect(message.to).to eq [all_casa_admin.email] }\n  end\n\n  describe \"#invitation_instructions_as_casa_admin\" do\n    let!(:casa_admin) { create(:casa_admin) }\n    let(:message) { subject.invitation_instructions_as_casa_admin }\n\n    it { expect(message.to).to eq [casa_admin.email] }\n  end\n\n  describe \"#invitation_instructions_as_supervisor\" do\n    let!(:supervisor) { create(:supervisor) }\n    let(:message) { subject.invitation_instructions_as_supervisor }\n\n    it { expect(message.to).to eq [supervisor.email] }\n  end\n\n  describe \"#invitation_instructions_as_volunteer\" do\n    let!(:volunteer) { create(:volunteer) }\n    let(:message) { subject.invitation_instructions_as_volunteer }\n\n    it { expect(message.to).to eq [volunteer.email] }\n  end\nend\n"
  },
  {
    "path": "spec/mailers/previews/supervisor_mailer_preview_spec.rb",
    "content": "require \"rails_helper\"\nrequire Rails.root.join(\"lib/mailers/previews/supervisor_mailer_preview\").to_s\n\nRSpec.describe SupervisorMailerPreview do\n  let!(:supervisor) { create(:supervisor) }\n  let!(:volunteer) { create(:volunteer, casa_org: supervisor.casa_org, supervisor: supervisor) }\n\n  describe \"#account_setup\" do\n    context \"When no ID is passed\" do\n      let(:preview) { described_class.new }\n      let(:email) { preview.account_setup }\n\n      it { expect(email.to).to eq [supervisor.email] }\n    end\n\n    context \"When passed ID is valid\" do\n      let(:preview) { described_class.new(id: supervisor.id) }\n      let(:email) { preview.account_setup }\n\n      it { expect(email.to).to eq [supervisor.email] }\n    end\n\n    context \"When passed ID is invalid\" do\n      let(:preview) { described_class.new(id: -1) }\n      let(:email) { preview.account_setup }\n\n      it { expect(email.to).to eq [\"missing_supervisor@example.com\"] }\n    end\n  end\n\n  describe \"#weekly_digest\" do\n    context \"When no ID is passed\" do\n      let(:preview) { described_class.new }\n      let(:email) { preview.weekly_digest }\n\n      it { expect(email.to).to eq [supervisor.email] }\n    end\n\n    context \"When passed ID is valid\" do\n      let(:preview) { described_class.new(id: supervisor.id) }\n      let(:email) { preview.weekly_digest }\n\n      it { expect(email.to).to eq [supervisor.email] }\n    end\n\n    context \"When passed ID is invalid\" do\n      let(:preview) { described_class.new(id: 3500) }\n      let(:email) { preview.weekly_digest }\n\n      it { expect(email.to).to eq [\"missing_supervisor@example.com\"] }\n    end\n  end\n\n  describe \"#reimbursement_request_email\" do\n    context \"When no ID is passed\" do\n      let(:preview) { described_class.new }\n      let(:email) { preview.reimbursement_request_email }\n\n      it { expect(email.to).to eq [supervisor.email] }\n    end\n\n    context \"When passed ID is valid\" do\n      let(:preview) { described_class.new(volunteer_id: volunteer.id, supervisor_id: supervisor.id) }\n      let(:email) { preview.reimbursement_request_email }\n\n      it { expect(email.to).to eq [supervisor.email] }\n    end\n  end\nend\n"
  },
  {
    "path": "spec/mailers/previews/volunteer_mailer_preview_spec.rb",
    "content": "require \"rails_helper\"\nrequire Rails.root.join(\"lib/mailers/previews/volunteer_mailer_preview\").to_s\n\nRSpec.describe VolunteerMailerPreview do\n  let!(:volunteer) { create(:volunteer) }\n\n  describe \"#account_setup\" do\n    context \"When no ID is passed\" do\n      let(:preview) { described_class.new }\n      let(:email) { preview.account_setup }\n\n      it { expect(email.to).to eq [volunteer.email] }\n    end\n\n    context \"When passed ID is valid\" do\n      let(:preview) { described_class.new(id: volunteer.id) }\n      let(:email) { preview.account_setup }\n\n      it { expect(email.to).to eq [volunteer.email] }\n    end\n\n    context \"When passed ID is invalid\" do\n      let(:preview) { described_class.new(id: -1) }\n      let(:email) { preview.account_setup }\n\n      it { expect(email.to).to eq [\"missing_volunteer@example.com\"] }\n    end\n  end\n\n  describe \"#court_report_reminder\" do\n    context \"When no ID is passed\" do\n      let(:preview) { described_class.new }\n      let(:email) { preview.court_report_reminder }\n\n      it { expect(email.to).to eq [volunteer.email] }\n    end\n\n    context \"When passed ID is valid\" do\n      let(:preview) { described_class.new(id: volunteer.id) }\n      let(:email) { preview.court_report_reminder }\n\n      it { expect(email.to).to eq [volunteer.email] }\n    end\n\n    context \"When passed ID is invalid\" do\n      let(:preview) { described_class.new(id: -1) }\n      let(:email) { preview.court_report_reminder }\n\n      it { expect(email.to).to eq [\"missing_volunteer@example.com\"] }\n    end\n  end\n\n  describe \"#case_contacts_reminder\" do\n    context \"When no ID is passed\" do\n      let(:preview) { described_class.new }\n      let(:email) { preview.case_contacts_reminder }\n\n      it { expect(email.to).to eq [volunteer.email] }\n    end\n\n    context \"When passed ID is valid\" do\n      let(:preview) { described_class.new(id: volunteer.id) }\n      let(:email) { preview.case_contacts_reminder }\n\n      it { expect(email.to).to eq [volunteer.email] }\n    end\n\n    context \"When passed ID is invalid\" do\n      let(:preview) { described_class.new(id: -1) }\n      let(:email) { preview.case_contacts_reminder }\n\n      it { expect(email.to).to eq [\"missing_volunteer@example.com\"] }\n    end\n  end\nend\n"
  },
  {
    "path": "spec/mailers/supervisor_mailer_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe SupervisorMailer, type: :mailer do\n  describe \".weekly_digest\" do\n    let(:supervisor) { build(:supervisor, :receive_reimbursement_attachment) }\n    let(:volunteer) { build(:volunteer, casa_org: supervisor.casa_org, supervisor: supervisor) }\n    let(:casa_case) { create(:casa_case, casa_org: supervisor.casa_org) }\n\n    let(:mail) { SupervisorMailer.weekly_digest(supervisor) }\n\n    context \"when a supervisor has volunteer assigned to a casa case\" do\n      let!(:case_assignment) { create(:case_assignment, casa_case: casa_case, volunteer: volunteer) }\n\n      it \"shows a summary for a volunteer assigned to the supervisor\" do\n        expect(mail.body.encoded).to match(\"Summary for <a href=\\\"#{edit_volunteer_url(volunteer)}\\\">#{volunteer.display_name}</a>\")\n      end\n\n      it \"does not show a case contact that did not occurr in the week\" do\n        build_stubbed(:case_contact, casa_case: casa_case, occurred_at: Date.today - 8.days)\n        expect(mail.body.encoded).not_to match(\"Most recent contact attempted:\")\n      end\n\n      it \"shows the latest case contact that occurred in the week\" do\n        most_recent_contact = create(:case_contact, casa_case: casa_case, occurred_at: Date.today - 1.days, notes: \"AAAAAAAAAAAAAAAAAAAAAAAA\")\n        other_contact = build(:case_contact, casa_case: casa_case, occurred_at: Date.today - 3.days, notes: \"BBBBBBBBBBBBBBBBBBBB\")\n\n        expect(mail.body.encoded).to match(\"Notes: #{most_recent_contact.notes}\")\n        expect(mail.body.encoded).not_to match(\"Notes: #{other_contact.notes}\")\n      end\n\n      it \"has a CSV attachment\" do\n        expect(mail.attachments.count).to eq(1)\n      end\n    end\n\n    context \"when a supervisor has a volunteer who is unassigned from a casa case during the week\" do\n      let!(:case_assignment) { create(:case_assignment, casa_case: casa_case, volunteer: volunteer, active: false, updated_at: Date.today - 2.days) }\n\n      it \"shows a summary for a volunteer recently unassigned from the supervisor\" do\n        expect(mail.body.encoded).to match(\"Summary for <a href=\\\"#{edit_volunteer_url(volunteer)}\\\">#{volunteer.display_name}</a>\")\n      end\n\n      it \"shows a disclaimer for a volunteer recently unassigned from the supervisor\" do\n        expect(mail.body.encoded).to match(\"This case was unassigned from #{volunteer.display_name}\")\n      end\n\n      it \"does not show a case contact that occurred past the unassignment date in the week\" do\n        contact_past_unassignment = build_stubbed(:case_contact, casa_case: casa_case, occurred_at: Date.today - 1.days, notes: \"AAAAAAAAAAAAAAAAAAAAAAAA\")\n\n        expect(mail.body.encoded).not_to match(\"Notes: #{contact_past_unassignment.notes}\")\n      end\n\n      it \"shows the latest case contact that occurred in the week before the unassignment date\" do\n        contact_past_unassignment = create(:case_contact, casa_case: casa_case, occurred_at: Date.today - 1.days, notes: \"AAAAAAAAAAAAAAAAAAAAAAAA\")\n        most_recent_contact_before_unassignment = create(:case_contact, casa_case: casa_case, occurred_at: Date.today - 3.days, notes: \"BBBBBBBBBBBBBBBBBB\")\n        older_contact = build_stubbed(:case_contact, casa_case: casa_case, occurred_at: Date.today - 4.days, notes: \"CCCCCCCCCCCCCCCCCCCC\")\n\n        expect(mail.body.encoded).to match(\"Notes: #{most_recent_contact_before_unassignment.notes}\")\n        expect(mail.body.encoded).not_to match(\"Notes: #{contact_past_unassignment.notes}\")\n        expect(mail.body.encoded).not_to match(\"Notes: #{older_contact.notes}\")\n      end\n    end\n\n    it \"does not show a summary for a volunteer unassigned from the supervisor before the week\" do\n      create(:case_assignment, casa_case: casa_case, volunteer: volunteer, active: false, updated_at: Date.today - 8.days)\n      expect(mail.body.encoded).not_to match(\"Summary for #{volunteer.display_name}\")\n    end\n\n    context \"when a supervisor has pending volunteer to accepts invitation\" do\n      let(:volunteer2) { create(:volunteer) }\n\n      before do\n        volunteer2.invite!(supervisor)\n      end\n\n      it \"shows a summary of pending volunteers\" do\n        expect(mail.body.encoded).to match(volunteer2.display_name.to_s)\n      end\n\n      it \"has a button to re-invite volunteer\" do\n        expect(mail.body.encoded).to match(\"<a href=\\\"#{resend_invitation_volunteer_url(volunteer2)}\\\">\")\n      end\n\n      it \"do not shows a summary of pending volunteers if the volunteer already accepted\" do\n        volunteer2.invitation_accepted_at = DateTime.current\n        volunteer2.save\n\n        expect(mail.body.encoded).not_to match(volunteer2.display_name.to_s)\n      end\n\n      it \"does not display 'There are no pending volunteers' message when there are pending volunteers\" do\n        expect(mail.body.encoded).not_to match(\"There are no pending volunteers.\")\n      end\n    end\n\n    it \"displays no pending volunteers message when there are no pending volunteers\" do\n      expect(mail.body.encoded).to match(\"There are no pending volunteers.\")\n    end\n\n    it \"does not display 'There are no additional notes' message when there are additional notes\" do\n      create(:case_assignment, casa_case: casa_case, volunteer: volunteer, active: false, updated_at: Date.today + 8.days)\n      expect(mail.body.encoded).not_to match(\"There are no additional notes.\")\n    end\n\n    it \"displays no additional notes message when there are no additional notes\" do\n      expect(mail.body.encoded).to match(\"There are no additional notes.\")\n    end\n\n    it \"does not display no_recent_sign_in section\" do\n      expect(mail.body.encoded).not_to match(\"volunteers have not signed in or created case contacts in the last 30 days\")\n    end\n\n    context \"when supervisor has a volunteer that has not been active for the last 30 days\" do\n      let!(:volunteer) do\n        create(:volunteer, casa_org: supervisor.casa_org, supervisor: supervisor, last_sign_in_at: 31.days.ago)\n      end\n      let(:casa_org) { volunteer.casa_org }\n      let(:other_org) { create(:casa_org) }\n      let!(:volunteer_for_other_supervisor_same_org) { create(:volunteer, last_sign_in_at: 31.days.ago, casa_org: casa_org, supervisor: create(:supervisor, casa_org: casa_org)) }\n      let!(:volunteer_for_other_org) { create(:volunteer, last_sign_in_at: 31.days.ago, casa_org: other_org, supervisor: create(:supervisor, casa_org: other_org)) }\n\n      it \"display no_recent_sign_in section\" do\n        expect(mail.body.encoded).to match(\"volunteers have not signed in or created case contacts in the last 30 days\")\n        expect(mail.body.encoded).to match(volunteer.display_name)\n        expect(mail.body.encoded).not_to match(volunteer_for_other_supervisor_same_org.display_name)\n        expect(mail.body.encoded).not_to match(volunteer_for_other_org.display_name)\n      end\n\n      context \"but volunteer has a recent case_contact to its name\" do\n        let!(:recent_case_contact) do\n          create(:case_contact, casa_case: casa_case, occurred_at: 29.days.ago, creator: volunteer)\n        end\n\n        it \"does not display no_recent_sign_in section\" do\n          expect(mail.body.encoded).not_to match(\"volunteers have not signed in or created case contacts in the last 30 days\")\n        end\n      end\n    end\n  end\n\n  describe \".invitation_instructions for a supervisor\" do\n    let(:supervisor) { create(:supervisor) }\n    let(:mail) { supervisor.invite! }\n    let(:expiration_date) { I18n.l(supervisor.invitation_due_at, format: :full, default: nil) }\n\n    it \"informs the correct expiration date\" do\n      email_body = mail.html_part.body.to_s.squish\n      expect(email_body).to include(\"This invitation will expire on #{expiration_date} (two weeks).\")\n    end\n  end\n\n  describe \".reimbursement_request_email\" do\n    let(:supervisor) { create(:supervisor, receive_reimbursement_email: true) }\n    let(:volunteer) { create(:volunteer, supervisor: supervisor) }\n    let(:casa_organization) { volunteer.casa_org }\n\n    let(:mail) { SupervisorMailer.reimbursement_request_email(volunteer, supervisor) }\n\n    it \"sends email reminder\" do\n      expect(mail.subject).to eq(\"New reimbursement request from #{volunteer.display_name}\")\n      expect(mail.to).to eq([supervisor.email])\n      expect(mail.body.encoded).to match(\"#{volunteer.display_name} has submitted a reimbursement request\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/mailers/user_mailer_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe UserMailer, type: :mailer do\n  describe \"password_changed_reminder\" do\n    subject(:mail) { described_class.password_changed_reminder(user) }\n\n    let(:user) { create(:user) }\n\n    it \"renders the headers\", :aggregate_failures do\n      expect(mail.subject).to eq(\"CASA Password Changed\")\n      expect(mail.to).to eq([user.email])\n    end\n\n    it \"renders the body\", :aggregate_failures do\n      expect(mail.body.encoded).to match(\"Hello #{user.display_name}\")\n      expect(mail.body.encoded).to match(\"Your CASA password has been changed.\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/mailers/volunteer_mailer_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe VolunteerMailer, type: :mailer do\n  let(:volunteer) { create(:volunteer) }\n  let(:volunteer_with_supervisor) { create(:volunteer, :with_assigned_supervisor) }\n\n  describe \".account_setup\" do\n    let(:mail) { VolunteerMailer.account_setup(volunteer) }\n\n    it \"generates a password reset token and sends email\" do\n      expect(volunteer.reset_password_token).to be_nil\n      expect(volunteer.reset_password_sent_at).to be_nil\n      expect(mail.body.encoded.squish).to match(\"Set Your Password\")\n      expect(volunteer.reset_password_token).not_to be_nil\n      expect(volunteer.reset_password_sent_at).not_to be_nil\n    end\n  end\n\n  describe \".court_report_reminder\" do\n    let(:report_due_date) { Date.current + 7.days }\n    let(:mail) { VolunteerMailer.court_report_reminder(volunteer, report_due_date) }\n\n    it \"sends email reminder\" do\n      expect(mail.body.encoded).to match(\"next court report is due on #{report_due_date}\")\n    end\n  end\n\n  describe \".case_contacts_reminder\" do\n    it \"sends an email reminding volunteer\" do\n      mail = VolunteerMailer.case_contacts_reminder(volunteer, [])\n      expect(mail.body.encoded).to match(\"Hello #{volunteer.display_name}\")\n      expect(mail.body.encoded).to match(\"as a reminder\")\n      expect(mail.body.encoded).to include(case_contacts_url.to_s)\n      expect(mail.cc).to be_empty\n    end\n\n    it \"sends and cc's recipients\" do\n      cc_recipients = %w[supervisor@example.com admin@example.com]\n      mail = VolunteerMailer.case_contacts_reminder(volunteer, cc_recipients)\n      expect(mail.cc).to match_array(cc_recipients)\n    end\n  end\n\n  describe \".invitation_instructions for a volunteer\" do\n    let(:mail) { volunteer.invite! }\n    let(:expiration_date) { I18n.l(volunteer.invitation_due_at, format: :full, default: nil) }\n\n    it \"informs the correct expiration date\" do\n      email_body = mail.html_part.body.to_s.squish\n      expect(email_body).to include(\"This invitation will expire on #{expiration_date} (one year).\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/models/acts_as_paranoid_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"acts_as_paranoid\" do\n  let(:currently_probably_buggy_classes_ignored) do\n    %w[]\n  end\n  let(:allows_multiple_deleted) { \"(deleted_at IS NULL)\" }\n\n  it \"checks that all activerecord models using acts_as_paranoid have the deleted exclusions on unique indexes\" do\n    errors = []\n    found_ignored_error_indexes = []\n    Zeitwerk::Loader.eager_load_all\n    expect(ApplicationRecord.descendants.count).to be >= 54 # make sure we are actually testing all model classes\n    ApplicationRecord.descendants.each do |clazz|\n      next if clazz.abstract_class?\n      next unless clazz.paranoid?\n      unique_indexes = ApplicationRecord.connection_pool.with_connection do |connection|\n        connection.indexes(clazz.table_name).select(&:unique)\n      end\n      unique_indexes.each do |idx|\n        next if idx.columns == [\"external_id\"] # it is ok for external_id to be unique\n        if currently_probably_buggy_classes_ignored.include?(idx.name)\n          found_ignored_error_indexes << idx.name\n          next\n        end\n        unless idx.where&.include?(allows_multiple_deleted)\n          errors << \"#{idx.name} on #{clazz} uses acts_as_paranoid but has a unique index without #{allows_multiple_deleted} but it does have: #{idx.where}\"\n        end\n      end\n    end\n    expect(errors).to be_empty\n    expect(found_ignored_error_indexes).to match_array(currently_probably_buggy_classes_ignored)\n  end\nend\n"
  },
  {
    "path": "spec/models/additional_expense_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe AdditionalExpense, type: :model do\n  it { is_expected.to belong_to(:case_contact) }\n  it { is_expected.to have_one(:casa_case).through(:case_contact) }\n  it { is_expected.to have_one(:casa_org).through(:case_contact) }\n  it { is_expected.to have_one(:contact_creator).through(:case_contact) }\n  it { is_expected.to have_one(:contact_creator_casa_org).through(:contact_creator) }\n\n  describe \"validations\" do\n    let(:case_contact) { build_stubbed :case_contact }\n\n    it \"requires describe only if amount is positive\" do\n      expense = build(:additional_expense, amount: 0, describe: nil, case_contact:)\n      expect(expense).to be_valid\n      expense.update(amount: 1)\n      expect(expense).to be_invalid\n    end\n  end\nend\n"
  },
  {
    "path": "spec/models/address_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe Address, type: :model do\n  describe \"validate associations\" do\n    it { is_expected.to belong_to(:user) }\n  end\nend\n"
  },
  {
    "path": "spec/models/all_casa_admin_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe AllCasaAdmin, type: :model do\n  describe \"#role\" do\n    subject(:all_casa_admin) { create :all_casa_admin }\n\n    it { expect(all_casa_admin.role).to eq \"All Casa Admin\" }\n  end\nend\n"
  },
  {
    "path": "spec/models/all_casa_admins/casa_org_metrics_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe AllCasaAdmins::CasaOrgMetrics, type: :model do\n  let(:organization) { create :casa_org }\n  let(:user) { build(:all_casa_admin) }\n\n  describe \"#metrics\" do\n    subject { described_class.new(organization).metrics }\n\n    context \"minimal data\" do\n      it \"shows stats\" do\n        expect(subject).to eq(\n          {\n            \"Number of admins\" => 0,\n            \"Number of supervisors\" => 0,\n            \"Number of active volunteers\" => 0,\n            \"Number of inactive volunteers\" => 0,\n            \"Number of active cases\" => 0,\n            \"Number of inactive cases\" => 0,\n            \"Number of all case contacts including inactives\" => 0,\n            \"Number of active supervisor to volunteer assignments\" => 0,\n            \"Number of active case assignments\" => 0\n          }\n        )\n      end\n    end\n\n    context \"with inactives\" do\n      let(:obj_types) {\n        [\n          :casa_admin,\n          :supervisor,\n          :volunteer,\n          :casa_case,\n          :case_assignment,\n          :supervisor_volunteer\n        ]\n      }\n\n      before do\n        obj_types.each do |obj_type|\n          create(obj_type, casa_org: organization)\n          create(obj_type, :inactive, casa_org: organization)\n        end\n      end\n\n      it \"shows stats\" do\n        expect(subject).to eq(\n          {\n            \"Number of active case assignments\" => 1,\n            \"Number of active cases\" => 3,\n            \"Number of active supervisor to volunteer assignments\" => 6,\n            \"Number of active volunteers\" => 5,\n            \"Number of admins\" => 2,\n            \"Number of all case contacts including inactives\" => 1,\n            \"Number of inactive cases\" => 1,\n            \"Number of inactive volunteers\" => 1,\n            \"Number of supervisors\" => 4\n          }\n        )\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/models/api_credential_spec.rb",
    "content": "require \"rails_helper\"\nrequire \"digest\"\n\nRSpec.describe ApiCredential, type: :model do\n  let(:api_credential) { create(:api_credential, user: user) }\n  let(:user) { create(:user) }\n\n  it { is_expected.to belong_to(:user) }\n\n  describe \"#authenticate_api_token\" do\n    it \"returns true for a valid api_token\" do\n      api_token = api_credential.return_new_api_token![:api_token]\n      expect(api_credential.authenticate_api_token(api_token)).to be true\n    end\n\n    it \"returns false for an invalid api_token\" do\n      expect(api_credential.authenticate_api_token(\"invalid_token\")).to be false\n    end\n  end\n\n  describe \"#authenticate_refresh_token\" do\n    it \"returns true for a valid refresh_token\" do\n      refresh_token = api_credential.return_new_refresh_token!(false)[:refresh_token]\n      expect(api_credential.authenticate_refresh_token(refresh_token)).to be true\n    end\n\n    it \"returns false for an invalid refresh_token\" do\n      expect(api_credential.authenticate_refresh_token(\"invalid_token\")).to be false\n    end\n  end\n\n  describe \"#return_new_api_token!\" do\n    it \"updates the api_token digest\" do\n      old_digest = api_credential.api_token_digest\n      api_credential.return_new_api_token![:api_token]\n      api_credential.reload\n      expect(api_credential.api_token_digest).not_to eq(old_digest)\n    end\n\n    it \"sets a new api_token\" do\n      new_token = api_credential.return_new_api_token![:api_token]\n\n      expect(new_token).not_to be_nil\n    end\n  end\n\n  describe \"#return_new_refresh_token!\" do\n    it \"updates the refresh_token digest\" do\n      old_digest = api_credential.refresh_token_digest\n      api_credential.return_new_refresh_token!(false)[:refresh_token]\n      api_credential.reload\n      expect(api_credential.refresh_token_digest).not_to eq(old_digest)\n    end\n\n    it \"sets a new refresh_token\" do\n      new_token = api_credential.return_new_refresh_token!(false)[:refresh_token]\n\n      expect(new_token).not_to be_nil\n    end\n  end\n\n  describe \"#is_api_token_expired?\" do\n    it \"returns false if token is still valid\" do\n      api_credential.update!(token_expires_at: 1.hour.from_now)\n      expect(api_credential.is_api_token_expired?).to be false\n    end\n\n    it \"returns true if token is expired\" do\n      api_credential.update!(token_expires_at: 1.hour.ago)\n      expect(api_credential.is_api_token_expired?).to be true\n    end\n  end\n\n  describe \"#is_refresh_token_expired?\" do\n    it \"returns false if token is still valid\" do\n      api_credential.update!(refresh_token_expires_at: 1.hour.from_now)\n      expect(api_credential.is_refresh_token_expired?).to be false\n    end\n\n    it \"returns true if token is expired\" do\n      api_credential.update!(token_expires_at: 1.hour.ago)\n      expect(api_credential.is_api_token_expired?).to be true\n    end\n  end\n\n  describe \"#generate_api_token\" do\n    it \"creates a secure hashed api_token\" do\n      api_credential.api_token_digest\n      api_token = api_credential.return_new_api_token![:api_token]\n\n      expect(api_credential.api_token_digest).to eq(Digest::SHA256.hexdigest(api_token))\n    end\n  end\n\n  describe \"#generate_refresh_token\" do\n    it \"creates a secure hashed refresh_token\" do\n      api_credential.refresh_token_digest\n      refresh_token = api_credential.return_new_refresh_token!(false)[:refresh_token]\n\n      expect(api_credential.refresh_token_digest).to eq(Digest::SHA256.hexdigest(refresh_token))\n    end\n  end\n\n  describe \"#revoke_api_token\" do\n    it \"sets api token to nil\" do\n      api_credential.return_new_api_token![:api_token]\n      api_credential.revoke_api_token\n\n      expect(api_credential.api_token_digest).to be_nil\n    end\n  end\n\n  describe \"#revoke_refresh_token\" do\n    it \"sets refresh token to nil\" do\n      api_credential.return_new_refresh_token!(false)[:refresh_token]\n      api_credential.revoke_refresh_token\n\n      expect(api_credential.refresh_token_digest).to be_nil\n    end\n  end\n\n  describe \"#generate_refresh_token_with_rememberme\" do\n    it \"updates token to be valid for 1 year\" do\n      api_credential.refresh_token_digest\n      api_credential.return_new_refresh_token!(true)[:refresh_token]\n\n      expect(api_credential.refresh_token_expires_at).to be_within(1.minutes).of(1.year.from_now)\n    end\n  end\n\n  describe \"#generate_refresh_token_without_rememberme\" do\n    it \"updates token to be valid for 30 days\" do\n      api_credential.refresh_token_digest\n      api_credential.return_new_refresh_token!(false)[:refresh_token]\n\n      expect(api_credential.refresh_token_expires_at).to be_within(1.minutes).of(30.days.from_now)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/models/application_record_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe ApplicationRecord, type: :model do\n  # TODO: Add tests for ApplicationRecord\n\n  pending \"add some tests for ApplicationRecord\"\nend\n"
  },
  {
    "path": "spec/models/banner_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe Banner, type: :model do\n  describe \"#valid?\" do\n    let(:casa_org) { create(:casa_org) }\n    let(:supervisor) { create(:supervisor, casa_org: casa_org) }\n\n    it \"does not allow multiple active banners for same organization\" do\n      create(:banner, casa_org: casa_org, user: supervisor)\n\n      banner = build(:banner, casa_org: casa_org, user: supervisor)\n      expect(banner).not_to be_valid\n    end\n\n    it \"does allow multiple active banners for different organization\" do\n      create(:banner, casa_org: casa_org, user: supervisor)\n\n      another_org = create(:casa_org)\n      another_supervisor = create(:supervisor, casa_org: another_org)\n      banner = build(:banner, casa_org: another_org, user: another_supervisor)\n      expect(banner).to be_valid\n    end\n\n    it \"does not allow an expiry date set in the past\" do\n      banner = build(:banner, casa_org: casa_org, user: supervisor, expires_at: 1.hour.ago)\n      expect(banner).not_to be_valid\n    end\n\n    it \"allows an expiry date set in the future\" do\n      banner = build(:banner, casa_org: casa_org, user: supervisor, expires_at: 1.day.from_now)\n      expect(banner).to be_valid\n    end\n\n    it \"does not allow content to be empty\" do\n      banner = build(:banner, casa_org: casa_org, user: supervisor, content: nil)\n      expect(banner).not_to be_valid\n    end\n  end\n\n  describe \"#expired?\" do\n    it \"is false when expires_at is nil\" do\n      banner = create(:banner, expires_at: nil)\n\n      expect(banner).not_to be_expired\n    end\n\n    it \"is false when expires_at is set but is in the future\" do\n      banner = create(:banner, expires_at: 7.days.from_now)\n\n      expect(banner).not_to be_expired\n    end\n\n    it \"is true when expires_at is set but is in the past\" do\n      banner = create(:banner, expires_at: nil)\n      banner.update_columns(expires_at: 1.hour.ago)\n      expect(banner).to be_expired\n    end\n\n    it \"sets active to false when banner is expired\" do\n      banner = create(:banner, expires_at: 1.hour.from_now)\n      expect(banner.active).to be true\n      travel 2.hours\n      banner.expired?\n      expect(banner.active).to be false\n    end\n  end\n\n  describe \"#expires_at_in_time_zone\" do\n    it \"can shift time by timezone for equivalent times\" do\n      travel_to Time.new(2024, 6, 1, 11, 0, 0, \"UTC\")\n      banner = create(:banner, expires_at: \"2024-06-13 12:00:00 UTC\")\n\n      expires_at_in_pacific_time = banner.expires_at_in_time_zone(\"America/Los_Angeles\")\n      expect(expires_at_in_pacific_time.to_s).to eq(\"2024-06-13 05:00:00 -0700\")\n\n      expires_at_in_eastern_time = banner.expires_at_in_time_zone(\"America/New_York\")\n      expect(expires_at_in_eastern_time.to_s).to eq(\"2024-06-13 08:00:00 -0400\")\n\n      expect(expires_at_in_pacific_time).to eq(expires_at_in_eastern_time)\n      travel_back\n    end\n  end\nend\n"
  },
  {
    "path": "spec/models/casa_admin_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CasaAdmin, type: :model do\n  let(:casa_admin) { build(:casa_admin) }\n\n  describe \"#deactivate\" do\n    it \"deactivates the casa admin\" do\n      casa_admin.deactivate\n\n      casa_admin.reload\n      expect(casa_admin.active).to eq(false)\n    end\n\n    it \"activates the casa admin\" do\n      casa_admin.active = false\n      casa_admin.save\n      casa_admin.activate\n\n      casa_admin.reload\n      expect(casa_admin.active).to eq(true)\n    end\n  end\n\n  describe \"#role\" do\n    subject(:admin) { build(:casa_admin) }\n\n    it { expect(admin.role).to eq \"Casa Admin\" }\n  end\n\n  describe \"invitation expiration\" do\n    let!(:mail) { casa_admin.invite! }\n    let(:expiration_date) { I18n.l(casa_admin.invitation_due_at, format: :full, default: nil) }\n    let(:two_weeks) { I18n.l(2.weeks.from_now, format: :full, default: nil) }\n\n    it { expect(expiration_date).to eq two_weeks }\n\n    it \"expires invitation token after two weeks\" do\n      travel_to 2.weeks.from_now\n\n      user = User.accept_invitation!(invitation_token: casa_admin.invitation_token)\n      expect(user.errors.full_messages).to include(\"Invitation token is invalid\")\n      travel_back\n    end\n  end\n\n  describe \"change to supervisor\" do\n    subject(:admin) { create(:casa_admin) }\n\n    it \"returns true if the change was successful\" do\n      expect(subject.change_to_supervisor!).to be_truthy\n    end\n\n    it \"changes the supervisor to an admin\" do\n      subject.change_to_supervisor!\n\n      user = User.find(subject.id) # subject.reload will cause RecordNotFound because it's looking in the wrong table\n      expect(user).not_to be_casa_admin\n      expect(user).to be_supervisor\n    end\n  end\nend\n"
  },
  {
    "path": "spec/models/casa_case_contact_type_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CasaCaseContactType, type: :model do\n  it \"does not allow adding the same contact type twice to a case\" do\n    expect {\n      casa_case = create(:casa_case)\n      contact_type = create(:contact_type)\n\n      casa_case.contact_types << contact_type\n      casa_case.contact_types << contact_type\n    }.to raise_error(ActiveRecord::RecordInvalid)\n  end\nend\n"
  },
  {
    "path": "spec/models/casa_case_emancipation_category_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CasaCaseEmancipationCategory, type: :model do\n  it { is_expected.to belong_to(:casa_case) }\n  it { is_expected.to belong_to(:emancipation_category) }\n\n  it \"does not allow adding the same category twice to a case\" do\n    expect {\n      casa_case = create(:casa_case)\n      emancipation_category = create(:emancipation_category)\n\n      casa_case.emancipation_categories << emancipation_category\n      casa_case.emancipation_categories << emancipation_category\n    }.to raise_error(ActiveRecord::RecordInvalid)\n  end\n\n  it \"has a valid factory\" do\n    case_category_association = build(:casa_case_emancipation_category)\n    expect(case_category_association.valid?).to be true\n  end\nend\n"
  },
  {
    "path": "spec/models/casa_case_emancipation_option_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CasaCaseEmancipationOption, type: :model do\n  it { is_expected.to belong_to(:casa_case) }\n  it { is_expected.to belong_to(:emancipation_option) }\n\n  it \"does not allow adding the same category twice to a case\" do\n    expect {\n      casa_case = create(:casa_case)\n      emancipation_option = build(:emancipation_option)\n\n      casa_case.emancipation_options << emancipation_option\n      casa_case.emancipation_options << emancipation_option\n    }.to raise_error(ActiveRecord::RecordInvalid)\n  end\n\n  it \"has a valid factory\" do\n    case_option_association = build(:casa_case_emancipation_option)\n    expect(case_option_association.valid?).to be true\n  end\nend\n"
  },
  {
    "path": "spec/models/casa_case_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CasaCase, type: :model do\n  subject { build(:casa_case) }\n\n  it { is_expected.to have_many(:case_assignments).dependent(:destroy) }\n  it { is_expected.to belong_to(:casa_org) }\n  it { is_expected.to have_many(:casa_case_emancipation_categories).dependent(:destroy) }\n  it { is_expected.to have_many(:emancipation_categories).through(:casa_case_emancipation_categories) }\n  it { is_expected.to have_many(:casa_case_emancipation_options).dependent(:destroy) }\n  it { is_expected.to have_many(:emancipation_options).through(:casa_case_emancipation_options) }\n  it { is_expected.to have_many(:case_court_orders).dependent(:destroy) }\n  it { is_expected.to have_many(:volunteers).through(:case_assignments) }\n\n  describe \"validations\" do\n    describe \"case_number\" do\n      it { is_expected.to validate_presence_of(:case_number) }\n      it { is_expected.to validate_uniqueness_of(:case_number).scoped_to(:casa_org_id).case_insensitive }\n    end\n\n    describe \"date_in_care\" do\n      it \"is valid when blank\" do\n        casa_case = CasaCase.new(date_in_care: nil)\n        casa_case.valid?\n        expect(casa_case.errors[:date_in_care]).to eq([])\n      end\n\n      it \"is not valid before 1989\" do\n        casa_case = CasaCase.new(date_in_care: \"1984-01-01\".to_date)\n        expect(casa_case.valid?).to be false\n        expect(casa_case.errors[:date_in_care]).to eq([\"is not valid: Youth's Date in Care cannot be prior to 1/1/1989.\"])\n      end\n\n      it \"is not valid with a future date\" do\n        casa_case = CasaCase.new(date_in_care: 1.day.from_now)\n        expect(casa_case.valid?).to be false\n        expect(casa_case.errors[:date_in_care]).to eq([\"is not valid: Youth's Date in Care cannot be a future date.\"])\n      end\n\n      it \"is valid today\" do\n        casa_case = CasaCase.new(date_in_care: Time.current)\n        casa_case.valid?\n        expect(casa_case.errors[:date_in_care]).to eq([])\n      end\n\n      it \"is valid in the past after 1989\" do\n        casa_case = CasaCase.new(date_in_care: \"1997-08-29\".to_date)\n        casa_case.valid?\n        expect(casa_case.errors[:date_in_care]).to eq([])\n      end\n    end\n\n    describe \"birth_month_year_youth\" do\n      it { is_expected.to validate_presence_of(:birth_month_year_youth) }\n\n      it \"is not valid before 1989\" do\n        casa_case = CasaCase.new(birth_month_year_youth: \"1984-01-01\".to_date)\n        expect(casa_case.valid?).to be false\n        expect(casa_case.errors[:birth_month_year_youth]).to eq([\"is not valid: Youth's Birth Month & Year cannot be prior to 1/1/1989.\"])\n      end\n\n      it \"is not valid with a future date\" do\n        casa_case = CasaCase.new(birth_month_year_youth: 1.day.from_now)\n        expect(casa_case.valid?).to be false\n        expect(casa_case.errors[:birth_month_year_youth]).to eq([\"is not valid: Youth's Birth Month & Year cannot be a future date.\"])\n      end\n\n      it \"is valid today\" do\n        casa_case = CasaCase.new(birth_month_year_youth: Time.current)\n        casa_case.valid?\n        expect(casa_case.errors[:birth_month_year_youth]).to eq([])\n      end\n\n      it \"is valid in the past after 1989\" do\n        casa_case = CasaCase.new(birth_month_year_youth: \"1997-08-29\".to_date)\n        casa_case.valid?\n        expect(casa_case.errors[:birth_month_year_youth]).to eq([])\n      end\n    end\n  end\n\n  describe \"scopes\" do\n    describe \".due_date_passed\" do\n      subject { described_class.due_date_passed }\n\n      context \"when casa_case is present\" do\n        let!(:court_date) { create(:court_date, date: 3.days.ago) }\n        let(:casa_case) { court_date.casa_case }\n\n        it { is_expected.to include(casa_case) }\n      end\n\n      context \"when casa_case is not present\" do\n        let!(:court_date) { create(:court_date, date: 3.days.from_now) }\n        let(:casa_case) { court_date.casa_case }\n\n        it { is_expected.not_to include(casa_case) }\n      end\n    end\n\n    describe \".birthday_next_month\" do\n      subject { described_class.birthday_next_month }\n\n      context \"when a youth has a birthday next month\" do\n        let(:casa_case) { create(:casa_case, birth_month_year_youth: 10.years.ago + 1.month) }\n\n        it { is_expected.to include(casa_case) }\n      end\n\n      context \"when no youth has a birthday next month\" do\n        let(:casa_case) { create(:casa_case) }\n\n        it { is_expected.to be_empty }\n      end\n    end\n  end\n\n  describe \".unassigned_volunteers\" do\n    let!(:casa_case) { create(:casa_case) }\n    let!(:volunteer_same_org) { create(:volunteer, display_name: \"Yelena Belova\", casa_org: casa_case.casa_org) }\n    let!(:volunteer_same_org_1_with_cases) { create(:volunteer, :with_casa_cases, display_name: \"Natasha Romanoff\", casa_org: casa_case.casa_org) }\n    let!(:volunteer_same_org_2_with_cases) { create(:volunteer, :with_casa_cases, display_name: \"Melina Vostokoff\", casa_org: casa_case.casa_org) }\n    let!(:volunteer_different_org) { create(:volunteer, casa_org: create(:casa_org)) }\n\n    it \"only shows volunteers for the current volunteers organization\" do\n      expect(casa_case.unassigned_volunteers).to include(volunteer_same_org)\n      expect(casa_case.unassigned_volunteers).not_to include(volunteer_different_org)\n    end\n\n    it \"sorts volunteers by display name with no cases to the top\" do\n      expect(casa_case.unassigned_volunteers).to contain_exactly(volunteer_same_org, volunteer_same_org_2_with_cases, volunteer_same_org_1_with_cases)\n    end\n  end\n\n  describe \".ordered\" do\n    it \"orders the casa cases by updated at date\" do\n      very_old_casa_case = create(:casa_case, updated_at: 5.days.ago)\n      old_casa_case = create(:casa_case, updated_at: 1.day.ago)\n      new_casa_case = create(:casa_case)\n\n      ordered_casa_cases = described_class.ordered\n\n      expect(ordered_casa_cases.map(&:id)).to eq [new_casa_case.id, old_casa_case.id, very_old_casa_case.id]\n    end\n  end\n\n  describe \".actively_assigned_to\" do\n    it \"only returns cases actively assigned to a volunteer\" do\n      current_user = build(:volunteer)\n      inactive_case = build(:casa_case, casa_org: current_user.casa_org)\n      build_stubbed(:case_assignment, casa_case: inactive_case, volunteer: current_user, active: false)\n      active_cases = create_list(:casa_case, 2, casa_org: current_user.casa_org)\n      active_cases.each do |casa_case|\n        create(:case_assignment, casa_case: casa_case, volunteer: current_user, active: true)\n      end\n\n      other_user = build(:volunteer)\n      other_active_case = build(:casa_case, casa_org: other_user.casa_org)\n      other_inactive_case = build(:casa_case, casa_org: other_user.casa_org)\n      create(:case_assignment, casa_case: other_active_case, volunteer: other_user, active: true)\n      create(\n        :case_assignment,\n        casa_case: other_inactive_case, volunteer: other_user, active: false\n      )\n\n      assert_equal active_cases.map(&:case_number).sort, described_class.actively_assigned_to(current_user).map(&:case_number).sort\n    end\n  end\n\n  describe \".not_assigned\" do\n    it \"only returns cases NOT actively assigned to ANY volunteer\" do\n      current_user = create(:volunteer)\n\n      never_assigned_case = create(:casa_case, casa_org: current_user.casa_org)\n\n      inactive_case = create(:casa_case, casa_org: current_user.casa_org)\n      create(:case_assignment, casa_case: inactive_case, volunteer: current_user, active: false)\n      active_cases = create_list(:casa_case, 2, casa_org: current_user.casa_org)\n      active_cases.each do |casa_case|\n        create(:case_assignment, casa_case: casa_case, volunteer: current_user, active: true)\n      end\n\n      other_user = create(:volunteer)\n      other_active_case = create(:casa_case, casa_org: other_user.casa_org)\n      other_inactive_case = create(:casa_case, casa_org: other_user.casa_org)\n      create(:case_assignment, casa_case: other_active_case, volunteer: other_user, active: true)\n      create(\n        :case_assignment,\n        casa_case: other_inactive_case, volunteer: other_user, active: false\n      )\n\n      expect(described_class.not_assigned(current_user.casa_org)).to contain_exactly(never_assigned_case, inactive_case, other_inactive_case)\n    end\n  end\n\n  describe \".should_transition\" do\n    it \"returns only youth who should have transitioned but have not\" do\n      not_transitioned_13_yo = create(:casa_case,\n        birth_month_year_youth: Date.current - 13.years)\n      already_transitioned_15_yo = create(:casa_case,\n        birth_month_year_youth: Date.current - 15.years)\n      should_transition_14_yo = create(:casa_case,\n        birth_month_year_youth: Date.current - 14.years)\n      cases = CasaCase.should_transition\n      aggregate_failures do\n        expect(cases.length).to eq 2\n        expect(cases.include?(should_transition_14_yo)).to eq true\n        expect(cases.include?(already_transitioned_15_yo)).to eq true\n        expect(cases.include?(not_transitioned_13_yo)).to eq false\n      end\n    end\n  end\n\n  describe \"#active_case_assignments\" do\n    it \"only includes active assignments\" do\n      casa_org = create(:casa_org)\n      casa_case = create(:casa_case, casa_org: casa_org)\n      case_assignments = 2.times.map { create(:case_assignment, casa_case: casa_case, volunteer: create(:volunteer, casa_org: casa_org)) }\n\n      expect(casa_case.active_case_assignments).to match_array case_assignments\n\n      case_assignments.first.update(active: false)\n      expect(casa_case.reload.active_case_assignments).to eq [case_assignments.last]\n    end\n  end\n\n  describe \"#add_emancipation_category\" do\n    let(:casa_case) { create(:casa_case) }\n    let(:emancipation_category) { create(:emancipation_category) }\n\n    it \"associates an emancipation category with the case when passed the id of the category\" do\n      expect {\n        casa_case.add_emancipation_category(emancipation_category.id)\n      }.to change { casa_case.emancipation_categories.count }.from(0).to(1)\n    end\n  end\n\n  describe \"#add_emancipation_option\" do\n    let(:casa_case) { create(:casa_case) }\n    let(:emancipation_category) { build(:emancipation_category, mutually_exclusive: true) }\n    let(:emancipation_option_a) { create(:emancipation_option, emancipation_category: emancipation_category) }\n    let(:emancipation_option_b) { create(:emancipation_option, emancipation_category: emancipation_category, name: \"Not the same name as option A to satisfy unique contraints\") }\n\n    it \"associates an emancipation option with the case when passed the id of the option\" do\n      expect {\n        casa_case.add_emancipation_option(emancipation_option_a.id)\n      }.to change { casa_case.emancipation_options.count }.from(0).to(1)\n    end\n\n    it \"raises an error when attempting to add multiple options belonging to a mutually exclusive category\" do\n      expect {\n        casa_case.add_emancipation_option(emancipation_option_a.id)\n        casa_case.add_emancipation_option(emancipation_option_b.id)\n      }.to raise_error(\"Attempted adding multiple options belonging to a mutually exclusive category\")\n    end\n  end\n\n  describe \"#assigned_volunteers\" do\n    let(:casa_org) { create(:casa_org) }\n    let(:casa_case) { build(:casa_case, casa_org: casa_org) }\n    let(:volunteer1) { build(:volunteer, casa_org: casa_org) }\n    let(:volunteer2) { build(:volunteer, casa_org: casa_org) }\n    let!(:case_assignment1) { create(:case_assignment, casa_case: casa_case, volunteer: volunteer1) }\n    let!(:case_assignment2) { create(:case_assignment, casa_case: casa_case, volunteer: volunteer2) }\n\n    it \"only includes volunteers through active assignments\" do\n      expect(casa_case.assigned_volunteers.order(:id)).to eq [volunteer1, volunteer2].sort_by(&:id)\n\n      case_assignment1.update(active: false)\n      expect(casa_case.reload.assigned_volunteers).to eq [volunteer2]\n    end\n\n    it \"only includes active volunteers\" do\n      expect(casa_case.assigned_volunteers.order(:id)).to eq [volunteer1, volunteer2].sort_by(&:id)\n\n      volunteer1.update(active: false)\n      expect(casa_case.reload.assigned_volunteers).to eq [volunteer2]\n    end\n  end\n\n  describe \"#clear_court_dates\" do\n    context \"when court date has passed\" do\n      it \"sets court report as unsubmitted\" do\n        casa_case = build(:casa_case, court_report_status: :submitted)\n        casa_case.clear_court_dates\n\n        expect(casa_case.court_report_status).to eq \"not_submitted\"\n      end\n    end\n  end\n\n  describe \"#court_report_status\" do\n    subject { casa_case.court_report_status = court_report_status }\n\n    let(:casa_case) { build(:casa_case) }\n\n    let(:submitted_time) { Time.parse(\"Sun Nov 08 11:06:20 2020\") }\n    let(:the_future) { submitted_time + 2.days }\n\n    before do\n      travel_to submitted_time\n    end\n\n    context \"when the case is already submitted\" do\n      let(:casa_case) { build(:casa_case, court_report_status: :submitted, court_report_submitted_at: submitted_time) }\n\n      before do\n        travel_to the_future\n      end\n\n      context \"when the status is completed\" do\n        let(:court_report_status) { :completed }\n\n        it \"completes the court report and does not update time\" do\n          expect(subject).to eq :completed\n          expect(casa_case.court_report_submitted_at).to eq(submitted_time)\n        end\n      end\n\n      context \"when the status is not_submitted\" do\n        let(:court_report_status) { :not_submitted }\n\n        it \"clears submission date and value\" do\n          expect(subject).to eq :not_submitted\n          expect(casa_case.court_report_submitted_at).to be_nil\n        end\n      end\n    end\n\n    context \"when status is submitted\" do\n      let(:court_report_status) { :submitted }\n\n      it \"tracks the court report submission\" do\n        expect(subject).to eq :submitted\n        expect(casa_case.court_report_submitted_at).to eq(submitted_time)\n      end\n    end\n\n    context \"when the status is in review\" do\n      let(:court_report_status) { :in_review }\n\n      it \"tracks the court report submission\" do\n        expect(subject).to eq :in_review\n        expect(casa_case.court_report_submitted_at).to eq(submitted_time)\n      end\n    end\n  end\n\n  describe \"#most_recent_past_court_date\" do\n    let(:casa_case) { create(:casa_case) }\n\n    it \"returns the latest past court date\" do\n      most_recent_past_court_date = create(:court_date, date: 3.months.ago)\n\n      casa_case.court_dates << create(:court_date, date: 9.months.ago)\n      casa_case.court_dates << most_recent_past_court_date\n      casa_case.court_dates << create(:court_date, date: 15.months.ago)\n\n      expect(casa_case.most_recent_past_court_date).to eq(most_recent_past_court_date)\n    end\n  end\n\n  describe \"#formatted_latest_court_date\" do\n    let(:casa_case) { create(:casa_case) }\n\n    before do\n      travel_to Date.new(2021, 1, 1)\n    end\n\n    context \"with a past court date\" do\n      it \"returns the latest past court date as a formatted string\" do\n        most_recent_past_court_date = create(:court_date, date: 3.months.ago)\n\n        casa_case.court_dates << create(:court_date, date: 9.months.ago)\n        casa_case.court_dates << most_recent_past_court_date\n        casa_case.court_dates << create(:court_date, date: 15.months.ago)\n\n        expect(casa_case.formatted_latest_court_date).to eq(\"October 01, 2020\") # 3 months before 1/1/21\n      end\n    end\n\n    context \"without a past court date\" do\n      it \"returns the current day as a formatted string\" do\n        allow(casa_case).to receive(:most_recent_past_court_date).and_return(nil)\n\n        expect(casa_case.formatted_latest_court_date).to eq(\"January 01, 2021\")\n      end\n    end\n  end\n\n  describe \"#remove_emancipation_category\" do\n    let(:casa_case) { create(:casa_case) }\n    let(:emancipation_category) { build(:emancipation_category) }\n\n    it \"dissociates an emancipation category with the case when passed the id of the category\" do\n      casa_case.emancipation_categories << emancipation_category\n\n      expect {\n        casa_case.remove_emancipation_category(emancipation_category.id)\n      }.to change { casa_case.emancipation_categories.count }.from(1).to(0)\n    end\n  end\n\n  describe \"#remove_emancipation_option\" do\n    let(:casa_case) { create(:casa_case) }\n    let(:emancipation_option) { build(:emancipation_option) }\n\n    it \"dissociates an emancipation option with the case when passed the id of the option\" do\n      casa_case.emancipation_options << emancipation_option\n\n      expect {\n        casa_case.remove_emancipation_option(emancipation_option.id)\n      }.to change { casa_case.emancipation_options.count }.from(1).to(0)\n    end\n  end\n\n  describe \"#update_cleaning_contact_types\" do\n    it \"cleans up contact types before saving\" do\n      group = build(:contact_type_group)\n      type1 = build(:contact_type, contact_type_group: group)\n      type2 = create(:contact_type, contact_type_group: group)\n\n      casa_case = create(:casa_case, contact_types: [type1])\n\n      expect(casa_case.casa_case_contact_types.count).to be 1\n      expect(casa_case.contact_types).to contain_exactly(type1)\n\n      casa_case.update_cleaning_contact_types({contact_type_ids: [type2.id]})\n\n      expect(casa_case.casa_case_contact_types.count).to be 1\n      expect(casa_case.contact_types.reload).to contain_exactly(type2)\n    end\n  end\n\n  describe \"report submission\" do\n    let(:bad_case) { build(:casa_case) }\n\n    # Creating a case whith a status other than not_submitted and a nil submission date\n    it \"rejects cases with a court report status, but no submission date\" do\n      bad_case.court_report_status = :in_review\n      bad_case.court_report_submitted_at = nil\n      bad_case.valid?\n\n      expect(bad_case.errors[:court_report_status]).to include(\n        \"Court report submission date can't be nil if status is anything but not_submitted.\"\n      )\n    end\n\n    it \"rejects cases with a submission date, but no status\" do\n      bad_case.court_report_status = :not_submitted\n      bad_case.court_report_submitted_at = DateTime.now\n      bad_case.valid?\n\n      expect(bad_case.errors[:court_report_submitted_at]).to include(\n        \"Submission date must be nil if court report status is not submitted.\"\n      )\n    end\n  end\n\n  describe \"slug\" do\n    let(:casa_case) { create(:casa_case, case_number: \"CINA-21-1234\") }\n\n    it \"is parameterized from the case number\" do\n      expect(casa_case.slug).to eq \"cina-21-1234\"\n    end\n\n    it \"updates when the case number changes\" do\n      casa_case.case_number = \"CINA-21-1234-changed\"\n      casa_case.save\n      expect(casa_case.slug).to eq \"cina-21-1234-changed\"\n    end\n  end\nend\n"
  },
  {
    "path": "spec/models/casa_org_spec.rb",
    "content": "require \"rails_helper\"\nrequire \"support/stubbed_requests/webmock_helper\"\n\nRSpec.describe CasaOrg, type: :model do\n  it { is_expected.to validate_presence_of(:name) }\n  it { is_expected.to have_many(:users).dependent(:destroy) }\n  it { is_expected.to have_many(:casa_cases).dependent(:destroy) }\n  it { is_expected.to have_many(:contact_type_groups).dependent(:destroy) }\n  it { is_expected.to have_many(:hearing_types).dependent(:destroy) }\n  it { is_expected.to have_many(:mileage_rates).dependent(:destroy) }\n  it { is_expected.to have_many(:case_assignments).through(:users) }\n  it { is_expected.to have_one_attached(:logo) }\n  it { is_expected.to have_one_attached(:court_report_template) }\n  it { is_expected.to have_many(:contact_topics) }\n  it { is_expected.to have_many(:custom_org_links).dependent(:destroy) }\n\n  it \"has unique name\" do\n    org = create(:casa_org)\n    new_org = build(:casa_org, name: org.name)\n    expect(new_org.valid?).to be false\n  end\n\n  describe \"CasaOrgValidator\" do\n    let(:casa_org) { build(:casa_org) }\n\n    it \"delegates phone validation to PhoneNumberHelper\" do\n      expect_any_instance_of(PhoneNumberHelper).to receive(:valid_phone_number).once.with(casa_org.twilio_phone_number)\n      casa_org.valid?\n    end\n  end\n\n  describe \"validate validate_twilio_credentials\" do\n    let(:casa_org) { create(:casa_org, twilio_enabled: true) }\n    let(:twilio_rest_error) do\n      error_response = double(\"error_response\", status_code: 401, body: {})\n      Twilio::REST::RestError.new(\"Error message\", error_response)\n    end\n\n    it \"validates twillio credentials on update\", :aggregate_failures do\n      twillio_client = instance_double(Twilio::REST::Client)\n      allow(Twilio::REST::Client).to receive(:new).and_return(twillio_client)\n      allow(twillio_client).to receive_message_chain(:messages, :list).and_raise(twilio_rest_error)\n\n      %i[twilio_account_sid twilio_api_key_sid twilio_api_key_secret].each do |field|\n        update_successful = casa_org.update(field => \"\")\n        aggregate_failures do\n          expect(update_successful).to be false\n          expect(casa_org.errors[:base]).to eq [\"Your Twilio credentials are incorrect, kindly check and try again.\"]\n        end\n      end\n    end\n\n    it \"returns error if credentials form invalid URI\" do\n      twillio_client = instance_double(Twilio::REST::Client)\n      allow(Twilio::REST::Client).to receive(:new).and_return(twillio_client)\n      allow(twillio_client).to receive_message_chain(:messages, :list).and_raise(URI::InvalidURIError)\n\n      casa_org.update(twilio_account_sid: \"some bad value\")\n\n      aggregate_failures do\n        expect(casa_org).not_to be_valid\n        expect(casa_org.errors[:base]).to eq [\"Your Twilio credentials are incorrect, kindly check and try again.\"]\n      end\n    end\n\n    context \"org with disabled twilio\" do\n      let(:casa_org) { create(:casa_org, twilio_enabled: false) }\n\n      it \"validates twillio credentials on update\", :aggregate_failures do\n        %i[twilio_account_sid twilio_api_key_sid twilio_api_key_secret].each do |field|\n          expect(casa_org.update(field => \"\")).to be true\n        end\n      end\n    end\n  end\n\n  describe \"Attachment\" do\n    it \"is valid\" do\n      aggregate_failures do\n        subject = build(:casa_org, twilio_enabled: false)\n\n        expect(subject.org_logo).to eq(Pathname.new(\"#{Rails.public_path.join(\"logo.jpeg\")}\"))\n\n        subject.logo.attach(\n          io: File.open(file_fixture(\"company_logo.png\")),\n          filename: \"company_logo.png\", content_type: \"image/png\"\n        )\n\n        subject.save!\n\n        expect(subject.logo).to be_an_instance_of(ActiveStorage::Attached::One)\n        expect(subject.org_logo).to eq(\"/rails/active_storage/blobs/redirect/#{subject.logo.signed_id}/#{subject.logo.filename}\")\n      end\n    end\n  end\n\n  context \"when creating an organization\" do\n    let(:org) { create(:casa_org, name: \"Prince George CASA\") }\n\n    it \"has a slug based on the name\" do\n      expect(org.slug).to eq \"prince-george-casa\"\n    end\n  end\n\n  describe \"generate_defaults\" do\n    let(:org) { create(:casa_org) }\n    let(:fake_topics) { [{\"question\" => \"Test Title\", \"details\" => \"Test details\"}] }\n\n    before do\n      allow(ContactTopic).to receive(:default_contact_topics).and_return(fake_topics)\n      org.generate_defaults\n    end\n\n    describe \"generates default contact type groups\" do\n      let(:groups) { ContactTypeGroup.where(casa_org: org).joins(:contact_types).pluck(:name, \"contact_types.name\").sort }\n\n      it \"matches default contact type groups\" do\n        expect(groups).to eq([[\"CASA\", \"Supervisor\"],\n          [\"CASA\", \"Youth\"],\n          [\"Education\", \"Guidance Counselor\"],\n          [\"Education\", \"IEP Team\"],\n          [\"Education\", \"School\"],\n          [\"Education\", \"Teacher\"],\n          [\"Family\", \"Aunt Uncle or Cousin\"],\n          [\"Family\", \"Fictive Kin\"],\n          [\"Family\", \"Grandparent\"],\n          [\"Family\", \"Other Family\"],\n          [\"Family\", \"Parent\"],\n          [\"Family\", \"Sibling\"],\n          [\"Health\", \"Medical Professional\"],\n          [\"Health\", \"Mental Health Therapist\"],\n          [\"Health\", \"Other Therapist\"],\n          [\"Health\", \"Psychiatric Practitioner\"],\n          [\"Legal\", \"Attorney\"],\n          [\"Legal\", \"Court\"],\n          [\"Placement\", \"Caregiver Family\"],\n          [\"Placement\", \"Foster Parent\"],\n          [\"Placement\", \"Therapeutic Agency Worker\"],\n          [\"Social Services\", \"Social Worker\"]])\n      end\n    end\n\n    describe \"generates default hearing types\" do\n      let(:hearing_types_names) { HearingType.where(casa_org: org).pluck(:name) }\n\n      it \"matches default hearing types\" do\n        expect(hearing_types_names).to include(*HearingType::DEFAULT_HEARING_TYPES)\n      end\n    end\n\n    describe \"generates default contact topics\" do\n      let(:contact_topics) { ContactTopic.where(casa_org: org).map(&:question) }\n\n      it \"matches default contact topics\" do\n        expected = fake_topics.pluck(\"question\")\n        expect(contact_topics).to include(*expected)\n      end\n    end\n  end\n\n  describe \"mileage rate for a given date\" do\n    let(:casa_org) { build(:casa_org) }\n\n    describe \"with a casa org with no rates\" do\n      it \"is nil\" do\n        expect(casa_org.mileage_rate_for_given_date(Date.today)).to be_nil\n      end\n    end\n\n    describe \"with a casa org with inactive dates\" do\n      let!(:mileage_rates) do\n        [\n          create(:mileage_rate, casa_org: casa_org, effective_date: 10.days.ago, is_active: false),\n          create(:mileage_rate, casa_org: casa_org, effective_date: 3.days.ago, is_active: false)\n        ]\n      end\n\n      it \"is nil\" do\n        expect(casa_org.mileage_rates.count).to eq 2\n        expect(casa_org.mileage_rate_for_given_date(Date.today)).to be_nil\n      end\n    end\n\n    describe \"with active dates in the future\" do\n      let!(:mileage_rate) { create(:mileage_rate, casa_org: casa_org, effective_date: 3.days.from_now) }\n\n      it \"is nil\" do\n        expect(casa_org.mileage_rates.count).to eq 1\n        expect(casa_org.mileage_rate_for_given_date(Date.today)).to be_nil\n      end\n    end\n\n    describe \"with active dates in the past\" do\n      let!(:mileage_rates) do\n        [\n          create(:mileage_rate, casa_org: casa_org, amount: 4.50, effective_date: 20.days.ago),\n          create(:mileage_rate, casa_org: casa_org, amount: 5.50, effective_date: 10.days.ago),\n          create(:mileage_rate, casa_org: casa_org, amount: 6.50, effective_date: 3.days.ago)\n        ]\n      end\n\n      it \"uses the most recent date\" do\n        expect(casa_org.mileage_rate_for_given_date(12.days.ago.to_date)).to eq 4.50\n        expect(casa_org.mileage_rate_for_given_date(5.days.ago.to_date)).to eq 5.50\n        expect(casa_org.mileage_rate_for_given_date(Date.today)).to eq 6.50\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/models/case_assignment_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CaseAssignment, type: :model do\n  let(:casa_org_1) { create(:casa_org) }\n  let(:casa_case_1) { create(:casa_case, casa_org: casa_org_1) }\n  let(:volunteer_1) { create(:volunteer, casa_org: casa_org_1) }\n  let(:inactive) { create(:volunteer, :inactive, casa_org: casa_org_1) }\n  let(:supervisor) { create(:supervisor, casa_org: casa_org_1) }\n  let(:casa_case_2) { create(:casa_case, casa_org: casa_org_1) }\n  let(:volunteer_2) { create(:volunteer, casa_org: casa_org_1) }\n  let(:casa_org_2) { create(:casa_org) }\n\n  it \"only allow active volunteers to be assigned\" do\n    expect(casa_case_1.case_assignments.new(volunteer: volunteer_1)).to be_valid\n    casa_case_1.reload\n\n    expect(casa_case_1.case_assignments.new(volunteer: inactive)).to be_invalid\n    casa_case_1.reload\n\n    expect(casa_case_1.case_assignments.new(volunteer: supervisor)).to be_invalid\n  end\n\n  it \"allows two volunteers to be assigned to the same case\" do\n    casa_case_1.volunteers << volunteer_1\n    casa_case_1.volunteers << volunteer_2\n    casa_case_1.save!\n\n    expect(volunteer_1.casa_cases).to eq([casa_case_1])\n    expect(volunteer_2.casa_cases).to eq([casa_case_1])\n  end\n\n  it \"allows volunteer to be assigned to multiple cases\" do\n    volunteer_1.casa_cases << casa_case_1\n    volunteer_1.casa_cases << casa_case_2\n    volunteer_1.save!\n\n    expect(casa_case_1.reload.volunteers).to eq([volunteer_1])\n    expect(casa_case_2.reload.volunteers).to eq([volunteer_1])\n  end\n\n  it \"does not allow a volunteer to be double assigned\" do\n    expect {\n      volunteer_1.casa_cases << casa_case_1\n      volunteer_1.casa_cases << casa_case_1\n    }.to raise_error(ActiveRecord::RecordInvalid)\n  end\n\n  it \"requires case and volunteer belong to the same organization\" do\n    case_assignment = casa_case_1.case_assignments.new(volunteer: volunteer_1)\n    expect { volunteer_1.update(casa_org: casa_org_2) }.to change(case_assignment, :valid?).to false\n  end\n\n  describe \".active\" do\n    it \"only includes active case assignments\" do\n      casa_case = create(:casa_case)\n      case_assignments = 2.times.map { create(:case_assignment, casa_case: casa_case, volunteer: create(:volunteer, casa_org: casa_case.casa_org)) }\n      expect(CaseAssignment.active).to match_array(case_assignments)\n\n      case_assignments.first.update(active: false)\n      expect(CaseAssignment.active).to eq [case_assignments.last]\n    end\n  end\nend\n"
  },
  {
    "path": "spec/models/case_contact_contact_type_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CaseContactContactType, type: :model do\n  it \"does not allow adding the same contact type twice to a case contact\" do\n    expect {\n      case_contact = create(:case_contact)\n      contact_type = create(:contact_type)\n\n      case_contact.contact_types << contact_type\n      case_contact.contact_types << contact_type\n    }.to raise_error(ActiveRecord::RecordInvalid)\n  end\nend\n"
  },
  {
    "path": "spec/models/case_contact_report_spec.rb",
    "content": "require \"rails_helper\"\nrequire \"csv\"\nRSpec.describe CaseContactReport, type: :model do\n  describe \"#generate_headers\" do\n    it \"matches the length of row data\" do\n      create(:case_contact)\n      csv = described_class.new.to_csv\n      parsed_csv = CSV.parse(csv, headers: true)\n\n      expect(parsed_csv.length).to eq(1) # length doesn't include header row\n      expect(parsed_csv.headers).to eq([\n        \"Internal Contact Number\",\n        \"Duration Minutes\",\n        \"Contact Types\",\n        \"Contact Made\",\n        \"Contact Medium\",\n        \"Occurred At\",\n        \"Added To System At\",\n        \"Miles Driven\",\n        \"Wants Driving Reimbursement\",\n        \"Casa Case Number\",\n        \"Creator Email\",\n        \"Creator Name\",\n        \"Supervisor Name\",\n        \"Case Contact Notes\"\n      ])\n\n      case_contact_data = parsed_csv.first\n      expect(parsed_csv.headers.length).to eq(case_contact_data.length)\n    end\n  end\n\n  describe \"CSV body serialization\" do\n    subject { CaseContactReport.new(casa_org_id: long_case_contact.casa_case.casa_org.id).to_csv }\n\n    let!(:long_case_contact) { create(:case_contact, :long_note) }\n    let!(:multi_line_case_contact) { create(:case_contact, :multi_line_note, casa_case: long_case_contact.casa_case) }\n\n    it \"includes entire note\" do\n      expect(subject).to include(long_case_contact.notes)\n      expect(subject).to include(multi_line_case_contact.notes)\n    end\n  end\n\n  describe \"filter behavior\" do\n    describe \"casa organization\" do\n      let(:casa_org) { create(:casa_org) }\n      let(:casa_case) { create(:casa_case, casa_org: casa_org) }\n      let(:case_contact) { create(:case_contact, casa_case: casa_case) }\n\n      it \"includes case contacts from current org\" do\n        report = CaseContactReport.new(casa_org_id: casa_org.id)\n\n        expect(report.case_contacts).to contain_exactly(case_contact)\n      end\n\n      context \"from other orgs\" do\n        let(:other_casa_org) { create(:casa_org) }\n        let(:casa_case) { create(:casa_case, casa_org: other_casa_org) }\n\n        it \"excludes case contacts\" do\n          report = CaseContactReport.new(casa_org_id: casa_org.id)\n\n          expect(report.case_contacts).to be_empty\n        end\n      end\n    end\n\n    context \"when result is empty\" do\n      it \"returns only headers if result is empty\" do\n        report = CaseContactReport.new(\n          {\n            \"start_date\" => 1.days.ago,\n            \"end_date\" => 1.days.ago,\n            \"contact_made\" => true,\n            \"has_transitioned\" => true,\n            \"want_driving_reimbursement\" => true,\n            \"contact_type_ids\" => [\"4\"],\n            \"contact_type_group_ids\" => [\"2\", \"3\"],\n            \"supervisor_ids\" => [\"2\"]\n          }\n        )\n        contacts = report.case_contacts\n\n        expect(report.to_csv).to eq(\n          \"Internal Contact Number,Duration Minutes,Contact Types,Contact Made,Contact Medium,Occurred At,Added To System At,Miles Driven,Wants Driving Reimbursement,Casa Case Number,Creator Email,Creator Name,Supervisor Name,Case Contact Notes\\n\"\n        )\n        expect(contacts.length).to eq(0)\n      end\n    end\n\n    context \"when result is not empty\" do\n      describe \"occured at range filter\" do\n        it \"uses date range if provided\" do\n          create(:case_contact, {occurred_at: 20.days.ago})\n          build(:case_contact, {occurred_at: 100.days.ago})\n          report = CaseContactReport.new({start_date: 30.days.ago, end_date: 10.days.ago})\n          contacts = report.case_contacts\n          expect(contacts.length).to eq(1)\n        end\n\n        it \"returns all date ranges if not provided\" do\n          create(:case_contact, {occurred_at: 20.days.ago})\n          create(:case_contact, {occurred_at: 100.days.ago})\n          report = CaseContactReport.new({})\n          contacts = report.case_contacts\n          expect(contacts.length).to eq(2)\n        end\n\n        it \"returns only the volunteer\" do\n          volunteer = create(:volunteer)\n          create(:case_contact, {occurred_at: 20.days.ago, creator_id: volunteer.id})\n          build(:case_contact, {occurred_at: 100.days.ago})\n          report = CaseContactReport.new({creator_ids: [volunteer.id]})\n          contacts = report.case_contacts\n          expect(contacts.length).to eq(1)\n        end\n\n        it \"returns only the volunteer with date range\" do\n          volunteer = create(:volunteer)\n          create(:case_contact, {occurred_at: 20.days.ago, creator_id: volunteer.id})\n          create(:case_contact, {occurred_at: 100.days.ago, creator_id: volunteer.id})\n\n          build(:case_contact, {occurred_at: 100.days.ago})\n          report = CaseContactReport.new({start_date: 30.days.ago, end_date: 10.days.ago, creator_ids: [volunteer.id]})\n          contacts = report.case_contacts\n          expect(contacts.length).to eq(1)\n        end\n\n        it \"returns only the volunteer with the specified supervisors\" do\n          casa_org = build(:casa_org)\n          supervisor = create(:supervisor, casa_org: casa_org)\n          volunteer = build(:volunteer, casa_org: casa_org)\n          volunteer2 = create(:volunteer, casa_org: casa_org)\n          create(:supervisor_volunteer, volunteer: volunteer, supervisor: supervisor)\n\n          contact = create(:case_contact, {occurred_at: 20.days.ago, creator_id: volunteer.id})\n          build_stubbed(:case_contact, {occurred_at: 100.days.ago, creator_id: volunteer2.id})\n\n          build_stubbed(:case_contact, {occurred_at: 100.days.ago})\n          report = CaseContactReport.new({supervisor_ids: [supervisor.id]})\n          contacts = report.case_contacts\n          expect(contacts.length).to eq(1)\n          expect(contacts).to eq([contact])\n        end\n      end\n    end\n\n    describe \"case contact behavior\" do\n      before do\n        create(:case_contact, {contact_made: true})\n        create(:case_contact, {contact_made: false})\n      end\n\n      it \"returns only the case contacts with where contact was made\" do\n        report = CaseContactReport.new({contact_made: true})\n        contacts = report.case_contacts\n        expect(contacts.length).to eq(1)\n      end\n\n      it \"returns only the case contacts with where contact was NOT made\" do\n        report = CaseContactReport.new({contact_made: false})\n        contacts = report.case_contacts\n        expect(contacts.length).to eq(1)\n      end\n\n      it \"returns only the case contacts with where contact was made or NOT made\" do\n        report = CaseContactReport.new({contact_made: [true, false]})\n        contacts = report.case_contacts\n        expect(contacts.length).to eq(2)\n      end\n    end\n\n    describe \"has transitioned behavior\" do\n      let(:case_case_1) { create(:casa_case, birth_month_year_youth: 15.years.ago) }\n      let(:case_case_2) { create(:casa_case, birth_month_year_youth: 10.years.ago) }\n\n      before do\n        create(:case_contact, {casa_case: case_case_1})\n        create(:case_contact, {casa_case: case_case_2})\n      end\n\n      it \"returns only case contacts the youth has transitioned\" do\n        contacts = CaseContactReport.new(has_transitioned: false).case_contacts\n\n        expect(contacts.length).to eq(1)\n      end\n\n      it \"returns only case contacts the youth has transitioned\" do\n        contacts = CaseContactReport.new(has_transitioned: true).case_contacts\n\n        expect(contacts.length).to eq(1)\n      end\n\n      it \"returns case contacts with both youth has transitioned and youth has not transitioned\" do\n        contacts = CaseContactReport.new(has_transitioned: \"\").case_contacts\n\n        expect(contacts.length).to eq(2)\n      end\n    end\n\n    describe \"wanting driving reimbursement functionality\" do\n      before do\n        create(:case_contact, {miles_driven: 50, want_driving_reimbursement: true})\n        create(:case_contact, {miles_driven: 50, want_driving_reimbursement: false})\n      end\n\n      it \"returns only contacts that want reimbursement\" do\n        report = CaseContactReport.new({want_driving_reimbursement: true})\n        contacts = report.case_contacts\n        expect(contacts.length).to eq(1)\n      end\n\n      it \"returns only contacts that DO NOT want reimbursement\" do\n        report = CaseContactReport.new({want_driving_reimbursement: false})\n        contacts = report.case_contacts\n        expect(contacts.length).to eq(1)\n      end\n\n      it \"returns contacts that both want reimbursement and do not want reimbursement\" do\n        report = CaseContactReport.new({want_driving_reimbursement: \"\"})\n        contacts = report.case_contacts\n        expect(contacts.length).to eq(2)\n      end\n    end\n\n    describe \"contact type filter functionality\" do\n      it \"returns only the case contacts that include the case contact\" do\n        casa_org = build(:casa_org)\n        supervisor = create(:supervisor, casa_org: casa_org)\n        volunteer = build(:volunteer, casa_org: casa_org)\n        volunteer2 = create(:volunteer, casa_org: casa_org)\n        court = build(:contact_type, name: \"Court\")\n        school = build_stubbed(:contact_type, name: \"School\")\n        create(:supervisor_volunteer, volunteer: volunteer, supervisor: supervisor)\n\n        contact = create(:case_contact, {occurred_at: 20.days.ago, creator_id: volunteer.id, contact_types: [court]})\n        build_stubbed(:case_contact, {occurred_at: 100.days.ago, creator_id: volunteer2.id, contact_types: [school]})\n        build_stubbed(:case_contact, {occurred_at: 100.days.ago})\n        report = CaseContactReport.new({contact_type_ids: [court.id]})\n        contacts = report.case_contacts\n        expect(contacts.length).to eq(1)\n        expect(contacts).to eq([contact])\n      end\n    end\n\n    describe \"contact type group filter functionality\" do\n      before do\n        casa_org = build(:casa_org)\n        supervisor = create(:supervisor, casa_org: casa_org)\n        volunteer = build(:volunteer, casa_org: casa_org)\n        volunteer2 = create(:volunteer, casa_org: casa_org)\n        create(:supervisor_volunteer, volunteer: volunteer, supervisor: supervisor)\n\n        @contact_type_group = build(:contact_type_group, name: \"Legal\")\n        legal_court = build_stubbed(:contact_type, name: \"Court\", contact_type_group: @contact_type_group)\n        legal_attorney = build(:contact_type, name: \"Attorney\", contact_type_group: @contact_type_group)\n        placement_school = build_stubbed(:contact_type, name: \"School\", contact_type_group: build(:contact_type_group, name: \"Placement\"))\n\n        @expected_contact = create(:case_contact, {occurred_at: 20.days.ago, creator_id: volunteer.id, contact_types: [legal_court, legal_attorney]})\n        create(:case_contact, {occurred_at: 100.days.ago, creator_id: volunteer2.id, contact_types: [placement_school]})\n        create(:case_contact, {occurred_at: 100.days.ago})\n      end\n\n      context \"3 contacts each with 1 contact type groups: Legal, Placement and 1 random\" do\n        context \"when select 1 contact type group\" do\n          it \"returns 1 case contact whose contact_types belong to that group\" do\n            report = CaseContactReport.new(\n              {contact_type_group_ids: [@contact_type_group.id]}\n            )\n            expect(report.case_contacts.length).to eq(1)\n            expect(report.case_contacts).to eq([@expected_contact])\n          end\n        end\n\n        context \"when select prompt option (value is empty) and 1 contact type group\" do\n          it \"returns 1 case contact whose contact_types belong to that group\" do\n            report = CaseContactReport.new(\n              {contact_type_group_ids: [\"\", @contact_type_group.id, \"\"]}\n            )\n            expect(report.case_contacts.length).to eq(1)\n            expect(report.case_contacts).to eq([@expected_contact])\n          end\n        end\n\n        context \"when select ONLY prompt option (value is empty) and NO contact type group\" do\n          it \"does no filtering & returns 3 case contacts\" do\n            report = CaseContactReport.new(\n              {contact_type_group_ids: [\"\"]}\n            )\n            expect(report.case_contacts.length).to eq(3)\n            expect(report.case_contacts).to eq(CaseContact.all)\n          end\n        end\n\n        context \"when select nothing on Case Type Group\" do\n          it \"does no filtering & returns 3 case contacts\" do\n            report = CaseContactReport.new(\n              {contact_type_group_ids: nil}\n            )\n            expect(report.case_contacts.length).to eq(3)\n            expect(report.case_contacts).to eq(CaseContact.all)\n          end\n        end\n      end\n    end\n\n    describe \"casa case number filter\" do\n      let!(:casa_case) { create(:casa_case) }\n      let!(:case_contacts) { create_list(:case_contact, 3, casa_case: casa_case) }\n\n      before { create_list(:case_contact, 8) }\n\n      context \"when providing casa case ids\" do\n        it \"returns all case contacts with the casa case ids\" do\n          report = described_class.new({casa_case_ids: [casa_case.id]})\n          expect(report.case_contacts.length).to eq(case_contacts.length)\n          expect(report.case_contacts).to match_array(case_contacts)\n        end\n      end\n\n      context \"when not providing casa case ids\" do\n        it \"return all case contacts\" do\n          report = described_class.new({casa_case_ids: nil})\n          expect(report.case_contacts.length).to eq(CaseContact.count)\n          expect(report.case_contacts).to eq(CaseContact.all)\n        end\n      end\n    end\n\n    describe \"multiple filter behavior\" do\n      it \"only returns records that occured less than 30 days ago, the youth has transitioned, and the contact type was either court or therapist\" do\n        court = build(:contact_type, name: \"Court\")\n        school = build(:contact_type, name: \"School\")\n        therapist = build(:contact_type, name: \"Therapist\")\n        untransitioned_casa_case = create(:casa_case, :pre_transition)\n        transitioned_casa_case = create(:casa_case)\n        contact1 = create(:case_contact, occurred_at: 20.days.ago, casa_case: transitioned_casa_case, contact_types: [court])\n        build_stubbed(:case_contact, occurred_at: 40.days.ago, casa_case: transitioned_casa_case, contact_types: [court])\n        build_stubbed(:case_contact, occurred_at: 20.days.ago, casa_case: untransitioned_casa_case, contact_types: [court])\n        contact4 = create(:case_contact, occurred_at: 20.days.ago, casa_case: transitioned_casa_case, contact_types: [school])\n        contact5 = create(:case_contact, occurred_at: 20.days.ago, casa_case: transitioned_casa_case, contact_types: [court, school])\n        contact6 = create(:case_contact, occurred_at: 20.days.ago, casa_case: transitioned_casa_case, contact_types: [therapist])\n\n        aggregate_failures do\n          report_1 = CaseContactReport.new({start_date: 30.days.ago, end_date: 10.days.ago, has_transitioned: true, contact_type_ids: [court.id]})\n          expect(report_1.case_contacts.length).to eq(2)\n          expect((report_1.case_contacts - [contact1, contact5]).empty?).to eq(true)\n\n          report_2 = CaseContactReport.new({start_date: 30.days.ago, end_date: 10.days.ago, has_transitioned: true, contact_type_ids: [school.id]})\n          expect(report_2.case_contacts.length).to eq(2)\n          expect((report_2.case_contacts - [contact4, contact5]).empty?).to eq(true)\n\n          report_3 = CaseContactReport.new({start_date: 30.days.ago, end_date: 10.days.ago, has_transitioned: true, contact_type_ids: [therapist.id]})\n          expect(report_3.case_contacts.length).to eq(1)\n          expect(report_3.case_contacts.include?(contact6)).to eq(true)\n        end\n      end\n    end\n\n    context \"when columns are filtered\" do\n      let(:args) do\n        {\n          filtered_csv_cols: {\n            internal_contact_number: \"true\",\n            duration_minutes: \"true\",\n            contact_types: \"false\"\n          }\n        }\n      end\n\n      it \"returns a report with only the selected columns\" do\n        create(:case_contact)\n        csv = described_class.new(args).to_csv\n        parsed_csv = CSV.parse(csv)\n\n        expect(parsed_csv.length).to eq(2)\n        expect(parsed_csv[0]).to eq([\n          \"Internal Contact Number\",\n          \"Duration Minutes\"\n        ])\n      end\n    end\n  end\n\n  context \"with court topics\" do\n    let(:report) { described_class.new(filtered_csv_cols: {court_topics: \"true\"}) }\n    let(:csv) { CSV.parse(report.to_csv, headers: true) }\n\n    let!(:used_topic_1) { create(:contact_topic, question: \"Used topic 1\") }\n    let!(:used_topic_2) { create(:contact_topic, question: \"Used topic 2\") }\n    let!(:unused_topic) { create(:contact_topic, question: \"Unused topic\") }\n\n    let(:contacts) { create_list(:case_contact, 3) }\n\n    # Create the answers in opposite order than the topics\n    before do\n      create(:contact_topic_answer, case_contact: contacts.first, contact_topic: used_topic_2, value: \"Ans Contact 1 Topic 2\")\n      create(:contact_topic_answer, case_contact: contacts.first, contact_topic: used_topic_1, value: \"Ans Contact 1 Topic 1\")\n      create(:contact_topic_answer, case_contact: contacts.second, contact_topic: used_topic_2, value: \"Ans Contact 2 Topic 2\")\n    end\n\n    it \"appends headers for any topics referenced by case_contacts in the report\" do\n      headers = csv.headers\n      expect(headers).not_to include(unused_topic.question)\n      expect(headers).to include(used_topic_1.question, used_topic_2.question)\n      expect(headers.select { |header| header == used_topic_1.question }.size).to be 1\n    end\n\n    it \"includes topic answers in csv rows\" do\n      expected_rows = [\n        # ['Used topic 1',        'Used topic 2'] (header)\n        [\"Ans Contact 1 Topic 1\", \"Ans Contact 1 Topic 2\"],\n        [nil, \"Ans Contact 2 Topic 2\"],\n        [nil, nil]\n      ]\n      csv.by_row.each do |row|\n        expect(expected_rows).to include(row.fields)\n      end\n    end\n\n    context \"when court topics are not requested\" do\n      let(:report) do\n        described_class.new(filtered_csv_cols: {\n          internal_contact_number: \"true\",\n          court_topics: \"false\"\n        })\n      end\n\n      it \"omits topics in headers and rows\" do\n        expect(csv.headers).not_to include(used_topic_1.question, used_topic_2.question)\n        expect(csv.first.fields).not_to include(\"Ans Contact 1 Topic 1\", \"Ans Contact 1 Topic 2\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/models/case_contact_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CaseContact, type: :model do\n  it { is_expected.to have_many(:contact_topic_answers).dependent(:destroy) }\n  it { is_expected.to validate_numericality_of(:miles_driven).is_less_than 10_000 }\n  it { is_expected.to validate_numericality_of(:miles_driven).is_greater_than_or_equal_to 0 }\n  it { is_expected.to belong_to(:creator) }\n  it { is_expected.to have_one(:casa_org).through(:casa_case) }\n  it { is_expected.to have_one(:creator_casa_org).through(:creator) }\n\n  context \"status is active\" do\n    it \"belongs to a creator\" do\n      case_contact = build_stubbed(:case_contact, creator: nil)\n      expect(case_contact).not_to be_valid\n      expect(case_contact.errors[:creator]).to eq([\"must exist\"])\n    end\n\n    it \"belongs to a casa case\" do\n      case_contact = build_stubbed(:case_contact, casa_case: nil)\n      expect(case_contact).not_to be_valid\n      expect(case_contact.errors[:casa_case_id]).to eq([\"can't be blank\"])\n    end\n\n    it \"defaults miles_driven to zero\" do\n      case_contact = build_stubbed(:case_contact)\n      expect(case_contact.miles_driven).to eq 0\n    end\n\n    it \"validates presence of occurred_at\" do\n      case_contact = build(:case_contact, occurred_at: nil)\n      expect(case_contact).not_to be_valid\n      expect(case_contact.errors[:occurred_at]).to eq([\"can't be blank\"])\n    end\n\n    it \"validates duration_minutes can be less than 15 minutes.\" do\n      case_contact = build_stubbed(:case_contact, duration_minutes: 10)\n      expect(case_contact).to be_valid\n    end\n\n    it \"verifies occurred at is not in the future\" do\n      case_contact = build_stubbed(:case_contact, occurred_at: 1.week.from_now)\n      expect(case_contact).not_to be_valid\n      expect(case_contact.errors[:occurred_at]).to eq([\"can't be in the future\"])\n    end\n\n    it \"verifies occurred at is not before 1/1/1989\" do\n      case_contact = build_stubbed(:case_contact, occurred_at: \"1984-01-01\".to_date)\n      expect(case_contact).not_to be_valid\n      expect(case_contact.errors[:occurred_at]).to eq([\"can't be prior to 01/01/1989.\"])\n    end\n\n    it \"validates want_driving_reimbursement can be true when miles_driven is positive\" do\n      case_contact = build_stubbed(:case_contact, want_driving_reimbursement: true, miles_driven: 1)\n      expect(case_contact).to be_valid\n    end\n\n    it \"validates want_driving_reimbursement cannot be true when miles_driven is nil\" do\n      case_contact = build_stubbed(:case_contact, want_driving_reimbursement: true, miles_driven: nil)\n      expect(case_contact).not_to be_valid\n      expect(case_contact.errors[:base]).to eq([\"Must enter miles driven to receive driving reimbursement.\"])\n    end\n\n    it \"validates want_driving_reimbursement cannot be true when miles_driven is not positive\" do\n      case_contact = build_stubbed(:case_contact, want_driving_reimbursement: true, miles_driven: 0)\n      expect(case_contact).not_to be_valid\n      expect(case_contact.errors[:base]).to eq([\"Must enter miles driven to receive driving reimbursement.\"])\n    end\n\n    it \"validates that contact_made cannot be null\" do\n      case_contact = build_stubbed(:case_contact, contact_made: nil)\n      expect(case_contact).not_to be_valid\n      expect(case_contact.errors.full_messages).to include(\"Contact made must be true or false\")\n    end\n\n    it \"can be updated even if it is old\" do\n      case_contact = build_stubbed(:case_contact)\n      case_contact.occurred_at = 1.year.ago\n      expect(case_contact).to be_valid\n    end\n\n    it \"can be updated for 30 days after end of quarter\" do\n      expect(build_stubbed(:case_contact, occurred_at: 4.months.ago + 1.day)).to be_valid\n    end\n  end\n\n  context \"status is started\" do\n    it \"ignores some validations\" do\n      case_contact = build_stubbed(:case_contact, :started_status, want_driving_reimbursement: true)\n      expect(case_contact.casa_case).to be_nil\n      expect(case_contact.medium_type).to be_nil\n      expect(case_contact.draft_case_ids).to eq []\n      expect(case_contact.occurred_at).to be_nil\n      expect(case_contact.miles_driven).to be 0\n      expect(case_contact.volunteer_address).to be_nil\n      expect(case_contact).to be_valid\n    end\n  end\n\n  context \"status is details\" do\n    it \"ignores some validations\" do\n      case_contact = build_stubbed(:case_contact, :details_status)\n      expect(case_contact.casa_case).to be_nil\n      expect(case_contact).to be_valid\n    end\n\n    it \"requires medium type\" do\n      case_contact = build_stubbed(:case_contact, :details_status, medium_type: nil)\n      expect(case_contact).not_to be_valid\n      expect(case_contact.errors.full_messages).to include(\"Medium type can't be blank\")\n    end\n\n    it \"requires a case to be selected\" do\n      case_contact = build_stubbed(:case_contact, :details_status, draft_case_ids: [])\n      expect(case_contact).not_to be_valid\n      expect(case_contact.errors.full_messages).to include(\"CASA Case must be selected\")\n    end\n\n    it \"requires occurred at\" do\n      case_contact = build_stubbed(:case_contact, :details_status, occurred_at: nil)\n      expect(case_contact).not_to be_valid\n      expect(case_contact.errors.full_messages).to include(\"Date can't be blank\")\n    end\n\n    it \"requires duration minutes\" do\n      obj = build_stubbed(:case_contact, :details_status, duration_minutes: nil)\n      expect(obj).not_to be_valid\n      expect(obj.errors.full_messages).to include(\"Duration minutes can't be blank\")\n    end\n\n    it \"validates miles driven if want reimbursement\" do\n      obj = build_stubbed(:case_contact, :details_status, want_driving_reimbursement: true)\n      expect(obj).not_to be_valid\n      expect(obj.errors.full_messages).to include(\"Must enter miles driven to receive driving reimbursement.\")\n    end\n  end\n\n  describe \"#update_cleaning_contact_types\" do\n    it \"cleans up contact types before saving\" do\n      group = build_stubbed(:contact_type_group)\n      type1 = build(:contact_type, contact_type_group: group)\n      type2 = create(:contact_type, contact_type_group: group)\n\n      case_contact = create(:case_contact, contact_types: [type1])\n\n      expect(case_contact.case_contact_contact_types.count).to be 1\n      expect(case_contact.contact_types).to contain_exactly(type1)\n\n      case_contact.update_cleaning_contact_types(contact_type_ids: [type2.id])\n\n      expect(case_contact.case_contact_contact_types.count).to eq 1\n      expect(case_contact.contact_types.reload).to contain_exactly(type2)\n    end\n  end\n\n  describe \"scopes\" do\n    describe \"date related scopes\" do\n      let!(:case_contacts) do\n        [\n          create(:case_contact, occurred_at: Time.zone.yesterday - 1),\n          create(:case_contact, occurred_at: Time.zone.yesterday),\n          create(:case_contact, occurred_at: Time.zone.today)\n        ]\n      end\n\n      let(:date) { Time.zone.yesterday }\n\n      describe \".occurred_starting_at\" do\n        subject(:occurred_starting_at) { described_class.occurred_starting_at(date) }\n\n        context \"with specified date\" do\n          it { is_expected.to contain_exactly(case_contacts.second, case_contacts.third) }\n        end\n\n        context \"with no specified date\" do\n          let(:date) { nil }\n\n          it { is_expected.to match_array(case_contacts) }\n        end\n      end\n\n      describe \".occurred_ending_at\" do\n        subject(:occurred_ending_at) { described_class.occurred_ending_at(date) }\n\n        context \"with specified date\" do\n          it { is_expected.to contain_exactly(case_contacts.first, case_contacts.second) }\n        end\n\n        context \"with no specified date\" do\n          let(:date) { nil }\n\n          it { is_expected.to match_array(case_contacts) }\n        end\n      end\n    end\n\n    describe \".contact_type\" do\n      subject(:contact_type) { described_class.contact_type([youth_type.id, supervisor_type.id]) }\n\n      let(:group) { build(:contact_type_group) }\n      let(:youth_type) { build(:contact_type, name: \"Youth\", contact_type_group: group) }\n      let(:supervisor_type) { build(:contact_type, name: \"Supervisor\", contact_type_group: group) }\n      let(:parent_type) { build(:contact_type, name: \"Parent\", contact_type_group: group) }\n\n      let!(:case_contacts_to_match) do\n        [\n          create(:case_contact, contact_types: [youth_type, supervisor_type]),\n          create(:case_contact, contact_types: [supervisor_type]),\n          create(:case_contact, contact_types: [youth_type, parent_type])\n        ]\n      end\n\n      let!(:other_case_contact) { build_stubbed(:case_contact, contact_types: [parent_type]) }\n\n      it { is_expected.to match_array(case_contacts_to_match) }\n    end\n\n    describe \".contact_made\" do\n      context \"with both option\" do\n        it \"returns case contacts filtered by contact made option\" do\n          case_contact_1 = create(:case_contact, contact_made: false)\n          case_contact_2 = create(:case_contact, contact_made: true)\n\n          expect(CaseContact.contact_made(\"\")).to contain_exactly(case_contact_1, case_contact_2)\n        end\n      end\n\n      context \"with yes option\" do\n        it \"returns case contacts filtered by contact made option\" do\n          case_contact = create(:case_contact, contact_made: true)\n          build_stubbed(:case_contact, contact_made: false)\n\n          expect(CaseContact.contact_made(true)).to contain_exactly(case_contact)\n        end\n      end\n\n      context \"with no option\" do\n        it \"returns case contacts filtered by contact made option\" do\n          case_contact = create(:case_contact, contact_made: false)\n          build_stubbed(:case_contact, contact_made: true)\n\n          expect(CaseContact.contact_made(false)).to contain_exactly(case_contact)\n        end\n      end\n    end\n\n    describe \".has_transitioned\" do\n      let(:casa_case_1) { create(:casa_case, birth_month_year_youth: 15.years.ago) }\n      let(:casa_case_2) { create(:casa_case, birth_month_year_youth: 10.years.ago) }\n\n      context \"with both option\" do\n        let!(:case_contact_1) { create(:case_contact, {casa_case: casa_case_1}) }\n        let!(:case_contact_2) { create(:case_contact, {casa_case: casa_case_2}) }\n\n        it \"returns case contacts filtered by contact made option\" do\n          expect(described_class.has_transitioned).to contain_exactly(case_contact_1, case_contact_2)\n        end\n      end\n\n      context \"with true option\" do\n        let!(:case_contact_1) { create(:case_contact, {casa_case: casa_case_1}) }\n        let!(:case_contact_2) { create(:case_contact, {casa_case: casa_case_2}) }\n\n        it \"returns case contacts filtered by contact made option\" do\n          expect(described_class.has_transitioned(true)).to contain_exactly(case_contact_1)\n        end\n      end\n\n      context \"with false option\" do\n        let!(:case_contact_1) { create(:case_contact, {casa_case: casa_case_1}) }\n        let!(:case_contact_2) { create(:case_contact, {casa_case: casa_case_2}) }\n\n        it \"returns case contacts filtered by contact made option\" do\n          expect(described_class.has_transitioned(false)).to contain_exactly(case_contact_2)\n        end\n      end\n    end\n\n    describe \".want_driving_reimbursement\" do\n      context \"with both option\" do\n        it \"returns case contacts filtered by contact made option\" do\n          case_contact_1 = create(:case_contact, {miles_driven: 50, want_driving_reimbursement: true})\n          case_contact_2 = create(:case_contact, {miles_driven: 50, want_driving_reimbursement: false})\n\n          expect(CaseContact.want_driving_reimbursement(\"\")).to contain_exactly(case_contact_1, case_contact_2)\n        end\n      end\n\n      context \"with yes option\" do\n        it \"returns case contacts filtered by contact made option\" do\n          case_contact = create(:case_contact, {miles_driven: 50, want_driving_reimbursement: true})\n          build_stubbed(:case_contact, {miles_driven: 50, want_driving_reimbursement: false})\n\n          expect(CaseContact.want_driving_reimbursement(true)).to contain_exactly(case_contact)\n        end\n      end\n\n      context \"with no option\" do\n        it \"returns case contacts filtered by contact made option\" do\n          build_stubbed(:case_contact, {miles_driven: 50, want_driving_reimbursement: true})\n          case_contact = create(:case_contact, {miles_driven: 50, want_driving_reimbursement: false})\n\n          expect(CaseContact.want_driving_reimbursement(false)).to contain_exactly(case_contact)\n        end\n      end\n    end\n\n    describe \".contact_medium\" do\n      subject(:contact_medium) { described_class.contact_medium(medium_type) }\n\n      let!(:case_contacts) do\n        [\n          create(:case_contact, medium_type: \"in-person\"),\n          create(:case_contact, medium_type: \"letter\")\n        ]\n      end\n\n      describe \"with specified medium parameter\" do\n        let(:medium_type) { \"in-person\" }\n\n        it { is_expected.to contain_exactly case_contacts.first }\n      end\n\n      describe \"without specified medium parameter\" do\n        let(:medium_type) { nil }\n\n        it { is_expected.to match_array(case_contacts) }\n      end\n    end\n\n    describe \".sorted_by\" do\n      subject(:sorted_by) { described_class.sorted_by(sort_option) }\n\n      context \"without sort option\" do\n        let(:sort_option) { nil }\n\n        it { expect { sorted_by }.to raise_error(ArgumentError, \"Invalid sort option: nil\") }\n      end\n\n      context \"with invalid sort option\" do\n        let(:sort_option) { \"1254645\" }\n\n        it { expect { sorted_by }.to raise_error(ArgumentError, \"Invalid sort option: \\\"1254645\\\"\") }\n      end\n\n      context \"with valid sort option\" do\n        context \"with occurred_at option\" do\n          let(:sort_option) { \"occurred_at_#{direction}\" }\n\n          let!(:case_contacts) do\n            [\n              create(:case_contact, occurred_at: Time.zone.today - 3),\n              create(:case_contact, occurred_at: Time.zone.today - 1),\n              create(:case_contact, occurred_at: Time.zone.today - 2)\n            ]\n          end\n\n          context \"when sorting by ascending order\" do\n            let(:direction) { \"asc\" }\n\n            it { is_expected.to contain_exactly(case_contacts[0], case_contacts[2], case_contacts[1]) }\n          end\n\n          context \"when sorting by descending order\" do\n            let(:direction) { \"desc\" }\n\n            it { is_expected.to contain_exactly(case_contacts[1], case_contacts[2], case_contacts[0]) }\n          end\n        end\n\n        context \"with contact_type option\" do\n          let(:sort_option) { \"contact_type_#{direction}\" }\n\n          let(:group) { create(:contact_type_group) }\n\n          let(:contact_types) do\n            [\n              create(:contact_type, name: \"Supervisor\", contact_type_group: group),\n              create(:contact_type, name: \"Parent\", contact_type_group: group),\n              create(:contact_type, name: \"Youth\", contact_type_group: group)\n            ]\n          end\n\n          let!(:case_contacts) do\n            contact_types.map do |contact_type|\n              create(:case_contact, contact_types: [contact_type])\n            end\n          end\n\n          context \"when sorting by ascending order\" do\n            let(:direction) { \"asc\" }\n\n            it { is_expected.to contain_exactly(case_contacts[1], case_contacts[0], case_contacts[2]) }\n          end\n\n          context \"when sorting by descending order\" do\n            let(:direction) { \"desc\" }\n\n            it { is_expected.to contain_exactly(case_contacts[2], case_contacts[0], case_contacts[1]) }\n          end\n        end\n\n        context \"with medium_type option\" do\n          let(:sort_option) { \"contact_type_#{direction}\" }\n\n          let!(:case_contacts) do\n            [\n              create(:case_contact, medium_type: \"in-person\"),\n              create(:case_contact, medium_type: \"text/email\"),\n              create(:case_contact, medium_type: \"letter\")\n            ]\n          end\n\n          context \"when sorting by ascending order\" do\n            let(:direction) { \"asc\" }\n\n            it { is_expected.to contain_exactly(case_contacts[0], case_contacts[2], case_contacts[1]) }\n          end\n\n          context \"when sorting by descending order\" do\n            let(:direction) { \"desc\" }\n\n            it { is_expected.to contain_exactly(case_contacts[1], case_contacts[2], case_contacts[0]) }\n          end\n        end\n\n        context \"with want_driving_reimbursement option\" do\n          let(:sort_option) { \"want_driving_reimbursement_#{direction}\" }\n\n          let!(:case_contacts) do\n            [\n              create(:case_contact, miles_driven: 1, want_driving_reimbursement: true),\n              create(:case_contact, miles_driven: 1, want_driving_reimbursement: false)\n            ]\n          end\n\n          context \"when sorting by ascending order\" do\n            let(:direction) { \"asc\" }\n\n            it { is_expected.to contain_exactly(case_contacts[0], case_contacts[1]) }\n          end\n\n          context \"when sorting by descending order\" do\n            let(:direction) { \"desc\" }\n\n            it { is_expected.to contain_exactly(case_contacts[1], case_contacts[0]) }\n          end\n        end\n\n        context \"with contact_made option\" do\n          let(:sort_option) { \"contact_made_#{direction}\" }\n\n          let!(:case_contacts) do\n            [\n              create(:case_contact, contact_made: true),\n              create(:case_contact, contact_made: false)\n            ]\n          end\n\n          context \"when sorting by ascending order\" do\n            let(:direction) { \"asc\" }\n\n            it { is_expected.to contain_exactly(case_contacts[1], case_contacts[0]) }\n          end\n\n          context \"when sorting by descending order\" do\n            let(:direction) { \"desc\" }\n\n            it { is_expected.to contain_exactly(case_contacts[0], case_contacts[1]) }\n          end\n        end\n      end\n    end\n\n    describe \".with_casa_case\" do\n      let!(:casa_case) { create(:casa_case) }\n      let!(:case_contacts) { create_list(:case_contact, 3, casa_case: casa_case) }\n\n      before { create_list(:case_contact, 3) }\n\n      context \"when parameter is nil\" do\n        it \"returns all casa cases\" do\n          expect(described_class.with_casa_case(nil)).to eq(CaseContact.all)\n        end\n      end\n\n      context \"when parameter is not nil\" do\n        it \"returns contacts with the given casa case ids\" do\n          expect(described_class.with_casa_case(casa_case.id)).to match_array(case_contacts)\n        end\n      end\n    end\n\n    describe \".used_create_another\" do\n      subject { described_class.used_create_another }\n\n      let!(:scope_case_contact) { create(:case_contact, metadata: {\"create_another\" => true}) }\n      let!(:false_case_contact) { create(:case_contact, metadata: {\"create_another\" => false}) }\n      let!(:empty_meta_case_contact) { create(:case_contact) }\n\n      it \"returns only the case contacts with the metadata key 'create_another' set to true\" do\n        expect(subject).to include(scope_case_contact)\n        expect(subject).not_to include(false_case_contact)\n        expect(subject).not_to include(empty_meta_case_contact)\n      end\n    end\n  end\n\n  describe \"#contact_groups_with_types\" do\n    it \"returns the groups with their associated case types\" do\n      group1 = build(:contact_type_group, name: \"Family\")\n      group2 = build(:contact_type_group, name: \"Health\")\n      contact_type1 = build(:contact_type, contact_type_group: group1, name: \"Parent\")\n      contact_type2 = build(:contact_type, contact_type_group: group2, name: \"Medical Professional\")\n      contact_type3 = build(:contact_type, contact_type_group: group2, name: \"Other Therapist\")\n      case_contact_types = [contact_type1, contact_type2, contact_type3]\n      case_contact = create(:case_contact)\n      case_contact.contact_types = case_contact_types\n\n      groups_with_types = case_contact.contact_groups_with_types\n\n      expect(groups_with_types.keys).to contain_exactly(\"Family\", \"Health\")\n      expect(groups_with_types[\"Family\"]).to contain_exactly(\"Parent\")\n      expect(groups_with_types[\"Health\"]).to contain_exactly(\"Medical Professional\", \"Other Therapist\")\n    end\n  end\n\n  describe \"#requested_followup\" do\n    context \"no followup exists in requested status\" do\n      it \"returns nil\" do\n        case_contact = build_stubbed(:case_contact)\n        expect(case_contact.requested_followup).to be_nil\n      end\n    end\n\n    context \"a followup exists in requested status\" do\n      it \"returns nil\" do\n        case_contact = build_stubbed(:case_contact)\n        followup = create(:followup, case_contact: case_contact)\n\n        expect(case_contact.requested_followup).to eq(followup)\n      end\n    end\n  end\n\n  describe \"reimbursement amount\" do\n    let(:case_contact) { build(:case_contact, :wants_reimbursement) }\n\n    describe \"when casa org has nil mileage_rate_for_given_date\" do\n      it \"is nil\" do\n        expect(case_contact.casa_case.casa_org.mileage_rate_for_given_date(case_contact.occurred_at.to_datetime)).to be_nil\n        expect(case_contact.reimbursement_amount).to be_nil\n      end\n    end\n\n    describe \"when casa org has value for mileage_rate_for_given_date\" do\n      let!(:mileage_rate) { create(:mileage_rate, casa_org: case_contact.casa_case.casa_org, effective_date: 3.days.ago, amount: 5.50) }\n\n      it \"is multiple of miles driven and mileage rate\" do\n        expect(case_contact.reimbursement_amount).to eq 2508\n      end\n    end\n  end\n\n  describe \"#should_send_reimbursement_email?\" do\n    let(:supervisor) { create(:supervisor, receive_reimbursement_email: true) }\n    let(:volunteer) { create(:volunteer, supervisor: supervisor) }\n    let(:casa_case) { create(:casa_case) }\n    let(:case_contact) { build(:case_contact, :wants_reimbursement, casa_case: casa_case, creator: volunteer) }\n\n    it \"returns true if wants reimbursement, reimbursement changed, and has active supervisor\" do\n      expect(case_contact.want_driving_reimbursement_changed?).to be true\n      expect(case_contact.should_send_reimbursement_email?).to be true\n    end\n\n    it \"returns false if doesn't want reimbursement\" do\n      case_contact.want_driving_reimbursement = false\n      expect(case_contact.should_send_reimbursement_email?).to be false\n    end\n\n    it \"returns false if creator doesn't have supervisor\" do\n      volunteer.supervisor_volunteer = nil\n      expect(case_contact.supervisor.blank?).to be true\n      expect(case_contact.should_send_reimbursement_email?).to be false\n    end\n\n    it \"returns false if creator's supervisor is inactive\" do\n      supervisor.update!(active: false)\n      expect(case_contact.should_send_reimbursement_email?).to be false\n    end\n  end\n\n  describe \"volunteer assignment\" do\n    let(:casa_org) { create(:casa_org) }\n    let(:admin) { create(:casa_admin, casa_org: casa_org) }\n    let(:supervisor) { create(:supervisor, casa_org: casa_org) }\n    let(:volunteer) { create(:volunteer, supervisor: supervisor, casa_org: casa_org) }\n    let(:casa_case) { create(:casa_case, casa_org: casa_org) }\n    let(:case_contact) { build(:case_contact, casa_case: casa_case, creator: creator) }\n\n    context \"when creator is volunteer\" do\n      let(:creator) { volunteer }\n\n      it \"creator is the volunteer\" do\n        expect(case_contact.volunteer).to eq volunteer\n      end\n\n      it \"enables address field\" do\n        expect(case_contact.address_field_disabled?).to be false\n      end\n    end\n\n    context \"when creator is admin\" do\n      let(:creator) { admin }\n\n      context \"when casa case has one volunteer assigned\" do\n        let!(:contact_assignment) { create(:case_assignment, volunteer: volunteer, casa_case: casa_case) }\n\n        it \"volunteer is the assigned volunteer\" do\n          expect(case_contact.volunteer).to eq volunteer\n        end\n\n        it \"enables address field\" do\n          expect(case_contact.address_field_disabled?).to be false\n        end\n      end\n\n      context \"when casa case has no volunteers assigned\" do\n        it \"volunteer is nil\" do\n          expect(case_contact.volunteer).to be_nil\n        end\n\n        it \"disbales address field\" do\n          expect(case_contact.address_field_disabled?).to be true\n        end\n      end\n\n      context \"when casa case has more than 1 volunteer assigned\" do\n        let(:other_volunteer) { create(:volunteer, casa_org: casa_org) }\n        let!(:contact_assignments) {\n          [\n            create(:case_assignment, volunteer: volunteer, casa_case: casa_case),\n            create(:case_assignment, volunteer: other_volunteer, casa_case: casa_case)\n          ]\n        }\n\n        it \"volunteer is nil\" do\n          expect(case_contact.volunteer).to be_nil\n        end\n\n        it \"disbales address field\" do\n          expect(case_contact.address_field_disabled?).to be true\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/models/case_court_order_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CaseCourtOrder, type: :model do\n  subject { build(:case_court_order) }\n\n  it { is_expected.to belong_to(:casa_case) }\n\n  it { is_expected.to validate_presence_of(:text) }\n\n  describe \".court_order_options\" do\n    it \"returns standard court order options\" do\n      expect(described_class.court_order_options.count).to eq(23)\n      expect(described_class.court_order_options).to be_an(Array)\n      expect(described_class.court_order_options).to all be_an(Array)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/models/case_court_report_context_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\nrequire \"sablon\"\n\nA_TIMEZONE = \"America/New_York\"\n\nRSpec.describe CaseCourtReportContext, type: :model do\n  let(:volunteer) { create(:volunteer, :with_casa_cases) }\n  let(:path_to_template) { Rails.root.join(\"app/documents/templates/default_report_template.docx\").to_s }\n  let(:path_to_report) { Rails.root.join(\"tmp/test_report.docx\").to_s }\n\n  before do\n    travel_to Date.new(2021, 1, 1)\n  end\n\n  describe \"#context\" do\n    it \"has the right shape\" do\n      date = 1.day.ago\n      court_date = build(:court_date, :with_hearing_type, date: date)\n      context = create(:case_court_report_context, court_date: court_date)\n\n      allow(context).to receive(:case_details).and_return({})\n      allow(context).to receive(:case_contacts).and_return([])\n      allow(context).to receive(:case_orders).and_return([])\n      allow(context).to receive(:org_address).and_return(nil)\n      allow(context).to receive(:volunteer_info).and_return({})\n      allow(context).to receive(:latest_hearing_date).and_return(\"\")\n      allow(context).to receive(:court_topics).and_return({})\n\n      expected_shape = {\n        created_date: \"January 1, 2021\",\n        casa_case: {},\n        case_contacts: [],\n        case_court_orders: [],\n        case_mandates: [],\n        latest_hearing_date: \"\",\n        org_address: nil,\n        volunteer: {},\n        hearing_type_name: court_date.hearing_type.name,\n        case_topics: []\n      }\n\n      expect(context.context).to eq(expected_shape)\n    end\n  end\n\n  describe \"case_orders\" do\n    it \"returns the correct shape\" do\n      court_orders = [\n        build(:case_court_order, text: \"Court order 1\", implementation_status: :unimplemented),\n        build(:case_court_order, text: \"Court order 2\", implementation_status: :implemented)\n      ]\n      expected = [\n        {order: \"Court order 1\", status: \"Unimplemented\"},\n        {order: \"Court order 2\", status: \"Implemented\"}\n      ]\n      context = build_stubbed(:case_court_report_context)\n\n      expect(context.case_orders(court_orders)).to match_array(expected)\n    end\n  end\n\n  describe \"org_address\" do\n    let(:volunteer) { create(:volunteer) }\n    let(:context) { build(:case_court_report_context, volunteer: volunteer) }\n\n    context \"when volunteer and default template are provided\" do\n      it \"returns the CASA org address\" do\n        path_to_template = \"default_report_template.docx\"\n        expected_address = volunteer.casa_org.address\n\n        expect(context.org_address(path_to_template)).to eq(expected_address)\n      end\n    end\n\n    context \"when volunteer is provided but not default template\" do\n      it \"returns nil\" do\n        path_to_template = \"some_other_template.docx\"\n\n        expect(context.org_address(path_to_template)).to be_nil\n      end\n    end\n\n    context \"when volunteer is not provided\" do\n      let(:context) { build(:case_court_report_context, volunteer: false) }\n\n      it \"returns nil\" do\n        path_to_template = \"default_report_template.docx\"\n        expect(context.org_address(path_to_template)).to be_nil\n      end\n    end\n  end\n\n  describe \"#latest_hearing_date\" do\n    context \"when casa_case has court_dates\" do\n      let(:court_date) { build(:court_date, date: 2.day.ago) }\n      let(:casa_case) { create(:casa_case, court_dates: [court_date]) }\n      let(:instance) { build(:case_court_report_context, casa_case: casa_case) }\n\n      it \"returns the formatted date\" do\n        expect(instance.latest_hearing_date).to eq(\"December 30, 2020\") # 2 days before spec default date\n      end\n    end\n\n    context \"when most recent past court date is nil\" do\n      let(:instance) { build(:case_court_report_context) }\n\n      it \"returns the placeholder string\" do\n        expect(instance.latest_hearing_date).to eq(\"___<LATEST HEARING DATE>____\")\n      end\n    end\n\n    context \"when there are multiple hearing dates\" do\n      let(:casa_case_with_court_dates) {\n        casa_case = create(:casa_case)\n\n        casa_case.court_dates << build(:court_date, date: 9.months.ago)\n        casa_case.court_dates << build(:court_date, date: 3.months.ago)\n        casa_case.court_dates << build(:court_date, date: 15.months.ago)\n\n        casa_case\n      }\n\n      let(:court_report_context_with_latest_hearing_date) { build(:case_court_report_context, casa_case: casa_case_with_court_dates) }\n\n      it \"sets latest_hearing_date as the latest past court date\" do\n        expect(court_report_context_with_latest_hearing_date.latest_hearing_date).to eq(\"October 1, 2020\")\n      end\n    end\n  end\n\n  describe \"#calculate_date_range\" do\n    context \"when @time_zone is set\" do\n      it \"converts to provided timezone\" do\n        context = build_context(start_date: 10.day.ago, end_date: 2.day.ago, court_date: nil, time_zone: A_TIMEZONE)\n        expect(context.date_range).to eq(zone_days_ago(10)..zone_days_ago(2))\n      end\n\n      it \"uses current time if end_date not provided\" do\n        context = build_context(start_date: 10.day.ago, end_date: nil, court_date: nil, time_zone: A_TIMEZONE)\n        expect(context.date_range).to eq(zone_days_ago(10)..nil)\n      end\n\n      it \"uses court date if available if no start_date\" do\n        context = build_context(start_date: nil, end_date: 2.day.ago, court_date: 6.day.ago, time_zone: A_TIMEZONE)\n        expect(context.date_range).to eq(zone_days_ago(6)..zone_days_ago(2))\n      end\n\n      it \"uses nil(includes everything) if no court date or start_date\" do\n        context = build_context(start_date: nil, end_date: 2.day.ago, court_date: nil, time_zone: A_TIMEZONE)\n\n        expect(context.date_range).to eq(nil..zone_days_ago(2))\n      end\n    end\n\n    context \"when @time_zone is not set\" do\n      it \"uses server time zone\" do\n        context = build_context(start_date: 10.day.ago, end_date: 2.day.ago, court_date: nil, time_zone: nil)\n        expect(context.date_range).to eq(days_ago(10)..days_ago(2))\n      end\n\n      it \"uses nil if end_date not provided\" do\n        context = build_context(start_date: 10.day.ago, end_date: nil, court_date: nil, time_zone: nil)\n        expect(context.date_range).to eq(days_ago(10)..nil)\n      end\n\n      it \"uses court date if available if no start_date\" do\n        context = build_context(start_date: nil, end_date: 2.day.ago, court_date: 6.day.ago, time_zone: nil)\n        expect(context.date_range).to eq(days_ago(6)..days_ago(2))\n      end\n\n      it \"uses nil if no court date or start_date\" do\n        context = build_context(start_date: nil, end_date: 2.day.ago, court_date: nil, time_zone: nil)\n\n        expect(context.date_range).to eq(nil..days_ago(2))\n      end\n    end\n  end\n\n  describe \"#court_topics\" do\n    let(:org) { create(:casa_org) }\n    let(:casa_case) { create(:casa_case, casa_org: org) }\n    let(:topics) { [1, 2, 3].map { |i| create(:contact_topic, casa_org: org, question: \"Question #{i}\", details: \"Details #{i}\") } }\n    let(:contacts) do\n      [1, 2, 3, 4].map do |i|\n        create(:case_contact,\n          casa_case: casa_case,\n          occurred_at: 1.month.ago + i.days,\n          contact_types: [\n            create(:contact_type, name: \"Type A#{i}\"),\n            create(:contact_type, name: \"Type B#{i}\")\n          ])\n      end\n    end\n\n    context \"when given data\" do\n      before do\n        # Contact 1 Answers\n        create(:contact_topic_answer, case_contact: contacts[0], contact_topic: topics[0], value: \"Answer 1\")\n        create(:contact_topic_answer, case_contact: contacts[0], contact_topic: topics[1], value: \"Answer 2\")\n\n        # Contact 2 Answers\n        create(:contact_topic_answer, case_contact: contacts[1], contact_topic: topics[0], value: \"Answer 3\")\n        create(:contact_topic_answer, case_contact: contacts[1], contact_topic: topics[2], value: nil)\n\n        # Contact 3 Answers\n        create(:contact_topic_answer, case_contact: contacts[2], contact_topic: topics[1], value: \"Answer 5\")\n        create(:contact_topic_answer, case_contact: contacts[2], contact_topic: topics[2], value: \"\")\n\n        # Contacts that will be filtered\n        one_day_ago_contact = create(:case_contact, casa_case: casa_case, medium_type: \"in-person\", occurred_at: 1.day.ago)\n        create_list(:contact_topic_answer, 2, case_contact: one_day_ago_contact, contact_topic: topics[0], value: \"Answer From One Day Ago\")\n\n        one_year_ago_contact = create(:case_contact, casa_case: casa_case, medium_type: \"in-person\", occurred_at: 1.year.ago)\n        create_list(:contact_topic_answer, 2, case_contact: one_year_ago_contact, contact_topic: topics[0], value: \"Answer From One Year Ago\")\n\n        other_case = create(:casa_case, casa_org: org)\n        other_case_contact = create(:case_contact, casa_case: other_case, medium_type: \"in-person\", occurred_at: 1.month.ago)\n        create_list(:contact_topic_answer, 2, case_contact: other_case_contact, contact_topic: topics[0], value: \"Answer From Another Case\")\n      end\n\n      it \"returns a hash of topics with the correct shape\" do\n        court_topics = build(:case_court_report_context, casa_case: casa_case).court_topics\n\n        expect(court_topics).to be_a(Hash)\n\n        expect(court_topics.keys).to all(a_kind_of(String))\n        expect(court_topics.values).to all(\n          a_hash_including(\n            topic: a_kind_of(String),\n            details: a_kind_of(String),\n            answers: all(\n              a_hash_including(\n                date: a_string_matching(/\\d{2}\\/\\d{2}\\/\\d{2}/),\n                medium: a_kind_of(String),\n                value: a_kind_of(String)\n              )\n            )\n          )\n        )\n      end\n\n      it \"returns topics related to the case\" do\n        court_topics = build(:case_court_report_context, casa_case: casa_case).court_topics\n\n        expect(court_topics.keys).to match_array([\"Question 1\", \"Question 2\", \"Question 3\"])\n        expect(court_topics[\"Question 1\"][:answers].pluck(:value)).to match_array(\n          [\"Answer From One Year Ago\", \"Answer 1\", \"Answer 3\", \"Answer From One Day Ago\"]\n        )\n        expect(court_topics[\"Question 2\"][:answers].pluck(:value)).to match_array([\"Answer 2\", \"Answer 5\"])\n        expect(court_topics[\"Question 3\"][:answers].pluck(:value)).to match_array([\"No Answer Provided\", \"No Answer Provided\"])\n      end\n\n      it \"filters by date range\" do\n        court_topics = build(:case_court_report_context, start_date: 45.day.ago.to_s, end_date: 5.day.ago.to_s, casa_case: casa_case).court_topics\n\n        expect(court_topics.keys).to match_array([\"Question 1\", \"Question 2\", \"Question 3\"])\n        expect(court_topics[\"Question 1\"][:answers].pluck(:value)).to match_array([\"Answer 1\", \"Answer 3\"])\n      end\n\n      it \"filters answers from topics set be excluded from court report\" do\n        topics[0].update(exclude_from_court_report: true)\n\n        court_topics = build(:case_court_report_context, casa_case: casa_case).court_topics\n\n        expect(court_topics.keys).not_to include(\"Question 1\")\n        expect(court_topics.keys).to include(\"Question 2\", \"Question 3\")\n      end\n    end\n\n    context \"when there are no contact topics\" do\n      it \"returns an empty hash\" do\n        court_report_context = build(:case_court_report_context, start_date: 45.day.ago.to_s, end_date: 5.day.ago.to_s, casa_case: casa_case)\n        expect(court_report_context.court_topics).to eq({})\n      end\n    end\n  end\n\n  describe \"#filtered_interviewees\" do\n    it \"filters based on date range\" do\n      casa_case = create(:casa_case)\n      court_report_context = build(:case_court_report_context, start_date: 5.day.ago.to_s, end_date: 5.day.ago.to_s, casa_case: casa_case)\n\n      create_list(:case_contact, 3, occurred_at: 10.day.ago, casa_case: casa_case)\n      create_list(:case_contact, 3, occurred_at: 1.day.ago, casa_case: casa_case)\n      included_interviewee = create(:case_contact, occurred_at: 5.day.ago, casa_case: casa_case)\n\n      result = court_report_context.filtered_interviewees.map(&:case_contact)\n\n      expect(result).to contain_exactly(included_interviewee)\n    end\n\n    it \"filters if start of date range is nil\" do\n      casa_case = create(:casa_case)\n      court_report_context = build(:case_court_report_context, start_date: nil, end_date: 5.day.ago.to_s, casa_case: casa_case)\n\n      create_list(:case_contact, 3, occurred_at: 1.day.ago, casa_case: casa_case)\n      interviewees = create_list(:case_contact, 3, occurred_at: 10.day.ago, casa_case: casa_case)\n      interviewees.append(create(:case_contact, occurred_at: 5.day.ago, casa_case: casa_case))\n\n      result = court_report_context.filtered_interviewees.map(&:case_contact)\n\n      expect(result).to match_array(interviewees)\n    end\n\n    it \"filters if end of date range is nil\" do\n      casa_case = create(:casa_case)\n      court_report_context = build(:case_court_report_context, start_date: 5.day.ago.to_s, end_date: nil, casa_case: casa_case)\n\n      create_list(:case_contact, 3, occurred_at: 10.day.ago, casa_case: casa_case)\n      interviewees = create_list(:case_contact, 3, occurred_at: 1.day.ago, casa_case: casa_case)\n      interviewees.append(create(:case_contact, occurred_at: 5.day.ago, casa_case: casa_case))\n\n      result = court_report_context.filtered_interviewees.map(&:case_contact)\n\n      expect(result).to match_array(interviewees)\n    end\n\n    it \"does not filter if both start and end of date range are nil\" do\n      casa_case = create(:casa_case)\n      court_report_context = build(:case_court_report_context, start_date: nil, end_date: nil, casa_case: casa_case)\n\n      create_list(:case_contact, 3, occurred_at: 10.day.ago, casa_case: casa_case)\n      create_list(:case_contact, 3, occurred_at: 1.day.ago, casa_case: casa_case)\n      create(:case_contact, occurred_at: 5.day.ago, casa_case: casa_case)\n\n      result = court_report_context.filtered_interviewees.map(&:case_contact)\n\n      expect(result).to match_array(CaseContact.all)\n    end\n\n    it \"returns an empty array if there are no interviewees\" do\n      casa_case = create(:casa_case)\n      court_report_context = build(:case_court_report_context, start_date: 5.day.ago.to_s, end_date: nil, casa_case: casa_case)\n\n      result = court_report_context.filtered_interviewees.map(&:case_contact)\n\n      expect(result).to be_empty\n    end\n  end\n\n  describe \"#context\" do\n    let(:court_report_context) { build(:case_court_report_context) }\n\n    describe \":created_date\" do\n      it \"has a created date equal to the current date\" do\n        expect(court_report_context.context[:created_date]).to eq(\"January 1, 2021\")\n      end\n    end\n  end\n\n  describe \"#volunteer_info\" do\n    let(:volunteer) { create(:volunteer, display_name: \"Y>cy%F7v;\\\\].-g$\", supervisor: build(:supervisor, display_name: \"Mm^ED;`zg(g<Z]q\")) }\n    let(:context) { build(:case_court_report_context, volunteer: volunteer) }\n\n    it \"correctly transforms the info\" do\n      expected = {\n        name: \"Y>cy%F7v;\\\\].-g$\",\n        supervisor_name: \"Mm^ED;`zg(g<Z]q\",\n        assignment_date: \"January 1, 2021\" # This is the default set in the spec\n      }\n\n      expect(context.volunteer_info).to eq(expected)\n    end\n  end\nend\n\ndef build_context(start_date:, end_date:, court_date:, time_zone:)\n  args = {time_zone: time_zone, start_date: start_date.to_s, end_date: end_date.to_s}\n\n  if court_date\n    court_date_object = build(:court_date, date: court_date)\n    court_case = create(:casa_case, court_dates: [court_date_object])\n    args[:casa_case] = court_case if court_date\n  end\n\n  build(:case_court_report_context, **args)\nend\n\ndef zone_days_ago(days)\n  ActiveSupport::TimeZone.new(A_TIMEZONE).now - days.days\nend\n\ndef days_ago(days)\n  days.days.ago\nend\n"
  },
  {
    "path": "spec/models/case_court_report_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\nrequire \"sablon\"\n\nRSpec.describe CaseCourtReport, type: :model do\n  include DownloadHelpers\n  let(:path_to_template) { Rails.root.join(\"app/documents/templates/default_report_template.docx\").to_s }\n  let(:path_to_report) { Rails.root.join(\"tmp/test_report.docx\").to_s }\n\n  describe \"#generate_to_string\" do\n    let(:full_context) do\n      {\n        created_date: \"April 9, 2024\",\n        casa_case: {\n          court_date: \"April 23, 2024\",\n          case_number: \"A-CASA-CASE-NUMBER-12345\",\n          dob: \"April 2012\",\n          is_transitioning: false,\n          judge_name: \"Judge Judy\"\n        },\n        case_contacts: [\n          {name: \"Some Name\", type: \"Type 1\", dates: \"4/09*\", dates_by_medium_type: {\"in-person\" => \"4/09*\"}},\n          {name: \"Some Other Name\", type: \"Type 4\", dates: \"4/09*\", dates_by_medium_type: {\"in-person\" => \"4/09*\"}}\n        ],\n        case_court_orders: [\n          {order: \"case_court_order_text\", status: \"Partially implemented\"}\n        ],\n        case_mandates: [\n          {order: \"case_mandates_text\", status: \"Partially implemented\"}\n        ],\n        latest_hearing_date: \"___<LATEST HEARING DATE>____\",\n        org_address: \"596 Unique Avenue Seattle, Washington\",\n        volunteer: {\n          name: \"name_of_volunteer\",\n          supervisor_name: \"name_of_supervisor\",\n          assignment_date: \"February 9, 2024\"\n        },\n        hearing_type_name: \"None\",\n        case_topics: [\n          {topic: \"Question 1\", details: \"Details 1\", answers: [\n            {date: \"12/01/20\", medium: \"Type A1, Type B1\", value: \"Answer 1\"},\n            {date: \"12/02/20\", medium: \"Type A2, Type B2\", value: \"Answer 3\"}\n          ]},\n          {topic: \"Question 2\", details: \"Details 2\", answers: [\n            {date: \"12/01/20\", medium: \"Type A1, Type B1\", value: \"Answer 2\"},\n            {date: \"12/02/20\", medium: \"Type A3, Type B3\", value: \"Answer 5\"}\n          ]},\n          {topic: \"Question 3\", details: \"Details 3\", answers: [\n            {date: \"12/01/20\", medium: \"Type A3, Type B3\", value: \"No Answer Provided\"},\n            {date: \"12/02/20\", medium: \"Type A2, Type B2\", value: \"No Answer Provided\"}\n          ]}\n        ]\n      }\n    end\n\n    describe \"contact_topics\" do\n      it \"all contact topics are present in the report\" do\n        docx_response = generate_doc(full_context, path_to_template)\n        expect(docx_response.paragraphs.map(&:to_s)).to include(/Question 1.*/)\n        expect(docx_response.paragraphs.map(&:to_s)).to include(/Question 2.*/)\n        expect(docx_response.paragraphs.map(&:to_s)).to include(/Question 3.*/)\n      end\n\n      it \"all topic details are present in the report\" do\n        docx_response = generate_doc(full_context, path_to_template)\n        expect(docx_response.paragraphs.map(&:to_s)).to include(/Details 1.*/)\n        expect(docx_response.paragraphs.map(&:to_s)).to include(/Details 2.*/)\n        expect(docx_response.paragraphs.map(&:to_s)).to include(/Details 3.*/)\n      end\n\n      it \"all answers are present with correct format\" do\n        docx_response = generate_doc(full_context, path_to_template)\n        expect(docx_response.paragraphs.map(&:to_s)).to include(/Type A1, Type B1 \\(12\\/01\\/20\\): Answer 1.*/)\n        expect(docx_response.paragraphs.map(&:to_s)).to include(/Type A2, Type B2 \\(12\\/02\\/20\\): Answer 3.*/)\n        expect(docx_response.paragraphs.map(&:to_s)).to include(/Type A1, Type B1 \\(12\\/01\\/20\\): Answer 2.*/)\n        expect(docx_response.paragraphs.map(&:to_s)).to include(/Type A3, Type B3 \\(12\\/02\\/20\\): Answer 5.*/)\n        expect(docx_response.paragraphs.map(&:to_s)).to include(/Type A3, Type B3 \\(12\\/01\\/20\\): No Answer Provided.*/)\n        expect(docx_response.paragraphs.map(&:to_s)).to include(/Type A2, Type B2 \\(12\\/02\\/20\\): No Answer Provided.*/)\n      end\n\n      context \"when there are topics but no answers\" do\n        let(:curr_context) do\n          full_context[:case_topics] = [\n            {topic: \"Question 1\", details: \"Details 1\", answers: []},\n            {topic: \"Question 2\", details: \"Details 2\", answers: []},\n            {topic: \"Question 3\", details: \"Details 3\", answers: []}\n          ]\n        end\n\n        it \"all contact topics are present in the report\" do\n          docx_response = generate_doc(full_context, path_to_template)\n          expect(docx_response.paragraphs.map(&:to_s)).to include(/Question 1.*/)\n          expect(docx_response.paragraphs.map(&:to_s)).to include(/Question 2.*/)\n          expect(docx_response.paragraphs.map(&:to_s)).to include(/Question 3.*/)\n        end\n\n        it \"all topic details are present in the report\" do\n          docx_response = generate_doc(full_context, path_to_template)\n          expect(docx_response.paragraphs.map(&:to_s)).to include(/Details 1.*/)\n          expect(docx_response.paragraphs.map(&:to_s)).to include(/Details 2.*/)\n          expect(docx_response.paragraphs.map(&:to_s)).to include(/Details 3.*/)\n        end\n      end\n\n      context \"when there no topics\" do\n        it \"report does not error and puts old defaults\" do\n          full_context[:case_topics] = []\n          docx_response = nil\n          expect {\n            docx_response = generate_doc(full_context, path_to_template)\n          }.not_to raise_error\n\n          expect(docx_response).not_to be_nil\n          expect(docx_response.paragraphs.map(&:to_s)).to include(/Placement.*/)\n          expect(docx_response.paragraphs.map(&:to_s)).to include(/Education\\/Vocation.*/)\n          expect(docx_response.paragraphs.map(&:to_s)).to include(/Objective Information.*/)\n        end\n      end\n    end\n\n    describe \"when receiving valid case, volunteer, and path_to_template\" do\n      let(:volunteer) { create(:volunteer, :with_cases_and_contacts, :with_assigned_supervisor) }\n      let(:casa_case_with_contacts) { volunteer.casa_cases.first }\n      let(:casa_case_without_contacts) { volunteer.casa_cases.second }\n      let(:report) do\n        args = {\n          case_id: casa_case_with_contacts.id,\n          volunteer_id: volunteer.id,\n          path_to_template: path_to_template,\n          path_to_report: path_to_report\n        }\n        context = CaseCourtReportContext.new(args).context\n        CaseCourtReport.new(path_to_template: path_to_template, context: context)\n      end\n\n      describe \"with volunteer without supervisor\" do\n        let(:volunteer) { create(:volunteer, :with_cases_and_contacts) }\n\n        it \"has supervisor name placeholder\" do\n          expect(report.context[:volunteer][:supervisor_name]).to eq(\"\")\n        end\n      end\n\n      describe \"has valid @context\" do\n        subject { report.context }\n\n        it { is_expected.not_to be_empty }\n        it { is_expected.to be_instance_of Hash }\n\n        it \"has the following keys [:created_date, :casa_case, :case_contacts, :latest_hearing_date, :org_address, :volunteer]\" do\n          expected = %i[created_date casa_case case_contacts volunteer]\n          expect(subject.keys).to include(*expected)\n        end\n\n        it \"must have Case Contacts as type Array\" do\n          expect(subject[:case_contacts]).to be_instance_of Array\n        end\n\n        it \"created_date is not nil\" do\n          expect(subject[:created_date]).not_to be_nil\n        end\n\n        context \"when the case has multiple past court dates\" do\n          before do\n            casa_case_with_contacts.court_dates << create(:court_date, date: 9.months.ago)\n            casa_case_with_contacts.court_dates << create(:court_date, date: 3.months.ago)\n            casa_case_with_contacts.court_dates << create(:court_date, date: 15.months.ago)\n          end\n\n          it \"sets latest_hearing_date as the latest past court date\" do\n            expect(subject[:latest_hearing_date]).to eq(I18n.l(3.months.ago, format: :full, default: nil))\n          end\n        end\n      end\n\n      describe \"the default generated report\" do\n        context \"when passed all displayable information\" do\n          let(:document_data) do\n            {\n              case_birthday: 12.years.ago,\n              case_contact_time: 3.days.ago,\n              case_contact_type: \"Unique Case Contact Type\",\n              case_hearing_date: 2.weeks.from_now,\n              case_number: \"A-CASA-CASE-NUMBER-12345\",\n              text: \"This text shall not be strikingly similar to other text in the document\",\n              org_address: \"596 Unique Avenue Seattle, Washington\",\n              supervisor_name: \"A very unique supervisor name\",\n              volunteer_case_assignment_date: 2.months.ago,\n              volunteer_name: \"An unmistakably unique volunteer name\"\n            }\n          end\n\n          let(:contact_type) { create(:contact_type, name: document_data[:case_contact_type]) }\n          let(:case_contact) { create(:case_contact, contact_made: false, occurred_at: document_data[:case_contact_time]) }\n          let(:court_order) { create(:case_court_order, implementation_status: :partially_implemented) }\n\n          before do\n            casa_case_with_contacts.casa_org.update_attribute(:address, document_data[:org_address])\n            casa_case_with_contacts.update_attribute(:birth_month_year_youth, document_data[:case_birthday])\n            casa_case_with_contacts.update_attribute(:case_number, document_data[:case_number])\n            create(:court_date, casa_case: casa_case_with_contacts, date: document_data[:case_hearing_date])\n            case_contact.contact_types << contact_type\n            casa_case_with_contacts.case_contacts << case_contact\n            casa_case_with_contacts.case_court_orders << court_order\n            court_order.update_attribute(:text, document_data[:text])\n            CaseAssignment.find_by(casa_case_id: casa_case_with_contacts.id, volunteer_id: volunteer.id).update_attribute(:created_at, document_data[:volunteer_case_assignment_date])\n            volunteer.update_attribute(:display_name, document_data[:volunteer_name])\n            volunteer.supervisor.update_attribute(:display_name, document_data[:supervisor_name])\n          end\n\n          it \"displays the org address\" do\n            docx_response = Docx::Document.open(StringIO.new(report.generate_to_string))\n            expect(header_text(docx_response)).to include(document_data[:org_address])\n          end\n\n          it \"displays today's date formatted\" do\n            docx_response = Docx::Document.open(StringIO.new(report.generate_to_string))\n            expect(docx_response.paragraphs.map(&:to_s)).to include(/#{Date.current.strftime(\"%B %-d, %Y\")}.*/)\n          end\n\n          it \"displays the case hearing date date formatted\" do\n            docx_response = Docx::Document.open(StringIO.new(report.generate_to_string))\n            expect(docx_response.paragraphs.map(&:to_s)).to include(/#{document_data[:case_hearing_date].strftime(\"%B %-d, %Y\")}.*/)\n          end\n\n          it \"displays the case number\" do\n            docx_response = Docx::Document.open(StringIO.new(report.generate_to_string))\n            expect(docx_response.paragraphs.map(&:to_s)).to include(/#{document_data[:case_number]}.*/)\n          end\n\n          it \"displays the case contact type\" do\n            docx_response = Docx::Document.open(StringIO.new(report.generate_to_string))\n\n            table_data = docx_response.tables.map { |t| t.rows.map(&:cells).flatten.map(&:to_s) }.flatten\n\n            expect(table_data).to include(/#{document_data[:case_contact_type]}.*/)\n          end\n\n          it \"displays the case contact time date formatted\" do\n            docx_response = Docx::Document.open(StringIO.new(report.generate_to_string))\n\n            table_data = docx_response.tables.map { |t| t.rows.map(&:cells).flatten.map(&:to_s) }.flatten\n\n            expect(table_data).to include(/#{document_data[:case_contact_time].strftime(\"%-m/%d\")}.*/)\n          end\n\n          it \"displays the text\" do\n            docx_response = Docx::Document.open(StringIO.new(report.generate_to_string))\n\n            table_data = docx_response.tables.map { |t| t.rows.map(&:cells).flatten.map(&:to_s) }.flatten\n\n            expect(table_data).to include(/#{document_data[:text]}.*/)\n          end\n\n          it \"displays the order status\" do\n            docx_response = Docx::Document.open(StringIO.new(report.generate_to_string))\n\n            table_data = docx_response.tables.map { |t| t.rows.map(&:cells).flatten.map(&:to_s) }.flatten\n\n            expect(table_data).to include(\"Partially implemented\")\n          end\n\n          it \"displays the volunteer name\" do\n            docx_response = Docx::Document.open(StringIO.new(report.generate_to_string))\n\n            table_data = docx_response.tables.map { |t| t.rows.map(&:cells).flatten.map(&:to_s) }.flatten\n\n            expect(table_data).to include(/#{document_data[:volunteer_name]}.*/)\n          end\n\n          it \"displays the volunteer case assignment date formatted\" do\n            docx_response = Docx::Document.open(StringIO.new(report.generate_to_string))\n\n            table_data = docx_response.tables.map { |t| t.rows.map(&:cells).flatten.map(&:to_s) }.flatten\n\n            expect(table_data).to include(/#{document_data[:volunteer_case_assignment_date].strftime(\"%B %-d, %Y\")}.*/)\n          end\n\n          it \"displayes the supervisor name\" do\n            docx_response = Docx::Document.open(StringIO.new(report.generate_to_string))\n\n            table_data = docx_response.tables.map { |t| t.rows.map(&:cells).flatten.map(&:to_s) }.flatten\n\n            expect(table_data).to include(/#{document_data[:supervisor_name]}.*/)\n          end\n        end\n\n        context \"when missing a volunteer\" do\n          let(:report) do\n            args = {\n              case_id: casa_case.id,\n              volunteer_id: nil,\n              path_to_template: path_to_template,\n              path_to_report: path_to_report\n            }\n            context = CaseCourtReportContext.new(args).context\n            CaseCourtReport.new(path_to_template: path_to_template, context: context)\n          end\n\n          let(:document_data) do\n            {\n              case_birthday: 12.years.ago,\n              case_contact_time: 3.days.ago,\n              case_contact_type: \"Unique Case Contact Type\",\n              case_hearing_date: 2.weeks.from_now,\n              case_number: \"A-CASA-CASE-NUMBER-12345\",\n              text: \"This text shall not be strikingly similar to other text in the document\",\n              org_address: nil,\n              supervisor_name: nil,\n              volunteer_case_assignment_date: 2.months.ago,\n              volunteer_name: nil\n            }\n          end\n\n          let(:casa_case) { create(:casa_case) }\n          let(:contact_type) { create(:contact_type, name: document_data[:case_contact_type]) }\n          let(:case_contact) { create(:case_contact, contact_made: false, occurred_at: document_data[:case_contact_time]) }\n          let(:court_order) { create(:case_court_order, implementation_status: :partially_implemented) }\n\n          before do\n            casa_case.casa_org.update_attribute(:address, document_data[:org_address])\n            casa_case.update_attribute(:birth_month_year_youth, document_data[:case_birthday])\n            casa_case.update_attribute(:case_number, document_data[:case_number])\n            create(:court_date, casa_case: casa_case, date: document_data[:case_hearing_date])\n            case_contact.contact_types << contact_type\n            casa_case.case_contacts << case_contact\n            casa_case.case_court_orders << court_order\n            court_order.update_attribute(:text, document_data[:text])\n          end\n\n          it \"displays today's date formatted\" do\n            docx_response = Docx::Document.open(StringIO.new(report.generate_to_string))\n\n            expect(docx_response.paragraphs.map(&:to_s)).to include(/#{Date.current.strftime(\"%B %-d, %Y\")}.*/)\n          end\n\n          it \"displays the case hearing date formatted\" do\n            docx_response = Docx::Document.open(StringIO.new(report.generate_to_string))\n\n            expect(docx_response.paragraphs.map(&:to_s)).to include(/#{document_data[:case_hearing_date].strftime(\"%B %-d, %Y\")}.*/)\n          end\n\n          it \"displays the case number\" do\n            docx_response = Docx::Document.open(StringIO.new(report.generate_to_string))\n            expect(docx_response.paragraphs.map(&:to_s)).to include(/.*#{document_data[:case_number]}.*/)\n          end\n\n          it \"displays the case contact type\" do\n            docx_response = Docx::Document.open(StringIO.new(report.generate_to_string))\n\n            table_data = docx_response.tables.map { |t| t.rows.map(&:cells).flatten.map(&:to_s) }.flatten\n\n            expect(table_data).to include(document_data[:case_contact_type])\n          end\n\n          it \"displays the case contact time formatted\" do\n            docx_response = Docx::Document.open(StringIO.new(report.generate_to_string))\n\n            table_data = docx_response.tables.map { |t| t.rows.map(&:cells).flatten.map(&:to_s) }.flatten\n\n            expect(table_data).to include(document_data[:case_contact_time].strftime(\"%-m/%d*\"))\n          end\n\n          it \"displays the test\" do\n            docx_response = Docx::Document.open(StringIO.new(report.generate_to_string))\n\n            table_data = docx_response.tables.map { |t| t.rows.map(&:cells).flatten.map(&:to_s) }.flatten\n\n            expect(table_data).to include(\"This text shall not be strikingly similar to other text in the document\")\n          end\n\n          it \"displays the order status\" do\n            docx_response = Docx::Document.open(StringIO.new(report.generate_to_string))\n\n            table_data = docx_response.tables.map { |t| t.rows.map(&:cells).flatten.map(&:to_s) }.flatten\n\n            expect(table_data).to include(\"Partially implemented\")\n          end\n        end\n      end\n    end\n\n    describe \"when receiving INVALID path_to_template\" do\n      let(:volunteer) { create(:volunteer, :with_cases_and_contacts, :with_assigned_supervisor) }\n      let(:casa_case_with_contacts) { volunteer.casa_cases.first }\n      let(:nonexistent_path) { \"app/documents/templates/nonexisitent_report_template.docx\" }\n\n      it \"raises Zip::Error when generating report\" do\n        args = {\n          case_id: casa_case_with_contacts.id,\n          volunteer_id: volunteer.id,\n          path_to_template: nonexistent_path\n        }\n        context = CaseCourtReportContext.new(args).context\n        expect {\n          CaseCourtReport.new(path_to_template: nonexistent_path, context: context)\n        }.to raise_error(Zip::Error, /Template file not found/)\n      end\n    end\n\n    describe \"when court orders has different implementation statuses\" do\n      let(:casa_case) { create(:casa_case, case_number: \"Sample-Case-12345\") }\n      let(:court_order_implemented) { create(:case_court_order, casa_case: casa_case, text: \"an order that got done\", implementation_status: :implemented) }\n      let(:court_order_unimplemented) { create(:case_court_order, casa_case: casa_case, text: \"an order that got not done\", implementation_status: :unimplemented) }\n      let(:court_order_partially_implemented) { create(:case_court_order, casa_case: casa_case, text: \"an order that got kinda done\", implementation_status: :partially_implemented) }\n      let(:court_order_not_specified) { create(:case_court_order, casa_case: casa_case, text: \"what is going on\", implementation_status: nil) }\n      let(:args) do\n        {\n          case_id: casa_case.id,\n          path_to_template: path_to_template,\n          path_to_report: path_to_report\n        }\n      end\n      let(:context) { CaseCourtReportContext.new(args).context }\n      let(:case_report) { CaseCourtReport.new(path_to_template: path_to_template, context: context) }\n\n      before do\n        casa_case.case_court_orders << court_order_implemented\n        casa_case.case_court_orders << court_order_unimplemented\n        casa_case.case_court_orders << court_order_partially_implemented\n        casa_case.case_court_orders << court_order_not_specified\n      end\n\n      it \"contains the case number\" do\n        docx_response = Docx::Document.open(StringIO.new(case_report.generate_to_string))\n\n        expect(docx_response.paragraphs.map(&:to_s)).to include(/#{casa_case.case_number}*/)\n      end\n\n      it \"contains the court order text\" do\n        docx_response = Docx::Document.open(StringIO.new(case_report.generate_to_string))\n\n        expect(table_text(docx_response)).to include(/#{court_order_implemented.text}.*/)\n      end\n\n      it \"contains the exact value of 'Implemented'\" do\n        docx_response = Docx::Document.open(StringIO.new(case_report.generate_to_string))\n\n        expect(table_text(docx_response)).to include(/Implemented.*/)\n      end\n\n      it \"contains the court order text\" do\n        docx_response = Docx::Document.open(StringIO.new(case_report.generate_to_string))\n\n        expect(table_text(docx_response)).to include(/#{court_order_unimplemented.text}.*/)\n      end\n\n      it \"contains the exact value of 'Unimplemented'\" do\n        docx_response = Docx::Document.open(StringIO.new(case_report.generate_to_string))\n\n        expect(table_text(docx_response)).to include(/Unimplemented.*/)\n      end\n\n      it \"contains the court order text\" do\n        docx_response = Docx::Document.open(StringIO.new(case_report.generate_to_string))\n\n        expect(table_text(docx_response)).to include(/#{court_order_partially_implemented.text}.*/)\n      end\n\n      it \"contains the exact value of 'Partially implemented'\" do\n        docx_response = Docx::Document.open(StringIO.new(case_report.generate_to_string))\n\n        expect(table_text(docx_response)).to include(/Partially implemented.*/)\n      end\n\n      it \"contains the court order text\" do\n        docx_response = Docx::Document.open(StringIO.new(case_report.generate_to_string))\n\n        expect(table_text(docx_response)).to include(/#{court_order_not_specified.text}.*/)\n      end\n\n      it \"contains the exact value of 'Not specified'\" do\n        docx_response = Docx::Document.open(StringIO.new(case_report.generate_to_string))\n\n        expect(table_text(docx_response)).to include(/Not specified.*/)\n      end\n    end\n  end\nend\n\ndef generate_doc(context, path_to_template)\n  report = CaseCourtReport.new(path_to_template: path_to_template, context: context)\n  Docx::Document.open(StringIO.new(report.generate_to_string))\nend\n"
  },
  {
    "path": "spec/models/case_group_membership_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CaseGroupMembership, type: :model do\n  it \"has a valid factory\" do\n    case_group_membership = build(:case_group_membership)\n\n    expect(case_group_membership).to be_valid\n  end\nend\n"
  },
  {
    "path": "spec/models/case_group_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CaseGroup, type: :model do\n  describe \"validations\" do\n    it { is_expected.to validate_presence_of(:case_group_memberships) }\n\n    it \"validates uniqueness of name scoped to casa_org\" do\n      casa_org = create(:casa_org)\n      create(:case_group, casa_org: casa_org, name: \"The Johnson Family\")\n      non_uniq_case_group = build(:case_group, casa_org: casa_org, name: \"The Johnson Family\")\n      non_uniq_case_group_whitespace = build(:case_group, casa_org: casa_org, name: \"The Johnson Family   \")\n      non_uniq_case_group_case_sensitive = build(:case_group, casa_org: casa_org, name: \"The Johnson family\")\n\n      expect(non_uniq_case_group).not_to be_valid\n      expect(non_uniq_case_group_case_sensitive).not_to be_valid\n      expect(non_uniq_case_group_whitespace).not_to be_valid\n      expect(non_uniq_case_group.errors[:name]).to include(\"has already been taken\")\n      expect(non_uniq_case_group_case_sensitive.errors[:name]).to include(\"has already been taken\")\n      expect(non_uniq_case_group_whitespace.errors[:name]).to include(\"has already been taken\")\n    end\n  end\n\n  describe \"relationships\" do\n    it { is_expected.to have_many(:case_group_memberships) }\n    it { is_expected.to have_many(:casa_cases).through(:case_group_memberships) }\n  end\n\n  it \"has a valid factory\" do\n    case_group = build(:case_group)\n\n    expect(case_group).to be_valid\n  end\nend\n"
  },
  {
    "path": "spec/models/checklist_item_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe ChecklistItem, type: :model do\n  describe \"associations\" do\n    it { is_expected.to belong_to(:hearing_type) }\n  end\n\n  describe \"validations\" do\n    it { is_expected.to validate_presence_of(:description) }\n    it { is_expected.to validate_presence_of(:category) }\n  end\nend\n"
  },
  {
    "path": "spec/models/concerns/CasaCase/validations_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CasaCase::Validations, type: :model do\n  # TODO: Add tests for CasaCase::Validations\n\n  pending \"add some tests for CasaCase::Validations\"\nend\n"
  },
  {
    "path": "spec/models/concerns/api_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe Api, type: :model do\n  # TODO: Add tests for Api\n\n  pending \"add some tests for Api\"\nend\n"
  },
  {
    "path": "spec/models/concerns/by_organization_scope_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe ByOrganizationScope, type: :model do\n  # TODO: Add tests for ByOrganizationScope\n\n  pending \"add some tests for ByOrganizationScope\"\nend\n"
  },
  {
    "path": "spec/models/concerns/roles_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe Roles, type: :model do\n  # TODO: Add tests for Roles\n\n  pending \"add some tests for Roles\"\nend\n"
  },
  {
    "path": "spec/models/contact_topic_answer_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe ContactTopicAnswer, type: :model do\n  it { is_expected.to belong_to(:case_contact) }\n  it { is_expected.to belong_to(:contact_topic).optional(true) }\n  it { is_expected.to have_one(:contact_creator).through(:case_contact) }\n  it { is_expected.to have_one(:contact_creator_casa_org).through(:contact_creator) }\n\n  it \"can hold more than 255 characters\" do\n    expect {\n      create(:contact_topic_answer, value: Faker::Lorem.characters(number: 300))\n    }.not_to raise_error\n  end\n\n  it \"soft deletes record instead of removing it from database\" do\n    answer = create(:contact_topic_answer)\n\n    answer.destroy\n\n    expect(answer.deleted_at).not_to be_nil\n    expect(ContactTopicAnswer.with_deleted).to include(answer)\n    expect(ContactTopicAnswer.all).not_to include(answer)\n\n    answer.restore\n\n    expect(answer.deleted_at).to be_nil\n    expect(ContactTopicAnswer.all).to include(answer)\n  end\nend\n"
  },
  {
    "path": "spec/models/contact_topic_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe ContactTopic, type: :model do\n  it { is_expected.to belong_to(:casa_org) }\n  it { is_expected.to have_many(:contact_topic_answers) }\n\n  it { is_expected.to validate_presence_of(:question) }\n  it { is_expected.to validate_presence_of(:details) }\n\n  describe \"scopes\" do\n    describe \".active\" do\n      it \"returns only active and non-soft deleted contact topics\" do\n        active_contact_topic = create(:contact_topic, active: true, soft_delete: false)\n        inactive_contact_topic = create(:contact_topic, active: false, soft_delete: false)\n        soft_deleted_contact_topic = create(:contact_topic, active: true, soft_delete: true)\n\n        expect(ContactTopic.active).to include(active_contact_topic)\n        expect(ContactTopic.active).not_to include(inactive_contact_topic)\n        expect(ContactTopic.active).not_to include(soft_deleted_contact_topic)\n      end\n    end\n  end\n\n  describe \"generate for org\" do\n    let(:org) { create(:casa_org) }\n    let(:fake_topics) { [{\"question\" => \"Test Title\", \"details\" => \"Test details\"}] }\n\n    describe \"generate_contact_topics\" do\n      before do\n        allow(ContactTopic).to receive(:default_contact_topics).and_return(fake_topics)\n      end\n\n      it \"creates contact topics\" do\n        expect { ContactTopic.generate_for_org!(org) }.to change { org.contact_topics.count }.by(1)\n\n        created_topic = org.contact_topics.first\n        expect(created_topic.question).to eq(fake_topics.first[\"question\"])\n        expect(created_topic.details).to eq(fake_topics.first[\"details\"])\n      end\n\n      context \"there are no default topics\" do\n        let(:fake_topics) { [] }\n\n        it { expect { ContactTopic.generate_for_org!(org) }.not_to(change { org.contact_topics.count }) }\n      end\n\n      it \"generates from parameter\" do\n        topics = fake_topics.push({\"question\" => \"a\", \"details\" => \"a\"})\n        expect { ContactTopic.generate_for_org!(org) }.to change { org.contact_topics.count }.by(2)\n\n        questions = org.contact_topics.map(&:question)\n        details = org.contact_topics.map(&:details)\n        expect(questions).to match_array(topics.pluck(\"question\"))\n        expect(details).to match_array(topics.pluck(\"details\"))\n      end\n\n      it \"fails if not all required attrs are present\" do\n        fake_topics.first[\"question\"] = nil\n\n        expect { ContactTopic.generate_for_org!(org) }.to raise_error(ActiveRecord::RecordInvalid)\n      end\n\n      it \"creates if needed fields all present\" do\n        fake_topics.first[\"invalid_field\"] = \"invalid\"\n        expect { ContactTopic.generate_for_org!(org) }.to change { org.contact_topics.count }.by(1)\n      end\n    end\n  end\n\n  describe \"details\" do\n    it \"can hold more than 255 characters\" do\n      contact_topic_details = build(:contact_topic, details: Faker::Lorem.characters(number: 300))\n      expect { contact_topic_details.save! }.not_to raise_error\n    end\n  end\nend\n"
  },
  {
    "path": "spec/models/contact_type_group_spec.rb",
    "content": "require \"rails_helper\"\n# require \"contact_type_group\"\nrequire \"./app/models/contact_type_group\"\n\nRSpec.describe ContactTypeGroup, type: :model do\n  it \"does not have duplicate names\" do\n    org_id = create(:casa_org).id\n    create_contact_type_group = -> { create(:contact_type_group, {name: \"Test1\", casa_org_id: org_id}) }\n    create_contact_type_group.call\n    expect { create_contact_type_group.call }.to raise_error(ActiveRecord::RecordInvalid, \"Validation failed: Name has already been taken\")\n  end\n\n  describe \"for_organization\" do\n    let!(:casa_org_1) { create(:casa_org) }\n    let!(:casa_org_2) { create(:casa_org) }\n    let!(:record_1) { create(:contact_type_group, casa_org: casa_org_1) }\n    let!(:record_2) { create(:contact_type_group, casa_org: casa_org_2) }\n\n    it \"returns only records matching the specified organization\" do\n      expect(described_class.for_organization(casa_org_1)).to eq([record_1])\n      expect(described_class.for_organization(casa_org_2)).to eq([record_2])\n    end\n  end\n\n  describe \".alphabetically scope\" do\n    subject { described_class.alphabetically }\n\n    let!(:contact_type_group1) { create(:contact_type_group, name: \"Family\") }\n    let!(:contact_type_group2) { create(:contact_type_group, name: \"Placement\") }\n\n    it \"orders alphabetically\", :aggregate_failures do\n      expect(subject.first).to eq(contact_type_group1)\n      expect(subject.last).to eq(contact_type_group2)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/models/contact_type_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe ContactType, type: :model do\n  let(:contact_type_group) { create(:contact_type_group, name: \"Group 1\") }\n  let(:contact_type) { create(:contact_type, name: \"Type 1\", contact_type_group: contact_type_group) }\n\n  describe \"#create\" do\n    it \"does have a unique name\" do\n      new_contact_type = create(:contact_type, name: \"Type 1\", contact_type_group: contact_type_group)\n      expect(subject).to validate_presence_of(:name)\n      expect(new_contact_type).to validate_uniqueness_of(:name).scoped_to(:contact_type_group_id)\n        .with_message(\"should be unique per contact type group\")\n    end\n  end\n\n  describe \"#update\" do\n    it \"can update to a valid name\" do\n      contact_type.name = \"New name\"\n      contact_type.save\n      expect(contact_type.name).to eq(\"New name\")\n    end\n\n    it \"can't update to an invalid name\" do\n      contact_type.name = nil\n      expect { contact_type.save! }.to raise_error(ActiveRecord::RecordInvalid, \"Validation failed: Name can't be blank\")\n    end\n\n    it \"can update contact type group\" do\n      new_group = create(:contact_type_group, name: \"New contact group\")\n      contact_type.contact_type_group_id = new_group.id\n      expect(contact_type.contact_type_group.name).to eq(\"New contact group\")\n    end\n\n    it \"can deactivate contact type\" do\n      contact_type.active = false\n      expect(contact_type.active?).to be_falsey\n    end\n  end\n\n  describe \"for_organization\" do\n    let!(:casa_org_1) { create(:casa_org) }\n    let!(:casa_org_2) { create(:casa_org) }\n    let!(:contact_type_group_record_1) { create(:contact_type_group, casa_org: casa_org_1) }\n    let!(:contact_type_group_record_2) { create(:contact_type_group, casa_org: casa_org_2) }\n    let!(:record_1) { create(:contact_type, contact_type_group: contact_type_group_record_1) }\n    let!(:record_2) { create(:contact_type, contact_type_group: contact_type_group_record_2) }\n\n    it \"returns only records matching the specified organization\" do\n      expect(described_class.for_organization(casa_org_1)).to eq([record_1])\n      expect(described_class.for_organization(casa_org_2)).to eq([record_2])\n    end\n  end\n\n  describe \".alphabetically scope\" do\n    subject { described_class.alphabetically }\n\n    let!(:contact_type1) { create(:contact_type, name: \"Aunt Uncle or Cousin\") }\n    let!(:contact_type2) { create(:contact_type, name: \"Parent\") }\n\n    it \"orders alphabetically\", :aggregate_failures do\n      expect(subject.first).to eq(contact_type1)\n      expect(subject.last).to eq(contact_type2)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/models/court_date_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CourtDate, type: :model do\n  subject(:court_date) { create(:court_date, casa_case: casa_case) }\n\n  let(:casa_case) { create(:casa_case, case_number: \"AAA123123\") }\n  let(:volunteer) { create(:volunteer, casa_org: casa_case.casa_org) }\n  let!(:case_assignment) { create(:case_assignment, volunteer: volunteer, casa_case: casa_case) }\n  let(:this_court_date) { subject.date }\n  let(:older_court_date) { subject.date - 6.months }\n  let(:path_to_template) { Rails.root.join(\"app/documents/templates/default_report_template.docx\").to_s }\n  let(:path_to_report) { Rails.root.join(\"tmp/test_report.docx\").to_s }\n\n  before do\n    travel_to Date.new(2021, 1, 1)\n  end\n\n  it { is_expected.to belong_to(:casa_case) }\n  it { is_expected.to validate_presence_of(:date) }\n  it { is_expected.to have_many(:case_court_orders) }\n  it { is_expected.to belong_to(:hearing_type).optional }\n  it { is_expected.to belong_to(:judge).optional }\n\n  describe \"date validation\" do\n    it \"is not valid before 1989\" do\n      court_date = CourtDate.new(date: \"1984-01-01\".to_date)\n      expect(court_date.valid?).to be false\n      expect(court_date.errors[:date]).to eq([\"is not valid. Court date cannot be prior to 1/1/1989.\"])\n    end\n\n    it \"is not valid more than 1 year in the future\" do\n      court_date = CourtDate.new(date: 367.days.from_now)\n      expect(court_date.valid?).to be false\n      expect(court_date.errors[:date]).to eq([\"is not valid. Court date must be within one year from today.\"])\n    end\n\n    it \"is valid within one year in the future\" do\n      court_date = CourtDate.new(date: 6.months.from_now)\n      court_date.valid?\n      expect(court_date.errors[:date]).to eq([])\n    end\n\n    it \"is valid in the past after 1989\" do\n      court_date = CourtDate.new(date: \"1997-08-29\".to_date)\n      court_date.valid?\n      expect(court_date.errors[:date]).to eq([])\n    end\n  end\n\n  it \"is invalid without a casa_case\" do\n    court_date = build(:court_date, casa_case: nil)\n    expect do\n      court_date.casa_case = casa_case\n    end.to change { court_date.valid? }.from(false).to(true)\n  end\n\n  describe \".ordered_ascending\" do\n    subject { described_class.ordered_ascending }\n\n    it \"orders the casa cases by updated at date\" do\n      very_old_pcd = create(:court_date, date: 10.days.ago)\n      old_pcd = create(:court_date, date: 5.day.ago)\n      recent_pcd = create(:court_date, date: 1.day.ago)\n\n      ordered_pcds = described_class.ordered_ascending\n\n      expect(ordered_pcds.map(&:id)).to eq [very_old_pcd.id, old_pcd.id, recent_pcd.id]\n    end\n  end\n\n  describe \"reports\" do\n    let!(:reports) do\n      [10, 30, 60].map do |days_ago|\n        path_to_template = \"app/documents/templates/default_report_template.docx\"\n        args = {\n          case_id: casa_case.id,\n          volunteer_id: volunteer.id,\n          path_to_template: path_to_template\n        }\n        context = CaseCourtReportContext.new(args).context\n        report = CaseCourtReport.new(path_to_template: path_to_template, context: context)\n        casa_case.court_reports.attach(io: StringIO.new(report.generate_to_string), filename: \"report-#{days_ago}.docx\")\n        attached_report = casa_case.latest_court_report\n        attached_report.created_at = days_ago.days.ago\n        attached_report.save!\n        attached_report\n      end\n    end\n    let(:ten_days_ago_report) { reports[0] }\n    let(:thirty_days_ago_report) { reports[1] }\n    let(:sixty_days_ago_report) { reports[2] }\n\n    describe \"#associated_reports\" do\n      subject(:associated_reports) { court_date.associated_reports }\n\n      context \"without other court dates\" do\n        it { is_expected.to eq [ten_days_ago_report, thirty_days_ago_report, sixty_days_ago_report] }\n      end\n\n      context \"with a previous court date\" do\n        let!(:other_court_date) { create(:court_date, casa_case: casa_case, date: 40.days.ago) }\n\n        it { is_expected.to eq [ten_days_ago_report, thirty_days_ago_report] }\n      end\n    end\n\n    describe \"#latest_associated_report\" do\n      it \"is the most recent report for the case\" do\n        expect(subject.latest_associated_report).to eq(ten_days_ago_report)\n      end\n    end\n  end\n\n  describe \"#additional_info?\" do\n    subject(:additional_info) { court_date.additional_info? }\n\n    context \"with orders\" do\n      it \"returns true\" do\n        create(:case_court_order, casa_case: casa_case, court_date: court_date)\n        expect(subject).to be_truthy\n      end\n    end\n\n    context \"with hearing type\" do\n      it \"returns true\" do\n        hearing_type = create(:hearing_type)\n        court_date.update!(hearing_type_id: hearing_type.id)\n        expect(subject).to be_truthy\n      end\n    end\n\n    context \"with judge\" do\n      it \"returns true\" do\n        judge = create(:judge)\n        court_date.update!(judge_id: judge.id)\n        expect(subject).to be_truthy\n      end\n    end\n\n    context \"with no extra data\" do\n      it \"returns false\" do\n        expect(subject).to be_falsy\n      end\n    end\n  end\n\n  describe \"#display_name\" do\n    subject { court_date.display_name }\n\n    it \"contains case number and date\" do\n      travel_to Time.zone.local(2020, 1, 2)\n      expect(subject).to eq(\"AAA123123 - Court Date - 2019-12-26\")\n      travel_back\n    end\n  end\nend\n"
  },
  {
    "path": "spec/models/custom_org_link_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CustomOrgLink, type: :model do\n  it { is_expected.to belong_to :casa_org }\n  it { is_expected.to validate_presence_of :text }\n  it { is_expected.to validate_presence_of :url }\n  it { is_expected.to validate_length_of(:text).is_at_most described_class::TEXT_MAX_LENGTH }\n\n  describe \"#trim_name\" do\n    let(:casa_org) { create(:casa_org) }\n\n    context \"when text is present\" do\n      it \"trims leading and trailing whitespace from text\" do\n        custom_link = build(:custom_org_link, casa_org: casa_org, text: \"  Example Text  \")\n        custom_link.save\n        expect(custom_link.text).to eq(\"Example Text\")\n      end\n    end\n  end\n\n  describe \"url validation - only allow http or https schemes\" do\n    it { is_expected.to allow_value(\"http://example.com\").for(:url) }\n    it { is_expected.to allow_value(\"https://example.com\").for(:url) }\n\n    it { is_expected.not_to allow_value(\"ftp://example.com\").for(:url) }\n    it { is_expected.not_to allow_value(\"example.com\").for(:url) }\n    it { is_expected.not_to allow_value(\"some arbitrary string\").for(:url) }\n  end\n\n  describe \"#active\" do\n    it \"only allows true or false\" do\n      casa_org = build(:casa_org)\n\n      expect(build(:custom_org_link, casa_org: casa_org, active: false)).to be_valid\n      expect(build(:custom_org_link, casa_org: casa_org, active: true)).to be_valid\n      expect(build(:custom_org_link, casa_org: casa_org, active: nil)).to be_invalid\n    end\n  end\nend\n"
  },
  {
    "path": "spec/models/emancipation_category_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe EmancipationCategory, type: :model do\n  it { is_expected.to have_many(:casa_case_emancipation_categories).dependent(:destroy) }\n  it { is_expected.to have_many(:casa_cases).through(:casa_case_emancipation_categories) }\n  it { is_expected.to have_many(:emancipation_options) }\n  it { is_expected.to validate_presence_of(:name) }\n\n  context \"When creating a new category\" do\n    it \"raises an exception for duplicate entries\" do\n      duplicate_category_name = \"test category\"\n\n      expect {\n        create(:emancipation_category, name: duplicate_category_name)\n        create(:emancipation_category, name: duplicate_category_name)\n      }.to raise_error(ActiveRecord::RecordNotUnique)\n    end\n  end\n\n  describe \"#add_option\" do\n    let(:emancipation_category) { create(:emancipation_category) }\n\n    after do\n      EmancipationOption.category_options(emancipation_category.id).destroy_all\n    end\n\n    it \"creates an option\" do\n      option_name = \"test option\"\n\n      expect {\n        emancipation_category.add_option(option_name)\n      }.to change(EmancipationOption, :count).by(1)\n    end\n  end\n\n  describe \"#delete_option\" do\n    let(:emancipation_category) { create(:emancipation_category) }\n\n    after do\n      EmancipationOption.category_options(emancipation_category.id).destroy_all\n    end\n\n    it \"deletes an existing option\" do\n      option_name = \"test option\"\n\n      emancipation_category.add_option(option_name)\n\n      expect {\n        emancipation_category.delete_option(option_name)\n      }.to change(EmancipationOption, :count).by(-1)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/models/emancipation_option_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe EmancipationOption, type: :model do\n  it { is_expected.to belong_to(:emancipation_category) }\n  it { is_expected.to have_many(:casa_case_emancipation_options).dependent(:destroy) }\n  it { is_expected.to have_many(:casa_cases).through(:casa_case_emancipation_options) }\n  it { is_expected.to validate_presence_of(:name) }\n\n  context \"When creating a new option\" do\n    context \"duplicate name entries\" do\n      duplicate_option_name = \"test option\"\n      let(:duplicate_category) { create(:emancipation_category) }\n      let(:non_duplicate_category) { create(:emancipation_category, name: \"Not the same name as the other category to satisfy unique contraints\") }\n\n      it \"is unique across emancipation_category, name\" do\n        eo = create(:emancipation_option)\n        eo_new = build(:emancipation_option, emancipation_category: eo.emancipation_category, name: eo.name)\n        expect(eo_new.valid?).to be false\n      end\n\n      it \"creates two new entries given different categories and same names\" do\n        expect {\n          build_stubbed(:emancipation_option, emancipation_category_id: non_duplicate_category.id, name: duplicate_option_name)\n          build_stubbed(:emancipation_option, emancipation_category_id: duplicate_category.id, name: duplicate_option_name)\n        }.not_to raise_error\n      end\n    end\n  end\n\n  describe \".category_options\" do\n    let(:category_a) { create(:emancipation_category, name: \"A\") }\n    let(:category_b) { create(:emancipation_category, name: \"B\") }\n    let(:option_a) { create(:emancipation_option, emancipation_category_id: category_a.id, name: \"A\") }\n    let(:option_b) { create(:emancipation_option, emancipation_category_id: category_a.id, name: \"B\") }\n    let(:option_c) { create(:emancipation_option, emancipation_category_id: category_a.id, name: \"C\") }\n    let(:option_d) { create(:emancipation_option, emancipation_category_id: category_b.id, name: \"D\") }\n\n    it \"contains exactly the options belonging to the category passed to it\" do\n      expect(EmancipationOption.category_options(category_a.id)).to contain_exactly(option_a, option_b, option_c)\n      expect(EmancipationOption.category_options(category_b.id)).to contain_exactly(option_d)\n    end\n  end\n\n  describe \".options_with_category_and_case\" do\n    let(:case_a) { create(:casa_case) }\n    let(:case_b) { create(:casa_case) }\n    let(:category_a) { create(:emancipation_category, name: \"A\") }\n    let(:category_b) { create(:emancipation_category, name: \"B\") }\n    let(:option_a) { build(:emancipation_option, emancipation_category_id: category_a.id, name: \"A\") }\n    let(:option_b) { build(:emancipation_option, emancipation_category_id: category_a.id, name: \"B\") }\n    let(:option_c) { build(:emancipation_option, emancipation_category_id: category_a.id, name: \"C\") }\n    let(:option_d) { build(:emancipation_option, emancipation_category_id: category_b.id, name: \"D\") }\n\n    it \"contains exactly the options belonging to the category and case passed to it\" do\n      case_a.emancipation_options += [option_a, option_b]\n      case_b.emancipation_options += [option_b, option_d]\n\n      expect(EmancipationOption.options_with_category_and_case(category_a.id, case_a.id)).to contain_exactly(option_a, option_b)\n      expect(EmancipationOption.options_with_category_and_case(category_a.id, case_b.id)).to contain_exactly(option_b)\n      expect(EmancipationOption.options_with_category_and_case(category_b.id, case_a.id)).to be_empty\n      expect(EmancipationOption.options_with_category_and_case(category_b.id, case_b.id)).to contain_exactly(option_d)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/models/followup_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe Followup, type: :model do\n  subject { build(:followup) }\n\n  it { is_expected.to belong_to(:case_contact) } # TOOD polymorph remove after migraion complete\n  it { is_expected.to belong_to(:creator).class_name(\"User\") }\n  it { is_expected.to belong_to(:followupable).optional }\n\n  it \"has polymorphic fields\" do\n    expect(Followup.new).to respond_to(:followupable_id)\n    expect(Followup.new).to respond_to(:followupable_type)\n  end\n\n  # TODO polymorph temporary test for dual writing\n  it \"writes to case_contact_id and both polymorphic columns when creating new followups\" do\n    case_contact = create(:case_contact)\n    followup = create(:followup, :with_note, case_contact: case_contact)\n\n    expect(followup.case_contact_id).not_to be_nil\n    expect(followup.followupable_id).not_to be_nil\n    expect(followup.followupable_type).to eq \"CaseContact\"\n    expect(followup.followupable_id).to eq followup.case_contact_id\n  end\n\n  it \"only allows 1 followup in requested status\" do\n    case_contact = build_stubbed(:case_contact)\n    create(:followup, case_contact: case_contact)\n    invalid_followup = build(:followup, status: :requested, case_contact: case_contact)\n\n    expect(invalid_followup).to be_invalid\n  end\n\n  it \"allows followup to be flipped to resolved\" do\n    followup = create(:followup, :with_note)\n\n    expect(followup.resolved!).to be_truthy\n  end\n\n  describe \".in_organization\" do\n    # this needs to run first so it is generated using a new \"default\" organization\n    subject { described_class.in_organization(second_org) }\n\n    let!(:followup_first_org) { create(:followup) }\n\n    # then these lets are generated for the org_to_search organization\n    let!(:second_org) { create(:casa_org) }\n    let!(:casa_case) { create(:casa_case, casa_org: second_org) }\n    let!(:casa_case_another) { create(:casa_case, casa_org: second_org) }\n    let!(:case_contact) { create(:case_contact, casa_case: casa_case) }\n    let!(:case_contact_another) { create(:case_contact, casa_case: casa_case_another) }\n    let!(:followup_second_org) { create(:followup, case_contact: case_contact) }\n    let!(:followup_second_org_another) { create(:followup, case_contact: case_contact_another) }\n\n    it \"includes followups from same organization\" do\n      expect(subject).to contain_exactly(followup_second_org, followup_second_org_another)\n    end\n\n    it \"excludes followups from other organizations\" do\n      expect(subject).not_to include(followup_first_org)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/models/fund_request_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe FundRequest, type: :model do\n  it { is_expected.to validate_presence_of(:submitter_email) }\nend\n"
  },
  {
    "path": "spec/models/health_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe Health, type: :model do\n  describe \"#instance\" do\n    it \"returns an instance of the health class\" do\n      expect(Health.instance).not_to eq nil\n    end\n\n    it \"returns a new instance of the health class if there are none\" do\n      Health.destroy_all\n      expect(Health.instance).not_to eq nil\n    end\n\n    it \"singleton_guard column is 0\" do\n      expect(Health.instance.singleton_guard).to eq 0\n    end\n  end\nend\n"
  },
  {
    "path": "spec/models/hearing_type_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe HearingType, type: :model do\n  it { is_expected.to belong_to(:casa_org) }\n  it { is_expected.to validate_presence_of(:name) }\n  it { is_expected.to have_many(:checklist_items) }\n\n  describe \"for_organization\" do\n    let!(:casa_org_1) { create(:casa_org) }\n    let!(:casa_org_2) { create(:casa_org) }\n    let!(:record_1) { create(:hearing_type, casa_org: casa_org_1) }\n    let!(:record_2) { create(:hearing_type, casa_org: casa_org_2) }\n\n    it \"returns only records matching the specified organization\" do\n      expect(described_class.for_organization(casa_org_1)).to eq([record_1])\n      expect(described_class.for_organization(casa_org_2)).to eq([record_2])\n    end\n  end\n\n  describe \"default scope\" do\n    let(:casa_org) { create(:casa_org) }\n    let(:hearing_types) do\n      5.times.map { create(:hearing_type, casa_org: casa_org) }\n    end\n\n    it \"orders alphabetically by name\" do\n      expect(described_class.for_organization(casa_org)).to eq(hearing_types.sort_by(&:name))\n    end\n  end\nend\n"
  },
  {
    "path": "spec/models/judge_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe Judge, type: :model do\n  it { is_expected.to belong_to(:casa_org) }\n  it { is_expected.to validate_presence_of(:name) }\n\n  it \"has a valid factory\" do\n    judge = build(:judge)\n\n    expect(judge).to be_valid\n  end\n\n  describe \".for_organization\" do\n    it \"returns only records matching the specified organization\" do\n      casa_org_1 = create(:casa_org)\n      casa_org_2 = create(:casa_org)\n      record_1 = create(:judge, casa_org: casa_org_1)\n      record_2 = create(:judge, casa_org: casa_org_2)\n\n      expect(described_class.for_organization(casa_org_1)).to eq([record_1])\n      expect(described_class.for_organization(casa_org_2)).to eq([record_2])\n    end\n  end\n\n  describe \"default scope\" do\n    it \"orders alphabetically by name\" do\n      casa_org = create(:casa_org)\n      judge1 = create(:judge, name: \"Gamma\")\n      judge2 = create(:judge, name: \"Alpha\")\n      judge3 = create(:judge, name: \"Epsilon\")\n\n      expect(described_class.for_organization(casa_org)).to eq [judge2, judge3, judge1]\n    end\n  end\nend\n"
  },
  {
    "path": "spec/models/language_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe Language, type: :model do\n  let(:organization) { create(:casa_org) }\n  let!(:language) { create(:language, name: \"Spanish\", casa_org: organization) }\n\n  it { is_expected.to belong_to(:casa_org) }\n  it { is_expected.to have_many(:user_languages) }\n  it { is_expected.to have_many(:users).through(:user_languages) }\n\n  it { is_expected.to validate_presence_of(:name) }\n\n  it \"validates uniqueness of language for an organization\" do\n    subject = build(:language, name: \"spanish\", casa_org: organization)\n\n    expect(subject).not_to be_valid\n  end\n\n  context \"when calling valid?\" do\n    it \"removes surrounding spaces from the name attribute\" do\n      subject = build(:language, name: \"  spanish  \", casa_org: organization)\n      subject.valid?\n      expect(subject.name).to eq \"spanish\"\n    end\n\n    it \"removes surrounding spaces from the name attribute but leaves in middle spaces\" do\n      subject = build(:language, name: \" Western Punjabi \", casa_org: organization)\n      subject.valid?\n      expect(subject.name).to eq \"Western Punjabi\"\n    end\n  end\nend\n"
  },
  {
    "path": "spec/models/learning_hour_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe LearningHour, type: :model do\n  it \"has a title\" do\n    learning_hour = build_stubbed(:learning_hour, name: nil)\n    expect(learning_hour).not_to be_valid\n    expect(learning_hour.errors[:name]).to eq([\"/ Title cannot be blank\"])\n  end\n\n  it \"has a learning_hour_type\" do\n    learning_hour = build_stubbed(:learning_hour, learning_hour_type: nil)\n    expect(learning_hour).not_to be_valid\n    expect(learning_hour.errors[:learning_hour_type]).to eq([\"must exist\"])\n  end\n\n  context \"duration_hours is zero\" do\n    it \"has a duration in minutes that is greater than 0\" do\n      learning_hour = build_stubbed(:learning_hour, duration_hours: 0, duration_minutes: 0)\n      expect(learning_hour).not_to be_valid\n      expect(learning_hour.errors[:duration_minutes]).to eq([\"and hours (total duration) must be greater than 0\"])\n    end\n  end\n\n  context \"duration_hours is greater than zero\" do\n    it \"has a duration in minutes that is greater than 0\" do\n      learning_hour = build_stubbed(:learning_hour, duration_hours: 1, duration_minutes: 0)\n      expect(learning_hour).to be_valid\n      expect(learning_hour.errors[:duration_minutes]).to eq([])\n    end\n  end\n\n  it \"has an occurred_at date\" do\n    learning_hour = build_stubbed(:learning_hour, occurred_at: nil)\n    expect(learning_hour).not_to be_valid\n    expect(learning_hour.errors[:occurred_at]).to eq([\"can't be blank\"])\n  end\n\n  it \"has date that is not in the future\" do\n    learning_hour = build_stubbed(:learning_hour, occurred_at: 1.day.from_now.strftime(\"%d %b %Y\"))\n    expect(learning_hour).not_to be_valid\n  end\n\n  it \"Date cannot be before 01-01-1989\" do\n    learning_hour = build_stubbed(:learning_hour, occurred_at: \"1984-01-01\".to_date)\n    expect(learning_hour).not_to be_valid\n    expect(learning_hour.errors[:occurred_at]).to eq([\"is not valid: Occurred on Date cannot be prior to 1/1/1989.\"])\n  end\n\n  it \"does not require learning_hour_topic if casa_org learning_hour_topic disabled\" do\n    learning_hour = build_stubbed(:learning_hour, learning_hour_topic: nil)\n    expect(learning_hour).to be_valid\n  end\n\n  it \"requires learning_hour_topic if casa_org learning_hour_topic enabled\" do\n    casa_org = build(:casa_org, learning_topic_active: true)\n    user = build(:user, casa_org: casa_org)\n    learning_hour = build(:learning_hour, user: user)\n    expect(learning_hour).not_to be_valid\n    expect(learning_hour.errors[:learning_hour_topic]).to eq([\"can't be blank\"])\n  end\n\n  describe \"scopes\" do\n    let(:casa_org_1) { build(:casa_org) }\n    let!(:learning_hours) do\n      [\n        create(:learning_hour, user: volunteer1, duration_hours: 1, duration_minutes: 0),\n        create(:learning_hour, user: volunteer1, duration_hours: 2, duration_minutes: 0),\n        create(:learning_hour, user: volunteer2, duration_hours: 1, duration_minutes: 0),\n        create(:learning_hour, user: volunteer2, duration_hours: 3, duration_minutes: 0),\n        create(:learning_hour, user: volunteer3, duration_hours: 1, duration_minutes: 0)\n      ]\n    end\n    let(:casa_org_2) { build(:casa_org) }\n\n    let(:casa_admin) { create(:casa_admin, display_name: \"Supervisor\", casa_org: casa_org_1) }\n    let(:supervisor) { create(:supervisor, display_name: \"Supervisor\", casa_org: casa_org_1) }\n    let(:volunteer1) { create(:volunteer, display_name: \"Volunteer 1\", casa_org: casa_org_1) }\n    let(:volunteer2) { create(:volunteer, display_name: \"Volunteer 2\", casa_org: casa_org_1) }\n    let(:volunteer3) { create(:volunteer, display_name: \"Volunteer 3\", casa_org: casa_org_2) }\n\n    before do\n      supervisor.volunteers << volunteer1\n    end\n\n    describe \".supervisor_volunteers_learning_hours\" do\n      subject(:supervisor_volunteers_learning_hours) { described_class.supervisor_volunteers_learning_hours(supervisor.id) }\n\n      context \"with specified supervisor\" do\n        it \"returns the total time spent for supervisor's volunteers\" do\n          expect(supervisor_volunteers_learning_hours.length).to eq(1)\n          expect(supervisor_volunteers_learning_hours.first.total_time_spent).to eq(180)\n        end\n      end\n    end\n\n    describe \".all_volunteers_learning_hours\" do\n      subject(:all_volunteers_learning_hours) { described_class.all_volunteers_learning_hours(casa_admin.casa_org_id) }\n\n      it \"returns the total time spent for all volunteers\" do\n        expect(all_volunteers_learning_hours.length).to eq(2)\n        expect(all_volunteers_learning_hours.last.total_time_spent).to eq(240)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/models/learning_hour_topic_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe LearningHourTopic, type: :model do\n  it { is_expected.to belong_to(:casa_org) }\n  it { is_expected.to validate_presence_of(:name) }\n\n  it \"has a valid factory\" do\n    expect(build(:learning_hour_topic).valid?).to be true\n  end\n\n  it \"has unique names for the specified organization\" do\n    casa_org_one = create(:casa_org)\n    casa_org_two = create(:casa_org)\n    create(:learning_hour_topic, casa_org: casa_org_one, name: \"Ethics\")\n    expect { create(:learning_hour_topic, casa_org: casa_org_one, name: \"Ethics\") }\n      .to raise_error(ActiveRecord::RecordInvalid)\n    expect { create(:learning_hour_topic, casa_org: casa_org_one, name: \"Ethics    \") }\n      .to raise_error(ActiveRecord::RecordInvalid)\n    expect { create(:learning_hour_topic, casa_org: casa_org_one, name: \"ethics\") }\n      .to raise_error(ActiveRecord::RecordInvalid)\n    expect { create(:learning_hour_topic, casa_org: casa_org_two, name: \"Ethics\") }\n      .not_to raise_error\n  end\n\n  describe \"for_organization\" do\n    let!(:casa_org_one) { create(:casa_org) }\n    let!(:casa_org_two) { create(:casa_org) }\n    let!(:record_1) { create(:learning_hour_topic, casa_org: casa_org_one) }\n    let!(:record_2) { create(:learning_hour_topic, casa_org: casa_org_two) }\n\n    it \"returns only records matching the specified organization\" do\n      expect(described_class.for_organization(casa_org_one)).to eq([record_1])\n      expect(described_class.for_organization(casa_org_two)).to eq([record_2])\n    end\n  end\nend\n"
  },
  {
    "path": "spec/models/learning_hour_type_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe LearningHourType, type: :model do\n  it { is_expected.to belong_to(:casa_org) }\n  it { is_expected.to validate_presence_of(:name) }\n\n  it \"has a valid factory\" do\n    expect(build(:learning_hour_type).valid?).to be true\n  end\n\n  it \"has unique names for the specified organization\" do\n    casa_org_1 = create(:casa_org)\n    casa_org_2 = create(:casa_org)\n    create(:learning_hour_type, casa_org: casa_org_1, name: \"Book\")\n    expect {\n      create(:learning_hour_type, casa_org: casa_org_1, name: \"Book\")\n    }.to raise_error(ActiveRecord::RecordInvalid)\n    expect {\n      create(:learning_hour_type, casa_org: casa_org_1, name: \"Book    \")\n    }.to raise_error(ActiveRecord::RecordInvalid)\n    expect {\n      create(:learning_hour_type, casa_org: casa_org_1, name: \"book\")\n    }.to raise_error(ActiveRecord::RecordInvalid)\n    expect {\n      create(:learning_hour_type, casa_org: casa_org_2, name: \"Book\")\n    }.not_to raise_error\n  end\n\n  describe \"for_organization\" do\n    let!(:casa_org_1) { create(:casa_org) }\n    let!(:casa_org_2) { create(:casa_org) }\n    let!(:record_1) { create(:learning_hour_type, casa_org: casa_org_1) }\n    let!(:record_2) { create(:learning_hour_type, casa_org: casa_org_2) }\n\n    it \"returns only records matching the specified organization\" do\n      expect(described_class.for_organization(casa_org_1)).to eq([record_1])\n      expect(described_class.for_organization(casa_org_2)).to eq([record_2])\n    end\n  end\n\n  describe \"default scope\" do\n    let(:casa_org) { create(:casa_org) }\n\n    it \"orders alphabetically by position and then name\" do\n      create(:learning_hour_type, casa_org: casa_org, name: \"Book\")\n      create(:learning_hour_type, casa_org: casa_org, name: \"Webinar\")\n      create(:learning_hour_type, casa_org: casa_org, name: \"Other\", position: 99)\n      create(:learning_hour_type, casa_org: casa_org, name: \"YouTube\")\n\n      type_names = %w[Book Webinar YouTube Other]\n      expect(described_class.for_organization(casa_org).map(&:name)).to eq(type_names)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/models/learning_hours_report_spec.rb",
    "content": "require \"rails_helper\"\nrequire \"csv\"\n\nRSpec.describe LearningHoursReport, type: :model do\n  describe \"#to_csv\" do\n    context \"when there are learning hours\" do\n      it \"includes all learning hours\" do\n        casa_org = build(:casa_org)\n        users = create_list(:user, 3, casa_org: casa_org)\n        learning_hour_types = create_list(:learning_hour_type, 3)\n        learning_hours =\n          [\n            create(:learning_hour, user: users[0], learning_hour_type: learning_hour_types[0]),\n            create(:learning_hour, user: users[1], learning_hour_type: learning_hour_types[1]),\n            create(:learning_hour, user: users[2], learning_hour_type: learning_hour_types[2])\n          ]\n        result = CSV.parse(described_class.new(casa_org.id).to_csv)\n\n        expect(result.length).to eq(learning_hours.length + 1)\n\n        result.each_with_index do |row, index|\n          next if index.zero?\n          expect(row[0]).to eq learning_hours[index - 1].user.display_name\n          expect(row[1]).to eq learning_hours[index - 1].name\n          expect(row[2]).to eq learning_hours[index - 1].learning_hour_type.name\n          expect(row[3]).to eq \"#{learning_hours[index - 1].duration_hours}:#{learning_hours[index - 1].duration_minutes}\"\n          expect(row[4]).to eq learning_hours[index - 1].occurred_at.strftime(\"%F\")\n        end\n      end\n    end\n\n    context \"when there are no learning hours\" do\n      it \"returns only the header\" do\n        casa_org = build(:casa_org)\n        result = CSV.parse(described_class.new(casa_org.id).to_csv)\n\n        expect(result.length).to eq(1)\n        expect(result[0]).to eq([\n          \"Volunteer Name\",\n          \"Learning Hours Title\",\n          \"Learning Hours Type\",\n          \"Duration\",\n          \"Date Of Learning\"\n        ])\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/models/login_activity_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe LoginActivity, type: :model do\n  it \"has a valid factory\" do\n    login_activity = build(:login_activity)\n\n    expect(login_activity).to be_valid\n  end\nend\n"
  },
  {
    "path": "spec/models/mileage_rate_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe MileageRate, type: :model do\n  subject { build(:mileage_rate) }\n\n  it { is_expected.to belong_to(:casa_org) }\n  it { is_expected.to validate_presence_of(:effective_date) }\n  it { is_expected.to validate_presence_of(:casa_org) }\n  it { is_expected.to validate_presence_of(:amount) }\n\n  describe \"for_organization\" do\n    let!(:casa_org_1) { create(:casa_org) }\n    let!(:casa_org_2) { create(:casa_org) }\n    let!(:record_1) { create(:mileage_rate, casa_org: casa_org_1) }\n    let!(:record_2) { create(:mileage_rate, casa_org: casa_org_2) }\n\n    it \"returns only records matching the specified organization\" do\n      expect(described_class.for_organization(casa_org_1)).to eq([record_1])\n      expect(described_class.for_organization(casa_org_2)).to eq([record_2])\n    end\n  end\n\n  describe \"#effective_date\" do\n    it \"cannot be before 1/1/1989\" do\n      mileage_rate = build(:mileage_rate, effective_date: \"1984-01-01\".to_date)\n      expect(mileage_rate).not_to be_valid\n      expect(mileage_rate.errors[:effective_date]).to eq([\"cannot be prior to 1/1/1989.\"])\n    end\n\n    it \"cannot be more than one year in the future\" do\n      mileage_rate = build(:mileage_rate, effective_date: 367.days.from_now)\n      expect(mileage_rate).not_to be_valid\n      expect(mileage_rate.errors[:effective_date]).to eq([\"must not be more than one year in the future.\"])\n    end\n\n    it \"is valid in the past after 1/1/1989\" do\n      mileage_rate = build(:mileage_rate, effective_date: \"1997-08-29\".to_date)\n      expect(mileage_rate).to be_valid\n      expect(mileage_rate.errors[:effective_date]).to eq([])\n    end\n\n    it \"is valid today\" do\n      mileage_rate = build(:mileage_rate, effective_date: Time.current)\n      expect(mileage_rate).to be_valid\n      expect(mileage_rate.errors[:effective_date]).to eq([])\n    end\n\n    it \"is unique within is_active and casa_org\" do\n      effective_date = Date.new(2020, 1, 1)\n      casa_org = create(:casa_org)\n      create(:mileage_rate, effective_date: effective_date, is_active: true, casa_org: casa_org)\n      expect do\n        create(:mileage_rate, effective_date: effective_date, is_active: true, casa_org: create(:casa_org))\n      end.not_to raise_error\n      expect do\n        create(:mileage_rate, effective_date: effective_date, is_active: false, casa_org: casa_org)\n      end.not_to raise_error\n      expect do\n        create(:mileage_rate, effective_date: effective_date, is_active: true, casa_org: casa_org)\n      end.to raise_error(ActiveRecord::RecordInvalid, \"Validation failed: Effective date must not have duplicate active dates\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/models/mileage_report_spec.rb",
    "content": "require \"rails_helper\"\nrequire \"csv\"\n\nRSpec.describe MileageReport, type: :model do\n  describe \"#to_csv\" do\n    it \"includes only case contacts that are eligible for driving reimbursement and not already reimbursed\" do\n      user1 = create(:volunteer, display_name: \"Linda\")\n      contact_type1 = create(:contact_type, name: \"Therapist\")\n      casa_case1 = create(:casa_case, case_number: \"Hello\")\n      case_contact1 = create(:case_contact, want_driving_reimbursement: true, miles_driven: 5, creator: user1, contact_types: [contact_type1], occurred_at: Date.new(2020, 1, 1), casa_case: casa_case1)\n      create(:case_contact, want_driving_reimbursement: false, miles_driven: 10, reimbursement_complete: false)\n      create(:case_contact, want_driving_reimbursement: false)\n      create(:case_contact, want_driving_reimbursement: true, miles_driven: 15, created_at: 2.years.ago)\n\n      csv = described_class.new(case_contact1.casa_case.casa_org_id).to_csv\n      parsed_csv = CSV.parse(csv)\n\n      expect(parsed_csv.length).to eq(2)\n      expect(parsed_csv[0].length).to eq(parsed_csv[1].length)\n      expect(parsed_csv[0]).to eq([\n        \"Contact Types\",\n        \"Occurred At\",\n        \"Miles Driven\",\n        \"Casa Case Number\",\n        \"Creator Name\",\n        \"Supervisor Name\",\n        \"Volunteer Address\",\n        \"Reimbursed\"\n      ])\n      case_contact_data = parsed_csv[1]\n      expect(case_contact_data[0]).to eq(\"Therapist\")\n      expect(case_contact_data[1]).to eq(\"January 1, 2020\")\n      expect(case_contact_data[2]).to eq(\"5\")\n      expect(case_contact_data[3]).to eq(\"Hello\")\n      expect(case_contact_data[4]).to eq(\"Linda\")\n    end\n\n    it \"generates an empty csv when there are no eligible case contacts\" do\n      faux_casa_org_id = 0\n      csv = described_class.new(faux_casa_org_id).to_csv\n      parsed_csv = CSV.parse(csv)\n\n      expect(parsed_csv.length).to eq(1)\n      expect(parsed_csv[0]).to eq([\n        \"Contact Types\",\n        \"Occurred At\",\n        \"Miles Driven\",\n        \"Casa Case Number\",\n        \"Creator Name\",\n        \"Supervisor Name\",\n        \"Volunteer Address\",\n        \"Reimbursed\"\n      ])\n    end\n\n    it \"includes case contacts from current org\" do\n      casa_org = create(:casa_org)\n      create(:casa_case, casa_org: casa_org)\n      create(:case_contact, want_driving_reimbursement: true, miles_driven: 15)\n\n      csv = described_class.new(casa_org.id).to_csv\n      parsed_csv = CSV.parse(csv)\n\n      expect(parsed_csv.length).to eq(2)\n    end\n\n    it \"excludes case contacts from other orgs\" do\n      casa_org = create(:casa_org)\n      other_casa_org = create(:casa_org)\n      casa_case = create(:casa_case, casa_org: other_casa_org)\n      create(:case_contact, casa_case: casa_case, want_driving_reimbursement: true, miles_driven: 60)\n\n      csv = described_class.new(casa_org.id).to_csv\n      parsed_csv = CSV.parse(csv)\n\n      expect(parsed_csv.length).to eq(1)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/models/missing_data_report_spec.rb",
    "content": "require \"rails_helper\"\nrequire \"csv\"\n\nRSpec.describe MissingDataReport, type: :model do\n  describe \"#to_csv\" do\n    let!(:casa_org) { create(:casa_org) }\n    let(:result) { CSV.parse(described_class.new(casa_org.id).to_csv) }\n\n    shared_examples \"report_with_header\" do\n      it \"contains header\" do\n        expect(result[0]).to eq([\n          \"Casa Case Number\",\n          \"Youth Birth Month And Year\",\n          \"Upcoming Hearing Date\",\n          \"Court Orders\"\n        ])\n      end\n    end\n\n    context \"when there are casa cases\" do\n      let!(:incomplete_casa_cases) do\n        [\n          create(:casa_case),\n          create(:casa_case, :with_one_court_order),\n          create(:casa_case, :with_upcoming_court_date)\n        ]\n      end\n\n      let!(:incomplete_casa_cases_from_other_org) { create_list(:casa_case, 3, casa_org: create(:casa_org)) }\n      let!(:complete_casa_cases) { create_list(:casa_case, 3, :with_upcoming_court_date, :with_one_court_order) }\n\n      let(:expected_result) do\n        [\n          [incomplete_casa_cases[0].case_number, \"OK\", \"MISSING\", \"MISSING\"],\n          [incomplete_casa_cases[1].case_number, \"OK\", \"MISSING\", \"OK\"],\n          [incomplete_casa_cases[2].case_number, \"OK\", \"OK\", \"MISSING\"]\n        ]\n      end\n\n      it \"includes only cases with missing data\" do\n        expect(result.length).to eq(4)\n        expect(result).to include(*expected_result)\n      end\n\n      it_behaves_like \"report_with_header\"\n    end\n\n    context \"when there are no casa cases\" do\n      it \"includes only the header\" do\n        expect(result.length).to eq(1)\n      end\n\n      it_behaves_like \"report_with_header\"\n    end\n  end\nend\n"
  },
  {
    "path": "spec/models/note_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe Note, type: :model do\n  it { is_expected.to belong_to(:notable) }\n  it { is_expected.to belong_to(:creator) }\n\n  it \"has a valid factory\" do\n    note = build(:note)\n\n    expect(note).to be_valid\n  end\nend\n"
  },
  {
    "path": "spec/models/other_duty_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe OtherDuty, type: :model do\n  it { is_expected.to belong_to(:creator) }\n\n  it \"validates presence of notes\" do\n    duty = build(:other_duty, notes: nil)\n    expect(duty).not_to be_valid\n    expect(duty.errors[:notes]).to eq([\"can't be blank\"])\n  end\n\n  it \"cannot be saved without a user\" do\n    other_duty = OtherDuty.new\n    other_duty.creator = nil\n    expect { other_duty.save!(validate: false) }.to raise_error ActiveRecord::NotNullViolation\n  end\n\n  describe \"occurred_at validation\" do\n    it \"is not valid before 1989\" do\n      other_duty = OtherDuty.new(occurred_at: \"1984-01-01\".to_date)\n      expect(other_duty.valid?).to be false\n      expect(other_duty.errors[:occurred_at]).to eq([\"is not valid. Occured on date cannot be prior to 1/1/1989.\"])\n    end\n\n    it \"is not valid more than 1 year in the future\" do\n      other_duty = OtherDuty.new(occurred_at: 367.days.from_now)\n      expect(other_duty.valid?).to be false\n      expect(other_duty.errors[:occurred_at]).to eq([\"is not valid. Occured on date must be within one year from today.\"])\n    end\n\n    it \"is valid within one year in the future\" do\n      other_duty = OtherDuty.new(occurred_at: 6.months.from_now)\n      other_duty.valid?\n      expect(other_duty.errors[:occurred_at]).to eq([])\n    end\n\n    it \"is valid in the past after 1989\" do\n      other_duty = OtherDuty.new(occurred_at: \"1997-08-29\".to_date)\n      other_duty.valid?\n      expect(other_duty.errors[:occurred_at]).to eq([])\n    end\n  end\nend\n"
  },
  {
    "path": "spec/models/patch_note_group_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe PatchNoteGroup, type: :model do\n  let!(:patch_note_group) { create(:patch_note_group, value: \"test\") }\n\n  it { is_expected.to validate_uniqueness_of(:value) }\n  it { is_expected.to validate_presence_of(:value) }\nend\n"
  },
  {
    "path": "spec/models/patch_note_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe PatchNote, type: :model do\n  let!(:patch_note) { create(:patch_note) }\n\n  it { is_expected.to belong_to(:patch_note_group) }\n  it { is_expected.to belong_to(:patch_note_type) }\n  it { is_expected.to validate_presence_of(:note) }\nend\n"
  },
  {
    "path": "spec/models/patch_note_type_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe PatchNoteType, type: :model do\n  let!(:patch_note_type) { create(:patch_note_type) }\n\n  it { is_expected.to validate_presence_of(:name) }\n  it { is_expected.to validate_uniqueness_of(:name) }\nend\n"
  },
  {
    "path": "spec/models/placement_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe Placement, type: :model do\n  let!(:object) { create(:placement) }\n\n  it { is_expected.to belong_to(:placement_type) }\n  it { is_expected.to belong_to(:creator) }\n  it { is_expected.to belong_to(:casa_case) }\n\n  context \"placement_started_at\" do\n    it \"cannot be before 1/1/1989\" do\n      placement = build(:placement, placement_started_at: \"1984-01-01\".to_date)\n      expect(placement).not_to be_valid\n      expect(placement.errors[:placement_started_at]).to eq([\"cannot be prior to 1/1/1989.\"])\n    end\n\n    it \"cannot be more than one year in the future\" do\n      placement = build(:placement, placement_started_at: 367.days.from_now)\n      expect(placement).not_to be_valid\n      expect(placement.errors[:placement_started_at]).to eq([\"must not be more than one year in the future.\"])\n    end\n\n    it \"is valid in the past after 1/1/1989\" do\n      placement = build(:placement, placement_started_at: \"1997-08-29\".to_date)\n      expect(placement).to be_valid\n      expect(placement.errors[:placement_started_at]).to eq([])\n    end\n\n    it \"is valid today\" do\n      placement = build(:placement, placement_started_at: Time.current)\n      expect(placement).to be_valid\n      expect(placement.errors[:placement_started_at]).to eq([])\n    end\n  end\nend\n"
  },
  {
    "path": "spec/models/placement_type_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe PlacementType, type: :model do\n  let!(:object) { create(:placement_type) }\n\n  it { is_expected.to validate_presence_of(:name) }\n  it { is_expected.to belong_to(:casa_org) }\nend\n"
  },
  {
    "path": "spec/models/preference_set_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe PreferenceSet, type: :model do\n  let(:preference_set) { PreferenceSet.create(params) }\n\n  describe \"allows setting values for case_volunteer_columns\" do\n    let(:params) do\n      {\n        case_volunteer_columns: {\n          case_number: \"show\",\n          hearing_type_name: \"hide\",\n          judge_name: \"show\",\n          status: \"show\",\n          transition_aged_youth: \"show\",\n          assigned_to: \"show\",\n          actions: \"hide\"\n        }\n      }\n    end\n\n    it { expect(preference_set.case_volunteer_columns[\"case_number\"]).to eq \"show\" }\n    it { expect(preference_set.case_volunteer_columns[\"hearing_type_name\"]).to eq \"hide\" }\n    it { expect(preference_set.case_volunteer_columns[\"judge_name\"]).to eq \"show\" }\n    it { expect(preference_set.case_volunteer_columns[\"status\"]).to eq \"show\" }\n    it { expect(preference_set.case_volunteer_columns[\"transition_aged_youth\"]).to eq \"show\" }\n    it { expect(preference_set.case_volunteer_columns[\"assigned_to\"]).to eq \"show\" }\n    it { expect(preference_set.case_volunteer_columns[\"actions\"]).to eq \"hide\" }\n  end\nend\n"
  },
  {
    "path": "spec/models/sent_email_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe SentEmail, type: :model do\n  it { is_expected.to belong_to(:casa_org) }\n  it { is_expected.to belong_to(:user) }\n  it { is_expected.to validate_presence_of(:mailer_type) }\n  it { is_expected.to validate_presence_of(:category) }\n  it { is_expected.to validate_presence_of(:sent_address) }\nend\n"
  },
  {
    "path": "spec/models/sms_notification_event_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe SmsNotificationEvent, type: :model do\n  it { is_expected.to have_many(:user_sms_notification_events) }\n  it { is_expected.to have_many(:users).through(:user_sms_notification_events) }\nend\n"
  },
  {
    "path": "spec/models/soft_deleted_model_shared_example_coverage_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"soft-deleted model shared example coverage\" do\n  let(:skip_classes) do\n    %w[]\n  end\n\n  let(:todo_currently_missing_specs) do\n    %w[\n      ContactTopicAnswer\n      CaseContact\n    ]\n  end\n\n  it \"checks that all acts_as_paranoid models have specs that include the soft-deleted model shared example\" do\n    missing = []\n    Zeitwerk::Loader.eager_load_all\n\n    ApplicationRecord.descendants.each do |clazz|\n      next if clazz.abstract_class?\n      next unless clazz.paranoid?\n      next if skip_classes.include?(clazz.name)\n      next if todo_currently_missing_specs.include?(clazz.name)\n\n      source_file = Object.const_source_location(clazz.name)&.first\n      next unless source_file\n\n      spec_file = source_file\n        .sub(%r{/app/models/}, \"/spec/models/\")\n        .sub(/\\.rb$/, \"_spec.rb\")\n\n      unless File.exist?(spec_file)\n        missing << \"#{clazz.name}: spec file not found (expected #{spec_file})\"\n        next\n      end\n\n      contents = File.read(spec_file)\n      unless contents.include?('\"a soft-deleted model\"')\n        missing << clazz.name.to_s\n      end\n    end\n\n    expect(missing).to be_empty, \"The following paranoid models are missing the shared example:\\n#{missing.join(\"\\n\")}\"\n  end\nend\n"
  },
  {
    "path": "spec/models/supervisor_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe Supervisor, type: :model do\n  include Devise::Test::IntegrationHelpers\n\n  subject(:supervisor) { create :supervisor }\n\n  describe \"#role\" do\n    it { expect(supervisor.role).to eq \"Supervisor\" }\n\n    it { is_expected.to have_many(:supervisor_volunteers) }\n    it { is_expected.to have_many(:active_supervisor_volunteers) }\n    it { is_expected.to have_many(:unassigned_supervisor_volunteers) }\n    it { is_expected.to have_many(:volunteers).through(:active_supervisor_volunteers) }\n    it { is_expected.to have_many(:volunteers_ever_assigned).through(:supervisor_volunteers) }\n  end\n\n  describe \"invitation expiration\" do\n    let!(:mail) { supervisor.invite! }\n    let(:expiration_date) { I18n.l(supervisor.invitation_due_at, format: :full, default: nil) }\n    let(:two_weeks) { I18n.l(2.weeks.from_now, format: :full, default: nil) }\n\n    it { expect(expiration_date).to eq two_weeks }\n\n    it \"expires invitation token after two weeks\" do\n      travel_to 2.weeks.from_now\n\n      user = User.accept_invitation!(invitation_token: supervisor.invitation_token)\n      expect(user.errors.full_messages).to include(\"Invitation token is invalid\")\n      travel_back\n    end\n  end\n\n  describe \"pending volunteers\" do\n    let(:volunteer) { create(:volunteer) }\n    let(:assign_volunteer) { create(:supervisor_volunteer, supervisor: supervisor, volunteer: volunteer) }\n\n    it \"returns volunteers invited by the supervisor\" do\n      volunteer.invite!(supervisor)\n      expect(supervisor.pending_volunteers).to eq([volunteer])\n    end\n\n    it \"returns volunteers invited by others but assigned to supervisor\" do\n      volunteer.invite!\n      assign_volunteer\n      expect(supervisor.pending_volunteers).to eq([volunteer])\n    end\n  end\n\n  describe \"not_signed_in_nor_have_case_contacts_volunteers\" do\n    subject { supervisor.inactive_volunteers }\n\n    let(:supervisor) { create(:supervisor) }\n    let(:volunteer) { create(:volunteer, last_sign_in_at: 31.days.ago) }\n    let(:other_volunteer) { create(:volunteer, last_sign_in_at: 29.days.ago) }\n\n    before do\n      create(:supervisor_volunteer, supervisor: supervisor, volunteer: volunteer)\n      create(:supervisor_volunteer, supervisor: supervisor, volunteer: other_volunteer)\n    end\n\n    context \"when volunteer has logged in in the last 30 days\" do\n      let(:volunteer) { create(:volunteer, last_sign_in_at: 29.days.ago) }\n\n      it { is_expected.to be_empty }\n\n      context \"when volunteer then logged out\" do\n        it \"is empty\" do\n          sign_out volunteer\n\n          expect(subject).to be_empty\n        end\n      end\n    end\n\n    context \"when volunteer hasn't logged in in the last 30 days\" do\n      it { is_expected.to contain_exactly(volunteer) }\n\n      context \"when volunteer is inactive\" do\n        let(:volunteer) { create(:volunteer, last_sign_in_at: 31.days.ago, active: false) }\n\n        it { is_expected.to be_empty }\n      end\n\n      context \"when volunteer has never logged in\" do\n        let(:volunteer) { create(:volunteer, last_sign_in_at: nil) }\n\n        it { is_expected.to contain_exactly(volunteer) }\n      end\n\n      context \"when volunteer has a recent case_contact\" do\n        let(:casa_case) { create(:casa_case, casa_org: supervisor.casa_org) }\n        let!(:case_contact) { create(:case_contact, casa_case: casa_case, occurred_at: 31.days.ago, creator: volunteer, created_at: 29.days.ago) }\n\n        it { is_expected.to be_empty }\n      end\n    end\n  end\n\n  describe \"change to admin\" do\n    it \"returns true if the change was successful\" do\n      expect(subject.change_to_admin!).to be_truthy\n    end\n\n    it \"changes the supervisor to an admin\" do\n      subject.change_to_admin!\n\n      user = User.find(subject.id) # subject.reload will cause RecordNotFound because it's looking in the wrong table\n      expect(user).not_to be_supervisor\n      expect(user).to be_casa_admin\n    end\n  end\nend\n"
  },
  {
    "path": "spec/models/supervisor_volunteer_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe SupervisorVolunteer do\n  let(:casa_org_1) { build(:casa_org) }\n  let(:casa_org_2) { build(:casa_org) }\n  let(:volunteer_1) { build(:volunteer, casa_org: casa_org_1) }\n  let(:supervisor_1) { create(:supervisor, casa_org: casa_org_1) }\n  let(:supervisor_2) { create(:supervisor, casa_org: casa_org_1) }\n\n  it \"assigns a volunteer to a supervisor\" do\n    supervisor_1.volunteers << volunteer_1\n    expect(volunteer_1.supervisor).to eq(supervisor_1)\n  end\n\n  it \"only allow 1 supervisor per volunteer\" do\n    supervisor_1.volunteers << volunteer_1\n    supervisor_1.save\n    expect { supervisor_2.volunteers << volunteer_1 }.to raise_error(StandardError)\n  end\n\n  it \"does not allow a volunteer to be double assigned\" do\n    expect {\n      supervisor_1.volunteers << volunteer_1\n      supervisor_1.volunteers << volunteer_1\n    }.to raise_error(ActiveRecord::RecordInvalid)\n  end\n\n  it \"requires supervisor and volunteer belong to same casa_org\" do\n    supervisor_volunteer = supervisor_1.supervisor_volunteers.new(volunteer: volunteer_1)\n    expect { volunteer_1.update(casa_org: casa_org_2) }.to change(supervisor_volunteer, :valid?).to(false)\n  end\nend\n"
  },
  {
    "path": "spec/models/user_language_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe UserLanguage, type: :model do\n  it { is_expected.to belong_to(:language) }\n  it { is_expected.to belong_to(:user) }\n\n  it \"validates uniqueness of language scoped to user\" do\n    existing_record = create(:user_language)\n    new_record = build(:user_language, user: existing_record.user, language: existing_record.language)\n    expect(new_record).not_to be_valid\n  end\nend\n"
  },
  {
    "path": "spec/models/user_reminder_time.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe UserReminderTime, type: :model do\n  it { is_expected.to belong_to(:user) }\nend\n"
  },
  {
    "path": "spec/models/user_sms_notification_event_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe UserSmsNotificationEvent, type: :model do\n  it { is_expected.to belong_to(:user) }\n  it { is_expected.to belong_to(:sms_notification_event) }\nend\n"
  },
  {
    "path": "spec/models/user_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe User, type: :model do\n  it { is_expected.to belong_to(:casa_org) }\n\n  it { is_expected.to have_many(:case_assignments) }\n  it { is_expected.to have_many(:casa_cases).through(:case_assignments) }\n  it { is_expected.to have_many(:case_contacts) }\n  it { is_expected.to have_many(:sent_emails) }\n  it { is_expected.to have_many(:user_languages) }\n  it { is_expected.to have_many(:languages).through(:user_languages) }\n\n  it { is_expected.to have_many(:followups).with_foreign_key(:creator_id) }\n\n  it { is_expected.to have_one(:supervisor_volunteer) }\n  it { is_expected.to have_one(:supervisor).through(:supervisor_volunteer) }\n\n  it { is_expected.to have_many(:notes) }\n\n  describe \"model validations\" do\n    it \"requires display name\" do\n      user = build(:user, display_name: \"\")\n      expect(user.valid?).to be false\n    end\n\n    it \"requires email\" do\n      user = build(:user, email: \"\")\n      expect(user.valid?).to be false\n    end\n\n    it \"requires 12 digit phone numbers\" do\n      user = build(:user, phone_number: \"+1416321\")\n      expect(user.valid?).to be false\n    end\n\n    it \"requires phone number to only contain numbers\" do\n      user = build(:user, phone_number: \"+1416eee4325\")\n      expect(user.valid?).to be false\n    end\n\n    it \"requires phone number with US area code\" do\n      user = build(:user, phone_number: \"+76758890432\")\n      expect(user.valid?).to be false\n    end\n\n    it \"requires date of birth to be in the past\" do\n      user = build(:user, date_of_birth: 10.days.from_now)\n      expect(user.valid?).to be false\n      expect(user.errors[:base]).to eq([\" Date of birth must be in the past.\"])\n    end\n\n    it \"requires date of birth to be no earlier than 1/1/1920\" do\n      user = build(:user, date_of_birth: \"1919-12-31\".to_date)\n      expect(user.valid?).to be false\n      expect(user.errors[:base]).to eq([\" Date of birth must be on or after 1/1/1920.\"])\n    end\n\n    it \"shows custom email uniqueness error message\" do\n      create(:user, email: \"volunteer1@example.com\")\n      user = build(:user, email: \"volunteer1@example.com\")\n      expect(user.valid?).to be false\n      expect(user.errors[:base]).to eq([I18n.t(\"activerecord.errors.messages.email_uniqueness\")])\n    end\n\n    it \"has an empty old_emails array when initialized\" do\n      user = build(:user)\n      expect(user.old_emails).to eq([])\n    end\n  end\n\n  describe \"#case_contacts_for\" do\n    let(:volunteer) { create(:volunteer, :with_casa_cases) }\n    let(:case_of_interest) { volunteer.casa_cases.first }\n    let!(:contact_a) { create(:case_contact, creator: volunteer, casa_case: case_of_interest) }\n    let!(:contact_b) { create(:case_contact, creator: volunteer, casa_case: volunteer.casa_cases.second) }\n\n    it \"returns all case_contacts associated with this user and the casa case id supplied\" do\n      sample_casa_case_id = case_of_interest.id\n\n      result = volunteer.case_contacts_for(sample_casa_case_id)\n\n      expect(result.length).to eq(1)\n    end\n\n    it \"does not return case_contacts associated with another volunteer user\" do\n      other_volunteer = build(:volunteer, :with_casa_cases, casa_org: volunteer.casa_org)\n\n      create(:case_assignment, casa_case: case_of_interest, volunteer: other_volunteer)\n      create(:case_contact, creator: other_volunteer, casa_case: case_of_interest)\n      build_stubbed(:case_contact)\n\n      sample_casa_case_id = case_of_interest.id\n\n      result = volunteer.case_contacts_for(sample_casa_case_id)\n      expect(result.length).to eq(1)\n      result = other_volunteer.case_contacts_for(sample_casa_case_id)\n      expect(result.length).to eq(1)\n    end\n\n    it \"does not return case_contacts neither unassigned cases or inactive cases\" do\n      inactive_case_assignment = create(:case_assignment, casa_case: create(:casa_case, casa_org: volunteer.casa_org), active: false, volunteer: volunteer)\n      case_assignment_to_inactve_case = create(:case_assignment, casa_case: create(:casa_case, active: false, casa_org: volunteer.casa_org), volunteer: volunteer)\n\n      expect {\n        volunteer.case_contacts_for(inactive_case_assignment.casa_case.id)\n      }.to raise_error(ActiveRecord::RecordNotFound)\n\n      expect {\n        volunteer.case_contacts_for(case_assignment_to_inactve_case.casa_case.id)\n      }.to raise_error(ActiveRecord::RecordNotFound)\n    end\n  end\n\n  describe \"supervisors\" do\n    describe \"#volunteers_serving_transition_aged_youth\" do\n      let(:casa_org) { build(:casa_org) }\n      let(:supervisor) { build(:supervisor, casa_org: casa_org) }\n\n      it \"returns the number of transition aged youth on a supervisor\" do\n        casa_cases = [\n          build(:casa_case, casa_org: casa_org),\n          build(:casa_case, casa_org: casa_org),\n          create(:casa_case, :pre_transition, casa_org: casa_org)\n        ]\n\n        casa_cases.each do |casa_case|\n          volunteer = create(:volunteer, supervisor: supervisor, casa_org: casa_org)\n          volunteer.casa_cases << casa_case\n        end\n\n        expect(supervisor.volunteers_serving_transition_aged_youth).to eq(2)\n      end\n\n      it \"ignores volunteers' inactive and unassgined cases\" do\n        volunteer = create(:volunteer, supervisor: supervisor, casa_org: casa_org)\n        create(:case_assignment, casa_case: create(:casa_case, casa_org: casa_org, active: false), volunteer: volunteer)\n        create(:case_assignment, casa_case: create(:casa_case, casa_org: casa_org), active: false, volunteer: volunteer)\n\n        expect(supervisor.volunteers_serving_transition_aged_youth).to eq(0)\n      end\n    end\n\n    describe \"#no_attempt_for_two_weeks\" do\n      let(:supervisor) { create(:supervisor) }\n\n      it \"returns zero for a volunteer that has attempted contact in at least one contact_case within the last 2 weeks\" do\n        volunteer_1 = create(:volunteer, :with_casa_cases, supervisor: supervisor)\n\n        case_of_interest_1 = volunteer_1.casa_cases.first\n        create(:case_contact, creator: volunteer_1, casa_case: case_of_interest_1, contact_made: true, created_at: 1.week.ago)\n        expect(supervisor.no_attempt_for_two_weeks).to eq(0)\n      end\n\n      it \"returns one for a supervisor with two volunteers, only one of which has a contact newer than 2 weeks old\" do\n        volunteer_1 = create(:volunteer, :with_casa_cases, supervisor: supervisor)\n        volunteer_2 = create(:volunteer, :with_casa_cases, supervisor: supervisor)\n\n        case_of_interest_1 = volunteer_1.casa_cases.first\n        case_of_interest_2 = volunteer_2.casa_cases.first\n        create(:case_contact, creator: volunteer_1, casa_case: case_of_interest_1, contact_made: true, created_at: 1.week.ago)\n        create(:case_contact, creator: volunteer_2, casa_case: case_of_interest_2, contact_made: true, created_at: 3.weeks.ago)\n        expect(supervisor.no_attempt_for_two_weeks).to eq(1)\n      end\n\n      it \"returns one for a volunteer that has not made any contact_cases within the last 2 weeks\" do\n        create(:volunteer, :with_casa_cases, supervisor: supervisor)\n        expect(supervisor.no_attempt_for_two_weeks).to eq(1)\n      end\n\n      it \"returns zero for a volunteer that is not assigned to any casa cases\" do\n        create(:volunteer, supervisor: supervisor)\n        expect(supervisor.no_attempt_for_two_weeks).to eq(0)\n      end\n\n      it \"returns one for a volunteer who has attempted contact in at least one contact_case with created_at after 2 weeks\" do\n        volunteer_1 = create(:volunteer, :with_casa_cases, supervisor: supervisor)\n\n        case_of_interest_1 = volunteer_1.casa_cases.first\n\n        create(:case_contact, creator: volunteer_1, casa_case: case_of_interest_1, contact_made: true, created_at: 3.weeks.ago)\n        expect(supervisor.no_attempt_for_two_weeks).to eq(1)\n      end\n\n      it \"returns zero for a volunteer that has no active casa case assignments\" do\n        volunteer_1 = create(:volunteer, :with_casa_cases, supervisor: supervisor)\n\n        case_of_interest_1 = volunteer_1.casa_cases.first\n        case_of_interest_2 = volunteer_1.casa_cases.last\n        case_assignment_1 = case_of_interest_1.case_assignments.find_by(volunteer: volunteer_1)\n        case_assignment_2 = case_of_interest_2.case_assignments.find_by(volunteer: volunteer_1)\n        case_assignment_1.update!(active: false)\n        case_assignment_2.update!(active: false)\n\n        expect(supervisor.no_attempt_for_two_weeks).to eq(0)\n      end\n    end\n  end\n\n  describe \"#active_for_authentication?\" do\n    it \"is false when the user is inactive\" do\n      user = build(:volunteer, :inactive)\n      expect(user).not_to be_active_for_authentication\n      expect(user.inactive_message).to eq(:inactive)\n    end\n\n    it \"is true otherwise\" do\n      user = build(:volunteer)\n      expect(user).to be_active_for_authentication\n\n      user = build(:supervisor)\n      expect(user).to be_active_for_authentication\n    end\n  end\n\n  describe \"#actively_assigned_and_active_cases\" do\n    let(:user) { build(:volunteer) }\n    let!(:active_case_assignment_with_active_case) do\n      create(:case_assignment, casa_case: build(:casa_case, casa_org: user.casa_org), volunteer: user)\n    end\n    let!(:active_case_assignment_with_inactive_case) do\n      create(:case_assignment, casa_case: build(:casa_case, casa_org: user.casa_org, active: false), volunteer: user)\n    end\n    let!(:inactive_case_assignment_with_active_case) do\n      create(:case_assignment, casa_case: build(:casa_case, casa_org: user.casa_org), active: false, volunteer: user)\n    end\n    let!(:inactive_case_assignment_with_inactive_case) do\n      create(:case_assignment, casa_case: build(:casa_case, casa_org: user.casa_org, active: false), active: false, volunteer: user)\n    end\n\n    it \"only returns the user's active cases with active case assignments\" do\n      expect(user.actively_assigned_and_active_cases).to contain_exactly(active_case_assignment_with_active_case.casa_case)\n    end\n  end\n\n  describe \"#serving_transition_aged_youth?\" do\n    let(:user) { build(:volunteer) }\n    let!(:case_assignment_without_transition_aged_youth) do\n      create(:case_assignment, casa_case: create(:casa_case, :pre_transition, casa_org: user.casa_org), volunteer: user)\n    end\n\n    context \"when the user has a transition-aged-youth case\" do\n      it \"is true\" do\n        create(:case_assignment, casa_case: create(:casa_case, casa_org: user.casa_org), volunteer: user)\n        expect(user).to be_serving_transition_aged_youth\n      end\n    end\n\n    context \"when the user does not have a transition-aged-youth case\" do\n      it \"is false\" do\n        expect(user).not_to be_serving_transition_aged_youth\n      end\n    end\n\n    context \"when the user's only transition-aged-youth case is inactive\" do\n      it \"is false\" do\n        create(:case_assignment, casa_case: create(:casa_case, casa_org: user.casa_org, active: false), volunteer: user)\n\n        expect(user).not_to be_serving_transition_aged_youth\n      end\n    end\n\n    context \"when the user is unassigned from a transition-aged-youth case\" do\n      it \"is false\" do\n        create(:case_assignment, casa_case: create(:casa_case, casa_org: user.casa_org), volunteer: user, active: false)\n\n        expect(user).not_to be_serving_transition_aged_youth\n      end\n    end\n  end\n\n  context \"when there is an associated Other Duty record\" do\n    let(:user) { create(:supervisor) }\n    let!(:duty) { create(:other_duty, creator: user) }\n\n    it \"cannot be destroyed without destroying the associated Other Duty record\" do\n      expect { user.delete }.to raise_error ActiveRecord::InvalidForeignKey\n    end\n  end\n\n  describe \".no_recent_sign_in\" do\n    it \"returns users who haven't signed in in 30 days\" do\n      old_sign_in_user = create(:user, last_sign_in_at: 39.days.ago)\n      create(:user, last_sign_in_at: 5.days.ago)\n      expect(User.no_recent_sign_in).to contain_exactly(old_sign_in_user)\n    end\n\n    it \"returns users who haven't signed in ever\" do\n      user = create(:user, last_sign_in_at: nil)\n      expect(User.no_recent_sign_in).to contain_exactly(user)\n    end\n  end\n\n  describe \"#record_previous_emails\" do\n    # create user, check for side effects, test method\n    let!(:new_volunteer) { create(:user, email: \"firstemail@example.com\") }\n\n    it \"instantiates with an empty old_emails attribute\" do\n      expect(new_volunteer.old_emails).to be_empty\n    end\n\n    it \"saves the old email when a volunteer changes their email\" do\n      new_volunteer.update(email: \"secondemail@example.com\")\n      new_volunteer.confirm\n\n      expect(new_volunteer.email).to eq(\"secondemail@example.com\")\n\n      expect(new_volunteer.old_emails).to contain_exactly(\"firstemail@example.com\")\n    end\n  end\n\n  describe \"#filter_old_emails\" do\n    let!(:new_volunteer) { create(:user, email: \"firstemail@example.com\") }\n\n    it \"correctly filters out reinstated emails from old_emails when updating\" do\n      new_volunteer.update(email: \"secondemail@example.com\")\n      new_volunteer.confirm\n      new_volunteer.filter_old_emails!(new_volunteer.email)\n\n      new_volunteer.update(email: \"firstemail@example.com\")\n      new_volunteer.confirm\n      new_volunteer.filter_old_emails!(new_volunteer.email)\n\n      expect(new_volunteer.email).to eq(\"firstemail@example.com\")\n      expect(new_volunteer.old_emails).to contain_exactly(\"secondemail@example.com\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/models/volunteer_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe Volunteer, type: :model do\n  describe \".email_court_report_reminder\" do\n    let!(:casa_org) { build(:casa_org) }\n    let!(:casa_org_twilio_disabled) { build(:casa_org, twilio_enabled: false) }\n    # Should send email for this case\n    let!(:casa_case1) { create(:casa_case, casa_org: casa_org) }\n    let!(:court_date1) { create(:court_date, casa_case: casa_case1, court_report_due_date: Date.current + 7.days) }\n\n    # Should NOT send emails for these cases\n    let!(:casa_case2) { build(:casa_case, casa_org: casa_org) }\n    let!(:court_date2) { create(:court_date, casa_case: casa_case2, court_report_due_date: Date.current + 8.days) }\n    let!(:casa_case3) { build(:casa_case, casa_org: casa_org, court_report_submitted_at: Time.current, court_report_status: :submitted) }\n    let!(:court_date3) { create(:court_date, casa_case: casa_case3, court_report_due_date: Date.current + 7.days) }\n    let!(:casa_case4) { build(:casa_case, casa_org: casa_org) }\n    let!(:court_date4) { create(:court_date, casa_case: casa_case4, court_report_due_date: Date.current + 7.days) }\n    let!(:casa_case5) { create(:casa_case, casa_org: casa_org_twilio_disabled) }\n    let!(:court_date5) { create(:court_date, casa_case: casa_case5, court_report_due_date: Date.current + 7.days) }\n\n    let(:case_assignment1) { build(:case_assignment, casa_org: casa_org, casa_case: casa_case1) }\n    let(:case_assignment2) { build(:case_assignment, casa_org: casa_org, casa_case: casa_case2) }\n    let(:case_assignment3) { build(:case_assignment, casa_org: casa_org, casa_case: casa_case3) }\n    let(:case_assignment_unassigned) { build(:case_assignment, casa_org: casa_org, casa_case: casa_case4, active: false) }\n    let(:case_assignment5) { build(:case_assignment, casa_org: casa_org_twilio_disabled, casa_case: casa_case5) }\n\n    let!(:v1) { create(:volunteer, casa_org: casa_org, case_assignments: [case_assignment1, case_assignment2, case_assignment3]) }\n    let!(:v2) { create(:volunteer, casa_org: casa_org, active: false) }\n    let!(:v3) { create(:volunteer, casa_org: casa_org) }\n    let!(:v4) { create(:volunteer, casa_org: casa_org, case_assignments: [case_assignment_unassigned]) }\n    let!(:v5) { create(:volunteer, casa_org: casa_org_twilio_disabled, case_assignments: [case_assignment5]) }\n\n    before do\n      stub_const(\"Volunteer::COURT_REPORT_SUBMISSION_REMINDER\", 7.days)\n      WebMockHelper.short_io_court_report_due_date_stub\n    end\n\n    it \"sends one mailer\" do\n      expect(VolunteerMailer).to receive(:court_report_reminder).with(v1, Date.current + 7.days)\n      expect(VolunteerMailer).not_to receive(:court_report_reminder).with(v2, anything)\n      expect(VolunteerMailer).not_to receive(:court_report_reminder).with(v3, anything)\n      described_class.send_court_report_reminder\n    end\n\n    it \"does not send reminders about unassigned cases\" do\n      expect(VolunteerMailer).not_to receive(:court_report_reminder).with(v4, anything)\n      described_class.send_court_report_reminder\n    end\n\n    it \"sends one sms\" do\n      expect(CourtReportDueSmsReminderService).to receive(:court_report_reminder).with(v1, Date.current + 7.days)\n      expect(CourtReportDueSmsReminderService).not_to receive(:court_report_reminder).with(v2, anything)\n      expect(CourtReportDueSmsReminderService).not_to receive(:court_report_reminder).with(v3, anything)\n      described_class.send_court_report_reminder\n    end\n\n    it \"does not send sms about unassigned cases\" do\n      expect(CourtReportDueSmsReminderService).not_to receive(:court_report_reminder).with(v4, anything)\n      described_class.send_court_report_reminder\n    end\n\n    it \"returns nil when twilio is disabled\" do\n      response = CourtReportDueSmsReminderService.court_report_reminder(v5, Date.current + 7.days)\n      expect(response).to eq(nil)\n    end\n  end\n\n  describe \"#activate\" do\n    let(:volunteer) { build(:volunteer, :inactive) }\n\n    it \"activates the volunteer\" do\n      volunteer.activate\n\n      volunteer.reload\n      expect(volunteer.active).to eq(true)\n    end\n  end\n\n  describe \"#deactivate\" do\n    let(:volunteer) { build(:volunteer) }\n\n    it \"deactivates the volunteer\" do\n      expect(volunteer.deactivate.reload.active).to eq(false)\n    end\n\n    it \"sets all of a volunteer's case assignments to inactive\" do\n      case_contacts =\n        3.times.map {\n          create(:case_assignment, casa_case: build(:casa_case, casa_org: volunteer.casa_org), volunteer: volunteer)\n        }\n\n      volunteer.deactivate\n\n      case_contacts.each { |c| c.reload }\n      expect(case_contacts).to all(satisfy { |c| !c.active })\n    end\n\n    context \"when volunteer has previously been assigned a supervisor\" do\n      let!(:supervisor_volunteer) { create(:supervisor_volunteer, volunteer: volunteer) }\n\n      it \"deactivates the supervisor-volunteer relationship\" do\n        expect { volunteer.deactivate.reload }.to change(volunteer, :supervisor_volunteer)\n      end\n    end\n\n    context \"when volunteer had no supervisor previously assigned\" do\n      it \"does not attempt to update a supervisor-volunteer table\" do\n        expect { volunteer.deactivate.reload }.not_to change(volunteer, :supervisor_volunteer)\n      end\n    end\n  end\n\n  describe \"#display_name\" do\n    it \"allows user to input dangerous values\" do\n      volunteer = build(:volunteer)\n      UserInputHelpers::DANGEROUS_STRINGS.each do |dangerous_string|\n        volunteer.update_attribute(:display_name, dangerous_string)\n        volunteer.reload\n\n        expect(volunteer.display_name).to eq dangerous_string\n      end\n    end\n  end\n\n  describe \"#has_supervisor?\" do\n    context \"when no supervisor_volunteer record\" do\n      let(:volunteer) { build_stubbed(:volunteer) }\n\n      it \"returns false\" do\n        expect(volunteer.has_supervisor?).to be false\n      end\n    end\n\n    context \"when active supervisor_volunteer record\" do\n      let(:sv) { create(:supervisor_volunteer, is_active: true) }\n      let(:volunteer) { sv.volunteer }\n\n      it \"returns true\" do\n        expect(volunteer.has_supervisor?).to be true\n      end\n    end\n\n    context \"when inactive supervisor_volunteer record\" do\n      let(:sv) { build_stubbed(:supervisor_volunteer, is_active: false) }\n      let(:volunteer) { sv.volunteer }\n\n      it \"returns false\" do\n        expect(volunteer.has_supervisor?).to be false\n      end\n    end\n  end\n\n  describe \"#made_contact_with_all_cases_in_days?\" do\n    let(:volunteer) { build(:volunteer) }\n    let(:casa_case) { build(:casa_case, casa_org: volunteer.casa_org) }\n\n    context \"when a volunteer is assigned to an active case\" do\n      let(:create_case_contact) do\n        lambda { |occurred_at, contact_made|\n          create(:case_contact, casa_case: casa_case, creator: volunteer, occurred_at: occurred_at, contact_made: contact_made)\n        }\n      end\n\n      before do\n        create(:case_assignment, casa_case: casa_case, volunteer: volunteer)\n      end\n\n      context \"when volunteer has made recent contact\" do\n        it \"returns true\" do\n          create_case_contact.call(Date.current, true)\n          expect(volunteer.made_contact_with_all_cases_in_days?).to eq(true)\n        end\n      end\n\n      context \"when volunteer has made recent contact attempt but no contact made\" do\n        it \"returns true\" do\n          create_case_contact.call(Date.current, false)\n          expect(volunteer.made_contact_with_all_cases_in_days?).to eq(false)\n        end\n      end\n\n      context \"when volunteer has not made recent contact\" do\n        it \"returns false\" do\n          create_case_contact.call(Date.current - 60.days, true)\n          expect(volunteer.made_contact_with_all_cases_in_days?).to eq(false)\n        end\n      end\n\n      context \"when volunteer has not made recent contact in just one case\" do\n        it \"returns false\" do\n          casa_case2 = build(:casa_case, casa_org: volunteer.casa_org)\n          create(:case_assignment, casa_case: casa_case2, volunteer: volunteer)\n          create(:case_contact, casa_case: casa_case2, creator: volunteer, occurred_at: Date.current - 60.days, contact_made: true)\n          create_case_contact.call(Date.current, true)\n          expect(volunteer.made_contact_with_all_cases_in_days?).to eq(false)\n        end\n      end\n    end\n\n    context \"when volunteer has no case assignments\" do\n      it \"returns true\" do\n        expect(volunteer.made_contact_with_all_cases_in_days?).to eq(true)\n      end\n    end\n\n    context \"when a volunteer has only an inactive case where contact was not made recently\" do\n      it \"returns true\" do\n        inactive_case = create(:casa_case, casa_org: volunteer.casa_org, active: false)\n        create(:case_assignment, casa_case: inactive_case, volunteer: volunteer)\n        create(:case_contact, casa_case: inactive_case, creator: volunteer, occurred_at: Date.current - 60.days, contact_made: true)\n\n        expect(volunteer.made_contact_with_all_cases_in_days?).to eq(true)\n      end\n    end\n\n    context \"when a volunteer has only an unassigned case where contact was not made recently\" do\n      it \"returns true\" do\n        create(:case_assignment, casa_case: casa_case, volunteer: volunteer, active: false)\n        create(:case_contact, casa_case: casa_case, creator: volunteer, occurred_at: Date.current - 60.days, contact_made: true)\n\n        expect(volunteer.made_contact_with_all_cases_in_days?).to eq(true)\n      end\n    end\n  end\n\n  describe \"#supervised_by?\" do\n    it \"is supervised by the currently active supervisor\" do\n      supervisor = create :supervisor\n      volunteer = create :volunteer, supervisor: supervisor\n\n      expect(volunteer).to be_supervised_by(supervisor)\n    end\n\n    it \"is not supervised by supervisors that have never supervised the volunteer before\" do\n      supervisor = create :supervisor\n      volunteer = create :volunteer\n\n      expect(volunteer).not_to be_supervised_by(supervisor)\n    end\n\n    it \"is not supervised by supervisor that had the volunteer unassinged\" do\n      old_supervisor = build :supervisor\n      new_supervisor = build :supervisor\n      volunteer = build :volunteer, supervisor: old_supervisor\n\n      volunteer.update supervisor: new_supervisor\n\n      expect(volunteer).not_to be_supervised_by(old_supervisor)\n      expect(volunteer).to be_supervised_by(new_supervisor)\n    end\n  end\n\n  describe \"#role\" do\n    subject(:volunteer) { build :volunteer }\n\n    it { expect(volunteer.role).to eq \"Volunteer\" }\n  end\n\n  describe \"#with_no_supervisor\" do\n    subject { Volunteer.with_no_supervisor(casa_org) }\n\n    let(:casa_org) { build(:casa_org) }\n\n    context \"no volunteers\" do\n      it \"returns none\" do\n        expect(subject).to eq []\n      end\n    end\n\n    context \"volunteers\" do\n      let!(:unassigned1) { create(:volunteer, display_name: \"aaa\", casa_org: casa_org) }\n      let!(:unassigned2) { create(:volunteer, display_name: \"bbb\", casa_org: casa_org) }\n      let!(:unassigned_inactive) { create(:volunteer, display_name: \"unassigned inactive\", casa_org: casa_org, active: false) }\n      let!(:different_org) { create(:casa_org) }\n      let!(:unassigned2_different_org) { create(:volunteer, display_name: \"ccc\", casa_org: different_org) }\n      let!(:assigned1) { create(:volunteer, display_name: \"ddd\", casa_org: casa_org) }\n      let!(:supervisor) { create(:supervisor, display_name: \"supe\", casa_org: casa_org) }\n      let!(:assignment1) { create(:supervisor_volunteer, volunteer: assigned1, supervisor: supervisor) }\n      let!(:assigned2_different_org) { assignment1.volunteer }\n      let!(:unassigned_inactive_volunteer) { create(:volunteer, display_name: \"eee\", casa_org: casa_org, active: false) }\n      let!(:previously_assigned) { create(:volunteer, display_name: \"fff\", casa_org: casa_org) }\n      let!(:inactive_assignment) { create(:supervisor_volunteer, volunteer: previously_assigned, is_active: false, supervisor: supervisor) }\n\n      it \"returns unassigned volunteers\" do\n        expect(subject.map(&:display_name).sort).to eq [\"aaa\", \"bbb\", \"fff\"]\n      end\n    end\n  end\n\n  describe \".with_supervisor\" do\n    subject { Volunteer.with_supervisor }\n\n    context \"no volunteers\" do\n      it { is_expected.to be_empty }\n    end\n\n    context \"volunteers\" do\n      let!(:unassigned1) { create(:volunteer, display_name: \"aaa\") }\n      let!(:unassigned2) { create(:volunteer, display_name: \"bbb\") }\n\n      let!(:supervisor1) { create(:supervisor, display_name: \"supe1\") }\n      let!(:assigned1) { create(:volunteer, display_name: \"ccc\") }\n      let!(:assignment1) { create(:supervisor_volunteer, volunteer: assigned1, supervisor: supervisor1) }\n\n      let!(:supervisor2) { create(:supervisor, display_name: \"supe2\") }\n      let!(:assigned2) { create(:volunteer, display_name: \"ddd\") }\n      let!(:assignment2) { create(:supervisor_volunteer, volunteer: assigned2, supervisor: supervisor2) }\n\n      let!(:assigned3) { create(:volunteer, display_name: \"eee\") }\n      let!(:assignment3) { create(:supervisor_volunteer, volunteer: assigned3, supervisor: supervisor2) }\n\n      it { is_expected.to contain_exactly(assigned1, assigned2, assigned3) }\n    end\n  end\n\n  describe \".birthday_next_month\" do\n    subject { Volunteer.birthday_next_month }\n\n    before do\n      travel_to Date.new(2022, 10, 1)\n    end\n\n    context \"there are volunteers whose birthdays are not next month\" do\n      let!(:volunteer1) { create(:volunteer, date_of_birth: Date.new(1990, 9, 1)) }\n      let!(:volunteer2) { create(:volunteer, date_of_birth: Date.new(1998, 10, 15)) }\n      let!(:volunteer3) { create(:volunteer, date_of_birth: Date.new(1920, 12, 1)) }\n\n      it { is_expected.to be_empty }\n    end\n\n    context \"there are volunteers whose birthdays are next month\" do\n      let!(:volunteer1) { create(:volunteer, date_of_birth: Date.new(2001, 11, 1)) }\n      let!(:volunteer2) { create(:volunteer, date_of_birth: Date.new(1920, 11, 15)) }\n      let!(:volunteer3) { create(:volunteer, date_of_birth: Date.new(1989, 11, 30)) }\n\n      let!(:volunteer4) { create(:volunteer, date_of_birth: Date.new(2001, 6, 1)) }\n      let!(:volunteer5) { create(:volunteer, date_of_birth: Date.new(1920, 1, 15)) }\n      let!(:volunteer6) { create(:volunteer, date_of_birth: Date.new(1967, 2, 21)) }\n\n      it { is_expected.to contain_exactly(volunteer1, volunteer2, volunteer3) }\n    end\n  end\n\n  describe \"#with_assigned_cases\" do\n    subject { Volunteer.with_assigned_cases }\n\n    let!(:volunteers) { create_list(:volunteer, 3) }\n    let!(:volunteer_with_cases) { create_list(:volunteer, 3, :with_casa_cases) }\n\n    it \"returns only volunteers assigned to active casa cases\" do\n      expect(subject).to match_array(volunteer_with_cases)\n    end\n  end\n\n  describe \"#with_no_assigned_cases\" do\n    subject { Volunteer.with_no_assigned_cases }\n\n    let!(:volunteers) { create_list(:volunteer, 3) }\n    let!(:volunteer_with_cases) { create_list(:volunteer, 3, :with_casa_cases) }\n\n    it \"returns only volunteers with no assigned active casa cases\" do\n      expect(subject).to match_array(volunteers)\n    end\n  end\n\n  describe \"#casa_cases\" do\n    let(:volunteer) { create :volunteer }\n    let!(:ca1) { create :case_assignment, volunteer: volunteer, active: true }\n    let!(:ca2) { create :case_assignment, volunteer: volunteer, active: false }\n    let!(:ca3) { create :case_assignment, volunteer: create(:volunteer), active: true }\n    let!(:ca4) { create :case_assignment, casa_case: create(:casa_case, active: false), active: true }\n    let!(:ca5) { create :case_assignment, casa_case: create(:casa_case, active: false), active: false }\n\n    it \"returns only active and actively assigned casa cases\" do\n      expect(volunteer.casa_cases.count).to eq(1)\n      expect(volunteer.casa_cases).to eq([ca1.casa_case])\n    end\n  end\n\n  describe \"invitation expiration\" do\n    let(:volunteer) { create :volunteer }\n    let!(:mail) { volunteer.invite! }\n    let(:expiration_date) { I18n.l(volunteer.invitation_due_at, format: :full, default: nil) }\n    let(:one_year) { I18n.l(1.year.from_now, format: :full, default: nil) }\n\n    it { expect(expiration_date).to eq one_year }\n\n    it \"expires invitation token after one year\" do\n      travel_to 1.year.from_now\n\n      user = User.accept_invitation!(invitation_token: volunteer.invitation_token)\n      expect(user.errors.full_messages).to include(\"Invitation token is invalid\")\n      travel_back\n    end\n  end\n\n  describe \"invitation\" do\n    it \"delivers an email invite\" do\n      volunteer = build(:volunteer, email: \"new_volunteer@example.com\")\n      volunteer.invite!\n\n      email = ActionMailer::Base.deliveries.last\n\n      expect(email).not_to be_nil\n      expect(email.to).to eq [\"new_volunteer@example.com\"]\n      expect(email.subject).to eq(\"CASA Console invitation instructions\")\n      expect(email.html_part.body.encoded).to match(/your new Volunteer account/i)\n      expect(volunteer.reload.invitation_created_at).to be_present\n    end\n  end\n\n  describe \"#learning_hours_spent_in_one_year\" do\n    let(:volunteer) { create :volunteer }\n    let(:learning_hour_type) { create :learning_hour_type }\n    let!(:learning_hours) do\n      [\n        create(:learning_hour, user: volunteer, duration_hours: 1, duration_minutes: 30, learning_hour_type: learning_hour_type),\n        create(:learning_hour, user: volunteer, duration_hours: 3, duration_minutes: 45, learning_hour_type: learning_hour_type),\n        create(:learning_hour, user: volunteer, duration_hours: 1, duration_minutes: 30, occurred_at: 2.year.ago, learning_hour_type: learning_hour_type)\n      ]\n    end\n\n    it \"returns the hours spent in one year\" do\n      expect(volunteer.learning_hours_spent_in_one_year).to eq(\"5h 15min\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/notifications/base_notifier_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe BaseNotifier do\n  # TODO: Add tests for BaseNotifier\n\n  pending \"add some tests for BaseNotifier\"\nend\n"
  },
  {
    "path": "spec/notifications/delivery_methods/sms_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe DeliveryMethods::Sms do\n  # TODO: Add tests for DeliveryMethods::Sms\n\n  pending \"add some tests for DeliveryMethods::Sms\"\nend\n"
  },
  {
    "path": "spec/notifications/emancipation_checklist_reminder_notifier_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe EmancipationChecklistReminderNotifier, type: :model do\n  let(:casa_case) { create :casa_case }\n\n  let(:notification) { EmancipationChecklistReminderNotifier.with(casa_case: casa_case) }\n\n  describe \"message\" do\n    it \"contains the case number\" do\n      case_number = casa_case.case_number\n      expect(notification.message).to include case_number\n    end\n  end\n\n  describe \"url\" do\n    it \"contains the case id\" do\n      case_id = casa_case.id.to_s\n      expect(notification.url).to include case_id\n    end\n  end\nend\n"
  },
  {
    "path": "spec/notifications/followup_notifier_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe FollowupNotifier do\n  # TODO: Add tests for FollowupNotifier\n\n  pending \"add some tests for FollowupNotifier\"\nend\n"
  },
  {
    "path": "spec/notifications/followup_resolved_notifier_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe FollowupResolvedNotifier do\n  # TODO: Add tests for FollowupResolvedNotifier\n\n  pending \"add some tests for FollowupResolvedNotifier\"\nend\n"
  },
  {
    "path": "spec/notifications/reimbursement_complete_notifier_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe ReimbursementCompleteNotifier, type: :model do\n  describe \"message\" do\n    let(:case_contact) { create(:case_contact, :wants_reimbursement) }\n\n    describe \"with case org with nil mileage rate\" do\n      it \"does not include reimbursement amount\" do\n        notification = ReimbursementCompleteNotifier.with(case_contact: case_contact)\n        expect(notification.message).not_to include \"$\"\n      end\n    end\n\n    describe \"with casa org with active mileage rate\" do\n      let!(:mileage_rate) { create(:mileage_rate, casa_org: case_contact.casa_case.casa_org, amount: 6.50, effective_date: 3.days.ago) }\n\n      it \"does include reimbursement amount\" do\n        notification = ReimbursementCompleteNotifier.with(case_contact: case_contact)\n        expect(notification.message).to include \"$2964\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/notifications/volunteer_birthday_notifier_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe VolunteerBirthdayNotifier, type: :model do\n  describe \"message\" do\n    let(:volunteer) do\n      create(:volunteer, display_name: \"Biday Sewn\", date_of_birth: Date.new(1968, 2, 8))\n    end\n\n    let(:volunteer_notification) { VolunteerBirthdayNotifier.with(volunteer: volunteer) }\n\n    it \"contains a short ordinal form of the volunteer's date of birth\" do\n      expect(volunteer_notification.message).to include \"February 8th\"\n    end\n\n    it \"contains the volunteer's name\" do\n      expect(volunteer_notification.message).to include \"Biday Sewn\"\n    end\n  end\nend\n"
  },
  {
    "path": "spec/notifications/youth_birthday_notifier_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe YouthBirthdayNotifier do\n  # TODO: Add tests for YouthBirthdayNotifier\n\n  pending \"add some tests for YouthBirthdayNotifier\"\nend\n"
  },
  {
    "path": "spec/policies/additional_expense_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe AdditionalExpensePolicy, :aggregate_failures, type: :policy do\n  subject { described_class }\n\n  let(:casa_org) { create :casa_org }\n  let(:volunteer) { create :volunteer, :with_single_case, casa_org: }\n  let(:supervisor) { create :supervisor, casa_org: }\n  let(:casa_admin) { create :casa_admin, casa_org: }\n  let(:all_casa_admin) { create :all_casa_admin }\n\n  let(:casa_case) { volunteer.casa_cases.first }\n  let(:case_contact) { create :case_contact, casa_case:, creator: volunteer }\n  let(:additional_expense) { create :additional_expense, case_contact: }\n\n  let(:draft_case_contact) { create :case_contact, :started_status, casa_case: nil, creator: volunteer }\n  let(:draft_additional_expense) { create :additional_expense, case_contact: draft_case_contact }\n  let(:new_additional_expense) do\n    build :additional_expense, case_contact: draft_case_contact, other_expense_amount: 0, other_expenses_describe: \"\"\n  end\n\n  let(:same_case_volunteer) { create :volunteer, casa_org: }\n  let(:same_case_volunteer_case_assignment) { create :case_assignment, volunteer: same_case_volunteer, casa_case: }\n  let(:same_case_volunteer_case_contact) do\n    same_case_volunteer_case_assignment\n    create :case_contact, casa_case:, creator: same_case_volunteer\n  end\n  let(:same_case_volunteer_additional_expense) do\n    create :additional_expense, case_contact: same_case_volunteer_case_contact\n  end\n\n  let(:other_org) { create :casa_org }\n  let(:other_org_volunteer) { create :volunteer, casa_org: other_org }\n  let(:other_org_casa_case) { create :casa_case, casa_org: other_org }\n  let(:other_org_case_contact) { create :case_contact, casa_case: other_org_casa_case, creator: other_org_volunteer }\n  let(:other_org_additional_expense) { create :additional_expense, case_contact: other_org_case_contact }\n\n  permissions :create?, :destroy? do\n    it \"does not permit a nil user\" do\n      expect(subject).not_to permit(nil, additional_expense)\n    end\n\n    it \"permits volunteers assigned to the expense's case contact\" do\n      expect(subject).to permit(volunteer, additional_expense)\n      expect(subject).to permit(volunteer, draft_additional_expense)\n      expect(subject).to permit(volunteer, new_additional_expense)\n\n      expect(subject).not_to permit(volunteer, same_case_volunteer_additional_expense)\n      expect(subject).not_to permit(volunteer, other_org_additional_expense)\n    end\n\n    it \"permits same org supervisors\" do\n      expect(subject).to permit(supervisor, additional_expense)\n      expect(subject).to permit(supervisor, draft_additional_expense)\n      expect(subject).to permit(supervisor, draft_additional_expense)\n      expect(subject).to permit(supervisor, same_case_volunteer_additional_expense)\n\n      expect(subject).not_to permit(supervisor, other_org_additional_expense)\n    end\n\n    it \"permits same org casa admins\" do\n      expect(subject).to permit(casa_admin, additional_expense)\n      expect(subject).to permit(casa_admin, draft_additional_expense)\n      expect(subject).to permit(casa_admin, new_additional_expense)\n      expect(subject).to permit(casa_admin, same_case_volunteer_additional_expense)\n\n      expect(subject).not_to permit(casa_admin, other_org_additional_expense)\n    end\n\n    it \"does not permit an all casa admin\" do\n      expect(subject).not_to permit(all_casa_admin, additional_expense)\n    end\n  end\n\n  describe \"Scope#resolve\" do\n    subject { described_class::Scope.new(user, AdditionalExpense.all).resolve }\n\n    before do\n      additional_expense\n      draft_additional_expense\n      same_case_volunteer_additional_expense\n      other_org_additional_expense\n    end\n\n    context \"when user is a visitor\" do\n      let(:user) { nil }\n\n      it \"returns no expenses\" do\n        expect(subject).not_to include(additional_expense, other_org_additional_expense)\n      end\n    end\n\n    context \"when user is a volunteer\" do\n      let(:user) { volunteer }\n\n      it \"includes expenses for contacts created by volunteer only\" do\n        expect(subject).to include(additional_expense, draft_additional_expense)\n        expect(subject).not_to include(same_case_volunteer_additional_expense, other_org_additional_expense)\n      end\n    end\n\n    context \"when user is a supervisor\" do\n      let(:user) { supervisor }\n\n      it \"includes same org expenses only\" do\n        expect(subject).to include(additional_expense, draft_additional_expense, same_case_volunteer_additional_expense)\n        expect(subject).not_to include(other_org_additional_expense)\n      end\n    end\n\n    context \"when user is a casa_admin\" do\n      let(:user) { casa_admin }\n\n      it \"includes same org expenses only\" do\n        expect(subject).to include(additional_expense, draft_additional_expense, draft_additional_expense)\n        expect(subject).not_to include(other_org_additional_expense)\n      end\n    end\n\n    context \"when user is an all_casa_admin\" do\n      let(:user) { all_casa_admin }\n\n      it \"returns no expenses\" do\n        expect(subject).not_to include(additional_expense, other_org_additional_expense)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/policies/application_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe ApplicationPolicy do\n  subject { described_class }\n\n  let(:casa_org) { build_stubbed(:casa_org) }\n  let(:casa_admin) { build_stubbed(:casa_admin, casa_org: casa_org) }\n  let(:supervisor) { build_stubbed(:supervisor, casa_org: casa_org) }\n  let(:volunteer) { build_stubbed(:volunteer, casa_org: casa_org) }\n  let(:all_casa_admin) { build_stubbed(:all_casa_admin) }\n\n  permissions :see_reports_page? do\n    it \"allows casa_admins\" do\n      expect(subject).to permit(casa_admin)\n    end\n\n    it \"allows supervisors\" do\n      expect(subject).to permit(supervisor)\n    end\n\n    it \"does not allow volunteers\" do\n      expect(subject).not_to permit(volunteer)\n    end\n  end\n\n  permissions :see_import_page? do\n    it \"allows casa_admins\" do\n      expect(subject).to permit(casa_admin)\n    end\n\n    it \"does not allow supervisors\" do\n      expect(subject).not_to permit(supervisor)\n    end\n\n    it \"does not allow volunteers\" do\n      expect(subject).not_to permit(volunteer)\n    end\n  end\n\n  permissions :see_court_reports_page? do\n    it \"allows volunteers\" do\n      expect(subject).to permit(create(:volunteer))\n    end\n\n    it \"allows casa_admins\" do\n      expect(subject).to permit(create(:casa_admin))\n    end\n\n    it \"allows supervisors\" do\n      expect(subject).to permit(create(:supervisor))\n    end\n  end\n\n  permissions :see_emancipation_checklist? do\n    it \"allows volunteers\" do\n      expect(subject).to permit(create(:volunteer))\n    end\n\n    it \"does not allow casa_admins\" do\n      expect(subject).not_to permit(create(:casa_admin))\n    end\n\n    it \"does not allow supervisors\" do\n      expect(subject).not_to permit(create(:supervisor))\n    end\n  end\n\n  permissions :see_mileage_rate? do\n    it \"does not allow volunters\" do\n      expect(subject).not_to permit(volunteer)\n    end\n\n    it \"does not allow supervisors\" do\n      expect(subject).not_to permit(supervisor)\n    end\n\n    it \"allow casa_admins for same org\" do\n      expect(subject).to permit(casa_admin)\n    end\n\n    context \"when org reimbursement is disabled\" do\n      before do\n        casa_org.show_driving_reimbursement = false\n      end\n\n      it \"does not allow casa_admins\" do\n        expect(subject).not_to permit(casa_admin)\n      end\n    end\n  end\n\n  describe \"#same_org?\" do\n    let(:org_record) { double }\n\n    before { allow(org_record).to receive(:casa_org).and_return(casa_org) }\n\n    context \"record with same casa_org\" do\n      before { expect(org_record).to receive(:casa_org).and_return(casa_org) }\n\n      permissions :same_org? do\n        it { is_expected.to permit(volunteer, org_record) }\n        it { is_expected.to permit(supervisor, org_record) }\n        it { is_expected.to permit(casa_admin, org_record) }\n      end\n    end\n\n    context \"record with different casa_org\" do\n      let(:other_org_record) { double }\n\n      before { expect(other_org_record).to receive(:casa_org).and_return(build_stubbed(:casa_org)) }\n\n      permissions :same_org? do\n        it { is_expected.not_to permit(volunteer, other_org_record) }\n        it { is_expected.not_to permit(supervisor, other_org_record) }\n        it { is_expected.not_to permit(casa_admin, other_org_record) }\n      end\n    end\n\n    context \"all_casa_admin user\" do\n      it \"raises a no method error for all_casa_admin.casa_org\" do\n        expect { subject.new(all_casa_admin, org_record).same_org? }.to raise_error(NoMethodError)\n      end\n    end\n\n    context \"user with no casa_org\" do\n      let(:volunteer) { build_stubbed(:volunteer, casa_org: nil) }\n      let(:supervisor) { build_stubbed(:supervisor, casa_org: nil) }\n      let(:casa_admin) { build_stubbed(:casa_admin, casa_org: nil) }\n\n      permissions :same_org? do\n        it { is_expected.not_to permit(volunteer, org_record) }\n        it { is_expected.not_to permit(supervisor, org_record) }\n        it { is_expected.not_to permit(casa_admin, org_record) }\n      end\n    end\n\n    context \"no user\" do\n      let(:user) { nil }\n\n      permissions :same_org? do\n        it { is_expected.not_to permit(user, org_record) }\n      end\n    end\n\n    context \"called with a class instead of a record\" do\n      let(:klass) { CasaCase }\n\n      [:volunteer, :casa_admin, :supervisor].each do |user_type|\n        it \"raises a no method error for #{user_type}\" do\n          user = send(user_type)\n          expect { subject.new(user, klass).same_org? }.to raise_error(NoMethodError)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/policies/bulk_court_date_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe BulkCourtDatePolicy, type: :policy do\n  subject { described_class }\n\n  let(:casa_org) { build :casa_org }\n  let(:casa_admin) { build :casa_admin, casa_org: }\n  let(:volunteer) { build :volunteer, casa_org: }\n  let(:supervisor) { build :supervisor, casa_org: }\n\n  permissions :new?, :create? do\n    it \"permits casa_admins\" do\n      expect(subject).to permit(casa_admin, :bulk_court_date)\n    end\n\n    it \"permits supervisor\" do\n      expect(subject).to permit(supervisor, :bulk_court_date)\n    end\n\n    it \"does not permit volunteer\" do\n      expect(subject).not_to permit(volunteer, :bulk_court_date)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/policies/casa_admin_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CasaAdminPolicy do\n  subject { described_class }\n\n  let(:organization) { build(:casa_org) }\n  let(:casa_admin) { create(:casa_admin, casa_org: organization) }\n  let(:volunteer) { build(:volunteer, casa_org: organization) }\n  let(:supervisor) { build(:supervisor, casa_org: organization) }\n\n  permissions :edit? do\n    context \"same org\" do\n      let(:record) { build_stubbed(:casa_admin, casa_org: casa_admin.casa_org) }\n\n      it \"allows editing admin\" do\n        expect(subject).to permit(casa_admin, record)\n      end\n    end\n\n    context \"different org\" do\n      let(:record) { build_stubbed(:casa_admin, casa_org: build_stubbed(:casa_org)) }\n\n      it \"does not allow editing admin\" do\n        expect(subject).not_to permit(casa_admin, record)\n      end\n    end\n  end\n\n  permissions :index?, :activate?, :change_to_supervisor?, :create?, :new?, :resend_invitation?, :update? do\n    it \"allows casa_admins\" do\n      expect(subject).to permit(casa_admin)\n    end\n  end\n\n  permissions :index?, :activate?, :change_to_supervisor?, :create?, :edit?, :new?, :resend_invitation?, :update? do\n    it \"does not permit supervisor\" do\n      expect(subject).not_to permit(supervisor)\n    end\n\n    it \"does not permit volunteer\" do\n      expect(subject).not_to permit(volunteer)\n    end\n  end\n\n  permissions :deactivate? do\n    context \"when user is a casa admin\" do\n      let(:admin_inactive) { build_stubbed(:casa_admin, active: false, casa_org: organization) }\n\n      it \"does not permit if is a inactive user\" do\n        expect(subject).not_to permit(admin_inactive, :casa_admin)\n      end\n\n      it \"does not permit if is the only admin\" do\n        expect(subject).not_to permit(casa_admin, :casa_admin)\n      end\n\n      it \"permits if is a active user and exist other casa admins\" do\n        create(:casa_admin, casa_org: organization)\n        expect(subject).to permit(casa_admin, :casa_admin)\n      end\n    end\n\n    context \"when user is a supervisor\" do\n      it \"does not permit\" do\n        expect(subject).not_to permit(supervisor, :casa_admin)\n      end\n    end\n\n    context \"when user is a volunteer\" do\n      it \"does not permit\" do\n        expect(subject).not_to permit(volunteer)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/policies/casa_case_policy/scope_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CasaCasePolicy::Scope do\n  let(:organization) { build(:casa_org) }\n\n  describe \"#resolve\" do\n    it \"returns all CasaCases when user is admin\" do\n      user = build(:casa_admin, casa_org: organization)\n      all_casa_cases = create_list(:casa_case, 2, casa_org: organization)\n      new_org = build(:casa_org)\n      create_list(:casa_case, 2, casa_org: new_org)\n\n      scope = described_class.new(user, organization.casa_cases)\n\n      expect(scope.resolve).to match_array(all_casa_cases)\n    end\n\n    it \"returns active cases of the volunteer when user is volunteer\" do\n      user = create(:volunteer, casa_org: organization)\n      casa_cases = create_list(:casa_case, 2, volunteers: [user], casa_org: organization)\n\n      more_user = build(:volunteer, casa_org: organization)\n      create_list(:casa_case, 2, volunteers: [more_user], casa_org: organization)\n\n      other_org = build(:casa_org)\n      other_user = create(:volunteer, casa_org: other_org)\n      create_list(:casa_case, 2, volunteers: [other_user], casa_org: other_org)\n\n      scope = described_class.new(user, organization.casa_cases)\n\n      expect(CasaCase.count).to eq 6\n      expect(scope.resolve.count).to eq 2\n      expect(scope.resolve).to match_array casa_cases\n    end\n  end\nend\n"
  },
  {
    "path": "spec/policies/casa_case_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CasaCasePolicy do\n  subject { described_class }\n\n  let(:organization) { build(:casa_org) }\n  let(:different_organization) { create(:casa_org) }\n\n  let(:casa_admin) { build(:casa_admin, casa_org: organization) }\n  let(:other_org_casa_admin) { build(:casa_admin, casa_org: different_organization) }\n  let(:casa_case) { build(:casa_case, casa_org: organization) }\n  let(:volunteer) { build(:volunteer, casa_org: organization) }\n  let(:other_org_volunteer) { build(:volunteer, casa_org: different_organization) }\n  let(:supervisor) { build(:supervisor, casa_org: organization) }\n  let(:other_org_supervisor) { build(:supervisor, casa_org: different_organization) }\n\n  permissions :update_case_number? do\n    context \"when user is an admin\" do\n      context \"from the same organization\" do\n        it \"does allow update\" do\n          expect(subject).to permit(casa_admin, casa_case)\n        end\n      end\n\n      context \"from a different organization\" do\n        it \"does not allow an update\" do\n          expect(subject).not_to permit(other_org_casa_admin, casa_case)\n        end\n      end\n    end\n\n    context \"when user is a volunteer\" do\n      it \"does not allow update case number\" do\n        expect(subject).not_to permit(volunteer, casa_case)\n      end\n    end\n  end\n\n  permissions :update_court_date?, :update_court_report_due_date? do\n    context \"when part of the same organization\" do\n      context \"an admin user\" do\n        it \"can update\" do\n          expect(subject).to permit(casa_admin, casa_case)\n        end\n      end\n\n      context \"a supervisor user\" do\n        it \"can update\" do\n          expect(subject).to permit(supervisor, casa_case)\n        end\n      end\n\n      context \"a volunteer user\" do\n        it \"can update\" do\n          expect(subject).to permit(volunteer, casa_case)\n        end\n      end\n    end\n\n    context \"when not part of the same organization\" do\n      context \"an admin user\" do\n        it \"can not update\" do\n          expect(subject).not_to permit(other_org_casa_admin, casa_case)\n        end\n      end\n\n      context \"a supervisor user\" do\n        it \"can not update\" do\n          expect(subject).not_to permit(other_org_supervisor, casa_case)\n        end\n      end\n\n      context \"a volunteer user\" do\n        it \"can not update\" do\n          expect(subject).not_to permit(other_org_volunteer, casa_case)\n        end\n      end\n    end\n  end\n\n  permissions :update_hearing_type?, :update_court_orders?, :update_judge? do\n    context \"when part of the same organization\" do\n      context \"an admin user\" do\n        it \"is allowed to update\" do\n          expect(subject).to permit(casa_admin, casa_case)\n        end\n      end\n\n      context \"a supervisor user\" do\n        it \"is allowed to update\" do\n          expect(subject).to permit(supervisor, casa_case)\n        end\n      end\n\n      context \"a volunteer user\" do\n        it \"is allowed to update\" do\n          expect(subject).to permit(volunteer, casa_case)\n        end\n      end\n    end\n\n    context \"when not part of the same organization\" do\n      context \"an admin user\" do\n        it \"is not allowed to update\" do\n          expect(subject).not_to permit(other_org_casa_admin, casa_case)\n        end\n      end\n\n      context \"a supervisor user\" do\n        it \"is not allowed to update\" do\n          expect(subject).not_to permit(other_org_supervisor, casa_case)\n        end\n      end\n\n      context \"a volunteer user\" do\n        it \"is not allowed to update\" do\n          expect(subject).not_to permit(other_org_volunteer, casa_case)\n        end\n      end\n    end\n  end\n\n  permissions :update_contact_types? do\n    context \"when part of the same organization\" do\n      context \"an admin user\" do\n        it \"can update\" do\n          expect(subject).to permit(casa_admin, casa_case)\n        end\n      end\n\n      context \"a supervisor\" do\n        it \"can update\" do\n          expect(subject).to permit(supervisor, casa_case)\n        end\n      end\n    end\n\n    context \"when not part of the same organization\" do\n      context \"an admin user\" do\n        it \"can not update\" do\n          expect(subject).not_to permit(other_org_casa_admin, casa_case)\n        end\n      end\n\n      context \"a supervisor\" do\n        it \"can not update\" do\n          expect(subject).not_to permit(other_org_supervisor, casa_case)\n        end\n      end\n    end\n\n    context \"a volunteer\" do\n      it \"does not allow update\" do\n        expect(subject).not_to permit(volunteer, casa_case)\n        expect(subject).not_to permit(other_org_volunteer, casa_case)\n      end\n    end\n  end\n\n  permissions :assign_volunteers? do\n    context \"when part of the same organization\" do\n      context \"an admin user\" do\n        it \"can do volunteer assignment\" do\n          expect(subject).to permit(casa_admin, casa_case)\n        end\n      end\n    end\n\n    context \"when not part of the same organization\" do\n      context \"an admin user\" do\n        it \"can not do volunteer assignment\" do\n          expect(subject).not_to permit(other_org_casa_admin, casa_case)\n        end\n      end\n    end\n\n    # TODO: What is the supervisor permission?\n\n    context \"when user is a volunteer\" do\n      it \"does not allow volunteer assignment\" do\n        expect(subject).not_to permit(volunteer, casa_case)\n        expect(subject).not_to permit(other_org_volunteer, casa_case)\n      end\n    end\n  end\n\n  permissions \"update_emancipation_option?\" do\n    context \"when an admin belongs to the same org as the case\" do\n      it \"allows casa_admins\" do\n        expect(subject).to permit(casa_admin, casa_case)\n      end\n    end\n\n    context \"when an admin belongs to a different org as the case\" do\n      it \"does not allow admin to update\" do\n        casa_case = build_stubbed(:casa_case, casa_org: different_organization)\n        expect(subject).to permit(other_org_casa_admin, casa_case)\n      end\n    end\n\n    context \"when a supervisor belongs to the same org as the case\" do\n      it \"allows the supervisor\" do\n        supervisor = build(:supervisor, casa_org: organization)\n        casa_case = build_stubbed(:casa_case, casa_org: organization)\n        expect(subject).to permit(supervisor, casa_case)\n      end\n    end\n\n    context \"when a supervisor does not belong to the same org as the case\" do\n      it \"does not allow the supervisor\" do\n        supervisor = build(:supervisor, casa_org: organization)\n        casa_case = build_stubbed(:casa_case, casa_org: different_organization)\n        expect(subject).not_to permit(supervisor, casa_case)\n      end\n    end\n\n    context \"when volunteer is assigned\" do\n      it \"allows the volunteer\" do\n        volunteer = create(:volunteer, casa_org: organization)\n        casa_case = build(:casa_case, casa_org: organization)\n        volunteer.casa_cases << casa_case\n        expect(subject).to permit(volunteer, casa_case)\n      end\n    end\n\n    context \"when volunteer is from another organization\" do\n      it \"does not allow the volunteer\" do\n        volunteer = create(:volunteer, casa_org: different_organization)\n        casa_case = build(:casa_case, casa_org: organization)\n        expect { volunteer.casa_cases << casa_case }\n          .to raise_error(\n            ActiveRecord::RecordInvalid,\n            /must belong to the same organization/\n          )\n      end\n    end\n\n    context \"when volunteer is not assigned\" do\n      it \"does not allow the volunteer\" do\n        expect(subject).not_to permit(volunteer, casa_case)\n        expect(subject).not_to permit(other_org_volunteer, casa_case)\n      end\n    end\n  end\n\n  permissions :show? do\n    context \"when part of the same organization\" do\n      context \"an admin user\" do\n        it \"allows casa_admins\" do\n          expect(subject).to permit(casa_admin, casa_case)\n        end\n      end\n    end\n\n    context \"when not part of the same organization\" do\n      context \"and admin user\" do\n        it \"does not allow admin to view\" do\n          expect(subject).not_to permit(other_org_casa_admin, casa_case)\n        end\n      end\n    end\n\n    context \"when a supervisor belongs to the same org as the case\" do\n      it \"allows the supervisor\" do\n        supervisor = create(:supervisor, casa_org: organization)\n        casa_case = build_stubbed(:casa_case, casa_org: organization)\n        expect(subject).to permit(supervisor, casa_case)\n      end\n    end\n\n    context \"when a supervisor does not belong to the same org as the case\" do\n      it \"does not allow the supervisor\" do\n        supervisor = build_stubbed(:supervisor, casa_org: organization)\n        casa_case = create(:casa_case, casa_org: different_organization)\n        expect(subject).not_to permit(supervisor, casa_case)\n      end\n    end\n\n    context \"when volunteer is assigned\" do\n      it \"allows the volunteer\" do\n        volunteer = create(:volunteer, casa_org: organization)\n        casa_case = create(:casa_case, casa_org: organization)\n        volunteer.casa_cases << casa_case\n        expect(subject).to permit(volunteer, casa_case)\n      end\n    end\n\n    context \"when volunteer is not assigned\" do\n      it \"does not allow the volunteer\" do\n        expect(subject).not_to permit(volunteer, casa_case)\n      end\n    end\n\n    context \"when volunteer is from another organization\" do\n      it \"does not allow the volunteer\" do\n        volunteer = create(:volunteer, casa_org: different_organization)\n        casa_case = build(:casa_case, casa_org: organization)\n        expect { volunteer.casa_cases << casa_case }\n          .to raise_error(\n            ActiveRecord::RecordInvalid,\n            /must belong to the same organization/\n          )\n      end\n    end\n  end\n\n  permissions :edit? do\n    context \"when part of the same organization\" do\n      it \"allows casa_admins\" do\n        expect(subject).to permit(casa_admin, casa_case)\n      end\n    end\n\n    context \"when not part of the same organization\" do\n      it \"does not allow admin to edit\" do\n        expect(subject).not_to permit(other_org_casa_admin, casa_case)\n      end\n    end\n\n    context \"when a supervisor belongs to the same org as the case\" do\n      it \"allows the supervisor\" do\n        supervisor = create(:supervisor, casa_org: organization)\n        casa_case = build(:casa_case, casa_org: organization)\n        expect(subject).to permit(supervisor, casa_case)\n      end\n    end\n\n    context \"when a supervisor does not belong to the same org as the case\" do\n      it \"does not allow the supervisor\" do\n        supervisor = build_stubbed(:supervisor, casa_org: organization)\n        casa_case = build(:casa_case, casa_org: different_organization)\n        expect(subject).not_to permit(supervisor, casa_case)\n      end\n    end\n\n    context \"when volunteer is assigned\" do\n      it \"allows the volunteer\" do\n        volunteer = create(:volunteer, casa_org: organization)\n        casa_case = build(:casa_case, casa_org: organization)\n        volunteer.casa_cases << casa_case\n        expect(subject).to permit(volunteer, casa_case)\n      end\n    end\n\n    context \"when volunteer is not assigned\" do\n      it \"does not allow the volunteer\" do\n        expect(subject).not_to permit(volunteer, casa_case)\n      end\n    end\n\n    context \"when volunteer is from another organization\" do\n      it \"does not allow the volunteer\" do\n        volunteer = create(:volunteer, casa_org: different_organization)\n        casa_case = build(:casa_case, casa_org: organization)\n        expect { volunteer.casa_cases << casa_case }\n          .to raise_error(\n            ActiveRecord::RecordInvalid,\n            /must belong to the same organization/\n          )\n      end\n    end\n  end\n\n  permissions :update? do\n    context \"when part of the same organization\" do\n      it \"allows casa_admins\" do\n        expect(subject).to permit(casa_admin, casa_case)\n      end\n    end\n\n    context \"when not part of the same organization\" do\n      it \"does not allow admin to update\" do\n        expect(subject).not_to permit(other_org_casa_admin, casa_case)\n      end\n    end\n\n    context \"when a supervisor belongs to the same org as the case\" do\n      it \"allows the supervisor\" do\n        supervisor = create(:supervisor, casa_org: organization)\n        casa_case = create(:casa_case, casa_org: organization)\n        expect(subject).to permit(supervisor, casa_case)\n      end\n    end\n\n    context \"when a supervisor does not belong to the same org as the case\" do\n      it \"does not allow the supervisor\" do\n        supervisor = create(:supervisor, casa_org: organization)\n        casa_case = build_stubbed(:casa_case, casa_org: different_organization)\n        expect(subject).not_to permit(supervisor, casa_case)\n      end\n    end\n\n    context \"when volunteer is assigned\" do\n      it \"allows the volunteer\" do\n        volunteer = create(:volunteer, casa_org: organization)\n        casa_case = build(:casa_case, casa_org: organization)\n        volunteer.casa_cases << casa_case\n        expect(subject).to permit(volunteer, casa_case)\n      end\n    end\n\n    it \"does not allow volunteers who are unassigned\" do\n      expect(subject).not_to permit(volunteer, casa_case)\n    end\n\n    context \"when volunteer is from another organization\" do\n      it \"does not allow the volunteer\" do\n        volunteer = create(:volunteer, casa_org: different_organization)\n        casa_case = build(:casa_case, casa_org: organization)\n        expect { volunteer.casa_cases << casa_case }\n          .to raise_error(\n            ActiveRecord::RecordInvalid,\n            /must belong to the same organization/\n          )\n      end\n    end\n  end\n\n  permissions :new?, :create?, :destroy? do\n    context \"when part of the same organizaton\" do\n      it \"allows casa_admins\" do\n        expect(subject).to permit(casa_admin, casa_case)\n      end\n    end\n\n    context \"when not part of the same organization\" do\n      it \"does not allow admin to create\" do\n        expect(subject).not_to permit(other_org_casa_admin, casa_case)\n      end\n    end\n\n    it \"does not allow superivsors\" do\n      expect(subject).not_to permit(supervisor, casa_case)\n    end\n\n    it \"does not allow volunteers\" do\n      expect(subject).not_to permit(volunteer, casa_case)\n      expect(subject).not_to permit(other_org_volunteer, casa_case)\n    end\n  end\n\n  permissions :index?, :save_emancipation? do\n    # Should :save_emancipation belong with :index?\n    # Because we are authorizing without an instance, should we only check a user's\n    # role?\n    it \"allows casa_admins\" do\n      expect(subject).to permit(casa_admin, CasaCase)\n      expect(subject).to permit(other_org_casa_admin, CasaCase)\n    end\n\n    it \"allows supervisor\" do\n      expect(subject).to permit(supervisor, CasaCase)\n      expect(subject).to permit(other_org_supervisor, CasaCase)\n    end\n\n    it \"allows volunteer\" do\n      expect(subject).to permit(volunteer, CasaCase)\n      expect(subject).to permit(other_org_volunteer, CasaCase)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/policies/casa_org_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CasaOrgPolicy do\n  subject { described_class }\n\n  let(:organization) { build(:casa_org, users: [volunteer, supervisor, casa_admin]) }\n  let(:different_organization) { create(:casa_org) }\n\n  let(:volunteer) { build(:volunteer) }\n  let(:supervisor) { build(:supervisor) }\n  let(:casa_admin) { build(:casa_admin) }\n\n  permissions :edit?, :update? do\n    context \"when admin belongs to the same org\" do\n      it \"allows casa_admins\" do\n        expect(subject).to permit(casa_admin, organization)\n      end\n    end\n\n    context \"when admin does not belong to org\" do\n      it \"does not permit admin\" do\n        expect(subject).not_to permit(casa_admin, different_organization)\n      end\n    end\n\n    it \"does not permit supervisor\" do\n      expect(subject).not_to permit(supervisor, organization)\n    end\n\n    it \"does not permit volunteer\" do\n      expect(subject).not_to permit(volunteer, organization)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/policies/case_assignment_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CaseAssignmentPolicy do\n  subject { described_class }\n\n  let(:organization) { build(:casa_org) }\n  let(:casa_admin) { build(:casa_admin, casa_org: organization) }\n  let(:casa_case) { create(:casa_case, casa_org: organization) }\n  let(:volunteer) { build(:volunteer, casa_org: organization) }\n  let(:case_assignment) { build(:case_assignment, casa_case: casa_case, volunteer: volunteer) }\n  let(:case_assignment_inactive) { build(:case_assignment, casa_case: casa_case, volunteer: volunteer, active: false) }\n  let(:supervisor) { create(:supervisor, casa_org: organization) }\n\n  let(:other_organization) { create(:casa_org) }\n  let(:other_casa_case) do\n    create(:casa_case, casa_org: other_organization)\n  end\n  let(:other_case_assignment) do\n    create(:case_assignment, casa_case: other_casa_case)\n  end\n\n  permissions :create? do\n    it \"allows casa_admins\" do\n      expect(subject).to permit(casa_admin)\n    end\n\n    it \"allows supervisor\" do\n      expect(subject).to permit(supervisor)\n    end\n\n    it \"does not permit volunteer\" do\n      expect(subject).not_to permit(volunteer)\n    end\n  end\n\n  permissions :unassign? do\n    it \"does not allow unassign if case_assignment is inactive\" do\n      expect(subject).not_to permit(casa_admin, case_assignment_inactive)\n    end\n\n    context \"when user is an admin\" do\n      it \"allow update when case_assignment is active\" do\n        expect(subject).to permit(casa_admin, case_assignment)\n      end\n    end\n\n    context \"when user is a supervisor\" do\n      it \"allow update when case_assignment is active\" do\n        expect(subject).to permit(supervisor, case_assignment)\n      end\n    end\n\n    context \"when user is a volunteer\" do\n      it \"does not allow unassign\" do\n        expect(subject).not_to permit(volunteer, case_assignment)\n      end\n    end\n\n    context \"when is a different organization\" do\n      it \"does not allow unassign\" do\n        expect(subject).not_to permit(supervisor, other_case_assignment)\n      end\n    end\n  end\n\n  permissions :show_or_hide_contacts? do\n    context \"when an admin\" do\n      context \"in the same organization\" do\n        it \"allows user to show or hide contacts\" do\n          expect(subject).to permit(casa_admin, case_assignment_inactive)\n        end\n      end\n\n      context \"in a different organization\" do\n        it \"does not allow user to show or hide contacts\" do\n          other_case_assignment.update(active: false)\n          expect(subject).not_to permit(casa_admin, other_case_assignment)\n        end\n      end\n    end\n\n    context \"when a supervisor\" do\n      context \"in the same organization\" do\n        it \"allows user to show or hide contacts\" do\n          expect(subject).to permit(supervisor, case_assignment_inactive)\n        end\n      end\n\n      context \"in a different organization\" do\n        it \"does not allow user to show or hide contacts\" do\n          other_case_assignment.update(active: false)\n          expect(subject).not_to permit(supervisor, other_case_assignment)\n        end\n      end\n    end\n\n    context \"when a volunteer\" do\n      it \"does not allow user to show or hide contacts\" do\n        expect(subject).not_to permit(volunteer, case_assignment_inactive)\n      end\n    end\n\n    context \"when the case_assignment is active\" do\n      describe \"it does not allow any user to show/hide contacts\" do\n        it { is_expected.not_to permit(casa_admin, case_assignment) }\n        it { is_expected.not_to permit(supervisor, case_assignment) }\n        it { is_expected.not_to permit(volunteer, case_assignment) }\n      end\n    end\n  end\n\n  permissions :destroy? do\n    context \"when user is an admin\" do\n      it \"allow destroy\" do\n        expect(subject).to permit(casa_admin, case_assignment)\n      end\n    end\n\n    context \"when user is a supervisor\" do\n      it \"allow destroy\" do\n        expect(subject).to permit(supervisor, case_assignment)\n      end\n    end\n\n    context \"when is a different organization\" do\n      it \"does not allow admins to destroy\" do\n        expect(subject).not_to permit(casa_admin, other_case_assignment)\n      end\n\n      it \"does not allow supervisor to destroy\" do\n        expect(subject).not_to permit(supervisor, other_case_assignment)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/policies/case_contact_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CaseContactPolicy, :aggregate_failures do\n  subject { described_class }\n\n  let(:casa_org) { create(:casa_org) }\n  let(:casa_admin) { build(:casa_admin, casa_org:) }\n  let(:supervisor) { build(:supervisor, casa_org:) }\n  let(:volunteer) { create(:volunteer, :with_single_case, supervisor:, casa_org:) }\n  let(:casa_case) { volunteer.casa_cases.first }\n\n  let(:case_contact) { create(:case_contact, casa_case:, creator: volunteer) }\n  let(:draft_case_contact) { create(:case_contact, :started_status, casa_case: nil, creator: volunteer) }\n\n  # another volunteer assigned to the same case\n  let(:same_case_volunteer) { create :volunteer, casa_org: }\n  let(:same_case_volunteer_case_assignment) { create :case_assignment, volunteer: same_case_volunteer, casa_case: }\n  let(:same_case_volunteer_case_contact) do\n    same_case_volunteer_case_assignment\n    create :case_contact, casa_case:, creator: same_case_volunteer\n  end\n\n  # same org case that volunteer is not assigned to\n  let(:unassigned_case_case_contact) do\n    create :case_contact, casa_case: create(:casa_case, casa_org:), creator: create(:volunteer, casa_org:)\n  end\n  let(:other_org_case_contact) { build(:case_contact, casa_org: create(:casa_org)) }\n\n  permissions :index? do\n    it \"allows casa_admins\" do\n      expect(subject).to permit(casa_admin)\n    end\n\n    it \"allows supervisor\" do\n      expect(subject).to permit(supervisor)\n    end\n\n    it \"allows volunteer\" do\n      expect(subject).to permit(volunteer)\n    end\n  end\n\n  permissions :show? do\n    it \"allows same org casa_admins\" do\n      expect(subject).to permit(casa_admin, case_contact)\n      expect(subject).to permit(casa_admin, draft_case_contact)\n      expect(subject).to permit(casa_admin, same_case_volunteer_case_contact)\n      expect(subject).to permit(casa_admin, unassigned_case_case_contact)\n\n      expect(subject).not_to permit(casa_admin, other_org_case_contact)\n    end\n\n    it \"allows same org supervisors\" do\n      expect(subject).to permit(supervisor, case_contact)\n      expect(subject).to permit(supervisor, draft_case_contact)\n      expect(subject).to permit(supervisor, same_case_volunteer_case_contact)\n      expect(subject).to permit(supervisor, unassigned_case_case_contact)\n\n      expect(subject).not_to permit(supervisor, other_org_case_contact)\n    end\n\n    it \"allows volunteer only if they created the case contact\" do\n      expect(subject).to permit(volunteer, case_contact)\n      expect(subject).to permit(volunteer, draft_case_contact)\n\n      expect(subject).not_to permit(volunteer, unassigned_case_case_contact)\n      expect(subject).not_to permit(volunteer, other_org_case_contact)\n    end\n  end\n\n  permissions :edit?, :update? do\n    it \"allows same org casa_admins\" do\n      expect(subject).to permit(casa_admin, case_contact)\n      expect(subject).to permit(casa_admin, draft_case_contact)\n      expect(subject).to permit(casa_admin, same_case_volunteer_case_contact)\n      expect(subject).to permit(casa_admin, unassigned_case_case_contact)\n\n      expect(subject).not_to permit(casa_admin, other_org_case_contact)\n    end\n\n    it \"allows same org supervisors\" do\n      expect(subject).to permit(supervisor, case_contact)\n      expect(subject).to permit(supervisor, draft_case_contact)\n      expect(subject).to permit(supervisor, same_case_volunteer_case_contact)\n\n      expect(subject).not_to permit(supervisor, other_org_case_contact)\n    end\n\n    it \"allows volunteer only if they created the case contact\" do\n      expect(subject).to permit(volunteer, case_contact)\n      expect(subject).to permit(volunteer, draft_case_contact)\n\n      expect(subject).not_to permit(volunteer, same_case_volunteer_case_contact)\n      expect(subject).not_to permit(volunteer, unassigned_case_case_contact)\n      expect(subject).not_to permit(volunteer, other_org_case_contact)\n    end\n  end\n\n  permissions :new? do\n    it \"allows same org casa_admins\" do\n      expect(subject).to permit(volunteer, case_contact.dup)\n      expect(subject).to permit(volunteer, draft_case_contact.dup)\n\n      expect(subject).not_to permit(casa_admin, CaseContact.new)\n    end\n\n    it \"allows same org supervisors\" do\n      expect(subject).to permit(volunteer, case_contact.dup)\n      expect(subject).to permit(volunteer, draft_case_contact.dup)\n\n      expect(subject).not_to permit(supervisor, CaseContact.new)\n    end\n\n    it \"allows volunteers who are the contact creator\" do\n      expect(subject).to permit(volunteer, case_contact.dup)\n      expect(subject).to permit(volunteer, draft_case_contact.dup)\n\n      expect(subject).not_to permit(volunteer, CaseContact.new)\n    end\n  end\n\n  permissions :datatable? do\n    it \"allows casa_admins\" do\n      expect(subject).to permit(casa_admin)\n    end\n\n    it \"allows supervisors\" do\n      expect(subject).to permit(supervisor)\n    end\n\n    it \"allows volunteers\" do\n      expect(subject).to permit(volunteer)\n    end\n  end\n\n  permissions :drafts? do\n    it \"allows casa_admins\" do\n      expect(subject).to permit(casa_admin)\n    end\n\n    it \"allows supervisors\" do\n      expect(subject).to permit(supervisor)\n    end\n\n    it \"does not allow volunteers\" do\n      expect(subject).not_to permit(volunteer)\n    end\n  end\n\n  permissions :destroy? do\n    it \"allows same org casa_admins\" do\n      expect(subject).to permit(casa_admin, case_contact)\n      expect(subject).to permit(casa_admin, draft_case_contact)\n      expect(subject).to permit(casa_admin, same_case_volunteer_case_contact)\n      expect(subject).to permit(casa_admin, unassigned_case_case_contact)\n\n      expect(subject).not_to permit(casa_admin, other_org_case_contact)\n    end\n\n    it \"allows supervisors\" do\n      expect(subject).to permit(supervisor, case_contact)\n      expect(subject).to permit(supervisor, draft_case_contact)\n      expect(subject).to permit(supervisor, same_case_volunteer_case_contact)\n\n      expect(subject).not_to permit(supervisor, other_org_case_contact)\n    end\n\n    it \"allows volunteer only for draft contacts they created\" do\n      expect(subject).to permit(volunteer, draft_case_contact)\n\n      expect(subject).not_to permit(volunteer, case_contact)\n      expect(subject).not_to permit(volunteer, same_case_volunteer_case_contact)\n      expect(subject).not_to permit(volunteer, unassigned_case_case_contact)\n      expect(subject).not_to permit(volunteer, other_org_case_contact)\n    end\n  end\n\n  permissions :restore? do\n    it \"allows same org casa_admins\" do\n      expect(subject).to permit(casa_admin, case_contact)\n      expect(subject).to permit(casa_admin, draft_case_contact)\n      expect(subject).to permit(casa_admin, same_case_volunteer_case_contact)\n      expect(subject).to permit(casa_admin, unassigned_case_case_contact)\n      expect(subject).not_to permit(casa_admin, other_org_case_contact)\n    end\n\n    it \"does not allow supervisors\" do\n      expect(subject).not_to permit(supervisor, case_contact)\n      expect(subject).not_to permit(supervisor, draft_case_contact)\n      expect(subject).not_to permit(supervisor, same_case_volunteer_case_contact)\n      expect(subject).not_to permit(supervisor, unassigned_case_case_contact)\n      expect(subject).not_to permit(supervisor, other_org_case_contact)\n    end\n\n    it \"does not allow volunteers\" do\n      expect(subject).not_to permit(volunteer, draft_case_contact)\n      expect(subject).not_to permit(volunteer, case_contact)\n      expect(subject).not_to permit(volunteer, same_case_volunteer_case_contact)\n      expect(subject).not_to permit(volunteer, unassigned_case_case_contact)\n      expect(subject).not_to permit(volunteer, other_org_case_contact)\n    end\n  end\n\n  describe \"Scope#resolve\" do\n    subject { described_class::Scope.new(user, CaseContact.all).resolve }\n\n    before do\n      case_contact\n      draft_case_contact\n      same_case_volunteer_case_contact\n      unassigned_case_case_contact\n      other_org_case_contact\n    end\n\n    context \"when user is a visitor\" do\n      let(:user) { nil }\n\n      it \"returns no case contacts\" do\n        expect(subject).not_to include(case_contact, other_org_case_contact)\n      end\n    end\n\n    context \"when user is a volunteer\" do\n      let(:user) { volunteer }\n\n      it \"returns case contacts created by the volunteer\" do\n        expect(subject).to include(case_contact, draft_case_contact)\n        expect(subject)\n          .not_to include(same_case_volunteer_case_contact, unassigned_case_case_contact, other_org_case_contact)\n      end\n    end\n\n    context \"when user is a supervisor\" do\n      let(:user) { supervisor }\n\n      it \"returns same org case contacts\" do\n        expect(subject)\n          .to include(case_contact, draft_case_contact, same_case_volunteer_case_contact, unassigned_case_case_contact)\n        expect(subject).not_to include(other_org_case_contact)\n      end\n    end\n\n    context \"when user is a casa_admin\" do\n      let(:user) { casa_admin }\n\n      it \"returns same org case contacts\" do\n        expect(subject)\n          .to include(case_contact, draft_case_contact, same_case_volunteer_case_contact, unassigned_case_case_contact)\n        expect(subject).not_to include(other_org_case_contact)\n      end\n    end\n\n    context \"when user is an all_casa_admin\" do\n      let(:user) { create :all_casa_admin }\n\n      it \"returns no case contacts\" do\n        expect(subject).not_to include(case_contact, other_org_case_contact)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/policies/case_court_order_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CaseCourtOrderPolicy do\n  subject { described_class }\n\n  let(:casa_admin) { build_stubbed(:casa_admin) }\n  let(:supervisor) { build_stubbed(:supervisor) }\n  let(:volunteer) { build_stubbed(:volunteer) }\n\n  permissions :destroy? do\n    it { is_expected.to permit(casa_admin) }\n    it { is_expected.to permit(supervisor) }\n    it { is_expected.to permit(volunteer) }\n  end\nend\n"
  },
  {
    "path": "spec/policies/case_court_report_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CaseCourtReportPolicy do\n  subject { described_class }\n\n  let(:casa_admin) { build_stubbed(:casa_admin) }\n  let(:volunteer) { build_stubbed(:volunteer) }\n  let(:supervisor) { build_stubbed(:supervisor) }\n\n  permissions :index?, :generate?, :show? do\n    it \"allows casa_admins\" do\n      expect(subject).to permit(casa_admin)\n    end\n\n    it \"allows supervisor\" do\n      expect(subject).to permit(supervisor)\n    end\n\n    it \"allows volunteer\" do\n      expect(subject).to permit(volunteer)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/policies/case_group_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CaseGroupPolicy, type: :policy do\n  subject { described_class }\n\n  let(:casa_org) { create :casa_org }\n  let(:volunteer) { create :volunteer, :with_casa_cases, casa_org: }\n  let(:supervisor) { create :supervisor, casa_org: }\n  let(:casa_admin) { create :casa_admin, casa_org: }\n  let(:all_casa_admin) { create :all_casa_admin }\n\n  let(:case_group) { create :case_group, casa_org: }\n  let(:volunteer_case_group) { create :case_group, casa_org:, casa_cases: volunteer.casa_cases }\n\n  permissions :new?, :create?, :destroy?, :edit?, :show?, :update? do\n    it \"does not permit a nil user\" do\n      expect(described_class).not_to permit(nil, case_group)\n    end\n\n    it \"does not permit a volunteer\" do\n      expect(described_class).not_to permit(volunteer, case_group)\n    end\n\n    it \"does not permit a volunteer assigned to the case group\" do\n      expect(described_class).not_to permit(volunteer, volunteer_case_group)\n    end\n\n    it \"permits a supervisor\" do\n      expect(described_class).to permit(supervisor, case_group)\n    end\n\n    it \"does not permit a supervisor for a different casa org\" do\n      other_org_supervisor = create :supervisor, casa_org: create(:casa_org)\n      expect(described_class).not_to permit(other_org_supervisor, case_group)\n    end\n\n    it \"permits a casa admin\" do\n      expect(described_class).to permit(casa_admin, case_group)\n    end\n\n    it \"does not permit a casa admin for a different casa org\" do\n      other_org_casa_admin = create :casa_admin, casa_org: create(:casa_org)\n      expect(described_class).not_to permit(other_org_casa_admin, case_group)\n    end\n\n    it \"does not permit an all casa admin\" do\n      expect(described_class).not_to permit(all_casa_admin, case_group)\n    end\n  end\n\n  permissions :index? do\n    it \"does not permit a nil user\" do\n      expect(described_class).not_to permit(nil, :case_group)\n    end\n\n    it \"does not permit a volunteer\" do\n      expect(described_class).not_to permit(volunteer, :case_group)\n    end\n\n    it \"permits a supervisor\" do\n      expect(described_class).to permit(supervisor, :case_group)\n    end\n\n    it \"permits a casa admin\" do\n      expect(described_class).to permit(casa_admin, :case_group)\n    end\n\n    it \"does not permit an all casa admin\" do\n      expect(described_class).not_to permit(all_casa_admin, :case_group)\n    end\n  end\n\n  describe \"Scope#resolve\" do\n    subject { described_class::Scope.new(user, CaseGroup.all).resolve }\n\n    let!(:casa_org_case_group) { create :case_group, casa_org: }\n    let!(:other_casa_org_case_group) { create :case_group, casa_org: create(:casa_org) }\n\n    context \"when user is a visitor\" do\n      let(:user) { nil }\n\n      it { is_expected.not_to include(casa_org_case_group) }\n      it { is_expected.not_to include(other_casa_org_case_group) }\n    end\n\n    context \"when user is a volunteer\" do\n      let(:user) { volunteer }\n      let!(:user_case_group) { volunteer_case_group }\n\n      it { is_expected.not_to include(user_case_group) }\n      it { is_expected.not_to include(casa_org_case_group) }\n      it { is_expected.not_to include(other_casa_org_case_group) }\n    end\n\n    context \"when user is a supervisor\" do\n      let(:user) { supervisor }\n\n      it { is_expected.to include(casa_org_case_group) }\n      it { is_expected.not_to include(other_casa_org_case_group) }\n    end\n\n    context \"when user is a casa_admin\" do\n      let(:user) { casa_admin }\n\n      it { is_expected.to include(casa_org_case_group) }\n      it { is_expected.not_to include(other_casa_org_case_group) }\n    end\n\n    context \"when user is an all_casa_admin\" do\n      let(:user) { all_casa_admin }\n\n      it { is_expected.not_to include(casa_org_case_group) }\n      it { is_expected.not_to include(other_casa_org_case_group) }\n    end\n  end\nend\n"
  },
  {
    "path": "spec/policies/checklist_item_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe ChecklistItemPolicy do\n  subject { described_class }\n\n  let(:casa_admin) { build_stubbed(:casa_admin) }\n  let(:volunteer) { build_stubbed(:volunteer) }\n  let(:supervisor) { build_stubbed(:supervisor) }\n\n  permissions :new?, :create?, :destroy?, :edit?, :update? do\n    it \"allows casa_admins\" do\n      expect(subject).to permit(casa_admin)\n    end\n\n    it \"does not permit supervisor\" do\n      expect(subject).not_to permit(supervisor)\n    end\n\n    it \"does not permit volunteer\" do\n      expect(subject).not_to permit(volunteer)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/policies/contact_topic_answer_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe ContactTopicAnswerPolicy, :aggregate_failures, type: :policy do\n  subject { described_class }\n\n  let(:casa_org) { create :casa_org }\n  let(:volunteer) { create :volunteer, :with_single_case, casa_org: }\n  let(:supervisor) { create :supervisor, casa_org: }\n  let(:casa_admin) { create :casa_admin, casa_org: }\n  let(:all_casa_admin) { create :all_casa_admin }\n\n  let(:contact_topic) { create :contact_topic, casa_org: }\n  let(:casa_case) { volunteer.casa_cases.first }\n  let(:case_contact) { create :case_contact, creator: volunteer }\n  let(:contact_topic_answer) { create :contact_topic_answer, contact_topic:, case_contact: }\n\n  let(:draft_case_contact) { create :case_contact, :started_status, casa_case: nil, creator: volunteer }\n  let(:draft_contact_topic_answer) { create :contact_topic_answer, contact_topic:, case_contact: draft_case_contact }\n\n  let(:same_case_volunteer) { create :volunteer, casa_org: }\n  let(:same_case_volunteer_case_assignment) { create :case_assignment, volunteer: same_case_volunteer, casa_case: }\n  let(:same_case_volunteer_case_contact) do\n    same_case_volunteer_case_assignment\n    create :case_contact, casa_case:, creator: same_case_volunteer\n  end\n  let(:same_case_volunteer_contact_topic_answer) do\n    create :contact_topic_answer, contact_topic:, case_contact: same_case_volunteer_case_contact\n  end\n\n  let(:other_org) { create :casa_org }\n  let(:other_org_volunteer) { create :volunteer, casa_org: other_org }\n  let(:other_org_contact_topic) { create :contact_topic, casa_org: other_org }\n  let(:other_org_casa_case) { create :casa_case, casa_org: other_org }\n  let(:other_org_case_contact) { create :case_contact, casa_case: other_org_casa_case, creator: other_org_volunteer }\n  let(:other_org_contact_topic_answer) do\n    create :contact_topic_answer, case_contact: other_org_case_contact, contact_topic: other_org_contact_topic\n  end\n\n  permissions :create?, :destroy? do\n    it \"does not permit a nil user\" do\n      expect(subject).not_to permit(nil, contact_topic_answer)\n    end\n\n    it \"permits a volunteer who created the case contact\" do\n      expect(subject).to permit(volunteer, contact_topic_answer)\n      expect(subject).to permit(volunteer, draft_contact_topic_answer)\n      expect(subject).not_to permit(volunteer, same_case_volunteer_contact_topic_answer)\n      expect(subject).not_to permit(volunteer, other_org_contact_topic_answer)\n    end\n\n    it \"permits same_org supervisors\" do\n      expect(subject).to permit(supervisor, contact_topic_answer)\n      expect(subject).to permit(supervisor, draft_contact_topic_answer)\n      expect(subject).to permit(supervisor, same_case_volunteer_contact_topic_answer)\n\n      expect(subject).not_to permit(supervisor, other_org_contact_topic_answer)\n    end\n\n    it \"permits same org casa admins\" do\n      expect(subject).to permit(casa_admin, contact_topic_answer)\n      expect(subject).to permit(casa_admin, draft_contact_topic_answer)\n      expect(subject).to permit(casa_admin, same_case_volunteer_contact_topic_answer)\n\n      expect(subject).not_to permit(casa_admin, other_org_contact_topic_answer)\n    end\n\n    it \"does not permit an all casa admin\" do\n      expect(subject).not_to permit(all_casa_admin, contact_topic_answer)\n    end\n  end\n\n  describe \"Scope#resolve\" do\n    subject { described_class::Scope.new(user, ContactTopicAnswer.all).resolve }\n\n    before do\n      contact_topic_answer\n      draft_contact_topic_answer\n      same_case_volunteer_contact_topic_answer\n      other_org_contact_topic_answer\n    end\n\n    context \"when user is a visitor\" do\n      let(:user) { nil }\n\n      it \"returns no contact topic answers\" do\n        expect(subject).not_to include(contact_topic_answer, other_org_contact_topic_answer)\n      end\n    end\n\n    context \"when user is a volunteer\" do\n      let(:user) { volunteer }\n\n      it \"returns contact topic answers of contacts created by the volunteer\" do\n        expect(subject).to include(contact_topic_answer, draft_contact_topic_answer)\n        expect(subject).not_to include(same_case_volunteer_contact_topic_answer, other_org_contact_topic_answer)\n      end\n    end\n\n    context \"when user is a supervisor\" do\n      let(:user) { supervisor }\n\n      it \"returns same org contact topic answers\" do\n        expect(subject)\n          .to include(contact_topic_answer, draft_contact_topic_answer, same_case_volunteer_contact_topic_answer)\n        expect(subject).not_to include(other_org_contact_topic_answer)\n      end\n    end\n\n    context \"when user is a casa_admin\" do\n      let(:user) { casa_admin }\n\n      it \"includes same org contact topic answers\" do\n        expect(subject)\n          .to include(contact_topic_answer, draft_contact_topic_answer, same_case_volunteer_contact_topic_answer)\n        expect(subject).not_to include(other_org_contact_topic_answer)\n      end\n    end\n\n    context \"when user is an all_casa_admin\" do\n      let(:user) { all_casa_admin }\n\n      it \"returns no contact topic answers\" do\n        expect(subject).not_to include(contact_topic_answer, other_org_contact_topic_answer)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/policies/contact_topic_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe ContactTopicPolicy, type: :policy do\n  subject { described_class }\n\n  let(:contact_topic) { build(:contact_topic, casa_org: organization) }\n\n  let(:organization) { build(:casa_org) }\n  let(:casa_admin) { create(:casa_admin, casa_org: organization) }\n  let(:other_org_admin) { create(:casa_admin) }\n  let(:volunteer) { build(:volunteer, casa_org: organization) }\n  let(:supervisor) { build(:supervisor, casa_org: organization) }\n\n  permissions :create?, :edit?, :new?, :show?, :soft_delete?, :update? do\n    it \"allows same org casa_admins\" do\n      expect(subject).to permit(casa_admin, contact_topic)\n    end\n\n    it \"allows does not allow different org casa_admins\" do\n      expect(subject).not_to permit(other_org_admin, contact_topic)\n    end\n\n    it \"does not permit supervisor\" do\n      expect(subject).not_to permit(supervisor, contact_topic)\n    end\n\n    it \"does not permit volunteer\" do\n      expect(subject).not_to permit(volunteer, contact_topic)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/policies/contact_type_group_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe ContactTypeGroupPolicy do\n  subject { described_class }\n\n  let(:casa_admin) { build_stubbed(:casa_admin) }\n  let(:volunteer) { build_stubbed(:volunteer) }\n  let(:supervisor) { build_stubbed(:supervisor) }\n\n  permissions :new?, :create?, :edit?, :update? do\n    it \"allows casa_admins\" do\n      expect(subject).to permit(casa_admin)\n    end\n\n    it \"does not permit supervisor\" do\n      expect(subject).not_to permit(supervisor)\n    end\n\n    it \"does not permit volunteer\" do\n      expect(subject).not_to permit(volunteer)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/policies/contact_type_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe ContactTypePolicy do\n  subject { described_class }\n\n  let(:casa_admin) { build_stubbed(:casa_admin) }\n  let(:volunteer) { build_stubbed(:volunteer) }\n  let(:supervisor) { build_stubbed(:supervisor) }\n\n  permissions :new?, :create?, :edit?, :update? do\n    it \"allows casa_admins\" do\n      expect(subject).to permit(casa_admin)\n    end\n\n    it \"does not permit supervisor\" do\n      expect(subject).not_to permit(supervisor)\n    end\n\n    it \"does not permit volunteer\" do\n      expect(subject).not_to permit(volunteer)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/policies/court_date_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CourtDatePolicy do\n  subject { described_class }\n\n  let(:organization) { create(:casa_org) }\n  let(:different_organization) { create(:casa_org) }\n\n  let(:court_date) { create(:court_date, casa_case: casa_case) }\n  let(:casa_case) { create(:casa_case, casa_org: organization) }\n\n  let(:casa_admin) { create(:casa_admin, casa_org: organization) }\n  let(:volunteer) { create(:volunteer, casa_org: organization) }\n  let(:supervisor) { create(:supervisor, casa_org: organization) }\n\n  permissions :show? do\n    it { is_expected.to permit(casa_admin, court_date) }\n\n    context \"when a supervisor belongs to the same org as the case\" do\n      it { expect(subject).to permit(supervisor, court_date) }\n    end\n\n    context \"when a supervisor does not belong to the same org as the case\" do\n      let(:casa_case) { create(:casa_case, casa_org: different_organization) }\n\n      it { expect(subject).not_to permit(supervisor, court_date) }\n    end\n\n    context \"when volunteer is assigned\" do\n      before { volunteer.casa_cases << casa_case }\n\n      it { is_expected.to permit(volunteer, court_date) }\n    end\n\n    context \"when volunteer is not assigned\" do\n      it { is_expected.not_to permit(volunteer, court_date) }\n    end\n  end\n\n  permissions :destroy? do\n    it { is_expected.to permit(casa_admin, court_date) }\n    it { is_expected.to permit(supervisor, court_date) }\n    it { is_expected.not_to permit(volunteer, court_date) }\n  end\nend\n"
  },
  {
    "path": "spec/policies/custom_org_link_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CustomOrgLinkPolicy, type: :policy do\n  let(:casa_admin) { build_stubbed(:casa_admin) }\n  let(:supervisor) { build_stubbed(:supervisor) }\n  let(:volunteer) { build_stubbed(:volunteer) }\n\n  permissions :new?, :create?, :edit?, :update? do\n    it \"permits casa_admins\" do\n      expect(described_class).to permit(casa_admin)\n    end\n\n    it \"does not permit supervisor\" do\n      expect(described_class).not_to permit(supervisor)\n    end\n\n    it \"does not permit volunteer\" do\n      expect(described_class).not_to permit(volunteer)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/policies/dashboard_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe DashboardPolicy do\n  subject { described_class }\n\n  let(:user) { build(:user) }\n  let(:casa_admin) { build(:casa_admin) }\n  let(:supervisor) { build(:supervisor) }\n  let(:volunteer) { build(:volunteer) }\n\n  permissions :show? do\n    it \"permits user to show\" do\n      expect(subject).to permit(user)\n    end\n  end\n\n  permissions :see_volunteers_section? do\n    it \"allows casa_admins\" do\n      expect(subject).to permit(casa_admin)\n    end\n\n    it \"does not allow volunteers\" do\n      expect(subject).not_to permit(volunteer)\n    end\n  end\n\n  permissions :create_cases_section? do\n    context \"when user is a volunteer with casa_cases\" do\n      it \"permits user to see cases section\" do\n        volunteer.casa_cases << build_stubbed(:casa_case, casa_org: volunteer.casa_org)\n        expect(Pundit.policy(volunteer, :dashboard).create_case_contacts?).to eq true\n      end\n    end\n\n    context \"when user is a volunteer without casa_cases\" do\n      it \"permits user to see cases section\" do\n        expect(Pundit.policy(volunteer, :dashboard).create_case_contacts?).to eq false\n      end\n    end\n\n    context \"when user is an admin\" do\n      it \"permits user to see cases section\" do\n        expect(Pundit.policy(casa_admin, :dashboard).create_case_contacts?).to eq false\n      end\n    end\n  end\n\n  permissions :see_cases_section? do\n    context \"when user is a volunteer\" do\n      it \"permits user to see cases section\" do\n        expect(subject).to permit(volunteer, :dashboard)\n      end\n    end\n  end\n\n  permissions :see_admins_section? do\n    it \"allows casa_admins\" do\n      expect(subject).to permit(casa_admin)\n    end\n\n    it \"does not allow supervisors and volunteers\" do\n      expect(subject).not_to permit(supervisor)\n    end\n\n    it \"does not allow volunteers\" do\n      expect(subject).not_to permit(volunteer)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/policies/followup_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe FollowupPolicy do\n  subject { described_class }\n\n  let(:casa_admin) { build_stubbed(:casa_admin) }\n  let(:volunteer) { build_stubbed(:volunteer) }\n  let(:supervisor) { build_stubbed(:supervisor) }\n\n  permissions :create?, :resolve? do\n    it \"allows casa_admins\" do\n      expect(subject).to permit(casa_admin)\n    end\n\n    it \"allows supervisor\" do\n      expect(subject).to permit(supervisor)\n    end\n\n    it \"allows volunteer\" do\n      expect(subject).to permit(volunteer)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/policies/fund_request_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe FundRequestPolicy, type: :policy do\n  # TODO: Add tests for FundRequestPolicy\n\n  pending \"add some tests for FundRequestPolicy\"\nend\n"
  },
  {
    "path": "spec/policies/hearing_type_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe HearingTypePolicy do\n  subject { described_class }\n\n  let(:casa_admin) { build_stubbed(:casa_admin) }\n  let(:volunteer) { build_stubbed(:volunteer) }\n  let(:supervisor) { build_stubbed(:supervisor) }\n\n  permissions :new?, :create?, :edit?, :update? do\n    it \"allows casa_admins\" do\n      expect(subject).to permit(casa_admin)\n    end\n\n    it \"does not permit supervisor\" do\n      expect(subject).not_to permit(supervisor)\n    end\n\n    it \"does not permit volunteer\" do\n      expect(subject).not_to permit(volunteer)\n    end\n  end\n\n  describe \"scope\" do\n    it \"onlies return hearing types that belong to a given casa organization\" do\n      hearing_type_1 = create(:hearing_type)\n      hearing_type_2 = create(:hearing_type)\n\n      hearing_type_3 = create(:hearing_type)\n      casa_org_2 = create(:casa_org)\n      hearing_type_3.update_attribute(:casa_org_id, casa_org_2.id)\n      hearing_type_3.update_attribute(:name, \"unwanted hearing type\")\n\n      scoped_hearing_types = Pundit.policy_scope!(create(:casa_admin), HearingType).to_a\n      expect(scoped_hearing_types).to contain_exactly(hearing_type_1, hearing_type_2)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/policies/import_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe ImportPolicy do\n  subject { described_class }\n\n  let(:casa_admin) { build_stubbed(:casa_admin) }\n  let(:volunteer) { build_stubbed(:volunteer) }\n  let(:supervisor) { build_stubbed(:supervisor) }\n\n  permissions :index?, :create?, :download_failed? do\n    it \"allows casa_admins\" do\n      expect(subject).to permit(casa_admin)\n    end\n\n    it \"does not permit supervisor\" do\n      expect(subject).not_to permit(supervisor)\n    end\n\n    it \"does not permit volunteer\" do\n      expect(subject).not_to permit(volunteer)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/policies/judge_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe JudgePolicy do\n  subject { described_class }\n\n  let(:casa_admin) { build_stubbed(:casa_admin) }\n  let(:volunteer) { build_stubbed(:volunteer) }\n  let(:supervisor) { build_stubbed(:supervisor) }\n\n  permissions :new?, :create?, :edit?, :update? do\n    it \"allows casa_admins\" do\n      expect(subject).to permit(casa_admin)\n    end\n\n    it \"does not permit supervisor\" do\n      expect(subject).not_to permit(supervisor)\n    end\n\n    it \"does not permit volunteer\" do\n      expect(subject).not_to permit(volunteer)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/policies/language_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe LanguagePolicy do\n  subject { described_class }\n\n  let(:admin) { build_stubbed(:casa_admin) }\n  let(:supervisor) { build_stubbed(:supervisor) }\n  let(:volunteer) { build_stubbed(:volunteer) }\n\n  permissions :add_language?, :remove_from_volunteer? do\n    context \"when user is a casa admin\" do\n      it \"doesn't permit\" do\n        expect(subject).not_to permit(admin)\n      end\n    end\n\n    context \"when user is a supervisor\" do\n      it \"doesn't permit\" do\n        expect(subject).not_to permit(supervisor)\n      end\n    end\n\n    context \"when user is a volunteer\" do\n      it \"allows\" do\n        expect(subject).to permit(volunteer)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/policies/learning_hour_policy/scope_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe LearningHourPolicy::Scope do\n  describe \"#resolve\" do\n    it \"returns all volunteers learning hours when user is a CasaAdmin\" do\n      casa_admin = create(:casa_admin)\n\n      scope = described_class.new(casa_admin, LearningHour)\n\n      expect(scope.resolve).to match_array(LearningHour.all_volunteers_learning_hours(casa_admin.casa_org_id))\n    end\n\n    it \"returns supervisor's volunteers learning hours when user is a Supervisor\" do\n      supervisor = create(:supervisor)\n      create(:supervisor_volunteer, supervisor: supervisor)\n\n      scope = described_class.new(supervisor, LearningHour)\n\n      expect(scope.resolve).to match_array(LearningHour.supervisor_volunteers_learning_hours(supervisor.id))\n    end\n\n    it \"returns volunteer's learning hours when user is a Volunteer\" do\n      volunteer = create(:volunteer)\n\n      scope = described_class.new(volunteer, LearningHour)\n\n      expect(scope.resolve).to match_array(LearningHour.where(user_id: volunteer.id))\n    end\n  end\nend\n"
  },
  {
    "path": "spec/policies/learning_hour_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe LearningHourPolicy do\n  subject { described_class }\n\n  let(:casa_admin) { build_stubbed(:casa_admin) }\n  let(:supervisor) { build_stubbed(:supervisor) }\n  let(:volunteer) { build_stubbed(:volunteer) }\n\n  permissions :index? do\n    it \"allows casa_admins\" do\n      expect(subject).to permit(casa_admin)\n    end\n\n    it \"allows supervisors\" do\n      expect(subject).to permit(supervisor)\n    end\n\n    it \"allows volunteer\" do\n      expect(subject).to permit(volunteer)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/policies/learning_hour_topic_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe LearningHourTopicPolicy, type: :policy do\n  # TODO: Add tests for LearningHourTopicPolicy\n\n  pending \"add some tests for LearningHourTopicPolicy\"\nend\n"
  },
  {
    "path": "spec/policies/learning_hour_type_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe LearningHourTypePolicy, type: :policy do\n  # TODO: Add tests for LearningHourTypePolicy\n\n  pending \"add some tests for LearningHourTypePolicy\"\nend\n"
  },
  {
    "path": "spec/policies/nil_class_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe NilClassPolicy do\n  subject { described_class.new(nil, nil) }\n\n  it \"doesn't permit index action\" do\n    expect(subject).not_to be_index\n  end\n\n  it \"doesn't permit show action\" do\n    expect(subject).not_to be_show\n  end\n\n  it \"doesn't permit create action\" do\n    expect(subject).not_to be_create\n  end\n\n  it \"doesn't permit new action\" do\n    expect(subject).not_to be_new\n  end\n\n  it \"doesn't permit update action\" do\n    expect(subject).not_to be_update\n  end\n\n  it \"doesn't permit edit action\" do\n    expect(subject).not_to be_edit\n  end\n\n  it \"doesn't permit destroy action\" do\n    expect(subject).not_to be_destroy\n  end\nend\n"
  },
  {
    "path": "spec/policies/note_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe NotePolicy, type: :policy do\n  # TODO: Add tests for NotePolicy\n\n  pending \"add some tests for NotePolicy\"\nend\n"
  },
  {
    "path": "spec/policies/notification_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe NotificationPolicy, type: :policy do\n  subject { described_class }\n\n  let(:recipient) { create(:volunteer) }\n  let(:casa_admin) { create(:casa_admin) }\n  let(:volunteer) { build(:volunteer) }\n  let(:supervisor) { build(:supervisor) }\n\n  permissions :index? do\n    it \"allows any volunteer\" do\n      expect(subject).to permit(casa_admin)\n    end\n\n    it \"allows any supervisor\" do\n      expect(subject).to permit(supervisor)\n    end\n\n    it \"allows any admin\" do\n      expect(subject).to permit(volunteer)\n    end\n  end\n\n  permissions :mark_as_read? do\n    let(:notification) { create(:notification, recipient: recipient) }\n\n    it \"allows recipient\" do\n      expect(subject).to permit(recipient, notification)\n    end\n\n    it \"does not allow other volunteer\" do\n      expect(subject).not_to permit(volunteer, notification)\n    end\n\n    it \"does not permit other supervisor\" do\n      expect(subject).not_to permit(supervisor, notification)\n    end\n\n    it \"does not permit other admin\" do\n      expect(subject).not_to permit(casa_admin, notification)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/policies/other_duty_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe OtherDutyPolicy do\n  subject { described_class }\n\n  let(:casa_admin) { build_stubbed(:casa_admin) }\n  let(:supervisor) { build_stubbed(:supervisor) }\n  let(:volunteer) { build_stubbed(:volunteer) }\n\n  permissions :index? do\n    it \"allows casa_admins\" do\n      expect(subject).to permit(casa_admin)\n    end\n\n    it \"allows supervisors\" do\n      expect(subject).to permit(supervisor)\n    end\n\n    it \"allows volunteer\" do\n      expect(subject).to permit(volunteer)\n    end\n\n    context \"when other_duties_enabled is false in casa_org\" do\n      before do\n        casa_admin.casa_org.other_duties_enabled = false\n        supervisor.casa_org.other_duties_enabled = false\n        volunteer.casa_org.other_duties_enabled = false\n      end\n\n      it \"not allows casa_admins\" do\n        expect(subject).not_to permit(casa_admin)\n      end\n\n      it \"not allows supervisors\" do\n        expect(subject).not_to permit(supervisor)\n      end\n\n      it \"not allows volunteer\" do\n        expect(subject).not_to permit(volunteer)\n      end\n    end\n\n    context \"when other_duties_enabled is true in casa_org\" do\n      before do\n        casa_admin.casa_org.other_duties_enabled = true\n        supervisor.casa_org.other_duties_enabled = true\n        volunteer.casa_org.other_duties_enabled = true\n      end\n\n      it \"allows casa_admins\" do\n        expect(subject).to permit(casa_admin)\n      end\n\n      it \"allows supervisors\" do\n        expect(subject).to permit(supervisor)\n      end\n\n      it \"allows volunteer\" do\n        expect(subject).to permit(volunteer)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/policies/patch_note_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe PatchNotePolicy, type: :policy do\n  subject { described_class }\n\n  let(:user) { User.new }\n\n  permissions \".scope\" do\n    pending \"add some examples to (or delete) #{__FILE__}\"\n  end\nend\n"
  },
  {
    "path": "spec/policies/placement_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe PlacementPolicy do\n  subject { described_class }\n\n  let(:casa_org) { create(:casa_org) }\n  let(:diff_org) { create(:casa_org) }\n\n  let(:casa_case) { create(:casa_case, casa_org:) }\n  let(:placement) { create(:placement, casa_case:) }\n\n  let(:casa_admin) { build(:casa_admin, casa_org:) }\n  let(:supervisor) { build(:supervisor, casa_org:) }\n  let(:volunteer) { build(:volunteer, casa_org:) }\n  let(:casa_admin_diff_org) { build(:casa_admin, casa_org: diff_org) }\n  let(:supervisor_diff_org) { build(:supervisor, casa_org: diff_org) }\n  let(:volunteer_diff_org) { build(:volunteer, casa_org: diff_org) }\n\n  permissions :create?, :edit?, :new?, :show?, :update? do\n    it { is_expected.to permit(casa_admin, placement) }\n\n    context \"when a supervisor belongs to the same org as the case\" do\n      it { expect(subject).to permit(supervisor, placement) }\n    end\n\n    context \"when a supervisor does not belong to the same org as the case\" do\n      let(:casa_case) { create(:casa_case, casa_org: diff_org) }\n\n      it { expect(subject).not_to permit(supervisor, placement) }\n    end\n\n    context \"when volunteer is assigned\" do\n      before { create(:case_assignment, volunteer:, casa_case:, active: true) }\n\n      it { is_expected.to permit(volunteer, placement) }\n    end\n\n    context \"when volunteer is not assigned\" do\n      it { is_expected.not_to permit(volunteer, placement) }\n    end\n  end\n\n  permissions :destroy? do\n    it { is_expected.to permit(casa_admin, placement) }\n    it { is_expected.to permit(supervisor, placement) }\n    it { is_expected.not_to permit(volunteer, placement) }\n  end\nend\n"
  },
  {
    "path": "spec/policies/placement_type_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe PlacementTypePolicy, type: :policy do\n  let(:casa_org) { create :casa_org }\n  let(:volunteer) { create :volunteer, casa_org: }\n  let(:supervisor) { create :supervisor, casa_org: }\n  let(:casa_admin) { create :casa_admin, casa_org: }\n  let(:all_casa_admin) { create :all_casa_admin }\n\n  let(:placement_type) { create :placement_type, casa_org: }\n\n  subject { described_class }\n\n  permissions :edit?, :new?, :update?, :create? do\n    it \"does not permit a nil user\" do\n      expect(described_class).not_to permit(nil, placement_type)\n    end\n\n    it \"does not permit a volunteer\" do\n      expect(described_class).not_to permit(volunteer, placement_type)\n    end\n\n    it \"does not permit a supervisor\" do\n      expect(described_class).not_to permit(supervisor, placement_type)\n    end\n\n    it \"permits a casa admin\" do\n      expect(described_class).to permit(casa_admin, placement_type)\n    end\n\n    it \"does not permit a casa admin for a different casa org\" do\n      other_org_casa_admin = create :casa_admin, casa_org: create(:casa_org)\n      expect(described_class).not_to permit(other_org_casa_admin, placement_type)\n    end\n\n    it \"does not permit an all casa admin\" do\n      expect(described_class).not_to permit(all_casa_admin, placement_type)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/policies/reimbursement_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe ReimbursementPolicy do\n  subject { described_class }\n\n  let(:casa_admin) { build_stubbed(:casa_admin) }\n  let(:volunteer) { build_stubbed(:volunteer) }\n  let(:supervisor) { build_stubbed(:supervisor) }\n  let(:organization) { build(:casa_org, users: [volunteer, supervisor, casa_admin]) }\n\n  context \"when org reimbursement is enabled\" do\n    permissions :index?, :change_complete_status? do\n      it { is_expected.to permit(casa_admin) }\n      it { is_expected.to permit(supervisor) }\n      it { is_expected.not_to permit(volunteer) }\n    end\n\n    permissions :datatable? do\n      it { is_expected.to permit(casa_admin) }\n      it { is_expected.to permit(supervisor) }\n      it { is_expected.not_to permit(volunteer) }\n    end\n  end\n\n  context \"when org reimbursement is disabled\" do\n    before do\n      organization.show_driving_reimbursement = false\n    end\n\n    permissions :index?, :change_complete_status? do\n      it { is_expected.not_to permit(casa_admin) }\n    end\n\n    permissions :datatable? do\n      it { is_expected.not_to permit(casa_admin) }\n    end\n  end\n\n  describe \"ReimbursementPolicy::Scope #resolve\" do\n    subject { described_class::Scope.new(user, scope).resolve }\n\n    let(:user) { build_stubbed(:casa_admin, casa_org: casa_org1) }\n    let(:scope) { CaseContact.joins(:casa_case) }\n    let(:casa_org1) { create(:casa_org) }\n    let(:casa_case1) { create(:casa_case, casa_org: casa_org1) }\n    let(:casa_case2) { create(:casa_case, casa_org: create(:casa_org)) }\n\n    let!(:contact1) { create(:case_contact, casa_case: casa_case1) }\n    let!(:contact2) { create(:case_contact, casa_case: casa_case2) }\n\n    it { is_expected.to include(contact1) }\n    it { is_expected.not_to include(contact2) }\n  end\nend\n"
  },
  {
    "path": "spec/policies/supervisor_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe SupervisorPolicy do\n  subject { described_class }\n\n  let(:organization) { create(:casa_org) }\n  let(:different_organization) { create(:casa_org) }\n\n  let!(:casa_admin) { create(:casa_admin, casa_org: organization) }\n  let!(:supervisor) { create(:supervisor, casa_org: organization) }\n  let(:volunteer) { create(:volunteer, casa_org: organization) }\n\n  permissions :update_supervisor_email? do\n    context \"when user is an admin or is the record\" do\n      it \"permits an admin to update supervisor email\" do\n        expect(subject).to permit(casa_admin, supervisor)\n      end\n\n      it \"permits the supervisor to update their own email\" do\n        expect(subject).to permit(supervisor, supervisor)\n      end\n    end\n\n    context \"when user is not an admin or the record\" do\n      let(:second_supervisor) { build_stubbed(:supervisor) }\n\n      it \"does not permit the other supervisor user to update volunteer email\" do\n        expect(subject).not_to permit(supervisor, second_supervisor)\n      end\n\n      it \"does not permit the volunteer user to update volunteer email\" do\n        expect(subject).not_to permit(volunteer, second_supervisor)\n      end\n    end\n  end\n\n  permissions :update? do\n    context \"same organization\" do\n      it \"allows casa_admins\" do\n        expect(subject).to permit(casa_admin, supervisor)\n      end\n    end\n\n    context \"different organization\" do\n      let(:other_admin) { create(:casa_admin, casa_org: different_organization) }\n\n      it \"does not allow casa_admins\" do\n        expect(subject).not_to permit(other_admin, supervisor)\n      end\n    end\n\n    it \"allows supervisors to update themselves\" do\n      expect(subject).to permit(supervisor, supervisor)\n    end\n\n    it \"does not allow supervisors to update other supervisors\" do\n      another_supervisor = build_stubbed(:supervisor)\n\n      expect(subject).not_to permit(supervisor, another_supervisor)\n    end\n  end\n\n  permissions :edit? do\n    context \"same org\" do\n      let(:record) { build_stubbed(:supervisor, casa_org: casa_admin.casa_org) }\n\n      context \"when user is admin\" do\n        it \"can edit a supervisor\" do\n          expect(subject).to permit(casa_admin, record)\n        end\n      end\n\n      context \"when user is supervisor\" do\n        it \"can edit a supervisor\" do\n          expect(subject).to permit(supervisor, record)\n        end\n      end\n    end\n\n    context \"different org\" do\n      let(:record) { build_stubbed(:supervisor, casa_org: different_organization) }\n\n      context \"when user is admin\" do\n        it \"cannot edit a supervisor\" do\n          expect(subject).not_to permit(casa_admin, record)\n        end\n      end\n\n      context \"when user is a supervisor\" do\n        it \"cannot edit a supervisor\" do\n          expect(subject).not_to permit(supervisor, record)\n        end\n      end\n    end\n  end\n\n  permissions :index?, :datatable? do\n    context \"when user is an admin\" do\n      it \"has access to the supervisors index action\" do\n        expect(subject).to permit(casa_admin, Supervisor)\n      end\n    end\n\n    context \"when user is a supervisor\" do\n      it \"has access to the supervisors index action\" do\n        expect(subject).to permit(supervisor, Supervisor)\n      end\n    end\n  end\n\n  permissions :index?, :datatable?, :edit? do\n    context \"when user is a volunteer\" do\n      it \"does not have access to the supervisors index action\" do\n        expect(subject).not_to permit(volunteer, Supervisor)\n      end\n    end\n  end\n\n  permissions :create?, :new? do\n    it \"allows admins to modify supervisors\" do\n      expect(subject).to permit(casa_admin, Supervisor)\n    end\n\n    it \"does not allow supervisors to modify supervisors\" do\n      expect(subject).not_to permit(supervisor, Supervisor)\n    end\n\n    it \"does not allow volunteers to modify supervisors\" do\n      expect(subject).not_to permit(volunteer, Supervisor)\n    end\n  end\n\n  permissions :resend_invitation?, :activate?, :deactivate? do\n    context \"same organization\" do\n      it \"allows admins to modify supervisors\" do\n        expect(subject).to permit(casa_admin, supervisor)\n      end\n    end\n\n    context \"different organization\" do\n      let(:other_admin) { create(:casa_admin, casa_org: different_organization) }\n\n      it \"does not allow admin to modify supervisors\" do\n        expect(subject).not_to permit(other_admin, supervisor)\n      end\n    end\n\n    it \"does not allow supervisors to modify supervisors\" do\n      expect(subject).not_to permit(supervisor, supervisor)\n    end\n\n    it \"does not allow volunteers to modify supervisors\" do\n      expect(subject).not_to permit(volunteer, supervisor)\n    end\n  end\n\n  permissions :change_to_admin? do\n    it \"allows admins to change to admin\" do\n      expect(subject).to permit(casa_admin, supervisor)\n    end\n\n    it \"does not allow supervisors to change to admin\" do\n      expect(subject).not_to permit(supervisor, Supervisor)\n    end\n\n    it \"does not allow volunteers to change to admin\" do\n      expect(subject).not_to permit(volunteer, Supervisor)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/policies/supervisor_volunteer_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe SupervisorVolunteerPolicy do\n  subject { described_class }\n\n  let(:casa_admin) { build_stubbed(:casa_admin) }\n  let(:volunteer) { build_stubbed(:volunteer) }\n  let(:supervisor) { build_stubbed(:supervisor) }\n\n  permissions :create? do\n    it \"allows casa_admins\" do\n      expect(subject).to permit(casa_admin)\n    end\n\n    it \"allows supervisor\" do\n      expect(subject).to permit(supervisor)\n    end\n\n    it \"allows volunteer\" do\n      expect(subject).to permit(volunteer)\n    end\n  end\n\n  permissions :unassign? do\n    it \"allows casa_admins\" do\n      expect(subject).to permit(casa_admin)\n    end\n\n    it \"allows supervisor\" do\n      expect(subject).to permit(supervisor)\n    end\n\n    it \"does not permit volunteer\" do\n      expect(subject).not_to permit(volunteer)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/policies/user_policy/scope_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe UserPolicy::Scope do\n  describe \"#resolve\" do\n    it \"returns all Users when user is admin\" do\n      admin = create(:casa_admin)\n\n      scope = described_class.new(admin, User)\n\n      expect(scope.resolve).to contain_exactly(admin)\n    end\n\n    it \"returns the user when user is volunteer\" do\n      user = create(:volunteer)\n\n      scope = described_class.new(user, User)\n\n      expect(scope.resolve).to eq [user]\n    end\n  end\nend\n"
  },
  {
    "path": "spec/policies/user_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe UserPolicy do\n  subject { described_class }\n\n  let(:org_a) { build_stubbed(:casa_org) }\n  let(:org_b) { build_stubbed(:casa_org) }\n\n  let(:casa_admin_a) { build_stubbed(:casa_admin, casa_org: org_a) }\n  let(:casa_admin_b) { build_stubbed(:casa_admin, casa_org: org_b) }\n  let(:supervisor_a) { build_stubbed(:supervisor, casa_org: org_a) }\n  let(:supervisor_b) { build_stubbed(:supervisor, casa_org: org_b) }\n  let(:volunteer_a) { build_stubbed(:volunteer, casa_org: org_a) }\n  let(:volunteer_b) { build_stubbed(:volunteer, casa_org: org_b) }\n\n  permissions :edit?, :update?, :update_password? do\n    it \"allows casa_admins\" do\n      expect(subject).to permit(casa_admin_a)\n    end\n\n    it \"allows supervisor\" do\n      expect(subject).to permit(supervisor_a)\n    end\n\n    it \"allows volunteer\" do\n      expect(subject).to permit(volunteer_a)\n    end\n  end\n\n  permissions :update_user_setting? do\n    context \"when user is an admin\" do\n      it \"allows update settings of all roles\" do\n        expect(subject).to permit(casa_admin_a)\n        expect(subject).to permit(casa_admin_b)\n      end\n    end\n\n    context \"when user is a supervisor\" do\n      it \"allows supervisors to update another volunteer settings in their casa org\" do\n        expect(subject).to permit(supervisor_a, volunteer_a)\n        expect(subject).to permit(supervisor_b, volunteer_b)\n      end\n\n      it \"does not allow supervisor to update a volunteer in a different casa org\" do\n        expect(subject).not_to permit(supervisor_a, volunteer_b)\n        expect(subject).not_to permit(supervisor_b, volunteer_a)\n      end\n\n      it \"allows supervisors to update their own settings\" do\n        expect(subject).to permit(supervisor_a, supervisor_a)\n        expect(subject).to permit(supervisor_b, supervisor_b)\n      end\n\n      it \"does not allow supervisor to update another supervisor settings\" do\n        expect(subject).not_to permit(supervisor_a, supervisor_b)\n        expect(subject).not_to permit(supervisor_b, supervisor_a)\n      end\n    end\n  end\n\n  permissions :add_language? do\n    context \"when user is a volunteer\" do\n      it \"allows volunteer to add a language to themselves\" do\n        expect(subject).to permit(volunteer_a, volunteer_a)\n        expect(subject).to permit(volunteer_b, volunteer_b)\n      end\n\n      it \"does not allow another volunteer to add a language to another volunteer\" do\n        expect(subject).not_to permit(volunteer_a, volunteer_b)\n        expect(subject).not_to permit(volunteer_b, volunteer_a)\n      end\n    end\n\n    context \"when user is a supervisor\" do\n      it \"allows supervisors to add a language to a volunteer in their organizations\" do\n        expect(subject).to permit(supervisor_a, volunteer_a)\n        expect(subject).to permit(supervisor_b, volunteer_b)\n      end\n\n      it \"does not allow a supervisor to add a language to a volunteer in a different organization\" do\n        expect(subject).not_to permit(supervisor_a, volunteer_b)\n        expect(subject).not_to permit(supervisor_b, volunteer_a)\n      end\n    end\n\n    context \"when user is an admin\" do\n      it \"allows admins to add a language to a volunteer in their organizations\" do\n        expect(subject).to permit(casa_admin_a, volunteer_a)\n        expect(subject).to permit(casa_admin_b, volunteer_b)\n      end\n\n      it \"does not allow an admin to add a language to a volunteer in a different organization\" do\n        expect(subject).not_to permit(casa_admin_a, volunteer_b)\n        expect(subject).not_to permit(casa_admin_b, volunteer_a)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/policies/volunteer_policy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe VolunteerPolicy do\n  subject { described_class }\n\n  let(:casa_org) { build_stubbed(:casa_org) }\n  let(:other_org) { build_stubbed(:casa_org) }\n  let(:admin) { build_stubbed(:casa_admin, casa_org: casa_org) }\n  let(:supervisor) { build_stubbed(:supervisor, casa_org: casa_org) }\n  let(:volunteer) { build_stubbed(:volunteer, casa_org: casa_org) }\n\n  permissions :edit? do\n    context \"same org\" do\n      let(:record) { build_stubbed(:volunteer, casa_org: casa_org) }\n\n      context \"when user is a casa admin\" do\n        it \"allows for same org\" do\n          expect(subject).to permit(admin, record)\n        end\n      end\n\n      context \"when user is a supervisor\" do\n        it \"allows\" do\n          expect(subject).to permit(supervisor, record)\n        end\n      end\n\n      context \"when user is a volunteer\" do\n        it \"does not permit\" do\n          expect(subject).not_to permit(volunteer)\n        end\n      end\n    end\n\n    context \"different org\" do\n      let(:record) { build_stubbed(:volunteer, casa_org: other_org) }\n\n      context \"when user is a casa admin\" do\n        it \"does not allow for different org\" do\n          expect(subject).not_to permit(admin, record)\n        end\n      end\n\n      context \"when user is a supervisor\" do\n        it \"does not allow\" do\n          expect(subject).not_to permit(supervisor, record)\n        end\n      end\n\n      context \"when user is a volunteer\" do\n        it \"does not permit\" do\n          expect(subject).not_to permit(volunteer)\n        end\n      end\n    end\n  end\n\n  permissions :index?, :activate?, :create?, :datatable?, :deactivate?, :new?, :show?, :update? do\n    context \"when user is a casa admin\" do\n      let(:record) { build_stubbed(:volunteer, casa_org: casa_org) }\n\n      it \"allows\" do\n        expect(subject).to permit(admin, record)\n      end\n    end\n\n    context \"when user is a supervisor\" do\n      let(:record) { build_stubbed(:volunteer, casa_org: casa_org) }\n\n      it \"allows\" do\n        expect(subject).to permit(supervisor, record)\n      end\n    end\n\n    context \"when user is a volunteer\" do\n      it \"does not permit\" do\n        expect(subject).not_to permit(volunteer)\n      end\n    end\n  end\n\n  permissions :update_volunteer_email? do\n    context \"when user is a casa admin\" do\n      it \"allows\" do\n        expect(subject).to permit(admin)\n      end\n    end\n\n    context \"when user is a supervisor\" do\n      it \"does not permit\" do\n        expect(subject).to permit(supervisor)\n      end\n    end\n\n    context \"when user is a volunteer\" do\n      it \"does not permit\" do\n        expect(subject).not_to permit(volunteer)\n      end\n    end\n  end\n\n  describe \"VolunteerPolicy::Scope\" do\n    describe \"#resolve\" do\n      subject { described_class::Scope.new(user, Volunteer.all).resolve }\n\n      let!(:volunteer1) { create(:volunteer, casa_org: casa_org) }\n      let!(:another_org_volunteer) { create(:volunteer, casa_org: another_casa_org) }\n\n      let(:casa_org) { create(:casa_org) }\n      let(:another_casa_org) { create(:casa_org) }\n\n      context \"when admin\" do\n        let(:user) { build_stubbed(:casa_admin, casa_org: casa_org) }\n\n        it { is_expected.to include(volunteer1) }\n        it { is_expected.not_to include(another_org_volunteer) }\n      end\n\n      context \"when supervisor\" do\n        let(:user) { build_stubbed(:supervisor, casa_org: casa_org) }\n\n        it { is_expected.to include(volunteer1) }\n        it { is_expected.not_to include(another_org_volunteer) }\n      end\n\n      context \"when volunteer\" do\n        let(:user) { build_stubbed(:volunteer, casa_org: casa_org) }\n\n        it { is_expected.to include(volunteer1) }\n        it { is_expected.not_to include(another_org_volunteer) }\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/presenters/base_presenter_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe BasePresenter do\n  # TODO: Add tests for BasePresenter\n\n  pending \"add some tests for BasePresenter\"\nend\n"
  },
  {
    "path": "spec/presenters/case_contact_presenter_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CaseContactPresenter do\n  let(:organization) { build(:casa_org) }\n  let(:user) { create(:casa_admin, casa_org: organization) }\n  let(:case_contacts) { create_list(:case_contact, 5, casa_case: casa_case) }\n  let(:presenter) { described_class.new(case_contacts) }\n\n  before do\n    allow_any_instance_of(described_class).to receive(:current_user).and_return(user)\n    allow_any_instance_of(described_class).to receive(:current_organization).and_return(organization)\n  end\n\n  describe \"#display_case_number\" do\n    context \"with transition aged youth\" do\n      let(:casa_case) { create(:casa_case, birth_month_year_youth: 15.years.ago, casa_org: organization) }\n\n      it \"displays the case number with correct icon\" do\n        casa_case_id = casa_case.id\n        case_number = casa_case.case_number\n\n        expect(presenter.display_case_number(casa_case_id)).to eql(\"🦋 #{case_number}\")\n      end\n\n      it \"does not error when case number is nil\" do\n        expect(presenter.display_case_number(nil)).to eql(\"\")\n      end\n    end\n\n    context \"with non-transition aged youth\" do\n      let(:casa_case) { create(:casa_case, birth_month_year_youth: 12.years.ago, casa_org: organization) }\n\n      it \"displays the case number with correct icon\" do\n        casa_case_id = casa_case.id\n        case_number = casa_case.case_number\n\n        expect(presenter.display_case_number(casa_case_id)).to eql(\"🐛 #{case_number}\")\n      end\n\n      it \"does not error when case number is nil\" do\n        expect(presenter.display_case_number(nil)).to eql(\"\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/rails_helper.rb",
    "content": "# This file is copied to spec/ when you run 'rails generate rspec:install'\nrequire \"spec_helper\"\nENV[\"RAILS_ENV\"] ||= \"test\"\nrequire_relative \"../config/environment\"\n# Prevent database truncation if the environment is production\nabort(\"The Rails environment is running in production mode!\") if Rails.env.production?\n# Uncomment the line below in case you have `--require rails_helper` in the `.rspec` file\n# that will avoid rails generators crashing because migrations haven't been run yet\n# return unless Rails.env.test?\n\nrequire \"rspec/rails\"\n# Add additional requires below this line. Rails is not loaded until this point!\nrequire \"pry\"\nrequire \"email_spec\"\nrequire \"email_spec/rspec\"\nrequire \"pundit/rspec\"\nrequire \"view_component/test_helpers\"\nrequire \"capybara/rspec\"\nrequire \"action_text/system_test_helper\"\nrequire \"webmock/rspec\"\n\n# Requires supporting ruby files with custom matchers and macros, etc, in\n# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are\n# run as spec files by default. This means that files in spec/support that end\n# in _spec.rb will both be required and run as specs, causing the specs to be\n# run twice. It is recommended that you do not name files matching this glob to\n# end with _spec.rb. You can configure this pattern with the --pattern\n# option on the command line or in ~/.rspec, .rspec or `.rspec-local`.\n#\n# The following line is provided for convenience purposes. It has the downside\n# of increasing the boot-up time by auto-requiring all files in the support\n# directory. Alternatively, in the individual `*_spec.rb` files, manually\n# require only the support files necessary.\n#\nRails.root.glob(\"spec/support/**/*.rb\").sort_by(&:to_s).each { |f| require f }\n\n# Checks for pending migrations and applies them before tests are run.\n# If you are not using ActiveRecord, you can remove these lines.\nbegin\n  ActiveRecord::Migration.maintain_test_schema!\nrescue ActiveRecord::PendingMigrationError => e\n  abort e.to_s.strip\nend\n\nci_environment = (ENV[\"GITHUB_ACTIONS\"] || ENV[\"CI\"]).present?\n\nRSpec.configure do |config|\n  config.include ActiveSupport::Testing::TimeHelpers\n  config.include DatatableHelper, type: :datatable\n  config.include Devise::Test::ControllerHelpers, type: :controller\n  config.include Devise::Test::ControllerHelpers, type: :view\n  config.include Devise::Test::IntegrationHelpers, type: :request\n  config.include Devise::Test::IntegrationHelpers, type: :system\n  config.include Organizational, type: :helper\n  config.include Organizational, type: :view\n  config.include PunditHelper, type: :view\n  config.include SessionHelper, type: :view\n  config.include SessionHelper, type: :request\n  config.include ViewComponent::TestHelpers, type: :component\n  config.include ViewComponent::SystemTestHelpers, type: :component\n  config.include Capybara::RSpecMatchers, type: :component\n  config.include TwilioHelper, type: :request\n  config.include TwilioHelper, type: :system\n  config.include Support::RequestHelpers, type: :request\n  config.include CaseCourtReportHelpers, type: :system\n\n  # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures\n  config.fixture_paths = [\n    Rails.root.join(\"spec/fixtures\")\n  ]\n\n  # If you're not using ActiveRecord, or you'd prefer not to run each of your\n  # examples within a transaction, remove the following line or assign false\n  # instead of true.\n  config.use_transactional_fixtures = true\n\n  # You can uncomment this line to turn off ActiveRecord support entirely.\n  # config.use_active_record = false\n\n  # RSpec Rails uses metadata to mix in different behaviours to your tests,\n  # for example enabling you to call `get` and `post` in request specs. e.g.:\n  #\n  #     RSpec.describe UsersController, type: :request do\n  #       # ...\n  #     end\n  #\n  # The different available types are documented in the features, such as in\n  # https://rspec.info/features/7-0/rspec-rails\n  #\n  # You can also this infer these behaviours automatically by location, e.g.\n  # /spec/models would pull in the same behaviour as `type: :model` but this\n  # behaviour is considered legacy and will be removed in a future version.\n  #\n  # To enable this behaviour uncomment the line below.\n  # config.infer_spec_type_from_file_location!\n  # Auto detect datatable type specs\n  config.define_derived_metadata(file_path: Regexp.new(\"/spec/datatables/\")) do |metadata|\n    metadata[:type] = :datatable\n  end\n  # Aggregate failures by default, except slow/sequence-dependant examples (as in system specs)\n  config.define_derived_metadata do |metadata|\n    non_aggregate_types = %i[system]\n    metadata[:aggregate_failures] = true unless non_aggregate_types.include?(metadata[:type])\n  end\n\n  # Filter lines from Rails gems in backtraces.\n  # Use `rspec --backtrace` option to see full backtraces.\n  config.filter_rails_from_backtrace!\n  # arbitrary gems may also be filtered via:\n  config.filter_gems_from_backtrace(*%w[\n    bootsnap capybara factory_bot puma rack railties shoulda-matchers\n    sprockets-rails pundit\n  ])\n\n  config.around do |example|\n    # If timeout is not set it will run without a timeout\n    Timeout.timeout(ENV[\"TEST_MAX_DURATION\"].to_i) do\n      example.run\n    end\n  rescue Timeout::Error\n    raise StandardError.new \"\\\"#{example.full_description}\\\" in #{example.location} timed out.\"\n  end\n\n  # NOTE: not applicable currently, left to show how to skip bullet errrors\n  # config.around :each, :disable_bullet do |example|\n  #   Bullet.raise = false\n  #   example.run\n  #   Bullet.raise = true\n  # end\n\n  config.around do |example|\n    Capybara.server_port = 7654 + ENV[\"TEST_ENV_NUMBER\"].to_i\n    example.run\n  end\n\n  config.filter_run_excluding :ci_only unless ci_environment\nend\n\nRSpec::Matchers.define_negated_matcher :not_change, :change\n\nShoulda::Matchers.configure do |shoulda_config|\n  shoulda_config.integrate do |with|\n    with.test_framework :rspec\n    with.library :rails\n  end\nend\n\nWebMock.disable_net_connect!(\n  allow_localhost: true,\n  allow: \"selenium_chrome:4444\"\n)\n"
  },
  {
    "path": "spec/requests/additional_expenses_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"/additional_expenses\", type: :request do\n  let(:casa_org) { create :casa_org }\n  let(:volunteer) { create :volunteer, :with_single_case, casa_org: }\n  let(:casa_case) { volunteer.casa_cases.first }\n  let(:case_contact) { create :case_contact, casa_case:, creator: volunteer }\n\n  let(:valid_attributes) do\n    attributes_for(:additional_expense)\n      .merge({case_contact_id: case_contact.id})\n  end\n  let(:invalid_attributes) { valid_attributes.merge({other_expenses_describe: nil, other_expense_amount: 1}) }\n\n  before { sign_in volunteer }\n\n  describe \"POST /create\" do\n    subject { post additional_expenses_path, params:, as: :json }\n\n    let(:params) { {additional_expense: valid_attributes} }\n\n    it \"creates a record and responds created\" do\n      expect { subject }.to change(AdditionalExpense, :count).by(1)\n      expect(response).to have_http_status(:created)\n    end\n\n    it \"returns the new contact topic answer as json\" do\n      subject\n      expect(response.content_type).to match(a_string_including(\"application/json\"))\n      answer = AdditionalExpense.last\n      expect(response_json[:id]).to eq answer.id\n      expect(response_json.keys)\n        .to include(:id, :case_contact_id, :other_expense_amount, :other_expenses_describe)\n    end\n\n    context \"with invalid parameters\" do\n      let(:params) { {additional_expense: invalid_attributes} }\n\n      it \"fails and responds unprocessable_content\" do\n        expect { subject }.not_to change(ContactTopicAnswer, :count)\n        expect(response).to have_http_status(:unprocessable_content)\n      end\n\n      it \"returns errors as json\" do\n        subject\n        expect(response.content_type).to match(a_string_including(\"application/json\"))\n        expect(response.body).to be_present\n        expect(response_json[:other_expenses_describe]).to include(\"can't be blank\")\n      end\n    end\n\n    context \"html request\" do\n      subject { post additional_expenses_path, params: }\n\n      it \"redirects to referrer/root without creating an additional expense\" do\n        expect { subject }.to not_change(AdditionalExpense, :count)\n        expect(response).to redirect_to(root_url)\n      end\n    end\n  end\n\n  describe \"DELETE /destroy\" do\n    subject { delete additional_expense_url(additional_expense), as: :json }\n\n    let!(:additional_expense) { create :additional_expense, case_contact: }\n\n    it \"destroys the record and responds no content\" do\n      expect { subject }\n        .to change(AdditionalExpense, :count).by(-1)\n      expect(response).to have_http_status(:no_content)\n      expect(response.body).to be_empty\n    end\n\n    context \"html request\" do\n      subject { delete additional_expense_url(additional_expense) }\n\n      it \"redirects to referrer/root without destroying the additional expense\" do\n        expect { subject }.to not_change(AdditionalExpense, :count)\n        expect(response).to redirect_to(root_url)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/all_casa_admins/casa_admins_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"All-Casa Admin\", type: :request do\n  let(:all_casa_admin) { build(:all_casa_admin) }\n  let(:casa_admin) { create(:casa_admin, email: \"admin1@example.com\", display_name: \"Example Admin\") }\n  let(:casa_org) { create(:casa_org) }\n\n  before {\n    sign_in all_casa_admin\n    expect_any_instance_of(AllCasaAdmins::CasaAdminsController).to receive(:authenticate_all_casa_admin!).and_call_original\n  }\n\n  describe \"GET /new\" do\n    it \"allows access to the new admin page\" do\n      get new_all_casa_admins_casa_org_casa_admin_path(casa_org)\n      expect(response).to be_successful\n    end\n  end\n\n  describe \"POST /create\" do\n    subject { post all_casa_admins_casa_org_casa_admins_path(casa_org), params: }\n\n    context \"with valid parameters\" do\n      let(:params) { {casa_admin: {email: \"admin1@example.com\", display_name: \"Example Admin\"}} }\n\n      it \"creates a new CASA admin for the organization\" do\n        expect { subject }.to change(CasaAdmin, :count).by(1)\n      end\n\n      it { is_expected.to redirect_to all_casa_admins_casa_org_path(casa_org) }\n\n      it \"shows correct flash message\" do\n        subject\n        expect(flash[:notice]).to include(\"New admin created successfully\")\n      end\n    end\n\n    context \"with invalid parameters\" do\n      let(:params) { {casa_admin: {email: \"\", display_name: \"\"}} }\n\n      it \"renders new page\" do\n        expect { subject }.not_to change(CasaAdmin, :count)\n\n        expect(response).to have_http_status(:unprocessable_content)\n        expect(response).to render_template \"casa_admins/new\"\n      end\n    end\n  end\n\n  describe \"GET /edit\" do\n    subject { get edit_all_casa_admins_casa_org_casa_admin_path(casa_org, casa_admin) }\n\n    it \"allows access to the edit admin page\" do\n      subject\n      expect(response).to be_successful\n    end\n\n    it \"shows correct admin\" do\n      subject\n      expect(response.body).to include(casa_admin.email)\n    end\n  end\n\n  describe \"PATCH /update\" do\n    subject { patch all_casa_admins_casa_org_casa_admin_path(casa_org, casa_admin), params: }\n\n    context \"with valid parameters\" do\n      let(:params) { {all_casa_admin: {email: \"casa_admin@example.com\"}} }\n\n      it \"allows current user to begin to update other casa admin's email and send a confirmation email\" do\n        subject\n        casa_admin.reload\n        expect(casa_admin.unconfirmed_email).to eq(\"casa_admin@example.com\")\n        expect(ActionMailer::Base.deliveries.count).to eq(1)\n        expect(ActionMailer::Base.deliveries.first).to be_a(Mail::Message)\n        expect(ActionMailer::Base.deliveries.first.body.encoded)\n          .to match(\"Click here to confirm your email\")\n      end\n\n      it { is_expected.to redirect_to edit_all_casa_admins_casa_org_casa_admin_path(casa_org, casa_admin) }\n\n      it \"shows correct flash message\" do\n        subject\n        expect(flash[:notice]).to eq(\"Casa Admin was successfully updated. Confirmation Email Sent.\")\n      end\n    end\n\n    context \"with invalid parameters\" do\n      let(:params) { {all_casa_admin: {email: \"\"}} }\n\n      it \"does not allow current user to successfully update other casa admin's email\" do\n        expect { subject }.not_to change { casa_admin.reload.email }\n      end\n\n      it \"renders new page\" do\n        subject\n        expect(response).to have_http_status(:unprocessable_content)\n        expect(response).to render_template \"casa_admins/edit\"\n      end\n    end\n  end\n\n  describe \"PATCH /activate\" do\n    subject { patch activate_all_casa_admins_casa_org_casa_admin_path(casa_org, casa_admin) }\n\n    let(:casa_admin) { create(:casa_admin, :inactive) }\n\n    it \"successfullies activate another casa admin's profile\" do\n      expect { subject }.to change { casa_admin.reload.active }.from(false).to(true)\n    end\n\n    it \"calls for CasaAdminMailer\" do\n      expect(CasaAdminMailer).to(\n        receive(:account_setup).with(casa_admin).once.and_return(double(\"mailer\", deliver: true))\n      )\n      subject\n    end\n\n    it { is_expected.to redirect_to edit_all_casa_admins_casa_org_casa_admin_path(casa_org, casa_admin) }\n\n    it \"shows correct flash message\" do\n      subject\n      expect(flash[:notice]).to include(\"Admin was activated. They have been sent an email.\")\n    end\n\n    context \"when activation fails\" do\n      before { allow_any_instance_of(CasaAdmin).to receive(:activate).and_return(false) }\n\n      it \"does not activate the casa admin's profile\" do\n        expect { subject }.not_to change { casa_admin.reload.active }\n      end\n\n      it \"renders edit page\" do\n        subject\n        expect(response).to have_http_status(:unprocessable_content)\n        expect(response).to render_template \"casa_admins/edit\"\n      end\n    end\n  end\n\n  describe \"PATCH /deactivate\" do\n    subject { patch deactivate_all_casa_admins_casa_org_casa_admin_path(casa_org, casa_admin) }\n\n    let(:casa_admin) { create(:casa_admin, active: true) }\n\n    it \"successfullies deactivate another casa admin's profile\" do\n      expect { subject }.to change { casa_admin.reload.active }.from(true).to(false)\n    end\n\n    it \"calls for CasaAdminMailer\" do\n      expect(CasaAdminMailer).to(\n        receive(:deactivation).with(casa_admin).once.and_return(double(\"mailer\", deliver: true))\n      )\n      subject\n    end\n\n    it { is_expected.to redirect_to edit_all_casa_admins_casa_org_casa_admin_path(casa_org, casa_admin) }\n\n    it \"shows correct flash message\" do\n      subject\n      expect(flash[:notice]).to include(\"Admin was deactivated.\")\n    end\n\n    context \"when deactivation fails\" do\n      before { allow_any_instance_of(CasaAdmin).to receive(:deactivate).and_return(false) }\n\n      it \"does not deactivate the casa admin's profile\" do\n        expect { subject }.not_to change { casa_admin.reload.active }\n      end\n\n      it \"renders edit page\" do\n        subject\n        expect(response).to have_http_status(:unprocessable_content)\n        expect(response).to render_template \"casa_admins/edit\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/all_casa_admins/casa_orgs_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"AllCasaAdmin::CasaOrgs\", type: :request do\n  let(:all_casa_admin) { create(:all_casa_admin) }\n\n  before { sign_in all_casa_admin }\n\n  describe \"GET /show\" do\n    subject(:request) do\n      get all_casa_admins_casa_org_path(casa_org)\n\n      response\n    end\n\n    let!(:casa_org) { create(:casa_org) }\n    let!(:other_casa_org) { create(:casa_org) }\n\n    it { is_expected.to be_successful }\n\n    it \"shows casa org correctly\", :aggregate_failures do\n      page = request.body\n      expect(page).to include(casa_org.name)\n      expect(page).not_to include(other_casa_org.name)\n    end\n\n    it \"load metrics correctly\" do\n      expect(AllCasaAdmins::CasaOrgMetrics).to receive(:new).with(casa_org).and_call_original\n      request\n    end\n  end\n\n  describe \"GET /new\" do\n    subject(:get_new) { get new_all_casa_admins_casa_org_path }\n\n    it \"returns http success\" do\n      get_new\n\n      expect(response).to have_http_status(:success)\n    end\n  end\n\n  describe \"POST /create\" do\n    subject(:post_create) { post all_casa_admins_casa_orgs_path, params: params }\n\n    context \"when successfully\" do\n      let(:params) do\n        {casa_org: {name: \"New Org\", display_name: \"New org display\",\n                    address: \"29207 Weimann Canyon, New Andrew, PA 40510-7416\"}}\n      end\n      let(:contact_topics) { [{\"question\" => \"Title 1\", \"details\" => \"details 1\"}, {\"question\" => \"Title 2\", \"details\" => \"details 2\"}] }\n\n      before do\n        allow(ContactTopic).to receive(:default_contact_topics).and_return(contact_topics)\n      end\n\n      it \"creates a new CASA org\" do\n        expect { post_create }.to change(CasaOrg, :count).by(1)\n      end\n\n      it \"generates correct defaults during creation\" do\n        expect { post_create }.to change(ContactTopic, :count).by(2)\n\n        casa_org = CasaOrg.last\n        expect(casa_org.contact_topics.map(&:question)).to match_array(contact_topics.pluck(\"question\"))\n        expect(casa_org.contact_topics.map(&:details)).to match_array(contact_topics.pluck(\"details\"))\n        expect(casa_org.contact_topics.pluck(:active)).to be_all true\n      end\n\n      it \"redirects to CASA org show page, with notice flash\", :aggregate_failures do\n        post_create\n\n        expect(response).to redirect_to all_casa_admins_casa_org_path(assigns(:casa_org))\n        expect(flash[:notice]).to eq \"CASA Organization was successfully created.\"\n      end\n\n      it \"also responds as json\", :aggregate_failures do\n        post all_casa_admins_casa_orgs_path(format: :json), params: params\n\n        expect(response.content_type).to eq \"application/json; charset=utf-8\"\n        expect(response).to have_http_status :created\n        expect(response.body).to match \"29207 Weimann Canyon, New Andrew, PA 40510-7416\"\n      end\n    end\n\n    context \"when failure\" do\n      let(:params) do\n        {casa_org: {name: nil, display_name: nil,\n                    address: \"29207 Weimann Canyon, New Andrew, PA 40510-7416\"}}\n      end\n\n      it \"does not create a new CASA org\" do\n        expect { post_create }.not_to change(CasaOrg, :count)\n      end\n\n      it \"renders new template\" do\n        post_create\n\n        expect(response).to render_template :new\n      end\n\n      it \"also responds as json\", :aggregate_failures do\n        post all_casa_admins_casa_orgs_path(format: :json), params: params\n\n        expect(response.content_type).to eq \"application/json; charset=utf-8\"\n        expect(response).to have_http_status :unprocessable_content\n        expect(response.body).to match \"Name can't be blank\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/all_casa_admins/dashboard_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"AllCasaAdmin::Dashboard\", type: :request do\n  let(:all_casa_admin) { create(:all_casa_admin) }\n\n  before { sign_in all_casa_admin }\n\n  describe \"GET /show\" do\n    subject(:request) do\n      get authenticated_all_casa_admin_root_path\n\n      response\n    end\n\n    let!(:casa_orgs) { create_list(:casa_org, 3) }\n\n    it { is_expected.to have_http_status(:success) }\n\n    it \"shows casa orgs\" do\n      # Code changes to fix response as earlier HTML String instead of Nokogiri::HTML5::Document object as received in Rails 7.1.0 to pass expectation\n      page = request.parsed_body.to_html\n      expect(page).to include(*casa_orgs.map(&:name))\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/all_casa_admins/patch_notes_spec.rb",
    "content": "require \"rails_helper\"\n\n# This spec was generated by rspec-rails when you ran the scaffold generator.\n# It demonstrates how one might use RSpec to test the controller code that\n# was generated by Rails when you ran the scaffold generator.\n#\n# It assumes that the implementation code is generated by the rails scaffold\n# generator. If you are using any extension libraries to generate different\n# controller code, this generated spec may or may not pass.\n#\n# It only uses APIs available in rails and/or rspec-rails. There are a number\n# of tools you can use to make these specs even more expressive, but we're\n# sticking to rails and rspec-rails APIs to keep things simple and stable.\n\nRSpec.describe \"/all_casa_admins/patch_notes\", type: :request do\n  # This should return the minimal set of attributes required to create a valid\n  # PatchNote. As you add validations to PatchNote, be sure to\n  # adjust the attributes here as well.\n  let(:all_casa_admin) { build(:all_casa_admin) }\n  let(:patch_note_group) { create(:patch_note_group) }\n  let(:patch_note_type) { create(:patch_note_type) }\n\n  let(:valid_attributes) do\n    {\n      note: \"not an empty note\",\n      patch_note_group_id: patch_note_group.id,\n      patch_note_type_id: patch_note_type.id\n    }\n  end\n\n  let(:invalid_attributes) do\n    {\n      note: \"\",\n      patch_note_group_id: patch_note_group.id + 1,\n      patch_note_type_id: patch_note_type.id + 1\n    }\n  end\n\n  before { sign_in all_casa_admin }\n\n  describe \"GET /index\" do\n    it \"renders a successful response\" do\n      PatchNote.create! valid_attributes\n      get all_casa_admins_patch_notes_path\n      expect(response).to be_successful\n    end\n  end\n\n  describe \"POST /create\" do\n    context \"with valid parameters\" do\n      it \"creates a new PatchNote\" do\n        expect {\n          post all_casa_admins_patch_notes_path, params: valid_attributes\n        }.to change(PatchNote, :count).by(1)\n      end\n\n      it \"shows json indicating the patch note was created\" do\n        post all_casa_admins_patch_notes_path, params: valid_attributes\n        expect(response.header[\"Content-Type\"]).to match(/application\\/json/)\n        expect(response.body).not_to be_nil\n        expect(response).to have_http_status(:created)\n      end\n    end\n\n    context \"with invalid parameters\" do\n      it \"does not create a new PatchNote\" do\n        expect {\n          post all_casa_admins_patch_notes_path, params: invalid_attributes\n        }.not_to change(PatchNote, :count)\n      end\n\n      it \"shows json indicating the patch note could not be created\" do\n        post all_casa_admins_patch_notes_path, params: invalid_attributes\n        expect(response.header[\"Content-Type\"]).to match(/application\\/json/)\n        expect(response.body).not_to be_nil\n        expect(response).to have_http_status(:unprocessable_content)\n        expect(JSON.parse(response.body)).to have_key(\"errors\")\n      end\n\n      it \"shows json with the id of the patch note created\" do\n        post all_casa_admins_patch_notes_path, params: valid_attributes\n        expect(response.header[\"Content-Type\"]).to match(/application\\/json/)\n        expect(response.body).not_to be_nil\n        expect(response).to have_http_status(:created)\n        expect(JSON.parse(response.body)).to have_key(\"id\")\n      end\n    end\n  end\n\n  describe \"PATCH /update\" do\n    context \"with valid parameters\" do\n      let(:patch_note_group_2) { create(:patch_note_group) }\n      let(:patch_note_type_2) { create(:patch_note_type) }\n\n      let(:new_attributes) do\n        {\n          note: \"a different update note\",\n          patch_note_group_id: patch_note_group_2.id,\n          patch_note_type_id: patch_note_type_2.id\n        }\n      end\n\n      it \"updates the requested patch_note\" do\n        patch_note = PatchNote.create! valid_attributes\n        patch all_casa_admins_patch_note_path(patch_note), params: new_attributes\n        patch_note.reload\n        expect(patch_note.note).to eq(new_attributes[:note])\n        expect(patch_note.patch_note_group_id).to eq(patch_note_group_2.id)\n        expect(patch_note.patch_note_type_id).to eq(patch_note_type_2.id)\n      end\n\n      it \"renders a successful response (a json with an ok status)\" do\n        patch_note = PatchNote.create! valid_attributes\n        patch all_casa_admins_patch_note_path(patch_note), params: new_attributes\n        expect(response.header[\"Content-Type\"]).to match(/application\\/json/)\n        expect(response.body).not_to be_nil\n        expect(response).to have_http_status(:ok)\n      end\n    end\n\n    context \"with invalid parameters\" do\n      it \"renders a successful response (a json with a list of errors)\" do\n        patch_note = PatchNote.create! valid_attributes\n        patch all_casa_admins_patch_note_path(patch_note), params: invalid_attributes\n        expect(response.body).not_to be_nil\n        expect(response).to have_http_status(:unprocessable_content)\n        expect(JSON.parse(response.body)).to have_key(\"errors\")\n      end\n    end\n  end\n\n  describe \"DELETE /destroy\" do\n    it \"destroys the requested patch_note\" do\n      patch_note = PatchNote.create! valid_attributes\n      expect {\n        delete all_casa_admins_patch_note_path(patch_note)\n      }.to change(PatchNote, :count).by(-1)\n    end\n\n    it \"renders a successful response (a json with an ok status)\" do\n      patch_note = PatchNote.create! valid_attributes\n      delete all_casa_admins_patch_note_path(patch_note)\n      expect(response.header[\"Content-Type\"]).to match(/application\\/json/)\n      expect(response.body).not_to be_nil\n      expect(response).to have_http_status(:ok)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/all_casa_admins/sessions_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"AllCasaAdmin::SessionsController\", type: :request do\n  let(:all_casa_admin) { create(:all_casa_admin) }\n\n  describe \"GET /new\" do\n    subject(:request) do\n      get new_all_casa_admin_session_path\n\n      response\n    end\n\n    it { is_expected.to be_successful }\n  end\n\n  describe \"POST /create\" do\n    subject(:request) do\n      post all_casa_admin_session_path\n\n      response\n    end\n\n    let(:params) { {email: all_casa_admin.email, password: all_casa_admin.password} }\n\n    it { is_expected.to be_successful }\n  end\n\n  describe \"GET /destroy\" do\n    subject(:request) do\n      get destroy_all_casa_admin_session_path\n\n      response\n    end\n\n    it { is_expected.to have_http_status(:redirect) }\n  end\nend\n"
  },
  {
    "path": "spec/requests/all_casa_admins_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"/all_casa_admins\", type: :request do\n  let(:admin) { create(:all_casa_admin) }\n\n  before { sign_in admin }\n\n  describe \"GET /new\" do\n    it \"authenticates the user\" do\n      sign_out admin\n      get new_all_casa_admin_path\n\n      expect(response).to have_http_status(:redirect)\n    end\n\n    it \"only allows all_casa_admin users\" do\n      sign_out admin\n      casa_admin = create(:casa_admin)\n      sign_in casa_admin\n      get new_all_casa_admin_path\n\n      expect(response).to have_http_status(:redirect)\n    end\n\n    context \"with a all_casa_admin signed in\" do\n      it \"renders a successful response\" do\n        get new_all_casa_admin_path\n\n        expect(response).to be_successful\n      end\n    end\n\n    it \"authenticates the user\" do\n      sign_out :admin\n      get new_all_casa_admin_path\n\n      expect(response).to be_successful\n    end\n\n    it \"only allows all_casa_admin users\" do\n      sign_out :admin\n      casa_admin = create(:casa_admin)\n      sign_in casa_admin\n      get new_all_casa_admin_path\n\n      expect(response).to be_successful\n    end\n  end\n\n  describe \"GET /edit\" do\n    context \"with a all_casa_admin signed in\" do\n      it \"renders a successful response\" do\n        get edit_all_casa_admins_path\n\n        expect(response).to be_successful\n      end\n    end\n  end\n\n  describe \"POST /create\" do\n    context \"with valid parameters\" do\n      it \"creates a new All CASA admin\" do\n        expect do\n          post all_casa_admins_path, params: {\n            all_casa_admin: {\n              email: \"admin1@example.com\"\n            }\n          }\n        end.to change(AllCasaAdmin, :count).by(1)\n        expect(response).to have_http_status(:redirect)\n        expect(flash[:notice]).to eq(\"New All CASA admin created successfully\")\n      end\n\n      it \"also responds as json\", :aggregate_failures do\n        post all_casa_admins_path(format: :json), params: {\n          all_casa_admin: {\n            email: \"admin1@example.com\"\n          }\n        }\n\n        expect(response).to have_http_status(:created)\n        expect(response.content_type).to eq(\"application/json; charset=utf-8\")\n        expect(response.body).to match(\"admin1@example.com\".to_json)\n      end\n    end\n\n    context \"with invalid parameters\" do\n      it \"renders new page\" do\n        post all_casa_admins_path, params: {\n          all_casa_admin: {\n            email: \"\"\n          }\n        }\n        expect(response).to have_http_status(:unprocessable_content)\n        expect(response).to render_template \"all_casa_admins/new\"\n      end\n\n      it \"also responds as json\", :aggregate_failures do\n        post all_casa_admins_path(format: :json), params: {\n          all_casa_admin: {\n            email: \"\"\n          }\n        }\n\n        expect(response).to have_http_status(:unprocessable_content)\n        expect(response.content_type).to eq(\"application/json; charset=utf-8\")\n        expect(response.body).to match(\"Email can't be blank\".to_json)\n      end\n    end\n  end\n\n  describe \"PATCH /update\" do\n    context \"with valid parameters\" do\n      it \"updates the all_casa_admin\" do\n        patch all_casa_admins_path, params: {all_casa_admin: {email: \"newemail@example.com\"}}\n        expect(response).to have_http_status(:redirect)\n\n        expect(admin.email).to eq \"newemail@example.com\"\n      end\n\n      it \"also responds as json\", :aggregate_failures do\n        patch all_casa_admins_path(format: :json),\n          params: {all_casa_admin: {email: \"newemail@example.com\"}}\n\n        expect(response).to have_http_status(:ok)\n        expect(response.content_type).to eq(\"application/json; charset=utf-8\")\n        expect(response.body).to match(\"newemail@example.com\".to_json)\n      end\n    end\n\n    context \"with invalid parameters\" do\n      it \"does not update the all_casa_admin\" do\n        other_admin = create(:all_casa_admin)\n        patch all_casa_admins_path, params: {all_casa_admin: {email: other_admin.email}}\n        expect(response).to have_http_status(:unprocessable_content)\n\n        expect(admin.email).not_to eq \"newemail@example.com\"\n      end\n\n      it \"also responds as json\", :aggregate_failures do\n        other_admin = create(:all_casa_admin)\n        patch all_casa_admins_path(format: :json),\n          params: {all_casa_admin: {email: other_admin.email}}\n\n        expect(response).to have_http_status(:unprocessable_content)\n        expect(response.content_type).to eq(\"application/json; charset=utf-8\")\n        expect(response.body).to match(\"Email has already been taken\".to_json)\n      end\n    end\n  end\n\n  describe \"PATCH /update_password\" do\n    context \"with valid parameters\" do\n      subject { patch update_password_all_casa_admins_path, params: params }\n\n      let(:params) do\n        {\n          all_casa_admin: {\n            password: \"newpassword\",\n            password_confirmation: \"newpassword\"\n          }\n        }\n      end\n\n      it \"updates the all_casa_admin password\", :aggregate_failures do\n        subject\n\n        expect(response).to have_http_status(:redirect)\n        expect(admin.valid_password?(\"newpassword\")).to be true\n      end\n\n      it \"call UserMailer.password_chaned_reminder\" do\n        mailer = double(UserMailer, deliver: nil)\n        allow(UserMailer).to receive(:password_changed_reminder).with(admin).and_return(mailer)\n        expect(mailer).to receive(:deliver)\n\n        subject\n      end\n\n      it \"also responds as json\", :aggregate_failures do\n        patch update_password_all_casa_admins_path(format: :json), params: params\n\n        expect(response).to have_http_status(:ok)\n        expect(response.content_type).to eq(\"application/json; charset=utf-8\")\n        expect(response.body).to match(\"Password was successfully updated.\")\n      end\n    end\n\n    context \"with invalid parameters\", :aggregate_failures do\n      subject { patch update_password_all_casa_admins_path, params: params }\n\n      let(:params) do\n        {\n          all_casa_admin: {\n            password: \"newpassword\",\n            password_confirmation: \"badmatch\"\n          }\n        }\n      end\n\n      it \"does not update the all_casa_admin password\" do\n        subject\n\n        expect(response).to have_http_status(:unprocessable_content)\n        expect(admin.reload.valid_password?(\"newpassword\")).to be false\n      end\n\n      it \"does not call UserMailer.password_changed_reminder\" do\n        mailer = double(UserMailer, deliver: nil)\n        allow(UserMailer).to receive(:password_changed_reminder).with(admin).and_return(mailer)\n        expect(mailer).not_to receive(:deliver)\n\n        subject\n      end\n\n      it \"also responds as json\", :aggregate_failures do\n        patch update_password_all_casa_admins_path(format: :json), params: params\n\n        expect(response).to have_http_status(:unprocessable_content)\n        expect(response.content_type).to eq(\"application/json; charset=utf-8\")\n        expect(response.body).to match(\"Password confirmation doesn't match Password\".to_json)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/android_app_associations_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"AndroidAppAssociations\", type: :request do\n  describe \"GET /.well-known/assetlinks.json\" do\n    let(:reponse_json) do\n      [\n        {\n          relation: [\n            \"delegate_permission/common.handle_all_urls\"\n          ],\n          target: {\n            namespace: \"android_app\",\n            package_name: \"org.rubyforgood.casa\",\n            sha256_cert_fingerprints: [\"fingerprint\"]\n          }\n        }\n      ].to_json\n    end\n\n    before do\n      allow(ENV).to receive(:[]).with(\"ANDROID_CERTIFICATE_FINGERPRINT\").and_return(\"fingeprint\")\n    end\n\n    it \"renders a json file\" do\n      get \"/.well-known/assetlinks.json\"\n\n      expect(response.header[\"Content-Type\"]).to include(\"application/json\")\n      expect(response.body).to match(reponse_json)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/api/v1/base_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"Base Controller\", type: :request do\n  before do\n    base_controller = Class.new(Api::V1::BaseController) do\n      def index\n        render json: {message: \"Successfully autenticated\"}\n      end\n    end\n    stub_const(\"BaseController\", base_controller)\n    Rails.application.routes.disable_clear_and_finalize = true\n    Rails.application.routes.draw do\n      get \"/index\", to: \"base#index\"\n    end\n  end\n\n  after { Rails.application.reload_routes! }\n\n  # test authenticate_user! works\n  describe \"GET #index\" do\n    let(:user) { create(:volunteer) }\n\n    it \"returns http unauthorized if invalid token\" do\n      get \"/index\", headers: {\"Authorization\" => \"Token token=, email=#{user.email}\"}\n      expect(response).to have_http_status(:unauthorized)\n      expect(response.body).to eq({message: \"Incorrect email or password.\"}.to_json)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/api/v1/users/sessions_spec.rb",
    "content": "require \"swagger_helper\"\n\nRSpec.describe \"sessions API\", type: :request do\n  let(:casa_org) { create(:casa_org) }\n  let(:volunteer) { create(:volunteer, casa_org: casa_org) }\n\n  path \"/api/v1/users/sign_in\" do\n    post \"Signs in a user\" do\n      tags \"Sessions\"\n      consumes \"application/json\"\n      produces \"application/json\"\n      parameter name: :user, in: :body, schema: {\n        type: :object,\n        properties: {\n          email: {type: :string},\n          password: {type: :string}\n        },\n        required: %w[email password]\n      }\n\n      response \"201\", \"user signed in\" do\n        let(:user) { {email: volunteer.email, password: volunteer.password} }\n        schema \"$ref\" => \"#/components/schemas/login_success\"\n        run_test! do |response|\n          parsed_response = JSON.parse(response.body)\n          expect(parsed_response[\"api_token\"]).not_to be_nil\n          expect(parsed_response[\"refresh_token\"]).not_to be_nil\n          expect(response.content_type).to eq(\"application/json; charset=utf-8\")\n          expect(response.status).to eq(201)\n        end\n      end\n\n      response \"401\", \"invalid credentials\" do\n        let(:user) { {email: \"foo\", password: \"bar\"} }\n        schema \"$ref\" => \"#/components/schemas/login_failure\"\n        run_test! do |response|\n          expect(response.content_type).to eq(\"application/json; charset=utf-8\")\n          expect(response.body).to eq({message: \"Incorrect email or password.\"}.to_json)\n          expect(response.status).to eq(401)\n        end\n      end\n    end\n  end\n\n  path \"/api/v1/users/sign_out\" do\n    delete \"Signs out a user\" do\n      tags \"Sessions\"\n      produces \"application/json\"\n      parameter name: :authorization, in: :header, type: :string, required: true\n\n      let(:api_token) { create(:api_credential, user: volunteer).return_new_api_token![:api_token] }\n\n      response \"200\", \"user signed out\" do\n        let(:authorization) { \"Bearer #{api_token}\" }\n        schema \"$ref\" => \"#/components/schemas/sign_out\"\n        run_test! do |response|\n          expect(response.content_type).to eq(\"application/json; charset=utf-8\")\n          expect(response.body).to eq({message: \"Signed out successfully.\"}.to_json)\n          expect(response.status).to eq(200)\n        end\n      end\n\n      response \"401\", \"unauthorized\" do\n        let(:authorization) { \"Bearer foo\" }\n        schema \"$ref\" => \"#/components/schemas/sign_out\"\n        run_test! do |response|\n          expect(response.content_type).to eq(\"application/json; charset=utf-8\")\n          expect(response.body).to eq({message: \"An error occured when signing out.\"}.to_json)\n          expect(response.status).to eq(401)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/banners_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"Banners\", type: :request do\n  let!(:casa_org) { create(:casa_org) }\n  let!(:active_banner) { create(:banner, casa_org: casa_org) }\n  let(:volunteer) { create(:volunteer, casa_org: casa_org) }\n\n  context \"when user dismisses a banner\" do\n    subject do\n      get dismiss_banner_path(active_banner)\n    end\n\n    it \"sets session variable\" do\n      sign_in volunteer\n      subject\n      expect(session[:dismissed_banner]).to eq active_banner.id\n    end\n\n    it \"does not display banner on page reloads\" do\n      sign_in volunteer\n      get casa_cases_path\n      expect(response.body).to include \"Please fill out this survey\"\n\n      subject\n      get casa_cases_path\n      expect(response.body).not_to include \"Please fill out this survey\"\n    end\n\n    context \"when user logs out and back in\" do\n      it \"nils out session variable\" do\n        sign_in volunteer\n        subject\n        get destroy_user_session_path\n        sign_in volunteer\n\n        expect(session[:dismissed_banner]).to be_nil\n      end\n\n      it \"displays banner\" do\n        sign_in volunteer\n        subject\n        get destroy_user_session_path\n        sign_in volunteer\n\n        get casa_cases_path\n        expect(response.body).to include \"Please fill out this survey\"\n      end\n    end\n  end\n\n  context \"when a banner has expires_at\" do\n    context \"when expires_at is after today\" do\n      let!(:active_banner) { create(:banner, casa_org: casa_org, expires_at: 7.days.from_now) }\n\n      it \"displays the banner\" do\n        sign_in volunteer\n        get casa_cases_path\n        expect(response.body).to include \"Please fill out this survey\"\n      end\n    end\n\n    context \"when expires_at is before today\" do\n      let!(:active_banner) do\n        banner = create(:banner, casa_org: casa_org, expires_at: nil)\n        banner.update_columns(expires_at: 1.day.ago)\n      end\n\n      it \"does not display the banner\" do\n        sign_in volunteer\n        get casa_cases_path\n        expect(response.body).not_to include \"Please fill out this survey\"\n      end\n    end\n  end\n\n  context \"when creating a banner\" do\n    let(:admin) { create(:casa_admin, casa_org: casa_org) }\n    let(:banner_params) do\n      {\n        user: admin,\n        active: false,\n        content: \"Test\",\n        name: \"Test Announcement\",\n        expires_at: expires_at\n      }\n    end\n\n    context \"when client timezone is ahead of UTC\" do\n      before { travel_to Time.new(2024, 6, 1, 11, 0, 0, \"+03:00\") } # 08:00 UTC\n\n      context \"when submitted time is behind client but ahead of UTC\" do\n        let(:expires_at) { Time.new(2024, 6, 1, 9, 0, 0, \"UTC\") } # 12:00 +03:00\n\n        it \"succeeds\" do\n          sign_in admin\n          post banners_path, params: {banner: banner_params}\n          expect(response).to redirect_to banners_path\n        end\n      end\n\n      context \"when submitted time is behind client and behind UTC\" do\n        let(:expires_at) { Time.new(2024, 6, 1, 7, 0, 0, \"UTC\") } # 10:00 +03:00\n\n        it \"fails\" do\n          sign_in admin\n          post banners_path, params: {banner: banner_params}\n          expect(response).to render_template \"banners/new\"\n          expect(response.body).to include \"Expires at must take place in the future (after 2024-06-01 08:00:00 UTC)\"\n        end\n      end\n    end\n\n    context \"when client timezone is behind UTC\" do\n      before { travel_to Time.new(2024, 6, 1, 11, 0, 0, \"-04:00\") } # 15:00 UTC\n\n      context \"when submitted time is ahead of client and ahead of UTC\" do\n        let(:expires_at) { Time.new(2024, 6, 1, 16, 0, 0, \"UTC\") } # 12:00 -04:00\n\n        it \"succeeds\" do\n          sign_in admin\n          post banners_path, params: {banner: banner_params}\n          expect(response).to redirect_to banners_path\n        end\n      end\n\n      context \"when submitted time is ahead of client but behind UTC\" do\n        let(:expires_at) { Time.new(2024, 6, 1, 14, 0, 0, \"UTC\") } # 10:00 -04:00\n\n        it \"fails\" do\n          sign_in admin\n          post banners_path, params: {banner: banner_params}\n          expect(response).to render_template \"banners/new\"\n          expect(response.body).to include \"Expires at must take place in the future (after 2024-06-01 15:00:00 UTC)\"\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/bulk_court_dates_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"BulkCourtDates\", type: :request do\n  let(:user) { create(:supervisor) }\n\n  before { sign_in user }\n\n  describe \"GET /new\" do\n    subject { get \"/bulk_court_dates/new\" }\n\n    it \"renders the new template\" do\n      subject\n      expect(response).to have_http_status :success\n      expect(response).to render_template :new\n    end\n  end\n\n  describe \"POST /create\" do\n    subject { post \"/bulk_court_dates\", params: }\n\n    let(:judge) { create :judge }\n    let(:hearing_type) { create :hearing_type }\n    let(:case_count) { 2 }\n    let(:case_group) { create :case_group, case_count:, casa_org: user.casa_org }\n    let(:court_date) { Date.tomorrow }\n    let(:case_court_orders_attributes) { {} }\n    let(:params) do\n      {\n        court_date: {\n          case_group_id: case_group.id,\n          date: Date.tomorrow,\n          court_report_due_date: Date.today,\n          judge_id: judge.id,\n          hearing_type_id: hearing_type.id,\n          case_court_orders_attributes:\n        }\n      }\n    end\n\n    it \"renders the new template on success\" do\n      subject\n      expect(response).to have_http_status(:found)\n      expect(response).to redirect_to(new_bulk_court_date_path)\n    end\n\n    it \"adds the court date to each group case\" do\n      expect(case_group.casa_cases.count).to be > 1\n      cc_one = case_group.casa_cases.first\n      cc_two = case_group.casa_cases.last\n\n      expect { subject }\n        .to change { cc_one.court_dates.count }.by(1)\n        .and change { cc_two.court_dates.count }.by(1)\n    end\n\n    context \"when different casa org's case group\" do\n      let(:case_group) { create :case_group, case_count:, casa_org: build(:casa_org) }\n\n      it \"raises ActiveRecord::RecordNotFound\" do\n        expect { subject }.to raise_error(ActiveRecord::RecordNotFound)\n      end\n    end\n\n    context \"when court orders are in params\" do\n      let(:case_court_orders_attributes) do\n        {\n          \"0\" => {\n            text: \"Some court order\",\n            implementation_status: \"partially_implemented\"\n          },\n          \"1\" => {\n            text: \"Another court order\",\n            implementation_status: \"implemented\"\n          }\n        }\n      end\n\n      it \"adds the court orders to each group case\" do\n        expect(case_group.casa_cases.count).to be > 1\n        cc_one = case_group.casa_cases.first\n        cc_two = case_group.casa_cases.last\n\n        expect { subject }\n          .to change { cc_one.case_court_orders.count }.by(case_court_orders_attributes.size)\n          .and change { cc_two.case_court_orders.count }.by(case_court_orders_attributes.size)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/casa_admins_spec.rb",
    "content": "require \"rails_helper\"\nrequire \"support/stubbed_requests/webmock_helper\"\n\nRSpec.describe \"/casa_admins\", type: :request do\n  describe \"GET /casa_admins\" do\n    it \"is successful\" do\n      admins = create_pair(:casa_admin)\n\n      sign_in admins.first\n      get casa_admins_path\n\n      expect(response).to be_successful\n    end\n  end\n\n  describe \"GET /casa_admins/:id/edit\" do\n    context \"logged in as admin user\" do\n      context \"same org\" do\n        it \"can successfully access a casa admin edit page\" do\n          casa_one = create(:casa_org)\n          casa_admin_one = create(:casa_admin, casa_org: casa_one)\n\n          sign_in(casa_admin_one)\n          get edit_casa_admin_path(create(:casa_admin, casa_org: casa_one))\n\n          expect(response).to be_successful\n        end\n      end\n\n      context \"different org\" do\n        it \"cannot access a casa admin edit page\" do\n          casa_admin = create(:casa_admin)\n          diff_org = create(:casa_org)\n          casa_admin_diff_org = create(:casa_admin, casa_org: diff_org)\n\n          sign_in casa_admin\n          get edit_casa_admin_path(casa_admin_diff_org)\n\n          expect(response).to redirect_to root_path\n          expect(response.request.flash[:notice]).to eq \"Sorry, you are not authorized to perform this action.\"\n        end\n      end\n    end\n\n    context \"logged in as a non-admin user\" do\n      it \"cannot access a casa admin edit page\" do\n        sign_in_as_volunteer\n        admin = create(:casa_admin)\n\n        get edit_casa_admin_path(admin)\n\n        expect(response).to redirect_to root_path\n        expect(response.request.flash[:notice]).to eq \"Sorry, you are not authorized to perform this action.\"\n      end\n    end\n\n    context \"unauthenticated request\" do\n      it \"cannot access a casa admin edit page\" do\n        admin = create(:casa_admin)\n\n        get edit_casa_admin_path(admin)\n\n        expect(response).to redirect_to new_user_session_path\n      end\n    end\n  end\n\n  describe \"PUT /casa_admins/:id\" do\n    context \"logged in as admin user\" do\n      it \"can successfully update a casa admin user\", :aggregate_failures do\n        casa_admin = create(:casa_admin)\n        expected_display_name = \"Admin 2\"\n        expected_phone_number = \"+14163218092\"\n\n        sign_in casa_admin\n\n        put casa_admin_path(casa_admin), params: {\n          casa_admin: {\n            display_name: expected_display_name,\n            phone_number: expected_phone_number\n          }\n        }\n\n        casa_admin.reload\n        expect(casa_admin.display_name).to eq expected_display_name\n        expect(casa_admin.phone_number).to eq expected_phone_number\n        expect(response).to redirect_to edit_casa_admin_path(casa_admin)\n        expect(response.request.flash[:notice]).to eq \"Casa Admin was successfully updated.\"\n      end\n\n      it \"can update a casa admin user's email and send them a confirmation email\", :aggregate_failures do\n        casa_admin = create(:casa_admin)\n        expected_email = \"admin2@casa.com\"\n\n        sign_in casa_admin\n        put casa_admin_path(casa_admin), params: {\n          casa_admin: {\n            email: expected_email\n          }\n        }\n\n        casa_admin.reload\n        expect(response).to have_http_status(:redirect)\n\n        expect(casa_admin.unconfirmed_email).to eq(\"admin2@casa.com\")\n        expect(ActionMailer::Base.deliveries.count).to eq(1)\n        expect(ActionMailer::Base.deliveries.first).to be_a(Mail::Message)\n        expect(ActionMailer::Base.deliveries.first.body.encoded)\n          .to match(\"Click here to confirm your email\")\n      end\n\n      it \"also respond as json\", :aggregate_failures do\n        casa_admin = create(:casa_admin)\n        expected_display_name = \"Admin 2\"\n\n        sign_in casa_admin\n        put casa_admin_path(casa_admin, format: :json), params: {\n          casa_admin: {\n            display_name: expected_display_name\n          }\n        }\n\n        casa_admin.reload\n        expect(response.content_type).to eq(\"application/json; charset=utf-8\")\n        expect(response).to have_http_status(:ok)\n      end\n    end\n\n    context \"when logged in as admin, but invalid data\" do\n      it \"cannot update the casa admin\", :aggregate_failures do\n        casa_admin = create(:casa_admin)\n\n        sign_in casa_admin\n        put casa_admin_path(casa_admin), params: {\n          casa_admin: {email: nil},\n          phone_number: {phone_number: \"dsadw323\"}\n        }\n\n        casa_admin.reload\n        expect(casa_admin.email).not_to eq nil\n        expect(casa_admin.phone_number).not_to eq \"dsadw323\"\n        expect(response).to render_template :edit\n      end\n\n      it \"also respond as json\", :aggregate_failures do\n        casa_admin = create(:casa_admin)\n\n        sign_in casa_admin\n        put casa_admin_path(casa_admin, format: :json), params: {\n          casa_admin: {email: nil}\n        }\n\n        expect(response.content_type).to eq(\"application/json; charset=utf-8\")\n        expect(response).to have_http_status(:unprocessable_content)\n        expect(response.body).to match(\"Email can't be blank\".to_json)\n      end\n    end\n\n    context \"logged in as a non-admin user\" do\n      it \"cannot update a casa admin user\" do\n        sign_in_as_volunteer\n        admin = create(:casa_admin)\n\n        put casa_admin_path(admin), params: {\n          casa_admin: {\n            email: \"admin@casa.com\",\n            display_name: \"The admin\"\n          }\n        }\n\n        expect(response).to redirect_to root_path\n        expect(response.request.flash[:notice]).to eq \"Sorry, you are not authorized to perform this action.\"\n      end\n    end\n\n    context \"unauthenticated request\" do\n      it \"cannot update a casa admin user\" do\n        admin = create(:casa_admin)\n\n        put casa_admin_path(admin), params: {\n          casa_admin: {\n            email: \"admin@casa.com\",\n            display_name: \"The admin\"\n          }\n        }\n\n        expect(response).to redirect_to new_user_session_path\n      end\n    end\n  end\n\n  describe \"PATCH /activate\" do\n    context \"when successfully\" do\n      it \"activates an inactive casa_admin\" do\n        casa_admin = create(:casa_admin)\n        casa_admin_other = create(:casa_admin, active: false)\n\n        sign_in casa_admin\n        patch activate_casa_admin_path(casa_admin_other)\n\n        casa_admin_other.reload\n        expect(casa_admin_other).to be_active\n      end\n\n      it \"sends an activation email\" do\n        casa_admin = create(:casa_admin)\n        casa_admin_inactive = create(:casa_admin, active: false)\n\n        sign_in casa_admin\n        expect { patch activate_casa_admin_path(casa_admin_inactive) }\n          .to change { ActionMailer::Base.deliveries.count }\n          .by(1)\n      end\n\n      it \"also respond as json\", :aggregate_failures do\n        casa_admin = create(:casa_admin)\n        casa_admin_inactive = create(:casa_admin, active: false)\n\n        sign_in casa_admin\n        patch activate_casa_admin_path(casa_admin_inactive, format: :json)\n\n        expect(response.content_type).to eq(\"application/json; charset=utf-8\")\n        expect(response).to have_http_status(:ok)\n        expect(response.body).to match(casa_admin.reload.active.to_json)\n      end\n    end\n\n    context \"when occurs send errors\" do\n      it \"redirects to admin edition page\" do\n        casa_admin = create(:casa_admin)\n        casa_admin_inactive = create(:casa_admin, active: false)\n        allow(CasaAdminMailer).to receive_message_chain(:account_setup, :deliver) { raise Errno::ECONNREFUSED }\n\n        sign_in casa_admin\n        patch activate_casa_admin_path(casa_admin_inactive)\n\n        expect(response).to redirect_to(edit_casa_admin_path(casa_admin_inactive))\n      end\n\n      it \"shows error message\" do\n        casa_admin = create(:casa_admin)\n        casa_admin_inactive = create(:casa_admin, active: false)\n        allow(CasaAdminMailer).to receive_message_chain(:account_setup, :deliver) { raise Errno::ECONNREFUSED }\n\n        sign_in casa_admin\n        patch activate_casa_admin_path(casa_admin_inactive)\n\n        expect(flash[:alert]).to eq(\"Email not sent.\")\n      end\n\n      it \"also respond as json\", :aggregate_failures do\n        casa_admin = create(:casa_admin)\n        casa_admin_inactive = create(:casa_admin, active: false)\n        allow_any_instance_of(CasaAdmin).to receive(:activate).and_return(false)\n        allow_any_instance_of(CasaAdmin).to receive_message_chain(:errors, :full_messages)\n          .and_return [\"Error message test\"]\n\n        sign_in casa_admin\n        patch activate_casa_admin_path(casa_admin_inactive, format: :json)\n\n        expect(response.content_type).to eq(\"application/json; charset=utf-8\")\n        expect(response).to have_http_status(:unprocessable_content)\n        expect(response.body).to match(\"Error message test\".to_json)\n      end\n    end\n  end\n\n  describe \"PATCH /casa_admins/:id/deactivate\" do\n    context \"logged in as admin user\" do\n      context \"when successfully\" do\n        it \"can successfully deactivate a casa admin user\" do\n          casa_admin = create(:casa_admin)\n          casa_admin_other = create(:casa_admin, active: true)\n\n          sign_in casa_admin\n          patch deactivate_casa_admin_path(casa_admin_other)\n\n          casa_admin_other.reload\n          expect(casa_admin_other).not_to be_active\n\n          expect(response).to redirect_to edit_casa_admin_path(casa_admin_other)\n          expect(response.request.flash[:notice]).to eq \"Admin was deactivated.\"\n        end\n\n        it \"sends a deactivation email\" do\n          casa_admin = create(:casa_admin)\n          casa_admin_active = create(:casa_admin, active: true)\n\n          sign_in casa_admin\n          expect { patch deactivate_casa_admin_path(casa_admin_active) }\n            .to change { ActionMailer::Base.deliveries.count }\n            .by(1)\n        end\n\n        it \"also respond as json\", :aggregate_failures do\n          casa_admin = create(:casa_admin)\n          casa_admin_active = create(:casa_admin, active: true)\n\n          sign_in casa_admin\n          patch deactivate_casa_admin_path(casa_admin_active, format: :json)\n\n          expect(response.content_type).to eq(\"application/json; charset=utf-8\")\n          expect(response).to have_http_status(:ok)\n          expect(response.body).to match(casa_admin.reload.active.to_json)\n        end\n      end\n\n      context \"when occurs send errors\" do\n        it \"redirects to admin edit page\" do\n          casa_admin = create(:casa_admin)\n          casa_admin_active = create(:casa_admin, active: true)\n          allow(CasaAdminMailer).to receive_message_chain(:deactivation, :deliver) { raise Errno::ECONNREFUSED }\n\n          sign_in casa_admin\n          patch deactivate_casa_admin_path(casa_admin_active)\n\n          expect(response).to redirect_to(edit_casa_admin_path(casa_admin_active))\n        end\n\n        it \"shows error message\" do\n          casa_admin = create(:casa_admin)\n          casa_admin_active = create(:casa_admin, active: true)\n          allow(CasaAdminMailer).to receive_message_chain(:deactivation, :deliver) { raise Errno::ECONNREFUSED }\n\n          sign_in casa_admin\n          patch deactivate_casa_admin_path(casa_admin_active)\n\n          expect(flash[:alert]).to eq(\"Email not sent.\")\n        end\n\n        it \"also respond as json\", :aggregate_failures do\n          casa_admin = create(:casa_admin)\n          casa_admin_active = create(:casa_admin, active: true)\n          allow_any_instance_of(CasaAdmin).to receive(:deactivate).and_return(false)\n          allow_any_instance_of(CasaAdmin).to receive_message_chain(:errors, :full_messages)\n            .and_return [\"Error message test\"]\n\n          sign_in casa_admin\n          patch deactivate_casa_admin_path(casa_admin_active, format: :json)\n\n          expect(response.content_type).to eq(\"application/json; charset=utf-8\")\n          expect(response).to have_http_status(:unprocessable_content)\n          expect(response.body).to match(\"Error message test\".to_json)\n        end\n      end\n    end\n\n    context \"logged in as a non-admin user\" do\n      it \"cannot update a casa admin user\" do\n        admin = create(:casa_admin)\n\n        sign_in_as_volunteer\n        patch deactivate_casa_admin_path(admin)\n\n        expect(response).to redirect_to root_path\n        expect(response.request.flash[:notice]).to eq \"Sorry, you are not authorized to perform this action.\"\n      end\n    end\n\n    context \"unauthenticated request\" do\n      it \"cannot update a casa admin user\" do\n        admin = create(:casa_admin)\n\n        patch deactivate_casa_admin_path(admin)\n\n        expect(response).to redirect_to new_user_session_path\n      end\n    end\n  end\n\n  describe \"PATCH /resend_invitation\" do\n    it \"resends an invitation email\" do\n      casa_admin = create(:casa_admin, active: true)\n\n      sign_in casa_admin\n      expect(casa_admin.invitation_created_at.present?).to eq(false)\n\n      patch resend_invitation_casa_admin_path(casa_admin)\n      casa_admin.reload\n\n      expect(casa_admin.invitation_created_at.present?).to eq(true)\n      expect(Devise.mailer.deliveries.count).to eq(1)\n      expect(Devise.mailer.deliveries.first.subject).to eq(I18n.t(\"devise.mailer.invitation_instructions.subject\"))\n      expect(response).to redirect_to(edit_casa_admin_path(casa_admin))\n    end\n  end\n\n  describe \"POST /casa_admins\" do\n    context \"when successfully\" do\n      it \"creates a new casa_admin\" do\n        org = create(:casa_org, twilio_enabled: true)\n        admin = create(:casa_admin, casa_org: org)\n        params = attributes_for(:casa_admin)\n\n        sign_in admin\n\n        expect {\n          post casa_admins_path, params: {casa_admin: params}\n        }.to change(CasaAdmin, :count).by(1)\n        expect(response).to redirect_to casa_admins_path\n        expect(flash[:notice]).to eq(\"New admin created successfully.\")\n      end\n\n      it \"also respond to json\", :aggregate_failures do\n        org = create(:casa_org, twilio_enabled: true)\n        admin = create(:casa_admin, casa_org: org)\n        params = attributes_for(:casa_admin)\n\n        sign_in admin\n        post casa_admins_path(format: :json), params: {casa_admin: params}\n\n        expect(response.content_type).to eq(\"application/json; charset=utf-8\")\n        expect(response).to have_http_status(:created)\n        expect(response.body).to match(params[:display_name].to_json)\n      end\n    end\n\n    context \"when creating new admin\" do\n      it \"sends SMS when phone number is provided\" do\n        org = create(:casa_org, twilio_enabled: true)\n        admin = create(:casa_admin, casa_org: org)\n        twilio_activation_success_stub = WebMockHelper.twilio_activation_success_stub(\"admin\")\n        short_io_stub = WebMockHelper.short_io_stub_sms\n        params = attributes_for(:casa_admin)\n        params[:phone_number] = \"+12222222222\"\n\n        sign_in admin\n        post casa_admins_path, params: {casa_admin: params}\n\n        expect(short_io_stub).to have_been_requested.times(2)\n        expect(twilio_activation_success_stub).to have_been_requested.times(1)\n        expect(response).to have_http_status(:redirect)\n        follow_redirect!\n        expect(flash[:notice]).to match(/New admin created successfully. SMS has been sent!/)\n      end\n\n      it \"does not send SMS when phone number not given\" do\n        org = create(:casa_org, twilio_enabled: true)\n        admin = create(:casa_admin, casa_org: org)\n        twilio_activation_success_stub = WebMockHelper.twilio_activation_success_stub(\"admin\")\n        twilio_activation_error_stub = WebMockHelper.twilio_activation_error_stub(\"admin\")\n        short_io_stub = WebMockHelper.short_io_stub_sms\n        params = attributes_for(:casa_admin)\n\n        sign_in admin\n        post casa_admins_path, params: {casa_admin: params}\n\n        expect(short_io_stub).to have_been_requested.times(0)\n        expect(twilio_activation_success_stub).to have_been_requested.times(0)\n        expect(twilio_activation_error_stub).to have_been_requested.times(0)\n        expect(response).to have_http_status(:redirect)\n        follow_redirect!\n        expect(flash[:notice]).to match(/New admin created successfully./)\n      end\n\n      it \"does not send SMS when Twilio has an error\" do\n        org = create(:casa_org, twilio_account_sid: \"articuno31\", twilio_enabled: true)\n        admin = build(:casa_admin, casa_org: org)\n        short_io_stub = WebMockHelper.short_io_stub_sms\n        twilio_activation_error_stub = WebMockHelper.twilio_activation_error_stub(\"admin\")\n        params = attributes_for(:casa_admin)\n        params[:phone_number] = \"+12222222222\"\n\n        sign_in admin\n        post casa_admins_path, params: {casa_admin: params}\n\n        expect(short_io_stub).to have_been_requested.times(2) # TODO: why is this called at all?\n        expect(twilio_activation_error_stub).to have_been_requested.times(1)\n        expect(response).to have_http_status(:redirect)\n        follow_redirect!\n        expect(flash[:notice]).to match(/New admin created successfully. SMS not sent. Error: ./)\n      end\n\n      it \"does not send SMS when Twilio is not enabled\" do\n        org = create(:casa_org, twilio_enabled: false)\n        admin = build(:casa_admin, casa_org: org)\n        params = attributes_for(:casa_admin)\n        params[:phone_number] = \"+12222222222\"\n        short_io_stub = WebMockHelper.short_io_stub_sms\n\n        sign_in admin\n        post casa_admins_path, params: {casa_admin: params}\n\n        expect(short_io_stub).to have_been_requested.times(2) # TODO: why is this called at all?\n        expect(response).to have_http_status(:redirect)\n        follow_redirect!\n        expect(flash[:notice]).to match(/New admin created successfully./)\n      end\n    end\n\n    context \"when failure\" do\n      it \"does not create a new casa_admin\" do\n        org = create(:casa_org, twilio_enabled: true)\n        admin = create(:casa_admin, casa_org: org)\n        allow_any_instance_of(CreateCasaAdminService).to receive(:create!).and_raise(ActiveRecord::RecordInvalid)\n        params = attributes_for(:casa_admin)\n\n        sign_in admin\n\n        expect {\n          post casa_admins_path, params: {casa_admin: params}\n        }.not_to change(CasaAdmin, :count)\n        expect(response).to render_template :new\n      end\n\n      it \"also responds to json\", :aggregate_failures do\n        org = create(:casa_org, twilio_enabled: true)\n        admin = create(:casa_admin, casa_org: org)\n\n        sign_in admin\n        casa_admin = instance_spy(CasaAdmin)\n        allow(casa_admin).to receive_message_chain(:errors, :full_messages).and_return([\"Some error message\"])\n        allow_any_instance_of(CreateCasaAdminService).to receive(:casa_admin).and_return(casa_admin)\n        allow_any_instance_of(CreateCasaAdminService).to receive(:create!)\n          .and_raise(ActiveRecord::RecordInvalid)\n        params = attributes_for(:casa_admin)\n\n        post casa_admins_path(format: :json), params: {casa_admin: params}\n\n        expect(response.content_type).to eq(\"application/json; charset=utf-8\")\n        expect(response).to have_http_status(:unprocessable_content)\n        expect(response.body).to match(\"Some error message\".to_json)\n      end\n    end\n  end\n\n  describe \"PATCH /change_to_supervisor\" do\n    context \"when signed in as an admin\" do\n      it \"changes the admin to a supervisor\" do\n        casa_admin = create(:casa_admin)\n\n        sign_in_as_admin\n        patch change_to_supervisor_casa_admin_path(casa_admin)\n\n        expect(response).to redirect_to(edit_supervisor_path(casa_admin))\n\n        # find the user after their type has changed\n        user = User.find(casa_admin.id)\n        expect(user).not_to be_casa_admin\n        expect(user).to be_supervisor\n      end\n    end\n\n    context \"when signed in as a supervisor\" do\n      it \"does not change the admin to a supervisor\" do\n        casa_admin = create(:casa_admin)\n        supervisor = create(:supervisor)\n\n        sign_in supervisor\n        patch change_to_supervisor_casa_admin_path(casa_admin)\n\n        casa_admin.reload\n        expect(casa_admin).to be_casa_admin\n        expect(casa_admin).not_to be_supervisor\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/casa_cases_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"/casa_cases\", type: :request do\n  let(:date_in_care) { Date.today }\n  let(:organization) { build(:casa_org) }\n  let(:group) { build(:contact_type_group) }\n  let(:volunteer) { create(:volunteer) }\n  let(:type1) { create(:contact_type, contact_type_group: group) }\n  let(:pre_transition_aged_youth_age) { Date.current - 14.years }\n  let(:valid_attributes) do\n    {\n      case_number: \"1234\",\n      birth_month_year_youth: pre_transition_aged_youth_age,\n      \"date_in_care(3i)\": date_in_care.day,\n      \"date_in_care(2i)\": date_in_care.month,\n      \"date_in_care(1i)\": date_in_care.year,\n      assigned_volunteer_id: volunteer.id,\n      casa_org_id: organization.id,\n      contact_type_ids: [type1.id],\n      case_assignments_attributes: {\"0\": {volunteer_id: volunteer.id.to_s}}\n    }\n  end\n  let(:invalid_attributes) { {case_number: nil, birth_month_year_youth: nil} }\n  let(:casa_case) { create(:casa_case, casa_org: organization, case_number: \"111\") }\n\n  let(:texts) { [\"1-New Court Order Text One\", \"0-New Court Order Text Two\"] }\n  let(:implementation_statuses) { [\"unimplemented\", nil] }\n\n  let(:orders_attributes) do\n    {\n      \"0\" => {text: texts[0], implementation_status: implementation_statuses[0]},\n      \"1\" => {text: texts[1], implementation_status: implementation_statuses[1]}\n    }\n  end\n\n  before { sign_in user }\n\n  describe \"as an admin\" do\n    let(:user) { create(:casa_admin, casa_org: organization) }\n\n    describe \"GET /index\" do\n      it \"renders a successful response\" do\n        create(:casa_case)\n        get casa_cases_url\n        expect(response).to be_successful\n      end\n\n      it \"shows all my organization's cases\" do\n        volunteer_1 = create(:volunteer, casa_org: user.casa_org)\n        volunteer_2 = create(:volunteer, casa_org: user.casa_org)\n        create(:case_assignment, volunteer: volunteer_1)\n        create(:case_assignment, volunteer: volunteer_2)\n\n        get casa_cases_url\n\n        expect(response.body).to include(volunteer_1.casa_cases.first.case_number)\n        expect(response.body).to include(volunteer_2.casa_cases.first.case_number)\n      end\n\n      it \"doesn't show other organizations' cases\" do\n        my_case_assignment = build(:case_assignment, casa_org: user.casa_org)\n        different_org = build(:casa_org)\n        not_my_case_assignment = build_stubbed(:case_assignment, casa_org: different_org)\n\n        get casa_cases_url\n\n        expect(response.body).to include(my_case_assignment.casa_case.case_number)\n        expect(response.body).not_to include(not_my_case_assignment.casa_case.case_number)\n      end\n    end\n\n    describe \"GET /show\" do\n      it \"renders a successful response\" do\n        get casa_case_url(casa_case)\n        expect(response).to be_successful\n      end\n\n      it \"fails across organizations\" do\n        other_org = build(:casa_org)\n        other_case = create(:casa_case, casa_org: other_org)\n\n        get casa_case_url(other_case)\n        expect(response).to be_redirect\n        expect(flash[:notice]).to eq(\"Sorry, you are not authorized to perform this action.\")\n      end\n\n      context \"when exporting a csv\" do\n        subject(:casa_case_show) { get casa_case_path(casa_case, format: :csv) }\n\n        let(:current_time) { Time.now.strftime(\"%Y-%m-%d\") }\n\n        it \"generates a csv\" do\n          casa_case_show\n\n          expect(response).to have_http_status :ok\n          expect(response.headers[\"Content-Type\"]).to include \"text/csv\"\n          expect(response.headers[\"Content-Disposition\"]).to include \"case-contacts-#{current_time}\"\n        end\n\n        it \"adds the correct headers to the csv\" do\n          casa_case_show\n\n          csv_headers = [\"Internal Contact Number\", \"Duration Minutes\", \"Contact Types\",\n            \"Contact Made\", \"Contact Medium\", \"Occurred At\", \"Added To System At\", \"Miles Driven\",\n            \"Wants Driving Reimbursement\", \"Casa Case Number\", \"Creator Email\", \"Creator Name\",\n            \"Supervisor Name\", \"Case Contact Notes\"]\n\n          csv_headers.each { |header| expect(response.body).to include header }\n        end\n      end\n\n      context \"when exporting a xlsx\" do\n        subject(:casa_case_show) { get casa_case_path(casa_case, format: :xlsx) }\n\n        let(:current_time) { Time.now.strftime(\"%Y-%m-%d\") }\n\n        it \"generates a xlsx file\" do\n          casa_case_show\n\n          expect(response).to have_http_status :ok\n          expect(response.headers[\"Content-Type\"]).to include \"application/vnd.openxmlformats\"\n          expect(response.headers[\"Content-Disposition\"]).to include \"case-contacts-#{current_time}\"\n        end\n      end\n    end\n\n    describe \"GET /new\" do\n      it \"renders a successful response\" do\n        get new_casa_case_url\n        expect(response).to be_successful\n      end\n    end\n\n    describe \"GET /edit\" do\n      it \"render a successful response\" do\n        get edit_casa_case_url(casa_case)\n        expect(response).to be_successful\n      end\n\n      it \"fails across organizations\" do\n        other_org = build(:casa_org)\n        other_case = create(:casa_case, casa_org: other_org)\n\n        get edit_casa_case_url(other_case)\n        expect(response).to be_redirect\n        expect(flash[:notice]).to eq(\"Sorry, you are not authorized to perform this action.\")\n      end\n    end\n\n    describe \"POST /create\" do\n      context \"with valid parameters\" do\n        it \"creates a new CasaCase\" do\n          expect { post casa_cases_url, params: {casa_case: valid_attributes} }.to change(\n            CasaCase,\n            :count\n          ).by(1)\n        end\n\n        it \"redirects to the created casa_case\" do\n          post casa_cases_url, params: {casa_case: valid_attributes}\n          expect(response).to redirect_to(casa_case_url(CasaCase.last))\n        end\n\n        it \"sets fields correctly\" do\n          post casa_cases_url, params: {casa_case: valid_attributes}\n          casa_case = CasaCase.last\n          expect(casa_case.casa_org).to eq organization\n          expect(casa_case.birth_month_year_youth).to eq pre_transition_aged_youth_age\n          expect(casa_case.date_in_care.to_date).to eq date_in_care\n        end\n\n        it \"also responds as json\", :aggregate_failures do\n          post casa_cases_url(format: :json), params: {casa_case: valid_attributes}\n\n          expect(response.content_type).to eq(\"application/json; charset=utf-8\")\n          expect(response).to have_http_status(:created)\n          expect(response.body).to match(valid_attributes[:case_number].to_json)\n        end\n\n        context \"with valid assigned_volunteer_id\" do\n          it \"creates a case assignment\" do\n            expect { post casa_cases_url, params: {casa_case: valid_attributes} }.to change(\n              CaseAssignment,\n              :count\n            ).by(1)\n          end\n        end\n\n        context \"without an assigned_volunteer_id\" do\n          let(:valid_attributes) do\n            {\n              case_number: \"1234\",\n              birth_month_year_youth: pre_transition_aged_youth_age,\n              \"date_in_care(3i)\": date_in_care.day,\n              \"date_in_care(2i)\": date_in_care.month,\n              \"date_in_care(1i)\": date_in_care.year,\n              assigned_volunteer_id: nil,\n              casa_org_id: organization.id,\n              contact_type_ids: [type1.id]\n            }\n          end\n\n          it \"does not create a case assignment\" do\n            expect { post casa_cases_url, params: {casa_case: valid_attributes} }.not_to change(\n              CaseAssignment,\n              :count\n            )\n          end\n        end\n      end\n\n      it \"only creates cases within user's organizations\" do\n        other_org = build(:casa_org)\n        attributes = {\n          case_number: \"1234\",\n          birth_month_year_youth: pre_transition_aged_youth_age,\n          casa_org_id: other_org.id,\n          contact_type_ids: [type1.id]\n        }\n\n        expect { post casa_cases_url, params: {casa_case: attributes} }.to(\n          change { [organization.casa_cases.count, other_org.casa_cases.count] }.from([0, 0]).to([1, 0])\n        )\n      end\n\n      describe \"invalid request\" do\n        context \"with invalid parameters\" do\n          it \"does not create a new CasaCase\" do\n            expect { post casa_cases_url, params: {casa_case: invalid_attributes} }.not_to change(\n              CasaCase,\n              :count\n            )\n          end\n\n          it \"renders an unprocessable entity response (i.e. to display the 'new' template)\" do\n            post casa_cases_url, params: {casa_case: invalid_attributes}\n            expect(response).to have_http_status(:unprocessable_content)\n          end\n\n          it \"also respond to json\", :aggregate_failures do\n            post casa_cases_url(format: :json), params: {casa_case: invalid_attributes}\n\n            expect(response.content_type).to eq(\"application/json; charset=utf-8\")\n            expect(response).to have_http_status(:unprocessable_content)\n            expected_response_body = [\n              \"Birth month year youth can't be blank\",\n              \"Case number can't be blank\",\n              \"Casa case contact types : At least one contact type must be selected\"\n            ].to_json\n            expect(response.body).to eq(expected_response_body)\n          end\n        end\n\n        context \"with case_court_orders_attributes being passed as a parameter\" do\n          let(:invalid_params) do\n            attributes = valid_attributes\n            attributes[:case_court_orders_attributes] = orders_attributes\n            {casa_case: attributes}\n          end\n\n          it \"Creates a new CasaCase, but no CaseCourtOrder\" do\n            expect { post casa_cases_url, params: invalid_params }.to change(\n              CasaCase,\n              :count\n            ).by(1)\n\n            expect { post casa_cases_url, params: invalid_params }.not_to change(\n              CaseCourtOrder,\n              :count\n            )\n          end\n\n          it \"renders an unprocessable entity response (i.e. to display the 'new' template)\" do\n            post casa_cases_url, params: {casa_case: invalid_params}\n            expect(response).to have_http_status(:unprocessable_content)\n          end\n        end\n      end\n    end\n\n    describe \"PATCH /update\" do\n      let(:group) { build(:contact_type_group) }\n      let(:type1) { create(:contact_type, contact_type_group: group) }\n      let(:new_attributes) do\n        {\n          case_number: \"12345\",\n          case_court_orders_attributes: orders_attributes\n        }\n      end\n      let(:new_attributes2) do\n        {\n          case_number: \"12345\",\n          case_court_orders_attributes: orders_attributes,\n          contact_type_ids: [type1.id]\n        }\n      end\n\n      context \"with valid parameters\" do\n        it \"updates the requested casa_case\" do\n          patch casa_case_url(casa_case), params: {casa_case: new_attributes2}\n          casa_case.reload\n          expect(casa_case.case_number).to eq \"12345\"\n          expect(casa_case.slug).to eq \"12345\"\n          expect(casa_case.case_court_orders[0].text).to eq texts[0]\n          expect(casa_case.case_court_orders[0].implementation_status).to eq implementation_statuses[0]\n          expect(casa_case.case_court_orders[1].text).to eq texts[1]\n          expect(casa_case.case_court_orders[1].implementation_status).to eq implementation_statuses[1]\n        end\n\n        it \"redirects to the casa_case\" do\n          patch casa_case_url(casa_case), params: {casa_case: new_attributes2}\n          casa_case.reload\n          expect(response).to redirect_to(edit_casa_case_path)\n        end\n\n        it \"displays changed attributes\" do\n          patch casa_case_url(casa_case), params: {casa_case: new_attributes2}\n          expect(flash[:notice]).to eq(\"CASA case was successfully updated.<ul><li>Changed Case number</li><li>[\\\"#{type1.name}\\\"] Contact types added</li><li>2 Court orders added or updated</li></ul>\")\n        end\n\n        it \"also responds as json\", :aggregate_failures do\n          patch casa_case_url(casa_case, format: :json), params: {casa_case: new_attributes2}\n\n          expect(response.content_type).to eq(\"application/json; charset=utf-8\")\n          expect(response).to have_http_status(:ok)\n          expect(response.body).to match(new_attributes2[:case_number].to_json)\n        end\n      end\n\n      context \"with invalid parameters\" do\n        it \"renders an unprocessable entity response displaying the edit template\" do\n          patch casa_case_url(casa_case), params: {casa_case: invalid_attributes}\n          expect(response).to have_http_status(:unprocessable_content)\n        end\n\n        it \"also responds as json\", :aggregate_failures do\n          patch casa_case_url(casa_case, format: :json), params: {casa_case: invalid_attributes}\n\n          expect(response.content_type).to eq(\"application/json; charset=utf-8\")\n          expect(response).to have_http_status(:unprocessable_content)\n          expect(response.body).to match([\"Case number can't be blank\"].to_json)\n        end\n      end\n\n      describe \"court orders\" do\n        context \"when the user tries to make an existing order empty\" do\n          let(:orders_updated) do\n            {\n              case_court_orders_attributes: {\n                \"0\" => {\n                  text: \"New Court Order Text One Updated\",\n                  implementation_status: :unimplemented\n                },\n                \"1\" => {\n                  text: \"\"\n                }\n              }\n            }\n          end\n\n          before do\n            patch casa_case_url(casa_case), params: {casa_case: new_attributes2}\n            casa_case.reload\n\n            orders_updated[:case_court_orders_attributes][\"0\"][:id] = casa_case.case_court_orders[0].id\n            orders_updated[:case_court_orders_attributes][\"1\"][:id] = casa_case.case_court_orders[1].id\n          end\n\n          it \"does not update the first court order\" do\n            expect { patch casa_case_url(casa_case), params: {casa_case: orders_updated} }.not_to(\n              change { casa_case.reload.case_court_orders[0].text }\n            )\n          end\n\n          it \"does not update the second court order\" do\n            expect { patch casa_case_url(casa_case), params: {casa_case: orders_updated} }.not_to(\n              change { casa_case.reload.case_court_orders[1].text }\n            )\n          end\n        end\n      end\n\n      it \"does not update across organizations\" do\n        other_org = build(:casa_org)\n        other_casa_case = create(:casa_case, case_number: \"abc\", casa_org: other_org)\n\n        expect { patch casa_case_url(other_casa_case), params: {casa_case: new_attributes} }.not_to(\n          change { other_casa_case.reload.case_number }\n        )\n      end\n    end\n\n    describe \"PATCH /casa_cases/:id/deactivate\" do\n      let(:casa_case) { create(:casa_case, :active, casa_org: organization, case_number: \"111\") }\n      let(:params) { {id: casa_case.id} }\n\n      it \"deactivates the requested casa_case\" do\n        patch deactivate_casa_case_path(casa_case), params: params\n        casa_case.reload\n        expect(casa_case.active).to eq false\n      end\n\n      it \"redirects to the casa_case\" do\n        patch deactivate_casa_case_path(casa_case), params: params\n        casa_case.reload\n        expect(response).to redirect_to(edit_casa_case_path)\n      end\n\n      it \"flashes success message\" do\n        patch deactivate_casa_case_path(casa_case), params: params\n        expect(flash[:notice]).to include(\"Case #{casa_case.case_number} has been deactivated.\")\n      end\n\n      it \"fails across organizations\" do\n        other_org = build(:casa_org)\n        other_casa_case = create(:casa_case, casa_org: other_org)\n\n        patch deactivate_casa_case_path(other_casa_case), params: params\n        expect(response).to be_redirect\n        expect(flash[:notice]).to eq(\"Sorry, you are not authorized to perform this action.\")\n      end\n\n      it \"also responds as json\", :aggregate_failures do\n        patch deactivate_casa_case_path(casa_case, format: :json), params: params\n\n        expect(response.content_type).to eq(\"application/json; charset=utf-8\")\n        expect(response).to have_http_status(:ok)\n        expect(response.body).to match(\"Case #{casa_case.case_number} has been deactivated.\")\n      end\n\n      context \"when deactivation fails\" do\n        before do\n          allow_any_instance_of(CasaCase).to receive(:deactivate).and_return(false)\n        end\n\n        it \"does not deactivate the requested casa_case\" do\n          patch deactivate_casa_case_path(casa_case), params: params\n          casa_case.reload\n          expect(casa_case.active).to eq true\n        end\n\n        it \"also responds as json\", :aggregate_failures do\n          patch deactivate_casa_case_path(casa_case, format: :json), params: params\n\n          expect(response.content_type).to eq(\"application/json; charset=utf-8\")\n          expect(response).to have_http_status(:unprocessable_content)\n          expect(response.body).to match([].to_json)\n        end\n      end\n    end\n\n    describe \"PATCH /casa_cases/:id/reactivate\" do\n      let(:casa_case) { create(:casa_case, :inactive, casa_org: organization, case_number: \"111\") }\n      let(:params) { {id: casa_case.id} }\n\n      it \"reactivates the requested casa_case\" do\n        patch reactivate_casa_case_path(casa_case), params: params\n        casa_case.reload\n        expect(casa_case.active).to eq true\n      end\n\n      it \"redirects to the casa_case\" do\n        patch reactivate_casa_case_path(casa_case), params: params\n        casa_case.reload\n        expect(response).to redirect_to(edit_casa_case_path)\n      end\n\n      it \"flashes success message\" do\n        patch reactivate_casa_case_path(casa_case), params: params\n        expect(flash[:notice]).to include(\"Case #{casa_case.case_number} has been reactivated.\")\n      end\n\n      it \"fails across organizations\" do\n        other_org = create(:casa_org)\n        other_casa_case = create(:casa_case, casa_org: other_org)\n\n        patch reactivate_casa_case_path(other_casa_case), params: params\n        expect(response).to be_redirect\n        expect(flash[:notice]).to eq(\"Sorry, you are not authorized to perform this action.\")\n      end\n\n      it \"also responds as json\", :aggregate_failures do\n        patch reactivate_casa_case_path(casa_case, format: :json), params: params\n\n        expect(response.content_type).to eq(\"application/json; charset=utf-8\")\n        expect(response).to have_http_status(:ok)\n        expect(response.body).to match(\"Case #{casa_case.case_number} has been reactivated.\")\n      end\n\n      context \"when reactivation fails\" do\n        before do\n          allow_any_instance_of(CasaCase).to receive(:reactivate).and_return(false)\n        end\n\n        it \"does not reactivate the requested casa_case\" do\n          patch deactivate_casa_case_path(casa_case), params: params\n          casa_case.reload\n          expect(casa_case.active).to eq false\n        end\n\n        it \"also responds as json\", :aggregate_failures do\n          patch reactivate_casa_case_path(casa_case, format: :json), params: params\n\n          expect(response.content_type).to eq(\"application/json; charset=utf-8\")\n          expect(response).to have_http_status(:unprocessable_content)\n          expect(response.body).to match([].to_json)\n        end\n      end\n    end\n  end\n\n  describe \"as a volunteer\" do\n    let(:user) { create(:volunteer, casa_org: organization) }\n    let!(:case_assignment) { create(:case_assignment, volunteer: user, casa_case: casa_case) }\n\n    describe \"GET /show\" do\n      it \"renders a successful response\" do\n        get casa_case_url(casa_case)\n        expect(response).to be_successful\n      end\n\n      it \"fails across organizations\" do\n        other_org = build(:casa_org)\n        other_case = create(:casa_case, casa_org: other_org)\n\n        get casa_case_url(other_case)\n        expect(response).to be_redirect\n        expect(flash[:notice]).to eq(\"Sorry, you are not authorized to perform this action.\")\n      end\n    end\n\n    describe \"GET /new\" do\n      it \"denies access and redirects elsewhere\" do\n        get new_casa_case_url\n\n        expect(response).not_to be_successful\n        expect(flash[:notice]).to match(/you are not authorized/)\n      end\n    end\n\n    describe \"POST /create\" do\n      context \"with valid parameters\" do\n        it \"denies access\" do\n          post casa_cases_url, params: {casa_case: valid_attributes}\n\n          expect(response).not_to be_successful\n          expect(flash[:notice]).to match(/you are not authorized/)\n        end\n      end\n    end\n\n    describe \"GET /edit\" do\n      it \"render a successful response\" do\n        get edit_casa_case_url(casa_case)\n        expect(response).to be_successful\n      end\n\n      it \"fails across organizations\" do\n        other_org = build(:casa_org)\n        other_case = create(:casa_case, casa_org: other_org)\n\n        get edit_casa_case_url(other_case)\n        expect(response).to be_redirect\n        expect(flash[:notice]).to eq(\"Sorry, you are not authorized to perform this action.\")\n      end\n    end\n\n    describe \"PATCH /update\" do\n      let(:new_attributes) {\n        {\n          case_number: \"12345\",\n          court_report_status: :submitted,\n          case_court_orders_attributes: orders_attributes\n        }\n      }\n\n      context \"with valid parameters\" do\n        it \"updates permitted fields\" do\n          patch casa_case_url(casa_case), params: {casa_case: new_attributes}\n          casa_case.reload\n\n          expect(casa_case.court_report_submitted?).to be_truthy\n\n          # Not permitted\n          expect(casa_case.case_number).to eq \"111\"\n          expect(casa_case.case_court_orders.size).to be 2\n        end\n\n        it \"redirects to the casa_case\" do\n          patch casa_case_url(casa_case), params: {casa_case: new_attributes}\n          expect(response).to redirect_to(edit_casa_case_path(casa_case))\n        end\n\n        it \"also responds as json\", :aggregate_failures do\n          patch casa_case_url(casa_case, format: :json), params: {casa_case: new_attributes}\n\n          expect(response.content_type).to eq(\"application/json; charset=utf-8\")\n          expect(response).to have_http_status(:ok)\n          expect(response.body).not_to match(new_attributes[:case_number].to_json)\n        end\n      end\n\n      it \"does not update across organizations\" do\n        other_org = build(:casa_org)\n        other_casa_case = create(:casa_case, case_number: \"abc\", casa_org: other_org)\n\n        expect { patch casa_case_url(other_casa_case), params: {casa_case: new_attributes} }.not_to(\n          change { other_casa_case.reload.attributes }\n        )\n      end\n    end\n\n    describe \"GET /index\" do\n      it \"shows only cases assigned to user\" do\n        mine = build(:casa_case, casa_org: organization, case_number: SecureRandom.hex(32))\n        other = build(:casa_case, casa_org: organization, case_number: SecureRandom.hex(32))\n\n        user.casa_cases << mine\n\n        get casa_cases_url\n\n        expect(response).to be_successful\n        expect(response.body).to include(mine.case_number)\n        expect(response.body).not_to include(other.case_number)\n      end\n    end\n\n    describe \"PATCH /casa_cases/:id/deactivate\" do\n      let(:casa_case) { build(:casa_case, :active, casa_org: organization, case_number: \"111\") }\n      let(:params) { {id: casa_case.id} }\n\n      it \"does not deactivate the requested casa_case\" do\n        patch deactivate_casa_case_path(casa_case), params: params\n        casa_case.reload\n        expect(casa_case.active).to eq true\n      end\n    end\n\n    describe \"PATCH /casa_cases/:id/reactivate\" do\n      let(:casa_case) { build(:casa_case, :inactive, casa_org: organization, case_number: \"111\") }\n      let(:params) { {id: casa_case.id} }\n\n      it \"does not deactivate the requested casa_case\" do\n        patch deactivate_casa_case_path(casa_case), params: params\n        casa_case.reload\n        expect(casa_case.active).to eq false\n      end\n    end\n  end\n\n  describe \"as a supervisor\" do\n    let(:user) { create(:supervisor, casa_org: organization) }\n\n    describe \"GET /show\" do\n      it \"renders a successful response\" do\n        get casa_case_url(casa_case)\n        expect(response).to be_successful\n      end\n\n      it \"fails across organizations\" do\n        other_org = build(:casa_org)\n        other_case = create(:casa_case, casa_org: other_org)\n\n        get casa_case_url(other_case)\n        expect(response).to be_redirect\n        expect(flash[:notice]).to eq(\"Sorry, you are not authorized to perform this action.\")\n      end\n    end\n\n    describe \"GET /new\" do\n      it \"renders a redirect\" do\n        get new_casa_case_url\n        expect(response).to be_redirect\n        expect(flash[:notice]).to eq(\"Sorry, you are not authorized to perform this action.\")\n      end\n    end\n\n    describe \"POST /create\" do\n      context \"with valid parameters\" do\n        it \"denies access\" do\n          post casa_cases_url, params: {casa_case: valid_attributes}\n\n          expect(response).not_to be_successful\n          expect(flash[:notice]).to match(/you are not authorized/)\n        end\n      end\n    end\n\n    describe \"GET /edit\" do\n      it \"render a successful response\" do\n        get edit_casa_case_url(casa_case)\n        expect(response).to be_successful\n      end\n\n      it \"fails across organizations\" do\n        other_org = build(:casa_org)\n        other_case = create(:casa_case, casa_org: other_org)\n\n        get edit_casa_case_url(other_case)\n        expect(response).to be_redirect\n        expect(flash[:notice]).to eq(\"Sorry, you are not authorized to perform this action.\")\n      end\n    end\n\n    describe \"PATCH /update\" do\n      let(:group) { build(:contact_type_group) }\n      let(:type1) { create(:contact_type, contact_type_group: group) }\n      let(:new_attributes) do\n        {\n          case_number: \"12345\",\n          court_report_status: :completed,\n          case_court_orders_attributes: orders_attributes\n        }\n      end\n      let(:new_attributes2) do\n        {\n          case_number: \"12345\",\n          court_report_status: :completed,\n          case_court_orders_attributes: orders_attributes,\n          contact_type_ids: [type1.id]\n        }\n      end\n\n      context \"with valid parameters\" do\n        it \"updates fields (except case_number)\" do\n          patch casa_case_url(casa_case), params: {casa_case: new_attributes2}\n          casa_case.reload\n\n          expect(casa_case.case_number).to eq \"111\"\n          expect(casa_case.court_report_completed?).to be true\n\n          expect(casa_case.case_court_orders[0].text).to eq texts[0]\n          expect(casa_case.case_court_orders[0].implementation_status).to eq implementation_statuses[0]\n\n          expect(casa_case.case_court_orders[1].text).to eq texts[1]\n          expect(casa_case.case_court_orders[1].implementation_status).to eq implementation_statuses[1]\n        end\n\n        it \"redirects to the casa_case\" do\n          patch casa_case_url(casa_case), params: {casa_case: new_attributes2}\n          expect(response).to redirect_to(edit_casa_case_path(casa_case))\n        end\n\n        it \"also responds as json\", :aggregate_failures do\n          patch casa_case_url(casa_case, format: :json), params: {casa_case: new_attributes2}\n\n          expect(response.content_type).to eq(\"application/json; charset=utf-8\")\n          expect(response).to have_http_status(:ok)\n          expect(response.body).not_to match(new_attributes[:case_number].to_json)\n        end\n      end\n\n      it \"does not update across organizations\" do\n        other_org = build(:casa_org)\n        other_casa_case = create(:casa_case, case_number: \"abc\", casa_org: other_org)\n\n        expect { patch casa_case_url(other_casa_case), params: {casa_case: new_attributes} }.not_to(\n          change { other_casa_case.reload.attributes }\n        )\n      end\n    end\n\n    describe \"GET /index\" do\n      it \"renders a successful response\" do\n        build_stubbed(:casa_case)\n        get casa_cases_url\n        expect(response).to be_successful\n      end\n    end\n\n    describe \"PATCH /casa_cases/:id/deactivate\" do\n      let(:casa_case) { create(:casa_case, :active, casa_org: organization, case_number: \"111\") }\n      let(:params) { {id: casa_case.id} }\n\n      it \"does not deactivate the requested casa_case\" do\n        patch deactivate_casa_case_path(casa_case), params: params\n        casa_case.reload\n        expect(casa_case.active).to eq true\n      end\n    end\n\n    describe \"PATCH /casa_cases/:id/reactivate\" do\n      let(:casa_case) { create(:casa_case, :inactive, casa_org: organization, case_number: \"111\") }\n      let(:params) { {id: casa_case.id} }\n\n      it \"does not deactivate the requested casa_case\" do\n        patch deactivate_casa_case_path(casa_case), params: params\n        casa_case.reload\n        expect(casa_case.active).to eq false\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/casa_org_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"CasaOrg\", type: :request do\n  let(:casa_org) { build(:casa_org) }\n  let(:casa_case) { build_stubbed(:casa_case, casa_org: casa_org) }\n\n  before {\n    stub_twilio\n    sign_in create(:casa_admin, casa_org: casa_org)\n  }\n\n  describe \"GET /edit\" do\n    subject(:request) do\n      get edit_casa_org_url(casa_org)\n\n      response\n    end\n\n    it { is_expected.to be_successful }\n  end\n\n  describe \"PATCH /update\" do\n    context \"with valid parameters\" do\n      subject(:request) do\n        patch casa_org_url(casa_org), params: {casa_org: attributes}\n\n        response\n      end\n\n      let(:attributes) do\n        {\n          name: \"name\", display_name: \"display_name\", address: \"address\",\n          twilio_account_sid: \"articuno34\", twilio_api_key_sid: \"Aladdin\",\n          twilio_api_key_secret: \"open sesame\", twilio_phone_number: \"+12223334444\",\n          show_driving_reimbursement: \"1\", additional_expenses_enabled: \"1\"\n        }\n      end\n\n      it \"updates the requested casa_org\" do\n        request\n        expect(casa_org.reload.name).to eq \"name\"\n        expect(casa_org.display_name).to eq \"display_name\"\n        expect(casa_org.address).to eq \"address\"\n        expect(casa_org.twilio_phone_number).to eq \"+12223334444\"\n        expect(casa_org.show_driving_reimbursement).to be true\n        expect(casa_org.additional_expenses_enabled).to be true\n      end\n\n      describe \"on logo update\" do\n        subject(:request) do\n          patch casa_org_url(casa_org), params: params\n\n          response\n        end\n\n        let(:logo) { fixture_file_upload \"company_logo.png\", \"image/png\" }\n\n        context \"with a new logo\" do\n          let(:params) { {casa_org: {logo: logo}} }\n\n          it \"uploads the company logo\" do\n            expect { request }.to change(ActiveStorage::Attachment, :count).by(1)\n          end\n        end\n\n        context \"with no logo\" do\n          let(:params) { {casa_org: {name: \"name\"}} }\n\n          it \"does not revert logo to default\" do\n            casa_org.update(logo: logo)\n\n            expect { request }.not_to change(ActiveStorage::Attachment, :count)\n          end\n        end\n      end\n\n      context \"and html format\" do\n        it { is_expected.to redirect_to(edit_casa_org_url) }\n\n        it \"shows the correct flash message\" do\n          request\n          expect(flash[:notice]).to eq(\"CASA organization was successfully updated.\")\n        end\n      end\n\n      context \"and json format\" do\n        subject(:request) do\n          patch casa_org_url(casa_org, format: :json), params: {casa_org: attributes}\n\n          response\n        end\n\n        it { is_expected.to have_http_status(:ok) }\n\n        it \"returns correct payload\", :aggregate_failures do\n          response_data = request.body\n          expect(response_data).to match(\"display_name\".to_json)\n        end\n      end\n    end\n\n    context \"with invalid parameters\" do\n      subject(:request) do\n        patch casa_org_url(casa_org), params: params\n\n        response\n      end\n\n      let(:params) { {casa_org: {name: nil}} }\n\n      it \"does not update the requested casa_org\" do\n        expect { request }.not_to change { casa_org.reload.name }\n      end\n\n      context \"and html format\" do\n        it { is_expected.to have_http_status(:unprocessable_content) }\n\n        it \"renders the edit template\" do\n          expect(request.body).to match(/error_explanation/)\n        end\n      end\n\n      context \"and json format\" do\n        subject(:request) do\n          patch casa_org_url(casa_org, format: :json), params: params\n\n          response\n        end\n\n        it { is_expected.to have_http_status(:unprocessable_content) }\n\n        it \"returns correct payload\" do\n          response_data = request.body\n          expect(response_data).to match(\"Name can't be blank\".to_json)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/case_assignments_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"/case_assignments\", type: :request do\n  let(:casa_org) { create(:casa_org) }\n  let(:admin) { create(:casa_admin, casa_org: casa_org) }\n  let(:volunteer) { create(:volunteer, casa_org: casa_org) }\n  let(:casa_case) { create(:casa_case, casa_org: casa_org) }\n\n  describe \"POST /create\" do\n    before { sign_in admin }\n\n    it \"authorizes action\" do\n      expect_any_instance_of(CaseAssignmentsController).to receive(:authorize).with(CaseAssignment).and_call_original\n      post case_assignments_url(volunteer_id: volunteer.id), params: {case_assignment: {casa_case_id: casa_case.id}}\n    end\n\n    context \"when the volunteer has been previously assigned to the casa_case\" do\n      subject(:request) do\n        post case_assignments_url(casa_case_id: casa_case.id), params: params\n\n        response\n      end\n\n      let!(:case_assignment) { create(:case_assignment, active: false, volunteer: volunteer, casa_case: casa_case) }\n      let(:params) { {case_assignment: {volunteer_id: volunteer.id}} }\n\n      it \"reassigns the volunteer to the casa_case\" do\n        expect { request }.to change { casa_case.case_assignments.first.active }.from(false).to(true)\n      end\n\n      it { is_expected.to redirect_to edit_casa_case_path(casa_case) }\n\n      it \"sets flash message correctly\" do\n        request\n        expect(flash.notice).to eq \"Volunteer reassigned to case\"\n      end\n\n      context \"when missing params\" do\n        let(:params) { {case_assignment: {volunteer_id: \"\"}} }\n\n        it { is_expected.to redirect_to edit_casa_case_path(casa_case) }\n\n        it \"sets flash message correctly\" do\n          request\n          expect(flash.alert).to match(/Unable to assign volunteer to case/)\n        end\n      end\n    end\n\n    context \"when the case assignment parent is a volunteer\" do\n      subject(:request) do\n        post case_assignments_url(volunteer_id: volunteer.id), params: params\n\n        response\n      end\n\n      let(:params) { {case_assignment: {casa_case_id: casa_case.id}} }\n\n      it \"creates a new case assignment for the volunteer\" do\n        expect { request }.to change(volunteer.casa_cases, :count).by(1)\n      end\n\n      it { is_expected.to redirect_to edit_volunteer_path(volunteer) }\n\n      it \"sets flash message correctly\" do\n        request\n        expect(flash.notice).to eq \"Volunteer assigned to case\"\n      end\n\n      context \"when missing params\" do\n        let(:params) { {case_assignment: {volunteer_id: \"\"}} }\n\n        it { is_expected.to redirect_to edit_volunteer_path(volunteer) }\n\n        it \"sets flash message correctly\" do\n          request\n          expect(flash.alert).to match(/Unable to assign volunteer to case/)\n        end\n      end\n    end\n\n    context \"when the case assignment parent is a casa_case\" do\n      subject(:request) do\n        post case_assignments_url(casa_case_id: casa_case.id), params: params\n\n        response\n      end\n\n      let(:params) { {case_assignment: {volunteer_id: volunteer.id}} }\n\n      it \"creates a new case assignment for the casa_case\" do\n        expect { request }.to change(casa_case.volunteers, :count).by(1)\n      end\n\n      it { is_expected.to redirect_to edit_casa_case_path(casa_case) }\n\n      it \"sets flash message correctly\" do\n        request\n        expect(flash.notice).to eq \"Volunteer assigned to case\"\n      end\n\n      context \"when missing params\" do\n        let(:params) { {case_assignment: {volunteer_id: \"\"}} }\n\n        it { is_expected.to redirect_to edit_casa_case_path(casa_case) }\n\n        it \"sets flash message correctly\" do\n          request\n          expect(flash.alert).to match(/Unable to assign volunteer to case/)\n        end\n      end\n    end\n\n    describe \"with another org params\" do\n      subject(:request) do\n        post url, params: params\n\n        response\n      end\n\n      let(:other_org) { build(:casa_org) }\n\n      context \"when the case belongs to another organization\" do\n        let(:other_casa_case) { create(:casa_case, casa_org: other_org) }\n        let(:url) { case_assignments_url(casa_case_id: other_casa_case.id) }\n        let(:params) { {case_assignment: {volunteer_id: volunteer.id}} }\n\n        it \"does not create a case assignment\" do\n          expect { request }.not_to change(other_casa_case.volunteers, :count)\n        end\n      end\n\n      context \"when the volunteer belongs to another organization\" do\n        let(:other_volunteer) { build_stubbed(:volunteer, casa_org: other_org) }\n        let(:url) { case_assignments_url(casa_case_id: casa_case.id) }\n        let(:params) { {case_assignment: {volunteer_id: other_volunteer.id}} }\n\n        it \"does not create a case assignment\" do\n          expect { request }.not_to change(casa_case.volunteers, :count)\n        end\n      end\n    end\n  end\n\n  describe \"DELETE /destroy\" do\n    before { sign_in admin }\n\n    let!(:assignment) { create(:case_assignment, volunteer: volunteer, casa_case: casa_case) }\n\n    it \"authorizes action\" do\n      expect_any_instance_of(CaseAssignmentsController).to receive(:authorize).with(assignment).and_call_original\n      delete case_assignment_url(assignment, volunteer_id: volunteer.id)\n    end\n\n    context \"when the case assignment parent is a volunteer\" do\n      subject(:request) do\n        delete case_assignment_url(assignment, volunteer_id: volunteer.id)\n\n        response\n      end\n\n      it \"destroys the case assignment from the volunteer\" do\n        expect { request }.to change(volunteer.casa_cases, :count).by(-1)\n      end\n\n      it { is_expected.to redirect_to edit_volunteer_path(volunteer) }\n    end\n\n    context \"when the case assignment parent is a casa_case\" do\n      subject(:request) do\n        delete case_assignment_url(assignment, casa_case_id: casa_case.id)\n\n        response\n      end\n\n      it \"destroys the case assignment from the casa_case\" do\n        expect { request }.to change(casa_case.volunteers, :count).by(-1)\n      end\n\n      it { is_expected.to redirect_to edit_casa_case_path(casa_case) }\n    end\n\n    context \"when the case belongs to another organization\" do\n      subject(:request) do\n        delete case_assignment_url(assignment, casa_case_id: other_casa_case.id)\n\n        response\n      end\n\n      let(:other_org) { build(:casa_org) }\n      let(:other_casa_case) { create(:casa_case, casa_org: other_org) }\n      let!(:assignment) { create(:case_assignment, casa_case: other_casa_case) }\n\n      it \"does not destroy the case assignment\" do\n        expect { request }.not_to change(other_casa_case.volunteers, :count).from(1)\n      end\n\n      it { is_expected.to be_not_found }\n    end\n  end\n\n  describe \"PATCH /unassign\" do\n    subject(:request) do\n      patch unassign_case_assignment_url(assignment, redirect_to_path: redirect_to_path)\n\n      response\n    end\n\n    before { sign_in admin }\n\n    let(:assignment) { create(:case_assignment, volunteer: volunteer, casa_case: casa_case) }\n    let(:redirect_to_path) { \"\" }\n\n    it \"authorizes action\" do\n      expect_any_instance_of(CaseAssignmentsController).to(\n        receive(:authorize).with(assignment, :unassign?).and_call_original\n      )\n      request\n    end\n\n    it \"deactivates the case assignment\" do\n      expect { request }.to change { assignment.reload.active? }.to(false)\n    end\n\n    it { is_expected.to redirect_to edit_casa_case_path(casa_case) }\n\n    it \"sets flash message correctly\" do\n      request\n      expect(flash.notice).to eq \"Volunteer was unassigned from Case #{casa_case.case_number}.\"\n    end\n\n    context \"when request format is json\" do\n      subject(:request) do\n        patch unassign_case_assignment_url(assignment, format: :json)\n\n        response\n      end\n\n      it \"sets body message correctly\" do\n        response_body = request.body\n        expect(response_body).to eq \"Volunteer was unassigned from Case #{casa_case.case_number}.\"\n      end\n    end\n\n    context \"when redirect_to_path is volunteer\" do\n      let(:redirect_to_path) { \"volunteer\" }\n\n      it { is_expected.to redirect_to edit_volunteer_path(volunteer) }\n    end\n\n    context \"when assignment belongs to another organization\" do\n      let(:other_org) { build(:casa_org) }\n      let(:other_casa_case) { create(:casa_case, casa_org: other_org) }\n      let(:assignment) { create(:case_assignment, casa_case: other_casa_case) }\n\n      it \"does not deactivate the case assignment\" do\n        expect { request }.not_to change { assignment.reload.active? }\n      end\n    end\n  end\n\n  describe \"PATCH /show_hide_contacts\" do\n    subject(:request) do\n      patch show_hide_contacts_case_assignment_path(assignment)\n\n      response\n    end\n\n    before { sign_in admin }\n\n    let(:assignment) { create(:case_assignment, casa_case: casa_case, volunteer: volunteer, active: false) }\n\n    it \"authorizes action\" do\n      expect_any_instance_of(CaseAssignmentsController).to(\n        receive(:authorize).with(assignment, :show_or_hide_contacts?).and_call_original\n      )\n      request\n    end\n\n    context \"when case contacts are visible\" do\n      it \"toggles to hide case contacts\" do\n        expect { request }.to change { assignment.reload.hide_old_contacts? }\n      end\n\n      it { is_expected.to redirect_to edit_casa_case_path(casa_case) }\n\n      it \"sets flash message correctly\" do\n        request\n        expect(flash.notice).to eq \"Old Case Contacts created by #{volunteer.display_name} were successfully hidden.\"\n      end\n    end\n\n    context \"when case contacts are hidden\" do\n      let(:assignment) do\n        create(:case_assignment, casa_case: casa_case, volunteer: volunteer, active: false, hide_old_contacts: true)\n      end\n\n      it \"toggles to show case contacts\" do\n        expect { request }.to change { assignment.reload.hide_old_contacts? }\n      end\n\n      it { is_expected.to redirect_to edit_casa_case_path(casa_case) }\n\n      it \"sets flash message correctly\" do\n        request\n        expect(flash.notice).to eq \"Old Case Contacts created by #{volunteer.display_name} are now visible.\"\n      end\n    end\n\n    # Note: we don't expect this endpoint to be exposed in the UI if the case is inactive\n    context \"when the case_assignment is active\" do\n      let(:assignment) { create(:case_assignment, casa_case: casa_case, volunteer: volunteer, active: true) }\n\n      it \"does not toggle contacts visibility\" do\n        expect { request }.not_to change { assignment.reload.hide_old_contacts? }\n      end\n\n      it \"redirects to root with an authorization failure message\" do\n        request\n        expect(response).to redirect_to root_path\n        expect(response.request.flash[:notice]).to match \"not authorized\"\n      end\n    end\n  end\n\n  describe \"PATCH /reimbursement\" do\n    subject(:request) do\n      patch reimbursement_case_assignment_url(assignment)\n\n      response\n    end\n\n    before { sign_in admin }\n\n    let(:assignment) { create(:case_assignment, casa_case: casa_case, volunteer: volunteer) }\n\n    it \"authorizes action\" do\n      expect_any_instance_of(CaseAssignmentsController).to(\n        receive(:authorize).with(assignment, :reimbursement?).and_call_original\n      )\n      request\n    end\n\n    it \"toggles allow_reimbursement\" do\n      expect { request }.to change { assignment.reload.allow_reimbursement }\n    end\n\n    it { is_expected.to redirect_to edit_casa_case_path(casa_case) }\n\n    it \"sets flash message correctly\" do\n      request\n      expect(flash.notice).to eq \"Volunteer allow reimbursement changed from Case #{casa_case.case_number}.\"\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/case_contact_reports_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"/case_contact_reports\", type: :request do\n  let!(:case_contact) { build(:case_contact) }\n\n  before do\n    travel_to Time.zone.local(2020, 1, 1)\n    sign_in user\n  end\n\n  describe \"GET /case_contact_reports\" do\n    context \"as volunteer\" do\n      let(:user) { build(:volunteer) }\n\n      it \"cannot view reports\" do\n        get case_contact_reports_url(format: :csv), params: {report: {}}\n        expect(response).to redirect_to root_path\n      end\n    end\n\n    shared_examples \"can view reports\" do\n      context \"with start_date and end_date\" do\n        let(:case_contact_report_params) do\n          {\n            start_date: 1.month.ago,\n            end_date: Date.today\n          }\n        end\n\n        it \"renders a csv file to download\" do\n          get case_contact_reports_url(format: :csv), params: {report: {start_date: 1.month.ago, end_date: Date.today}}\n\n          expect(response).to be_successful\n          expect(\n            response.headers[\"Content-Disposition\"]\n          ).to include 'attachment; filename=\"case-contacts-report-1577836800.csv'\n        end\n      end\n\n      context \"without start_date and end_date\" do\n        it \"renders a csv file to download\" do\n          get case_contact_reports_url(format: :csv), params: {report: {start_date: \"\", end_date: \"\"}}\n\n          expect(response).to be_successful\n          expect(\n            response.headers[\"Content-Disposition\"]\n          ).to include 'attachment; filename=\"case-contacts-report-1577836800.csv'\n        end\n      end\n\n      context \"with supervisor_ids filter\" do\n        it \"renders csv with only the volunteer\" do\n          volunteer = create(:volunteer)\n          casa_case = create(:casa_case, casa_org: volunteer.casa_org)\n          contact = create(:case_contact, creator_id: volunteer.id, casa_case: casa_case)\n          build_stubbed(:case_contact, creator_id: user.id, casa_case: casa_case)\n\n          get case_contact_reports_url(format: :csv), params: {report: {creator_ids: [volunteer.id]}}\n\n          expect(response).to be_successful\n          expect(\n            response.headers[\"Content-Disposition\"]\n          ).to include 'attachment; filename=\"case-contacts-report-'\n          expect(response.body).to match(/^#{contact.id},/)\n          expect(response.body.lines.length).to eq(2)\n        end\n      end\n\n      context \"casa_case_ids filter\" do\n        let!(:casa_case) { create(:casa_case) }\n        let!(:case_contacts) { create_list(:case_contact, 3, casa_case: casa_case) }\n\n        before { create_list(:case_contact, 5) }\n\n        it \"returns success with proper headers\" do\n          get case_contact_reports_url(format: :csv),\n            params: {report: {casa_case_ids: [casa_case.id]}}\n\n          expect(response).to be_successful\n          expect(\n            response.headers[\"Content-Disposition\"]\n          ).to include 'attachment; filename=\"case-contacts-report-'\n        end\n\n        context \"when filter is provided\" do\n          it \"renders csv with contacts from the casa cases\" do\n            get case_contact_reports_url(format: :csv),\n              params: {report: {casa_case_ids: [casa_case.id]}}\n\n            expect(response.body.lines.length).to eq(4)\n\n            case_contacts.each do |contact|\n              expect(response.body).to match(/^#{contact.id}/)\n            end\n          end\n        end\n\n        context \"when filter not provided\" do\n          it \"renders a csv with all case contacts\" do\n            get case_contact_reports_url(format: :csv),\n              params: {report: {casa_case_ids: nil}}\n\n            expect(response.body.lines.length).to eq(9)\n\n            CaseContact.all.pluck(:id).each do |id|\n              expect(response.body).to match(/^#{id}/)\n            end\n          end\n        end\n      end\n    end\n\n    context \"as supervisor\" do\n      it_behaves_like \"can view reports\" do\n        let(:user) { build(:supervisor) }\n      end\n    end\n\n    context \"as casa_admin\" do\n      it_behaves_like \"can view reports\" do\n        let(:user) { build(:casa_admin) }\n      end\n\n      let(:user) { build(:casa_admin) }\n\n      it \"passes in casa_org_id to CaseContractReport\" do\n        allow(CaseContactReport).to receive(:new).and_return([])\n\n        get case_contact_reports_url(format: :csv), params: {report: {creator_ids: [user.id]}}\n\n        expect(CaseContactReport).to have_received(:new)\n          .with(hash_including(casa_org_id: user.casa_org_id))\n      end\n    end\n  end\n\n  def case_contact_report_params\n    {\n      start_date: 1.month.ago,\n      end_date: Date.today\n    }\n  end\nend\n"
  },
  {
    "path": "spec/requests/case_contacts/case_contacts_new_design_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"/case_contacts_new_design\", type: :request do\n  let(:organization) { create(:casa_org) }\n  let(:admin) { create(:casa_admin, casa_org: organization) }\n\n  before { sign_in admin }\n\n  context \"when new_case_contact_table flag is disabled\" do\n    before do\n      allow(Flipper).to receive(:enabled?).with(:new_case_contact_table).and_return(false)\n    end\n\n    describe \"GET /index\" do\n      it \"redirects to case_contacts_path\" do\n        get case_contacts_new_design_path\n        expect(response).to redirect_to(case_contacts_path)\n      end\n\n      it \"sets an alert message\" do\n        get case_contacts_new_design_path\n        expect(flash[:alert]).to eq(\"This feature is not available.\")\n      end\n    end\n  end\n\n  context \"when new_case_contact_table flag is enabled\" do\n    before do\n      allow(Flipper).to receive(:enabled?).with(:new_case_contact_table).and_return(true)\n    end\n\n    describe \"GET /index\" do\n      subject(:request) do\n        get case_contacts_new_design_path\n\n        response\n      end\n\n      let!(:casa_case) { create(:casa_case, casa_org: organization) }\n      let!(:past_contact) { create(:case_contact, :active, casa_case: casa_case, occurred_at: 3.weeks.ago) }\n      let!(:recent_contact) { create(:case_contact, :active, casa_case: casa_case, occurred_at: 3.days.ago) }\n      let!(:draft_contact) { create(:case_contact, casa_case: casa_case, occurred_at: 5.days.ago, status: \"started\") }\n\n      it { is_expected.to have_http_status(:success) }\n\n      it \"lists exactly two active contacts and one draft\" do\n        doc = Nokogiri::HTML(request.body)\n        case_contact_rows = doc.css('[data-testid=\"case_contact-row\"]')\n        expect(case_contact_rows.size).to eq(3)\n      end\n\n      it \"shows the draft badge exactly once\" do\n        doc = Nokogiri::HTML(request.body)\n        expect(doc.css('[data-testid=\"draft-badge\"]').count).to eq(1)\n      end\n\n      it \"orders contacts by occurred_at desc\" do\n        body = request.body\n\n        recent_index = body.index(I18n.l(recent_contact.occurred_at, format: :full))\n        past_index = body.index(I18n.l(past_contact.occurred_at, format: :full))\n\n        expect(recent_index).to be < past_index\n      end\n    end\n\n    describe \"POST /datatable\" do\n      let!(:casa_case) { create(:casa_case, casa_org: organization) }\n      let!(:case_contact) { create(:case_contact, :active, casa_case: casa_case, occurred_at: 3.days.ago) }\n\n      let(:datatable_params) do\n        {\n          draw: \"1\",\n          start: \"0\",\n          length: \"10\",\n          search: {value: \"\"},\n          order: {\"0\" => {column: \"0\", dir: \"desc\"}},\n          columns: {\n            \"0\" => {name: \"occurred_at\", orderable: \"true\"}\n          }\n        }\n      end\n\n      context \"when user is authorized\" do\n        it \"returns JSON with case contacts data\" do\n          post datatable_case_contacts_new_design_path, params: datatable_params, as: :json\n\n          expect(response).to have_http_status(:success)\n          expect(response.content_type).to include(\"application/json\")\n\n          json = JSON.parse(response.body, symbolize_names: true)\n          expect(json).to have_key(:data)\n          expect(json).to have_key(:recordsTotal)\n          expect(json).to have_key(:recordsFiltered)\n        end\n\n        it \"includes case contact in the data array\" do\n          post datatable_case_contacts_new_design_path, params: datatable_params, as: :json\n\n          json = JSON.parse(response.body, symbolize_names: true)\n          expect(json[:data]).to be_an(Array)\n          expect(json[:data].first[:id]).to eq(case_contact.id.to_s)\n        end\n\n        it \"handles search parameter\" do\n          searchable_contact = create(:case_contact, :active,\n            casa_case: casa_case,\n            creator: create(:volunteer, display_name: \"John Doe\", casa_org: organization))\n\n          search_params = datatable_params.merge(search: {value: \"John\"})\n          post datatable_case_contacts_new_design_path, params: search_params, as: :json\n\n          json = JSON.parse(response.body, symbolize_names: true)\n          ids = json[:data].pluck(:id)\n          expect(ids).to include(searchable_contact.id.to_s)\n        end\n      end\n\n      context \"when user is a volunteer\" do\n        let(:volunteer) { create(:volunteer, casa_org: organization) }\n\n        before { sign_in volunteer }\n\n        it \"allows access to datatable endpoint\" do\n          post datatable_case_contacts_new_design_path, params: datatable_params, as: :json\n\n          expect(response).to have_http_status(:success)\n        end\n\n        it \"only returns case contacts created by the volunteer\" do\n          volunteer_contact = create(:case_contact, :active, casa_case: casa_case, creator: volunteer)\n          other_volunteer_contact = create(:case_contact, :active, casa_case: casa_case,\n            creator: create(:volunteer, casa_org: organization))\n\n          post datatable_case_contacts_new_design_path, params: datatable_params, as: :json\n\n          json = JSON.parse(response.body, symbolize_names: true)\n          ids = json[:data].pluck(:id)\n\n          expect(ids).to include(volunteer_contact.id.to_s)\n          expect(ids).not_to include(other_volunteer_contact.id.to_s)\n        end\n      end\n\n      context \"when user is a supervisor\" do\n        let(:supervisor) { create(:supervisor, casa_org: organization) }\n\n        before { sign_in supervisor }\n\n        it \"allows access to datatable endpoint\" do\n          post datatable_case_contacts_new_design_path, params: datatable_params, as: :json\n\n          expect(response).to have_http_status(:success)\n        end\n\n        it \"returns all case contacts in the organization\" do\n          contact1 = create(:case_contact, :active, casa_case: casa_case,\n            creator: create(:volunteer, casa_org: organization))\n          contact2 = create(:case_contact, :active, casa_case: casa_case,\n            creator: create(:volunteer, casa_org: organization))\n\n          post datatable_case_contacts_new_design_path, params: datatable_params, as: :json\n\n          json = JSON.parse(response.body, symbolize_names: true)\n          ids = json[:data].pluck(:id)\n\n          expect(ids).to include(contact1.id.to_s, contact2.id.to_s)\n        end\n      end\n\n      context \"when user is not authenticated\" do\n        before { sign_out admin }\n\n        it \"returns unauthorized status\" do\n          post datatable_case_contacts_new_design_path, params: datatable_params, as: :json\n\n          expect(response).to have_http_status(:unauthorized)\n        end\n      end\n\n      context \"expanded content fields\" do\n        let(:contact_topic) { create(:contact_topic, casa_org: organization) }\n        let(:case_contact_with_details) do\n          create(:case_contact, :active, casa_case: casa_case, notes: \"Important follow-up\")\n        end\n\n        before do\n          create(:contact_topic_answer,\n            case_contact: case_contact_with_details,\n            contact_topic: contact_topic,\n            value: \"Youth is doing well\")\n        end\n\n        it \"includes contact_topic_answers in the response\" do\n          post datatable_case_contacts_new_design_path, params: datatable_params, as: :json\n\n          json = JSON.parse(response.body, symbolize_names: true)\n          record = json[:data].find { |d| d[:id] == case_contact_with_details.id.to_s }\n          expect(record[:contact_topic_answers]).to be_an(Array)\n          expect(record[:contact_topic_answers].first[:value]).to eq(\"Youth is doing well\")\n        end\n\n        it \"includes the topic question in contact_topic_answers\" do\n          post datatable_case_contacts_new_design_path, params: datatable_params, as: :json\n\n          json = JSON.parse(response.body, symbolize_names: true)\n          record = json[:data].find { |d| d[:id] == case_contact_with_details.id.to_s }\n          expect(record[:contact_topic_answers].first[:question]).to eq(contact_topic.question)\n        end\n\n        it \"includes notes in the response\" do\n          post datatable_case_contacts_new_design_path, params: datatable_params, as: :json\n\n          json = JSON.parse(response.body, symbolize_names: true)\n          record = json[:data].find { |d| d[:id] == case_contact_with_details.id.to_s }\n          expect(record[:notes]).to eq(\"Important follow-up\")\n        end\n\n        it \"omits blank topic answer values\" do\n          create(:contact_topic_answer,\n            case_contact: case_contact_with_details,\n            contact_topic: contact_topic,\n            value: \"\")\n\n          post datatable_case_contacts_new_design_path, params: datatable_params, as: :json\n\n          json = JSON.parse(response.body, symbolize_names: true)\n          record = json[:data].find { |d| d[:id] == case_contact_with_details.id.to_s }\n          expect(record[:contact_topic_answers].pluck(:value)).to all(be_present)\n        end\n\n        it \"returns a blank value for notes when notes are empty\" do\n          case_contact_without_notes = create(:case_contact, :active, casa_case: casa_case, notes: \"\")\n\n          post datatable_case_contacts_new_design_path, params: datatable_params, as: :json\n\n          json = JSON.parse(response.body, symbolize_names: true)\n          record = json[:data].find { |d| d[:id] == case_contact_without_notes.id.to_s }\n          expect(record[:notes]).to be_blank\n        end\n      end\n\n      context \"contact_topics field\" do\n        let(:contact_topic) { create(:contact_topic, casa_org: organization) }\n        let(:case_contact_with_topics) { create(:case_contact, :active, casa_case: casa_case) }\n\n        before do\n          case_contact_with_topics.contact_topics << contact_topic\n        end\n\n        it \"returns contact_topics as an array of strings\" do\n          post datatable_case_contacts_new_design_path, params: datatable_params, as: :json\n\n          json = JSON.parse(response.body, symbolize_names: true)\n          record = json[:data].find { |d| d[:id] == case_contact_with_topics.id.to_s }\n          expect(record[:contact_topics]).to be_an(Array)\n        end\n\n        it \"includes the topic question in the array\" do\n          post datatable_case_contacts_new_design_path, params: datatable_params, as: :json\n\n          json = JSON.parse(response.body, symbolize_names: true)\n          record = json[:data].find { |d| d[:id] == case_contact_with_topics.id.to_s }\n          expect(record[:contact_topics]).to include(contact_topic.question)\n        end\n      end\n\n      context \"with permission flags and action metadata\" do\n        let(:volunteer) { create(:volunteer, casa_org: organization) }\n        let!(:active_contact) { create(:case_contact, :active, casa_case: casa_case, creator: volunteer) }\n        let!(:draft_contact) { create(:case_contact, casa_case: casa_case, creator: volunteer, status: \"started\") }\n\n        def post_datatable\n          post datatable_case_contacts_new_design_path, params: datatable_params, as: :json\n        end\n\n        def row_for(contact_id)\n          json = JSON.parse(response.body, symbolize_names: true)\n          json[:data].find { |row| row[:id] == contact_id.to_s }\n        end\n\n        context \"when signed in as admin\" do\n          it \"includes can_edit as true\" do\n            post_datatable\n            expect(row_for(active_contact.id)[:can_edit]).to eq(\"true\")\n          end\n\n          it \"includes can_destroy as true\" do\n            post_datatable\n            expect(row_for(active_contact.id)[:can_destroy]).to eq(\"true\")\n          end\n\n          it \"includes edit_path for the contact\" do\n            post_datatable\n            expect(row_for(active_contact.id)[:edit_path]).to eq(edit_case_contact_path(active_contact))\n          end\n\n          it \"includes followup_id as empty when no followup exists\" do\n            post_datatable\n            expect(row_for(active_contact.id)[:followup_id]).to eq(\"\")\n          end\n\n          it \"includes followup_id when a requested followup exists\" do\n            followup = create(:followup, case_contact: active_contact, status: :requested, creator: admin)\n            post_datatable\n            expect(row_for(active_contact.id)[:followup_id]).to eq(followup.id.to_s)\n          end\n        end\n\n        context \"when signed in as volunteer\" do\n          before { sign_in volunteer }\n\n          it \"includes can_edit as true for their own contact\" do\n            post_datatable\n            expect(row_for(active_contact.id)[:can_edit]).to eq(\"true\")\n          end\n\n          it \"includes can_destroy as false for their own active contact\" do\n            post_datatable\n            expect(row_for(active_contact.id)[:can_destroy]).to eq(\"false\")\n          end\n\n          it \"includes can_destroy as true for their own draft contact\" do\n            post_datatable\n            expect(row_for(draft_contact.id)[:can_destroy]).to eq(\"true\")\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/case_contacts/followups_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"CaseContacts::FollowupsController\", type: :request do\n  let(:volunteer) { create(:volunteer) }\n  let(:case_contact) { create(:case_contact) }\n\n  describe \"POST /create\" do\n    subject(:request) do\n      post case_contact_followups_path(case_contact), params: params\n\n      response\n    end\n\n    let(:notification_double) { double(\"FollowupNotifier\") }\n    let(:params) { {note: \"Hello, world!\"} }\n\n    before do\n      sign_in volunteer\n      allow(FollowupNotifier).to receive(:with).and_return(notification_double)\n      allow(notification_double).to receive(:deliver)\n    end\n\n    it \"creates a followup\", :aggregate_failures do\n      expect { request }.to change(Followup, :count).by(1)\n\n      followup = Followup.last\n      expect(followup.note).to eq \"Hello, world!\"\n    end\n\n    context \"when requested as JSON\" do\n      subject(:request) do\n        post case_contact_followups_path(case_contact),\n          params: params,\n          headers: {\"Accept\" => \"application/json\"}\n\n        response\n      end\n\n      it \"returns 204 No Content\" do\n        request\n        expect(response).to have_http_status(:no_content)\n      end\n    end\n\n    it \"sends a Followup Notifier to case contact creator\" do\n      request\n      followup = Followup.last\n      expect(FollowupNotifier).to(\n        have_received(:with).once.with(followup: followup, created_by: volunteer)\n      )\n      expect(notification_double).to have_received(:deliver).once.with(case_contact.creator)\n    end\n\n    context \"with invalid case_contact\" do\n      it \"raises ActiveRecord::RecordNotFound\" do\n        expect { post case_contact_followups_path(444444) }.to raise_error(ActiveRecord::RecordNotFound)\n      end\n    end\n  end\n\n  describe \"PATCH /resolve\" do\n    let(:notification_double) { double(\"FollowupResolvedNotifier\") }\n\n    before do\n      sign_in volunteer\n      allow(FollowupResolvedNotifier).to receive(:with).and_return(notification_double)\n      allow(notification_double).to receive(:deliver)\n    end\n\n    context \"followup exists\" do\n      subject(:request) do\n        patch resolve_followup_path(followup)\n\n        response\n      end\n\n      let(:followup) { create(:followup, case_contact: case_contact, creator: volunteer) }\n\n      it \"marks it as :resolved\" do\n        followup\n        expect { request }.to change { followup.reload.resolved? }.from(false).to(true)\n      end\n\n      context \"when requested as JSON\" do\n        subject(:request) do\n          patch resolve_followup_path(followup),\n            headers: {\"Accept\" => \"application/json\"}\n\n          response\n        end\n\n        it \"returns 204 No Content\" do\n          followup\n          request\n          expect(response).to have_http_status(:no_content)\n        end\n      end\n\n      it \"does not send Followup Notifier\" do\n        followup\n        expect(FollowupResolvedNotifier).not_to receive(:with)\n        expect { request }.to change { followup.reload.resolved? }.from(false).to(true)\n      end\n\n      context \"when who resolves the followup is not the followup's creator\" do\n        let(:followup) { create(:followup, case_contact: case_contact) }\n\n        it \"sends a Followup Notifier to the creator\" do\n          request\n          expect(FollowupResolvedNotifier).to(\n            have_received(:with).once.with(followup: followup, created_by: volunteer)\n          )\n          expect(notification_double).to have_received(:deliver).once.with(followup.creator)\n        end\n      end\n    end\n\n    context \"followup doesn't exists\" do\n      it \"raises ActiveRecord::RecordNotFound\" do\n        expect { patch resolve_followup_path(444444) }.to raise_error(ActiveRecord::RecordNotFound)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/case_contacts/form_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"CaseContacts::Forms\", type: :request do\n  let(:casa_org) { build(:casa_org) }\n  let(:contact_topics) { create_list(:contact_topic, 3, casa_org:) }\n  let(:casa_admin) { create(:casa_admin, casa_org:) }\n  let(:supervisor) { create(:supervisor, casa_org:) }\n  let(:volunteer) { create(:volunteer, :with_single_case, casa_org:, supervisor: supervisor) }\n  let(:creator) { volunteer }\n  let(:casa_case) { volunteer.casa_cases.first }\n\n  let(:user) { volunteer }\n\n  before { sign_in user }\n\n  describe \"GET /new\" do\n    subject(:request) { get new_case_contact_path(casa_case_id: casa_case.id) }\n\n    it \"creates a new case_contact record with user as creator and status 'started'\" do\n      expect { request }.to change(CaseContact, :count).by(1)\n      case_contact = CaseContact.last\n      expect(case_contact.status).to eq \"started\"\n      expect(case_contact.creator).to eq user\n    end\n\n    it \"does not set the contact's casa_case_id\" do\n      expect { request }.to change(CaseContact, :count).by(1)\n      case_contact = CaseContact.last\n      expect(case_contact.casa_case_id).to be_nil\n    end\n\n    it \"redirects to show(:details) with the created contact id\" do\n      expect { request }.to change(CaseContact, :count).by(1)\n      case_contact = CaseContact.last\n      expect(request).to redirect_to(case_contact_form_path(:details, case_contact_id: case_contact.id))\n    end\n  end\n\n  describe \"GET /show\" do\n    subject(:request) { get case_contact_form_path(:details, case_contact_id: case_contact.id) }\n\n    let(:case_contact) { create(:case_contact, :started_status, casa_case:, creator:) }\n\n    it \"renders details form with success status\" do\n      request\n\n      expect(response).to have_http_status(:success)\n      expect(response).to render_template(:details)\n    end\n\n    it \"does not change status from 'started'\" do\n      expect(case_contact.status).to eq \"started\"\n      request\n\n      expect(response).to have_http_status(:success)\n      expect(case_contact.reload.status).to eq \"started\"\n    end\n\n    context \"when contact created by another casa org volunteer\" do\n      let(:other_volunteer) { create(:volunteer, casa_org:) }\n      let(:creator) { other_volunteer }\n      let!(:case_assignment) { create(:case_assignment, volunteer: other_volunteer, casa_case:) }\n\n      it \"redirects to root/sign in\" do\n        expect(casa_case.volunteers).to include(volunteer, other_volunteer)\n        expect(case_contact.creator).to eq other_volunteer\n        request\n\n        expect(response).to redirect_to(root_path)\n      end\n    end\n\n    context \"when user is supervisor\" do\n      let(:user) { supervisor }\n\n      it \"allows volunteer's supervisor to view the form\" do\n        expect(supervisor.volunteers).to include(volunteer)\n        expect(casa_case.volunteers).to include(volunteer)\n        expect(case_contact.creator).to eq volunteer\n        request\n\n        expect(response).to have_http_status(:success)\n        expect(response).to render_template(:details)\n      end\n    end\n\n    context \"when user is casa admin\" do\n      let(:user) { casa_admin }\n\n      it \"allows admin to view the form\" do\n        expect(casa_case.volunteers).to include(volunteer)\n        expect(case_contact.creator).to eq volunteer\n        request\n\n        expect(response).to have_http_status(:success)\n        expect(response).to render_template(:details)\n      end\n    end\n\n    context \"when step is not :details\" do\n      subject(:request) { get case_contact_form_path(:notes, case_contact_id: case_contact.id) }\n\n      it \"raises Wicked::Wizard::InvalidStepError\" do\n        expect { request }.to raise_error(Wicked::Wizard::InvalidStepError)\n      end\n    end\n  end\n\n  describe \"PATCH /update\" do\n    subject(:request) { patch \"/case_contacts/#{case_contact.id}/form/details\", params: params }\n\n    let(:case_contact) { create(:case_contact, :started_status, creator: volunteer) }\n    let(:contact_type_group) { create(:contact_type_group, casa_org:) }\n    let!(:contact_types) { create_list(:contact_type, 2, contact_type_group:) }\n    let(:medium_type) { CaseContact::CONTACT_MEDIUMS.second }\n    let(:contact_type_ids) { [contact_types.first.id] }\n    let(:draft_case_ids) { [casa_case.id] }\n\n    let(:required_attributes) do\n      {\n        draft_case_ids: draft_case_ids.map(&:to_s),\n        occurred_at: 3.days.ago.to_date, # iso format\n        medium_type:\n      }\n    end\n    let(:valid_attributes) do\n      required_attributes.merge({\n        contact_type_ids: contact_type_ids.map(&:to_s),\n        contact_made: \"1\",\n        duration_minutes: 50,\n        duration_hours: 1\n      })\n    end\n    let(:invalid_attributes) do\n      {occurred_at: 3.days.from_now, duration_minutes: 50, contact_made: true}\n    end\n    let(:attributes) { valid_attributes }\n    let(:params) { {case_contact: attributes} }\n\n    it \"updates the requested case_contact attributes\" do\n      case_contact.update!(duration_minutes: 5, contact_made: false, contact_type_ids: [contact_types.second.id])\n      submitted_hours = attributes[:duration_hours]\n      submitted_minutes = attributes[:duration_minutes]\n      submitted_minutes += (60 * submitted_hours) if submitted_hours\n      request\n\n      case_contact.reload\n      expect(case_contact.contact_type_ids).to contain_exactly(contact_types.first.id)\n      expect(case_contact.contact_made).to be true\n      expect(case_contact.duration_minutes).to eq submitted_minutes\n    end\n\n    it \"updates the requested contact_type_ids\" do\n      expect(case_contact.contact_types).not_to include(contact_types.first)\n      request\n\n      case_contact.reload\n      expect(case_contact.contact_types).to contain_exactly(contact_types.first)\n      expect(case_contact.contact_types.size).to eq 1\n    end\n\n    it \"sets the case_contact's casa_case_id and status: 'active'\" do\n      expect(case_contact.casa_case_id).to be_nil\n      expect(case_contact.status).to eq \"started\"\n      request\n\n      case_contact.reload\n      expect(case_contact.status).to eq \"active\"\n      expect(case_contact.casa_case_id).to eq casa_case.id\n    end\n\n    it \"changes status to 'active' if it was 'started'\" do\n      case_contact.update!(status: \"started\")\n      request\n      expect(case_contact.reload.status).to eq \"active\"\n    end\n\n    it \"raises RoutingError if no step in url\" do\n      expect { patch \"/case_contacts/#{case_contact.id}/form\", params: {case_contact: attributes} }\n        .to raise_error(ActionController::RoutingError)\n    end\n\n    it \"redirects to referrer (fallback /case_contacts?success=true)\" do\n      request\n      expect(response).to have_http_status :redirect\n      expect(response).to redirect_to case_contacts_path(success: true)\n    end\n\n    context \"with invalid attributes\" do\n      let(:attributes) { invalid_attributes }\n\n      it \"does not update the requested case_contact\" do\n        original_attributes = case_contact.attributes\n        request\n\n        expect(case_contact.reload).to have_attributes original_attributes\n        expect(case_contact.duration_minutes).not_to eq(50)\n        expect(case_contact.contact_made).not_to be(true)\n      end\n\n      it \"re-renders the form\" do\n        request\n\n        # this should be a different status, but wicked wizard's 'render' method is a bit different?\n        expect(response).to have_http_status(:success)\n        expect(response).to render_template(:details)\n      end\n\n      it \"does not change the contact status from 'started'\" do\n        case_contact.started!\n        expect { request }.not_to change(case_contact, :status)\n        expect(case_contact.reload.status).to eq \"started\"\n      end\n    end\n\n    context \"with duplicate contact type ids in params\" do\n      let(:contact_type_ids) { [contact_types.first.id, contact_types.first.id] }\n\n      it \"dedupes and updates the contact type ids\" do\n        expect(case_contact.contact_type_ids).to be_empty\n        request\n        expect(response).to have_http_status(:redirect)\n\n        expect(case_contact.reload.contact_type_ids).to contain_exactly(contact_types.first.id)\n      end\n    end\n\n    context \"when contact types were previously assigned\" do\n      before { case_contact.update!(contact_type_ids: [contact_types.second.id]) }\n\n      it \"changes to contact types in params\" do\n        expect(case_contact.contact_type_ids).to contain_exactly(contact_types.second.id)\n        request\n\n        case_contact.reload\n        expect(case_contact.contact_type_ids).to contain_exactly(contact_types.first.id)\n      end\n\n      it \"allows re-assigning the same contact type without uniqueness validation error\" do\n        # This test prevents regression of Bugsnag error:\n        # \"Validation failed: Case contact has already been taken\"\n        case_contact.update!(contact_type_ids: [contact_types.first.id])\n        expect(case_contact.contact_type_ids).to contain_exactly(contact_types.first.id)\n\n        # Re-submit form with same contact type (simulates user editing and saving)\n        request\n\n        case_contact.reload\n        expect(case_contact.contact_type_ids).to contain_exactly(contact_types.first.id)\n      end\n\n      it \"handles overlapping contact types (mix of existing and new)\" do\n        # Prevent regression: Rails should update associations without destroy_all race condition\n        case_contact.update!(contact_type_ids: [contact_types.first.id])\n        expect(case_contact.contact_type_ids).to contain_exactly(contact_types.first.id)\n\n        # Update to include both contact types (one existing, one new)\n        updated_attributes = valid_attributes.merge(contact_type_ids: contact_types.map(&:id).map(&:to_s))\n        patch \"/case_contacts/#{case_contact.id}/form/details\", params: {case_contact: updated_attributes}\n\n        case_contact.reload\n        expect(case_contact.contact_type_ids).to contain_exactly(contact_types.first.id, contact_types.second.id)\n      end\n\n      it \"handles multiple consecutive updates without uniqueness errors\" do\n        # Simulate rapid updates that could trigger race conditions in production\n        case_contact.update!(contact_type_ids: [contact_types.first.id])\n\n        # First update: change to second contact type\n        updated_attributes = valid_attributes.merge(contact_type_ids: [contact_types.second.id.to_s])\n        patch \"/case_contacts/#{case_contact.id}/form/details\", params: {case_contact: updated_attributes}\n        case_contact.reload\n        expect(case_contact.contact_type_ids).to contain_exactly(contact_types.second.id)\n\n        # Second update: change back to first contact type\n        updated_attributes = valid_attributes.merge(contact_type_ids: [contact_types.first.id.to_s])\n        patch \"/case_contacts/#{case_contact.id}/form/details\", params: {case_contact: updated_attributes}\n        case_contact.reload\n        expect(case_contact.contact_type_ids).to contain_exactly(contact_types.first.id)\n\n        # Third update: use both contact types\n        updated_attributes = valid_attributes.merge(contact_type_ids: contact_types.map(&:id).map(&:to_s))\n        patch \"/case_contacts/#{case_contact.id}/form/details\", params: {case_contact: updated_attributes}\n        case_contact.reload\n        expect(case_contact.contact_type_ids).to contain_exactly(contact_types.first.id, contact_types.second.id)\n      end\n\n      it \"reduces contact types from multiple to single without errors\" do\n        # Start with multiple contact types\n        case_contact.update!(contact_type_ids: contact_types.map(&:id))\n        expect(case_contact.contact_type_ids).to contain_exactly(contact_types.first.id, contact_types.second.id)\n\n        # Update to single contact type\n        request\n\n        case_contact.reload\n        expect(case_contact.contact_type_ids).to contain_exactly(contact_types.first.id)\n      end\n\n      it \"expands contact types from single to multiple without errors\" do\n        # Start with single contact type\n        case_contact.update!(contact_type_ids: [contact_types.first.id])\n        expect(case_contact.contact_type_ids).to contain_exactly(contact_types.first.id)\n\n        # Update to multiple contact types\n        updated_attributes = valid_attributes.merge(contact_type_ids: contact_types.map(&:id).map(&:to_s))\n        patch \"/case_contacts/#{case_contact.id}/form/details\", params: {case_contact: updated_attributes}\n\n        case_contact.reload\n        expect(case_contact.contact_type_ids).to contain_exactly(contact_types.first.id, contact_types.second.id)\n      end\n\n      it \"replaces all contact types with completely different ones\" do\n        # Start with second contact type\n        case_contact.update!(contact_type_ids: [contact_types.second.id])\n        expect(case_contact.contact_type_ids).to contain_exactly(contact_types.second.id)\n\n        # Replace with first contact type (no overlap)\n        request\n\n        case_contact.reload\n        expect(case_contact.contact_type_ids).to contain_exactly(contact_types.first.id)\n        expect(case_contact.contact_type_ids).not_to include(contact_types.second.id)\n      end\n    end\n\n    context \"when contact topic answers in params\" do\n      let(:contact_topics) { create_list(:contact_topic, 3, casa_org:) }\n      let(:topic_one) { contact_topics.first }\n      let(:contact_topic_answers_attributes) do\n        {\n          \"0\" => {contact_topic_id: topic_one.id, value: \"Topic 1 Answer\"},\n          \"1\" => {contact_topic_id: contact_topics.second.id, value: \"Topic 2 Answer\"},\n          \"2\" => {contact_topic_id: contact_topics.third.id, value: \"Topic 3 Answer\"}\n        }\n      end\n\n      let(:attributes) { valid_attributes.merge({contact_topic_answers_attributes:}) }\n\n      it \"creates contact topic answers\" do\n        expect(attributes[:contact_topic_answers_attributes]).to eq contact_topic_answers_attributes\n        request\n\n        case_contact.reload\n        expect(case_contact.contact_topic_answers.size).to eq 3\n        expect(case_contact.contact_topic_answers.pluck(:value))\n          .to contain_exactly(\"Topic 1 Answer\", \"Topic 2 Answer\", \"Topic 3 Answer\")\n        expect(case_contact.contact_topics.flat_map(&:id)).to match_array(contact_topics.collect(&:id))\n      end\n\n      context \"when answer exists for the same contact topic\" do\n        let!(:contact_topic_one_answer) do\n          create(:contact_topic_answer, value: \"Original Discussion Topic Answer.\", contact_topic: topic_one, case_contact:)\n        end\n\n        it \"overwrites existing answer with id in answer attributes\" do\n          contact_topic_answers_attributes[\"0\"][:id] = contact_topic_one_answer.id\n          expect(case_contact.contact_topic_answers.size).to eq 1\n          request\n\n          case_contact.reload\n          topic_one_contact_answers = case_contact.contact_topic_answers.where(contact_topic: topic_one)\n          expect(topic_one_contact_answers.size).to eq 1\n          expect(case_contact.contact_topic_answers.size).to eq 3\n          expect(topic_one_contact_answers.first.value).to eq \"Topic 1 Answer\"\n        end\n      end\n    end\n\n    context \"when notes attribute in params\" do\n      let(:notes) { \"This is a note.\" }\n      let(:attributes) { valid_attributes.merge({notes:}) }\n\n      it \"updates the requested case_contact\" do\n        request\n\n        case_contact.reload\n        expect(case_contact.notes).to eq \"This is a note.\"\n      end\n    end\n\n    it \"does not send reimbursement email for non-reimbursement case contacts\" do\n      expect(attributes[:want_driving_reimbursement]).to be_nil\n\n      expect { request }.not_to have_enqueued_job(ActionMailer::MailDeliveryJob)\n    end\n\n    context \"when no volunteer address in params\" do\n      before { volunteer.create_address!(content: \"123 Before St\") }\n\n      it \"does not update the volunteer's address\" do\n        expect(attributes[:volunteer_address]).to be_nil\n        request\n\n        expect(case_contact.reload.volunteer_address).to be_nil\n        expect(volunteer.reload.address.content).to eq \"123 Before St\"\n      end\n    end\n\n    context \"when blank volunteer address in params\" do\n      let(:attributes) { valid_attributes.merge({volunteer_address: \"\"}) }\n\n      before { volunteer.create_address!(content: \"123 Before St\") }\n\n      it \"does not update the volunteer's address\" do\n        expect(attributes[:volunteer_address]).to eq \"\"\n        request\n\n        expect(case_contact.reload.volunteer_address).to be_empty\n        expect(volunteer.reload.address.content).to eq \"123 Before St\"\n      end\n    end\n\n    context \"when reimbursement info is in params\" do\n      let(:attributes) do\n        valid_attributes.merge({\n          want_driving_reimbursement: true,\n          miles_driven: 60,\n          volunteer_address: \"123 Params St\"\n        })\n      end\n\n      it \"updates the case contact with the info\" do\n        request\n\n        case_contact.reload\n        expect(case_contact.want_driving_reimbursement).to be true\n        expect(case_contact.miles_driven).to eq 60\n        expect(case_contact.volunteer_address).to eq \"123 Params St\"\n      end\n\n      it \"sends reimbursement email\" do\n        expect {\n          request\n        }.to change { have_enqueued_job(ActionMailer::MailDeliveryJob).with(\"SupervisorMailer\", \"reimbursement_request_email\", volunteer, supervisor) }\n      end\n\n      it \"updates the volunteer's address with the new address\" do\n        expect(user).to eq volunteer\n        expect(attributes[:volunteer_address]).to eq \"123 Params St\"\n        request\n\n        expect(case_contact.reload.volunteer_address).to eq \"123 Params St\"\n        expect(volunteer.reload.address.content).to eq \"123 Params St\"\n      end\n\n      context \"when admin edits volunteer contact\" do\n        let(:user) { casa_admin }\n\n        it \"changes the volunteer address, not the admin's\" do\n          casa_admin.create_address!(content: \"321 Admin Ave\")\n          expect(attributes[:volunteer_address]).to eq \"123 Params St\"\n          request\n\n          expect(case_contact.reload.volunteer_address).to eq \"123 Params St\"\n          expect(volunteer.reload.address.content).to eq \"123 Params St\"\n          expect(casa_admin.reload.address&.content).to eq \"321 Admin Ave\"\n        end\n      end\n\n      context \"when supervisor edits volunteer contact\" do\n        let(:user) { supervisor }\n\n        it \"changes the volunteer address, not the supervisor's\" do\n          supervisor.create_address!(content: \"321 Super Ave\")\n          expect(attributes[:volunteer_address]).to eq \"123 Params St\"\n          request\n\n          expect(case_contact.reload.volunteer_address).to eq \"123 Params St\"\n          expect(volunteer.reload.address.content).to eq \"123 Params St\"\n          expect(supervisor.reload.address&.content).to eq \"321 Super Ave\"\n        end\n      end\n    end\n\n    context \"when additional expenses in params\" do\n      let(:additional_expenses_attributes) do\n        {\n          \"0\" => {other_expense_amount: 50, other_expenses_describe: \"meal\"},\n          \"1\" => {other_expense_amount: 100, other_expenses_describe: \"hotel\"}\n        }\n      end\n      let(:attributes) { valid_attributes.merge({additional_expenses_attributes:}) }\n\n      it \"creates additional expenses for the case contact\" do\n        request\n\n        case_contact.reload\n        expect(case_contact.additional_expenses.first.other_expense_amount).to eq 50\n        expect(case_contact.additional_expenses.first.other_expenses_describe).to eq \"meal\"\n        expect(case_contact.additional_expenses.last.other_expense_amount).to eq 100\n        expect(case_contact.additional_expenses.last.other_expenses_describe).to eq \"hotel\"\n      end\n\n      it \"succeeds when wants_driving_reimbursement is not true\" do\n        case_contact.update!(want_driving_reimbursement: false)\n        attributes[:want_driving_reimbursement] = \"0\"\n        request\n\n        expect(case_contact.reload.additional_expenses.size).to eq 2\n      end\n    end\n\n    context \"when json request (autosave)\" do\n      subject(:request) do\n        patch \"/case_contacts/#{case_contact.id}/form/details\", params:, as: :json\n\n        response\n      end\n\n      it { is_expected.to have_http_status(:success) }\n\n      it \"updates with the attributes\" do\n        request\n\n        case_contact.reload\n        expect(case_contact.occurred_at).to eq(attributes[:occurred_at])\n        expect(case_contact.contact_made).to be true\n      end\n\n      it \"does not change status\" do\n        expect(case_contact.status).to eq \"started\"\n        request\n\n        expect(case_contact.reload.status).to eq \"started\"\n      end\n\n      context \"when contact is in details status\" do\n        let(:case_contact) { create(:case_contact, :details_status, casa_case:, creator: volunteer) }\n\n        it \"does not change status\" do\n          expect(case_contact.status).to eq \"details\"\n          request\n\n          expect(case_contact.reload.status).to eq \"details\"\n        end\n      end\n\n      context \"when contact is in active status\" do\n        let(:case_contact) { create(:case_contact, casa_case:, creator: volunteer) }\n\n        it \"does not change the status\" do\n          expect(case_contact.status).to eq \"active\"\n          request\n\n          expect(case_contact.reload.status).to eq \"active\"\n        end\n      end\n\n      context \"when attribute is invalid\" do\n        let(:attributes) { invalid_attributes }\n\n        it \"does not update the requested case_contact\" do\n          expect { request }.not_to change(case_contact.reload, :attributes)\n        end\n\n        it \"responds :unprocessable_content and returns the errors\" do\n          request\n\n          expect(response).to have_http_status(:unprocessable_content)\n        end\n      end\n    end\n\n    context \"when metadata create_another attribute is truthy\" do\n      let(:attributes) { valid_attributes.merge({metadata: {create_another: \"1\"}}) }\n\n      it \"redirects to contact form with the same draft_case_id, ignore_referer\" do\n        expect(attributes[:draft_case_ids]).to be_present\n\n        request\n        expect(response).to have_http_status :redirect\n        expect(response).to redirect_to(\n          new_case_contact_path(draft_case_ids: attributes[:draft_case_ids], ignore_referer: true)\n        )\n      end\n    end\n\n    context \"with multiple cases selected\" do\n      let!(:second_casa_case) { create(:casa_case, casa_org:, volunteers: [volunteer]) }\n      let!(:third_casa_case) { create(:casa_case, casa_org:, volunteers: [volunteer]) }\n      let(:draft_case_ids) { [casa_case.id, second_casa_case.id, third_casa_case.id] }\n\n      it \"copies the contact attributes for each contact\" do\n        expect { request }.to change(CaseContact.active, :count).by(3)\n\n        original_case_contact = case_contact.reload\n        second_case_contact = CaseContact.active.where(casa_case_id: second_casa_case.id).first\n        third_case_contact = CaseContact.active.where(casa_case_id: third_casa_case.id).first\n\n        unique_columns = [\"id\", \"casa_case_id\", \"created_at\", \"updated_at\", \"draft_case_ids\", \"metadata\"]\n        copied_attrs = original_case_contact.attributes.except(*unique_columns)\n        expect([second_case_contact, third_case_contact]).to all have_attributes copied_attrs\n      end\n\n      it \"sets casa_case and draft_case_ids per contact\" do\n        expect { request }.to change(CaseContact.active, :count).by(3)\n\n        second_case_contact = CaseContact.active.where(casa_case_id: second_casa_case.id).first\n        third_case_contact = CaseContact.active.where(casa_case_id: third_casa_case.id).first\n\n        expect(case_contact.reload.casa_case_id).to eq draft_case_ids.first\n        expect(case_contact.draft_case_ids).to contain_exactly draft_case_ids.first\n        expect(second_case_contact.casa_case_id).to eq draft_case_ids.second\n        expect(second_case_contact.draft_case_ids).to contain_exactly draft_case_ids.second\n        expect(third_case_contact.casa_case_id).to eq draft_case_ids.third\n        expect(third_case_contact.draft_case_ids).to contain_exactly draft_case_ids.third\n      end\n\n      it \"sets contact_type_ids for all contacts\" do\n        expect { request }.to change(CaseContact.active, :count).by(3)\n\n        contacts = CaseContact.active.last(3)\n        expect(contacts.collect(&:contact_type_ids)).to all match_array(contact_type_ids)\n      end\n\n      it \"copies contact_topic answers for the cases\" do\n        case_contact.contact_topic_answers.create!(contact_topic: contact_topics.first, value: \"test answer\")\n        expect { request }.to change(CaseContact.active, :count).by(3)\n\n        contacts = CaseContact.active.last(3)\n        contacts.each do |contact|\n          expect(contact.contact_topic_answers.first.contact_topic_id).to eq contact_topics.first.id\n          expect(contact.contact_topic_answers.first.value).to eq \"test answer\"\n        end\n      end\n\n      it \"redirects to referrer (fallback) page\" do\n        request\n        expect(response).to have_http_status :redirect\n        expect(response).to redirect_to case_contacts_path(success: true)\n      end\n\n      context \"when create_another option is truthy\" do\n        before { params[:case_contact][:metadata] = {create_another: \"1\"} }\n\n        it \"redirects to new contact with the same draft_case_ids, :ignore_referer\" do\n          request\n          expect(response).to have_http_status :redirect\n          expect(response).to redirect_to new_case_contact_path(draft_case_ids:, ignore_referer: true)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/case_contacts_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"/case_contacts\", type: :request do\n  let(:organization) { build(:casa_org) }\n  let(:admin) { create(:casa_admin, casa_org: organization) }\n  let(:volunteer) { create(:volunteer, casa_org: organization) }\n\n  before { sign_in admin }\n\n  describe \"GET /index\" do\n    subject(:request) do\n      get case_contacts_path(filterrific: filterrific)\n\n      response\n    end\n\n    let!(:casa_case) { create(:casa_case, casa_org: organization) }\n    let!(:past_contact) { create(:case_contact, casa_case: casa_case, occurred_at: 3.weeks.ago) }\n    let!(:recent_contact) { create(:case_contact, casa_case: casa_case, occurred_at: 3.days.ago) }\n    let(:filterrific) { {} }\n\n    it { is_expected.to have_http_status(:success) }\n\n    it \"returns all case contacts\" do\n      page = request.parsed_body.to_html\n      expect(page).to include(past_contact.creator.display_name, recent_contact.creator.display_name)\n    end\n\n    context \"with filters applied\" do\n      let(:filterrific) { {occurred_starting_at: 1.week.ago} }\n\n      it \"returns all case contacts\" do\n        page = request.parsed_body.to_html\n        expect(page).to include(recent_contact.creator.display_name)\n        expect(page).not_to include(past_contact.creator.display_name)\n      end\n    end\n\n    context \"when logged in as a volunteer\" do\n      let(:assigned_case) { create(:casa_case, :with_one_case_assignment, casa_org: organization) }\n      let(:unassigned_case) { casa_case }\n      let(:volunteer) { assigned_case.assigned_volunteers.first }\n      let!(:assigned_case_contact) { create(:case_contact, casa_case: assigned_case, creator: volunteer) }\n      let!(:unassigned_case_contact) { create(:case_contact, casa_case: unassigned_case, creator: volunteer, duration_minutes: 180) }\n\n      before { sign_in volunteer }\n\n      it \"returns only currently assigned cases\" do\n        page = request.parsed_body.to_html\n        expect(page).to include(\"60 minutes\")\n        expect(page).not_to include(\"3 hours\")\n      end\n    end\n  end\n\n  describe \"GET /new\" do\n    subject(:request) do\n      get new_case_contact_path\n\n      response\n    end\n\n    it { is_expected.to have_http_status(:redirect) }\n\n    it \"creates a 'started' status case contact and redirects to the form\" do\n      expect { request }.to change(CaseContact, :count).by(1)\n      new_case_contact = CaseContact.last\n      expect(new_case_contact.status).to eq \"started\"\n      expect(response).to redirect_to(case_contact_form_path(:details, case_contact_id: new_case_contact.id))\n    end\n\n    context \"when current org has contact topics\" do\n      let(:contact_topics) do\n        [build(:contact_topic, active: true, soft_delete: false)]\n      end\n      let(:organization) { create(:casa_org, contact_topics:) }\n\n      it \"does not create contact topic answers\" do\n        expect { request }\n          .to change(CaseContact.started, :count).by(1)\n          .and not_change(ContactTopicAnswer, :count)\n\n        expect(CaseContact.started.last.contact_topic_answers).to be_empty\n      end\n    end\n  end\n\n  describe \"GET /edit\" do\n    subject(:request) do\n      get edit_case_contact_url(case_contact)\n\n      response\n    end\n\n    let(:case_contact) { create(:case_contact, casa_case: create(:casa_case, :with_case_assignments), notes: \"Notes\") }\n\n    it { is_expected.to have_http_status(:redirect) }\n\n    it \"redirects to case contact form\" do\n      request\n      expect(response).to redirect_to(case_contact_form_path(:details, case_contact_id: case_contact.id))\n    end\n  end\n\n  describe \"GET /drafts\" do\n    subject(:request) do\n      get case_contacts_drafts_path\n\n      response\n    end\n\n    it { is_expected.to have_http_status(:success) }\n\n    context \"when user is volunteer\" do\n      before { sign_in volunteer }\n\n      it { is_expected.to have_http_status(:redirect) }\n    end\n  end\n\n  describe \"DELETE /destroy\" do\n    subject(:request) do\n      delete case_contact_path(case_contact), headers: {HTTP_REFERER: case_contacts_path}\n\n      response\n    end\n\n    let(:case_contact) { create(:case_contact) }\n\n    it { is_expected.to redirect_to(case_contacts_path) }\n\n    it \"shows correct flash message\" do\n      request\n      expect(flash[:notice]).to eq(\"Contact is successfully deleted.\")\n    end\n\n    it \"soft deletes the case_contact\" do\n      expect { request }.to change { case_contact.reload.deleted? }.from(false).to(true)\n    end\n  end\n\n  describe \"GET /restore\" do\n    subject(:request) do\n      post restore_case_contact_path(case_contact), headers: {HTTP_REFERER: case_contacts_path}\n\n      response\n    end\n\n    let(:case_contact) { create(:case_contact) }\n\n    before { case_contact.destroy }\n\n    it { is_expected.to redirect_to(case_contacts_path) }\n\n    it \"shows correct flash message\" do\n      request\n      expect(flash[:notice]).to eq(\"Contact is successfully restored.\")\n    end\n\n    it \"soft deletes the case_contact\" do\n      expect { request }.to change { case_contact.reload.deleted? }.from(true).to(false)\n    end\n  end\n\n  xdescribe \"GET /leave\" do\n    subject(:request) do\n      get leave_case_contact_path\n\n      response\n    end\n\n    it { is_expected.to redirect_to(case_contacts_path) }\n\n    it \"redirects back to referer or fallback location\" do\n      request\n      expect(response).to redirect_to(case_contacts_path)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/case_court_orders_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"/case_court_orders\", type: :request do\n  let(:user) { build(:casa_admin) }\n  let(:case_court_order) { build(:case_court_order) }\n\n  before do\n    sign_in user\n    casa_case = create(:casa_case)\n    casa_case.case_court_orders << case_court_order\n  end\n\n  describe \"DELETE /destroy\" do\n    subject(:request) do\n      delete case_court_order_url(case_court_order)\n\n      response\n    end\n\n    it { is_expected.to be_successful }\n\n    it \"deletes the court order\" do\n      expect { request }.to change(CaseCourtOrder, :count).from(1).to(0)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/case_court_reports_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"/case_court_reports\", type: :request do\n  include DownloadHelpers\n  let(:volunteer) { create(:volunteer, :with_cases_and_contacts, :with_assigned_supervisor) }\n\n  before do\n    sign_in volunteer\n  end\n\n  # case_court_reports#index\n  describe \"GET /case_court_reports\" do\n    context \"as volunteer\" do\n      it \"can view 'Generate Court Report' page\", :aggregate_failures do\n        get case_court_reports_path\n        expect(response).to be_successful\n        expect(assigns(:assigned_cases)).not_to be_empty\n      end\n    end\n\n    context \"as a supervisor\" do\n      let(:supervisor) { volunteer.supervisor }\n\n      before do\n        sign_in supervisor\n      end\n\n      it \"can view the 'Generate Court Report' page\", :aggregate_failures do\n        get case_court_reports_path\n        expect(response).to be_successful\n        expect(assigns(:assigned_cases)).not_to be_empty\n      end\n\n      context \"with no cases in the organization\" do\n        let(:supervisor) { create(:supervisor, casa_org: create(:casa_org)) }\n\n        it \"can view 'Generate Court Report page\", :aggregate_failures do\n          get case_court_reports_path\n          expect(response).to be_successful\n          expect(assigns(:assigned_cases)).to be_empty\n        end\n      end\n    end\n  end\n\n  # case_court_reports#show\n  describe \"GET /case_court_reports/:id\" do\n    context \"when a valid / existing case is sent\" do\n      subject(:request) do\n        get case_court_report_path(casa_case.case_number, format: \"docx\")\n\n        response\n      end\n\n      let(:casa_case) { volunteer.casa_cases.first }\n\n      before do\n        Tempfile.create do |t|\n          casa_case.court_reports.attach(\n            io: File.open(t.path), filename: \"#{casa_case.case_number}.docx\"\n          )\n        end\n      end\n\n      it \"authorizes action\" do\n        expect_any_instance_of(CaseCourtReportsController).to receive(:authorize).with(CaseCourtReport).and_call_original\n        request\n      end\n\n      it \"send response as a .DOCX file\" do\n        expect(request.content_type).to eq Mime::Type.lookup_by_extension(:docx)\n      end\n\n      it \"send response with a status :ok\" do\n        expect(request).to have_http_status(:ok)\n      end\n    end\n\n    context \"when an INVALID / non-existing case is sent\" do\n      let(:invalid_casa_case) { build_stubbed(:casa_case) }\n\n      before do\n        Capybara.current_driver = :selenium_chrome\n        get case_court_report_path(invalid_casa_case.case_number, format: \"docx\")\n      end\n\n      it \"redirects back to 'Generate Court Report' page\", :aggregate_failures, :js do\n        expect(response).to redirect_to(case_court_reports_path)\n        expect(response.content_type).to eq \"text/html; charset=utf-8\"\n      end\n\n      it \"shows correct flash message\" do\n        request\n        expect(flash[:alert]).to eq \"Report #{invalid_casa_case.case_number} is not found.\"\n      end\n    end\n  end\n\n  # case_court_reports#generate\n  describe \"POST /case_court_reports\" do\n    subject(:request) do\n      post generate_case_court_reports_path, params: params, headers: {ACCEPT: \"application/json\"}\n\n      response\n    end\n\n    let(:casa_case) { volunteer.casa_cases.first }\n    let(:params) {\n      {\n        case_court_report: {\n          case_number: casa_case.case_number.to_s,\n          start_date: \"January 1, 2020\",\n          end_date: \"January 1, 2021\"\n        }\n      }\n    }\n\n    it \"authorizes action\" do\n      expect_any_instance_of(CaseCourtReportsController).to receive(:authorize).with(CaseCourtReport).and_call_original\n      request\n    end\n\n    context \"when no custom template is set\" do\n      it \"sends response as a JSON string\", :aggregate_failures do\n        expect(request.content_type).to eq(\"application/json; charset=utf-8\")\n        expect(request.parsed_body).to be_a(ActiveSupport::HashWithIndifferentAccess)\n      end\n\n      it \"has keys ['link', 'status'] in JSON string\", :aggregate_failures do\n        body_hash = request.parsed_body\n\n        expect(body_hash).to have_key \"link\"\n        expect(body_hash).to have_key \"status\"\n      end\n\n      it \"sends response with status :ok\" do\n        expect(request).to have_http_status(:ok)\n      end\n\n      it \"contains a link ending with .DOCX extension\" do\n        expect(request.parsed_body[\"link\"]).to end_with(\".docx\")\n      end\n\n      it \"uses the default template\" do\n        get request.parsed_body[\"link\"]\n\n        docx_response = Docx::Document.open(StringIO.new(response.body))\n\n        expect(header_text(docx_response)).to include(\"YOUR CASA ORG’S NUMBER\")\n      end\n    end\n\n    context \"when a custom template is set\" do\n      before do\n        stub_twilio\n        volunteer.casa_org.court_report_template.attach(io: File.new(Rails.root.join(\"app/documents/templates/montgomery_report_template.docx\")), filename: \"montgomery_report_template.docx\")\n      end\n\n      it \"uses the custom template\" do\n        get request.parsed_body[\"link\"]\n        followed_link_response = response\n\n        docx_response = Docx::Document.open(StringIO.new(followed_link_response.body))\n\n        expect(docx_response.paragraphs.map(&:to_s)).to include(\"Did you forget to enter your court orders?\")\n      end\n    end\n\n    context \"with date filtering\" do\n      let(:casa_case) { volunteer.casa_cases.first }\n      let!(:contact_in_range) do\n        create(:case_contact, casa_case: casa_case, occurred_at: Date.new(2025, 10, 10))\n      end\n      let!(:contact_out_of_range) do\n        create(:case_contact, casa_case: casa_case, occurred_at: Date.new(2024, 10, 30))\n      end\n      let(:params) {\n        {\n          case_court_report: {\n            case_number: casa_case.case_number.to_s,\n            start_date: \"2025-10-01\",\n            end_date: \"2025-10-23\"\n          }\n        }\n      }\n\n      it \"includes contacts within the date range in the generated report\" do\n        post generate_case_court_reports_path, params: params, headers: {ACCEPT: \"application/json\"}\n        get response.parsed_body[\"link\"]\n        docx_response = Docx::Document.open(StringIO.new(response.body))\n        # The contact dates are in table cells, so we need to extract them specifically.\n        table_texts = docx_response.tables.flat_map { |table| table.rows.flat_map { |row| row.cells.map(&:text) } }\n\n        expect(table_texts.join(\" \")).to include(contact_in_range.occurred_at.strftime(\"%-m/%-d\"))\n      end\n\n      it \"does not include contacts outside the date range in the generated report\" do\n        post generate_case_court_reports_path, params: params, headers: {ACCEPT: \"application/json\"}\n        get response.parsed_body[\"link\"]\n        docx_response = Docx::Document.open(StringIO.new(response.body))\n        table_texts = docx_response.tables.flat_map { |table| table.rows.flat_map { |row| row.cells.map(&:text) } }\n\n        expect(table_texts.join(\" \")).not_to include(contact_out_of_range.occurred_at.strftime(\"%-m/%-d\"))\n      end\n    end\n\n    context \"when user timezone\" do\n      let(:server_time) { Time.zone.parse(\"2020-12-31 23:00:00\") }\n      let(:user_different_timezone) do\n        ActiveSupport::TimeZone[\"Tokyo\"]\n      end\n      let(:params) { {case_court_report: {case_number: casa_case.case_number.to_s}, time_zone: \"Tokyo\"} }\n\n      before do\n        travel_to server_time\n      end\n\n      it \"is different than server\" do\n        get request.parsed_body[\"link\"]\n        followed_link_response = response\n\n        docx_response = Docx::Document.open(StringIO.new(followed_link_response.body))\n\n        expect(docx_response.paragraphs.map(&:to_s))\n          .to include(\"Date Written: #{I18n.l(user_different_timezone.at(server_time)\n            .to_date, format: :full, default: nil)}\")\n      end\n    end\n\n    context \"when an INVALID / non-existing case is sent\" do\n      let(:casa_case) { build_stubbed(:casa_case) }\n\n      it \"sends response as a JSON string\", :aggregate_failures do\n        expect(request.content_type).to eq(\"application/json; charset=utf-8\")\n        expect(request.parsed_body).to be_a(ActiveSupport::HashWithIndifferentAccess)\n      end\n\n      it \"has keys ['link','status','error_messages'] in JSON string\", :aggregate_failures do\n        body_hash = request.parsed_body\n\n        expect(body_hash).to have_key \"link\"\n        expect(body_hash).to have_key \"status\"\n        expect(body_hash).to have_key \"error_messages\"\n      end\n\n      it \"sends response with status :not_found\" do\n        expect(request).to have_http_status(:not_found)\n      end\n\n      it \"contains a empty link\" do\n        expect(request.parsed_body[\"link\"].length).to be 0\n      end\n\n      # TODO: Fix controller to have the error message actually get the param with `case_params[:case_number]`\n      it \"shows correct error messages\" do\n        expect(request.parsed_body[\"error_messages\"]).to include(\"Report  is not found\")\n      end\n    end\n\n    context \"when zip report fails\" do\n      before do\n        expect_any_instance_of(CaseCourtReportsController).to receive(:save_report).and_raise Zip::Error.new\n      end\n\n      it { is_expected.to have_http_status(:not_found) }\n\n      it \"shows the correct error message\" do\n        expect(request.parsed_body[\"error_messages\"]).to include(\"Template is not found\")\n      end\n    end\n\n    context \"when an unpredictable error occurs\" do\n      before do\n        expect_any_instance_of(CaseCourtReportsController).to receive(:save_report).and_raise StandardError.new(\"Unexpected Error\")\n      end\n\n      it { is_expected.to have_http_status(:unprocessable_content) }\n\n      it \"shows the correct error message\" do\n        expect(request.parsed_body[\"error_messages\"]).to include(\"Unexpected Error\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/case_groups_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"/case_groups\", type: :request do\n  let(:casa_org) { create :casa_org }\n  let(:supervisor) { create :supervisor, casa_org: }\n  let(:user) { supervisor }\n\n  let(:casa_cases) { create_list :casa_case, 2, casa_org: }\n  let(:case_group) { create :case_group, casa_org:, casa_cases: }\n  let(:valid_attributes) { attributes_for :case_group, casa_org:, casa_case_ids: casa_cases.map(&:id) }\n  let(:invalid_attributes) do\n    valid_attributes.merge(name: nil, casa_case_ids: [])\n  end\n\n  before { sign_in user }\n\n  describe \"GET /index\" do\n    subject { get case_groups_path }\n\n    let!(:case_groups) { create_list :case_group, 2, casa_org: }\n\n    it \"renders a successful response\" do\n      subject\n      expect(response).to have_http_status(:success)\n      expect(response).to render_template(:index)\n    end\n\n    it \"displays information of the records\" do\n      subject\n      expect(response.body).to include(*case_groups.map(&:name))\n    end\n  end\n\n  describe \"GET /new\" do\n    subject { get new_case_group_path }\n\n    it \"renders a successful response\" do\n      subject\n      expect(response).to have_http_status(:success)\n      expect(response).to render_template(:new)\n    end\n  end\n\n  describe \"POST /create\" do\n    subject { post case_groups_path, params: }\n\n    let(:params) { {case_group: valid_attributes} }\n\n    it \"creates new record\" do\n      expect { subject }.to change(CaseGroup, :count).by(1)\n    end\n\n    it \"redirects to the case group index\" do\n      subject\n      expect(response).to redirect_to(case_groups_path)\n    end\n\n    context \"with invalid params\" do\n      let(:params) { {case_group: invalid_attributes} }\n\n      it \"does not create a new record\" do\n        expect { subject }.not_to change(CaseGroup, :count)\n      end\n\n      it \"renders new template\" do\n        subject\n        expect(response).to have_http_status(:unprocessable_content)\n        expect(response).to render_template(:new)\n      end\n    end\n  end\n\n  describe \"GET edit\" do\n    subject { get edit_case_group_path(case_group) }\n\n    let(:case_group) { create :case_group, casa_org: }\n\n    it \"renders a successful response\" do\n      subject\n      expect(response).to have_http_status(:success)\n      expect(response).to render_template(:edit)\n    end\n  end\n\n  describe \"PATCH /update\" do\n    subject { patch case_group_path(case_group), params: }\n\n    let(:params) { {case_group: valid_attributes} }\n\n    it \"updates the requested record\" do\n      expect(case_group.name).not_to eq(valid_attributes[:name])\n      subject\n      case_group.reload\n      expect(case_group.name).to eq(valid_attributes[:name])\n    end\n\n    it \"redirects to the updated record\" do\n      subject\n      expect(response).to redirect_to(case_groups_path)\n    end\n\n    context \"with invalid params\" do\n      let(:params) { {case_group: invalid_attributes} }\n\n      it \"renders new template\" do\n        subject\n        expect(response).to have_http_status(:unprocessable_content)\n        expect(response).to render_template(:edit)\n      end\n    end\n  end\n\n  describe \"DELETE destroy\" do\n    subject { delete case_group_path(case_group) }\n\n    let!(:case_group) { create :case_group, casa_org: }\n\n    it \"destroys the requested record\" do\n      expect { subject }.to change(CaseGroup, :count).by(-1)\n    end\n\n    it \"redirects to the case_groups index\" do\n      subject\n      expect(response).to redirect_to(case_groups_path)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/checklist_items_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"ChecklistItems\", type: :request do\n  describe \"GET new\" do\n    context \"when logged in as an admin user\" do\n      it \"the new checklist item page should load successfully\" do\n        sign_in_as_admin\n        get new_hearing_type_checklist_item_path(create(:hearing_type))\n        expect(response).to be_successful\n      end\n    end\n\n    context \"when logged in as a non-admin user\" do\n      it \"does not allow access to the new checklist item page\" do\n        sign_in_as_volunteer\n        get new_hearing_type_checklist_item_path(create(:hearing_type))\n        expect(response).to redirect_to root_path\n        expect(response.request.flash[:notice]).to eq \"Sorry, you are not authorized to perform this action.\"\n      end\n    end\n  end\n\n  describe \"POST create\" do\n    context \"when logged in as an admin user\" do\n      it \"allows for the creation of checklist items\" do\n        sign_in_as_admin\n        hearing_type = create(:hearing_type)\n        post hearing_type_checklist_items_path(\n          {\n            hearing_type_id: hearing_type.id,\n            checklist_item: {\n              description: \"checklist item description\",\n              category: \"checklist item category\",\n              mandatory: false\n            }\n          }\n        )\n        expect(response).to redirect_to edit_hearing_type_path(hearing_type)\n        expect(ChecklistItem.count).to eq 1\n      end\n    end\n\n    context \"when logged in as a non-admin user\" do\n      it \"does not allow for the creation of checklist items\" do\n        sign_in_as_volunteer\n        hearing_type = create(:hearing_type)\n        post hearing_type_checklist_items_path(\n          {\n            hearing_type_id: hearing_type.id,\n            checklist_item: {\n              description: \"checklist item description\",\n              category: \"checklist item category\",\n              mandatory: false\n            }\n          }\n        )\n        expect(response).to redirect_to root_path\n        expect(response.request.flash[:notice]).to eq \"Sorry, you are not authorized to perform this action.\"\n      end\n    end\n  end\n\n  describe \"GET edit\" do\n    context \"when logged in as an admin user\" do\n      it \"the edit page should load successfully\" do\n        sign_in_as_admin\n        hearing_type = create(:hearing_type)\n        checklist_item = create(:checklist_item)\n        get edit_hearing_type_checklist_item_path(hearing_type, checklist_item)\n        expect(response).to be_successful\n      end\n    end\n\n    context \"when logged in as a non-admin user\" do\n      it \"does not allow access to the edit page\" do\n        sign_in_as_volunteer\n        hearing_type = create(:hearing_type)\n        checklist_item = create(:checklist_item)\n        get edit_hearing_type_checklist_item_path(hearing_type, checklist_item)\n        expect(response).to redirect_to root_path\n        expect(response.request.flash[:notice]).to eq \"Sorry, you are not authorized to perform this action.\"\n      end\n    end\n  end\n\n  describe \"PATCH update\" do\n    context \"when logged in as an admin user\" do\n      it \"lets admin users update checklist items\" do\n        sign_in_as_admin\n        hearing_type = create(:hearing_type)\n        checklist_item = create(:checklist_item)\n        patch hearing_type_checklist_item_path(\n          {\n            hearing_type_id: hearing_type.id,\n            id: checklist_item.id,\n            checklist_item: {\n              description: \"updated checklist item description\",\n              category: \"updated checklist item category\",\n              mandatory: true\n            }\n          }\n        )\n        expect(response).to redirect_to edit_hearing_type_path(hearing_type)\n        checklist_item.reload\n        expect(checklist_item.description).to eq \"updated checklist item description\"\n        expect(checklist_item.category).to eq \"updated checklist item category\"\n        expect(checklist_item.mandatory).to eq true\n      end\n    end\n\n    context \"when logged in as a non-admin user\" do\n      it \"does not allow updates\" do\n        sign_in_as_volunteer\n        hearing_type = create(:hearing_type)\n        checklist_item = create(:checklist_item)\n        patch hearing_type_checklist_item_path(\n          {\n            hearing_type_id: hearing_type.id,\n            id: checklist_item.id,\n            checklist_item: {\n              description: \"updated checklist item description\",\n              category: \"updated checklist item category\",\n              mandatory: true\n            }\n          }\n        )\n        expect(response).to redirect_to root_path\n        expect(response.request.flash[:notice]).to eq \"Sorry, you are not authorized to perform this action.\"\n        checklist_item.reload\n        expect(checklist_item.description).to eq \"checklist item description\"\n        expect(checklist_item.category).to eq \"checklist item category\"\n        expect(checklist_item.mandatory).to eq false\n      end\n    end\n  end\n\n  describe \"DELETE destroy\" do\n    context \"when logged in as an admin user\" do\n      it \"allows for the deletion of checklist items\" do\n        sign_in_as_admin\n        hearing_type = create(:hearing_type)\n        checklist_item = create(:checklist_item)\n        delete hearing_type_checklist_item_path(hearing_type, checklist_item)\n        expect(response).to redirect_to edit_hearing_type_path(hearing_type)\n        expect(ChecklistItem.count).to eq 0\n      end\n    end\n\n    context \"when logged in as a non-admin user\" do\n      it \"does not allow for the deletion of checklist items\" do\n        sign_in_as_volunteer\n        hearing_type = create(:hearing_type)\n        checklist_item = create(:checklist_item)\n        delete hearing_type_checklist_item_path(hearing_type, checklist_item)\n        expect(response).to redirect_to root_path\n        expect(response.request.flash[:notice]).to eq \"Sorry, you are not authorized to perform this action.\"\n        expect(ChecklistItem.count).to eq 1\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/contact_topic_answers_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"/contact_topic_answers\", type: :request do\n  let(:casa_org) { create :casa_org }\n  let(:contact_topic) { create :contact_topic, casa_org: }\n  let(:casa_admin) { create :casa_admin, casa_org: }\n  let(:supervisor) { create :supervisor, casa_org: }\n  let(:volunteer) { create :volunteer, :with_single_case, supervisor:, casa_org: }\n  let(:user) { volunteer }\n  let(:casa_case) { volunteer.casa_cases.first }\n  let(:case_contact) { create :case_contact, casa_case:, creator: volunteer }\n\n  let(:valid_attributes) do\n    attributes_for(:contact_topic_answer)\n      .merge({contact_topic_id: contact_topic.id, case_contact_id: case_contact.id})\n  end\n  let(:invalid_attributes) { valid_attributes.merge({contact_topic_id: nil, value: \"something\"}) }\n\n  before { sign_in user }\n\n  describe \"POST /create\" do\n    subject { post contact_topic_answers_path, params:, as: :json }\n\n    let(:new_attributes) { valid_attributes.except(:value) }\n    let(:params) { {contact_topic_answer: new_attributes} }\n\n    it \"creates a record and responds created\" do\n      expect { subject }.to change(ContactTopicAnswer, :count).by(1)\n      expect(response).to have_http_status(:created)\n    end\n\n    it \"returns the record as json\" do\n      subject\n      expect(response.content_type).to match(a_string_including(\"application/json\"))\n      answer = ContactTopicAnswer.last\n      expect(response_json[:id]).to eq answer.id\n      expect(response_json.keys)\n        .to contain_exactly(:id, :contact_topic_id, :value, :case_contact_id, :created_at, :updated_at, :selected, :deleted_at)\n    end\n\n    context \"as casa_admin\" do\n      let(:user) { casa_admin }\n\n      it \"creates a record and responds created\" do\n        expect { subject }.to change(ContactTopicAnswer, :count).by(1)\n        expect(response).to have_http_status(:created)\n      end\n    end\n\n    context \"with invalid parameters\" do\n      let(:params) { {contact_topic_answer: invalid_attributes} }\n\n      it \"fails and responds unprocessable_content\" do\n        expect { subject }.not_to change(ContactTopicAnswer, :count)\n        expect(response).to have_http_status(:unprocessable_content)\n      end\n\n      it \"returns errors as json\" do\n        subject\n        expect(response.content_type).to match(a_string_including(\"application/json\"))\n        expect(response.body).to be_present\n        expect(response_json[:contact_topic]).to include(\"must be selected\")\n      end\n    end\n\n    context \"html request\" do\n      subject { post contact_topic_answers_path, params: }\n\n      it \"redirects to referrer/root without creating a contact topic answer\" do\n        expect { subject }.to not_change(ContactTopicAnswer, :count)\n        expect(response).to redirect_to(root_url)\n      end\n    end\n  end\n\n  describe \"DELETE /destroy\" do\n    subject { delete contact_topic_answer_url(contact_topic_answer), as: :json }\n\n    let!(:contact_topic_answer) { create :contact_topic_answer, case_contact:, contact_topic: }\n\n    it \"destroys the record and responds no content\" do\n      expect { subject }\n        .to change(ContactTopicAnswer, :count).by(-1)\n      expect(response).to have_http_status(:no_content)\n      expect(response.body).to be_empty\n    end\n\n    context \"html request\" do\n      subject { delete contact_topic_answer_url(contact_topic_answer) }\n\n      it \"redirects to referrer/root without destroying the contact topic answer\" do\n        expect { subject }.to not_change(ContactTopicAnswer, :count)\n        expect(response).to redirect_to(root_url)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/contact_topics_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"/contact_topics\", type: :request do\n  # This should return the minimal set of attributes required to create a valid\n  # ContactTopic. As you add validations to ContactTopic, be sure to\n  # adjust the attributes here as well.\n  let(:casa_org) { create(:casa_org) }\n  let(:is_active) { nil }\n  let(:contact_topic) { create(:contact_topic, casa_org:) }\n  let(:attributes) { {casa_org_id: casa_org.id} }\n  let(:admin) { create(:casa_admin, casa_org: casa_org) }\n\n  before { sign_in admin }\n\n  describe \"GET /new\" do\n    it \"renders a successful response\" do\n      get new_contact_topic_url\n      expect(response).to be_successful\n    end\n  end\n\n  describe \"GET /edit\" do\n    it \"renders a successful response\" do\n      get edit_contact_topic_url(contact_topic)\n      expect(response).to be_successful\n      expect(response.body).to include(contact_topic.question)\n      expect(response.body).to include(contact_topic.details)\n    end\n  end\n\n  describe \"POST /create\" do\n    context \"with valid parameters\" do\n      let(:attributes) do\n        {\n          casa_org_id: casa_org.id,\n          question: \"test question\",\n          details: \"test details\"\n        }\n      end\n\n      it \"creates a new ContactTopic\" do\n        expect do\n          post contact_topics_url, params: {contact_topic: attributes}\n        end.to change(ContactTopic, :count).by(1)\n\n        topic = ContactTopic.last\n\n        expect(topic.question).to eq(\"test question\")\n        expect(topic.details).to eq(\"test details\")\n      end\n\n      it \"redirects to the edit casa_org\" do\n        post contact_topics_url, params: {contact_topic: attributes}\n        expect(response).to redirect_to(edit_casa_org_path(casa_org))\n      end\n\n      it \"can set exclude_from_court_report attribute\" do\n        attributes[:exclude_from_court_report] = true\n\n        expect do\n          post contact_topics_url, params: {contact_topic: attributes}\n        end.to change(ContactTopic, :count).by(1)\n\n        topic = ContactTopic.last\n        expect(topic.exclude_from_court_report).to be true\n      end\n    end\n\n    context \"with invalid parameters\" do\n      let(:attributes) do\n        {\n          casa_org_id: casa_org.id,\n          question: \"\",\n          details: \"\"\n        }\n      end\n\n      it \"does not create a new ContactTopic\" do\n        expect do\n          post contact_topics_url, params: {contact_topic: attributes}\n        end.not_to change(ContactTopic, :count)\n      end\n\n      it \"renders a response with 422 status (i.e. to display the 'new' template)\" do\n        post contact_topics_url, params: {contact_topic: attributes}\n        expect(response).to have_http_status(:unprocessable_content)\n      end\n    end\n  end\n\n  describe \"PATCH /update\" do\n    context \"with valid parameters\" do\n      let!(:contact_topic) { create(:contact_topic, casa_org:) }\n\n      let(:new_attributes) do\n        {\n          casa_org_id: casa_org.id,\n          active: false,\n          question: \"test question\",\n          details: \"test details\",\n          soft_delete: true\n        }\n      end\n\n      it \"updates only active, details, question contact_topic\" do\n        expect(contact_topic.soft_delete).to eq(false)\n\n        patch contact_topic_url(contact_topic), params: {contact_topic: new_attributes}\n        contact_topic.reload\n\n        expect(contact_topic.soft_delete).to eq(false)\n        expect(contact_topic.active).to eq(false)\n        expect(contact_topic.details).to eq(\"test details\")\n        expect(contact_topic.question).to eq(\"test question\")\n      end\n\n      it \"redirects to the casa_org edit\" do\n        patch contact_topic_url(contact_topic), params: {contact_topic: new_attributes}\n        expect(response).to redirect_to(edit_casa_org_path(casa_org))\n      end\n\n      it \"can change exclude_from_court_report\" do\n        new_attributes = {exclude_from_court_report: true}\n\n        expect {\n          patch contact_topic_url(contact_topic), params: {contact_topic: new_attributes}\n        }.to change { contact_topic.reload.exclude_from_court_report }.from(false).to(true)\n      end\n    end\n\n    context \"with invalid parameters\" do\n      let(:attributes) { {casa_org_id: 0} }\n\n      it \"renders a response with 422 status (i.e. to display the 'edit' template)\" do\n        patch contact_topic_url(contact_topic), params: {contact_topic: attributes}\n        expect(response).to have_http_status(:unprocessable_content)\n      end\n    end\n  end\n\n  describe \"DELETE /soft_delete\" do\n    let!(:contact_topic) { create(:contact_topic, casa_org: casa_org) }\n\n    it \"does not destroy the requested contact_topic\" do\n      expect do\n        delete soft_delete_contact_topic_url(contact_topic)\n      end.not_to change(ContactTopic, :count)\n    end\n\n    it \"set the requested contact_topic to soft_deleted\" do\n      delete soft_delete_contact_topic_url(contact_topic)\n      contact_topic.reload\n      expect(contact_topic.soft_delete).to be true\n    end\n\n    it \"redirects to edit casa_org\" do\n      delete soft_delete_contact_topic_url(contact_topic)\n      expect(response).to redirect_to(edit_casa_org_path(casa_org))\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/contact_type_groups_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"/contact_type_groups\", type: :request do\n  describe \"GET /contact_type_groups/new\" do\n    context \"logged in as admin user\" do\n      it \"can successfully access a contact type group create page\" do\n        sign_in_as_admin\n\n        get new_contact_type_group_path\n\n        expect(response).to be_successful\n      end\n    end\n\n    context \"logged in as a non-admin user\" do\n      it \"cannot access a contact type group create page\" do\n        sign_in_as_volunteer\n\n        get new_contact_type_group_path\n\n        expect(response).to redirect_to root_path\n        expect(response.request.flash[:notice]).to eq \"Sorry, you are not authorized to perform this action.\"\n      end\n    end\n\n    context \"unauthenticated request\" do\n      it \"cannot access a contact type group create page\" do\n        get new_contact_type_group_path\n\n        expect(response).to redirect_to new_user_session_path\n      end\n    end\n  end\n\n  describe \"POST /contact_type_groups\" do\n    let(:params) { {contact_type_group: {name: \"New Group\", active: true}} }\n\n    context \"logged in as admin user\" do\n      it \"can successfully create a contact type group\" do\n        casa_org = build(:casa_org)\n        sign_in build(:casa_admin, casa_org: casa_org)\n\n        expect {\n          post contact_type_groups_path, params: params\n        }.to change(ContactTypeGroup, :count).by(1)\n\n        group = ContactTypeGroup.last\n\n        expect(group.name).to eql \"New Group\"\n        expect(group.casa_org).to eql casa_org\n        expect(group.active).to be_truthy\n        expect(response).to redirect_to edit_casa_org_path(casa_org)\n        expect(response.request.flash[:notice]).to eq \"Contact Type Group was successfully created.\"\n      end\n    end\n\n    context \"logged in as a non-admin user\" do\n      it \"cannot create a contact type group\" do\n        sign_in_as_volunteer\n\n        post contact_type_groups_path, params: params\n\n        expect(response).to redirect_to root_path\n        expect(response.request.flash[:notice]).to eq \"Sorry, you are not authorized to perform this action.\"\n      end\n    end\n\n    context \"unauthenticated request\" do\n      it \"cannot create a contact type group\" do\n        post contact_type_groups_path, params: params\n\n        expect(response).to redirect_to new_user_session_path\n      end\n    end\n  end\n\n  describe \"GET /contact_type_groups/:id/edit\" do\n    context \"logged in as admin user\" do\n      it \"can successfully access a contact type group edit page\" do\n        sign_in_as_admin\n\n        get edit_contact_type_group_path(create(:contact_type_group))\n\n        expect(response).to be_successful\n      end\n    end\n\n    context \"logged in as a non-admin user\" do\n      it \"cannot access a contact type group edit page\" do\n        sign_in_as_volunteer\n\n        get edit_contact_type_group_path(create(:contact_type_group))\n\n        expect(response).to redirect_to root_path\n        expect(response.request.flash[:notice]).to eq \"Sorry, you are not authorized to perform this action.\"\n      end\n    end\n\n    context \"unauthenticated request\" do\n      it \"cannot access a contact type group edit page\" do\n        get edit_contact_type_group_path(create(:contact_type_group))\n\n        expect(response).to redirect_to new_user_session_path\n      end\n    end\n  end\n\n  describe \"PUT /contact_type_groups/:id\" do\n    let(:params) { {contact_type_group: {name: \"New Group Name\", active: false}} }\n\n    context \"logged in as admin user\" do\n      it \"can successfully update a contact type group\" do\n        casa_org = build(:casa_org)\n        sign_in build(:casa_admin, casa_org: casa_org)\n\n        group = create(:contact_type_group, casa_org: casa_org, active: true)\n\n        put contact_type_group_path(group), params: params\n\n        group.reload\n        expect(group.name).to eq \"New Group Name\"\n        expect(group.casa_org).to eq casa_org\n        expect(group.active).to be_falsey\n\n        expect(response).to redirect_to edit_casa_org_path(casa_org)\n        expect(response.request.flash[:notice]).to eq \"Contact Type Group was successfully updated.\"\n      end\n    end\n\n    context \"logged in as a non-admin user\" do\n      it \"cannot update a update a contact type group\" do\n        sign_in_as_volunteer\n\n        put contact_type_group_path(create(:contact_type_group)), params: params\n\n        expect(response).to redirect_to root_path\n        expect(response.request.flash[:notice]).to eq \"Sorry, you are not authorized to perform this action.\"\n      end\n    end\n\n    context \"unauthenticated request\" do\n      it \"cannot update a update a contact type group\" do\n        put contact_type_group_path(create(:contact_type_group)), params: params\n\n        expect(response).to redirect_to new_user_session_path\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/contact_types_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"/contact_types\", type: :request do\n  let(:group) { create(:contact_type_group) }\n\n  describe \"GET /contact_types/new\" do\n    context \"logged in as admin user\" do\n      it \"can successfully access a contact type create page\" do\n        sign_in_as_admin\n\n        get new_contact_type_path\n\n        expect(response).to be_successful\n      end\n    end\n\n    context \"logged in as a non-admin user\" do\n      it \"cannot access a contact type create page\" do\n        sign_in_as_volunteer\n\n        get new_contact_type_path\n\n        expect(response).to redirect_to root_path\n        expect(response.request.flash[:notice]).to eq \"Sorry, you are not authorized to perform this action.\"\n      end\n    end\n\n    context \"unauthenticated request\" do\n      it \"cannot access a contact type create page\" do\n        get new_contact_type_path\n\n        expect(response).to redirect_to new_user_session_path\n      end\n    end\n  end\n\n  describe \"POST /contact_types\" do\n    let(:params) { {contact_type: {name: \"New Contact\", contact_type_group_id: group.id, active: true}} }\n\n    context \"logged in as admin user\" do\n      it \"can successfully create a contact type\" do\n        casa_org = build(:casa_org)\n        sign_in create(:casa_admin, casa_org: casa_org)\n\n        expect {\n          post contact_types_path, params: params\n        }.to change(ContactType, :count).by(1)\n\n        contact_type = ContactType.last\n\n        expect(contact_type.name).to eql \"New Contact\"\n        expect(contact_type.contact_type_group).to eql group\n        expect(contact_type.active).to be_truthy\n        expect(response).to redirect_to edit_casa_org_path(casa_org)\n        expect(response.request.flash[:notice]).to eq \"Contact Type was successfully created.\"\n      end\n    end\n\n    context \"logged in as a non-admin user\" do\n      it \"cannot create a contact type\" do\n        sign_in_as_volunteer\n\n        post contact_types_path, params: params\n\n        expect(response).to redirect_to root_path\n        expect(response.request.flash[:notice]).to eq \"Sorry, you are not authorized to perform this action.\"\n      end\n    end\n\n    context \"unauthenticated request\" do\n      it \"cannot create a contact type\" do\n        post contact_types_path, params: params\n\n        expect(response).to redirect_to new_user_session_path\n      end\n    end\n  end\n\n  describe \"GET /contact_types/:id/edit\" do\n    context \"logged in as admin user\" do\n      it \"can successfully access a contact type edit page\" do\n        sign_in_as_admin\n\n        get edit_contact_type_path(create(:contact_type))\n\n        expect(response).to be_successful\n      end\n    end\n\n    context \"logged in as a non-admin user\" do\n      it \"cannot access a contact type edit page\" do\n        sign_in_as_volunteer\n\n        get edit_contact_type_path(create(:contact_type))\n\n        expect(response).to redirect_to root_path\n        expect(response.request.flash[:notice]).to eq \"Sorry, you are not authorized to perform this action.\"\n      end\n    end\n\n    context \"unauthenticated request\" do\n      it \"cannot access a contact type edit page\" do\n        get edit_contact_type_path(create(:contact_type))\n\n        expect(response).to redirect_to new_user_session_path\n      end\n    end\n  end\n\n  describe \"PUT /contact_types/:id\" do\n    let(:casa_org) { build(:casa_org) }\n    let(:new_group) { create(:contact_type_group, casa_org: casa_org) }\n    let(:params) { {contact_type: {name: \"New Name\", contact_type_group_id: new_group.id, active: false}} }\n\n    context \"logged in as admin user\" do\n      it \"can successfully update a contact type\" do\n        sign_in build(:casa_admin, casa_org: casa_org)\n\n        contact_type = create(:contact_type, contact_type_group: group)\n\n        put contact_type_path(contact_type), params: params\n\n        contact_type.reload\n        expect(contact_type.name).to eq \"New Name\"\n        expect(contact_type.contact_type_group).to eq new_group\n        expect(contact_type.active).to be_falsey\n\n        expect(response).to redirect_to edit_casa_org_path(casa_org)\n        expect(response.request.flash[:notice]).to eq \"Contact Type was successfully updated.\"\n      end\n    end\n\n    context \"logged in as a non-admin user\" do\n      it \"cannot update a update a contact type\" do\n        sign_in_as_volunteer\n\n        put contact_type_path(create(:contact_type)), params: params\n\n        expect(response).to redirect_to root_path\n        expect(response.request.flash[:notice]).to eq \"Sorry, you are not authorized to perform this action.\"\n      end\n    end\n\n    context \"unauthenticated request\" do\n      it \"cannot update a update a contact type\" do\n        put contact_type_path(create(:contact_type)), params: params\n\n        expect(response).to redirect_to new_user_session_path\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/court_dates_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"/casa_cases/:casa_case_id/court_dates/:id\", type: :request do\n  include DownloadHelpers\n  let(:admin) { create(:casa_admin) }\n  let(:casa_case) { court_date.casa_case }\n  let(:court_date) { create(:court_date) }\n  let(:hearing_type) { create(:hearing_type) }\n  let(:judge) { create(:judge, name: \"8`l/UR*|`=Iab'A\") }\n  let(:valid_attributes) do\n    {\n      date: Date.yesterday,\n      hearing_type_id: hearing_type.id,\n      judge_id: judge.id\n    }\n  end\n  let(:texts) { [\"1-New Order Text One\", \"0-New Order Text Two\"] }\n  let(:implementation_statuses) { [\"unimplemented\", nil] }\n  let(:orders_attributes) do\n    {\n      \"0\" => {text: texts[0], implementation_status: implementation_statuses[0], casa_case_id: casa_case.id},\n      \"1\" => {text: texts[1], implementation_status: implementation_statuses[1], casa_case_id: casa_case.id}\n    }\n  end\n  let(:invalid_attributes) do\n    {\n      date: nil,\n      hearing_type_id: hearing_type.id,\n      judge_id: judge.id\n    }\n  end\n\n  before do\n    travel_to Date.new(2021, 1, 1)\n    sign_in admin\n  end\n\n  describe \"GET /show\" do\n    subject(:show) { get casa_case_court_date_path(casa_case, court_date) }\n\n    before do\n      casa_org = court_date.casa_case.casa_org\n      casa_org.court_report_template.attach(io: File.new(Rails.root.join(\"spec/fixtures/files/default_past_court_date_template.docx\")), filename: \"test_past_date_template.docx\")\n      casa_org.court_report_template.save!\n      show\n    end\n\n    context \"when the request is authenticated\" do\n      it { expect(response).to have_http_status(:success) }\n    end\n\n    context \"when the request is unauthenticated\" do\n      it \"redirects to login page\" do\n        sign_out admin\n        get casa_case_court_date_path(casa_case, court_date)\n        expect(response).to redirect_to new_user_session_path\n      end\n    end\n\n    context \"when request format is word document\" do\n      subject(:show) { get casa_case_court_date_path(casa_case, court_date), headers: headers }\n\n      let(:headers) { {accept: \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\"} }\n\n      it { expect(response).to be_successful }\n\n      it \"displays the court date\" do\n        show\n\n        docx_response = Docx::Document.open(StringIO.new(response.body))\n\n        expect(docx_response.paragraphs.map(&:to_s)).to include(/December 25, 2020/)\n      end\n\n      context \"when a judge is attached\" do\n        let!(:court_date) {\n          create(:court_date, date: Date.yesterday, judge: judge)\n        }\n\n        it \"includes the judge's name in the document\" do\n          show\n\n          docx_response = Docx::Document.open(StringIO.new(response.body))\n\n          expect(docx_response.paragraphs.map(&:to_s)).to include(/#{judge.name}/)\n        end\n      end\n\n      context \"without a judge\" do\n        let!(:court_date) {\n          create(:court_date, date: Date.yesterday, judge: nil)\n        }\n\n        it \"includes None for the judge's name in the document\" do\n          show\n\n          docx_response = Docx::Document.open(StringIO.new(response.body))\n\n          expect(docx_response.paragraphs.map(&:to_s)).not_to include(/#{judge.name}/)\n          expect(docx_response.paragraphs.map(&:to_s)).to include(/Judge:/)\n          expect(docx_response.paragraphs.map(&:to_s)).to include(/None/)\n        end\n      end\n\n      context \"with a hearing type\" do\n        let!(:court_date) {\n          create(:court_date, date: Date.yesterday, hearing_type: hearing_type)\n        }\n\n        it \"includes the hearing type in the document\" do\n          show\n\n          docx_response = Docx::Document.open(StringIO.new(response.body))\n\n          expect(docx_response.paragraphs.map(&:to_s)).to include(/#{hearing_type.name}/)\n        end\n      end\n\n      context \"without a hearing type\" do\n        let!(:court_date) {\n          create(:court_date, date: Date.yesterday, hearing_type: nil)\n        }\n\n        it \"includes None for the hearing type in the document\" do\n          show\n\n          docx_response = Docx::Document.open(StringIO.new(response.body))\n\n          expect(docx_response.paragraphs.map(&:to_s)).not_to include(/#{hearing_type.name}/)\n          expect(docx_response.paragraphs.map(&:to_s)).to include(/Hearing Type:/)\n          expect(docx_response.paragraphs.map(&:to_s)).to include(/None/)\n        end\n      end\n\n      context \"with a court order\" do\n        let!(:court_date) {\n          create(:court_date, :with_court_order)\n        }\n\n        it \"includes court order info\" do\n          show\n\n          docx_response = Docx::Document.open(StringIO.new(response.body))\n\n          expect(docx_response.paragraphs.map(&:to_s)).to include(/Court Orders/)\n          expect(table_text(docx_response)).to include(/#{court_date.case_court_orders.first.text}/)\n          expect(table_text(docx_response)).to include(/#{court_date.case_court_orders.first.implementation_status.humanize}/)\n        end\n      end\n\n      context \"without a court order\" do\n        let!(:court_date) {\n          create(:court_date)\n        }\n\n        it \"does not include court orders section\" do\n          show\n\n          docx_response = Docx::Document.open(StringIO.new(response.body))\n\n          expect(docx_response.paragraphs.map(&:to_s)).not_to include(/Court Orders/)\n        end\n      end\n    end\n  end\n\n  describe \"GET /new\" do\n    it \"renders a successful response\" do\n      get new_casa_case_court_date_path(casa_case)\n      expect(response).to be_successful\n    end\n  end\n\n  describe \"GET /edit\" do\n    it \"render a successful response\" do\n      get edit_casa_case_court_date_path(casa_case, court_date)\n      expect(response).to be_successful\n    end\n\n    it \"fails across organizations\" do\n      other_org = create(:casa_org)\n      other_case = create(:casa_case, casa_org: other_org)\n\n      get edit_casa_case_court_date_path(other_case, court_date)\n      expect(response).to redirect_to(casa_cases_path)\n      expect(response.status).to match 302\n      expect(flash[:notice]).to eq(\"Sorry, you are not authorized to perform this action.\")\n    end\n  end\n\n  describe \"POST /create\" do\n    let(:casa_case) { create(:casa_case) }\n    let(:court_date) { CourtDate.last }\n\n    context \"with valid parameters\" do\n      it \"creates a new CourtDate\" do\n        expect do\n          post casa_case_court_dates_path(casa_case), params: {court_date: valid_attributes}\n        end.to change(CourtDate, :count).by(1)\n      end\n\n      it \"sets the court_report_due_date to be 3 weeks before the court_date\" do\n        post casa_case_court_dates_path(casa_case), params: {court_date: valid_attributes}\n\n        expect(court_date.casa_case.court_dates.last.court_report_due_date).to eq(valid_attributes[:date] - 3.weeks)\n      end\n\n      it \"redirects to the casa_case\" do\n        post casa_case_court_dates_path(casa_case), params: {court_date: valid_attributes}\n        expect(response).to redirect_to(casa_case_court_date_path(casa_case, court_date))\n      end\n\n      it \"sets fields correctly\" do\n        post casa_case_court_dates_path(casa_case), params: {court_date: valid_attributes}\n\n        expect(court_date.casa_case).to eq casa_case\n        expect(court_date.date).to eq Date.yesterday\n        expect(court_date.hearing_type).to eq hearing_type\n        expect(court_date.judge).to eq judge\n      end\n\n      context \"with case_court_orders_attributes being passed as a parameter\" do\n        let(:valid_params) do\n          attributes = valid_attributes\n          attributes[:case_court_orders_attributes] = orders_attributes\n          attributes\n        end\n\n        it \"Creates a new CaseCourtOrder\" do\n          expect do\n            post casa_case_court_dates_path(casa_case), params: {court_date: valid_params}\n          end.to change(CaseCourtOrder, :count).by(2)\n        end\n\n        it \"sets fields correctly\" do\n          post casa_case_court_dates_path(casa_case), params: {court_date: valid_params}\n\n          expect(court_date.case_court_orders.count).to eq 2\n          expect(court_date.case_court_orders[0].text).to eq texts[0]\n          expect(court_date.case_court_orders[0].implementation_status).to eq implementation_statuses[0]\n          expect(court_date.case_court_orders[1].text).to eq texts[1]\n          expect(court_date.case_court_orders[1].implementation_status).to eq implementation_statuses[1]\n        end\n      end\n    end\n\n    context \"for a future court date\" do\n      let(:valid_attributes) do\n        {\n          date: 10.days.from_now,\n          hearing_type_id: hearing_type.id,\n          judge_id: judge.id\n        }\n      end\n\n      it \"creates a new CourtDate\" do\n        expect do\n          post casa_case_court_dates_path(casa_case), params: {court_date: valid_attributes}\n        end.to change(CourtDate, :count).by(1)\n      end\n    end\n\n    describe \"invalid request\" do\n      context \"with invalid parameters\" do\n        it \"does not create a new CourtDate\" do\n          expect do\n            post casa_case_court_dates_path(casa_case), params: {court_date: invalid_attributes}\n          end.not_to change(CourtDate, :count)\n        end\n\n        it \"renders an unprocessable entity response (i.e. to display the 'new' template)\" do\n          post casa_case_court_dates_path(casa_case), params: {court_date: invalid_attributes}\n          expect(response).to have_http_status(:unprocessable_content)\n          expected_errors = [\n            \"Date can't be blank\"\n          ].freeze\n          expect(assigns[:court_date].errors.full_messages).to eq expected_errors\n        end\n      end\n    end\n  end\n\n  describe \"PATCH /update\" do\n    let(:new_attributes) {\n      {\n        date: 1.week.ago.to_date,\n        hearing_type_id: hearing_type.id,\n        judge_id: judge.id,\n        case_court_orders_attributes: orders_attributes\n      }\n    }\n\n    context \"with valid parameters\" do\n      it \"updates the requested court_date\" do\n        patch casa_case_court_date_path(casa_case, court_date), params: {court_date: new_attributes}\n        court_date.reload\n        expect(court_date.date).to eq 1.week.ago.to_date\n        expect(court_date.hearing_type).to eq hearing_type\n        expect(court_date.judge).to eq judge\n        expect(court_date.case_court_orders[0].text).to eq texts[0]\n        expect(court_date.case_court_orders[0].implementation_status).to eq implementation_statuses[0]\n        expect(court_date.case_court_orders[1].text).to eq texts[1]\n        expect(court_date.case_court_orders[1].implementation_status).to eq implementation_statuses[1]\n      end\n\n      it \"redirects to the court_date\" do\n        patch casa_case_court_date_path(casa_case, court_date), params: {court_date: new_attributes}\n\n        expect(response).to redirect_to casa_case_court_date_path(casa_case, court_date)\n      end\n    end\n\n    context \"with invalid parameters\" do\n      it \"renders an unprocessable entity response displaying the edit template\" do\n        patch casa_case_court_date_path(casa_case, court_date), params: {court_date: invalid_attributes}\n\n        expect(response).to have_http_status(:unprocessable_content)\n        expected_errors = [\n          \"Date can't be blank\"\n        ].freeze\n        expect(assigns[:court_date].errors.full_messages).to eq expected_errors\n      end\n    end\n\n    describe \"court orders\" do\n      context \"when the user tries to make an existing order empty\" do\n        let(:orders_updated) do\n          {\n            case_court_orders_attributes: {\n              \"0\" => {\n                text: \"New Order Text One Updated\",\n                implementation_status: :unimplemented\n              },\n              \"1\" => {\n                text: \"\"\n              }\n            }\n          }\n        end\n\n        before do\n          patch casa_case_court_date_path(casa_case, court_date), params: {court_date: new_attributes}\n          court_date.reload\n\n          @first_order_id = court_date.case_court_orders[0].id\n          @second_order_id = court_date.case_court_orders[1].id\n\n          orders_updated[:case_court_orders_attributes][\"0\"][:id] = @first_order_id\n          orders_updated[:case_court_orders_attributes][\"1\"][:id] = @second_order_id\n        end\n\n        it \"still updates the first order\" do\n          expect do\n            patch casa_case_court_date_path(casa_case, court_date), params: {court_date: orders_updated}\n          end.to(\n            change { court_date.reload.case_court_orders.find(@first_order_id).text }\n          )\n        end\n\n        it \"does not update the second order\" do\n          expect do\n            patch casa_case_court_date_path(casa_case, court_date), params: {court_date: orders_updated}\n          end.not_to(\n            change { court_date.reload.case_court_orders.find(@second_order_id).text }\n          )\n        end\n      end\n    end\n\n    it \"does not update across organizations\" do\n      other_org = create(:casa_org)\n      other_casa_case = create(:casa_case, case_number: \"abc\", casa_org: other_org)\n\n      expect do\n        patch casa_case_court_date_path(other_casa_case, court_date), params: {court_date: new_attributes}\n      end.not_to(\n        change { court_date.reload.date }\n      )\n    end\n  end\n\n  describe \"DELETE /destroy\" do\n    subject(:request) do\n      delete casa_case_court_date_path(casa_case, court_date)\n\n      response\n    end\n\n    shared_examples \"successful deletion\" do\n      it \"removes court date record\" do\n        court_date\n        expect { request }.to change { CourtDate.count }.by(-1)\n      end\n\n      it { is_expected.to redirect_to(casa_case_path(casa_case)) }\n\n      it \"shows correct flash message\" do\n        request\n        expect(flash[:notice]).to match(/Court date was successfully deleted./)\n      end\n    end\n\n    shared_examples \"unsuccessful deletion\" do\n      it \"does not remove court date record\" do\n        court_date\n        expect { request }.not_to change { CourtDate.count }\n      end\n\n      it { is_expected.to redirect_to(casa_case_court_date_path(casa_case, court_date)) }\n\n      it \"shows correct flash message\" do\n        request\n        expect(flash[:notice]).to match(/You can delete only future court dates./)\n      end\n    end\n\n    context \"when the court date is in the past\" do\n      it_behaves_like \"unsuccessful deletion\"\n    end\n\n    context \"when the court date is today\" do\n      let(:court_date) { create(:court_date, date: Date.current) }\n\n      it_behaves_like \"unsuccessful deletion\"\n    end\n\n    context \"when the court date is in the future\" do\n      let(:court_date) { create(:court_date, date: 1.day.from_now) }\n\n      it_behaves_like \"successful deletion\"\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/custom_org_links_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"/custom_org_links\", type: :request do\n  let(:casa_org) { create(:casa_org) }\n  let(:casa_admin) { create :casa_admin, casa_org: casa_org }\n  let(:volunteer) { create :volunteer, casa_org: casa_org, active: true }\n\n  describe \"GET /custom_org_links/new\" do\n    context \"when logged in as admin user\" do\n      before { sign_in casa_admin }\n\n      it \"can successfully access a custom org link create page\" do\n        get new_custom_org_link_path\n        expect(response).to be_successful\n      end\n    end\n\n    context \"when logged in as a non-admin user\" do\n      before { sign_in volunteer }\n\n      it \"cannot access a custom org link create page\" do\n        get new_custom_org_link_path\n        expect(response).to redirect_to root_path\n        expect(response.request.flash[:notice]).to eq \"Sorry, you are not authorized to perform this action.\"\n      end\n    end\n\n    context \"when not logged in\" do\n      it \"cannot access a custom org link create page\" do\n        get new_custom_org_link_path\n        expect(response).to redirect_to new_user_session_path\n      end\n    end\n  end\n\n  describe \"POST /custom_org_links\" do\n    let(:params) { {custom_org_link: {text: \"New Custom Link\", url: \"http://www.custom.link\", active: true}} }\n\n    context \"when logged in as admin user\" do\n      let(:expected_custom_link_attributes) { params[:custom_org_link].merge(casa_org_id: casa_org.id).stringify_keys }\n      before { sign_in casa_admin }\n\n      it \"can successfully create a custom org link\" do\n        expect { post custom_org_links_path, params: params }.to change { CustomOrgLink.count }.by(1)\n        expect(CustomOrgLink.last.attributes).to include(**expected_custom_link_attributes)\n        expect(response).to redirect_to edit_casa_org_path(casa_org)\n        expect(response.request.flash[:notice]).to eq \"Custom link was successfully created.\"\n      end\n    end\n\n    context \"when logged in as a non-admin user\" do\n      before { sign_in volunteer }\n\n      it \"cannot create a custom org link\" do\n        post custom_org_links_path, params: params\n        expect(response).to redirect_to root_path\n        expect(response.request.flash[:notice]).to eq \"Sorry, you are not authorized to perform this action.\"\n      end\n    end\n\n    context \"when not logged in\" do\n      it \"cannot create a custom org link\" do\n        post custom_org_links_path, params: params\n        expect(response).to redirect_to new_user_session_path\n      end\n    end\n  end\n\n  describe \"GET /custom_org_links/:id/edit\" do\n    context \"when logged in as admin user\" do\n      before { sign_in_as_admin }\n\n      it \"can successfully access a contact type edit page\" do\n        get edit_custom_org_link_path(create(:custom_org_link))\n        expect(response).to be_successful\n      end\n    end\n\n    context \"when logged in as a non-admin user\" do\n      before { sign_in_as_volunteer }\n\n      it \"cannot access a contact type edit page\" do\n        get edit_custom_org_link_path(create(:custom_org_link))\n        expect(response).to redirect_to root_path\n        expect(response.request.flash[:notice]).to eq \"Sorry, you are not authorized to perform this action.\"\n      end\n    end\n\n    context \"when not logged in\" do\n      it \"cannot access a contact type edit page\" do\n        get edit_custom_org_link_path(create(:custom_org_link))\n        expect(response).to redirect_to new_user_session_path\n      end\n    end\n  end\n\n  describe \"PUT /custom_org_links/:id\" do\n    let!(:custom_org_link) { create :custom_org_link, casa_org: casa_org, text: \"Existing Link\", url: \"http://existing.com\", active: false }\n    let(:params) { {custom_org_link: {text: \"New Custom Link\", url: \"http://www.custom.link\", active: true}} }\n\n    context \"when logged in as admin user\" do\n      let(:expected_custom_link_attributes) { params[:custom_org_link].merge(casa_org_id: casa_org.id).stringify_keys }\n      before { sign_in casa_admin }\n\n      it \"can successfully update a custom org link\" do\n        expect { put custom_org_link_path(custom_org_link), params: params }.to_not change { CustomOrgLink.count }\n        expect(custom_org_link.reload.attributes).to include(**expected_custom_link_attributes)\n        expect(response).to redirect_to edit_casa_org_path(casa_org)\n        expect(response.request.flash[:notice]).to eq \"Custom link was successfully updated.\"\n      end\n    end\n\n    context \"when logged in as a non-admin user\" do\n      before { sign_in volunteer }\n\n      it \"cannot update a custom org link\" do\n        put custom_org_link_path(custom_org_link), params: params\n        expect(response).to redirect_to root_path\n        expect(response.request.flash[:notice]).to eq \"Sorry, you are not authorized to perform this action.\"\n      end\n    end\n\n    context \"when not logged in\" do\n      it \"cannot update a custom org link\" do\n        put custom_org_link_path(custom_org_link), params: params\n        expect(response).to redirect_to new_user_session_path\n      end\n    end\n  end\n\n  describe \"DELETE /custom_org_links/:id\" do\n    let!(:custom_org_link) { create :custom_org_link, casa_org: casa_org, text: \"Existing Link\", url: \"http://existing.com\", active: false }\n\n    context \"when logged in as admin user\" do\n      before { sign_in casa_admin }\n\n      it \"can successfully delete a custom org link\" do\n        expect { delete custom_org_link_path(custom_org_link) }.to change { CustomOrgLink.count }.by(-1)\n        expect(response).to redirect_to edit_casa_org_path(casa_org)\n        expect(response.request.flash[:notice]).to eq \"Custom link was successfully deleted.\"\n      end\n    end\n\n    context \"when logged in as a non-admin user\" do\n      before { sign_in volunteer }\n\n      it \"cannot delete a custom org link\" do\n        delete custom_org_link_path(custom_org_link)\n        expect(response).to redirect_to root_path\n        expect(response.request.flash[:notice]).to eq \"Sorry, you are not authorized to perform this action.\"\n      end\n    end\n\n    context \"when not logged in\" do\n      it \"cannot delete a custom org link\" do\n        delete custom_org_link_path(custom_org_link)\n        expect(response).to redirect_to new_user_session_path\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/dashboard_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"/dashboard\", type: :request do\n  let(:organization) { create(:casa_org) }\n\n  context \"as a volunteer\" do\n    let(:volunteer) { create(:volunteer, casa_org: organization) }\n    let!(:case_assignment) { create(:case_assignment, volunteer: volunteer) }\n\n    before do\n      sign_in volunteer\n    end\n\n    describe \"GET /show\" do\n      context \"with one active case\" do\n        it \"redirects to the new case contact\" do\n          get root_url\n\n          expect(response).to redirect_to(new_case_contact_path)\n        end\n      end\n\n      context \"more than one active case\" do\n        let!(:active_case_assignment) { create :case_assignment, volunteer: volunteer }\n\n        it \"renders a successful response\" do\n          get root_url\n\n          expect(response).to redirect_to(casa_cases_path)\n        end\n\n        it \"shows my cases\" do\n          get root_url\n          follow_redirect!\n\n          expect(response.body).to include(active_case_assignment.casa_case.case_number)\n          expect(response.body).to include(case_assignment.casa_case.case_number)\n        end\n\n        it \"doesn't show other volunteers' cases\" do\n          not_logged_in_volunteer = create(:volunteer)\n          create(:case_assignment, volunteer: not_logged_in_volunteer)\n\n          get root_url\n          follow_redirect!\n\n          expect(response.body).to include(active_case_assignment.casa_case.case_number)\n          expect(response.body).to include(case_assignment.casa_case.case_number)\n          expect(response.body).not_to include(not_logged_in_volunteer.casa_cases.first.case_number)\n        end\n\n        it \"doesn't show other organizations' cases\" do\n          different_org = create(:casa_org)\n          not_my_case_assignment = create(:case_assignment, casa_org: different_org)\n\n          get root_url\n          follow_redirect!\n\n          expect(response.body).to include(active_case_assignment.casa_case.case_number)\n          expect(response.body).to include(case_assignment.casa_case.case_number)\n          expect(response.body).not_to include(not_my_case_assignment.casa_case.case_number)\n        end\n      end\n    end\n  end\n\n  context \"as a supervisor\" do\n    let(:supervisor) { create(:supervisor, casa_org: organization) }\n\n    before do\n      sign_in supervisor\n    end\n\n    describe \"GET /show\" do\n      it \"redirects to the volunteers overview\" do\n        get root_url\n\n        expect(response).to redirect_to(volunteers_url)\n      end\n    end\n  end\n\n  context \"as an admin\" do\n    let(:admin) { create(:casa_admin, casa_org: organization) }\n\n    before do\n      sign_in admin\n    end\n\n    describe \"GET /show\" do\n      it \"renders a successful response\" do\n        get root_url\n\n        expect(response).to redirect_to(supervisors_path)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/emancipation_checklists_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"/emancipation_checklists\", type: :request do\n  describe \"GET /index\" do\n    before { sign_in volunteer }\n\n    context \"when viewing the page as a volunteer\" do\n      let(:volunteer) { build(:volunteer) }\n\n      context \"when viewing the page with exactly one transitioning case\" do\n        let(:casa_case) { build(:casa_case, casa_org: volunteer.casa_org) }\n        let!(:case_assignment) { create(:case_assignment, volunteer: volunteer, casa_case: casa_case) }\n\n        it \"redirects to the emancipation checklist page for that case\" do\n          get emancipation_checklists_path\n          expect(response).to redirect_to(casa_case_emancipation_path(casa_case))\n        end\n      end\n\n      context \"when viewing the page with zero transitioning cases\" do\n        it \"renders a successful response\" do\n          get emancipation_checklists_path\n          expect(response).to be_successful\n        end\n      end\n\n      context \"when viewing the page with more than one transitioning cases\" do\n        let(:casa_case_a) { build(:casa_case, casa_org: volunteer.casa_org) }\n        let(:casa_case_b) { build(:casa_case, casa_org: volunteer.casa_org) }\n        let!(:case_assignment_a) { create(:case_assignment, volunteer: volunteer, casa_case: casa_case_a) }\n        let!(:case_assignment_b) { create(:case_assignment, volunteer: volunteer, casa_case: casa_case_b) }\n\n        it \"renders a successful response\" do\n          get emancipation_checklists_path\n          expect(response).to be_successful\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/emancipations_request_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"/casa_case/:id/emancipation\", type: :request do\n  let(:organization) { build(:casa_org) }\n  let(:other_organization) { create(:casa_org) }\n  let(:casa_case) { create(:casa_case, casa_org: organization, birth_month_year_youth: 15.years.ago) }\n  let(:casa_admin) { create(:casa_admin, casa_org: organization) }\n\n  describe \"GET /show\" do\n    subject(:request) do\n      get casa_case_emancipation_path(casa_case, :docx)\n\n      response\n    end\n\n    before { sign_in casa_admin }\n\n    it { is_expected.to be_successful }\n\n    it \"authorizes casa case\" do\n      expect_any_instance_of(EmancipationsController).to receive(:authorize).with(casa_case).and_call_original\n      request\n    end\n\n    it \"populates and sends correct emancipation template\" do\n      sablon_template = double(\"Sablon::Template\")\n      allow(Sablon).to(\n        receive(:template).with(\n          File.expand_path(\"app/documents/templates/emancipation_checklist_template.docx\")\n        ).and_return(sablon_template)\n      )\n      allow(Sablon).to receive(:content).and_return([])\n\n      expect(EmancipationChecklistDownloadHtml).to receive(:new).with(casa_case, []).and_call_original\n\n      expected_context = {case_number: casa_case.case_number, emancipation_checklist: []}\n      expect(sablon_template).to(\n        receive(:render_to_string).with(expected_context, type: :docx).and_return(\"rendered context\")\n      )\n\n      expect_any_instance_of(EmancipationsController).to(\n        receive(:send_data).with(\n          \"rendered context\", filename: \"#{casa_case.case_number} Emancipation Checklist.docx\"\n        ).and_call_original\n      )\n      request\n    end\n\n    context \"when request is not .docx\" do\n      subject(:request) do\n        get casa_case_emancipation_path(casa_case)\n\n        response\n      end\n\n      it { is_expected.to be_successful }\n\n      it \"does not send any data\" do\n        expect_any_instance_of(EmancipationsController).not_to receive(:send_data)\n        request\n      end\n    end\n  end\n\n  describe \"POST /save\" do\n    subject(:request) do\n      post save_casa_case_emancipation_path(casa_case), params: params\n\n      response\n    end\n\n    before { sign_in casa_admin }\n\n    let(:category) { create(:emancipation_category) }\n    let(:option_a) { create(:emancipation_option, emancipation_category_id: category.id, name: \"A\") }\n    let(:params) { {check_item_action: \"add_option\", check_item_id: option_a.id} }\n\n    it { is_expected.to be_successful }\n\n    it \"authorizes save_emancipation?\" do\n      expect_any_instance_of(EmancipationsController).to(\n        receive(:authorize).with(CasaCase, :save_emancipation?).and_call_original\n      )\n      expect_any_instance_of(EmancipationsController).to(\n        receive(:authorize).with(casa_case, :update_emancipation_option?).and_call_original\n      )\n      request\n    end\n\n    context \"when check_item_id is invalid\" do\n      let(:params) { {check_item_action: \"add_option\", check_item_id: -1} }\n\n      it { is_expected.not_to be_successful }\n\n      it \"shows correct error message\" do\n        body = request.parsed_body\n        expect(body).to eq({\"error\" => \"Tried to destroy an association that does not exist\"})\n      end\n    end\n\n    context \"when check_item_action is invalid\" do\n      let(:params) { {check_item_action: \"invalid\", check_item_id: option_a.id} }\n\n      it { is_expected.not_to be_successful }\n\n      it \"shows correct error message\" do\n        body = request.parsed_body\n        expect(body).to eq({\"error\" => \"Check item action: invalid is not a supported action\"})\n      end\n    end\n\n    context \"when casa_case is not transitioning\" do\n      let(:params) { {check_item_action: \"add_option\", check_item_id: option_a.id} }\n      let(:casa_case) do\n        create(:casa_case,\n          casa_org: organization, emancipation_options: [], emancipation_categories: [],\n          birth_month_year_youth: 13.years.ago)\n      end\n\n      it { is_expected.not_to be_successful }\n\n      it \"shows correct error message\" do\n        body = request.parsed_body\n        expect(body).to eq({\"error\" => \"The current case is not marked as transitioning\"})\n      end\n    end\n\n    describe \"each check_item_action\" do\n      context \"with the add_category action\" do\n        let(:params) { {check_item_action: \"add_category\", check_item_id: category.id} }\n\n        it { is_expected.to be_successful }\n\n        it \"adds the category\" do\n          expect { request }.to change { casa_case.emancipation_categories.count }.by(1)\n        end\n\n        context \"when the category is already added to the case\" do\n          let(:casa_case) do\n            create(:casa_case, casa_org: organization, emancipation_categories: [category])\n          end\n\n          it { is_expected.not_to be_successful }\n\n          it \"does not add the category\" do\n            expect { request }.not_to change { casa_case.emancipation_categories.count }\n          end\n\n          it \"shows correct error message\" do\n            body = request.parsed_body\n            expect(body).to eq({\"error\" => \"The record already exists as an association on the case\"})\n          end\n        end\n      end\n\n      context \"with the add_option action\" do\n        let(:params) { {check_item_action: \"add_option\", check_item_id: option_a.id} }\n\n        it { is_expected.to be_successful }\n\n        it \"adds the option\" do\n          expect { request }.to change { casa_case.emancipation_options.count }.by(1)\n        end\n\n        context \"when the option is already added to the case\" do\n          let(:casa_case) do\n            create(:casa_case, casa_org: organization, emancipation_options: [option_a])\n          end\n\n          it { is_expected.not_to be_successful }\n\n          it \"does not add the option\" do\n            expect { request }.not_to change { casa_case.emancipation_options.count }\n          end\n\n          it \"shows correct error message\" do\n            body = request.parsed_body\n            expect(body).to eq({\"error\" => \"The record already exists as an association on the case\"})\n          end\n        end\n      end\n\n      context \"with the delete_category action\" do\n        let(:params) { {check_item_action: \"delete_category\", check_item_id: category.id} }\n        let(:casa_case) do\n          create(:casa_case,\n            casa_org: organization, emancipation_categories: [category], emancipation_options: [option_a])\n        end\n\n        it { is_expected.to be_successful }\n\n        it \"removes the category\" do\n          expect { request }.to change { casa_case.emancipation_categories.count }.by(-1)\n        end\n\n        it \"removes all options associated with the category\" do\n          expect { request }.to change { casa_case.emancipation_options.count }.by(-1)\n        end\n\n        context \"when the category is not added to the case\" do\n          let(:casa_case) { create(:casa_case, casa_org: organization, emancipation_categories: []) }\n\n          it { is_expected.not_to be_successful }\n\n          it \"does not remove anything\" do\n            expect { request }.not_to change { casa_case.emancipation_categories.count }\n          end\n\n          it \"shows correct error message\" do\n            body = request.parsed_body\n            expect(body).to eq({\"error\" => \"Tried to destroy an association that does not exist\"})\n          end\n        end\n      end\n\n      context \"with the delete_option action\" do\n        let(:params) { {check_item_action: \"delete_option\", check_item_id: option_a.id} }\n        let(:casa_case) do\n          create(:casa_case, casa_org: organization, emancipation_options: [option_a])\n        end\n\n        it { is_expected.to be_successful }\n\n        it \"removes the option\" do\n          expect { request }.to change { casa_case.emancipation_options.count }.by(-1)\n        end\n\n        context \"when the option is not added to the case\" do\n          let(:casa_case) do\n            create(:casa_case, casa_org: organization, emancipation_options: [])\n          end\n\n          it { is_expected.not_to be_successful }\n\n          it \"does not remove anything\" do\n            expect { request }.not_to change { casa_case.emancipation_options.count }\n          end\n\n          it \"shows correct error message\" do\n            body = request.parsed_body\n            expect(body).to eq({\"error\" => \"Tried to destroy an association that does not exist\"})\n          end\n        end\n      end\n\n      context \"with the set_option action\" do\n        let(:other_category) { create(:emancipation_category) }\n        let(:options) { create_list(:emancipation_option, 3, emancipation_category_id: other_category.id) }\n        let(:option) { options.first }\n        let(:params) { {check_item_action: \"set_option\", check_item_id: option.id} }\n\n        let(:casa_case) do\n          create(:casa_case, casa_org: organization, emancipation_options: [option_a, *options])\n        end\n\n        it { is_expected.to be_successful }\n\n        it \"sets the option according to the right category\" do\n          request\n          expect(casa_case.reload.emancipation_options).to contain_exactly(option_a, option)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/error_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"/error\", type: :request do\n  describe \"GET /error\" do\n    it \"renders the error test page\" do\n      get error_path\n\n      expect(response).to be_successful\n    end\n  end\n\n  describe \"POST /error\" do\n    it \"raises an error causing an internal server error\" do\n      expect {\n        post error_path\n      }.to raise_error(StandardError, /This is an intentional test exception/)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/followup_reports_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe \"FollowupReports\", type: :request do\n  describe \"GET /index\" do\n    context \"when the user has access\" do\n      let(:admin) { build(:casa_admin) }\n\n      it \"returns the CSV report\" do\n        sign_in admin\n\n        get followup_reports_path(format: :csv)\n\n        expect(response).to have_http_status(:success)\n        expect(response.header[\"Content-Type\"]).to eq(\"text/csv\")\n        expect(response.headers[\"Content-Disposition\"]).to(\n          match(\"followup-report-#{Time.current.strftime(\"%Y-%m-%d\")}.csv\")\n        )\n      end\n\n      it \"adds the correct headers to the csv\" do\n        sign_in admin\n\n        get followup_reports_path(format: :csv)\n\n        csv_headers = [\n          \"Case Number\",\n          \"Volunteer Name(s)\",\n          \"Note Creator Name\",\n          \"Note\"\n        ]\n\n        csv_headers.each { |header| expect(response.body).to include(header) }\n      end\n    end\n\n    context \"when the user is not authorized to access\" do\n      it \"redirects to root and displays an unauthorized message\" do\n        volunteer = build(:volunteer)\n        sign_in volunteer\n\n        get followup_reports_path(format: :csv)\n\n        expect(response).to redirect_to root_path\n        expect(response.request.flash[:notice]).to eq \"Sorry, you are not authorized to perform this action.\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/fund_requests_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe FundRequestsController, type: :request do\n  describe \"GET /casa_cases/:casa_id/fund_request/new\" do\n    context \"when volunteer\" do\n      context \"when casa_case is within organization\" do\n        it \"is successful\" do\n          volunteer = create(:volunteer, :with_casa_cases)\n          casa_case = volunteer.casa_cases.first\n\n          sign_in volunteer\n          get new_casa_case_fund_request_path(casa_case)\n\n          expect(response).to be_successful\n        end\n      end\n\n      context \"when casa case is not within organization\" do\n        it \"redirects to root\" do\n          volunteer = create(:volunteer)\n          casa_case = create(:casa_case, casa_org: create(:casa_org))\n\n          sign_in volunteer\n          get new_casa_case_fund_request_path(casa_case)\n\n          expect(response).to redirect_to root_path\n        end\n      end\n    end\n\n    context \"when supervisor\" do\n      context \"when casa_case is within organization\" do\n        it \"is successful\" do\n          org = create(:casa_org)\n          supervisor = create(:supervisor, casa_org: org)\n          casa_case = create(:casa_case, casa_org: org)\n\n          sign_in supervisor\n          get new_casa_case_fund_request_path(casa_case)\n\n          expect(response).to be_successful\n        end\n      end\n\n      context \"when casa_case is not within organization\" do\n        it \"redirects to root\" do\n          supervisor = create(:supervisor)\n          casa_case = create(:casa_case, casa_org: create(:casa_org))\n\n          sign_in supervisor\n          get new_casa_case_fund_request_path(casa_case)\n\n          expect(response).to redirect_to root_path\n        end\n      end\n    end\n\n    context \"when admin\" do\n      context \"when casa_case is within organization\" do\n        it \"is successful\" do\n          org = create(:casa_org)\n          admin = create(:casa_admin, casa_org: org)\n          casa_case = create(:casa_case, casa_org: org)\n\n          sign_in admin\n          get new_casa_case_fund_request_path(casa_case)\n\n          expect(response).to be_successful\n        end\n      end\n\n      context \"when casa_case is not within organization\" do\n        it \"redirects to root\" do\n          admin = create(:casa_admin)\n          casa_case = create(:casa_case, casa_org: create(:casa_org))\n\n          sign_in admin\n          get new_casa_case_fund_request_path(casa_case)\n\n          expect(response).to redirect_to root_path\n        end\n      end\n    end\n  end\n\n  describe \"POST /casa_cases/:casa_id/fund_request\" do\n    let(:params) do\n      {\n        fund_request: {\n          submitter_email: \"submitter@example.com\",\n          youth_name: \"CINA-123\",\n          payment_amount: \"$10.00\",\n          deadline: \"2022-12-31\",\n          request_purpose: \"something noble\",\n          payee_name: \"Minnie Mouse\",\n          requested_by_and_relationship: \"Favorite Volunteer\",\n          other_funding_source_sought: \"Some other agency\",\n          impact: \"Great\",\n          extra_information: \"foo bar\"\n        }\n      }\n    end\n\n    context \"when volunteer\" do\n      context \"when casa_case is within organization\" do\n        context \"with valid params\" do\n          it \"creates fund request, calls mailer, and redirects to casa case\" do\n            volunteer = create(:volunteer, :with_casa_cases)\n            casa_case = volunteer.casa_cases.first\n            stub_const(\"ENV\", {\"FUND_REQUEST_RECIPIENT_EMAIL\" => \"recipient@example.com\"})\n\n            sign_in volunteer\n\n            expect {\n              post casa_case_fund_request_path(casa_case), params: params\n            }.to change(FundRequest, :count).by(1)\n              .and change(ActionMailer::Base.deliveries, :count).by(1)\n\n            fr = FundRequest.last\n            aggregate_failures do\n              expect(fr.submitter_email).to eq \"submitter@example.com\"\n              expect(fr.youth_name).to eq \"CINA-123\"\n              expect(fr.payment_amount).to eq \"$10.00\"\n              expect(fr.deadline).to eq \"2022-12-31\"\n              expect(fr.request_purpose).to eq \"something noble\"\n              expect(fr.payee_name).to eq \"Minnie Mouse\"\n              expect(fr.requested_by_and_relationship).to eq \"Favorite Volunteer\"\n              expect(fr.other_funding_source_sought).to eq \"Some other agency\"\n              expect(fr.impact).to eq \"Great\"\n              expect(fr.extra_information).to eq \"foo bar\"\n              expect(response).to redirect_to casa_case\n            end\n\n            mail = ActionMailer::Base.deliveries.last\n            aggregate_failures do\n              expect(mail.subject).to eq(\"Fund request from submitter@example.com\")\n              expect(mail.to).to contain_exactly(\"recipient@example.com\", \"submitter@example.com\")\n              expect(mail.body.encoded).to include(\"Youth name\")\n              expect(mail.body.encoded).to include(\"CINA-123\")\n              expect(mail.body.encoded).to include(\"Payment amount\")\n              expect(mail.body.encoded).to include(\"$10.00\")\n              expect(mail.body.encoded).to include(\"Deadline\")\n              expect(mail.body.encoded).to include(\"2022-12-31\")\n              expect(mail.body.encoded).to include(\"Request purpose\")\n              expect(mail.body.encoded).to include(\"something noble\")\n              expect(mail.body.encoded).to include(\"Payee name\")\n              expect(mail.body.encoded).to include(\"Minnie Mouse\")\n              expect(mail.body.encoded).to include(\"Requested by and relationship\")\n              expect(mail.body.encoded).to include(\"Favorite Volunteer\")\n              expect(mail.body.encoded).to include(\"Other funding source sought\")\n              expect(mail.body.encoded).to include(\"Some other agency\")\n              expect(mail.body.encoded).to include(\"Impact\")\n              expect(mail.body.encoded).to include(\"Great\")\n              expect(mail.body.encoded).to include(\"Extra information\")\n              expect(mail.body.encoded).to include(\"foo bar\")\n            end\n          end\n        end\n\n        context \"with invalid params\" do\n          it \"does not create fund request or call mailer\" do\n            volunteer = create(:volunteer, :with_casa_cases)\n            casa_case = volunteer.casa_cases.first\n            allow_any_instance_of(FundRequest).to receive(:save).and_return(false)\n\n            sign_in volunteer\n            expect(FundRequestMailer).not_to receive(:send_request)\n            expect {\n              post casa_case_fund_request_path(casa_case), params: params\n            }.not_to change(FundRequest, :count)\n\n            expect(response).to have_http_status(:unprocessable_content)\n          end\n        end\n      end\n\n      context \"when casa_case is not within organization\" do\n        it \"does not create fund request or call mailer\" do\n          volunteer = create(:volunteer, :with_casa_cases)\n          casa_case = create(:casa_case, casa_org: create(:casa_org))\n\n          sign_in volunteer\n          expect(FundRequestMailer).not_to receive(:send_request)\n          expect {\n            post casa_case_fund_request_path(casa_case), params: params\n          }.not_to change(FundRequest, :count)\n\n          expect(response).to redirect_to root_path\n        end\n      end\n    end\n\n    context \"when supervisor\" do\n      it \"creates fund request, calls mailer, and redirects to casa case\" do\n        supervisor = create(:supervisor)\n        casa_case = create(:casa_case)\n        mailer_mock = double(\"mailer\", deliver: nil)\n\n        sign_in supervisor\n\n        expect(FundRequestMailer).to receive(:send_request).with(nil, instance_of(FundRequest)).and_return(mailer_mock)\n        expect(mailer_mock).to receive(:deliver)\n        expect {\n          post casa_case_fund_request_path(casa_case), params: params\n        }.to change(FundRequest, :count).by(1)\n\n        expect(response).to redirect_to casa_case\n      end\n    end\n\n    context \"when admin\" do\n      it \"creates fund request, calls mailer, and redirects to casa case\" do\n        admin = create(:casa_admin)\n        casa_case = create(:casa_case)\n        mailer_mock = double(\"mailer\", deliver: nil)\n\n        sign_in admin\n\n        expect(FundRequestMailer).to receive(:send_request).with(nil, instance_of(FundRequest)).and_return(mailer_mock)\n        expect(mailer_mock).to receive(:deliver)\n        expect {\n          post casa_case_fund_request_path(casa_case), params: params\n        }.to change(FundRequest, :count).by(1)\n\n        expect(response).to redirect_to casa_case\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/health_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"Health\", type: :request do\n  before do\n    Casa::Application.load_tasks\n    Rake::Task[\"after_party:store_deploy_time\"].invoke\n  end\n\n  describe \"GET /health\" do\n    before do\n      get \"/health\"\n    end\n\n    it \"renders an html file\" do\n      # delete this test when there are more specific tests about the page\n      expect(response.header[\"Content-Type\"]).to include(\"text/html\")\n    end\n  end\n\n  describe \"GET /health.json\" do\n    before do\n      get \"/health.json\"\n    end\n\n    it \"renders a json file\" do\n      expect(response.header[\"Content-Type\"]).to include(\"application/json\")\n    end\n\n    it \"has key latest_deploy_time\" do\n      hash_body = nil # This is here for the linter\n      expect { hash_body = JSON.parse(response.body).with_indifferent_access }.not_to raise_exception\n      expect(hash_body.keys).to contain_exactly(\"latest_deploy_time\")\n    end\n  end\n\n  describe \"GET #case_contacts_creation_times_in_last_week\" do\n    it \"returns timestamps of case contacts created in the last week\" do\n      case_contact1 = create(:case_contact, created_at: 6.days.ago)\n      case_contact2 = create(:case_contact, created_at: 2.weeks.ago)\n      get case_contacts_creation_times_in_last_week_health_index_path\n      expect(response).to have_http_status(:ok)\n      expect(response.content_type).to include(\"application/json\")\n      timestamps = JSON.parse(response.body)[\"timestamps\"]\n      expect(timestamps).to include(case_contact1.created_at.to_i)\n      expect(timestamps).not_to include(case_contact2.created_at.to_i)\n    end\n  end\n\n  describe \"GET #monthly_line_graph_data\" do\n    def setup\n      # Create case contacts for testing\n      create(:case_contact, notes: \"Test Notes\", created_at: 11.months.ago)\n      create(:case_contact, notes: \"\", created_at: 11.months.ago)\n      create(:case_contact, created_at: 10.months.ago)\n      create(:case_contact, created_at: 9.months.ago)\n\n      # Create associated contact_topic_answers\n      create(:contact_topic_answer, case_contact: CaseContact.first)\n      create(:contact_topic_answer, case_contact: CaseContact.last)\n    end\n\n    it \"returns case contacts creation times in the last year\" do\n      travel_to Time.zone.local(2024, 5, 2)\n      setup\n\n      get monthly_line_graph_data_health_index_path\n\n      expect(response).to have_http_status(:ok)\n      expect(response.content_type).to include(\"application/json\")\n\n      chart_data = JSON.parse(response.body)\n      expect(chart_data).to be_an(Array)\n      expect(chart_data.length).to eq(12)\n\n      expect(chart_data[0]).to eq([11.months.ago.strftime(\"%b %Y\"), 2, 1, 2])\n      expect(chart_data[1]).to eq([10.months.ago.strftime(\"%b %Y\"), 1, 0, 1])\n      expect(chart_data[2]).to eq([9.months.ago.strftime(\"%b %Y\"), 1, 1, 1])\n      expect(chart_data[3]).to eq([8.months.ago.strftime(\"%b %Y\"), 0, 0, 0])\n    end\n\n    it \"returns case contacts creation times in the last year (on the first of the month)\" do\n      travel_to Time.zone.local(2024, 5, 1)\n      setup\n\n      get monthly_line_graph_data_health_index_path\n\n      expect(response).to have_http_status(:ok)\n      expect(response.content_type).to include(\"application/json\")\n\n      chart_data = JSON.parse(response.body)\n      expect(chart_data).to be_an(Array)\n      expect(chart_data.length).to eq(12)\n\n      expect(chart_data[0]).to eq([11.months.ago.strftime(\"%b %Y\"), 2, 1, 2])\n      expect(chart_data[1]).to eq([10.months.ago.strftime(\"%b %Y\"), 1, 0, 1])\n      expect(chart_data[2]).to eq([9.months.ago.strftime(\"%b %Y\"), 1, 1, 1])\n      expect(chart_data[3]).to eq([8.months.ago.strftime(\"%b %Y\"), 0, 0, 0])\n    end\n  end\n\n  describe \"GET #monthly_unique_users_graph_data\" do\n    def setup\n      volunteer1 = create(:user, type: \"Volunteer\")\n      volunteer2 = create(:user, type: \"Volunteer\")\n      supervisor = create(:user, type: \"Supervisor\")\n      casa_admin = create(:user, type: \"CasaAdmin\")\n      supervisor_volunteer1 = create(:supervisor_volunteer, is_active: true)\n      supervisor_volunteer2 = create(:supervisor_volunteer, is_active: true)\n\n      create(:login_activity, user: volunteer1, created_at: 11.months.ago, success: true)\n      create(:login_activity, user: volunteer2, created_at: 11.months.ago, success: true)\n      create(:login_activity, user: supervisor, created_at: 11.months.ago, success: true)\n      create(:login_activity, user: casa_admin, created_at: 11.months.ago, success: true)\n      create(:login_activity, user: volunteer1, created_at: 10.months.ago, success: true)\n      create(:login_activity, user: volunteer2, created_at: 9.months.ago, success: true)\n      create(:login_activity, user: supervisor, created_at: 9.months.ago, success: true)\n      create(:login_activity, user: casa_admin, created_at: 9.months.ago, success: true)\n      create(:case_contact, creator_id: supervisor_volunteer1.volunteer_id, created_at: 11.months.ago)\n      create(:case_contact, creator_id: supervisor_volunteer1.volunteer_id, created_at: 11.months.ago)\n      create(:case_contact, creator_id: supervisor_volunteer2.volunteer_id, created_at: 11.months.ago)\n      create(:case_contact, creator_id: supervisor_volunteer1.volunteer_id, created_at: 10.months.ago)\n    end\n\n    it \"returns monthly unique users data for volunteers, supervisors, and admins in the last year\" do\n      travel_to Time.zone.local(2024, 5, 2)\n      setup\n\n      get monthly_unique_users_graph_data_health_index_path\n\n      expect(response).to have_http_status(:ok)\n      expect(response.content_type).to include(\"application/json\")\n\n      chart_data = JSON.parse(response.body)\n      expect(chart_data).to be_an(Array)\n      expect(chart_data.length).to eq(12)\n\n      expect(chart_data[0]).to eq([11.months.ago.strftime(\"%b %Y\"), 2, 1, 1, 2])\n      expect(chart_data[1]).to eq([10.months.ago.strftime(\"%b %Y\"), 1, 0, 0, 1])\n      expect(chart_data[2]).to eq([9.months.ago.strftime(\"%b %Y\"), 1, 1, 1, 0])\n    end\n\n    it \"returns monthly unique users data for volunteers, supervisors, and admins in the last year (on the first of the month)\" do\n      travel_to Time.zone.local(2024, 5, 1)\n      setup\n\n      get monthly_unique_users_graph_data_health_index_path\n\n      expect(response).to have_http_status(:ok)\n      expect(response.content_type).to include(\"application/json\")\n\n      chart_data = JSON.parse(response.body)\n      expect(chart_data).to be_an(Array)\n      expect(chart_data.length).to eq(12)\n\n      expect(chart_data[0]).to eq([11.months.ago.strftime(\"%b %Y\"), 2, 1, 1, 2])\n      expect(chart_data[1]).to eq([10.months.ago.strftime(\"%b %Y\"), 1, 0, 0, 1])\n      expect(chart_data[2]).to eq([9.months.ago.strftime(\"%b %Y\"), 1, 1, 1, 0])\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/hearing_types_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"/hearing_types\", type: :request do\n  describe \"GET /hearing_types/new\" do\n    context \"when logged in as admin user\" do\n      it \"allows access to hearing type create page\" do\n        sign_in_as_admin\n\n        get new_hearing_type_path\n\n        expect(response).to be_successful\n      end\n    end\n\n    context \"when logged in as a non-admin user\" do\n      it \"does not allow access to hearing type create page\" do\n        sign_in_as_volunteer\n\n        get new_hearing_type_path\n\n        expect(response).to redirect_to root_path\n        expect(response.request.flash[:notice]).to eq \"Sorry, you are not authorized to perform this action.\"\n      end\n    end\n\n    context \"when an unauthenticated request is made\" do\n      it \"does not allow access to hearing type create page\" do\n        get new_hearing_type_path\n\n        expect(response).to redirect_to new_user_session_path\n      end\n    end\n  end\n\n  describe \"POST /hearing_types\" do\n    let(:params) { {hearing_type: {name: \"New Hearing\", active: true}} }\n\n    context \"when logged in as admin user\" do\n      it \"successfully create a hearing type\" do\n        casa_org = create(:casa_org)\n        sign_in create(:casa_admin, casa_org: casa_org)\n\n        expect {\n          post hearing_types_path, params: params\n        }.to change(HearingType, :count).by(1)\n\n        hearing_type = HearingType.last\n\n        expect(hearing_type.name).to eql \"New Hearing\"\n        expect(hearing_type.active).to be_truthy\n        expect(response).to redirect_to edit_casa_org_path(casa_org)\n        expect(response.request.flash[:notice]).to eq \"Hearing Type was successfully created.\"\n      end\n    end\n\n    context \"when logged in as a non-admin user\" do\n      it \"does not create a hearing type\" do\n        sign_in_as_volunteer\n\n        post hearing_types_path, params: params\n\n        expect(response).to redirect_to root_path\n        expect(response.request.flash[:notice]).to eq \"Sorry, you are not authorized to perform this action.\"\n      end\n    end\n\n    context \"when an unauthenticated request is made\" do\n      it \"does not create a hearing type\" do\n        post hearing_types_path, params: params\n\n        expect(response).to redirect_to new_user_session_path\n      end\n    end\n  end\n\n  describe \"GET /hearing_types/:id/edit\" do\n    context \"when logged in as admin user\" do\n      it \"allows access to hearing type edit page\" do\n        sign_in_as_admin\n\n        get edit_hearing_type_path(create(:hearing_type))\n\n        expect(response).to be_successful\n      end\n    end\n\n    context \"when logged in as a non-admin user\" do\n      it \"does not allow access to hearing type edit page\" do\n        sign_in_as_volunteer\n\n        get edit_hearing_type_path(create(:hearing_type))\n\n        expect(response).to redirect_to root_path\n        expect(response.request.flash[:notice]).to eq \"Sorry, you are not authorized to perform this action.\"\n      end\n    end\n\n    context \"when an unauthenticated request is made\" do\n      it \"does not allow access to hearing type edit page\" do\n        get edit_hearing_type_path(create(:hearing_type))\n\n        expect(response).to redirect_to new_user_session_path\n      end\n    end\n  end\n\n  describe \"PUT /hearing_types/:id\" do\n    let(:casa_org) { create(:casa_org) }\n    let(:params) { {hearing_type: {name: \"New Name\", active: true}} }\n\n    context \"when logged in as admin user\" do\n      it \"successfully update hearing type with active status\" do\n        sign_in create(:casa_admin, casa_org: casa_org)\n\n        hearing_type = create(:hearing_type)\n\n        put hearing_type_path(hearing_type), params: params\n\n        hearing_type.reload\n        expect(hearing_type.name).to eq \"New Name\"\n        expect(hearing_type.active).to be_truthy\n\n        expect(response).to redirect_to edit_casa_org_path(casa_org)\n        expect(response.request.flash[:notice]).to eq \"Hearing Type was successfully updated.\"\n      end\n\n      it \"successfully update hearing type with inactive status\" do\n        sign_in create(:casa_admin, casa_org: casa_org)\n\n        hearing_type = create(:hearing_type)\n\n        put hearing_type_path(hearing_type), params: params\n\n        hearing_type.update(active: false)\n        hearing_type.reload\n        expect(hearing_type.name).to eq \"New Name\"\n        expect(hearing_type.active).to be_falsey\n\n        expect(response).to redirect_to edit_casa_org_path(casa_org)\n        expect(response.request.flash[:notice]).to eq \"Hearing Type was successfully updated.\"\n      end\n    end\n\n    context \"when logged in as a non-admin user\" do\n      it \"does not update hearing type\" do\n        sign_in_as_volunteer\n\n        put hearing_type_path(create(:hearing_type)), params: params\n\n        expect(response).to redirect_to root_path\n        expect(response.request.flash[:notice]).to eq \"Sorry, you are not authorized to perform this action.\"\n      end\n    end\n\n    context \"when an unauthenticated request is made\" do\n      it \"does not update hearing type\" do\n        put hearing_type_path(create(:hearing_type)), params: params\n\n        expect(response).to redirect_to new_user_session_path\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/imports_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"/imports\", type: :request do\n  let(:volunteer_file) { fixture_file_upload \"volunteers.csv\", \"text/csv\" }\n  let(:supervisor_file) { fixture_file_upload \"supervisors.csv\", \"text/csv\" }\n  let(:case_file) { fixture_file_upload \"casa_cases.csv\", \"text/csv\" }\n  let(:existing_case_file) { fixture_file_upload \"existing_casa_case.csv\", \"text/csv\" }\n  let(:supervisor_volunteers_file) { fixture_file_upload \"supervisor_volunteers.csv\", \"text/csv\" }\n  let(:casa_admin) { build(:casa_admin) }\n  let(:pre_transition_aged_youth_age) { Date.current - 14.years }\n\n  before do\n    # next_court_date in casa_cases.csv needs to be a future date\n    travel_to Date.parse(\"Sept 15 2022\")\n  end\n\n  describe \"GET /index\" do\n    it \"renders an unsuccessful response when the user is not an admin\" do\n      sign_in create(:volunteer)\n\n      get imports_url\n\n      expect(response).not_to be_successful\n    end\n\n    it \"renders a successful response when the user is an admin\" do\n      sign_in casa_admin\n\n      get imports_url\n\n      expect(response).to be_successful\n    end\n\n    it \"validates volunteers CSV header\" do\n      sign_in casa_admin\n\n      post imports_url, params: {\n        import_type: \"volunteer\",\n        file: supervisor_file,\n        sms_opt_in: \"1\"\n      }\n\n      expect(request.session[:import_error]).to include(\"Expected\", VolunteerImporter::IMPORT_HEADER.join(\", \"))\n      expect(response).to redirect_to(imports_url(import_type: \"volunteer\"))\n    end\n\n    it \"validates supervisors CSV header\" do\n      sign_in casa_admin\n\n      post imports_url, params: {\n        import_type: \"supervisor\",\n        file: volunteer_file,\n        sms_opt_in: \"1\"\n      }\n\n      expect(request.session[:import_error]).to include(\"Expected\", SupervisorImporter::IMPORT_HEADER.join(\", \"))\n      expect(response).to redirect_to(imports_url(import_type: \"supervisor\"))\n    end\n\n    it \"validates cases CSV header\" do\n      sign_in casa_admin\n\n      post imports_url, params: {\n        import_type: \"casa_case\",\n        file: supervisor_file,\n        sms_opt_in: \"1\"\n      }\n\n      expect(request.session[:import_error]).to include(\"Expected\", CaseImporter::IMPORT_HEADER.join(\", \"))\n      expect(response).to redirect_to(imports_url(import_type: \"casa_case\"))\n    end\n\n    it \"creates volunteers in volunteer CSV imports\" do\n      sign_in casa_admin\n\n      expect(Volunteer.count).to eq(0)\n\n      expect {\n        post imports_url,\n          params: {\n            import_type: \"volunteer\",\n            file: volunteer_file,\n            sms_opt_in: \"1\"\n          }\n      }.to change(Volunteer, :count).by(3)\n\n      expect(response).to redirect_to(imports_url(import_type: \"volunteer\"))\n    end\n\n    it \"creates supervisors and adds volunteers in supervisor CSV imports\" do\n      sign_in casa_admin\n\n      # make sure appropriate volunteers exist\n      VolunteerImporter.import_volunteers(volunteer_file, casa_admin.casa_org_id)\n\n      expect(Supervisor.count).to eq(0)\n\n      expect {\n        post imports_url,\n          params: {\n            import_type: \"supervisor\",\n            file: supervisor_file,\n            sms_opt_in: \"1\"\n          }\n      }.to change(Supervisor, :count).by(3)\n\n      expect(Supervisor.find_by(email: \"supervisor1@example.net\").volunteers.size).to eq(1)\n      expect(Supervisor.find_by(email: \"supervisor2@example.net\").volunteers.size).to eq(2)\n      expect(Supervisor.find_by(email: \"supervisor3@example.net\").volunteers.size).to eq(0)\n\n      expect(response).to redirect_to(imports_url(import_type: \"supervisor\"))\n    end\n\n    it \"creates supervisors and assigns the volunteer if not already assigned\" do\n      sign_in casa_admin\n\n      # make sure appropriate volunteers exist\n      VolunteerImporter.new(volunteer_file, casa_admin.casa_org_id).import_volunteers\n\n      expect(Supervisor.count).to eq(0)\n\n      expect {\n        post imports_url,\n          params: {\n            import_type: \"supervisor\",\n            file: supervisor_volunteers_file,\n            sms_opt_in: \"1\"\n          }\n      }.to change(Supervisor, :count).by(2)\n\n      expect(Supervisor.find_by(email: \"s5@example.com\").volunteers.size).to eq(1)\n      expect(Supervisor.find_by(email: \"s6@example.com\").volunteers.size).to eq(0)\n\n      expect(response).to redirect_to(imports_url(import_type: \"supervisor\"))\n    end\n\n    it \"creates case in cases CSV imports and adds next court date\" do\n      sign_in casa_admin\n\n      expect(CasaCase.count).to eq(0)\n\n      expect {\n        post imports_url,\n          params: {\n            import_type: \"casa_case\",\n            file: case_file,\n            sms_opt_in: \"1\"\n          }\n      }.to change(CasaCase, :count).by(3)\n\n      expect(CasaCase.first.next_court_date).not_to be_nil\n      expect(CasaCase.last.next_court_date).to be_nil\n\n      expect(response).to redirect_to(imports_url(import_type: \"casa_case\"))\n    end\n\n    it \"produces an error when a deactivated case already exists in cases CSV imports\" do\n      sign_in casa_admin\n\n      create(:casa_case, case_number: \"CINA-00-0000\", birth_month_year_youth: pre_transition_aged_youth_age, active: \"false\")\n\n      expect(CasaCase.count).to eq(1)\n\n      expect {\n        post imports_url,\n          params: {\n            import_type: \"casa_case\",\n            file: existing_case_file\n          }\n      }.not_to change(CasaCase, :count)\n\n      expect(request.session[:import_error]).to include(\"Not all rows were imported.\")\n      expect(response).to redirect_to(imports_url(import_type: \"casa_case\"))\n\n      failed_csv_path = FailedImportCsvService.new(import_type: :casa_case, user: casa_admin).csv_filepath\n      expect(File.exist?(failed_csv_path)).to be true\n\n      file_contents = File.read(failed_csv_path)\n      expect(file_contents).to include(\"Case CINA-00-0000 already exists, but is inactive. Reactivate the CASA case instead.\")\n\n      FileUtils.rm_f(failed_csv_path)\n    end\n\n    it \"calls FailedImportCsvService#store when there are failed rows from the import\" do\n      sign_in casa_admin\n\n      csv_service_double = instance_double(FailedImportCsvService)\n      allow(csv_service_double).to receive(:failed_rows=)\n      allow(csv_service_double).to receive(:store)\n      allow(csv_service_double).to receive(:cleanup)\n      allow(FailedImportCsvService).to receive(:new).and_return(csv_service_double)\n\n      allow(CaseImporter).to receive(:import_cases).and_return({\n        message: \"Some cases were not imported.\",\n        exported_rows: \"Case CINA-00-0000 already exists, but is inactive. Reactivate the CASA case instead.\",\n        type: :error\n      })\n\n      expect(csv_service_double).to receive(:failed_rows=).with(\"Case CINA-00-0000 already exists, but is inactive. Reactivate the CASA case instead.\")\n      expect(csv_service_double).to receive(:store)\n      expect(csv_service_double).to receive(:cleanup)\n\n      post imports_url,\n        params: {\n          import_type: \"casa_case\",\n          file: fixture_file_upload(\"existing_casa_case.csv\", \"text/csv\")\n        }\n\n      expect(request.session[:import_error]).to include(\"Click here to download failed rows.\")\n      expect(response).to redirect_to(imports_url(import_type: \"casa_case\"))\n    end\n\n    it \"writes a fallback message when exported rows exceed max size\" do\n      sign_in casa_admin\n\n      large_exported_content = \"a\" * (FailedImportCsvService::MAX_FILE_SIZE_BYTES + 1)\n\n      allow(CaseImporter).to receive(:import_cases).and_return({\n        message: \"Some rows were too large.\",\n        exported_rows: large_exported_content,\n        type: :error\n      })\n\n      post imports_url,\n        params: {\n          import_type: \"casa_case\",\n          file: case_file\n        }\n\n      failed_csv_path = FailedImportCsvService.new(import_type: :casa_case, user: casa_admin).csv_filepath\n      expect(File.exist?(failed_csv_path)).to be true\n\n      written_content = File.read(failed_csv_path)\n      expect(written_content).to include(\"The file was too large to save\")\n\n      expect(request.session[:import_error]).to include(\"Click here to download failed rows.\")\n\n      FileUtils.rm_f(failed_csv_path)\n    end\n\n    it \"shows a fallback error message when the failed import CSV was never created\" do\n      sign_in casa_admin\n\n      get download_failed_imports_path(import_type: \"casa_case\")\n\n      expect(response.body).to include(\"Please upload a CASA Case CSV\")\n      expect(response.headers[\"Content-Disposition\"]).to include(\"attachment; filename=\\\"failed_rows.csv\\\"\")\n    end\n\n    it \"shows a fallback error message when the failed import CSV expired\" do\n      sign_in casa_admin\n\n      service = FailedImportCsvService.new(\n        import_type: :casa_case,\n        user: casa_admin,\n        failed_rows: \"some content\",\n        filepath: path\n      )\n      service.store\n\n      File.utime(2.days.ago.to_time, 2.days.ago.to_time, service.csv_filepath)\n\n      get download_failed_imports_path(import_type: \"casa_case\")\n\n      expect(response.body).to include(\"Please upload a CASA Case CSV\")\n      expect(response.headers[\"Content-Disposition\"]).to include(\"attachment; filename=\\\"failed_rows.csv\\\"\")\n      expect(File.exist?(path)).to be_falsey\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/judges_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"/judges\", type: :request do\n  describe \"GET /judges/new\" do\n    context \"logged in as admin user\" do\n      it \"can successfully access a judge create page\" do\n        sign_in_as_admin\n\n        get new_judge_path\n\n        expect(response).to be_successful\n      end\n    end\n\n    context \"logged in as a non-admin user\" do\n      it \"cannot access a judge create page\" do\n        sign_in_as_volunteer\n\n        get new_judge_path\n\n        expect(response).to redirect_to root_path\n        expect(response.request.flash[:notice]).to eq \"Sorry, you are not authorized to perform this action.\"\n      end\n    end\n\n    context \"unauthenticated request\" do\n      it \"cannot access a judge create page\" do\n        get new_judge_path\n\n        expect(response).to redirect_to new_user_session_path\n      end\n    end\n  end\n\n  describe \"POST /judges\" do\n    let(:params) { {judge: {name: \"Joe Judge\", active: true}} }\n\n    context \"logged in as admin user\" do\n      it \"can successfully create a judge\" do\n        casa_org = build(:casa_org)\n        sign_in build(:casa_admin, casa_org: casa_org)\n\n        expect {\n          post judges_path, params: params\n        }.to change(Judge, :count).by(1)\n\n        judge = Judge.last\n\n        expect(judge.name).to eql \"Joe Judge\"\n        expect(judge.casa_org).to eql casa_org\n        expect(judge.active).to be_truthy\n        expect(response).to redirect_to edit_casa_org_path(casa_org)\n        expect(response.request.flash[:notice]).to eq \"Judge was successfully created.\"\n      end\n    end\n\n    context \"logged in as a non-admin user\" do\n      it \"cannot create a judge\" do\n        sign_in_as_volunteer\n\n        post judges_path, params: params\n\n        expect(response).to redirect_to root_path\n        expect(response.request.flash[:notice]).to eq \"Sorry, you are not authorized to perform this action.\"\n      end\n    end\n\n    context \"unauthenticated request\" do\n      it \"cannot create a judge\" do\n        post judges_path, params: params\n\n        expect(response).to redirect_to new_user_session_path\n      end\n    end\n  end\n\n  describe \"GET /judges/:id/edit\" do\n    let(:judge) { create(:judge) }\n\n    context \"logged in as admin user\" do\n      it \"can successfully access a judge edit page\" do\n        sign_in_as_admin\n\n        get edit_judge_path(judge)\n\n        expect(response).to be_successful\n      end\n    end\n\n    context \"logged in as a non-admin user\" do\n      it \"cannot access a judge edit page\" do\n        sign_in_as_volunteer\n\n        get edit_judge_path(judge)\n\n        expect(response).to redirect_to root_path\n        expect(response.request.flash[:notice]).to eq \"Sorry, you are not authorized to perform this action.\"\n      end\n    end\n\n    context \"unauthenticated request\" do\n      it \"cannot access a judge edit page\" do\n        get edit_judge_path(judge)\n\n        expect(response).to redirect_to new_user_session_path\n      end\n    end\n  end\n\n  describe \"PUT /judges/:id\" do\n    let(:judge) { create(:judge) }\n    let(:params) { {judge: {name: \"New Name\", judge_id: judge.id, active: false}} }\n\n    context \"logged in as admin user\" do\n      it \"can successfully update a judge\" do\n        casa_org = build(:casa_org)\n        sign_in build(:casa_admin, casa_org: casa_org)\n\n        put judge_path(judge), params: params\n\n        judge.reload\n        expect(judge.name).to eq \"New Name\"\n        expect(judge.active).to be_falsey\n\n        expect(response).to redirect_to edit_casa_org_path(casa_org)\n        expect(response.request.flash[:notice]).to eq \"Judge was successfully updated.\"\n      end\n    end\n\n    context \"logged in as a non-admin user\" do\n      it \"cannot update a judge\" do\n        sign_in_as_volunteer\n\n        put judge_path(judge), params: params\n\n        expect(response).to redirect_to root_path\n        expect(response.request.flash[:notice]).to eq \"Sorry, you are not authorized to perform this action.\"\n      end\n    end\n\n    context \"unauthenticated request\" do\n      it \"cannot update a judge\" do\n        put judge_path(judge), params: params\n\n        expect(response).to redirect_to new_user_session_path\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/languages_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe LanguagesController, type: :request do\n  describe \"POST /create\" do\n    context \"when request params are valid\" do\n      it \"creates a new language\" do\n        organization = create(:casa_org)\n        admin = create(:casa_admin, casa_org: organization)\n\n        sign_in admin\n        post languages_path, params: {\n          language: {\n            name: \"Spanish\"\n          }\n        }\n\n        expect(response.status).to eq 302\n        expect(response).to redirect_to(edit_casa_org_path(organization.id))\n        expect(flash[:notice]).to eq \"Language was successfully created.\"\n      end\n    end\n  end\n\n  describe \"PATCH /update\" do\n    context \"when request params are valid\" do\n      it \"creates a new language\" do\n        organization = create(:casa_org)\n        admin = create(:casa_admin, casa_org: organization)\n        language = create(:language, casa_org: organization)\n\n        sign_in admin\n        patch language_path(language), params: {\n          language: {\n            name: \"Spanishes\"\n          }\n        }\n\n        expect(response.status).to eq 302\n        expect(response).to redirect_to(edit_casa_org_path(organization.id))\n        expect(flash[:notice]).to eq \"Language was successfully updated.\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/learning_hour_topics_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe \"LearningHourTopics\", type: :request do\n  let(:casa_org) { create(:casa_org) }\n  let(:admin) { build(:casa_admin, casa_org:) }\n\n  before do\n    sign_in admin\n  end\n\n  describe \"GET /new\" do\n    it \"returns a successful response\" do\n      get new_learning_hour_topic_path\n\n      expect(response).to have_http_status(:success)\n    end\n  end\n\n  describe \"GET /edit\" do\n    it \"returns a successful response\" do\n      learning_hour_topic = create(:learning_hour_topic, casa_org:)\n\n      get edit_learning_hour_topic_path(learning_hour_topic)\n\n      expect(response).to have_http_status(:success)\n    end\n  end\n\n  describe \"POST /create\" do\n    context \"when the params are valid\" do\n      it \"creates the learning hour topic successfully and redirects to the organization's edit page\" do\n        params = {\n          learning_hour_topic: {\n            name: \"Social Science\"\n          }\n        }\n\n        expect do\n          post learning_hour_topics_path, params: params\n        end.to change(LearningHourTopic, :count).by(1)\n\n        expect(response).to have_http_status(:redirect)\n        expect(flash[:notice]).to match(/learning topic was successfully created/i)\n        expect(response).to redirect_to(edit_casa_org_path(casa_org))\n      end\n    end\n\n    context \"when the params are not valid\" do\n      it \"returns an unprocessable_content response\" do\n        params = {\n          learning_hour_topic: {\n            name: nil\n          }\n        }\n\n        expect do\n          post learning_hour_topics_path, params: params\n        end.not_to change(LearningHourTopic, :count)\n\n        expect(response).to have_http_status(:unprocessable_content)\n      end\n    end\n  end\n\n  describe \"PATCH /update\" do\n    context \"when the params are valid\" do\n      it \"updates the learning hour type successfully and redirects to the organization's edit page\" do\n        learning_hour_topic = create(:learning_hour_topic, casa_org:, name: \"Homeschooling\")\n\n        params = {learning_hour_topic: {name: \"Remote\"}}\n        patch learning_hour_topic_path(learning_hour_topic), params: params\n\n        expect(response).to redirect_to(edit_casa_org_path(casa_org))\n        expect(learning_hour_topic.reload.name).to eq(\"Remote\")\n        expect(flash[:notice]).to match(/learning topic was successfully updated/i)\n      end\n    end\n\n    context \"when the params are invalid\" do\n      it \"returns an unprocessable_content response\" do\n        learning_hour_topic = create(:learning_hour_topic, casa_org:, name: \"Homeschooling\")\n\n        params = {learning_hour_topic: {name: nil}}\n        patch learning_hour_topic_path(learning_hour_topic), params: params\n\n        expect(response).to have_http_status(:unprocessable_content)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/learning_hour_types_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe \"LearningHourTypes\", type: :request do\n  let(:casa_org) { create(:casa_org) }\n  let(:admin) { build(:casa_admin, casa_org:) }\n\n  before do\n    sign_in admin\n  end\n\n  describe \"GET /new\" do\n    it \"returns a successful response\" do\n      get new_learning_hour_type_path\n\n      expect(response).to have_http_status(:success)\n    end\n  end\n\n  describe \"GET /edit\" do\n    it \"returns a successful response\" do\n      learning_hour_type = create(:learning_hour_type, casa_org:)\n\n      get edit_learning_hour_type_path(learning_hour_type)\n\n      expect(response).to have_http_status(:success)\n    end\n  end\n\n  describe \"POST /create\" do\n    context \"when the params are valid\" do\n      it \"creates the learning hour type successfully and redirects to the organization's edit page\" do\n        params = {\n          learning_hour_type: {\n            name: \"Homeschooling\",\n            active: true\n          }\n        }\n\n        expect do\n          post learning_hour_types_path, params: params\n        end.to change(LearningHourType, :count).by(1)\n\n        expect(response).to have_http_status(:redirect)\n        expect(flash[:notice]).to match(/learning type was successfully created/i)\n        expect(response).to redirect_to(edit_casa_org_path(casa_org))\n      end\n    end\n\n    context \"when the params are not valid\" do\n      it \"returns an unprocessable_content response\" do\n        params = {\n          learning_hour_type: {\n            active: true\n          }\n        }\n\n        expect do\n          post learning_hour_types_path, params: params\n        end.not_to change(LearningHourType, :count)\n\n        expect(response).to have_http_status(:unprocessable_content)\n      end\n    end\n  end\n\n  describe \"PATCH /update\" do\n    context \"when the params are valid\" do\n      it \"updates the learning hour type successfully and redirects to the organization's edit page\" do\n        learning_hour_type = create(:learning_hour_type, casa_org:, name: \"Homeschooling\")\n\n        params = {learning_hour_type: {name: \"Remote\"}}\n        patch learning_hour_type_path(learning_hour_type), params: params\n\n        expect(response).to redirect_to(edit_casa_org_path(casa_org))\n        expect(learning_hour_type.reload.name).to eq(\"Remote\")\n        expect(flash[:notice]).to match(/learning type was successfully updated/i)\n      end\n    end\n\n    context \"when the params are invalid\" do\n      it \"returns an unprocessable_content response\" do\n        learning_hour_type = create(:learning_hour_type, casa_org:, name: \"Homeschooling\")\n\n        params = {learning_hour_type: {name: nil}}\n        patch learning_hour_type_path(learning_hour_type), params: params\n\n        expect(response).to have_http_status(:unprocessable_content)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/learning_hours_reports_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"LearningHoursReports\", type: :request do\n  let(:organization) { create(:casa_org) }\n  let(:admin) { build(:casa_admin, casa_org: organization) }\n\n  describe \"GET /index\" do\n    subject(:request) do\n      get \"#{learning_hours_reports_url}.csv\"\n\n      response\n    end\n\n    before do\n      sign_in admin\n      allow(LearningHoursReport).to receive(:new).and_call_original\n    end\n\n    it { is_expected.to be_successful }\n\n    it \"triggers report generation correctly\" do\n      request\n      expect(LearningHoursReport).to have_received(:new).once.with(organization.id)\n    end\n\n    it \"sends downloadable data correctly\", :aggregate_failures do\n      response_headers = request.headers\n\n      expect(response_headers[\"Content-Type\"]).to match(\"text/csv\")\n      expect(response_headers[\"Content-Disposition\"]).to(\n        match(\"learning-hours-report-#{Time.current.strftime(\"%Y-%m-%d\")}.csv\")\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/learning_hours_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"LearningHours\", type: :request do\n  let(:volunteer) { create(:volunteer) }\n\n  context \"as a volunteer user\" do\n    before { sign_in volunteer }\n\n    describe \"GET /index\" do\n      it \"succeeds\" do\n        get learning_hours_path\n        expect(response).to have_http_status(:success)\n      end\n\n      it \"displays the Learning Topic column if learning_topic_active is true\" do\n        volunteer.casa_org.update(learning_topic_active: true)\n        get learning_hours_path\n        expect(response.body).to include(\"Learning Topic\")\n      end\n\n      it \"does not display the Learning Topic column if learning_topic_active is false\" do\n        volunteer.casa_org.update(learning_topic_active: false)\n        get learning_hours_path\n        expect(response.body).not_to include(\"Learning Topic\")\n      end\n    end\n  end\n\n  context \"as a supervisor user\" do\n    let(:supervisor) { create(:supervisor) }\n\n    before { sign_in supervisor }\n\n    describe \"GET /index\" do\n      it \"succeeds\" do\n        get learning_hours_path\n        expect(response).to have_http_status(:success)\n      end\n\n      it \"displays the time completed column\" do\n        get learning_hours_path\n        expect(response.body).to include(\"Time Completed YTD\")\n      end\n    end\n  end\n\n  context \"as an admin user\" do\n    let(:admin) { create(:casa_admin) }\n\n    before { sign_in admin }\n\n    describe \"GET /index\" do\n      it \"succeeds\" do\n        get learning_hours_path\n        expect(response).to have_http_status(:success)\n      end\n\n      it \"displays the time completed column\" do\n        get learning_hours_path\n        expect(response.body).to include(\"Time Completed YTD\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/mileage_rates_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"/mileage_rates\", type: :request do\n  let(:casa_org) { create(:casa_org) }\n  let(:admin) { create(:casa_admin, casa_org: casa_org) }\n\n  describe \"GET /index\" do\n    subject(:request) do\n      get mileage_rates_path\n\n      response\n    end\n\n    let!(:mileage_rate) { create(:mileage_rate, effective_date: Date.new(2023, 1, 1), casa_org: casa_org) }\n    let!(:other_mileage_rate) do\n      create(:mileage_rate, effective_date: Date.new(2023, 2, 1), casa_org: casa_org)\n    end\n    let!(:other_casa_org_mileage_rate) do\n      create(:mileage_rate, effective_date: Date.new(2023, 2, 1))\n    end\n\n    before { sign_in admin }\n\n    it { is_expected.to be_successful }\n\n    it \"shows mileage rates correctly\", :aggregate_failures do\n      page = request.body\n      expect(page).to match(/#{mileage_rate_path(mileage_rate)}.*#{mileage_rate_path(other_mileage_rate)}/m)\n      expect(page).not_to include(mileage_rate_path(other_casa_org_mileage_rate))\n    end\n  end\n\n  describe \"GET /new\" do\n    it \"renders a successful response only for admin user\" do\n      sign_in admin\n\n      get new_mileage_rate_path\n      expect(response).to be_successful\n    end\n  end\n\n  describe \"POST /create\" do\n    let(:mileage_rate) { MileageRate.last }\n\n    before do\n      sign_in admin\n    end\n\n    context \"with valid params\" do\n      let(:params) do\n        {\n          mileage_rate: {\n            casa_org_id: admin.casa_org_id,\n            effective_date: DateTime.current,\n            amount: \"22.87\"\n          }\n        }\n      end\n\n      it \"creates a new mileage rate\" do\n        expect { post mileage_rates_path, params: params }.to change(MileageRate, :count).by(1)\n        expect(response).to have_http_status(:redirect)\n        expect(mileage_rate[:casa_org_id]).to eq(admin.casa_org_id)\n        expect(mileage_rate[:effective_date]).to eq(params[:mileage_rate][:effective_date].to_date)\n        expect(mileage_rate[:amount]).to eq(params[:mileage_rate][:amount].to_f)\n        expect(response).to redirect_to mileage_rates_path\n      end\n    end\n\n    context \"with invalid parameters\" do\n      let(:params) do\n        {\n          mileage_rate: {\n            casa_org_id: admin.casa_org_id,\n            effective_date: DateTime.current,\n            amount: \"\"\n          }\n        }\n      end\n\n      it \"does not create a mileage rate\" do\n        expect {\n          post mileage_rates_path, params: params\n        }.not_to change { MileageRate.count }\n        expect(response).to have_http_status(:unprocessable_content)\n      end\n    end\n\n    context \"when a previous mileage rate exists for the effective date\" do\n      let(:date) { DateTime.current }\n      let(:params) do\n        {\n          mileage_rate: {\n            casa_org_id: admin.casa_org_id,\n            effective_date: date,\n            amount: \"\"\n          }\n        }\n      end\n\n      before do\n        create(:mileage_rate, effective_date: date)\n      end\n\n      it \"must not create a new mileage rate\" do\n        expect {\n          post mileage_rates_path, params: params\n        }.not_to change { MileageRate.count }\n        expect(response).to have_http_status(:unprocessable_content)\n      end\n    end\n  end\n\n  describe \"PATCH /update\" do\n    let(:mileage_rate) { create(:mileage_rate, amount: 10.11, effective_date: DateTime.parse(\"01-01-2021\")) }\n\n    before { sign_in admin }\n\n    context \"with valid params\" do\n      it \"updates the mileage_rate\" do\n        patch mileage_rate_path(mileage_rate), params: {\n          mileage_rate: {\n            amount: \"22.87\"\n          }\n        }\n        expect(response).to have_http_status(:redirect)\n\n        mileage_rate.reload\n        expect(mileage_rate.amount).to eq 22.87\n      end\n    end\n\n    context \"with invalid parameters\" do\n      let(:params) do\n        {\n          mileage_rate: {\n            amount: \"\"\n          }\n        }\n      end\n\n      it \"does not update a mileage rate\" do\n        patch mileage_rate_path(mileage_rate), params: params\n\n        expect(response).to have_http_status(:unprocessable_content)\n\n        mileage_rate.reload\n        expect(mileage_rate.amount).to eq(10.11)\n      end\n    end\n\n    context \"when updating the mileage rate effective date and already exists one\" do\n      let(:another_mileage_rate) { create(:mileage_rate) }\n      let(:params) do\n        {\n          mileage_rate: {\n            effective_date: DateTime.parse(\"01-01-2021\")\n          }\n        }\n      end\n\n      it \"does not update a mileage rate\" do\n        expect { patch mileage_rate_path(another_mileage_rate), params: params }.not_to change { another_mileage_rate }\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/mileage_reports_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe \"MileageReports\", type: :request do\n  describe \"GET /index\" do\n    context \"when the user has access\" do\n      let(:admin) { build(:casa_admin) }\n\n      it \"returns the CSV report\" do\n        sign_in admin\n\n        get mileage_reports_path(format: :csv)\n\n        expect(response).to have_http_status(:success)\n        expect(response.header[\"Content-Type\"]).to eq(\"text/csv\")\n        expect(response.headers[\"Content-Disposition\"]).to(\n          match(\"mileage-report-#{Time.current.strftime(\"%Y-%m-%d\")}.csv\")\n        )\n      end\n\n      it \"adds the correct headers to the csv\" do\n        sign_in admin\n\n        get mileage_reports_path(format: :csv)\n\n        csv_headers = [\n          \"Contact Types\",\n          \"Occurred At\",\n          \"Miles Driven\",\n          \"Casa Case Number\",\n          \"Creator Name\",\n          \"Supervisor Name\",\n          \"Volunteer Address\",\n          \"Reimbursed\"\n        ]\n\n        csv_headers.each { |header| expect(response.body).to include(header) }\n      end\n    end\n\n    context \"when the user is not authorized to access\" do\n      it \"redirects to root and displays an unauthorized message\" do\n        volunteer = build(:volunteer)\n        sign_in volunteer\n\n        get mileage_reports_path(format: :csv)\n\n        expect(response).to redirect_to root_path\n        expect(response.request.flash[:notice]).to eq \"Sorry, you are not authorized to perform this action.\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/missing_data_reports_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe MissingDataReportsController, type: :request do\n  let(:admin) { create(:casa_admin) }\n\n  context \"as an admin user\" do\n    describe \"GET /index\" do\n      before do\n        sign_in admin\n        get missing_data_reports_path(format: :csv)\n      end\n\n      it \"returns a successful response\" do\n        expect(response).to be_successful\n        expect(response.header[\"Content-Type\"]).to eq(\"text/csv\")\n      end\n    end\n  end\n\n  context \"without authenctication\" do\n    describe \"GET /index\" do\n      before { get missing_data_reports_path(format: :csv) }\n\n      it \"return unauthorized\" do\n        expect(response).to have_http_status(:unauthorized)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/notes_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"/volunteers/notes\", type: :request do\n  describe \"POST /create\" do\n    context \"when logged in as admin\" do\n      it \"can create a note for volunteer in same organization\" do\n        organization = create(:casa_org)\n        admin = create(:casa_admin, casa_org: organization)\n        volunteer = create(:volunteer, :with_assigned_supervisor, casa_org: organization)\n        sign_in admin\n        expect {\n          post volunteer_notes_path(volunteer), params: {note: {content: \"Very nice!\"}}\n        }.to change(Note, :count).by(1)\n        expect(response).to redirect_to edit_volunteer_path(volunteer)\n        expect(Note.last.content).to eq \"Very nice!\"\n      end\n\n      it \"cannot create a note for volunteer in different organization\" do\n        organization = create(:casa_org)\n        other_organization = create(:casa_org)\n        admin = create(:casa_admin, casa_org: organization)\n        volunteer = create(:volunteer, casa_org: other_organization)\n\n        sign_in admin\n        expect {\n          post volunteer_notes_path(volunteer), params: {note: {content: \"Very nice!\"}}\n        }.not_to change(Note, :count)\n        expect(response).to redirect_to root_path\n      end\n    end\n\n    context \"when logged in as a supervisor\" do\n      it \"can create a note for volunteer in same organization\" do\n        organization = create(:casa_org)\n        supervisor = create(:supervisor, casa_org: organization)\n        volunteer = create(:volunteer, :with_assigned_supervisor, casa_org: organization)\n        sign_in supervisor\n        expect {\n          post volunteer_notes_path(volunteer), params: {note: {content: \"Very nice!\"}}\n        }.to change(Note, :count).by(1)\n        expect(response).to redirect_to edit_volunteer_path(volunteer)\n        expect(Note.last.content).to eq \"Very nice!\"\n      end\n\n      it \"cannot create a note for volunteer in different organization\" do\n        organization = create(:casa_org)\n        other_organization = create(:casa_org)\n        supervisor = create(:supervisor, casa_org: organization)\n        volunteer = create(:volunteer, casa_org: other_organization)\n\n        sign_in supervisor\n        expect {\n          post volunteer_notes_path(volunteer), params: {note: {content: \"Very nice!\"}}\n        }.not_to change(Note, :count)\n        expect(response).to redirect_to root_path\n      end\n    end\n\n    context \"when logged in as volunteer\" do\n      it \"cannot create a note\" do\n        organization = create(:casa_org)\n        volunteer = create(:volunteer, :with_assigned_supervisor, casa_org: organization)\n\n        sign_in volunteer\n        expect {\n          post volunteer_notes_path(volunteer), params: {note: {content: \"Very nice!\"}}\n        }.not_to change(Note, :count)\n        expect(response).to redirect_to root_path\n      end\n    end\n  end\n\n  describe \"GET /edit\" do\n    context \"when logged in as admin\" do\n      context \"when volunteer in same organization\" do\n        it \"is successful if note belongs to volunteer\" do\n          organization = create(:casa_org)\n          admin = create(:casa_admin, casa_org: organization)\n          volunteer = create(:volunteer, :with_assigned_supervisor, casa_org: organization)\n          note = create(:note, notable: volunteer)\n\n          sign_in admin\n          get edit_volunteer_note_path(volunteer, note)\n\n          expect(response).to be_successful\n        end\n\n        it \"redirects to root path if note does not belong to volunteer\" do\n          organization = create(:casa_org)\n          admin = create(:casa_admin, casa_org: organization)\n          volunteer = create(:volunteer, :with_assigned_supervisor, casa_org: organization)\n          other_volunteer = create(:volunteer, :with_assigned_supervisor, casa_org: organization)\n          note = create(:note, notable: other_volunteer)\n\n          sign_in admin\n          get edit_volunteer_note_path(volunteer, note)\n\n          expect(response).to redirect_to root_path\n        end\n      end\n\n      context \"when volunteer in different organization\" do\n        it \"redirects to root path\" do\n          organization = create(:casa_org)\n          other_organization = create(:casa_org)\n          admin = create(:casa_admin, casa_org: organization)\n\n          volunteer = create(:volunteer, casa_org: other_organization)\n          note = create(:note, notable: volunteer)\n\n          sign_in admin\n          get edit_volunteer_note_path(volunteer, note)\n\n          expect(response).to redirect_to root_path\n        end\n      end\n    end\n\n    context \"when logged in as supervisor\" do\n      context \"when volunteer in same organization\" do\n        it \"is successful if note belongs to volunteer\" do\n          organization = create(:casa_org)\n          supervisor = create(:supervisor, casa_org: organization)\n          volunteer = create(:volunteer, :with_assigned_supervisor, casa_org: organization)\n          note = create(:note, notable: volunteer)\n\n          sign_in supervisor\n          get edit_volunteer_note_path(volunteer, note)\n\n          expect(response).to be_successful\n        end\n\n        it \"redirects to root path if note does not belong to volunteer\" do\n          organization = create(:casa_org)\n          supervisor = create(:supervisor, casa_org: organization)\n          volunteer = create(:volunteer, :with_assigned_supervisor, casa_org: organization)\n          other_volunteer = create(:volunteer, :with_assigned_supervisor, casa_org: organization)\n          note = create(:note, notable: other_volunteer)\n\n          sign_in supervisor\n          get edit_volunteer_note_path(volunteer, note)\n\n          expect(response).to redirect_to root_path\n        end\n      end\n\n      context \"when volunteer in different organization\" do\n        it \"redirects to root path\" do\n          organization = create(:casa_org)\n          other_organization = create(:casa_org)\n          supervisor = create(:supervisor, casa_org: organization)\n\n          volunteer = create(:volunteer, casa_org: other_organization)\n          note = create(:note, notable: volunteer)\n\n          sign_in supervisor\n          get edit_volunteer_note_path(volunteer, note)\n\n          expect(response).to redirect_to root_path\n        end\n      end\n    end\n\n    context \"when logged in as volunteer\" do\n      context \"when note belongs to volunteer\" do\n        it \"redirects to root path\" do\n          organization = create(:casa_org)\n          volunteer = create(:volunteer, :with_assigned_supervisor, casa_org: organization)\n          note = create(:note, notable: volunteer)\n\n          sign_in volunteer\n          get edit_volunteer_note_path(volunteer, note)\n\n          expect(response).to redirect_to root_path\n        end\n      end\n    end\n  end\n\n  describe \"PATCH /update\" do\n    context \"when logged in as an admin\" do\n      context \"when volunteer in same org\" do\n        it \"updates note and redirects to edit volunteer page\" do\n          organization = create(:casa_org)\n          admin = create(:casa_admin, casa_org: organization)\n          volunteer = create(:volunteer, :with_assigned_supervisor, casa_org: organization)\n          note = create(:note, notable: volunteer, creator: admin, content: \"Good job.\")\n\n          sign_in admin\n          patch volunteer_note_path(volunteer, note), params: {note: {content: \"Very nice!\"}}\n\n          expect(response).to redirect_to(edit_volunteer_path(volunteer))\n          expect(note.reload.content).to eq \"Very nice!\"\n        end\n      end\n\n      context \"when volunteer in different org\" do\n        it \"does not update note and redirects to root path\" do\n          organization = create(:casa_org)\n          other_organization = create(:casa_org)\n          admin = create(:casa_admin, casa_org: organization)\n          volunteer = create(:volunteer, casa_org: other_organization)\n          note = create(:note, notable: volunteer, content: \"Good job.\")\n\n          sign_in admin\n          patch volunteer_note_path(volunteer, note), params: {note: {content: \"Very nice!\"}}\n\n          expect(response).to redirect_to root_path\n          expect(note.reload.content).to eq \"Good job.\"\n        end\n      end\n    end\n\n    context \"when logged in as a supervisor\" do\n      context \"when volunteer in same org\" do\n        it \"updates note and redirects to edit volunteer page\" do\n          organization = create(:casa_org)\n          supervisor = create(:supervisor, casa_org: organization)\n          volunteer = create(:volunteer, :with_assigned_supervisor, casa_org: organization)\n          note = create(:note, notable: volunteer, content: \"Good job.\")\n\n          sign_in supervisor\n          patch volunteer_note_path(volunteer, note), params: {note: {content: \"Very nice!\"}}\n\n          expect(response).to redirect_to(edit_volunteer_path(volunteer))\n          expect(note.reload.content).to eq \"Very nice!\"\n        end\n      end\n\n      context \"when volunteer in different org\" do\n        it \"does not update note and redirects to root path\" do\n          organization = create(:casa_org)\n          other_organization = create(:casa_org)\n          supervisor = create(:supervisor, casa_org: organization)\n          volunteer = create(:volunteer, casa_org: other_organization)\n          note = create(:note, notable: volunteer, content: \"Good job.\")\n\n          sign_in supervisor\n          patch volunteer_note_path(volunteer, note), params: {note: {content: \"Very nice!\"}}\n\n          expect(response).to redirect_to root_path\n          expect(note.reload.content).to eq \"Good job.\"\n        end\n      end\n    end\n\n    context \"when logged in as a volunteer\" do\n      context \"when updating note belonging to volunteer\" do\n        it \"does not update note and redirects to root path\" do\n          organization = create(:casa_org)\n          volunteer = create(:volunteer, casa_org: organization)\n          note = create(:note, notable: volunteer, content: \"Good job.\")\n\n          sign_in volunteer\n          patch volunteer_note_path(volunteer, note), params: {note: {content: \"Very nice!\"}}\n\n          expect(response).to redirect_to root_path\n          expect(note.reload.content).to eq \"Good job.\"\n        end\n      end\n    end\n  end\n\n  describe \"DELETE /destroy\" do\n    context \"when logged in as an admin\" do\n      it \"can delete notes about a volunteer in same organization\" do\n        organization = create(:casa_org)\n        admin = create(:casa_admin, casa_org: organization)\n        volunteer = create(:volunteer, :with_assigned_supervisor, casa_org: organization)\n        note = create(:note, notable: volunteer)\n\n        sign_in admin\n        expect {\n          delete volunteer_note_path(volunteer, note)\n        }.to change(Note, :count).by(-1)\n        expect(response).to redirect_to edit_volunteer_path(volunteer)\n      end\n\n      it \"cannot delete notes about a volunteer in different organization\" do\n        organization = create(:casa_org)\n        other_organization = create(:casa_org)\n        admin = create(:casa_admin, casa_org: organization)\n        volunteer = create(:volunteer, casa_org: other_organization)\n        note = create(:note, notable: volunteer)\n\n        sign_in admin\n        expect {\n          delete volunteer_note_path(volunteer, note)\n        }.not_to change(Note, :count)\n        expect(response).to redirect_to root_path\n      end\n    end\n\n    context \"when logged in as a supervisor\" do\n      it \"can delete notes about a volunteer in same organization\" do\n        organization = create(:casa_org)\n        supervisor = create(:supervisor, casa_org: organization)\n        volunteer = create(:volunteer, :with_assigned_supervisor, casa_org: organization)\n        note = create(:note, notable: volunteer)\n\n        sign_in supervisor\n        expect {\n          delete volunteer_note_path(volunteer, note)\n        }.to change(Note, :count).by(-1)\n        expect(response).to redirect_to edit_volunteer_path(volunteer)\n      end\n\n      it \"cannot delete notes about a volunteer in different organization\" do\n        organization = create(:casa_org)\n        other_organization = create(:casa_org)\n        supervisor = create(:supervisor, casa_org: organization)\n        volunteer = create(:volunteer, casa_org: other_organization)\n        note = create(:note, notable: volunteer)\n\n        sign_in supervisor\n        expect {\n          delete volunteer_note_path(volunteer, note)\n        }.not_to change(Note, :count)\n        expect(response).to redirect_to root_path\n      end\n    end\n\n    context \"when logged in as a volunteer\" do\n      it \"cannot delete notes\" do\n        volunteer = create(:volunteer, :with_assigned_supervisor)\n        note = create(:note, notable: volunteer)\n\n        sign_in volunteer\n        expect {\n          delete volunteer_note_path(volunteer, note)\n        }.not_to change(Note, :count)\n        expect(response).to redirect_to root_path\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/notifications_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"/notifications\", type: :request do\n  before do\n    travel_to Date.new(2021, 1, 1)\n  end\n\n  describe \"GET /index\" do\n    context \"when there are no patch notes\" do\n      context \"when logged in as an admin\" do\n        let(:admin) { create(:casa_admin) }\n\n        before do\n          sign_in admin\n        end\n\n        it \"shows the no notification message\" do\n          get notifications_url\n\n          expect(response.body).to include(\"You currently don't have any notifications. Notifications are generated when someone requests follow-up on a case contact.\")\n        end\n\n        context \"when there is a deploy date\" do\n          before do\n            Health.instance.update_attribute(:latest_deploy_time, Date.today)\n          end\n\n          it \"does not show the patch notes section\" do\n            get notifications_url\n\n            queryable_html = Nokogiri.HTML5(response.body)\n\n            expect(queryable_html.css(\"h3\").text).not_to include(\"Patch Notes\")\n          end\n        end\n      end\n    end\n\n    context \"when there are patch notes\" do\n      let(:patch_note_group_all_users) { create(:patch_note_group, :all_users) }\n      let(:patch_note_group_no_volunteers) { create(:patch_note_group, :only_supervisors_and_admins) }\n      let(:patch_note_type_a) { create(:patch_note_type, name: \"patch_note_type_a\") }\n      let(:patch_note_type_b) { create(:patch_note_type, name: \"patch_note_type_b\") }\n      let(:patch_note_1) { create(:patch_note, note: \"Patch Note 1\", patch_note_type: patch_note_type_a) }\n      let(:patch_note_2) { create(:patch_note, note: \"Patch Note B\", patch_note_type: patch_note_type_b) }\n\n      before do\n        patch_note_1.update(created_at: Date.new(2020, 12, 31), patch_note_group: patch_note_group_all_users)\n        patch_note_2.update(created_at: Date.new(2020, 12, 31), patch_note_group: patch_note_group_no_volunteers)\n      end\n\n      context \"when logged in as an admin\" do\n        let(:admin) { create(:casa_admin) }\n\n        before do\n          sign_in admin\n        end\n\n        context \"when there is no deploy date\" do\n          it \"shows the no notification message\" do\n            get notifications_url\n\n            expect(response.body).to include(\"You currently don't have any notifications. Notifications are generated when someone requests follow-up on a case contact.\")\n          end\n\n          it \"does not show the patch notes section\" do\n            get notifications_url\n\n            queryable_html = Nokogiri.HTML5(response.body)\n\n            expect(queryable_html.css(\"h3\").text).not_to include(\"Patch Notes\")\n          end\n        end\n\n        context \"when there is a deploy date\" do\n          before do\n            Health.instance.update_attribute(:latest_deploy_time, Date.new(2021, 1, 1))\n          end\n\n          it \"does not show the no notification message\" do\n            get notifications_url\n\n            expect(response.body).not_to include(\"You currently don't have any notifications. Notifications are generated when someone requests follow-up on a case contact.\")\n          end\n\n          it \"does not show patch notes made after the deploy date\" do\n            patch_note_1.update_attribute(:created_at, Date.new(2021, 1, 2))\n            patch_note_2.update_attribute(:created_at, Date.new(2020, 12, 31))\n\n            get notifications_url\n\n            expect(response.body).not_to include(CGI.escapeHTML(patch_note_1.note))\n            expect(response.body).to include(CGI.escapeHTML(patch_note_2.note))\n          end\n        end\n      end\n\n      context \"when logged in as volunteer\" do\n        let(:volunteer) { create(:volunteer) }\n\n        before do\n          sign_in volunteer\n          Health.instance.update_attribute(:latest_deploy_time, Date.new(2021, 1, 1))\n          patch_note_1.update(created_at: Date.new(2020, 12, 31), patch_note_group: patch_note_group_all_users)\n          patch_note_2.update(created_at: Date.new(2020, 12, 31), patch_note_group: patch_note_group_no_volunteers)\n        end\n\n        it \"shows only the patch notes available to their user group\" do\n          get notifications_url\n\n          expect(response.body).to include(CGI.escapeHTML(patch_note_1.note))\n          expect(response.body).not_to include(CGI.escapeHTML(patch_note_2.note))\n        end\n      end\n    end\n  end\n\n  describe \"POST #mark_as_read\" do\n    let(:user) { create(:volunteer) }\n    let(:notification) { create(:notification, :followup_with_note, recipient: user, read_at: nil) }\n\n    before { sign_in user }\n\n    context \"when user is authorized\" do\n      it \"marks the notification as read\" do\n        post mark_as_read_notification_path(notification)\n\n        expect(notification.reload.read_at).not_to be_nil\n      end\n\n      it \"redirects to the notification event URL\" do\n        post mark_as_read_notification_path(notification)\n\n        case_contact_url = edit_case_contact_path(CaseContact.last)\n\n        expect(response).to redirect_to(case_contact_url)\n      end\n    end\n\n    context \"when user is not authorized\" do\n      let(:other_user) { create(:volunteer) }\n\n      before { sign_in other_user }\n\n      it \"does not mark the notification as read\" do\n        post mark_as_read_notification_path(notification)\n\n        expect(notification.reload.read_at).to be_nil\n      end\n\n      it \"redirects to root\" do\n        post mark_as_read_notification_path(notification)\n\n        expect(response).to redirect_to(root_path)\n      end\n    end\n\n    it \"does not mark the notification as read if it is already read\" do\n      notification = create(:notification, :followup_read, recipient: user)\n\n      expect { post mark_as_read_notification_path(notification) }.not_to(change { notification.reload.read_at })\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/other_duties_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"/other_duties\", type: :request do\n  describe \"GET /new\" do\n    context \"when volunteer\" do\n      it \"is successful\" do\n        volunteer = create(:volunteer)\n\n        sign_in volunteer\n        get new_other_duty_path\n\n        expect(response).to be_successful\n      end\n    end\n\n    context \"when supervisor\" do\n      it \"redirects to root path\" do\n        supervisor = create(:supervisor)\n\n        sign_in supervisor\n        get new_other_duty_path\n\n        expect(response).to redirect_to root_path\n      end\n    end\n\n    context \"when admin\" do\n      it \"redirects to root path\" do\n        admin = create(:casa_admin)\n\n        sign_in admin\n        get new_other_duty_path\n\n        expect(response).to redirect_to root_path\n      end\n    end\n  end\n\n  describe \"POST /create\" do\n    context \"when volunteer\" do\n      context \"with valid parameters\" do\n        it \"creates one new Duty and returns to other duties page\" do\n          volunteer = create(:volunteer)\n\n          sign_in volunteer\n\n          expect {\n            post other_duties_path, params: {other_duty: attributes_for(:other_duty)}\n          }.to change(OtherDuty, :count).by(1)\n          expect(response).to redirect_to(other_duties_path)\n        end\n      end\n\n      context \"with invalid parameters\" do\n        it \"does not create a new Duty and renders new page\" do\n          volunteer = create(:volunteer)\n\n          sign_in volunteer\n\n          expect {\n            post other_duties_path, params: {other_duty: attributes_for(:other_duty, notes: \"\")}\n          }.not_to change(OtherDuty, :count)\n          expect(response).to have_http_status(:unprocessable_content)\n        end\n      end\n    end\n\n    context \"when supervisor\" do\n      it \"does not create record and redirects to root\" do\n        supervisor = create(:supervisor)\n\n        sign_in supervisor\n\n        expect {\n          post other_duties_path, params: {other_duty: attributes_for(:other_duty)}\n        }.not_to change(OtherDuty, :count)\n\n        expect(response).to redirect_to root_path\n      end\n    end\n\n    context \"when admin\" do\n      it \"does not create record and redirects to root\" do\n        admin = create(:casa_admin)\n\n        sign_in admin\n\n        expect {\n          post other_duties_path, params: {other_duty: attributes_for(:other_duty)}\n        }.not_to change(OtherDuty, :count)\n\n        expect(response).to redirect_to root_path\n      end\n    end\n  end\n\n  describe \"GET /edit\" do\n    context \"when volunteer\" do\n      context \"when viewing own record\" do\n        it \"is successful\" do\n          volunteer = create(:volunteer)\n          duty = create(:other_duty, creator: volunteer)\n\n          sign_in volunteer\n          get edit_other_duty_path(duty)\n\n          expect(response).to be_successful\n        end\n      end\n\n      context \"when viewing other's record\" do\n        it \"redirects to root path\" do\n          volunteer = create(:volunteer)\n          duty = create(:other_duty)\n\n          sign_in volunteer\n          get edit_other_duty_path(duty)\n\n          expect(response).to redirect_to root_path\n        end\n      end\n    end\n\n    context \"when supervisor\" do\n      it \"redirects to root path\" do\n        supervisor = create(:supervisor)\n        duty = create(:other_duty)\n\n        sign_in supervisor\n        get edit_other_duty_path(duty)\n\n        expect(response).to redirect_to root_path\n      end\n    end\n\n    context \"when admin\" do\n      it \"redirects to root path\" do\n        admin = create(:casa_admin)\n        duty = create(:other_duty)\n\n        sign_in admin\n        get edit_other_duty_path(duty)\n\n        expect(response).to redirect_to root_path\n      end\n    end\n  end\n\n  describe \"PATCH /update\" do\n    context \"when volunteer updating own duty\" do\n      context \"with valid parameters\" do\n        it \"updates the duty and redirects to other duties page\" do\n          volunteer = create(:volunteer)\n          other_duty = create(:other_duty, notes: \"Test 1\", creator: volunteer)\n\n          sign_in volunteer\n          patch other_duty_path(other_duty), params: {other_duty: {notes: \"Test 2\"}}\n\n          expect(other_duty.reload.notes).to eq(\"Test 2\")\n          expect(response).to redirect_to other_duties_path\n        end\n      end\n\n      context \"with invalid parameters\" do\n        it \"does not update and re-renders edit page\" do\n          volunteer = create(:volunteer)\n          other_duty = create(:other_duty, notes: \"Test 1\", creator: volunteer)\n\n          sign_in volunteer\n          patch other_duty_path(other_duty), params: {other_duty: {notes: \"\"}}\n\n          expect(other_duty.reload.notes).to eq \"Test 1\"\n          expect(response).to have_http_status(:unprocessable_content)\n        end\n      end\n    end\n\n    context \"when volunteer updating other person's record\" do\n      it \"does not update the duty and redirects to root path\" do\n        volunteer = create(:volunteer)\n        other_duty = create(:other_duty, notes: \"Test 1\")\n\n        sign_in volunteer\n        patch other_duty_path(other_duty), params: {other_duty: {notes: \"Test 2\"}}\n\n        expect(other_duty.reload.notes).to eq(\"Test 1\")\n        expect(response).to redirect_to root_path\n      end\n    end\n\n    context \"when supervisor\" do\n      it \"does not update the duty and redirects to root path\" do\n        supervisor = create(:supervisor)\n        other_duty = create(:other_duty, notes: \"Test 1\")\n\n        sign_in supervisor\n        patch other_duty_path(other_duty), params: {other_duty: {notes: \"Test 2\"}}\n\n        expect(other_duty.reload.notes).to eq(\"Test 1\")\n        expect(response).to redirect_to root_path\n      end\n    end\n\n    context \"when admin\" do\n      it \"does not update the duty and redirects to root path\" do\n        admin = create(:casa_admin)\n        other_duty = create(:other_duty, notes: \"Test 1\")\n\n        sign_in admin\n        patch other_duty_path(other_duty), params: {other_duty: {notes: \"Test 2\"}}\n\n        expect(other_duty.reload.notes).to eq(\"Test 1\")\n        expect(response).to redirect_to root_path\n      end\n    end\n  end\n\n  describe \"GET /index\" do\n    context \"when admin\" do\n      it \"can see volunteer's other duties from own organization\" do\n        volunteer = create(:volunteer)\n        duties = create_pair(:other_duty, creator: volunteer)\n        admin = create(:casa_admin, casa_org: volunteer.casa_org)\n        other_org_duty = create(:other_duty)\n\n        sign_in admin\n        get other_duties_path\n\n        expect(response.body).to include(volunteer.display_name)\n        expect(response.body).to include(duties.first.decorate.truncate_notes)\n        expect(response.body).to include(duties.second.decorate.truncate_notes)\n        expect(response.body).not_to include(other_org_duty.decorate.truncate_notes)\n      end\n    end\n\n    context \"when supervisor\" do\n      it \"can see own active volunteer's other duties from own organization\" do\n        supervisor = create(:supervisor, :with_volunteers)\n        volunteer1 = supervisor.volunteers.first\n        volunteer2 = supervisor.volunteers.last\n        SupervisorVolunteer.find_by(supervisor: supervisor, volunteer: volunteer2).update!(is_active: false)\n        duties = create_pair(:other_duty, creator: volunteer1)\n        inactive_duty = create(:other_duty, creator: volunteer2)\n\n        volunteer_other_sup = create(:volunteer, casa_org: volunteer1.casa_org)\n        other_sup_duty = create(:other_duty, creator: volunteer_other_sup)\n\n        other_org = create(:casa_org)\n        volunteer_other_org = create(:volunteer, casa_org: other_org)\n        other_org_duty = create(:other_duty, creator: volunteer_other_org)\n\n        sign_in supervisor\n        get other_duties_path\n\n        expect(response.body).to include(volunteer1.display_name)\n        expect(response.body).to include(duties.first.decorate.truncate_notes)\n        expect(response.body).to include(duties.second.decorate.truncate_notes)\n\n        expect(response.body).not_to include(volunteer2.display_name)\n        expect(response.body).not_to include(inactive_duty.decorate.truncate_notes)\n\n        expect(response.body).not_to include(volunteer_other_sup.display_name)\n        expect(response.body).not_to include(other_sup_duty.decorate.truncate_notes)\n\n        expect(response.body).not_to include(volunteer_other_org.display_name)\n        expect(response.body).not_to include(other_org_duty.decorate.truncate_notes)\n      end\n    end\n\n    context \"when volunteer\" do\n      it \"shows only duties from the volunteer\" do\n        volunteer = create(:volunteer)\n        mine = build(:other_duty)\n        other = build(:other_duty)\n\n        volunteer.other_duties << mine\n\n        sign_in volunteer\n        get other_duties_url\n\n        expect(response).to be_successful\n        expect(response.body).to include(mine.decorate.truncate_notes)\n        expect(response.body).not_to include(other.decorate.truncate_notes)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/placement_reports_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"/placement_reports\", type: :request do\n  let(:admin) { create(:casa_admin) }\n\n  describe \"GET /index\" do\n    it \"renders a successful response\" do\n      sign_in admin\n\n      get placement_reports_path, headers: {\"ACCEPT\" => \"*/*\"}\n      expect(response).to be_successful\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/placement_types_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"PlacementTypes\", type: :request do\n  let(:organization) { create(:casa_org) }\n  let(:admin) { create(:casa_admin, casa_org: organization) }\n  let(:placement_type) { create(:placement_type, casa_org: organization) }\n\n  describe \"as an admin\" do\n    before do\n      sign_in admin\n    end\n\n    describe \"GET /edit\" do\n      it \"returns http success\" do\n        get edit_placement_type_path(placement_type)\n        expect(response).to have_http_status(:success)\n      end\n    end\n\n    describe \"GET /new\" do\n      it \"returns http success\" do\n        get \"/placement_types/new\"\n        expect(response).to have_http_status(:success)\n      end\n    end\n\n    describe \"POST /create\" do\n      it \"returns http success\" do\n        expect {\n          post \"/placement_types\", params: {placement_type: {name: \"New Placement Type\"}}\n        }.to change(PlacementType, :count).by(1)\n        expect(response).to redirect_to(edit_casa_org_path(organization))\n      end\n    end\n\n    describe \"PATCH /update\" do\n      it \"returns http success\" do\n        patch placement_type_path(placement_type), params: {placement_type: {name: \"Updated Placement Type\"}}\n        expect(response).to redirect_to(edit_casa_org_path(organization))\n        expect(placement_type.reload.name).to eq(\"Updated Placement Type\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/placements_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"Placements\", type: :request do\n  let(:casa_org) { build(:casa_org) }\n  let(:admin) { create(:casa_admin, casa_org:) }\n  let(:casa_case) { create(:casa_case, casa_org:) }\n\n  before do\n    sign_in admin\n  end\n\n  describe \"GET /index\" do\n    it \"displays the placement information\" do\n      get casa_case_placements_path(casa_case)\n\n      expect(response).to have_http_status(:success)\n      expect(response.body).to include(\"Placement History\")\n      expect(response.body).to include(casa_case.case_number)\n    end\n  end\n\n  describe \"GET /show\" do\n    it \"displays the placement details\" do\n      placement_type = build(:placement_type, casa_org:, name: \"Reunification\")\n      placement = create(:placement, casa_case:, placement_type:)\n\n      get casa_case_placement_path(casa_case, placement)\n\n      expect(response).to have_http_status(:success)\n      expect(response.body).to include(\"Placement\")\n      expect(response.body).to include(\"Reunification\")\n      expect(response.body).to include(casa_case.case_number)\n    end\n  end\n\n  describe \"GET /new\" do\n    it \"returns a successful response\" do\n      get new_casa_case_placement_path(casa_case)\n\n      expect(response).to have_http_status(:success)\n    end\n  end\n\n  describe \"GET /edit\" do\n    it \"returns a successful response\" do\n      placement = create(:placement, casa_case:)\n\n      get edit_casa_case_placement_path(casa_case, placement)\n\n      expect(response).to have_http_status(:success)\n    end\n  end\n\n  describe \"POST /create\" do\n    context \"when the params are valid\" do\n      it \"creates the placement successfully and redirects to the placement\" do\n        placement_type = create(:placement_type, casa_org:, name: \"Adoption by relative\")\n\n        params = {\n          placement: {\n            placement_started_at: Date.new(2026, 2, 1),\n            placement_type_id: placement_type.id\n          }\n        }\n\n        expect do\n          post casa_case_placements_path(casa_case), params: params\n        end.to change(Placement, :count).by(1)\n\n        expect(response).to have_http_status(:redirect)\n        expect(flash[:notice]).to match(/placement was successfully created/i)\n        follow_redirect!\n        expect(response.body).to include(\"Placement\")\n        expect(response.body).to include(\"Adoption by relative\")\n        expect(response.body).to include(casa_case.case_number)\n      end\n    end\n  end\n\n  describe \"PATCH /update\" do\n    context \"when the params are valid\" do\n      it \"updates the placement successfully\" do\n        placement = create(:placement, casa_case:, placement_started_at: Date.new(2026, 4, 1))\n\n        params = {placement: {placement_started_at: Date.new(2026, 1, 1)}}\n        patch casa_case_placement_path(casa_case, placement), params: params\n\n        expect(response).to redirect_to(casa_case_placements_path(casa_case))\n        expect(placement.reload.placement_started_at).to eq(Date.new(2026, 1, 1))\n        expect(flash[:notice]).to match(/placement was successfully updated/i)\n      end\n    end\n\n    context \"when the params are invalid\" do\n      it \"returns an unprocessable_content response\" do\n        placement = create(:placement, casa_case:, placement_started_at: Date.new(2026, 4, 1))\n\n        params = {placement: {placement_started_at: 1000.years.ago}}\n        patch casa_case_placement_path(casa_case, placement), params: params\n\n        expect(response).to have_http_status(:unprocessable_content)\n      end\n    end\n  end\n\n  describe \"DELETE /destroy\" do\n    it \"deletes the placement successfully\" do\n      placement = create(:placement, casa_case:)\n\n      expect do\n        delete casa_case_placement_path(casa_case, placement)\n      end.to change(Placement, :count).by(-1)\n\n      expect(response).to have_http_status(:redirect)\n      expect(flash[:notice]).to match(/placement was successfully deleted/i)\n      follow_redirect!\n      expect(response.body).to include(\"Placement History\")\n      expect(response.body).to include(casa_case.case_number)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/preference_sets_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"PreferenceSets\", type: :request do\n  let!(:supervisor) { create(:supervisor) }\n  let!(:preference_set) { supervisor.preference_set }\n  let!(:table_state) { {\"columns\" => [{\"visible\" => \"false\"}, {\"visible\" => \"true\"}, {\"visible\" => \"false\"}, {\"visible\" => \"true\"}]} }\n\n  describe \"GET /preference_sets/table_state/volunteers_table\" do\n    subject { get \"/preference_sets/table_state/volunteers_table\" }\n\n    before do\n      sign_in supervisor\n      supervisor.preference_set.table_state[\"volunteers_table\"] = table_state\n      supervisor.preference_set.save!\n    end\n\n    it \"returns the table state\" do\n      subject\n      expect(response.body).to eq(table_state.to_json)\n    end\n  end\n\n  describe \"POST /preference_sets/table_state_update/volunteers_table\" do\n    subject { post \"/preference_sets/table_state_update/volunteers_table\", params: {table_name: \"volunteers_table\", table_state: table_state} }\n\n    before do\n      sign_in supervisor\n    end\n\n    it \"updates the table state\" do\n      subject\n      preference_set.reload\n      expect(preference_set.table_state[\"volunteers_table\"]).to eq(table_state)\n    end\n\n    it \"returns a 200 OK status\" do\n      subject\n      expect(response).to have_http_status(:ok)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/reimbursements_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe ReimbursementsController, type: :request do\n  let(:admin) { create(:casa_admin) }\n  let(:casa_org) { admin.casa_org }\n  let(:case_contact) { create(:case_contact) }\n  let(:notification_double) { double(\"ReimbursementCompleteNotifier\") }\n\n  before do\n    sign_in(admin)\n    allow(ReimbursementCompleteNotifier).to receive(:with).and_return(notification_double)\n    allow(notification_double).to receive(:deliver)\n  end\n\n  describe \"GET /index\" do\n    it \"calls ReimbursementPolicy::Scope to filter reimbursements\" do\n      contact_relation = double(CaseContact)\n      allow(contact_relation).to receive_message_chain(\n        :want_driving_reimbursement,\n        :created_max_ago,\n        :filter_by_reimbursement_status\n      ).and_return([])\n      allow(ReimbursementPolicy::Scope).to receive(:new)\n        .with(admin, CaseContact.joins(:casa_case))\n        .and_return(double(resolve: contact_relation))\n\n      expect(contact_relation).to receive_message_chain(\n        :want_driving_reimbursement,\n        :created_max_ago,\n        :filter_by_reimbursement_status\n      )\n\n      get reimbursements_url\n\n      expect(ReimbursementPolicy::Scope).to have_received(:new)\n        .with(admin, CaseContact.joins(:casa_case))\n    end\n  end\n\n  describe \"PATCH /mark_as_complete\" do\n    it \"changes reimbursement status to complete\" do\n      patch reimbursement_mark_as_complete_url(case_contact, case_contact: {reimbursement_complete: true})\n      expect(ReimbursementCompleteNotifier).to(have_received(:with).once.with(case_contact: case_contact))\n      expect(response).to redirect_to(reimbursements_path)\n      expect(response).to have_http_status(:redirect)\n      expect(case_contact.reload.reimbursement_complete).to be_truthy\n    end\n  end\n\n  describe \"PATCH /mark_as_needs_review\" do\n    before { case_contact.update(reimbursement_complete: true) }\n\n    it \"changes reimbursement status to needs review\" do\n      patch reimbursement_mark_as_needs_review_url(case_contact, case_contact: {reimbursement_complete: false})\n      expect(response).to redirect_to(reimbursements_path)\n      expect(response).to have_http_status(:redirect)\n      expect(case_contact.reload.reimbursement_complete).to be_falsey\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/reports_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"/reports\", type: :request do\n  describe \"GET #index\" do\n    subject do\n      get reports_url\n      response\n    end\n\n    context \"while signed in as an admin\" do\n      before do\n        sign_in build(:casa_admin)\n      end\n\n      it { is_expected.to be_successful }\n    end\n\n    context \"while signed in as a supervisor\" do\n      before do\n        sign_in build(:supervisor)\n      end\n\n      it { is_expected.to be_successful }\n    end\n\n    context \"while signed in as a volunteer\" do\n      before do\n        sign_in build(:volunteer)\n      end\n\n      it { is_expected.not_to be_successful }\n    end\n  end\n\n  describe \"GET #export_emails\" do\n    before do\n      sign_in build(:casa_admin)\n    end\n\n    it \"renders a csv file to download\" do\n      get export_emails_url(format: :csv)\n\n      expect(response).to be_successful\n      expect(\n        response.headers[\"Content-Disposition\"]\n      ).to include \"attachment; filename=\\\"volunteers-emails-#{Time.current.strftime(\"%Y-%m-%d\")}.csv\"\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/static_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe \"/\", type: :request do\n  describe \"GET /\" do\n    subject(:request) do\n      get root_path\n\n      response\n    end\n\n    it { is_expected.to be_successful }\n  end\nend\n"
  },
  {
    "path": "spec/requests/supervisor_volunteers_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"/supervisor_volunteers\", type: :request do\n  let!(:casa_org) { build(:casa_org) }\n  let!(:admin) { build(:casa_admin, casa_org: casa_org) }\n  let!(:supervisor) { create(:supervisor, casa_org: casa_org) }\n\n  describe \"POST /create\" do\n    let!(:volunteer) { create(:volunteer, casa_org: casa_org) }\n\n    context \"when no pre-existing association between supervisor and volunteer exists\" do\n      it \"creates a new supervisor_volunteers association\" do\n        valid_parameters = {\n          supervisor_volunteer: {volunteer_id: volunteer.id},\n          supervisor_id: supervisor.id\n        }\n        sign_in(admin)\n\n        expect {\n          post supervisor_volunteers_url, params: valid_parameters, headers: {HTTP_REFERER: edit_volunteer_path(volunteer).to_s}\n        }.to change(SupervisorVolunteer, :count).by(1)\n        expect(response).to redirect_to edit_volunteer_path(volunteer)\n      end\n    end\n\n    context \"when an inactive association between supervisor and volunteer exists\" do\n      let!(:association) do\n        create(\n          :supervisor_volunteer,\n          supervisor: supervisor,\n          volunteer: volunteer,\n          is_active: false\n        )\n      end\n\n      it \"sets that association to active\" do\n        valid_parameters = {\n          supervisor_volunteer: {volunteer_id: volunteer.id},\n          supervisor_id: supervisor.id\n        }\n        sign_in(admin)\n\n        expect {\n          post supervisor_volunteers_url, params: valid_parameters, headers: {HTTP_REFERER: edit_volunteer_path(volunteer).to_s}\n        }.not_to change(SupervisorVolunteer, :count)\n        expect(response).to redirect_to edit_volunteer_path(volunteer)\n\n        association.reload\n        expect(association.is_active?).to be(true)\n      end\n    end\n\n    context \"when an inactive association between the volunteer and a different supervisor exists\" do\n      let!(:other_supervisor) { build(:supervisor, casa_org: casa_org) }\n      let!(:previous_association) do\n        create(\n          :supervisor_volunteer,\n          supervisor: other_supervisor,\n          volunteer: volunteer,\n          is_active: false\n        )\n      end\n\n      it \"does not remove association\" do\n        valid_parameters = {\n          supervisor_volunteer: {volunteer_id: volunteer.id},\n          supervisor_id: supervisor.id\n        }\n        sign_in(admin)\n\n        expect {\n          post supervisor_volunteers_url, params: valid_parameters, headers: {HTTP_REFERER: edit_volunteer_path(volunteer).to_s}\n        }.to change(SupervisorVolunteer, :count).by(1)\n        expect(response).to redirect_to edit_volunteer_path(volunteer)\n\n        expect(previous_association.reload.is_active?).to be(false)\n        expect(SupervisorVolunteer.where(supervisor: supervisor, volunteer: volunteer).exists?).to be(true)\n      end\n    end\n\n    context \"when passing the supervisor_id as the supervisor_volunteer_params\" do\n      let!(:association) do\n        create(\n          :supervisor_volunteer,\n          supervisor: supervisor,\n          volunteer: volunteer,\n          is_active: false\n        )\n      end\n\n      it \"stills set the association as active\" do\n        valid_parameters = {\n          supervisor_volunteer: {supervisor_id: supervisor.id, volunteer_id: volunteer.id}\n        }\n        sign_in(admin)\n\n        expect {\n          post supervisor_volunteers_url, params: valid_parameters, headers: {HTTP_REFERER: edit_volunteer_path(volunteer).to_s}\n        }.not_to change(SupervisorVolunteer, :count)\n        expect(response).to redirect_to edit_volunteer_path(volunteer)\n\n        association.reload\n        expect(association.is_active?).to be(true)\n      end\n    end\n  end\n\n  describe \"PATCH /unassign\" do\n    let!(:volunteer) { create(:volunteer, casa_org: casa_org) }\n    let!(:association) do\n      create(:supervisor_volunteer, supervisor: supervisor, volunteer: volunteer)\n    end\n\n    context \"when the logged in user is an admin\" do\n      it \"sets the is_active flag for assignment of a volunteer to a supervisor to false\" do\n        sign_in admin\n\n        expect {\n          patch unassign_supervisor_volunteer_path(volunteer), headers: {HTTP_REFERER: edit_volunteer_path(volunteer).to_s}\n        }.to change(supervisor.volunteers, :count)\n\n        association.reload\n        expect(association.is_active?).to be(false)\n        expect(response).to redirect_to edit_volunteer_path(volunteer)\n      end\n    end\n\n    context \"when the logged in user is a supervisor\" do\n      it \"sets the is_active flag for assignment of a volunteer to a supervisor to false\" do\n        sign_in supervisor\n\n        expect {\n          patch unassign_supervisor_volunteer_path(volunteer), headers: {HTTP_REFERER: edit_volunteer_path(volunteer).to_s}\n        }.to change(supervisor.volunteers, :count)\n\n        association.reload\n\n        expect(association.is_active?).to be(false)\n        expect(response).to redirect_to edit_volunteer_path(volunteer)\n      end\n    end\n\n    context \"when the logged in user is not an admin or supervisor\" do\n      let!(:association) do\n        create(:supervisor_volunteer, supervisor: supervisor, volunteer: volunteer)\n      end\n\n      it \"does not set the is_active flag on the association to false\" do\n        sign_in volunteer\n\n        expect {\n          patch unassign_supervisor_volunteer_path(volunteer)\n        }.not_to change(supervisor.volunteers, :count)\n\n        association.reload\n\n        expect(association.is_active?).to be(true)\n      end\n    end\n  end\n\n  describe \"POST /bulk_assignment\" do\n    subject do\n      post bulk_assignment_supervisor_volunteers_url, params: params\n    end\n\n    let!(:volunteer_1) { create(:volunteer, casa_org: casa_org) }\n    let!(:volunteer_2) { create(:volunteer, casa_org: casa_org) }\n    let!(:volunteer_3) { create(:volunteer, casa_org: casa_org) }\n    let(:supervisor_id) { supervisor.id }\n    let(:params) do\n      {\n        supervisor_volunteer: {\n          supervisor_id: supervisor_id,\n          volunteer_ids: [volunteer_1.id, volunteer_2.id, volunteer_3.id]\n        }\n      }\n    end\n\n    it \"creates an association for each volunteer\" do\n      sign_in(admin)\n\n      expect { subject }.to change(SupervisorVolunteer, :count).by(3)\n    end\n\n    context \"when association already exists\" do\n      let!(:associations) do\n        [\n          create(:supervisor_volunteer, :inactive, supervisor: supervisor, volunteer: volunteer_1),\n          create(:supervisor_volunteer, :inactive, supervisor: supervisor, volunteer: volunteer_2),\n          create(:supervisor_volunteer, :inactive, supervisor: supervisor, volunteer: volunteer_3)\n        ]\n      end\n\n      it \"sets to active\" do\n        sign_in(admin)\n        subject\n\n        associations.each do |association|\n          association.reload\n          expect(association.is_active?).to be true\n        end\n      end\n\n      it \"does not create new associations\" do\n        sign_in(admin)\n\n        expect { subject }.not_to change(SupervisorVolunteer, :count)\n      end\n    end\n\n    context \"when none passed as supervisor\" do\n      let(:supervisor_id) { \"\" }\n      let!(:associations) do\n        [\n          create(:supervisor_volunteer, supervisor: supervisor, volunteer: volunteer_1),\n          create(:supervisor_volunteer, supervisor: supervisor, volunteer: volunteer_2),\n          create(:supervisor_volunteer, supervisor: supervisor, volunteer: volunteer_3)\n        ]\n      end\n\n      it \"sets associations to inactive\" do\n        sign_in(admin)\n        subject\n\n        associations.each do |association|\n          association.reload\n          expect(association.is_active?).to be false\n        end\n      end\n\n      it \"does not remove associations\" do\n        sign_in(admin)\n\n        expect { subject }.not_to change(SupervisorVolunteer, :count)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/supervisors_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\nrequire \"support/stubbed_requests/webmock_helper\"\n\nRSpec.describe \"/supervisors\", type: :request do\n  let(:org) { create(:casa_org) }\n  let(:admin) { build(:casa_admin, casa_org: org) }\n  let(:supervisor) { create(:supervisor, casa_org: org) }\n\n  let(:update_supervisor_params) do\n    {supervisor: {display_name: \"New Name\", phone_number: \"+14163218092\"}}\n  end\n\n  describe \"GET /index\" do\n    it \"returns http status ok\" do\n      sign_in admin\n\n      get supervisors_path\n\n      expect(response).to have_http_status(:ok)\n    end\n\n    context \"when casa case has court_dates\" do\n      let!(:casa_case) { create(:casa_case, casa_org: org, court_dates: [court_date]) }\n      let(:court_date) { create(:court_date) }\n\n      it \"does not return casa case\" do\n        sign_in admin\n\n        get supervisors_path\n\n        expect(response.body).not_to include(casa_case.case_number)\n      end\n    end\n\n    context \"when casa case does not have court_dates\" do\n      let!(:casa_case) { create(:casa_case, casa_org: org, court_dates: []) }\n\n      it \"does not return casa case\" do\n        sign_in admin\n\n        get supervisors_path\n\n        expect(response.body).to include(casa_case.case_number)\n      end\n    end\n  end\n\n  describe \"GET /new\" do\n    it \"admin can view the new supervisor page\" do\n      sign_in admin\n\n      get new_supervisor_url\n\n      expect(response).to be_successful\n    end\n\n    it \"supervisors can not view the new supervisor page\" do\n      sign_in supervisor\n\n      get new_supervisor_url\n\n      expect(response).not_to be_successful\n    end\n  end\n\n  describe \"GET /edit\" do\n    context \"same org\" do\n      it \"admin can view the edit supervisor page\" do\n        sign_in admin\n\n        get edit_supervisor_url(supervisor)\n\n        expect(response).to be_successful\n      end\n\n      it \"supervisor can view the edit supervisor page\" do\n        sign_in supervisor\n\n        get edit_supervisor_url(supervisor)\n\n        expect(response).to be_successful\n      end\n\n      it \"other supervisor can view the edit supervisor page\" do\n        sign_in build(:supervisor, casa_org: org)\n\n        get edit_supervisor_url(supervisor)\n\n        expect(response).to be_successful\n      end\n\n      it \"returns volunteers ever assigned if include_unassigned param is present\" do\n        sign_in admin\n\n        get edit_supervisor_url(supervisor), params: {include_unassigned: true}\n\n        expect(response).to be_successful\n        expect(assigns(:all_volunteers_ever_assigned)).not_to be_nil\n      end\n\n      it \"returns no volunteers ever assigned if include_unassigned param is false\" do\n        sign_in admin\n\n        get edit_supervisor_url(supervisor), params: {include_unassigned: false}\n\n        expect(response).to be_successful\n        expect(assigns(:all_volunteers_ever_assigned)).to be_nil\n      end\n    end\n\n    context \"different org\" do\n      let(:diff_org) { create(:casa_org) }\n      let(:supervisor_diff_org) { create(:supervisor, casa_org: diff_org) }\n\n      it \"admin cannot view the edit supervisor page\" do\n        sign_in_as_admin\n\n        get edit_supervisor_url(supervisor_diff_org)\n\n        expect(response).to redirect_to root_path\n        expect(response.request.flash[:notice]).to eq \"Sorry, you are not authorized to perform this action.\"\n      end\n\n      it \"supervisor cannot view the edit supervisor page\" do\n        sign_in_as_supervisor\n\n        get edit_supervisor_url(supervisor_diff_org)\n\n        expect(response).to redirect_to root_path\n        expect(response.request.flash[:notice]).to eq \"Sorry, you are not authorized to perform this action.\"\n      end\n    end\n  end\n\n  describe \"PATCH /update\" do\n    context \"while signed in as an admin\" do\n      before do\n        sign_in admin\n      end\n\n      it \"admin updates the supervisor\" do\n        patch supervisor_path(supervisor), params: update_supervisor_params\n        supervisor.reload\n\n        expect(supervisor.display_name).to eq \"New Name\"\n        expect(supervisor.phone_number).to eq \"+14163218092\"\n      end\n\n      it \"updates supervisor email and sends a confirmation email\" do\n        patch supervisor_path(supervisor), params: {\n          supervisor: {email: \"newemail@gmail.com\"}\n        }\n\n        supervisor.reload\n        expect(response).to have_http_status(:redirect)\n\n        expect(supervisor.unconfirmed_email).to eq(\"newemail@gmail.com\")\n        expect(ActionMailer::Base.deliveries.count).to eq(1)\n        expect(ActionMailer::Base.deliveries.first).to be_a(Mail::Message)\n        expect(ActionMailer::Base.deliveries.first.body.encoded)\n          .to match(\"Click here to confirm your email\")\n      end\n\n      it \"can set the supervisor to be inactive\" do\n        patch supervisor_path(supervisor), params: {supervisor: {active: false}}\n        supervisor.reload\n\n        expect(supervisor).not_to be_active\n      end\n\n      context \"when the email exists already and the supervisor has volunteers assigned\" do\n        let(:other_supervisor) { create(:supervisor) }\n        let(:supervisor) { create(:supervisor, :with_volunteers) }\n\n        it \"gracefully fails\" do\n          patch supervisor_path(supervisor), params: {supervisor: {email: other_supervisor.email}}\n\n          expect(response).to have_http_status(:unprocessable_content)\n        end\n      end\n    end\n\n    context \"while signed in as a supervisor\" do\n      before do\n        sign_in supervisor\n      end\n\n      it \"supervisor updates their own name\" do\n        patch supervisor_path(supervisor), params: update_supervisor_params\n        supervisor.reload\n\n        expect(supervisor.display_name).to eq \"New Name\"\n        expect(supervisor).to be_active\n      end\n\n      it \"supervisor updates their own email and receives a confirmation email\" do\n        patch supervisor_path(supervisor), params: {\n          supervisor: {email: \"newemail@gmail.com\"}\n        }\n\n        supervisor.reload\n        expect(response).to have_http_status(:redirect)\n        expect(supervisor.unconfirmed_email).to eq(\"newemail@gmail.com\")\n        expect(ActionMailer::Base.deliveries.count).to eq(1)\n        expect(ActionMailer::Base.deliveries.first).to be_a(Mail::Message)\n        expect(ActionMailer::Base.deliveries.first.body.encoded)\n          .to match(\"Click here to confirm your email\")\n      end\n\n      it \"cannot change its own type\" do\n        patch supervisor_path(supervisor), params: update_supervisor_params.merge(type: \"casa_admin\")\n        supervisor.reload\n\n        expect(supervisor).not_to be_casa_admin\n      end\n\n      it \"cannot set itself to be inactive\" do\n        patch supervisor_path(supervisor), params: update_supervisor_params.merge(active: false)\n        supervisor.reload\n\n        expect(supervisor).to be_active\n      end\n\n      it \"supervisor cannot update another supervisor\" do\n        supervisor2 = create(:supervisor, display_name: \"Old Name\", email: \"oldemail@gmail.com\")\n\n        patch supervisor_path(supervisor2), params: update_supervisor_params\n        supervisor2.reload\n\n        expect(supervisor2.display_name).to eq \"Old Name\"\n        expect(supervisor2.email).to eq \"oldemail@gmail.com\"\n        expect(response).to redirect_to(root_url)\n      end\n    end\n  end\n\n  describe \"POST /create\" do\n    let(:params) do\n      {\n        supervisor: {\n          display_name: \"Display Name\",\n          email: \"displayname@example.com\"\n        }\n      }\n    end\n\n    it \"sends an invitation email\" do\n      org = create(:casa_org, twilio_enabled: true)\n      admin = build(:casa_admin, casa_org: org)\n\n      sign_in admin\n\n      post supervisors_url, params: params\n\n      expect(Devise.mailer.deliveries.count).to eq(1)\n      expect(Devise.mailer.deliveries.first.text_part.body.to_s).to include(admin.casa_org.display_name)\n      expect(Devise.mailer.deliveries.first.text_part.body.to_s).to include(\"This is the first step to accessing your new Supervisor account.\")\n    end\n\n    it \"sends a SMS when a phone number exists\" do\n      org = create(:casa_org, twilio_enabled: true)\n      admin = build(:casa_admin, casa_org: org)\n      twilio_activation_success_stub = WebMockHelper.twilio_activation_success_stub(\"supervisor\")\n      WebMockHelper.twilio_activation_error_stub(\"supervisor\")\n      short_io_stub = WebMockHelper.short_io_stub_sms\n      params[:supervisor][:phone_number] = \"+12222222222\"\n\n      sign_in admin\n      post supervisors_url, params: params\n\n      expect(short_io_stub).to have_been_requested.times(2)\n      expect(twilio_activation_success_stub).to have_been_requested.times(1)\n      expect(response).to have_http_status(:redirect)\n      follow_redirect!\n      expect(flash[:notice]).to match(/New supervisor created successfully. SMS has been sent!/)\n    end\n\n    it \"does not send a SMS if phone number not given\" do\n      org = create(:casa_org, twilio_enabled: true)\n      admin = build(:casa_admin, casa_org: org)\n      twilio_activation_success_stub = WebMockHelper.twilio_activation_success_stub(\"supervisor\")\n      twilio_activation_error_stub = WebMockHelper.twilio_activation_error_stub(\"supervisor\")\n      short_io_stub = WebMockHelper.short_io_stub_sms\n\n      sign_in admin\n      post supervisors_url, params: params\n\n      expect(short_io_stub).to have_been_requested.times(0)\n      expect(twilio_activation_success_stub).to have_been_requested.times(0)\n      expect(twilio_activation_error_stub).to have_been_requested.times(0)\n      expect(response).to have_http_status(:redirect)\n      follow_redirect!\n      expect(flash[:notice]).to match(/New supervisor created successfully./)\n    end\n\n    it \"does not send a SMS if Twilio has an error\" do\n      # ex. credentials entered wrong\n      org = create(:casa_org, twilio_account_sid: \"articuno31\", twilio_enabled: true)\n      admin = create(:casa_admin, casa_org: org)\n      WebMockHelper.twilio_activation_success_stub(\"supervisor\")\n      twilio_activation_error_stub = WebMockHelper.twilio_activation_error_stub(\"supervisor\")\n      short_io_stub = WebMockHelper.short_io_stub_sms\n\n      sign_in admin\n\n      params[:supervisor][:phone_number] = \"+12222222222\"\n      post supervisors_url, params: params\n      expect(short_io_stub).to have_been_requested.times(2) # TODO: why is this called at all?\n      expect(twilio_activation_error_stub).to have_been_requested.times(1)\n      expect(response).to have_http_status(:redirect)\n      follow_redirect!\n      expect(flash[:notice]).to match(/New supervisor created successfully. SMS not sent. Error: ./)\n    end\n\n    it \"does not send a SMS if the casa_org does not have Twilio enabled\" do\n      org = create(:casa_org, twilio_enabled: false)\n      admin = build(:casa_admin, casa_org: org)\n      short_io_stub = WebMockHelper.short_io_stub_sms\n\n      sign_in admin\n      params[:supervisor][:phone_number] = \"+12222222222\"\n      post supervisors_url, params: params\n\n      expect(short_io_stub).to have_been_requested.times(2) # TODO: why is this called at all?\n      expect(response).to have_http_status(:redirect)\n      follow_redirect!\n      expect(flash[:notice]).to match(/New supervisor created successfully./)\n    end\n  end\n\n  describe \"PATCH /activate\" do\n    let(:inactive_supervisor) { create(:supervisor, :inactive) }\n\n    before { sign_in admin }\n\n    it \"activates an inactive supervisor\" do\n      patch activate_supervisor_path(inactive_supervisor)\n      expect(flash[:notice]).to eq(\"Supervisor was activated. They have been sent an email.\")\n      inactive_supervisor.reload\n      expect(inactive_supervisor.active).to be true\n    end\n\n    it \"sends an activation mail\" do\n      expect { patch activate_supervisor_path(inactive_supervisor) }.to change { ActionMailer::Base.deliveries.count }.by(1)\n    end\n  end\n\n  describe \"PATCH /deactivate\" do\n    before { sign_in admin }\n\n    it \"deactivates an active supervisor\" do\n      patch deactivate_supervisor_path(supervisor)\n\n      supervisor.reload\n      expect(supervisor.active).to be false\n    end\n\n    it \"doesn't send an deactivation email\" do\n      expect {\n        patch deactivate_supervisor_path(supervisor)\n      }.not_to change { ActionMailer::Base.deliveries.count }\n    end\n  end\n\n  describe \"PATCH /resend_invitation\" do\n    before { sign_in admin }\n\n    it \"resends an invitation email\" do\n      expect(supervisor.invitation_created_at.present?).to eq(false)\n\n      patch resend_invitation_supervisor_path(supervisor)\n      supervisor.reload\n\n      expect(supervisor.invitation_created_at.present?).to eq(true)\n      expect(Devise.mailer.deliveries.count).to eq(1)\n      expect(Devise.mailer.deliveries.first.subject).to eq(I18n.t(\"devise.mailer.invitation_instructions.subject\"))\n      expect(response).to redirect_to(edit_supervisor_path(supervisor))\n    end\n  end\n\n  describe \"PATCH /change_to_admin\" do\n    let(:user) { User.find(supervisor.id) } # find the user after their type has changed\n\n    context \"when signed in as an admin\" do\n      before do\n        sign_in admin\n        patch change_to_admin_supervisor_path(supervisor)\n      end\n\n      it \"changes the supervisor to an admin\" do\n        expect(user).not_to be_supervisor\n        expect(user).to be_casa_admin\n      end\n\n      it \"redirects to the edit page for an admin\" do\n        expect(response).to redirect_to(edit_casa_admin_path(supervisor))\n      end\n    end\n\n    context \"when signed in as a supervisor\" do\n      before do\n        sign_in supervisor\n        patch change_to_admin_supervisor_path(supervisor)\n      end\n\n      it \"does not changes the supervisor to an admin\" do\n        expect(user).to be_supervisor\n        expect(user).not_to be_casa_admin\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/users/invitations_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"Users::InvitationsController\", type: :request do\n  let(:casa_org) { create(:casa_org) }\n  let(:admin) { create(:casa_admin, casa_org: casa_org) }\n  let(:volunteer) { create(:volunteer, casa_org: casa_org) }\n\n  describe \"GET /users/invitation/accept\" do\n    context \"with valid invitation token\" do\n      let(:invitation_token) do\n        volunteer.invite!(admin)\n        volunteer.raw_invitation_token\n      end\n\n      it \"renders the invitation acceptance form\" do\n        get accept_user_invitation_path(invitation_token: invitation_token)\n\n        expect(response).to have_http_status(:success)\n        expect(response.body).to include(\"Set my password\")\n      end\n\n      it \"sets the invitation_token on the resource\" do\n        get accept_user_invitation_path(invitation_token: invitation_token)\n\n        # Check that the hidden field contains the token\n        expect(response.body).to include('name=\"user[invitation_token]\"')\n        expect(response.body).to include(\"value=\\\"#{invitation_token}\\\"\")\n      end\n    end\n\n    context \"without invitation token\" do\n      it \"redirects to root path\" do\n        get accept_user_invitation_path\n\n        expect(response).to redirect_to(root_path)\n      end\n    end\n  end\n\n  describe \"PUT /users/invitation\" do\n    let(:invitation_token) do\n      volunteer.invite!(admin)\n      volunteer.raw_invitation_token\n    end\n\n    context \"with valid password\" do\n      let(:params) do\n        {\n          user: {\n            invitation_token: invitation_token,\n            password: \"SecurePassword123!\",\n            password_confirmation: \"SecurePassword123!\"\n          }\n        }\n      end\n\n      it \"accepts the invitation\" do\n        put user_invitation_path, params: params\n\n        volunteer.reload\n        expect(volunteer.invitation_accepted_at).not_to be_nil\n      end\n\n      it \"redirects to the dashboard\" do\n        put user_invitation_path, params: params\n\n        expect(response).to redirect_to(root_path)\n      end\n\n      it \"signs in the user\" do\n        put user_invitation_path, params: params\n\n        # Follow redirects until we reach the final authenticated page\n        follow_redirect! while response.status == 302\n\n        # User should be on an authenticated page\n        expect(response).to have_http_status(:success)\n      end\n    end\n\n    context \"with mismatched passwords\" do\n      let(:params) do\n        {\n          user: {\n            invitation_token: invitation_token,\n            password: \"SecurePassword123!\",\n            password_confirmation: \"DifferentPassword456!\"\n          }\n        }\n      end\n\n      it \"does not accept the invitation\" do\n        put user_invitation_path, params: params\n\n        volunteer.reload\n        expect(volunteer.invitation_accepted_at).to be_nil\n      end\n\n      it \"renders the edit page with errors\" do\n        put user_invitation_path, params: params\n\n        expect(response).to have_http_status(:ok)\n        expect(response.body).to include(\"Password confirmation doesn&#39;t match\")\n      end\n    end\n\n    context \"with password too short\" do\n      let(:params) do\n        {\n          user: {\n            invitation_token: invitation_token,\n            password: \"short\",\n            password_confirmation: \"short\"\n          }\n        }\n      end\n\n      it \"does not accept the invitation\" do\n        put user_invitation_path, params: params\n\n        volunteer.reload\n        expect(volunteer.invitation_accepted_at).to be_nil\n      end\n\n      it \"renders the edit page with errors\" do\n        put user_invitation_path, params: params\n\n        expect(response).to have_http_status(:ok)\n        expect(response.body).to include(\"Password is too short\")\n      end\n    end\n\n    context \"with blank password\" do\n      let(:params) do\n        {\n          user: {\n            invitation_token: invitation_token,\n            password: \"\",\n            password_confirmation: \"\"\n          }\n        }\n      end\n\n      it \"does not accept the invitation\" do\n        put user_invitation_path, params: params\n\n        volunteer.reload\n        expect(volunteer.invitation_accepted_at).to be_nil\n      end\n\n      it \"renders the edit page with errors\" do\n        put user_invitation_path, params: params\n\n        expect(response).to have_http_status(:ok)\n        expect(response.body).to include(\"can&#39;t be blank\")\n      end\n    end\n\n    context \"without invitation token\" do\n      let(:params) do\n        {\n          user: {\n            password: \"SecurePassword123!\",\n            password_confirmation: \"SecurePassword123!\"\n          }\n        }\n      end\n\n      it \"does not accept the invitation\" do\n        put user_invitation_path, params: params\n\n        volunteer.reload\n        expect(volunteer.invitation_accepted_at).to be_nil\n      end\n\n      it \"renders the edit page with errors\" do\n        put user_invitation_path, params: params\n\n        expect(response).to have_http_status(:ok)\n        expect(response.body).to include(\"Invitation token can&#39;t be blank\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/users/passwords_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"Users::PasswordsController\", type: :request do\n  let!(:org) { create(:casa_org) }\n  let!(:user) { create(:user, phone_number: \"+12222222222\", casa_org: org) }\n\n  let!(:twillio_service_double) { instance_double(TwilioService) }\n  let!(:short_url_service_double) { instance_double(ShortUrlService) }\n\n  before do\n    allow(TwilioService).to(\n      receive(:new).with(\n        org\n      ).and_return(twillio_service_double)\n    )\n\n    allow(twillio_service_double).to receive(:send_sms)\n\n    allow(ShortUrlService).to receive(:new).and_return(short_url_service_double)\n\n    allow(short_url_service_double).to(\n      receive(:create_short_url).with(a_string_matching(edit_user_password_path))\n    )\n\n    allow(short_url_service_double).to receive(:short_url).and_return(\"reset_url\")\n  end\n\n  describe \"POST /create\" do\n    subject(:request) do\n      post user_password_url, params: params\n\n      response\n    end\n\n    context \"with valid parameters\" do\n      let(:params) { {user: {email: user.email, phone_number: user.phone_number}} }\n\n      it \"sends a password reset SMS to existing user\" do\n        request\n        expect(twillio_service_double).to have_received(:send_sms).once.with(\n          {From: org.twilio_phone_number, Body: a_string_matching(\"reset_url\"), To: user.phone_number}\n        )\n      end\n\n      it \"sends a password reset email to existing user\" do\n        expect_any_instance_of(User).to receive(:send_reset_password_instructions).once\n        request\n      end\n\n      it { is_expected.to redirect_to(user_session_url) }\n\n      it \"shows the correct flash message\" do\n        request\n        expect(flash[:notice]).to(\n          eq(\"If the account exists you will receive an email or SMS with instructions on how to reset your password in a few minutes.\")\n        )\n      end\n\n      describe \"(email only)\" do\n        let(:params) { {user: {email: user.email, phone_number: \"\"}} }\n\n        it \"sends a password reset email to existing user\" do\n          expect_any_instance_of(User).to receive(:send_reset_password_instructions).once\n          request\n        end\n\n        it \"does not send sms with reset password\" do\n          request\n          expect(twillio_service_double).not_to have_received(:send_sms)\n        end\n      end\n\n      describe \"(phone_number only)\" do\n        let(:params) { {user: {email: \"\", phone_number: user.phone_number}} }\n\n        it \"sends a password reset SMS to existing user\" do\n          request\n          expect(twillio_service_double).to have_received(:send_sms).once.with(\n            {From: org.twilio_phone_number, Body: a_string_matching(\"reset_url\"), To: user.phone_number}\n          )\n        end\n\n        it \"does not send email with reset password\" do\n          expect_any_instance_of(User).not_to receive(:send_reset_password_instructions)\n          request\n        end\n      end\n    end\n\n    context \"with invalid parameters\" do\n      let(:params) { {user: {email: \"\", phone_number: \"\"}} }\n\n      it \"sets errors correctly\" do\n        request\n        expect(request.parsed_body.to_html).to include(\"Please enter at least one field.\")\n      end\n    end\n\n    context \"with wrong parameters\" do\n      let(:params) { {user: {phone_number: \"13333333333\"}} }\n\n      it \"sets errors correctly\" do\n        request\n        expect(flash[:notice]).to(\n          eq(\"If the account exists you will receive an email or SMS with instructions on how to reset your password in a few minutes.\")\n        )\n      end\n    end\n\n    context \"when twilio is disabled\" do\n      let(:params) { {user: {email: user.email, phone_number: user.phone_number}} }\n\n      before do\n        org.update(twilio_enabled: false)\n      end\n\n      it \"does not send an sms, only an email\" do\n        expect_any_instance_of(User).to receive(:send_reset_password_instructions).once\n        request\n        expect(flash[:notice]).to(\n          eq(\"If the account exists you will receive an email or SMS with instructions on how to reset your password in a few minutes.\")\n        )\n      end\n    end\n  end\n\n  describe \"PUT /update\" do\n    let(:token) do\n      raw_token, enc_token = Devise.token_generator.generate(User, :reset_password_token)\n      user.update!(reset_password_token: enc_token, reset_password_sent_at: Time.current)\n      raw_token\n    end\n\n    let(:params) do\n      {\n        user: {\n          reset_password_token: token,\n          password: \"newpassword123!\",\n          password_confirmation: \"newpassword123!\"\n        }\n      }\n    end\n\n    subject(:submit_reset) { put user_password_path, params: params }\n\n    it \"successfully resets the password\" do\n      submit_reset\n      expect(response).to redirect_to(new_user_session_path)\n      expect(flash[:notice]).to eq(\"Your password has been changed successfully.\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/users_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"/users\", type: :request do\n  before {\n    sms_notification_event = SmsNotificationEvent.new(name: \"test\", user_type: Volunteer)\n    sms_notification_event.save\n  }\n\n  describe \"GET /edit\" do\n    context \"with a volunteer signed in\" do\n      it \"renders a successful response\" do\n        volunteer = create(:volunteer)\n        sign_in volunteer\n\n        get edit_users_path\n\n        expect(response).to be_successful\n        expect(response.body).to include(volunteer.email)\n      end\n    end\n\n    context \"with an admin signed in\" do\n      it \"renders a successful response\" do\n        admin = build(:casa_admin)\n        sign_in admin\n\n        get edit_users_path\n\n        expect(response).to be_successful\n        expect(response.body).to include(admin.email)\n      end\n    end\n  end\n\n  describe \"PATCH /update\" do\n    it \"updates the user\" do\n      volunteer = build(:volunteer)\n      sign_in volunteer\n\n      patch users_path, params: {user: {display_name: \"New Name\", address_attributes: {content: \"some address\"}, phone_number: \"+12223334444\", date_of_birth: Date.new(1958, 12, 1), sms_notification_event_ids: [SmsNotificationEvent.first.id]}}\n\n      expect(volunteer.address.content).to eq \"some address\"\n      expect(volunteer.display_name).to eq \"New Name\"\n      expect(volunteer.phone_number).to eq \"+12223334444\"\n      expect(volunteer.date_of_birth).to eq Date.new(1958, 12, 1)\n      expect(volunteer.sms_notification_event_ids).to include SmsNotificationEvent.first.id\n      expect(UserSmsNotificationEvent.count).to eq 1\n    end\n  end\n\n  describe \"PATCH /update_password\" do\n    subject do\n      patch update_password_users_path(user),\n        params: {\n          user: {\n            current_password: \"12345678\",\n            password: \"new_pass\",\n            password_confirmation: \"new_pass\"\n          }\n        }\n    end\n\n    before { sign_in user }\n\n    context \"when volunteer\" do\n      let(:user) { create(:volunteer) }\n\n      context \"when successfully\" do\n        it \"updates the user password\" do\n          subject\n\n          expect(user.valid_password?(\"new_pass\")).to be_truthy\n        end\n\n        it \"calls UserMailer to reminder the user that password has changed\" do\n          mailer = double(UserMailer, deliver: nil)\n          allow(UserMailer).to receive(:password_changed_reminder).with(user).and_return(mailer)\n          expect(mailer).to receive(:deliver)\n\n          subject\n        end\n      end\n\n      context \"when failure\" do\n        subject do\n          patch update_password_users_path(user),\n            params: {\n              user: {\n                password: \"\",\n                password_confirmation: \"wrong\"\n              }\n            }\n        end\n\n        it \"does not update the user password\", :aggregate_failures do\n          subject\n\n          expect(user.valid_password?(\"wrong\")).to be_falsey\n          expect(user.valid_password?(\"\")).to be_falsey\n        end\n\n        it \"does not call UserMailer to reminder the user that password has changed\" do\n          mailer = double(UserMailer, deliver: nil)\n          allow(UserMailer).to receive(:password_changed_reminder).with(user).and_return(mailer)\n          expect(mailer).not_to receive(:deliver)\n\n          subject\n        end\n      end\n    end\n\n    context \"when supervisor\" do\n      let(:user) { create(:supervisor) }\n\n      context \"when successfully\" do\n        it \"updates the user password\" do\n          subject\n\n          expect(user.valid_password?(\"new_pass\")).to be_truthy\n        end\n\n        it \"calls UserMailer to reminder the user that password has changed\" do\n          mailer = double(UserMailer, deliver: nil)\n          allow(UserMailer).to receive(:password_changed_reminder).with(user).and_return(mailer)\n          expect(mailer).to receive(:deliver)\n\n          subject\n        end\n\n        it \"bypasses sign in if the current user is the true user\" do\n          expect_any_instance_of(UsersController).to receive(:bypass_sign_in).with(user)\n          subject\n        end\n\n        it \"does not bypass sign in when the current user is not the true user\" do\n          allow_any_instance_of(UsersController).to receive(:true_user).and_return(User.new)\n          expect_any_instance_of(UsersController).not_to receive(:bypass_sign_in).with(user)\n          subject\n        end\n      end\n\n      context \"when failure\" do\n        subject do\n          patch update_password_users_path(user),\n            params: {\n              user: {\n                password: \"\",\n                password_confirmation: \"wrong\"\n              }\n            }\n        end\n\n        it \"does not update the user password\", :aggregate_failures do\n          subject\n\n          expect(user.valid_password?(\"wrong\")).to be_falsey\n          expect(user.valid_password?(\"\")).to be_falsey\n        end\n\n        it \"does not call UserMailer to reminder the user that password has changed\" do\n          mailer = double(UserMailer, deliver: nil)\n          allow(UserMailer).to receive(:password_changed_reminder).with(user).and_return(mailer)\n          expect(mailer).not_to receive(:deliver)\n\n          subject\n        end\n      end\n    end\n\n    context \"when casa_admin\" do\n      let(:user) { create(:casa_admin) }\n\n      context \"when successfully\" do\n        it \"updates the user password\" do\n          subject\n\n          expect(user.valid_password?(\"new_pass\")).to be_truthy\n        end\n\n        it \"calls UserMailer to reminder the user that password has changed\" do\n          mailer = double(UserMailer, deliver: nil)\n          allow(UserMailer).to receive(:password_changed_reminder).with(user).and_return(mailer)\n          expect(mailer).to receive(:deliver)\n\n          subject\n        end\n\n        it \"bypasses sign in if the current user is the true user\" do\n          expect_any_instance_of(UsersController).to receive(:bypass_sign_in).with(user)\n          subject\n        end\n\n        it \"does not bypass sign in when the current user is not the true user\" do\n          allow_any_instance_of(UsersController).to receive(:true_user).and_return(User.new)\n          expect_any_instance_of(UsersController).not_to receive(:bypass_sign_in).with(user)\n          subject\n        end\n      end\n\n      context \"when failure\" do\n        subject do\n          patch update_password_users_path(user),\n            params: {\n              user: {\n                password: \"\",\n                password_confirmation: \"wrong\"\n              }\n            }\n        end\n\n        it \"does not update the user password\", :aggregate_failures do\n          subject\n\n          expect(user.valid_password?(\"wrong\")).to be_falsey\n          expect(user.valid_password?(\"\")).to be_falsey\n        end\n\n        it \"does not call UserMailer to reminder the user that password has changed\" do\n          mailer = double(UserMailer, deliver: nil)\n          allow(UserMailer).to receive(:password_changed_reminder).with(user).and_return(mailer)\n          expect(mailer).not_to receive(:deliver)\n\n          subject\n        end\n      end\n    end\n  end\n\n  describe \"PATCH /update_email\" do\n    subject do\n      patch update_email_users_path(user),\n        params: {\n          user: {\n            current_password: \"12345678\",\n            email: \"newemail@example.com\"\n          }\n        }\n    end\n\n    before { sign_in user }\n\n    context \"when volunteer\" do\n      let(:user) { create(:volunteer, email: \"old_email@example.com\") }\n\n      context \"when successfully\" do\n        it \"updates the user email\" do\n          subject\n          user.confirm\n          expect(user.valid_password?(\"12345678\")).to be_truthy\n          expect(user.email).to eq(\"newemail@example.com\")\n          expect(user.old_emails).to contain_exactly(\"old_email@example.com\")\n        end\n\n        it \"send an alert and a confirmation email\" do\n          subject\n\n          expect(ActionMailer::Base.deliveries.count).to eq(1)\n          expect(ActionMailer::Base.deliveries.last.body.encoded)\n            .to match(\"Click here to confirm your email\")\n        end\n\n        it \"strips whitespace from email\" do\n          patch update_email_users_path(user),\n            params: {\n              user: {\n                current_password: \"12345678\",\n                email: \"  newemail@example.com  \"\n              }\n            }\n          user.confirm\n          expect(user.email).to eq(\"newemail@example.com\")\n        end\n      end\n\n      context \"when failure\" do\n        subject do\n          patch update_email_users_path(user),\n            params: {\n              user: {\n                current_password: \"wrongpassword\",\n                email: \"wrong@example.com\"\n              }\n            }\n        end\n\n        it \"does not update the user email\", :aggregate_failures do\n          subject\n\n          expect(user.valid_password?(\"wrongpassword\")).to be_falsey\n          expect(user.valid_password?(\"\")).to be_falsey\n          expect(user.email).not_to eq(\"wrong@example.com\")\n        end\n\n        it \"does not call UserMailer to reminder the user that password has changed\" do\n          subject\n          expect(ActionMailer::Base.deliveries.count).to eq(0)\n\n          subject\n        end\n      end\n    end\n\n    context \"when supervisor\" do\n      let(:user) { create(:supervisor) }\n\n      context \"when successfully\" do\n        it \"updates the user email\" do\n          subject\n          user.confirm\n          expect(user.valid_password?(\"12345678\")).to be_truthy\n          expect(user.email).to eq(\"newemail@example.com\")\n        end\n\n        it \"calls DeviseMailer to remind the user that email has changed along with a confirmation link\" do\n          subject\n\n          expect(ActionMailer::Base.deliveries.count).to eq(1)\n          expect(ActionMailer::Base.deliveries.last.body.encoded)\n            .to match(\"Click here to confirm your email\")\n        end\n\n        it \"bypasses sign in if the current user is the true user\" do\n          expect_any_instance_of(UsersController).to receive(:bypass_sign_in).with(user)\n          subject\n        end\n\n        it \"does not bypass sign in when the current user is not the true user\" do\n          allow_any_instance_of(UsersController).to receive(:true_user).and_return(User.new)\n          expect_any_instance_of(UsersController).not_to receive(:bypass_sign_in).with(user)\n          subject\n        end\n      end\n\n      context \"when failure\" do\n        subject do\n          patch update_password_users_path(user),\n            params: {\n              user: {\n                password: \"wrong\",\n                email: \"wrong@example.com\"\n              }\n            }\n        end\n\n        it \"does not update the user password\", :aggregate_failures do\n          subject\n\n          expect(user.valid_password?(\"wrong\")).to be_falsey\n          expect(user.valid_password?(\"\")).to be_falsey\n          expect(user.email).not_to eq(\"wrong@example.com\")\n        end\n\n        it \"does not call UserMailer to reminder the user that password has changed\" do\n          expect(ActionMailer::Base.deliveries.count).to eq(0)\n\n          subject\n        end\n      end\n    end\n\n    context \"when casa_admin\" do\n      let(:user) { create(:casa_admin) }\n\n      context \"when successfully\" do\n        it \"updates the user email\" do\n          subject\n          user.confirm\n\n          expect(user.valid_password?(\"12345678\")).to be_truthy\n          expect(user.email).to eq(\"newemail@example.com\")\n        end\n\n        it \"calls DeviseMailer to remind the user that email has changed along with a confirmation link\" do\n          subject\n\n          expect(ActionMailer::Base.deliveries.count).to eq(1)\n          expect(ActionMailer::Base.deliveries.last.body.encoded)\n            .to match(\"Click here to confirm your email\")\n        end\n\n        it \"bypasses sign in if the current user is the true user\" do\n          expect_any_instance_of(UsersController).to receive(:bypass_sign_in).with(user)\n          subject\n        end\n\n        it \"does not bypass sign in when the current user is not the true user\" do\n          allow_any_instance_of(UsersController).to receive(:true_user).and_return(User.new)\n          expect_any_instance_of(UsersController).not_to receive(:bypass_sign_in).with(user)\n          subject\n        end\n      end\n\n      context \"when failure\" do\n        subject do\n          patch update_password_users_path(user),\n            params: {\n              user: {\n                password: \"\",\n                email: \"wrong@example.com\"\n              }\n            }\n        end\n\n        it \"does not update the user email\", :aggregate_failures do\n          subject\n\n          expect(user.valid_password?(\"wrong\")).to be_falsey\n          expect(user.valid_password?(\"\")).to be_falsey\n          expect(user.email).not_to eq(\"wrong@example.com\")\n        end\n\n        it \"does not call UserMailer to reminder the user that password has changed\" do\n          expect(ActionMailer::Base.deliveries.count).to eq(0)\n        end\n      end\n    end\n  end\n\n  describe \"PATCH /add_language\" do\n    let(:volunteer) { create(:volunteer) }\n\n    before { sign_in volunteer }\n\n    context \"when request params are valid\" do\n      let(:language) { create(:language) }\n\n      before do\n        patch add_language_users_path(volunteer), params: {\n          language_id: language.id\n        }\n      end\n\n      it \"adds language to current user\" do\n        expect(volunteer.languages).to include(language)\n      end\n\n      it \"notifies the user that the language has been added\" do\n        expect(response).to redirect_to(edit_users_path)\n        expect(flash[:notice]).to eq \"#{language.name} was added to your languages list.\"\n      end\n    end\n\n    context \"when request params are invalid\" do\n      it \"displays an error message when the Language id is empty\" do\n        patch add_language_users_path(volunteer), params: {\n          language_id: \"\"\n        }\n        expect(response).to have_http_status(:unprocessable_content)\n        expect(response.body).to include(\"Please select a language before adding.\")\n      end\n    end\n\n    context \"when the user tries to add the same language again\" do\n      let(:language) { create(:language) }\n\n      before do\n        # Add the language once\n        patch add_language_users_path(volunteer), params: {\n          language_id: language.id\n        }\n        # Try to add the same language again\n        patch add_language_users_path(volunteer), params: {\n          language_id: language.id\n        }\n      end\n\n      it \"does not add the language again\" do\n        expect(volunteer.languages.count).to eq(1) # Ensure the language count remains the same\n      end\n\n      it \"notifies the user that the language is already in their list\" do\n        expect(response).to have_http_status(:unprocessable_content)\n        expect(response.body).to include(\"#{language.name} is already in your languages list.\")\n      end\n    end\n  end\n\n  describe \"DELETE /remove_language\" do\n    let(:volunteer) { create(:volunteer) }\n\n    before { sign_in volunteer }\n\n    context \"when request params are valid\" do\n      let(:language) { create(:language) }\n\n      before do\n        patch add_language_users_path(volunteer), params: {\n          language_id: language.id\n        }\n      end\n\n      it \"removes a language from a volunteer languages list\" do\n        delete remove_language_users_path(language_id: language.id)\n\n        expect(response.status).to eq 302\n        expect(response).to redirect_to(edit_users_path)\n        expect(flash[:notice]).to eq \"#{language.name} was removed from your languages list.\"\n        expect(volunteer.languages).not_to include language\n      end\n    end\n\n    context \"when request params are invalid\" do\n      let(:language) { create(:language) }\n\n      before do\n        patch add_language_users_path(volunteer), params: {\n          language_id: language.id\n        }\n      end\n\n      it \"raises error when Language do not exist\" do\n        expect { delete remove_language_users_path(999) }.to raise_error(ActiveRecord::RecordNotFound)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/requests/volunteers_spec.rb",
    "content": "require \"rails_helper\"\nrequire \"support/stubbed_requests/webmock_helper\"\n\nRSpec.describe \"/volunteers\", type: :request do\n  let(:organization) { create(:casa_org) }\n  let(:admin) { build(:casa_admin, casa_org: organization) }\n  let(:supervisor) { create(:supervisor, casa_org: organization) }\n  let(:volunteer) { create(:volunteer, casa_org: organization) }\n\n  describe \"GET /index\" do\n    it \"renders a successful response\" do\n      sign_in admin\n\n      get volunteers_path\n      expect(response).to be_successful\n    end\n  end\n\n  describe \"GET /show\" do\n    it \"renders a successful response\" do\n      sign_in admin\n\n      get volunteer_path(volunteer.id)\n      expect(response).to redirect_to(edit_volunteer_path(volunteer.id))\n    end\n\n    context \"with admin from different organization\" do\n      let(:other_org_admin) { build(:casa_admin, casa_org: create(:casa_org)) }\n\n      it \"does not show\" do\n        sign_in other_org_admin\n        get volunteer_path(volunteer.id)\n        expect(response).to redirect_to(\"/\")\n      end\n    end\n  end\n\n  describe \"POST /datatable\" do\n    let(:data) { {recordsTotal: 51, recordsFiltered: 10, data: 10.times.map { {} }} }\n\n    before do\n      allow(VolunteerDatatable).to receive(:new).and_return double \"datatable\", to_json: data.to_json\n    end\n\n    it \"is successful\" do\n      sign_in admin\n\n      post datatable_volunteers_path\n      expect(response).to be_successful\n    end\n\n    it \"renders json data\" do\n      sign_in admin\n\n      post datatable_volunteers_path\n      expect(response.body).to eq data.to_json\n    end\n  end\n\n  describe \"GET /new\" do\n    it \"renders a successful response for admin user\" do\n      sign_in admin\n\n      get new_volunteer_path\n      expect(response).to be_successful\n    end\n\n    it \"renders a successful response for supervisor user\" do\n      sign_in supervisor\n\n      get new_volunteer_path\n      expect(response).to be_successful\n    end\n\n    it \"does not render for volunteers\" do\n      sign_in volunteer\n\n      get new_volunteer_path\n      expect(response).not_to be_successful\n    end\n  end\n\n  describe \"GET /edit\" do\n    subject(:request) do\n      get edit_volunteer_url(volunteer)\n\n      response\n    end\n\n    before { sign_in admin }\n\n    it { is_expected.to be_successful }\n\n    it \"shows correct volunteer\", :aggregate_failures do\n      create(:volunteer, casa_org: organization)\n\n      page = request.parsed_body.to_html\n      expect(page).to include(volunteer.email)\n      expect(page).to include(volunteer.display_name)\n      expect(page).to include(volunteer.phone_number)\n    end\n\n    it \"shows correct supervisor options\", :aggregate_failures do\n      supervisors = create_list(:supervisor, 3, casa_org: organization)\n      supervisors.append(create(:supervisor, casa_org: organization, display_name: \"O'Hara\")) # test for HTML escaping\n\n      page = Nokogiri::HTML(subject.body)\n      names = page.css(\"#supervisor_volunteer_supervisor_id option\").map(&:text)\n      expect(supervisors.map(&:display_name)).to match_array(names)\n    end\n  end\n\n  describe \"POST /create\" do\n    context \"with valid params\" do\n      let(:params) do\n        {\n          volunteer: {\n            display_name: \"Example\",\n            email: \"volunteer1@example.com\"\n          }\n        }\n      end\n\n      it \"creates a new volunteer and sends account_setup email\" do\n        organization = create(:casa_org)\n        admin = create(:casa_admin, casa_org: organization)\n\n        sign_in admin\n        expect {\n          post volunteers_url, params: params\n        }.to change { ActionMailer::Base.deliveries.count }.by(1)\n\n        expect(response).to have_http_status(:redirect)\n        volunteer = Volunteer.last\n        expect(volunteer.email).to eq(\"volunteer1@example.com\")\n        expect(volunteer.display_name).to eq(\"Example\")\n        expect(volunteer.casa_org).to eq(admin.casa_org)\n        expect(response).to redirect_to edit_volunteer_path(volunteer)\n      end\n\n      it \"sends a SMS when phone number exists\" do\n        organization = create(:casa_org, twilio_enabled: true)\n        admin = create(:casa_admin, casa_org: organization)\n        params[:volunteer][:phone_number] = \"+12222222222\"\n        twilio_activation_success_stub = WebMockHelper.twilio_activation_success_stub(\"volunteer\")\n        short_io_stub = WebMockHelper.short_io_stub_sms\n\n        sign_in admin\n        post volunteers_url, params: params\n\n        expect(short_io_stub).to have_been_requested.times(2)\n        expect(twilio_activation_success_stub).to have_been_requested.times(1)\n        expect(response).to have_http_status(:redirect)\n        follow_redirect!\n        expect(flash[:notice]).to match(/New volunteer created successfully. SMS has been sent!/)\n      end\n\n      it \"does not send a SMS when phone number is not provided\" do\n        organization = create(:casa_org, twilio_enabled: true)\n        admin = create(:casa_admin, casa_org: organization)\n        sign_in admin\n        post volunteers_url, params: params\n\n        expect(response).to have_http_status(:redirect)\n        follow_redirect!\n        expect(flash[:notice]).to match(/New volunteer created successfully./)\n      end\n\n      it \"does not send a SMS when Twilio API has an error\" do\n        org = create(:casa_org, twilio_account_sid: \"articuno31\", twilio_enabled: true)\n        admin = create(:casa_admin, casa_org: org)\n        twilio_activation_error_stub = WebMockHelper.twilio_activation_error_stub(\"volunteer\")\n        short_io_stub = WebMockHelper.short_io_stub_sms\n        params[:volunteer][:phone_number] = \"+12222222222\"\n\n        sign_in admin\n        post volunteers_url, params: params\n\n        expect(short_io_stub).to have_been_requested.times(2) # TODO: why is this called at all?\n        expect(twilio_activation_error_stub).to have_been_requested.times(1)\n        expect(response).to have_http_status(:redirect)\n        follow_redirect!\n        expect(flash[:notice]).to match(/New volunteer created successfully. SMS not sent. Error: ./)\n      end\n\n      it \"does not send a SMS if the casa_org does not have Twilio enabled\" do\n        org = create(:casa_org, twilio_enabled: false)\n        admin = build(:casa_admin, casa_org: org)\n        params[:volunteer][:phone_number] = \"+12222222222\"\n        short_io_stub = WebMockHelper.short_io_stub_sms\n\n        sign_in admin\n        post volunteers_url, params: params\n\n        expect(short_io_stub).to have_been_requested.times(2) # TODO: why is this called at all?\n        expect(response).to have_http_status(:redirect)\n        follow_redirect!\n        expect(flash[:notice]).to match(/New volunteer created successfully./)\n      end\n    end\n\n    context \"with invalid parameters\" do\n      let(:params) do\n        {\n          volunteer: {\n            display_name: \"\",\n            email: \"volunteer1@example.com\"\n          }\n        }\n      end\n\n      it \"does not create a new volunteer\" do\n        org = create(:casa_org, twilio_enabled: false)\n        admin = build(:casa_admin, casa_org: org)\n\n        sign_in admin\n\n        expect {\n          post volunteers_url, params: params\n        }.to change(Volunteer, :count).by(0)\n          .and change(ActionMailer::Base.deliveries, :count).by(0)\n        expect(response).to have_http_status(:unprocessable_content)\n      end\n    end\n  end\n\n  describe \"PATCH /update\" do\n    before { sign_in admin }\n\n    context \"with valid params\" do\n      it \"updates the volunteer\" do\n        patch volunteer_path(volunteer), params: {\n          volunteer: {display_name: \"New Name\", phone_number: \"+15463457898\"}\n        }\n        expect(response).to have_http_status(:redirect)\n\n        volunteer.reload\n        expect(volunteer.display_name).to eq \"New Name\"\n        expect(volunteer.phone_number).to eq \"15463457898\"\n      end\n\n      it \"sends the volunteer a confirmation email upon email change\" do\n        patch volunteer_path(volunteer), params: {\n          volunteer: {email: \"newemail@gmail.com\"}\n        }\n        expect(response).to have_http_status(:redirect)\n\n        volunteer.reload\n        expect(volunteer.unconfirmed_email).to eq(\"newemail@gmail.com\")\n        expect(ActionMailer::Base.deliveries.count).to eq(1)\n        expect(ActionMailer::Base.deliveries.first).to be_a(Mail::Message)\n        expect(ActionMailer::Base.deliveries.first.body.encoded)\n          .to match(\"Click here to confirm your email\")\n      end\n    end\n\n    context \"with invalid params\" do\n      let!(:other_volunteer) { create(:volunteer) }\n\n      it \"does not update the volunteer\" do\n        volunteer.supervisor = build(:supervisor)\n\n        patch volunteer_path(volunteer), params: {\n          volunteer: {email: other_volunteer.email, display_name: \"New Name\", phone_number: \"+15463457898\"}\n        }\n        expect(response).to have_http_status(:unprocessable_content)\n\n        volunteer.reload\n        expect(volunteer.display_name).not_to eq \"New Name\"\n        expect(volunteer.email).not_to eq other_volunteer.email\n        expect(volunteer.phone_number).not_to eq \"+15463457898\"\n      end\n    end\n\n    # Activation/deactivation must be done separately through /activate and\n    # /deactivate, respectively\n    it \"cannot change the active state\" do\n      patch volunteer_path(volunteer), params: {\n        volunteer: {active: false}\n      }\n      volunteer.reload\n\n      expect(volunteer.active).to eq(true)\n    end\n  end\n\n  describe \"PATCH /activate\" do\n    let(:volunteer) { create(:volunteer, :inactive, casa_org: organization) }\n    let(:volunteer_with_cases) { create(:volunteer, :with_cases_and_contacts, casa_org: organization) }\n    let(:case_number) { volunteer_with_cases.casa_cases.first.case_number.parameterize }\n\n    it \"activates an inactive volunteer\" do\n      sign_in admin\n\n      patch activate_volunteer_path(volunteer)\n\n      volunteer.reload\n      expect(volunteer.active).to eq(true)\n    end\n\n    it \"sends an activation email\" do\n      sign_in admin\n\n      expect {\n        patch activate_volunteer_path(volunteer)\n      }.to change { ActionMailer::Base.deliveries.count }.by(1)\n    end\n\n    context \"activated volunteer without cases\" do\n      it \"shows a flash messages indicating the volunteer has been activated and sent an email\" do\n        sign_in admin\n\n        patch activate_volunteer_path(volunteer)\n\n        expect(response).to redirect_to(edit_volunteer_path(volunteer))\n        follow_redirect!\n        expect(flash[:notice]).to match(/Volunteer was activated. They have been sent an email./)\n      end\n    end\n\n    context \"activated volunteer with cases\" do\n      it \"shows a flash message indicating the volunteer has been activated and sent an email\" do\n        sign_in admin\n\n        patch activate_volunteer_path(id: volunteer_with_cases, redirect_to_path: \"casa_case\", casa_case_id: case_number)\n\n        expect(response).to redirect_to(edit_casa_case_path(case_number))\n        follow_redirect!\n        expect(flash[:notice]).to match(/Volunteer was activated. They have been sent an email./)\n      end\n    end\n  end\n\n  describe \"PATCH /deactivate\" do\n    subject(:request) do\n      patch deactivate_volunteer_path(volunteer)\n\n      response\n    end\n\n    before { sign_in admin }\n\n    it { is_expected.to redirect_to(edit_volunteer_path(volunteer)) }\n\n    it \"shows the correct flash message\" do\n      request\n      expect(flash[:notice]).to eq(\"Volunteer was deactivated.\")\n    end\n\n    it \"deactivates an active volunteer\" do\n      request\n      expect(volunteer.reload.active).to eq(false)\n    end\n\n    it \"doesn't send a deactivation email\" do\n      expect { request }.not_to change { ActionMailer::Base.deliveries.count }\n    end\n  end\n\n  describe \"PATCH /resend_invitation\" do\n    it \"resends an invitation email as an admin\" do\n      sign_in admin\n\n      expect(volunteer.invitation_created_at.present?).to eq(false)\n\n      get resend_invitation_volunteer_path(volunteer)\n      volunteer.reload\n\n      expect(volunteer.invitation_created_at.present?).to eq(true)\n      expect(Devise.mailer.deliveries.count).to eq(1)\n      expect(Devise.mailer.deliveries.first.subject).to eq(I18n.t(\"devise.mailer.invitation_instructions.subject\"))\n      expect(response).to redirect_to(edit_volunteer_path(volunteer))\n    end\n\n    it \"resends an invitation email as a supervisor\" do\n      sign_in supervisor\n\n      expect(volunteer.invitation_created_at.present?).to eq(false)\n\n      get resend_invitation_volunteer_path(volunteer)\n      volunteer.reload\n\n      expect(volunteer.invitation_created_at.present?).to eq(true)\n      expect(Devise.mailer.deliveries.count).to eq(1)\n      expect(Devise.mailer.deliveries.first.subject).to eq(I18n.t(\"devise.mailer.invitation_instructions.subject\"))\n      expect(response).to redirect_to(edit_volunteer_path(volunteer))\n    end\n  end\n\n  describe \"PATCH /reminder\" do\n    describe \"as admin\" do\n      it \"emails the volunteer\" do\n        organization = create(:casa_org)\n        admin = create(:casa_admin, casa_org_id: organization.id)\n        supervisor = build(:supervisor, casa_org: organization)\n        volunteer = create(:volunteer, supervisor: supervisor, casa_org_id: organization.id)\n\n        sign_in admin\n\n        patch reminder_volunteer_path(volunteer)\n\n        email = ActionMailer::Base.deliveries.last\n        expect(email).not_to be_nil\n        expect(email.to).to eq [volunteer.email]\n        expect(email.subject).to eq(\"Reminder to input case contacts\")\n      end\n\n      it \"cc's their supervisor and admin when the 'with_cc` param is present\" do\n        admin = create(:casa_admin, casa_org_id: organization.id)\n        supervisor = build(:supervisor, casa_org: organization)\n        volunteer = create(:volunteer, supervisor: supervisor, casa_org_id: organization.id)\n\n        sign_in admin\n\n        patch reminder_volunteer_path(volunteer), params: {\n          with_cc: true\n        }\n\n        email = ActionMailer::Base.deliveries.last\n        expect(email).not_to be_nil\n        expect(email.to).to eq [volunteer.email]\n        expect(email.subject).to eq(\"Reminder to input case contacts\")\n        expect(email.cc).to include(volunteer.supervisor.email)\n        expect(email.cc).to include(admin.email)\n      end\n    end\n\n    describe \"as supervisor\" do\n      it \"emails the volunteer\" do\n        organization = create(:casa_org)\n        supervisor = build(:supervisor, casa_org: organization)\n        volunteer = create(:volunteer, supervisor: supervisor, casa_org_id: organization.id)\n\n        sign_in supervisor\n\n        patch reminder_volunteer_path(volunteer)\n\n        email = ActionMailer::Base.deliveries.last\n        expect(email).not_to be_nil\n        expect(email.to).to eq [volunteer.email]\n        expect(email.subject).to eq(\"Reminder to input case contacts\")\n      end\n\n      it \"cc's their supervisor when the 'with_cc` param is present\" do\n        organization = create(:casa_org)\n        supervisor = build(:supervisor, casa_org: organization)\n        volunteer = create(:volunteer, supervisor: supervisor, casa_org_id: organization.id)\n\n        sign_in supervisor\n\n        patch reminder_volunteer_path(volunteer), params: {\n          with_cc: true\n        }\n\n        email = ActionMailer::Base.deliveries.last\n        expect(email).not_to be_nil\n        expect(email.to).to eq [volunteer.email]\n        expect(email.subject).to eq(\"Reminder to input case contacts\")\n        expect(email.cc).to eq([supervisor.email])\n      end\n    end\n\n    it \"emails the volunteer without a supervisor\" do\n      organization = create(:casa_org)\n      volunteer_without_supervisor = create(:volunteer)\n      supervisor = build(:supervisor, casa_org: organization)\n\n      sign_in supervisor\n\n      patch reminder_volunteer_path(volunteer_without_supervisor), params: {\n        with_cc: true\n      }\n\n      email = ActionMailer::Base.deliveries.last\n      expect(email).not_to be_nil\n      expect(email.to).to eq [volunteer_without_supervisor.email]\n      expect(email.subject).to eq(\"Reminder to input case contacts\")\n      expect(email.cc).to be_empty\n    end\n  end\n\n  describe \"POST /send_reactivation_alert\" do\n    before do\n      sign_in admin\n      @short_io_stub = WebMockHelper.twilio_activation_success_stub\n    end\n\n    it \"sends an reactivation SMS\" do\n      get send_reactivation_alert_volunteer_path(volunteer)\n      expect(response).to redirect_to(edit_volunteer_path(volunteer))\n      expect(response.status).to match 302\n    end\n\n    it \"does not send a reactivation SMS when Casa Org has Twilio disabled\" do\n      org = create(:casa_org, twilio_enabled: false)\n      adm = create(:casa_admin, casa_org: org)\n      vol = create(:volunteer, casa_org: org)\n\n      sign_in adm\n\n      get send_reactivation_alert_volunteer_path(vol)\n      expect(response).to redirect_to(edit_volunteer_path(vol))\n      expect(flash[:notice]).to match(/Volunteer reactivation alert not sent. Twilio is disabled for #{org.name}/)\n    end\n  end\n\n  describe \"GET /impersonate\" do\n    let!(:other_volunteer) { create(:volunteer, casa_org: organization) }\n    let!(:supervisor) { create(:supervisor, casa_org: organization) }\n\n    it \"can impersonate a volunteer as an admin\" do\n      sign_in admin\n\n      get impersonate_volunteer_path(volunteer)\n      expect(response).to redirect_to(root_path)\n      expect(controller.current_user).to eq(volunteer)\n    end\n\n    it \"can impersonate a volunteer as a supervisor\" do\n      sign_in supervisor\n\n      get impersonate_volunteer_path(volunteer)\n      expect(response).to redirect_to(root_path)\n      expect(controller.current_user).to eq(volunteer)\n    end\n\n    it \"can not impersonate as a volunteer\" do\n      sign_in volunteer\n\n      get impersonate_volunteer_path(other_volunteer)\n      expect(response).to redirect_to(root_path)\n      expect(controller.current_user).to eq(volunteer)\n\n      follow_redirect!\n      expect(flash[:notice]).to match(/Sorry, you are not authorized to perform this action./)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/routing/all_casa_admins/patch_notes_routing_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe AllCasaAdmins::PatchNotesController, type: :routing do\n  describe \"routing\" do\n    it \"routes to #index\" do\n      expect(get: \"/all_casa_admins/patch_notes\").to route_to(\"all_casa_admins/patch_notes#index\")\n    end\n\n    it \"routes to #create\" do\n      expect(post: \"/all_casa_admins/patch_notes\").to route_to(\"all_casa_admins/patch_notes#create\")\n    end\n\n    it \"routes to #update via PUT\" do\n      expect(put: \"/all_casa_admins/patch_notes/1\").to route_to(\"all_casa_admins/patch_notes#update\", id: \"1\")\n    end\n\n    it \"routes to #update via PATCH\" do\n      expect(patch: \"/all_casa_admins/patch_notes/1\").to route_to(\"all_casa_admins/patch_notes#update\", id: \"1\")\n    end\n\n    it \"routes to #destroy\" do\n      expect(delete: \"/all_casa_admins/patch_notes/1\").to route_to(\"all_casa_admins/patch_notes#destroy\", id: \"1\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/seeds/seeds_spec.rb",
    "content": "require \"rails_helper\"\nrequire \"rake\"\n\ndef empty_ar_classes\n  ar_classes = [\n    AllCasaAdmin,\n    CasaAdmin,\n    CasaCase,\n    Judge,\n    CasaOrg,\n    CaseAssignment,\n    CaseContact,\n    ContactType,\n    ContactTypeGroup,\n    Supervisor,\n    SupervisorVolunteer,\n    User,\n    LearningHour,\n    HearingType,\n    Volunteer,\n    CaseCourtOrder\n  ]\n  ar_classes.select { |klass| klass.count == 0 }.map(&:name)\nend\n\nRSpec.describe \"Seeds\" do\n  describe \"test development DB\" do\n    before do\n      Rails.application.load_tasks\n      allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new(\"test\"))\n    end\n\n    it \"successfully populates all necessary tables\" do\n      ActiveRecord::Tasks::DatabaseTasks.load_seed\n      expect(empty_ar_classes).to eq([])\n    end\n  end\nend\n"
  },
  {
    "path": "spec/services/additional_expense_params_service_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe AdditionalExpenseParamsService do\n  subject { described_class.new(params).calculate }\n\n  context \"single existing additional expense\" do\n    let(:params) { ActionController::Parameters.new(case_contact: {additional_expenses_attributes: {\"0\": {other_expense_amount: 10, other_expenses_describe: \"hi\", id: 1}}}) }\n\n    it \"calculates\" do\n      expect(subject.to_json).to eq(\"[{\\\"other_expense_amount\\\":10,\\\"other_expenses_describe\\\":\\\"hi\\\",\\\"id\\\":1}]\")\n    end\n  end\n\n  context \"multiple new additional expense\" do\n    let(:params) {\n      ActionController::Parameters.new(case_contact: {\n        additional_expenses_attributes: {\n          \"0\": {other_expense_amount: 10, other_expenses_describe: \"new expense 0\"},\n          \"1\": {other_expense_amount: 20, other_expenses_describe: \"new expense 1\"}\n        }\n      })\n    }\n\n    it \"calculates\" do\n      expect(subject.length).to eq(2)\n      expect(subject[0][\"other_expense_amount\"]).to eq(10)\n      expect(subject[0][\"other_expenses_describe\"]).to eq(\"new expense 0\")\n      expect(subject[1][\"other_expense_amount\"]).to eq(20)\n      expect(subject[1][\"other_expenses_describe\"]).to eq(\"new expense 1\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/services/backfill_followupable_service_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe BackfillFollowupableService do\n  include ActiveJob::TestHelper\n\n  let(:run_backfill) { described_class.new.fill_followup_id_and_type }\n\n  after do\n    clear_enqueued_jobs\n  end\n\n  describe \"backfilling followup polymorphic columns\" do\n    let(:case_contact) { create(:case_contact) }\n    let!(:followup) { create(:followup, :without_dual_writing, case_contact: case_contact) }\n\n    it \"updates followupable_id and followupable_type correctly\" do\n      expect { run_backfill }.to change { followup.reload.followupable_id }.from(nil).to(case_contact.id)\n        .and change { followup.reload.followupable_type }.from(nil).to(\"CaseContact\")\n    end\n\n    context \"when an error occurs during update\" do\n      before do\n        allow_any_instance_of(Followup).to receive(:update_columns).and_raise(StandardError.new(\"Update failed\"))\n      end\n\n      it \"logs the error and notifies Bugsnag\" do\n        expect(Bugsnag).to receive(:notify).with(instance_of(StandardError))\n        expect(Rails.logger).to receive(:error).with(/Failed to update Followup/)\n        expect {\n          run_backfill\n        }.not_to change { followup.reload.followupable_id }\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/services/casa_case_change_service_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CasaCaseChangeService do\n  subject { described_class.new(original, changed).changed_attributes_messages }\n\n  context \"with same original and changed\" do\n    let(:original) { create(:casa_case).full_attributes_hash }\n    let(:changed) { original }\n\n    it \"does not show diff\" do\n      expect(subject).to eq(nil)\n    end\n  end\n\n  context \"with different original and changed\" do\n    let(:original) { create(:casa_case).full_attributes_hash }\n    let(:changed) { create(:casa_case, :with_case_assignments, :with_one_court_order, :active, :with_case_contacts).full_attributes_hash }\n\n    it \"shows useful diff\" do\n      expect(subject).to contain_exactly(\"Changed Id\", \"Changed Case number\", \"Changed Created at\", \"Changed Birth month year youth\", \"1 Court order added or updated\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/services/case_contacts_contact_dates_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CaseContactsContactDates do\n  before do\n    travel_to Date.new(2021, 6, 1)\n  end\n\n  describe \"#contact_dates_details\" do\n    subject { described_class.new(interviewees).contact_dates_details }\n\n    context \"without interviewees\" do\n      let(:interviewees) { [] }\n\n      it \"returns an empty array\" do\n        expect(subject).to eq([])\n      end\n    end\n\n    context \"with interviewees\" do\n      let(:contact_type_1) { create(:contact_type, name: \"Mental therapist\") }\n      let(:contact_type_2) { create(:contact_type, name: \"Physical therapist\") }\n      let(:contact_type_3) { create(:contact_type, name: \"Aunt\") }\n\n      let(:ccct_1) { create(:case_contact_contact_type, contact_type: contact_type_1) }\n      let(:ccct_2) { create(:case_contact_contact_type, contact_type: contact_type_2) }\n      let(:ccct_3) { create(:case_contact_contact_type, contact_type: contact_type_2, case_contact: create(:case_contact, occurred_at: 1.month.ago)) }\n      let(:ccct_4) do\n        create(\n          :case_contact_contact_type,\n          contact_type: contact_type_2,\n          case_contact: create(\n            :case_contact,\n            occurred_at: 2.months.ago,\n            medium_type: CaseContact::TEXT_EMAIL\n          )\n        )\n      end\n      let(:ccct_5) { create(:case_contact_contact_type, contact_type: contact_type_3, case_contact: create(:case_contact, occurred_at: 2.months.ago)) }\n\n      let(:interviewees) { [ccct_1, ccct_2, ccct_3, ccct_4, ccct_5] }\n\n      it \"returns formatted data\" do\n        expect(subject).to eq([\n          {dates: \"6/01*\",\n           dates_by_medium_type: {\"in-person\" => \"6/01*\"},\n           name: \"Names of persons involved, starting with the child's name\",\n           type: \"Mental therapist\"},\n          {dates: \"4/01*, 5/01*, 6/01*\",\n           dates_by_medium_type: {\"in-person\" => \"5/01*, 6/01*\", \"text/email\" => \"4/01*\"},\n           name: \"Names of persons involved, starting with the child's name\",\n           type: \"Physical therapist\"},\n          {dates: \"4/01*\",\n           dates_by_medium_type: {\"in-person\" => \"4/01*\"},\n           name: \"Names of persons involved, starting with the child's name\",\n           type: \"Aunt\"}\n        ])\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/services/case_contacts_export_csv_service_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe CaseContactsExportCsvService, type: :service do\n  describe \"#perform\" do\n    it \"exports the case contacts without the court topics header by default\" do\n      casa_case = create(:casa_case)\n      create(:case_contact, casa_case: casa_case, medium_type: \"text/email\", occurred_at: Date.new(2026, 1, 8))\n      case_contacts = casa_case.decorate.case_contacts_ordered_by_occurred_at\n\n      csv = CaseContactsExportCsvService.new(case_contacts, filtered_columns).perform\n\n      parsed_csv = CSV.parse(csv, headers: true)\n      expect(parsed_csv.count).to eq(1)\n      expect(parsed_csv.headers).to eq(expected_headers)\n      expect(csv).to match(%r{text/email})\n      expect(csv).to match(/January 8, 2026/)\n      expect(parsed_csv.headers).not_to include(\"Court Topics\")\n    end\n\n    context \"when there are no case contacts\" do\n      it \"exports only the headers\" do\n        casa_case = build(:casa_case)\n        case_contacts = casa_case.decorate.case_contacts_ordered_by_occurred_at\n\n        csv = CaseContactsExportCsvService.new(case_contacts, filtered_columns).perform\n\n        parsed_csv = CSV.parse(csv, headers: true)\n        expect(parsed_csv.count).to eq(0)\n        expect(parsed_csv.headers).to eq(expected_headers)\n      end\n    end\n\n    context \"when the filtered columns includes court topics\" do\n      it \"exports the case contacts with the CaseContactReport::COLUMNS with the contact topics\" do\n        casa_case = create(:casa_case)\n        case_contact = build(:case_contact, casa_case:, medium_type: \"text/email\", occurred_at: Date.new(2026, 1, 8))\n        create(:case_contact, casa_case:, medium_type: \"in-person\", occurred_at: Date.new(2026, 3, 16))\n        contact_topic = build(:contact_topic, question: \"A Topic\")\n        create(:contact_topic_answer, case_contact:, contact_topic:, value: \"An answer\")\n        case_contacts = casa_case.decorate.case_contacts_ordered_by_occurred_at\n\n        csv = CaseContactsExportCsvService.new(case_contacts, filtered_columns).perform\n\n        parsed_csv = CSV.parse(csv, headers: true)\n        expect(parsed_csv.count).to eq(2)\n        expect(parsed_csv.headers).to eq(expected_headers + [\"A Topic\"])\n        expect(csv).to match(/in-person/)\n        expect(csv).to match(/March 16, 2026/)\n        expect(csv).to match(%r{text/email})\n        expect(csv).to match(/January 8, 2026/)\n        expect(csv).to match(/a topic/i)\n        expect(csv).to match(/an answer/i)\n      end\n\n      it \"does not include topics that don't have any answers\" do\n        casa_case = create(:casa_case)\n        case_contact = build(:case_contact, casa_case: casa_case, medium_type: \"text/email\", occurred_at: Date.new(2026, 1, 8))\n        contact_topic = build(:contact_topic, question: \"A Topic with an Answer\")\n        create(:contact_topic_answer, contact_topic:, case_contact:, value: \"An answer\")\n        build(:contact_topic, question: \"Nothing to show\")\n        case_contacts = casa_case.decorate.case_contacts_ordered_by_occurred_at\n\n        csv = CaseContactsExportCsvService.new(case_contacts, filtered_columns).perform\n\n        parsed_csv = CSV.parse(csv, headers: true)\n        expect(parsed_csv.count).to eq(1)\n        expect(parsed_csv.headers).to eq(expected_headers + [\"A Topic with an Answer\"])\n        expect(csv).to match(%r{text/email})\n        expect(csv).to match(/January 8, 2026/)\n        expect(csv).to include(\"An answer\")\n        expect(csv).not_to include(\"Nothing to show\")\n      end\n\n      context \"when there are multiple answers to a case contact's court topic\" do\n        it \"exports the case contact including only the latest contact topic answer\" do\n          casa_case = create(:casa_case)\n          case_contact = build(:case_contact, casa_case: casa_case, medium_type: \"text/email\", occurred_at: Date.new(2026, 1, 8))\n          contact_topic = build(:contact_topic, question: \"A Topic\")\n          create(:contact_topic_answer, case_contact:, contact_topic:, value: \"First answer\")\n          create(:contact_topic_answer, case_contact:, contact_topic:, value: \"Second answer\")\n          case_contacts = casa_case.decorate.case_contacts_ordered_by_occurred_at\n\n          csv = CaseContactsExportCsvService.new(case_contacts, filtered_columns).perform\n\n          parsed_csv = CSV.parse(csv, headers: true)\n          expect(parsed_csv.count).to eq(1)\n          expect(parsed_csv.headers).to eq(expected_headers + [\"A Topic\"])\n          expect(csv).to match(%r{text/email})\n          expect(csv).to match(/January 8, 2026/)\n          expect(csv).to match(/a topic/i)\n          expect(csv).to include(\"Second answer\")\n          expect(csv).not_to include(\"First answer\")\n        end\n      end\n    end\n\n    context \"when court topics are filtered out\" do\n      it \"exports the case contacts with the CaseContactReport::COLUMNS without the Court topics entries\" do\n        casa_case = create(:casa_case)\n        case_contact = build(:case_contact, casa_case:, medium_type: \"text/email\", occurred_at: Date.new(2026, 1, 8))\n        create(:case_contact, casa_case:, medium_type: \"in-person\", occurred_at: Date.new(2026, 3, 16))\n        contact_topic = build(:contact_topic, question: \"Another Topic\")\n        create(:contact_topic_answer, case_contact:, contact_topic:, value: \"Another answer\")\n        case_contacts = casa_case.decorate.case_contacts_ordered_by_occurred_at\n        filtered_columns = CaseContactReport::COLUMNS - [:court_topics]\n\n        csv = CaseContactsExportCsvService.new(case_contacts, filtered_columns).perform\n\n        parsed_csv = CSV.parse(csv, headers: true)\n        expect(parsed_csv.count).to eq(2)\n        expect(parsed_csv.headers).to eq(expected_headers - [\"A Topic\"])\n        expect(csv).to match(/in-person/)\n        expect(csv).to match(/March 16, 2026/)\n        expect(csv).to match(%r{text/email})\n        expect(csv).to match(/January 8, 2026/)\n        expect(csv).not_to include(\"Another Topic\")\n        expect(csv).not_to include(\"Another answer\")\n      end\n    end\n  end\n\n  def filtered_columns\n    CaseContactReport::COLUMNS\n  end\n\n  def expected_headers\n    [\n      \"Internal Contact Number\",\n      \"Duration Minutes\",\n      \"Contact Types\",\n      \"Contact Made\",\n      \"Contact Medium\",\n      \"Occurred At\",\n      \"Added To System At\",\n      \"Miles Driven\",\n      \"Wants Driving Reimbursement\",\n      \"Casa Case Number\",\n      \"Creator Email\",\n      \"Creator Name\",\n      \"Supervisor Name\",\n      \"Case Contact Notes\"\n    ]\n  end\nend\n"
  },
  {
    "path": "spec/services/court_report_due_sms_reminder_service_spec.rb",
    "content": "require \"rails_helper\"\nrequire \"support/stubbed_requests/webmock_helper\"\n\nRSpec.describe CourtReportDueSmsReminderService do\n  include SmsBodyHelper\n\n  describe \"court report due sms reminder service\" do\n    let(:org) { create(:casa_org, twilio_enabled: true) }\n    let!(:volunteer) { create(:volunteer, casa_org: org, receive_sms_notifications: true, phone_number: \"+12223334444\") }\n    let!(:report_due_date) { Date.current + 7.days }\n\n    before do\n      WebMockHelper.short_io_court_report_due_date_stub\n      WebMockHelper.twilio_court_report_due_date_stub\n    end\n\n    context \"when sending sms reminder\" do\n      it \"sends a SMS with a short url successfully\" do\n        response = CourtReportDueSmsReminderService.court_report_reminder(volunteer, report_due_date)\n\n        expect(response.error_code).to match nil\n        expect(response.status).to match \"sent\"\n        expect(response.body).to match court_report_due_msg(report_due_date, \"https://42ni.short.gy/jzTwdF\")\n      end\n    end\n\n    context \"when volunteer is not opted into sms notifications\" do\n      let(:volunteer) { create(:volunteer, receive_sms_notifications: false) }\n\n      it \"does not send a SMS\" do\n        response = CourtReportDueSmsReminderService.court_report_reminder(volunteer, report_due_date)\n        expect(response).to be_nil\n      end\n    end\n\n    context \"when volunteer does not have a valid phone number\" do\n      let(:volunteer) { create(:volunteer, phone_number: nil) }\n\n      it \"does not send a SMS\" do\n        response = CourtReportDueSmsReminderService.court_report_reminder(volunteer, report_due_date)\n        expect(response).to be_nil\n      end\n    end\n\n    context \"when volunteer's casa_org does not have twilio enabled\" do\n      let(:org) { create(:casa_org, twilio_enabled: false) }\n      let(:volunteer_2) { create(:volunteer, casa_org: org) }\n\n      it \"does not send a SMS\" do\n        response = CourtReportDueSmsReminderService.court_report_reminder(volunteer_2, report_due_date)\n        expect(response).to be_nil\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/services/court_report_format_contact_date_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe CourtReportFormatContactDate, type: :service do\n  describe \"#format\" do\n    context \"when there has been a successful contact\" do\n      it \"returns the day and month of when the Case Contact's occcurred\" do\n        case_contact = build(\n          :case_contact,\n          occurred_at: Date.new(2026, 4, 16),\n          contact_made: true\n        )\n\n        contact_date = CourtReportFormatContactDate.new(case_contact).format\n\n        expect(contact_date).to eq(\"4/16\")\n      end\n    end\n\n    context \"when there has been no contact made\" do\n      it \"returns the day and month of when the Case Contact's occcurred with a suffix\" do\n        case_contact = build(:case_contact, occurred_at: Date.new(2026, 3, 16))\n\n        contact_date = CourtReportFormatContactDate.new(case_contact).format\n\n        expect(contact_date).to eq(\"3/16*\")\n      end\n    end\n  end\n\n  describe \"#format_long\" do\n    it \"returns the day, month and year of when the Case Contact's in the long format\" do\n      case_contact = build(:case_contact, occurred_at: Date.new(2026, 2, 16))\n\n      contact_date = CourtReportFormatContactDate.new(case_contact).format_long\n\n      expect(contact_date).to eq(\"02/16/26\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/services/create_all_casa_admin_service_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe CreateAllCasaAdminService, type: :service do\n  let(:user) { build(:user) }\n  let(:params) do\n    ActionController::Parameters.new(\n      {\n        all_casa_admin: {\n          email: \"casa_admin23@example.com\"\n        }\n      }\n    ).permit!\n  end\n\n  describe \"#build\" do\n    it \"initializes an AllCasaAdmin with the given params and a password\" do\n      allow(SecureRandom).to receive(:hex).with(10).and_return(\"12345678910\")\n\n      admin = CreateAllCasaAdminService.new(params, user)\n\n      all_casa_admin = admin.build\n\n      expect(all_casa_admin).to be_instance_of(AllCasaAdmin)\n      expect(all_casa_admin).not_to be_persisted\n      expect(all_casa_admin).to have_attributes(\n        email: params[:all_casa_admin][:email],\n        password: \"12345678910\"\n      )\n    end\n  end\n\n  describe \"#create!\" do\n    it \"creates an AllCasaAdmin with the given params and sends an invite\" do\n      admin = CreateAllCasaAdminService.new(params, user)\n      admin.build\n\n      expect do\n        admin.create!\n      end.to change(AllCasaAdmin, :count).by(1)\n\n      casa_admin = AllCasaAdmin.last\n      expect(casa_admin.invited_by_id).to eq(user.id)\n      expect(casa_admin.invited_by_type).to eq(\"User\")\n      expect(casa_admin).to have_attributes(\n        email: params[:all_casa_admin][:email]\n      )\n    end\n\n    context \"when there are errors\" do\n      it \"does not create an AllCasaAdmin and returns the errors\" do\n        params = ActionController::Parameters.new(\n          {\n            all_casa_admin: {\n              email: \"invalid_email_format\"\n            }\n          }\n        ).permit!\n\n        admin = CreateAllCasaAdminService.new(params, user)\n        admin.build\n\n        expect do\n          admin.create!\n        end.to raise_error(ActiveRecord::RecordInvalid, /email is invalid/i)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/services/create_casa_admin_service_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe CreateCasaAdminService, type: :service do\n  let(:organization) { create(:casa_org) }\n  let(:user) { build(:user) }\n  let(:params) do\n    ActionController::Parameters.new(\n      {\n        casa_admin: {\n          email: \"casa_admin23@example.com\",\n          display_name: \"Bob Cat\",\n          phone_number: \"+16306149615\",\n          date_of_birth: Date.new(1990, 1, 1),\n          receive_reimbursement_email: \"1\",\n          monthly_learning_hours_report: \"1\"\n        }\n      }\n    ).permit!\n  end\n\n  describe \"#build\" do\n    it \"initializes a CasaAdmin with the given params\" do\n      admin = CreateCasaAdminService.new(organization, params, user)\n\n      casa_admin = admin.build\n\n      expect(casa_admin).to be_instance_of(CasaAdmin)\n      expect(casa_admin).not_to be_persisted\n      expect(casa_admin).to have_attributes(\n        display_name: params[:casa_admin][:display_name],\n        phone_number: params[:casa_admin][:phone_number],\n        email: params[:casa_admin][:email],\n        date_of_birth: params[:casa_admin][:date_of_birth],\n        receive_reimbursement_email: true,\n        monthly_learning_hours_report: true\n      )\n    end\n\n    it \"initializes a CasaAdmin with custom fields\" do\n      admin = CreateCasaAdminService.new(organization, params, user)\n\n      casa_admin = admin.build\n\n      expect(casa_admin).to have_attributes(\n        active: true,\n        casa_org_id: organization.id,\n        type: \"CasaAdmin\"\n      )\n      expect(casa_admin.password).to be_present\n    end\n  end\n\n  describe \"#create!\" do\n    it \"creates a CasaAdmin with the given params\" do\n      admin = CreateCasaAdminService.new(organization, params, user)\n      admin.build\n\n      expect do\n        admin.create!\n      end.to change(CasaAdmin, :count).by(1)\n\n      casa_admin = CasaAdmin.last\n      expect(casa_admin).to have_attributes(\n        display_name: params[:casa_admin][:display_name],\n        phone_number: params[:casa_admin][:phone_number],\n        email: params[:casa_admin][:email],\n        date_of_birth: params[:casa_admin][:date_of_birth],\n        receive_reimbursement_email: true,\n        monthly_learning_hours_report: true,\n        active: true,\n        casa_org_id: organization.id,\n        password: nil\n      )\n    end\n\n    it \"sends an invite from the user\" do\n      admin = CreateCasaAdminService.new(organization, params, user)\n      admin.build\n\n      casa_admin = admin.create!\n\n      expect(casa_admin.invited_by_id).to eq(user.id)\n      expect(casa_admin.invited_by_type).to eq(\"User\")\n    end\n\n    context \"when there are errors\" do\n      it \"does not create the CasaAdmin and returns the errors\" do\n        params = ActionController::Parameters.new(\n          {\n            casa_admin: {\n              email: \"invalid_email_format\",\n              display_name: \"Bob Cat\"\n            }\n          }\n        ).permit!\n\n        admin = CreateCasaAdminService.new(organization, params, user)\n        admin.build\n\n        expect do\n          admin.create!\n        end.to raise_error(ActiveRecord::RecordInvalid, /email is invalid/i)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/services/deployment/backfill_case_contact_started_metadata_service_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe Deployment::BackfillCaseContactStartedMetadataService do\n  let(:past) { Date.new(2020, 1, 1).in_time_zone }\n  let(:parsed_past) { past.as_json }\n\n  let(:present) { Date.new(2024, 1, 1).in_time_zone }\n  let(:parsed_present) { present.as_json }\n\n  before { travel_to present }\n\n  context \"when a case contact has status metadata\" do\n    let(:case_contact) { create(:case_contact) }\n\n    context \"when a case contact has status started metadata\" do\n      let!(:case_contact) { create(:case_contact, :started, created_at: past) }\n\n      it \"does not change metadata\" do\n        described_class.new.backfill_metadata\n\n        expect(case_contact.reload.metadata.dig(\"status\", \"started\")).to eq(parsed_past)\n      end\n    end\n\n    context \"when a case contact has other status metadata\" do\n      let!(:case_contact) {\n        create(:case_contact, created_at: past, metadata:\n        {\"status\" => {\"details\" => parsed_past}})\n      }\n\n      it \"does not change status details\" do\n        described_class.new.backfill_metadata\n\n        expect(case_contact.reload.metadata.dig(\"status\", \"started\")).to eq(parsed_past)\n      end\n\n      it \"sets status started\" do\n        described_class.new.backfill_metadata\n\n        expect(case_contact.reload.metadata.dig(\"status\", \"started\")).to eq(parsed_past)\n      end\n    end\n  end\n\n  context \"when a case contact has no metadata\" do\n    let!(:case_contact) { create(:case_contact, created_at: past, metadata: {}) }\n\n    it \"does not change metadata\" do\n      described_class.new.backfill_metadata\n\n      expect(case_contact.reload.metadata.dig(\"status\", \"started\")).to be_nil\n    end\n  end\nend\n"
  },
  {
    "path": "spec/services/emancipation_checklist_download_html_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe EmancipationChecklistDownloadHtml do\n  describe \"#call\" do\n    it \"renders the form correctly\" do\n      ec1_option_a = create(:emancipation_option, name: \"With friend\")\n      ec1_option_b = create(:emancipation_option, name: \"With relative\")\n      ec1 = create(:emancipation_category, name: \"Youth has housing\", emancipation_options: [ec1_option_a, ec1_option_b])\n\n      ec2 = create(:emancipation_category, name: \"Youth has completed a budget\")\n      create(:emancipation_category, name: \"Youth is employed\")\n\n      current_case = create(:casa_case, emancipation_categories: [ec1, ec2], emancipation_options: [ec1_option_a])\n      emancipation_form_data = EmancipationCategory.all\n\n      service = described_class.new(current_case, emancipation_form_data)\n\n      str = service.call\n\n      expect(str).to match \"With friend\"\n      expect(str).to match \"With relative\"\n      expect(str).to match \"Youth has housing\"\n      expect(str).to match \"Youth has completed a budget\"\n      expect(str).to match \"Youth is employed\"\n    end\n  end\nend\n"
  },
  {
    "path": "spec/services/emancipation_checklist_reminder_service_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe EmancipationChecklistReminderService do\n  include ActiveJob::TestHelper\n\n  let(:send_reminders) { described_class.new.send_reminders }\n\n  before do\n    travel_to Date.new(2022, 10, 1)\n  end\n\n  after do\n    clear_enqueued_jobs\n  end\n\n  context \"with only two eligible cases\" do\n    subject(:task) { described_class.new }\n\n    let!(:eligible_case1) { create(:case_assignment) }\n    let!(:eligible_case2) { create(:case_assignment) }\n    let!(:ineligible_case1) { create(:case_assignment, pre_transition: true) }\n    let!(:inactive_case) { create(:case_assignment, :inactive) }\n\n    it \"#initialize correctly captures the eligible cases\" do\n      expect(CasaCase.count).to eq(4)\n      expect(task.cases).not_to be_empty\n      expect(task.cases.length).to eq(2)\n    end\n  end\n\n  context \"volunteer with transition age youth case\" do\n    let!(:casa_case) { create(:casa_case, :with_one_case_assignment) }\n\n    it \"sends notification\" do\n      expect { send_reminders }.to change { casa_case.case_assignments.first.volunteer.notifications.count }.by(1)\n    end\n  end\n\n  context \"volunteer with multiple transition age youth cases\" do\n    let!(:volunteer) { create(:volunteer, :with_casa_cases) }\n\n    it \"sends notification for each case\" do\n      expect { send_reminders }.to change { volunteer.notifications.count }.by(2)\n    end\n  end\n\n  context \"volunteer without transition age youth case\" do\n    let!(:casa_case) { create(:casa_case, :with_one_case_assignment, birth_month_year_youth: 13.years.ago) }\n\n    it \"does not send notification\" do\n      expect { send_reminders }.not_to change { casa_case.case_assignments.first.volunteer.notifications.count }\n    end\n  end\n\n  context \"when the case assignment is inactive\" do\n    let!(:case_assignment) { create(:case_assignment, :inactive) }\n\n    it \"does not send notification\" do\n      expect { send_reminders }.not_to change { case_assignment.volunteer.notifications.count }\n    end\n  end\n\n  context \"when there are no case assignments\" do\n    it \"does not raise error\" do\n      expect { send_reminders }.not_to raise_error\n    end\n  end\nend\n"
  },
  {
    "path": "spec/services/failed_import_csv_service_spec.rb",
    "content": "require \"rails_helper\"\nrequire \"fileutils\"\nrequire \"csv\"\n\nRSpec.describe FailedImportCsvService do\n  let(:import_type) { \"casa_case\" }\n  let(:user) { create(:casa_admin) }\n  let(:csv_string) { \"case_number,birth_month_year_youth\\n12345,2001-04\\n\" }\n  let(:user_id_hash) { Digest::SHA256.hexdigest(user.id.to_s)[0..15] }\n  let(:csv_path) { Rails.root.join(\"tmp\", import_type, \"failed_rows_userid_#{user_id_hash}.csv\") }\n\n  subject(:service) { described_class.new(failed_rows: failed_rows, import_type: import_type, user: user) }\n\n  before { FileUtils.rm_f(csv_path) }\n  after { FileUtils.rm_f(csv_path) }\n\n  def create_file(content: csv_string, mtime: Time.current)\n    FileUtils.mkdir_p(File.dirname(csv_path))\n    File.write(csv_path, content)\n    File.utime(mtime.to_time, mtime.to_time, csv_path)\n  end\n\n  describe \"#store\" do\n    context \"when file is within size limit\" do\n      let(:failed_rows) { csv_string }\n\n      it \"writes the CSV content to the tmp file\" do\n        service.store\n\n        expect(File.exist?(csv_path)).to be true\n        expect(File.read(csv_path)).to eq csv_string\n      end\n    end\n\n    context \"when file exceeds size limit\" do\n      let(:failed_rows) { \"a\" * (described_class::MAX_FILE_SIZE_BYTES + 1) }\n\n      it \"logs a warning and stores a warning\" do\n        expect(Rails.logger).to receive(:warn).with(/CSV too large to save for user/)\n        service.store\n\n        expect(File.read(csv_path)).to match(/The file was too large to save/)\n      end\n    end\n  end\n\n  describe \"#read\" do\n    let(:failed_rows) { \"\" }\n\n    context \"when file exists and has not expired\" do\n      before { create_file }\n\n      it \"returns the contents\" do\n        expect(service.read).to eq csv_string\n      end\n    end\n\n    context \"when file is expired\" do\n      let(:failed_rows) { \"The failed import file has expired. Please upload a new CSV.\" }\n      before { create_file(mtime: 2.days.ago.to_time) }\n\n      it \"deletes the file and returns fallback message\" do\n        expect(File.exist?(csv_path)).to be true\n        expect(service.read).to include(\"The failed import file has expired\")\n        expect(File.exist?(csv_path)).to be false\n      end\n    end\n\n    context \"when file never existed\" do\n      it \"returns fallback message\" do\n        expect(service.read).to include(\"No failed import file found\")\n      end\n    end\n  end\n\n  describe \"#cleanup\" do\n    let(:failed_rows) { \"\" }\n\n    context \"when file exists\" do\n      before { create_file }\n\n      it \"removes the file\" do\n        expect(File.exist?(csv_path)).to be true\n        expect(Rails.logger).to receive(:info).with(/Removing old failed rows CSV/)\n        service.cleanup\n        expect(File.exist?(csv_path)).to be false\n      end\n    end\n\n    context \"when file does not exist\" do\n      it \"does nothing\" do\n        expect { service.cleanup }.not_to raise_error\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/services/fdf_inputs_service_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe FdfInputsService, type: :service do\n  describe \".clean\" do\n    context \"when the string is nil\" do\n      it \"returns nil\" do\n        expect(FdfInputsService.clean(\"\")).to be_nil\n        expect(FdfInputsService.clean(\"  \")).to be_nil\n        expect(FdfInputsService.clean(nil)).to be_nil\n      end\n    end\n\n    it \"returns the escaped string\" do\n      expect(FdfInputsService.clean(\"hello world\")).to eq(\"hello world\")\n      expect(FdfInputsService.clean(\"(test)\")).to eq(\"\\\\(test\\\\)\")\n      expect(FdfInputsService.clean(\"path\\\\to\\\\file\")).to eq(\"path\\\\\\\\to\\\\\\\\file\")\n      expect(FdfInputsService.clean(\"(a\\\\b)\")).to eq(\"\\\\(a\\\\\\\\b\\\\)\")\n      expect(FdfInputsService.clean(\"((a))\")).to eq(\"\\\\(\\\\(a\\\\)\\\\)\")\n      expect(FdfInputsService.clean(\"\\\\(test\\\\)\")).to eq(\"\\\\\\\\\\\\(test\\\\\\\\\\\\)\")\n    end\n  end\n\n  describe \"#write_to_file\" do\n    it \"calls PdfForms with the given arguments and returns a file\" do\n      inputs = {name: \"Bob Cat\"}\n      pdf_template_path = \"/path/to/template.pdf\"\n      basename = \"test_file\"\n\n      fake_pdf_forms = double(\"PdfForms\")\n      allow(PdfForms).to receive(:new).and_return(fake_pdf_forms)\n      allow(fake_pdf_forms).to receive(:fill_form_with_fdf)\n\n      service = FdfInputsService.new(\n        inputs: inputs,\n        pdf_template_path: pdf_template_path,\n        basename: basename\n      )\n\n      result = service.write_to_file\n\n      expect(fake_pdf_forms).to have_received(:fill_form_with_fdf) do |template, output_path, fdf_path, flatten|\n        expect(template).to eq(pdf_template_path)\n        expect(output_path).to be_a(String)\n        expect(File.exist?(output_path)).to be(true)\n        expect(fdf_path).to be_a(String)\n        expect(File.exist?(fdf_path)).to be(true)\n        expect(flatten).to eq(flatten: true)\n      end\n\n      expect(result).to be_a(Tempfile)\n      expect(File.exist?(result.path)).to be(true)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/services/followup_export_csv_service_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe FollowupExportCsvService do\n  subject { described_class.new(casa_case.casa_org) }\n\n  let!(:casa_case) { create(:casa_case) }\n  let!(:creator) { create(:user, display_name: \"Craig\") }\n  let!(:alice) { create(:volunteer, display_name: \"Alice\", casa_org: casa_case.casa_org) }\n  let!(:bob) { create(:volunteer, display_name: \"Bob\", casa_org: casa_case.casa_org) }\n  let!(:case_contact) { create(:case_contact, casa_case: casa_case) }\n  let!(:followup) { create(:followup, creator: creator, case_contact: case_contact, note: \"hello, this is the thing, \") }\n\n  before do\n    create(:case_assignment, casa_case: casa_case, volunteer: alice)\n    create(:case_assignment, casa_case: casa_case, volunteer: bob)\n  end\n\n  describe \"#perform\" do\n    it \"Exports case contact followup data\" do\n      results = subject.perform.split(\"\\n\")\n      expect(results.count).to eq(2)\n      expect(results[0].split(\",\")).to eq([\"Case Number\", \"Volunteer Name(s)\", \"Note Creator Name\", \"Note\"])\n      expect(results[1]).to eq %(#{case_contact.casa_case.case_number},Alice and Bob,Craig,\"#{followup.note}\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/services/followup_service_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe FollowupService do\n  describe \".create_followup\" do\n    let(:case_contact) { create(:case_contact) }\n    let(:creator) { create(:volunteer) }\n    let(:note) { \"This is a test note.\" }\n    let(:notification_double) { double(\"FollowupNotifier\") }\n\n    before do\n      allow(FollowupNotifier).to receive(:with).and_return(notification_double)\n      allow(notification_double).to receive(:deliver)\n    end\n\n    it \"successfully creates a followup and sends notification\" do\n      expect {\n        FollowupService.create_followup(case_contact, creator, note)\n      }.to change(Followup, :count).by(1)\n\n      followup = Followup.last\n      expect(followup.note).to eq(note)\n      expect(followup.creator).to eq(creator)\n      expect(followup.followupable).to eq(case_contact)\n\n      expect(FollowupNotifier).to have_received(:with).with(\n        followup: followup,\n        created_by: creator\n      )\n\n      expect(notification_double).to have_received(:deliver)\n    end\n\n    context \"when followup fails to save\" do\n      before do\n        allow_any_instance_of(Followup).to receive(:save).and_return(false)\n      end\n\n      it \"does not send a notification\" do\n        expect(FollowupService.create_followup(case_contact, creator, note)).to be_a_new(Followup)\n        expect(FollowupNotifier).not_to have_received(:with)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/services/inactive_messages_service_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe InactiveMessagesService do\n  describe \"#inactive_messages\" do\n    subject { described_class.new(supervisor).inactive_messages }\n\n    let(:supervisor) { create :supervisor }\n\n    it \"has messages\" do\n      v1 = create(:supervisor_volunteer, supervisor: supervisor).volunteer\n      create(:case_assignment, :inactive, volunteer: v1, casa_case: create(:casa_case, case_number: \"ABC\"))\n      create(:case_assignment, :inactive, volunteer: v1, casa_case: create(:casa_case, case_number: \"DEF\"))\n      create(:case_assignment, volunteer: v1, casa_case: create(:casa_case, case_number: \"active-case\"))\n      create(:supervisor_volunteer, supervisor: supervisor)\n      expect(subject.count).to eq(2)\n      expect(subject.first).to match(/Case .* marked inactive this week./)\n      expect(subject.first).to match(/Case .* marked inactive this week./)\n    end\n\n    it \"has no messages\" do\n      expect(subject).to eq([])\n    end\n  end\nend\n"
  },
  {
    "path": "spec/services/learning_hours_export_csv_service_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe LearningHoursExportCsvService do\n  let!(:user) { create(:user) }\n  let!(:learning_hour_type) { create(:learning_hour_type) }\n  let!(:learning_hour) do\n    create(:learning_hour,\n      duration_hours: 2,\n      duration_minutes: 30,\n      occurred_at: \"2022-06-20\",\n      learning_hour_type: learning_hour_type)\n  end\n\n  describe \"#perform\" do\n    let(:result) { described_class.new(LearningHour.all).perform }\n\n    it \"returns a csv as a string starting with the learning hours headers\" do\n      csv_headers = \"Volunteer Name,Learning Hours Title,Learning Hours Type,Duration,Date Of Learning\\n\"\n\n      expect(result).to start_with(csv_headers)\n    end\n\n    it \"returns a csv as a string ending with the learning hours values\" do\n      csv_values = \"#{user.display_name},#{learning_hour.name},#{learning_hour.learning_hour_type.name},\" \\\n        \"2:30,2022-06-20\\n\"\n\n      if learning_hour.name.include? \",\"\n        csv_values.gsub!(learning_hour.name, '\"' + learning_hour.name + '\"')\n      end\n\n      expect(result).to end_with(csv_values)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/services/mileage_export_csv_service_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe MileageExportCsvService do\n  subject { described_class.new(case_contacts).perform }\n\n  let(:case_contacts) { CaseContact.where(id: case_contact) }\n  let(:case_contact) { create(:case_contact) }\n\n  it \"creates CSV\" do\n    results = subject.split(\"\\n\")\n    expect(results.count).to eq(2)\n    expect(results[0].split(\",\")).to eq([\n      \"Contact Types\",\n      \"Occurred At\",\n      \"Miles Driven\",\n      \"Casa Case Number\",\n      \"Creator Name\",\n      \"Supervisor Name\",\n      \"Volunteer Address\",\n      \"Reimbursed\"\n    ])\n    expect(results[1].split(\",\").count).to eq(9)\n  end\nend\n"
  },
  {
    "path": "spec/services/missing_data_export_csv_service_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe MissingDataExportCsvService do\n  let!(:casa_cases) { create_list(:casa_case, 3) }\n  let(:result) { described_class.new(CasaCase.all).perform }\n\n  describe \"#perform\" do\n    it \"returns a string formatted as csv\" do\n      expect(result).to match(\"Casa Case Number,Youth Birth Month And Year,Upcoming Hearing Date,Court Orders\\n\")\n      expect(result).to match(\"#{casa_cases[0].case_number},OK,MISSING,MISSING\\n\")\n      expect(result).to match(\"#{casa_cases[1].case_number},OK,MISSING,MISSING\\n\")\n      expect(result).to match(\"#{casa_cases[2].case_number},OK,MISSING,MISSING\\n\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/services/no_contact_made_sms_reminder_service_spec.rb",
    "content": "require \"rails_helper\"\nrequire \"support/stubbed_requests/webmock_helper\"\n\nRSpec.describe NoContactMadeSmsReminderService do\n  include SmsBodyHelper\n\n  describe \"court report due sms reminder service\" do\n    let(:org) { create(:casa_org, twilio_enabled: true) }\n    let!(:volunteer) { create(:volunteer, receive_sms_notifications: true, phone_number: \"+12222222222\", casa_org: org) }\n    let!(:contact_type) { \"test\" }\n\n    before do\n      WebMockHelper.short_io_stub_localhost\n      WebMockHelper.twilio_no_contact_made_stub\n    end\n\n    context \"when sending sms reminder\" do\n      it \"sends a SMS with a short url successfully\" do\n        response = NoContactMadeSmsReminderService.no_contact_made_reminder(volunteer, contact_type)\n\n        expect(response.error_code).to match nil\n        expect(response.status).to match \"sent\"\n        expect(response.body).to match no_contact_made_msg(contact_type, \"https://42ni.short.gy/jzTwdF\")\n      end\n    end\n\n    context \"when volunteer is not opted into sms notifications\" do\n      let(:volunteer) { create(:volunteer, receive_sms_notifications: false) }\n\n      it \"does not send a SMS\" do\n        response = NoContactMadeSmsReminderService.no_contact_made_reminder(volunteer, contact_type)\n        expect(response).to be_nil\n      end\n    end\n\n    context \"when volunteer does not have a valid phone number\" do\n      let(:volunteer) { create(:volunteer, phone_number: nil) }\n\n      it \"does not send a SMS\" do\n        response = NoContactMadeSmsReminderService.no_contact_made_reminder(volunteer, contact_type)\n        expect(response).to be_nil\n      end\n    end\n\n    context \"when volunteer's casa_org does not have twilio enabled\" do\n      let(:casa_org) { create(:casa_org, twilio_enabled: false) }\n      let(:volunteer) { create(:volunteer, casa_org: casa_org) }\n\n      it \"does not send a SMS\" do\n        response = NoContactMadeSmsReminderService.no_contact_made_reminder(volunteer, contact_type)\n        expect(response).to be_nil\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/services/placement_export_csv_service_spec.rb",
    "content": "require \"rails_helper\"\nrequire \"factory_bot_rails\"\n\nRSpec.describe PlacementExportCsvService do\n  it \"creates a Placements CSV with placement headers\" do\n    casa_org = create(:casa_org, name: \"Fake Name\", display_name: \"Fake Display Name\")\n    placement_type = create(:placement_type, casa_org: casa_org)\n    creator = create(:user)\n    create(:placement, creator: creator, placement_type: placement_type)\n\n    csv_headers = \"Casa Org,Casa Case Number,Placement Type,Placement Started At,Created At,Creator Name\\n\"\n    result = PlacementExportCsvService.new(casa_org: casa_org).perform\n    expect(result).to start_with(csv_headers)\n  end\nend\n"
  },
  {
    "path": "spec/services/preference_set_table_state_service_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe PreferenceSetTableStateService do\n  subject { described_class.new(user_id: user.id) }\n\n  let!(:user) { create(:user) }\n  let(:preference_set) { user.preference_set }\n  let!(:table_state) { {\"volunteers_table\" => {\"columns\" => [{\"visible\" => true}]}} }\n  let(:table_state2) { {\"columns\" => [{\"visible\" => false}]} }\n  let(:table_name) { \"volunteers_table\" }\n\n  describe \"#update!\" do\n    context \"when the update is successful\" do\n      it \"updates the table state\" do\n        expect {\n          subject.update!(table_state: table_state2, table_name: table_name)\n        }.to change {\n          preference_set.reload.table_state\n        }.from({}).to({table_name => table_state2})\n      end\n    end\n\n    context \"when the update fails\" do\n      before do\n        allow_any_instance_of(PreferenceSet).to receive(:save!).and_raise(ActiveRecord::RecordNotSaved)\n      end\n\n      it \"raises an error\" do\n        expect {\n          subject.update!(table_state: table_state2, table_name: table_name)\n        }.to raise_error(PreferenceSetTableStateService::TableStateUpdateFailed, \"Failed to update table state for '#{table_name}'\")\n      end\n    end\n  end\n\n  describe \"#table_state\" do\n    context \"when the preference set exists\" do\n      before do\n        table_state = {\"columns\" => [{\"visible\" => true}]}\n        user.preference_set.table_state[\"volunteers_table\"] = table_state\n        user.preference_set.save!\n      end\n\n      it \"returns the table state\" do\n        expect(subject.table_state(table_name: \"volunteers_table\")).to eq(table_state[\"volunteers_table\"])\n      end\n    end\n\n    context \"when there is no data for that table name\" do\n      before do\n        preference_set.table_state = {}\n        preference_set.save\n      end\n\n      it \"returns nil\" do\n        expect(subject.table_state(table_name: table_name)).to eq(nil)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/services/short_url_service_spec.rb",
    "content": "require \"rails_helper\"\nrequire \"support/stubbed_requests/webmock_helper\"\n\nRSpec.describe ShortUrlService do\n  let!(:original_url) { \"https://www.google.com\" }\n  let!(:notification_object) { ShortUrlService.new }\n  let!(:short_io_domain) { Rails.application.credentials[:SHORT_IO_DOMAIN] }\n\n  describe \"short.io API\" do\n    before do\n      WebMockHelper.short_io_stub\n    end\n\n    it \"returns a successful response with correct http request\" do\n      response = notification_object.create_short_url(original_url)\n      expect(a_request(:post, \"https://api.short.io/links\")\n        .with(body: {originalURL: original_url, domain: short_io_domain}.to_json, headers: {\"Accept\" => \"application/json\", \"Content-Type\" => \"application/json\", \"Authorization\" => \"1337\"}))\n        .to have_been_made.once\n      expect(response.code).to match 200\n      expect(response.body).to match \"{\\\"shortURL\\\":\\\"https://42ni.short.gy/jzTwdF\\\"}\"\n    end\n\n    it \"returns a short url\" do\n      notification_object.create_short_url(original_url)\n      short_url = notification_object.short_url\n      expect(short_url).to be_an_instance_of(String)\n      expect(short_url).to match \"https://42ni.short.gy/jzTwdF\"\n    end\n  end\nend\n"
  },
  {
    "path": "spec/services/sms_reminder_service_spec.rb",
    "content": "require \"rails_helper\"\nrequire \"support/stubbed_requests/webmock_helper\"\n\nRSpec.describe SmsReminderService do\n  describe \"court report due sms reminder service\" do\n    let(:org) { create(:casa_org, twilio_enabled: true) }\n    let!(:volunteer) { create(:volunteer, casa_org: org, receive_sms_notifications: true, phone_number: \"+12222222222\") }\n    let!(:message) { \"It's been two weeks since you've tried reaching 'test'. Try again! https://42ni.short.gy/jzTwdF\" }\n\n    before do\n      WebMockHelper.short_io_stub_localhost\n      WebMockHelper.twilio_no_contact_made_stub\n    end\n\n    context \"when sending sms reminder\" do\n      it \"sends a SMS with a short url successfully\" do\n        response = SmsReminderService.send_reminder(volunteer, message)\n\n        expect(response.error_code).to match nil\n        expect(response.status).to match \"sent\"\n        expect(response.body).to match \"It's been two weeks since you've tried reaching 'test'. Try again! https://42ni.short.gy/jzTwdF\"\n      end\n    end\n\n    context \"when volunteer is not opted into sms notifications\" do\n      let(:volunteer) { create(:volunteer, receive_sms_notifications: false) }\n\n      it \"does not send a SMS\" do\n        response = SmsReminderService.send_reminder(volunteer, message)\n        expect(response).to be_nil\n      end\n    end\n\n    context \"when volunteer does not have a valid phone number\" do\n      let(:volunteer) { create(:volunteer, phone_number: nil) }\n\n      it \"does not send a SMS\" do\n        response = SmsReminderService.send_reminder(volunteer, message)\n        expect(response).to be_nil\n      end\n    end\n\n    context \"when a volunteer's casa_org does not have twilio enabled\" do\n      let(:casa_org_twilio_disabled) { create(:casa_org, twilio_enabled: false) }\n      let(:volunteer_twilio_disabled) { create(:volunteer, casa_org: casa_org_twilio_disabled) }\n\n      it \"does not send a SMS\" do\n        response = SmsReminderService.send_reminder(volunteer_twilio_disabled, message)\n        expect(response).to be_nil\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/services/svg_sanitizer_service_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe SvgSanitizerService do\n  let(:result) { described_class.sanitize(file) }\n\n  describe \"when receiving a svg file\" do\n    let(:file) { fixture_file_upload(\"unsafe_svg.svg\", \"image/svg+xml\") }\n\n    it \"removes script tags\" do\n      expect(result.read).not_to match(\"script\")\n    end\n  end\n\n  describe \"when not receiving a svg file\" do\n    let(:file) { fixture_file_upload(\"company_logo.png\", \"image/png\") }\n\n    it \"returns the file without changes\" do\n      expect(result).to eq(file)\n    end\n  end\n\n  describe \"when receiving nil\" do\n    let(:file) { nil }\n\n    it \"returns nil\" do\n      expect(result).to be_nil\n    end\n  end\nend\n"
  },
  {
    "path": "spec/services/twilio_service_spec.rb",
    "content": "require \"rails_helper\"\nrequire \"support/stubbed_requests/webmock_helper\"\n\nRSpec.describe TwilioService do\n  describe \"twilio API\" do\n    context \"SMS messaging\" do\n      let(:short_url) { ShortUrlService.new }\n      let!(:casa_org) do\n        create(\n          :casa_org,\n          twilio_phone_number: \"+15555555555\",\n          twilio_account_sid: \"articuno34\",\n          twilio_api_key_sid: \"Aladdin\",\n          twilio_api_key_secret: \"open sesame\",\n          twilio_enabled: true\n        )\n      end\n\n      before do\n        WebMockHelper.short_io_stub_sms\n        WebMockHelper.twilio_success_stub\n      end\n\n      it \"can send a SMS with a short url successfully\" do\n        twilio = TwilioService.new(casa_org)\n        short_url.create_short_url(\"https://www.google.com\")\n        params = {\n          From: \"+15555555555\",\n          Body: \"Execute Order 66 - \",\n          To: \"+12222222222\",\n          URL: short_url.short_url\n        }\n\n        # response is a Twilio API obj\n        response = twilio.send_sms(params)\n        expect(response.error_code).to match nil\n        expect(response.status).to match \"sent\"\n        expect(response.body).to match \"Execute Order 66 - https://42ni.short.gy/jzTwdF\"\n      end\n    end\n\n    context \"when twilio is disabled\" do\n      let!(:casa_org_twilio_disabled) do\n        create(\n          :casa_org,\n          twilio_phone_number: \"+15555555553\",\n          twilio_account_sid: \"zapdos43\",\n          twilio_api_key_sid: \"Jasmine\",\n          twilio_api_key_secret: \"hakuna matata\",\n          twilio_enabled: false\n        )\n      end\n\n      before do\n        WebMockHelper.short_io_stub_sms\n        WebMockHelper.twilio_success_stub\n      end\n\n      it \"returns nil\" do\n        short_url = ShortUrlService.new\n        twilio = TwilioService.new(casa_org_twilio_disabled)\n        short_url.create_short_url(\"https://www.google.com\")\n        params = {\n          From: \"+15555555555\",\n          Body: \"Execute Order 66 - \",\n          To: \"+12222222222\",\n          URL: short_url.short_url\n        }\n        response = twilio.send_sms(params)\n        expect(response).to eq nil\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/services/volunteer_birthday_reminder_service_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe VolunteerBirthdayReminderService do\n  include ActiveJob::TestHelper\n\n  let(:send_reminders) { described_class.new.send_reminders }\n  let(:now) { Date.new(2022, 10, 15) }\n  let(:this_month) { now.month }\n  let(:this_month_15th) { Date.new(now.year, now.month, 15) }\n  let(:next_month) { Date.new(1988, this_month + 1, 1) }\n  let(:not_next_month) { Date.new(1998, this_month - 1, 1) }\n\n  before do\n    travel_to now\n  end\n\n  after do\n    clear_enqueued_jobs\n  end\n\n  context \"there is a volunteer with a birthday next month\" do\n    let!(:volunteer) do\n      create(:volunteer, :with_assigned_supervisor, date_of_birth: next_month)\n    end\n\n    it \"creates a notification\" do\n      expect { send_reminders }.to change { volunteer.supervisor.notifications.count }.by(1)\n    end\n  end\n\n  context \"there are multiple volunteers with birthdays next month\" do\n    let(:supervisor) { create(:supervisor) }\n    let!(:volunteer) do\n      create_list(:volunteer, 4, :with_assigned_supervisor, date_of_birth: next_month, supervisor: supervisor)\n    end\n\n    it \"creates multiple notifications\" do\n      expect { send_reminders }.to change { Noticed::Notification.count }.by(4)\n    end\n  end\n\n  context \"there is an unsupervised volunteer with a birthday next month\" do\n    let!(:volunteer) do\n      create(:volunteer, date_of_birth: next_month)\n    end\n\n    it \"does not create a notification\" do\n      expect { send_reminders }.not_to change { Noticed::Notification.count }\n    end\n  end\n\n  context \"there is a volunteer with no date_of_birth\" do\n    let!(:volunteer) do\n      create(:volunteer, :with_assigned_supervisor, date_of_birth: nil)\n    end\n\n    it \"does not create a notification\" do\n      expect { send_reminders }.not_to change { volunteer.supervisor.notifications.count }\n    end\n  end\n\n  context \"there is a volunteer with a birthday that is not next month\" do\n    let!(:volunteer) do\n      create(:volunteer, :with_assigned_supervisor, date_of_birth: not_next_month)\n    end\n\n    it \"does not create a notification\" do\n      expect { send_reminders }.not_to change { volunteer.supervisor.notifications.count }\n    end\n  end\n\n  context \"when today is the 15th\" do\n    before { travel_to(this_month_15th) }\n\n    let!(:volunteer) do\n      create(:volunteer, :with_assigned_supervisor, date_of_birth: next_month)\n    end\n\n    it \"runs the rake task\" do\n      expect { send_reminders }.to change { volunteer.supervisor.notifications.count }.by(1)\n    end\n  end\n\n  context \"when today is not the 15th\" do\n    before { travel_to(this_month_15th + 2.days) }\n\n    it \"skips the rake task\" do\n      expect { send_reminders }.not_to change { Noticed::Notification.count }\n    end\n  end\nend\n"
  },
  {
    "path": "spec/services/volunteers_emails_export_csv_service_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe VolunteersEmailsExportCsvService do\n  describe \"#call\" do\n    it \"Exports correct data from volunteers\" do\n      casa_org = create(:casa_org)\n      other_casa_org = create(:casa_org)\n\n      active_volunteer = create(:volunteer, :with_casa_cases, casa_org: casa_org)\n      inactive_volunteer = create(:volunteer, :inactive, casa_org: casa_org)\n      other_org_volunteer = create(:volunteer, casa_org: other_casa_org)\n      active_volunteer_cases = active_volunteer.casa_cases.active.map { |c| [c.case_number, c.in_transition_age?] }.to_h\n\n      csv = described_class.new(casa_org).call\n\n      results = csv.split(\"\\n\")\n      expect(results.count).to eq(2)\n      expect(results[0].split(\",\")).to eq([\"Email\", \"Old Emails\", \"Case Number\", \"Volunteer Name\", \"Case Transition Aged Status\"])\n      expect(results[1]).to eq(\"#{active_volunteer.email},No Old Emails,\\\"#{active_volunteer_cases.keys.join(\", \")}\\\",#{active_volunteer.display_name},\\\"#{active_volunteer_cases.values.join(\", \")}\\\"\")\n      expect(csv).to match(/#{active_volunteer.email}/)\n      expect(csv).not_to match(/#{inactive_volunteer.email}/)\n      expect(csv).not_to match(/#{other_org_volunteer.email}/)\n    end\n\n    it \"Exports correct data from volunteers, including old emails\" do\n      casa_org_2 = create(:casa_org)\n      volunteer_with_old_emails = create(:volunteer, old_emails: [\"old_email@example.com\"], casa_org: casa_org_2)\n      csv = described_class.new(casa_org_2).call\n\n      results = csv.split(\"\\n\")\n      expect(results.count).to eq(2)\n      expect(results[0].split(\",\")).to eq([\"Email\", \"Old Emails\", \"Case Number\", \"Volunteer Name\", \"Case Transition Aged Status\"])\n      expect(results[1]).to eq(\"#{volunteer_with_old_emails.email},#{volunteer_with_old_emails.old_emails.join(\", \")},\\\"\\\",#{volunteer_with_old_emails.display_name},\\\"\\\"\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/spec_helper.rb",
    "content": "# This file was generated by the `rails generate rspec:install` command. Conventionally, all\n# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.\n# The generated `.rspec` file contains `--require spec_helper` which will cause\n# this file to always be loaded, without a need to explicitly require it in any\n# files.\n#\n# Given that it is always loaded, you are encouraged to keep this file as\n# light-weight as possible. Requiring heavyweight dependencies from this file\n# will add to the boot time of your test suite on EVERY test run, even for an\n# individual file that may not need all of that loaded. Instead, consider making\n# a separate helper file that requires the additional dependencies and performs\n# the additional setup, and require it from the spec files that actually need\n# it.\n\nif ENV[\"RUN_SIMPLECOV\"]\n  require \"simplecov\"\n  SimpleCov.start do\n    command_name \"Job #{ENV[\"TEST_ENV_NUMBER\"]}\" if ENV[\"TEST_ENV_NUMBER\"]\n\n    add_filter \"/spec/\"\n    add_filter \"/lib/tasks/auto_annotate_models.rake\"\n    add_group \"Models\", \"/app/models\"\n    add_group \"Controllers\", \"/app/controllers\"\n    add_group \"Channels\", \"/app/channels\"\n    add_group \"Decorators\", \"/app/decorators\"\n    add_group \"Helpers\", \"/app/helpers\"\n    add_group \"Jobs\", \"/app/jobs\"\n    add_group \"Importers\", \"/app/lib/importers\"\n    add_group \"Mailers\", \"/app/mailers\"\n    add_group \"Policies\", \"/app/policies\"\n    add_group \"Values\", \"/app/values\"\n    add_group \"Tasks\", \"/lib/tasks\"\n    add_group \"Config\", \"/config\"\n    add_group \"Database\", \"/db\"\n  end\n  # https://github.com/simplecov-ruby/simplecov?tab=readme-ov-file#want-to-use-spring-with-simplecov\n  # Rails.application.eager_load!\nend\n\n# See https://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration\nRSpec.configure do |config|\n  # rspec-expectations config goes here. You can use an alternate\n  # assertion/expectation library such as wrong or the stdlib/minitest\n  # assertions if you prefer.\n  config.expect_with :rspec do |expectations|\n    # This option will default to `true` in RSpec 4. It makes the `description`\n    # and `failure_message` of custom matchers include text for helper methods\n    # defined using `chain`, e.g.:\n    #     be_bigger_than(2).and_smaller_than(4).description\n    #     # => \"be bigger than 2 and smaller than 4\"\n    # ...rather than:\n    #     # => \"be bigger than 2\"\n    expectations.include_chain_clauses_in_custom_matcher_descriptions = true\n  end\n\n  # rspec-mocks config goes here. You can use an alternate test double\n  # library (such as bogus or mocha) by changing the `mock_with` option here.\n  config.mock_with :rspec do |mocks|\n    # Prevents you from mocking or stubbing a method that does not exist on\n    # a real object. This is generally recommended, and will default to\n    # `true` in RSpec 4.\n    mocks.verify_partial_doubles = true\n  end\n\n  # This option will default to `:apply_to_host_groups` in RSpec 4 (and will\n  # have no way to turn it off -- the option exists only for backwards\n  # compatibility in RSpec 3). It causes shared context metadata to be\n  # inherited by the metadata hash of host groups and examples, rather than\n  # triggering implicit auto-inclusion in groups with matching metadata.\n  config.shared_context_metadata_behavior = :apply_to_host_groups\n\n  # The settings below are suggested to provide a good initial experience\n  # with RSpec, but feel free to customize to your heart's content.\n\n  # This allows you to limit a spec run to individual examples or groups\n  # you care about by tagging them with `:focus` metadata. When nothing\n  # is tagged with `:focus`, all examples get run. RSpec also provides\n  # aliases for `it`, `describe`, and `context` that include `:focus`\n  # metadata: `fit`, `fdescribe` and `fcontext`, respectively.\n  config.filter_run_when_matching :focus\n\n  # Allows RSpec to persist some state between runs in order to support\n  # the `--only-failures` and `--next-failure` CLI options. We recommend\n  # you configure your source control system to ignore this file.\n  config.example_status_persistence_file_path = \"tmp/persistent_examples.txt\"\n\n  # Limits the available syntax to the non-monkey patched syntax that is\n  # recommended. For more details, see:\n  # https://rspec.info/features/3-12/rspec-core/configuration/zero-monkey-patching-mode/\n  config.disable_monkey_patching!\n\n  # Many RSpec users commonly either run the entire suite or an individual\n  # file, and it's useful to allow more verbose output when running an\n  # individual spec file.\n  if config.files_to_run.one?\n    # Use the documentation formatter for detailed output,\n    # unless a formatter has already been configured\n    # (e.g. via a command-line flag).\n    config.default_formatter = \"doc\"\n  end\n\n  # Print the 10 slowest examples and example groups at the\n  # end of the spec run, to help surface which specs are running\n  # particularly slow.\n  # config.profile_examples = 10\n\n  # Run specs in random order to surface order dependencies. If you find an\n  # order dependency and want to debug it, you can fix the order by providing\n  # the seed, which is printed after each run.\n  #     --seed 1234\n  config.order = :random\n\n  # Seed global randomization in this process using the `--seed` CLI option.\n  # Setting this allows you to use `--seed` to deterministically reproduce\n  # test failures related to randomization by passing the same `--seed` value\n  # as the one that triggered the failure.\n  Kernel.srand config.seed\nend\n"
  },
  {
    "path": "spec/support/api_helper.rb",
    "content": "module ApiHelper\n  include Rack::Test::Methods\n\n  def app\n    Rails.application\n  end\nend\n\nRSpec.configure do |config|\n  config.include ApiHelper, type: :api # apply to all spec for apis folder\nend\n"
  },
  {
    "path": "spec/support/capybara.rb",
    "content": "require \"capybara/rails\"\nrequire \"capybara/rspec\"\nrequire \"capybara-screenshot/rspec\"\nrequire \"selenium/webdriver\"\n\nCapybara.default_max_wait_time = ENV.fetch(\"CAPYBARA_WAIT_TIME\", \"10\").to_i\n\n# not used unless you swap it out for selenium_chrome_headless_in_container to watch tests running in docker\nCapybara.register_driver :selenium_chrome_in_container do |app|\n  Capybara::Selenium::Driver.new app,\n    browser: :remote,\n    url: \"http://selenium_chrome:4444/wd/hub\",\n    capabilities: [:chrome]\nend\n\n# disable CSS transitions and js animations\nCapybara.disable_animation = true\n\nCapybara::Screenshot.autosave_on_failure = true\nCapybara.save_path = Rails.root.join(\"tmp\", \"screenshots#{ENV[\"GROUPS_UNDERSCORE\"]}\")\n\noptions = Selenium::WebDriver::Chrome::Options.new\noptions.add_argument(\"--disable-gpu\")\noptions.add_argument(\"--ignore-certificate-errors\")\noptions.add_argument(\"--window-size=1280,1900\")\n\noptions.add_preference(:browser, set_download_behavior: {behavior: \"allow\"})\n\n# used in docker\nCapybara.register_driver :selenium_chrome_headless_in_container do |app|\n  options.add_argument(\"--headless\")\n  options.add_preference(:download, prompt_for_download: false, default_directory: \"/home/seluser/Downloads\")\n  options.add_argument(\"--disable-dev-shm-usage\")\n\n  Capybara::Selenium::Driver.new app,\n    browser: :remote,\n    url: \"http://selenium_chrome:4444/wd/hub\",\n    options: options\nend\n\n# used without docker\nCapybara.register_driver :selenium_chrome_headless do |app|\n  options.add_argument(\"--headless\")\n  options.add_argument(\"--disable-site-isolation-trials\")\n  options.add_preference(:download, prompt_for_download: false, default_directory: DownloadHelpers::PATH.to_s)\n\n  Capybara::Selenium::Driver.new app,\n    browser: :chrome,\n    options: options\nend\n\nRSpec.configure do |config|\n  config.before(:each, type: :system) do\n    driven_by :rack_test\n  end\n\n  config.before(:each, :js, type: :system) do\n    config.include DownloadHelpers\n    clear_downloads\n    if ENV[\"DOCKER\"]\n      driven_by :selenium_chrome_headless_in_container\n      Capybara.server_host = \"0.0.0.0\"\n      Capybara.server_port = 4000\n      Capybara.app_host = \"http://web:4000\"\n    else\n      driven_by :selenium_chrome_headless\n    end\n  end\n\n  config.before(:each, :debug, type: :system) do\n    config.include DownloadHelpers\n    clear_downloads\n    if ENV[\"DOCKER\"]\n      driven_by :selenium_chrome_in_container\n      Capybara.server_host = \"0.0.0.0\"\n      Capybara.server_port = 4000\n      Capybara.app_host = \"http://web:4000\"\n    else\n      driven_by :selenium_chrome\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/case_court_report_helpers.rb",
    "content": "# frozen_string_literal: true\n\n# Helper methods for case court reports system specs\nmodule CaseCourtReportHelpers\n  # Finds the 'Generate Report' button, clicks it, and confirms the modal is visible.\n  # Assumes the user is already on the case_court_reports_path.\n  def open_court_report_modal\n    find('[data-bs-target=\"#generate-docx-report-modal\"]').click\n    expect(page).to have_selector(\"#generate-docx-report-modal\", visible: :visible)\n  end\n\n  # Opens the Select2 dropdown within the (already open) report modal.\n  # Confirms the dropdown options are visible.\n  def open_case_select2_dropdown\n    # Wait for the Select2 container to be visible\n    expect(page).to have_css(\"#case_select_body .selection\", visible: :visible)\n\n    # Click the container to open the dropdown\n    find(\"#case_select_body .selection\").click\n\n    # Wait for the dropdown to appear\n    expect(page).to have_css(\".select2-dropdown\", visible: :visible)\n  end\nend\n"
  },
  {
    "path": "spec/support/datatable_helper.rb",
    "content": "module DatatableHelper\n  def datatable_params(order_by:, additional_filters: {}, order_direction: \"ASC\", page: nil, per_page: nil, search_term: nil)\n    if page.present?\n      raise \":per_page argument required when :page present\" if per_page.blank?\n\n      start = [page - 1, 0].max * per_page\n    end\n\n    {\n      additional_filters: additional_filters,\n      columns: {\"0\" => {name: order_by}},\n      length: per_page,\n      order: {\"0\" => {column: \"0\", dir: order_direction}},\n      search: {value: search_term},\n      start: start\n    }\n  end\n\n  def described_class\n    class_name = self.class.name.split(\"::\")[2]\n    class_name.constantize\n  rescue NameError\n    # TODO warning log to bugsnag here ?\n  end\n\n  def escaped(value)\n    ERB::Util.html_escape value\n  end\nend\n"
  },
  {
    "path": "spec/support/download_helpers.rb",
    "content": "module DownloadHelpers\n  TIMEOUT = 10\n  PATH = Rails.root.join(\"tmp/downloads#{ENV[\"TEST_ENV_NUMBER\"]}\")\n\n  def downloads\n    Dir[PATH.join(\"*\")]\n  end\n\n  def download\n    downloads.first\n  end\n\n  def download_content\n    wait_for_download\n    File.read(download)\n  end\n\n  def download_docx\n    wait_for_download\n    Docx::Document.open(download)\n  end\n\n  def header_text(download_docx)\n    zip = download_docx.zip\n    files = zip.glob(\"word/header*.xml\").map { |h| h.name }\n    filename_and_contents_pairs = files.map do |file|\n      simple_file_name = file.sub(/^word\\//, \"\").sub(/\\.xml$/, \"\")\n      [simple_file_name, Nokogiri::XML(zip.read(file))]\n    end\n\n    filename_and_contents_pairs.map { |name, doc| doc.text }.join(\"\\n\")\n  end\n\n  def table_text(download_docx)\n    download_docx.tables.map { |t| t.rows.map(&:cells).flatten.map(&:to_s) }.flatten\n  end\n\n  def download_file_name\n    File.basename(download)\n  end\n\n  def wait_for_download\n    Timeout.timeout(TIMEOUT) do\n      sleep 0.1 until downloaded?\n    end\n  end\n\n  def downloaded?\n    !downloading? && downloads.any?\n  end\n\n  def downloading?\n    downloads.grep(/\\.crdownload$/).any?\n  end\n\n  def clear_downloads\n    FileUtils.rm_f(downloads)\n  end\nend\n"
  },
  {
    "path": "spec/support/factory_bot.rb",
    "content": "# frozen_string_literal: true\n\ndef described_class_factory\n  described_class.name.gsub(\"::\", \"\").underscore\nend\n\nRSpec.configure do |config|\n  config.include FactoryBot::Syntax::Methods\n\n  # Any factory that takes more than .5 seconds to create will show in the\n  # console when running the tests.\n  config.before(:suite) do\n    ActiveSupport::Notifications.subscribe(\"factory_bot.run_factory\") do |name, start, finish, id, payload|\n      execution_time_in_seconds = finish - start\n\n      if execution_time_in_seconds >= 0.5\n        Rails.logger.warn { \"Slow factory: #{payload[:name]} takes #{execution_time_in_seconds} seconds using strategy #{payload[:strategy]}\" }\n      end\n    end\n  end\n\n  # This will output records as they are created. Handy for debugging but very\n  # noisy.\n  # config.before(:each) do\n  #   ActiveSupport::Notifications.subscribe(\"factory_bot.run_factory\") do |name, start, finish, id, payload|\n  #     $stderr.puts \"FactoryBot: #{payload[:strategy]}(:#{payload[:name]})\"\n  #   end\n  # end\n\n  # This will output total database records being created.\n  if ENV.fetch(\"SPEC_OUTPUT_FACTORY_BOT_OBJECT_CREATION_STATS\", false)\n    factory_bot_results = {}\n    config.before(:suite) do\n      ActiveSupport::Notifications.subscribe(\"factory_bot.run_factory\") do |name, start, finish, id, payload|\n        factory_name = payload[:name]\n        strategy_name = payload[:strategy]\n        factory_bot_results[factory_name] ||= {}\n        factory_bot_results[factory_name][:total] ||= 0\n        factory_bot_results[factory_name][:total] += 1\n        factory_bot_results[factory_name][strategy_name] ||= 0\n        factory_bot_results[factory_name][strategy_name] += 1\n      end\n    end\n\n    config.after(:suite) do\n      puts \"How many objects did factory_bot create? (probably too many- let's tune some factories...)\"\n      pp factory_bot_results\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/fill_in_case_contact_fields.rb",
    "content": "module FillInCaseContactFields\n  DETAILS_ID = \"#contact-form-details\"\n  NOTES_ID = \"#contact-form-notes\"\n  TOPIC_VALUE_CLASS = \".contact-topic-answer-input\"\n  TOPIC_SELECT_CLASS = \".contact-topic-id-select\"\n  REIMBURSEMENT_ID = \"#contact-form-reimbursement\"\n  EXPENSE_AMOUNT_CLASS = \".expense-amount-input\"\n  EXPENSE_DESCRIBE_CLASS = \".expense-describe-input\"\n\n  def fill_in_contact_details(case_numbers: [], contact_types: [], contact_made: true,\n    medium: \"In Person\", occurred_on: Time.zone.today, hours: nil, minutes: nil)\n    within DETAILS_ID do\n      within \"#draft-case-id-selector\" do\n        if case_numbers.nil?\n          all(:element, \"a\", title: \"Remove this item\").each(&:click)\n        end\n\n        if case_numbers.present?\n          find(\".ts-control\").click\n\n          Array.wrap(case_numbers).each_with_index do |case_number, index|\n            checkbox_for_case_number = first(\"span\", text: case_number).sibling(\"input\")\n            checkbox_for_case_number.click unless checkbox_for_case_number.checked?\n          end\n\n          find(\".ts-control\").click\n        end\n      end\n\n      fill_in \"case_contact_occurred_at\", with: occurred_on if occurred_on\n\n      if contact_types.present?\n        contact_types.each do |contact_type|\n          check contact_type\n        end\n      elsif !contact_types.nil?\n        contact_type = ContactType.first\n        check contact_type.name\n      end\n\n      choose medium if medium\n\n      if contact_made\n        check \"Contact was made\"\n      else\n        uncheck \"Contact was made\"\n      end\n\n      fill_in \"case_contact_duration_hours\", with: hours if hours\n      fill_in \"case_contact_duration_minutes\", with: minutes if minutes\n    end\n  end\n  alias_method :complete_details_page, :fill_in_contact_details\n\n  def fill_in_notes(notes: nil, contact_topic_answers_attrs: [])\n    within NOTES_ID do\n      Array.wrap(contact_topic_answers_attrs).each_with_index do |attributes, index|\n        click_on \"Add Another Discussion Topic\" if index > 0\n        answer_topic_unscoped attributes[:question], attributes[:answer]\n      end\n\n      if notes.present?\n        fill_in \"Additional Notes\", with: notes\n      end\n    end\n  end\n  alias_method :complete_notes_page, :fill_in_notes\n\n  # @param miles [Integer]\n  # @param want_reimbursement [Boolean]\n  # @param address [String]\n  def fill_in_mileage(miles: 0, want_reimbursement: false, address: nil)\n    within REIMBURSEMENT_ID do\n      check_reimbursement(want_reimbursement)\n      return unless want_reimbursement\n\n      fill_in \"case_contact_miles_driven\", with: miles if miles.present?\n      fill_in \"case_contact_volunteer_address\", with: address if address\n    end\n  end\n  alias_method :fill_in_expenses_page, :fill_in_mileage\n\n  def choose_medium(medium)\n    choose medium if medium\n  end\n\n  def check_reimbursement(want_reimbursement = true)\n    if want_reimbursement\n      check \"Request travel or other reimbursement\"\n    else\n      uncheck \"Request travel or other reimbursement\"\n    end\n  end\n\n  def fill_expense_fields(amount, describe, index: nil)\n    within REIMBURSEMENT_ID do\n      amount_field = index.present? ? all(EXPENSE_AMOUNT_CLASS)[index] : all(EXPENSE_AMOUNT_CLASS).last\n      describe_field = index.present? ? all(EXPENSE_DESCRIBE_CLASS)[index] : all(EXPENSE_DESCRIBE_CLASS).last\n      amount_field.fill_in(with: amount) if amount\n      describe_field.fill_in(with: describe) if describe\n    end\n  end\n\n  def answer_topic(question, answer, index: nil)\n    within NOTES_ID do\n      answer_topic_unscoped(question, answer, index:)\n    end\n  end\n\n  private\n\n  # use when already 'within' notes div\n  def answer_topic_unscoped(question, answer, index: nil)\n    topic_select = index.nil? ? all(TOPIC_SELECT_CLASS).last : all(TOPIC_SELECT_CLASS)[index]\n    answer_field = index.nil? ? all(TOPIC_VALUE_CLASS).last : all(TOPIC_VALUE_CLASS)[index]\n    topic_select.select(question) if question\n    answer_field.fill_in(with: answer) if answer\n  end\nend\n\nRSpec.configure do |config|\n  config.include FillInCaseContactFields, type: :system\nend\n"
  },
  {
    "path": "spec/support/flipper_helper.rb",
    "content": "RSpec.configure do |config|\n  config.before(:each, :flipper) do\n    allow(Flipper).to receive(:enabled?)\n  end\nend\n"
  },
  {
    "path": "spec/support/pretender_context.rb",
    "content": "module PretenderContext\n  def true_user\n  end\nend\n"
  },
  {
    "path": "spec/support/prosopite.rb",
    "content": "# frozen_string_literal: true\n\nreturn unless defined?(Prosopite)\n\n# Test configuration — this file owns all Prosopite settings for the test env\nProsopite.enabled = true\nProsopite.raise = true\nProsopite.rails_logger = true\nProsopite.prosopite_logger = true\n\n# Allowlist for known acceptable N+1 patterns (e.g., test matchers)\nProsopite.allow_stack_paths = [\n  \"shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb\",\n  \"shoulda/matchers/active_model/validate_presence_of_matcher.rb\",\n  \"shoulda/matchers/active_model/validate_inclusion_of_matcher.rb\",\n  \"shoulda/matchers/active_model/allow_value_matcher.rb\"\n]\n\n# Load ignore list from file for gradual rollout — directories listed in\n# .prosopite_ignore are scanned but won't raise, only log.\nPROSOPITE_IGNORE = if File.exist?(\"spec/.prosopite_ignore\")\n  File.read(\"spec/.prosopite_ignore\")\n    .lines\n    .map(&:chomp)\n    .reject { |line| line.empty? || line.start_with?(\"#\") }\nelse\n  []\nend\n\nRSpec.configure do |config|\n  # Pause Prosopite during factory creation to prevent false positives\n  # from factory callbacks and associations\n  config.before(:suite) do\n    if defined?(FactoryBot)\n      FactoryBot::SyntaxRunner.class_eval do\n        alias_method :original_create, :create\n\n        def create(*args, **kwargs, &block)\n          if defined?(Prosopite) && Prosopite.enabled?\n            Prosopite.pause { original_create(*args, **kwargs, &block) }\n          else\n            original_create(*args, **kwargs, &block)\n          end\n        end\n      end\n    end\n  end\n\n  config.around do |example|\n    if use_prosopite?(example)\n      Prosopite.scan { example.run }\n    else\n      original_enabled = Prosopite.enabled?\n      Prosopite.enabled = false\n      begin\n        example.run\n      ensure\n        Prosopite.enabled = original_enabled\n      end\n    end\n  end\nend\n\ndef use_prosopite?(example)\n  # Explicit metadata takes precedence\n  return false if example.metadata[:disable_prosopite]\n  return true if example.metadata[:enable_prosopite]\n\n  # Check against ignore list\n  PROSOPITE_IGNORE.none? do |path|\n    File.fnmatch?(\"./#{path}/*\", example.metadata[:rerun_file_path].to_s)\n  end\nend\n"
  },
  {
    "path": "spec/support/pundit_helper.rb",
    "content": "module PunditHelper\n  def enable_pundit(view, user)\n    without_partial_double_verification do\n      allow(view).to receive(:policy) do |record|\n        Pundit.policy(user, record)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/rack_attack.rb",
    "content": "RSpec.configure do |config|\n  config.before do\n    Rack::Attack.enabled = false\n  end\nend\n"
  },
  {
    "path": "spec/support/request_helpers.rb",
    "content": "module Support\n  module RequestHelpers\n    def response_json\n      @response_json ||= JSON.parse(response.body, symbolize_names: true)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/rspec_retry.rb",
    "content": "# spec/spec_helper.rb\nrequire \"rspec/retry\"\n\nRSpec.configure do |config|\n  # show retry status in spec process\n  config.verbose_retry = true\n  # show exception that triggers a retry if verbose_retry is set to true\n  config.display_try_failure_messages = true\n\n  if ENV[\"CI\"] == \"true\"\n    # run retry only on features\n    config.around :each, :js do |ex|\n      ex.run_with_retry retry: 3\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/session_helper.rb",
    "content": "module SessionHelper\n  def sign_in_as_admin\n    sign_in(CasaAdmin.first || create(:casa_admin))\n  end\n\n  def sign_in_as_volunteer\n    sign_in(Volunteer.first || create(:volunteer))\n  end\n\n  # TODO use this more\n  def sign_in_as_supervisor\n    sign_in(Supervisor.first || create(:supervisor))\n  end\n\n  # TODO use this more\n  def sign_in_as_all_casa_admin\n    sign_in(AllCasaAdmin.first || create(:all_casa_admin))\n  end\nend\n"
  },
  {
    "path": "spec/support/shared_examples/shows_court_dates_links.rb",
    "content": "RSpec.shared_examples_for \"shows court dates links\" do\n  before do\n    travel_to Date.new(2020, 1, 2)\n    _newest_pcd = create(:court_date, date: DateTime.current - 5.days, casa_case: casa_case)\n    _oldest_pcd = create(:court_date, date: DateTime.current - 10.days, casa_case: casa_case)\n    hearing_type = create(:hearing_type, name: \"Some Hearing Name\")\n    _court_date_with_details = create(:court_date, :with_court_details, casa_case: casa_case, hearing_type: hearing_type)\n  end\n\n  it \"shows court orders\" do\n    visit edit_casa_case_path(casa_case)\n    expect(page).to have_link(\"December 23, 2019\")\n    expect(page).to have_link(\"December 26, 2019\")\n    expect(page).to have_link(\"December 26, 2019 - Some Hearing Name\")\n  end\n\n  it \"past court dates are ordered\" do\n    visit casa_case_path(casa_case)\n    expect(page).to have_text(\"December 23, 2019\")\n    expect(page).to have_text(/December 23, 2019.*December 28, 2019/m)\n  end\nend\n"
  },
  {
    "path": "spec/support/shared_examples/shows_error_for_invalid_phone_numbers.rb",
    "content": "RSpec.shared_examples_for \"shows error for invalid phone numbers\" do\n  it \"shows error message for phone number < 12 digits\" do\n    (role == \"admin\" || role == \"user\") ? fill_in(\"Phone number\", with: \"+141632489\") : fill_in(\"#{role}_phone_number\", with: \"+141632489\")\n    (role == \"user\") ? click_on(\"Update Profile\") : click_on(\"Submit\")\n    expect(page).to have_text \"Phone number must be 10 digits or 12 digits including country code (+1)\"\n  end\n\n  it \"shows error message for phone number > 12 digits\" do\n    (role == \"admin\" || role == \"user\") ? fill_in(\"Phone number\", with: \"+141632180923\") : fill_in(\"#{role}_phone_number\", with: \"+141632180923\")\n    (role == \"user\") ? click_on(\"Update Profile\") : click_on(\"Submit\")\n    expect(page).to have_text \"Phone number must be 10 digits or 12 digits including country code (+1)\"\n  end\n\n  it \"shows error message for bad phone number\" do\n    (role == \"admin\" || role == \"user\") ? fill_in(\"Phone number\", with: \"+141632u809o\") : fill_in(\"#{role}_phone_number\", with: \"+141632u809o\")\n    (role == \"user\") ? click_on(\"Update Profile\") : click_on(\"Submit\")\n    expect(page).to have_text \"Phone number must be 10 digits or 12 digits including country code (+1)\"\n  end\n\n  it \"shows error message for phone number without country code\" do\n    (role == \"admin\" || role == \"user\") ? fill_in(\"Phone number\", with: \"+24163218092\") : fill_in(\"#{role}_phone_number\", with: \"+24163218092\")\n    (role == \"user\") ? click_on(\"Update Profile\") : click_on(\"Submit\")\n    expect(page).to have_text \"Phone number must be 10 digits or 12 digits including country code (+1)\"\n  end\nend\n"
  },
  {
    "path": "spec/support/shared_examples/soft_deleted_model.rb",
    "content": "RSpec.shared_examples_for \"a soft-deleted model\" do |skip_ignores_deleted_records_in_validations_check: false, skip_deleted_at_index_check: false|\n  # for usage with acts_as_paranoid models\n\n  it { is_expected.to have_db_column(:deleted_at) }\n\n  unless skip_deleted_at_index_check\n    it { is_expected.to have_db_index(:deleted_at) }\n  end\n\n  it \"cannot be found, by default\" do\n    model ||= create(described_class_factory)\n    model.destroy!\n    expect(described_class.find_by(id: model.id)).to be_nil\n  end\n\n  it \"returned when unscoped\" do\n    model ||= create(described_class_factory)\n    model.destroy!\n    expect(described_class.unscoped.find_by(id: model.id)).to be_present\n  end\n\n  context \"uniqueness\" do\n    it \"ignores deleted records in validations\" do\n      unless skip_ignores_deleted_records_in_validations_check\n        obj = create(described_class_factory)\n        new_obj = obj.dup\n        expect(new_obj).not_to be_valid\n        obj.destroy!\n        expect(new_obj).to be_valid\n        expect { new_obj.save! }.not_to raise_exception\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/stubbed_requests/short_io_api.rb",
    "content": "module ShortIOAPI\n  def short_io_stub(base_url = \"https://www.google.com\")\n    WebMock.stub_request(:post, \"https://api.short.io/links\")\n      .with(\n        body: {originalURL: base_url, domain: \"42ni.short.gy\"}.to_json,\n        headers: {\n          \"Accept\" => \"application/json\",\n          \"Accept-Encoding\" => \"gzip;q=1.0,deflate;q=0.6,identity;q=0.3\",\n          \"Authorization\" => \"1337\",\n          \"Content-Type\" => \"application/json\",\n          \"User-Agent\" => \"Ruby\"\n        }\n      )\n      .to_return(status: 200, body: \"{\\\"shortURL\\\":\\\"https://42ni.short.gy/jzTwdF\\\"}\", headers: {})\n  end\n\n  def short_io_stub_sms\n    WebMock.stub_request(:post, \"https://api.short.io/links\")\n      .with(\n        headers: {\n          \"Accept\" => \"application/json\",\n          \"Accept-Encoding\" => \"gzip;q=1.0,deflate;q=0.6,identity;q=0.3\",\n          \"Authorization\" => \"1337\",\n          \"Content-Type\" => \"application/json\",\n          \"User-Agent\" => \"Ruby\"\n        }\n      )\n      .to_return(status: 200, body: \"{\\\"shortURL\\\":\\\"https://42ni.short.gy/jzTwdF\\\"}\", headers: {})\n  end\n\n  def short_io_error_stub\n    WebMock.stub_request(:post, \"https://api.short.io/links\")\n      .with(\n        body: {originalURL: \"www.badrequest.com\", domain: \"42ni.short.gy\"}.to_json\n      )\n      .to_return(status: 401, body: \"{\\\"shortURL\\\":\\\"https://42ni.short.gy/jzTwdF\\\"}\", headers: {})\n  end\n\n  def short_io_stub_localhost(base_url = \"http://localhost:3000/case_contacts/new\")\n    WebMock.stub_request(:post, \"https://api.short.io/links\")\n      .with(\n        body: {originalURL: base_url, domain: \"42ni.short.gy\"}.to_json,\n        headers: {\n          \"Accept\" => \"application/json\",\n          \"Accept-Encoding\" => \"gzip;q=1.0,deflate;q=0.6,identity;q=0.3\",\n          \"Authorization\" => \"1337\",\n          \"Content-Type\" => \"application/json\",\n          \"User-Agent\" => \"Ruby\"\n        }\n      )\n      .to_return(status: 200, body: \"{\\\"shortURL\\\":\\\"https://42ni.short.gy/jzTwdF\\\"}\", headers: {})\n  end\n\n  def short_io_court_report_due_date_stub(base_url = \"http://localhost:3000/case_court_reports\")\n    WebMock.stub_request(:post, \"https://api.short.io/links\")\n      .with(\n        body: {originalURL: base_url, domain: \"42ni.short.gy\"}.to_json,\n        headers: {\n          \"Accept\" => \"application/json\",\n          \"Accept-Encoding\" => \"gzip;q=1.0,deflate;q=0.6,identity;q=0.3\",\n          \"Authorization\" => \"1337\",\n          \"Content-Type\" => \"application/json\",\n          \"User-Agent\" => \"Ruby\"\n        }\n      )\n      .to_return(status: 200, body: \"{\\\"shortURL\\\":\\\"https://42ni.short.gy/jzTwdF\\\"}\", headers: {})\n  end\nend\n"
  },
  {
    "path": "spec/support/stubbed_requests/twilio_api.rb",
    "content": "module TwilioAPI\n  def twilio_success_stub_messages_60_days\n    WebMock.stub_request(:post, \"https://api.twilio.com/2010-04-01/Accounts/articuno34/Messages.json\").with(\n      headers: {\n        \"Content-Type\" => \"application/x-www-form-urlencoded\",\n        \"Authorization\" => \"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\"\n      }\n    ).to_return(\n      {body: \"{\\\"body\\\":\\\"It's been 60 days or more since you've reached out to these members of your youth's network:\\\"}\"},\n      {body: \"{\\\"body\\\":\\\"• test\\\"}\"},\n      {body: \"{\\\"body\\\":\\\"If you have made contact with them in the past 60 days, remember to log it: https://42ni.short.gy/jzTwdF\\\"}\"}\n    )\n  end\n\n  def twilio_success_stub\n    WebMock.stub_request(:post, \"https://api.twilio.com/2010-04-01/Accounts/articuno34/Messages.json\")\n      .with(\n        body: {From: \"+15555555555\", Body: \"Execute Order 66 - https://42ni.short.gy/jzTwdF\", To: \"+12222222222\"},\n        headers: {\n          \"Content-Type\" => \"application/x-www-form-urlencoded\",\n          \"Authorization\" => \"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\"\n        }\n      )\n      .to_return(body: \"{\\\"error_code\\\":null, \\\"status\\\":\\\"sent\\\", \\\"body\\\":\\\"Execute Order 66 - https://42ni.short.gy/jzTwdF\\\"}\")\n  end\n\n  def twilio_activation_success_stub(resource = \"\")\n    WebMock.stub_request(:post, \"https://api.twilio.com/2010-04-01/Accounts/articuno34/Messages.json\")\n      .with(\n        headers: {\n          \"Content-Type\" => \"application/x-www-form-urlencoded\",\n          \"Authorization\" => \"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\"\n        }\n      )\n      .to_return(body: \"{\\\"error_code\\\":null, \\\"status\\\":\\\"sent\\\", \\\"body\\\":\\\"Execute Order 66 - https://42ni.short.gy/jzTwdF\\\"}\")\n  end\n\n  def twilio_activation_error_stub(resource = \"\")\n    WebMock.stub_request(:post, \"https://api.twilio.com/2010-04-01/Accounts/articuno31/Messages.json\")\n      .with(\n        headers: {\n          \"Content-Type\" => \"application/x-www-form-urlencoded\",\n          \"Authorization\" => \"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\"\n        }\n      )\n      .to_return(body: \"{\\\"error_code\\\":\\\"42\\\", \\\"status\\\":\\\"failed\\\", \\\"body\\\":\\\"My tea's gone cold I wonder why\\\"}\")\n  end\n\n  def twilio_court_report_due_date_stub(resource = \"\")\n    court_due_date = Date.current + 7.days\n    WebMock.stub_request(:post, \"https://api.twilio.com/2010-04-01/Accounts/articuno34/Messages.json\")\n      .with(\n        body: {\"Body\" => \"Your court report is due on #{court_due_date}. Generate a court report to complete & submit here: https://42ni.short.gy/jzTwdF\", \"From\" => \"+15555555555\", \"To\" => \"+12223334444\"},\n        headers: {\n          \"Content-Type\" => \"application/x-www-form-urlencoded\",\n          \"Authorization\" => \"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\"\n        }\n      )\n      .to_return(body: \"{\\\"error_code\\\":null, \\\"status\\\":\\\"sent\\\", \\\"body\\\":\\\"Your court report is due on #{court_due_date}. Generate a court report to complete & submit here: https://42ni.short.gy/jzTwdF\\\"}\")\n  end\n\n  def twilio_no_contact_made_stub(resource = \"\")\n    WebMock.stub_request(:post, \"https://api.twilio.com/2010-04-01/Accounts/articuno34/Messages.json\")\n      .with(\n        body: {\"Body\" => \"It's been two weeks since you've tried reaching 'test'. Try again! https://42ni.short.gy/jzTwdF\", \"From\" => \"+15555555555\", \"To\" => \"+12222222222\"},\n        headers: {\n          \"Content-Type\" => \"application/x-www-form-urlencoded\",\n          \"Authorization\" => \"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\"\n        }\n      )\n      .to_return(body: \"{\\\"error_code\\\":null, \\\"status\\\":\\\"sent\\\", \\\"body\\\":\\\"It's been two weeks since you've tried reaching 'test'. Try again! https://42ni.short.gy/jzTwdF\\\"}\")\n  end\n\n  def twilio_password_reset_stub(resource)\n    WebMock.stub_request(:post, \"https://api.twilio.com/2010-04-01/Accounts/articuno34/Messages.json\")\n      .with(\n        body: {From: \"+15555555555\", Body: \"Hi #{resource.display_name}, click here to reset your password: https://42ni.short.gy/jzTwdF\", To: \"+12222222222\"},\n        headers: {\n          \"Content-Type\" => \"application/x-www-form-urlencoded\",\n          \"Authorization\" => \"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\"\n        }\n      )\n      .to_return(body: \"{\\\"error_code\\\":null, \\\"status\\\":\\\"sent\\\", \\\"body\\\":\\\"Execute Order 66 - https://42ni.short.gy/jzTwdF\\\"}\")\n  end\nend\n"
  },
  {
    "path": "spec/support/stubbed_requests/webmock_helper.rb",
    "content": "require \"support/stubbed_requests/short_io_api\"\nrequire \"support/stubbed_requests/twilio_api\"\n\nclass WebMockHelper\n  extend ShortIOAPI\n  extend TwilioAPI\nend\n"
  },
  {
    "path": "spec/support/twilio_helper.rb",
    "content": "module TwilioHelper\n  def stub_twilio\n    twilio_client = instance_double(Twilio::REST::Client)\n    messages = instance_double(Twilio::REST::Api::V2010::AccountContext::MessageList)\n    allow(Twilio::REST::Client).to receive(:new).with(\"Aladdin\", \"open sesame\", \"articuno34\").and_return(twilio_client)\n    allow(twilio_client).to receive(:messages).and_return(messages)\n    allow(messages).to receive(:list).and_return([])\n  end\nend\n"
  },
  {
    "path": "spec/support/user_input_helpers.rb",
    "content": "class UserInputHelpers\n  DANGEROUS_STRINGS = [\n    \"مرحبا بالعالم هذا اسم من ترجمة جوجل\",\n    \"שלום עולם זה שם מגוגל תרגם\",\n    '\"1\\'; DROP TABLE users-- 1\"',\n    '<<SCRIPT>alert(\\\"XSS\\\");//<</SCRIPT>',\n    \"Dr. Jane Smith, MS; MST; Esq.\"\n  ].freeze\nend\n"
  },
  {
    "path": "spec/swagger_helper.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.configure do |config|\n  # Specify a root folder where Swagger JSON files are generated\n  # NOTE: If you're using the rswag-api to serve API descriptions, you'll need\n  # to ensure that it's configured to serve Swagger from the same folder\n  config.openapi_root = Rails.root.join(\"swagger\").to_s\n\n  # Define one or more Swagger documents and provide global metadata for each one\n  # When you run the 'rswag:specs:swaggerize' rake task, the complete Swagger will\n  # be generated at the provided relative path under openapi_root\n  # By default, the operations defined in spec files are added to the first\n  # document below. You can override this behavior by adding a swagger_doc tag to the\n  # the root example_group in your specs, e.g. describe '...', swagger_doc: 'v2/swagger.json'\n  config.openapi_specs = {\n    \"v1/swagger.yaml\" => {\n      openapi: \"3.0.1\",\n      info: {\n        title: \"API V1\",\n        version: \"v1\"\n      },\n      components: {\n        schemas: {\n          login_success: {\n            type: :object,\n            properties: {\n              api_token: {type: :string},\n              refresh_token: {type: :string},\n              user: {\n                id: {type: :integer},\n                display_name: {type: :string},\n                email: {type: :string},\n                token_expires_at: {type: :datetime},\n                refresh_token_expires_at: {type: :datetime}\n              }\n            }\n          },\n          login_failure: {\n            type: :object,\n            properties: {\n              message: {type: :string}\n            }\n          },\n          sign_out: {\n            type: :object,\n            properties: {\n              message: {type: :string}\n            }\n          }\n        }\n      },\n      paths: {},\n      servers: [\n        {\n          url: \"https://{defaultHost}\",\n          variables: {\n            defaultHost: {\n              default: \"www.example.com\"\n            }\n          }\n        }\n      ]\n    }\n  }\n\n  # Specify the format of the output Swagger file when running 'rswag:specs:swaggerize'.\n  # The swagger_docs configuration option has the filename including format in\n  # the key, this may want to be changed to avoid putting yaml in json files.\n  # Defaults to json. Accepts ':json' and ':yaml'.\n  config.openapi_format = :yaml\nend\n"
  },
  {
    "path": "spec/system/all_casa_admins/all_casa_admin_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"all_casa_admins/casa_orgs/casa_admins/new\", type: :system do\n  it \"requires login\" do\n    visit new_all_casa_admins_casa_org_path\n    expect(page).to have_content \"You need to sign in before continuing.\"\n    expect(page).to have_current_path \"/all_casa_admins/sign_in\", ignore_query: true\n\n    visit all_casa_admins_casa_org_path(id: create(:casa_org).id)\n    expect(page).to have_current_path \"/all_casa_admins/sign_in\", ignore_query: true\n    expect(page).to have_content \"You need to sign in before continuing.\"\n  end\n\n  it \"does not allow logged in non all casa admin\" do\n    casa_admin = create(:casa_admin)\n    sign_in casa_admin\n    visit new_all_casa_admins_casa_org_path\n    expect(page).to have_current_path \"/all_casa_admins/sign_in\", ignore_query: true\n    expect(page).to have_text \"You need to sign in before continuing.\"\n    visit \"/\"\n    expect(page).to have_current_path \"/supervisors\", ignore_query: true\n    expect(page).to have_text \"Sign Out\"\n  end\n\n  it \"login and create new CasaOrg and new CasaAdmin for CasaOrg\" do\n    all_casa_admin = create(:all_casa_admin)\n    sign_in all_casa_admin\n\n    visit \"/\"\n    expect(page).to have_text \"All CASA Admin\"\n    expect(page).to have_text \"New CASA Organization\"\n    expect(page).to have_text \"New All CASA Admin\"\n    expect(page).to have_text \"CASA Organizations\"\n\n    # left sidebar\n    expect(page).to have_text \"Patch Notes\"\n    expect(page).to have_text \"Edit Profile\"\n    expect(page).to have_text \"Feature Flags\"\n    expect(page).to have_text \"Log Out\"\n\n    # footer\n    expect(page).to have_link(\"Ruby For Good\", href: \"https://rubyforgood.org/\")\n    expect(page).to have_link(\"Report a site issue\", href: \"https://form.typeform.com/to/iXY4BubB\")\n    expect(page).to have_link(\"SMS Terms & Conditions\", href: \"/sms-terms-conditions.html\")\n\n    # create new org\n    click_on \"New CASA Organization\"\n    expect(page).to have_current_path \"/all_casa_admins/casa_orgs/new\", ignore_query: true\n    expect(page).to have_text \"Create a new CASA Organization\"\n    fill_in \"Name\", with: \"Cool Org Name\"\n    fill_in \"Display name\", with: \"display name\"\n    fill_in \"Address\", with: \"123 Main St\"\n    click_on \"Create CASA Organization\"\n    expect(page).to have_text \"CASA Organization was successfully created.\"\n    expect(page).to have_text \"Cool Org Name\"\n    expect(page).to have_current_path(%r{/all_casa_admins/casa_orgs/\\d+}, ignore_query: true)\n    expect(page).to have_content \"Administrators\"\n    expect(page).to have_content \"Details\"\n    expect(page).to have_content \"Number of admins: 0\"\n    expect(page).to have_content \"Number of supervisors: 0\"\n    expect(page).to have_content \"Number of active volunteers: 0\"\n    expect(page).to have_content \"Number of inactive volunteers: 0\"\n    expect(page).to have_content \"Number of active cases: 0\"\n    expect(page).to have_content \"Number of inactive cases: 0\"\n    expect(page).to have_content \"Number of all case contacts including inactives: 0\"\n    expect(page).to have_content \"Number of active supervisor to volunteer assignments: 0\"\n    expect(page).to have_content \"Number of active case assignments: 0\"\n\n    # create new admin\n    click_on \"New CASA Admin\"\n    expect(page).to have_content \"New CASA Admin for Cool Org Name\"\n\n    click_button \"Submit\"\n    expect(page).to have_content \"2 errors prohibited this Casa admin from being saved:\"\n    expect(page).to have_content \"Email can't be blank\"\n    expect(page).to have_content \"Display name can't be blank\"\n\n    fill_in \"Email\", with: \"invalid email\"\n    fill_in \"Display name\", with: \"Freddy\"\n    click_button \"Submit\"\n    expect(page).to have_content \"1 error prohibited this Casa admin from being saved:\"\n    expect(page).to have_content \"Email is invalid\"\n\n    fill_in \"Email\", with: \"valid@example.com\"\n    fill_in \"Display name\", with: \"Freddy Valid\"\n    click_button \"Submit\"\n    expect(page).to have_content \"New admin created successfully\"\n    expect(page).to have_content \"valid@example.com\"\n\n    click_on \"New CASA Admin\"\n    fill_in \"Email\", with: \"valid@example.com\"\n    fill_in \"Display name\", with: \"Freddy Valid\"\n    click_button \"Submit\"\n    expect(page).to have_content \"Email has already been taken\"\n\n    expect(CasaAdmin.find_by(email: \"valid@example.com\").invitation_created_at).not_to be_nil\n  end\n\n  it \"edits all casa admins\" do\n    admin = create(:all_casa_admin)\n    other_admin = create(:all_casa_admin)\n\n    sign_in admin\n    visit edit_all_casa_admins_path\n\n    # validate email uniqueness\n    fill_in \"all_casa_admin_email\", with: other_admin.email\n    click_on \"Update Profile\"\n    expect(page).to have_text \"already been taken\"\n\n    # update email\n    fill_in \"all_casa_admin_email\", with: \"newemail@example.com\"\n    click_on \"Update Profile\"\n    expect(page).to have_text \"successfully updated\"\n    expect(ActionMailer::Base.deliveries.last.body.encoded).to match(\">Your CASA account's email has been updated to newemail@example.com\")\n\n    # change password\n    click_on \"Change Password\"\n    fill_in \"all_casa_admin_password\", with: \"newpassword\"\n    fill_in \"all_casa_admin_password_confirmation\", with: \"badmatch\"\n    click_on \"Update Password\"\n    expect(page).to have_text \"confirmation doesn't match\"\n\n    click_on \"Change Password\"\n    fill_in \"all_casa_admin_password\", with: \"newpassword\"\n    fill_in \"all_casa_admin_password_confirmation\", with: \"newpassword\"\n    click_on \"Update Password\"\n    expect(page).to have_text \"Password was successfully updated.\"\n    expect(ActionMailer::Base.deliveries.last.body.encoded).to match(\"Your CASA password has been changed.\")\n  end\n\n  it \"admin invitations expire\" do\n    all_casa_admin = AllCasaAdmin.invite!(email: \"valid@email.com\")\n    raw_token = all_casa_admin.raw_invitation_token\n\n    # Invitation is valid within 1 week\n    travel 2.days\n    visit accept_all_casa_admin_invitation_path(invitation_token: raw_token)\n    expect(page).to have_text \"Set my password\"\n\n    # Invitation expires after 1 week\n    travel 8.days\n    visit accept_all_casa_admin_invitation_path(invitation_token: raw_token)\n    expect(page).to have_text \"The invitation token provided is not valid!\"\n  end\nend\n"
  },
  {
    "path": "spec/system/all_casa_admins/edit_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe \"AllCasaAdmin edit page\", type: :system do\n  let(:admin) { create(:all_casa_admin) }\n\n  before do\n    sign_in admin\n    visit edit_all_casa_admins_path\n  end\n\n  it \"shows the password section only after clicking 'Change Password'\", :aggregate_failures, :js do\n    expect_password_section_hidden\n\n    # Click the Change Password button\n    click_button \"Change Password\"\n\n    # Password section should now be visible\n    expect_password_section_visible\n  end\n\n  private\n\n  def expect_password_section_hidden\n    expect(page).to have_selector(\"#collapseOne.collapse:not(.show)\", visible: :all)\n  end\n\n  def expect_password_section_visible\n    expect(page).to have_selector(\"#collapseOne.collapse.show\")\n    expect(page).to have_field(\"all_casa_admin[password]\")\n    expect(page).to have_field(\"all_casa_admin[password_confirmation]\")\n    expect(page).to have_button(\"Update Password\")\n  end\nend\n"
  },
  {
    "path": "spec/system/all_casa_admins/password_change_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe \"AllCasaAdmin password change\", type: :system do\n  let(:admin) { create(:all_casa_admin, email: \"all_casa_admin1@example.com\", password: \"12345678\") }\n\n  before do\n    sign_in admin\n    visit edit_all_casa_admins_path\n    click_button \"Change Password\"\n  end\n\n  it \"shows error when password fields are blank\", :aggregate_failures, :js do\n    click_button \"Update Password\"\n    expect(page).to have_selector(\"#error_explanation.alert\")\n    expect(page).to have_text(\"Password can't be blank\")\n  end\n\n  it \"shows error when password confirmation doesn't match\", :aggregate_failures, :js do\n    fill_in \"all_casa_admin[password]\", with: \"newpassword\"\n    fill_in \"all_casa_admin[password_confirmation]\", with: \"wrongconfirmation\"\n    click_button \"Update Password\"\n    expect(page).to have_selector(\"#error_explanation.alert\")\n    expect(page).to have_text(\"Password confirmation doesn't match Password\")\n  end\n\n  it \"shows success flash when password is updated\", :aggregate_failures, :js do\n    fill_in \"all_casa_admin[password]\", with: \"newpassword\"\n    fill_in \"all_casa_admin[password_confirmation]\", with: \"newpassword\"\n    click_button \"Update Password\"\n    expect(page).to have_selector(\".header-flash\")\n    expect(page).to have_text(\"Password was successfully updated.\")\n  end\nend\n"
  },
  {
    "path": "spec/system/all_casa_admins/patch_notes/index_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"all_casa_admins/patch_notes/index\", type: :system do\n  context \"the new patch note form\" do\n    let(:all_casa_admin) { create(:all_casa_admin) }\n\n    context \"when the new patch note form's textarea is blank\" do\n      it \"displays a warning after trying to create\", :js do\n        sign_in all_casa_admin\n        visit all_casa_admins_patch_notes_path\n\n        within \"#new-patch-note\" do\n          click_on \"Create\"\n        end\n\n        expect(page).to have_selector(\".warning-notification\", text: \"Cannot save an empty patch note\")\n      end\n    end\n\n    context \"when the patch note form is filled out\" do\n      let(:patch_note_text) { \"/6cg0lad1P/NFtV\" }\n      let!(:patch_note_group) { create(:patch_note_group, :all_users) }\n      let!(:patch_note_type) { create(:patch_note_type, name: \"5[1ht=d\\\\%*^qRON\") }\n\n      it \"displays a the new patch note text on the page\", :js do\n        sign_in all_casa_admin\n        visit all_casa_admins_patch_notes_path\n\n        expect(page).to have_text(\"Patch Notes\")\n        within \"#new-patch-note\" do\n          text_area = first(:css, \"textarea\").native\n          text_area.send_keys(patch_note_text)\n\n          click_on \"Create\"\n        end\n\n        # wait_for_ajax\n        # Failure/Error: window_handles.slice(1..).each { |win| close_window(win) }\n        #\n        #           NoMethodError:\n        #             undefined method `slice' for nil:NilClass\n        expect(page).to have_text(\"Patch Notes\")\n\n        expect(page.find(\".patch-note-list-item.new textarea\")&.value).to eq(patch_note_text)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/all_casa_admins/sessions/new_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"all_casa_admins/sessions/new\", type: :system do\n  let(:all_casa_admin) { create(:all_casa_admin) }\n  let(:volunteer) { build_stubbed(:volunteer) }\n\n  context \"when authenticated user\" do\n    before { sign_in all_casa_admin }\n\n    it \"renders AllCasaAdmin dashboard page\" do\n      visit \"/\"\n      expect(page).to have_text \"All CASA Admin\"\n    end\n\n    it \"allows sign out\" do\n      visit \"/\"\n      find(\"#all-casa-log-out\").click\n      expect(page).not_to have_text \"sign in before continuing\"\n      expect(page).to have_text \"Signed out successfully\"\n      expect(page).to have_text \"All CASA Log In\"\n    end\n\n    it \"allows access to flipper\" do\n      visit \"/flipper\"\n      expect(page).to have_text \"Flipper\"\n    end\n  end\n\n  context \"when unauthenticated\" do\n    it \"shows sign in page\" do\n      visit \"/all_casa_admins/sign_in\"\n      expect(page).to have_text \"All CASA Log In\"\n    end\n\n    it \"allows sign in\" do\n      visit \"/all_casa_admins/sign_in\"\n\n      fill_in \"Email\", with: all_casa_admin.email\n      fill_in \"Password\", with: \"12345678\"\n      within \".actions\" do\n        click_on \"Log in\"\n      end\n\n      expect(page).to have_text \"All CASA Admin\"\n    end\n\n    it \"prevents User sign in\" do\n      visit \"/all_casa_admins/sign_in\"\n\n      fill_in \"Email\", with: volunteer.email\n      fill_in \"Password\", with: \"12345678\"\n      within \".actions\" do\n        click_on \"Log in\"\n      end\n\n      expect(page).to have_text(/invalid email or password/i)\n    end\n\n    it \"denies access to flipper\" do\n      original = Rails.application.env_config[\"action_dispatch.show_exceptions\"]\n      Rails.application.env_config[\"action_dispatch.show_exceptions\"] = :rescuable\n      visit \"/flipper\"\n      expect(page).to have_text \"No route matches [GET] \\\"/flipper\\\"\"\n    ensure\n      Rails.application.env_config[\"action_dispatch.show_exceptions\"] = original\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/application/timeout_warning_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"/*\", type: :system do\n  context \"when user is signed in\" do\n    let(:user) { create(:volunteer) }\n\n    before do\n      sign_in user\n    end\n\n    it \"renders the seconds before logout as a javascript variable\" do\n      visit \"/\"\n      parsed_page = Nokogiri::HTML(page.html)\n      expect(parsed_page.at(\"script\").text.strip).to include(user.timeout_in.in_seconds.to_s)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/banners/dismiss_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"banners/dismiss\", :js, type: :system do\n  let!(:casa_org) { create(:casa_org) }\n  let!(:active_banner) { create(:banner, casa_org: casa_org) }\n  let(:volunteer) { create(:volunteer, casa_org: casa_org) }\n\n  context \"when user dismisses a banner\" do\n    it \"hides banner\" do\n      sign_in volunteer\n\n      visit root_path\n      expect(page).to have_text(\"Please fill out this survey\")\n\n      click_on \"Dismiss\"\n      expect(page).not_to have_text(\"Please fill out this survey\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/banners/new_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"Banners\", :js, type: :system do\n  include ActionText::SystemTestHelper\n\n  let(:admin) { create(:casa_admin) }\n  let(:organization) { admin.casa_org }\n\n  it \"adds a banner\" do\n    sign_in admin\n\n    visit banners_path\n    click_on \"New Banner\"\n    fill_in \"Name\", with: \"Volunteer Survey Announcement\"\n    check \"Active?\"\n    fill_in_rich_text_area \"banner_content\", with: \"Please fill out this survey.\"\n    click_on \"Submit\"\n\n    visit banners_path\n    expect(page).to have_text(\"Volunteer Survey Announcement\")\n\n    within \"#banners\" do\n      click_on \"Edit\", match: :first\n    end\n    fill_in \"Name\", with: \"Better Volunteer Survey Announcement\"\n    click_on \"Submit\"\n\n    expect(page).to have_text(\"Better Volunteer Survey Announcement\")\n\n    visit root_path\n    expect(page).to have_text(\"Please fill out this survey.\")\n  end\n\n  it \"lets you create banner with expiration time and edit it\" do\n    sign_in admin\n\n    visit banners_path\n    click_on \"New Banner\"\n    fill_in \"Name\", with: \"Expiring Announcement\"\n    check \"Active?\"\n    find(\"#banner_expires_at\").execute_script(\"this.value = arguments[0]\", 7.days.from_now.strftime(\"%Y-%m-%dT%H:%M\"))\n    fill_in_rich_text_area \"banner_content\", with: \"Please fill out this survey.\"\n    click_on \"Submit\"\n\n    expect(page).to have_text(\"Expiring Announcement Yes in 7 days\")\n\n    within \"#banners\" do\n      click_on \"Edit\", match: :first\n    end\n    find(\"#banner_expires_at\").execute_script(\"this.value = arguments[0]\", 3.days.from_now.strftime(\"%Y-%m-%dT%H:%M\"))\n    click_on \"Submit\"\n\n    expect(page).to have_text(\"Expiring Announcement Yes in 3 days\")\n\n    visit root_path\n    expect(page).to have_text(\"Please fill out this survey.\")\n  end\n\n  it \"does not allow creation of banner with an expiration time set in the past\" do\n    sign_in admin\n\n    visit banners_path\n    click_on \"New Banner\"\n    fill_in \"Name\", with: \"Announcement not created\"\n    find(\"#banner_expires_at\").execute_script(\"this.value = arguments[0]\", 1.week.ago.strftime(\"%Y-%m-%dT%H:%M\"))\n    fill_in_rich_text_area \"banner_content\", with: \"Please fill out this survey.\"\n    click_on \"Submit\"\n\n    visit banners_path\n    expect(page).not_to have_text(\"Announcement not created\")\n  end\n\n  describe \"when an organization has an active banner\" do\n    let(:admin) { create(:casa_admin) }\n    let(:organization) { create(:casa_org) }\n    let(:active_banner) { create(:banner, casa_org: organization) }\n\n    context \"when a banner is submitted as active\" do\n      it \"deactivates and replaces the current active banner\" do\n        active_banner\n\n        sign_in admin\n\n        visit banners_path\n        expect(page).to have_text(active_banner.content.body.to_plain_text)\n        click_on \"New Banner\"\n        fill_in \"Name\", with: \"New active banner name\"\n        check \"Active?\"\n        fill_in_rich_text_area \"banner_content\", with: \"New active banner content.\"\n        click_on \"Submit\"\n\n        # We're still on the banners page\n        expect(page).to have_current_path(banners_path)\n\n        within(\"table#banners\") do\n          already_existing_banner_row = find(\"tr\", text: active_banner.name)\n\n          expect(already_existing_banner_row).to have_selector(\"td.min-width\", text: \"No\")\n        end\n\n        expect(page).to have_text(\"New active banner content.\")\n      end\n    end\n\n    context \"when a banner is submitted as inactive\" do\n      it \"does not deactivate the current active banner\" do\n        active_banner\n\n        sign_in admin\n\n        visit banners_path\n        expect(page).to have_text(active_banner.content.body.to_plain_text)\n        click_on \"New Banner\"\n        fill_in \"Name\", with: \"New active banner name\"\n        fill_in_rich_text_area \"banner_content\", with: \"New active banner content.\"\n        click_on \"Submit\"\n\n        visit banners_path\n\n        within(\"table#banners\") do\n          already_existing_banner_row = find(\"tr\", text: active_banner.name)\n\n          expect(already_existing_banner_row).to have_selector(\"td.min-width\", text: \"Yes\")\n        end\n\n        expect(page).to have_text(active_banner.content.body.to_plain_text)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/bulk_court_dates/new_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe \"bulk_court_dates/new\", type: :system do\n  let(:now) { Date.new(2021, 1, 1) }\n  let(:casa_org) { create(:casa_org) }\n  let(:admin) { create(:casa_admin, casa_org: casa_org) }\n  let!(:casa_case) { create(:casa_case, casa_org: casa_org) }\n  let!(:court_date) { create(:court_date, :with_court_details, casa_case: casa_case, date: now - 1.week) }\n  let!(:judge) { create(:judge) }\n  let!(:hearing_type) { create(:hearing_type) }\n  let(:court_order_text) { Faker::Lorem.paragraph(sentence_count: 2) }\n\n  it \"is successful\", :js do\n    case_group = build(:case_group, casa_org: casa_org)\n    case_group.case_group_memberships.first.casa_case = casa_case\n    case_group.save!\n\n    travel_to now\n    sign_in admin\n    visit casa_cases_path\n    click_on \"New Bulk Court Date\"\n\n    select case_group.name, from: \"Case Group\"\n    fill_in \"court_date_date\", with: :now\n    fill_in \"court_date_court_report_due_date\", with: :now\n    select judge.name, from: \"Judge\"\n    select hearing_type.name, from: \"Hearing type\"\n\n    click_on \"Add a court order\"\n    text_area = first(:css, \"textarea\").native\n    text_area.send_keys(court_order_text)\n    page.find(\"select.implementation-status\").find(:option, text: \"Partially implemented\").select_option\n\n    within \".top-page-actions\" do\n      click_on \"Create\"\n    end\n\n    visit casa_case_path(casa_case)\n    expect(page).to have_content(hearing_type.name)\n    expect(page).to have_content(court_order_text)\n    travel_back\n  end\nend\n"
  },
  {
    "path": "spec/system/casa_admins/edit_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"casa_admins/edit\", type: :system do\n  let(:admin) { create :casa_admin, monthly_learning_hours_report: false }\n\n  before { sign_in admin }\n\n  context \"with valid data\" do\n    it \"can successfully edit user display name and phone number\" do\n      expected_display_name = \"Root Admin\"\n      expected_phone_number = \"+14398761234\"\n      expected_date_of_birth = \"1997-04-16\"\n\n      visit edit_casa_admin_path(admin)\n\n      fill_in \"Display name\", with: expected_display_name\n      fill_in \"Phone number\", with: expected_phone_number\n      fill_in \"Date of birth\", with: expected_date_of_birth\n      check \"Receive Monthly Learning Hours Report\"\n\n      click_on \"Submit\"\n\n      expect(page).to have_text \"Casa Admin was successfully updated.\"\n      expect(page).to have_field \"Display name\", with: expected_display_name\n      expect(page).to have_field \"Phone number\", with: expected_phone_number\n      expect(page).to have_field \"Date of birth\", with: expected_date_of_birth\n      expect(page).to have_checked_field(\"Receive Monthly Learning Hours Report\")\n    end\n  end\n\n  context \"with valid email data\" do\n    before do\n      visit edit_casa_admin_path(admin)\n      @old_email = admin.email\n      fill_in \"Email\", with: \"new_admin_email@example.com\"\n\n      click_on \"Submit\"\n      admin.reload\n    end\n\n    it \"sends a confirmation email upon submission and does not change the user's displayed email\" do\n      expect(ActionMailer::Base.deliveries.count).to eq(1)\n      expect(ActionMailer::Base.deliveries.first).to be_a(Mail::Message)\n      expect(ActionMailer::Base.deliveries.first.body.encoded)\n        .to match(\"Click here to confirm your email\")\n\n      expect(page).to have_text \"Admin was successfully updated. Confirmation Email Sent.\"\n      expect(page).to have_field(\"Email\", with: @old_email)\n      admin.reload\n      expect(admin.unconfirmed_email).to eq(\"new_admin_email@example.com\")\n    end\n\n    it \"succesfully updates the user email once the user confirms the changes\" do\n      admin.confirm\n      visit edit_casa_admin_path(admin)\n\n      expect(page).to have_field(\"Email\", with: \"new_admin_email@example.com\")\n      admin.reload\n      expect(admin.old_emails).to eq([@old_email])\n    end\n  end\n\n  context \"with invalid data\" do\n    let(:role) { \"admin\" }\n\n    before do\n      visit edit_casa_admin_path(admin)\n      fill_in \"Email\", with: \"newemail@example.com\"\n      fill_in \"Display name\", with: \"Kaedehara Kazuha\"\n    end\n\n    it_behaves_like \"shows error for invalid phone numbers\"\n\n    it \"shows error message for invalid date\" do\n      fill_in \"Date of birth\", with: 8.days.from_now.strftime(\"%Y/%m/%d\")\n      click_on \"Submit\"\n\n      expect(page).to have_text \"Date of birth must be in the past.\"\n    end\n\n    it \"shows error message for empty email\" do\n      fill_in \"Email\", with: \"\"\n      fill_in \"Display name\", with: \"\"\n\n      click_on \"Submit\"\n\n      expect(page).to have_text \"Email can't be blank\"\n      expect(page).to have_text \"Display name can't be blank\"\n    end\n  end\n\n  it \"can successfully deactivate\", :js do\n    another = create(:casa_admin)\n    visit edit_casa_admin_path(another)\n\n    dismiss_confirm do\n      click_on \"Deactivate\"\n    end\n\n    expect(page).not_to have_text(\"Admin was deactivated.\")\n\n    accept_confirm do\n      click_on \"Deactivate\"\n    end\n\n    expect(page).to have_text(\"Admin was deactivated.\")\n    expect(another.reload.active).to be_falsey\n  end\n\n  it \"can resend invitation to a another admin\", :js do\n    another = create(:casa_admin)\n    visit edit_casa_admin_path(another)\n\n    click_on \"Resend Invitation\"\n\n    expect(page).to have_content(\"Invitation sent\")\n  end\n\n  it \"can convert the admin to a supervisor\", :js do\n    visit edit_casa_admin_path(admin)\n\n    click_on \"Change to Supervisor\"\n\n    expect(page).to have_text(\"Admin was changed to Supervisor.\")\n    expect(User.find(admin.id)).to be_supervisor\n  end\n\n  it \"is not able to edit last sign in\" do\n    visit edit_casa_admin_path(admin)\n\n    expect(page).to have_text \"Added to system \"\n    expect(page).to have_text \"Invitation email sent never\"\n    expect(page).to have_text \"Last logged in\"\n    expect(page).to have_text \"Invitation accepted never\"\n    expect(page).to have_text \"Password reset last sent never\"\n  end\nend\n"
  },
  {
    "path": "spec/system/casa_admins/index_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"casa_admins/index\", type: :system do\n  let(:organization) { build(:casa_org) }\n  let(:admin) { build(:casa_admin, casa_org: organization) }\n\n  it \"displays other admins within the same CASA organization\" do\n    admin2 = create(:casa_admin, email: \"Jon@org.com\", casa_org: organization)\n    admin3 = create(:casa_admin, email: \"Bon@org.com\", casa_org: organization)\n    different_org_admin = build_stubbed(:casa_admin, email: \"Jovi@something.else\", casa_org: build_stubbed(:casa_org))\n    supervisor = build(:supervisor, email: \"super@visor.com\", casa_org: organization)\n    volunteer = build(:volunteer, email: \"volun@tear.com\", casa_org: organization)\n\n    sign_in admin\n    visit casa_admins_path\n\n    within \"#admins\" do\n      expect(page).to have_content(admin2.email)\n      expect(page).to have_content(admin3.email)\n      expect(page).to have_no_content(different_org_admin.email)\n      expect(page).to have_no_content(supervisor.email)\n      expect(page).to have_no_content(volunteer.email)\n    end\n  end\n\n  it \"displays a deactivated tag for inactive admins\" do\n    inactive_admin = create(:casa_admin, :inactive, casa_org: organization)\n\n    sign_in admin\n    visit casa_admins_path\n\n    within \"#admins\" do\n      expect(page).to have_content(inactive_admin.email)\n      expect(page).to have_content(\"Deactivated\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/casa_admins/new_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"casa_admins/new\", type: :system do\n  let(:admin) { create :casa_admin }\n\n  it \"validates and creates new admin\" do\n    visit casa_admins_path\n    expect(page).to have_content \"You need to sign in before continuing.\"\n\n    sign_in admin\n    visit casa_admins_path\n    click_on \"New Admin\"\n    expect(page).to have_content \"Create New Casa Admin\"\n\n    click_button \"Submit\"\n    expect(page).to have_content \"2 errors prohibited this Casa admin from being saved:\"\n    expect(page).to have_content \"Email can't be blank\"\n    expect(page).to have_content \"Display name can't be blank\"\n\n    fill_in \"Email\", with: \"invalid email\"\n    fill_in \"Display name\", with: \"Freddy\"\n    click_button \"Submit\"\n    expect(page).to have_content \"1 error prohibited this Casa admin from being saved:\"\n    expect(page).to have_content \"Email is invalid\"\n\n    fill_in \"Email\", with: \"valid@example.com\"\n    fill_in \"Display name\", with: \"Freddy Valid\"\n    click_button \"Submit\"\n    expect(page).to have_content \"New admin created successfully\"\n    expect(page).to have_content \"valid@example.com\"\n\n    last_email = ActionMailer::Base.deliveries.last\n    expect(last_email.to).to eq [\"valid@example.com\"]\n    expect(last_email.subject).to have_text \"CASA Console invitation instructions\"\n    expect(last_email.html_part.body.encoded).to have_text \"your new CasaAdmin account.\"\n\n    click_on \"New Admin\"\n    fill_in \"Email\", with: \"valid@example.com\"\n    fill_in \"Display name\", with: \"Freddy Valid\"\n    click_button \"Submit\"\n    expect(page).to have_content \"Email has already been taken\"\n\n    expect(CasaAdmin.find_by(email: \"valid@example.com\").invitation_created_at).not_to be_nil\n  end\nend\n"
  },
  {
    "path": "spec/system/casa_cases/additional_index_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"casa_cases/index\", type: :system do\n  # TODO combine with other casa cases index system spec\n  let(:user) { build_stubbed :casa_admin }\n\n  let(:organization) { create(:casa_org) }\n  let(:volunteer) { build :volunteer, casa_org: organization }\n  let(:admin) { create(:casa_admin, casa_org: organization) }\n\n  context \"logged in as admin\" do\n    before do\n      sign_in admin\n      visit casa_cases_path\n    end\n\n    it \"has content\" do\n      expect(page).to have_text(\"Cases\")\n      expect(page).to have_link(\"New Case\", href: new_casa_case_path)\n      expect(page).to have_selector(\"button\", text: \"Casa Case Prefix\")\n      expect(page).to have_selector(\"th\", text: \"Case Number\")\n      expect(page).to have_selector(\"th\", text: \"Hearing Type\")\n      expect(page).to have_selector(\"th\", text: \"Judge\")\n      expect(page).to have_selector(\"th\", text: \"Status\")\n      expect(page).to have_selector(\"th\", text: \"Transition Aged Youth\")\n      expect(page).to have_selector(\"th\", text: \"Assigned To\")\n    end\n\n    it \"filters active/inactive\", :js do\n      active_case = build(:casa_case, active: true, casa_org: organization)\n      active_case1 = build(:casa_case, active: true, casa_org: organization)\n      inactive_case = build(:casa_case, active: false, casa_org: organization)\n\n      create(:case_assignment, volunteer: volunteer, casa_case: active_case)\n      create(:case_assignment, volunteer: volunteer, casa_case: active_case1)\n      create(:case_assignment, volunteer: volunteer, casa_case: inactive_case)\n\n      visit casa_cases_path\n      expect(page).to have_selector(\".casa-case-filters\")\n\n      # by default, only active casa cases are shown\n      expect(page.all(\"table#casa-cases tbody tr\").count).to eq [active_case, active_case1].size\n\n      click_on \"Status\"\n      find(:css, 'input[data-value=\"Active\"]').click\n      expect(page).to have_text(\"No matching records found\")\n\n      find(:css, 'input[data-value=\"Inactive\"]').click\n      expect(page.all(\"table#casa-cases tbody tr\").count).to eq [inactive_case].size\n    end\n\n    it \"Only displays cases belonging to user's org\" do\n      org_cases = create_list :casa_case, 3, active: true, casa_org: organization\n      new_org = create :casa_org\n      other_org_cases = create_list :casa_case, 3, active: true, casa_org: new_org\n\n      visit casa_cases_path\n\n      org_cases.each { |casa_case| expect(page).to have_content casa_case.case_number }\n      other_org_cases.each { |casa_case| expect(page).not_to have_content casa_case.case_number }\n    end\n  end\n\n  context \"logged in as volunteer\" do\n    before do\n      sign_in volunteer\n      visit casa_cases_path\n    end\n\n    it \"hides filters\" do\n      expect(page).not_to have_text(\"Assigned to Volunteer\")\n      expect(page).not_to have_text(\"Assigned to more than 1 Volunteer\")\n      expect(page).not_to have_text(\"Assigned to Transition Aged Youth\")\n      expect(page).not_to have_text(\"Casa Case Prefix\")\n      expect(page).not_to have_text(\"Select columns\")\n      expect(page).not_to have_selector(\".casa-case-filters\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/casa_cases/edit_spec.rb",
    "content": "require \"rails_helper\"\nrequire \"stringio\"\n\nRSpec.describe \"Edit CASA Case\", type: :system do\n  context \"logged in as admin\" do\n    let(:organization) { build(:casa_org) }\n    let(:other_organization) { build(:casa_org) }\n    let(:admin) { create(:casa_admin, casa_org: organization) }\n    let(:other_org_admin) { create(:casa_admin, casa_org: other_organization) }\n    let(:casa_case) { create(:casa_case, :with_one_court_order, casa_org: organization) }\n    let(:other_org_casa_case) { create(:casa_case, :with_one_court_order, casa_org: other_organization) }\n    let(:contact_type_group) { create(:contact_type_group, casa_org: organization) }\n    let(:other_org_contact_type_group) { create(:contact_type_group, casa_org: other_organization) }\n    let!(:contact_type) { create(:contact_type, contact_type_group: contact_type_group) }\n    let!(:another_contact_type) { create(:contact_type, contact_type_group: contact_type_group) }\n    let!(:saved_case_contact_type) { create(:casa_case_contact_type, casa_case: casa_case, contact_type: contact_type) }\n    let!(:other_org_contact_type) { create(:contact_type, contact_type_group: other_org_contact_type_group) }\n    let!(:siblings_casa_cases) do\n      create(:casa_case, :with_one_court_order, casa_org: organization)\n      organization.casa_cases.excluding(casa_case)\n    end\n    let!(:other_org_siblings_casa_cases) do\n      create(:casa_case, :with_one_court_order, casa_org: other_organization)\n      other_organization.casa_cases.excluding(other_org_casa_case)\n    end\n\n    before { sign_in admin }\n\n    it_behaves_like \"shows court dates links\"\n\n    it \"shows court orders\" do\n      visit edit_casa_case_path(casa_case)\n\n      court_order = casa_case.case_court_orders.first\n\n      expect(page).to have_text(court_order.text)\n      expect(page).to have_text(court_order.implementation_status.humanize)\n    end\n\n    it \"edits case\", :js do\n      visit casa_case_path(casa_case.id)\n      click_on \"Edit Case Details\"\n      select \"Submitted\", from: \"casa_case_court_report_status\"\n\n      find(\".ts-control\").click\n\n      page.all(\".ts-dropdown-content input\")\n\n      select_all_el = page.find(\"span[data-test=select-all-input]\")\n      # uncheck all contact type options\n      select_all_el.click\n      within \".ts-dropdown-content\" do\n        expect(page).not_to have_css(\".form-check-input--checked\")\n        expect(page).to have_css(\".form-check-input--unchecked\", count: 3)\n      end\n      # check all contact type options\n      select_all_el.click\n      within \".ts-dropdown-content\" do\n        expect(page).not_to have_css(\"input.form-check-input--unchecked\")\n        expect(page).to have_css(\"input.form-check-input--checked\", count: 3)\n      end\n\n      # unselect contact_type from dropdown\n      find(\"span\", text: contact_type.name).click\n\n      page.find('button[data-action=\"court-order-form#add\"]').click\n      find(\"#court-orders-list-container\").first(\"textarea\").send_keys(\"Court Order Text One\")\n\n      within \".top-page-actions\" do\n        click_on \"Update CASA Case\"\n      end\n      expect(page).to have_text(\"Submitted\")\n      expect(page).to have_text(\"Court Date\")\n      expect(page).not_to have_text(\"Court Report Due Date\")\n      expect(page).not_to have_field(\"Court Report Due Date\")\n      expect(page).to have_text(\"Youth's Date in Care\")\n      expect(page).to have_text(\"Court Order Text One\")\n      expect(page).not_to have_text(\"Deactivate Case\")\n\n      expect(casa_case.contact_types).to eq [another_contact_type]\n      has_checked_field? contact_type.name\n    end\n\n    it \"does not display anything when not part of the organization\", :js do\n      visit casa_case_path(other_org_casa_case.id)\n      expect(page).to have_text(\"Sorry, you are not authorized to perform this action.\")\n    end\n\n    it \"deactivates a case when part of the same organization\", :js do\n      visit edit_casa_case_path(casa_case)\n\n      click_on \"Deactivate CASA Case\"\n      click_on \"Yes, deactivate\"\n      expect(page).to have_text(\"Case #{casa_case.case_number} has been deactivated\")\n      expect(page).to have_text(\"Case was deactivated on: #{I18n.l(casa_case.updated_at, format: :standard, default: nil)}\")\n      expect(page).to have_text(\"Reactivate CASA Case\")\n      expect(page).not_to have_text(\"Court Date\")\n      expect(page).not_to have_text(\"Court Report Due Date\")\n      expect(page).not_to have_field(\"Court Report Due Date\")\n    end\n\n    it \"does not allow an admin to deactivate a case if not in an organization\" do\n      visit edit_casa_case_path(other_org_casa_case)\n      expect(page).to have_text(\"Sorry, you are not authorized to perform this action.\")\n    end\n\n    it \"reactivates a case\", :js do\n      visit edit_casa_case_path(casa_case)\n      click_on \"Deactivate CASA Case\"\n      click_on \"Yes, deactivate\"\n      click_link(\"Reactivate CASA Case\")\n\n      expect(page).to have_text(\"Case #{casa_case.case_number} has been reactivated.\")\n      expect(page).to have_text(\"Deactivate CASA Case\")\n      expect(page).to have_text(\"Court Date\")\n      expect(page).not_to have_text(\"Court Report Due Date\")\n      expect(page).not_to have_field(\"Court Report Due Date\")\n    end\n\n    context \"when trying to assign a volunteer to a case\" do\n      it \"is able to assign volunteers if in the same organization\", :js do\n        visit edit_casa_case_path(casa_case)\n\n        expect(page).to have_content(\"Manage Volunteers\")\n        expect(page).to have_css(\"#volunteer-assignment\")\n      end\n\n      it \"errors if trying to assign volunteers for another organization\" do\n        visit edit_casa_case_path(other_org_casa_case)\n        expect(page).to have_text(\"Sorry, you are not authorized to perform this action.\")\n      end\n    end\n\n    context \"Copy all court orders from a case\" do\n      it \"does not allow access to cases not within the organization\" do\n        visit edit_casa_case_path(other_org_casa_case)\n        expect(page).to have_text(\"Sorry, you are not authorized to perform this action.\")\n      end\n\n      it \"copy button should be disabled when no case is selected\", :js do\n        visit edit_casa_case_path(casa_case)\n        expect(page).to have_button(\"copy-court-button\", disabled: true)\n      end\n\n      it \"copy button should be enabled when a case is selected\", :js do\n        visit edit_casa_case_path(casa_case)\n        select siblings_casa_cases.first.case_number, from: \"casa_case_siblings_casa_cases\"\n        expect(page).to have_button(\"copy-court-button\", disabled: false)\n      end\n\n      it \"containses all case from organization except current case\", :js do\n        visit edit_casa_case_path(casa_case)\n        within \"#casa_case_siblings_casa_cases\" do\n          siblings_casa_cases.each do |scc|\n            expect(page).to have_selector(\"option\", text: scc.case_number)\n          end\n          expect(page).not_to have_selector(\"option\", text: casa_case.case_number)\n        end\n      end\n\n      it \"copies all court orders from selected case\", :js do\n        visit casa_case_path(casa_case.id)\n        click_on \"Edit Case Details\"\n        selected_case = siblings_casa_cases.first\n        select selected_case.case_number, from: \"casa_case_siblings_casa_cases\"\n        click_on \"Copy\"\n        within \".swal2-popup\" do\n          expect(page).to have_text(\"Copy all orders from case ##{selected_case.case_number}?\")\n          click_on \"Copy\"\n        end\n        expect(page).to have_text(\"Court orders have been copied\")\n        casa_case.reload\n        court_orders_text = casa_case.case_court_orders.map(&:text)\n        court_orders_status = casa_case.case_court_orders.map(&:implementation_status)\n        selected_case.case_court_orders.each do |orders|\n          expect(court_orders_text).to include orders.text\n          expect(court_orders_status).to include orders.implementation_status\n        end\n      end\n\n      it \"does not overwrite existing court orders\", :js do\n        visit casa_case_path(casa_case.id)\n        click_on \"Edit Case Details\"\n        selected_case = siblings_casa_cases.first\n        current_orders = casa_case.case_court_orders.each(&:dup)\n        select selected_case.case_number, from: \"casa_case_siblings_casa_cases\"\n        click_on \"Copy\"\n        within \".swal2-popup\" do\n          expect(page).to have_text(\"Copy all orders from case ##{selected_case.case_number}?\")\n          click_on \"Copy\"\n        end\n        expect(page).to have_text(\"Court orders have been copied\")\n        casa_case.reload\n        current_orders.each do |orders|\n          expect(casa_case.case_court_orders.map(&:text)).to include orders.text\n        end\n        expect(casa_case.case_court_orders.count).to be >= current_orders.count\n      end\n\n      it \"does not move court orders from one case to another\", :js do\n        visit casa_case_path(casa_case.id)\n        click_on \"Edit Case Details\"\n        selected_case = siblings_casa_cases.first\n        select selected_case.case_number, from: \"casa_case_siblings_casa_cases\"\n        click_on \"Copy\"\n        within \".swal2-popup\" do\n          expect(page).to have_text(\"Copy all orders from case ##{selected_case.case_number}?\")\n          click_on \"Copy\"\n        end\n        expect(page).to have_text(\"Court orders have been copied\")\n        casa_case.reload\n        expect(selected_case.case_court_orders.count).to be > 0\n      end\n    end\n  end\n\n  context \"logged in as supervisor\" do\n    let(:casa_org) { build(:casa_org) }\n    let(:supervisor) { create(:supervisor, casa_org: casa_org) }\n    let(:casa_case) { create(:casa_case, :with_one_court_order, casa_org: casa_org) }\n    let!(:contact_type_group) { build(:contact_type_group, casa_org: casa_org) }\n    let!(:contact_type_1) { create(:contact_type, name: \"Youth\", contact_type_group: contact_type_group) }\n    let!(:contact_type_2) { build(:contact_type, name: \"Supervisor\", contact_type_group: contact_type_group) }\n    let!(:next_year) { (Date.today.year + 1).to_s }\n\n    before { sign_in supervisor }\n\n    it_behaves_like \"shows court dates links\"\n\n    it \"edits case\", :js do\n      stub_twilio\n      visit casa_case_path(casa_case)\n      expect(page).to have_text(\"Court Report Status: Not submitted\")\n      visit edit_casa_case_path(casa_case)\n      select \"Submitted\", from: \"casa_case_court_report_status\"\n\n      scroll_to('button[data-action=\"court-order-form#add\"]').click\n      find(\"#court-orders-list-container\").first(\"textarea\").send_keys(\"Court Order Text One\")\n\n      select \"Partially implemented\", from: \"casa_case[case_court_orders_attributes][0][implementation_status]\"\n\n      expect(page).to have_text(\"Set Implementation Status\")\n\n      find(\".ts-control\").click\n\n      select_all_el = page.find(\"span[data-test=select-all-input]\")\n      # uncheck all contact type options\n      select_all_el.click\n      within \".ts-dropdown-content\" do\n        expect(page).not_to have_css(\".form-check-input--checked\")\n        expect(page).to have_css(\".form-check-input--unchecked\", count: 2)\n      end\n      # check all contact type options\n      select_all_el.click\n      within \".ts-dropdown-content\" do\n        expect(page).not_to have_css(\"input.form-check-input--unchecked\")\n        expect(page).to have_css(\"input.form-check-input--checked\", count: 2)\n      end\n      # since all contact type options checked, don't need to select one\n      within \".top-page-actions\" do\n        click_on \"Update CASA Case\"\n      end\n      has_checked_field? \"Youth\"\n      has_no_checked_field? \"Supervisor\"\n\n      expect(page).to have_text(\"Court Date\")\n      expect(page).not_to have_text(\"Court Report Due Date\")\n      expect(page).not_to have_field(\"Court Report Due Date\")\n      expect(page).not_to have_field(\"Court Report Due Date\", with: \"#{next_year}-09-08\")\n      expect(page).to have_text(\"Youth's Date in Care\")\n      expect(page).to have_text(\"Court Order Text One\")\n      expect(page).to have_text(\"Partially implemented\")\n\n      visit casa_case_path(casa_case)\n\n      expect(page).to have_text(\"Court Report Status: Submitted\")\n      expect(page).not_to have_text(\"8-SEP-#{next_year}\")\n    end\n\n    it \"views deactivated case\" do\n      casa_case.deactivate\n      visit edit_casa_case_path(casa_case)\n\n      expect(page).to have_text(\"Case was deactivated on: #{I18n.l(casa_case.updated_at, format: :standard, default: nil)}\")\n      expect(page).not_to have_text(\"Court Date\")\n      expect(page).not_to have_text(\"Court Report Due Date\")\n      expect(page).not_to have_text(\"Youth's Date in Care\")\n      expect(page).not_to have_text(\"Day\")\n      expect(page).not_to have_text(\"Month\")\n      expect(page).not_to have_text(\"Year\")\n      expect(page).not_to have_text(\"Reactivate Case\")\n      expect(page).not_to have_text(\"Update Casa Case\")\n    end\n\n    it \"shows court orders\" do\n      visit edit_casa_case_path(casa_case)\n\n      court_order = casa_case.case_court_orders.first\n\n      expect(page).to have_text(court_order.text)\n      expect(page).to have_text(court_order.implementation_status.humanize)\n    end\n\n    describe \"assign and unassign a volunteer to a case\" do\n      let(:organization) { build(:casa_org) }\n      let(:casa_case) { create(:casa_case, casa_org: organization) }\n      let(:supervisor1) { build(:supervisor, casa_org: organization) }\n      let!(:volunteer) { create(:volunteer, supervisor: supervisor1, casa_org: organization) }\n\n      def sign_in_and_assign_volunteer\n        sign_in supervisor1\n        visit casa_case_path(casa_case.id)\n        click_on \"Edit Case Details\"\n\n        select volunteer.display_name, from: \"case_assignment[volunteer_id]\"\n\n        click_on \"Assign Volunteer\"\n      end\n\n      before do\n        travel_to Time.zone.local(2020, 8, 29, 4, 5, 6)\n      end\n\n      context \"when a volunteer is assigned to a case\" do\n        it \"marks the volunteer as assigned and shows the start date of the assignment\", :js do\n          sign_in_and_assign_volunteer\n          expect(page).to have_content(\"Volunteer assigned to case\")\n\n          expect(casa_case.case_assignments.count).to eq 1\n\n          unassign_button = page.find(\"button.btn-outline-danger\")\n          expect(unassign_button.text).to eq \"Unassign Volunteer\"\n\n          assign_badge = page.find(\"span.bg-success\")\n          expect(assign_badge.text).to eq \"ASSIGNED\"\n        end\n\n        it \"shows an assignment start date and no assignment end date\" do\n          sign_in_and_assign_volunteer\n          assignment_start = page.find(\"td[data-test=assignment-start]\").text\n          assignment_end = page.find(\"td[data-test=assignment-end]\").text\n\n          expect(assignment_start).to eq(\"August 29, 2020\")\n          expect(assignment_end).to be_empty\n        end\n      end\n\n      context \"when a volunteer is unassigned from a case\" do\n        it \"marks the volunteer as unassigned and shows assignment start/end dates\", :js do\n          sign_in_and_assign_volunteer\n          unassign_button = page.find(\"button.btn-outline-danger\")\n          expect(unassign_button.text).to eq \"Unassign Volunteer\"\n\n          click_on \"Unassign Volunteer\"\n\n          assign_badge = page.find(\"span.bg-danger\")\n          expect(assign_badge.text).to eq \"UNASSIGNED\"\n\n          expected_start_and_end_date = \"August 29, 2020\"\n\n          assignment_start = page.find(\"td[data-test=assignment-start]\").text\n          assignment_end = page.find(\"td[data-test=assignment-end]\").text\n\n          expect(assignment_start).to eq(expected_start_and_end_date)\n          expect(assignment_end).to eq(expected_start_and_end_date)\n        end\n      end\n\n      context \"when supervisor other than volunteer's supervisor\" do\n        before { volunteer.update(supervisor: build(:supervisor)) }\n\n        it \"unassigns volunteer\", :js do\n          sign_in_and_assign_volunteer\n          unassign_button = page.find(\"button.btn-outline-danger\")\n          expect(unassign_button.text).to eq \"Unassign Volunteer\"\n\n          click_on \"Unassign Volunteer\"\n\n          assign_badge = page.find(\"span.bg-danger\")\n          expect(assign_badge.text).to eq \"UNASSIGNED\"\n        end\n      end\n\n      it \"when can assign only active volunteer to a case\" do\n        create(:volunteer, casa_org: organization)\n        build_stubbed(:volunteer, :inactive, casa_org: organization)\n\n        sign_in_and_assign_volunteer\n\n        expect(find(\"select[name='case_assignment[volunteer_id]']\").all(\"option\").count { |option| option[:value].present? }).to eq 1\n      end\n    end\n\n    describe \"case assigned to multiple volunteers\" do\n      let(:organization) { build(:casa_org) }\n      let(:supervisor) { create(:casa_admin, casa_org: organization) }\n      let(:casa_case) { create(:casa_case, casa_org: organization) }\n\n      let!(:volunteer_1) { create(:volunteer, display_name: \"AAA\", casa_org: organization) }\n      let!(:volunteer_2) { create(:volunteer, display_name: \"BBB\", casa_org: organization) }\n\n      it \"supervisor assigns multiple volunteers to the same case\" do\n        sign_in supervisor\n        visit edit_casa_case_path(casa_case.id)\n\n        select volunteer_1.display_name, from: \"Select a Volunteer\"\n        click_on \"Assign Volunteer\"\n        expect(page).to have_text(\"Volunteer assigned to case\")\n        expect(page).to have_text(volunteer_1.display_name)\n\n        # Attempt to assign a second volunteer without selecting one\n        click_on \"Assign Volunteer\"\n        expect(page).to have_text(\"Unable to assign volunteer to case: Volunteer must exist. Volunteer can't be blank.\")\n\n        select volunteer_2.display_name, from: \"Select a Volunteer\"\n        click_on \"Assign Volunteer\"\n        expect(page).to have_text(\"Volunteer assigned to case\")\n        expect(page).to have_text(volunteer_2.display_name)\n      end\n    end\n\n    describe \"form behavior\" do\n      it \"displays 'Please select volunteer' in the dropdown\" do\n        sign_in supervisor\n        visit edit_casa_case_path(casa_case.id)\n\n        select_element = find(\"#case_assignment_casa_case_id\")\n\n        # Check if the default option exists and has the expected text\n        expect(select_element).to have_selector(\"option[value='']\", text: \"Please Select Volunteer\")\n      end\n    end\n\n    context \"deleting court orders\", :js do\n      let(:casa_case) { create(:casa_case, :with_one_court_order, :with_casa_case_contact_types) }\n      let(:text) { casa_case.case_court_orders.first.text }\n\n      it \"can delete a court order\" do\n        visit edit_casa_case_path(casa_case.case_number.parameterize)\n\n        expect(page).to have_text(text)\n\n        find('button[data-action=\"click->court-order-form#remove\"]').click\n        expect(page).to have_text(\"Are you sure you want to remove this court order? Doing so will delete all records of it unless it was included in a previous court report.\")\n\n        find(\"button.swal2-confirm\").click\n        expect(page).not_to have_text(text)\n\n        within \".actions-cc\" do\n          click_on \"Update CASA Case\"\n        end\n        expect(page).not_to have_text(text)\n      end\n    end\n\n    context \"a casa case with contact type\" do\n      let(:organization) { build(:casa_org) }\n      let(:casa_case_with_contact_type) { create(:casa_case, :with_casa_case_contact_types, casa_org: organization) }\n\n      it \"has contact type checked\" do\n        contact_types = casa_case_with_contact_type.contact_types.map(&:id)\n        visit edit_casa_case_path(casa_case_with_contact_type)\n        all(\"input[type=checkbox][class~=case-contact-contact-type]\").each do |checkbox|\n          if contact_types.include? checkbox.value\n            expect(checkbox).to be_checked\n          else\n            expect(checkbox).not_to be_checked\n          end\n        end\n      end\n    end\n\n    context \"when trying to assign a volunteer to a case\" do\n      it \"is able to assign volunteers\", :js do\n        visit edit_casa_case_path(casa_case)\n\n        expect(page).to have_content(\"Manage Volunteers\")\n        expect(page).to have_css(\"#volunteer-assignment\")\n      end\n    end\n  end\n\n  context \"logged in as volunteer\" do\n    let(:volunteer) { build(:volunteer) }\n    let(:casa_case) { create(:casa_case, :with_one_court_order, casa_org: volunteer.casa_org) }\n    let!(:case_assignment) { create(:case_assignment, volunteer: volunteer, casa_case: casa_case) }\n\n    let!(:court_dates) do\n      [10, 30, 31, 90].map { |n| create(:court_date, casa_case: casa_case, date: n.days.ago) }\n    end\n\n    let!(:reports) do\n      [5, 11, 23, 44, 91].map do |n|\n        path_to_template = \"app/documents/templates/default_report_template.docx\"\n        args = {\n          volunteer_id: volunteer.id,\n          case_id: casa_case.id,\n          path_to_template: path_to_template\n        }\n        context = CaseCourtReportContext.new(args).context\n        report = CaseCourtReport.new(path_to_template: path_to_template, context: context)\n        casa_case.court_reports.attach(io: StringIO.new(report.generate_to_string), filename: \"report#{n}.docx\")\n        attached_report = casa_case.latest_court_report\n        attached_report.created_at = n.days.ago\n        attached_report.save!\n        attached_report\n      end\n    end\n\n    let!(:siblings_casa_cases) do\n      organization = volunteer.casa_org\n      casa_case2 = create(:casa_case, :with_one_court_order, casa_org: organization)\n      create(:case_assignment, volunteer: volunteer, casa_case: casa_case2)\n      organization.casa_cases.excluding(casa_case)\n    end\n\n    before { sign_in volunteer }\n\n    it_behaves_like \"shows court dates links\"\n\n    it \"views attached court reports\" do\n      visit edit_casa_case_path(casa_case)\n\n      # test court dates with reports get the correct ones\n      [[0, 1], [2, 3], [3, 4]].each do |di, ri|\n        expect(page).to have_link(\"(Attached Report)\", href: rails_blob_path(reports[ri], disposition: \"attachment\"))\n        expect(page).to have_link(I18n.l(court_dates[di].date, format: :full, default: nil))\n      end\n\n      # and that the one with no report still gets one\n      expect(page).to have_link(I18n.l(court_dates[1].date, format: :full, default: nil))\n      expect(page).to have_text(I18n.l(court_dates[1].date, format: :full, default: nil))\n    end\n\n    it \"shows court orders\" do\n      visit edit_casa_case_path(casa_case)\n\n      court_order = casa_case.case_court_orders.first\n\n      expect(page).to have_text(court_order.text)\n      expect(page).to have_text(court_order.implementation_status.humanize)\n    end\n\n    it \"edits case\" do\n      visit casa_case_path(casa_case)\n      expect(page).to have_text(\"Court Report Status: Not submitted\")\n      visit edit_casa_case_path(casa_case)\n      select \"Submitted\", from: \"casa_case_court_report_status\"\n      within \".actions-cc\" do\n        click_on \"Update CASA Case\"\n      end\n\n      expect(page).not_to have_field(\"Court Report Due Date\")\n      expect(page).not_to have_text(\"Youth's Date in Care\")\n      expect(page).not_to have_text(\"Deactivate Case\")\n\n      expect(page).to have_css('button[data-action=\"court-order-form#add\"]')\n\n      visit casa_case_path(casa_case)\n      expect(page).to have_text(\"Court Report Status: Submitted\")\n    end\n\n    it \"adds a standard court order\", :js do\n      visit edit_casa_case_path(casa_case)\n      select(\"Family therapy\", from: \"Court Order Type\")\n      click_button(\"Add a court order\")\n\n      textarea = all(\"textarea.court-order-text-entry\").last\n      expect(textarea.value).to eq(\"Family therapy\")\n    end\n\n    it \"adds a custom court order\", :js do\n      visit edit_casa_case_path(casa_case)\n      click_button(\"Add a court order\")\n\n      textarea = all(\"textarea.court-order-text-entry\").last\n      expect(textarea.value).to eq(\"\")\n    end\n\n    context \"Copy all court orders from a case\" do\n      it \"copy button should be disabled when no case is selected\", :js do\n        visit edit_casa_case_path(casa_case)\n        expect(page).to have_button(\"copy-court-button\", disabled: true)\n      end\n\n      it \"copy button should be enabled when a case is selected\", :js do\n        visit edit_casa_case_path(casa_case)\n        select siblings_casa_cases.first.case_number, from: \"casa_case_siblings_casa_cases\"\n        expect(page).to have_button(\"copy-court-button\", disabled: false)\n      end\n\n      it \"copy button and select shouldn't be visible when a volunteer only has one case\", :js do\n        volunteer = build(:volunteer)\n        casa_case = create(:casa_case, :with_one_court_order, casa_org: volunteer.casa_org)\n        create(:case_assignment, volunteer: volunteer, casa_case: casa_case)\n        visit edit_casa_case_path(casa_case)\n        expect(page).not_to have_button(\"copy-court-button\")\n        expect(page).not_to have_selector(\"casa_case_siblings_casa_cases\")\n      end\n\n      it \"containses all cases associated to current volunteer except current case\", :js do\n        visit edit_casa_case_path(casa_case)\n        within \"#casa_case_siblings_casa_cases\" do\n          siblings_casa_cases.each do |scc|\n            expect(page).to have_selector(\"option\", text: scc.case_number)\n          end\n          expect(page).not_to have_selector(\"option\", text: casa_case.case_number)\n        end\n      end\n\n      it \"copies all court orders from selected case\", :js do\n        visit casa_case_path(casa_case.id)\n        click_on \"Edit Case Details\"\n        selected_case = siblings_casa_cases.first\n        select selected_case.case_number, from: \"casa_case_siblings_casa_cases\"\n        click_on \"Copy\"\n        within \".swal2-popup\" do\n          expect(page).to have_text(\"Copy all orders from case ##{selected_case.case_number}?\")\n          click_on \"Copy\"\n        end\n        expect(page).to have_text(\"Court orders have been copied\")\n        casa_case.reload\n        court_orders_text = casa_case.case_court_orders.map(&:text)\n        court_orders_status = casa_case.case_court_orders.map(&:implementation_status)\n        selected_case.case_court_orders.each do |orders|\n          expect(court_orders_text).to include orders.text\n          expect(court_orders_status).to include orders.implementation_status\n        end\n      end\n\n      it \"does not overwrite existing court orders\", :js do\n        visit casa_case_path(casa_case.id)\n        click_on \"Edit Case Details\"\n        selected_case = siblings_casa_cases.first\n        current_orders = casa_case.case_court_orders.each(&:dup)\n        select selected_case.case_number, from: \"casa_case_siblings_casa_cases\"\n        click_on \"Copy\"\n        within \".swal2-popup\" do\n          expect(page).to have_text(\"Copy all orders from case ##{selected_case.case_number}?\")\n          click_on \"Copy\"\n        end\n        expect(page).to have_text(\"Court orders have been copied\")\n        casa_case.reload\n        current_orders.each do |orders|\n          expect(casa_case.case_court_orders.map(&:text)).to include orders.text\n        end\n        expect(casa_case.case_court_orders.count).to be >= current_orders.count\n      end\n\n      it \"does not move court orders from one case to another\", :js do\n        visit casa_case_path(casa_case.id)\n        click_on \"Edit Case Details\"\n        selected_case = siblings_casa_cases.first\n        select selected_case.case_number, from: \"casa_case_siblings_casa_cases\"\n        click_on \"Copy\"\n        within \".swal2-popup\" do\n          expect(page).to have_text(\"Copy all orders from case ##{selected_case.case_number}?\")\n          click_on \"Copy\"\n        end\n        expect(page).to have_text(\"Court orders have been copied\")\n        casa_case.reload\n        expect(selected_case.case_court_orders.count).to be > 0\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/casa_cases/emancipation/show_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"casa_cases/show\", type: :system do\n  let(:organization) { build(:casa_org) }\n  let(:volunteer) { build(:volunteer, casa_org: organization) }\n  let(:casa_case) { build(:casa_case, casa_org: organization) }\n  let!(:case_assignment) { create(:case_assignment, volunteer: volunteer, casa_case: casa_case) }\n  let!(:emancipation_category) { build(:emancipation_category, mutually_exclusive: true) }\n  let!(:emancipation_option) { create(:emancipation_option, emancipation_category: emancipation_category) }\n  let(:supervisor) { create(:supervisor, casa_org: organization) }\n\n  before do\n    sign_in user\n    visit casa_case_emancipation_path(casa_case.id)\n  end\n\n  context \"volunteer user\", :js do\n    let(:user) { volunteer }\n\n    it \"has a title\" do\n      expect(page).to have_content(\"Emancipation Checklist\")\n      expect(page).to have_content(emancipation_category.name)\n    end\n\n    it \"opens through main input, selects an option, and unselects option through main input\" do\n      emancipation_category = page.find(\".emancipation-category\")\n      find(\".emacipation-category-input-label-pair\").click\n      expect(page).to have_content(emancipation_option.name)\n      expect(emancipation_category[\"data-is-open\"]).to match(/true/)\n      find(\".check-item\").click\n      find(\".emacipation-category-input-label-pair\").click\n      expect(page).to have_css(\".success-notification\", text: \"Unchecked #{emancipation_option.name}\")\n      expect(emancipation_category[\"data-is-open\"]).to match(/true/)\n    end\n\n    it \"shows and hides the options through collapse icon\" do\n      emancipation_category = page.find(\".emancipation-category\")\n      find(\".category-collapse-icon\").click\n      expect(emancipation_category[\"data-is-open\"]).to match(/true/)\n      find(\".category-collapse-icon\").click\n      expect(emancipation_category[\"data-is-open\"]).to match(/false/)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/casa_cases/fund_requests/new_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"fund_requests/new\", type: :system do\n  let(:org) { create(:casa_org) }\n  let(:volunteer) { create(:volunteer, :with_casa_cases, casa_org: org) }\n  let(:casa_case) { volunteer.casa_cases.first }\n\n  before do\n    sign_in volunteer\n    visit new_casa_case_fund_request_path(casa_case)\n  end\n\n  it \"creates a fund request for the casa case\" do\n    aggregate_failures do\n      expect(page).to have_field \"Your email\", with: volunteer.email\n      expect(page).to have_field \"Name or case number of youth\", with: casa_case.case_number\n      expect(page).to have_field \"Requested by & relationship to youth\", with: \"#{volunteer.display_name} CASA Volunteer\"\n    end\n\n    fill_in \"Amount of payment\", with: \"100\"\n    fill_in \"Deadline\", with: \"2022-12-31\"\n    fill_in \"Request is for\", with: \"Fun outing\"\n    fill_in \"Name of payee\", with: \"Minnie Mouse\"\n    fill_in \"Other source of funding\", with: \"some other agency\"\n    fill_in \"How will this funding positively impact\", with: \"provide support\"\n    fill_in \"Please use this space\", with: \"foo bar\"\n\n    expect {\n      click_on \"Submit Fund Request\"\n    }.to change(FundRequest, :count).by(1)\n\n    expect(page).to have_text \"Fund Request was sent for case #{casa_case.case_number}\"\n\n    fr = FundRequest.last\n    aggregate_failures do\n      expect(fr.deadline).to eq \"2022-12-31\"\n      expect(fr.extra_information).to eq \"foo bar\"\n      expect(fr.impact).to eq \"provide support\"\n      expect(fr.other_funding_source_sought).to eq \"some other agency\"\n      expect(fr.payee_name).to eq \"Minnie Mouse\"\n      expect(fr.payment_amount).to eq \"100\"\n      expect(fr.request_purpose).to eq \"Fun outing\"\n      expect(fr.requested_by_and_relationship).to eq \"#{volunteer.display_name} CASA Volunteer\"\n      expect(fr.submitter_email).to eq volunteer.email\n      expect(fr.youth_name).to eq casa_case.case_number\n    end\n  end\n\n  it \"shows error when submitter email is blank\" do\n    fill_in \"Your email\", with: \"\"\n    fill_in \"Amount of payment\", with: \"100\"\n    fill_in \"Deadline\", with: \"2022-12-31\"\n    fill_in \"Request is for\", with: \"Fun outing\"\n    fill_in \"Name of payee\", with: \"Minnie Mouse\"\n\n    expect {\n      click_on \"Submit Fund Request\"\n    }.not_to change(FundRequest, :count)\n\n    expect(page).to have_content(\"Submitter email can't be blank\")\n  end\nend\n"
  },
  {
    "path": "spec/system/casa_cases/index_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"casa_cases/index\", type: :system do\n  let(:organization) { create(:casa_org) }\n  let(:volunteer) { create(:volunteer, display_name: \"Bob Loblaw\", casa_org: organization) }\n  let(:case_number) { \"CINA-1\" }\n\n  it \"is filterable and linkable\", :js do\n    organization = build(:casa_org)\n    admin = build(:casa_admin, casa_org: organization)\n    volunteer = build(:volunteer, display_name: \"Cool Volunteer\", casa_org: organization)\n    cina = build(:casa_case, active: true, casa_org: organization, case_number: case_number)\n    tpr = create(:casa_case, active: true, casa_org: organization, case_number: \"TPR-100\")\n    no_prefix = create(:casa_case, active: true, casa_org: organization, case_number: \"123-12-123\")\n    create(:case_assignment, volunteer: volunteer, casa_case: cina)\n\n    sign_in admin\n    visit casa_cases_path\n\n    expect(page).to have_link(\"Cool Volunteer\", href: \"/volunteers/#{volunteer.id}/edit\")\n    expect(page).to have_link(\"CINA-1\", href: \"/casa_cases/#{cina.case_number.parameterize}\")\n    expect(page).to have_link(\"TPR-100\", href: \"/casa_cases/#{tpr.case_number.parameterize}\")\n    expect(page).to have_link(\"123-12-123\", href: \"/casa_cases/#{no_prefix.case_number.parameterize}\")\n\n    click_on \"Status\"\n    click_on \"Assigned to Volunteer\"\n    click_on \"Assigned to more than 1 Volunteer\"\n    click_on \"Assigned to Transition Aged Youth\"\n    click_on \"Casa Case Prefix\"\n  end\n\n  it \"has a usable dropdown in sidebar\" do\n    cina = build(:casa_case, active: true, casa_org: organization, case_number: case_number)\n    create(:case_assignment, volunteer: volunteer, casa_case: cina)\n\n    sign_in volunteer\n\n    visit root_path\n    click_on \"My Cases\"\n    within \"#ddmenu_my-cases\" do\n      click_on case_number\n    end\n\n    expect(page).to have_text(\"CASA Case Details\")\n    expect(page).to have_text(\"Case number: CINA-1\")\n  end\nend\n"
  },
  {
    "path": "spec/system/casa_cases/new_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"casa_cases/new\", type: :system do\n  context \"when signed in as a Casa Org Admin\" do\n    context \"when all fields are filled\" do\n      let(:casa_org) { build(:casa_org) }\n      let(:admin) { create(:casa_admin, casa_org: casa_org) }\n      let(:contact_type_group) { create(:contact_type_group, casa_org: casa_org) }\n      let!(:contact_type) { create(:contact_type, contact_type_group: contact_type_group) }\n      let(:volunteer_display_name) { \"Test User\" }\n      let!(:supervisor) { create(:supervisor, casa_org: casa_org) }\n      let!(:volunteer) { create(:volunteer, display_name: volunteer_display_name, supervisor: supervisor, casa_org: casa_org) }\n\n      it \"is successful\", :js do\n        case_number = \"12345\"\n\n        sign_in admin\n        visit root_path\n\n        click_on \"Cases\"\n        click_on \"New Case\"\n\n        travel_to Time.zone.local(2020, 12, 1) do\n          court_date = 21.days.from_now\n          fourteen_years = (Date.today.year - 14).to_s\n          fill_in \"Case number\", with: case_number\n\n          fill_in \"Court Date\", with: court_date\n\n          select \"March\", from: \"casa_case_birth_month_year_youth_2i\"\n          select fourteen_years, from: \"casa_case_birth_month_year_youth_1i\"\n\n          select \"Submitted\", from: \"casa_case_court_report_status\"\n\n          find(\".ts-control\").click\n\n          select_all_el = page.find(\"span[data-test=select-all-input]\")\n          # uncheck all contact type options\n          select_all_el.click\n          within \".ts-dropdown-content\" do\n            expect(page).not_to have_css(\".form-check-input--checked\")\n            expect(page).to have_css(\".form-check-input--unchecked\", count: 2)\n          end\n          # check all contact type options\n          select_all_el.click\n          within \".ts-dropdown-content\" do\n            expect(page).not_to have_css(\"input.form-check-input--unchecked\")\n            expect(page).to have_css(\"input.form-check-input--checked\", count: 2)\n          end\n\n          select \"Test User\", from: \"casa_case[case_assignments_attributes][0][volunteer_id]\"\n\n          within \".top-page-actions\" do\n            click_on \"Create CASA Case\"\n          end\n\n          expect(page).to have_content(case_number)\n          expect(page).to have_content(I18n.l(court_date, format: :day_and_date))\n          expect(page).to have_content(\"CASA case was successfully created.\")\n          expect(page).not_to have_content(\"Court Report Due Date: Thursday, 1-APR-2021\") # accurate for frozen time\n          expect(page).to have_content(\"Transition Aged Youth: Yes\")\n          expect(page).to have_content(volunteer_display_name)\n        end\n      end\n    end\n\n    context \"when non-mandatory fields are not filled\" do\n      it \"is successful\", :js do\n        casa_org = build(:casa_org)\n        admin = create(:casa_admin, casa_org: casa_org)\n        contact_type_group = create(:contact_type_group, casa_org: casa_org)\n        create(:contact_type, contact_type_group: contact_type_group)\n        case_number = \"12345\"\n\n        sign_in admin\n        visit new_casa_case_path\n\n        fill_in \"Case number\", with: case_number\n        fill_in \"Next Court Date\", with: DateTime.now.next_month\n        five_years = (Date.today.year - 5).to_s\n        select \"March\", from: \"casa_case_birth_month_year_youth_2i\"\n        select five_years, from: \"casa_case_birth_month_year_youth_1i\"\n\n        within \".actions-cc\" do\n          click_on \"Create CASA Case\"\n        end\n\n        expect(page).to have_content(case_number)\n        expect(page).to have_content(\"CASA case was successfully created.\")\n        expect(page).to have_content(\"Next Court Date: \")\n        expect(page).not_to have_content(\"Court Report Due Date:\")\n        expect(page).to have_content(\"Transition Aged Youth: No\")\n      end\n    end\n\n    context \"when the case number field is not filled\" do\n      it \"does not create a new case\" do\n        casa_org = build(:casa_org)\n        admin = create(:casa_admin, casa_org: casa_org)\n\n        sign_in admin\n        visit new_casa_case_path\n        check \"casa_case_empty_court_date\"\n\n        within \".actions-cc\" do\n          click_on \"Create CASA Case\"\n        end\n\n        expect(find(\"#casa_case_empty_court_date\")).to be_checked\n        expect(page).to have_current_path(casa_cases_path, ignore_query: true)\n        expect(page).to have_content(\"Case number can't be blank\")\n      end\n    end\n\n    context \"when the court date field is not filled\" do\n      context \"when empty court date checkbox is checked\" do\n        it \"creates a new case\", :js do\n          casa_org = build(:casa_org)\n          admin = create(:casa_admin, casa_org: casa_org)\n          contact_type_group = create(:contact_type_group, casa_org: casa_org)\n          create(:contact_type, contact_type_group: contact_type_group)\n          case_number = \"12345\"\n\n          sign_in admin\n          visit new_casa_case_path\n\n          fill_in \"Case number\", with: case_number\n          five_years = (Date.today.year - 5).to_s\n          select \"March\", from: \"casa_case_birth_month_year_youth_2i\"\n          select five_years, from: \"casa_case_birth_month_year_youth_1i\"\n          check \"casa_case_empty_court_date\"\n\n          within \".actions-cc\" do\n            click_on \"Create CASA Case\"\n          end\n\n          expect(page).to have_content(case_number)\n          expect(page).to have_content(\"CASA case was successfully created.\")\n          expect(page).to have_content(\"Next Court Date:\")\n          expect(page).not_to have_content(\"Court Report Due Date:\")\n          expect(page).to have_content(\"Transition Aged Youth: No\")\n        end\n      end\n\n      context \"when empty court date checkbox is not checked\" do\n        it \"does not create a new case\", :js do\n          casa_org = build(:casa_org)\n          admin = create(:casa_admin, casa_org: casa_org)\n          contact_type_group = create(:contact_type_group, casa_org: casa_org)\n          contact_type = create(:contact_type, contact_type_group: contact_type_group)\n          case_number = \"12345\"\n\n          sign_in admin\n          visit new_casa_case_path\n\n          fill_in \"Case number\", with: case_number\n          five_years = (Date.today.year - 5).to_s\n          select \"March\", from: \"casa_case_birth_month_year_youth_2i\"\n          select five_years, from: \"casa_case_birth_month_year_youth_1i\"\n\n          # 2/14/2025 - by default, all contact types are selected on page load so don't need to manually select\n\n          within \".actions-cc\" do\n            click_on \"Create CASA Case\"\n          end\n\n          selected_contact_type = find(\".ts-control .item\").text\n\n          expect(selected_contact_type).to eq(contact_type.name)\n          expect(page).to have_current_path(casa_cases_path, ignore_query: true)\n          expect(page).to have_content(\"Court dates date can't be blank\")\n        end\n      end\n    end\n\n    context \"when the case number already exists in the organization\" do\n      it \"does not create a new case\" do\n        casa_org = build(:casa_org)\n        admin = create(:casa_admin, casa_org: casa_org)\n        case_number = \"12345\"\n        _existing_casa_case = create(:casa_case, case_number: case_number, casa_org: casa_org)\n\n        sign_in admin\n        visit new_casa_case_path\n\n        fill_in \"Case number\", with: case_number\n        within \".actions-cc\" do\n          click_on \"Create CASA Case\"\n        end\n\n        expect(page).to have_current_path(casa_cases_path, ignore_query: true)\n        expect(page).to have_content(\"Case number has already been taken\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/casa_cases/show_more_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"casa_cases/show\", :js, type: :system do\n  let(:user) { build_stubbed :casa_admin }\n\n  let(:organization) { create(:casa_org) }\n  let(:admin) { create(:casa_admin, casa_org: organization) }\n  let(:supervisor) { create(:supervisor, casa_org: organization) }\n  let(:volunteer) { create :volunteer, display_name: \"Andy Dwyer\", casa_org: organization }\n  let!(:case_assignment) { create(:case_assignment, casa_case: casa_case, volunteer: volunteer) }\n  let(:casa_case) { create(:casa_case, casa_org: organization) }\n\n  context \"user is an admin\" do\n    it \"redirects to edit volunteer page when volunteer name clicked\" do\n      sign_in admin\n      visit casa_case_path(casa_case.id)\n\n      expect(page).to have_text(\"Assigned Volunteers:\\nAndy Dwyer\")\n      expect(page).to have_link(\"Andy Dwyer\")\n\n      click_on \"Andy Dwyer\"\n\n      expect(page).to have_text(\"Editing Volunteer\")\n    end\n\n    it \"sends reminder to volunteer\" do\n      sign_in admin\n      visit casa_case_path(casa_case.id)\n\n      expect(page).to have_button(\"Send Reminder\")\n      expect(page).to have_text(\"Send CC to Supervisor and Admin\")\n\n      click_on \"Send Reminder\"\n\n      expect(page).to have_current_path(edit_volunteer_path(volunteer))\n      expect(page).to have_text(\"Reminder sent to volunteer\")\n    end\n  end\n\n  context \"user is a supervisor\" do\n    it \"sends reminder to volunteer\" do\n      sign_in supervisor\n      visit casa_case_path(casa_case.id)\n\n      expect(page).to have_button(\"Send Reminder\")\n      expect(page).to have_text(/Send CC to Supervisor and Admin$/)\n\n      click_on \"Send Reminder\"\n\n      expect(page).to have_current_path(edit_volunteer_path(volunteer))\n      expect(page).to have_text(\"Reminder sent to volunteer\")\n    end\n  end\n\n  context \"user is a volunteer\" do\n    it \"does not render a link to edit volunteer page\" do\n      sign_in volunteer\n      visit casa_case_path(casa_case.id)\n\n      expect(page).to have_text(\"Assigned Volunteers:\\nAndy Dwyer\")\n      expect(page).to have_no_link(\"Andy Dwyer\", href: volunteer_path(volunteer.id))\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/casa_cases/show_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"casa_cases/show\", type: :system do\n  include ActionView::Helpers::DateHelper\n\n  let(:organization) { create(:casa_org) }\n  let(:admin) { create(:casa_admin, casa_org: organization) }\n  let(:volunteer) { build(:volunteer, display_name: \"Bob Loblaw\", casa_org: organization) }\n  let(:casa_case) {\n    create(:casa_case, :with_one_court_order, casa_org: organization,\n      case_number: \"CINA-1\", date_in_care: date_in_care)\n  }\n  let!(:court_date) { create(:court_date, court_report_due_date: 1.month.from_now) }\n  let(:date_in_care) { 6.years.ago }\n  let!(:case_assignment) { create(:case_assignment, volunteer: volunteer, casa_case: casa_case) }\n  let!(:case_contact) { create(:case_contact, creator: volunteer, casa_case: casa_case) }\n  let!(:emancipation_categories) { create_list(:emancipation_category, 3) }\n  let!(:future_court_date) { create(:court_date, date: 21.days.from_now, casa_case: casa_case) }\n\n  before do\n    sign_in user\n    visit casa_case_path(casa_case.id)\n  end\n\n  shared_examples \"shows emancipation checklist link\" do\n    context \"when youth is in transition age\" do\n      it \"sees link to emancipation\" do\n        expect(page).to have_link(\"Emancipation 0 / #{emancipation_categories.size}\")\n      end\n    end\n\n    context \"when youth is not in transition age\" do\n      before do\n        casa_case.update!(birth_month_year_youth: DateTime.current)\n        visit casa_case_path(casa_case)\n      end\n\n      it \"does not see a link to emancipation checklist\" do\n        expect(page).not_to have_link(\"Emancipation 0 / #{emancipation_categories.size}\")\n      end\n    end\n  end\n\n  describe \"Report Generation\", :js do\n    let(:modal_selector) { '[data-bs-target=\"#generate-docx-report-modal\"]' }\n    let(:user) { volunteer }\n\n    before do\n      travel_to Date.new(2021, 1, 1)\n      sign_in user\n      visit casa_case_path(casa_case.id)\n    end\n\n    context \"when first arriving to 'Generate Court Report' page\" do\n      it \"generation modal hidden\" do\n        expect(page).to have_selector \"#btnGenerateReport\", text: \"Generate Report\", visible: false\n        expect(page).not_to have_selector \".select2\"\n      end\n    end\n\n    context \"after opening 'Download Court Report' modal\" do\n      before do\n        page.find(modal_selector).click\n      end\n\n      # putting all this in the same system test shaves 3 seconds off the test suite\n      it \"modal has correct contents\" do\n        start_date = page.find(\"#start_date\").value\n        expect(start_date).to eq(\"January 01, 2021\") # default date\n\n        end_date = page.find(\"#end_date\").value\n        expect(end_date).to eq(\"January 01, 2021\") # default date\n\n        expect(page).to have_selector \"#btnGenerateReport\", text: \"Generate Report\", visible: true\n        expect(page).not_to have_selector \".select2\"\n\n        expect(page).to have_selector(\"#btnGenerateReport .lni-download\", visible: true)\n        expect(page).not_to have_selector(\"#btnGenerateReport[disabled]\")\n        expect(page).to have_selector(\"#spinner\", visible: :hidden)\n\n        within(\"#generate-docx-report-modal\") do\n          expect(page).to have_content(casa_case.case_number)\n\n          # when choosing the prompt option (value is empty) and click on 'Generate Report' button, nothing should happen\"\n          # should have disabled generate button, download icon and no spinner\n          click_button \"Generate Report\"\n        end\n\n        wait_for_download\n        expect(download_file_name).to match(/#{casa_case.case_number}.docx/)\n      end\n    end\n  end\n\n  context \"admin user\" do\n    let(:user) { admin }\n\n    it_behaves_like \"shows court dates links\"\n    it_behaves_like \"shows emancipation checklist link\"\n\n    it \"can see case creator in table\" do\n      expect(page).to have_text(\"Bob Loblaw\")\n    end\n\n    it \"can navigate to edit volunteer page\" do\n      expect(page).to have_link(\"Bob Loblaw\", href: \"/volunteers/#{volunteer.id}/edit\")\n    end\n\n    it \"sees link to profile page\" do\n      expect(page).to have_link(href: \"/users/edit\")\n    end\n\n    it \"can see court orders\" do\n      expect(page).to have_content(\"Court Orders\")\n      expect(page).to have_content(casa_case.case_court_orders[0].text)\n      expect(page).to have_content(casa_case.case_court_orders[0].implementation_status_symbol)\n    end\n\n    it \"can see next court date\", :js do\n      expect(page).to have_content(\n        \"Next Court Date: #{I18n.l(future_court_date.date, format: :day_and_date)}\"\n      )\n    end\n\n    it \"can see the youth's Date In Care\", :js do\n      expect(page).to have_content(\n        \"Youth's Date in Care: #{I18n.l(date_in_care, format: :youth_date_of_birth)}\"\n      )\n    end\n\n    it \"can see the time since the youth's Date In Care\", :js do\n      expect(page).to have_content(\"#{time_ago_in_words(date_in_care)} ago\")\n    end\n\n    it \"can see Add to Calendar buttons\", :js do\n      expect(page).to have_content(\"Add to Calendar\")\n    end\n\n    context \"court report download link visibility\" do\n      it \"does not show download link to admin when report status is not submitted\" do\n        fixture = Rails.root.join(\"spec/fixtures/files/sample_report.docx\")\n        casa_case.court_reports.attach(\n          io: File.open(fixture),\n          filename: \"sample_report.docx\",\n          content_type: \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\"\n        )\n        casa_case.update!(court_report_status: :in_review)\n\n        visit casa_case_path(casa_case.id)\n        expect(page).not_to have_link(\"Click to download\")\n      end\n\n      it \"shows download link to admin when report status is submitted\" do\n        fixture = Rails.root.join(\"spec/fixtures/files/sample_report.docx\")\n        casa_case.court_reports.attach(\n          io: File.open(fixture),\n          filename: \"sample_report.docx\",\n          content_type: \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\"\n        )\n        casa_case.update!(court_report_status: :submitted)\n\n        visit casa_case_path(casa_case.id)\n        expect(page).to have_link(\"Click to download\")\n      end\n    end\n\n    context \"when there is no future court date or court report due date\" do\n      before do\n        casa_case = create(:casa_case, casa_org: organization)\n        visit casa_case_path(casa_case.id)\n      end\n\n      it \"can not see Add to Calendar buttons\", :js do\n        expect(page).not_to have_content(\"Add to Calendar\")\n      end\n    end\n\n    context \"when old case contacts are hidden\" do\n      it \"displays all case contacts to admin\", :js do\n        casa_case = create(:casa_case, casa_org: organization)\n        volunteer_1 = create(:volunteer, display_name: \"Volunteer 1\", casa_org: casa_case.casa_org)\n        volunteer_2 = create(:volunteer, display_name: \"Volunteer 2\", casa_org: casa_case.casa_org)\n        create(:case_assignment, casa_case: casa_case, volunteer: volunteer_1)\n        create(:case_assignment, casa_case: casa_case, volunteer: volunteer_2, active: false, hide_old_contacts: true)\n        create(:case_contact, contact_made: true, casa_case: casa_case, creator: volunteer_1, occurred_at: DateTime.now - 1)\n        create(:case_contact, contact_made: true, casa_case: casa_case, creator: volunteer_2, occurred_at: DateTime.now - 1)\n\n        visit casa_case_path(casa_case.id)\n\n        expect(page).to have_css(\"#case_contacts_list .card-content\", count: 2)\n      end\n    end\n  end\n\n  context \"supervisor user\" do\n    let(:user) { create(:supervisor, casa_org: organization) }\n    let!(:case_contact) { create(:case_contact, creator: user, casa_case: casa_case) }\n\n    it_behaves_like \"shows emancipation checklist link\"\n\n    it \"sees link to own edit page\" do\n      expect(page).to have_link(href: \"/supervisors/#{user.id}/edit\")\n    end\n\n    context \"case contact by another supervisor\" do\n      let(:other_supervisor) { create(:supervisor, casa_org: organization) }\n      let!(:case_contact) { create(:case_contact, creator: other_supervisor, casa_case: casa_case) }\n\n      it \"sees link to other supervisor\" do\n        expect(page).to have_link(href: \"/supervisors/#{other_supervisor.id}/edit\")\n      end\n    end\n\n    it \"can see court orders\" do\n      expect(page).to have_content(\"Court Orders\")\n      expect(page).to have_content(casa_case.case_court_orders[0].text)\n      expect(page).to have_content(casa_case.case_court_orders[0].implementation_status_symbol)\n    end\n\n    context \"court report download link visibility\" do\n      it \"does not show download link to supervisor when report status is not submitted\" do\n        fixture = Rails.root.join(\"spec/fixtures/files/sample_report.docx\")\n        casa_case.court_reports.attach(\n          io: File.open(fixture),\n          filename: \"sample_report.docx\",\n          content_type: \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\"\n        )\n        casa_case.update!(court_report_status: :in_review)\n\n        visit casa_case_path(casa_case.id)\n        expect(page).not_to have_link(\"Click to download\")\n      end\n\n      it \"shows download link to supervisor when report status is submitted\" do\n        fixture = Rails.root.join(\"spec/fixtures/files/sample_report.docx\")\n        casa_case.court_reports.attach(\n          io: File.open(fixture),\n          filename: \"sample_report.docx\",\n          content_type: \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\"\n        )\n        casa_case.update!(court_report_status: :submitted)\n\n        visit casa_case_path(casa_case.id)\n        expect(page).to have_link(\"Click to download\")\n      end\n    end\n\n    context \"when old case contacts are hidden\" do\n      it \"displays all case contacts to supervisor\", :js do\n        casa_case = create(:casa_case, casa_org: organization)\n        volunteer_1 = create(:volunteer, display_name: \"Volunteer 1\", casa_org: casa_case.casa_org)\n        volunteer_2 = create(:volunteer, display_name: \"Volunteer 2\", casa_org: casa_case.casa_org)\n        create(:case_assignment, casa_case: casa_case, volunteer: volunteer_1)\n        create(:case_assignment, casa_case: casa_case, volunteer: volunteer_2, active: false, hide_old_contacts: true)\n        create(:case_contact, contact_made: true, casa_case: casa_case, creator: volunteer_1, occurred_at: DateTime.now - 1)\n        create(:case_contact, contact_made: true, casa_case: casa_case, creator: volunteer_2, occurred_at: DateTime.now - 1)\n\n        visit casa_case_path(casa_case.id)\n\n        expect(page).to have_css(\"#case_contacts_list .card-content\", count: 2)\n      end\n    end\n  end\n\n  context \"volunteer user\" do\n    let(:user) { volunteer }\n\n    it_behaves_like \"shows emancipation checklist link\"\n\n    it \"can see court orders\" do\n      expect(page).to have_content(\"Court Orders\")\n      expect(page).to have_content(casa_case.case_court_orders[0].text)\n      expect(page).to have_content(casa_case.case_court_orders[0].implementation_status_symbol)\n    end\n\n    context \"when old case contacts are hidden\" do\n      before do\n        volunteer_2 = create(:volunteer, display_name: \"Volunteer 2\", casa_org: casa_case.casa_org)\n        create(:case_assignment, casa_case: casa_case, volunteer: volunteer_2, active: false, hide_old_contacts: true)\n        create(:case_contact, contact_made: true, casa_case: casa_case, creator: volunteer_2, occurred_at: DateTime.now - 1)\n      end\n\n      it \"displays only visible cases to volunteer\", :js do\n        visit casa_case_path(casa_case.id)\n        expect(page).to have_css(\"#case_contacts_list .card-content\", count: 1)\n      end\n    end\n\n    context \"when old case contacts are displayed\" do\n      before do\n        volunteer_2 = create(:volunteer, display_name: \"Volunteer 2\", casa_org: casa_case.casa_org)\n        create(:case_assignment, casa_case: casa_case, volunteer: volunteer_2, active: false, hide_old_contacts: false)\n        create(:case_contact, contact_made: true, casa_case: casa_case, creator: volunteer_2, occurred_at: DateTime.now - 1)\n      end\n\n      it \"displays all cases to the volunteer\" do\n        visit casa_case_path(casa_case.id)\n        expect(page).to have_css(\"#case_contacts_list .card-content\", count: 2)\n      end\n    end\n  end\n\n  context \"court order - implementation status symbol\" do\n    let(:user) { admin }\n\n    it \"when implemented\" do\n      casa_case.case_court_orders[0].update(implementation_status: :implemented)\n\n      visit casa_case_path(casa_case)\n\n      expect(page).to have_content(\"Court Orders\")\n      expect(page).to have_content(casa_case.case_court_orders[0].text)\n      expect(page).to have_content(\"✅\")\n    end\n\n    it \"when not implemented\" do\n      casa_case.case_court_orders[0].update(implementation_status: :unimplemented)\n\n      visit casa_case_path(casa_case)\n\n      expect(page).to have_content(\"Court Orders\")\n      expect(page).to have_content(casa_case.case_court_orders[0].text)\n      expect(page).to have_content(\"❌\")\n    end\n\n    it \"when partial implemented\" do\n      casa_case.case_court_orders[0].update(implementation_status: :partially_implemented)\n\n      visit casa_case_path(casa_case)\n\n      expect(page).to have_content(\"Court Orders\")\n      expect(page).to have_content(casa_case.case_court_orders[0].text)\n      expect(page).to have_content(\"🕗\")\n    end\n\n    it \"when not specified\" do\n      casa_case.case_court_orders[0].update(implementation_status: nil)\n\n      visit casa_case_path(casa_case)\n\n      expect(page).to have_content(\"Court Orders\")\n      expect(page).to have_content(casa_case.case_court_orders[0].text)\n      expect(page).to have_content(\"❌\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/casa_org/edit_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"casa_org/edit\", type: :system do\n  it \"can update show_driving_reimbursement flag\" do\n    organization = create(:casa_org)\n    admin = create(:casa_admin, casa_org_id: organization.id)\n\n    sign_in admin\n    visit edit_casa_org_path(organization)\n\n    uncheck \"Show driving reimbursement\"\n    click_on \"Submit\"\n    expect(page).not_to have_checked_field(\"Show driving reimbursement\")\n\n    check \"Show driving reimbursement\"\n    click_on \"Submit\"\n    expect(page).to have_checked_field(\"Show driving reimbursement\")\n  end\n\n  it \"can upload a logo image\" do\n    organization = create(:casa_org)\n    admin = create(:casa_admin, casa_org: organization)\n\n    stub_twilio\n    sign_in admin\n    visit edit_casa_org_path(organization)\n\n    page.attach_file(\"Logo\", file_fixture(\"company_logo.png\"), visible: :visible)\n\n    click_on \"Submit\"\n\n    expect(page).to have_content(\"CASA organization was successfully updated.\")\n  end\n\n  it \"hides Twilio Form if twilio is not enabled\", :js do\n    organization = create(:casa_org, twilio_enabled: true)\n    admin = create(:casa_admin, casa_org: organization)\n\n    sign_in admin\n    visit edit_casa_org_path(organization)\n\n    uncheck \"Enable Twilio\"\n    expect(page).to have_selector(\"#casa_org_twilio_account_sid\", visible: :hidden)\n    expect(page).to have_selector(\"#casa_org_twilio_api_key_sid\", visible: :hidden)\n    expect(page).to have_selector(\"#casa_org_twilio_api_key_secret\", visible: :hidden)\n    expect(page).to have_selector(\"#casa_org_twilio_phone_number\", visible: :hidden)\n  end\n\n  it \"displays Twilio Form when Enable Twilio is checked\" do\n    organization = create(:casa_org, twilio_enabled: true)\n    admin = create(:casa_admin, casa_org: organization)\n\n    sign_in admin\n    visit edit_casa_org_path(organization)\n\n    expect(page).to have_text(\"Enable Twilio\")\n    expect(page).to have_selector(\"#casa_org_twilio_account_sid\", visible: :visible)\n    expect(page).to have_selector(\"#casa_org_twilio_api_key_sid\", visible: :visible)\n    expect(page).to have_selector(\"#casa_org_twilio_api_key_secret\", visible: :visible)\n    expect(page).to have_selector(\"#casa_org_twilio_phone_number\", visible: :visible)\n  end\n\n  it \"requires Twilio Form to be filled in correctly\", :js do\n    organization = create(:casa_org, twilio_enabled: true)\n    admin = create(:casa_admin, casa_org: organization)\n\n    sign_in admin\n    visit edit_casa_org_path(organization)\n\n    fill_in \"Twilio Phone Number\", with: \"\"\n    click_on \"Submit\"\n\n    expect(page).to have_css(\"#casa_org_twilio_phone_number:invalid\")\n  end\nend\n"
  },
  {
    "path": "spec/system/case_contacts/additional_expenses_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"CaseContact AdditionalExpenses Form\", :flipper, type: :system do\n  subject do\n    visit new_case_contact_path(casa_case)\n    fill_in_contact_details(contact_types: [contact_type.name])\n    fill_in_mileage want_reimbursement: true, miles: 50, address: \"123 Params St\"\n  end\n\n  let(:casa_org) { build :casa_org, :all_reimbursements_enabled }\n  let(:volunteer) { create :volunteer, :with_single_case, casa_org: }\n  let(:casa_case) { volunteer.casa_cases.first }\n  let!(:contact_type) { create :contact_type, casa_org: }\n\n  before do\n    allow(Flipper).to receive(:enabled?).with(:show_additional_expenses).and_return(true)\n    sign_in volunteer\n  end\n\n  it \"is not rendered when casa org expenses disabled\" do\n    casa_org.update! additional_expenses_enabled: false\n\n    subject\n    check \"Request travel or other reimbursement\"\n\n    expect(page).to have_no_field(class: \"expense-amount-input\", visible: :all)\n    expect(page).to have_no_field(class: \"expense-describe-input\", visible: :all)\n    expect(page).to have_no_button(\"Add Another Expense\", visible: :all)\n  end\n\n  it \"is not shown until Reimbursement is checked and Add Another Expense clicked\", :js do\n    sign_in volunteer\n    visit new_case_contact_path casa_case\n    fill_in_contact_details\n\n    expect(page).to have_no_button \"Add Another Expense\"\n    check \"Request travel or other reimbursement\"\n    expect(page).to have_no_field(class: \"expense-amount-input\", visible: :all)\n    expect(page).to have_no_field(class: \"expense-describe-input\", visible: :all)\n    click_on \"Add Another Expense\"\n    expect(page).to have_field(class: \"expense-describe-input\")\n    expect(page).to have_field(class: \"expense-amount-input\")\n  end\n\n  it \"does not submit values if reimbursement is cancelled (unchecked)\", :js do\n    subject\n\n    click_on \"Add Another Expense\"\n    fill_expense_fields 5.34, \"Lunch\"\n    uncheck \"Request travel or other reimbursement\"\n\n    click_on \"Submit\"\n    expect(page).to have_text(\"Case contact successfully created\")\n\n    expect(CaseContact.active.count).to eq(1)\n    case_contact = CaseContact.active.last\n    expect(case_contact.additional_expenses).to be_empty\n    expect(case_contact.miles_driven).to be_zero\n    expect(case_contact.want_driving_reimbursement).to be false\n  end\n\n  it \"can remove an expense\", :js do\n    subject\n    fill_in_contact_details\n    check \"Request travel or other reimbursement\"\n    fill_in \"case_contact_miles_driven\", with: 50\n    fill_in \"case_contact_volunteer_address\", with: \"123 Params St\"\n\n    click_on \"Add Another Expense\"\n    fill_expense_fields 1.50, \"1st meal\"\n    click_on \"Add Another Expense\"\n    fill_expense_fields 2.50, \"2nd meal\"\n    click_on \"Add Another Expense\"\n    fill_expense_fields 2.00, \"3rd meal\"\n\n    within \"#contact-form-expenses\" do\n      click_on \"Delete\", match: :first\n    end\n\n    expect(page).to have_field(class: \"expense-amount-input\", count: 2)\n\n    click_on \"Submit\"\n    expect(page).to have_text(\"Case contact successfully created\")\n\n    case_contact = CaseContact.active.last\n    expect(case_contact.additional_expenses.size).to eq(2)\n    expect(CaseContact.count).to eq(1)\n    expect(AdditionalExpense.count).to eq(2)\n  end\n\n  it \"requires a description for each additional expense\", :js do\n    subject\n\n    click_on \"Add Another Expense\"\n    fill_expense_fields 5.34, nil\n\n    click_on \"Submit\"\n\n    expect(page).to have_text(\"Other Expense Details can't be blank\")\n\n    expect(CaseContact.active.count).to eq(0)\n    expect(AdditionalExpense.count).to eq(1)\n  end\n\n  context \"when editing existing case contact expenses\" do\n    subject { visit edit_case_contact_path case_contact }\n\n    let(:case_contact) { create :case_contact, :wants_reimbursement, casa_case:, creator: volunteer, contact_types: [contact_type] }\n    let!(:additional_expenses) do\n      [\n        create(:additional_expense, case_contact:, other_expense_amount: 1.11, other_expenses_describe: \"First Expense\"),\n        create(:additional_expense, case_contact:, other_expense_amount: 2.22, other_expenses_describe: \"Second Expense\")\n      ]\n    end\n\n    it \"shows existing expenses in the form\" do\n      subject\n\n      expect(page).to have_field(class: \"expense-amount-input\", count: additional_expenses.size)\n      expect(page).to have_field(class: \"expense-describe-input\", count: additional_expenses.size)\n      expect(page).to have_field(class: \"expense-amount-input\", with: \"1.11\")\n      expect(page).to have_field(class: \"expense-describe-input\", with: \"First Expense\")\n      expect(page).to have_field(class: \"expense-amount-input\", with: \"2.22\")\n      expect(page).to have_field(class: \"expense-describe-input\", with: \"Second Expense\")\n      expect(page).to have_button \"Add Another Expense\"\n    end\n\n    it \"allows removing expenses\", :js do\n      subject\n\n      expect(page).to have_css(\".expense-amount-input\", count: 2)\n      expect(page).to have_css(\".expense-describe-input\", count: 2)\n\n      within \"#contact-form-expenses\" do\n        click_on \"Delete\", match: :first\n      end\n\n      expect(page).to have_css(\".expense-amount-input\", count: 1)\n      expect(page).to have_css(\".expense-describe-input\", count: 1)\n\n      click_on \"Submit\"\n\n      expect(page).to have_text(/Case contact .* was successfully updated./)\n\n      expect(CaseContact.active.count).to eq(1)\n      expect(AdditionalExpense.count).to eq(1)\n      expect(case_contact.reload.additional_expenses.size).to eq(1)\n    end\n\n    it \"can add an expense\", :js do\n      subject\n\n      click_on \"Add Another Expense\"\n      fill_expense_fields 11.50, \"Gas\"\n      click_on \"Submit\"\n\n      expect(page).to have_text(/Case contact .* was successfully updated./)\n\n      expect(case_contact.reload.additional_expenses.size).to eq(3)\n      expect(AdditionalExpense.count).to eq(3)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/case_contacts/case_contacts_new_design_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"Case contacts new design\", type: :system, js: true do\n  let(:organization) { create(:casa_org) }\n  let(:admin) { create(:casa_admin, casa_org: organization) }\n  let(:casa_case) { create(:casa_case, casa_org: organization) }\n  let(:contact_topic) { create(:contact_topic, casa_org: organization, question: \"What was discussed?\") }\n  let!(:case_contact) do\n    create(:case_contact, :active, casa_case: casa_case, notes: \"Important follow-up needed\")\n  end\n\n  before do\n    create(:contact_topic_answer,\n      case_contact: case_contact,\n      contact_topic: contact_topic,\n      value: \"Youth is doing well in school\")\n    allow(Flipper).to receive(:enabled?).and_call_original\n    allow(Flipper).to receive(:enabled?).with(:new_case_contact_table).and_return(true)\n  end\n\n  shared_context \"signed in as admin\" do\n    before do\n      sign_in admin\n      visit case_contacts_new_design_path\n    end\n  end\n\n  describe \"New Case Contact button\" do\n    include_context \"signed in as admin\"\n\n    it \"is visible to an admin\" do\n      expect(page).to have_link(\"New Case Contact\", href: new_case_contact_path)\n    end\n\n    it \"navigates to the new case contact form when clicked as an admin\" do\n      click_link \"New Case Contact\"\n      expect(page).to have_current_path(%r{/case_contacts/\\d+/form/details})\n    end\n\n    context \"when signed in as a volunteer\" do\n      let(:volunteer) { create(:volunteer, casa_org: organization) }\n\n      before do\n        sign_in volunteer\n        visit case_contacts_new_design_path\n      end\n\n      it \"is visible to a volunteer\" do\n        expect(page).to have_link(\"New Case Contact\", href: new_case_contact_path)\n      end\n\n      it \"navigates to the new case contact form when clicked as a volunteer\" do\n        click_link \"New Case Contact\"\n        expect(page).to have_current_path(%r{/case_contacts/\\d+/form/details})\n      end\n    end\n  end\n\n  describe \"row expansion\" do\n    include_context \"signed in as admin\"\n    it \"shows the expanded content after clicking the chevron\" do\n      find(\".expand-toggle\").click\n\n      expect(page).to have_content(\"What was discussed?\")\n      expect(page).to have_content(\"Youth is doing well in school\")\n    end\n\n    it \"shows notes in the expanded content\" do\n      find(\".expand-toggle\").click\n\n      expect(page).to have_content(\"Additional Notes\")\n      expect(page).to have_content(\"Important follow-up needed\")\n    end\n\n    it \"hides the expanded content after clicking the chevron again\" do\n      find(\".expand-toggle\").click\n      expect(page).to have_content(\"Youth is doing well in school\")\n\n      find(\".expand-toggle\").click\n      expect(page).to have_no_content(\"Youth is doing well in school\")\n    end\n  end\n\n  describe \"action menu\" do\n    include_context \"signed in as admin\"\n    it \"opens the dropdown when the ellipsis button is clicked\" do\n      find(\".cc-ellipsis-toggle\").click\n\n      expect(page).to have_css(\".dropdown-menu.show\")\n    end\n\n    it \"shows Edit in the menu\" do\n      find(\".cc-ellipsis-toggle\").click\n\n      expect(page).to have_text(\"Edit\")\n    end\n\n    it \"shows Delete in the menu\" do\n      find(\".cc-ellipsis-toggle\").click\n\n      expect(page).to have_text(\"Delete\")\n    end\n\n    it \"shows Set Reminder when no followup exists\" do\n      find(\".cc-ellipsis-toggle\").click\n\n      expect(page).to have_text(\"Set Reminder\")\n      expect(page).to have_no_text(\"Resolve Reminder\")\n    end\n\n    it \"shows Resolve Reminder when a requested followup exists\" do\n      create(:followup, case_contact: case_contact, status: :requested, creator: admin)\n      visit case_contacts_new_design_path\n\n      find(\".cc-ellipsis-toggle\").click\n\n      expect(page).to have_text(\"Resolve Reminder\")\n      expect(page).to have_no_text(\"Set Reminder\")\n    end\n\n    it \"closes the dropdown when clicking outside\" do\n      find(\".cc-ellipsis-toggle\").click\n      expect(page).to have_css(\".dropdown-menu.show\")\n\n      find(\"h1\").click\n      expect(page).to have_no_css(\".dropdown-menu.show\")\n    end\n  end\n\n  describe \"Edit action\" do\n    include_context \"signed in as admin\"\n    it \"navigates to the edit form when Edit is clicked\" do\n      find(\".cc-ellipsis-toggle\").click\n      click_link \"Edit\"\n\n      expect(page).to have_current_path(/case_contacts\\/#{case_contact.id}\\/form/)\n    end\n  end\n\n  describe \"Delete action\" do\n    include_context \"signed in as admin\"\n    let(:occurred_at_text) { I18n.l(case_contact.occurred_at, format: :full) }\n\n    it \"removes the row after confirming the delete dialog\" do\n      expect(page).to have_text(occurred_at_text)\n\n      find(\".cc-ellipsis-toggle\").click\n      find(\".cc-delete-action\").click\n      click_button \"Delete\"\n\n      expect(page).to have_no_text(occurred_at_text)\n    end\n\n    it \"leaves the row in place when the delete dialog is cancelled\" do\n      expect(page).to have_text(occurred_at_text)\n\n      find(\".cc-ellipsis-toggle\").click\n      find(\".cc-delete-action\").click\n      click_button \"Cancel\"\n\n      expect(page).to have_text(occurred_at_text)\n    end\n  end\n\n  describe \"Set Reminder action\" do\n    include_context \"signed in as admin\"\n    it \"creates a followup and shows Resolve Reminder in the menu after confirming\" do\n      find(\".cc-ellipsis-toggle\").click\n      find(\".cc-set-reminder-action\").click\n      click_button \"Confirm\"\n\n      expect(page).to have_css(\"i.fas.fa-bell:not([style])\")\n\n      find(\".cc-ellipsis-toggle\").click\n      expect(page).to have_text(\"Resolve Reminder\")\n      expect(page).to have_no_text(\"Set Reminder\")\n    end\n\n    it \"does not create a followup when cancelled\" do\n      find(\".cc-ellipsis-toggle\").click\n      find(\".cc-set-reminder-action\").click\n      click_button \"Cancel\"\n\n      expect(case_contact.followups.reload).to be_empty\n    end\n  end\n\n  describe \"Resolve Reminder action\" do\n    include_context \"signed in as admin\"\n\n    let!(:followup) { create(:followup, case_contact: case_contact, status: :requested, creator: admin) }\n\n    before { visit case_contacts_new_design_path }\n\n    it \"resolves the followup and shows Set Reminder in the menu afterwards\" do\n      find(\".cc-ellipsis-toggle\").click\n      find(\".cc-resolve-reminder-action\").click\n\n      expect(page).to have_css(\"i.fas.fa-bell[style*='opacity']\")\n\n      find(\".cc-ellipsis-toggle\").click\n      expect(page).to have_text(\"Set Reminder\")\n      expect(page).to have_no_text(\"Resolve Reminder\")\n    end\n\n    it \"marks the followup as resolved\" do\n      find(\".cc-ellipsis-toggle\").click\n      find(\".cc-resolve-reminder-action\").click\n\n      # Wait for reload to confirm the AJAX completed before checking DB\n      expect(page).to have_css(\"i.fas.fa-bell[style*='opacity']\")\n\n      expect(followup.reload.status).to eq(\"resolved\")\n    end\n  end\n\n  describe \"permission states\" do\n    let(:volunteer) { create(:volunteer, casa_org: organization) }\n    let(:casa_case_for_volunteer) { create(:casa_case, casa_org: organization) }\n    let!(:active_contact) { create(:case_contact, :active, casa_case: casa_case_for_volunteer, creator: volunteer, occurred_at: 5.days.ago) }\n    let!(:draft_contact) { create(:case_contact, casa_case: casa_case_for_volunteer, creator: volunteer, status: \"started\", occurred_at: 10.days.ago) }\n\n    before do\n      sign_in volunteer\n      visit case_contacts_new_design_path\n    end\n\n    it \"shows Delete as disabled for an active contact\" do\n      find(\"#cc-actions-btn-#{active_contact.id}\").click\n      expect(page).to have_css(\".dropdown-menu[aria-labelledby='cc-actions-btn-#{active_contact.id}'].show\")\n      expect(page).to have_css(\".dropdown-menu[aria-labelledby='cc-actions-btn-#{active_contact.id}'] button.dropdown-item.disabled\", text: \"Delete\")\n    end\n\n    it \"shows Delete as enabled for a draft contact\" do\n      find(\"#cc-actions-btn-#{draft_contact.id}\").click\n      expect(page).to have_css(\".dropdown-menu[aria-labelledby='cc-actions-btn-#{draft_contact.id}'].show\")\n      expect(page).to have_css(\".dropdown-menu[aria-labelledby='cc-actions-btn-#{draft_contact.id}'] button.cc-delete-action\", text: \"Delete\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/case_contacts/contact_topic_answers_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"CaseContact form ContactTopicAnswers and notes\", type: :system do\n  subject do\n    sign_in user\n    visit new_case_contact_path(casa_case)\n  end\n\n  let(:casa_org) { create :casa_org, :all_reimbursements_enabled }\n  let(:casa_case) { volunteer.casa_cases.first }\n  let(:volunteer) { create :volunteer, :with_single_case, casa_org: }\n  let(:user) { volunteer }\n\n  let!(:contact_type) { create :contact_type, casa_org: }\n  let!(:contact_topics) { create_list :contact_topic, 2, casa_org: }\n  let(:contact_topic_questions) { contact_topics.map(&:question) }\n  let(:select_options) { contact_topic_questions + [\"Select a discussion topic\"] }\n\n  let(:topic_select_class) { \"contact-topic-id-select\" }\n  let(:topic_answer_input_class) { \"contact-topic-answer-input\" }\n\n  let(:autosave_alert_div) { \"#contact-form-notes\" }\n  let(:autosave_alert_css) { 'small[role=\"alert\"]' }\n  let(:autosave_alert_text) { \"Saved!\" }\n\n  def notes_section\n    page.find_by_id(\"contact-form-notes\")\n  end\n\n  it \"shows a topic form when page is loaded and lists all contact topics\" do\n    subject\n\n    expect(notes_section).to have_field(class: topic_select_class)\n    expect(notes_section).to have_field(class: topic_answer_input_class)\n    expect(notes_section).to have_select(class: topic_select_class, with_options: contact_topic_questions)\n  end\n\n  it \"adds contact answers for the topics\", :js do\n    subject\n    fill_in_contact_details(contact_types: [contact_type.name])\n\n    topic_one = contact_topics.first\n    topic_two = contact_topics.last\n\n    fill_in_notes(contact_topic_answers_attrs: [\n      {question: topic_one.question, answer: \"First discussion topic answer.\"},\n      {question: topic_two.question, answer: \"Second discussion topic answer.\"}\n    ])\n\n    click_on \"Submit\"\n    expect(page).to have_content(\"Case contact successfully created.\")\n    expect(CaseContact.active.size).to eq 1\n\n    case_contact = CaseContact.active.last\n    expect(case_contact.reload.contact_topic_answers).to be_present\n    expect(case_contact.reload.contact_topic_answers.size).to eq 2\n    first_topic_answer = case_contact.contact_topic_answers.find_by(contact_topic_id: topic_one.id)\n    second_topic_answer = case_contact.contact_topic_answers.find_by(contact_topic_id: topic_two.id)\n    expect(first_topic_answer.value).to eq \"First discussion topic answer.\"\n    expect(second_topic_answer.value).to eq \"Second discussion topic answer.\"\n  end\n\n  it \"does not add multiple records for the same answer due to autosave\", :js do\n    subject\n    fill_in_contact_details(contact_types: [contact_type.name])\n\n    answer_topic contact_topics.first.question, \"First discussion topic answer.\"\n    within notes_section do\n      expect(page).to have_text \"Saved\"  # autosave success alert\n      expect(page).to have_no_text \"Saved\" # wait for clearing of alert\n    end\n    answer_topic contact_topics.first.question, \"Changing the first topic answer.\"\n    within notes_section { expect(page).to have_text \"Saved\" }\n\n    click_on \"Submit\"\n    expect(page).to have_content(\"Case contact successfully created.\")\n\n    expect(CaseContact.active.count).to eq(1)\n    expect(ContactTopicAnswer.count).to eq(1)\n    case_contact = CaseContact.active.last\n    created_answer = ContactTopicAnswer.last\n    expect(created_answer.contact_topic).to eq(contact_topics.first)\n    expect(created_answer.value).to eq \"Changing the first topic answer.\"\n    expect(case_contact.contact_topic_answers.size).to eq 1\n    expect(case_contact.contact_topic_answers).to include created_answer\n  end\n\n  it \"prevents adding more answers than topics\", :js do\n    subject\n\n    (contact_topics.size - 1).times do\n      click_on \"Add Another Discussion Topic\"\n    end\n\n    expect(notes_section).to have_button(\"Add Another Discussion Topic\", disabled: true)\n  end\n\n  it \"disables contact topics that are already selected\", :js do\n    subject\n\n    topic_one_question = contact_topics.first.question\n    answer_topic topic_one_question, \"First discussion topic answer.\"\n\n    expect(notes_section).to have_select(class: topic_select_class, count: 1)\n    expect(notes_section).to have_no_select(class: topic_select_class, disabled_options: [topic_one_question])\n    click_on \"Add Another Discussion Topic\"\n    expect(notes_section).to have_select(class: topic_select_class, count: 2)\n    expect(notes_section).to have_select(class: topic_select_class, disabled_options: [topic_one_question], count: 1)\n  end\n\n  context \"when casa org has no contact topics\" do\n    let(:contact_topics) { [] }\n\n    it \"displays a field for contact.notes\", :js do\n      subject\n      expect(page).to have_no_button \"Add Another Discussion Topic\"\n      expect(notes_section).to have_field \"Additional Notes\"\n\n      fill_in_contact_details\n      fill_in \"Additional Notes\", with: \"This is a note.\"\n\n      click_on \"Submit\"\n      expect(page).to have_content(\"Case contact successfully created.\")\n\n      contact = CaseContact.active.last\n      expect(CaseContact.active.count).to eq(1)\n      expect(contact.contact_topic_answers).to be_empty\n      expect(contact.notes).to eq \"This is a note.\"\n    end\n\n    it \"saves 'Additional Notes' answer as contact.notes\", :js do\n      subject\n      fill_in_contact_details(contact_types: [contact_type.name])\n\n      fill_in \"Additional Notes\", with: \"This is a fake a topic answer.\"\n\n      click_on \"Submit\"\n      expect(page).to have_text(\"Case contact successfully created\")\n\n      contact = CaseContact.active.last\n      expect(CaseContact.active.count).to eq(1)\n      expect(contact.contact_topic_answers).to be_empty\n      expect(contact.notes).to eq \"This is a fake a topic answer.\"\n    end\n  end\n\n  context \"when editing an existing case contact\" do\n    subject do\n      sign_in user\n      visit edit_case_contact_path(case_contact)\n    end\n\n    let(:case_contact) { create :case_contact, casa_case:, creator: user }\n\n    context \"when there are existing contact topic answers\" do\n      let(:topic_one) { contact_topics.first }\n      let!(:answer_one) { create :contact_topic_answer, contact_topic: topic_one, case_contact: }\n      let(:topic_two) { contact_topics.second }\n      let!(:answer_two) { create :contact_topic_answer, contact_topic: topic_two, case_contact: }\n\n      it \"fills inputs with the answers\" do\n        subject\n\n        expect(notes_section).to have_select(class: topic_select_class, count: 2)\n        expect(notes_section).to have_field(class: topic_answer_input_class, count: 2)\n\n        expect(notes_section).to have_field(class: topic_answer_input_class, with: answer_one.value)\n        expect(notes_section).to have_field(class: topic_answer_input_class, with: answer_two.value)\n\n        expect(notes_section).to have_select(\n          class: topic_select_class, selected: topic_one.question, options: select_options\n        )\n        expect(notes_section).to have_select(\n          class: topic_select_class, selected: topic_two.question, options: select_options\n        )\n      end\n\n      it \"can remove an existing answer\", :js do\n        subject\n        fill_in_contact_details\n\n        expect(notes_section).to have_select(class: topic_select_class, count: 2)\n\n        accept_confirm do\n          notes_section.find_button(text: \"Delete\", match: :first).click\n        end\n\n        expect(notes_section).to have_select(class: topic_select_class, count: 1, visible: :all)\n\n        click_on \"Submit\"\n        expect(page).to have_content(/Case contact .* was successfully updated./)\n\n        case_contact.reload\n        expect(ContactTopicAnswer.count).to eq(1)\n        expect(case_contact.contact_topic_answers.size).to eq(1)\n      end\n    end\n\n    it \"autosaves form with answer inputs\", :js do\n      subject\n\n      fill_in_contact_details(\n        contact_made: false, medium: \"In Person\", occurred_on: 1.day.ago.to_date, hours: 1, minutes: 5\n      )\n\n      click_on \"Add Another Discussion Topic\"\n      answer_topic contact_topics.first.question, \"Topic One answer.\"\n      within autosave_alert_div do\n        find(autosave_alert_css, text: autosave_alert_text, wait: 3)\n      end\n\n      expect(page).to have_content(\"Editing Existing Case Contact\")\n\n      expect(CaseContact.count).to eq(1)\n      case_contact = CaseContact.last\n      expect(case_contact.casa_case).to eq casa_case\n      expect(ContactTopicAnswer.count).to eq(1)\n      expect(case_contact.contact_topic_answers.size).to eq(1)\n      expect(case_contact.contact_topic_answers.last.value).to eq \"Topic One answer.\"\n\n      expect(case_contact.contact_made).to be false\n      expect(case_contact.medium_type).to eq \"in-person\"\n      expect(case_contact.duration_minutes).to eq 65\n      expect(case_contact.occurred_at).to eq 1.day.ago.to_date\n    end\n\n    context \"when contact notes exist\" do\n      let(:notes) { \"This was previously saved as 'case_contact.notes'.\" }\n\n      before { case_contact.update! notes: }\n\n      it \"presents an 'Additional Notes' field\" do\n        subject\n\n        expect(notes_section).to have_field(\"Additional Notes\", with: case_contact.notes)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/case_contacts/drafts_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"case_contacts/drafts\", type: :system do\n  let(:organization) { build(:casa_org) }\n  let(:admin) { build(:casa_admin, casa_org: organization) }\n\n  context \"with case contacts\" do\n    let!(:casa_case) { build(:casa_case, casa_org: organization) }\n    let!(:other_org_case) { build(:case_contact, notes: \"NOTE_A\") }\n    let!(:past_contact) { build(:case_contact, casa_case: casa_case, occurred_at: 3.weeks.ago, notes: \"NOTE_B\") }\n    let!(:past_contact_draft) { create(:case_contact, :started_status, casa_case: casa_case, occurred_at: 3.weeks.ago, notes: \"NOTE_C\") }\n    let!(:recent_contact) { build(:case_contact, casa_case: casa_case, occurred_at: 3.days.ago, notes: \"NOTE_D\") }\n    let!(:recent_contact_draft) { create(:case_contact, :started_status, casa_case: casa_case, occurred_at: 3.days.ago, notes: \"NOTE_E\") }\n\n    it \"shows only same orgs drafts\" do\n      sign_in admin\n\n      visit case_contacts_drafts_path\n\n      expect(page).not_to have_content(\"NOTE_A\")\n      expect(page).not_to have_content(\"NOTE_B\")\n      expect(page).to have_content(\"NOTE_C\")\n      expect(page).not_to have_content(\"NOTE_D\")\n      expect(page).to have_content(\"NOTE_E\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/case_contacts/edit_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"case_contacts/edit\", type: :system do\n  let(:organization) { build(:casa_org, :all_reimbursements_enabled) }\n  let(:volunteer) { create(:volunteer, :with_single_case, casa_org: organization) }\n  let(:casa_case) { volunteer.casa_cases.first }\n  let(:contact_types) { create_list(:contact_type, 2, casa_org: organization) }\n  let(:case_contact) do\n    create(:case_contact, duration_minutes: 105, casa_case:, creator: volunteer, contact_types: [contact_types.first])\n  end\n\n  let(:user) { volunteer }\n\n  before { sign_in user }\n\n  context \"when admin\" do\n    let(:admin) { create(:casa_admin, casa_org: organization) }\n\n    let(:user) { admin }\n\n    it \"successfully edits case contact\", :js do\n      visit edit_case_contact_path(case_contact)\n\n      complete_details_page(case_numbers: [], contact_types: [], contact_made: true, medium: \"Letter\")\n      complete_notes_page\n      fill_in_expenses_page\n\n      click_on \"Submit\"\n\n      expect(page).to have_text \"Case contact created at #{case_contact.created_at.strftime(\"%-I:%-M %p on %m-%e-%Y\")}, was successfully updated.\"\n      case_contact.reload\n      expect(case_contact.casa_case_id).to eq casa_case.id\n      expect(case_contact.duration_minutes).to eq 105\n      expect(case_contact.medium_type).to eq \"letter\"\n      expect(case_contact.contact_made).to be true\n    end\n\n    it \"successfully edits case contact with mileage reimbursement\", :js do\n      visit edit_case_contact_path(case_contact)\n\n      complete_details_page(case_numbers: [], contact_types: [], contact_made: true, medium: \"In Person\", hours: 1, minutes: 45, occurred_on: Date.new(2020, 4, 4))\n      complete_notes_page\n      fill_in_expenses_page(miles: 10, want_reimbursement: true, address: \"123 str\")\n\n      click_on \"Submit\"\n      expect(page).to have_text \"Case contact created at #{case_contact.created_at.strftime(\"%-I:%-M %p on %m-%e-%Y\")}, was successfully updated.\"\n      case_contact.reload\n      volunteer.address&.reload\n      expect(case_contact.casa_case.volunteers[0]).to eq volunteer\n      expect(volunteer.address&.content).to eq \"123 str\"\n      expect(case_contact.casa_case_id).to eq casa_case.id\n      expect(case_contact.duration_minutes).to eq 105\n      expect(case_contact.medium_type).to eq \"in-person\"\n      expect(case_contact.contact_made).to be true\n    end\n\n    it \"does not allow volunteer address edit for case contact with ambiguous volunteer\" do\n      create(:case_assignment, casa_case:, volunteer: build(:volunteer, casa_org: organization))\n      case_contact.update!(creator: admin)\n      expect(casa_case.volunteers).not_to include case_contact.creator\n      expect(casa_case.volunteers.size).to be > 1\n\n      visit edit_case_contact_path(case_contact)\n\n      complete_details_page(case_numbers: [], contact_types: [], contact_made: true, medium: \"In Person\", hours: 1, minutes: 45, occurred_on: \"04/04/2020\")\n\n      check \"Request travel or other reimbursement\"\n      expect(page).to have_field(\"case_contact_volunteer_address\", disabled: true)\n      expect(page).to have_text(\"There are two or more volunteers assigned to this case and you are trying to set the address for both of them. This is not currently possible.\")\n    end\n\n    context \"when user is part of a different organization\" do\n      let(:other_organization) { build(:casa_org) }\n      let(:admin) { create(:casa_admin, casa_org: other_organization) }\n\n      it \"fails across organizations\" do\n        visit edit_case_contact_path(case_contact)\n        expect(page).to have_current_path supervisors_path, ignore_query: true\n      end\n    end\n  end\n\n  it \"can update case contact attributes\", :js do\n    expect(case_contact.active?).to be true\n    expect(case_contact.contact_types).to contain_exactly(contact_types.first)\n    expect(case_contact.contact_made).to be false\n    expect(case_contact.medium_type).to eq \"in-person\"\n    expect(case_contact.duration_minutes).to eq 105\n    expect(case_contact.volunteer_address).to be_blank\n    expect(case_contact.notes).to be_blank\n    expect(case_contact.contact_topic_answers).to be_empty\n    expect(case_contact.miles_driven).to be_zero\n    expect(case_contact.want_driving_reimbursement).to be false\n    expect(case_contact.additional_expenses).to be_empty\n\n    contact_topic = create(:contact_topic, casa_org: organization)\n\n    visit edit_case_contact_path(case_contact)\n\n    complete_details_page(\n      case_numbers: [], contact_made: true, medium: \"Letter\",\n      occurred_on: Time.zone.today - 5.days, hours: 1, minutes: 5\n    )\n    uncheck contact_types.first.name\n    check contact_types.second.name\n    click_on \"Add Another Discussion Topic\"\n    answer_topic contact_topic.question, \"Topic 1 Answer.\"\n    fill_in_expenses_page(miles: 50, want_reimbursement: true, address: \"123 Form St\")\n    click_on \"Submit\"\n\n    expect(page).to have_text \"Case contact created at #{case_contact.created_at.strftime(\"%-I:%-M %p on %m-%e-%Y\")}, was successfully updated.\"\n    case_contact.reload\n    case_contact.contact_topic_answers&.reload\n    expect(case_contact.duration_minutes).to eq 65\n    expect(case_contact.medium_type).to eq \"letter\"\n    expect(case_contact.contact_made).to be true\n    expect(case_contact.contact_types).to contain_exactly(contact_types.second)\n    # notes\n    expect(case_contact.contact_topic_answers).to be_present\n    expect(case_contact.contact_topic_answers.first.contact_topic).to eq contact_topic\n    expect(case_contact.contact_topic_answers.first.value).to eq \"Topic 1 Answer.\"\n    # reimbursement\n    expect(case_contact.miles_driven).to eq 50\n    expect(case_contact.want_driving_reimbursement).to be true\n    expect(case_contact.volunteer_address).to eq \"123 Form St\"\n  end\n\n  it \"is successful with mileage reimbursement on\", :js do\n    visit edit_case_contact_path(case_contact)\n\n    complete_details_page(contact_made: true, medium: \"In Person\", hours: 1, minutes: 45, occurred_on: Date.new(2020, 4, 4))\n    complete_notes_page\n    fill_in_expenses_page(miles: 10, want_reimbursement: true, address: \"123 str\")\n\n    click_on \"Submit\"\n\n    expect(page).to have_text \"Case contact created at #{case_contact.created_at.strftime(\"%-I:%-M %p on %m-%e-%Y\")}, was successfully updated.\"\n    case_contact.reload\n    volunteer.reload\n    expect(volunteer.address.content).to eq \"123 str\"\n    expect(case_contact.casa_case_id).to eq casa_case.id\n    expect(case_contact.duration_minutes).to eq 105\n    expect(case_contact.medium_type).to eq \"in-person\"\n    expect(case_contact.contact_made).to be true\n  end\n\n  it \"autosaves notes\", :js do\n    autosave_alert_div = \"#contact-form-notes\"\n    autosave_alert_css = 'small[role=\"alert\"]'\n    autosave_alert_text = \"Saved!\"\n\n    case_contact = create(:case_contact, duration_minutes: 105, casa_case: casa_case, creator: volunteer, notes: \"Hello from the other side\")\n    visit edit_case_contact_path(case_contact)\n\n    complete_details_page(contact_made: true)\n    expect(case_contact.reload.notes).to eq \"Hello from the other side\"\n\n    fill_in \"Additional Notes\", with: \"Hello world\"\n\n    within autosave_alert_div do\n      find(autosave_alert_css, text: autosave_alert_text)\n    end\n    expect(case_contact.reload.notes).to eq \"Hello world\"\n  end\n\n  context \"when 'Create Another' option is checked\" do\n    it \"creates a duplicate case contact for the second contact\", :js do\n      case_contact_draft_ids = case_contact.draft_case_ids\n      visit edit_case_contact_path(case_contact)\n\n      check \"Create Another\"\n\n      click_on \"Submit\"\n\n      expect(page).to have_text(\"successfully updated\")\n      expect(page).to have_text \"New Case Contact\"\n      expect(page).to have_text casa_case.case_number\n\n      expect(CaseContact.started.count).to eq(1)\n      new_case_contact = CaseContact.last\n      expect(new_case_contact.draft_case_ids).to match_array(case_contact_draft_ids)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/case_contacts/followups/create_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"followups/create\", :js, type: :system do\n  let(:admin) { create(:casa_admin) }\n  let(:case_contact) { create(:case_contact) }\n  let(:note) { \"Lorem ipsum dolor sit amet.\" }\n\n  describe \"Creating a followup\" do\n    before do\n      sign_in admin\n      visit casa_case_path(case_contact.casa_case)\n\n      click_button \"Make Reminder\"\n    end\n\n    it \"displays correct prompt\" do\n      expect(page).to have_content(\"Optional: Add a note about what followup is needed.\")\n    end\n\n    context \"when confirming the Swal alert\" do\n      it \"creates a followup with a note when the note textarea is filled\" do\n        find(\".swal2-textarea\").set(note)\n\n        click_button \"Confirm\"\n\n        expect(page).to have_button(\"Resolve Reminder\")\n\n        case_contact.followups.reload\n\n        expect(case_contact.followups.count).to eq(1)\n        expect(case_contact.followups.last.note).to eq(note)\n      end\n\n      it \"creates a followup without a note when the note textarea is empty\" do\n        click_button \"Confirm\"\n\n        expect(page).to have_button(\"Resolve Reminder\")\n\n        case_contact.followups.reload\n\n        expect(case_contact.followups.count).to eq(1)\n        expect(case_contact.followups.last.note).to be_nil\n      end\n    end\n\n    context \"when cancelling the Swal alert\" do\n      it \"does nothing when there is text in the note textarea\" do\n        find(\".swal2-textarea\").set(note)\n\n        click_button \"Cancel\"\n\n        expect(page).not_to have_text(note)\n\n        expect(case_contact.followups.reload.count).to be_zero\n      end\n\n      it \"does nothing when there is no text in the note textarea\" do\n        click_button \"Cancel\"\n\n        expect(page).not_to have_text(note)\n\n        expect(case_contact.followups.reload.count).to be_zero\n      end\n    end\n\n    context \"when closing the Swal alert\" do\n      it \"does nothing when there is text in the note textarea\" do\n        find(\".swal2-textarea\").set(note)\n\n        find(\".swal2-close\").click\n\n        expect(page).not_to have_text(note)\n\n        expect(case_contact.followups.reload.count).to be_zero\n      end\n\n      it \"does nothing when there is no text in the note textarea\" do\n        find(\".swal2-close\").click\n\n        expect(page).not_to have_text(note)\n\n        expect(case_contact.followups.reload.count).to be_zero\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/case_contacts/followups/resolve_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"followups/resolve\", type: :system do\n  let(:casa_org) { build(:casa_org) }\n  let(:admin) { build(:casa_admin, casa_org: casa_org) }\n  let(:supervisor) { build(:supervisor, casa_org: casa_org) }\n  let(:volunteer) { build(:volunteer, casa_org: casa_org) }\n  let(:casa_case) { create(:casa_case, casa_org: casa_org) }\n  let(:cc_creator) { admin }\n  let(:followup_creator) { volunteer }\n  let(:case_contact) { build(:case_contact, casa_case: casa_case, creator: cc_creator) }\n  let!(:followup) { create(:followup, case_contact: case_contact, creator: followup_creator) }\n\n  before { sign_in admin }\n\n  it \"changes status of followup to resolved\" do\n    visit casa_case_path(case_contact.casa_case)\n\n    click_button \"Resolve Reminder\"\n    expect(page).to have_button(\"Make Reminder\")\n\n    expect(case_contact.followups.count).to eq(1)\n    expect(case_contact.followups.first.resolved?).to be_truthy\n  end\n\n  context \"logged in as admin, followup created by volunteer\" do\n    let(:cc_creator) { volunteer }\n    let(:followup_creator) { volunteer }\n\n    before { sign_in admin }\n\n    it \"changes status of followup to resolved\" do\n      visit casa_case_path(case_contact.casa_case)\n\n      click_button \"Resolve Reminder\"\n      expect(page).to have_button(\"Make Reminder\")\n\n      expect(case_contact.followups.count).to eq(1)\n      expect(case_contact.followups.first.resolved?).to be_truthy\n    end\n\n    it \"removes followup icon and button changes back to 'Make Reminder'\" do\n      visit casa_case_path(case_contact.casa_case)\n\n      click_button \"Resolve Reminder\"\n      expect(page).to have_button(\"Make Reminder\")\n    end\n  end\n\n  context \"logged in as supervisor, followup created by volunteer\" do\n    let(:cc_creator) { supervisor }\n    let(:followup_creator) { volunteer }\n\n    before { sign_in supervisor }\n\n    it \"changes status of followup to resolved\" do\n      visit casa_case_path(case_contact.casa_case)\n\n      click_button \"Resolve Reminder\"\n      expect(page).to have_button(\"Make Reminder\")\n\n      expect(case_contact.followups.count).to eq(1)\n      expect(case_contact.followups.first.resolved?).to be_truthy\n    end\n  end\n\n  context \"logged in as volunteer, followup created by admin\" do\n    let(:cc_creator) { volunteer }\n    let(:followup_creator) { admin }\n\n    before do\n      case_contact.casa_case.assigned_volunteers << volunteer\n      sign_in volunteer\n    end\n\n    it \"changes status of followup to resolved\" do\n      visit case_contacts_path\n\n      click_button \"Resolve Reminder\"\n      expect(page).to have_button(\"Make Reminder\")\n\n      expect(case_contact.followups.count).to eq(1)\n      expect(case_contact.followups.first.resolved?).to be_truthy\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/case_contacts/index_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"case_contacts/index\", type: :system do\n  subject { visit case_contacts_path }\n\n  let(:volunteer) { build(:volunteer, display_name: \"Bob Loblaw\", casa_org: organization) }\n  let(:organization) { build(:casa_org) }\n\n  before { sign_in volunteer }\n\n  context \"with case contacts\" do\n    let(:case_number) { \"CINA-1\" }\n    let(:casa_case) { build(:casa_case, casa_org: organization, case_number: case_number) }\n    let!(:case_assignment) { create(:case_assignment, volunteer: volunteer, casa_case: casa_case) }\n\n    context \"without filter\" do\n      it \"can see case creator in card\" do\n        create(:case_contact, creator: volunteer, casa_case: casa_case, occurred_at: 2.days.ago)\n\n        subject\n\n        within(\".full-card\", match: :first) do\n          expect(page).to have_text(\"Bob Loblaw\")\n        end\n      end\n\n      it \"can navigate to edit volunteer page\" do\n        subject\n\n        expect(page).to have_no_link(\"Bob Loblaw\")\n      end\n\n      it \"allows the volunteer to delete a draft they created\" do\n        create(:case_contact, creator: volunteer, casa_case: casa_case, occurred_at: 2.days.ago)\n        create(:case_contact, :started_status, creator: volunteer, casa_case: casa_case, occurred_at: 3.days.ago,\n          contact_types: [build(:contact_type, name: \"DRAFT Case Contact\")])\n\n        subject\n\n        card = find(\".container-fluid.mb-1\", text: \"DRAFT Case Contact\")\n        expect(card).not_to be_nil\n\n        within_element(card) do\n          expect(card).to have_text(\"Draft\")\n          click_on \"Delete\"\n        end\n\n        expect(page).to have_no_css(\".container-fluid.mb-1\", text: \"DRAFT Case Contact\")\n      end\n\n      it \"displays the contact type groups\" do\n        create(:case_contact, creator: volunteer, casa_case: casa_case, occurred_at: Time.zone.now,\n          contact_types: [build(:contact_type, name: \"Most Recent Case Contact\")])\n        create(:case_contact, :started_status, creator: volunteer, casa_case: casa_case, occurred_at: 3.days.ago,\n          contact_types: [build(:contact_type, name: \"DRAFT Case Contact\")])\n\n        subject\n\n        expect(page).to have_text(\"Most Recent Case Contact\")\n        expect(page).to have_text(\"DRAFT Case Contact\")\n      end\n    end\n\n    describe \"automated filtering case contacts\" do\n      describe \"by date of contact\" do\n        it \"only shows the contacts with the correct date\", :js do\n          yesterday = Time.zone.yesterday\n          day_before_yesterday = yesterday - 1.day\n          today = Time.zone.today\n          create(:case_contact, creator: volunteer, casa_case: casa_case, occurred_at: day_before_yesterday)\n          create(:case_contact, creator: volunteer, casa_case: casa_case, occurred_at: yesterday)\n          create(:case_contact, creator: volunteer, casa_case: casa_case, occurred_at: today)\n          subject\n\n          click_on \"Expand / Hide\"\n\n          yesterday_display = I18n.l(yesterday, format: :full, default: nil)\n          day_before_yesterday_display = I18n.l(day_before_yesterday, format: :full, default: nil)\n          today_display = I18n.l(today, format: :full, default: nil)\n          expect(page).to have_content day_before_yesterday_display\n          expect(page).to have_content yesterday_display\n          expect(page).to have_content today_display\n\n          fill_in \"filterrific_occurred_starting_at\", with: yesterday\n          fill_in \"filterrific_occurred_ending_at\", with: Time.zone.tomorrow\n\n          expect(page).to have_no_content day_before_yesterday_display\n          expect(page).to have_content yesterday_display\n          expect(page).to have_content today_display\n        end\n      end\n\n      describe \"by casa_case_id\" do\n        subject { visit case_contacts_path(casa_case_id: casa_case.id) }\n\n        let!(:other_casa_case) { build(:casa_case, casa_org: organization, case_number: \"CINA-2\") }\n\n        it \"displays the draft\" do\n          create(:case_contact, :details_status, creator: volunteer, draft_case_ids: [casa_case.id])\n\n          subject\n\n          expect(page).to have_no_content \"You have no case contacts for this case.\"\n          expect(page).to have_content \"Draft\"\n        end\n\n        it \"only displays the filtered case\" do\n          subject\n\n          expect(page).to have_no_content other_casa_case.case_number\n          expect(page).to have_content casa_case.case_number\n        end\n      end\n\n      describe \"by hide drafts\" do\n        it \"does not show draft contacts\", :js do\n          build(:case_contact, creator: volunteer, casa_case: casa_case)\n          build(:case_contact, :started_status, creator: volunteer, casa_case: casa_case)\n          subject\n\n          check \"Hide drafts\"\n\n          expect(page).to have_no_content \"Draft\"\n        end\n      end\n\n      describe \"collapsing filter menu\" do\n        before do\n          subject\n        end\n\n        it \"displays sticky filters before clicking expand\" do\n          expect(page).to have_field \"Hide drafts\", type: :checkbox\n        end\n\n        it \"does not expand menu when filtering only by sticky filter\", :js do\n          check \"Hide drafts\"\n\n          expect(page).to have_field \"Hide drafts\", type: :checkbox\n          expect(page).to have_no_content \"Other filters\"\n        end\n\n        it \"displays other filters when expanded\" do\n          click_on \"Expand / Hide\"\n\n          expect(page).to have_content \"Other filters\"\n        end\n\n        it \"does not hide menu when filtering by placement filter\" do\n          click_on \"Expand / Hide\"\n          select \"In Person\", from: \"Contact medium\"\n\n          expect(page).to have_content \"Other filters\"\n        end\n      end\n    end\n\n    describe \"case contacts text color\" do\n      let(:contact_group_text) { case_contact.contact_groups_with_types.keys.first }\n\n      context \"with active case contact\" do\n        let!(:case_contact) { create(:case_contact, creator: volunteer, casa_case: casa_case, occurred_at: Time.zone.yesterday) }\n\n        it \"displays correct color for contact\" do\n          subject\n\n          within \".card-title\" do\n            title = find(\"strong.text-primary\")\n            expect(title).to have_content(contact_group_text)\n          end\n        end\n      end\n    end\n\n    it \"can show only case contacts for one case\", :js do\n      yesterday = Time.zone.yesterday\n      day_before_yesterday = yesterday - 1.day\n      today = Time.zone.today\n      create(:case_contact, creator: volunteer, casa_case: casa_case, notes: \"Case 1 Notes\", occurred_at: day_before_yesterday)\n\n      another_case_number = \"CINA-2\"\n      another_case = build(:casa_case, casa_org: organization, case_number: another_case_number)\n      create(:case_assignment, volunteer: volunteer, casa_case: another_case)\n      create(:case_contact, creator: volunteer, casa_case: another_case, notes: \"Case 2 Notes\", occurred_at: today)\n\n      # showing all cases\n      visit root_path\n      click_on \"Case Contacts\"\n      within \"#ddmenu_case-contacts\" do\n        click_on \"All\"\n      end\n      expect(page).to have_text(\"Case 1 Notes\")\n      expect(page).to have_text(\"Case 2 Notes\")\n\n      # showing case 1\n      visit root_path\n      click_on \"Case Contacts\"\n      within \"#ddmenu_case-contacts\" do\n        click_on case_number\n      end\n      expect(page).to have_text(\"Case 1 Notes\")\n      expect(page).to have_no_text(\"Case 2 Notes\")\n\n      # showing case 2\n      visit root_path\n      click_on \"Case Contacts\"\n      within \"#ddmenu_case-contacts\" do\n        click_on another_case_number\n      end\n      expect(page).to have_text(\"Case 2 Notes\")\n      expect(page).to have_no_text(\"Case 1 Notes\")\n\n      # filtering to only show case 2\n      click_on \"Expand / Hide\"\n      fill_in \"filterrific_occurred_starting_at\", with: yesterday\n      fill_in \"filterrific_occurred_ending_at\", with: Time.zone.tomorrow\n\n      expect(page).to have_text(\"Case 2 Notes\")\n      expect(page).to have_no_text(\"Case 1 Notes\")\n\n      visit root_path\n      click_on \"Case Contacts\"\n      within \"#ddmenu_case-contacts\" do\n        click_on case_number\n      end\n      click_on \"Expand / Hide\"\n      fill_in \"filterrific_occurred_starting_at\", with: yesterday\n      fill_in \"filterrific_occurred_ending_at\", with: Time.zone.tomorrow\n\n      # no contacts because we're only showing case 1 and that occurred before the filter dates\n      expect(page).to have_no_text(\"Case 1 Notes\")\n      expect(page).to have_no_text(\"Case 2 Notes\")\n    end\n\n    describe \"contact notes\" do\n      let(:contact_topics) { build_list(:contact_topic, 2, casa_org: organization) }\n      let(:contact_topic) { contact_topics.first }\n      let(:case_contact) { build(:case_contact, casa_case:, creator: volunteer) }\n      let!(:contact_topic_answer) do\n        create(:contact_topic_answer, case_contact:, contact_topic:)\n      end\n\n      let(:user) { volunteer }\n\n      before { sign_in user }\n\n      it \"show topic answers and allows expanding to show full answer\", :js do\n        subject\n\n        expect(page).to have_text contact_topics.first.question\n        expect(page).to have_no_text contact_topics.first.details\n\n        within(\".truncation-container\", match: :first) do\n          expect(page).to have_content(contact_topic.question)\n          # capybara has trouble with css `overflow: hidden` truncated text. just test class is applied/not\n          expect(page).to have_css(\".line-clamp-1\", text: contact_topic_answer.value)\n          click_on \"read more\"\n          expect(page).to have_no_css(\".line-clamp-1\")\n          expect(page).to have_content(contact_topic_answer.value)\n          expect(page).to have_no_content contact_topics.first.details\n        end\n      end\n    end\n  end\n\n  context \"without case contacts\" do\n    it \"shows helper text\" do\n      subject\n      expect(page).to have_text(\"You have no case contacts for this case. Please click New Case Contact button above to create a case contact for your youth!\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/case_contacts/new_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"case_contacts/new\", type: :system do\n  subject { visit new_case_contact_path casa_case }\n\n  let(:casa_org) { build :casa_org }\n  let(:contact_type_group) { build :contact_type_group, casa_org: }\n  let!(:school_contact_type) { create :contact_type, contact_type_group:, name: \"School\" }\n\n  let(:volunteer) { create :volunteer, :with_single_case, casa_org: }\n  let(:casa_admin) { build :casa_admin, casa_org: }\n  let(:casa_case) { volunteer.casa_cases.first }\n  let(:case_number) { casa_case.case_number }\n\n  let(:user) { volunteer }\n\n  before { sign_in user }\n\n  it \"page load creates a case_contact with status: 'started' & draft_case_ids: [casa_case.id]\" do\n    subject\n\n    expect(page).to have_content(\"New Case Contact\")\n\n    expect(CaseContact.started.count).to eq(1)\n    case_contact = CaseContact.started.last\n    expect(case_contact.draft_case_ids).to contain_exactly(casa_case.id)\n    expect(case_contact.casa_case_id).to be_nil\n  end\n\n  it \"saves entered details and updates status to 'active'\", :js do\n    subject\n\n    expect(page).to have_text \"New Case Contact\"\n    case_contact = CaseContact.started.last\n\n    complete_details_page(\n      case_numbers: [case_number], contact_types: %w[School], contact_made: true,\n      medium: \"In Person\", occurred_on: Time.zone.yesterday, hours: 1, minutes: 45\n    )\n    click_on \"Submit\"\n    expect(page).to have_text \"Case contact successfully created.\"\n\n    case_contact.reload\n    aggregate_failures do\n      expect(case_contact.status).to eq \"active\"\n      # entered details\n      expect(case_contact.draft_case_ids).to eq [casa_case.id]\n      expect(case_contact.occurred_at).to eq Time.zone.yesterday\n      expect(case_contact.contact_types.map(&:name)).to contain_exactly(\"School\")\n      expect(case_contact.medium_type).to eq \"in-person\"\n      expect(case_contact.contact_made).to be true\n      expect(case_contact.duration_minutes).to eq 105\n      # skipped fields\n      expect(case_contact.want_driving_reimbursement).to be false\n      expect(case_contact.miles_driven).to be_zero\n      expect(case_contact.volunteer_address).to be_empty\n      expect(case_contact.notes).to be_nil\n      # associations\n      expect(case_contact.casa_case).to eq casa_case\n      expect(case_contact.creator).to eq volunteer\n      # other attributes\n      expect(case_contact.reimbursement_complete).to be false\n      expect(case_contact.status).to eq \"active\"\n      expect(case_contact.metadata).to be_present\n    end\n  end\n\n  context \"with invalid inputs\" do\n    it \"re-renders the form with errors, preserving all previously entered selections\" do\n      subject\n      complete_details_page(\n        case_numbers: [], contact_types: %w[School], contact_made: true, medium: nil,\n        hours: 1, minutes: 45\n      )\n\n      click_on \"Submit\"\n\n      expect(page).to have_text(\"Medium type can't be blank\")\n      expect(page).to have_field(\"case_contact_duration_hours\", with: 1)\n      expect(page).to have_field(\"case_contact_duration_minutes\", with: 45)\n      expect(page).to have_field(\"case_contact_contact_made\", with: \"1\")\n      expect(page).to have_field(class: \"contact-form-type-checkbox\", with: school_contact_type.id, checked: true)\n\n      expect(CaseContact.count).to eq(1)\n    end\n  end\n\n  describe \"contact types\" do\n    it \"requires a contact type\", :js do\n      contact_types = [school_contact_type]\n      subject\n\n      fill_in_contact_details(contact_types: nil)\n\n      click_on \"Submit\"\n\n      expect(page).to have_text(\"Contact Type must be selected\")\n      expect(CaseContact.active.count).to eq(0)\n\n      check contact_types.first.name\n\n      click_on \"Submit\"\n      expect(page).to have_text(\"Case contact successfully created.\")\n\n      expect(CaseContact.active.count).to eq(1)\n    end\n\n    it \"does not display empty contact groups or hidden contact types\" do\n      create(:contact_type, name: \"Shown Checkbox\", contact_type_group:)\n      build(:contact_type_group, name: \"Empty Group\", casa_org:)\n      grp_with_hidden = build(:contact_type_group, name: \"OnlyHiddenTypes\", casa_org:)\n      build(:contact_type, name: \"Hidden\", active: false, contact_type_group: grp_with_hidden)\n\n      subject\n\n      expect(page).to have_text(contact_type_group.name)\n      expect(page).to have_no_text(\"OnlyHiddenTypes\")\n      expect(page).to have_no_text(\"Empty Group\")\n\n      expect(page).to have_field(class: \"contact-form-type-checkbox\", count: 2)\n      expect(page).to have_field(\"School\", class: \"contact-form-type-checkbox\")\n      expect(page).to have_field(\"Shown Checkbox\", class: \"contact-form-type-checkbox\")\n      expect(page).to have_no_text(\"Empty\")\n      expect(page).to have_no_text(\"Hidden\")\n    end\n\n    context \"when the case has case contact types assigned\" do\n      let!(:casa_case) { create(:casa_case, :with_casa_case_contact_types, :with_one_case_assignment, casa_org:) }\n      let(:volunteer) { casa_case.volunteers.first }\n      let(:casa_case_contact_types) { casa_case.contact_types }\n\n      it \"shows only the casa case's contact types\" do\n        therapist_contact_type = create :contact_type, contact_type_group:, name: \"Therapist\"\n\n        subject\n\n        expect(page).to have_field(class: \"contact-form-type-checkbox\", with: casa_case_contact_types.first.id)\n        expect(page).to have_field(class: \"contact-form-type-checkbox\", with: casa_case_contact_types.last.id)\n        expect(page).to have_field(class: \"contact-form-type-checkbox\", count: casa_case_contact_types.size) # (no others)\n        expect(casa_org.contact_types).to contain_exactly(school_contact_type, therapist_contact_type, *casa_case_contact_types)\n        expect(casa_case_contact_types).not_to include([school_contact_type, therapist_contact_type])\n      end\n    end\n  end\n\n  describe \"notes/contact topic answsers section\" do\n    let(:contact_topics) do\n      [\n        create(:contact_topic, casa_org:, question: \"Active Topic\", active: true, soft_delete: false),\n        create(:contact_topic, casa_org:, question: \"Inactive Not Soft Deleted\", active: false, soft_delete: false),\n        create(:contact_topic, casa_org:, question: \"Active Soft Deleted\", active: true, soft_delete: true),\n        create(:contact_topic, casa_org:, question: \"Inactive Soft Deleted\", active: false, soft_delete: true)\n      ]\n    end\n    let(:notes_section_selector) { \"#contact-form-notes\" }\n    let(:autosave_alert_div) { \"#contact-form-notes\" }\n    let(:autosave_alert_css) { 'small[role=\"alert\"]' }\n\n    it \"does not show topic questions that are inactive or soft deleted in select\" do\n      contact_topics\n      subject\n\n      within notes_section_selector do\n        expect(page).to have_select(class: \"contact-topic-id-select\", options: [\"Active Topic\", \"Select a discussion topic\"])\n        expect(page).to have_no_text(\"Inactive Not Soft Deleted\")\n        expect(page).to have_no_text(\"Active Soft Deleted\")\n        expect(page).to have_no_text(\"Inactive Soft Deleted\")\n      end\n    end\n\n    it \"autosaves notes & answers\", :js do\n      contact_topics\n      subject\n\n      complete_details_page(\n        case_numbers: [case_number], contact_types: %w[School], contact_made: true,\n        medium: \"In Person\", occurred_on: Date.new(2020, 4, 4), hours: 1, minutes: 45\n      )\n\n      answer_topic \"Active Topic\", \"Hello world\"\n\n      within autosave_alert_div do\n        find(autosave_alert_css, text: \"Saved!\")\n      end\n\n      expect(page.find(\".contact-topic-answer-input\")&.value).to eq(\"Hello world\")\n    end\n\n    context \"when org has no contact topics\" do\n      it \"allows entering contact notes\", :js do\n        subject\n\n        fill_in_contact_details contact_types: %w[School]\n\n        fill_in \"Additional Notes\", with: \"This is the note\"\n\n        click_on \"Submit\"\n\n        expect(page).to have_text(\"Case contact successfully created.\")\n\n        expect(casa_org.contact_topics.size).to eq 0\n        expect(CaseContact.active.count).to eq 1\n        case_contact = CaseContact.active.last\n        expect(case_contact.contact_topic_answers).to be_empty\n        expect(case_contact.notes).to eq \"This is the note\"\n      end\n\n      it \"guides volunteer to contact admin\" do\n        subject\n\n        expect(page).to have_text(\"Your organization has not set any Court Report Topics yet. Contact your admin to learn more.\")\n        expect(CaseContact.active.count).to eq(0)\n      end\n\n      context \"with admin user\" do\n        let(:casa_admin) { create(:casa_admin, casa_org: casa_org) }\n        let(:user) { casa_admin }\n\n        it \"shows the admin the contact topics link\" do\n          subject\n\n          expect(page).to have_link(\"Manage Case Contact Topics\")\n          expect(CaseContact.active.count).to eq(0)\n        end\n      end\n\n      context \"with supervisor user\" do\n        let(:supervisor) { create :supervisor, casa_org: }\n        let(:user) { supervisor }\n\n        it \"guides supervisor to contact admin\" do\n          subject\n\n          expect(page).to have_text(\"Your organization has not set any Court Report Topics yet. Contact your admin to learn more.\")\n          expect(CaseContact.active.count).to eq(0)\n        end\n      end\n    end\n  end\n\n  describe \"reimbursement section\" do\n    let(:casa_org) { build(:casa_org, :all_reimbursements_enabled) }\n\n    let(:reimbursement_section_id) { \"#contact-form-reimbursement\" }\n    let(:reimbursement_checkbox) { \"case_contact_want_driving_reimbursement\" }\n    let(:miles_driven_input) { \"case_contact_miles_driven\" }\n    let(:volunteer_address_input) { \"case_contact_volunteer_address\" }\n    let(:add_expense_button_text) { \"Add Another Expense\" }\n\n    before do\n      allow(Flipper).to receive(:enabled?).with(:show_additional_expenses).and_return(true)\n      allow(Flipper).to receive(:enabled?).with(:reimbursement_warning, casa_org).and_call_original\n    end\n\n    it \"is not shown until 'Request travel or other reimbursement' is checked\", :js do\n      subject\n\n      expect(page).to have_no_field(miles_driven_input)\n      expect(page).to have_no_field(volunteer_address_input)\n      expect(page).to have_no_button(add_expense_button_text)\n\n      check reimbursement_checkbox\n\n      expect(page).to have_field(miles_driven_input)\n      expect(page).to have_field(volunteer_address_input)\n      expect(page).to have_button(add_expense_button_text)\n    end\n\n    it \"clears mileage info if reimbursement unchecked\", :js do\n      subject\n      fill_in_contact_details contact_types: %w[School]\n\n      check reimbursement_checkbox\n      fill_in miles_driven_input, with: 50\n      fill_in volunteer_address_input, with: \"123 Example St\"\n      uncheck reimbursement_checkbox\n\n      click_on \"Submit\"\n\n      expect(page).to have_text(\"Case contact successfully created.\")\n\n      expect(CaseContact.active.count).to eq(1)\n      case_contact = CaseContact.active.last\n      expect(case_contact.want_driving_reimbursement).to be false\n      expect(case_contact.miles_driven).to be_zero\n    end\n\n    it \"saves mileage and address information\", :js do\n      subject\n      fill_in_contact_details contact_types: %w[School]\n\n      check reimbursement_checkbox\n\n      fill_in miles_driven_input, with: 50\n      fill_in volunteer_address_input, with: \"123 Example St\"\n\n      click_on \"Submit\"\n\n      expect(page).to have_text(\"Case contact successfully created.\")\n\n      expect(CaseContact.active.count).to eq(1)\n      case_contact = CaseContact.active.last\n      expect(case_contact.want_driving_reimbursement).to be true\n      expect(case_contact.volunteer_address).to eq \"123 Example St\"\n      expect(case_contact.miles_driven).to eq 50\n    end\n\n    it \"does not accept decimal mileage\" do\n      subject\n\n      check reimbursement_checkbox\n\n      fill_in miles_driven_input, with: 50.5\n      fill_in volunteer_address_input, with: \"123 Example St\"\n\n      click_on \"Submit\"\n\n      expect(page).to have_text(\"No changes have been saved\")\n\n      expect(CaseContact.active.count).to eq(0)\n    end\n\n    it \"requires inputs if checkbox checked\" do\n      subject\n      complete_details_page\n\n      check reimbursement_checkbox\n\n      click_on \"Submit\"\n\n      expect(page).to have_text(\"Must enter a valid mailing address for the reimbursement\")\n\n      expect(CaseContact.active.count).to eq(0)\n    end\n\n    context \"when volunteer case assignment reimbursement is false\" do\n      let(:volunteer) { build :volunteer, :with_disallow_reimbursement, casa_org: }\n\n      it \"does not show reimbursement section\" do\n        subject\n\n        expect(page).to have_no_button(add_expense_button_text)\n        expect(page).to have_no_field(miles_driven_input)\n        expect(page).to have_no_field(volunteer_address_input)\n        expect(page).to have_no_field(reimbursement_checkbox)\n        expect(page).to have_no_selector(reimbursement_section_id)\n        expect(page).to have_no_text(\"reimbursement\")\n      end\n    end\n\n    context \"when casa org driving reimbursement false, additional expenses true\" do\n      before { casa_org.update! show_driving_reimbursement: false }\n\n      it \"does not render the reimbursement section\" do\n        subject\n\n        expect(page).to have_no_field(reimbursement_checkbox, visible: :all)\n        expect(page).to have_no_field(miles_driven_input, visible: :all)\n        expect(page).to have_no_field(volunteer_address_input, visible: :all)\n        expect(page).to have_no_button(add_expense_button_text, visible: :all)\n        expect(page).to have_no_field(class: \"expense-amount-input\")\n        expect(page).to have_no_field(class: \"expense-describe-input\")\n        expect(page).to have_no_text(\"reimbursement\")\n      end\n    end\n\n    context \"when casa org additional expenses false\" do\n      before { casa_org.update! additional_expenses_enabled: false }\n\n      it \"enables mileage reimbursement but does shows additional expenses\", :js do\n        subject\n\n        complete_details_page(case_numbers: [case_number], contact_types: %w[School])\n\n        check reimbursement_checkbox\n\n        expect(page).to have_field(miles_driven_input)\n        expect(page).to have_field(volunteer_address_input)\n\n        expect(page).to have_no_button(add_expense_button_text)\n        expect(page).to have_no_field(class: \"expense-amount-input\", visible: :all)\n        expect(page).to have_no_field(class: \"expense-describe-input\", visible: :all)\n      end\n    end\n\n    context \"when casa org does not allow mileage or expense reimbursement\" do\n      let(:casa_org) { build :casa_org, show_driving_reimbursement: false, additional_expenses_enabled: false }\n\n      it \"does not show reimbursement section\" do\n        subject\n\n        expect(page).to have_no_button(add_expense_button_text)\n        expect(page).to have_no_field(miles_driven_input)\n        expect(page).to have_no_field(volunteer_address_input)\n        expect(page).to have_no_field(reimbursement_checkbox)\n        expect(page).to have_no_selector(reimbursement_section_id)\n        expect(page).to have_no_text(\"reimbursement\")\n      end\n    end\n  end\n\n  context \"when 'Create Another' is checked\" do\n    it \"redirects to the new CaseContact form with the same case selected\", :js do\n      subject\n\n      complete_details_page(\n        case_numbers: [case_number], contact_types: %w[School], contact_made: true,\n        medium: \"In Person\", occurred_on: Date.today, hours: 1, minutes: 45\n      )\n\n      check \"Create Another\"\n      click_on \"Submit\"\n\n      expect(page).to have_text \"Case contact successfully created.\"\n      expect(page).to have_text \"New Case Contact\"\n      expect(page).to have_text case_number\n\n      expect(CaseContact.active.count).to eq(1)\n      expect(CaseContact.started.count).to eq(1)\n\n      submitted_case_contact = CaseContact.active.last\n      next_case_contact = CaseContact.started.last\n\n      expect(submitted_case_contact.reload.metadata[\"create_another\"]).to be true\n      # new contact uses draft_case_ids from the original & form selects them\n      expect(next_case_contact.draft_case_ids).to eq [casa_case.id]\n      # default values for other attributes (not from the last contact)\n      expect(next_case_contact.status).to eq \"started\"\n      expect(next_case_contact.miles_driven).to be_zero\n      %i[casa_case_id duration_minutes occurred_at medium_type\n        want_driving_reimbursement notes].each do |attribute|\n        expect(next_case_contact.send(attribute)).to be_blank\n      end\n      expect(next_case_contact.contact_made).to be true\n    end\n\n    it \"does not reset referring location\", :js do\n      visit casa_case_path casa_case\n      # referrer will be set by CaseContactsController#new to casa_case_path(casa_case)\n      click_on \"New Case Contact\"\n      fill_in_contact_details contact_types: %w[School]\n\n      # goes through CaseContactsController#new, but should not set a referring location\n      check \"Create Another\"\n      click_on \"Submit\"\n\n      fill_in_contact_details contact_types: %w[School]\n\n      click_on \"Submit\"\n      # update should redirect to the original referrer, casa_case_path(casa_case)\n      expect(page).to have_text \"CASA Case Details\"\n      expect(page).to have_text \"Case number: #{case_number}\"\n    end\n\n    context \"when multiple cases selected\" do\n      let(:volunteer) { create(:volunteer, :with_casa_cases, casa_org:) }\n      let(:casa_case) { volunteer.casa_cases.first }\n      let(:casa_case_two) { volunteer.casa_cases.second }\n      let(:case_number_two) { casa_case_two.case_number }\n      let!(:draft_case_ids) { [casa_case.id, casa_case_two.id] }\n\n      it \"redirects to the new CaseContact form with the same cases selected\", :js do\n        expect {\n          visit new_case_contact_path(casa_case, {draft_case_ids:})\n          expect(page).to have_content(\"Record New Case Contact\")\n        }.to change(CaseContact.started, :count).by(1)\n        this_case_contact = CaseContact.started.last\n\n        expect(page).to have_select(\"case_contact_draft_case_ids\", selected: [case_number, case_number_two])\n        complete_details_page(case_numbers: [])\n        expect(page).to have_select(\"case_contact_draft_case_ids\", selected: [case_number, case_number_two])\n\n        check \"Create Another\"\n\n        expect {\n          click_on \"Submit\"\n          expect(page).to have_text \"Case contacts successfully created.\"\n        }.to change(CaseContact.active, :count).by(2)\n\n        expect(page).to have_text \"New Case Contact\"\n        expect(this_case_contact.reload.status).to eq \"active\"\n        next_case_contact = CaseContact.not_active.last\n        expect(next_case_contact).to be_present\n\n        expect(next_case_contact.status).to eq \"started\"\n        expect(page).to have_text case_number\n        expect(page).to have_text case_number_two\n        expect(next_case_contact.draft_case_ids).to match_array draft_case_ids\n      end\n    end\n  end\n\n  context \"when volunteer has multiple cases\" do\n    let(:volunteer) { create(:volunteer, :with_casa_cases, casa_org:) }\n    let(:first_case) { volunteer.casa_cases.first }\n    let(:second_case) { volunteer.casa_cases.second }\n\n    describe \"case default selection\" do\n      it \"selects no cases\", :js do\n        subject\n\n        expect(page).to have_no_text(first_case.case_number)\n        expect(page).to have_no_text(second_case.case_number)\n      end\n\n      context \"when there are params defined\" do\n        it \"select the cases defined in the params\", :js do\n          visit new_case_contact_path(case_contact: {casa_case_id: first_case.id})\n\n          expect(page).to have_text(first_case.case_number)\n          expect(page).to have_no_text(second_case.case_number)\n        end\n      end\n    end\n  end\n\n  context \"when volunteer has one case\" do\n    let(:first_case) { volunteer.casa_cases.first }\n\n    it \"selects the only case\" do\n      subject\n\n      expect(page).to have_text(first_case.case_number)\n      expect(volunteer.casa_cases.size).to eq 1\n    end\n  end\n\n  context \"with admin user\" do\n    it \"can create CaseContact\", :js do\n      subject\n\n      complete_details_page(\n        case_numbers: [], contact_types: %w[School], contact_made: true,\n        medium: \"Video\", occurred_on: Date.new(2020, 4, 5), hours: 1, minutes: 45\n      )\n\n      click_on \"Submit\"\n\n      expect(page).to have_text \"Case contact successfully created.\"\n\n      expect(CaseContact.active.count).to eq(1)\n      contact = CaseContact.active.last\n      expect(contact.casa_case_id).to eq casa_case.id\n      expect(contact.contact_types.map(&:name)).to include(\"School\")\n      expect(contact.duration_minutes).to eq 105\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/case_court_reports/index_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.configure do |config|\n  config.include CaseCourtReportHelpers, type: :system\nend\n\nRSpec.shared_context \"when on the court reports page\" do |user_role|\n  let(:current_user) { send(user_role) }\n\n  before do\n    sign_in current_user\n    visit case_court_reports_path\n  end\nend\n\nRSpec.shared_examples \"a user with organization-level case visibility in autocomplete\" do\n  before do\n    open_court_report_modal\n    open_case_select2_dropdown\n  end\n\n  it \"shows all cases in their organization\", :aggregate_failures do\n    # Ensure the dropdown results area is ready\n    expect(page).to have_css(\"ul.select2-results__options\")\n\n    # Check for the unassigned case created in the calling context\n    expect(page).to have_css(\".select2-results__option\", text: /#{Regexp.escape(unassigned_case.case_number)}/i)\n\n    # Check for each case assigned to the volunteer (created in the calling context)\n    volunteer.casa_cases.each do |casa_case|\n      # Use regex to flexibly match text format (e.g., \"CASE-NUM - status(assigned...)\")\n      expect(page).to have_css(\".select2-results__option\", text: /#{Regexp.escape(casa_case.case_number)}/i)\n    end\n  end\n\n  it \"hides cases from other organizations\", :aggregate_failures do\n    # Find and interact with the search field\n    input_field = find(\"input.select2-search__field\", visible: :all)\n    input_field.click # Ensure focus before typing\n    input_field.send_keys(other_org_case.case_number)\n\n    # Assert that \"No results found\" IS visible (Capybara waits)\n    expect(page).to have_css(\".select2-results__option\", text: \"No results found\", visible: :visible, wait: 5)\n\n    # Assert that the specific other org case number is NOT visible\n    expect(page).not_to have_css(\".select2-results__option\", text: other_org_case.case_number, visible: :visible)\n  end\nend\n\nRSpec.describe \"case_court_reports/index\", type: :system do\n  context \"when first arriving to 'Generate Court Report' page\", :js do\n    let(:volunteer) { create(:volunteer) }\n\n    include_context \"when on the court reports page\", :volunteer\n\n    it \"generation modal hidden\", :aggregate_failures do\n      expect(page).to have_selector \"#btnGenerateReport\", text: \"Generate Report\", visible: :hidden\n      expect(page).to have_selector \"#case-selection\", visible: :hidden\n      expect(page).not_to have_selector \".select2\"\n    end\n  end\n\n  context \"when opening 'Download Court Report' modal\", :js do\n    let(:volunteer) do\n      create(:volunteer, :with_cases_and_contacts, :with_assigned_supervisor, display_name: \"Volunteer\")\n    end\n    let(:supervisor) { volunteer.supervisor }\n    let(:casa_cases) { CasaCase.actively_assigned_to(volunteer) }\n\n    include_context \"when on the court reports page\", :volunteer\n\n    before do\n      open_court_report_modal\n    end\n\n    it \"shows the Generate button\", :aggregate_failures do\n      expect(page).to have_selector \"#btnGenerateReport\", text: \"Generate Report\", visible: :visible\n      expect(page).not_to have_selector \".select2\"\n    end\n\n    it \"shows correct default dates\", :aggregate_failures do\n      date = Date.current\n      formatted_date = date.strftime(\"%B %d, %Y\") # January 01, 2021\n\n      expect(page.find(\"#start_date\").value).to eq(formatted_date)\n      expect(page.find(\"#end_date\").value).to eq(formatted_date)\n    end\n\n    it \"lists all assigned cases\" do\n      expected_number_of_options = casa_cases.size + 1 # +1 for \"Select case\"\n      expect(page).to have_selector \"#case-selection option\", count: expected_number_of_options\n    end\n\n    it \"shows correct transition status labels\", :aggregate_failures do # rubocop:disable RSpec/ExampleLength\n      younger_than_transition_age = volunteer.casa_cases.reject(&:in_transition_age?).first\n      at_least_transition_age = volunteer.casa_cases.detect(&:in_transition_age?)\n\n      expected_text_transition = \"#{at_least_transition_age.case_number} - transition\"\n      expect(page).to have_selector \"#case-selection option\", text: expected_text_transition\n\n      expected_text_non_transition = \"#{younger_than_transition_age.case_number} - non-transition\"\n      expect(page).to have_selector \"#case-selection option\", text: expected_text_non_transition\n    end\n\n    it \"adds data-lookup attribute for volunteer searching\" do\n      casa_cases.each do |casa_case|\n        lookup = casa_case.assigned_volunteers.map(&:display_name).join(\",\")\n        expect(page).to have_selector \"#case-selection option[data-lookup='#{lookup}']\"\n      end\n    end\n\n    it \"defaults to 'Select case number' prompt\", :aggregate_failures do\n      expect(page).to have_select \"case-selection\", selected: \"Select case number\"\n      # Extra check for the first option specifically\n      expect(page).to have_selector \"#case-selection option:first-of-type\", text: \"Select case number\"\n    end\n\n    it \"shows an error when generating without a selection\", :aggregate_failures do # rubocop:disable RSpec/ExampleLength\n      # Ensure default is selected\n      page.select \"Select case number\", from: \"case-selection\"\n      click_button \"Generate Report\"\n\n      expect(page).to have_selector(\".select-required-error\", visible: :visible)\n      # Check button state remains unchanged (not disabled, spinner hidden)\n      expect(page).to have_selector(\"#btnGenerateReport .lni-download\", visible: :visible)\n      expect(page).not_to have_selector(\"#btnGenerateReport[disabled]\")\n      expect(page).to have_selector(\"#spinner\", visible: :hidden)\n    end\n\n    it \"hides the error when a valid case is selected\", :aggregate_failures do\n      click_button \"Generate Report\" # First, make the error appear\n      expect(page).to have_selector(\".select-required-error\", visible: :visible)\n\n      test_case_number = casa_cases.detect(&:in_transition_age?).case_number.to_s\n      page.select test_case_number, from: \"case-selection\"\n      expect(page).not_to have_selector(\".select-required-error\", visible: :visible)\n    end\n\n    it \"clears the error message when the modal is reopened\", :aggregate_failures do\n      click_button \"Generate Report\" # Make error appear\n      expect(page).to have_selector(\".select-required-error\", visible: :visible)\n\n      click_button \"Close\"\n      open_court_report_modal # Reopen using the helper\n      expect(page).not_to have_selector(\".select-required-error\", visible: :visible) # Error should be gone\n    end\n\n    # NOTE: select by option VALUE (stable), stub `window.open` to capture the download URL,\n    # wait for the button to re-enable (page-level signal), and assert UI state + opened URL.\n    it \"generates a report and opens the download link on success\", :aggregate_failures, :js do # rubocop:disable RSpec/ExampleLength\n      transition_case = casa_cases.detect(&:in_transition_age?)\n\n      # Stub window.open so we can capture the download URL in the browser\n      page.execute_script(<<~JS)\n        window.__last_opened_url = null;\n        window.open = function(url) { window.__last_opened_url = url; };\n      JS\n\n      # Ensure the option exists, then select it by VALUE (case number)\n      expect(page).to have_selector(\"#case-selection option[value='#{transition_case.case_number}']\", visible: :all)\n      find(\"#case-selection\").find(\"option[value='#{transition_case.case_number}']\").select_option\n\n      # Trigger generation\n      click_button \"Generate Report\"\n\n      # Button should be disabled while processing\n      expect(page).to have_selector(\"#btnGenerateReport[disabled]\")\n\n      # Wait for the button to re-enable (report generated successfully)\n      expect(page).not_to have_selector(\"#btnGenerateReport[disabled]\", wait: 10)\n\n      # Verify the UI reflects a successful generation\n      expect(page).to have_selector(\"#btnGenerateReport .lni-download\", visible: :visible)\n      expect(page).to have_selector(\"#spinner\", visible: :hidden)\n\n      # Verify the browser attempted to open the generated .docx link\n      opened_url = page.evaluate_script(\"window.__last_opened_url\")\n      expect(opened_url).to be_present\n      expect(opened_url).to match(/#{Regexp.escape(transition_case.case_number)}.*\\.docx$/i)\n    end\n  end\n\n  context \"when logged in as a supervisor\" do\n    let(:volunteer) do\n      create(:volunteer, :with_cases_and_contacts, :with_assigned_supervisor, display_name: \"Name Last\")\n    end\n    let(:supervisor) { volunteer.supervisor }\n\n    include_context \"when on the court reports page\", :supervisor\n\n    it { expect(page).to have_selector \".select2\" }\n    it { expect(page).to have_text \"Search by volunteer name or case number\" }\n\n    context \"when searching for cases\" do\n      let(:casa_case) { volunteer.casa_cases.first }\n      let(:search_term) { casa_case.case_number[-3..] }\n\n      it \"selects the correct case\", :aggregate_failures, :js do # rubocop:disable RSpec/ExampleLength\n        open_court_report_modal\n        open_case_select2_dropdown\n        send_keys(search_term)\n        # Wait for the search result to appear in the dropdown\n        expect(page).to have_css(\".select2-results__option\", text: casa_case.case_number, visible: :visible)\n        # Click the result instead of sending enter\n        find(\".select2-results__option\", text: casa_case.case_number).click\n        # Wait for selection to update\n        expect(page).to have_css(\".select2-selection__rendered\", text: casa_case.case_number, visible: :visible)\n      end\n    end\n  end\n\n  # rubocop:disable RSpec/MultipleMemoizedHelpers\n  describe \"case selection visibility by user role\", :js do\n    let!(:volunteer_assigned_to_case) do\n      create(:volunteer, :with_cases_and_contacts, :with_assigned_supervisor, display_name: \"Assigned Volunteer\")\n    end\n    let(:casa_org) { volunteer_assigned_to_case.casa_org } # Derive org from the volunteer\n    let!(:unassigned_case) { create(:casa_case, casa_org: casa_org, case_number: \"UNASSIGNED-CASE-1\", active: true) }\n    let!(:other_org) { create(:casa_org) }\n    let!(:other_org_case) { create(:casa_case, casa_org: other_org, case_number: \"OTHER-ORG-CASE-99\", active: true) } # rubocop:disable RSpec/LetSetup\n\n    context \"when logged in as a volunteer\" do\n      let(:volunteer) { volunteer_assigned_to_case }\n      let!(:other_volunteer) { create(:volunteer, casa_org: volunteer.casa_org) }\n      let!(:other_volunteer_case) do\n        create(:casa_case, casa_org: volunteer.casa_org, case_number: \"OTHER-VOL-CASE-88\", volunteers: [other_volunteer],\n          active: true)\n      end\n\n      include_context \"when on the court reports page\", :volunteer\n\n      before do\n        open_court_report_modal\n      end\n\n      it \"shows all assigned cases in autocomplete search\", :aggregate_failures do\n        volunteer.casa_cases.select(&:active?).each do |c|\n          expect(page).to have_selector(\"#case-selection option\", text: /#{Regexp.escape(c.case_number)}/i)\n        end\n      end\n\n      it \"does not show unassigned cases in autocomplete search\" do\n        expect(page).not_to have_selector(\"#case-selection option\",\n          text: /#{Regexp.escape(unassigned_case.case_number)}/i)\n      end\n\n      it \"does not show cases assigned to other volunteers in autocomplete search\" do\n        expect(page).not_to have_selector(\"#case-selection option\",\n          text: /#{Regexp.escape(other_volunteer_case.case_number)}/i)\n      end\n    end\n\n    context \"when logged in as a supervisor\" do\n      let(:volunteer) { volunteer_assigned_to_case }\n      let(:supervisor) { create(:supervisor, casa_org: volunteer.casa_org) }\n\n      include_context \"when on the court reports page\", :supervisor\n      it_behaves_like \"a user with organization-level case visibility in autocomplete\"\n    end\n\n    context \"when logged in as an admin\" do\n      let(:volunteer) { volunteer_assigned_to_case }\n      let(:casa_admin) { create(:casa_admin, casa_org: volunteer.casa_org) }\n\n      include_context \"when on the court reports page\", :casa_admin\n      it_behaves_like \"a user with organization-level case visibility in autocomplete\"\n    end\n  end\n  # rubocop:enable RSpec/MultipleMemoizedHelpers\nend\n"
  },
  {
    "path": "spec/system/case_groups/case_groups_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"Case Groups\", :js, type: :system do\n  let(:admin) { create(:casa_admin) }\n  let(:organization) { admin.casa_org }\n\n  it \"create a case group\" do\n    casa_case1 = create(:casa_case)\n    casa_case2 = create(:casa_case)\n\n    sign_in admin\n\n    visit case_groups_path\n    click_on \"New Case Group\"\n    fill_in \"Name\", with: \"A family\"\n    find(\".ts-control > input\").click\n    find(\"div.option\", text: casa_case1.case_number).click\n    find(\"div.option\", text: casa_case2.case_number).click\n    find(\"#case_group_name\").click\n\n    click_on \"Submit\"\n\n    within \"#case-groups\" do\n      expect(page).to have_text(\"A family\")\n      click_on \"Edit\", match: :first\n    end\n    fill_in \"Name\", with: \"Another family\"\n    click_on \"Submit\"\n\n    expect(page).to have_text(\"Another family\")\n  end\n\n  it \"remove from a case group\" do\n    casa_case1 = create(:casa_case)\n    casa_case2 = create(:casa_case)\n\n    sign_in admin\n\n    visit case_groups_path\n    click_on \"New Case Group\"\n    fill_in \"Name\", with: \"A family\"\n    find(\".ts-control > input\").click\n    find(\"div.option\", text: casa_case1.case_number).click\n    find(\"div.option\", text: casa_case2.case_number).click\n    find(\"#case_group_name\").click\n\n    click_on \"Submit\"\n\n    list_item_text = find_all(\"table li\").map(&:text)\n    expect(list_item_text.count).to be 2\n    expect(list_item_text[0]).to match casa_case1.case_number\n    expect(list_item_text[1]).to match casa_case2.case_number\n\n    within \"#case-groups\" do\n      click_on \"Edit\", match: :first\n    end\n    case2_selector = find(\".ts-control > div.item\", text: casa_case2.case_number)\n    within case2_selector do\n      find(\"a\").click\n    end\n    click_on \"Submit\"\n\n    expect(page).to have_text(casa_case1.case_number)\n    expect(page).not_to have_text(casa_case2.case_number)\n  end\n\n  it \"does not create a case group if the name is not unique\" do\n    casa_case = create(:casa_case)\n\n    sign_in admin\n\n    visit case_groups_path\n    click_on \"New Case Group\"\n    fill_in \"Name\", with: \"A family\"\n    find(\".ts-control > input\").click\n    find(\"div.option\", text: casa_case.case_number).click\n    find(\"#case_group_name\").click\n    click_on \"Submit\"\n\n    visit case_groups_path\n    click_on \"New Case Group\"\n    fill_in \"Name\", with: \"A Family \"\n    find(\".ts-control > input\").click\n    find(\"div.option\", text: casa_case.case_number).click\n    find(\"#case_group_name\").click\n    click_on \"Submit\"\n\n    expect(page).to have_text(\"Name has already been taken\")\n\n    visit case_groups_path\n    expect(page).to have_text(\"A family\").once\n  end\nend\n"
  },
  {
    "path": "spec/system/checklist_items/destroy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"checklist_items/destroy\", type: :system do\n  let(:casa_admin) { create(:casa_admin) }\n  let(:checklist_item) { create(:checklist_item) }\n  let(:hearing_type) { create(:hearing_type, checklist_items: [checklist_item]) }\n\n  before do\n    sign_in casa_admin\n    visit edit_hearing_type_path(hearing_type)\n  end\n\n  it \"deletes checklist items\", :aggregate_failures do\n    click_on \"Delete\", match: :first\n\n    expect(page).to have_text(\"Checklist item was successfully deleted.\")\n    expect(page).not_to have_text(checklist_item.category)\n    expect(page).not_to have_text(checklist_item.description)\n\n    click_on \"Submit\"\n    current_date = Time.new.strftime(\"%m/%d/%Y\")\n    expect(page).to have_text(\"Updated #{current_date}\")\n  end\nend\n"
  },
  {
    "path": "spec/system/checklist_items/edit_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"checklist_items/edit\", type: :system do\n  let(:casa_admin) { create(:casa_admin) }\n  let(:hearing_type) { create(:hearing_type) }\n  let(:checklist_item) { create(:checklist_item, hearing_type: hearing_type) }\n\n  before do\n    sign_in casa_admin\n    visit edit_hearing_type_checklist_item_path(hearing_type, checklist_item)\n  end\n\n  it \"edits with valid data\", :aggregate_failures do\n    fill_in \"Category\", with: \"checklist item category EDIT\"\n    fill_in \"Description\", with: \"checklist item description EDIT\"\n    check \"Mandatory\"\n    click_on \"Submit\"\n\n    expect(page).to have_text(\"Checklist item was successfully updated.\")\n    expect(page).to have_text(\"checklist item category EDIT\")\n    expect(page).to have_text(\"checklist item description EDIT\")\n    expect(page).to have_text(\"Yes\")\n\n    click_on \"Submit\"\n    current_date = Time.new.strftime(\"%m/%d/%Y\")\n    expect(page).to have_text(\"Updated #{current_date}\")\n  end\n\n  it \"rejects with invalid data\" do\n    fill_in \"Category\", with: \"\"\n    fill_in \"Description\", with: \"\"\n    click_on \"Submit\"\n\n    expect(page).to have_text(\"Edit this checklist item\")\n  end\nend\n"
  },
  {
    "path": "spec/system/checklist_items/new_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"checklist_items/new\", type: :system do\n  let(:casa_admin) { create(:casa_admin) }\n  let(:hearing_type) { create(:hearing_type) }\n\n  before do\n    sign_in casa_admin\n    visit new_hearing_type_checklist_item_path(hearing_type)\n  end\n\n  it \"creates with valid data\", :aggregate_failures do\n    fill_in \"Category\", with: \"checklist item category\"\n    fill_in \"Description\", with: \"checklist item description\"\n    click_on \"Submit\"\n\n    expect(page).to have_text(\"Checklist item was successfully created.\")\n    expect(page).to have_text(\"checklist item category\")\n    expect(page).to have_text(\"checklist item description\")\n    expect(page).to have_text(\"Optional\")\n\n    click_on \"Submit\"\n    current_date = Time.new.strftime(\"%m/%d/%Y\")\n    expect(page).to have_text(\"Updated #{current_date}\")\n  end\n\n  it \"rejects with invalid data\" do\n    fill_in \"Category\", with: \"\"\n    fill_in \"Description\", with: \"\"\n    click_on \"Submit\"\n\n    expect(page).to have_text(\"Add a new checklist item\")\n  end\nend\n"
  },
  {
    "path": "spec/system/components/truncated_text_component_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe TruncatedTextComponent, type: :system do\n  it \"renders the component with the provided text\", :js do\n    visit(\"/rails/view_components/truncated_text_component/default\")\n\n    aggregate_failures do\n      expect(page).to have_css(\"span\", text: \"Some Label\")\n      expect(page).to have_css(\".truncation-container\")\n      expect(page).to have_css(\".line-clamp-1\")\n      expect(page).to have_css(\"a\", text: \"[read more]\")\n      expect(page).to have_css(\"a\", text: \"[hide]\", visible: false)\n    end\n\n    click_on \"read more\"\n\n    aggregate_failures do\n      expect(page).to have_css(\"span\", text: \"Some Label\")\n      expect(page).to have_no_css(\".line-clamp-1\")\n      expect(page).to have_css(\"a\", text: \"[read more]\", visible: false)\n      expect(page).to have_css(\"a\", text: \"[hide]\", visible: true)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/contact_types/edit_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"contact_types/edit\", type: :system do\n  let!(:organization) { create(:casa_org) }\n  let!(:admin) { create(:casa_admin, casa_org_id: organization.id) }\n  let!(:contact_type_group) { create(:contact_type_group, casa_org: organization, name: \"Contact type group 1\") }\n  let!(:contact_type) { create(:contact_type, name: \"Contact type 1\") }\n\n  before do\n    sign_in admin\n\n    visit edit_contact_type_path(contact_type)\n  end\n\n  it \"errors with invalid name\" do\n    fill_in \"Name\", with: \"\"\n    click_on \"Submit\"\n\n    expect(page).to have_text(\"Name can't be blank\")\n  end\n\n  it \"creates with valid data\" do\n    fill_in \"Name\", with: \"Edit Contact Type test\"\n    click_on \"Submit\"\n\n    expect(page).to have_text(\"Contact Type was successfully updated.\")\n  end\nend\n"
  },
  {
    "path": "spec/system/contact_types/new_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe \"contact_types/new\", type: :system do\n  let!(:organization) { create(:casa_org) }\n  let!(:admin) { create(:casa_admin, casa_org: organization) }\n  let!(:contact_type_group) { create(:contact_type_group, casa_org: organization, name: \"Contact type group 1\") }\n\n  before do\n    sign_in admin\n    visit new_contact_type_path\n  end\n\n  context \"with valid data\" do\n    it \"creates contact type successfully\" do\n      fill_in \"Name\", with: \"New Contact Type test\"\n      click_on \"Submit\"\n\n      expect(page).to have_text(\"Contact Type was successfully created.\")\n    end\n  end\n\n  context \"with invalid data\" do\n    it \"shows error when name is blank\" do\n      fill_in \"Name\", with: \"\"\n      click_on \"Submit\"\n\n      expect(page).to have_text(\"Name can't be blank\")\n    end\n\n    it \"shows error when name is not unique within group\" do\n      create(:contact_type, name: \"Existing Name\", contact_type_group:)\n\n      fill_in \"Name\", with: \"Existing Name\"\n      select \"Contact type group 1\", from: \"contact_type_contact_type_group_id\"\n      click_on \"Submit\"\n\n      expect(page).to have_text(\"Name should be unique per contact type group\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/court_dates/edit_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe \"court_dates/edit\", type: :system do\n  let(:now) { Date.new(2021, 1, 1) }\n  let(:organization) { create(:casa_org) }\n  let(:admin) { create(:casa_admin, casa_org: organization) }\n  let(:volunteer) { create(:volunteer) }\n  let(:supervisor) { create(:supervisor, casa_org: organization) }\n  let!(:casa_case) { create(:casa_case, casa_org: organization) }\n  let!(:past_court_date) { create(:court_date, :with_court_details, casa_case: casa_case, date: now - 1.week) }\n  let!(:future_court_date) { create(:court_date, :with_court_details, casa_case: casa_case, date: now + 1.week) }\n\n  before do\n    travel_to now\n  end\n\n  context \"as an admin\" do\n    before do\n      sign_in admin\n      visit casa_case_path(casa_case)\n      click_on past_court_date.date.strftime(\"%B %-d, %Y\")\n      click_on \"Edit\"\n    end\n\n    it \"shows court orders\" do\n      court_order = past_court_date.case_court_orders.first\n\n      expect(page).to have_text(court_order.text)\n      expect(page).to have_text(court_order.implementation_status.humanize)\n    end\n\n    it \"adds a standard court order\", :js do\n      select(\"Family therapy\", from: \"Court Order Type\")\n      click_button(\"Add a court order\")\n\n      textarea = all(\"textarea.court-order-text-entry\").last\n      expect(textarea.value).to eq(\"Family therapy\")\n    end\n\n    it \"adds a custom court order\", :js do\n      click_button(\"Add a court order\")\n\n      textarea = all(\"textarea.court-order-text-entry\").last\n      expect(textarea.value).to eq(\"\")\n    end\n\n    it \"edits past court date\", :js do\n      expect(page).to have_text(\"Editing Court Date\")\n      expect(page).to have_text(\"Case Number:\")\n      expect(page).to have_text(casa_case.case_number)\n      expect(page).to have_text(\"Add Court Date\")\n      expect(page).to have_field(\"court_date_date\", with: \"2020-12-25\")\n      expect(page).to have_text(\"Add Court Report Due Date\")\n      expect(page).to have_field(\"court_date_court_report_due_date\")\n      expect(page).to have_select(\"Judge\")\n      expect(page).to have_select(\"Hearing type\")\n      expect(page).to have_text(\"Court Orders - Please check that you didn't enter any youth names\")\n      expect(page).to have_text(\"Add a court order\")\n\n      page.find('button[data-action=\"court-order-form#add\"]').click\n      find(\"#court-orders-list-container\").first(\"textarea\").send_keys(\"Court Order Text One\")\n\n      within \".top-page-actions\" do\n        click_on \"Update\"\n      end\n\n      expect(page).to have_text(\"Court Order Text One\")\n    end\n\n    it \"allows deleting a future court date\", :js do\n      visit root_path\n      click_on \"Cases\"\n      click_on casa_case.case_number\n\n      expect(page).to have_content past_court_date.date.strftime(\"%B %-d, %Y\")\n      expect(page).to have_content future_court_date.date.strftime(\"%B %-d, %Y\")\n      page.find(\"a\", text: future_court_date.date.strftime(\"%B %-d, %Y\")).click\n      accept_alert \"Are you sure?\" do\n        page.find(\"a\", text: \"Delete Future Court Date\").click\n      end\n      expect(page).to have_content \"Court date was successfully deleted\"\n\n      expect(page).to have_content past_court_date.date.strftime(\"%B %-d, %Y\")\n      expect(page).not_to have_content future_court_date.date.strftime(\"%B %-d, %Y\")\n    end\n  end\n\n  context \"as a supervisor\" do\n    it \"allows deleting a future court date\", :js do\n      sign_in supervisor\n\n      visit root_path\n      click_on \"Cases\"\n      click_on casa_case.case_number\n\n      expect(page).to have_content past_court_date.date.strftime(\"%B %-d, %Y\")\n      expect(page).to have_content future_court_date.date.strftime(\"%B %-d, %Y\")\n      page.find(\"a\", text: future_court_date.date.strftime(\"%B %-d, %Y\")).click\n      accept_alert \"Are you sure?\" do\n        page.find(\"a\", text: \"Delete Future Court Date\").click\n      end\n\n      expect(page).to have_content \"Court date was successfully deleted.\"\n      expect(page).to have_content past_court_date.date.strftime(\"%B %-d, %Y\")\n      expect(page).not_to have_content future_court_date.date.strftime(\"%B %-d, %Y\")\n    end\n  end\n\n  context \"as a volunteer\" do\n    it \"does not allow deleting a future court date\", :js do\n      volunteer.casa_cases = [casa_case]\n      sign_in volunteer\n\n      visit root_path\n      click_on \"Cases\"\n      click_on casa_case.case_number\n\n      expect(page).to have_content past_court_date.date.strftime(\"%B %-d, %Y\")\n      expect(page).to have_content future_court_date.date.strftime(\"%B %-d, %Y\")\n      page.find(\"a\", text: future_court_date.date.strftime(\"%B %-d, %Y\")).click\n\n      expect(page).not_to have_content \"Delete Future Court Date\"\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/court_dates/new_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe \"court_dates/new\", type: :system do\n  let(:now) { Date.new(2021, 1, 2) }\n  let(:casa_org) { create(:casa_org) }\n  let(:admin) { create(:casa_admin, casa_org: casa_org) }\n  let!(:casa_case) { create(:casa_case, casa_org: casa_org) }\n  let!(:court_date) { create(:court_date, :with_court_details, casa_case: casa_case, date: now - 1.week) }\n  let!(:judge) { create(:judge) }\n  let!(:hearing_type) { create(:hearing_type) }\n  let(:text) { Faker::Lorem.paragraph(sentence_count: 2) }\n\n  before do\n    travel_to now\n    sign_in admin\n    visit casa_case_path(casa_case)\n    click_link(\"Add a court date\")\n  end\n\n  context \"when all fields are filled\" do\n    it \"is successful\", :js do\n      expect(page).to have_content(casa_case.case_number)\n\n      fill_in \"court_date_date\", with: now\n      fill_in \"court_date_court_report_due_date\", with: now\n      select judge.name, from: \"Judge\"\n      select hearing_type.name, from: \"Hearing type\"\n\n      click_on \"Add a court order\"\n      text_area = first(:css, \"textarea\").native\n      text_area.send_keys(text)\n      page.find(\"select.implementation-status\").find(:option, text: \"Partially implemented\").select_option\n\n      within \".top-page-actions\" do\n        click_on \"Create\"\n      end\n\n      expect(page).to have_content(\"Court date was successfully created.\")\n      expect(page).to have_content(casa_case.case_number)\n      expect(page).to have_content(\"Court Report Due Date:\\nJanuary 2, 2021\")\n      expect(page).to have_content(judge.name)\n      expect(page).to have_content(hearing_type.name)\n      expect(page).to have_content(text)\n      expect(page).to have_content(\"Partially implemented\")\n    end\n  end\n\n  context \"without changing default court date\" do\n    it \"does create a new court_date\" do\n      within \".top-page-actions\" do\n        click_on \"Create\"\n      end\n\n      expect(page).to have_content(\"Court date was successfully created.\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/court_dates/view_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"court_dates/edit\", type: :system do\n  let(:organization) { create(:casa_org) }\n\n  let(:now) { Date.new(2021, 1, 1) }\n  let(:displayed_date_format) { \"%B %-d, %Y\" }\n  let(:casa_case_number) { \"CASA-CASE-NUMBER\" }\n  let!(:casa_case) { create(:casa_case, casa_org: organization, case_number: casa_case_number) }\n  let(:court_date_as_date_object) { now + 1.week }\n  let(:court_report_due_date) { now + 2.weeks }\n  let!(:court_date) { create(:court_date, casa_case: casa_case, court_report_due_date: court_report_due_date, date: court_date_as_date_object) }\n\n  before do\n    travel_to now\n  end\n\n  shared_examples \"a user able to view court date\" do |user_type|\n    let(:user) { create(user_type, casa_org: organization) }\n\n    it \"can visit the court order page\" do\n      if user_type === :volunteer\n        user.casa_cases << casa_case\n      end\n\n      sign_in user\n      visit casa_case_court_date_path(casa_case, court_date)\n\n      expect(page).not_to have_text \"Sorry, you are not authorized to perform this action.\"\n    end\n\n    it \"displays all information associated with the court date correctly\" do\n      if user_type === :volunteer\n        user.casa_cases << casa_case\n      end\n\n      sign_in user\n      visit casa_case_court_date_path(casa_case, court_date)\n\n      expect(page).to have_text court_date_as_date_object.strftime(displayed_date_format)\n      expect(page).to have_text court_report_due_date.strftime(displayed_date_format)\n      expect(page).to have_text casa_case_number\n\n      court_date_court_orders = find(:xpath, \"//h6[text()='Court Orders:']/following-sibling::p[1]\")\n      expect(court_date_court_orders).to have_text(\"There are no court orders associated with this court date.\")\n      court_date_hearing_type = find(:xpath, \"//dt[h6[text()='Hearing Type:']]/following-sibling::dd[1]\")\n      expect(court_date_hearing_type).to have_text(\"None\")\n      court_date_judge = find(:xpath, \"//dt[h6[text()='Judge:']]/following-sibling::dd[1]\")\n      expect(court_date_judge).to have_text(\"None\")\n\n      court_order = create(:case_court_order, casa_case: casa_case)\n      hearing_type = create(:hearing_type)\n      judge = create(:judge)\n      court_date.case_court_orders << court_order\n      court_date.hearing_type = hearing_type\n      court_date.judge = judge\n      court_date.save!\n\n      visit current_path\n\n      expect(page).to have_text court_order.text\n      expect(page).to have_text hearing_type.name\n      expect(page).to have_text judge.name\n    end\n  end\n\n  context \"as a user from an organization not containing the court date\" do\n    let(:other_organization) { create(:casa_org) }\n\n    xit \"does not allow the user to view the court date\" do\n      # TODO the app or browser can't gracefully handle the URL\n      sign_in create(:casa_admin, casa_org: other_organization)\n      visit casa_case_court_date_path(casa_case, court_date)\n\n      expect(page).to have_text \"Sorry, you are not authorized to perform this action.\"\n    end\n  end\n\n  context \"as a user under the same org as the court date\" do\n    context \"as a volunteer not assigned to the case associated with the court date\" do\n      let(:volunteer_not_assigned_to_case) { create(:volunteer, casa_org: organization) }\n\n      it \"does not allow the user to view the court date\" do\n        sign_in volunteer_not_assigned_to_case\n        visit casa_case_court_date_path(casa_case, court_date)\n\n        expect(page).to have_text \"Sorry, you are not authorized to perform this action.\"\n      end\n    end\n\n    context \"as a volunteer assigned to the case associated with the court date\" do\n      it_should_behave_like \"a user able to view court date\", :volunteer\n    end\n\n    context \"as a supervisor belonging to the same org as the case associated with the court date\" do\n      it_should_behave_like \"a user able to view court date\", :supervisor\n    end\n\n    context \"as an admin belonging to the same org as the case associated with the court date\" do\n      it_should_behave_like \"a user able to view court date\", :casa_admin\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/dashboard/show_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"dashboard/show\", type: :system do\n  let(:volunteer) { create(:volunteer, display_name: \"Bob Loblaw\") }\n  let(:casa_admin) { create(:casa_admin, display_name: \"John Doe\") }\n\n  context \"volunteer user\" do\n    before do\n      sign_in volunteer\n    end\n\n    it \"sees all their casa cases\" do\n      casa_case_1 = build(:casa_case, active: true, casa_org: volunteer.casa_org, case_number: \"CINA-1\")\n      casa_case_2 = build(:casa_case, active: true, casa_org: volunteer.casa_org, case_number: \"CINA-2\")\n      casa_case_3 = build(:casa_case, active: true, casa_org: volunteer.casa_org, case_number: \"CINA-3\")\n      create(:case_assignment, volunteer: volunteer, casa_case: casa_case_1)\n      create(:case_assignment, volunteer: volunteer, casa_case: casa_case_2)\n\n      visit casa_cases_path\n      expect(page).to have_text(\"My Cases\")\n      expect(page).to have_text(casa_case_1.case_number)\n      expect(page).to have_text(casa_case_2.case_number)\n      expect(page).not_to have_text(casa_case_3.case_number)\n    end\n\n    it \"volunteer does not see his name in Cases table\" do\n      casa_case = build(:casa_case, active: true, casa_org: volunteer.casa_org, case_number: \"CINA-1\")\n      create(:case_assignment, volunteer: volunteer, casa_case: casa_case)\n\n      visit casa_cases_path\n\n      expect(page).not_to have_css(\"td\", text: \"Bob Loblaw\")\n    end\n\n    it \"displays 'No active cases' when they don't have any assignments\", :js do\n      visit casa_cases_path\n      expect(page).to have_text(\"My Cases\")\n      expect(page).not_to have_css(\"td\", text: \"Bob Loblaw\")\n      expect(page).not_to have_text(\"Detail View\")\n    end\n  end\n\n  context \"admin user\" do\n    before do\n      sign_in casa_admin\n    end\n\n    it \"sees volunteer names in Cases table as a link\" do\n      casa_case = build(:casa_case, active: true, casa_org: volunteer.casa_org, case_number: \"CINA-1\")\n      create(:case_assignment, volunteer: volunteer, casa_case: casa_case)\n\n      visit casa_cases_path\n\n      expect(page).to have_text(\"Bob Loblaw\")\n      expect(page).to have_link(\"Bob Loblaw\")\n      expect(page).to have_css(\"td\", text: \"Bob Loblaw\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/deep_link/deep_link_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"deep_link\", type: :system do\n  describe \"when user recieves a deep link\" do\n    %w[volunteer supervisor casa_admin].each do |user_type|\n      let(:user) { create(user_type.to_sym) }\n\n      it \"redirects #{user_type} to target url immediately after sign in\" do\n        visit \"/users/edit\"\n        fill_in \"Email\", with: user.email\n        fill_in \"Password\", with: \"12345678\"\n        within \".actions\" do\n          find(\"#log-in\").click\n        end\n        expect(page).to have_current_path \"/users/edit\", ignore_query: true\n        expect(page).to have_text \"Edit Profile\"\n      end\n    end\n\n    context \"when user is a volunteer or supervisor\" do\n      %w[volunteer supervisor].each do |user_type|\n        let(:user) { create(user_type.to_sym) }\n\n        it \"flashes unauthorized notice when #{user_type} tries to access a casa_admin link\" do\n          visit \"/casa_admins\"\n          fill_in \"Email\", with: user.email\n          fill_in \"Password\", with: \"12345678\"\n          within \".actions\" do\n            find(\"#log-in\").click\n          end\n          expect(page).to have_text \"Sorry, you are not authorized to perform this action.\"\n        end\n      end\n\n      let(:volunteer) { create(:volunteer) }\n\n      it \"flashes unauthorized notice when volunteer tries to access a supervisor link\" do\n        visit \"/supervisors\"\n        fill_in \"Email\", with: volunteer.email\n        fill_in \"Password\", with: \"12345678\"\n        within \".actions\" do\n          find(\"#log-in\").click\n        end\n        expect(page).to have_text \"Sorry, you are not authorized to perform this action.\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/devise/passwords/new_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"users/passwords/new\", type: :system do\n  before do\n    visit new_user_session_path\n    click_on \"Forgot your password?\"\n  end\n\n  describe \"reset password page\" do\n    it \"displays error messages for non-existent user\" do\n      user = build(:user, email: \"glados@example.com\", phone_number: \"+16578900012\")\n\n      fill_in \"Email\", with: \"tangerine@example.com\"\n      fill_in \"Phone number\", with: user.phone_number\n\n      click_on \"Send me reset password instructions\"\n      expect(page).to have_content \"If the account exists you will receive an email or SMS with instructions on how to reset your password in a few minutes.\"\n    end\n\n    it \"displays phone number error messages for incorrect formatting\" do\n      user = create(:user, email: \"glados@example.com\", phone_number: \"+16578900012\")\n\n      fill_in \"Email\", with: user.email\n      fill_in \"Phone number\", with: \"2134567eee\"\n\n      click_on \"Send me reset password instructions\"\n      expect(page).to have_content \"1 error prohibited this User from being saved:\"\n      expect(page).to have_text(\"Phone number must be 10 digits or 12 digits including country code (+1)\")\n    end\n\n    it \"displays error if user tries to submit an empty form\" do\n      click_on \"Send me reset password instructions\"\n      expect(page).to have_text(\"Please enter at least one field.\")\n    end\n\n    it \"redirects to sign up page for email\" do\n      user = build(:user, email: \"glados@example.com\", phone_number: \"+16578900012\")\n\n      fill_in \"Email\", with: user.email\n\n      click_on \"Send me reset password instructions\"\n      expect(page).to have_content \"If the account exists you will receive an email or SMS with instructions on how to reset your password in a few minutes.\"\n    end\n  end\n\n  describe \"reset password email\" do\n    let!(:user) { create(:user, type: \"Volunteer\", email: \"glados@aperture.labs\") }\n\n    it \"sends user email\" do\n      fill_in \"Email\", with: user.email\n\n      click_on \"Send me reset password instructions\"\n\n      expect(page).to have_content \"If the account exists you will receive an email or SMS with instructions on how to reset your password in a few minutes.\"\n\n      expect(ActionMailer::Base.deliveries.count).to eq(1)\n      expect(ActionMailer::Base.deliveries.last.to).to eq([user.email])\n    end\n\n    it \"has reset password url with token\" do\n      fill_in \"Email\", with: user.email\n      click_on \"Send me reset password instructions\"\n\n      expect(page).to have_content \"If the account exists you will receive an email or SMS with instructions on how to reset your password in a few minutes.\"\n      expect(reset_password_link(user.email)).to match(/http:\\/\\/localhost:3000\\/users\\/password\\/edit\\?reset_password_token=.*/)\n    end\n\n    it \"url token matches user's encrypted token\" do\n      fill_in \"Email\", with: user.email\n      click_on \"Send me reset password instructions\"\n\n      expect(page).to have_content \"If the account exists you will receive an email or SMS with instructions on how to reset your password in a few minutes.\"\n\n      token = reset_password_link(user.email).gsub(\"http://localhost:3000/users/password/edit?reset_password_token=\", \"\")\n      encrypted_token = Devise.token_generator.digest(User, :reset_password_token, token)\n      expect(User.find_by(reset_password_token: encrypted_token)).to be_present\n    end\n\n    it \"user can update password\" do\n      fill_in \"Email\", with: user.email\n      click_on \"Send me reset password instructions\"\n\n      visit reset_password_link(user.email)\n      fill_in \"New password\", with: \"new password\"\n      fill_in \"Confirm new password\", with: \"new password\"\n      click_on \"Change my password\"\n\n      expect(page).to have_text(\"Your password has been changed successfully.\")\n      fill_in \"Email\", with: user.email\n      fill_in \"Password\", with: \"new password\"\n      click_on \"Log In\"\n\n      expect(page).to have_text(user.display_name)\n      expect(page).to have_text(\"My Cases\")\n      expect(page).not_to have_text(\"Sign in\")\n    end\n  end\nend\n\ndef reset_password_link(email_address)\n  email = open_email(email_address)\n  links = links_in_email(email)\n  links[2]\nend\n"
  },
  {
    "path": "spec/system/emancipations/show_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"emancipations/show\", type: :system do\n  let(:org) { build(:casa_org) }\n  let(:volunteer) { build(:volunteer, casa_org: org) }\n  let(:supervisor) { create(:supervisor, casa_org: org) }\n  let(:casa_case) { build(:casa_case, casa_org: org) }\n  let!(:case_assignment) { create(:case_assignment, volunteer: volunteer, casa_case: casa_case) }\n\n  it \"has a download emancipation checklist button\" do\n    sign_in volunteer\n    visit casa_case_emancipation_path(casa_case)\n\n    expect(page).to have_link \"Download Checklist\", href: casa_case_emancipation_path(casa_case, format: :docx)\n  end\n\n  it \"expands the emancipation checklist options\", :js do\n    emancipation_category = create(:emancipation_category)\n    emancipation_option = create(:emancipation_option, emancipation_category: emancipation_category)\n\n    sign_in supervisor\n    visit casa_case_emancipation_path(casa_case)\n\n    find(\".category-collapse-icon\").click\n    expect(page).to have_content(emancipation_option.name)\n    find(\".category-collapse-icon\").click\n    expect(page).not_to have_content(emancipation_option.name)\n  end\nend\n"
  },
  {
    "path": "spec/system/hearing_types/new_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"hearing_types/new\", type: :system do\n  let(:organization) { create(:casa_org) }\n  let(:admin) { create(:casa_admin, casa_org_id: organization.id) }\n  let(:hearing_type) { build_stubbed(:hearing_type, casa_org: organization, name: \"Spec Test Hearing Type\") }\n\n  before do\n    sign_in admin\n\n    visit new_hearing_type_path\n  end\n\n  it \"errors with invalid name\" do\n    fill_in \"Name\", with: \"\"\n    click_on \"Submit\"\n\n    expect(page).to have_text(\"Name can't be blank\")\n  end\n\n  it \"creates with valid data\" do\n    fill_in \"Name\", with: \"Emergency Hearing Type\"\n    click_on \"Submit\"\n\n    expect(page).to have_text(\"Hearing Type was successfully created.\")\n  end\nend\n"
  },
  {
    "path": "spec/system/imports/index_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"imports/index\", type: :system do\n  context \"as a volunteer\" do\n    it \"redirects the user with an error message\" do\n      volunteer = create(:volunteer)\n\n      sign_in volunteer\n      visit imports_path\n\n      expect(page).to have_selector(\".alert\", text: \"Sorry, you are not authorized to perform this action.\")\n    end\n  end\n\n  context \"as an admin\" do\n    context \"import volunteer csv with phone numbers\", :js do\n      it \"shows sms opt in modal\" do\n        import_file_path = file_fixture \"volunteers.csv\"\n        admin = create(:casa_admin)\n\n        sign_in admin\n        visit imports_path(:volunteer)\n\n        expect(page).to have_content(\"Import Volunteers\")\n        expect(page).to have_button(\"volunteer-import-button\", disabled: true)\n\n        attach_file \"volunteer-file\", import_file_path\n        click_button \"volunteer-import-button\"\n\n        expect(page).to have_text(\"SMS Opt In\")\n        expect(page).to have_button(\"sms-opt-in-continue-button\", disabled: true)\n\n        check \"sms-opt-in-checkbox\"\n        click_button \"sms-opt-in-continue-button\"\n\n        expect(page).to have_text(\"You successfully imported\")\n      end\n    end\n\n    context \"import volunteer csv without phone numbers\", :js do\n      it \"shows successful import\" do\n        import_file_path = file_fixture \"volunteers_without_phone_numbers.csv\"\n        admin = create(:casa_admin)\n\n        sign_in admin\n        visit imports_path(:volunteer)\n\n        expect(page).to have_content(\"Import Volunteers\")\n\n        attach_file \"volunteer-file\", import_file_path\n        click_button \"volunteer-import-button\"\n\n        expect(page).to have_text(\"You successfully imported\")\n      end\n    end\n\n    context \"import volunteer csv without display names\", :js do\n      it \"shows failed import modal\" do\n        import_file_path = file_fixture \"volunteers_without_display_names.csv\"\n        admin = create(:casa_admin)\n\n        sign_in admin\n        visit imports_path(:volunteer)\n\n        expect(page).to have_content(\"Import Volunteers\")\n\n        attach_file \"volunteer-file\", import_file_path\n        click_button \"volunteer-import-button\"\n\n        check \"sms-opt-in-checkbox\"\n        click_button \"sms-opt-in-continue-button\"\n\n        expect(page).to have_text(\"CSV Import Error\")\n      end\n    end\n\n    context \"import supervisors csv with phone numbers\", :js do\n      it \"shows sms opt in modal\" do\n        import_file_path = file_fixture \"supervisors.csv\"\n        admin = create(:casa_admin)\n\n        sign_in admin\n        visit imports_path\n        click_on \"supervisor-tab\"\n\n        expect(page).to have_content(\"Import Supervisors\")\n        expect(page).to have_button(\"supervisor-import-button\", disabled: true)\n\n        attach_file \"supervisor-file\", import_file_path\n        click_button \"supervisor-import-button\"\n\n        expect(page).to have_text(\"SMS Opt In\")\n        expect(page).to have_button(\"sms-opt-in-continue-button\", disabled: true)\n\n        find(\"#sms-opt-in-checkbox\", visible: true).check\n        click_button \"sms-opt-in-continue-button\"\n\n        expect(page).to have_text(\"You successfully imported\")\n      end\n    end\n\n    context \"import supervisors csv without phone numbers\", :js do\n      it \"shows successful import\" do\n        import_file_path = file_fixture \"supervisors_without_phone_numbers.csv\"\n        admin = create(:casa_admin)\n\n        sign_in admin\n        visit imports_path\n\n        click_on \"Import Supervisors\"\n\n        expect(page).to have_content(\"Import Supervisors\")\n\n        attach_file \"supervisor-file\", import_file_path\n        click_button \"supervisor-import-button\"\n\n        expect(page).to have_text(\"You successfully imported\")\n      end\n    end\n\n    context \"import supervisors csv without display names\", :js do\n      it \"shows failed import modal\" do\n        import_file_path = file_fixture \"supervisors_without_display_names.csv\"\n        admin = create(:casa_admin)\n\n        sign_in admin\n        visit imports_path\n\n        click_on \"Import Supervisors\"\n\n        expect(page).to have_content(\"Import Supervisors\")\n\n        attach_file \"supervisor-file\", import_file_path\n        click_button \"supervisor-import-button\"\n\n        check \"sms-opt-in-checkbox\"\n        click_button \"sms-opt-in-continue-button\"\n\n        expect(page).to have_text(\"CSV Import Error\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/judges/new_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe \"judges/new\", type: :system do\n  let(:organization) { create(:casa_org) }\n  let(:admin) { create(:casa_admin, casa_org_id: organization.id) }\n  let(:active_name) { Faker::Name.unique.name }\n  let(:inactive_name) { Faker::Name.unique.name }\n\n  before do\n    sign_in admin\n    visit new_judge_path\n  end\n\n  # rubocop:disable RSpec/ExampleLength\n  it \"creates an active judge with valid name\", :aggregate_failures do\n    submit_judge_form(name: active_name, active: true)\n    expect(page).to have_text(\"Judge was successfully created.\")\n    expect(page).to have_text(active_name)\n\n    judge = Judge.find_by(name: active_name)\n    expect(judge).not_to be_nil\n    expect(judge.active).to be true\n  end\n  # rubocop:enable RSpec/ExampleLength\n\n  # rubocop:disable RSpec/ExampleLength\n  it \"creates an inactive judge with valid name\", :aggregate_failures do\n    submit_judge_form(name: inactive_name, active: false)\n    expect(page).to have_text(\"Judge was successfully created.\")\n    expect(page).to have_text(inactive_name)\n\n    judge = Judge.find_by(name: inactive_name)\n    expect(judge).not_to be_nil\n    expect(judge.active).to be false\n  end\n  # rubocop:enable RSpec/ExampleLength\n\n  # rubocop:disable RSpec/ExampleLength\n  it \"creates a judge with a very long name\", :aggregate_failures do\n    long_name = Faker::Lorem.characters(number: 255)\n    submit_judge_form(name: long_name)\n    expect(page).to have_text(\"Judge was successfully created.\")\n    expect(page).to have_text(long_name)\n\n    judge = Judge.find_by(name: long_name)\n    expect(judge).not_to be_nil\n  end\n  # rubocop:enable RSpec/ExampleLength\n\n  # rubocop:disable RSpec/ExampleLength\n  it \"creates a judge with special characters in the name\", :aggregate_failures do\n    special_name = \"#{Faker::Lorem.characters(number: 30, min_alpha: 10, min_numeric: 5)}!@#$%^&*()\"\n    submit_judge_form(name: special_name)\n    expect(page).to have_text(\"Judge was successfully created.\")\n    expect(page).to have_text(special_name)\n\n    judge = Judge.find_by(name: special_name)\n    expect(judge).not_to be_nil\n  end\n  # rubocop:enable RSpec/ExampleLength\n\n  context \"when validations fail\" do\n    it \"shows validation error when name is blank\" do\n      submit_judge_form(name: \"\")\n      expect(page).to have_text(\"Name can't be blank\")\n    end\n\n    it \"does not allow duplicate judge names in the same organization\", :aggregate_failures do\n      duplicate_name = Faker::Name.unique.name\n      create(:judge, name: duplicate_name, casa_org: organization)\n      submit_judge_form(name: duplicate_name)\n      expect(page).to have_text(\"Name has already been taken\")\n      expect(Judge.where(name: duplicate_name, casa_org: organization).count).to eq 1\n    end\n  end\n\n  private\n\n  def submit_judge_form(name:, active: true)\n    fill_in \"Name\", with: name\n    active ? check(\"Active?\") : uncheck(\"Active?\")\n    click_on \"Submit\"\n  end\nend\n"
  },
  {
    "path": "spec/system/languages/languages_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"languages/new\", type: :system do\n  let(:admin) { create(:casa_admin) }\n  let(:organization) { admin.casa_org }\n\n  before do\n    sign_in admin\n\n    visit new_language_path\n  end\n\n  it \"requires name text field\" do\n    expect(page).to have_selector(\"input[required=required]\", id: \"language_name\")\n  end\nend\n"
  },
  {
    "path": "spec/system/learning_hours/edit_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"learning_hours/edit\", type: :system do\n  let(:organization) { create(:casa_org) }\n  let(:volunteer) { create(:volunteer, casa_org_id: organization.id) }\n  let(:learning_hours) { create(:learning_hour, user: volunteer) }\n\n  before do\n    sign_in volunteer\n\n    visit edit_learning_hour_path(learning_hours)\n  end\n\n  it \"shows error message when future date entered\" do\n    datepicker_input = find(\"#learning_hour_occurred_at\")\n    datepicker_input.set((Date.today + 1.month).strftime(\"%Y-%m-%d\"))\n\n    click_on \"Update Learning Hours Entry\"\n\n    expect(page).to have_text(\"Date cannot be in the future\")\n  end\n\n  it \"can update learning hours entry with proper data\" do\n    title = \"Updated Title\"\n\n    expect(page).to have_field(\"Learning Hours Title\", with: learning_hours.name)\n\n    fill_in \"Learning Hours Title\",\twith: title\n    click_on \"Update Learning Hours Entry\"\n\n    expect(page).to have_text(\"Entry was successfully updated.\")\n    expect(page).to have_text(title)\n  end\nend\n"
  },
  {
    "path": "spec/system/learning_hours/index_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"Learning Hours Index\", type: :system do\n  let!(:supervisor) { create(:supervisor, :with_volunteers) }\n  let!(:volunteer) { supervisor.volunteers.first }\n  let!(:learning_hours) { create_list(:learning_hour, 2, user: volunteer) }\n\n  before do\n    sign_in user\n  end\n\n  context \"when the user is a volunteer\" do\n    let(:user) { volunteer }\n\n    it \"displays the volunteer learning hours\" do\n      visit learning_hours_path\n      expect(page).to have_content(\"Learning Hours\")\n      expect(page).to have_content(\"Title\")\n      expect(page).to have_content(\"Time Spent\")\n      expect(page).to have_link(\"Record Learning Hours\", href: new_learning_hour_path)\n    end\n  end\n\n  context \"when the user is a supervisor or admin\" do\n    let(:user) { supervisor }\n\n    before do\n      visit learning_hours_path\n    end\n\n    it \"displays a list of volunteers and the learning hours they completed\", :js do\n      expect(page).to have_content(\"Learning Hours\")\n      expect(page).to have_content(\"Volunteer\")\n      expect(page).to have_content(volunteer.display_name)\n      expect(page).to have_content(\"Time Completed\")\n      expect(page).to have_content(\"#{volunteer.learning_hours.sum(:duration_hours)} hours\")\n    end\n\n    it \"when clicking on a volunteer's name it redirects to the `learning_hours_volunteer_path` for the volunteer\" do\n      click_on volunteer.display_name\n      expect(page).to have_current_path(learning_hours_volunteer_path(volunteer.id))\n    end\n\n    RSpec.shared_examples_for \"functioning sort buttons\" do\n      it \"sorts table columns\" do\n        expect(page).to have_selector(\"tr:nth-child(1)\", text: expected_first_ordered_value)\n\n        find(\"th\", text: column_to_sort).click\n\n        expect(page).to have_selector(\"th.sorting_asc\", text: column_to_sort)\n        expect(page).to have_selector(\"tr:nth-child(1)\", text: expected_last_ordered_value)\n      end\n    end\n\n    it \"shows pagination\", :js do\n      expect(page).to have_content(\"Previous\")\n      expect(page).to have_content(\"Next\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/learning_hours/new_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"learning_hours/new\", :js, type: :system do\n  let(:organization) { create(:casa_org) }\n  let(:volunteer) { create(:volunteer, casa_org_id: organization.id) }\n\n  before do\n    create(:learning_hour_type, casa_org: organization, name: \"Book\")\n\n    sign_in volunteer\n\n    visit new_learning_hour_path\n  end\n\n  it \"errors without selected type of learning\" do\n    fill_in \"Learning Hours Title\",\twith: \"Test title\"\n    fill_in \"Hour(s)\", with: \"0\"\n    fill_in \"Minute(s)\", with: \"30\"\n    click_on \"Create New Learning Hours Entry\"\n\n    expect(page).to have_text(\"Learning hour type must exist\")\n  end\n\n  it \"creates learning hours entry with valid data\" do\n    fill_in \"Learning Hours Title\",\twith: \"Test title\"\n    select \"Book\", from: \"Type of Learning\"\n    fill_in \"Hour(s)\", with: \"0\"\n    fill_in \"Minute(s)\", with: \"30\"\n    click_on \"Create New Learning Hours Entry\"\n\n    expect(page).to have_text(\"New entry was successfully created.\")\n  end\n\n  it \"creates learning hours entry without minutes duration\" do\n    fill_in \"Learning Hours Title\", with: \"Test title\"\n    select \"Book\", from: \"Type of Learning\"\n    fill_in \"Hour(s)\", with: \"3\"\n    click_on \"Create New Learning Hours Entry\"\n\n    expect(page).to have_text(\"New entry was successfully created.\")\n  end\n\n  it \"creates learning hours entry without hours duration\" do\n    fill_in \"Learning Hours Title\", with: \"Test title\"\n    select \"Book\", from: \"Type of Learning\"\n    fill_in \"Minute(s)\", with: \"30\"\n    click_on \"Create New Learning Hours Entry\"\n\n    expect(page).to have_text(\"New entry was successfully created.\")\n  end\n\n  it \"errors without hours and minutes duration\" do\n    fill_in \"Learning Hours Title\", with: \"Test title\"\n    select \"Book\", from: \"Type of Learning\"\n    click_on \"Create New Learning Hours Entry\"\n\n    expect(page).to have_text(\"Duration minutes and hours (total duration) must be greater than 0\")\n  end\n\n  it \"errors if occured on date set in the future\" do\n    fill_in \"Learning Hours Title\", with: \"Test title\"\n    select \"Book\", from: \"Type of Learning\"\n    fill_in \"Hour(s)\", with: \"2\"\n    fill_in \"Minute(s)\", with: \"30\"\n    fill_in \"Occurred On\", with: Date.tomorrow\n    click_on \"Create New Learning Hours Entry\"\n\n    expect(page).to have_text(\"Date cannot be in the future\")\n  end\nend\n"
  },
  {
    "path": "spec/system/learning_hours/volunteers/show_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"LearningHours::Volunteers #show\", type: :system do\n  let!(:volunteer) { create(:volunteer) }\n  let!(:supervisor) { create(:supervisor) }\n  let!(:learning_hours) { create_list(:learning_hour, 5, user: volunteer) }\n\n  before do\n    sign_in user\n  end\n\n  context \"when the user is a volunteer\" do\n    let(:user) { volunteer }\n\n    it \"cannot access this page\" do\n      visit learning_hours_volunteer_path(volunteer.id)\n      expect(page).to have_content(\"Sorry, you are not authorized to perform this action.\")\n    end\n  end\n\n  context \"when the user is a supervisor or admin\" do\n    let(:user) { supervisor }\n\n    before do\n      visit learning_hours_volunteer_path(volunteer.id)\n    end\n\n    it \"displays the volunteer's name\" do\n      expect(page).to have_content(\"#{volunteer.display_name}'s Learning Hours\")\n    end\n\n    it \"displays the volunteer's first learning hours\", :js do\n      expect(page).to have_content(learning_hours.first.name)\n      expect(page).to have_content(learning_hours.first.occurred_at.strftime(\"%B %-d, %Y\"))\n    end\n\n    it \"displays the volunteer's last learning hours\", :js do\n      expect(page).to have_content(learning_hours.last.name)\n      expect(page).to have_content(learning_hours.last.occurred_at.strftime(\"%B %-d, %Y\"))\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/mileage_rates/mileage_rates_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"mileage_rates/new\", :js, type: :system do\n  let(:admin) { create(:casa_admin) }\n  let(:organization) { admin.casa_org }\n\n  before do\n    sign_in admin\n\n    visit mileage_rates_path\n  end\n\n  it \"add new mileage rate\" do\n    click_on \"New Mileage Rate\"\n    expect(page).to have_text(\"New Mileage Rate\")\n    fill_in \"Effective date\", with: Date.new(2020, 1, 2)\n    fill_in \"Amount\", with: 1.35\n    uncheck \"Currently active?\"\n    click_on \"Save Mileage Rate\"\n\n    expect(page).to have_text(\"Mileage Rates\")\n    expect(page).to have_text(\"Effective date\")\n    expect(page).to have_text(\"January 2, 2020\")\n    expect(page).to have_text(\"Amount\")\n    expect(page).to have_text(\"$1.35\")\n    expect(page).to have_text(\"Active?\")\n    expect(page).to have_text(\"No\")\n    expect(page).to have_text(\"Actions\")\n    expect(page).to have_text(\"Edit\")\n  end\nend\n"
  },
  {
    "path": "spec/system/notifications/index_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"notifications/index\", :js, type: :system do\n  let(:admin) { create(:casa_admin) }\n  let(:volunteer) { build(:volunteer) }\n  let(:case_contact) { create(:case_contact, creator: volunteer) }\n  let(:casa_case) { case_contact.casa_case }\n\n  before { casa_case.assigned_volunteers << volunteer }\n\n  context \"FollowupResolvedNotifier\" do\n    let(:notification_message) { \"#{volunteer.display_name} resolved a follow up. Click to see more.\" }\n    let!(:followup) { create(:followup, creator: admin, case_contact: case_contact) }\n\n    before do\n      sign_in volunteer\n\n      visit case_contacts_path\n      click_button \"Resolve Reminder\"\n      has_button?(\"Make Reminder\")\n    end\n\n    it \"lists my notifications\" do\n      sign_in admin\n      visit notifications_path\n\n      expect(page).to have_text(notification_message)\n      expect(page).to have_text(\"Followup resolved\")\n      expect(page).not_to have_text(\"You currently don't have any notifications. Notifications are generated when someone requests follow-up on a case contact.\")\n    end\n\n    context \"when volunteer changes its name\" do\n      let(:created_by_name) { \"Foo bar\" }\n      let(:new_notification_message) { \"#{created_by_name} resolved a follow up. Click to see more.\" }\n\n      it \"lists notifications showing it's current name\" do\n        visit edit_users_path\n        fill_in \"Display name\", with: created_by_name\n        click_on \"Update Profile\"\n        expect(page).to have_content \"Profile was successfully updated\"\n\n        sign_in admin\n        visit notifications_path\n\n        expect(page).to have_text(new_notification_message)\n        expect(page).not_to have_text(notification_message)\n        expect(page).not_to have_text(\"You currently don't have any notifications. Notifications are generated when someone requests follow-up on a case contact.\")\n      end\n    end\n  end\n\n  context \"FollowupNotifier\", :js do\n    let(:note) { \"Lorem ipsum dolor sit amet.\" }\n\n    let(:notification_message_heading) { \"#{admin.display_name} has flagged a Case Contact that needs follow up.\" }\n    let(:notification_message_more_info) { \"Click to see more.\" }\n\n    let(:inline_notification_message) { \"#{notification_message_heading} #{notification_message_more_info}\" }\n\n    before do\n      sign_in admin\n      visit casa_case_path(casa_case)\n    end\n\n    context \"when followup has a note\" do\n      before do\n        click_button \"Make Reminder\"\n        find(\".swal2-textarea\").set(note)\n\n        click_button \"Confirm\"\n      end\n\n      it \"lists followup notifications, showing their note\" do\n        within(\"#resolve\", wait: 5) do\n          expect(page).to have_content \"Resolve Reminder\"\n        end\n\n        sign_in volunteer\n        visit notifications_path\n\n        expect(page).to have_text(notification_message_heading)\n        expect(page).to have_text(note)\n        expect(page).to have_text(notification_message_more_info)\n        expect(page).not_to have_text(\"You currently don't have any notifications. Notifications are generated when someone requests follow-up on a case contact.\")\n        expect(page).to have_text(\"New followup\")\n      end\n    end\n\n    context \"when followup doesn't have a note\" do\n      before do\n        click_button \"Make Reminder\"\n        click_button \"Confirm\"\n      end\n\n      it \"lists followup notifications, showing the information in a single line when there are no notes\" do\n        within(\"#resolve\", wait: 5) do\n          expect(page).to have_content \"Resolve Reminder\"\n        end\n\n        sign_in volunteer\n        visit notifications_path\n\n        expect(page).to have_text(inline_notification_message)\n        expect(page).not_to have_text(\"You currently don't have any notifications. Notifications are generated when someone requests follow-up on a case contact.\")\n        expect(page).to have_text(\"New followup\")\n      end\n    end\n\n    context \"when admin changes its name\" do\n      let(:created_by_name) { \"Foo bar\" }\n      let(:new_notification_message) { \"#{created_by_name} has flagged a Case Contact that needs follow up.\" }\n\n      before do\n        click_button \"Make Reminder\"\n      end\n\n      it \"lists followup notifications showing admin current name\" do\n        click_button \"Confirm\"\n\n        within(\"#resolve\", wait: 5) do\n          expect(page).to have_content \"Resolve Reminder\"\n        end\n\n        visit edit_users_path\n        fill_in \"Display name\", with: created_by_name\n        click_on \"Update Profile\"\n        expect(page).to have_content \"Profile was successfully updated\"\n\n        sign_in volunteer\n        visit notifications_path\n\n        expect(page).to have_text(new_notification_message)\n        expect(page).not_to have_text(inline_notification_message)\n        expect(page).not_to have_text(\"You currently don't have any notifications. Notifications are generated when someone requests follow-up on a case contact.\")\n        expect(page).to have_text(\"New followup\")\n      end\n    end\n  end\n\n  context \"EmancipationChecklistReminder\" do\n    let(:notifier) { create(:emancipation_checklist_reminder_notifier, params: {casa_case: casa_case}) }\n    let(:notification) { create(:notification, :emancipation_checklist_reminder, event: notifier) }\n\n    before do\n      volunteer.notifications << notification\n      sign_in volunteer\n      visit notifications_path\n    end\n\n    it \"displays a notification reminder that links to the emancipation checklist\" do\n      notification_message = \"Your case #{casa_case.case_number} is a transition aged youth. We want to make sure that along the way, we’re preparing our youth for emancipation. Make sure to check the emancipation checklist.\"\n      expect(page).not_to have_text(\"You currently don't have any notifications. Notifications are generated when someone requests follow-up on a case contact.\")\n      expect(page).to have_content(\"Emancipation Checklist Reminder\")\n      expect(page).to have_link(notification_message, href: mark_as_read_notification_path(notification))\n    end\n  end\n\n  context \"YouthBirthdayNotifier\" do\n    let(:notifier) { create(:youth_birthday_notifier, params: {casa_case: casa_case}) }\n    let(:notification) { create(:notification, :youth_birthday, event: notifier) }\n\n    before do\n      volunteer.notifications << notification\n      sign_in volunteer\n      visit notifications_path\n    end\n\n    it \"displays a notification on the notifications page\" do\n      notification_message = \"Your youth, case number: #{casa_case.case_number} has a birthday next month.\"\n      expect(page).not_to have_text(\"You currently don't have any notifications. Notifications are generated when someone requests follow-up on a case contact.\")\n      expect(page).to have_content(\"Youth Birthday Notification\")\n      expect(page).to have_link(notification_message, href: mark_as_read_notification_path(notification))\n    end\n  end\n\n  context \"ReimbursementCompleteNotifier\" do\n    it \"displays a notification on the notifications page\" do\n      case_contact = create(:case_contact, :wants_reimbursement, casa_case: volunteer.casa_cases.first)\n      notifier = create(:reimbursement_complete_notifier, params: {case_contact: case_contact})\n      notification = create(:notification, :reimbursement_complete, event: notifier)\n\n      volunteer.notifications << notification\n      sign_in volunteer\n      visit notifications_path\n      notification_message = \"Volunteer #{case_contact.creator.display_name}'s request for reimbursement for \" \\\n        \"#{case_contact.miles_driven}mi on #{case_contact.occurred_at_display} has been processed and is \" \\\n        \"en route.\"\n      expect(page).not_to have_text(\"You currently don't have any notifications. Notifications are generated when someone requests follow-up on a case contact.\")\n      expect(page).to have_content(\"Reimbursement Approved\")\n      expect(page).to have_content(notification_message)\n      expect(page).to have_link(href: mark_as_read_notification_path(notification))\n    end\n  end\n\n  context \"when there are no notifications\" do\n    it \"displays a message to the user\" do\n      sign_in volunteer\n      visit notifications_path\n\n      expect(page).to have_text(\"You currently don't have any notifications. Notifications are generated when someone requests follow-up on a case contact.\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/other_duties/new_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"other_duties/new\", type: :system do\n  let(:casa_org) { build(:casa_org) }\n  let(:admin) { create(:casa_admin, casa_org: casa_org) }\n  let(:case_number) { \"12345\" }\n  let!(:next_year) { (Date.today.year + 1).to_s }\n  let(:court_date) { 21.days.from_now }\n\n  let(:organization) { build(:casa_org) }\n  let(:volunteer) { create(:volunteer, :with_casa_cases, casa_org: organization) }\n\n  before do\n    sign_in volunteer\n    visit root_path\n  end\n\n  context \"as a volunteer\", :js do\n    it \"sees a New Duty link\" do\n      visit other_duties_path\n      expect(page).to have_link(\"New Duty\", href: new_other_duty_path)\n    end\n\n    it \"sees all their other duties\", :js do\n      volunteer_2 = create(:volunteer, display_name: \"Other Volunteer\")\n\n      other_duty_1 = create(:other_duty, notes: \"Test 1\", creator_id: volunteer.id)\n      other_duty_2 = create(:other_duty, notes: \"Test 2\", creator_id: volunteer.id)\n      other_duty_3 = create(:other_duty, notes: \"Test 3\", creator_id: volunteer_2.id)\n\n      visit other_duties_path\n\n      expect(page).to have_text(\"Other Duties\")\n      expect(page).to have_text(other_duty_1.notes)\n      expect(page).to have_text(other_duty_2.notes)\n      expect(page).not_to have_text(other_duty_3.notes)\n    end\n\n    it \"has an error if a new duty is attempted to be created without any notes\" do\n      click_on \"Other Duties\"\n      click_on \"New Duty\"\n\n      click_on \"Submit\"\n\n      message = page.find(\"#other_duty_notes\").native.attribute(\"validationMessage\")\n      expect(message).to match(/Please fill (in|out) this field./)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/placements/destroy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"placements/destroy\", type: :system do\n  let(:now) { Date.new(2025, 1, 2) }\n  let(:casa_org) { create(:casa_org, :with_placement_types) }\n  let(:admin) { create(:casa_admin, casa_org:) }\n  let(:casa_case) { create(:casa_case, casa_org:, case_number: \"123\") }\n  let(:placement_type) { create(:placement_type, name: \"Reunification\", casa_org:) }\n  let(:placement) { create(:placement, placement_started_at: \"2024-08-15 20:40:44 UTC\", casa_case:, placement_type:) }\n\n  before do\n    travel_to now\n    sign_in admin\n    visit casa_case_placements_path(casa_case, placement)\n    click_on \"Delete\"\n  end\n\n  it \"does not delete on modal close\" do\n    expect(page).to have_text(\"Delete Placement?\")\n    click_on \"Close\"\n\n    expect(page).to have_text(\"Reunification\")\n    expect(page).to have_text(\"August 15, 2024 - Present\")\n  end\n\n  it \"deletes placement\" do\n    expect(page).to have_text(\"Delete Placement?\")\n    click_on \"Confirm\"\n\n    expect(page).to have_text(\"Placement was successfully deleted.\")\n    expect(page).not_to have_text(\"Reunification\")\n    expect(page).not_to have_text(\"August 15, 2024 - Present\")\n  end\nend\n"
  },
  {
    "path": "spec/system/placements/edit_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe \"placements/edit\", type: :system do\n  let(:now) { Date.new(2025, 1, 2) }\n  let(:casa_org) { create(:casa_org, :with_placement_types) }\n  let(:admin) { create(:casa_admin, casa_org:) }\n  let(:casa_case) { create(:casa_case, casa_org:, case_number: \"123\") }\n  let(:placement_type) { create(:placement_type, name: \"Reunification\", casa_org:) }\n  let(:placement) { create(:placement, placement_started_at: \"2024-08-15 20:40:44 UTC\", casa_case:, placement_type:) }\n\n  before do\n    travel_to now\n    sign_in admin\n    visit casa_case_placement_path(casa_case, placement)\n    click_link(\"Edit\")\n  end\n\n  it \"updates placement with valid form data\", :js do\n    expect(page).to have_content(\"123\")\n\n    fill_in \"Placement Started At\", with: now - 5.years\n    select \"Kinship\", from: \"Placement Type\"\n\n    click_on \"Update\"\n\n    expect(page).to have_content(\"Placement was successfully updated.\")\n    expect(page).to have_content(\"123\")\n    expect(page).to have_content(\"January 2, 2020\")\n    expect(page).to have_content(\"Kinship\")\n  end\n\n  it \"rejects placement update with invalid form data\" do\n    fill_in \"Placement Started At\", with: 1000.years.ago\n    click_on \"Update\"\n\n    expect(page).to have_content(\"1 error prohibited this Placement from being saved:\\nPlacement started at cannot be prior to 1/1/1989.\")\n  end\nend\n"
  },
  {
    "path": "spec/system/placements/index_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"placements\", type: :system do\n  let(:now) { Date.new(2025, 1, 2) }\n  let(:casa_org) { create(:casa_org, :with_placement_types) }\n  let(:admin) { create(:casa_admin, casa_org:) }\n  let(:casa_case) { create(:casa_case, casa_org:, case_number: \"123\") }\n  let(:placement_current) { create(:placement_type, name: \"Reunification\", casa_org:) }\n  let(:placement_prev) { create(:placement_type, name: \"Kinship\", casa_org:) }\n  let(:placement_first) { create(:placement_type, name: \"Adoption\", casa_org:) }\n  let(:placements) do\n    [\n      create(:placement, placement_started_at: \"2024-08-15 20:40:44 UTC\", casa_case:, placement_type: placement_current),\n      create(:placement, placement_started_at: \"2023-06-02 00:00:00 UTC\", casa_case:, placement_type: placement_prev),\n      create(:placement, placement_started_at: \"2021-12-25 10:10:10 UTC\", casa_case:, placement_type: placement_first)\n    ]\n  end\n\n  before do\n    travel_to now\n    sign_in admin\n    visit casa_case_placements_path(casa_case, placements)\n  end\n\n  it \"displays all placements for org\" do\n    expect(page).to have_text(\"Reunification\")\n    expect(page).to have_text(\"August 15, 2024 - Present\")\n    expect(page).to have_text(\"Kinship\")\n    expect(page).to have_text(\"June 2, 2023 - August 15, 2024\")\n    expect(page).to have_text(\"Adoption\")\n    expect(page).to have_text(\"December 25, 2021 - June 2, 2023\")\n  end\nend\n"
  },
  {
    "path": "spec/system/placements/new_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe \"placements/new\", type: :system do\n  let(:now) { Date.current }\n  let(:formatted_date) { now.strftime(\"%B %-d, %Y\") }\n  let(:casa_org) { create(:casa_org, :with_placement_types) }\n  let(:admin) { create(:casa_admin, casa_org:) }\n  let(:casa_case) { create(:casa_case, casa_org:, case_number: \"123\") }\n  let(:placement_type) { create(:placement_type, name: \"Reunification\", casa_org:) }\n  let(:placement) { create(:placement, placement_started_at: \"2024-08-15 20:40:44 UTC\", casa_case:, placement_type:) }\n\n  before do\n    sign_in admin\n    visit casa_case_placements_path(casa_case)\n    click_link(\"New Placement\")\n  end\n\n  it \"creates placement with valid form data\", :js do\n    expect(page).to have_content(\"123\")\n\n    fill_in \"Placement Started At\", with: now\n    select placement_type.name, from: \"Placement Type\"\n\n    click_on \"Create\"\n\n    expect(page).to have_content(\"Placement was successfully created.\")\n    expect(page).to have_content(\"123\")\n    expect(page).to have_content(formatted_date)\n    expect(page).to have_content(\"Reunification\")\n  end\n\n  it \"rejects placement with invalid form data\" do\n    fill_in \"Placement Started At\", with: 1000.years.ago\n    click_on \"Create\"\n\n    expect(page).to have_content(\"2 errors prohibited this Placement from being saved:\\nPlacement type must exist Placement started at cannot be prior to 1/1/1989.\")\n  end\nend\n"
  },
  {
    "path": "spec/system/reimbursements/reimbursements_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe \"reimbursements\", type: :system do\n  let(:admin) { create(:casa_admin) }\n  let!(:contact1) { create(:case_contact, :wants_reimbursement) }\n  let!(:contact2) { create(:case_contact, :wants_reimbursement) }\n\n  before do\n    sign_in admin\n    visit reimbursements_path\n  end\n\n  it \"shows reimbursements\", :js do\n    expect(page).to have_content(\"Needs Review\")\n    expect(page).to have_content(\"Reimbursement Complete\")\n    expect(page).to have_content(\"Occurred At\")\n    expect(page).to have_content(contact1.casa_case.case_number)\n    expect(page).to have_content(contact2.miles_driven)\n  end\n\n  it \"shows pagination\", :js do\n    expect(page).to have_content(\"Previous\")\n    expect(page).to have_content(\"Next\")\n  end\n\n  it \"filters by volunteers\", :js do\n    expect(page).to have_selector(\"#reimbursements-datatable tbody tr\", count: 2)\n\n    page.find(\".select2-search__field\").click\n    send_keys(contact1.creator.display_name)\n    send_keys(:enter)\n\n    expect(page).to have_selector(\"#reimbursements-datatable tbody tr\", count: 1)\n    expect(page).to have_content contact1.creator.display_name\n\n    page.find(\".select2-selection__choice__remove\").click\n\n    expect(page).to have_selector(\"#reimbursements-datatable tbody tr\", count: 2)\n  end\nend\n"
  },
  {
    "path": "spec/system/reports/export_data_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"case_contact_reports/index\", type: :system do\n  let(:admin) { create(:casa_admin) }\n\n  it \"filters report by date and selected contact type\", :js do\n    sign_in admin\n\n    contact_type_group = create(:contact_type_group)\n    court = create(:contact_type, name: \"Court\", contact_type_group: contact_type_group)\n    school = create(:contact_type, name: \"School\", contact_type_group: contact_type_group)\n\n    contact1 = create(:case_contact, occurred_at: 20.days.ago, contact_types: [court], notes: \"Case Contact 1\")\n    contact2 = create(:case_contact, occurred_at: 20.days.ago, contact_types: [court], notes: \"Case Contact 2\")\n    contact3 = create(:case_contact, occurred_at: 20.days.ago, contact_types: [court, school], notes: \"Case Contact 3\")\n\n    excluded_by_date = create(:case_contact, occurred_at: 40.days.ago, contact_types: [court], notes: \"Excluded by date\")\n    excluded_by_contact_type = create(:case_contact, occurred_at: 20.days.ago, contact_types: [school], notes: \"Excluded by Contact Type\")\n\n    visit reports_path\n    start_date = 30.days.ago\n    end_date = 10.days.ago\n    fill_in \"report_start_date\", with: start_date\n    fill_in \"report_end_date\", with: end_date\n    select court.name, from: \"multiple-select-field3\"\n    click_button \"Download Report\"\n    wait_for_download\n\n    expect(download_content).to include(contact1.notes)\n    expect(download_content).to include(contact2.notes)\n    expect(download_content).to include(contact3.notes)\n\n    expect(download_content).not_to include(excluded_by_date.notes)\n    expect(download_content).not_to include(excluded_by_contact_type.notes)\n  end\n\n  it \"filters report by contact type group\", :js do\n    sign_in admin\n\n    contact_type_group = create(:contact_type_group)\n    court = create(:contact_type, name: \"Court\", contact_type_group: contact_type_group)\n    contact1 = create(:case_contact, occurred_at: Date.yesterday, contact_types: [court], notes: \"Case Contact 1\")\n\n    excluded_contact_type_group = create(:contact_type_group)\n    school = create(:contact_type, name: \"School\", contact_type_group: excluded_contact_type_group)\n    excluded_by_contact_type_group = create(:case_contact, occurred_at: Date.yesterday, contact_types: [school], notes: \"Excluded by Contact Type\")\n\n    visit reports_path\n    select contact_type_group.name, from: \"multiple-select-field4\"\n    click_button \"Download Report\"\n    wait_for_download\n\n    expect(download_content).to include(contact1.notes)\n    expect(download_content).not_to include(excluded_by_contact_type_group.notes)\n  end\n\n  it \"downloads mileage report\", :js do\n    sign_in admin\n\n    supervisor = create(:supervisor)\n    volunteer = create(:volunteer, supervisor: supervisor)\n    case_contact_with_mileage = create(:case_contact, want_driving_reimbursement: true, miles_driven: 10, creator: volunteer)\n    case_contact_without_mileage = create(:case_contact)\n\n    visit reports_path\n    click_button \"Mileage Report\"\n    wait_for_download\n\n    expect(download_file_name).to match(/mileage-report-\\d{4}-\\d{2}-\\d{2}.csv/)\n    expect(download_content).to include(case_contact_with_mileage.creator.display_name)\n    expect(download_content).to include(case_contact_with_mileage.creator.supervisor.display_name)\n    expect(download_content).not_to include(case_contact_without_mileage.creator.display_name)\n  end\n\n  it \"downloads missing data report\", :js do\n    sign_in admin\n\n    visit reports_path\n    click_button \"Missing Data Report\"\n    wait_for_download\n\n    expect(download_file_name).to match(/missing-data-report-\\d{4}-\\d{2}-\\d{2}.csv/)\n  end\n\n  it \"downloads learning hours report\", :js do\n    sign_in admin\n\n    visit reports_path\n    click_button \"Learning Hours Report\"\n    wait_for_download\n\n    expect(download_file_name).to match(/learning-hours-report-\\d{4}-\\d{2}-\\d{2}.csv/)\n  end\n\n  it \"downloads followup report\", :js do\n    sign_in admin\n\n    visit reports_path\n    click_button \"Followups Report\"\n    wait_for_download\n\n    expect(download_file_name).to match(/followup-report-\\d{4}-\\d{2}-\\d{2}.csv/)\n  end\n\n  context \"as volunteer\" do\n    let(:volunteer) { create(:volunteer) }\n\n    it \"cannot accesses reports page\" do\n      sign_in volunteer\n\n      visit reports_path\n      expect(page).to have_current_path(casa_cases_path, ignore_query: true)\n      expect(page).to have_text \"Sorry, you are not authorized to perform this action.\"\n    end\n\n    it \"cannot download followup report\" do\n      sign_in volunteer\n\n      visit followup_reports_path\n      expect(page).to have_current_path(casa_cases_path, ignore_query: true)\n      expect(page).to have_text \"Sorry, you are not authorized to perform this action.\"\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/reports/index_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nVOLUNTEER_SELECT_ID = \"multiple-select-field2\"\nSUPERVISOR_SELECT_ID = \"multiple-select-field1\"\nCONTACT_TYPE_SELECT_ID = \"multiple-select-field3\"\nCONTACT_TYPE_GROUP_SELECT_ID = \"multiple-select-field4\"\n\nRSpec.describe \"reports\", :js, type: :system do\n  shared_examples \"downloads report button\" do |button_name, feedback|\n    it \"downloads #{button_name.downcase}\", :aggregate_failures do\n      expect(page).to have_button(button_name)\n      click_on button_name\n      expect(page).to have_text(feedback)\n    end\n  end\n\n  shared_examples \"downloads case contacts report with filter\" do |filter_name, setup_action, filter_action|\n    it \"downloads case contacts report with #{filter_name}\" do\n      instance_exec(&setup_action) if setup_action\n      visit reports_path\n      instance_exec(&filter_action)\n      click_on \"Download Report\"\n      expect(page).to have_text(\"Downloading Report\")\n    end\n  end\n\n  shared_examples \"empty select downloads report\" do |select_id, description|\n    it \"renders the #{description} select with no options and downloads the report\" do\n      expect(page).to have_select(select_id, options: [])\n      click_on \"Download Report\"\n      expect(page).to have_text(\"Downloading Report\")\n    end\n  end\n\n  context \"with a volunteer user\" do\n    before do\n      user = create(:volunteer)\n\n      sign_in user\n      visit reports_path\n    end\n\n    it \"redirects to root\", :aggregate_failures do\n      expect(page).not_to have_text \"Case Contacts Report\"\n      expect(page).to have_text \"Sorry, you are not authorized to perform this action.\"\n    end\n  end\n\n  %i[supervisor casa_admin].each do |role|\n    # rubocop:disable RSpec/MultipleMemoizedHelpers\n    context \"with a #{role} user\" do\n      let(:user) { create(role) }\n      let(:volunteer_name) { Faker::Name.unique.name }\n      let(:supervisor_name) { Faker::Name.unique.name }\n      let(:contact_type_name) { Faker::Lorem.unique.word }\n      let(:contact_type_group_name) { Faker::Lorem.unique.word }\n      let(:filter_start_date) { \"2025-01-01\" }\n      let(:filter_end_date) { \"2025-10-08\" }\n\n      before do\n        sign_in user\n        visit reports_path\n      end\n\n      it \"renders form elements\", :aggregate_failures do\n        expect(page).to have_text \"Case Contacts Report\"\n        expect(page).to have_field(\"report_start_date\", with: 6.months.ago.strftime(\"%Y-%m-%d\"))\n        expect(page).to have_field(\"report_end_date\", with: Date.today)\n        expect(page).to have_text \"Assigned To\"\n        expect(page).to have_text \"Volunteers\"\n        expect(page).to have_text \"Contact Type\"\n        expect(page).to have_text \"Contact Type Group\"\n        expect(page).to have_text \"Want Driving Reimbursement\"\n        expect(page).to have_text \"Contact Made\"\n        expect(page).to have_text \"Transition Aged Youth\"\n        expect(page).to have_field(\"Both\", count: 3)\n        expect(page).to have_field(\"Yes\", count: 3)\n        expect(page).to have_field(\"No\", count: 3)\n      end\n\n      it \"downloads case contacts report with default filters\" do\n        click_on \"Download Report\"\n        expect(page).to have_text(\"Downloading Report\")\n      end\n\n      include_examples \"downloads report button\", \"Mileage Report\", \"Downloading Mileage Report\"\n      include_examples \"downloads report button\", \"Missing Data Report\", \"Downloading Missing Data Report\"\n      include_examples \"downloads report button\", \"Learning Hours Report\", \"Downloading Learning Hours Report\"\n      include_examples \"downloads report button\", \"Export Volunteers Emails\", \"Downloading Export Volunteers Emails\"\n      include_examples \"downloads report button\", \"Followups Report\", \"Downloading Followups Report\"\n      include_examples \"downloads report button\", \"Placements Report\", \"Downloading Placements Report\"\n\n      shared_examples \"case contacts report with filter\" do |filter_type|\n        it \"downloads case contacts report with #{filter_type}\" do\n          click_on \"Download Report\"\n          expect(page).to have_text(\"Downloading Report\")\n        end\n      end\n\n      context \"with an assigned supervisor filter\" do\n        before do\n          create(:supervisor, casa_org: user.casa_org, display_name: supervisor_name)\n          visit reports_path\n          select_report_filter_option(SUPERVISOR_SELECT_ID, supervisor_name)\n        end\n\n        include_examples \"case contacts report with filter\", \"assigned supervisor\"\n      end\n\n      context \"with a volunteer filter\" do\n        before do\n          create(:volunteer, casa_org: user.casa_org, display_name: volunteer_name)\n          visit reports_path\n          select_report_filter_option(VOLUNTEER_SELECT_ID, volunteer_name)\n        end\n\n        include_examples \"case contacts report with filter\", \"volunteer\"\n      end\n\n      context \"with a contact type filter\" do\n        before do\n          create(:contact_type, casa_org: user.casa_org, name: contact_type_name)\n          visit reports_path\n          select_report_filter_option(CONTACT_TYPE_SELECT_ID, contact_type_name)\n        end\n\n        include_examples \"case contacts report with filter\", \"contact type\"\n      end\n\n      context \"with a contact type group filter\" do\n        before do\n          create(:contact_type_group, casa_org: user.casa_org, name: contact_type_group_name)\n          visit reports_path\n          select_report_filter_option(CONTACT_TYPE_GROUP_SELECT_ID, contact_type_group_name)\n        end\n\n        include_examples \"case contacts report with filter\", \"contact type group\"\n      end\n\n      context \"with a driving reimbursement filter\" do\n        before do\n          visit reports_path\n          choose_report_radio_option(\"want_driving_reimbursement\", \"true\")\n        end\n\n        include_examples \"case contacts report with filter\", \"driving reimbursement\"\n      end\n\n      context \"with a contact made filters\" do\n        before do\n          visit reports_path\n          choose_report_radio_option(\"contact_made\", \"true\")\n        end\n\n        include_examples \"case contacts report with filter\", \"contact made\"\n      end\n\n      context \"with a transition aged youth filter\" do\n        before do\n          visit reports_path\n          choose_report_radio_option(\"has_transitioned\", \"true\")\n        end\n\n        include_examples \"case contacts report with filter\", \"transition aged youth\"\n      end\n\n      context \"with a date range filter\" do\n        before do\n          visit reports_path\n          set_report_date_range(start_date: filter_start_date, end_date: filter_end_date)\n        end\n\n        include_examples \"case contacts report with filter\", \"date range\"\n      end\n\n      context \"with multiple filters\" do\n        before do\n          create(:volunteer, casa_org: user.casa_org, display_name: volunteer_name)\n          create(:contact_type, casa_org: user.casa_org, name: contact_type_name)\n          visit reports_path\n          set_report_date_range(start_date: filter_start_date, end_date: filter_end_date)\n          select_report_filter_option(VOLUNTEER_SELECT_ID, volunteer_name)\n          select_report_filter_option(CONTACT_TYPE_SELECT_ID, contact_type_name)\n          choose_report_radio_option(\"want_driving_reimbursement\", \"false\")\n        end\n\n        include_examples \"case contacts report with filter\", \"multiple filters\"\n      end\n\n      context \"with no volunteers in the org\" do\n        include_examples \"empty select downloads report\", VOLUNTEER_SELECT_ID, \"volunteers\"\n      end\n\n      context \"with no contact type groups in the org\" do\n        include_examples \"empty select downloads report\", CONTACT_TYPE_GROUP_SELECT_ID, \"contact type groups\"\n      end\n\n      context \"with no contact types in the org\" do\n        include_examples \"empty select downloads report\", CONTACT_TYPE_SELECT_ID, \"contact types\"\n      end\n    end\n    # rubocop:enable RSpec/MultipleMemoizedHelpers\n  end\n\n  private\n\n  def select_report_filter_option(select_id, option)\n    expect(page).to have_select(select_id, with_options: [option])\n    find(\"##{select_id}\").select(option)\n  end\n\n  def set_report_date_range(start_date:, end_date:)\n    fill_in \"report_start_date\", with: start_date\n    fill_in \"report_end_date\", with: end_date\n  end\n\n  def choose_report_radio_option(field_name, value)\n    find(\"input[name=\\\"report[#{field_name}]\\\"][value=\\\"#{value}\\\"]\", visible: :all).click\n  end\nend\n"
  },
  {
    "path": "spec/system/sessions/destroy_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"sessions/destroy\", type: :system do\n  context \"when a user is timed out\" do\n    let(:user) { build(:casa_admin) }\n\n    before { sign_in user }\n\n    it \"ends the current session and redirects to sign in page after timeout\" do\n      allow(user).to receive(:timedout?).and_return(true)\n      visit \"/case_contacts/new\"\n      expect(page).to have_current_path \"/users/sign_in\", ignore_query: true\n      expect(page).to have_text \"Your session expired. Please sign in again to continue.\"\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/sessions/login_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"User Login\", type: :system do\n  %w[volunteer supervisor casa_admin].each do |user_type|\n    let!(:user) { create(user_type.to_sym) }\n\n    it \"shows the user's email after successful login\" do\n      visit new_user_session_path\n      fill_in \"Email\", with: user.email\n      fill_in \"Password\", with: \"12345678\"\n      within \".actions\" do\n        find(\"#log-in\").click\n      end\n\n      expect(page).to have_text user.email\n    end\n\n    it \"shows an error message after failed login\" do\n      visit new_user_session_path\n      fill_in \"Email\", with: user.email\n      fill_in \"Password\", with: \"wrong_password\"\n      within \".actions\" do\n        find(\"#log-in\").click\n      end\n\n      expect(page).to have_content(/invalid email or password/i)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/sessions/new_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"sessions/new\", type: :system do\n  context \"when guest\" do\n    it \"renders sign in page with no flash messages\" do\n      visit \"/\"\n      expect(page).to have_text \"Login\"\n      expect(page).not_to have_text \"sign in before continuing\"\n    end\n\n    %w[volunteer supervisor casa_admin].each do |user_type|\n      before do\n        visit \"/\"\n      end\n\n      it \"allows #{user_type} to click email link\" do\n        expect(page).to have_text \"Want to use the CASA Volunteer Tracking App?\"\n        expect(page).to have_link(\"casa@rubyforgood.org\", href: \"mailto:casa@rubyforgood.org?Subject=CASA%20Interest\")\n      end\n\n      it \"renders sign in page with no flash messages\" do\n        expect(page).to have_text \"Login\"\n        expect(page).not_to have_text \"sign in before continuing\"\n      end\n\n      context \"when a #{user_type} fills in their email and password\" do\n        let!(:user) { create(user_type.to_sym) }\n\n        before do\n          visit \"/users/sign_in\"\n          fill_in \"Email\", with: user.email\n          fill_in \"Password\", with: \"12345678\"\n          within \".actions\" do\n            find(\"#log-in\").click\n          end\n        end\n\n        it \"allows them to sign in\" do\n          expect(page).to have_text user.email\n        end\n\n        context \"but they are inactive\" do\n          let!(:user) { create(user_type.to_sym, active: false) }\n\n          it \"does not allow them to sign in\" do\n            expect(page).to have_text I18n.t(\"devise.failure.inactive\")\n          end\n        end\n      end\n    end\n\n    it \"does not allow AllCasaAdmin to sign in\" do\n      user = build_stubbed(:all_casa_admin)\n\n      visit \"/users/sign_in\"\n      expect(page).to have_text \"Log In\"\n      expect(page).not_to have_text \"sign in before continuing\"\n\n      fill_in \"Email\", with: user.email\n      fill_in \"Password\", with: \"12345678\"\n      within \".actions\" do\n        find(\"#log-in\").click\n      end\n\n      expect(page).to have_text(/invalid email or password/i)\n    end\n  end\n\n  context \"when authenticated admin\" do\n    let(:user) { create(:casa_admin) }\n\n    before { sign_in user }\n\n    it \"renders dashboard page and shows correct flash message upon sign out\" do\n      visit \"/\"\n      expect(page).to have_text \"Volunteers\"\n      # click_link \"Log out\"\n      # expect(page).to_not have_text \"sign in before continuing\"\n      # expect(page).to have_text \"Signed out successfully\"\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/static/index_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"static/index\", type: :system do\n  context \"when visiting the CASA volunteer landing page\", :js do\n    describe \"when all organizations have logos\" do\n      before do\n        create_list(:casa_org, 3, :with_logo, display_name: \"CASA of Awesome\")\n        visit root_path\n      end\n\n      it \"has CASA organizations section\" do\n        expect(page).to have_text \"CASA Organizations Powered by Our App\"\n        expect(page).to have_text \"CASA of Awesome\"\n      end\n\n      it \"displays all organizations that have attached logos\" do\n        within(\"#organizations\") do\n          expect(page).to have_css(\".org_logo\", count: 3)\n        end\n      end\n    end\n\n    describe \"when some orgs are missing logos\" do\n      before do\n        create(:casa_org, :with_logo)\n        create(:casa_org)\n        visit root_path\n      end\n\n      it \"does not display organizations that don't have attached logos\" do\n        within(\"#organizations\") do\n          expect(page).to have_css(\".org_logo\", count: 1)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/supervisors/edit_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"supervisors/edit\", type: :system do\n  let(:organization) { create(:casa_org) }\n\n  context \"logged in as an admin\" do\n    let(:user) { create(:casa_admin, casa_org: organization) }\n\n    it \"can edit supervisor by clicking on the edit link from the supervisors list page\" do\n      supervisor_name = \"Leslie Knope\"\n      create(:supervisor, display_name: supervisor_name, casa_org: organization)\n      sign_in user\n\n      visit supervisors_path\n\n      expect(page).to have_text(supervisor_name)\n\n      within \"#supervisors\" do\n        click_on \"Edit\", match: :first\n      end\n\n      expect(page).to have_text(\"Editing Supervisor\")\n    end\n\n    it \"can edit supervisor by clicking on the supervisor's name from the supervisors list page\" do\n      supervisor_name = \"Leslie Knope\"\n      create(:supervisor, display_name: supervisor_name, casa_org: organization)\n      sign_in user\n\n      visit supervisors_path\n\n      within \"#supervisors\" do\n        click_on supervisor_name\n      end\n\n      expect(page).to have_text(\"Editing Supervisor\")\n    end\n\n    context \"with invalid data\" do\n      let(:role) { \"supervisor\" }\n      let(:supervisor) { create(:supervisor, display_name: \"Leslie Knope\", casa_org: organization) }\n\n      before do\n        sign_in user\n        visit edit_supervisor_path(supervisor)\n      end\n\n      it_behaves_like \"shows error for invalid phone numbers\"\n\n      it \"shows error for invalid date of birth\" do\n        fill_in \"Date of birth\", with: 5.days.from_now.strftime(\"%Y/%m/%d\")\n      end\n    end\n\n    it \"can go to the supervisor edit page and see red message when there are no active volunteers\" do\n      supervisor = create :supervisor, casa_org: organization\n\n      sign_in user\n\n      visit edit_supervisor_path(supervisor)\n\n      expect(page).to have_text(\"There are no active, unassigned volunteers available\")\n    end\n\n    it \"can go to the supervisor edit page and see invite and login info\" do\n      supervisor = create :supervisor, casa_org: organization\n\n      sign_in user\n\n      visit edit_supervisor_path(supervisor)\n\n      expect(page).to have_text \"CASA organization \"\n      expect(page).to have_text \"Added to system \"\n      expect(page).to have_text \"Invitation email sent never\"\n      expect(page).to have_text \"Last logged in\"\n      expect(page).to have_text \"Invitation accepted never\"\n      expect(page).to have_text \"Password reset last sent never\"\n    end\n\n    it \"can deactivate a supervisor\", :js do\n      supervisor = create :supervisor, casa_org: organization\n\n      sign_in user\n      visit edit_supervisor_path(supervisor)\n\n      dismiss_confirm do\n        click_link \"Deactivate Supervisor\"\n      end\n\n      accept_confirm do\n        click_link \"Deactivate Supervisor\"\n      end\n      expect(page).to have_text(\"Supervisor was deactivated on\")\n\n      expect(supervisor.reload).not_to be_active\n    end\n\n    it \"can activate a supervisor\" do\n      inactive_supervisor = create(:supervisor, casa_org_id: organization.id)\n      inactive_supervisor.deactivate\n\n      sign_in user\n\n      visit edit_supervisor_path(inactive_supervisor)\n\n      click_on \"Activate supervisor\"\n\n      expect(page).not_to have_text(\"Supervisor was deactivated on\")\n\n      expect(inactive_supervisor.reload).to be_active\n    end\n\n    it \"can resend invitation to a supervisor\" do\n      supervisor = create :supervisor, casa_org: organization\n\n      sign_in user\n\n      visit edit_supervisor_path(supervisor)\n\n      click_on \"Resend Invitation\"\n\n      expect(page).to have_content(\"Invitation sent\")\n\n      deliveries = ActionMailer::Base.deliveries\n      expect(deliveries.count).to eq(1)\n      expect(deliveries.last.subject).to have_text \"CASA Console invitation instructions\"\n    end\n\n    it \"can convert the supervisor to an admin\" do\n      supervisor = create(:supervisor, casa_org_id: organization.id)\n\n      sign_in user\n\n      visit supervisors_path\n      visit edit_supervisor_path(supervisor)\n\n      click_on \"Change to Admin\"\n\n      expect(page).to have_text(\"Supervisor was changed to Admin.\")\n      expect(User.find(supervisor.id)).to be_casa_admin\n    end\n\n    context \"logged in as a supervisor\" do\n      let(:supervisor) { create(:supervisor) }\n\n      it \"can't deactivate a supervisor\" do\n        supervisor2 = create :supervisor, casa_org: organization\n\n        sign_in supervisor\n        visit edit_supervisor_path(supervisor2)\n\n        expect(page).not_to have_text(\"Deactivate supervisor\")\n      end\n\n      it \"can't activate a supervisor\" do\n        inactive_supervisor = create(:supervisor, casa_org_id: organization.id)\n        inactive_supervisor.deactivate\n\n        sign_in supervisor\n\n        visit edit_supervisor_path(inactive_supervisor)\n\n        expect(page).not_to have_text(\"Activate supervisor\")\n      end\n    end\n\n    context \"when entering valid information\" do\n      before do\n        sign_in user\n        @supervisor = create(:supervisor)\n        @old_email = @supervisor.email\n        visit edit_supervisor_path(@supervisor)\n        fill_in \"supervisor_email\", with: \"new_supervisor_email@example.com\"\n        fill_in \"supervisor_phone_number\", with: \"+14155556876\"\n        fill_in \"supervisor_date_of_birth\", with: \"2003/05/06\"\n\n        click_on \"Submit\"\n        @supervisor.reload\n      end\n\n      it \"sends a confirmation email to the supervisor and displays current email\" do\n        expect(page).to have_text \"Supervisor was successfully updated. Confirmation Email Sent.\"\n        expect(page).to have_field(\"Email\", with: @old_email)\n        expect(@supervisor.unconfirmed_email).to eq(\"new_supervisor_email@example.com\")\n\n        expect(ActionMailer::Base.deliveries.count).to eq(1)\n        expect(ActionMailer::Base.deliveries.first).to be_a(Mail::Message)\n        expect(ActionMailer::Base.deliveries.first.body.encoded)\n          .to match(\"Click here to confirm your email\")\n      end\n\n      it \"correctly updates the supervisor email once confirmed\" do\n        @supervisor.confirm\n        @supervisor.reload\n        visit edit_supervisor_path(@supervisor)\n\n        expect(page).to have_field(\"Email\", with: \"new_supervisor_email@example.com\")\n        expect(@supervisor.old_emails).to match([@old_email])\n      end\n    end\n\n    context \"when entering invalid information\" do\n      before do\n        sign_in user\n        @supervisor = create(:supervisor)\n        visit edit_supervisor_path(@supervisor)\n      end\n\n      it \"shows error message for invalid phone number\" do\n        fill_in \"supervisor_phone_number\", with: \"+24155556760\"\n        click_on \"Submit\"\n        expect(page).to have_text \"Phone number must be 10 digits or 12 digits including country code (+1)\"\n      end\n\n      it \"shows error message for invalid date of birth\" do\n        fill_in \"supervisor_date_of_birth\", with: 5.days.from_now.strftime(\"%Y/%m/%d\")\n        click_on \"Submit\"\n        expect(page).to have_text \"Date of birth must be in the past.\"\n      end\n    end\n\n    context \"when the email exists already\" do\n      let!(:existing_supervisor) { create(:supervisor, casa_org_id: organization.id) }\n\n      it \"responds with a notice\" do\n        sign_in user\n        supervisor = create(:supervisor)\n        visit edit_supervisor_path(supervisor)\n        fill_in \"supervisor_email\", with: \"\"\n        fill_in \"supervisor_email\", with: existing_supervisor.email\n        click_on \"Submit\"\n\n        within \"#error_explanation\" do\n          expect(page).to have_content(/already been taken/i)\n        end\n      end\n    end\n  end\n\n  context \"logged in as a supervisor\" do\n    before do\n      sign_in user\n      visit edit_supervisor_path(supervisor)\n    end\n\n    context \"when editing other supervisor\" do\n      let(:user) { build(:supervisor, casa_org: organization) }\n      let(:supervisor) { create(:supervisor, casa_org: organization) }\n\n      it \"sees red message when there are no active volunteers\" do\n        expect(page).to have_text(\"There are no active, unassigned volunteers available\")\n      end\n\n      it \"does not have a submit button\" do\n        expect(page).not_to have_selector(:link_or_button, \"Submit\")\n      end\n    end\n\n    context \"when editing own page\" do\n      let(:supervisor) { create(:supervisor, casa_org: organization) }\n      let(:user) { supervisor }\n\n      it \"displays a submit button\" do\n        visit edit_supervisor_path(supervisor)\n\n        expect(page).to have_selector(:link_or_button, \"Submit\")\n      end\n\n      it \"sees last invite and login info\" do\n        expect(page).to have_text \"Added to system \"\n        expect(page).to have_text \"Invitation email sent never\"\n        expect(page).to have_text \"Last logged in\"\n        expect(page).to have_text \"Invitation accepted never\"\n        expect(page).to have_text \"Password reset last sent never\"\n      end\n\n      context \"when no volunteers exist\" do\n        let!(:volunteer_1) { create(:volunteer, display_name: \"AAA\", casa_org: organization) }\n\n        it \"does not error out when adding non-existent volunteer\" do\n          visit edit_supervisor_path(supervisor)\n          select volunteer_1.display_name, from: \"Select a Volunteer\"\n          click_on \"Assign Volunteer\"\n          expect(page.find_button(\"Assign Volunteer\", disabled: true)).to be_present\n          expect(page).to have_text(\"There are no active, unassigned volunteers available.\")\n        end\n      end\n\n      context \"when there are assigned volunteers\" do\n        let(:supervisor) { create(:supervisor, :with_volunteers, casa_org: organization) }\n\n        it \"shows assigned volunteers\" do\n          visit edit_supervisor_path(supervisor)\n\n          expect(page).to have_text \"Assigned Volunteers\"\n          expect(page).not_to have_button(\"Include unassigned\")\n          expect(page).not_to have_text(\"Currently Assigned To\")\n          supervisor.volunteers.each do |volunteer|\n            expect(page).to have_text volunteer.email\n          end\n        end\n\n        context \"when there are previously unassigned volunteers\" do\n          let!(:unassigned_volunteer) { create(:supervisor_volunteer, :inactive, supervisor: supervisor).volunteer }\n\n          it \"does not show them by default\" do\n            visit edit_supervisor_path(supervisor)\n\n            expect(page).not_to have_text unassigned_volunteer.email\n            expect(page).to have_button(\"Include unassigned\")\n\n            click_on \"Include unassigned\"\n\n            expect(page).to have_button(\"Hide unassigned\")\n            expect(page).to have_text(\"All Volunteers\")\n            expect(page).to have_text unassigned_volunteer.email\n            expect(page).to have_text \"Currently Assigned To\"\n          end\n        end\n      end\n\n      context \"when there are no currently assigned volunteers\" do\n        let(:supervisor) { create(:supervisor, casa_org: organization) }\n\n        context \"and there are previously unassigned volunteers\" do\n          let!(:unassigned_volunteer) { create(:supervisor_volunteer, :inactive, supervisor: supervisor).volunteer }\n\n          it \"does not show them by default\" do\n            visit edit_supervisor_path(supervisor)\n\n            expect(page).to have_text \"Assigned Volunteers\"\n            expect(page).not_to have_text unassigned_volunteer.email\n            expect(page).to have_button(\"Include unassigned\")\n\n            click_on \"Include unassigned\"\n\n            expect(page).to have_button(\"Hide unassigned\")\n            expect(page).to have_text unassigned_volunteer.email\n            expect(page).to have_text \"No One\"\n            expect(page).to have_text \"Currently Assigned To\"\n\n            click_on \"Hide unassigned\"\n\n            expect(page).not_to have_text \"Currently Assigned To\"\n            expect(page).not_to have_text \"No One\"\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/supervisors/index_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"supervisors/index\", type: :system do\n  shared_examples_for \"functioning sort buttons\" do\n    it \"sorts table columns\" do\n      expect(page).to have_css(\"tr:nth-child(1)\", text: expected_first_ordered_value)\n\n      find(\"th\", text: column_to_sort).click\n\n      expect(page).to have_css(\"th.sorting_asc\", text: column_to_sort)\n      expect(page).to have_css(\"tr:nth-child(1)\", text: expected_last_ordered_value)\n    end\n  end\n\n  let(:organization) { build(:casa_org) }\n  let(:supervisor_user) { create(:supervisor, casa_org: organization, display_name: \"Logged Supervisor\") }\n  let(:organization_two) { build(:casa_org) }\n  let(:supervisor_other_org) { create(:supervisor, casa_org: organization_two, display_name: \"No Volunteers Org\") }\n  let(:other_supervisor) { create(:supervisor, casa_org: organization, display_name: \"Other Supervisor\") }\n  let(:only_contacts_supervisor) { create(:supervisor, casa_org: organization, display_name: \"Only Contacts Supervisor\") }\n  let(:no_contacts_supervisor) { create(:supervisor, casa_org: organization, display_name: \"No Contacts Supervisor\") }\n  let(:no_active_volunteers_supervisor) { create(:supervisor, casa_org: organization, display_name: \"No Active Volunteers Supervisor\") }\n  let(:admin) { create(:casa_admin, casa_org: organization) }\n\n  context \"when signed in as a supervisor\" do\n    before { sign_in supervisor_user }\n\n    context \"when editing supervisor\", :js do\n      let(:supervisor_name) { \"Leslie Knope\" }\n      let!(:supervisor) { create(:supervisor, display_name: supervisor_name, casa_org: organization) }\n\n      before { visit supervisors_path }\n\n      it \"can edit supervisor by clicking on the edit link from the supervisors list page\" do\n        expect(page).to have_text(supervisor_name)\n\n        within \"#supervisors\" do\n          click_on \"Edit\", match: :first\n        end\n\n        expect(page).to have_text(\"Editing Supervisor\")\n      end\n\n      it \"can edit supervisor by clicking on the supervisor's name from the supervisors list page\" do\n        expect(page).to have_text(supervisor_name)\n\n        within \"#supervisors\" do\n          click_on supervisor_name\n        end\n\n        expect(page).to have_text(\"Editing Supervisor\")\n      end\n    end\n\n    describe \"supervisor table\" do\n      let!(:first_supervisor) { create(:supervisor, display_name: \"First Supervisor\", casa_org: organization) }\n      let!(:last_supervisor) { create(:supervisor, display_name: \"Last Supervisor\", casa_org: organization) }\n      let!(:active_volunteers_for_first_supervisor) { create_list(:volunteer, 2, supervisor: first_supervisor, casa_org: organization) }\n      let!(:active_volunteers_for_last_supervisor) { create_list(:volunteer, 5, supervisor: last_supervisor, casa_org: organization) }\n      let!(:deacticated_supervisor) {\n        create(:supervisor, :inactive, display_name: \"Deactivated supervisor\", casa_org: organization)\n      }\n\n      before do\n        # Stub our `@supervisors` collection so we've got control over column values for sorting.\n        allow_any_instance_of(SupervisorPolicy::Scope).to receive(:resolve).and_return(\n          Supervisor.where.not(display_name: supervisor_user.display_name).order(display_name: :asc)\n        )\n\n        active_volunteers_for_first_supervisor.map { |av|\n          casa_case = create(:casa_case, casa_org: av.casa_org)\n          create(:case_contact, contact_made: false, occurred_at: 1.week.ago, casa_case_id: casa_case.id)\n          create(:case_assignment, casa_case: casa_case, volunteer: av)\n        }\n\n        active_volunteers_for_last_supervisor.map { |av|\n          casa_case = create(:casa_case, casa_org: av.casa_org)\n          create(:case_contact, contact_made: false, occurred_at: 1.week.ago, casa_case_id: casa_case.id)\n          create(:case_assignment, casa_case: casa_case, volunteer: av)\n        }\n\n        sign_in supervisor_user\n        visit supervisors_path\n      end\n\n      context \"with active and deactivated supervisors\" do\n        it \"shows deactivated supervisor on show button click\", :js do\n          expect(page).to have_text(\"Showing 1 to 2 of 2 entries (filtered from 3 total entries)\")\n          expect(page).to have_no_text(\"Deactivated supervisor\")\n\n          find(\".supervisor-filters\").click_on(\"Filter Status\")\n          check(\"status_option_inactive\")\n\n          expect(page).to have_text(\"Showing 1 to 3 of 3 entries\")\n          expect(page).to have_text(\"Deactivated supervisor\")\n\n          uncheck(\"status_option_inactive\")\n\n          expect(page).to have_text(\"Showing 1 to 2 of 2 entries (filtered from 3 total entries)\")\n          expect(page).to have_no_text(\"Deactivated supervisor\")\n        end\n      end\n\n      context \"with unassigned volunteers\" do\n        let(:unassigned_volunteer_name) { \"Tony Ruiz\" }\n        let!(:unassigned_volunteer) { create(:volunteer, casa_org: organization, display_name: unassigned_volunteer_name) }\n\n        before do\n          sign_in supervisor_user\n          visit supervisors_path\n        end\n\n        it \"shows a list of unassigned volunteers\" do\n          expect(page).to have_text(\"Active volunteers not assigned to supervisors\")\n          expect(page).to have_text(\"Assigned to Case(s)\")\n          expect(page).to have_text(unassigned_volunteer_name)\n\n          expect(page).to have_no_text(\"There are no unassigned volunteers\")\n        end\n\n        it \"links to edit page of volunteer\" do\n          click_on unassigned_volunteer_name\n          expect(page).to have_current_path(\"/volunteers/#{unassigned_volunteer.id}/edit\")\n        end\n      end\n\n      context \"without unassigned volunteers\" do\n        before do\n          sign_in supervisor_other_org\n          visit supervisors_path\n        end\n\n        it \"does not show a list of volunteers not assigned to supervisors\", :js do\n          expect(page).to have_text(\"There are no active volunteers without supervisors to display here\")\n\n          expect(page).to have_no_text(\"Active volunteers not assigned to supervisors\")\n          expect(page).to have_no_text(\"Assigned to Case(s)\")\n        end\n      end\n    end\n\n    describe \"supervisor table filters\" do\n      let(:supervisor_user) { create(:supervisor, casa_org: organization) }\n\n      before do\n        sign_in supervisor_user\n        visit supervisors_path\n      end\n\n      describe \"status\", :js do\n        let!(:active_supervisor) do\n          create(:supervisor, display_name: \"Active Supervisor\", casa_org: organization, active: true)\n        end\n\n        let!(:inactive_supervisor) do\n          create(:supervisor, display_name: \"Inactive Supervisor\", casa_org: organization, active: false)\n        end\n\n        context \"when only active checked\" do\n          it \"filters the supervisors correctly\", :aggregate_failures do\n            within(:css, \".supervisor-filters\") do\n              click_on \"Status\"\n              find(:css, \".active\").set(false)\n              find(:css, \".active\").set(true)\n              find(:css, \".inactive\").set(false)\n            end\n\n            within(\"table#supervisors\") do\n              expect(page).to have_text(\"Active Supervisor\")\n              expect(page).to have_no_text(\"Inactive Supervisor\")\n            end\n          end\n        end\n\n        context \"when only inactive checked\" do\n          it \"filters the supervisors correctly\", :aggregate_failures do\n            within(:css, \".supervisor-filters\") do\n              click_on \"Status\"\n              find(:css, \".active\").set(false)\n              find(:css, \".inactive\").set(true)\n              click_on \"Status\"\n            end\n\n            within(\"table#supervisors\") do\n              expect(page).to have_no_content(\"Active Supervisor\")\n              expect(page).to have_content(\"Inactive Supervisor\")\n            end\n          end\n        end\n\n        context \"when both checked\" do\n          it \"filters the supervisors correctly\", :aggregate_failures do # TODO fix test\n            within(:css, \".supervisor-filters\") do\n              click_on \"Status\"\n              find(:css, \".active\").set(true)\n              find(:css, \".inactive\").set(true)\n              click_on \"Status\"\n            end\n\n            within(\"table#supervisors\") do\n              expect(page).to have_content(\"Active Supervisor\")\n              expect(page).to have_content(\"Inactive Supervisor\")\n            end\n          end\n        end\n      end\n    end\n  end\n\n  context \"when signed in as an admin\" do\n    let!(:no_active_volunteers_supervisor) { create(:supervisor, casa_org: organization, display_name: \"No Active Volunteers Supervisor\") }\n\n    let!(:no_contact_volunteer) do\n      create(\n        :volunteer,\n        :with_casa_cases,\n        :with_assigned_supervisor,\n        supervisor: supervisor_user,\n        casa_org: organization\n      )\n    end\n\n    let!(:no_contact_pre_transition_volunteer) do\n      create(\n        :volunteer,\n        :with_pretransition_age_case,\n        :with_assigned_supervisor,\n        supervisor: supervisor_user,\n        casa_org: organization\n      )\n    end\n\n    let!(:with_contact_volunteer) do\n      create(\n        :volunteer,\n        :with_cases_and_contacts,\n        :with_assigned_supervisor,\n        supervisor: supervisor_user,\n        casa_org: organization\n      )\n    end\n\n    let!(:active_unassigned) do\n      create(\n        :volunteer,\n        :with_casa_cases,\n        casa_org: organization\n      )\n    end\n\n    let!(:other_supervisor_active_volunteer1) do\n      create(\n        :volunteer,\n        :with_cases_and_contacts,\n        :with_assigned_supervisor,\n        supervisor: other_supervisor,\n        casa_org: organization\n      )\n    end\n\n    let!(:other_supervisor_active_volunteer2) do\n      create(\n        :volunteer,\n        :with_cases_and_contacts,\n        :with_assigned_supervisor,\n        supervisor: other_supervisor,\n        casa_org: organization\n      )\n    end\n\n    let!(:other_supervisor_no_contact_volunteer1) do\n      create(\n        :volunteer,\n        :with_casa_cases,\n        :with_assigned_supervisor,\n        supervisor: other_supervisor,\n        casa_org: organization\n      )\n    end\n\n    let!(:other_supervisor_no_contact_volunteer2) do\n      create(\n        :volunteer,\n        :with_casa_cases,\n        :with_assigned_supervisor,\n        supervisor: other_supervisor,\n        casa_org: organization\n      )\n    end\n\n    let!(:only_contact_volunteer1) do\n      create(\n        :volunteer,\n        :with_cases_and_contacts,\n        :with_assigned_supervisor,\n        supervisor: only_contacts_supervisor,\n        casa_org: organization\n      )\n    end\n\n    let!(:only_contact_volunteer2) do\n      create(\n        :volunteer,\n        :with_cases_and_contacts,\n        :with_assigned_supervisor,\n        supervisor: only_contacts_supervisor,\n        casa_org: organization\n      )\n    end\n\n    let!(:only_contact_volunteer3) do\n      create(\n        :volunteer,\n        :with_cases_and_contacts,\n        :with_assigned_supervisor,\n        supervisor: only_contacts_supervisor,\n        casa_org: organization\n      )\n    end\n\n    let!(:no_contact_volunteer1) do\n      create(\n        :volunteer,\n        :with_casa_cases,\n        :with_assigned_supervisor,\n        supervisor: no_contacts_supervisor,\n        casa_org: organization\n      )\n    end\n\n    let!(:no_contact_volunteer2) do\n      create(\n        :volunteer,\n        :with_casa_cases,\n        :with_assigned_supervisor,\n        supervisor: no_contacts_supervisor,\n        casa_org: organization\n      )\n    end\n\n    before do\n      sign_in admin\n      visit supervisors_path\n    end\n\n    it \"shows all active supervisors\", :js do\n      supervisor_table = page.find(\"table#supervisors\")\n      expect(supervisor_table.all(\"div.supervisor_case_contact_stats\").length).to eq(5)\n    end\n\n    it \"shows the correct volunteers for the first supervisor with both volunteer types\", :js do\n      supervisor_table = page.find(\"table#supervisors\")\n      expect(supervisor_table).to have_text(supervisor_user.display_name.html_safe)\n\n      supervisor_stats = page.find(\"tr#supervisor-#{supervisor_user.id}-information\")\n      active_contacts_expected = 1\n      no_active_contacts_expected = 2\n      transition_aged_youth_expected = 2\n      active_contact_element = supervisor_stats.find(\"span.attempted-contact\")\n      no_active_contact_element = supervisor_stats.find(\"span.no-attempted-contact\")\n\n      expect(active_contact_element).to have_text(active_contacts_expected)\n      expect(active_contact_element).to match_css(\".pr-#{active_contacts_expected * 15}\")\n      expect(no_active_contact_element).to have_text(no_active_contacts_expected)\n      expect(no_active_contact_element).to match_css(\".pl-#{no_active_contacts_expected * 15}\")\n      expect(supervisor_stats.find(\".supervisor-stat.deactive-bg\")).to have_text(transition_aged_youth_expected)\n    end\n\n    it \"shows the correct volunteers for the second supervisor with both volunteer types\", :js do\n      supervisor_table = page.find(\"table#supervisors\")\n      expect(supervisor_table).to have_text(other_supervisor.display_name.html_safe)\n\n      supervisor_stats = page.find(\"tr#supervisor-#{other_supervisor.id}-information\")\n      active_contacts_expected = 2\n      no_active_contacts_expected = 2\n      transition_aged_youth_expected = 4\n      active_contact_element = supervisor_stats.find(\"span.attempted-contact\")\n      no_active_contact_element = supervisor_stats.find(\"span.no-attempted-contact\")\n\n      expect(active_contact_element).to have_text(active_contacts_expected)\n      expect(active_contact_element).to match_css(\".pr-#{active_contacts_expected * 15}\")\n      expect(no_active_contact_element).to have_text(no_active_contacts_expected)\n      expect(no_active_contact_element).to match_css(\".pl-#{no_active_contacts_expected * 15}\")\n      expect(supervisor_stats.find(\".supervisor-stat.deactive-bg\")).to have_text(transition_aged_youth_expected)\n    end\n\n    it \"shows the correct element for a supervisor with only contact volunteers\", :js do\n      supervisor_table = page.find(\"table#supervisors\")\n      expect(supervisor_table).to have_text(only_contacts_supervisor.display_name.html_safe)\n\n      supervisor_stats = page.find(\"tr#supervisor-#{only_contacts_supervisor.id}-information\")\n      active_contacts_expected = 3\n      transition_aged_youth_expected = 3\n      active_contact_element = supervisor_stats.find(\"span.attempted-contact\")\n\n      expect(active_contact_element).to have_text(active_contacts_expected)\n      expect(active_contact_element).to match_css(\".pl-#{active_contacts_expected * 15}\")\n      expect(supervisor_stats.find(\".supervisor-stat.deactive-bg\")).to have_text(transition_aged_youth_expected)\n      expect(supervisor_stats).not_to have_css(\"span.no-attempted-contact\")\n    end\n\n    it \"shows the correct element for a supervisor with only no contact volunteers\", :js do\n      supervisor_table = page.find(\"table#supervisors\")\n      expect(supervisor_table).to have_text(no_contacts_supervisor.display_name.html_safe)\n\n      supervisor_stats = page.find(\"tr#supervisor-#{no_contacts_supervisor.id}-information\")\n      no_contacts_expected = 2\n      transition_aged_youth_expected = 2\n      no_contact_element = supervisor_stats.find(\"span.no-attempted-contact\")\n\n      expect(no_contact_element).to have_text(no_contacts_expected)\n      expect(no_contact_element).to match_css(\".pl-#{no_contacts_expected * 15}\")\n      expect(supervisor_stats.find(\".supervisor-stat.deactive-bg\")).to have_text(transition_aged_youth_expected)\n      expect(supervisor_stats).not_to have_css(\"span.attempted-contact\")\n      expect(supervisor_stats).not_to have_css(\"span.attempted-contact-end\")\n    end\n\n    it \"shows the correct text with a supervisor with no assigned volunteers\", :js do\n      supervisor_table = page.find(\"table#supervisors\")\n      expect(supervisor_table).to have_text(no_active_volunteers_supervisor.display_name.html_safe)\n\n      supervisor_no_volunteer_stats = page.find(\"tr#supervisor-#{no_active_volunteers_supervisor.id}-information\")\n      expect(supervisor_no_volunteer_stats).to have_text(\"No assigned volunteers\")\n      expect(supervisor_no_volunteer_stats.find(\"span.no-volunteers\")).to be_truthy\n      expect(supervisor_no_volunteer_stats.find(\"span.no-volunteers\").style(\"flex-grow\")).to eq({\"flex-grow\" => \"1\"})\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/supervisors/new_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe \"supervisors/new\", type: :system do\n  context \"when logged in as an admin\" do\n    let(:admin) { create(:casa_admin) }\n    let(:new_supervisor_name) { Faker::Name.name }\n    let(:new_supervisor_email) { Faker::Internet.email }\n    let(:new_supervisor_phone_number) { \"1234567890\" }\n\n    before do\n      # Stub the request to the URL shortener service (needed if phone is provided)\n      stub_request(:post, \"https://api.short.io/links\")\n        .to_return(\n          status: 200,\n          body: {shortURL: \"https://short.url/example\"}.to_json,\n          headers: {\"Content-Type\" => \"application/json\"}\n        )\n\n      sign_in admin\n      visit new_supervisor_path\n    end\n\n    context \"with valid form submission\" do\n      let(:new_supervisor) { User.find_by(email: new_supervisor_email) }\n\n      before do\n        fill_in \"Email\", with: new_supervisor_email\n        fill_in \"Display name\", with: new_supervisor_name\n        fill_in \"Phone number\", with: new_supervisor_phone_number\n\n        click_on \"Create Supervisor\"\n      end\n\n      it \"shows a success message\" do\n        expect(page).to have_text(\"New supervisor created successfully.\")\n      end\n\n      it \"redirects to the edit supervisor page\", :aggregate_failures do\n        expect(page).to have_text(\"New supervisor created successfully.\") # Guard to ensure redirection happened\n        expect(page).to have_current_path(edit_supervisor_path(new_supervisor))\n      end\n\n      it \"persists the new supervisor with correct attributes\", :aggregate_failures do\n        expect(new_supervisor).to be_present\n        expect(new_supervisor.display_name).to eq(new_supervisor_name)\n        expect(new_supervisor.phone_number).to end_with(new_supervisor_phone_number)\n        expect(new_supervisor.supervisor?).to be(true)\n        expect(new_supervisor.active?).to be(true)\n      end\n\n      it \"sends an invitation email to the new supervisor\", :aggregate_failures do\n        last_email = ActionMailer::Base.deliveries.last\n        expect(last_email.to).to eq [new_supervisor_email]\n        expect(last_email.subject).to have_text \"CASA Console invitation instructions\"\n        expect(last_email.html_part.body.encoded).to have_text \"your new Supervisor account.\"\n      end\n    end\n\n    context \"with invalid form submission\" do\n      before do\n        # Don't fill in any fields\n        click_on \"Create Supervisor\"\n      end\n\n      it \"does not create a new user\" do\n        expect(User.count).to eq(1) # Only the admin user exists\n      end\n\n      it \"shows validation error messages\" do\n        expect(page).to have_text \"errors prohibited this Supervisor from being saved:\"\n      end\n\n      it \"stays on the new supervisor page\", :aggregate_failures do\n        expect(page).to have_text \"errors prohibited this Supervisor from being saved:\" # Guard to ensure no redirection happened\n        expect(page).to have_current_path(supervisors_path)\n      end\n    end\n  end\n\n  context \"when logged in as a supervisor\" do\n    let(:supervisor) { create(:supervisor) }\n\n    before { sign_in supervisor }\n\n    it \"redirects the user with an error message\" do\n      visit new_supervisor_path\n\n      expect(page).to have_selector(\".alert\", text: \"Sorry, you are not authorized to perform this action.\")\n    end\n  end\n\n  context \"when logged in as a volunteer\" do\n    let(:volunteer) { create(:volunteer) }\n\n    before { sign_in volunteer }\n\n    it \"redirects the user with an error message\" do\n      visit new_supervisor_path\n\n      expect(page).to have_selector(\".alert\", text: \"Sorry, you are not authorized to perform this action.\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/users/edit_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"users/edit\", type: :system do\n  context \"volunteer user\" do\n    it \"displays password errors messages when user is unable to set a password with incorrect current password\" do\n      organization = create(:casa_org)\n      volunteer = create(:volunteer, casa_org: organization)\n\n      sign_in volunteer\n      visit edit_users_path\n\n      click_on \"Change Password\"\n\n      fill_in \"Current Password\", with: \"12345\"\n      fill_in \"New Password\", with: \"123456789\"\n      fill_in \"New Password Confirmation\", with: \"123456789\"\n\n      click_on \"Update Password\"\n      expect(page).to have_content \"1 error prohibited this password change from being saved:\"\n      expect(page).to have_text(\"Current password is incorrect\")\n    end\n\n    it \"displays password errors messages when user is unable to set a password\" do\n      organization = create(:casa_org)\n      volunteer = create(:volunteer, casa_org: organization)\n\n      sign_in volunteer\n      visit edit_users_path\n\n      click_on \"Change Password\"\n\n      fill_in \"Current Password\", with: \"12345678\"\n      fill_in \"New Password\", with: \"123\"\n      fill_in \"New Password Confirmation\", with: \"1234\"\n\n      click_on \"Update Password\"\n      expect(page).to have_content \"2 errors prohibited this password change from being saved:\"\n      expect(page).to have_text(\"Password confirmation doesn't match Password\")\n      expect(page).to have_text(\"Password is too short (minimum is #{User.password_length.min} characters)\")\n    end\n\n    it \"displays sms notification events for the volunteer user\" do\n      organization = create(:casa_org, twilio_enabled: true)\n      volunteer = create(:volunteer, casa_org: organization)\n\n      SmsNotificationEvent.delete_all\n      SmsNotificationEvent.new(name: \"sms_event_test_volunteer\", user_type: Volunteer).save\n      SmsNotificationEvent.new(name: \"sms_event_test_supervisor\", user_type: Supervisor).save\n      SmsNotificationEvent.new(name: \"sms_event_test_casa_admin\", user_type: CasaAdmin).save\n\n      sign_in volunteer\n      visit edit_users_path\n\n      expect(page).to have_content \"sms_event_test_volunteer\"\n      expect(page).not_to have_content \"sms_event_test_supervisor\"\n      expect(page).not_to have_content \"sms_event_test_casa_admin\"\n    end\n\n    it \"notifies a user when they update their password\" do\n      organization = create(:casa_org)\n      volunteer = create(:volunteer, casa_org: organization)\n\n      sign_in volunteer\n      visit edit_users_path\n\n      click_on \"Change Password\"\n\n      fill_in \"Current Password\", with: \"12345678\"\n      fill_in \"New Password\", with: \"123456789\"\n      fill_in \"New Password Confirmation\", with: \"123456789\"\n\n      click_on \"Update Password\"\n\n      expect(page).to have_text(\"Password was successfully updated.\")\n    end\n\n    it \"notifies password changed by email\", :aggregate_failures do\n      organization = create(:casa_org)\n      volunteer = create(:volunteer, casa_org: organization)\n\n      sign_in volunteer\n      visit edit_users_path\n\n      click_on \"Change Password\"\n\n      fill_in \"Current Password\", with: \"12345678\"\n      fill_in \"New Password\", with: \"123456789\"\n      fill_in \"Password Confirmation\", with: \"123456789\"\n\n      click_on \"Update Password\"\n\n      page.has_content?(\"Password was successfully updated.\")\n\n      expect(ActionMailer::Base.deliveries.count).to eq(1)\n      expect(ActionMailer::Base.deliveries.first).to be_a(Mail::Message)\n      expect(ActionMailer::Base.deliveries.first.body.encoded)\n        .to match(\"Your CASA password has been changed.\")\n    end\n\n    it \"is able to send a confirmation email when Volunteer updates their email\" do\n      organization = create(:casa_org)\n      volunteer = create(:volunteer, casa_org: organization)\n\n      sign_in volunteer\n      visit edit_users_path\n\n      click_on \"Change Email\"\n      expect(page).to have_field(\"New Email\", disabled: false)\n\n      fill_in \"current_password_email\", with: \"12345678\"\n\n      fill_in \"New Email\", with: \"new_volunteer@example.com\"\n      click_on \"Update Email\"\n\n      expect(page).to have_content \"Click the link in your new email to finalize the email transfer\"\n\n      expect(ActionMailer::Base.deliveries.count).to eq(1)\n      expect(ActionMailer::Base.deliveries.first).to be_a(Mail::Message)\n      expect(ActionMailer::Base.deliveries.first.body.encoded)\n        .to have_text(\"Click here to confirm your email\")\n    end\n\n    it \"displays email errors messages when user is unable to set a email with incorrect current password\" do\n      organization = create(:casa_org)\n      volunteer = create(:volunteer, casa_org: organization)\n\n      sign_in volunteer\n      visit edit_users_path\n\n      click_on \"Change Email\"\n\n      fill_in \"current_password_email\", with: \"12345\"\n      fill_in \"New Email\", with: \"new_volunteer@example.com\"\n\n      click_on \"Update Email\"\n      expect(page).to have_content \"1 error prohibited this Volunteer from being saved:\"\n      expect(page).to have_text(\"Current password is incorrect\")\n    end\n\n    it \"displays current sign in date\" do\n      organization = create(:casa_org)\n      volunteer = create(\n        :volunteer,\n        casa_org: organization,\n        last_sign_in_at: \"2020-01-01 00:00:00\",\n        current_sign_in_at: \"2020-01-02 00:00:00\"\n      )\n\n      sign_in volunteer\n      visit edit_users_path\n\n      formatted_current_sign_in_at = I18n.l(volunteer.current_sign_in_at, format: :edit_profile, default: nil)\n      formatted_last_sign_in_at = I18n.l(volunteer.last_sign_in_at, format: :edit_profile, default: nil)\n      expect(page).to have_text(\"Last logged in #{formatted_current_sign_in_at}\")\n      expect(page).not_to have_text(\"Last logged in #{formatted_last_sign_in_at}\")\n    end\n\n    it \"displays Volunteer error message if no communication preference is selected\" do\n      organization = create(:casa_org, twilio_enabled: true)\n      volunteer = create(:volunteer, casa_org: organization)\n\n      sign_in volunteer\n      visit edit_users_path\n\n      uncheck \"user_receive_email_notifications\"\n      click_on \"Save Preferences\"\n      expect(page).to have_content \"1 error prohibited this Volunteer from being saved:\"\n      expect(page).to have_text(\"At least one communication preference must be selected.\")\n    end\n\n    it \"displays Volunteer error message if SMS communication preference is selected without adding a valid phone number\" do\n      organization = create(:casa_org, twilio_enabled: true)\n      volunteer = create(:volunteer, casa_org: organization)\n\n      sign_in volunteer\n      visit edit_users_path\n\n      uncheck \"user_receive_email_notifications\"\n      check \"user_receive_sms_notifications\"\n      click_on \"Save Preferences\"\n      expect(page).to have_content \"1 error prohibited this Volunteer from being saved:\"\n      expect(page).to have_text(\"Must add a valid phone number to receive SMS notifications.\")\n    end\n\n    it \"displays notification events selection as enabled if sms notification preference is selected\" do\n      organization = create(:casa_org, twilio_enabled: true)\n      volunteer = create(:volunteer, casa_org: organization)\n\n      SmsNotificationEvent.delete_all\n      SmsNotificationEvent.new(name: \"sms_event_test_volunteer\", user_type: Volunteer).save\n      SmsNotificationEvent.new(name: \"sms_event_test_supervisor\", user_type: Supervisor).save\n      SmsNotificationEvent.new(name: \"sms_event_test_casa_admin\", user_type: CasaAdmin).save\n\n      sign_in volunteer\n      visit edit_users_path\n\n      check \"user_receive_sms_notifications\"\n      expect(page).to have_field(\"toggle-sms-notification-event\", type: \"checkbox\", disabled: false)\n    end\n\n    it \"displays notification events selection as disabled if sms notification preference is not selected\", :js do\n      organization = create(:casa_org, twilio_enabled: true)\n      volunteer = create(:volunteer, casa_org: organization)\n\n      SmsNotificationEvent.delete_all\n      SmsNotificationEvent.new(name: \"sms_event_test_volunteer\", user_type: Volunteer).save\n      SmsNotificationEvent.new(name: \"sms_event_test_supervisor\", user_type: Supervisor).save\n      SmsNotificationEvent.new(name: \"sms_event_test_casa_admin\", user_type: CasaAdmin).save\n\n      sign_in volunteer\n      visit edit_users_path\n\n      uncheck \"user_receive_sms_notifications\"\n      expect(page).to have_field(\"toggle-sms-notification-event\", type: \"checkbox\", disabled: true)\n    end\n  end\n\n  context \"when a user's casa organization does not have twilio enabled\" do\n    it \"disables a users SMS communication checkbox\" do\n      organization = create(:casa_org)\n      volunteer = create(:volunteer, casa_org: organization)\n\n      sign_in volunteer\n      visit edit_users_path\n\n      expect(page).to have_field(\"Enable Twilio For Text Messaging\", type: \"checkbox\", disabled: true)\n    end\n  end\n\n  context \"supervisor user\" do\n    it \"notifies password changed by email\", :aggregate_failures do\n      org = create(:casa_org)\n      supervisor = create(:supervisor, casa_org: org)\n\n      sign_in supervisor\n      visit edit_users_path\n\n      click_on \"Change Password\"\n\n      fill_in \"Current Password\", with: \"12345678\"\n      fill_in \"New Password\", with: \"123456789\"\n      fill_in \"Password Confirmation\", with: \"123456789\"\n\n      click_on \"Update Password\"\n\n      page.has_content?(\"Password was successfully updated.\")\n\n      expect(ActionMailer::Base.deliveries.count).to eq(1)\n      expect(ActionMailer::Base.deliveries.first).to be_a(Mail::Message)\n      expect(ActionMailer::Base.deliveries.first.body.encoded)\n        .to match(\"Your CASA password has been changed.\")\n    end\n\n    it \"is able to send a confrimation email when supervisor is updating email\" do\n      org = create(:casa_org)\n      supervisor = create(:supervisor, casa_org: org)\n\n      sign_in supervisor\n      visit edit_users_path\n\n      click_on \"Change Email\"\n      expect(page).to have_field(\"New Email\", disabled: false)\n\n      fill_in \"current_password_email\", with: \"12345678\"\n\n      fill_in \"New Email\", with: \"new_supervisor@example.com\"\n      click_on \"Update Email\"\n\n      expect(page).to have_content \"Click the link in your new email to finalize the email transfer\"\n\n      expect(ActionMailer::Base.deliveries.count).to eq(1)\n      expect(ActionMailer::Base.deliveries.first).to be_a(Mail::Message)\n      expect(ActionMailer::Base.deliveries.first.body.encoded)\n        .to match(\"Click here to confirm your email\")\n    end\n\n    it \"displays email errors messages when user is unable to set a email with incorrect current password\" do\n      org = create(:casa_org)\n      supervisor = create(:supervisor, casa_org: org)\n\n      sign_in supervisor\n      visit edit_users_path\n\n      click_on \"Change Email\"\n\n      fill_in \"current_password_email\", with: \"12345\"\n      fill_in \"New Email\", with: \"new_supervisor@example\"\n\n      click_on \"Update Email\"\n      expect(page).to have_content \"1 error prohibited this Supervisor from being saved:\"\n      expect(page).to have_text(\"Current password is incorrect\")\n    end\n\n    it \"displays sms notification events for the supervisor user\" do\n      org = create(:casa_org, twilio_enabled: true)\n      supervisor = create(:supervisor, casa_org: org)\n\n      SmsNotificationEvent.delete_all\n      SmsNotificationEvent.new(name: \"sms_event_test_volunteer\", user_type: Volunteer).save\n      SmsNotificationEvent.new(name: \"sms_event_test_supervisor\", user_type: Supervisor).save\n      SmsNotificationEvent.new(name: \"sms_event_test_casa_admin\", user_type: CasaAdmin).save\n\n      sign_in supervisor\n      visit edit_users_path\n\n      expect(page).not_to have_content \"sms_event_test_volunteer\"\n      expect(page).to have_content \"sms_event_test_supervisor\"\n      expect(page).not_to have_content \"sms_event_test_casa_admin\"\n    end\n\n    it \"displays Supervisor error message if no communication preference is selected\" do\n      org = create(:casa_org)\n      supervisor = create(:supervisor, casa_org: org)\n\n      sign_in supervisor\n      visit edit_users_path\n\n      uncheck \"user_receive_email_notifications\"\n      click_on \"Save Preferences\"\n      expect(page).to have_content \"1 error prohibited this Supervisor from being saved:\"\n      expect(page).to have_text(\"At least one communication preference must be selected.\")\n    end\n\n    it \"displays Supervisor error message if SMS communication preference is selected without adding a valid phone number\" do\n      org = create(:casa_org, twilio_enabled: true)\n      supervisor = create(:supervisor, casa_org: org)\n\n      sign_in supervisor\n      visit edit_users_path\n\n      uncheck \"user_receive_email_notifications\"\n      check \"user_receive_sms_notifications\"\n      click_on \"Save Preferences\"\n      expect(page).to have_content \"1 error prohibited this Supervisor from being saved:\"\n      expect(page).to have_text(\"Must add a valid phone number to receive SMS notifications.\")\n    end\n\n    it \"displays Supervisor error message if invalid date of birth\" do\n      org = create(:casa_org)\n      supervisor = create(:supervisor, casa_org: org)\n\n      sign_in supervisor\n      visit edit_users_path\n\n      fill_in \"Date of birth\", with: 8.days.from_now.strftime(\"%Y/%m/%d\")\n      click_on \"Update Profile\"\n      expect(page).to have_content \"1 error prohibited this Supervisor from being saved:\"\n      expect(page).to have_text(\"Date of birth must be in the past.\")\n    end\n  end\n\n  context \"when admin\" do\n    it \"is not able to update the profile without display name as an admin\" do\n      org = create(:casa_org)\n      admin = create(:casa_admin, casa_org: org)\n\n      sign_in admin\n      visit edit_users_path\n\n      fill_in \"Display name\", with: \"\"\n      click_on \"Update Profile\"\n      expect(page).to have_text(\"Display name can't be blank\")\n    end\n\n    context \"shows error for invalid phone number\" do\n      it \"shows error message for phone number < 12 digits\" do\n        org = create(:casa_org)\n        admin = create(:casa_admin, casa_org: org)\n\n        sign_in admin\n        visit edit_users_path\n\n        fill_in \"Phone number\", with: \"+141632489\"\n        click_on(\"Update Profile\")\n        expect(page).to have_text \"Phone number must be 10 digits or 12 digits including country code (+1)\"\n      end\n\n      it \"shows error message for phone number > 12 digits\" do\n        org = create(:casa_org)\n        admin = create(:casa_admin, casa_org: org)\n\n        sign_in admin\n        visit edit_users_path\n\n        fill_in \"Phone number\", with: \"+141632180923\"\n        click_on(\"Update Profile\")\n\n        expect(page).to have_text \"Phone number must be 10 digits or 12 digits including country code (+1)\"\n      end\n\n      it \"shows error message for bad phone number\" do\n        org = create(:casa_org)\n        admin = create(:casa_admin, casa_org: org)\n\n        sign_in admin\n        visit edit_users_path\n\n        fill_in(\"Phone number\", with: \"+141632u809o\")\n        click_on(\"Update Profile\")\n\n        expect(page).to have_text \"Phone number must be 10 digits or 12 digits including country code (+1)\"\n      end\n\n      it \"shows error message for phone number without country code\" do\n        org = create(:casa_org)\n        admin = create(:casa_admin, casa_org: org)\n\n        sign_in admin\n        visit edit_users_path\n\n        fill_in(\"Phone number\", with: \"+24163218092\")\n        click_on(\"Update Profile\")\n        expect(page).to have_text \"Phone number must be 10 digits or 12 digits including country code (+1)\"\n      end\n    end\n\n    it \"is able to send a confirmation email when Casa Admin updates their email\" do\n      org = create(:casa_org)\n      admin = create(:casa_admin, casa_org: org)\n\n      sign_in admin\n      visit edit_users_path\n\n      click_on \"Change Email\"\n      expect(page).to have_field(\"New Email\", disabled: false)\n\n      fill_in \"current_password_email\", with: \"12345678\"\n\n      fill_in \"New Email\", with: \"new_admin@example.com\"\n      click_on \"Update Email\"\n\n      expect(ActionMailer::Base.deliveries.count).to eq(1)\n      expect(ActionMailer::Base.deliveries.first).to be_a(Mail::Message)\n      expect(ActionMailer::Base.deliveries.first.body.encoded)\n        .to match(\"Click here to confirm your email\")\n    end\n\n    it \"displays email errors messages when user is unable to set a email with incorrect current password\" do\n      org = create(:casa_org)\n      admin = create(:casa_admin, casa_org: org)\n\n      sign_in admin\n      visit edit_users_path\n\n      click_on \"Change Email\"\n\n      fill_in \"current_password_email\", with: \"12345\"\n      fill_in \"New Email\", with: \"new_admin@example.com\"\n\n      click_on \"Update Email\"\n      expect(page).to have_content \"1 error prohibited this Casa admin from being saved:\"\n      expect(page).to have_text(\"Current password is incorrect\")\n    end\n\n    it \"displays password errors messages when admin is unable to set a password\" do\n      org = create(:casa_org)\n      admin = create(:casa_admin, casa_org: org)\n\n      sign_in admin\n      visit edit_users_path\n\n      click_on \"Change Password\"\n\n      fill_in \"Current Password\", with: \"12345678\"\n      fill_in \"New Password\", with: \"123\"\n      fill_in \"Password Confirmation\", with: \"1234\"\n\n      click_on \"Update Password\"\n      expect(page).to have_content \"2 errors prohibited this password change from being saved:\"\n      expect(page).to have_text(\"Password confirmation doesn't match Password\")\n      expect(page).to have_text(\"Password is too short (minimum is #{User.password_length.min} characters)\")\n    end\n\n    it \"display success message when admin update password\" do\n      org = create(:casa_org)\n      admin = create(:casa_admin, casa_org: org)\n\n      sign_in admin\n      visit edit_users_path\n\n      click_on \"Change Password\"\n\n      fill_in \"Current Password\", with: \"12345678\"\n      fill_in \"New Password\", with: \"123456789\"\n      fill_in \"Password Confirmation\", with: \"123456789\"\n\n      click_on \"Update Password\"\n\n      expect(page).to have_text(\"Password was successfully updated.\")\n    end\n\n    it \"displays sms notification events for the casa admin user\" do\n      org = create(:casa_org, twilio_enabled: true)\n      admin = create(:casa_admin, casa_org: org)\n\n      SmsNotificationEvent.delete_all\n      SmsNotificationEvent.new(name: \"sms_event_test_volunteer\", user_type: Volunteer).save\n      SmsNotificationEvent.new(name: \"sms_event_test_supervisor\", user_type: Supervisor).save\n      SmsNotificationEvent.new(name: \"sms_event_test_casa_admin\", user_type: CasaAdmin).save\n\n      sign_in admin\n      visit edit_users_path\n\n      expect(page).not_to have_content \"sms_event_test_volunteer\"\n      expect(page).not_to have_content \"sms_event_test_supervisor\"\n      expect(page).to have_content \"sms_event_test_casa_admin\"\n    end\n\n    it \"notifies password changed by email\", :aggregate_failures do\n      org = create(:casa_org)\n      admin = create(:casa_admin, casa_org: org)\n\n      sign_in admin\n      visit edit_users_path\n\n      click_on \"Change Password\"\n\n      fill_in \"Current Password\", with: \"12345678\"\n      fill_in \"New Password\", with: \"123456789\"\n      fill_in \"Password Confirmation\", with: \"123456789\"\n\n      click_on \"Update Password\"\n\n      page.has_content?(\"Password was successfully updated.\")\n\n      expect(ActionMailer::Base.deliveries.count).to eq(1)\n      expect(ActionMailer::Base.deliveries.first).to be_a(Mail::Message)\n      expect(ActionMailer::Base.deliveries.first.body.encoded)\n        .to match(\"Your CASA password has been changed.\")\n    end\n\n    it \"displays admin error message if no communication preference is selected\" do\n      org = create(:casa_org)\n      admin = create(:casa_admin, casa_org: org)\n\n      sign_in admin\n      visit edit_users_path\n\n      uncheck \"user_receive_email_notifications\"\n      click_on \"Save Preferences\"\n      expect(page).to have_content \"1 error prohibited this Casa admin from being saved:\"\n      expect(page).to have_text(\"At least one communication preference must be selected.\")\n    end\n\n    it \"displays admin error message if SMS communication preference is selected without adding a valid phone number\" do\n      org = create(:casa_org, twilio_enabled: true)\n      admin = create(:casa_admin, casa_org: org)\n\n      sign_in admin\n      visit edit_users_path\n\n      uncheck \"user_receive_email_notifications\"\n      check \"user_receive_sms_notifications\"\n      click_on \"Save Preferences\"\n      expect(page).to have_content \"1 error prohibited this Casa admin from being saved:\"\n      expect(page).to have_text(\"Must add a valid phone number to receive SMS notifications.\")\n    end\n\n    it \"displays admin error message if invalid date of birth\" do\n      org = create(:casa_org)\n      admin = create(:casa_admin, casa_org: org)\n\n      sign_in admin\n      visit edit_users_path\n\n      fill_in \"Date of birth\", with: 8.days.from_now.strftime(\"%Y/%m/%d\")\n      click_on \"Update Profile\"\n      expect(page).to have_text(\"Date of birth must be in the past.\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/volunteers/edit_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"volunteers/edit\", type: :system do\n  describe \"updating volunteer personal data\" do\n    context \"with valid data\" do\n      it \"updates successfully\" do\n        organization = create(:casa_org)\n        admin = create(:casa_admin, casa_org_id: organization.id)\n        volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id)\n\n        sign_in admin\n        visit edit_volunteer_path(volunteer)\n\n        fill_in \"volunteer_display_name\", with: \"Kamisato Ayato\"\n        fill_in \"volunteer_phone_number\", with: \"+14163248967\"\n        fill_in \"volunteer_date_of_birth\", with: Date.new(1998, 7, 1)\n        click_on \"Submit\"\n\n        expect(page).to have_text \"Volunteer was successfully updated.\"\n      end\n    end\n\n    context \"with invalid data\" do\n      context \"shows error for invalid phone number\" do\n        it \"shows error message for phone number < 12 digits\" do\n          organization = create(:casa_org)\n          admin = create(:casa_admin, casa_org_id: organization.id)\n          volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id)\n\n          sign_in admin\n          visit edit_volunteer_path(volunteer)\n\n          fill_in \"volunteer_phone_number\", with: \"+141632489\"\n          click_on \"Submit\"\n          expect(page).to have_text \"Phone number must be 10 digits or 12 digits including country code (+1)\"\n        end\n\n        it \"shows error message for phone number > 12 digits\" do\n          organization = create(:casa_org)\n          admin = create(:casa_admin, casa_org_id: organization.id)\n          volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id)\n\n          sign_in admin\n          visit edit_volunteer_path(volunteer)\n\n          fill_in \"volunteer_phone_number\", with: \"+141632180923\"\n          click_on \"Submit\"\n\n          expect(page).to have_text \"Phone number must be 10 digits or 12 digits including country code (+1)\"\n        end\n\n        it \"shows error message for bad phone number\" do\n          organization = create(:casa_org)\n          admin = create(:casa_admin, casa_org_id: organization.id)\n          volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id)\n\n          sign_in admin\n          visit edit_volunteer_path(volunteer)\n\n          fill_in \"volunteer_phone_number\", with: \"+141632u809o\"\n          click_on \"Submit\"\n\n          expect(page).to have_text \"Phone number must be 10 digits or 12 digits including country code (+1)\"\n        end\n\n        it \"shows error message for phone number without country code\" do\n          organization = create(:casa_org)\n          admin = create(:casa_admin, casa_org_id: organization.id)\n          volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id)\n\n          sign_in admin\n          visit edit_volunteer_path(volunteer)\n\n          fill_in \"volunteer_phone_number\", with: \"+24163218092\"\n          click_on \"Submit\"\n\n          expect(page).to have_text \"Phone number must be 10 digits or 12 digits including country code (+1)\"\n        end\n\n        it \"shows error message for invalid date of birth\" do\n          organization = create(:casa_org)\n          admin = create(:casa_admin, casa_org_id: organization.id)\n          volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id)\n\n          sign_in admin\n          visit edit_volunteer_path(volunteer)\n\n          fill_in \"volunteer_date_of_birth\", with: 5.days.from_now\n          click_on \"Submit\"\n\n          expect(page).to have_text \"Date of birth must be in the past.\"\n        end\n      end\n\n      it \"shows error message for duplicate email\" do\n        organization = create(:casa_org)\n        admin = create(:casa_admin, casa_org_id: organization.id)\n        volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id)\n        volunteer.supervisor = build(:supervisor)\n\n        sign_in admin\n        visit edit_volunteer_path(volunteer)\n\n        fill_in \"volunteer_display_name\", with: \"Kamisato Ayato\"\n        fill_in \"volunteer_email\", with: admin.email\n        fill_in \"volunteer_display_name\", with: \"Mickey Mouse\"\n        click_on \"Submit\"\n\n        expect(page).to have_text \"already been taken\"\n      end\n\n      it \"shows error message for empty fields\" do\n        organization = create(:casa_org)\n        admin = create(:casa_admin, casa_org_id: organization.id)\n        volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id)\n        volunteer.supervisor = create(:supervisor)\n\n        sign_in admin\n        visit edit_volunteer_path(volunteer)\n\n        fill_in \"volunteer_email\", with: \"\"\n        fill_in \"volunteer_display_name\", with: \"\"\n        click_on \"Submit\"\n\n        expect(page).to have_text \"can't be blank\"\n      end\n    end\n  end\n\n  describe \"updating a volunteer's email\" do\n    context \"with a valid email\" do\n      it \"sends volunteer a confirmation email and does not change the displayed email\" do\n        organization = create(:casa_org)\n        admin = create(:casa_admin, casa_org: organization)\n        volunteer = create(:volunteer, :with_assigned_supervisor, casa_org: organization)\n        old_email = volunteer.email\n\n        sign_in admin\n        visit edit_volunteer_path(volunteer)\n\n        fill_in \"Email\", with: \"newemail@example.com\"\n        click_on \"Submit\"\n\n        expect(page).to have_text \"Volunteer was successfully updated. Confirmation Email Sent.\"\n        expect(page).to have_field(\"Email\", with: old_email)\n        expect(volunteer.reload.unconfirmed_email).to eq(\"newemail@example.com\")\n\n        expect(ActionMailer::Base.deliveries.count).to eq(1)\n        expect(ActionMailer::Base.deliveries.first).to be_a(Mail::Message)\n        expect(ActionMailer::Base.deliveries.first.body.encoded)\n          .to match(\"Click here to confirm your email\")\n      end\n\n      it \"succesfully displays the new email once the user confirms\" do\n        organization = create(:casa_org)\n        admin = create(:casa_admin, casa_org: organization)\n        volunteer = create(:volunteer, :with_assigned_supervisor, casa_org: organization)\n        old_email = volunteer.email\n\n        sign_in admin\n        visit edit_volunteer_path(volunteer)\n\n        fill_in \"Email\", with: \"newemail@example.com\"\n        click_on \"Submit\"\n        volunteer.reload\n        volunteer.confirm\n\n        visit edit_volunteer_path(volunteer)\n\n        expect(page).to have_field(\"Email\", with: \"newemail@example.com\")\n        expect(page).not_to have_field(\"Email\", with: old_email)\n        expect(volunteer.old_emails).to eq([old_email])\n      end\n    end\n  end\n\n  it \"saves the user as inactive, but only if the admin confirms\", :js do\n    organization = create(:casa_org)\n    admin = create(:casa_admin, casa_org: organization)\n    volunteer = create(:volunteer, :with_assigned_supervisor, casa_org: organization)\n\n    sign_in admin\n    visit edit_volunteer_path(volunteer)\n\n    dismiss_confirm do\n      scroll_to(\".actions\")\n      click_on \"Deactivate volunteer\"\n    end\n    expect(page).not_to have_text(\"Volunteer was deactivated on\")\n\n    accept_confirm do\n      click_on \"Deactivate volunteer\"\n    end\n    expect(page).to have_text(\"Volunteer was deactivated on\")\n\n    expect(volunteer.reload).not_to be_active\n  end\n\n  it \"allows an admin to reactivate a volunteer\" do\n    organization = create(:casa_org)\n    admin = create(:casa_admin, casa_org: organization)\n    inactive_volunteer = build(:volunteer, casa_org: organization)\n    inactive_volunteer.deactivate\n\n    sign_in admin\n\n    visit edit_volunteer_path(inactive_volunteer)\n\n    click_on \"Activate volunteer\"\n\n    expect(page).not_to have_text(\"Volunteer was deactivated on\")\n\n    expect(inactive_volunteer.reload).to be_active\n  end\n\n  it \"allows the admin to unassign a volunteer from a supervisor\" do\n    organization = create(:casa_org)\n    supervisor = build(:supervisor, display_name: \"Haka Haka\", casa_org: organization)\n    volunteer = create(:volunteer, display_name: \"Bolu Bolu\", supervisor: supervisor, casa_org: organization)\n    admin = create(:casa_admin, casa_org: organization)\n\n    sign_in admin\n    visit edit_volunteer_path(volunteer)\n\n    expect(page).to have_content(\"Current Supervisor: Haka Haka\")\n\n    click_on \"Unassign from Supervisor\"\n\n    expect(page).to have_content(\"Bolu Bolu was unassigned from Haka Haka\")\n  end\n\n  it \"shows the admin the option to assign an unassigned volunteer to a different active supervisor\" do\n    organization = create(:casa_org)\n    volunteer = create(:volunteer, casa_org: organization)\n    deactivated_supervisor = build(:supervisor, active: false, casa_org: organization, display_name: \"Inactive Supervisor\")\n    active_supervisor = create(:supervisor, active: true, casa_org: organization, display_name: \"Active Supervisor\")\n    admin = create(:casa_admin, casa_org: organization)\n\n    sign_in admin\n    visit edit_volunteer_path(volunteer)\n\n    expect(page).not_to have_select(\"supervisor_volunteer[supervisor_id]\", with_options: [deactivated_supervisor.display_name])\n    expect(page).to have_select(\"supervisor_volunteer[supervisor_id]\", options: [active_supervisor.display_name])\n    expect(page).to have_content(\"Select a Supervisor\")\n    expect(page).to have_content(\"Assign a Supervisor\")\n  end\n\n  context \"when the volunteer is unassigned from all of their cases\" do\n    it \"does not show any active assignment status in the Manage Cases section\" do\n      organization = create(:casa_org)\n      admin = create(:casa_admin, casa_org_id: organization.id)\n      volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id)\n      casa_case_1 = create(:casa_case, casa_org: organization, case_number: \"CINA1\")\n      casa_case_2 = create(:casa_case, casa_org: organization, case_number: \"CINA2\")\n      create(:case_assignment, volunteer: volunteer, casa_case: casa_case_1)\n      create(:case_assignment, volunteer: volunteer, casa_case: casa_case_2)\n      casa_case_1.update!(active: false)\n      casa_case_2.update!(active: false)\n\n      sign_in admin\n      visit edit_volunteer_path(volunteer)\n\n      within \"#manage_cases\" do\n        expect(page).not_to have_content(\"Volunteer is Active\")\n      end\n    end\n\n    it \"shows the unassigned cases in the Manage Cases section\" do\n      organization = create(:casa_org)\n      admin = create(:casa_admin, casa_org_id: organization.id)\n      volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id)\n      casa_case_1 = create(:casa_case, casa_org: organization, case_number: \"CINA1\")\n      casa_case_2 = create(:casa_case, casa_org: organization, case_number: \"CINA2\")\n      case_assignment_1 = create(:case_assignment, volunteer: volunteer, casa_case: casa_case_1)\n      case_assignment_2 = create(:case_assignment, volunteer: volunteer, casa_case: casa_case_2)\n      casa_case_1.update!(active: false)\n      casa_case_2.update!(active: false)\n\n      sign_in admin\n      visit edit_volunteer_path(volunteer)\n\n      within \"#case_assignment_#{case_assignment_1.id}\" do\n        expect(page).to have_link(\"CINA1\", href: \"/casa_cases/#{casa_case_1.case_number.parameterize}\")\n      end\n\n      within \"#case_assignment_#{case_assignment_2.id}\" do\n        expect(page).to have_link(\"CINA2\", href: \"/casa_cases/#{casa_case_2.case_number.parameterize}\")\n      end\n    end\n\n    it \"shows assignment status as 'Volunteer is Unassigned' for each unassigned case\" do\n      organization = create(:casa_org)\n      admin = create(:casa_admin, casa_org_id: organization.id)\n      volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id)\n      casa_case_1 = create(:casa_case, casa_org: organization, case_number: \"CINA1\")\n      casa_case_2 = create(:casa_case, casa_org: organization, case_number: \"CINA2\")\n      case_assignment_1 = build(:case_assignment, volunteer: volunteer, casa_case: casa_case_1)\n      case_assignment_2 = build(:case_assignment, volunteer: volunteer, casa_case: casa_case_2)\n\n      case_assignment_1.active = false\n      case_assignment_2.active = false\n      case_assignment_1.save\n      case_assignment_2.save\n\n      sign_in admin\n      visit edit_volunteer_path(volunteer)\n\n      within \"#case_assignment_#{case_assignment_1.id}\" do\n        expect(page).to have_content(\"Volunteer is Unassigned\")\n      end\n\n      within \"#case_assignment_#{case_assignment_2.id}\" do\n        expect(page).to have_content(\"Volunteer is Unassigned\")\n      end\n    end\n  end\n\n  context \"with a deactivated case\" do\n    it \"displays inactive message\" do\n      organization = create(:casa_org)\n      admin = create(:casa_admin, casa_org_id: organization.id)\n      volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id)\n      deactivated_casa_case = create(:casa_case, active: false, casa_org: volunteer.casa_org, volunteers: [volunteer])\n\n      sign_in admin\n      visit edit_volunteer_path(volunteer)\n\n      expect(page).to have_text \"Case was deactivated on: #{I18n.l(deactivated_casa_case.updated_at, format: :standard, default: nil)}\"\n    end\n  end\n\n  context \"when volunteer is assigned to multiple cases\" do\n    it \"supervisor assigns multiple cases to the same volunteer\" do\n      casa_org = build(:casa_org)\n      supervisor = create(:casa_admin, casa_org: casa_org)\n      volunteer = create(:volunteer, casa_org: casa_org, display_name: \"AAA\")\n      casa_case_1 = create(:casa_case, casa_org: casa_org, case_number: \"CINA1\")\n      casa_case_2 = create(:casa_case, casa_org: casa_org, case_number: \"CINA2\")\n\n      sign_in supervisor\n      visit edit_volunteer_path(volunteer)\n\n      select casa_case_1.case_number, from: \"Select a Case\"\n      click_on \"Assign Case\"\n      expect(page).to have_text(\"Volunteer assigned to case\")\n      expect(page).to have_text(casa_case_1.case_number)\n\n      select casa_case_2.case_number, from: \"Select a Case\"\n      click_on \"Assign Case\"\n      expect(page).to have_text(\"Volunteer assigned to case\")\n      expect(page).to have_text(casa_case_2.case_number)\n    end\n  end\n\n  context \"with previously assigned cases\" do\n    it \"shows the unassign button for assigned cases and not for unassigned cases\" do\n      casa_org = build(:casa_org)\n      supervisor = create(:casa_admin, casa_org: casa_org)\n      volunteer = create(:volunteer, casa_org: casa_org, display_name: \"AAA\")\n      casa_case_1 = build(:casa_case, casa_org: casa_org, case_number: \"CINA1\")\n      casa_case_2 = build(:casa_case, casa_org: casa_org, case_number: \"CINA2\")\n      assignment1 = volunteer.case_assignments.create(casa_case: casa_case_1, active: true)\n      assignment2 = volunteer.case_assignments.create(casa_case: casa_case_2, active: false)\n\n      sign_in supervisor\n      visit edit_volunteer_path(volunteer)\n\n      within(\"#case_assignment_#{assignment1.id}\") do\n        expect(page).to have_text(casa_case_1.case_number)\n        expect(page).to have_button(\"Unassign Case\")\n      end\n\n      within(\"#case_assignment_#{assignment2.id}\") do\n        expect(page).to have_text(casa_case_2.case_number)\n        expect(page).not_to have_button(\"Unassign Case\")\n      end\n\n      select casa_case_2.case_number, from: \"Select a Case\"\n      click_on \"Assign Case\"\n\n      within(\"#case_assignment_#{assignment2.id}\") do\n        expect(page).to have_text(casa_case_2.case_number)\n        expect(page).to have_button(\"Unassign Case\")\n      end\n    end\n  end\n\n  describe \"inactive case visibility\" do\n    it \"supervisor does not have inactive cases as an option to assign to a volunteer\" do\n      organization = build(:casa_org)\n      active_casa_case = create(:casa_case, casa_org: organization, case_number: \"ACTIVE\")\n      inactive_casa_case = create(:casa_case, casa_org: organization, active: false, case_number: \"INACTIVE\")\n      volunteer = create(:volunteer, display_name: \"Awesome Volunteer\", casa_org: organization)\n      supervisor = build(:casa_admin, casa_org: organization)\n\n      sign_in supervisor\n      visit edit_volunteer_path(volunteer)\n\n      expect(page).to have_content(active_casa_case.case_number)\n      expect(page).not_to have_content(inactive_casa_case.case_number)\n    end\n  end\n\n  describe \"resend invite\" do\n    it \"allows a supervisor resend invitation to a volunteer\" do\n      organization = create(:casa_org)\n      volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id)\n      supervisor = create(:supervisor, casa_org: organization)\n\n      sign_in supervisor\n      visit edit_volunteer_path(volunteer)\n\n      click_on \"Resend Invitation\"\n\n      expect(page).to have_content(\"Invitation sent\")\n\n      deliveries = ActionMailer::Base.deliveries\n      expect(deliveries.count).to eq(1)\n      expect(deliveries.last.subject).to have_text \"CASA Console invitation instructions\"\n    end\n  end\n\n  it \"allows an administrator resend invitation to a volunteer\" do\n    organization = create(:casa_org)\n    volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id)\n    admin = create(:casa_admin, casa_org: organization)\n\n    sign_in admin\n    visit edit_volunteer_path(volunteer)\n\n    click_on \"Resend Invitation\"\n\n    expect(page).to have_content(\"Invitation sent\")\n\n    deliveries = ActionMailer::Base.deliveries\n    expect(deliveries.count).to eq(1)\n    expect(deliveries.last.subject).to have_text \"CASA Console invitation instructions\"\n  end\n\n  describe \"Send Reactivation (SMS)\" do\n    it \"allows admin to send a reactivation SMS to a volunteer if their org has twilio enabled\" do\n      organization = create(:casa_org, twilio_enabled: true)\n      volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id)\n      admin = create(:casa_admin, casa_org: organization)\n\n      sign_in admin\n      visit edit_volunteer_path(volunteer)\n\n      expect(page).to have_content(\"Send Reactivation Alert (SMS)\")\n      expect(page).not_to have_content(\"Enable Twilio\")\n      expect(page).to have_selector(\"#twilio_enabled\")\n    end\n\n    context \"admin's organization does not have twilio enabled\" do\n      it \"displays a disabed (SMS) button with appropriate message\" do\n        org_twilio = create(:casa_org, twilio_enabled: false)\n        admin_twilio = create(:casa_admin, casa_org: org_twilio)\n        volunteer_twilio = create(:volunteer, casa_org: org_twilio)\n\n        sign_in admin_twilio\n        visit edit_volunteer_path(volunteer_twilio)\n\n        expect(page).to have_content(\"Enable Twilio To Send Reactivation Alert (SMS)\")\n        expect(page).to have_selector(\"#twilio_disabled\")\n      end\n    end\n  end\n\n  describe \"send reminder as a supervisor\" do\n    it \"emails the volunteer\" do\n      organization = create(:casa_org)\n      volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id)\n      supervisor = create(:supervisor, casa_org: organization)\n\n      sign_in supervisor\n      visit edit_volunteer_path(volunteer)\n\n      expect(page).to have_button(\"Send Reminder\")\n      expect(page).to have_text(\"Send CC to Supervisor\")\n      uncheck \"with_cc\"\n      click_on \"Send Reminder\"\n\n      expect(page).to have_content(\"Reminder sent to volunteer\")\n\n      expect(ActionMailer::Base.deliveries.count).to eq(1)\n      expect(ActionMailer::Base.deliveries.first.cc).to be_empty\n    end\n\n    it \"emails volunteer and cc's the supervisor\" do\n      organization = create(:casa_org)\n      volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id)\n      supervisor = create(:supervisor, casa_org: organization)\n\n      sign_in supervisor\n      visit edit_volunteer_path(volunteer)\n\n      check \"with_cc\"\n      click_on \"Send Reminder\"\n\n      expect(page).to have_content(\"Reminder sent to volunteer\")\n\n      expect(ActionMailer::Base.deliveries.count).to eq(1)\n      expect(ActionMailer::Base.deliveries.first.cc).to include(volunteer.supervisor.email)\n    end\n\n    it \"emails the volunteer without a supervisor\" do\n      organization = create(:casa_org)\n      volunteer_without_supervisor = create(:volunteer)\n      supervisor = create(:supervisor, casa_org: organization)\n\n      sign_in supervisor\n      visit edit_volunteer_path(volunteer_without_supervisor)\n\n      check \"with_cc\"\n      click_on \"Send Reminder\"\n\n      expect(page).to have_content(\"Reminder sent to volunteer\")\n\n      expect(ActionMailer::Base.deliveries.count).to eq(1)\n      expect(ActionMailer::Base.deliveries.first.cc).to be_empty\n    end\n  end\n\n  describe \"send reminder as admin\" do\n    it \"emails the volunteer\" do\n      organization = create(:casa_org)\n      admin = create(:casa_admin, casa_org_id: organization.id)\n      volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id)\n\n      sign_in admin\n      visit edit_volunteer_path(volunteer)\n\n      expect(page).to have_button(\"Send Reminder\")\n      expect(page).to have_text(\"Send CC to Supervisor and Admin\")\n\n      click_on \"Send Reminder\"\n\n      expect(page).to have_content(\"Reminder sent to volunteer\")\n\n      expect(ActionMailer::Base.deliveries.count).to eq(1)\n    end\n\n    it \"emails the volunteer and cc's their supervisor and admin\" do\n      organization = create(:casa_org)\n      admin = create(:casa_admin, casa_org_id: organization.id)\n      volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id)\n\n      sign_in admin\n      visit edit_volunteer_path(volunteer)\n      check \"with_cc\"\n      click_on \"Send Reminder\"\n\n      expect(page).to have_content(\"Reminder sent to volunteer\")\n\n      expect(ActionMailer::Base.deliveries.count).to eq(1)\n      expect(ActionMailer::Base.deliveries.first.cc).to include(volunteer.supervisor.email)\n      expect(ActionMailer::Base.deliveries.first.cc).to include(admin.email)\n    end\n  end\n\n  describe \"impersonate button\" do\n    context \"when user is an admin\" do\n      it \"impersonates the volunteer\" do\n        organization = create(:casa_org)\n        admin = create(:casa_admin, casa_org: organization)\n        volunteer = create(:volunteer, casa_org: organization, display_name: \"John Doe\")\n        sign_in admin\n        visit edit_volunteer_path(volunteer)\n\n        click_on \"Impersonate\"\n\n        within(\".header\") do\n          expect(page).to have_text(\n            \"You (#{admin.display_name}) are signed in as John Doe. \" \\\n              \"Click here to stop impersonating.\"\n          )\n        end\n      end\n    end\n\n    context \"when user is a supervisor\" do\n      it \"impersonates the volunteer\" do\n        organization = create(:casa_org)\n        supervisor = create(:supervisor, casa_org: organization)\n        volunteer = create(:volunteer, casa_org: organization, display_name: \"John Doe\")\n        sign_in supervisor\n        visit edit_volunteer_path(volunteer)\n\n        click_on \"Impersonate\"\n\n        within(\".header\") do\n          expect(page).to have_text(\n            \"You (#{supervisor.display_name}) are signed in as John Doe. \" \\\n              \"Click here to stop impersonating.\"\n          )\n        end\n      end\n    end\n\n    context \"when user is a volunteer\" do\n      it \"does not show the impersonate button\", :aggregate_failures do\n        organization = create(:casa_org)\n        volunteer = create(:volunteer, casa_org: organization)\n        user = create(:volunteer, casa_org: organization)\n\n        sign_in user\n        visit edit_volunteer_path(volunteer)\n\n        expect(page).not_to have_link(\"Impersonate\")\n        expect(page).to have_no_current_path(edit_volunteer_path(volunteer), ignore_query: true)\n      end\n    end\n  end\n\n  context \"logged in as an admin\" do\n    it \"can save notes about a volunteer\" do\n      organization = create(:casa_org)\n      admin = create(:casa_admin, casa_org_id: organization.id)\n      volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id)\n\n      sign_in admin\n      visit edit_volunteer_path(volunteer)\n\n      freeze_time do\n        current_date = Date.today\n        fill_in(\"note[content]\", with: \"Great job today.\")\n        within(\".notes\") do\n          click_on(\"Save Note\")\n        end\n\n        expect(page).to have_current_path(edit_volunteer_path(volunteer), ignore_query: true)\n        within(\".notes\") do\n          expect(page).to have_text(\"Great job today.\")\n          expect(page).to have_text(admin.display_name)\n          expect(page).to have_text(I18n.l(current_date.to_date, format: :standard, default: \"\"))\n        end\n      end\n    end\n\n    it \"can delete notes about a volunteer\" do\n      organization = create(:casa_org)\n      admin = create(:casa_admin, casa_org_id: organization.id)\n      volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id)\n      volunteer.notes.create(creator: admin, content: \"Note_1\")\n      volunteer.notes.create(creator: admin, content: \"Note_2\")\n      volunteer.notes.create(creator: admin, content: \"Note_3\")\n\n      sign_in admin\n      visit edit_volunteer_path(volunteer)\n\n      expect(page).to have_css \".notes .table tbody tr\", count: 3\n\n      click_on(\"Delete\", match: :first)\n\n      expect(page).to have_css \".notes .table tbody tr\", count: 2\n    end\n  end\n\n  context \"logged in as a supervisor\" do\n    it \"can save notes about a volunteer\" do\n      organization = create(:casa_org)\n      volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id)\n      supervisor = volunteer.supervisor\n\n      sign_in supervisor\n      visit edit_volunteer_path(volunteer)\n\n      freeze_time do\n        current_date = Date.today\n        fill_in(\"note[content]\", with: \"Great job today.\")\n        within(\".notes\") do\n          click_on(\"Save Note\")\n        end\n\n        expect(page).to have_current_path(edit_volunteer_path(volunteer), ignore_query: true)\n        within(\".notes\") do\n          expect(page).to have_text(\"Great job today.\")\n          expect(page).to have_text(volunteer.supervisor.display_name)\n          expect(page).to have_text(I18n.l(current_date.to_date, format: :standard, default: \"\"))\n        end\n      end\n    end\n\n    it \"can delete notes about a volunteer\" do\n      organization = create(:casa_org)\n      admin = create(:casa_admin, casa_org_id: organization.id)\n      volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id)\n      supervisor = volunteer.supervisor\n      volunteer.notes.create(creator: admin, content: \"Note_1\")\n      volunteer.notes.create(creator: admin, content: \"Note_2\")\n      volunteer.notes.create(creator: admin, content: \"Note_3\")\n\n      sign_in supervisor\n      visit edit_volunteer_path(volunteer)\n\n      expect(page).to have_css \".notes .table tbody tr\", count: 3\n\n      click_on(\"Delete\", match: :first)\n\n      expect(page).to have_css \".notes .table tbody tr\", count: 2\n    end\n  end\n\n  context \"logged in as volunteer\" do\n    it \"can't see the notes section\" do\n      organization = create(:casa_org)\n      volunteer = create(:volunteer, :with_assigned_supervisor, casa_org: organization)\n      sign_in volunteer\n      visit edit_volunteer_path(volunteer)\n\n      expect(page).not_to have_selector(\".notes\")\n      expect(page).to have_content(\"Sorry, you are not authorized to perform this action.\")\n    end\n  end\n\n  describe \"updating volunteer address\" do\n    context \"with mileage reimbursement turned on\" do\n      it \"shows 'Mailing address' label\" do\n        organization = create(:casa_org)\n        admin = create(:casa_admin, casa_org_id: organization.id)\n        volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id)\n\n        sign_in admin\n        visit edit_volunteer_path(volunteer)\n\n        expect(page).to have_text \"Mailing address\"\n        expect(page).to have_selector \"input[type=text][id=volunteer_address_attributes_content]\"\n      end\n\n      it \"updates successfully\" do\n        organization = create(:casa_org)\n        admin = create(:casa_admin, casa_org_id: organization.id)\n        volunteer = create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id)\n\n        sign_in admin\n        visit edit_volunteer_path(volunteer)\n\n        fill_in \"volunteer_address_attributes_content\", with: \"123 Main St\"\n        click_on \"Submit\"\n        expect(page).to have_text \"Volunteer was successfully updated.\"\n        expect(page).to have_selector(\"input[value='123 Main St']\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/volunteers/index_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"view all volunteers\", :js, type: :system do\n  let(:organization) { create(:casa_org) }\n  let(:admin) { create(:casa_admin, casa_org: organization) }\n  let!(:supervisor) { create(:supervisor, casa_org: organization) }\n\n  context \"admin user\" do\n    context \"when no logo_url\" do\n      it \"can see volunteers and navigate to their cases\", :js do\n        volunteer = create(:volunteer, display_name: \"User 1\", email: \"casa@example.com\", casa_org: organization, supervisor: supervisor)\n        volunteer.casa_cases << build(:casa_case, casa_org: organization, birth_month_year_youth: CasaCase::TRANSITION_AGE.years.ago)\n        volunteer.casa_cases << build(:casa_case, casa_org: organization, birth_month_year_youth: CasaCase::TRANSITION_AGE.years.ago)\n        casa_case = volunteer.casa_cases[0]\n\n        sign_in admin\n\n        visit volunteers_path\n\n        expect(page).to have_text(\"User 1\")\n        expect(page).to have_text(casa_case.case_number)\n\n        within \"#volunteers\" do\n          click_on volunteer.casa_cases.first.case_number\n        end\n\n        expect(page).to have_text(\"CASA Case Details\")\n        expect(page).to have_text(\"Case number: #{casa_case.case_number}\")\n        expect(page).to have_text(\"Transition Aged Youth: Yes\")\n        expect(page).to have_text(\"Next Court Date:\")\n        expect(page).to have_text(\"Court Report Status: Not submitted\")\n        expect(page).to have_text(\"Assigned Volunteers:\")\n      end\n\n      it \"displays default logo\" do\n        sign_in admin\n\n        visit volunteers_path\n\n        expect(page).to have_css(\"#casa-logo[src*='default-logo']\")\n        expect(page).to have_css(\"#casa-logo[alt='CASA Logo']\")\n      end\n    end\n\n    it \"displays last attempted contact by default\", :js do\n      create(:volunteer, display_name: \"User 1\", email: \"casa@example.com\", casa_org: organization, supervisor: supervisor)\n\n      sign_in admin\n\n      visit volunteers_path\n\n      expect(page).to have_content(:visible, \"Last Attempted Contact\")\n    end\n\n    it \"can show/hide columns on volunteers table\", :js do\n      sign_in admin\n\n      visit volunteers_path\n      expect(page).to have_text(\"Pick displayed columns\")\n\n      click_on \"Pick displayed columns\"\n      expect(page).to have_text(\"Name\")\n      expect(page).to have_text(\"Status\")\n      expect(page).to have_text(\"Contact Made In Past 60 Days\")\n      expect(page).to have_text(\"Last Attempted Contact\")\n      check \"Name\"\n      check \"Status\"\n      uncheck \"Contact Made In Past 60 Days\"\n      uncheck \"Last Attempted Contact\"\n      within(\".modal-dialog\") do\n        click_on \"Close\"\n      end\n\n      expect(page).to have_text(\"Name\")\n      expect(page).to have_text(\"Status\")\n      within(\"#volunteers\") do\n        expect(page).to have_no_text(\"Contact Made In Past 60 Days\")\n        expect(page).to have_no_text(\"Last Attempted Contact\")\n      end\n    end\n\n    it \"can filter volunteers\", :js do\n      assigned_volunteers = create_list(:volunteer, 2, casa_org: organization, supervisor: supervisor)\n      inactive_volunteers = create_list(:volunteer, 2, :inactive, casa_org: organization)\n      unassigned_volunteer = create(:volunteer, casa_org: organization)\n\n      sign_in admin\n\n      visit volunteers_path\n      expect(page).to have_css(\".volunteer-filters\")\n\n      assigned_volunteers.each do |assigned_volunteer|\n        expect(page).to have_text assigned_volunteer.display_name\n      end\n      expect(page).to have_text unassigned_volunteer.display_name\n\n      click_on \"Status\"\n      find(:css, 'input[data-value=\"true\"]').set(false)\n      expect(page).to have_text(\"No matching records found\")\n\n      find(:css, 'input[data-value=\"false\"]').set(true)\n      inactive_volunteers.each do |inactive_volunteer|\n        expect(page).to have_text inactive_volunteer.display_name\n      end\n      expect(page).to have_css(\"table#volunteers tbody tr\", count: inactive_volunteers.count)\n\n      visit volunteers_path\n      click_on \"Supervisor\"\n      find_by_id(\"unassigned-vol-filter\").set(false)\n      assigned_volunteers.each do |assigned_volunteer|\n        expect(page).to have_text assigned_volunteer.display_name\n      end\n      expect(page).to have_css(\"table#volunteers tbody tr\", count: assigned_volunteers.count)\n    end\n\n    it \"can go to the volunteer edit page from the volunteer list\", :js do\n      create(:volunteer, casa_org: organization, supervisor: supervisor)\n      sign_in admin\n\n      visit volunteers_path\n\n      within \"#volunteers\" do\n        click_on \"Edit\"\n      end\n\n      expect(page).to have_text(\"Editing Volunteer\")\n    end\n\n    it \"can go to the new volunteer page\" do\n      sign_in admin\n\n      visit volunteers_path\n\n      click_on \"New Volunteer\"\n\n      expect(page).to have_text(\"New Volunteer\")\n      expect(page).to have_css(\"form#new_volunteer\")\n    end\n\n    describe \"supervisor column of volunteers table\" do\n      it \"is blank when volunteer has no supervisor\", :js do\n        create(:volunteer, casa_org: organization)\n        sign_in admin\n\n        visit volunteers_path\n        click_on \"Supervisor\"\n        find_by_id(\"unassigned-vol-filter\").set(true)\n\n        expect(page).to have_css(\"tbody .supervisor-column\", text: \"\")\n      end\n\n      it \"displays supervisor's name when volunteer has supervisor\", :js do\n        name = \"Superduper Visor\"\n        supervisor = create(:supervisor, display_name: name, casa_org: organization)\n        create(:volunteer, supervisor: supervisor, casa_org: organization)\n        sign_in admin\n\n        visit volunteers_path\n        expect(page).to have_css(\"tbody .supervisor-column\", text: name)\n      end\n\n      it \"is blank when volunteer's supervisor is inactive\", :js do\n        create(:volunteer, :with_inactive_supervisor, casa_org: organization)\n        sign_in admin\n\n        visit volunteers_path\n        click_on \"Supervisor\"\n        find_by_id(\"unassigned-vol-filter\").set(true)\n\n        expect(page).to have_css(\"tbody .supervisor-column\", text: \"\")\n      end\n    end\n\n    describe \"Manage Volunteers button\" do\n      let!(:volunteers) { create_list(:volunteer, 2, casa_org: organization) }\n\n      before do\n        sign_in admin\n        visit volunteers_path\n      end\n\n      it \"does not display by default\" do\n        expect(page).to have_no_text \"Manage Volunteer\"\n      end\n\n      context \"when one or more volunteers selected\" do\n        it \"is displayed\" do\n          find(\"#supervisor_volunteer_volunteer_ids_#{volunteers[0].id}\").click\n\n          expect(page).to have_text \"Manage Volunteer\"\n        end\n\n        it \"displays number of volunteers selected\" do\n          volunteers.each_with_index do |volunteer, index|\n            find(\"#supervisor_volunteer_volunteer_ids_#{volunteer.id}\").click\n            expect(page).to have_css(\"[data-select-all-target='buttonLabel']\", text: \"#{index + 1})\")\n          end\n        end\n\n        it \"text matches pluralization of volunteers selected\" do\n          find(\"#supervisor_volunteer_volunteer_ids_#{volunteers[0].id}\").click\n          expect(page).to have_no_text \"Manage Volunteers\"\n\n          find(\"#supervisor_volunteer_volunteer_ids_#{volunteers[1].id}\").click\n          expect(page).to have_text \"Manage Volunteers\"\n        end\n\n        it \"is hidden when all volunteers unchecked\" do\n          find(\"#supervisor_volunteer_volunteer_ids_#{volunteers[0].id}\").click\n          expect(page).to have_text \"Manage Volunteer\"\n\n          find(\"#supervisor_volunteer_volunteer_ids_#{volunteers[0].id}\").click\n          expect(page).to have_no_text \"Manage Volunteer\"\n        end\n      end\n    end\n\n    describe \"Select All Checkbox\" do\n      let!(:volunteers) { create_list(:volunteer, 2, casa_org: organization) }\n\n      before do\n        sign_in admin\n        visit volunteers_path\n      end\n\n      it \"selects all volunteers\" do\n        find(\"#supervisor_volunteer_volunteer_ids_#{volunteers[0].id}\") # Wait for data table to be loaded\n        find_by_id(\"checkbox-toggle-all\").click\n\n        volunteers.each do |volunteer|\n          expect(page).to have_field(\"supervisor_volunteer_volunteer_ids_#{volunteer.id}\", checked: true)\n        end\n      end\n\n      context \"when all are checked\" do\n        it \"deselects all volunteers\" do\n          volunteers.each do |volunteer|\n            find(\"#supervisor_volunteer_volunteer_ids_#{volunteer.id}\").click\n          end\n\n          find_by_id(\"checkbox-toggle-all\").click\n          expect(page).to have_field(\"checkbox-toggle-all\", checked: false)\n\n          volunteers.each do |volunteer|\n            expect(page).to have_field(\"supervisor_volunteer_volunteer_ids_#{volunteer.id}\", checked: false)\n          end\n        end\n      end\n\n      context \"when some are checked\" do\n        it \"is semi-checked (indeterminate)\" do\n          find(\"#supervisor_volunteer_volunteer_ids_#{volunteers[0].id}\").click\n\n          expect(page).to have_field(\"checkbox-toggle-all\", checked: false)\n          expect(find_by_id(\"checkbox-toggle-all\")[:indeterminate]).to eq(\"true\")\n        end\n\n        it \"selects all volunteers\" do\n          find(\"#supervisor_volunteer_volunteer_ids_#{volunteers[0].id}\").click\n          find_by_id(\"checkbox-toggle-all\").click\n\n          volunteers.each do |volunteer|\n            expect(page).to have_field(\"supervisor_volunteer_volunteer_ids_#{volunteer.id}\", checked: true)\n          end\n        end\n      end\n    end\n\n    describe \"Select Supervisor Modal Submit button\" do\n      let!(:volunteer) { create(:volunteer, casa_org: organization) }\n\n      before do\n        sign_in admin\n        visit volunteers_path\n      end\n\n      it \"is disabled by default\" do\n        find(\"#supervisor_volunteer_volunteer_ids_#{volunteer.id}\").click\n        find(\"[data-select-all-target='button']\").click\n\n        expect(page).to have_button(\"Confirm\", disabled: true, class: %w[deactive-btn main-btn])\n      end\n\n      context \"when none is selected\" do\n        it \"is enabled\" do\n          find(\"#supervisor_volunteer_volunteer_ids_#{volunteer.id}\").click\n          find(\"[data-select-all-target='button']\").click\n          select \"None\", from: \"supervisor_volunteer_supervisor_id\"\n\n          expect(page).to have_button(\"Confirm\", disabled: false, class: %w[!deactive-btn dark-btn btn-hover])\n        end\n      end\n\n      context \"when a supervisor is selected\" do\n        it \"is enabled\" do\n          find(\"#supervisor_volunteer_volunteer_ids_#{volunteer.id}\").click\n          find(\"[data-select-all-target='button']\").click\n\n          select supervisor.display_name, from: \"supervisor_volunteer_supervisor_id\"\n\n          expect(page).to have_button(\"Confirm\", disabled: false, class: %w[!deactive-btn dark-btn btn-hover])\n        end\n      end\n\n      context \"when Choose a supervisor is selected\" do\n        it \"is disabled\" do\n          visit volunteers_path\n          find(\"#supervisor_volunteer_volunteer_ids_#{volunteer.id}\").click\n          find(\"[data-select-all-target='button']\").click\n\n          select supervisor.display_name, from: \"supervisor_volunteer_supervisor_id\"\n          select \"Choose a supervisor\", from: \"supervisor_volunteer_supervisor_id\"\n\n          expect(page).to have_button(\"Confirm\", disabled: true, class: %w[deactive-btn !dark-btn !btn-hover])\n        end\n      end\n    end\n  end\n\n  context \"supervisor user\" do\n    it \"can filter volunteers\", :js do\n      active_volunteer = create(:volunteer, :with_assigned_supervisor, casa_org: organization)\n      active_volunteer.supervisor = supervisor\n\n      inactive_volunteers = create_list(:volunteer, 2, :inactive, supervisor: supervisor, casa_org: organization)\n\n      sign_in supervisor\n\n      visit volunteers_path\n      expect(page).to have_css(\".volunteer-filters\")\n      expect(page).to have_css(\"table#volunteers tbody tr\", count: 1)\n\n      click_on \"Status\"\n      find(:css, 'input[data-value=\"true\"]').set(false)\n      expect(page).to have_text(\"No matching records found\")\n\n      find(:css, 'input[data-value=\"false\"]').set(true)\n      inactive_volunteers.each do |inactive_volunteer|\n        expect(page).to have_text inactive_volunteer.display_name\n      end\n      expect(page).to have_css(\"table#volunteers tbody tr\", count: inactive_volunteers.count)\n    end\n\n    it \"can show/hide columns on volunteers table\", :js do\n      sign_in supervisor\n\n      visit volunteers_path\n      expect(page).to have_text(\"Pick displayed columns\")\n\n      click_on \"Pick displayed columns\"\n      expect(page).to have_text(\"Name\")\n      expect(page).to have_text(\"Status\")\n      expect(page).to have_text(\"Contact Made In Past 60 Days\")\n      expect(page).to have_text(\"Last Attempted Contact\")\n      check \"Name\"\n      check \"Status\"\n      uncheck \"Contact Made In Past 60 Days\"\n      uncheck \"Last Attempted Contact\"\n      within(\".modal-dialog\") do\n        click_on \"Close\"\n      end\n\n      expect(page).to have_text(\"Name\")\n      expect(page).to have_text(\"Status\")\n      within(\"#volunteers\") do\n        expect(page).to have_no_text(\"Contact Made In Past 60 Days\")\n        expect(page).to have_no_text(\"Last Attempted Contact\")\n      end\n    end\n\n    it \"can persist 'show/hide' column preference settings\", :js do\n      sign_in supervisor\n\n      visit volunteers_path\n\n      expect(page).to have_text(\"Pick displayed columns\")\n      within(\"#volunteers\") do\n        expect(page).to have_text(\"Name\")\n        expect(page).to have_text(\"Email\")\n        expect(page).to have_text(\"Status\")\n        expect(page).to have_text(\"Assigned To Transition Aged Youth\")\n        expect(page).to have_text(\"Case Number(s)\")\n        expect(page).to have_text(\"Last Attempted Contact\")\n        expect(page).to have_text(\"Contacts Made in Past 60 Day\")\n      end\n\n      click_on \"Pick displayed columns\"\n\n      uncheck \"Name\"\n      uncheck \"Status\"\n      uncheck \"Contact Made In Past 60 Days\"\n      uncheck \"Last Attempted Contact\"\n\n      within(\".modal-dialog\") do\n        click_on \"Close\"\n      end\n\n      within(\"#volunteers\") do\n        expect(page).to have_no_text(\"Name\")\n        expect(page).to have_no_text(\"Status\")\n        expect(page).to have_no_text(\"Contact Made In Past 60 Days\")\n        expect(page).to have_no_text(\"Last Attempted Contact\")\n        expect(page).to have_text(\"Email\")\n        expect(page).to have_text(\"Assigned To Transition Aged Youth\")\n        expect(page).to have_text(\"Case Number(s)\")\n      end\n\n      refresh\n      # Expectations after page reload\n      within(\"#volunteers\") do\n        expect(page).to have_no_text(\"Name\")\n        expect(page).to have_no_text(\"Status\")\n        expect(page).to have_no_text(\"Contact Made In Past 60 Days\")\n        expect(page).to have_no_text(\"Last Attempted Contact\")\n        expect(page).to have_text(\"Email\")\n        expect(page).to have_text(\"Assigned To Transition Aged Youth\")\n        expect(page).to have_text(\"Case Number(s)\")\n      end\n    end\n\n    context \"with volunteers\" do\n      it \"Search history is clean after navigating away from volunteers view\", :js do\n        sign_in supervisor\n        visit volunteers_path\n\n        page.fill_in(\"Search:\", with: \"Test\")\n\n        visit supervisors_path\n        visit volunteers_path\n\n        expect(page).to have_css(\"#volunteers_filter input\", text: \"\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/volunteers/invite_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"Inviting volunteers\", type: :system do\n  let(:organization) { create(:casa_org) }\n  let(:admin) { create(:casa_admin, casa_org: organization) }\n\n  before do\n    sign_in admin\n  end\n\n  describe \"creating and sending invitation\" do\n    it \"creates a new volunteer and sends invitation email\" do\n      visit new_volunteer_path\n\n      fill_in \"Email\", with: \"new_volunteer@example.com\"\n      fill_in \"Display name\", with: \"Jane Doe\"\n      fill_in \"Date of birth\", with: Date.new(1995, 5, 15)\n\n      click_on \"Create Volunteer\"\n\n      expect(page).to have_selector(\".notice\", text: \"New volunteer created successfully\")\n\n      volunteer = Volunteer.find_by(email: \"new_volunteer@example.com\")\n      expect(volunteer).to be_present\n      expect(volunteer.invitation_created_at).not_to be_nil\n      expect(volunteer.invitation_accepted_at).to be_nil\n\n      # Verify invitation email was sent\n      last_email = ActionMailer::Base.deliveries.last\n      expect(last_email.to).to eq [\"new_volunteer@example.com\"]\n      expect(last_email.subject).to have_text \"CASA Console invitation instructions\"\n      expect(last_email.html_part.body.encoded).to have_text \"your new Volunteer account\"\n    end\n\n    it \"sets invitation_created_at timestamp\" do\n      visit new_volunteer_path\n\n      fill_in \"Email\", with: \"volunteer_with_token@example.com\"\n      fill_in \"Display name\", with: \"John Smith\"\n      fill_in \"Date of birth\", with: Date.new(1990, 1, 1)\n\n      click_on \"Create Volunteer\"\n\n      expect(page).to have_selector(\".notice\", text: \"New volunteer created successfully\")\n\n      volunteer = Volunteer.find_by(email: \"volunteer_with_token@example.com\")\n      expect(volunteer.invitation_created_at).to be_present\n      expect(volunteer.invitation_accepted_at).to be_nil\n    end\n  end\n\n  describe \"accepting invitation\" do\n    let(:volunteer) { create(:volunteer, casa_org: organization, phone_number: nil) }\n    let!(:invitation_token) do\n      volunteer.invite!(admin)\n      volunteer.raw_invitation_token\n    end\n\n    before do\n      sign_out admin\n    end\n\n    it \"shows the invitation acceptance form\" do\n      visit accept_user_invitation_path(invitation_token: invitation_token)\n\n      expect(page).to have_text \"Set my password\"\n      expect(page).to have_field(\"Password\")\n      expect(page).to have_field(\"Password confirmation\")\n      expect(page).to have_button(\"Set my password\")\n    end\n\n    it \"allows volunteer to set password and accept invitation\" do\n      visit accept_user_invitation_path(invitation_token: invitation_token)\n\n      expect(page).to have_text \"Set my password\"\n\n      fill_in \"Password\", with: \"SecurePassword123!\"\n      fill_in \"Password confirmation\", with: \"SecurePassword123!\"\n\n      click_on \"Set my password\"\n\n      expect(page).to have_selector(\".notice\", text: \"Your password was set successfully. You are now signed in\")\n      expect(page).to have_text(\"My Cases\")\n\n      volunteer.reload\n      expect(volunteer.invitation_accepted_at).not_to be_nil\n    end\n\n    it \"shows error when passwords don't match\" do\n      visit accept_user_invitation_path(invitation_token: invitation_token)\n\n      fill_in \"Password\", with: \"SecurePassword123!\"\n      fill_in \"Password confirmation\", with: \"DifferentPassword456!\"\n\n      click_on \"Set my password\"\n\n      expect(page).to have_text \"Password confirmation doesn't match\"\n\n      volunteer.reload\n      expect(volunteer.invitation_accepted_at).to be_nil\n    end\n\n    it \"shows error when password is too short\" do\n      visit accept_user_invitation_path(invitation_token: invitation_token)\n\n      fill_in \"Password\", with: \"short\"\n      fill_in \"Password confirmation\", with: \"short\"\n\n      click_on \"Set my password\"\n\n      expect(page).to have_text \"Password is too short\"\n\n      volunteer.reload\n      expect(volunteer.invitation_accepted_at).to be_nil\n    end\n\n    it \"shows error when password is blank\" do\n      visit accept_user_invitation_path(invitation_token: invitation_token)\n\n      fill_in \"Password\", with: \"\"\n      fill_in \"Password confirmation\", with: \"\"\n\n      click_on \"Set my password\"\n\n      expect(page).to have_text \"can't be blank\"\n\n      volunteer.reload\n      expect(volunteer.invitation_accepted_at).to be_nil\n    end\n  end\n\n  describe \"resending invitation\" do\n    let(:volunteer) { create(:volunteer, casa_org: organization, phone_number: nil) }\n\n    before do\n      volunteer.invite!(admin)\n    end\n\n    it \"allows admin to resend invitation to volunteer who hasn't accepted\" do\n      visit edit_volunteer_path(volunteer)\n\n      click_on \"Resend Invitation\"\n\n      expect(page).to have_text \"Invitation sent\"\n\n      last_email = ActionMailer::Base.deliveries.last\n      expect(last_email.to).to eq [volunteer.email]\n      expect(last_email.subject).to have_text \"CASA Console invitation instructions\"\n    end\n\n    it \"hides resend button after invitation is accepted\" do\n      volunteer.update!(invitation_accepted_at: Time.current)\n\n      visit edit_volunteer_path(volunteer)\n\n      expect(page).not_to have_link(\"Resend Invitation\")\n    end\n  end\n\n  describe \"supervisor creating volunteer\" do\n    let(:supervisor) { create(:supervisor, casa_org: organization) }\n\n    before do\n      sign_out admin\n      sign_in supervisor\n    end\n\n    it \"allows supervisor to create and invite a volunteer\" do\n      visit new_volunteer_path\n\n      fill_in \"Email\", with: \"supervisor_volunteer@example.com\"\n      fill_in \"Display name\", with: \"Supervisor's Volunteer\"\n      fill_in \"Date of birth\", with: Date.new(1992, 3, 20)\n\n      click_on \"Create Volunteer\"\n\n      expect(page).to have_selector(\".notice\", text: \"New volunteer created successfully\")\n\n      volunteer = Volunteer.find_by(email: \"supervisor_volunteer@example.com\")\n      expect(volunteer).to be_present\n      expect(volunteer.invitation_created_at).not_to be_nil\n\n      # Verify invitation email was sent\n      last_email = ActionMailer::Base.deliveries.last\n      expect(last_email.to).to eq [\"supervisor_volunteer@example.com\"]\n    end\n  end\n\n  describe \"volunteer user trying to create another volunteer\" do\n    let(:volunteer) { create(:volunteer, casa_org: organization) }\n\n    before do\n      sign_out admin\n      sign_in volunteer\n    end\n\n    it \"denies access with error message\" do\n      visit new_volunteer_path\n\n      expect(page).to have_selector(\".alert\", text: \"Sorry, you are not authorized to perform this action.\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/volunteers/new_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"volunteers/new\", type: :system do\n  context \"when supervisor\" do\n    let(:supervisor) { create(:supervisor) }\n\n    it \"creates a new volunteer\", :js do\n      sign_in supervisor\n      visit new_volunteer_path\n\n      fill_in \"Email\", with: \"new_volunteer2@example.com\"\n      fill_in \"Display name\", with: \"New Volunteer Display Name 2\"\n      fill_in \"Date of birth\", with: Date.new(2000, 1, 2)\n\n      click_on \"Create Volunteer\"\n\n      visit volunteers_path\n      expect(page).to have_text(\"New Volunteer Display Name 2\")\n      expect(page).to have_text(\"new_volunteer2@example.com\")\n      expect(page).to have_text(\"Active\")\n    end\n  end\n\n  context \"volunteer user\" do\n    it \"redirects the user with an error message\" do\n      volunteer = create(:volunteer)\n      sign_in volunteer\n\n      visit new_volunteer_path\n\n      expect(page).to have_selector(\".alert\", text: \"Sorry, you are not authorized to perform this action.\")\n    end\n\n    it \"displays learning hour topic when enabled\", :js do\n      organization = build(:casa_org, learning_topic_active: true)\n      volunteer = create(:volunteer, casa_org: organization)\n\n      sign_in volunteer\n      visit new_learning_hour_path\n      expect(page).to have_text(\"Learning Topic\")\n    end\n\n    it \"does not display learning hour topic when disabled\", :js do\n      organization = build(:casa_org)\n      volunteer = create(:volunteer, casa_org: organization)\n\n      sign_in volunteer\n      visit new_learning_hour_path\n      expect(page).not_to have_text(\"Learning Topic\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/system/volunteers/notes/edit_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"volunteers/notes/edit\", type: :system do\n  let(:organization) { create(:casa_org) }\n  let(:admin) { build(:casa_admin, casa_org_id: organization.id) }\n  let(:volunteer) { create(:volunteer, :with_assigned_supervisor, casa_org_id: organization.id) }\n  let(:note) { volunteer.notes.create(creator: admin, content: \"Good job.\") }\n\n  context \"when logged in as an admin\" do\n    before do\n      sign_in admin\n      visit edit_volunteer_note_path(volunteer, note)\n    end\n\n    scenario \"editing an existing note\" do\n      expect(page).to have_text(\"Good job.\")\n\n      fill_in(\"note[content]\", with: \"Great job!\")\n\n      click_on(\"Update Note\")\n\n      expect(page).to have_current_path edit_volunteer_path(volunteer), ignore_query: true\n\n      expect(page).to have_text(\"Great job!\")\n\n      expect(note.reload.content).to eq \"Great job!\"\n    end\n  end\nend\n"
  },
  {
    "path": "spec/validators/casa_org_validator_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CasaOrgValidator, type: :validator do\n  # TODO: Add tests for CasaOrgValidator\n\n  pending \"add some tests for CasaOrgValidator\"\nend\n"
  },
  {
    "path": "spec/validators/court_report_validator_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CourtReportValidator, type: :validator do\n  # TODO: Add tests for CourtReportValidator\n\n  pending \"add some tests for CourtReportValidator\"\nend\n"
  },
  {
    "path": "spec/validators/url_validator_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe UrlValidator, type: :validator do\n  # TODO: Add tests for UrlValidator\n\n  pending \"add some tests for UrlValidator\"\nend\n"
  },
  {
    "path": "spec/validators/user_validator_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe UserValidator, type: :validator do\n  # TODO: Add tests for UserValidator\n\n  pending \"add some tests for UserValidator\"\nend\n"
  },
  {
    "path": "spec/values/all_casa_admin_parameters_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe AllCasaAdminParameters do\n  # TODO: Add tests for AllCasaAdminParameters\n\n  pending \"add some tests for AllCasaAdminParameters\"\nend\n"
  },
  {
    "path": "spec/values/banner_parameters_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe BannerParameters do\n  subject { described_class.new(params, user, timezone) }\n\n  let(:params) {\n    ActionController::Parameters.new(\n      banner: ActionController::Parameters.new(\n        active: \"1\",\n        content: \"content\",\n        name: \"name\"\n      )\n    )\n  }\n\n  let(:user) { create(:user) }\n\n  let(:timezone) { nil }\n\n  it \"returns data\" do\n    expect(subject[\"active\"]).to eq(\"1\")\n    expect(subject[\"content\"]).to eq(\"content\")\n    expect(subject[\"name\"]).to eq(\"name\")\n    expect(subject[\"expires_at\"]).to be_blank\n    expect(subject[\"user\"]).to eq(user)\n  end\n\n  context \"when expires_at is set\" do\n    let(:params) {\n      ActionController::Parameters.new(\n        banner: ActionController::Parameters.new(\n          expires_at: \"2024-06-10T12:12\"\n        )\n      )\n    }\n\n    let(:timezone) { \"America/Los_Angeles\" }\n\n    it \"attaches timezone information to expires_at\" do\n      expect(subject[\"expires_at\"]).to eq(\"2024-06-10 12:12:00 -0700\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/values/casa_admin_parameters_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CasaAdminParameters do\n  # TODO: Add tests for CasaAdminParameters\n\n  pending \"add some tests for CasaAdminParameters\"\nend\n"
  },
  {
    "path": "spec/values/case_contact_parameters_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe CaseContactParameters do\n  subject { described_class.new(params) }\n\n  let(:params) {\n    ActionController::Parameters.new(\n      case_contact: ActionController::Parameters.new(\n        duration_hours: \"1\",\n        duration_minutes: \"2\",\n        occurred_at: \"occurred_at\",\n        contact_made: \"contact_made\",\n        medium_type: \"medium_type\",\n        miles_driven: \"123\",\n        want_driving_reimbursement: \"want_driving_reimbursement\",\n        notes: \"notes\",\n        contact_type_ids: [],\n        contact_topic_answers_attributes:,\n        metadata: {\"create_another\" => \"1\", \"bad_key\" => \"bad_value\"}\n      )\n    )\n  }\n  let(:contact_topic_answers_attributes) do\n    {\"0\" => {\"id\" => 1, \"value\" => \"test\",\n             \"question\" => \"question\", \"selected\" => true}}\n  end\n\n  it \"returns data\" do\n    aggregate_failures do\n      expect(subject[\"duration_minutes\"]).to eq(62)\n      expect(subject[\"occurred_at\"]).to eq(\"occurred_at\")\n      expect(subject[\"contact_made\"]).to eq(\"contact_made\")\n      expect(subject[\"medium_type\"]).to eq(\"medium_type\")\n      expect(subject[\"miles_driven\"]).to eq(123)\n      expect(subject[\"want_driving_reimbursement\"]).to eq(\"want_driving_reimbursement\")\n      expect(subject[\"notes\"]).to eq(\"notes\")\n      expect(subject[\"contact_type_ids\"]).to eq([])\n\n      expected_attrs = contact_topic_answers_attributes[\"0\"].except(\"question\")\n      expect(subject[\"contact_topic_answers_attributes\"][\"0\"].to_h).to eq(expected_attrs)\n\n      expect(subject[\"metadata\"][\"create_another\"]).to eq(true)\n      expect(subject[\"metadata\"][\"bad_key\"]).not_to be_present\n    end\n  end\nend\n"
  },
  {
    "path": "spec/values/supervisor_parameters_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe SupervisorParameters do\n  # TODO: Add tests for SupervisorParameters\n\n  pending \"add some tests for SupervisorParameters\"\nend\n"
  },
  {
    "path": "spec/values/user_parameters_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe UserParameters do\n  # TODO: Add tests for UserParameters\n\n  pending \"add some tests for UserParameters\"\nend\n"
  },
  {
    "path": "spec/values/volunteer_parameters_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe VolunteerParameters do\n  subject { described_class.new(params) }\n\n  let(:params) {\n    ActionController::Parameters.new(\n      volunteer: ActionController::Parameters.new(\n        email: \"volunteer@example.com\",\n        display_name: \"Volunteer\",\n        phone_number: \"1(401) 827-9485\",\n        date_of_birth: \"\",\n        receive_reimbursement_email: \"0\"\n      )\n    )\n  }\n\n  it \"returns data\" do\n    expect(subject[\"email\"]).to eq(\"volunteer@example.com\")\n    expect(subject[\"display_name\"]).to eq(\"Volunteer\")\n    expect(subject[\"phone_number\"]).to eq(\"14018279485\")\n  end\nend\n"
  },
  {
    "path": "spec/views/all_casa_admins/casa_orgs/new.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"all_casa_admins/casa_orgs/new\", type: :view do\n  let(:organization) { create :casa_org }\n  let(:admin) { build_stubbed(:all_casa_admin) }\n\n  before do\n    allow(view).to receive(:selected_organization).and_return(organization)\n    sign_in admin\n\n    render template: \"all_casa_admins/casa_orgs/new\"\n  end\n\n  it \"shows new CASA Organization page title\" do\n    expect(rendered).to have_text(\"Create a new CASA Organization\")\n  end\n\n  it \"shows new CASA Organization form\" do\n    expect(rendered).to have_selector(\"input\", id: \"casa_org_name\")\n    expect(rendered).to have_selector(\"input\", id: \"casa_org_display_name\")\n    expect(rendered).to have_selector(\"input\", id: \"casa_org_address\")\n    expect(rendered).to have_selector(\"button\", id: \"submit\")\n  end\n\n  it \"requires name text field\" do\n    expect(rendered).to have_selector(\"input[required=required]\", id: \"casa_org_name\")\n  end\nend\n"
  },
  {
    "path": "spec/views/all_casa_admins/casa_orgs/show.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"all_casa_admins/casa_orgs/show\", type: :view do\n  context \"All casa admin organization dashboard\" do\n    let(:organization) { create :casa_org }\n    let(:user) { build_stubbed(:all_casa_admin) }\n    let(:metrics) {\n      {\n        \"metric name 1\" => 1,\n        \"metric name 2\" => 2\n      }\n    }\n\n    before do\n      allow(view).to receive(:current_user).and_return(user)\n      allow(view).to receive(:selected_organization).and_return(organization)\n      assign :casa_org_metrics, metrics\n      render\n    end\n\n    it \"shows new admin button\" do\n      expect(rendered).to have_text(\"New CASA Admin\")\n    end\n\n    it \"shows metrics\" do\n      expect(rendered).to have_text(\"metric name 1: 1\")\n      expect(rendered).to have_text(\"metric name 2: 2\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/views/all_casa_admins/edit.html.erb_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails_helper\"\n\nRSpec.describe \"all_casa_admins/edit\", type: :view do\n  let(:user) { build_stubbed(:all_casa_admin) }\n\n  before do\n    assign(:user, user)\n    render\n  end\n\n  it \"renders the edit profile form\", :aggregate_failures do\n    expect(rendered).to have_selector(\"form[action='#{all_casa_admins_path}'][method='post']\")\n    expect(rendered).to have_field(\"all_casa_admin[email]\")\n    expect(rendered).to have_button(\"Update Profile\")\n  end\n\n  it \"renders the change password collapse section, hidden by default\", :aggregate_failures do\n    expect(rendered).to have_selector(\"#collapseOne.collapse\")\n    expect(rendered).not_to include('class=\"collapse show\"')\n    expect(rendered).to have_field(\"all_casa_admin[password]\")\n    expect(rendered).to have_field(\"all_casa_admin[password_confirmation]\")\n    expect(rendered).to have_button(\"Update Password\")\n  end\n\n  context \"when there are error and flash messages\" do\n    before do\n      user.errors.add(:email, \"can't be blank\")\n      flash[:notice] = \"Profile updated\"\n      render\n    end\n\n    it \"renders error and flash messages partials\", :aggregate_failures do\n      expect(rendered).to have_selector(\"#error_explanation.alert\")\n      expect(rendered).to have_text(\"can't be blank\")\n      expect(rendered).to have_selector(\".header-flash\")\n      expect(rendered).to have_text(\"Profile updated\")\n    end\n  end\n\n  context \"when submitting the password change form\" do\n    before do\n      sign_in user\n      assign(:user, user)\n      render\n    end\n\n    it \"shows error when password fields are blank\", :aggregate_failures do\n      user.errors.add(:password, \"can't be blank\")\n      render\n      expect(rendered).to have_selector(\"#error_explanation.alert\")\n      expect(rendered).to have_text(\"can't be blank\")\n    end\n\n    it \"shows error when password confirmation does not match\", :aggregate_failures do\n      user.errors.add(:password_confirmation, \"doesn't match Password\")\n      render\n      expect(rendered).to have_selector(\"#error_explanation.alert\")\n      expect(rendered).to have_text(\"doesn't match Password\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/views/all_casa_admins/patch_notes/index.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"patch_notes/index\", type: :view do\n  let(:all_casa_admin) { build(:all_casa_admin) }\n  let!(:patch_notes) { create_list(:patch_note, 2) }\n\n  before do\n    assign(:patch_notes, patch_notes)\n    assign(:patch_note_groups, PatchNoteGroup.all)\n    assign(:patch_note_types, PatchNoteType.all)\n\n    sign_in all_casa_admin\n  end\n\n  describe \"the new patch note form\" do\n    it \"is present on the page\" do\n      render template: \"all_casa_admins/patch_notes/index\"\n\n      parsed_html = Nokogiri.HTML5(rendered)\n\n      expect(parsed_html.css(\"#new-patch-note\").length).to eq(1)\n    end\n\n    it \"contains a button to submit the form\" do\n      render template: \"all_casa_admins/patch_notes/index\"\n\n      parsed_html = Nokogiri.HTML5(rendered)\n\n      expect(parsed_html.css(\"#new-patch-note button\").length).to eq(1)\n    end\n\n    it \"contains a dropdown for the patch note group\" do\n      render template: \"all_casa_admins/patch_notes/index\"\n\n      parsed_html = Nokogiri.HTML5(rendered)\n\n      expect(parsed_html.css(\"#new-patch-note #new-patch-note-group\").length).to eq(1)\n    end\n\n    it \"contains a dropdown for the patch note type\" do\n      render template: \"all_casa_admins/patch_notes/index\"\n\n      parsed_html = Nokogiri.HTML5(rendered)\n\n      expect(parsed_html.css(\"#new-patch-note #new-patch-note-type\").length).to eq(1)\n    end\n\n    it \"contains a textarea to enter the patch note\" do\n      render template: \"all_casa_admins/patch_notes/index\"\n\n      parsed_html = Nokogiri.HTML5(rendered)\n\n      expect(parsed_html.css(\"#new-patch-note textarea\").length).to eq(1)\n    end\n\n    describe \"the patch note group dropdown\" do\n      let!(:patch_note_group_1) { create(:patch_note_group, value: \"8Sm02WT!zZnJ\") }\n\n      it \"contains all the patch note group values as options\" do\n        render template: \"all_casa_admins/patch_notes/index\"\n\n        parsed_html = Nokogiri.HTML5(rendered)\n\n        option_text = parsed_html.css(\"#new-patch-note #new-patch-note-group option\").text\n\n        expect(option_text).to include(patch_note_group_1.value)\n      end\n    end\n\n    describe \"the patch note type dropdown\" do\n      let!(:patch_note_type_1) { create(:patch_note_type, name: \"3dI!9a9@s$KX\") }\n\n      it \"contains all the patch note type values as options\" do\n        render template: \"all_casa_admins/patch_notes/index\"\n\n        parsed_html = Nokogiri.HTML5(rendered)\n\n        option_text = parsed_html.css(\"#new-patch-note #new-patch-note-type option\").text\n\n        expect(option_text).to include(patch_note_type_1.name)\n      end\n    end\n  end\n\n  describe \"the patch note list\" do\n    it \"displays the patch_notes\" do\n      patch_notes[0].update(note: \"?UvV*Z~v\\\"`P]4ol\")\n      patch_notes[1].update(note: \"#tjJ/+o\\\"3s@osjV\")\n\n      render template: \"all_casa_admins/patch_notes/index\"\n      parsed_html = Nokogiri.HTML5(rendered)\n\n      expect(parsed_html.css(\".patch-note-list-item textarea\").text).to include(patch_notes[0].note)\n      expect(parsed_html.css(\".patch-note-list-item textarea\").text).to include(patch_notes[1].note)\n    end\n\n    it \"displays the latest patch notes first\" do\n      patch_notes[0].update(note: \"#'hQ+`dGC(qc=}wu\")\n      patch_notes[1].update(note: \"k2cz&c'xYLr|&)B)\")\n\n      render template: \"all_casa_admins/patch_notes/index\"\n      parsed_html = Nokogiri.HTML5(rendered)\n\n      expect(parsed_html.css(\".patch-note-list-item textarea\")[1].text).to include(patch_notes[0].note)\n      expect(parsed_html.css(\".patch-note-list-item textarea\")[2].text).to include(patch_notes[1].note)\n      expect(patch_notes[0].created_at < patch_notes[1].created_at).to eq(true)\n    end\n\n    it \"displays the correct patch note group and patch note type with the patch note\" do\n      patch_notes[0].update(note: \"#'hQ+`dGC(qc=}wu\")\n      patch_notes[1].update(note: \"k2cz&c'xYLr|&)B)\")\n\n      render template: \"all_casa_admins/patch_notes/index\"\n      parsed_html = Nokogiri.HTML5(rendered)\n\n      patch_note_element = parsed_html.css(\".patch-note-list-item\")[1]\n\n      expect(patch_note_element.css(\"textarea\").text).to include(patch_notes[0].note)\n      expect(patch_note_element\n        .css(\"#patch-note-#{patch_notes[0].id}-group option[@selected=\\\"selected\\\"]\")\n        .attr(\"value\").value).to eq(patch_notes[0].patch_note_group_id.to_s)\n      expect(patch_note_element\n        .css(\"#patch-note-#{patch_notes[0].id}-type option[@selected=\\\"selected\\\"]\")\n        .attr(\"value\").value).to eq(patch_notes[0].patch_note_type_id.to_s)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/views/banners/new.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"banners/new\", type: :view do\n  context \"when new banner is marked as inactive\" do\n    it \"does not warn that current active banner will be deactivated\" do\n      user = build_stubbed(:casa_admin)\n      current_organization = user.casa_org\n      current_organization_banner = build(:banner, active: true, casa_org: current_organization)\n\n      allow(view).to receive(:current_user).and_return(user)\n      allow(view).to receive(:current_organization).and_return(current_organization)\n      without_partial_double_verification do\n        allow(view).to receive(:browser_time_zone).and_return(\"America/New_York\")\n      end\n      allow(current_organization).to receive(:has_alternate_active_banner?).and_return(true)\n\n      assign :banners, [current_organization_banner]\n      assign :banner, Banner.new(active: false)\n\n      render template: \"banners/new\"\n\n      expect(rendered).not_to have_checked_field(\"banner_active\")\n      expect(rendered).to have_css(\"span.d-none\", text: \"Warning: This will replace your current active banner\")\n    end\n  end\n\n  context \"when organization has an active banner\" do\n    context \"when new banner is marked as active\" do\n      it \"warns that current active banner will be deactivated\" do\n        user = build_stubbed(:casa_admin)\n        current_organization = user.casa_org\n        current_organization_banner = build(:banner, active: true, casa_org: current_organization)\n\n        allow(view).to receive(:current_user).and_return(user)\n        allow(view).to receive(:current_organization).and_return(current_organization)\n        without_partial_double_verification do\n          allow(view).to receive(:browser_time_zone).and_return(\"America/New_York\")\n        end\n        allow(current_organization).to receive(:has_alternate_active_banner?).and_return(true)\n\n        assign :banners, [current_organization_banner]\n        assign :banner, Banner.new(active: true)\n\n        render template: \"banners/new\"\n\n        expect(rendered).to have_checked_field(\"banner_active\")\n        expect(rendered).not_to have_css(\"span.d-none\", text: \"Warning: This will replace your current active banner\")\n      end\n    end\n  end\n\n  context \"when organization has no active banner\" do\n    context \"when new banner is marked as active\" do\n      it \"does not warn that current active banner will be deactivated\" do\n        user = build_stubbed(:casa_admin)\n        current_organization = user.casa_org\n\n        allow(view).to receive(:current_user).and_return(user)\n        allow(view).to receive(:current_organization).and_return(current_organization)\n        without_partial_double_verification do\n          allow(view).to receive(:browser_time_zone).and_return(\"America/New_York\")\n        end\n        allow(current_organization).to receive(:has_alternate_active_banner?).and_return(false)\n\n        assign :banners, []\n        assign :banner, Banner.new(active: true)\n\n        render template: \"banners/new\"\n\n        expect(rendered).to have_checked_field(\"banner_active\")\n        expect(rendered).to have_css(\"span.d-none\", text: \"Warning: This will replace your current active banner\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/views/bulk_court_date/new.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"bulk_court_date/new.html.erb\", type: :view do\n  pending \"add some examples to (or delete) #{__FILE__}\"\nend\n"
  },
  {
    "path": "spec/views/casa_admins/admins_table.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"admins_table\", type: :view do\n  it \"allows editing admin users\" do\n    admin = build_stubbed :casa_admin\n    enable_pundit(view, admin)\n    allow(view).to receive(:current_user).and_return(admin)\n\n    assign :admins, [admin.decorate]\n\n    sign_in admin\n\n    render template: \"casa_admins/index\"\n\n    expect(rendered).to have_link(\"Edit\", href: \"/casa_admins/#{admin.id}/edit\")\n  end\nend\n"
  },
  {
    "path": "spec/views/casa_admins/edit.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"casa_admins/edit\", type: :view do\n  let(:admin) { build_stubbed :casa_admin }\n\n  it \"shows invite and login info\" do\n    enable_pundit(view, admin)\n    allow(view).to receive(:current_user).and_return(admin)\n    allow(view).to receive(:current_organization).and_return(admin.casa_org)\n    assign :casa_admin, admin\n\n    render template: \"casa_admins/edit\"\n\n    expect(rendered).to have_text(\"Added to system \")\n    expect(rendered).to have_text(\"Invitation email sent \\n  never\")\n    expect(rendered).to have_text(\"Last logged in\")\n    expect(rendered).to have_text(\"Invitation accepted \\n  never\")\n    expect(rendered).to have_text(\"Password reset last sent \\n  never\")\n  end\n\n  describe \"'Change to Supervisor' button\" do\n    let(:supervisor) { build_stubbed :supervisor }\n\n    before do\n      assign :casa_admin, admin\n      assign :available_volunteers, []\n    end\n\n    it \"shows for an admin editing an admin\" do\n      enable_pundit(view, admin)\n      allow(view).to receive(:current_user).and_return(admin)\n      allow(view).to receive(:current_organization).and_return(admin.casa_org)\n      render template: \"casa_admins/edit\"\n\n      expect(rendered).to have_text(\"Change to Supervisor\")\n      expect(rendered).to include(change_to_supervisor_casa_admin_path(admin))\n    end\n\n    it \"does not show for a supervisor editing an admin\" do\n      enable_pundit(view, admin)\n      allow(view).to receive(:current_user).and_return(supervisor)\n      allow(view).to receive(:current_organization).and_return(supervisor.casa_org)\n      render template: \"casa_admins/edit\"\n\n      expect(rendered).not_to have_text(\"Change to Supervisor\")\n      expect(rendered).not_to include(change_to_supervisor_casa_admin_path(admin))\n    end\n  end\nend\n"
  },
  {
    "path": "spec/views/casa_cases/edit.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"casa_cases/edit\", type: :view do\n  let(:organization) { create(:casa_org) }\n  let(:contact_type_group) { create(:contact_type_group, casa_org: organization) }\n  let(:contact_type) { create(:contact_type, contact_type_group: contact_type_group) }\n\n  before do\n    enable_pundit(view, user)\n    assign :contact_types, []\n    allow(view).to receive(:current_user).and_return(user)\n    allow(view).to receive(:current_organization).and_return(user.casa_org)\n  end\n\n  context \"when accessed by a volunteer\" do\n    let(:user) { build_stubbed(:volunteer, casa_org: organization) }\n    let(:casa_case) { create(:casa_case, casa_org: organization) }\n\n    it \"does not allow editing the case number\" do\n      assign :casa_case, casa_case\n\n      render template: \"casa_cases/edit\"\n\n      expect(rendered).to have_link(casa_case.case_number, href: \"/casa_cases/#{casa_case.case_number.parameterize}\")\n      expect(rendered).not_to have_selector(\"input[value='#{casa_case.case_number}']\")\n    end\n\n    it \"does not include volunteer assignment\" do\n      assign :casa_case, casa_case\n\n      render template: \"casa_cases/edit\"\n\n      parsed_html = Nokogiri.HTML5(rendered)\n\n      expect(parsed_html.css(\"#volunteer-assignment\").length).to eq(0)\n    end\n  end\n\n  context \"when accessed by an admin\" do\n    let(:user) { build_stubbed(:casa_admin, casa_org: organization) }\n    let(:casa_case) { create(:casa_case, casa_org: organization) }\n\n    it \"includes an editable case number\" do\n      assign :casa_case, casa_case\n      assign :contact_types, organization.contact_types\n\n      render template: \"casa_cases/edit\"\n\n      expect(rendered).to have_link(casa_case.case_number, href: \"/casa_cases/#{casa_case.case_number.parameterize}\")\n      expect(rendered).to have_selector(\"input[value='#{casa_case.case_number}']\")\n    end\n\n    it \"includes volunteer assignment\" do\n      assign :casa_case, casa_case\n      assign :contact_types, organization.contact_types\n\n      render template: \"casa_cases/edit\"\n\n      parsed_html = Nokogiri.HTML5(rendered)\n\n      expect(parsed_html.css(\"#volunteer-assignment\").length).to eq(1)\n    end\n  end\n\n  context \"when assigning a new volunteer\" do\n    let(:user) { build_stubbed(:casa_admin, casa_org: organization) }\n\n    it \"does not have an option to select a volunteer that is already assigned to the casa case\" do\n      casa_case = create(:casa_case, casa_org: organization)\n      assign :casa_case, casa_case\n      assign :contact_types, organization.contact_types\n      assigned_volunteer = build_stubbed(:volunteer)\n      build_stubbed(:case_assignment, volunteer: assigned_volunteer, casa_case: casa_case)\n      unassigned_volunteer = create(:volunteer)\n\n      render template: \"casa_cases/edit\"\n\n      expect(rendered).to have_select(\"case_assignment_casa_case_id\", options: [\"Please Select Volunteer\", unassigned_volunteer.display_name])\n    end\n  end\nend\n"
  },
  {
    "path": "spec/views/casa_cases/index.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"casa_cases/index\", type: :view do\n  context \"when accessed by a volunteer\" do\n    it \"can not see the Assigned To column\" do\n      user = create(:volunteer, display_name: \"Bob Loblaw\")\n      enable_pundit(view, user)\n      allow(view).to receive(:current_user).and_return(user)\n\n      casa_case = build(:casa_case, active: true, casa_org: user.casa_org, case_number: \"CINA-1\")\n      create(:case_assignment, volunteer: user, casa_case: casa_case)\n      assign :casa_cases, [casa_case]\n      assign :duties, OtherDuty.none\n\n      render template: \"casa_cases/index\"\n\n      expect(rendered).not_to have_text \"Assigned To\"\n      expect(rendered).not_to have_text(\"Bob Loblaw\")\n    end\n  end\n\n  context \"when accessed by an admin\" do\n    it \"can see the New Case button\" do\n      organization = create(:casa_org)\n      user = build_stubbed(:casa_admin, casa_org: organization)\n      enable_pundit(view, user)\n      allow(view).to receive(:current_user).and_return(user)\n\n      assign :casa_cases, []\n      assign :duties, OtherDuty.none\n\n      render template: \"casa_cases/index\"\n\n      expect(rendered).to have_link \"New Case\"\n    end\n  end\n\n  context \"when accessed by a supervisor\" do\n    it \"does not see the New Case button\" do\n      organization = create(:casa_org)\n      user = build_stubbed(:supervisor, casa_org: organization)\n      enable_pundit(view, user)\n      allow(view).to receive(:current_user).and_return(user)\n\n      assign :casa_cases, []\n      assign :duties, OtherDuty.none\n\n      render template: \"casa_cases/index\"\n\n      expect(rendered).not_to have_link \"New Case\"\n    end\n  end\nend\n"
  },
  {
    "path": "spec/views/casa_cases/new.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"casa_cases/new\", type: :view do\n  let(:casa_org) { create(:casa_org) }\n  let(:user) { create(:casa_admin, casa_org: casa_org) }\n  let(:contact_type_group) { create(:contact_type_group, casa_org: casa_org) }\n  let(:contact_type) { create(:contact_type, contact_type_group: contact_type_group) }\n\n  context \"while signed in as admin\" do\n    it \"has youth birth month and year\" do\n      enable_pundit(view, user)\n      allow(view).to receive(:current_user).and_return(user)\n      allow(view).to receive(:current_organization).and_return(casa_org)\n\n      assign :casa_case, build(:casa_case, casa_org: casa_org)\n      assign :contact_types, casa_org.contact_types\n\n      render template: \"casa_cases/new\"\n\n      expect(rendered).to include(\"Youth's Birth Month & Year\")\n    end\n  end\n\n  context \"when trying to assign a volunteer to a case\" do\n    it \"is able to assign volunteers\" do\n      enable_pundit(view, user)\n      allow(view).to receive(:current_user).and_return(user)\n      allow(view).to receive(:current_organization).and_return(user.casa_org)\n\n      assign :casa_case, build(:casa_case, casa_org: user.casa_org)\n      assign :contact_types, casa_org.contact_types\n\n      render template: \"casa_cases/new\"\n\n      expect(rendered).to have_content(\"Assign a Volunteer\")\n      expect(rendered).to have_css(\"#casa_case_case_assignments_attributes_0_volunteer_id\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/views/casa_cases/show.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"casa_cases/show\", type: :view do\n  let(:user) { create(:casa_admin) }\n\n  before do\n    enable_pundit(view, user)\n    allow(view).to receive(:current_user).and_return(user)\n    allow(view).to receive(:current_organization).and_return(user.casa_org)\n  end\n\n  context \"when there is court date\" do\n    it \"renders casa case with court dates\" do\n      casa_case = create(:casa_case, case_number: \"111\")\n      create(:court_date, casa_case: casa_case, date: Date.new(2023, 5, 6))\n\n      assign(:casa_case, casa_case)\n      render\n\n      expect(rendered).to match(\"111\")\n      expect(rendered).to match(\"May 6, 2023\")\n    end\n\n    it \"render button to add court date\" do\n      casa_case = create(:casa_case)\n      assign(:casa_case, casa_case)\n\n      render\n\n      expect(rendered).to have_content(\"Add a court date\")\n    end\n  end\n\n  context \"where there is no court date\" do\n    it \"render casa case without court dates\" do\n      casa_case = create(:casa_case)\n      assign(:casa_case, casa_case)\n\n      render\n\n      expect(rendered).to match(casa_case.case_number)\n      expect(rendered).to have_content(\"No Court Dates\")\n    end\n\n    it \"render button to add court date\" do\n      casa_case = create(:casa_case)\n      assign(:casa_case, casa_case)\n\n      render\n\n      expect(rendered).to have_content(\"Add a court date\")\n    end\n  end\n\n  context \"when there is a placement\" do\n    it \"renders casa case with placements\" do\n      casa_case = create(:casa_case, case_number: \"111\")\n      create(:placement, casa_case: casa_case, placement_started_at: Date.new(2023, 5, 6))\n\n      assign(:casa_case, casa_case)\n      render\n\n      expect(rendered).to match(\"111\")\n      expect(rendered).to match(\"Current Placement:\")\n      expect(rendered).to match(/Placement Type/)\n      expect(rendered).to match(\"Placed since: May 6, 2023\")\n      expect(rendered).to match(\"See All Placements\")\n    end\n  end\n\n  context \"when there is no placement\" do\n    it \"renders casa case without placements\" do\n      casa_org = create(:casa_org, :with_placement_types)\n      casa_case = create(:casa_case, casa_org:)\n      assign(:casa_case, casa_case)\n\n      render\n\n      expect(rendered).to match(casa_case.case_number)\n      expect(rendered).to have_content(\"Current Placement:\")\n      expect(rendered).to have_content(\"Unknown\")\n      expect(rendered).to have_content(\"See All Placements\")\n    end\n\n    it \"renders nothing about placements when org has no placement types\" do\n      casa_case = create(:casa_case)\n      assign(:casa_case, casa_case)\n\n      render\n\n      expect(rendered).to match(casa_case.case_number)\n      expect(rendered).not_to have_content(\"Current Placement:\")\n      expect(rendered).not_to have_content(\"Unknown\")\n      expect(rendered).not_to have_content(\"See All Placements\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/views/casa_orgs/edit.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"casa_org/edit\", type: :view do\n  let(:organization) { build_stubbed(:casa_org) }\n\n  before do\n    assign(:contact_type_groups, [])\n    assign(:contact_types, [])\n    assign(:hearing_types, [])\n    assign(:judges, [])\n    assign(:learning_hour_types, [])\n    assign(:learning_hour_topics, [])\n    assign(:sent_emails, [])\n    assign(:contact_topics, [])\n    assign(:custom_org_links, [])\n    assign(:placement_types, [])\n\n    allow(view).to receive(:current_organization).and_return(organization)\n\n    sign_in build_stubbed(:casa_admin)\n  end\n\n  it \"has casa org edit page text\" do\n    render template: \"casa_org/edit\"\n\n    expect(rendered).to have_text \"Editing CASA Organization\"\n    expect(rendered).not_to have_text \"sign in before continuing\"\n    expect(rendered).to have_selector(\"input[required=required]\", id: \"casa_org_name\")\n  end\n\n  it \"has contact topic content\" do\n    contact_topic = build_stubbed(:contact_topic, question: \"Test Question\", details: \"Test details\")\n    assign(:contact_topics, [contact_topic])\n\n    render template: \"casa_org/edit\"\n\n    expect(rendered).to have_text(\"Test Question\")\n    expect(rendered).to have_text(\"Test details\")\n    expect(rendered).to have_table(\"contact-topics\",\n      with_rows:\n      [\n        [\"Test Question\", \"Test details\", \"Edit\"]\n      ])\n  end\n\n  it \"has contact types content\" do\n    contact_type = build_stubbed(:contact_type, name: \"Contact type 1\")\n    assign(:contact_types, [contact_type])\n\n    render template: \"casa_org/edit\"\n\n    expect(rendered).to have_text(\"Contact type 1\")\n    expect(rendered).to have_text(contact_type.name)\n    expect(rendered).to have_table(\"contact-types\",\n      with_rows:\n      [\n        [\"Contact type 1\", \"Yes\", \"Edit\"]\n      ])\n  end\n\n  it \"has contact type groups content\" do\n    contact_type_group = build_stubbed(:contact_type_group, casa_org: organization, name: \"Contact type group 1\")\n    assign(:contact_type_groups, [contact_type_group])\n\n    render template: \"casa_org/edit\"\n\n    expect(rendered).to have_text(\"Contact type group 1\")\n    expect(rendered).to have_text(contact_type_group.name)\n    expect(rendered).to have_table(\"contact-type-groups\",\n      with_rows: [\n        [\"Contact type group 1\", \"Yes\", \"Edit\"]\n      ])\n  end\n\n  it \"has hearing types content\" do\n    hearing_type = build_stubbed(:hearing_type, casa_org: organization, name: \"Hearing type 1\")\n    assign(:hearing_types, [hearing_type])\n\n    render template: \"casa_org/edit\"\n\n    expect(rendered).to have_text(\"Hearing type 1\")\n    expect(rendered).to have_table(\"hearing-types\",\n      with_rows:\n      [\n        [\"Hearing type 1\", \"Yes\", \"Edit\"]\n      ])\n  end\n\n  it \"has judge content\" do\n    judge = build_stubbed(:judge, casa_org: organization, name: \"Joey Tom\")\n    assign(:judges, [judge])\n\n    render template: \"casa_org/edit\"\n\n    expect(rendered).to have_text(judge.name)\n  end\n\n  it \"has placement types content\" do\n    placement_type = build_stubbed(:placement_type, name: \"Placement type 1\")\n    placement_type_2 = build_stubbed(:placement_type, name: \"Placement type 2\")\n    assign(:placement_types, [placement_type, placement_type_2])\n\n    render template: \"casa_org/edit\"\n\n    expect(rendered).to have_text(\"Manage Case Placement Types\")\n    expect(rendered).to have_table(\"placement-types-table\",\n      with_rows:\n      [\n        [\"Placement type 1\", \"Edit\"],\n        [\"Placement type 2\", \"Edit\"]\n      ])\n  end\n\n  it \"does not show download prompt with no custom template\" do\n    render template: \"casa_org/edit\"\n\n    expect(rendered).not_to have_text(\"Download Current Template\")\n  end\n\n  it \"has sent emails content\" do\n    admin = build_stubbed(:casa_admin, casa_org: organization)\n    without_partial_double_verification do\n      allow(view).to receive(:to_user_timezone).and_return(Time.zone.local(2021, 1, 2, 12, 30, 0))\n    end\n\n    sent_email = build_stubbed(:sent_email, user: admin, created_at: Time.zone.local(2021, 1, 2, 12, 30, 0))\n    assign(:sent_emails, [sent_email])\n\n    render template: \"casa_org/edit\"\n\n    expect(rendered).to have_text(sent_email.sent_address)\n    expect(rendered).to have_table(\"sent-emails\",\n      with_rows: [\n        [\"Mailer Type\", \"Mail Action Category\", admin.email, \"12:30pm 02 Jan 2021\"]\n      ])\n  end\n\n  context \"with a template uploaded\" do\n    # NOTE(@abachman): Use create instead of build_stubbed because ActiveStorage\n    # needs to save to the DB\n    let(:organization) { create(:casa_org) }\n\n    it \"renders a prompt to download current template\" do\n      organization.court_report_template.attach(io: File.open(\"#{Rails.root.join(\"app/documents/templates/default_report_template.docx\")}\"), filename: 'default_report_template\n.docx', content_type: \"application/docx\")\n\n      render template: \"casa_org/edit\"\n\n      expect(rendered).to have_text(\"Download Current Template\")\n    end\n  end\n\n  describe \"additional expense feature flag\" do\n    context \"enabled\" do\n      it \"has option to enable additional expenses\" do\n        allow(Flipper).to receive(:enabled?).with(:show_additional_expenses).and_return(true)\n\n        render template: \"casa_org/edit\"\n\n        expect(rendered).to have_text(\"Volunteers can add Other Expenses\")\n      end\n    end\n\n    context \"disabled\" do\n      it \"has option to enable additional expenses\" do\n        allow(Flipper).to receive(:enabled?).with(:show_additional_expenses).and_return(false)\n\n        render template: \"casa_org/edit\"\n\n        expect(rendered).not_to have_text(\"Volunteers can add Other Expenses\")\n      end\n    end\n  end\n\n  describe \"custom org links\" do\n    let(:casa_org) { build_stubbed :casa_org }\n    before { allow(view).to receive(:current_organization).and_return(casa_org) }\n\n    it \"has custom org link content\" do\n      render template: \"casa_org/edit\"\n      expect(rendered).to have_text(\"Custom Links\")\n    end\n\n    context \"when the org has no custom links\" do\n      before { assign(:custom_org_links, []) }\n\n      it \"includes a helpful message\" do\n        render template: \"casa_org/edit\"\n        expect(rendered).to have_text(\"No custom links have been added for this organization.\")\n      end\n    end\n\n    context \"when the org has custom links\" do\n      let(:link_text) { \"Example Link\" }\n      let(:link_url) { \"https://www.example.com\" }\n      let(:custom_org_link) { build_stubbed :custom_org_link, text: link_text, url: link_url }\n      before { assign(:custom_org_links, [custom_org_link]) }\n\n      it \"has custom link details\" do\n        render template: \"casa_org/edit\"\n        expect(rendered).to have_text link_text\n        expect(rendered).to have_text link_url\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/views/case_contacts/case_contact.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"case_contacts/case_contact\", type: :view do\n  let(:casa_org) { create(:casa_org) }\n  let(:admin) { build_stubbed(:casa_admin, casa_org:) }\n  let(:volunteer) { create(:volunteer, casa_org:) }\n  let(:supervisor) { build_stubbed(:supervisor, casa_org:) }\n\n  describe \"case contact notes\" do\n    before do\n      enable_pundit(view, admin)\n      allow(view).to receive(:current_user).and_return(admin)\n    end\n\n    context \"when case contact has contact topic responses\" do\n      let(:case_contact) do\n        build_stubbed(:case_contact, contact_topic_answers: [contact_topic_answer1, contact_topic_answer2], creator: volunteer)\n      end\n\n      let(:contact_topic1) { build_stubbed(:contact_topic, question: \"Some question\") }\n      let(:contact_topic2) { build_stubbed(:contact_topic, question: \"Hidden question\") }\n\n      let(:contact_topic_answer1) do\n        build_stubbed(:contact_topic_answer, contact_topic: contact_topic1, value: \"Some answer\")\n      end\n\n      let(:contact_topic_answer2) do\n        build_stubbed(:contact_topic_answer, contact_topic: contact_topic2, value: \"\")\n      end\n\n      it \"shows the contact topic responses\" do\n        assign :case_contact, case_contact\n        assign :casa_cases, [case_contact.casa_case]\n\n        render(partial: \"case_contacts/case_contact\", locals: {contact: case_contact})\n\n        expect(rendered).to have_text(\"Some question:\")\n        expect(rendered).to have_text(\"Some answer\")\n        expect(rendered).not_to have_text(\"Hidden question\")\n      end\n    end\n\n    context \"when case contact has no notes\" do\n      let(:case_contact) { build_stubbed(:case_contact, notes: nil) }\n\n      it \"does not show the notes\" do\n        assign :case_contact, case_contact\n        assign :casa_cases, [case_contact.casa_case]\n\n        render(partial: \"case_contacts/case_contact\", locals: {contact: case_contact})\n\n        expect(rendered).not_to have_text(\"Additional Notes:\")\n      end\n    end\n\n    context \"when case contact has notes\" do\n      let(:case_contact) { build_stubbed(:case_contact, notes: \"This is a note\") }\n\n      it \"shows the notes\" do\n        assign :case_contact, case_contact\n        assign :casa_cases, [case_contact.casa_case]\n\n        render(partial: \"case_contacts/case_contact\", locals: {contact: case_contact})\n\n        expect(rendered).to have_text(\"Additional Notes:\")\n        expect(rendered).to have_text(\"This is a note\")\n      end\n    end\n  end\n\n  describe \"edit and make reminder buttons\" do\n    before do\n      enable_pundit(view, admin)\n      allow(view).to receive(:current_user).and_return(admin)\n    end\n\n    context \"occurred_at is before the last day of the month in the quarter that the case contact was created\" do\n      let(:case_contact) { build_stubbed(:case_contact, creator: volunteer) }\n      let(:case_contact2) { build_stubbed(:case_contact, deleted_at: Time.current, creator: volunteer) }\n\n      it \"shows edit button\" do\n        assign :case_contact, case_contact\n        assign :casa_cases, [case_contact.casa_case]\n\n        render(partial: \"case_contacts/case_contact\", locals: {contact: case_contact})\n        expect(rendered).to have_link(nil, href: \"/case_contacts/#{case_contact.id}/edit\")\n      end\n\n      it \"shows make reminder button\" do\n        assign :case_contact, case_contact\n        assign :casa_cases, [case_contact.casa_case]\n\n        render(partial: \"case_contacts/case_contact\", locals: {contact: case_contact})\n        expect(rendered).to have_button(\"Make Reminder\")\n      end\n    end\n  end\n\n  describe \"delete and undelete buttons\" do\n    let(:case_contact) { build_stubbed(:case_contact, creator: volunteer) }\n    let(:case_contact2) { build_stubbed(:case_contact, deleted_at: Time.current, creator: volunteer) }\n\n    context \"when logged in as admin\" do\n      before do\n        enable_pundit(view, admin)\n        allow(view).to receive(:current_user).and_return(admin)\n      end\n\n      it \"shows delete button\" do\n        assign :case_contact, case_contact\n        assign :casa_cases, [case_contact.casa_case]\n\n        render(partial: \"case_contacts/case_contact\", locals: {contact: case_contact})\n        expect(rendered).to have_link(\"Delete\", href: \"/case_contacts/#{case_contact.id}\")\n      end\n\n      it \"shows undelete button\" do\n        assign :case_contact, case_contact2\n        assign :casa_cases, [case_contact2.casa_case]\n\n        render(partial: \"case_contacts/case_contact\", locals: {contact: case_contact2})\n        expect(rendered).to have_link(\"undelete\", href: \"/case_contacts/#{case_contact2.id}/restore\")\n      end\n    end\n\n    context \"when logged in as volunteer\" do\n      before do\n        enable_pundit(view, volunteer)\n        allow(view).to receive(:current_user).and_return(volunteer)\n      end\n\n      it \"does not show delete button\" do\n        assign :case_contact, case_contact\n        assign :casa_cases, [case_contact.casa_case]\n\n        render(partial: \"case_contacts/case_contact\", locals: {contact: case_contact})\n        expect(rendered).not_to have_link(\"Delete\", href: \"/case_contacts/#{case_contact.id}\")\n      end\n\n      it \"does not show undelete button\" do\n        assign :case_contact, case_contact2\n        assign :casa_cases, [case_contact2.casa_case]\n\n        render(partial: \"case_contacts/case_contact\", locals: {contact: case_contact2})\n        expect(rendered).not_to have_link(\"undelete\", href: \"/case_contacts/#{case_contact2.id}/restore\")\n      end\n    end\n\n    context \"when logged in as supervisor\" do\n      before do\n        enable_pundit(view, supervisor)\n        allow(view).to receive(:current_user).and_return(supervisor)\n      end\n\n      it \"shows delete button\" do\n        assign :case_contact, case_contact\n        assign :casa_cases, [case_contact.casa_case]\n\n        render(partial: \"case_contacts/case_contact\", locals: {contact: case_contact})\n        expect(rendered).to have_link(\"Delete\", href: \"/case_contacts/#{case_contact.id}\")\n      end\n\n      it \"does not show undelete button\" do\n        assign :case_contact, case_contact2\n        assign :casa_cases, [case_contact2.casa_case]\n\n        render(partial: \"case_contacts/case_contact\", locals: {contact: case_contact2})\n        expect(rendered).not_to have_link(\"undelete\", href: \"/case_contacts/#{case_contact2.id}/restore\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/views/case_contacts/index.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"case_contacts/index\", type: :view do\n  let(:user) { build_stubbed(:volunteer) }\n  let(:case_contacts) { CaseContact.all }\n  let(:pagy) { Pagy.new(count: 0) }\n\n  let(:filterrific_param_set) do\n    param_set = Filterrific::ParamSet.new(case_contacts, {})\n    param_set.select_options = {sorted_by: CaseContact.options_for_sorted_by}\n\n    param_set\n  end\n\n  let(:groups) do\n    user.casa_org.contact_type_groups\n      .joins(:contact_types)\n      .where(contact_types: {active: true})\n      .uniq\n  end\n\n  before do\n    enable_pundit(view, user)\n\n    # Allow filterrific to fetch the correct controller name\n    allow_any_instance_of(ActionView::TestCase::TestController).to receive(:controller_name).and_return(\"case_contacts\")\n\n    allow(RequestStore).to receive(:read).with(:current_user).and_return(user)\n    allow(RequestStore).to receive(:read).with(:current_organization).and_return(user.casa_org)\n\n    assign(:current_organization_groups, groups)\n    assign(:filterrific, filterrific_param_set)\n    assign(:presenter, CaseContactPresenter.new(case_contacts))\n    assign(:pagy, pagy)\n\n    render template: \"case_contacts/index\"\n  end\n\n  it \"Displays the Case Contacts title\" do\n    expect(rendered).to have_text(\"Case Contacts\")\n  end\n\n  it \"Has a New Case Contact button\" do\n    expect(rendered).to have_link(\"New Case Contact\", href: new_case_contact_path)\n  end\nend\n"
  },
  {
    "path": "spec/views/case_court_reports/index.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"case_court_reports/index\", type: :view do\n  context \"Volunteer views 'Generate Court Report' form\" do\n    let(:user) { create(:volunteer, :with_casa_cases) }\n    let(:active_assigned_cases) { CasaCase.actively_assigned_to(user) }\n\n    before do\n      allow(view).to receive(:current_user).and_return(user)\n      assign :assigned_cases, active_assigned_cases\n      render\n    end\n\n    it \"renders the index page\" do\n      expect(controller.request.fullpath).to eq case_court_reports_path\n    end\n\n    it \"has a card with card title 'Generate Court Report'\", :aggregate_failures do\n      expect(rendered).to have_selector(\"h6\", text: \"Court Reports\", count: 1)\n      expect(rendered).to have_selector(\"div\", class: \"card-style\", count: 1)\n    end\n\n    it \"page has title 'Gererate Reports'\" do\n      expect(rendered).to have_selector(\"h1\", text: \"Generate Reports\", count: 1)\n    end\n\n    it \"has button with 'Download Court Report as .docx' text\" do\n      expect(rendered).to have_selector(\"button\", text: /Download Court Report as \\.docx/i, count: 1)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/views/checklist_items/edit.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"checklist_items/edit\", type: :view do\n  let(:admin) { build_stubbed(:casa_admin) }\n\n  before do\n    assign :hearing_type, HearingType.new(id: 1)\n    assign :checklist_item, ChecklistItem.new\n    sign_in admin\n\n    render template: \"checklist_items/edit\"\n  end\n\n  it \"shows edit checklist item page title\" do\n    expect(rendered).to have_text(\"Edit this checklist item\")\n  end\n\n  it \"shows edit checklist item form\" do\n    expect(rendered).to have_selector(\"input\", id: \"checklist_item_category\")\n    expect(rendered).to have_selector(\"input\", id: \"checklist_item_description\")\n    expect(rendered).to have_selector(\"input\", id: \"checklist_item_mandatory\")\n    expect(rendered).to have_selector(:link_or_button, \"Submit\")\n  end\n\n  it \"requires category text field\" do\n    expect(rendered).to have_selector(\"input[required=required]\", id: \"checklist_item_category\")\n  end\n\n  it \"requires description text field\" do\n    expect(rendered).to have_selector(\"input[required=required]\", id: \"checklist_item_description\")\n  end\nend\n"
  },
  {
    "path": "spec/views/checklist_items/new.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"checklist_items/new\", type: :view do\n  let(:admin) { build_stubbed(:casa_admin) }\n\n  before do\n    assign :hearing_type, HearingType.new(id: 1)\n    assign :checklist_item, ChecklistItem.new\n    sign_in admin\n\n    render template: \"checklist_items/new\"\n  end\n\n  it \"shows new checklist item page title\" do\n    expect(rendered).to have_text(\"Add a new checklist item\")\n  end\n\n  it \"shows new checklist item form\" do\n    expect(rendered).to have_selector(\"input\", id: \"checklist_item_category\")\n    expect(rendered).to have_selector(\"input\", id: \"checklist_item_description\")\n    expect(rendered).to have_selector(\"input\", id: \"checklist_item_mandatory\")\n    expect(rendered).to have_selector(\"button[type=submit]\")\n  end\n\n  it \"requires category text field\" do\n    expect(rendered).to have_selector(\"input[required=required]\", id: \"checklist_item_category\")\n  end\n\n  it \"requires description text field\" do\n    expect(rendered).to have_selector(\"input[required=required]\", id: \"checklist_item_description\")\n  end\nend\n"
  },
  {
    "path": "spec/views/court_dates/edit.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"court_dates/edit\", type: :view do\n  subject { render template: \"court_dates/edit\" }\n\n  let(:organization) { create(:casa_org) }\n  let(:user) { build_stubbed(:casa_admin, casa_org: organization) }\n  let(:casa_case) { create(:casa_case, casa_org: organization) }\n  let(:court_date) { create(:court_date, :with_court_details, casa_case: casa_case) }\n  let(:court_order) { court_date.case_court_orders.first }\n  let(:implementation_status_name) do\n    \"court_date_case_court_orders_attributes_0_implementation_status\"\n  end\n  let(:implementation_status) do\n    court_order.implementation_status.humanize\n  end\n\n  before do\n    assign :casa_case, casa_case\n    assign :court_date, court_date\n\n    enable_pundit(view, user)\n    allow(view).to receive(:current_user).and_return(user)\n    allow(view).to receive(:current_organization).and_return(user.casa_org)\n  end\n\n  it { is_expected.to have_select(\"court_date_judge_id\", selected: court_date.judge.name) }\n  it { is_expected.to have_select(\"court_date_hearing_type_id\", selected: court_date.hearing_type.name) }\n  it { is_expected.to have_selector(\"textarea\", text: court_order.text) }\n  it { is_expected.to have_select(implementation_status_name, selected: implementation_status) }\n  it { is_expected.to have_selector(\".primary-btn\") }\nend\n"
  },
  {
    "path": "spec/views/court_dates/new.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"court_dates/new\", type: :view do\n  subject { render template: \"court_dates/new\" }\n\n  before do\n    assign :casa_case, casa_case\n    assign :court_date, CourtDate.new\n\n    allow(view).to receive(:current_organization).and_return(user.casa_org)\n  end\n\n  let(:user) { build_stubbed(:casa_admin) }\n  let(:casa_case) { create(:casa_case) }\n\n  it { is_expected.to have_selector(\"h1\", text: \"New Court Date\") }\n  it { is_expected.to have_selector(\"h6\", text: casa_case.case_number) }\n  it { is_expected.to have_link(casa_case.case_number, href: \"/casa_cases/#{casa_case.case_number.parameterize}\") }\n  it { is_expected.to have_selector(\".primary-btn\") }\nend\n"
  },
  {
    "path": "spec/views/court_dates/show.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"court_dates/show\", type: :view do\n  RSpec.shared_examples_for \"a past court date with all court details\" do\n    let(:court_date) { create(:court_date, :with_court_details) }\n    let(:case_court_order) { court_date.case_court_orders.first }\n\n    before { render template: \"court_dates/show\" }\n\n    it \"displays all court details\" do\n      expect(rendered).to include(\"/casa_cases/#{court_date.casa_case.case_number.parameterize}\")\n      expect(rendered).to include(ERB::Util.html_escape(court_date.judge.name))\n      expect(rendered).to include(court_date.hearing_type.name)\n\n      expect(rendered).to include(case_court_order.text)\n      expect(rendered).to include(case_court_order.implementation_status.humanize)\n    end\n\n    context \"when judge's name has escaped characters\" do\n      let(:court_date) { create(:court_date, :with_court_details, judge: create(:judge, name: \"/-'<>#&\")) }\n\n      it \"correctly displays judge's name\" do\n        expect(rendered).to include(ERB::Util.html_escape(court_date.judge.name))\n      end\n    end\n\n    it \"displays the download button for .docx\" do\n      expect(rendered).to include \"Download Report (.docx)\"\n      expect(rendered).to include \"/casa_cases/#{court_date.casa_case.case_number.parameterize}/court_dates/#{court_date.id}.docx\"\n    end\n  end\n\n  RSpec.shared_examples_for \"a past court date with no court details\" do\n    let(:court_date) { create(:court_date) }\n\n    it \"displays all court details\" do\n      render template: \"court_dates/show\"\n\n      expect(rendered).to include(\"Judge:\")\n      expect(rendered).to include(\"Hearing Type\")\n      expect(rendered).to include(\"None\")\n\n      expect(rendered).to include(\"There are no court orders associated with this court date.\")\n    end\n  end\n\n  let(:organization) { create(:casa_org) }\n  let(:casa_case) { create(:casa_case, casa_org: organization) }\n\n  before do\n    enable_pundit(view, user)\n\n    assign :casa_case, court_date.casa_case\n    assign :court_date, court_date\n\n    allow(view).to receive(:current_user).and_return(user)\n    allow(view).to receive(:current_organization).and_return(user.casa_org)\n  end\n\n  context \"with court details\" do\n    context \"when accessed by a casa admin\" do\n      let(:user) { build_stubbed(:casa_admin, casa_org: organization) }\n\n      it_behaves_like \"a past court date with all court details\"\n    end\n\n    context \"when accessed by a supervisor\" do\n      let(:user) { build_stubbed(:supervisor, casa_org: organization) }\n\n      it_behaves_like \"a past court date with all court details\"\n    end\n\n    context \"when accessed by a volunteer\" do\n      let(:user) { build_stubbed(:volunteer, casa_org: organization) }\n\n      it_behaves_like \"a past court date with all court details\"\n    end\n  end\n\n  context \"without court details\" do\n    context \"when accessed by an admin\" do\n      let(:user) { build_stubbed(:casa_admin, casa_org: organization) }\n\n      it_behaves_like \"a past court date with no court details\"\n    end\n\n    context \"when accessed by a supervisor\" do\n      let(:user) { build_stubbed(:supervisor, casa_org: organization) }\n\n      it_behaves_like \"a past court date with no court details\"\n    end\n\n    context \"when accessed by a volunteer\" do\n      let(:user) { build_stubbed(:volunteer, casa_org: organization) }\n\n      it_behaves_like \"a past court date with no court details\"\n    end\n  end\nend\n"
  },
  {
    "path": "spec/views/devise/invitations/edit.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"users/invitations/edit\", type: :view do\n  it \"displays title\" do\n    render template: \"devise/invitations/edit\"\n    expect(rendered).to have_text(\"Set password\")\n  end\n\n  it \"displays fields for user to set password\" do\n    render template: \"devise/invitations/edit\"\n    expect(rendered).to have_field(\"user_invitation_token\", type: :hidden)\n    expect(rendered).to have_text(\"Password\")\n    expect(rendered).to have_field(\"user_password\")\n    expect(rendered).to have_text(\"Password confirmation\")\n    expect(rendered).to have_field(\"user_password_confirmation\")\n    expect(rendered).to have_button(\"Set my password\")\n  end\nend\n"
  },
  {
    "path": "spec/views/devise/invitations/new.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"users/invitations/new\", type: :view do\n  it \"displays title\" do\n    render template: \"devise/invitations/new\"\n    expect(rendered).to have_text(\"Send invitation\")\n  end\n\n  it \"displays fields for inviting a user\" do\n    render template: \"devise/invitations/new\"\n    expect(rendered).to have_text(\"Email\")\n    expect(rendered).to have_field(\"user_email\")\n    expect(rendered).to have_button(\"Send an invitation\")\n  end\nend\n"
  },
  {
    "path": "spec/views/devise/passwords/new.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"users/password/new\", type: :view do\n  it \"displays title\" do\n    render template: \"devise/passwords/new\"\n    expect(rendered).to have_text(\"Forgot your password?\")\n  end\n\n  it \"displays text above form fields\" do\n    render template: \"devise/passwords/new\"\n    expect(rendered).to have_text(\"Please enter email or phone number to receive reset instructions.\")\n  end\n\n  it \"displays contact fields for user to reset password\" do\n    render template: \"devise/passwords/new\"\n    expect(rendered).to have_text(\"Email\")\n    expect(rendered).to have_field(\"user_email\")\n    expect(rendered).to have_text(\"Phone number\")\n    expect(rendered).to have_field(\"user_phone_number\")\n  end\nend\n"
  },
  {
    "path": "spec/views/emancipations/show.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"emancipation/show\", type: :view do\n  subject { render template: \"emancipation/show\" }\n\n  let(:organization) { build_stubbed(:casa_org) }\n  let(:admin) { build_stubbed(:casa_admin, casa_org: organization) }\n  let(:casa_case) { build_stubbed(:casa_case) }\n  let(:emancipation_form_data) { [build_stubbed(:emancipation_category)] }\n\n  before do\n    assign :current_case, casa_case\n    assign :emancipation_form_data, emancipation_form_data\n  end\n\n  it \"has a link to return to case from emancipation\" do\n    sign_in admin\n    render template: \"emancipations/show\"\n    expect(rendered).to have_link(casa_case.case_number, href: \"/casa_cases/#{casa_case.id}\")\n  end\nend\n"
  },
  {
    "path": "spec/views/hearing_types/edit.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"hearing_types/edit\", type: :view do\n  let(:admin) { build_stubbed(:casa_admin) }\n\n  before do\n    assign :hearing_type, HearingType.new\n    sign_in admin\n\n    render template: \"hearing_types/edit\"\n  end\n\n  it \"shows edit hearing type form\" do\n    expect(rendered).to have_text(\"Edit\")\n    expect(rendered).to have_selector(\"input\", id: \"hearing_type_name\")\n    expect(rendered).to have_selector(\"input\", id: \"hearing_type_active\")\n    expect(rendered).to have_selector(\"button[type=submit]\")\n  end\n\n  it \"requires name text_field\" do\n    expect(rendered).to have_selector(\"input[required=required]\", id: \"hearing_type_name\")\n  end\nend\n"
  },
  {
    "path": "spec/views/hearing_types/new.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"hearing_types/new\", type: :view do\n  let(:admin) { build_stubbed(:casa_admin) }\n\n  before do\n    assign :hearing_type, HearingType.new\n    sign_in admin\n\n    render template: \"hearing_types/new\"\n  end\n\n  it \"shows new hearing type form\" do\n    expect(rendered).to have_text(\"New Hearing Type\")\n    expect(rendered).to have_selector(\"input\", id: \"hearing_type_name\")\n    expect(rendered).to have_selector(\"input\", id: \"hearing_type_active\")\n    expect(rendered).to have_selector(:link_or_button, \"Submit\")\n  end\n\n  it \"requires name text_field\" do\n    expect(rendered).to have_selector(\"input[required=required]\", id: \"hearing_type_name\")\n  end\nend\n"
  },
  {
    "path": "spec/views/judges/new.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"judges/new\", type: :view do\n  let(:admin) { build_stubbed(:casa_admin) }\n\n  before do\n    assign :judge, Judge.new\n    sign_in admin\n\n    render template: \"judges/new\"\n  end\n\n  it \"shows new judge form\" do\n    expect(rendered).to have_text(\"New Judge\")\n    expect(rendered).to have_selector(\"input\", id: \"judge_name\")\n    expect(rendered).to have_selector(\"input\", id: \"judge_active\")\n    expect(rendered).to have_selector(\"button[type=submit]\")\n  end\n\n  it \"requires name text_field\" do\n    expect(rendered).to have_selector(\"input[required=required]\", id: \"judge_name\")\n  end\nend\n"
  },
  {
    "path": "spec/views/layouts/application.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"layouts/application\", type: :view do\n  subject { rendered }\n\n  let(:title) { \"CASA Volunteer Tracking\" }\n  let(:description) { \"Volunteer activity tracking for CASA volunteers, supervisors, and administrators.\" }\n\n  it \"renders correct title\" do\n    render\n    expect(subject).to match \"<title>#{title}</title>\"\n  end\n\n  it \"renders correct description\" do\n    render\n    expect(subject).to match \"<meta name=\\\"description\\\" content=\\\"#{description}\\\">\"\n  end\n\n  it \"renders correct og meta tags\" do\n    render\n\n    expect(rendered.scan(/<meta property=\"og:.*>/).count).to be(4)\n    expect(subject).to match(\"og:title\\\" content=\\\"#{title}\\\"\")\n    expect(subject).to match('og:url\" content=\"http://test.host/\"')\n    expect(subject).to match('og:image\" content=\"http://test.host/assets/.*.jpg\"')\n    expect(subject).to match(\n      'og:description\" content=\"Volunteer activity tracking for CASA volunteers, supervisors, and administrators.\"'\n    )\n  end\nend\n"
  },
  {
    "path": "spec/views/layouts/footer.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"layout/footer\", type: :view do\n  context \"when not logged in\" do\n    it \"renders report issue link on the footer\" do\n      render partial: \"layouts/footers/not_logged_in\"\n      expect(rendered).to have_link(\"Report a site issue\", href: \"https://form.typeform.com/to/iXY4BubB\")\n    end\n\n    it \"renders SMS terms and conditions link on the footer\" do\n      render partial: \"layouts/footers/not_logged_in\"\n      expect(rendered).to have_link(\"SMS Terms & Conditions\", href: \"/sms-terms-conditions.html\")\n    end\n\n    it \"renders Ruby For Good link on the footer\" do\n      render partial: \"layouts/footers/not_logged_in\"\n      expect(rendered).to have_link(\"Ruby For Good\", href: \"https://rubyforgood.org/\")\n    end\n  end\n\n  context \"when logged in\" do\n    it \"renders report issue link on the footer\" do\n      render partial: \"layouts/footers/logged_in\"\n      expect(rendered).to have_link(\"Report a site issue\", href: \"https://form.typeform.com/to/iXY4BubB\")\n    end\n\n    it \"renders SMS terms and conditions link on the footer\" do\n      render partial: \"layouts/footers/logged_in\"\n      expect(rendered).to have_link(\"Terms & Conditions\", href: \"/sms-terms-conditions.html\")\n    end\n\n    it \"renders Ruby For Good link on the footer\" do\n      render partial: \"layouts/footers/logged_in\"\n      expect(rendered).to have_link(\"Ruby For Good\", href: \"https://rubyforgood.org/\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/views/layouts/header.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"layout/header\", type: :view do\n  before do\n    view.class.include PretenderContext\n\n    enable_pundit(view, user)\n    allow(view).to receive(:true_user).and_return(user)\n    allow(view).to receive(:current_user).and_return(user)\n    allow(view).to receive(:current_role).and_return(user.role)\n\n    casa_org = build_stubbed :casa_org\n    allow(view).to receive(:current_organization).and_return(casa_org)\n  end\n\n  context \"when logged in as a casa admin\" do\n    let(:user) { build_stubbed :casa_admin }\n\n    it \"renders user information\", :aggregate_failures do\n      sign_in user\n\n      render partial: \"layouts/header\"\n\n      expect(rendered).to match \"<strong>Role: Casa Admin</strong>\"\n      expect(rendered).to match CGI.escapeHTML user.display_name\n      expect(rendered).to match CGI.escapeHTML user.email\n    end\n\n    it \"renders help issue link on the header\" do\n      render partial: \"layouts/header\"\n      expect(rendered).to have_link(\"Help\", href: \"https://thunder-flower-8c2.notion.site/Casa-Volunteer-Tracking-App-HelpSite-3b95705e80c742ffa729ccce7beeabfa\")\n    end\n  end\n\n  context \"when logged in as a supervisor\" do\n    let(:user) { build_stubbed :supervisor }\n\n    it \"renders user information\", :aggregate_failures do\n      sign_in user\n\n      render partial: \"layouts/header\"\n\n      expect(rendered).to match \"<strong>Role: Supervisor</strong>\"\n      expect(rendered).to match CGI.escapeHTML user.display_name\n      expect(rendered).to match CGI.escapeHTML user.email\n    end\n\n    it \"renders help issue link on the header\" do\n      render partial: \"layouts/header\"\n      expect(rendered).to have_link(\"Help\", href: \"https://thunder-flower-8c2.notion.site/Casa-Volunteer-Tracking-App-HelpSite-3b95705e80c742ffa729ccce7beeabfa\")\n    end\n  end\n\n  context \"when logged in as a volunteer\" do\n    let(:user) { build_stubbed :volunteer }\n\n    it \"renders user information\", :aggregate_failures do\n      sign_in user\n\n      render partial: \"layouts/header\"\n\n      expect(rendered).to match \"<strong>Role: Volunteer</strong>\"\n      expect(rendered).to match CGI.escapeHTML user.display_name\n      expect(rendered).to match CGI.escapeHTML user.email\n    end\n\n    it \"does not render unauthorized links\" do\n      sign_in user\n\n      render partial: \"layouts/header\"\n\n      expect(rendered).not_to have_link(\"Edit Organization\")\n    end\n\n    it \"renders help issue link on the header\" do\n      render partial: \"layouts/header\"\n      expect(rendered).to have_link(\"Help\", href: \"https://thunder-flower-8c2.notion.site/Casa-Volunteer-Tracking-App-HelpSite-Volunteers-c24d9d2ef8b249bbbda8192191365039?pvs=4\")\n    end\n  end\n\n  context \"notifications\" do\n    let(:user) { build_stubbed :casa_admin }\n\n    it \"displays unread notification count if the user has unread notifications\" do\n      sign_in user\n      build(:notification)\n      allow(user).to receive_message_chain(:notifications, :unread).and_return([:notification])\n\n      render partial: \"layouts/header\"\n\n      expect(rendered).to match \"<span>1</span>\"\n    end\n\n    it \"does not display unread notification count if the user has no unread notifications\" do\n      sign_in user\n      allow(user).to receive_message_chain(:notifications, :unread).and_return([])\n\n      render partial: \"layouts/header\"\n\n      expect(rendered).not_to match \"<span>0</span>\"\n    end\n  end\n\n  context \"impersonation\" do\n    let(:user) { build_stubbed :volunteer }\n    let(:true_user) { build_stubbed :casa_admin }\n\n    it \"renders correct role name when impersonating a volunteer\" do\n      allow(view).to receive(:true_user).and_return(true_user)\n\n      render partial: \"layouts/header\"\n\n      expect(rendered).to match \"<strong>Role: Volunteer</strong>\"\n    end\n\n    it \"renders a stop impersonating link when impersonating\" do\n      allow(view).to receive(:true_user).and_return(true_user)\n\n      render partial: \"layouts/header\"\n\n      expect(rendered).to have_link(href: \"/volunteers/stop_impersonating\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/views/layouts/sidebar.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"layout/sidebar\", type: :view do\n  before do\n    view.class.include PretenderContext\n\n    enable_pundit(view, user)\n    allow(view).to receive(:current_user).and_return(user)\n    allow(view).to receive(:true_user).and_return(user)\n    allow(view).to receive(:user_signed_in?).and_return(true)\n    allow(view).to receive(:current_role).and_return(user.role)\n    allow(view).to receive(:current_organization).and_return(user.casa_org)\n\n    assign :casa_org, user.casa_org\n  end\n\n  shared_examples_for \"properly rendering custom org links\" do\n    let(:active_link_text) { \"Example Link\" }\n    let(:active_link_url) { \"https://www.example.com\" }\n    let(:inactive_link_text) { \"Hidden Link\" }\n    let(:inactive_link_url) { \"https://www.nothing.com\" }\n    let(:other_org_link_text) { \"That Other Link\" }\n    let(:other_org_link_url) { \"https://www.elsewhere.com\" }\n\n    before do\n      create :custom_org_link, casa_org: user.casa_org, text: active_link_text, url: active_link_url, active: true\n      create :custom_org_link, casa_org: user.casa_org, text: inactive_link_text, url: inactive_link_url, active: false\n      create :custom_org_link, text: other_org_link_text, url: other_org_link_url, active: true\n    end\n\n    it \"renders active custom links for the user's org\" do\n      render partial: \"layouts/sidebar\"\n      expect(rendered).to have_link(active_link_text, href: active_link_url)\n    end\n\n    it \"does not render inactive custom links for the user's org\" do\n      render partial: \"layouts/sidebar\"\n      expect(rendered).not_to have_link(inactive_link_text, href: inactive_link_url)\n    end\n\n    it \"does not render custom links for other orgs\" do\n      render partial: \"layouts/sidebar\"\n      expect(rendered).not_to have_link(other_org_link_text, href: other_org_link_url)\n    end\n  end\n\n  context \"when no organization logo is set\" do\n    let(:user) { build_stubbed :volunteer }\n\n    it \"displays default logo\" do\n      sign_in user\n\n      render partial: \"layouts/sidebar\"\n\n      expect(rendered).to have_xpath(\"//img[contains(@src,'default-logo') and @alt='CASA Logo']\")\n    end\n  end\n\n  context \"when logged in as a supervisor\" do\n    let(:user) do\n      build_stubbed :supervisor, display_name: \"Supervisor's name\",\n        email: \"supervisor&email@test.com\"\n    end\n\n    it \"renders only menu items visible by supervisors\" do\n      sign_in user\n\n      render partial: \"layouts/sidebar\"\n\n      expect(rendered).to have_link(\"Supervisors\", href: \"/supervisors\")\n      expect(rendered).to have_link(\"Volunteers\", href: \"/volunteers\")\n      expect(rendered).to have_link(\"Cases\", href: \"/casa_cases\")\n      expect(rendered).not_to have_link(\"Case Contacts\", href: \"/case_contacts\")\n      expect(rendered).not_to have_link(\"Admins\", href: \"/casa_admins\")\n      expect(rendered).to have_link(\"Generate Court Reports\", href: \"/case_court_reports\")\n      expect(rendered).to have_link(\"Export Data\", href: \"/reports\")\n      expect(rendered).not_to have_link(\"Emancipation Checklist\", href: \"/emancipation_checklists/0\")\n      expect(rendered).not_to have_link(\"System Settings\", href: \"/settings\")\n      expect(rendered).to have_link(\"Other Duties\", href: \"/other_duties\")\n      expect(rendered).not_to have_link(\"Organization Details\", href: \"/casa_org/#{user.casa_org.id}/edit#organization-details\")\n      expect(rendered).not_to have_link(\"Contact Types\", href: \"/casa_org/#{user.casa_org.id}/edit#contact-types\")\n      expect(rendered).not_to have_link(\"Court Details\", href: \"/casa_org/#{user.casa_org.id}/edit#court-details\")\n      expect(rendered).not_to have_link(\"Learning Hours\", href: \"/casa_org/#{user.casa_org.id}/edit#learning-hours\")\n      expect(rendered).not_to have_link(\"Case Contact Topics\", href: \"/casa_org/#{user.casa_org.id}/edit#case-contact-topics\")\n    end\n\n    it_behaves_like \"properly rendering custom org links\"\n\n    context \"when casa_org other_duties_enabled is true\" do\n      before do\n        user.casa_org.other_duties_enabled = true\n        sign_in user\n        render partial: \"layouts/sidebar\"\n      end\n\n      it \"renders Other Duties\" do\n        expect(rendered).to have_link(\"Other Duties\", href: \"/other_duties\")\n      end\n    end\n\n    context \"when casa_org other_duties_enabled is false\" do\n      before do\n        user.casa_org.other_duties_enabled = false\n\n        sign_in user\n        render partial: \"layouts/sidebar\"\n      end\n\n      it \"does not renders Other Duties\" do\n        expect(rendered).not_to have_link(\"Other Duties\", href: \"/other_duties\")\n      end\n    end\n  end\n\n  context \"when logged in as a volunteer\" do\n    let(:organization) { build(:casa_org) }\n\n    let(:user) do\n      create(\n        :volunteer,\n        casa_org: organization,\n        display_name: \"Volunteer's name%\"\n      )\n    end\n\n    it \"renders only menu items visible by volunteers\" do\n      sign_in user\n\n      render partial: \"layouts/sidebar\"\n\n      expect(rendered).to have_link(\"All\", href: \"/casa_cases\")\n      expect(rendered).to have_link(\"All\", href: \"/case_contacts\")\n      expect(rendered).to have_link(\"Generate Court Report\", href: \"/case_court_reports\")\n      expect(rendered).not_to have_link(\"Export Data\", href: \"/reports\")\n      expect(rendered).not_to have_link(\"Volunteers\", href: \"/volunteers\")\n      expect(rendered).not_to have_link(\"Supervisors\", href: \"/supervisors\")\n      expect(rendered).not_to have_link(\"Admins\", href: \"/casa_admins\")\n      expect(rendered).not_to have_link(\"System Settings\", href: \"/settings\")\n      expect(rendered).to have_link(\"Other Duties\", href: \"/other_duties\")\n      expect(rendered).not_to have_link(\"Organization Details\", href: \"/casa_org/#{user.casa_org.id}/edit#organization-details\")\n      expect(rendered).not_to have_link(\"Contact Types\", href: \"/casa_org/#{user.casa_org.id}/edit#contact-types\")\n      expect(rendered).not_to have_link(\"Court Details\", href: \"/casa_org/#{user.casa_org.id}/edit#court-details\")\n      expect(rendered).not_to have_link(\"Learning Hours\", href: \"/casa_org/#{user.casa_org.id}/edit#learning-hours\")\n      expect(rendered).not_to have_link(\"Case Contact Topics\", href: \"/casa_org/#{user.casa_org.id}/edit#case-contact-topics\")\n    end\n\n    it_behaves_like \"properly rendering custom org links\"\n\n    context \"when casa_org other_duties_enabled is true\" do\n      before do\n        user.casa_org.other_duties_enabled = true\n        sign_in user\n        render partial: \"layouts/sidebar\"\n      end\n\n      it \"renders Other Duties\" do\n        expect(rendered).to have_link(\"Other Duties\", href: \"/other_duties\")\n      end\n    end\n\n    context \"when casa_org other_duties_enabled is false\" do\n      before do\n        user.casa_org.other_duties_enabled = false\n\n        sign_in user\n        render partial: \"layouts/sidebar\"\n      end\n\n      it \"does not renders Other Duties\" do\n        expect(rendered).not_to have_link(\"Other Duties\", href: \"/other_duties\")\n      end\n    end\n\n    context \"when the volunteer does not have a transitioning case\" do\n      it \"does not render emancipation checklist(s)\" do\n        sign_in user\n\n        # 0 Cases\n        render partial: \"layouts/sidebar\"\n        expect(rendered).not_to have_link(\"Emancipation Checklist\", href: \"/emancipation_checklists\")\n\n        # 1 Non transitioning case\n        casa_case = build_stubbed(:casa_case, :pre_transition, casa_org: organization)\n        build_stubbed(:case_assignment, volunteer: user, casa_case: casa_case)\n\n        render partial: \"layouts/sidebar\"\n        expect(rendered).not_to have_link(\"Emancipation Checklist\", href: \"/emancipation_checklists\")\n      end\n    end\n\n    context \"when the user has only inactive or unassigned transiting cases\" do\n      it \"does not render emancipation checklist(s)\" do\n        sign_in user\n\n        inactive_case = build_stubbed(:casa_case, casa_org: organization, active: false)\n        build_stubbed(:case_assignment, volunteer: user, casa_case: inactive_case)\n\n        unassigned_case = build_stubbed(:casa_case, casa_org: organization)\n        build_stubbed(:case_assignment, volunteer: user, casa_case: unassigned_case, active: false)\n\n        render partial: \"layouts/sidebar\"\n        expect(rendered).not_to have_link(\"Emancipation Checklist\", href: \"/emancipation_checklists\")\n      end\n    end\n\n    context \"when the volunteer has a transitioning case\" do\n      let(:casa_case) { create(:casa_case, casa_org: organization) }\n      let!(:case_assignment) { create(:case_assignment, volunteer: user, casa_case: casa_case) }\n\n      it \"renders emancipation checklist(s)\" do\n        sign_in user\n\n        render partial: \"layouts/sidebar\"\n        expect(rendered).to have_link(\"Emancipation Checklist\", href: \"/emancipation_checklists\")\n      end\n    end\n  end\n\n  context \"when logged in as a casa admin\" do\n    let(:user) { build_stubbed :casa_admin, display_name: \"Superviso's another n&ame\" }\n\n    it \"renders only menu items visible by admins\" do\n      sign_in user\n\n      render partial: \"layouts/sidebar\"\n\n      expect(rendered).to have_link(\"Volunteers\", href: \"/volunteers\")\n      expect(rendered).to have_link(\"Cases\", href: \"/casa_cases\")\n      expect(rendered).not_to have_link(\"Case Contacts\", href: \"/case_contacts\")\n      expect(rendered).to have_link(\"Supervisors\", href: \"/supervisors\")\n      expect(rendered).to have_link(\"Admins\", href: \"/casa_admins\")\n      expect(rendered).to have_link(\"System Imports\", href: \"/imports\")\n      expect(rendered).to have_link(\"Generate Court Reports\", href: \"/case_court_reports\")\n      expect(rendered).to have_link(\"Export Data\", href: \"/reports\")\n      expect(rendered).not_to have_link(\"Emancipation Checklist\", href: \"/emancipation_checklists\")\n      expect(rendered).to have_link(\"Other Duties\", href: \"/other_duties\")\n      expect(rendered).to have_link(\"Organization Details\", href: \"/casa_org/#{user.casa_org.id}/edit#organization-details\")\n      expect(rendered).to have_link(\"Contact Types\", href: \"/casa_org/#{user.casa_org.id}/edit#contact-types\")\n      expect(rendered).to have_link(\"Court Details\", href: \"/casa_org/#{user.casa_org.id}/edit#court-details\")\n      expect(rendered).to have_link(\"Learning Hours\", href: \"/casa_org/#{user.casa_org.id}/edit#learning-hours\")\n      expect(rendered).to have_link(\"Case Contact Topics\", href: \"/casa_org/#{user.casa_org.id}/edit#case-contact-topics\")\n    end\n\n    it_behaves_like \"properly rendering custom org links\"\n\n    context \"when casa_org other_duties_enabled is true\" do\n      before do\n        user.casa_org.other_duties_enabled = true\n        sign_in user\n        render partial: \"layouts/sidebar\"\n      end\n\n      it \"renders Other Duties\" do\n        expect(rendered).to have_link(\"Other Duties\", href: \"/other_duties\")\n      end\n    end\n\n    context \"when casa_org other_duties_enabled is false\" do\n      before do\n        user.casa_org.other_duties_enabled = false\n\n        sign_in user\n        render partial: \"layouts/sidebar\"\n      end\n\n      it \"does not renders Other Duties\" do\n        expect(rendered).not_to have_link(\"Other Duties\", href: \"/other_duties\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/views/mileage_rates/index.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"Index Mileage Rates\", type: :view do\n  let(:admin) { build_stubbed :casa_admin }\n  let(:mileage_rate) { build_stubbed :mileage_rate }\n\n  before do\n    enable_pundit(view, admin)\n    allow(view).to receive(:current_user).and_return(admin)\n    sign_in admin\n  end\n\n  it \"allows editing the mileage rate\" do\n    assign :mileage_rates, [mileage_rate]\n\n    render template: \"mileage_rates/index\"\n    expect(rendered).to have_link(\"Edit\", href: \"/mileage_rates/#{mileage_rate.id}/edit\")\n  end\nend\n"
  },
  {
    "path": "spec/views/notifications/index.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"notifications/index\", type: :view do\n  let(:notification_1_hour_ago) { create(:notification, :followup_with_note) }\n  let(:notification_1_day_ago) { create(:notification, :emancipation_checklist_reminder) }\n  let(:notification_2_days_ago) { create(:notification, :youth_birthday) }\n  let(:notification_3_days_ago) { create(:notification, :reimbursement_complete) }\n\n  let(:patch_note_group_all_users) { create(:patch_note_group, :all_users) }\n  let(:patch_note_group_no_volunteers) { create(:patch_note_group, :only_supervisors_and_admins) }\n  let(:patch_note_type_a) { create(:patch_note_type, name: \"patch_note_type_a\") }\n  let(:patch_note_type_b) { create(:patch_note_type, name: \"patch_note_type_b\") }\n  let(:patch_note_1) { create(:patch_note, note: \"Patch Note 1\", patch_note_type: patch_note_type_a) }\n  let(:patch_note_2) { create(:patch_note, note: \"Patch Note B\", patch_note_type: patch_note_type_b) }\n\n  before do\n    assign(:notifications, Noticed::Notification.all.newest_first)\n    assign(:patch_notes, PatchNote.all)\n    assign(:deploy_time, deploy_time)\n  end\n\n  context \"when there is a deploy date\" do\n    let(:deploy_time) { 2.days.ago }\n\n    before do\n      Health.instance.update_attribute(:latest_deploy_time, deploy_time)\n    end\n\n    context \"when there are notifications\" do\n      before do\n        notification_1_hour_ago.update!(created_at: 1.hour.ago)\n        notification_1_day_ago.update!(created_at: 1.day.ago)\n        notification_2_days_ago.update!(created_at: 2.days.ago)\n        notification_3_days_ago.update!(created_at: 3.days.ago)\n\n        patch_note_1.update!(patch_note_group: patch_note_group_all_users)\n      end\n\n      it \"has all notifications created after and including the deploy date above the patch note\" do\n        render template: \"notifications/index\"\n\n        notifications_html = Nokogiri::HTML5(rendered).css(\".list-group-item\")\n        patch_note_index = notifications_html.index { |node| node.text.include?(\"Patch Notes\") }\n\n        aggregate_failures do\n          expect(notifications_html[0].text).to match(/User \\d+ has flagged a Case Contact that needs follow up/)\n          expect(notifications_html[1].text).to match(/Your case CINA-\\d+ is a transition aged youth/)\n          expect(notifications_html[2].text).to match(/Your youth, case number: CINA-\\d+ has a birthday next month/)\n          expect(patch_note_index).to eq(3)\n        end\n      end\n\n      it \"has all notifications created before the deploy date below the patch note\" do\n        render template: \"notifications/index\"\n\n        notifications_html = Nokogiri::HTML5(rendered).css(\".list-group-item\")\n        patch_note_index = notifications_html.index { |node| node.text.include?(\"Patch Notes\") }\n\n        expect(patch_note_index).to eq(3)\n        expect(notifications_html[patch_note_index + 1].text).to match(/Volunteer User \\d+'s request for reimbursement for 0mi on .* has been processed and is en route./)\n      end\n    end\n\n    context \"when there are patch notes\" do\n      before do\n        patch_note_1.update_attribute(:patch_note_group, patch_note_group_all_users)\n        patch_note_2.update_attribute(:patch_note_group, patch_note_group_no_volunteers)\n      end\n\n      it \"shows all the patch notes available\" do\n        render template: \"notifications/index\"\n\n        expect(rendered).to have_text(\"Patch Note 1\")\n        expect(rendered).to have_text(\"Patch Note B\")\n      end\n\n      it \"shows the patch notes under the correct type\" do\n        render template: \"notifications/index\"\n\n        queryable_html = Nokogiri.HTML5(rendered)\n\n        patch_note_type_a_header = queryable_html.xpath(\"//*[text()[contains(.,'#{patch_note_type_a.name}')]]\").first\n        patch_note_type_b_header = queryable_html.xpath(\"//*[text()[contains(.,'#{patch_note_type_b.name}')]]\").first\n\n        patch_note_a_data = patch_note_type_a_header.parent.css(\"ul\").first\n        expect(patch_note_a_data.text).to include(\"Patch Note 1\")\n\n        patch_note_b_data = patch_note_type_b_header.parent.css(\"ul\").first\n        expect(patch_note_b_data.text).to include(\"Patch Note B\")\n      end\n    end\n  end\n\n  context \"without a deploy date\" do\n    let(:deploy_time) { nil }\n\n    before do\n      notification_1_hour_ago.update_attribute(:created_at, 1.hour.ago)\n      notification_1_day_ago.update_attribute(:created_at, 1.day.ago)\n      notification_2_days_ago.update_attribute(:created_at, 2.days.ago)\n      notification_3_days_ago.update_attribute(:created_at, 3.days.ago)\n\n      patch_note_1.update_attribute(:patch_note_group, patch_note_group_all_users)\n      patch_note_2.update_attribute(:patch_note_group, patch_note_group_no_volunteers)\n    end\n\n    it \"shows the correct number of notifications\" do\n      render template: \"notifications/index\"\n\n      expect(rendered).to have_css(\".list-group-item\", count: 4)\n    end\n\n    it \"does not display patch notes\" do\n      render template: \"notifications/index\"\n\n      notifications_html = Nokogiri::HTML5(rendered).css(\".list-group-item\")\n      view_patch_notes = notifications_html.select { |node| node.text.include?(\"Patch Notes\") }\n\n      expect(PatchNote.all.size).to eql(2)\n      expect(view_patch_notes).to be_empty\n    end\n  end\nend\n"
  },
  {
    "path": "spec/views/other_duties/edit.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"other_duties/edit\", type: :view do\n  let(:other_duty) { create(:other_duty) }\n\n  before do\n    assign :other_duty, other_duty\n  end\n\n  it \"display all form fields\" do\n    render template: \"other_duties/edit\"\n\n    expect(rendered).to have_text(\"Editing Duty\")\n    expect(rendered).to have_text(\"Occurred On\")\n    expect(rendered).to have_text(\"Duty Duration\")\n    expect(rendered).to have_text(\"Enter Notes\")\n  end\n\n  it \"displays occurred time in the occurred at form field\" do\n    render template: \"other_duties/edit\"\n\n    expect(rendered).to include(other_duty.occurred_at.strftime(\"%Y-%m-%d\"))\n  end\n\n  it \"displays notes in the notes form field\" do\n    render template: \"other_duties/edit\"\n\n    expect(rendered).to include(CGI.escapeHTML(other_duty.notes))\n  end\nend\n"
  },
  {
    "path": "spec/views/other_duties/new.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"other_duties/new\", type: :view do\n  let(:current_time) { Time.zone.now.strftime(\"%Y-%m-%d\") }\n\n  before do\n    assign :other_duty, OtherDuty.new\n    render template: \"other_duties/new\"\n  end\n\n  it \"display all form fields\" do\n    expect(rendered).to have_text(\"New Duty\")\n    expect(rendered).to have_field(\"Occurred On\", with: current_time)\n    expect(rendered).to have_text(\"Duty Duration\")\n    expect(rendered).to have_text(\"Enter Notes\")\n  end\nend\n"
  },
  {
    "path": "spec/views/placement_types/edit.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"placement_types/edit.html.erb\", type: :view do\n  let(:organization) { build_stubbed :casa_org }\n  let(:admin) { build_stubbed :casa_admin, casa_org: organization }\n  let(:placement_type) { build_stubbed :placement_type, casa_org: organization }\n\n  before do\n    enable_pundit(view, admin)\n    sign_in admin\n  end\n\n  it \"allows editing the placement type\" do\n    assign :placement_type, placement_type\n    render template: \"placement_types/edit\"\n    expect(rendered).to have_text(\"Edit Placement Type\")\n    expect(rendered).to have_field(\"placement_type[name]\", with: placement_type.name)\n    expect(rendered).to have_button(\"Submit\")\n  end\nend\n"
  },
  {
    "path": "spec/views/placement_types/new.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"placement_types/new.html.erb\", type: :view do\n  let(:organization) { build_stubbed :casa_org }\n  let(:admin) { build_stubbed :casa_admin, casa_org: organization }\n  let(:placement_type) { organization.placement_types.new }\n\n  before do\n    enable_pundit(view, admin)\n    sign_in admin\n  end\n\n  it \"allows creating a placement type\" do\n    assign :placement_type, placement_type\n    render template: \"placement_types/new\"\n    expect(rendered).to have_text(\"New Placement Type\")\n    expect(rendered).to have_field(\"placement_type[name]\")\n    expect(rendered).to have_button(\"Submit\")\n  end\nend\n"
  },
  {
    "path": "spec/views/placements/edit.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"placements/edit\", type: :view do\n  subject { render template: \"placements/edit\" }\n\n  let(:organization) { create(:casa_org) }\n  let(:user) { build_stubbed(:casa_admin, casa_org: organization) }\n  let(:casa_case) { create(:casa_case, casa_org: organization, case_number: \"123\") }\n  let(:placement_type) { create(:placement_type, name: \"Reunification\") }\n  let(:placement) { create(:placement, placement_started_at: \"2024-08-15 12:39:00 UTC\", placement_type:, casa_case:) }\n\n  before do\n    assign :casa_case, casa_case\n    assign :placement, placement\n\n    enable_pundit(view, user)\n    allow(view).to receive(:current_user).and_return(user)\n    allow(view).to receive(:current_organization).and_return(user.casa_org)\n    render\n  end\n\n  it { is_expected.to have_selector(\"h1\", text: \"Editing Placement\") }\n\n  it \"has a date input for placement started at with the correct value\" do\n    expect(rendered).to have_field(\"placement[placement_started_at]\", with: \"2024-08-15\")\n  end\n\n  it \"has a select input for placement type with the correct placeholder\" do\n    expect(rendered).to have_select(\"placement[placement_type_id]\", with_options: [\"-Select Placement Type-\"])\n  end\nend\n"
  },
  {
    "path": "spec/views/placements/index.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"placements/index\", type: :view do\n  let(:casa_org) { create(:casa_org, :with_placement_types) }\n  let(:casa_case) { create(:casa_case, casa_org:, case_number: \"CINA-12345\") }\n  let(:placement_current) { create(:placement_type, name: \"Reunification\", casa_org:) }\n  let(:placement_prev) { create(:placement_type, name: \"Kinship\", casa_org:) }\n  let(:placement_first) { create(:placement_type, name: \"Adoption\", casa_org:) }\n\n  let(:placements) do\n    [\n      create(:placement, placement_started_at: \"2024-08-15 20:40:44 UTC\", casa_case:, placement_type: placement_current),\n      create(:placement, placement_started_at: \"2023-06-02 00:00:00 UTC\", casa_case:, placement_type: placement_prev),\n      create(:placement, placement_started_at: \"2021-12-25 10:10:10 UTC\", casa_case:, placement_type: placement_first)\n    ]\n  end\n\n  before do\n    assign(:casa_case, casa_case)\n    assign(:placements, placements.sort_by(&:placement_started_at).reverse)\n\n    render\n  end\n\n  it \"displays the case number in the header\" do\n    expect(rendered).to have_selector(\"h1\", text: \"CINA-12345\")\n  end\n\n  it \"has a link to create a new placement\" do\n    expect(rendered).to have_link(\"New Placement\", href: new_casa_case_placement_path(casa_case))\n  end\n\n  it \"displays placement information for each placement\" do\n    expect(rendered).to have_content(\"Reunification\")\n    expect(rendered).to have_content(/August 15, 2024/)\n    expect(rendered).to have_content(/Present/)\n\n    expect(rendered).to have_content(\"Kinship\")\n    expect(rendered).to have_content(/June 2, 2023/)\n\n    expect(rendered).to have_content(\"Adoption\")\n    expect(rendered).to have_content(/December 25, 2021/)\n  end\n\n  it \"has edit links for each placement\" do\n    placements.each do |placement|\n      expect(rendered).to have_link(\"Edit\", href: edit_casa_case_placement_path(casa_case, placement))\n    end\n  end\n\n  it \"has delete buttons for each placement\" do\n    placements.each do |placement|\n      expect(rendered).to have_selector(\"a[data-bs-target='##{placement.id}']\", text: \"Delete\")\n    end\n  end\n\n  it \"renders delete confirmation modals for each placement\" do\n    placements.each do |placement|\n      expect(rendered).to have_selector(\"##{placement.id}.modal\")\n      within \"##{placement.id}\" do\n        expect(rendered).to have_content(\"Delete Placement?\")\n        expect(rendered).to have_link(\"Delete Placement\", href: casa_case_placement_path(casa_case, placement))\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/views/placements/new.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"placements/new\", type: :view do\n  subject { render template: \"placements/new\" }\n\n  before do\n    assign :casa_case, casa_case\n    assign :placement, Placement.new\n\n    allow(view).to receive(:current_organization).and_return(user.casa_org)\n  end\n\n  let(:user) { build_stubbed(:casa_admin) }\n  let(:casa_case) { create(:casa_case) }\n\n  it { is_expected.to have_selector(\"h1\", text: \"New Placement\") }\n  it { is_expected.to have_selector(\"h6\", text: casa_case.case_number) }\n  it { is_expected.to have_link(casa_case.case_number, href: \"/casa_cases/#{casa_case.case_number.parameterize}\") }\n  it { is_expected.to have_selector(\".primary-btn\") }\nend\n"
  },
  {
    "path": "spec/views/reimbursements/index.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"reimbursements/index\", type: :view do\n  before do\n    admin = build_stubbed :casa_admin\n    enable_pundit(view, admin)\n    allow(view).to receive(:current_user).and_return(admin)\n  end\n\n  it \"does not have any translation missing classes\" do\n    supervisor = create :supervisor\n    volunteer = create :volunteer, supervisor: supervisor\n\n    case_contact = create :case_contact, :wants_reimbursement, creator: volunteer, contact_made: true, occurred_at: 6.days.ago\n    assign :reimbursements, [case_contact]\n    assign :grouped_reimbursements, []\n    assign :volunteers_for_filter, []\n    render template: \"reimbursements/index\"\n\n    expect(rendered).not_to have_css(\"span.translation_missing\")\n  end\nend\n"
  },
  {
    "path": "spec/views/supervisor_mailer/weekly_digest.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"supervisor_mailer/weekly_digest\", type: :view do\n  let(:organization) { create(:casa_org) }\n  let(:supervisor) { create(:supervisor, casa_org: organization) }\n  let(:volunteer) { create(:volunteer, casa_org: organization) }\n  let(:casa_case) { create(:casa_case, casa_org: organization) }\n  let(:inactive_volunteer) { create(:volunteer, :with_casa_cases, casa_org: organization) }\n  let(:supervisor_mailer) { SupervisorMailer.new }\n\n  let(:contact_topic_1) { create(:contact_topic, question: \"Contact Topic 1\") }\n  let(:contact_topic_2) { create(:contact_topic, question: \"Contact Topic 2\") }\n  let(:contact_topic_answer_1) { create(:contact_topic_answer, contact_topic: contact_topic_1, value: \"Contact Topic 1 Answer\") }\n  let(:contact_topic_answer_2) { create(:contact_topic_answer, contact_topic: contact_topic_2, value: \"\") }\n\n  context \"when there are successful and unsuccessful contacts\" do\n    before do\n      supervisor.volunteers << volunteer\n      inactive_volunteer.update active: false\n      supervisor.volunteers_ever_assigned << inactive_volunteer\n      volunteer.casa_cases << casa_case\n      create_list :case_contact, 2, creator: volunteer, casa_case: casa_case, contact_made: false, occurred_at: 6.days.ago\n      @case_contact = create :case_contact, creator: volunteer, casa_case: casa_case, contact_made: true, occurred_at: 6.days.ago, contact_topic_answers: [contact_topic_answer_1, contact_topic_answer_2]\n      assign :supervisor, supervisor\n      assign :inactive_volunteers, []\n      sign_in supervisor\n      @inactive_messages = InactiveMessagesService.new(supervisor).inactive_messages\n      render template: \"supervisor_mailer/weekly_digest\"\n    end\n\n    it { expect(rendered).to have_text(\"Here's a summary of what happened with your volunteers this last week.\") }\n    it { expect(rendered).to have_link(volunteer.display_name) }\n    it { expect(rendered).to have_link(casa_case.case_number) }\n    it { expect(rendered).not_to have_text(inactive_volunteer.display_name) }\n    it { expect(rendered).to have_text(\"Number of unsuccessful case contacts made this week: 2\") }\n    it { expect(rendered).to have_text(\"Number of successful case contacts made this week: 1\") }\n    it { expect(rendered).to have_text(\"- Date: #{I18n.l(@case_contact.occurred_at, format: :full, default: nil)}\") }\n    it { expect(rendered).to have_text(\"- Type: #{@case_contact.decorate.contact_types}\") }\n    it { expect(rendered).to have_text(\"- Duration: #{@case_contact.duration_minutes}\") }\n    it { expect(rendered).to have_text(\"- Contact Made: #{@case_contact.contact_made}\") }\n    it { expect(rendered).to have_text(\"- Medium Type: #{@case_contact.medium_type}\") }\n    it { expect(rendered).to have_text(\"- Notes: #{@case_contact.notes}\") }\n    it { expect(rendered).to have_text(\"Contact Topic 1\") }\n    it { expect(rendered).to have_text(\"Contact Topic 1 Answer\") }\n    it { expect(rendered).not_to have_text(\"Contact Topic 2\") }\n  end\n\n  context \"when there are no volunteers\" do\n    before do\n      sign_in supervisor\n      assign :supervisor, supervisor\n      assign :inactive_volunteers, []\n      @inactive_messages = InactiveMessagesService.new(supervisor).inactive_messages\n      render template: \"supervisor_mailer/weekly_digest\"\n    end\n\n    it { expect(rendered).to have_text(\"You have no volunteers with assigned cases at the moment. When you do, you will see their status here.\") }\n  end\n\n  context \"when there are volunteers but no contacts\" do\n    before do\n      supervisor.volunteers << volunteer\n      inactive_volunteer.update active: false\n      supervisor.volunteers_ever_assigned << inactive_volunteer\n      volunteer.casa_cases << casa_case\n      sign_in supervisor\n      assign :supervisor, supervisor\n      assign :inactive_volunteers, []\n      @inactive_messages = InactiveMessagesService.new(supervisor).inactive_messages\n      render template: \"supervisor_mailer/weekly_digest\"\n    end\n\n    it { expect(rendered).to have_text(\"No contact attempts were logged for this week.\") }\n  end\n\n  context \"when a volunteer has been reassigned to a new supervisor\" do\n    before do\n      supervisor.volunteers << volunteer\n      volunteer.casa_cases << casa_case\n\n      # reassign volunteer\n      volunteer.supervisor_volunteer.update(is_active: false)\n      other_supervisor.volunteers << volunteer\n      volunteer.supervisor_volunteer.update(is_active: true)\n\n      sign_in supervisor\n      assign :supervisor, supervisor\n      assign :inactive_volunteers, []\n      render template: \"supervisor_mailer/weekly_digest\"\n    end\n\n    let(:other_supervisor) { create(:supervisor) }\n\n    it { expect(rendered).to include(\"The following volunteers have been unassigned from you\", volunteer.display_name) }\n  end\n\n  context \"when a volunteer has been unassigned\" do\n    before do\n      supervisor.volunteers << volunteer\n      sign_in supervisor\n      volunteer.supervisor_volunteer.update(is_active: false)\n\n      new_supervisor.volunteers << volunteer\n      volunteer.supervisor_volunteer.update(is_active: true)\n      assign :supervisor, supervisor\n      assign :inactive_volunteers, []\n\n      render template: \"supervisor_mailer/weekly_digest\"\n    end\n\n    let(:new_supervisor) { create(:supervisor) }\n\n    it { expect(rendered).to have_text(\"The following volunteers have been unassigned from you\") }\n    it { expect(rendered).to have_text(\"- #{volunteer.display_name}\") }\n  end\n\n  context \"when a volunteer unassigned and has not been assigned to a new supervisor\" do\n    before do\n      supervisor.volunteers << volunteer\n      sign_in supervisor\n      assign :supervisor, supervisor\n      assign :inactive_volunteers, []\n      volunteer.supervisor_volunteer.update(is_active: false)\n      @inactive_messages = []\n      render template: \"supervisor_mailer/weekly_digest\"\n    end\n\n    it { expect(rendered).to have_text(\"The following volunteers have been unassigned from you\") }\n    it { expect(rendered).to have_text(\"- #{volunteer.display_name}\") }\n    it { expect(rendered).to have_text(\"(not assigned to a new supervisor)\") }\n  end\n\n  context \"when a volunteer has not recently signed in, within 30 days\" do\n    let(:volunteer) { create(:volunteer, casa_org: organization, last_sign_in_at: 39.days.ago) }\n\n    before do\n      supervisor.volunteers << volunteer\n      sign_in supervisor\n      assign :supervisor, supervisor\n      assign :inactive_volunteers, supervisor.inactive_volunteers\n      render template: \"supervisor_mailer/weekly_digest\"\n    end\n\n    it { expect(rendered).to have_text(\"The following volunteers have not signed in or created case contacts in the last 30 days\") }\n    it { expect(rendered).to have_text(\"- #{volunteer.display_name}\") }\n  end\nend\n"
  },
  {
    "path": "spec/views/supervisors/edit.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"supervisors/edit\", type: :view do\n  before do\n    admin = build_stubbed :casa_admin\n    enable_pundit(view, admin)\n    allow(view).to receive(:current_user).and_return(admin)\n    allow(view).to receive(:current_organization).and_return(admin.casa_org)\n  end\n\n  it \"displays the 'Unassign' button next to volunteer being currently supervised by the supervisor\" do\n    supervisor = create :supervisor\n    volunteer = create :volunteer, supervisor: supervisor\n\n    assign :supervisor, supervisor\n    assign :all_volunteers_ever_assigned, [volunteer]\n    assign :available_volunteers, []\n\n    render template: \"supervisors/edit\"\n\n    expect(rendered).to include(unassign_supervisor_volunteer_path(volunteer))\n    expect(rendered).not_to include(\"Unassigned\")\n  end\n\n  it \"does not display the 'Unassign' button next to volunteer no longer supervised by the supervisor\" do\n    casa_org = create :casa_org\n    supervisor = create :supervisor, casa_org: casa_org\n    volunteer = create :volunteer, casa_org: casa_org\n    create :supervisor_volunteer, :inactive, supervisor: supervisor, volunteer: volunteer\n\n    assign :supervisor, supervisor\n    assign :supervisor_has_unassigned_volunteers, true\n    assign :all_volunteers_ever_assigned, [volunteer]\n    assign :available_volunteers, []\n\n    render template: \"supervisors/edit\"\n\n    expect(rendered).not_to include(unassign_supervisor_volunteer_path(volunteer))\n    expect(rendered).to include(\"Unassigned\")\n  end\n\n  describe \"invite and login info\" do\n    let(:volunteer) { create :volunteer }\n    let(:supervisor) { build_stubbed :supervisor }\n    let(:admin) { build_stubbed :casa_admin }\n\n    it \"shows for a supervisor editig a supervisor\" do\n      enable_pundit(view, supervisor)\n      allow(view).to receive(:current_user).and_return(supervisor)\n      allow(view).to receive(:current_organization).and_return(supervisor.casa_org)\n      assign :supervisor, supervisor\n      assign :all_volunteers_ever_assigned, [volunteer]\n      assign :available_volunteers, []\n\n      render template: \"supervisors/edit\"\n\n      expect(rendered).to have_text(\"Added to system \")\n      expect(rendered).to have_text(\"Invitation email sent \\n  never\")\n      expect(rendered).to have_text(\"Last logged in\")\n      expect(rendered).to have_text(\"Invitation accepted \\n  never\")\n      expect(rendered).to have_text(\"Password reset last sent \\n  never\")\n    end\n\n    it \"shows profile info form fields as editable for a supervisor editing their own profile\" do\n      enable_pundit(view, supervisor)\n      allow(view).to receive(:current_organization).and_return(supervisor.casa_org)\n      assign :supervisor, supervisor\n      assign :all_volunteers_ever_assigned, [volunteer]\n      assign :available_volunteers, []\n\n      render template: \"supervisors/edit\"\n\n      expect(rendered).to have_field(\"supervisor_email\")\n      expect(rendered).to have_field(\"supervisor_display_name\")\n      expect(rendered).to have_field(\"supervisor_phone_number\")\n    end\n\n    it \"shows profile info form fields as disabled for a supervisor editing another supervisor\" do\n      enable_pundit(view, supervisor)\n      allow(view).to receive(:current_organization).and_return(supervisor.casa_org)\n      assign :supervisor, build_stubbed(:supervisor, casa_org: build_stubbed(:casa_org))\n      assign :all_volunteers_ever_assigned, [volunteer]\n      assign :available_volunteers, []\n\n      render template: \"supervisors/edit\"\n\n      expect(rendered).not_to have_field(\"supervisor_email\")\n      expect(rendered).not_to have_field(\"supervisor_display_name\")\n      expect(rendered).not_to have_field(\"supervisor_phone_number\")\n    end\n\n    it \"shows for an admin editing a supervisor\" do\n      enable_pundit(view, supervisor)\n      allow(view).to receive(:current_user).and_return(admin)\n\n      assign :supervisor, supervisor\n      assign :all_volunteers_ever_assigned, [volunteer]\n      assign :available_volunteers, []\n\n      render template: \"supervisors/edit\"\n\n      expect(rendered).to have_text(\"Added to system \")\n      expect(rendered).to have_text(\"Invitation email sent \\n  never\")\n      expect(rendered).to have_text(\"Last logged in\")\n      expect(rendered).to have_text(\"Invitation accepted \\n  never\")\n      expect(rendered).to have_text(\"Password reset last sent \\n  never\")\n    end\n  end\n\n  describe \"'Change to Admin' button\" do\n    let(:supervisor) { build_stubbed :supervisor }\n\n    before do\n      assign :supervisor, supervisor\n      assign :available_volunteers, []\n    end\n\n    it \"shows for an admin editing a supervisor\" do\n      render template: \"supervisors/edit\"\n\n      expect(rendered).to have_text(\"Change to Admin\")\n      expect(rendered).to include(change_to_admin_supervisor_path(supervisor))\n    end\n\n    it \"does not show for a supervisor editing a supervisor\" do\n      enable_pundit(view, supervisor)\n      allow(view).to receive(:current_user).and_return(supervisor)\n      allow(view).to receive(:current_organization).and_return(supervisor.casa_org)\n      render template: \"supervisors/edit\"\n\n      expect(rendered).not_to have_text(\"Change to Admin\")\n      expect(rendered).not_to include(change_to_admin_supervisor_path(supervisor))\n    end\n  end\nend\n"
  },
  {
    "path": "spec/views/supervisors/index.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"supervisors/index\", type: :view do\n  context \"when logged in as an admin\" do\n    it \"can access the 'New Supervisor' button\" do\n      user = create(:casa_admin)\n      enable_pundit(view, user)\n      casa_cases = create_list(:casa_case, 2, court_dates: [])\n      assign :casa_cases, casa_cases\n      assign :supervisors, []\n      assign :available_volunteers, []\n\n      sign_in user\n\n      render template: \"supervisors/index\"\n\n      expect(rendered).to have_link(\"New Supervisor\", href: new_supervisor_path)\n    end\n\n    it \"show casa_cases list\" do\n      user = create(:casa_admin)\n      enable_pundit(view, user)\n      casa_case1 = create(:casa_case,\n        case_number: \"123\",\n        active: true,\n        birth_month_year_youth: \"1999-01-01\".to_date)\n      casa_case2 = create(:casa_case,\n        case_number: \"456\",\n        active: false,\n        birth_month_year_youth: \"2024-01-01\".to_date)\n      assign :casa_cases, [casa_case1, casa_case2]\n      assign :supervisors, []\n      assign :available_volunteers, []\n\n      sign_in user\n      render template: \"supervisors/index\"\n\n      expect(rendered).to have_text \"123\"\n      expect(rendered).to have_text \"Active\"\n      expect(rendered).to have_text \"Yes #{CasaCase::TRANSITION_AGE_YOUTH_ICON}\"\n\n      expect(rendered).to have_text \"456\"\n      expect(rendered).to have_text \"Inactive\"\n      expect(rendered).to have_text \"No #{CasaCase::NON_TRANSITION_AGE_YOUTH_ICON}\"\n    end\n\n    context \"when a supervisor has volunteers who have and have not submitted a case contact in 14 days\" do\n      it \"shows positive and negative numbers\" do\n        supervisor = create(:supervisor)\n        enable_pundit(view, supervisor)\n        create(:volunteer, :with_cases_and_contacts, supervisor: supervisor)\n        create(:volunteer, :with_casa_cases, supervisor: supervisor)\n\n        assign :supervisors, [supervisor]\n        assign :casa_cases, []\n        assign :available_volunteers, []\n\n        sign_in supervisor\n        render template: \"supervisors/index\"\n\n        parsed_html = Nokogiri.HTML5(rendered)\n\n        expect(parsed_html.css(\"#supervisors .success-bg\").length).to eq(1)\n        expect(parsed_html.css(\"#supervisors .danger-bg\").length).to eq(1)\n      end\n\n      it \"accurately displays the number of active and inactive volunteers per supervisor\" do\n        user = create(:casa_admin)\n        enable_pundit(view, user)\n        supervisor = create(:supervisor)\n        create_list(:volunteer, 2, :with_cases_and_contacts, supervisor: supervisor)\n        create(:volunteer, :with_casa_cases, supervisor: supervisor)\n        casa_cases = create_list(:casa_case, 2, court_dates: [])\n\n        assign :supervisors, [supervisor]\n        assign :casa_cases, casa_cases\n        assign :available_volunteers, []\n\n        sign_in user\n        render template: \"supervisors/index\"\n\n        parsed_html = Nokogiri.HTML5(rendered)\n\n        active_bar = parsed_html.css(\"#supervisors .success-bg\")\n        inactive_bar = parsed_html.css(\"#supervisors .danger-bg\")\n        active_flex = active_bar.inner_html\n        inactive_flex = inactive_bar.inner_html\n        active_content = active_bar.children[0].text.strip\n        inactive_content = inactive_bar.children[0].text.strip\n\n        expect(active_flex).to eq(active_content)\n        expect(inactive_flex).to eq(inactive_content)\n        expect(active_flex.to_i).to eq(2)\n        expect(inactive_flex.to_i).to eq(1)\n      end\n    end\n\n    context \"when a supervisor only has volunteers who have not submitted a case contact in 14 days\" do\n      it \"omits the attempted contact stat bar\" do\n        user = create(:casa_admin)\n        enable_pundit(view, user)\n        supervisor = create(:supervisor)\n        create(:volunteer, :with_casa_cases, supervisor: supervisor)\n        casa_cases = create_list(:casa_case, 2, court_dates: [])\n\n        assign :supervisors, [supervisor]\n        assign :casa_cases, casa_cases\n        assign :available_volunteers, []\n\n        sign_in user\n        render template: \"supervisors/index\"\n\n        parsed_html = Nokogiri.HTML5(rendered)\n\n        expect(parsed_html.css(\"#supervisors .success-bg\").length).to eq(0)\n        expect(parsed_html.css(\"#supervisors .danger-bg\").length).to eq(1)\n      end\n    end\n\n    context \"when a supervisor only has volunteers who have submitted a case contact in 14 days\" do\n      it \"shows the end of the attempted contact bar instead of the no attempted contact bar\" do\n        user = create(:casa_admin)\n        enable_pundit(view, user)\n        supervisor = create(:supervisor)\n        create(:volunteer, :with_cases_and_contacts, supervisor: supervisor)\n        casa_cases = create_list(:casa_case, 2, court_dates: [])\n\n        assign :supervisors, [supervisor]\n        assign :casa_cases, casa_cases\n        assign :available_volunteers, []\n\n        sign_in user\n        render template: \"supervisors/index\"\n\n        parsed_html = Nokogiri.HTML5(rendered)\n\n        expect(parsed_html.css(\"#supervisors .success-bg\").length).to eq(1)\n        expect(parsed_html.css(\"#supervisors .danger-bg\").length).to eq(0)\n      end\n    end\n\n    context \"when a supervisor does not have volunteers\" do\n      it \"shows a no assigned volunteers message instead of attempted and no attempted contact bars\" do\n        user = create(:casa_admin)\n        enable_pundit(view, user)\n        supervisor = create(:supervisor)\n        casa_cases = create_list(:casa_case, 2, court_dates: [])\n\n        assign :supervisors, [supervisor]\n        assign :casa_cases, casa_cases\n        assign :available_volunteers, []\n\n        sign_in user\n        render template: \"supervisors/index\"\n\n        parsed_html = Nokogiri.HTML5(rendered)\n\n        expect(parsed_html.css(\"#supervisors .success-bg\").length).to eq(0)\n        expect(parsed_html.css(\"#supervisors .danger-bg\").length).to eq(0)\n        expect(parsed_html.css(\"#supervisors .bg-secondary\").length).to eq(1)\n      end\n    end\n  end\n\n  context \"when logged in as a supervisor\" do\n    it \"cannot access the 'New Supervisor' button\" do\n      user = create(:supervisor)\n      enable_pundit(view, user)\n      casa_cases = create_list(:casa_case, 2, court_dates: [])\n\n      assign :casa_cases, casa_cases\n      assign :supervisors, []\n      assign :available_volunteers, []\n\n      sign_in user\n      render template: \"supervisors/index\"\n\n      expect(rendered).not_to have_link(\"New Supervisor\", href: new_supervisor_path)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/views/supervisors/new.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"supervisors/new\", type: :view do\n  subject { render template: \"supervisors/new\" }\n\n  before do\n    assign :supervisor, Supervisor.new\n  end\n\n  context \"while signed in as admin\" do\n    before do\n      sign_in_as_admin\n    end\n  end\nend\n"
  },
  {
    "path": "spec/views/templates/email_templates_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.shared_examples \"compares opening and closing tags\" do\n  include TemplateHelper\n\n  it \"validates html tags\" do\n    file_content = File.read(Rails.root.join(file_path))\n    tags_are_equal = validate_closing_tags_exist(file_content)\n\n    expect(tags_are_equal).to be true\n  end\nend\n\nRSpec.describe \"casa_admin_mailer\", type: :view do\n  describe \"should validate that account_setup email template is valid\" do\n    let(:file_path) { \"app/views/casa_admin_mailer/account_setup.html.erb\" }\n\n    it_behaves_like \"compares opening and closing tags\"\n  end\n\n  describe \"should validate that deactivation email template is valid\" do\n    let(:file_path) { \"app/views/casa_admin_mailer/deactivation.html.erb\" }\n\n    it_behaves_like \"compares opening and closing tags\"\n  end\nend\n\nRSpec.describe \"devise\", type: :view do\n  describe \"should validate that confirmation_instructions email template is valid\" do\n    let(:file_path) { \"app/views/devise/mailer/confirmation_instructions.html.erb\" }\n\n    it_behaves_like \"compares opening and closing tags\"\n  end\n\n  describe \"should validate that email_changed email template is valid\" do\n    let(:file_path) { \"app/views/devise/mailer/email_changed.html.erb\" }\n\n    it_behaves_like \"compares opening and closing tags\"\n  end\n\n  describe \"should validate that invitation_instruction email template is valid\" do\n    let(:file_path) { \"app/views/devise/mailer/invitation_instructions.html.erb\" }\n\n    it_behaves_like \"compares opening and closing tags\"\n  end\n\n  describe \"should validate that password_change email template is valid\" do\n    let(:file_path) { \"app/views/devise/mailer/password_change.html.erb\" }\n\n    it_behaves_like \"compares opening and closing tags\"\n  end\n\n  describe \"should validate that reset_password_instructions email template is valid\" do\n    let(:file_path) { \"app/views/devise/mailer/reset_password_instructions.html.erb\" }\n\n    it_behaves_like \"compares opening and closing tags\"\n  end\n\n  describe \"should validate that unlock_instructions email template is valid\" do\n    let(:file_path) { \"app/views/devise/mailer/unlock_instructions.html.erb\" }\n\n    it_behaves_like \"compares opening and closing tags\"\n  end\nend\n\nRSpec.describe \"supervisor_mailer\", type: :view do\n  describe \"should validate that account_setup email template is valid\" do\n    let(:file_path) { \"app/views/supervisor_mailer/account_setup.html.erb\" }\n\n    it_behaves_like \"compares opening and closing tags\"\n  end\n\n  describe \"should validate that weekly_digest email template is valid\" do\n    let(:file_path) { \"app/views/supervisor_mailer/weekly_digest.html.erb\" }\n\n    it_behaves_like \"compares opening and closing tags\"\n  end\nend\n\nRSpec.describe \"user_mailer\", type: :view do\n  describe \"should validate that password_changed_reminder email template is valid\" do\n    let(:file_path) { \"app/views/user_mailer/password_changed_reminder.html.erb\" }\n\n    it_behaves_like \"compares opening and closing tags\"\n  end\nend\n\nRSpec.describe \"volunteer_mailer\", type: :view do\n  describe \"should validate that account_setup email template is valid\" do\n    let(:file_path) { \"app/views/volunteer_mailer/account_setup.html.erb\" }\n\n    it_behaves_like \"compares opening and closing tags\"\n  end\n\n  describe \"should validate that case_contacts_reminder email template is valid\" do\n    let(:file_path) { \"app/views/volunteer_mailer/case_contacts_reminder.html.erb\" }\n\n    it_behaves_like \"compares opening and closing tags\"\n  end\n\n  describe \"should validate that court_report_reminder email template is valid\" do\n    let(:file_path) { \"app/views/volunteer_mailer/court_report_reminder.html.erb\" }\n\n    it_behaves_like \"compares opening and closing tags\"\n  end\nend\n"
  },
  {
    "path": "spec/views/volunteers/edit.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"volunteers/edit\", type: :view do\n  it \"allows an administrator to edit a volunteers email address\" do\n    administrator = build_stubbed :casa_admin\n    enable_pundit(view, administrator)\n    org = create :casa_org\n    volunteer = create :volunteer, casa_org: org\n    allow(view).to receive(:current_user).and_return(administrator)\n    allow(view).to receive(:current_organization).and_return(administrator.casa_org)\n\n    assign :volunteer, volunteer\n    assign :supervisors, []\n\n    render template: \"volunteers/edit\"\n\n    expect(rendered).not_to have_field(\"volunteer_email\", readonly: true)\n  end\n\n  it \"allows an administrator to edit a volunteers phone number\" do\n    administrator = build_stubbed :casa_admin\n    enable_pundit(view, administrator)\n    org = create :casa_org\n    volunteer = create :volunteer, casa_org: org\n    allow(view).to receive(:current_user).and_return(administrator)\n    allow(view).to receive(:current_organization).and_return(administrator.casa_org)\n\n    assign :volunteer, volunteer\n    assign :supervisors, []\n\n    render template: \"volunteers/edit\"\n\n    expect(rendered).not_to have_field(\"volunteer_email\", readonly: true)\n  end\n\n  it \"allows an administrator to edit a volunteers date of birth\" do\n    administrator = build_stubbed :casa_admin\n    enable_pundit(view, administrator)\n    org = create :casa_org\n    volunteer = create :volunteer, casa_org: org\n    allow(view).to receive(:current_user).and_return(administrator)\n    allow(view).to receive(:current_organization).and_return(administrator.casa_org)\n\n    assign :volunteer, volunteer\n    assign :supervisors, []\n\n    render template: \"volunteers/edit\"\n\n    expect(rendered).not_to have_field(\"volunteer_date_of_birth\", readonly: true)\n    expect(rendered).to have_field(\"volunteer_date_of_birth\", readonly: false)\n  end\n\n  it \"allows a supervisor to edit a volunteers email address\" do\n    supervisor = build_stubbed :supervisor\n    enable_pundit(view, supervisor)\n    org = create :casa_org\n    volunteer = create :volunteer, casa_org: org\n    allow(view).to receive(:current_user).and_return(supervisor)\n    allow(view).to receive(:current_organization).and_return(supervisor.casa_org)\n\n    assign :volunteer, volunteer\n    assign :supervisors, []\n\n    render template: \"volunteers/edit\"\n\n    expect(rendered).not_to have_field(\"volunteer_email\", readonly: true)\n  end\n\n  it \"allows a supervisor in the same org to edit a volunteers phone number\" do\n    org = create :casa_org\n    supervisor = build_stubbed :supervisor, casa_org: org\n    enable_pundit(view, supervisor)\n    volunteer = create :volunteer, casa_org: org\n    allow(view).to receive(:current_user).and_return(supervisor)\n    allow(view).to receive(:current_organization).and_return(supervisor.casa_org)\n\n    assign :volunteer, volunteer\n    assign :supervisors, []\n\n    render template: \"volunteers/edit\"\n\n    expect(rendered).to have_field(\"volunteer_phone_number\")\n  end\n\n  it \"allows a supervisor in the same org to edit a volunteers date of birth\" do\n    org = create :casa_org\n    supervisor = build_stubbed :supervisor, casa_org: org\n    enable_pundit(view, supervisor)\n    volunteer = create :volunteer, casa_org: org\n    allow(view).to receive(:current_user).and_return(supervisor)\n    allow(view).to receive(:current_organization).and_return(supervisor.casa_org)\n\n    assign :volunteer, volunteer\n    assign :supervisors, []\n\n    render template: \"volunteers/edit\"\n\n    expect(rendered).not_to have_field(\"volunteer_date_of_birth\", readonly: true)\n    expect(rendered).to have_field(\"volunteer_date_of_birth\", readonly: false)\n  end\n\n  it \"does not allow a supervisor from a different org to edit a volunteers phone number\" do\n    different_supervisor = build_stubbed :supervisor\n    enable_pundit(view, different_supervisor)\n    org = create :casa_org\n    volunteer = create :volunteer, casa_org: org\n    allow(view).to receive(:current_user).and_return(different_supervisor)\n    allow(view).to receive(:current_organization).and_return(different_supervisor.casa_org)\n\n    assign :volunteer, volunteer\n    assign :supervisors, []\n\n    render template: \"volunteers/edit\"\n\n    expect(rendered).not_to have_field(\"volunteer_phone_number\")\n  end\n\n  it \"does not allow a supervisor from a different org to edit a volunteers date of birth\" do\n    different_supervisor = build_stubbed :supervisor\n    enable_pundit(view, different_supervisor)\n    org = create :casa_org\n    volunteer = create :volunteer, casa_org: org\n    allow(view).to receive(:current_user).and_return(different_supervisor)\n    allow(view).to receive(:current_organization).and_return(different_supervisor.casa_org)\n\n    assign :volunteer, volunteer\n    assign :supervisors, []\n\n    render template: \"volunteers/edit\"\n\n    expect(rendered).not_to have_field(\"volunteer_date_of_birth\", readonly: false)\n  end\n\n  it \"shows invite and login info\" do\n    supervisor = build_stubbed :supervisor\n    enable_pundit(view, supervisor)\n    org = create :casa_org\n    volunteer = create :volunteer, casa_org: org\n    allow(view).to receive(:current_user).and_return(supervisor)\n    allow(view).to receive(:current_organization).and_return(supervisor.casa_org)\n\n    assign :volunteer, volunteer\n    assign :supervisors, []\n\n    render template: \"volunteers/edit\"\n\n    expect(rendered).to have_text(\"Added to system \")\n    expect(rendered).to have_text(\"Invitation email sent \\n  never\")\n    expect(rendered).to have_text(\"Last logged in\")\n    expect(rendered).to have_text(\"Invitation accepted \\n  never\")\n    expect(rendered).to have_text(\"Password reset last sent \\n  never\")\n    expect(rendered).to have_text(\"Learning Hours This Year\\n    0h 0min\")\n  end\n\n  context \"the user has requested to reset their password\" do\n    describe \"shows resend invitation\" do\n      it \"allows an administrator resend invitation to a volunteer\" do\n        volunteer = create :volunteer\n        supervisor = build_stubbed :supervisor\n        admin = build_stubbed :casa_admin\n\n        enable_pundit(view, supervisor)\n        allow(view).to receive(:current_user).and_return(admin)\n        allow(view).to receive(:current_organization).and_return(admin.casa_org)\n\n        assign :volunteer, volunteer\n        assign :supervisors, []\n\n        render template: \"volunteers/edit\"\n\n        expect(rendered).to have_content(\"Resend Invitation\")\n      end\n\n      it \"allows a supervisor to resend invitation to a volunteer\" do\n        volunteer = create :volunteer\n        supervisor = build_stubbed :supervisor\n\n        enable_pundit(view, supervisor)\n        allow(view).to receive(:current_user).and_return(supervisor)\n        allow(view).to receive(:current_organization).and_return(supervisor.casa_org)\n\n        assign :volunteer, volunteer\n        assign :supervisors, []\n\n        render template: \"volunteers/edit\"\n\n        expect(rendered).to have_content(\"Resend Invitation\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/views/volunteers/index.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"volunteers\", type: :view do\n  subject { render template: \"volunteers/index\" }\n\n  let(:user) { build_stubbed :volunteer }\n  let(:volunteer) { create :volunteer }\n\n  before do\n    enable_pundit(view, user)\n    allow(view).to receive(:current_user).and_return(user)\n    allow(view).to receive(:current_organization).and_return user.casa_org\n    assign :volunteers, [volunteer]\n    sign_in user\n  end\n\n  context \"when NOT signed in as an admin\" do\n    it { is_expected.not_to have_selector(\"a\", text: \"New Volunteer\") }\n  end\n\n  context \"when signed in as an admin\" do\n    let(:user) { build_stubbed :casa_admin }\n\n    it { is_expected.to have_selector(\"a\", text: \"New Volunteer\") }\n  end\n\n  describe \"supervisor's dropdown\" do\n    let!(:supervisor_volunteer) { create(:supervisor_volunteer, volunteer: volunteer, supervisor: supervisor) }\n\n    context \"when the supervisor is active\" do\n      let(:supervisor) { build(:supervisor) }\n\n      it \"shows up in the supervisor dropdown\" do\n        expect(subject).to include(CGI.escapeHTML(supervisor.display_name))\n      end\n    end\n\n    context \"when the supervisor is not active\" do\n      let(:supervisor) { build(:supervisor, active: false) }\n\n      it \"doesn't show up in the dropdown\" do\n        expect(subject).not_to include(supervisor.display_name)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/views/volunteers/new.html.erb_spec.rb",
    "content": "require \"rails_helper\"\n\nRSpec.describe \"volunteers/new\", type: :view do\n  subject { render template: \"volunteers/new\" }\n\n  before do\n    assign :volunteer, Volunteer.new\n  end\n\n  context \"while signed in as admin\" do\n    before do\n      sign_in_as_admin\n    end\n  end\nend\n"
  },
  {
    "path": "storage/.keep",
    "content": ""
  },
  {
    "path": "swagger/v1/swagger.yaml",
    "content": "---\nopenapi: 3.0.1\ninfo:\n  title: API V1\n  version: v1\ncomponents:\n  schemas:\n    login_success:\n      type: object\n      properties:\n        api_token:\n          type: string\n        refresh_token:\n          type: string\n        user:\n          id:\n            type: integer\n          display_name:\n            type: string\n          email:\n            type: string\n          token_expires_at:\n            type: datetime\n          refresh_token_expires_at:\n            type: datetime\n    login_failure:\n      type: object\n      properties:\n        message:\n          type: string\n    sign_out:\n      type: object\n      properties:\n        message:\n          type: string\npaths:\n  \"/api/v1/users/sign_in\":\n    post:\n      summary: Signs in a user\n      tags:\n      - Sessions\n      parameters: []\n      responses:\n        '201':\n          description: user signed in\n          content:\n            application/json:\n              schema:\n                \"$ref\": \"#/components/schemas/login_success\"\n        '401':\n          description: invalid credentials\n          content:\n            application/json:\n              schema:\n                \"$ref\": \"#/components/schemas/login_failure\"\n      requestBody:\n        content:\n          application/json:\n            schema:\n              type: object\n              properties:\n                email:\n                  type: string\n                password:\n                  type: string\n              required:\n              - email\n              - password\n  \"/api/v1/users/sign_out\":\n    delete:\n      summary: Signs out a user\n      tags:\n      - Sessions\n      parameters:\n      - name: Authorization\n        in: header\n        required: true\n        schema:\n          type: string\n      responses:\n        '200':\n          description: user signed out\n          content:\n            application/json:\n              schema:\n                \"$ref\": \"#/components/schemas/sign_out\"\n        '401':\n          description: unauthorized\n          content:\n            application/json:\n              schema:\n                \"$ref\": \"#/components/schemas/sign_out\"\nservers:\n- url: https://{defaultHost}\n  variables:\n    defaultHost:\n      default: www.example.com\n"
  },
  {
    "path": "vendor/.keep",
    "content": ""
  }
]