Skip to content

[Settings]: Company panel — assign Email Templates #239

Closed

Description

@nielsdrost7

SMART Plan — As an Admin, in the company Panel I want to be able to assign Email Templates to that single company

Specific

Within the Company panel (/{tenant}), add an action (e.g. on the Settings page or as a dedicated relation manager / select field) that allows the customer_admin to choose a default EmailTemplate for their company. The selected template is stored on the company record (e.g. companies.default_email_template_id FK column) or via the company_email_template pivot with a is_default flag.

Measurable

  • A customer_admin can open the Settings page (or Email Template assignment page) in the company panel.
  • A select field shows all available email templates for the company.
  • Saving the form persists the chosen template association.
  • The association is retrievable via $company->defaultEmailTemplate.

Achievable

  1. Add default_email_template_id column (nullable FK) to companies table via migration — Modules/Core/Database/Migrations/
  2. Add defaultEmailTemplate() BelongsTo on Company model — Modules/Core/Models/Company.php
  3. Add a Select field for default_email_template_id on the company Settings schema — Modules/Core/Filament/Company/Pages/Settings.php (or create this page)
  4. Scope the select options to templates available to this company.
  5. Protect the field / page with a customer_admin role check.

Relevant

Companies send invoices and quotes by email. Letting a customer_admin choose the default email template (subject line, greeting, footer) for outbound mail ensures brand consistency without requiring admin intervention on each document.

Time-bound

Sprint 6.


Arrange · Act · Assert

// Modules/Core/Tests/Feature/Company/CompanyEmailTemplateAssignmentTest.php

#[Test]
public function it_saves_default_email_template_for_company(): void
{
    /* Arrange */
    $template = EmailTemplate::factory()->for($this->company)->create();

    /* Act */
    $response = $this->testLivewire(CompanySettings::class)
        ->fillForm(['default_email_template_id' => $template->id])
        ->call('save');

    /* Assert */
    $response->assertHasNoErrors();
    $this->assertDatabaseHas('companies', [
        'id'                        => $this->company->id,
        'default_email_template_id' => $template->id,
    ]);
}

Activity

  1. coderabbitai commented on Dec 28, 2025

    @coderabbitai
    Contributor

    📝 CodeRabbit Plan Mode

    Generate an implementation plan and prompts that you can use with your favorite coding agent.

    • Create Plan
    Examples

    🔗 Similar Issues

    Related Issues

    👤 Suggested Assignees

    🧪 Issue enrichment is currently in open beta.

    You can configure auto-planning by selecting labels in the issue_enrichment configuration.

    To disable automatic issue enrichment, add the following to your .coderabbit.yaml:

    issue_enrichment:
      auto_enrich:
        enabled: false

    💬 Have feedback or questions? Drop into our discord!

  2. added this to the v2.0.0-alpha milestone on Dec 28, 2025
  3. nielsdrost7 commented on Jan 2, 2026

    @nielsdrost7
    CollaboratorAuthor

    @coderabbitai make pull-request with AAA test Scenario's: Arrange, Act, Assert. And also make sure you test edge cases

  4. coderabbitai commented on Jan 2, 2026

    @coderabbitai
    Contributor

    Coding Plan

    Approach

    Enable admins to assign email templates to companies through the Admin panel's Company management interface. This implementation adds a many-to-many relationship between companies and email templates, preserving existing ownership while allowing templates to be shared across companies. Companies will be able to access their own templates, system-level templates, and templates explicitly assigned to them.

    Observations

    This is a modular Laravel application with Filament admin panels. The Core module manages companies, users, and email templates. The application uses a multi-tenant architecture with three Filament panels (Admin, Company, User), where the Company panel provides tenant-scoped access. Email templates are currently company-owned resources stored in the email_templates table with a company_id foreign key. The CompanyResource in the Admin panel currently has no relationship managers but uses delegated form/table schemas. The ReportBuilder module demonstrates a pattern for system-level templates using an is_system boolean flag.

    Assumptions
    Assumption 1: Template Availability Scope

    Options Considered:

    • Only allow assignment of company-owned templates between companies
    • Allow assignment of both system templates and company-owned templates
    • Create a template library with system templates that can be assigned

    Chosen Option: 2

    Rationale: This provides maximum flexibility while following the established is_system pattern from ReportBuilder, allowing admins to share both system templates and company-specific templates across companies.

    Assumption 2: Template Ownership and Assignment Relationship

    Options Considered:

    • Add a many-to-many relationship while keeping the existing company_id for ownership
    • Remove company_id and make all templates use only many-to-many relationships
    • Use a polymorphic relationship for flexible template assignment

    Chosen Option: 1

    Rationale: Preserve the existing company_id field to maintain ownership and add a many-to-many pivot table for assignments. This maintains backward compatibility and clearly distinguishes ownership from access.

    Assumption 3: Admin UI Location for Template Assignment

    Options Considered:

    • Add a relationship manager on the CompanyResource edit page
    • Add a custom action on the email templates list/table
    • Create a separate template assignment page

    Chosen Option: 1

    Rationale: Follow Filament conventions by adding an EmailTemplatesRelationManager to CompanyResource, providing a clear, contextual interface for managing template assignments per company.

    💡 User Tips

    Regenerate the plan with different choices with @coderabbitai <feedback>.

    Implementation Steps

    Phase 1: Database Schema and Model Relationships

    Add support for template assignment relationships while preserving existing company ownership structure.

    Task 1: Add is_system Flag to Email Templates

    Add capability to mark templates as system-level templates that can be shared across companies.

    • Create migration in Modules/Core/Database/Migrations/ to add is_system boolean column to email_templates table (default false)
    • Update Modules/Core/Models/EmailTemplate.php to cast is_system as boolean
    • Add isSystem() accessor method following the ReportBuilder pattern
    Task 2: Create Template Assignment Pivot Table

    Establish many-to-many relationship infrastructure for assigning templates to companies.

    • Create migration in Modules/Core/Database/Migrations/ for company_email_template pivot table with columns: company_id, email_template_id, assigned_at timestamp
    • Add foreign key constraints with cascade delete behavior
    • Add unique constraint on ['company_id', 'email_template_id'] to prevent duplicate assignments
    Task 3: Add Assignment Relationships to Models

    Connect companies and templates through the new many-to-many relationship.

    • Update Modules/Core/Models/EmailTemplate.php to add assignedCompanies() BelongsToMany relationship using company_email_template pivot
    • Update Modules/Core/Models/Company.php to add assignedEmailTemplates() BelongsToMany relationship using company_email_template pivot
    • Add scopeAccessible() method to EmailTemplate model that returns templates owned by OR assigned to a given company using where('company_id', $companyId)->orWhereHas('assignedCompanies')
    🤖 Prompt for AI agents
    Set up database schema and model relationships for template assignments. This
    phase establishes the foundation for sharing templates across companies while
    maintaining ownership.
    
    Add `is_system` boolean column to `email_templates` table with default false
    Update EmailTemplate model to cast `is_system` as boolean and add `isSystem()`
    accessor
    
    Create `company_email_template` pivot table with `company_id`,
    `email_template_id`, `assigned_at` columns
    Add foreign key constraints with cascade delete
    Add unique constraint on company_id and email_template_id
    
    Add `assignedCompanies()` BelongsToMany relationship to EmailTemplate model
    Add `assignedEmailTemplates()` BelongsToMany relationship to Company model
    Add `scopeAccessible()` method to EmailTemplate that returns owned, assigned,
    and system templates for a company
    

    Phase 2: Template Management Service Layer

    Extend service layer to handle template assignment operations while maintaining existing template creation functionality.

    Extend EmailTemplateService for Assignments

    Add template assignment business logic to the existing service.

    • Update Modules/Core/Services/EmailTemplateService.php to add assignTemplatesToCompany(Company $company, array $templateIds) method
    • Add unassignTemplatesFromCompany(Company $company, array $templateIds) method
    • Add getAccessibleTemplates(Company $company) method that retrieves both owned and assigned templates using the scopeAccessible() scope
    • Ensure assignment methods validate that templates are either system templates or belong to other companies (not self-assignment)
    🤖 Prompt for AI agents
    Extend EmailTemplateService with assignment management functionality. This phase
    adds business logic for assigning and unassigning templates to companies.
    
    Add `assignTemplatesToCompany(Company $company, array $templateIds)` method to
    EmailTemplateService
    Add `unassignTemplatesFromCompany(Company $company, array $templateIds)` method
    Add `getAccessibleTemplates(Company $company)` method that uses
    `scopeAccessible()` to retrieve owned and assigned templates
    Validate that assigned templates are either system templates or belong to other
    companies (prevent self-assignment)
    

    Phase 3: Admin Panel UI for Template Assignment

    Implement Filament relationship manager interface in the Admin panel's Company management area.

    Task 1: Create Email Templates Relationship Manager

    Build Filament relationship manager for assigning templates to companies.

    • Create EmailTemplatesRelationManager.php in Modules/Core/Filament/Admin/Resources/Companies/RelationManagers/
    • Configure table to display assignable templates with columns: template title, type, owning company name, is_system badge
    • Add attach form modal with Select field to choose from available system templates and other companies' templates
    • Filter out templates already owned by the current company in the attach form options
    • Add bulk detach action for removing assigned templates
    Task 2: Register Relationship Manager on CompanyResource

    Connect the new relationship manager to the Company edit interface.

    • Update Modules/Core/Filament/Admin/Resources/Companies/CompanyResource.php to add EmailTemplatesRelationManager::class to the getRelations() array
    • Ensure the relationship manager tab appears on the company edit page
    Task 3: Update Template Display to Show Assignment Status

    Enhance template visibility to indicate ownership vs assignment.

    • Update Modules/Core/Filament/Admin/Resources/EmailTemplates/Schemas/EmailTemplatesTable.php to add badge column showing "Owned", "System", or "Assigned" status
    • Add filter for template source type (Owned/System/Assigned) if not already present
    🤖 Prompt for AI agents
    Build Filament admin interface for managing template assignments to companies.
    This phase creates the relationship manager and integrates it into the
    CompanyResource.
    
    Create EmailTemplatesRelationManager in Admin panel
    Configure table with columns: title, type, owning company name, is_system badge
    Add attach form modal with Select field for system templates and other
    companies' templates
    Filter out templates owned by the current company from attach options
    Add bulk detach action
    
    Register EmailTemplatesRelationManager in CompanyResource getRelations() method
    
    Update EmailTemplatesTable schema to add badge column showing "Owned", "System",
    or "Assigned" status
    Add filter for template source type
    

    Phase 4: Query Integration and Template Selection

    Update existing template retrieval logic to include assigned templates alongside owned templates.

    Task 1: Update Template Query Scopes

    Ensure all template queries respect the new assignment relationships.

    • Update Modules/Core/Services/EmailTemplateService.php methods that retrieve templates to use scopeAccessible() instead of just scopeForCompany()
    • Ensure template listings in the Company panel show both owned and assigned templates
    Task 2: Update Template Selection Fields

    Populate template selection dropdowns with accessible templates.

    • Update stubbed template selects in Modules/Core/Filament/Admin/Pages/Settings.php to use EmailTemplate::accessible($company)->pluck('title', 'id') pattern
    • Apply the same pattern to any other locations where templates are selected in forms
    🤖 Prompt for AI agents
    Update template queries throughout the application to include assigned
    templates. This phase ensures all template retrieval uses the new accessible
    scope.
    
    Update EmailTemplateService methods to use `scopeAccessible()` instead of
    `scopeForCompany()`
    Ensure Company panel template listings show both owned and assigned templates
    
    Update template selection fields in Settings page to use
    `EmailTemplate::accessible($company)` pattern
    Apply the same pattern to other template selection dropdowns throughout the
    application
    

    Phase 5: System Template Seeding and Migration

    Initialize system templates and migrate existing templates appropriately.

    Task 1: Create System Template Seeder

    Establish default system templates available to all companies.

    • Create SystemEmailTemplatesSeeder.php in Modules/Core/Database/Seeders/ that creates system templates with is_system => true and company_id => null or a designated system company ID
    • Follow the pattern from EmailTemplatesSeeder.php but mark templates as system templates
    • Register the seeder in database/seeders/DatabaseSeeder.php within the seedGlobal() method
    Task 2: Data Migration for Existing Templates

    Ensure existing company templates remain functional.

    • Create data migration in Modules/Core/Database/Migrations/ to set is_system => false for all existing templates (if not already handled by column default)
    • No changes needed to existing company_id values as all current templates remain company-owned
    🤖 Prompt for AI agents
    Set up system templates and migrate existing data. This phase creates default
    system templates and ensures existing templates continue to function.
    
    Create SystemEmailTemplatesSeeder that creates templates with `is_system =>
    true` and `company_id => null`
    Follow EmailTemplatesSeeder pattern for template structure
    Register seeder in DatabaseSeeder's seedGlobal() method
    
    Create data migration to set `is_system => false` for existing templates if
    needed
    Preserve existing `company_id` values to maintain ownership
    

    Phase 6: Testing Infrastructure

    Implement comprehensive test coverage following AAA pattern for all template assignment scenarios.

    Task 1: Model and Service Tests

    Test template assignment business logic and model relationships.

    • Create EmailTemplateAssignmentTest.php in Modules/Core/Tests/Unit/ covering:
      • Company can have assigned templates relationship
      • Template can have assigned companies relationship
      • Accessible scope returns owned + assigned + system templates
      • Accessible scope excludes unrelated company templates
      • Self-assignment validation prevents assigning owned templates
    • Create EmailTemplateServiceTest.php in Modules/Core/Tests/Unit/ covering:
      • assignTemplatesToCompany() creates pivot records
      • unassignTemplatesFromCompany() removes pivot records
      • getAccessibleTemplates() returns correct template set
      • Edge case: attempting to assign already-assigned templates (idempotent)
      • Edge case: attempting to assign non-existent templates (validation)
    Task 2: Filament Relationship Manager Tests

    Test admin UI interactions for template assignment.

    • Create EmailTemplatesRelationManagerTest.php in Modules/Core/Tests/Feature/Filament/ covering:
      • Admin can view relationship manager on company edit page
      • Admin can attach system templates to company
      • Admin can attach other company templates to company
      • Attach form excludes company's owned templates
      • Admin can detach assigned templates
      • Edge case: attempting to attach already-owned template (filtered out)
      • Edge case: bulk detach removes multiple assignments
    Task 3: Query Integration Tests

    Test template retrieval in various contexts.

    • Create EmailTemplateAccessTest.php in Modules/Core/Tests/Feature/ covering:
      • Company panel template listing shows owned + assigned templates
      • Template selection dropdowns include accessible templates
      • System templates appear for all companies
      • Template queries respect tenant context in Company panel
      • Edge case: company with no owned templates but has assigned templates
      • Edge case: company with owned templates but no assignments
      • Edge case: orphaned assignments when template is deleted (cascade handles)
    🤖 Prompt for AI agents
    Implement comprehensive test coverage for template assignment functionality.
    This phase creates unit and feature tests following the AAA pattern.
    
    Create EmailTemplateAssignmentTest for model relationships and scopes
    Test assigned templates relationships on both Company and EmailTemplate models
    Test accessible scope returns owned, assigned, and system templates
    Test accessible scope excludes unrelated templates
    Test self-assignment validation
    
    Create EmailTemplateServiceTest for service layer methods
    Test assignTemplatesToCompany and unassignTemplatesFromCompany
    Test getAccessibleTemplates returns correct results
    Test edge cases: duplicate assignments, non-existent templates
    
    Create EmailTemplatesRelationManagerTest for admin UI
    Test relationship manager visibility and template attachment/detachment
    Test attach form filters out owned templates
    Test bulk detach functionality
    
    Create EmailTemplateAccessTest for query integration
    Test template listings and selection dropdowns include accessible templates
    Test system templates appear for all companies
    Test tenant context and edge cases
    

    🚀 Next Steps

    🤖 All AI agent prompts combined
    Task: 1
    
    Set up database schema and model relationships for template assignments. This
    phase establishes the foundation for sharing templates across companies while
    maintaining ownership.
    
    Add `is_system` boolean column to `email_templates` table with default false
    Update EmailTemplate model to cast `is_system` as boolean and add `isSystem()`
    accessor
    
    Create `company_email_template` pivot table with `company_id`,
    `email_template_id`, `assigned_at` columns
    Add foreign key constraints with cascade delete
    Add unique constraint on company_id and email_template_id
    
    Add `assignedCompanies()` BelongsToMany relationship to EmailTemplate model
    Add `assignedEmailTemplates()` BelongsToMany relationship to Company model
    Add `scopeAccessible()` method to EmailTemplate that returns owned, assigned,
    and system templates for a company
    ===============================================================================
    
    Task: 2
    
    Extend EmailTemplateService with assignment management functionality. This phase
    adds business logic for assigning and unassigning templates to companies.
    
    Add `assignTemplatesToCompany(Company $company, array $templateIds)` method to
    EmailTemplateService
    Add `unassignTemplatesFromCompany(Company $company, array $templateIds)` method
    Add `getAccessibleTemplates(Company $company)` method that uses
    `scopeAccessible()` to retrieve owned and assigned templates
    Validate that assigned templates are either system templates or belong to other
    companies (prevent self-assignment)
    ===============================================================================
    
    Task: 3
    
    Build Filament admin interface for managing template assignments to companies.
    This phase creates the relationship manager and integrates it into the
    CompanyResource.
    
    Create EmailTemplatesRelationManager in Admin panel
    Configure table with columns: title, type, owning company name, is_system badge
    Add attach form modal with Select field for system templates and other
    companies' templates
    Filter out templates owned by the current company from attach options
    Add bulk detach action
    
    Register EmailTemplatesRelationManager in CompanyResource getRelations() method
    
    Update EmailTemplatesTable schema to add badge column showing "Owned", "System",
    or "Assigned" status
    Add filter for template source type
    ===============================================================================
    
    Task: 4
    
    Update template queries throughout the application to include assigned
    templates. This phase ensures all template retrieval uses the new accessible
    scope.
    
    Update EmailTemplateService methods to use `scopeAccessible()` instead of
    `scopeForCompany()`
    Ensure Company panel template listings show both owned and assigned templates
    
    Update template selection fields in Settings page to use
    `EmailTemplate::accessible($company)` pattern
    Apply the same pattern to other template selection dropdowns throughout the
    application
    ===============================================================================
    
    Task: 5
    
    Set up system templates and migrate existing data. This phase creates default
    system templates and ensures existing templates continue to function.
    
    Create SystemEmailTemplatesSeeder that creates templates with `is_system =>
    true` and `company_id => null`
    Follow EmailTemplatesSeeder pattern for template structure
    Register seeder in DatabaseSeeder's seedGlobal() method
    
    Create data migration to set `is_system => false` for existing templates if
    needed
    Preserve existing `company_id` values to maintain ownership
    ===============================================================================
    
    Task: 6
    
    Implement comprehensive test coverage for template assignment functionality.
    This phase creates unit and feature tests following the AAA pattern.
    
    Create EmailTemplateAssignmentTest for model relationships and scopes
    Test assigned templates relationships on both Company and EmailTemplate models
    Test accessible scope returns owned, assigned, and system templates
    Test accessible scope excludes unrelated templates
    Test self-assignment validation
    
    Create EmailTemplateServiceTest for service layer methods
    Test assignTemplatesToCompany and unassignTemplatesFromCompany
    Test getAccessibleTemplates returns correct results
    Test edge cases: duplicate assignments, non-existent templates
    
    Create EmailTemplatesRelationManagerTest for admin UI
    Test relationship manager visibility and template attachment/detachment
    Test attach form filters out owned templates
    Test bulk detach functionality
    
    Create EmailTemplateAccessTest for query integration
    Test template listings and selection dropdowns include accessible templates
    Test system templates appear for all companies
    Test tenant context and edge cases
    
    💡 Iterate on the plan with: @coderabbitai <feedback>
    Example Feedback
    - @coderabbitai You can skip phase 3. Add a simple unit test case for phase 2.
    - @coderabbitai For assumption 1 go ahead with option 3 and replan.
    

    💬 Have feedback or questions? Drop into our discord!

  5. changed the title [-]As an Admin, in the company Panel I want to be able to assign Email Templates to that single company[/-] [+][Settings]: Company panel — assign Email Templates[/+] on Jun 13, 2026
  6. nielsdrost7 commented on Jul 24, 2026

    @nielsdrost7
    CollaboratorAuthor

    SMART Plan — Company panel — assign Email Templates

    Specific

    Within the Company panel Settings page, add multi-select fields (or a table) to assign Email Templates to the company for different lifecycle states: invoice sent, invoice paid, invoice overdue, quote sent, quote accepted. Templates are populated from EmailTemplate records scoped to the company. Only assigned templates appear in invoice/quote email form dropdowns.

    Measurable

    • Email templates searchable/selectable per lifecycle state.
    • Selections stored in a pivot table or settings rows (one per state).
    • On email composition forms, dropdowns show only assigned templates.
    • Success message confirms assignments.

    Achievable

    1. Define Company::emailTemplates() as a BelongsToMany if not already present.
    2. Create multi-select fields in CompanySettings for each lifecycle state.
    3. Update email composition forms to filter by $company->emailTemplates()->wherePivot('state', 'sent').

    Relevant

    Template-heavy companies want to curate which templates each business unit can use, avoiding template pollution and accidental sends from wrong templates.

    Time-bound

    Sprint 6.

  7. added a commit that references this issue on Jul 24, 2026
  8. nielsdrost7 commented on Jul 24, 2026

    @nielsdrost7
    CollaboratorAuthor

    Implemented on feature/235-243-company-settings-cluster. CompanySettings.php's Invoices/Quotes tabs already had stubbed Select fields for this (invoice_email_template, invoice_paid_email_template, invoice_overdue_email_template, quote_email_template) with empty options([]) — wired them to EmailTemplate::query()->where('company_id', $this->getCompanyId())->pluck('title', 'id'), matching the pattern already used for the invoice-numbering and tax-rate defaults. Persisted via the existing Setting key/value mechanism (the Setting::KEY_* constants and allKeys() entries already existed). Test: CompanySettingsTest::it_offers_and_persists_default_email_templates_scoped_to_the_company proves both the per-company scoping and persistence for all four keys.

  9. added a commit that references this issue on Aug 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    refinedRefined with SMART plan / test suggestions

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions