mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-02-08 22:56:06 +00:00
Generate invoice model
There are three main components: 1. The invoice model 2. order serializers: serialize the order for the invoice 3. data presenters: the object that will be use to access the order's serialize data
This commit is contained in:
committed by
Konrad
parent
dd224b953d
commit
0fbf88190e
@@ -33,7 +33,7 @@ module Spree
|
||||
|
||||
def model_class
|
||||
const_name = controller_name.classify
|
||||
return "Spree::#{const_name}".constantize if Spree.const_defined?(const_name)
|
||||
return "Spree::#{const_name}".constantize if Object.const_defined?("Spree::#{const_name}")
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
@@ -6,6 +6,10 @@ module Spree
|
||||
respond_to :json
|
||||
authorize_resource class: false
|
||||
|
||||
def index
|
||||
@order = Spree::Order.find_by_number(params[:order_id])
|
||||
end
|
||||
|
||||
def create
|
||||
invoice_service = BulkInvoiceService.new
|
||||
invoice_service.start_pdf_job(params[:order_ids])
|
||||
|
||||
@@ -99,6 +99,11 @@ module Spree
|
||||
end
|
||||
|
||||
def print
|
||||
# This is for testing on realtime
|
||||
# I'll replace it later
|
||||
data = Invoice::OrderSerializer.new(@order).serializable_hash
|
||||
@invoice=Invoice.new(order: @order, data: data, date: Time.now.to_date)
|
||||
@invoice_presenter= @invoice.presenter
|
||||
render_with_wicked_pdf InvoiceRenderer.new.args(@order)
|
||||
end
|
||||
|
||||
|
||||
15
app/models/invoice.rb
Normal file
15
app/models/invoice.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Invoice < ApplicationRecord
|
||||
belongs_to :order, class_name: 'Spree::Order'
|
||||
serialize :data, Hash
|
||||
before_validation :serialize_order
|
||||
|
||||
def presenter
|
||||
@presenter ||= Invoice::DataPresenter.new(self)
|
||||
end
|
||||
|
||||
def serialize_order
|
||||
self.data ||= order.serialize_for_invoice
|
||||
end
|
||||
end
|
||||
102
app/models/invoice/data_presenter.rb
Normal file
102
app/models/invoice/data_presenter.rb
Normal file
@@ -0,0 +1,102 @@
|
||||
class Invoice::DataPresenter
|
||||
attr_reader :invoice
|
||||
delegate :data, :date, to: :invoice
|
||||
|
||||
FINALIZED_NON_SUCCESSFUL_STATES = %w(canceled returned).freeze
|
||||
|
||||
extend Invoice::DataPresenterAttributes
|
||||
|
||||
attributes :included_tax_total, :additional_tax_total, :state, :total, :payment_total,
|
||||
:currency
|
||||
attributes :number, :note, :special_instructions, prefix: :order
|
||||
attributes_with_presenter :order_cycle, :distributor, :customer, :ship_address,
|
||||
:shipping_method, :bill_address
|
||||
|
||||
array_attribute :sorted_line_items, class_name: 'LineItem'
|
||||
array_attribute :all_eligible_adjustments, class_name: 'Adjustment'
|
||||
array_attribute :payments, class_name: 'Payment'
|
||||
|
||||
relevant_attributes :order_note, :distributor, :sorted_line_items
|
||||
|
||||
def initialize(invoice)
|
||||
@invoice = invoice
|
||||
end
|
||||
|
||||
def has_taxes_included
|
||||
included_tax_total > 0
|
||||
end
|
||||
|
||||
def total_tax
|
||||
additional_tax_total + included_tax_total
|
||||
end
|
||||
|
||||
def order_completed_at
|
||||
return nil if data[:completed_at].blank?
|
||||
|
||||
Time.zone.parse(data[:completed_at])
|
||||
end
|
||||
|
||||
def checkout_adjustments(exclude: [], reject_zero_amount: true)
|
||||
adjustments = all_eligible_adjustments
|
||||
|
||||
if exclude.include? :line_item
|
||||
adjustments.reject! { |a|
|
||||
a.adjustable_type == 'Spree::LineItem'
|
||||
}
|
||||
end
|
||||
|
||||
if reject_zero_amount
|
||||
adjustments.reject! { |a| a.amount == 0 }
|
||||
end
|
||||
|
||||
adjustments
|
||||
end
|
||||
|
||||
def invoice_date
|
||||
date
|
||||
end
|
||||
|
||||
def paid?
|
||||
data[:payment_state] == 'paid' || data[:payment_state] == 'credit_owed'
|
||||
end
|
||||
|
||||
def outstanding_balance?
|
||||
!new_outstanding_balance.zero?
|
||||
end
|
||||
|
||||
def new_outstanding_balance
|
||||
if state.in?(FINALIZED_NON_SUCCESSFUL_STATES)
|
||||
-payment_total
|
||||
else
|
||||
total - payment_total
|
||||
end
|
||||
end
|
||||
|
||||
def outstanding_balance_label
|
||||
new_outstanding_balance.negative? ? I18n.t(:credit_owed) : I18n.t(:balance_due)
|
||||
end
|
||||
|
||||
def last_payment
|
||||
payments.max_by(&:created_at)
|
||||
end
|
||||
|
||||
def last_payment_method
|
||||
last_payment&.payment_method
|
||||
end
|
||||
|
||||
def display_outstanding_balance
|
||||
Spree::Money.new(new_outstanding_balance, currency: currency)
|
||||
end
|
||||
|
||||
def display_checkout_tax_total
|
||||
Spree::Money.new(total_tax, currency: currency)
|
||||
end
|
||||
|
||||
def display_checkout_total_less_tax
|
||||
Spree::Money.new(total - total_tax, currency: currency)
|
||||
end
|
||||
|
||||
def display_total
|
||||
Spree::Money.new(total, currency: currency)
|
||||
end
|
||||
end
|
||||
25
app/models/invoice/data_presenter/address.rb
Normal file
25
app/models/invoice/data_presenter/address.rb
Normal file
@@ -0,0 +1,25 @@
|
||||
class Invoice::DataPresenter::Address < Invoice::DataPresenter::Base
|
||||
attributes :firstname, :lastname, :address1, :address2, :city, :zipcode, :company, :phone
|
||||
attributes_with_presenter :state
|
||||
def full_name
|
||||
"#{firstname} #{lastname}".strip
|
||||
end
|
||||
|
||||
def address_part1
|
||||
render_address([address1, address2])
|
||||
end
|
||||
|
||||
def address_part2
|
||||
render_address([city, zipcode, state&.name])
|
||||
end
|
||||
|
||||
def full_address
|
||||
render_address([address1, address2, city, zipcode, state&.name])
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def render_address(address_parts)
|
||||
address_parts.reject(&:blank?).join(', ')
|
||||
end
|
||||
end
|
||||
19
app/models/invoice/data_presenter/adjustment.rb
Normal file
19
app/models/invoice/data_presenter/adjustment.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
class Invoice::DataPresenter::Adjustment < Invoice::DataPresenter::Base
|
||||
attributes :adjustable_type, :label, :included_tax_total, :additional_tax_total, :amount,
|
||||
:currency
|
||||
|
||||
def display_amount
|
||||
Spree::Money.new(amount, currency: currency)
|
||||
end
|
||||
|
||||
def display_taxes(display_zero: false)
|
||||
if included_tax_total.positive?
|
||||
amount = Spree::Money.new(included_tax_total, currency: currency)
|
||||
I18n.t(:tax_amount_included, amount: amount)
|
||||
elsif additional_tax_total.positive?
|
||||
Spree::Money.new(additional_tax_total, currency: currency)
|
||||
elsif display_zero
|
||||
Spree::Money.new(0.00, currency: currency)
|
||||
end
|
||||
end
|
||||
end
|
||||
7
app/models/invoice/data_presenter/base.rb
Normal file
7
app/models/invoice/data_presenter/base.rb
Normal file
@@ -0,0 +1,7 @@
|
||||
class Invoice::DataPresenter::Base
|
||||
attr :data
|
||||
def initialize(data)
|
||||
@data = data
|
||||
end
|
||||
extend Invoice::DataPresenterAttributes
|
||||
end
|
||||
2
app/models/invoice/data_presenter/bill_address.rb
Normal file
2
app/models/invoice/data_presenter/bill_address.rb
Normal file
@@ -0,0 +1,2 @@
|
||||
class Invoice::DataPresenter::BillAddress < Invoice::DataPresenter::Address
|
||||
end
|
||||
2
app/models/invoice/data_presenter/business_address.rb
Normal file
2
app/models/invoice/data_presenter/business_address.rb
Normal file
@@ -0,0 +1,2 @@
|
||||
class Invoice::DataPresenter::BusinessAddress < Invoice::DataPresenter::Address
|
||||
end
|
||||
3
app/models/invoice/data_presenter/contact.rb
Normal file
3
app/models/invoice/data_presenter/contact.rb
Normal file
@@ -0,0 +1,3 @@
|
||||
class Invoice::DataPresenter::Contact < Invoice::DataPresenter::Base
|
||||
attributes :name, :email
|
||||
end
|
||||
3
app/models/invoice/data_presenter/customer.rb
Normal file
3
app/models/invoice/data_presenter/customer.rb
Normal file
@@ -0,0 +1,3 @@
|
||||
class Invoice::DataPresenter::Customer < Invoice::DataPresenter::Base
|
||||
attributes :code, :email
|
||||
end
|
||||
9
app/models/invoice/data_presenter/distributor.rb
Normal file
9
app/models/invoice/data_presenter/distributor.rb
Normal file
@@ -0,0 +1,9 @@
|
||||
class Invoice::DataPresenter::Distributor < Invoice::DataPresenter::Base
|
||||
attributes :name, :abn, :acn, :logo_url, :display_invoice_logo, :invoice_text, :email_address
|
||||
attributes_with_presenter :contact, :address, :business_address
|
||||
relevant_attributes :name
|
||||
|
||||
def display_invoice_logo?
|
||||
display_invoice_logo == true
|
||||
end
|
||||
end
|
||||
20
app/models/invoice/data_presenter/line_item.rb
Normal file
20
app/models/invoice/data_presenter/line_item.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
class Invoice::DataPresenter::LineItem < Invoice::DataPresenter::Base
|
||||
attributes :quantity, :price_with_adjustments, :added_tax, :included_tax, :currency
|
||||
attributes_with_presenter :variant
|
||||
relevant_attributes :quantity
|
||||
delegate :name_to_display, :options_text, to: :variant
|
||||
|
||||
def display_amount_with_adjustments
|
||||
Spree::Money.new(price_with_adjustments, currency: currency)
|
||||
end
|
||||
|
||||
def display_line_items_taxes(display_zero = true)
|
||||
if included_tax.positive?
|
||||
Spree::Money.new( included_tax, currency: currency)
|
||||
elsif added_tax.positive?
|
||||
Spree::Money.new( added_tax, currency: currency)
|
||||
elsif display_zero
|
||||
Spree::Money.new(0.00, currency: currency)
|
||||
end
|
||||
end
|
||||
end
|
||||
3
app/models/invoice/data_presenter/order_cycle.rb
Normal file
3
app/models/invoice/data_presenter/order_cycle.rb
Normal file
@@ -0,0 +1,3 @@
|
||||
class Invoice::DataPresenter::OrderCycle < Invoice::DataPresenter::Base
|
||||
attributes :name
|
||||
end
|
||||
17
app/models/invoice/data_presenter/payment.rb
Normal file
17
app/models/invoice/data_presenter/payment.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
class Invoice::DataPresenter::Payment < Invoice::DataPresenter::Base
|
||||
attributes :amount, :currency, :state
|
||||
attributes_with_presenter :payment_method
|
||||
|
||||
def created_at
|
||||
datetime = data&.[](:created_at)
|
||||
datetime.present? ? Time.zone.parse(datetime) : nil
|
||||
end
|
||||
|
||||
def display_amount
|
||||
Spree::Money.new(amount, currency: currency)
|
||||
end
|
||||
|
||||
def payment_method_name
|
||||
payment_method&.name
|
||||
end
|
||||
end
|
||||
3
app/models/invoice/data_presenter/payment_method.rb
Normal file
3
app/models/invoice/data_presenter/payment_method.rb
Normal file
@@ -0,0 +1,3 @@
|
||||
class Invoice::DataPresenter::PaymentMethod < Invoice::DataPresenter::Base
|
||||
attributes :name, :description
|
||||
end
|
||||
4
app/models/invoice/data_presenter/product.rb
Normal file
4
app/models/invoice/data_presenter/product.rb
Normal file
@@ -0,0 +1,4 @@
|
||||
class Invoice::DataPresenter::Product < Invoice::DataPresenter::Base
|
||||
attributes :name
|
||||
attributes_with_presenter :supplier
|
||||
end
|
||||
2
app/models/invoice/data_presenter/ship_address.rb
Normal file
2
app/models/invoice/data_presenter/ship_address.rb
Normal file
@@ -0,0 +1,2 @@
|
||||
class Invoice::DataPresenter::ShipAddress < Invoice::DataPresenter::Address
|
||||
end
|
||||
3
app/models/invoice/data_presenter/shipping_method.rb
Normal file
3
app/models/invoice/data_presenter/shipping_method.rb
Normal file
@@ -0,0 +1,3 @@
|
||||
class Invoice::DataPresenter::ShippingMethod < Invoice::DataPresenter::Base
|
||||
attributes :name, :require_ship_address
|
||||
end
|
||||
3
app/models/invoice/data_presenter/state.rb
Normal file
3
app/models/invoice/data_presenter/state.rb
Normal file
@@ -0,0 +1,3 @@
|
||||
class Invoice::DataPresenter::State < Invoice::DataPresenter::Base
|
||||
attributes :name
|
||||
end
|
||||
3
app/models/invoice/data_presenter/supplier.rb
Normal file
3
app/models/invoice/data_presenter/supplier.rb
Normal file
@@ -0,0 +1,3 @@
|
||||
class Invoice::DataPresenter::Supplier < Invoice::DataPresenter::Base
|
||||
attributes :name
|
||||
end
|
||||
10
app/models/invoice/data_presenter/variant.rb
Normal file
10
app/models/invoice/data_presenter/variant.rb
Normal file
@@ -0,0 +1,10 @@
|
||||
class Invoice::DataPresenter::Variant < Invoice::DataPresenter::Base
|
||||
attributes :display_name, :options_text
|
||||
attributes_with_presenter :product
|
||||
|
||||
def name_to_display
|
||||
return product.name if display_name.blank?
|
||||
|
||||
display_name
|
||||
end
|
||||
end
|
||||
43
app/models/invoice/data_presenter_attributes.rb
Normal file
43
app/models/invoice/data_presenter_attributes.rb
Normal file
@@ -0,0 +1,43 @@
|
||||
module Invoice::DataPresenterAttributes
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def attributes(*attributes,prefix: nil)
|
||||
attributes.each do |attribute|
|
||||
define_method([prefix,attribute].reject(&:blank?).join("_")) do
|
||||
data&.[](attribute)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def attributes_with_presenter(*attributes)
|
||||
attributes.each do |attribute|
|
||||
define_method(attribute) do
|
||||
instance_variable = instance_variable_get("@#{attribute}")
|
||||
return instance_variable if instance_variable
|
||||
|
||||
instance_variable_set("@#{attribute}",
|
||||
Invoice::DataPresenter.const_get(attribute.to_s.classify).new(data&.[](attribute)))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def array_attribute(attribute_name,class_name: nil)
|
||||
define_method(attribute_name) do
|
||||
instance_variable = instance_variable_get("@#{attribute_name}")
|
||||
return instance_variable if instance_variable
|
||||
|
||||
instance_variable_set("@#{attribute_name}",
|
||||
data&.[](attribute_name)&.map { |item|
|
||||
Invoice::DataPresenter.const_get(class_name).new(item)
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
def relevant_attributes(*attributes)
|
||||
define_method(:relevant_values) do
|
||||
attributes.map do |attribute|
|
||||
send(attribute)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -68,6 +68,7 @@ module Spree
|
||||
},
|
||||
class_name: 'Spree::Adjustment',
|
||||
dependent: :destroy
|
||||
has_many :invoices
|
||||
|
||||
belongs_to :order_cycle
|
||||
belongs_to :distributor, class_name: 'Enterprise'
|
||||
@@ -574,6 +575,29 @@ module Spree
|
||||
end
|
||||
end
|
||||
|
||||
def can_generate_new_invoice?
|
||||
return true if invoices.empty?
|
||||
|
||||
!invoice_comparator.equal? current_state_invoice, invoices.last
|
||||
end
|
||||
|
||||
def invoice_comparator
|
||||
@invoice_comparator ||= InvoiceComparator.new
|
||||
end
|
||||
|
||||
def current_state_invoice
|
||||
Invoice.new(
|
||||
order: self,
|
||||
data: serialize_for_invoice,
|
||||
date: Time.now.to_date,
|
||||
number: invoices.count + 1
|
||||
)
|
||||
end
|
||||
|
||||
def serialize_for_invoice
|
||||
Invoice::OrderSerializer.new(self).serializable_hash
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def deliver_order_confirmation_email
|
||||
|
||||
4
app/serializers/invoice/address_serializer.rb
Normal file
4
app/serializers/invoice/address_serializer.rb
Normal file
@@ -0,0 +1,4 @@
|
||||
class Invoice::AddressSerializer < ActiveModel::Serializer
|
||||
attributes :firstname, :lastname, :address1, :address2, :city, :zipcode, :phone, :company
|
||||
has_one :state, serializer: Invoice::StateSerializer
|
||||
end
|
||||
3
app/serializers/invoice/adjustment_serializer.rb
Normal file
3
app/serializers/invoice/adjustment_serializer.rb
Normal file
@@ -0,0 +1,3 @@
|
||||
class Invoice::AdjustmentSerializer < ActiveModel::Serializer
|
||||
attributes :adjustable_type, :label, :included_tax_total,:additional_tax_total, :amount, :currency
|
||||
end
|
||||
3
app/serializers/invoice/customer_serializer.rb
Normal file
3
app/serializers/invoice/customer_serializer.rb
Normal file
@@ -0,0 +1,3 @@
|
||||
class Invoice::CustomerSerializer < ActiveModel::Serializer
|
||||
attributes :code, :email
|
||||
end
|
||||
9
app/serializers/invoice/enterprise_serializer.rb
Normal file
9
app/serializers/invoice/enterprise_serializer.rb
Normal file
@@ -0,0 +1,9 @@
|
||||
class Invoice::EnterpriseSerializer < ActiveModel::Serializer
|
||||
attributes :name, :abn, :acn, :invoice_text, :email_address, :display_invoice_logo, :logo_url
|
||||
has_one :contact, serializer: Invoice::UserSerializer
|
||||
has_one :business_address, serializer: Invoice::AddressSerializer
|
||||
has_one :address, serializer: Invoice::AddressSerializer
|
||||
def logo_url
|
||||
object.logo_url(:small)
|
||||
end
|
||||
end
|
||||
8
app/serializers/invoice/line_item_serializer.rb
Normal file
8
app/serializers/invoice/line_item_serializer.rb
Normal file
@@ -0,0 +1,8 @@
|
||||
class Invoice::LineItemSerializer < ActiveModel::Serializer
|
||||
attributes :quantity,
|
||||
:price_with_adjustments,
|
||||
:added_tax,
|
||||
:included_tax
|
||||
|
||||
has_one :variant, serializer: Invoice::VariantSerializer
|
||||
end
|
||||
3
app/serializers/invoice/order_cycle_serializer.rb
Normal file
3
app/serializers/invoice/order_cycle_serializer.rb
Normal file
@@ -0,0 +1,3 @@
|
||||
class Invoice::OrderCycleSerializer < ActiveModel::Serializer
|
||||
attributes :name
|
||||
end
|
||||
22
app/serializers/invoice/order_serializer.rb
Normal file
22
app/serializers/invoice/order_serializer.rb
Normal file
@@ -0,0 +1,22 @@
|
||||
class Invoice::OrderSerializer < ActiveModel::Serializer
|
||||
attributes :number, :special_instructions, :note, :payment_state, :total, :payment_total, :state,
|
||||
:currency, :additional_tax_total, :included_tax_total, :completed_at, :has_taxes_included
|
||||
has_one :order_cycle, serializer: Invoice::OrderCycleSerializer
|
||||
has_one :customer, serializer: Invoice::CustomerSerializer
|
||||
has_one :distributor, serializer: Invoice::EnterpriseSerializer
|
||||
has_one :bill_address, serializer: Invoice::AddressSerializer
|
||||
has_one :shipping_method, serializer: Invoice::ShippingMethodSerializer
|
||||
has_one :ship_address, serializer: Invoice::AddressSerializer
|
||||
has_many :sorted_line_items, serializer: Invoice::LineItemSerializer
|
||||
has_many :sorted_line_items, serializer: Invoice::LineItemSerializer
|
||||
has_many :payments, serializer: Invoice::PaymentSerializer
|
||||
has_many :all_eligible_adjustments, serializer: Invoice::AdjustmentSerializer
|
||||
|
||||
def all_eligible_adjustments
|
||||
object.all_adjustments.eligible.where.not(originator_type: 'Spree::TaxRate')
|
||||
end
|
||||
|
||||
def completed_at
|
||||
object.completed_at.to_s
|
||||
end
|
||||
end
|
||||
3
app/serializers/invoice/payment_method_serializer.rb
Normal file
3
app/serializers/invoice/payment_method_serializer.rb
Normal file
@@ -0,0 +1,3 @@
|
||||
class Invoice::PaymentMethodSerializer < ActiveModel::Serializer
|
||||
attributes :name, :description
|
||||
end
|
||||
8
app/serializers/invoice/payment_serializer.rb
Normal file
8
app/serializers/invoice/payment_serializer.rb
Normal file
@@ -0,0 +1,8 @@
|
||||
class Invoice::PaymentSerializer < ActiveModel::Serializer
|
||||
attributes :state, :created_at, :amount, :currency
|
||||
has_one :payment_method, serializer: Invoice::PaymentMethodSerializer
|
||||
|
||||
def created_at
|
||||
object.created_at.to_s
|
||||
end
|
||||
end
|
||||
4
app/serializers/invoice/product_serializer.rb
Normal file
4
app/serializers/invoice/product_serializer.rb
Normal file
@@ -0,0 +1,4 @@
|
||||
class Invoice::ProductSerializer < ActiveModel::Serializer
|
||||
attributes :name
|
||||
has_one :supplier, serializer: Invoice::EnterpriseSerializer
|
||||
end
|
||||
3
app/serializers/invoice/shipping_method_serializer.rb
Normal file
3
app/serializers/invoice/shipping_method_serializer.rb
Normal file
@@ -0,0 +1,3 @@
|
||||
class Invoice::ShippingMethodSerializer < ActiveModel::Serializer
|
||||
attributes :name, :require_ship_address
|
||||
end
|
||||
3
app/serializers/invoice/state_serializer.rb
Normal file
3
app/serializers/invoice/state_serializer.rb
Normal file
@@ -0,0 +1,3 @@
|
||||
class Invoice::StateSerializer < ActiveModel::Serializer
|
||||
attributes :name
|
||||
end
|
||||
3
app/serializers/invoice/user_serializer.rb
Normal file
3
app/serializers/invoice/user_serializer.rb
Normal file
@@ -0,0 +1,3 @@
|
||||
class Invoice::UserSerializer < ActiveModel::Serializer
|
||||
attributes :email
|
||||
end
|
||||
4
app/serializers/invoice/variant_serializer.rb
Normal file
4
app/serializers/invoice/variant_serializer.rb
Normal file
@@ -0,0 +1,4 @@
|
||||
class Invoice::VariantSerializer < ActiveModel::Serializer
|
||||
attributes :display_name, :options_text
|
||||
has_one :product, serializer: Invoice::ProductSerializer
|
||||
end
|
||||
38
app/services/order_invoice_comparator.rb
Normal file
38
app/services/order_invoice_comparator.rb
Normal file
@@ -0,0 +1,38 @@
|
||||
class OrderInvoiceComparator
|
||||
def equal?(invoice1, invoice2)
|
||||
# We'll use a recursive BFS algorithm to find if current state of invoice is outdated
|
||||
# the root will be the order
|
||||
# On each node, we'll a list of relevant attributes that will be used on the comparison
|
||||
bfs(invoice1.presenter, invoice2.presenter)
|
||||
end
|
||||
|
||||
def bfs(node1, node2)
|
||||
simple_values1, presenters1 = group_relevant_values(node1)
|
||||
simple_values2, presenters2 = group_relevant_values(node2)
|
||||
return false if simple_values1 != simple_values2
|
||||
|
||||
return false if presenters1.size != presenters2.size
|
||||
|
||||
presenters1.zip(presenters2).each do |presenter1, presenter2|
|
||||
return false unless bfs(presenter1, presenter2)
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
def group_relevant_values(node)
|
||||
return [[], []] unless node.respond_to?(:relevant_values)
|
||||
|
||||
grouped = node.relevant_values.group_by(&grouper)
|
||||
[grouped[:simple] || [], grouped[:presenters]&.flatten || []]
|
||||
end
|
||||
|
||||
def grouper
|
||||
proc do |value|
|
||||
if value.is_a?(Array) || value.class.to_s.starts_with?("Invoice::DataPresenter")
|
||||
:presenters
|
||||
else
|
||||
:simple
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
24
app/views/spree/admin/invoices/_invoices_table.html.haml
Normal file
24
app/views/spree/admin/invoices/_invoices_table.html.haml
Normal file
@@ -0,0 +1,24 @@
|
||||
%table.index{"data-hook" => "invoices"}
|
||||
%thead{"data-hook" => "invoice_head"}
|
||||
%tr
|
||||
%th= "#{t('spree.date')}/#{t('spree.time')}"
|
||||
%th= t(:invoice_number)
|
||||
%th= t(:amount)
|
||||
%th= t(:status)
|
||||
%th= t(:file)
|
||||
%tbody
|
||||
- @order.invoices.each do |invoice|
|
||||
- tr_class = cycle('odd', 'even')
|
||||
- tr_id = spree_dom_id(invoice)
|
||||
%tr{:class => tr_class, "data-hook" => "invoice_row", :id => tr_id}
|
||||
%td.align-center.created_at
|
||||
= pretty_time(invoice.date)
|
||||
%td.align-center.label
|
||||
= invoice.number
|
||||
%td.align-center.label
|
||||
= invoice.data['order']['total']
|
||||
%td.align-center.label
|
||||
= t(invoice.status)
|
||||
%td.align-center.label
|
||||
=link_to(t(:download),invoice.status)
|
||||
|
||||
14
app/views/spree/admin/invoices/index.html.haml
Normal file
14
app/views/spree/admin/invoices/index.html.haml
Normal file
@@ -0,0 +1,14 @@
|
||||
= render partial: 'spree/admin/shared/order_page_title'
|
||||
= render partial: 'spree/admin/shared/order_tabs', locals: { current: 'Invoices' }
|
||||
|
||||
- content_for :page_title do
|
||||
%i.icon-arrow-right
|
||||
= t(:invoices)
|
||||
|
||||
- content_for :page_actions do
|
||||
- if @order.can_generate_new_invoice?
|
||||
%li= button_link_to t(:new_invoice), new_admin_order_adjustment_url(@order), :icon => 'icon-plus', disabled: true
|
||||
= render partial: 'spree/admin/shared/order_links'
|
||||
%li= button_link_to t(:back_to_orders_list), admin_orders_path, :icon => 'icon-arrow-left'
|
||||
|
||||
= render :partial => 'invoices_table'
|
||||
@@ -0,0 +1,6 @@
|
||||
%h5.inline-header
|
||||
= "#{raw(line_item.variant.product.name)}"
|
||||
- unless line_item.variant.product.name.include? line_item.name_to_display
|
||||
%span= "- #{raw(line_item.name_to_display)}"
|
||||
- if line_item.options_text
|
||||
= "(#{raw(line_item.options_text)})"
|
||||
@@ -0,0 +1,4 @@
|
||||
%p.callout{style: "margin-top: 30px"}
|
||||
%strong= t :additional_information
|
||||
%p{style: "margin: 5px"}
|
||||
= @invoice_presenter.order_note
|
||||
20
app/views/spree/admin/orders/_invoice/_payment.html.haml
Normal file
20
app/views/spree/admin/orders/_invoice/_payment.html.haml
Normal file
@@ -0,0 +1,20 @@
|
||||
%p.callout
|
||||
%span{:style => "float:right;"}
|
||||
- if @invoice_presenter.outstanding_balance?
|
||||
= @invoice_presenter.outstanding_balance_label
|
||||
\:
|
||||
%strong= @invoice_presenter.display_outstanding_balance
|
||||
- else
|
||||
- if @invoice_presenter.paid?
|
||||
= t :email_payment_paid
|
||||
- else
|
||||
= t :email_payment_not_paid
|
||||
%strong
|
||||
= t :email_payment_summary
|
||||
- if @invoice_presenter.payments.any?
|
||||
= render partial: 'spree/admin/orders/_invoice/payments_list', locals: { payments: @invoice_presenter.payments }
|
||||
- if @invoice_presenter.last_payment_method
|
||||
%p.callout{style: "margin-top: 40px"}
|
||||
%strong
|
||||
= t :email_payment_description
|
||||
%p{style: "margin: 5px"}= @invoice_presenter.last_payment_method.description
|
||||
@@ -0,0 +1,14 @@
|
||||
%table.payments-list
|
||||
%thead
|
||||
%tr
|
||||
%th= t('.date_time')
|
||||
%th= t('.payment_method')
|
||||
%th.payment-state= t('.payment_state')
|
||||
%th.amount= t('.amount')
|
||||
%tbody
|
||||
- payments.each do |payment|
|
||||
%tr
|
||||
%td= l(payment.created_at, format: "%b %d, %Y %H:%M")
|
||||
%td.payment-method-name= payment.payment_method_name
|
||||
%td.payment-state.payment-state-value= t(payment.state, scope: [:spree, :payment_states], default: payment.state.capitalize)
|
||||
%td.amount= payment.display_amount.to_html
|
||||
@@ -6,33 +6,33 @@
|
||||
%th{:align => "right", :width => "15%"}
|
||||
%h4= t(:invoice_column_qty)
|
||||
%th{:align => "right", :width => "15%"}
|
||||
%h4= @order.total_tax > 0 ? t(:invoice_column_tax) : ""
|
||||
%h4= @invoice_presenter.total_tax > 0 ? t(:invoice_column_tax) : ""
|
||||
%th{:align => "right", :width => "15%"}
|
||||
%h4= t(:invoice_column_price)
|
||||
%tbody
|
||||
- @order.sorted_line_items.each do |item|
|
||||
- @invoice_presenter.sorted_line_items.each do |item|
|
||||
%tr
|
||||
%td
|
||||
= render 'spree/shared/line_item_name', line_item: item
|
||||
= render 'spree/admin/orders/_invoice/line_item_name', line_item: item
|
||||
%br
|
||||
%small
|
||||
%em= raw(item.variant.product.supplier.name)
|
||||
%td{:align => "right"}
|
||||
= item.quantity
|
||||
%td{:align => "right"}
|
||||
= display_line_items_taxes(item)
|
||||
= item.display_line_items_taxes
|
||||
%td{:align => "right"}
|
||||
= item.display_amount_with_adjustments
|
||||
|
||||
- checkout_adjustments_for(@order, exclude: [:line_item]).reverse_each do |adjustment|
|
||||
- taxable = adjustment.adjustable_type == "Spree::Shipment" ? adjustment.adjustable : adjustment
|
||||
- @invoice_presenter.checkout_adjustments(exclude: [:line_item]).reverse_each do |adjustment|
|
||||
- taxable = adjustment#.adjustable_type == "Spree::Shipment" ? adjustment.adjustable : adjustment
|
||||
%tr
|
||||
%td
|
||||
%strong= "#{raw(adjustment.label)}"
|
||||
%td{:align => "right"}
|
||||
1
|
||||
%td{:align => "right"}
|
||||
= display_taxes(taxable, display_zero: false)
|
||||
= adjustment.display_taxes
|
||||
%td{:align => "right"}
|
||||
= adjustment.display_amount
|
||||
%tfoot
|
||||
@@ -40,16 +40,16 @@
|
||||
%td{:align => "right", :colspan => "2"}
|
||||
%strong= t(:invoice_tax_total)
|
||||
%td{:align => "right", :colspan => "2"}
|
||||
%strong= display_checkout_tax_total(@order)
|
||||
%strong= @invoice_presenter.display_checkout_tax_total
|
||||
%tr
|
||||
%td{:align => "right", :colspan => "2"}
|
||||
%strong= t(:total_excl_tax)
|
||||
%td{:align => "right", :colspan => "2"}
|
||||
%strong= display_checkout_total_less_tax(@order)
|
||||
%strong= @invoice_presenter.display_checkout_total_less_tax
|
||||
%tr
|
||||
%td{:align => "right", :colspan => "2"}
|
||||
%strong= t(:total_incl_tax)
|
||||
%td{:align => "right", :colspan => "2"}
|
||||
%strong= @order.display_total
|
||||
%strong= @invoice_presenter.display_total
|
||||
%p
|
||||
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
%th{:align => "right", :width => "15%"}
|
||||
%h5= t(:invoice_column_qty)
|
||||
%th{:align => "right", :width => "15%"}
|
||||
%h5= @order.has_taxes_included ? t(:invoice_column_unit_price_with_taxes) : t(:invoice_column_unit_price_without_taxes)
|
||||
%h5= @invoice_presenter.has_taxes_included ? t(:invoice_column_unit_price_with_taxes) : t(:invoice_column_unit_price_without_taxes)
|
||||
%th{:align => "right", :width => "15%"}
|
||||
%h5= @order.has_taxes_included ? t(:invoice_column_price_with_taxes) : t(:invoice_column_price_without_taxes)
|
||||
- if @order.total_tax > 0
|
||||
%h5= @invoice_presenter.has_taxes_included ? t(:invoice_column_price_with_taxes) : t(:invoice_column_price_without_taxes)
|
||||
- if @invoice_presenter.total_tax > 0
|
||||
%th{:align => "right", :width => "15%"}
|
||||
%h5= t(:invoice_column_tax_rate)
|
||||
%tbody
|
||||
@@ -44,9 +44,9 @@
|
||||
%tfoot
|
||||
%tr
|
||||
%td{:align => "right", :colspan => "3"}
|
||||
%strong= @order.has_taxes_included ? t(:total_incl_tax) : t(:total_excl_tax)
|
||||
%strong= @invoice_presenter.has_taxes_included ? t(:total_incl_tax) : t(:total_excl_tax)
|
||||
%td{:align => "right", :colspan => "2"}
|
||||
%strong= @order.has_taxes_included ? @order.display_total : display_checkout_total_less_tax(@order)
|
||||
%strong= @invoice_presenter.has_taxes_included ? @invoice_presenter.display_total : @invoice_presenter.display_checkout_total_less_tax
|
||||
- display_checkout_taxes_hash(@order).each do |tax|
|
||||
%tr
|
||||
%td{:align => "right", :colspan => "3"}
|
||||
@@ -55,8 +55,8 @@
|
||||
= tax[:amount]
|
||||
%tr
|
||||
%td{:align => "right", :colspan => "3"}
|
||||
= @order.has_taxes_included ? t(:total_excl_tax) : t(:total_incl_tax)
|
||||
= @invoice_presenter.has_taxes_included ? t(:total_excl_tax) : t(:total_incl_tax)
|
||||
%td{:align => "right", :colspan => "2"}
|
||||
= @order.has_taxes_included ? display_checkout_total_less_tax(@order) : @order.display_total
|
||||
= @invoice_presenter.has_taxes_included ? @invoice_presenter.display_checkout_total_less_tax : @invoice_presenter.display_total
|
||||
%p
|
||||
|
||||
|
||||
@@ -6,32 +6,32 @@
|
||||
%td{ :align => "left", colspan: 3 }
|
||||
%h6
|
||||
= "#{t('.issued_on')}: "
|
||||
= l Time.zone.now.to_date
|
||||
= l @invoice_presenter.invoice_date
|
||||
%tr{ valign: "top" }
|
||||
%td{ :align => "left" }
|
||||
%h4
|
||||
= "#{t('.tax_invoice')}: "
|
||||
= "#{@order.number}"
|
||||
= "#{@invoice_presenter.order_number}"
|
||||
%td{width: "10%" }
|
||||
|
||||
%td{ :align => "right" }
|
||||
%h4= @order.order_cycle&.name
|
||||
%h4= @invoice_presenter.order_cycle.name
|
||||
%tr{ valign: "top" }
|
||||
%td{ align: "left", colspan: 3 }
|
||||
- if @order.distributor.business_address.blank?
|
||||
%strong= "#{t('.from')}: #{@order.distributor.name}"
|
||||
- if @invoice_presenter.distributor.business_address.blank?
|
||||
%strong= "#{t('.from')}: #{@invoice_presenter.distributor.name}"
|
||||
- else
|
||||
%strong= "#{t('.from')}: #{@order.distributor.business_address.company}"
|
||||
- if @order.distributor.abn.present?
|
||||
%strong= "#{t('.from')}: #{@invoice_presenter.distributor.business_address.company}"
|
||||
- if @invoice_presenter.distributor.abn.present?
|
||||
%br
|
||||
= "#{t(:abn)} #{@order.distributor.abn}"
|
||||
= "#{t(:abn)} #{@invoice_presenter.distributor.abn}"
|
||||
%br
|
||||
- if @order.distributor.business_address.blank?
|
||||
= @order.distributor.address.full_address
|
||||
- if @invoice_presenter.distributor.business_address.blank?
|
||||
= @invoice_presenter.distributor.address.full_address
|
||||
- else
|
||||
= @order.distributor.business_address.full_address
|
||||
= @invoice_presenter.distributor.business_address.full_address
|
||||
%br
|
||||
= @order.distributor.contact.email
|
||||
= @invoice_presenter.distributor.contact.email
|
||||
%tr{ valign: "top" }
|
||||
%td{ colspan: 3 }
|
||||
|
||||
@@ -39,44 +39,44 @@
|
||||
%td{ align: "left" }
|
||||
%strong= "#{t('.to')}:"
|
||||
%br
|
||||
- if @order.bill_address
|
||||
= @order.bill_address.full_name
|
||||
- if @order&.customer&.code.present?
|
||||
- if @invoice_presenter.bill_address
|
||||
= @invoice_presenter.bill_address.full_name
|
||||
- if @invoice_presenter.customer.code.present?
|
||||
%br
|
||||
= "#{t('.code')}: #{@order.customer.code}"
|
||||
= "#{t('.code')}: #{@invoice_presenter.customer.code}"
|
||||
%br
|
||||
- if @order.bill_address
|
||||
= @order.bill_address.full_address
|
||||
- if @invoice_presenter.bill_address
|
||||
= @invoice_presenter.bill_address.full_address
|
||||
%br
|
||||
- if @order&.customer&.email.present?
|
||||
= "#{@order.customer.email},"
|
||||
- if @order.bill_address
|
||||
= "#{@order.bill_address.phone}"
|
||||
- if @invoice_presenter.customer.email.present?
|
||||
= "#{@invoice_presenter.customer.email},"
|
||||
- if @invoice_presenter.bill_address
|
||||
= "#{@invoice_presenter.bill_address.phone}"
|
||||
%td
|
||||
|
||||
%td{ align: "left", style: "border-left: .1em solid black; padding-left: 1em" }
|
||||
%strong= "#{t('.shipping')}: #{@order.shipping_method&.name}"
|
||||
- if @order.shipping_method&.require_ship_address
|
||||
%strong= "#{t('.shipping')}: #{@invoice_presenter.shipping_method.name}"
|
||||
- if @invoice_presenter.shipping_method.require_ship_address
|
||||
%br
|
||||
= @order.ship_address.full_name
|
||||
= @invoice_presenter.ship_address.full_name
|
||||
%br
|
||||
= @order.ship_address.full_address
|
||||
= @invoice_presenter.ship_address.full_address
|
||||
%br
|
||||
= @order.ship_address.phone
|
||||
- if @order.special_instructions.present?
|
||||
= @invoice_presenter.ship_address.phone
|
||||
- if @invoice_presenter.order_special_instructions.present?
|
||||
%br
|
||||
%br
|
||||
%strong= t :customer_instructions
|
||||
= @order.special_instructions
|
||||
= @invoice_presenter.order_special_instructions
|
||||
|
||||
|
||||
= render 'spree/admin/orders/invoice_table'
|
||||
|
||||
- if @order.distributor.invoice_text.present?
|
||||
- if @invoice_presenter.distributor.invoice_text.present?
|
||||
%p
|
||||
= @order.distributor.invoice_text
|
||||
= @invoice_presenter.distributor.invoice_text
|
||||
|
||||
= render 'spree/shared/payment'
|
||||
= render 'spree/admin/orders/_invoice/payment'
|
||||
|
||||
- if @order.note.present?
|
||||
= render partial: 'spree/shared/order_note'
|
||||
- if @invoice_presenter.order_note.present?
|
||||
= render partial: 'spree/admin/orders/_invoice/order_note'
|
||||
|
||||
@@ -6,89 +6,89 @@
|
||||
%td{ :align => "left" }
|
||||
%h4
|
||||
= t :tax_invoice
|
||||
- if @order.distributor.display_invoice_logo? && @order.distributor.logo.variable?
|
||||
- if @invoice_presenter.distributor.display_invoice_logo? && @invoice_presenter.distributor.logo_url
|
||||
%td{ :align => "right", rowspan: 2 }
|
||||
= wicked_pdf_image_tag @order.distributor.logo_url(:small)
|
||||
= wicked_pdf_image_tag @invoice_presenter.distributor.logo_url
|
||||
%tr{ valign: "top" }
|
||||
%td{ :align => "left" }
|
||||
- if @order.distributor.business_address.blank?
|
||||
%strong= @order.distributor.name
|
||||
- if @invoice_presenter.distributor.business_address.blank?
|
||||
%strong= @invoice_presenter.distributor.name
|
||||
%br
|
||||
= @order.distributor.address.address_part1
|
||||
= @invoice_presenter.distributor.address.address_part1
|
||||
%br
|
||||
= @order.distributor.address.address_part2
|
||||
= @invoice_presenter.distributor.address.address_part2
|
||||
%br
|
||||
= @order.distributor.email_address
|
||||
- if @order.distributor.phone.present?
|
||||
= @invoice_presenter.distributor.email_address
|
||||
- if @invoice_presenter.distributor.phone.present?
|
||||
%br
|
||||
= @order.distributor.phone
|
||||
= @invoice_presenter.distributor.phone
|
||||
- else
|
||||
%strong= @order.distributor.business_address.company
|
||||
%strong= @invoice_presenter.distributor.business_address.company
|
||||
%br
|
||||
= @order.distributor.business_address.address_part1
|
||||
= @invoice_presenter.distributor.business_address.address_part1
|
||||
%br
|
||||
= @order.distributor.business_address.address_part2
|
||||
= @invoice_presenter.distributor.business_address.address_part2
|
||||
%br
|
||||
= @order.distributor.email_address
|
||||
- if @order.distributor.business_address.phone.present?
|
||||
= @invoice_presenter.distributor.email_address
|
||||
- if @invoice_presenter.distributor.business_address.phone.present?
|
||||
%br
|
||||
= @order.distributor.business_address.phone
|
||||
- if @order.distributor.abn.present?
|
||||
= @invoice_presenter.distributor.business_address.phone
|
||||
- if @invoice_presenter.distributor.abn.present?
|
||||
%br
|
||||
= "#{t :abn} #{@order.distributor.abn}"
|
||||
- if @order.distributor.acn.present?
|
||||
= "#{t :abn} #{@invoice_presenter.distributor.abn}"
|
||||
- if @invoice_presenter.distributor.acn.present?
|
||||
%br
|
||||
= "#{t :acn} #{@order.distributor.acn}"
|
||||
= "#{t :acn} #{@invoice_presenter.distributor.acn}"
|
||||
%tr{ valign: "top" }
|
||||
%td{ :align => "left", colspan: 2 }
|
||||
%tr{ valign: "top" }
|
||||
%td{ :align => "left" }
|
||||
%br
|
||||
= t :invoice_issued_on
|
||||
= l Time.zone.now.to_date
|
||||
= l @invoice_presenter.invoice_date
|
||||
%br
|
||||
= t :date_of_transaction
|
||||
= l @order.completed_at.to_date
|
||||
= l @invoice_presenter.order_completed_at.to_date
|
||||
%br
|
||||
= t :order_number
|
||||
= @order.number
|
||||
= @invoice_presenter.order_number
|
||||
%td{ :align => "right" }
|
||||
= t :invoice_billing_address
|
||||
%br
|
||||
- if @order.bill_address
|
||||
%strong= @order.bill_address.full_name
|
||||
- if @order&.customer&.code.present?
|
||||
- if @invoice_presenter.bill_address
|
||||
%strong= @invoice_presenter.bill_address.full_name
|
||||
- if @invoice_presenter&.customer&.code.present?
|
||||
%br
|
||||
= "Code: #{@order.customer.code}"
|
||||
= "Code: #{@invoice_presenter.customer.code}"
|
||||
%br
|
||||
- if @order.bill_address
|
||||
= @order.bill_address.address_part1
|
||||
- if @invoice_presenter.bill_address
|
||||
= @invoice_presenter.bill_address.address_part1
|
||||
%br
|
||||
- if @order.bill_address
|
||||
= @order.bill_address.address_part2
|
||||
- if @order.bill_address.phone.present?
|
||||
- if @invoice_presenter.bill_address
|
||||
= @invoice_presenter.bill_address.address_part2
|
||||
- if @invoice_presenter.bill_address.phone.present?
|
||||
%br
|
||||
= @order.bill_address.phone
|
||||
- if @order&.customer&.email.present?
|
||||
= @invoice_presenter.bill_address.phone
|
||||
- if @invoice_presenter&.customer&.email.present?
|
||||
%br
|
||||
= @order.customer.email
|
||||
= @invoice_presenter.customer.email
|
||||
|
||||
= render 'spree/admin/orders/invoice_table2'
|
||||
|
||||
- if @order.special_instructions.present?
|
||||
- if @invoice_presenter.order_special_instructions.present?
|
||||
%p.callout
|
||||
%strong
|
||||
= t :customer_instructions
|
||||
%p
|
||||
%em= @order.special_instructions
|
||||
%em= @invoice_presenter.order_special_instructions
|
||||
%p
|
||||
|
||||
|
||||
- if @order.distributor.invoice_text.present?
|
||||
- if @invoice_presenter.distributor.invoice_text.present?
|
||||
%p
|
||||
= @order.distributor.invoice_text
|
||||
= @invoice_presenter.distributor.invoice_text
|
||||
|
||||
= render 'spree/shared/payment'
|
||||
= render 'spree/admin/orders/_invoice/payment'
|
||||
|
||||
- if @order.note.present?
|
||||
= render partial: 'spree/shared/order_note'
|
||||
- if @invoice_presenter.order_note.present?
|
||||
= render partial: 'spree/admin/orders/_invoice/order_note'
|
||||
|
||||
@@ -61,6 +61,10 @@
|
||||
%li{ class: adjustments_classes }
|
||||
= link_to_with_icon 'icon-cogs', t(:adjustments), spree.admin_order_adjustments_url(@order)
|
||||
|
||||
- invoices_classes = "active" if current == 'Invoices'
|
||||
%li{ class: invoices_classes }
|
||||
= link_to_with_icon 'icon-cogs', t(:invoices), spree.admin_order_invoices_url(@order)
|
||||
|
||||
- if @order.completed?
|
||||
- authorizations_classes = "active" if current == "Return Authorizations"
|
||||
%li{ class: authorizations_classes }
|
||||
|
||||
@@ -3117,8 +3117,10 @@ See the %{link} to find out more about %{sitename}'s features and to start using
|
||||
no_orders_found: "No Orders Found"
|
||||
order_information: "Order Information"
|
||||
new_payment: "New Payment"
|
||||
new_invoice: "New Invoice"
|
||||
date_completed: "Date Completed"
|
||||
amount: "Amount"
|
||||
invoice_number: "Invoice Number"
|
||||
state_names:
|
||||
ready: Ready
|
||||
pending: Pending
|
||||
|
||||
@@ -100,6 +100,7 @@ Spree::Core::Engine.routes.draw do
|
||||
end
|
||||
|
||||
resources :adjustments
|
||||
resources :invoices
|
||||
|
||||
resources :payments do
|
||||
member do
|
||||
|
||||
15
db/migrate/20230308075421_create_invoices.rb
Normal file
15
db/migrate/20230308075421_create_invoices.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class CreateInvoices < ActiveRecord::Migration[6.1]
|
||||
def change
|
||||
create_table :invoices do |t|
|
||||
t.references :order, foreign_key: true, foreign_key: { to_table: :spree_orders }
|
||||
t.string :status
|
||||
t.integer :number
|
||||
t.jsonb :data
|
||||
t.date :date, default: -> { "CURRENT_TIMESTAMP" }, nil: false
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
||||
12
db/schema.rb
12
db/schema.rb
@@ -302,6 +302,17 @@ ActiveRecord::Schema[7.0].define(version: 2023_05_22_120633) do
|
||||
t.index ["enterprise_id", "variant_id"], name: "index_inventory_items_on_enterprise_id_and_variant_id", unique: true
|
||||
end
|
||||
|
||||
create_table "invoices", force: :cascade do |t|
|
||||
t.bigint "order_id"
|
||||
t.string "status"
|
||||
t.integer "number"
|
||||
t.jsonb "data"
|
||||
t.date "date", default: -> { "CURRENT_TIMESTAMP" }
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["order_id"], name: "index_invoices_on_order_id"
|
||||
end
|
||||
|
||||
create_table "order_cycle_schedules", id: :serial, force: :cascade do |t|
|
||||
t.integer "order_cycle_id", null: false
|
||||
t.integer "schedule_id", null: false
|
||||
@@ -1259,6 +1270,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_05_22_120633) do
|
||||
add_foreign_key "exchanges", "enterprises", column: "receiver_id", name: "exchanges_receiver_id_fk"
|
||||
add_foreign_key "exchanges", "enterprises", column: "sender_id", name: "exchanges_sender_id_fk"
|
||||
add_foreign_key "exchanges", "order_cycles", name: "exchanges_order_cycle_id_fk"
|
||||
add_foreign_key "invoices", "spree_orders", column: "order_id"
|
||||
add_foreign_key "order_cycle_schedules", "order_cycles", name: "oc_schedules_order_cycle_id_fk"
|
||||
add_foreign_key "order_cycle_schedules", "schedules", name: "oc_schedules_schedule_id_fk"
|
||||
add_foreign_key "order_cycles", "enterprises", column: "coordinator_id", name: "order_cycles_coordinator_id_fk"
|
||||
|
||||
6
spec/factories/invoice_factory.rb
Normal file
6
spec/factories/invoice_factory.rb
Normal file
@@ -0,0 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :invoice, class: Invoice do
|
||||
end
|
||||
end
|
||||
7
spec/models/invoice_spec.rb
Normal file
7
spec/models/invoice_spec.rb
Normal file
@@ -0,0 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Invoice, type: :model do
|
||||
pending "add some examples to (or delete) #{__FILE__}"
|
||||
end
|
||||
54
spec/services/order_invoice_comparator_spec.rb
Normal file
54
spec/services/order_invoice_comparator_spec.rb
Normal file
@@ -0,0 +1,54 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe OrderInvoiceComparator do
|
||||
describe '#equal?' do
|
||||
let!(:order) { create(:completed_order_with_fees) }
|
||||
let(:current_state_invoice){ order.current_state_invoice }
|
||||
let!(:invoice){ create(:invoice, order: order) }
|
||||
|
||||
context "changes on the order object" do
|
||||
it "returns true if the order didn't change" do
|
||||
expect(OrderInvoiceComparator.new.equal?(current_state_invoice, invoice)).to be true
|
||||
end
|
||||
|
||||
it "returns true if a relevant attribute changes" do
|
||||
order.update!(note: 'THIS IS AN UPDATE')
|
||||
|
||||
expect(OrderInvoiceComparator.new.equal?(current_state_invoice, invoice)).to be false
|
||||
end
|
||||
|
||||
it "returns true if a non-relevant attribute changes" do
|
||||
order.update!(last_ip_address: "192.168.172.165")
|
||||
|
||||
expect(OrderInvoiceComparator.new.equal?(current_state_invoice, invoice)).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context "change on associate objects (belong_to)" do
|
||||
let(:distributor){ order.distributor }
|
||||
|
||||
it "returns false if the distributor change relavant attribute" do
|
||||
distributor.update!(name: 'THIS IS A NEW NAME')
|
||||
|
||||
expect(OrderInvoiceComparator.new.equal?(current_state_invoice, invoice)).to be false
|
||||
end
|
||||
|
||||
it "returns true if the distributor change non-relavant attribute" do
|
||||
distributor.update!(description: 'THIS IS A NEW DESCRIPTION')
|
||||
|
||||
expect(OrderInvoiceComparator.new.equal?(current_state_invoice, invoice)).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context "changes on associate objects (has_many)" do
|
||||
let(:line_item){ order.line_items.first }
|
||||
it "return true if relavant attribute change" do
|
||||
line_item.update!(quantity: line_item.quantity + 1)
|
||||
|
||||
expect(OrderInvoiceComparator.new.equal?(current_state_invoice, invoice)).to be false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user