Skip to content

[Invoices]: Reactivate a stopped recurring invoice #362

Open

Description

@xam-ps

Title

No response

Expected Behavior

Restarting a stopped recurring invoice should be an option

Current Behavior

The options button only shows "stop" as an option for stopped recurring invoices

Steps to Reproduce

  1. Go to recurring invoices
  2. Stop one of then my clicking on "options" -> "stop"

Screenshots (Optional)


Possible Solution (Optional)

No response

Context (Environment)

This was tested with v1.6.2 and v1.7.0 beta

Failure Logs (Optional)

No response

Activity

  1. added theissue type on Dec 23, 2025
  2. added theissue type on Jan 19, 2026
  3. nielsdrost7 commented on Jun 13, 2026

    @nielsdrost7
    Collaborator

    Planning Note — v2 Implementation

    Story: As a company admin, I want to reactivate a stopped recurring invoice, so that I can resume billing without recreating the entire recurring schedule from scratch.

    v2 context: The RecurringInvoice model has a recurring_invoice_status field (enum). In v1, the "stop" action set status to stopped but no "reactivate" action existed. In v2 the RecurringInvoicesTable action group needs a conditional action.

    Acceptance Criteria:

    • A stopped recurring invoice shows an Activate action in the actions dropdown.
    • An active recurring invoice shows a Stop action (existing).
    • Activating a stopped recurring invoice sets recurring_invoice_status = 'active' and resumes the billing schedule from the next due date.
    • Stopping and re-activating does not generate back-dated invoices for the paused period.

    Implementation:

    • Add Action::make('activate') in RecurringInvoicesTable (or wherever the Stop action lives), visible only when $record->recurring_invoice_status === 'stopped'.
    • RecurringInvoiceService::activate($record): set status to active, recalculate next_invoice_date from today + interval.
    • Hide the stop action when status is already stopped.

    Test Scenarios (PHPUnit + Livewire):

    // Reactivate action appears for stopped recurring invoice
    /* Arrange */ $ri = RecurringInvoice::factory()->for($this->company)
                      ->create(['recurring_invoice_status' => 'stopped']);
    /* Act */     $this->testLivewire(ListRecurringInvoices::class)
    /* Assert */  ->assertTableActionVisible('activate', $ri);
    // Activate action sets status to active
    /* Arrange */ $ri = RecurringInvoice::factory()->for($this->company)
                      ->create(['recurring_invoice_status' => 'stopped']);
    /* Act */     $this->testLivewire(ListRecurringInvoices::class)
                      ->callTableAction('activate', $ri);
    /* Assert */  $this->assertDatabaseHas('recurring_invoices',
                      ['id' => $ri->id, 'recurring_invoice_status' => 'active']);
    // Stop action hidden when recurring invoice is already stopped
    /* Arrange */ $ri = RecurringInvoice::factory()->for($this->company)
                      ->create(['recurring_invoice_status' => 'stopped']);
    /* Act */     $this->testLivewire(ListRecurringInvoices::class)
    /* Assert */  ->assertTableActionHidden('stop', $ri);

    Generated by Claude Code

  4. changed the title [-]Recurring invoice can't be activated after being "stopped"[/-] [+][Invoices]: Reactivate a stopped recurring invoice[/+] on Jun 13, 2026
  5. added this to the v2.0.0-alpha milestone on Jun 13, 2026
  6. nielsdrost7 commented on Jul 16, 2026

    @nielsdrost7
    Collaborator

    Test Suggestions

    Added to support future implementation of this request. The issue body is unchanged.

    RecurringInvoicesTable (Modules/Invoices/Filament/Company/Resources/RecurringInvoices/Tables/RecurringInvoicesTable.php) currently only exposes EditAction and DeleteAction inside an ActionGroup — there is no "stop"/"options" dropdown at all in v2 today. The reporter is describing v1 behavior ("options button only shows stop"); in the current codebase there isn't yet a stop action to reactivate from. RecurringInvoice only has start_at/end_at date columns — there's no boolean/status column (e.g. is_stopped) representing "stopped" vs. "active" at all, and Modules\Invoices\Services\RecurringInvoiceService is an empty stub, so there's no service-layer home for a reactivate() method either. This needs the stop/active concept designed and built (likely alongside #52's full recurring lifecycle) before "reactivate" has any meaning.

    • PHPUnit: it_reactivates_a_stopped_recurring_invoice()
    #[Test]
    public function it_reactivates_a_stopped_recurring_invoice(): void
    {
        /* Arrange */
        $recurring = RecurringInvoice::factory()->for($this->company)->create(['is_stopped' => true]);
    
        /* Act */
        app(RecurringInvoiceService::class)->reactivate($recurring);
    
        /* Assert */
        $this->assertFalse($recurring->fresh()->is_stopped);
    }
    • PHPUnit: it_hides_the_reactivate_action_for_a_recurring_invoice_that_is_already_active()
    #[Test]
    public function it_hides_the_reactivate_action_for_a_recurring_invoice_that_is_already_active(): void
    {
        /* Arrange */
        $recurring = RecurringInvoice::factory()->for($this->company)->create(['is_stopped' => false]);
    
        /* Act */
        $component = $this->testLivewire(ListRecurringInvoices::class);
    
        /* Assert */
        $component->assertTableActionHidden('reactivate', $recurring);
    }
    • PHPUnit: it_resumes_generating_invoices_on_the_next_scheduled_run_after_reactivation()
    #[Test]
    public function it_resumes_generating_invoices_on_the_next_scheduled_run_after_reactivation(): void
    {
        /* Arrange */
        $recurring = RecurringInvoice::factory()->for($this->company)->create([
            'is_stopped' => true,
            'start_at'   => now()->subDay(),
        ]);
        app(RecurringInvoiceService::class)->reactivate($recurring);
    
        /* Act */
        app(RecurringInvoiceService::class)->generateDueInvoices();
    
        /* Assert */
        $this->assertDatabaseHas('invoices', ['recurring_invoice_id' => $recurring->id]);
    }

    Edge cases worth covering: a recurring invoice whose end_at has already passed (does reactivating also require bumping end_at, or is that a separate step?), and reactivating a profile whose linked numbering scheme was deleted in the meantime.

  7. nielsdrost7 commented on Jul 18, 2026

    @nielsdrost7
    Collaborator

    Original report (preserved): Restarting a stopped recurring invoice should be an option. The options button only shows "stop" for stopped recurring invoices. Steps: Go to recurring invoices → options → stop. Tested with v1.6.2 and v1.7.0 beta.

    v1 could stop a schedule (application/modules/invoices/controllers/Recurring.php:51 → Mdl_invoices_recurring::stop() sets recur_end_date = today, recur_next_date = NULL) but never restart it — this issue closes that hole in v2. Depends on the v2 Stop action ([Invoices]: Stop a recurring invoice) and the next_run_at column from the generation-engine issue (Epic #52).

    Specific

    Add a reactivate record action to Modules/Invoices/Filament/Company/Resources/RecurringInvoices/Tables/RecurringInvoicesTable.php, visible only for stopped schedules (next_run_at IS NULL). It opens a small form: new optional end_at (must be in the future), then recomputes next_run_at as the first frequency-step from the original start_at (or last last_run_at) that lands strictly after today, clears/updates end_at, and returns the schedule to "active".

    Measurable

    • Stopped schedules show "Reactivate" and not "Stop"; active schedules show "Stop" and not "Reactivate".
    • After reactivation the schedule generates again on its next due date; no back-fill of the stopped period occurs.
    • Recomputed next_run_at is aligned to the original cadence, not simply "today".

    Achievable

    Single Filament action + one service method RecurringInvoiceService::reactivate(RecurringInvoice $r, ?Carbon $newEndAt); date walk uses the same frequency-increment helper as the engine.

    Relevant

    Stop is destructive-but-reversible in spirit (pause a subscription over a dispute, resume later); without reactivation users must delete and re-create the schedule, losing history. Epic #52 lists "Pausing stops generation without deleting history" as a requirement.

    Time-Bound

    Half a sprint, after Stop action and engine schema land; part of Epic #52.

    Arrange

    • A monthly schedule started 3 months ago, stopped last month (end_at = stop date, next_run_at = NULL), with last_run_at set.

    Act

    • Trigger the reactivate action with end_at = null; run the generation engine for today.

    Assert

    • next_run_at equals the first monthly step after today on the original cadence; end_at is null.
    • Row shows status "active"; "Stop" action visible again, "Reactivate" hidden.
    • Engine run generates no invoice today (next_run_at is future) and exactly one on the recomputed date.
    • Reactivating with a past end_at is rejected with a validation error.
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

    bugSomething isn't workingrefinedRefined with SMART plan / test suggestions

    Type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions