Skip to content

[Invoices]: Generate invoice number for draft recurring invoices #368

Open

Description

@gklimm

Title

Generate the invoice number for draft invoices for recurring invoices does not respect the setting on invoice settings

Expected Behavior

When I choose "Generate the invoice number for draft invoices" with "No" it should be used for generating the recurring invoices too when the draft invoices are generated from recurring invoices

Current Behavior

When you run the recurring invoices with "Automatically Email recurring invoices" to "No" a invoice number is generated for the draft invoices no matter if the setting "Generate the invoice number for draft invoices" is set to "No"

Steps to Reproduce

  1. Go to Settings
  2. Set "Generate the invoice number for draft invoices" to "No"
  3. Set "Automatically Email recurring invoices" to "No" because
  4. Generate the draft invoices with the "index.php/invoices/cron/recur/...."
  5. Take a look on the generated draft invoices

Screenshots (Optional)


Possible Solution (Optional)

No response

Context (Environment)

No response

Failure Logs (Optional)

No response

Activity

  1. added theissue type on Apr 6, 2025
  2. nielsdrost7 commented on Apr 6, 2025

    @nielsdrost7
    Collaborator

    Take a look on the generated draft invoices

    Can you take a screenshot of the current situation and then add with your editor in a new image what you wanted to see (expected behavior)?

  3. gklimm commented on Apr 6, 2025

    @gklimm
    Author

    Take a look on the generated draft invoices

    Can you take a screenshot of the current situation and then add with your editor in a new image what you wanted to see (expected behavior)?

    Generating the draft invoices in the process of recurring invoices must follow the same rules like the manual copy of a invoice or generating a new invoice. What you see in a screenshot depends additionally on what you setup un the invoice group form so it make no sense to put here a screenshot

  4. added theissue type on Jan 19, 2026
  5. nielsdrost7 commented on Jun 13, 2026

    @nielsdrost7
    Collaborator

    Planning Note — v2 Implementation

    Bug story: As a company admin, I want the "Generate invoice number for draft invoices" setting to be respected when the recurring invoice cron generates draft invoices, so that draft invoices created from recurring schedules do not get a permanent invoice number until they are finalised.

    v2 context: v2 uses RecurringInvoiceService (no cron URL — uses Laravel's artisan scheduler). The setting is stored in company settings as generate_invoice_number_for_draft. When the scheduler fires and generates a draft invoice from a RecurringInvoice, it must check this setting before assigning a number.

    Acceptance Criteria:

    • When generate_invoice_number_for_draft = false and the recurring invoice scheduler runs with auto_send = false, the generated draft invoice has invoice_number = null.
    • When generate_invoice_number_for_draft = true, the draft invoice is assigned a number immediately on creation.
    • Setting changes take effect on the next scheduler run (not retroactively).
    • The fix applies via php artisan invoiceplane:process-recurring (or equivalent artisan command).

    Implementation:

    • In RecurringInvoiceService::generateInvoice(), after creating the draft invoice, check $company->settings->generate_invoice_number_for_draft.
    • If false, skip calling NumberingService::assignInvoiceNumber($invoice).
    • If true, call NumberingService::assignInvoiceNumber($invoice) as normal.

    Test Scenarios (PHPUnit):

    // Draft from recurring has no number when setting is false
    /* Arrange */ $this->company->settings()->update(['generate_invoice_number_for_draft' => false]);
                  $ri = RecurringInvoice::factory()->for($this->company)
                      ->create(['auto_send' => false, 'next_invoice_date' => today()]);
    /* Act */     app(RecurringInvoiceService::class)->processRecurring();
    /* Assert */  $this->assertDatabaseHas('invoices',
                      ['recurring_invoice_id' => $ri->id, 'invoice_number' => null]);
    // Draft from recurring gets number when setting is true
    /* Arrange */ $this->company->settings()->update(['generate_invoice_number_for_draft' => true]);
                  $ri = RecurringInvoice::factory()->for($this->company)
                      ->create(['auto_send' => false, 'next_invoice_date' => today()]);
    /* Act */     app(RecurringInvoiceService::class)->processRecurring();
    /* Assert */  $invoice = Invoice::where('recurring_invoice_id', $ri->id)->first();
                  $this->assertNotNull($invoice->invoice_number);

    Generated by Claude Code

  6. changed the title [-]Generate the invoice number for draft invoices for recurring invoices[/-] [+][Invoices]: Generate invoice number for draft recurring invoices[/+] on Jun 13, 2026
  7. added this to the v2.0.0-alpha milestone on Jun 13, 2026
  8. nielsdrost7 commented on Jul 16, 2026

    @nielsdrost7
    Collaborator

    Test Suggestions

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

    There is no "Generate the invoice number for draft invoices" setting anywhere in this codebase today (ip2 has no generate_invoice_number/invoice_number_for_draft-style key at all) — this v1 setting was never ported to v2. More fundamentally, Modules\Invoices\Services\RecurringInvoiceService is an empty stub class: there is currently no code in v2 that generates draft invoices from a recurring profile at all, so the index.php/invoices/cron/recur/... flow this bug describes doesn't exist yet either — this report is really about a not-yet-built feature (tracked as #52 in this epic). The scaffolding around recurring invoices is also inconsistent independent of this bug: RecurringInvoice (model and factory) still import a Modules\Core\Models\DocumentGroup class that no longer exists in the codebase (it was renamed to Numbering), and RecurringInvoiceForm binds a document_group_id field that isn't a real column on recurring_invoices (the actual FK is numbering_id).

    • PHPUnit: it_leaves_the_generated_invoice_number_null_when_auto_numbering_is_disabled()
    #[Test]
    public function it_leaves_the_generated_invoice_number_null_when_auto_numbering_is_disabled(): void
    {
        /* Arrange */
        $this->company->update(['generate_invoice_number_for_drafts' => false]);
        $recurring = RecurringInvoice::factory()->for($this->company)->create(['start_at' => now()->subDay()]);
    
        /* Act */
        $invoice = app(RecurringInvoiceService::class)->generateDueInvoices()->first();
    
        /* Assert */
        $this->assertNull($invoice->invoice_number);
    }
    • PHPUnit: it_assigns_an_invoice_number_immediately_when_auto_numbering_is_enabled()
    #[Test]
    public function it_assigns_an_invoice_number_immediately_when_auto_numbering_is_enabled(): void
    {
        /* Arrange */
        $this->company->update(['generate_invoice_number_for_drafts' => true]);
        $recurring = RecurringInvoice::factory()->for($this->company)->create(['start_at' => now()->subDay()]);
    
        /* Act */
        $invoice = app(RecurringInvoiceService::class)->generateDueInvoices()->first();
    
        /* Assert */
        $this->assertNotNull($invoice->invoice_number);
    }
    • PHPUnit: it_respects_the_setting_independently_of_the_auto_email_recurring_toggle()
    #[Test]
    public function it_respects_the_setting_independently_of_the_auto_email_recurring_toggle(): void
    {
        /* Arrange */
        $this->company->update([
            'generate_invoice_number_for_drafts' => false,
            'auto_email_recurring_invoices'      => false,
        ]);
        $recurring = RecurringInvoice::factory()->for($this->company)->create(['start_at' => now()->subDay()]);
    
        /* Act */
        $invoice = app(RecurringInvoiceService::class)->generateDueInvoices()->first();
    
        /* Assert */
        $this->assertNull($invoice->invoice_number);
    }

    Edge cases worth covering: a company that flips the setting between two generation runs (only new invoices should respect the new value; already-generated drafts shouldn't be retroactively renumbered), and a recurring profile whose numbering_id scheme was deleted after being referenced.

  9. nielsdrost7 commented on Jul 18, 2026

    @nielsdrost7
    Collaborator

    Original report (preserved): With "Generate the invoice number for draft invoices" = No and "Automatically Email recurring invoices" = No, running the recurring cron still assigns invoice numbers to the generated draft invoices, ignoring the setting.

    v1 spec: application/modules/invoices/models/Mdl_invoices.php:414-422 — on create, a DRAFT invoice gets a real number only when generate_invoice_number_for_draft = 1, otherwise invoice_number = ''; the number is assigned later on first status change away from draft (Mdl_invoices.php:677-686). The v1 recurring cron bypassed this by passing a pre-computed number (application/modules/invoices/controllers/Cron.php:83) — that bypass is the bug being reported.

    v2 status: the Toggle exists (Modules/Core/Filament/Admin/Pages/Settings.php:330-331) and manual invoice creation honors it (Modules/Invoices/Tests/Feature/InvoiceNumberGenerationOnCreateTest.php), but the recurring generation engine (Epic #52) does not exist yet. This issue pins the requirement so the engine does not re-introduce the v1 bug.

    Specific

    The recurring generation engine must route generated invoices through the same numbering path as manual creation: when generate_invoice_number_for_draft is off, engine-generated drafts get no number (assigned on transition out of DRAFT, drawing from the schedule's numbering_id group); when on, the number is reserved at generation time from recurring_invoices.numbering_id (fallback: source invoice's numbering group).

    Measurable

    • Toggle off → generated draft has null/empty number; sending it later assigns the next number in the schedule's numbering group with no gaps.
    • Toggle on → generated draft carries the next number immediately.
    • Two schedules sharing one numbering group never collide (sequential numbers, no duplicates) — v2 already guards duplicates (InvoiceDuplicateNumberPreventionTest.php).

    Achievable

    No new code path: the engine calls the existing invoice-creation service which already implements the toggle; this issue is the regression test contract.

    Relevant

    Sequential-numbering compliance (many jurisdictions forbid gaps); drafts that may be discarded must not burn numbers.

    Time-Bound

    Ships inside the generation-engine sprint (Epic #52) as its numbering acceptance tests.

    Arrange

    • Numbering group at next value N; generate_invoice_number_for_draft = false; automatic_email_on_recur = false; one due monthly schedule with numbering_id set.

    Act

    • Run the engine; then move the generated invoice from DRAFT to SENT.

    Assert

    • After generation: invoice exists in DRAFT with empty/null invoice_number; numbering group still at N.
    • After status change: invoice number equals N's formatted value; group advanced to N+1.
    • Re-run with toggle = true: generated draft is numbered immediately from the schedule's group.
    • Schedule without numbering_id falls back to the source invoice's numbering group.
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