From 37a491cf79868e4746da901a9b60d5e201b85081 Mon Sep 17 00:00:00 2001 From: Sergei Poljanski Date: Mon, 8 Jun 2026 19:00:03 +0300 Subject: [PATCH] fix: pending/paid invoices --- asxpio.rb | 15 ++++++++++----- lib/invoice.rb | 11 +++++++++++ lib/invoice_pdf.rb | 11 +++++++---- lib/s3.rb | 7 +++++++ 4 files changed, 35 insertions(+), 9 deletions(-) diff --git a/asxpio.rb b/asxpio.rb index c8a7107..56a4652 100644 --- a/asxpio.rb +++ b/asxpio.rb @@ -202,10 +202,11 @@ class AsxpioWeb < Sinatra::Base end invoice = Invoice.build(@form_values) - pdf_bytes = InvoicePdf.render(invoice) - pdf_key = "invoices/#{invoice.number}-#{invoice.uuid}.pdf" - S3.put(pdf_key, pdf_bytes) - invoice.pdf_key = pdf_key + # Base key (no suffix); the two status variants get -pending/-paid appended. + invoice.pdf_key = "invoices/#{invoice.number}-#{invoice.uuid}.pdf" + %w[pending paid].each do |st| + S3.put(invoice.pdf_key_for(st), InvoicePdf.render(invoice, status: st)) + end invoice.save_changes redirect "/admin/invoices/#{invoice.uuid}" @@ -237,7 +238,11 @@ class AsxpioWeb < Sinatra::Base get '/i/:uuid/pdf' do invoice = Invoice[uuid: params[:uuid]] or halt 404 - url = S3.presigned_url(invoice.pdf_key, + # Prefer the status variant; fall back to the legacy single-PDF key for + # invoices created before the pending/paid split. + key = invoice.current_pdf_key + key = invoice.pdf_key unless S3.exists?(key) + url = S3.presigned_url(key, expires_in: 300, filename: "#{invoice.number}.pdf") redirect url, 302 diff --git a/lib/invoice.rb b/lib/invoice.rb index b5bdbe2..a323d93 100644 --- a/lib/invoice.rb +++ b/lib/invoice.rb @@ -27,6 +27,17 @@ class Invoice < Sequel::Model(:invoices) paid? ? 'paid' : 'pending' end + # Both status variants are pre-rendered at creation; `pdf_key` is the base + # path (no suffix). The download route picks a variant by current status. + def pdf_key_for(status) + base = pdf_key.sub(/\.pdf\z/, '') + "#{base}-#{status}.pdf" + end + + def current_pdf_key + pdf_key_for(status) + end + def total BigDecimal(subtotal.to_s) end diff --git a/lib/invoice_pdf.rb b/lib/invoice_pdf.rb index 9883e99..3dcdfdc 100644 --- a/lib/invoice_pdf.rb +++ b/lib/invoice_pdf.rb @@ -31,12 +31,15 @@ class InvoicePdf COLOR_RULE = 'DADADA'.freeze COLOR_ACCENT = '1A1A1A'.freeze - def self.render(invoice) - new(invoice).render + # `status` overrides the status shown in the PDF (e.g. to pre-render a "paid" + # copy before any payment date exists). Defaults to the invoice's live status. + def self.render(invoice, status: nil) + new(invoice, status: status).render end - def initialize(invoice) + def initialize(invoice, status: nil) @invoice = invoice + @status = status || invoice.status end def render @@ -155,7 +158,7 @@ class InvoicePdf ['ISSUED', @invoice.issued_on.strftime('%Y-%m-%d')], ['DUE', @invoice.due_on.strftime('%Y-%m-%d')], ['CURRENCY', @invoice.currency], - ['STATUS', @invoice.status.upcase] + ['STATUS', @status.upcase] ] col_w = pdf.bounds.width / cells.size.to_f top = pdf.cursor diff --git a/lib/s3.rb b/lib/s3.rb index 8647c24..91cacd3 100644 --- a/lib/s3.rb +++ b/lib/s3.rb @@ -40,6 +40,13 @@ module S3 key end + def exists?(key) + client.head_object(bucket: bucket, key: key) + true + rescue Aws::S3::Errors::NotFound, Aws::S3::Errors::NoSuchKey + false + end + def presigned_url(key, expires_in: 300, filename: nil) params = { bucket: bucket, key: key } if filename