Skip to content

Allow to customize invoice filename via invoice template mechanism - #2076

Merged
dmitry-sinina merged 4 commits into
yeti-switch:masterfrom
dmitry-sinina:invoice_file_templates
Aug 10, 2026
Merged

Allow to customize invoice filename via invoice template mechanism#2076
dmitry-sinina merged 4 commits into
yeti-switch:masterfrom
dmitry-sinina:invoice_file_templates

Conversation

@dmitry-sinina

Copy link
Copy Markdown
Contributor

No description provided.

Copilot AI lite review requested due to automatic review settings August 9, 2026 20:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds invoice filename customization by introducing a filename_template on Billing::InvoiceTemplate, passing it through to yeti-pdf, and persisting/serving the rendered filename with generated invoice PDFs.

Changes:

  • Add billing.invoice_templates.filename_template (DB + model validation) and expose/edit it via Admin UI and REST resource.
  • Update YetiPdf::Client#render_pdf to send filename_template and return both PDF bytes and rendered base filename (no extension).
  • Store and serve invoice PDFs using the rendered filename (and adjust specs accordingly), removing Billing::Invoice#file_name.

Reviewed changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
spec/services/yeti_pdf/client_spec.rb Updates expectations for render_pdf to return {pdf, filename} and tests filename-template behavior.
spec/services/billing_invoice/generate_document_spec.rb Adapts document-generation specs to the new client result and filename handling.
spec/services/billing_invoice/approve_spec.rb Updates invoice document filename expectations to align with new naming scheme.
spec/requests/api/rest/admin/invoices_spec.rb Expects served PDF filename to match stored InvoiceDocument#filename.
spec/models/billing/invoice_template_spec.rb Adds coverage for default/blank validation of filename_template.
spec/features/billing/invoice_templates/new_invoice_template_spec.rb Ensures UI create flow persists filename_template.
spec/factories/billing/invoice_templates.rb Updates schema comment for the new filename_template column.
spec/acceptance/rest/admin/api/invoice_template_spec.rb Extends API acceptance to send/document filename_template.
db/structure.sql Adds filename_template column to billing.invoice_templates and schema migration entry.
db/migrate/20260809120000_add_filename_template_to_invoice_templates.rb Migration adding filename_template with default and NOT NULL constraint.
db/custom_seeds/invoice_template_example.rb Seeds example invoice template with a filename_template.
app/services/yeti_pdf/client.rb Returns a Result (pdf + filename) and supports sending filename_template.
app/services/billing_invoice/generate_document.rb Stores rendered filename and errors when yeti-pdf returns no filename.
app/resources/api/rest/admin/invoice_template_resource.rb Exposes filename_template via the admin REST resource.
app/models/billing/invoice.rb Removes legacy file_name helper.
app/models/billing/invoice_template.rb Adds validation for filename_template presence/length.
app/controllers/api/rest/admin/invoices_controller.rb Serves PDFs using stored doc.filename for Content-Disposition.
app/assets/javascripts/template_playground.js Adds filename-template editing and preview of rendered filename in playground UI.
app/admin/billing/invoice_templates.rb Permits and displays filename_template in ActiveAdmin invoice templates.
app/admin/billing/invoice_template_playground.rb Sends/receives filename_template in preview/save actions and returns rendered filename via header.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread app/services/yeti_pdf/client.rb Outdated
Comment on lines 46 to 50
def render_pdf(template:, data:, filename_template: nil, options: {})
response = post(RENDER_PATH, template: template, data: data, options: options,
filename_template: filename_template)
Result.new(response.body.to_s, rendered_filename(response))
end

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 20 changed files in this pull request and generated no new comments.

Suppressed comments (4)

app/admin/billing/invoice_template_playground.rb:28

  • When yeti-pdf accepts the request but omits Content-Disposition (the incompatible-service case handled by GenerateDocument), this action still returns 200 with a blank filename preview. The playground then reports a successful render even though real document generation will reject the same response. Reject a blank result.filename before streaming the PDF so the existing rescue returns 422.
    # The name the document would be filed under, for the editor to display —
    # a header, so it rides along with the inline PDF the iframe is loading.
    response.set_header('X-Rendered-Filename', ERB::Util.url_encode(result.filename.to_s))
    send_data result.pdf, type: 'application/pdf', disposition: 'inline'

app/assets/javascripts/template_playground.js:123

  • Each edit can start a new render while an earlier request is still in flight, and responses are applied without checking which request is newest. Because PDF rendering may be slow, an older response can arrive last and replace the filename preview for the current input. Abort the previous request or use a monotonically increasing request token before applying response, error, and spinner state.
                showFilename(resp.headers.get('X-Rendered-Filename'));

spec/models/billing/invoice_template_spec.rb:41

  • This repeats the opposite behavior from YetiPdf::Client: the client deliberately preserves an empty value so yeti-pdf rejects the broken naming request. Update the explanation so this test documents the actual contract.
  # NOT NULL alone would let '' through, which yeti-pdf reads as "no filename
  # template" — the document would come back nameless.

app/models/billing/invoice_template.rb:24

  • This comment contradicts the new client contract and its tests: a present-but-empty filename_template is sent as a broken naming request and rejected by yeti-pdf; it is not treated as a request for a nameless document. Please describe the actual reason for rejecting blank values.
  # NOT NULL alone would still allow '', which yeti-pdf treats as "no filename
  # template" and would leave the document with no name to store.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 21 out of 22 changed files in this pull request and generated no new comments.

Suppressed comments (1)

app/admin/billing/invoice_template_playground.rb:33

  • When an older yeti-pdf ignores filename_template, this action still returns 200 and displays a successful preview with no filename, while real invoice generation rejects the same response as FilenameMissing. Reject a blank rendered filename here too so the playground cannot validate a document that production will refuse to store.
    # The name the document would be filed under, for the editor to display —
    # a header, so it rides along with the inline PDF the iframe is loading.
    response.set_header('X-Rendered-Filename', ERB::Util.url_encode(result.filename.to_s))
    send_data result.pdf, type: 'application/pdf', disposition: 'inline'

@dmitry-sinina
dmitry-sinina merged commit 5834b3d into yeti-switch:master Aug 10, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants