Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
A view object based architecture for printing arbitrary objects
Through clever naming of attributes and delegates, all integration tests are still green. Orders will have the exact same API as before. * Move invoice rendering methods to Spree::BookkeepingDocument * Add view objects to connect Spree Objects with Spree::BookkeepingDocument Your PDF template should be agnostic about the structure of your printable object. The view objects achieve that. * Improve NotImplementeError messages * Add item and adjustment POROs, invoices controller * Add order#documents tab, translations * Fix feature spec: Documents tab instead of print buttons * Rrename all the partials * Rename "file" to "pdf" * Add a working index action to the PdfsController * Fix specs accordingly * Remove selecting template feature Before, you were able to select a template from the backend. As it is considerable development effort to create a Prawn template, I think it makes sense to remove this option from the user. Now, there's a PDF model, and you can create a new PDF by creating it with the underlying Object as `printable` and a string specifying the template to be used. * First version of index.html.erb * Make BookkeepingDocument model searchable * Move invoice number logic to Spree::BookkeepingDocument * Add a base view that complains when essential things are not defined on a view * Mmove increasing invoice numbers to base invoice view * Tests for deprecated methods on Spree::Order * Use order number for order packaging slip
- Loading branch information
Showing
61 changed files
with
1,530 additions
and
359 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
36 changes: 36 additions & 0 deletions
36
app/controllers/spree/admin/bookkeeping_documents_controller.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
module Spree | ||
module Admin | ||
class BookkeepingDocumentsController < ResourceController | ||
before_action :load_order, if: :order_focused? | ||
|
||
helper_method :order_focused? | ||
|
||
def show | ||
respond_with(@bookkeeping_document) do |format| | ||
format.pdf do | ||
send_data @bookkeeping_document.pdf, type: 'application/pdf', disposition: 'inline' | ||
end | ||
end | ||
end | ||
|
||
def index | ||
# Massaging the params for the index view like Spree::Admin::Orders#index | ||
params[:q] ||= {} | ||
@search = Spree::BookkeepingDocument.ransack(params[:q]) | ||
@bookkeeping_documents = @search.result | ||
@bookkeeping_documents = @bookkeeping_documents.where(printable: @order) if order_focused? | ||
@bookkeeping_documents = @bookkeeping_documents.page(params[:page] || 1).per(10) | ||
end | ||
|
||
private | ||
|
||
def order_focused? | ||
params[:order_id].present? | ||
end | ||
|
||
def load_order | ||
@order = Spree::Order.find_by(number: params[:order_id]) | ||
end | ||
end | ||
end | ||
end |
28 changes: 0 additions & 28 deletions
28
app/controllers/spree/admin/orders_controller_decorator.rb
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
module Spree | ||
class BookkeepingDocument < ActiveRecord::Base | ||
PERSISTED_ATTRS = [ | ||
:firstname, | ||
:lastname, | ||
:email, | ||
:total, | ||
:number | ||
] | ||
|
||
# Spree::BookkeepingDocument cares about creating PDFs. Whenever it needs to know | ||
# anything about the document to send to the view, it asks a view object. | ||
# | ||
# +printable+ should be an Object, such as Spree::Order or Spree::Shipment. | ||
# template should be a string, such as "invoice" or "packaging_slip" | ||
# | ||
belongs_to :printable, polymorphic: true | ||
validates :printable, :template, presence: true | ||
validates *PERSISTED_ATTRS, presence: true, if: -> { self.persisted? } | ||
scope :invoices, -> { where(template: 'invoice') } | ||
|
||
before_create :copy_view_attributes | ||
after_save :after_save_actions | ||
|
||
|
||
# An instance of Spree::Printable::#{YourModel}::#{YourTemplate}Presenter | ||
# | ||
def view | ||
@_view ||= view_class.new(printable) | ||
end | ||
|
||
def date | ||
created_at.to_date | ||
end | ||
|
||
def template_name | ||
"spree/printables/#{single_lower_case_name(printable.class.name)}/#{template}" | ||
end | ||
|
||
# If the document is called from the view with some method it doesn't know, | ||
# just call the view object. It should know. | ||
def method_missing(method_name, *args, &block) | ||
if view.respond_to? method_name | ||
view.send(method_name, *args, &block) | ||
else | ||
super | ||
end | ||
end | ||
|
||
def document_type | ||
"#{printable_type.demodulize.tableize.singularize}_#{template}" | ||
end | ||
|
||
# Returns the given template as pdf binary suitable for Rails send_data | ||
# | ||
# If the file is already present it returns this | ||
# else it generates a new file, stores and returns this. | ||
# | ||
# You can disable the pdf file generation with setting | ||
# | ||
# Spree::PrintInvoice::Config.store_pdf to false | ||
# | ||
def pdf | ||
if Spree::PrintInvoice::Config.store_pdf | ||
send_or_create_pdf | ||
else | ||
render_pdf | ||
end | ||
end | ||
|
||
# = The PDF file_name | ||
# | ||
def file_name | ||
@_file_name ||= "#{template}-#{printable.number}.pdf" | ||
end | ||
|
||
# = PDF file path | ||
# | ||
def file_path | ||
@_file_path ||= Rails.root.join(storage_path, "#{file_name}") | ||
end | ||
|
||
# = PDF storage folder path for given template name | ||
# | ||
# Configure the storage path with +Spree::PrintInvoice::Config.storage_path+ | ||
# | ||
# Each template type gets it own pluralized folder inside | ||
# of +Spree::PrintInvoice::Config.storage_path+ | ||
# | ||
# == Example: | ||
# | ||
# storage_path('invoice') => "tmp/pdf_prints/invoices" | ||
# | ||
# Creates the folder if it's not present yet. | ||
# | ||
def storage_path | ||
storage_path = Rails.root.join(Spree::PrintInvoice::Config.storage_path, template.pluralize) | ||
FileUtils.mkdir_p(storage_path) | ||
storage_path | ||
end | ||
|
||
# Renders the prawn template for give template name in context of ActionView. | ||
# | ||
# Prawn templates need to be placed in the correct folder. For example, for a PDF from | ||
# a Spree::Order with the invoice template, it would be | ||
# the +app/views/spree/printables/order/invoices+ folder. | ||
# | ||
# Assigns +@doc+ instance variable | ||
# | ||
def render_pdf | ||
ActionView::Base.new( | ||
ActionController::Base.view_paths, | ||
{doc: self} | ||
).render(template: "#{template_name}.pdf.prawn") | ||
end | ||
|
||
private | ||
|
||
def copy_view_attributes | ||
PERSISTED_ATTRS.each do |attr| | ||
self.send("#{attr}=", view.send(attr)) | ||
end | ||
end | ||
|
||
# For a Spree::Order printable and an "invoice" template, | ||
# you would get "spree/documents/order/invoice_view" | ||
# --> Spree::Printables::Order::InvoiceView | ||
# | ||
def view_class | ||
@_view_class ||= "#{template_name}_view".classify.constantize | ||
end | ||
|
||
def single_lower_case_name(class_string) | ||
@_single_lower_class_name ||= class_string.demodulize.tableize.singularize | ||
end | ||
|
||
# Sends stored pdf for given template from disk. | ||
# | ||
# Renders and stores it if it's not yet present. | ||
# | ||
def send_or_create_pdf | ||
unless File.exist?(file_path) | ||
File.open(file_path, 'wb') { |f| f.puts render_pdf } | ||
end | ||
|
||
IO.binread(file_path) | ||
end | ||
end | ||
end |
Oops, something went wrong.