From da683e3ecf56a317d67e6fa985e79906d2245481 Mon Sep 17 00:00:00 2001 From: Luis Ramos Date: Fri, 7 Aug 2020 18:54:23 +0100 Subject: [PATCH] Merge decorators with original code from spree_core --- app/models/spree/adjustment.rb | 61 ++++++++++++++++++++++- app/models/spree/adjustment_decorator.rb | 62 ------------------------ app/models/spree/calculator.rb | 16 ++++++ app/models/spree/calculator_decorator.rb | 19 -------- app/models/spree/tax_rate.rb | 47 ++++++++++++++++-- app/models/spree/tax_rate_decorator.rb | 61 ----------------------- 6 files changed, 119 insertions(+), 147 deletions(-) delete mode 100644 app/models/spree/adjustment_decorator.rb delete mode 100644 app/models/spree/calculator_decorator.rb delete mode 100644 app/models/spree/tax_rate_decorator.rb diff --git a/app/models/spree/adjustment.rb b/app/models/spree/adjustment.rb index 9168d55429..37de2b20df 100644 --- a/app/models/spree/adjustment.rb +++ b/app/models/spree/adjustment.rb @@ -1,3 +1,6 @@ +require 'spree/localized_number' +require 'concerns/adjustment_scopes' + # Adjustments represent a change to the +item_total+ of an Order. Each adjustment # has an +amount+ that can be either positive or negative. # @@ -24,9 +27,18 @@ # it might be reinstated. module Spree class Adjustment < ActiveRecord::Base + extend Spree::LocalizedNumber + + # Deletion of metadata is handled in the database. + # So we don't need the option `dependent: :destroy` as long as + # AdjustmentMetadata has no destroy logic itself. + has_one :metadata, class_name: 'AdjustmentMetadata' + belongs_to :adjustable, polymorphic: true belongs_to :source, polymorphic: true belongs_to :originator, polymorphic: true + belongs_to :tax_rate, -> { where spree_adjustments: { originator_type: 'Spree::TaxRate' } }, + foreign_key: 'originator_id' validates :label, presence: true validates :amount, numericality: true @@ -50,14 +62,26 @@ module Spree scope :tax, -> { where(originator_type: 'Spree::TaxRate', adjustable_type: 'Spree::Order') } scope :price, -> { where(adjustable_type: 'Spree::LineItem') } - scope :shipping, -> { where(originator_type: 'Spree::ShippingMethod') } scope :optional, -> { where(mandatory: false) } - scope :eligible, -> { where(eligible: true) } scope :charge, -> { where('amount >= 0') } scope :credit, -> { where('amount < 0') } scope :promotion, -> { where(originator_type: 'Spree::PromotionAction') } scope :return_authorization, -> { where(source_type: "Spree::ReturnAuthorization") } + scope :enterprise_fee, -> { where(originator_type: 'EnterpriseFee') } + scope :admin, -> { where(source_type: nil, originator_type: nil) } + scope :included_tax, -> { + where(originator_type: 'Spree::TaxRate', adjustable_type: 'Spree::LineItem') + } + + scope :with_tax, -> { where('spree_adjustments.included_tax <> 0') } + scope :without_tax, -> { where('spree_adjustments.included_tax = 0') } + scope :payment_fee, -> { where(AdjustmentScopes::PAYMENT_FEE_SCOPE) } + scope :shipping, -> { where(AdjustmentScopes::SHIPPING_SCOPE) } + scope :eligible, -> { where(AdjustmentScopes::ELIGIBLE_SCOPE) } + + localize_number :amount + def promotion? originator_type == 'Spree::PromotionAction' end @@ -112,6 +136,39 @@ module Spree state != "open" end + def set_included_tax!(rate) + tax = amount - (amount / (1 + rate)) + set_absolute_included_tax! tax + end + + def set_absolute_included_tax!(tax) + # This rubocop issue can now fixed by renaming Adjustment#update! to something else, + # then AR's update! can be used instead of update_attributes! + # rubocop:disable Rails/ActiveRecordAliases + update_attributes! included_tax: tax.round(2) + # rubocop:enable Rails/ActiveRecordAliases + end + + def display_included_tax + Spree::Money.new(included_tax, currency: currency) + end + + def has_tax? + included_tax > 0 + end + + def self.without_callbacks + skip_callback :save, :after, :update_adjustable + skip_callback :destroy, :after, :update_adjustable + + result = yield + ensure + set_callback :save, :after, :update_adjustable + set_callback :destroy, :after, :update_adjustable + + result + end + private def update_adjustable diff --git a/app/models/spree/adjustment_decorator.rb b/app/models/spree/adjustment_decorator.rb deleted file mode 100644 index cba3b3e879..0000000000 --- a/app/models/spree/adjustment_decorator.rb +++ /dev/null @@ -1,62 +0,0 @@ -require 'spree/localized_number' -require 'concerns/adjustment_scopes' - -module Spree - Adjustment.class_eval do - extend Spree::LocalizedNumber - - # Deletion of metadata is handled in the database. - # So we don't need the option `dependent: :destroy` as long as - # AdjustmentMetadata has no destroy logic itself. - has_one :metadata, class_name: 'AdjustmentMetadata' - belongs_to :tax_rate, -> { where spree_adjustments: { originator_type: 'Spree::TaxRate' } }, - foreign_key: 'originator_id' - - scope :enterprise_fee, -> { where(originator_type: 'EnterpriseFee') } - scope :admin, -> { where(source_type: nil, originator_type: nil) } - scope :included_tax, -> { - where(originator_type: 'Spree::TaxRate', adjustable_type: 'Spree::LineItem') - } - - scope :with_tax, -> { where('spree_adjustments.included_tax <> 0') } - scope :without_tax, -> { where('spree_adjustments.included_tax = 0') } - scope :payment_fee, -> { where(AdjustmentScopes::PAYMENT_FEE_SCOPE) } - scope :shipping, -> { where(AdjustmentScopes::SHIPPING_SCOPE) } - scope :eligible, -> { where(AdjustmentScopes::ELIGIBLE_SCOPE) } - - localize_number :amount - - def set_included_tax!(rate) - tax = amount - (amount / (1 + rate)) - set_absolute_included_tax! tax - end - - def set_absolute_included_tax!(tax) - # This rubocop issue can only be fixed when Adjustment#update! is brought from Spree to OFN - # and renamed to something else, then AR's update! can be used instead of update_attributes! - # rubocop:disable Rails/ActiveRecordAliases - update_attributes! included_tax: tax.round(2) - # rubocop:enable Rails/ActiveRecordAliases - end - - def display_included_tax - Spree::Money.new(included_tax, currency: currency) - end - - def has_tax? - included_tax > 0 - end - - def self.without_callbacks - skip_callback :save, :after, :update_adjustable - skip_callback :destroy, :after, :update_adjustable - - result = yield - ensure - set_callback :save, :after, :update_adjustable - set_callback :destroy, :after, :update_adjustable - - result - end - end -end diff --git a/app/models/spree/calculator.rb b/app/models/spree/calculator.rb index b9aeed3333..4ce5b53c13 100644 --- a/app/models/spree/calculator.rb +++ b/app/models/spree/calculator.rb @@ -35,5 +35,21 @@ module Spree def available?(object) true end + + private + + # Given an object which might be an Order or a LineItem (amongst + # others), return a collection of line items. + def line_items_for(object) + if object.is_a?(Spree::LineItem) + [object] + elsif object.respond_to? :line_items + object.line_items + elsif object.respond_to?(:order) && object.order.present? + object.order.line_items + else + [object] + end + end end end diff --git a/app/models/spree/calculator_decorator.rb b/app/models/spree/calculator_decorator.rb deleted file mode 100644 index 5d130d86aa..0000000000 --- a/app/models/spree/calculator_decorator.rb +++ /dev/null @@ -1,19 +0,0 @@ -module Spree - Calculator.class_eval do - private - - # Given an object which might be an Order or a LineItem (amongst - # others), return a collection of line items. - def line_items_for(object) - if object.is_a?(Spree::LineItem) - [object] - elsif object.respond_to? :line_items - object.line_items - elsif object.respond_to?(:order) && object.order.present? - object.order.line_items - else - [object] - end - end - end -end diff --git a/app/models/spree/tax_rate.rb b/app/models/spree/tax_rate.rb index fde950b3cd..d31345e0da 100644 --- a/app/models/spree/tax_rate.rb +++ b/app/models/spree/tax_rate.rb @@ -22,11 +22,12 @@ module Spree scope :by_zone, ->(zone) { where(zone_id: zone) } # Gets the array of TaxRates appropriate for the specified order - def self.match(order) + def match(order) + return [] if order.distributor && !order.distributor.charges_sales_tax return [] unless order.tax_zone + all.select do |rate| - (!rate.included_in_price && (rate.zone == order.tax_zone || rate.zone.contains?(order.tax_zone) || (order.tax_address.nil? && rate.zone.default_tax))) || - (rate.included_in_price && !order.tax_address.nil? && !rate.zone.contains?(order.tax_zone) && rate.zone.default_tax) + rate.zone == order.tax_zone || rate.zone.contains?(order.tax_zone) || rate.zone.default_tax end end @@ -73,6 +74,32 @@ module Spree else create_adjustment(label, order, order) end + + order.adjustments(:reload) + order.line_items(:reload) + # TaxRate adjustments (order.adjustments.tax) and price adjustments (tax included on line items) consist of 100% tax + (order.adjustments.tax + order.price_adjustments).each do |adjustment| + adjustment.set_absolute_included_tax! adjustment.amount + end + end + + # Manually apply a TaxRate to a particular amount. TaxRates normally compute against + # LineItems or Orders, so we mock out a line item here to fit the interface + # that our calculator (usually DefaultTax) expects. + def compute_tax(amount) + line_item = LineItem.new quantity: 1 + line_item.tax_category = tax_category + line_item.define_singleton_method(:price) { amount } + + # Tax on adjustments (represented by the included_tax field) is always inclusive of + # tax. However, there's nothing to stop an admin from setting one up with a tax rate + # that's marked as not inclusive of tax, and that would result in the DefaultTax + # calculator generating a slightly incorrect value. Therefore, we treat the tax + # rate as inclusive of tax for the calculations below, regardless of its original + # setting. + with_tax_included_in_price do + calculator.compute line_item + end end private @@ -82,5 +109,19 @@ module Spree label << (name.present? ? name : tax_category.name) + " " label << (show_rate_in_label? ? "#{amount * 100}%" : "") end + + def with_tax_included_in_price + old_included_in_price = included_in_price + + self.included_in_price = true + calculator.calculable.included_in_price = true + + result = yield + ensure + self.included_in_price = old_included_in_price + calculator.calculable.included_in_price = old_included_in_price + + result + end end end diff --git a/app/models/spree/tax_rate_decorator.rb b/app/models/spree/tax_rate_decorator.rb deleted file mode 100644 index f15ded6de8..0000000000 --- a/app/models/spree/tax_rate_decorator.rb +++ /dev/null @@ -1,61 +0,0 @@ -module Spree - TaxRate.class_eval do - class << self - def match(order) - return [] if order.distributor && !order.distributor.charges_sales_tax - return [] unless order.tax_zone - - all.select do |rate| - rate.zone == order.tax_zone || rate.zone.contains?(order.tax_zone) || rate.zone.default_tax - end - end - end - - def adjust_with_included_tax(order) - adjust_without_included_tax(order) - - order.adjustments(:reload) - order.line_items(:reload) - # TaxRate adjustments (order.adjustments.tax) and price adjustments (tax included on line items) consist of 100% tax - (order.adjustments.tax + order.price_adjustments).each do |adjustment| - adjustment.set_absolute_included_tax! adjustment.amount - end - end - alias_method_chain :adjust, :included_tax - - # Manually apply a TaxRate to a particular amount. TaxRates normally compute against - # LineItems or Orders, so we mock out a line item here to fit the interface - # that our calculator (usually DefaultTax) expects. - def compute_tax(amount) - line_item = LineItem.new quantity: 1 - line_item.tax_category = tax_category - line_item.define_singleton_method(:price) { amount } - - # Tax on adjustments (represented by the included_tax field) is always inclusive of - # tax. However, there's nothing to stop an admin from setting one up with a tax rate - # that's marked as not inclusive of tax, and that would result in the DefaultTax - # calculator generating a slightly incorrect value. Therefore, we treat the tax - # rate as inclusive of tax for the calculations below, regardless of its original - # setting. - with_tax_included_in_price do - calculator.compute line_item - end - end - - private - - def with_tax_included_in_price - old_included_in_price = included_in_price - - self.included_in_price = true - calculator.calculable.included_in_price = true - - result = yield - ensure - self.included_in_price = old_included_in_price - calculator.calculable.included_in_price = old_included_in_price - - result - end - end -end