diff --git a/core/app/helpers/spree/store_helper.rb b/core/app/helpers/spree/store_helper.rb index 631c9a0fce3..f62139d43ed 100644 --- a/core/app/helpers/spree/store_helper.rb +++ b/core/app/helpers/spree/store_helper.rb @@ -1,8 +1,8 @@ -# Methods added to this helper will be available to all templates in the frontend. module Spree + # Methods added to this helper will be available to all templates in the + # frontend. module StoreHelper - - # helper to determine if its appropriate to show the store menu + # @return [Boolean] true when it is appropriate to show the store menu def store_menu? %w{thank_you}.exclude? params[:action] end diff --git a/core/app/models/spree/address.rb b/core/app/models/spree/address.rb index dcf8584f11a..fc574bbabf7 100644 --- a/core/app/models/spree/address.rb +++ b/core/app/models/spree/address.rb @@ -30,19 +30,19 @@ def self.default(user = nil, kind = "bill") end end - # Can modify an address if it's not been used in an order (but checkouts controller has finer control) - # def editable? - # new_record? || (shipments.empty? && checkouts.empty?) - # end - + # @return [String] the full name on this address def full_name "#{firstname} #{lastname}".strip end + # @return [String] a string representation of this state def state_text state.try(:abbr) || state.try(:name) || state_name end + # @param other [Spree::Address, nil] the address we are comparing with + # @return [Boolean] true if this fields on this address match the fields on + # the other address def same_as?(other) return false if other.nil? attributes.except('id', 'updated_at', 'created_at') == other.attributes.except('id', 'updated_at', 'created_at') @@ -50,15 +50,22 @@ def same_as?(other) alias same_as same_as? + # @return [String] the full name on the address followed by the first line + # of the address def to_s "#{full_name}: #{address1}" end + # @return [Spree::Address] a new address that is the same_as? this address def clone ActiveSupport::Deprecation.warn "Spree::Address.clone is deprecated and may be removed from future releases, Use Spree::Address.dup instead", caller self.dup end + # @note This compares the addresses based on only the fields that make up + # the logical "address" and excludes their order IDs. Use #same_as? to + # include the order IDs in the comparison + # @return [Boolean] true if the two addresses have the same address fields def ==(other_address) self_attrs = self.attributes other_attrs = other_address.respond_to?(:attributes) ? other_address.attributes : {} @@ -68,11 +75,13 @@ def ==(other_address) self_attrs == other_attrs end + # @return [Boolean] true if the order is missing all of the address fields + # are nil def empty? attributes.except('id', 'created_at', 'updated_at', 'order_id', 'country_id').all? { |_, v| v.nil? } end - # Generates an ActiveMerchant compatible address hash + # @return [Hash] an ActiveMerchant compatible address hash def active_merchant_hash { name: full_name, @@ -86,10 +95,15 @@ def active_merchant_hash } end + # @todo Remove this from the public API if possible. + # @return [true] whether or not the address requires a phone number to be + # valid def require_phone? true end + # @todo Remove this from the public API if possible. + # @return [true] whether or not the address requires a zipcode to be valid def require_zipcode? true end diff --git a/core/app/models/spree/adjustment.rb b/core/app/models/spree/adjustment.rb index 7b305bfabf4..bc013c40e23 100644 --- a/core/app/models/spree/adjustment.rb +++ b/core/app/models/spree/adjustment.rb @@ -1,26 +1,27 @@ -# Adjustments represent a change to the +item_total+ of an Order. Each adjustment -# has an +amount+ that can be either positive or negative. -# -# Adjustments can be "opened" or "closed". -# Once an adjustment is closed, it will not be automatically updated. -# -# Boolean attributes: -# -# +mandatory+ -# -# If this flag is set to true then it means the the charge is required and will not -# be removed from the order, even if the amount is zero. In other words a record -# will be created even if the amount is zero. This is useful for representing things -# such as shipping and tax charges where you may want to make it explicitly clear -# that no charge was made for such things. -# -# +eligible?+ -# -# This boolean attributes stores whether this adjustment is currently eligible -# for its order. Only eligible adjustments count towards the order's adjustment -# total. This allows an adjustment to be preserved if it becomes ineligible so -# it might be reinstated. module Spree + # Adjustments represent a change to the +item_total+ of an Order. Each + # adjustment has an +amount+ that can be either positive or negative. + # + # Adjustments can be "opened" or "closed". Once an adjustment is closed, it + # will not be automatically updated. + # + # == Boolean attributes + # + # 1. *mandatory* + # + # If this flag is set to true then it means the the charge is required and + # will not be removed from the order, even if the amount is zero. In other + # words a record will be created even if the amount is zero. This is + # useful for representing things such as shipping and tax charges where + # you may want to make it explicitly clear that no charge was made for + # such things. + # + # 2. *eligible?* + # + # This boolean attributes stores whether this adjustment is currently + # eligible for its order. Only eligible adjustments count towards the + # order's adjustment total. This allows an adjustment to be preserved if + # it becomes ineligible so it might be reinstated. class Adjustment < Spree::Base belongs_to :adjustable, polymorphic: true, touch: true belongs_to :source, polymorphic: true diff --git a/core/app/models/spree/calculator/free_shipping.rb b/core/app/models/spree/calculator/free_shipping.rb index 488af63ec3e..5a70acba8a1 100644 --- a/core/app/models/spree/calculator/free_shipping.rb +++ b/core/app/models/spree/calculator/free_shipping.rb @@ -1,8 +1,7 @@ -# TODO: Deprecate this class. -# This calculator will be removed in future versions of Spree. -# The only case where it was used was for Free Shipping Promotions. -# There is now a Promotion Action which deals with these types of promotions instead. module Spree + # @deprecated This calculator will be removed in future versions of Spree. + # The only case where it was used was for Free Shipping Promotions. There is + # now a Promotion Action which deals with these types of promotions instead. class Calculator::FreeShipping < Calculator def self.description Spree.t(:free_shipping) @@ -19,4 +18,4 @@ def compute(object) order.ship_total end end -end \ No newline at end of file +end diff --git a/core/app/models/spree/credit_card.rb b/core/app/models/spree/credit_card.rb index 8ebc5aff8fe..918a7e99664 100644 --- a/core/app/models/spree/credit_card.rb +++ b/core/app/models/spree/credit_card.rb @@ -35,6 +35,10 @@ class CreditCard < Spree::Base jcb: /^(?:2131|1800|35\d{3})\d{11}$/ } + # Sets the expiry date on this credit card. + # + # @param expiry [String] the desired new expiry date in one of the + # following formats: "mm/yy", "mm / yyyy", "mmyy", "mmyyyy" def expiry=(expiry) return unless expiry.present? @@ -51,13 +55,20 @@ def expiry=(expiry) self[:month] = self[:month].to_i if self[:month] end + # Sets the credit card number, removing any non-numeric characters. + # + # @param num [String] the desired credit card number def number=(num) @number = num.gsub(/[^0-9]/, '') rescue nil end - # cc_type is set by jquery.payment, which helpfully provides different - # types from Active Merchant. Converting them is necessary. + # Sets the credit card type, converting it to the preferred internal + # representation from jquery.payment's representation when appropriate. + # + # @param type [String] the desired credit card type def cc_type=(type) + # cc_type is set by jquery.payment, which helpfully provides different + # types from Active Merchant. Converting them is necessary. self[:cc_type] = case type when 'mastercard', 'maestro' then 'master' when 'amex' then 'american_express' @@ -67,61 +78,84 @@ def cc_type=(type) end end + # Sets the last digits field based on the assigned credit card number. def set_last_digits number.to_s.gsub!(/\s/,'') verification_value.to_s.gsub!(/\s/,'') self.last_digits ||= number.to_s.length <= 4 ? number : number.to_s.slice(-4..-1) end + # @return [String] the credit card type if it can be determined from the + # number, otherwise the empty string def try_type_from_number numbers = number.delete(' ') if number CARD_TYPES.find{|type, pattern| return type.to_s if numbers =~ pattern}.to_s end + # @return [Boolean] true when a verification value is present def verification_value? verification_value.present? end - # Show the card number, with all but last 4 numbers replace with "X". (XXXX-XXXX-XXXX-4338) + # @return [String] the card number, with all but last 4 numbers replace + # with "X", as in "XXXX-XXXX-XXXX-4338" def display_number "XXXX-XXXX-XXXX-#{last_digits}" end + # @return [Array] the actions available on this credit card def actions %w{capture void credit} end - # Indicates whether its possible to capture the payment + # @param payment [Spree::Payment] the payment we want to know if can be captured + # @return [Boolean] true when the payment is in the pending or checkout states def can_capture?(payment) payment.pending? || payment.checkout? end - # Indicates whether its possible to void the payment. + # @param payment [Spree::Payment] the payment we want to know if can be voided + # @return [Boolean] true when the payment is not failed or voided def can_void?(payment) !payment.failed? && !payment.void? end - # Indicates whether its possible to credit the payment. Note that most gateways require that the - # payment be settled first which generally happens within 12-24 hours of the transaction. + # Indicates whether its possible to credit the payment. Note that most + # gateways require that the payment be settled first which generally + # happens within 12-24 hours of the transaction. + # + # @param payment [Spree::Payment] the payment we want to know if can be credited + # @return [Boolean] true when the payment is completed and can be credited def can_credit?(payment) payment.completed? && payment.credit_allowed > 0 end + # @return [Boolean] true when there is a gateway customer or payment + # profile id present def has_payment_profile? gateway_customer_profile_id.present? || gateway_payment_profile_id.present? end - # ActiveMerchant needs first_name/last_name because we pass it a Spree::CreditCard and it calls those methods on it. - # Looking at the ActiveMerchant source code we should probably be calling #to_active_merchant before passing - # the object to ActiveMerchant but this should do for now. + # @note ActiveMerchant needs first_name/last_name because we pass it a + # Spree::CreditCard and it calls those methods on it. + # @todo We should probably be calling #to_active_merchant before passing + # the object to ActiveMerchant. + # @return [String] the first name on this credit card def first_name name.to_s.split(/[[:space:]]/, 2)[0] end + # @note ActiveMerchant needs first_name/last_name because we pass it a + # Spree::CreditCard and it calls those methods on it. + # @todo We should probably be calling #to_active_merchant before passing + # the object to ActiveMerchant. + # @return [String] the last name on this credit card def last_name name.to_s.split(/[[:space:]]/, 2)[1] end + # @return [ActiveMerchant::Billing::CreditCard] an ActiveMerchant credit + # card that represents this credit card def to_active_merchant ActiveMerchant::Billing::CreditCard.new( :number => number, diff --git a/core/app/models/spree/gateway/bogus_simple.rb b/core/app/models/spree/gateway/bogus_simple.rb index 8b0757b220a..a60271303d5 100644 --- a/core/app/models/spree/gateway/bogus_simple.rb +++ b/core/app/models/spree/gateway/bogus_simple.rb @@ -1,5 +1,5 @@ -# Bogus Gateway that doesn't support payment profiles module Spree + # Bogus Gateway that doesn't support payment profiles. class Gateway::BogusSimple < Gateway::Bogus def payment_profiles_supported? diff --git a/core/app/models/spree/inventory_unit.rb b/core/app/models/spree/inventory_unit.rb index d9924cc2195..62e26b5f423 100644 --- a/core/app/models/spree/inventory_unit.rb +++ b/core/app/models/spree/inventory_unit.rb @@ -52,18 +52,24 @@ class InventoryUnit < Spree::Base end end - # This was refactored from a simpler query because the previous implementation - # led to issues once users tried to modify the objects returned. That's due - # to ActiveRecord `joins(shipment: :stock_location)` only returning readonly - # objects - # - # Returns an array of backordered inventory units as per a given stock item + # @param stock_item [Spree::StockItem] the stock item of the desired + # inventory units + # @return [Array] an array of backordered inventory + # units for the given stock item def self.backordered_for_stock_item(stock_item) + # This was refactored from a simpler query because the previous + # implementation led to issues once users tried to modify the objects + # returned. That's due to ActiveRecord `joins(shipment: :stock_location)` + # only returning readonly objects backordered_per_variant(stock_item).select do |unit| unit.shipment.stock_location == stock_item.stock_location end end + # Updates the given inventory units to not be pending. + # + # @param inventory_units [] the inventory to be + # finalized def self.finalize_units!(inventory_units) inventory_units.map do |iu| iu.update_columns( @@ -73,28 +79,42 @@ def self.finalize_units!(inventory_units) end end + # @return [Spree::StockItem] the first stock item from this shipment's + # stock location that is associated with this inventory unit's variant def find_stock_item Spree::StockItem.where(stock_location_id: shipment.stock_location_id, variant_id: variant_id).first end - # Remove variant default_scope `deleted_at: nil` + # @note This returns the variant regardless of whether it was soft + # deleted. + # @return [Spree::Variant] this inventory unit's variant. def variant Spree::Variant.unscoped { super } end + # @return [Spree::ReturnItem] a valid return item for this inventory unit + # if one exists, or a new one if one does not def current_or_new_return_item Spree::ReturnItem.from_inventory_unit(self) end + # @return [BigDecimal] the portion of the additional tax on the line item + # this inventory unit belongs to that is associated with this individual + # inventory unit def additional_tax_total line_item.additional_tax_total * percentage_of_line_item end + # @return [BigDecimal] the portion of the included tax on the line item + # this inventory unit belongs to that is associated with this + # individual inventory unit def included_tax_total line_item.included_tax_total * percentage_of_line_item end + # @return [Boolean] true if this inventory unit has any return items + # which have requested exchanges def exchange_requested? return_items.not_expired.any?(&:exchange_requested?) end diff --git a/core/app/models/spree/legacy_user.rb b/core/app/models/spree/legacy_user.rb index 9b973887e7b..22e5b11ac0d 100644 --- a/core/app/models/spree/legacy_user.rb +++ b/core/app/models/spree/legacy_user.rb @@ -1,5 +1,8 @@ -# Default implementation of User. This class is intended to be modified by extensions (ex. spree_auth_devise) module Spree + # Default implementation of User. + # + # @note This class is intended to be modified by extensions (ex. + # spree_auth_devise) class LegacyUser < Spree::Base include UserAddress include UserPaymentSource diff --git a/core/app/models/spree/order_promotion.rb b/core/app/models/spree/order_promotion.rb index e663eb50068..85436f8ba23 100644 --- a/core/app/models/spree/order_promotion.rb +++ b/core/app/models/spree/order_promotion.rb @@ -1,7 +1,8 @@ -# Spree::OrderPromotion represents the relationship between: -# 1. A promotion that a user attempted to apply to their order -# 2. The specific code that they used module Spree + # Spree::OrderPromotion represents the relationship between: + # + # 1. A promotion that a user attempted to apply to their order + # 2. The specific code that they used class OrderPromotion < ActiveRecord::Base self.table_name = 'spree_orders_promotions' diff --git a/core/app/models/spree/payment.rb b/core/app/models/spree/payment.rb index 9bc11a93658..69f87c1ad00 100644 --- a/core/app/models/spree/payment.rb +++ b/core/app/models/spree/payment.rb @@ -50,11 +50,6 @@ class Payment < Spree::Base scope :risky, -> { where("avs_response IN (?) OR (cvv_response_code IS NOT NULL and cvv_response_code != 'M') OR state = 'failed'", RISKY_AVS_CODES) } scope :valid, -> { where.not(state: %w(failed invalid)) } - # transaction_id is much easier to understand - def transaction_id - response_code - end - # order state machine (see http://github.com/pluginaweek/state_machine/tree/master for details) state_machine initial: :checkout do # With card payments, happens before purchase or authorization happens @@ -94,15 +89,23 @@ def transaction_id end end - def currency - order.currency + # @return [String] this payment's response code + def transaction_id + response_code end + # @return [String] this payment's currency + delegate :currency, to: :order + + # @return [Spree::Money] this amount of this payment as money object def money Spree::Money.new(amount, { currency: currency }) end alias display_amount money + # Sets the amount, parsing it based on i18n settings if it is a string. + # + # @param amount [BigDecimal, String] the desired new amount def amount=(amount) self[:amount] = case amount @@ -113,19 +116,30 @@ def amount=(amount) end || amount end + # The total amount of the offsets (for old-style refunds) for this payment. + # + # @return [BigDecimal] the total amount of this payment's offsets def offsets_total offsets.pluck(:amount).sum end + # The total amount this payment can be credited. + # + # @return [BigDecimal] the amount of this payment minus the offsets + # (old-style refunds) and refunds def credit_allowed amount - (offsets_total.abs + refunds.sum(:amount)) end + # @return [Boolean] true when this payment can be credited def can_credit? credit_allowed > 0 end - # see https://github.com/spree/spree/issues/981 + # When this is a new record without a source, builds a new source based on + # this payment's payment method and associates it correctly. + # + # @see https://github.com/spree/spree/issues/981 def build_source return unless new_record? if source_attributes.present? && source.blank? && payment_method.try(:payment_source_class) @@ -135,21 +149,25 @@ def build_source end end + # @return [Array] the actions available on this payment def actions return [] unless payment_source and payment_source.respond_to? :actions payment_source.actions.select { |action| !payment_source.respond_to?("can_#{action}?") or payment_source.send("can_#{action}?", self) } end + # @return [Object] the source of ths payment def payment_source res = source.is_a?(Payment) ? source.source : source res || payment_method end + # @return [Boolean] true when this payment is risky based on address def is_avs_risky? return false if avs_response.blank? || NON_RISKY_AVS_CODES.include?(avs_response) return true end + # @return [Boolean] true when this payment is risky based on cvv def is_cvv_risky? return false if cvv_response_code == "M" return false if cvv_response_code.nil? @@ -157,10 +175,12 @@ def is_cvv_risky? return true end + # @return [BigDecimal] the total amount captured on this payment def captured_amount capture_events.sum(:amount) end + # @return [BigDecimal] the total amount left uncaptured on this payment def uncaptured_amount amount - captured_amount end diff --git a/core/app/models/spree/price.rb b/core/app/models/spree/price.rb index dce4f8035e5..b67bb3f653d 100644 --- a/core/app/models/spree/price.rb +++ b/core/app/models/spree/price.rb @@ -11,19 +11,27 @@ class Price < Spree::Base extend DisplayMoney money_methods :amount, :price + # @return [Spree::Money] this price as a Spree::Money object def money Spree::Money.new(amount || 0, { currency: currency }) end + # An alias for #amount def price amount end + # Sets this price's amount to a new value, parsing it if the new value is + # a string. + # + # @param price [String, #to_d] a new amount def price=(price) self[:amount] = Spree::LocalizedNumber.parse(price) end - # Remove variant default_scope `deleted_at: nil` + # @note This returns the variant regardless of whether it was soft + # deleted. + # @return [Spree::Variant] this price's variant. def variant Spree::Variant.unscoped { super } end diff --git a/core/app/models/spree/promotion/rules/item_total.rb b/core/app/models/spree/promotion/rules/item_total.rb index 9a3e5178335..482c4e4c0d8 100644 --- a/core/app/models/spree/promotion/rules/item_total.rb +++ b/core/app/models/spree/promotion/rules/item_total.rb @@ -1,8 +1,8 @@ -# A rule to apply to an order greater than (or greater than or equal to) -# a specific amount module Spree class Promotion module Rules + # A rule to apply to an order greater than (or greater than or equal to) + # a specific amount class ItemTotal < PromotionRule preference :amount_min, :decimal, default: 100.00 preference :operator_min, :string, default: '>' diff --git a/core/app/models/spree/promotion/rules/product.rb b/core/app/models/spree/promotion/rules/product.rb index a71d7214c94..1f937a85229 100644 --- a/core/app/models/spree/promotion/rules/product.rb +++ b/core/app/models/spree/promotion/rules/product.rb @@ -1,9 +1,10 @@ -# A rule to limit a promotion based on products in the order. -# Can require all or any of the products to be present. -# Valid products either come from assigned product group or are assingned directly to the rule. module Spree class Promotion module Rules + # A rule to limit a promotion based on products in the order. Can + # require all or any of the products to be present. Valid products + # either come from assigned product group or are assingned directly to + # the rule. class Product < PromotionRule has_and_belongs_to_many :products, class_name: '::Spree::Product', join_table: 'spree_products_promotion_rules', foreign_key: 'promotion_rule_id' diff --git a/core/app/models/spree/promotion_action.rb b/core/app/models/spree/promotion_action.rb index e737ad70306..15717b4fb3c 100644 --- a/core/app/models/spree/promotion_action.rb +++ b/core/app/models/spree/promotion_action.rb @@ -1,6 +1,8 @@ -# Base class for all types of promotion action. -# PromotionActions perform the necessary tasks when a promotion is activated by an event and determined to be eligible. module Spree + # Base class for all types of promotion action. + # + # PromotionActions perform the necessary tasks when a promotion is activated + # by an event and determined to be eligible. class PromotionAction < Spree::Base acts_as_paranoid @@ -8,10 +10,12 @@ class PromotionAction < Spree::Base scope :of_type, ->(t) { where(type: t) } - # This method should be overriden in subclass - # Updates the state of the order or performs some other action depending on the subclass - # options will contain the payload from the event that activated the promotion. This will include - # the key :user which allows user based actions to be performed in addition to actions on the order + # Updates the state of the order or performs some other action depending on + # the subclass options will contain the payload from the event that + # activated the promotion. This will include the key :user which allows + # user based actions to be performed in addition to actions on the order + # + # @note This method should be overriden in subclassses. def perform(options = {}) raise 'perform should be implemented in a sub-class of PromotionAction' end diff --git a/core/app/models/spree/promotion_rule.rb b/core/app/models/spree/promotion_rule.rb index a7d62f04b6a..bd7ae059d97 100644 --- a/core/app/models/spree/promotion_rule.rb +++ b/core/app/models/spree/promotion_rule.rb @@ -1,5 +1,5 @@ -# Base class for all promotion rules module Spree + # Base class for all promotion rules class PromotionRule < Spree::Base belongs_to :promotion, class_name: 'Spree::Promotion', inverse_of: :promotion_rules diff --git a/core/app/models/spree/return_item.rb b/core/app/models/spree/return_item.rb index 9e502352f4c..d0f8b44308a 100644 --- a/core/app/models/spree/return_item.rb +++ b/core/app/models/spree/return_item.rb @@ -4,12 +4,27 @@ class ReturnItem < ActiveRecord::Base INTERMEDIATE_RECEPTION_STATUSES = %i(given_to_customer lost_in_transit shipped_wrong_item short_shipped) COMPLETED_RECEPTION_STATUSES = INTERMEDIATE_RECEPTION_STATUSES + [:received] + # @!scope class + # @!attribute return_eligibility_validator + # Configurable validator for determining whether given return item is + # eligible for return. + # @return [Class] class_attribute :return_eligibility_validator self.return_eligibility_validator = ReturnItem::EligibilityValidator::Default + # @!scope class + # @!attribute exchange_variant_engine + # Configurable engine for determining which variants can be exchanged for a + # given variant. + # @return [Class] class_attribute :exchange_variant_engine self.exchange_variant_engine = ReturnItem::ExchangeVariantEligibility::SameProduct + # @!scope class + # @!attribute refund_amount_calculator + # Configurable calculator for determining the amount ro refund when + # refunding. + # @return [Class] class_attribute :refund_amount_calculator self.refund_amount_calculator = Calculator::Returns::DefaultRefundAmount @@ -77,6 +92,8 @@ class ReturnItem < ActiveRecord::Base extend DisplayMoney money_methods :pre_tax_amount, :total + # @return [Boolean] true when this retur item is in a complete reception + # state def reception_completed? COMPLETED_RECEPTION_STATUSES.map(&:to_s).include?(reception_status.to_s) end @@ -107,31 +124,49 @@ def reception_completed? after_transition any => any, :do => :persist_acceptance_status_errors end + # @param inventory_unit [Spree::InventoryUnit] the inventory for which we + # want a return item + # @return [Spree::ReturnItem] a valid return item for the given inventory + # unit if one exists, or a new one if one does not def self.from_inventory_unit(inventory_unit) valid.find_by(inventory_unit: inventory_unit) || new(inventory_unit: inventory_unit).tap(&:set_default_pre_tax_amount) end + # @return [Boolean] true when an exchange has been requested on this return + # item def exchange_requested? exchange_variant.present? end + # @return [Boolean] true when an exchange has been processed for this + # return item def exchange_processed? exchange_inventory_unit.present? end + # @return [Boolean] true when an exchange has been requested but has yet to + # be processed def exchange_required? exchange_requested? && !exchange_processed? end + # @return [BigDecimal] the cost of the item before tax, plus the included + # and additional taxes def total pre_tax_amount + included_tax_total + additional_tax_total end + # @note This uses the exchange_variant_engine configured on the class. + # @return [ActiveRecord::Relation] the variants eligible + # for exchange for this return item def eligible_exchange_variants exchange_variant_engine.eligible_variants(variant) end + # Builds the exchange inventory unit for this return item, only if an + # exchange is required, correctly associating the variant, line item and + # order. def build_exchange_inventory_unit # The inventory unit needs to have the new variant # but it also needs to know the original line item @@ -141,10 +176,15 @@ def build_exchange_inventory_unit super(variant: exchange_variant, line_item: inventory_unit.line_item, order: inventory_unit.order) if exchange_required? end + # @return [Spree::Shipment, nil] the exchange inventory unit's shipment if it exists def exchange_shipment exchange_inventory_unit.try(:shipment) end + # Calculates and sets the default pre-tax amount to be refunded. + # + # @note This uses the configured refund_amount_calculator configured on the + # class. def set_default_pre_tax_amount self.pre_tax_amount = refund_amount_calculator.new.compute(self) end diff --git a/core/app/models/spree/stock/adjuster.rb b/core/app/models/spree/stock/adjuster.rb index a0d56e35b3a..fdc825e24c0 100644 --- a/core/app/models/spree/stock/adjuster.rb +++ b/core/app/models/spree/stock/adjuster.rb @@ -1,7 +1,8 @@ -# Used by Prioritizer to adjust item quantities -# see prioritizer_spec for use cases module Spree module Stock + # Used by Prioritizer to adjust item quantities. + # + # See spec/models/spree/stock/prioritizer_spec.rb for use cases. class Adjuster attr_accessor :inventory_unit, :status, :fulfilled diff --git a/core/app/models/spree/stock/estimator.rb b/core/app/models/spree/stock/estimator.rb index a1f7f66f2cf..e869560f63c 100644 --- a/core/app/models/spree/stock/estimator.rb +++ b/core/app/models/spree/stock/estimator.rb @@ -3,11 +3,19 @@ module Stock class Estimator attr_reader :order, :currency + # @param order [Spree::Order] the order whose shipping rates to estimate def initialize(order) @order = order @currency = order.currency end + # Estimate the shipping rates for a package. + # + # @param package [Spree::Stock::Package] the package to be shipped + # @param frontend_only [Boolean] restricts the shipping methods to only + # those marked frontend if truthy + # @return [Array] the shipping rates sorted by + # descending cost, with the least costly marked "selected" def shipping_rates(package, frontend_only = true) rates = calculate_shipping_rates(package) rates.select! { |rate| rate.shipping_method.frontend? } if frontend_only diff --git a/core/app/models/spree/stock/package.rb b/core/app/models/spree/stock/package.rb index 0b0edcac36a..4f27a4f5893 100644 --- a/core/app/models/spree/stock/package.rb +++ b/core/app/models/spree/stock/package.rb @@ -4,43 +4,74 @@ class Package attr_reader :stock_location, :contents attr_accessor :shipping_rates + # @param stock_location [Spree::StockLocation] the stock location this package originates from + # @param contents [Array] the contents of this package def initialize(stock_location, contents=[]) @stock_location = stock_location @contents = contents @shipping_rates = Array.new end + # Adds an inventory unit to this package. + # + # @param inventory_unit [Spree::InventoryUnit] an inventory unit to be + # added to this package + # @param state [:on_hand, :backordered] the state of the item to be + # added to this package def add(inventory_unit, state = :on_hand) contents << ContentItem.new(inventory_unit, state) unless find_item(inventory_unit) end + # Adds multiple inventory units to this package. + # + # @param inventory_units [Array] a collection of + # inventory units to be added to this package + # @param state [:on_hand, :backordered] the state of the items to be + # added to this package def add_multiple(inventory_units, state = :on_hand) inventory_units.each { |inventory_unit| add(inventory_unit, state) } end + # Removes a given inventory unit from this package. + # + # @param inventory_unit [Spree::InventoryUnit] the inventory unit to be + # removed from this package def remove(inventory_unit) item = find_item(inventory_unit) @contents -= [item] if item end - # Fix regression that removed package.order. - # Find it dynamically through an inventory_unit. + # @return [Spree::Order] the order associated with this package def order + # Fix regression that removed package.order. + # Find it dynamically through an inventory_unit. contents.detect {|item| !!item.try(:inventory_unit).try(:order) }.try(:inventory_unit).try(:order) end + # @return [Float] the summed weight of the contents of this package def weight contents.sum(&:weight) end + # @return [Array] the content items in this + # package which are on hand def on_hand contents.select(&:on_hand?) end + # @return [Array] the content items in this + # package which are backordered def backordered contents.select(&:backordered?) end + # Find a content item in this package by inventory unit and optionally + # state. + # + # @param inventory_unit [Spree::InventoryUnit] the desired inventory + # unit + # @param state [:backordered, :on_hand, nil] the state of the desired + # content item, or nil for any state def find_item(inventory_unit, state = nil) contents.detect do |item| item.inventory_unit == inventory_unit && @@ -48,31 +79,45 @@ def find_item(inventory_unit, state = nil) end end + # @param state [:backordered, :on_hand, nil] the state of the content + # items of which we want the quantity, or nil for the full quantity + # @return [Fixnum] the number of inventory units in the package, + # counting only those in the given state if it was specified def quantity(state = nil) matched_contents = state.nil? ? contents : contents.select { |c| c.state.to_s == state.to_s } matched_contents.map(&:quantity).sum end + # @return [Boolean] true if there are no inventory units in this + # package def empty? quantity == 0 end + # @return [String] the currency of the order this package belongs to def currency order.currency end + # @return [Array] the shipping categories of the + # variants in this package def shipping_categories contents.map { |item| item.variant.shipping_category }.compact.uniq end + # @return [Array] the shipping methods available + # for this pacakges shipping categories def shipping_methods shipping_categories.map(&:shipping_methods).reduce(:&).to_a end + # @return [Spree::Shipment] a new shipment containing this package's + # inventory units, with the appropriate shipping rates and associated + # with the correct stock location def to_shipment # At this point we should only have one content item per inventory unit - # across the entire set of inventory units to be shipped, which has been - # taken care of by the Prioritizer + # across the entire set of inventory units to be shipped, which has + # been taken care of by the Prioritizer contents.each { |content_item| content_item.inventory_unit.state = content_item.state.to_s } Spree::Shipment.new( diff --git a/core/app/models/spree/stock/quantifier.rb b/core/app/models/spree/stock/quantifier.rb index 1c79ac5cd27..50cfcb8acde 100644 --- a/core/app/models/spree/stock/quantifier.rb +++ b/core/app/models/spree/stock/quantifier.rb @@ -8,6 +8,10 @@ def initialize(variant) @stock_items = Spree::StockItem.joins(:stock_location).where(:variant_id => @variant, Spree::StockLocation.table_name =>{ :active => true}) end + # Returns the total number of inventory units on hand for the variant. + # + # @return [Fixnum] number of inventory units on hand, or infinity if + # inventory is not tracked on the variant. def total_on_hand if @variant.should_track_inventory? stock_items.sum(:count_on_hand) @@ -16,10 +20,18 @@ def total_on_hand end end + # Checks if any of its stock items are backorderable. + # + # @return [Boolean] true if any stock items are backorderable def backorderable? stock_items.any?(&:backorderable) end + # Checks if it is possible to supply a given number of units. + # + # @param required [Fixnum] the number of required stock units + # @return [Boolean] true if we have the required amount on hand or the + # variant is backorderable, otherwise false def can_supply?(required) total_on_hand >= required || backorderable? end diff --git a/core/app/models/spree/stock_item.rb b/core/app/models/spree/stock_item.rb index 9b04a7b8430..c5840ae09cf 100644 --- a/core/app/models/spree/stock_item.rb +++ b/core/app/models/spree/stock_item.rb @@ -15,14 +15,22 @@ class StockItem < Spree::Base after_save :conditional_variant_touch, if: :changed? after_touch { variant.touch } + # @return [Array] the backordered inventory units + # associated with this stock item def backordered_inventory_units Spree::InventoryUnit.backordered_for_stock_item(self) end + # @return [String] the name of this stock item's variant def variant_name variant.name end + # Adjusts the count on hand by a given value. + # + # @note This will cause backorders to be processed. + # @param value [Fixnum] the amount to change the count on hand by, positive + # or negative values are valid def adjust_count_on_hand(value) self.with_lock do self.count_on_hand = self.count_on_hand + value @@ -32,6 +40,10 @@ def adjust_count_on_hand(value) end end + # Sets this stock item's count on hand. + # + # @note This will cause backorders to be processed. + # @param value [Fixnum] the desired count on hand def set_count_on_hand(value) self.count_on_hand = value process_backorders(count_on_hand - count_on_hand_was) @@ -39,19 +51,26 @@ def set_count_on_hand(value) self.save! end + # @return [Boolean] true if this stock item's count on hand is not zero def in_stock? self.count_on_hand > 0 end - # Tells whether it's available to be included in a shipment + # @return [Boolean] true if this stock item can be included in a shipment def available? self.in_stock? || self.backorderable? end + # @note This returns the variant regardless of whether it was soft + # deleted. + # @return [Spree::Variant] this stock item's variant. def variant Spree::Variant.unscoped { super } end + # Sets the count on hand to zero if it not already zero. + # + # @note This processes backorders if the count on hand is not zero. def reduce_count_on_hand_to_zero self.set_count_on_hand(0) if count_on_hand > 0 end diff --git a/core/app/models/spree/taxon.rb b/core/app/models/spree/taxon.rb index e1c888f8e9a..483d1ddb110 100644 --- a/core/app/models/spree/taxon.rb +++ b/core/app/models/spree/taxon.rb @@ -28,8 +28,8 @@ class Taxon < Spree::Base validates_attachment :icon, content_type: { content_type: ["image/jpg", "image/jpeg", "image/png", "image/gif"] } - # indicate which filters should be used for a taxon - # this method should be customized to your own site + # @note This method is meant to be overridden on a store by store basis. + # @return [Array] filters that should be used for a taxon def applicable_filters fs = [] # fs << ProductFilters.taxons_below(self) @@ -40,7 +40,8 @@ def applicable_filters fs end - # Return meta_title if set otherwise generates from root name and/or taxon name + # @return [String] meta_title if set otherwise a string containing the + # root name and taxon name def seo_title unless meta_title.blank? meta_title @@ -49,7 +50,8 @@ def seo_title end end - # Creates permalink based on Stringex's .to_url method + # Sets this taxons permalink to a valid url encoded string based on its + # name and its parents permalink (if present.) def set_permalink if parent.present? self.permalink = [parent.permalink, (permalink.blank? ? name.to_url : permalink.split('/').last)].join('/') @@ -58,15 +60,19 @@ def set_permalink end end - # For #2759 + # @return [String] this taxon's permalink def to_param permalink end + # @return [ActiveRecord::Relation] the active products the + # belong to this taxon def active_products products.active end + # @return [String] this taxon's ancestors names followed by its own name, + # separated by arrows def pretty_name ancestor_chain = self.ancestors.inject("") do |name, ancestor| name += "#{ancestor.name} -> " @@ -74,13 +80,14 @@ def pretty_name ancestor_chain + "#{name}" end - # awesome_nested_set sorts by :lft and :rgt. This call re-inserts the child - # node so that its resulting position matches the observable 0-indexed position. - # ** Note ** no :position column needed - a_n_s doesn't handle the reordering if - # you bring your own :order_column. - # - # See #3390 for background. + # @see https://github.com/spree/spree/issues/3390 def child_index=(idx) + # awesome_nested_set sorts by :lft and :rgt. This call re-inserts the + # child node so that its resulting position matches the observable + # 0-indexed position. + # + # NOTE: no :position column needed - awesom_nested_set doesn't handle the + # reordering if you bring your own :order_column. move_to_child_with_index(parent, idx.to_i) unless self.new_record? end diff --git a/core/lib/spree/localized_number.rb b/core/lib/spree/localized_number.rb index 0fb62a19be5..e74e63709fb 100644 --- a/core/lib/spree/localized_number.rb +++ b/core/lib/spree/localized_number.rb @@ -1,7 +1,12 @@ module Spree class LocalizedNumber - - # Strips all non-price-like characters from the number, taking into account locale settings. + # Given a string, strips all non-price-like characters from it, + # taking into account locale settings. Returns the input given anything + # else. + # + # @param number [String, anything] the number to be parsed or anything else + # @return [BigDecimal, anything] the number parsed from the string passed + # in, or whatever you passed in def self.parse(number) return number unless number.is_a?(String) @@ -15,6 +20,5 @@ def self.parse(number) number.to_d end - end end diff --git a/core/lib/spree/money.rb b/core/lib/spree/money.rb index a6a4badbab1..57e47168a03 100644 --- a/core/lib/spree/money.rb +++ b/core/lib/spree/money.rb @@ -3,11 +3,26 @@ require 'money' module Spree + # Spree::Money is a relatively thin wrapper around Monetize which handles + # formatting via Spree::Config. class Money attr_reader :money delegate :cents, to: :money + # @param amount [#to_s] the value of the money object + # @param options [Hash] the options for creating the money object + # @option options [String] currency the currency for the money object + # @option options [Boolean] with_currency when true, show the currency + # @option options [Boolean] no_cents when true, round to the closest dollar + # @option options [String] decimal_mark the mark for delimiting the + # decimals + # @option options [String, false, nil] thousands_separator the character to + # delimit powers of 1000, if one is desired, otherwise false or nil + # @option options [Boolean] sign_before_symbol when true the sign of the + # value comes before the currency symbol + # @option options [:before, :after] symbol_position the position of the + # currency symbol def initialize(amount, options={}) @money = Monetize.parse([amount, (options[:currency] || Spree::Config[:currency])].join) @options = {} @@ -22,10 +37,16 @@ def initialize(amount, options={}) @options[:symbol_position] = @options[:symbol_position].to_sym end + # @return [String] the value of this money object formatted according to + # its options def to_s @money.format(@options) end + # @note If you pass in options, ensure you pass in the html: true as well. + # @param options [Hash] additional formatting options + # @return [String] the value of this money object formatted according to + # its options and any additional options, by default as html. def to_html(options = { html: true }) output = @money.format(@options.merge(options)) if options[:html] @@ -36,10 +57,14 @@ def to_html(options = { html: true }) output end + # (see #to_s) def as_json(*) to_s end + # Delegates comparison to the internal ruby money instance. + # + # @see http://www.rubydoc.info/gems/money/Money/Arithmetic#%3D%3D-instance_method def ==(obj) @money == obj.money end diff --git a/core/lib/spree/permitted_attributes.rb b/core/lib/spree/permitted_attributes.rb index 7213e2ab965..182d42080aa 100644 --- a/core/lib/spree/permitted_attributes.rb +++ b/core/lib/spree/permitted_attributes.rb @@ -1,4 +1,7 @@ module Spree + # Spree::PermittedAttributes contains the attributes permitted through strong + # params in various controllers in the frontend. Extensions and stores that + # need additional params to be accepted can mutate these arrays to add them. module PermittedAttributes ATTRIBUTES = [ :address_attributes, diff --git a/core/lib/spree/testing_support/controller_requests.rb b/core/lib/spree/testing_support/controller_requests.rb index cdb331d391c..272cad424c5 100644 --- a/core/lib/spree/testing_support/controller_requests.rb +++ b/core/lib/spree/testing_support/controller_requests.rb @@ -1,30 +1,29 @@ -# Use this module to easily test Spree actions within Spree components -# or inside your application to test routes for the mounted Spree engine. -# -# Inside your spec_helper.rb, include this module inside the RSpec.configure -# block by doing this: -# -# require 'spree/testing_support/controller_requests' -# RSpec.configure do |c| -# c.include Spree::TestingSupport::ControllerRequests, :type => :controller -# end -# -# Then, in your controller tests, you can access spree routes like this: -# -# require 'spec_helper' -# -# describe Spree::ProductsController do -# it "can see all the products" do -# spree_get :index -# end -# end -# -# Use spree_get, spree_post, spree_put or spree_delete to make requests -# to the Spree engine, and use regular get, post, put or delete to make -# requests to your application. -# module Spree module TestingSupport + # Use this module to easily test Spree actions within Spree components or + # inside your application to test routes for the mounted Spree engine. + # + # Inside your spec_helper.rb, include this module inside the + # RSpec.configure block by doing this: + # + # require 'spree/testing_support/controller_requests' + # RSpec.configure do |c| + # c.include Spree::TestingSupport::ControllerRequests, :type => :controller + # end + # + # Then, in your controller tests, you can access spree routes like this: + # + # require 'spec_helper' + # + # describe Spree::ProductsController do + # it "can see all the products" do + # spree_get :index + # end + # end + # + # Use spree_get, spree_post, spree_put or spree_delete to make requests to + # the Spree engine, and use regular get, post, put or delete to make + # requests to your application. module ControllerRequests def spree_get(action, parameters = nil, session = nil, flash = nil) process_spree_action(action, parameters, session, flash, "GET") diff --git a/core/lib/spree/testing_support/i18n.rb b/core/lib/spree/testing_support/i18n.rb index 786e2f1cf91..d326072da30 100644 --- a/core/lib/spree/testing_support/i18n.rb +++ b/core/lib/spree/testing_support/i18n.rb @@ -1,13 +1,14 @@ -# This file exists solely to test whether or not there are missing translations -# within the code that Spree's test suite covers. -# -# If there is a translation referenced which has no corresponding key within the -# .yml file, then there will be a message output at the end of the suite showing -# that. -# -# If there is a translation within the locale file which *isn't* used in the -# test, this will also be shown at the end of the suite run. module Spree + # This file exists solely to test whether or not there are missing translations + # within the code that Spree's test suite covers. + # + # If there is a translation referenced which has no corresponding key within the + # .yml file, then there will be a message output at the end of the suite showing + # that. + # + # If there is a translation within the locale file which *isn't* used in the + # test, this will also be shown at the end of the suite run. + class << self attr_accessor :used_translations, :missing_translation_messages, :unused_translations, :unused_translation_messages