Class: SpreeCmCommissioner::ServiceFeeRule

Inherits:
Base
  • Object
show all
Includes:
Spree::AdjustmentSource, Spree::CalculatedAdjustments
Defined in:
app/models/spree_cm_commissioner/service_fee_rule.rb

Constant Summary collapse

ALLOWED_RULEABLE_TYPES =
%w[Spree::Product Spree::Taxon Spree::Vendor].freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.adjust(order, line_items) ⇒ Object

Deletes all service-fee adjustments on these line items, then recreates them from whichever rule currently resolves for each one. Mirrors Spree::TaxRate.adjust: a plain destroy-then-create (not create_unique_adjustment, which only guards against duplicates and would leave a stale amount in place after a rule edit or a quantity change).



67
68
69
70
71
72
73
74
# File 'app/models/spree_cm_commissioner/service_fee_rule.rb', line 67

def self.adjust(order, line_items)
  Spree::Adjustment.where(adjustable: line_items).service_fee.destroy_all

  line_items.each do |line_item|
    rule = resolve_for(line_item: line_item, currency: order.currency)
    rule&.adjust(order, line_item)
  end
end

.available_calculator_types ⇒ Object

Spree calculators only expose a fixed, hardcoded set of calculable model types (Rails.application.config.spree.calculators is a plain Struct — tax_rates, shipping_methods, promotion_actions_*). There's no slot for a commissioner-owned model, so — same as SpreeCmCommissioner::PricingAction — we expose our own list instead of relying on the inherited calculators class method.



118
119
120
# File 'app/models/spree_cm_commissioner/service_fee_rule.rb', line 118

def self.available_calculator_types
  [SpreeCmCommissioner::Calculators::ServiceFeePercent, SpreeCmCommissioner::Calculators::ServiceFeeFlatAmount]
end

.resolve_for(line_item:, currency:) ⇒ Object

Walks product -> event -> vendor -> global, returning the first active rule that matches the currency. The product/event/vendor leg mirrors SpreeCmCommissioner::TicketTransfers::FeeCalculator's precedence.

"Global" means two different things depending on whether the vendor belongs to a tenant:

  • No tenant (a base BookMe+ vendor): the true platform-wide default (tenant_id nil).
  • Has a tenant: ONLY that tenant's own default (tenant_id: vendor.tenant_id) — deliberately does NOT fall back to the platform-wide default if the tenant hasn't set one. Each white-label tenant is expected to configure their own fee (or explicitly have none); a tenant silently inheriting BookMe+'s own fee because they forgot to configure one would be a surprising, not a sensible, default.


95
96
97
98
99
100
101
102
# File 'app/models/spree_cm_commissioner/service_fee_rule.rb', line 95

def self.resolve_for(line_item:, currency:)
  ruleables_for(line_item).each do |ruleable|
    rule = active_for(ruleable: ruleable, currency: currency).first
    return rule if rule.present?
  end

  active_for_tenant(tenant_id: vendor_for(line_item)&.tenant_id, currency: currency).first
end

.ruleables_for(line_item) ⇒ Object



104
105
106
# File 'app/models/spree_cm_commissioner/service_fee_rule.rb', line 104

def self.ruleables_for(line_item)
  [line_item.product, line_item.event, vendor_for(line_item)]
end

.vendor_for(line_item) ⇒ Object



108
109
110
111
# File 'app/models/spree_cm_commissioner/service_fee_rule.rb', line 108

def self.vendor_for(line_item)
  product = line_item.product
  product.respond_to?(:vendor) ? product.vendor : nil
end

Instance Method Details

#adjust(order, line_item) ⇒ Object

create_adjustment (from Spree::AdjustmentSource) is protected — callable with an explicit receiver only from inside another instance method of this class, not from the self.adjust class method above, where self is the class rather than an instance. This public wrapper is the same shape Spree::TaxRate#adjust uses for the identical reason.



80
81
82
# File 'app/models/spree_cm_commissioner/service_fee_rule.rb', line 80

def adjust(order, line_item)
  create_adjustment(order, line_item)
end

#compute_amount(line_item) ⇒ Object



122
123
124
# File 'app/models/spree_cm_commissioner/service_fee_rule.rb', line 122

def compute_amount(line_item)
  calculator&.compute(line_item) || 0
end

#fee_description ⇒ Object

A short, human-readable summary for admin listings, e.g. "10%" or "$0.25" — so a rule's actual fee doesn't require opening it to see. Two calculator types (not one combined percent+flat calculator) means there's always exactly one number to show here.



129
130
131
132
133
134
135
136
137
138
139
140
# File 'app/models/spree_cm_commissioner/service_fee_rule.rb', line 129

def fee_description
  case calculator
  when SpreeCmCommissioner::Calculators::ServiceFeePercent
    "#{calculator.preferred_percent.to_d.round(2).to_s('F')}%"
  when SpreeCmCommissioner::Calculators::ServiceFeeFlatAmount
    Spree::Money.new(calculator.preferred_flat_amount, currency: currency).to_s
  when nil
    Spree.t(:service_fee_rule_not_configured)
  else
    calculator.description
  end
end

#ruleable_type_label ⇒ Object



156
157
158
159
160
161
162
163
# File 'app/models/spree_cm_commissioner/service_fee_rule.rb', line 156

def ruleable_type_label
  case ruleable_type
  when 'Spree::Product' then Spree.t(:service_fee_rule_scope_product)
  when 'Spree::Taxon' then Spree.t(:service_fee_rule_scope_event)
  when 'Spree::Vendor' then Spree.t(:service_fee_rule_scope_vendor)
  else ruleable_type&.demodulize || Spree.t(:service_fee_rule_scope_global)
  end
end

#scope_label ⇒ Object



142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'app/models/spree_cm_commissioner/service_fee_rule.rb', line 142

def scope_label
  if ruleable.blank?
    # A tenant-scoped row is that tenant's own "global" — it never falls back to the
    # BookMe+-level one (see resolve_for), so it isn't really "global" in the broad sense;
    # labeled as its own Tenant scope rather than reusing the word "Global" for both.
    return Spree.t(:service_fee_rule_scope_tenant, name: tenant&.name || "##{tenant_id}") if tenant_id.present?

    return Spree.t(:service_fee_rule_scope_global)
  end

  label_value = ruleable.respond_to?(:name) ? ruleable.name.to_s : "##{ruleable_id}"
  "#{ruleable_type_label}: #{label_value}"
end