solidus_core 1.1.0.pre1 → 1.1.0.pre2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of solidus_core might be problematic. Click here for more details.

Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/app/mailers/spree/carton_mailer.rb +12 -1
  3. data/app/models/concerns/spree/user_address_book.rb +17 -8
  4. data/app/models/concerns/spree/user_methods.rb +3 -6
  5. data/app/models/spree/ability.rb +1 -1
  6. data/app/models/spree/adjustment.rb +1 -1
  7. data/app/models/spree/app_configuration.rb +11 -0
  8. data/app/models/spree/order.rb +6 -6
  9. data/app/models/spree/order/checkout.rb +9 -0
  10. data/app/models/spree/order_capturing.rb +4 -1
  11. data/app/models/spree/order_contents.rb +5 -0
  12. data/app/models/spree/order_shipping.rb +1 -1
  13. data/app/models/spree/payment.rb +5 -2
  14. data/app/models/spree/payment/processing.rb +1 -1
  15. data/app/models/spree/product.rb +1 -1
  16. data/app/models/spree/promotion.rb +8 -1
  17. data/app/models/spree/promotion/rules/first_repeat_purchase_since.rb +36 -0
  18. data/app/models/spree/promotion/rules/product.rb +2 -1
  19. data/app/models/spree/promotion_handler/cart.rb +1 -14
  20. data/app/models/spree/prototype.rb +3 -1
  21. data/app/models/spree/prototype_taxon.rb +6 -0
  22. data/app/models/spree/taxon.rb +2 -1
  23. data/app/models/spree/tracker.rb +1 -1
  24. data/app/models/spree/transfer_item.rb +2 -6
  25. data/config/locales/en.yml +10 -1
  26. data/db/default/spree/zones.rb +2 -2
  27. data/db/migrate/20140713140455_create_spree_return_authorization_reasons.rb +13 -11
  28. data/db/migrate/20150128032538_remove_environment_from_tracker.rb +6 -0
  29. data/db/migrate/20150515211137_fix_adjustment_order_id.rb +80 -0
  30. data/db/migrate/20150609093816_increase_scale_on_pre_tax_amounts.rb +16 -0
  31. data/db/migrate/20150826002555_convert_habtm_to_hmt_for_taxon_prototypes.rb +15 -0
  32. data/db/migrate/20151010003252_add_foreign_keys_for_taxons_prototypes.rb +6 -0
  33. data/db/migrate/20151013222921_remove_token_permissions_table.rb +16 -0
  34. data/db/migrate/20151014213349_rename_identifier_to_number_for_payment.rb +7 -0
  35. data/db/migrate/20151015203732_add_foreign_keys_for_product_promotion_rules.rb +9 -0
  36. data/db/migrate/20151021113730_add_sale_to_spree_promotions.rb +6 -0
  37. data/db/migrate/20151021163309_convert_sale_promotions.rb +28 -0
  38. data/lib/spree/core.rb +2 -0
  39. data/lib/spree/core/controller_helpers/strong_parameters.rb +7 -0
  40. data/lib/spree/core/engine.rb +2 -1
  41. data/lib/spree/deprecation.rb +3 -0
  42. data/lib/spree/mailer_previews/carton_preview.rb +1 -1
  43. data/lib/spree/permission_sets.rb +23 -0
  44. data/{app/models → lib}/spree/permission_sets/base.rb +0 -0
  45. data/{app/models → lib}/spree/permission_sets/configuration_display.rb +0 -0
  46. data/{app/models → lib}/spree/permission_sets/configuration_management.rb +0 -0
  47. data/{app/models → lib}/spree/permission_sets/dashboard_display.rb +0 -0
  48. data/{app/models → lib}/spree/permission_sets/default_customer.rb +2 -1
  49. data/{app/models → lib}/spree/permission_sets/order_display.rb +0 -0
  50. data/{app/models → lib}/spree/permission_sets/order_management.rb +0 -0
  51. data/{app/models → lib}/spree/permission_sets/product_display.rb +0 -0
  52. data/{app/models → lib}/spree/permission_sets/product_management.rb +0 -0
  53. data/{app/models → lib}/spree/permission_sets/promotion_display.rb +0 -0
  54. data/{app/models → lib}/spree/permission_sets/promotion_management.rb +0 -0
  55. data/{app/models → lib}/spree/permission_sets/report_display.rb +0 -0
  56. data/{app/models → lib}/spree/permission_sets/restricted_stock_display.rb +0 -0
  57. data/{app/models → lib}/spree/permission_sets/restricted_stock_management.rb +0 -0
  58. data/{app/models → lib}/spree/permission_sets/restricted_stock_transfer_display.rb +0 -0
  59. data/{app/models → lib}/spree/permission_sets/restricted_stock_transfer_management.rb +22 -12
  60. data/{app/models → lib}/spree/permission_sets/stock_display.rb +0 -0
  61. data/{app/models → lib}/spree/permission_sets/stock_management.rb +0 -0
  62. data/{app/models → lib}/spree/permission_sets/stock_transfer_display.rb +0 -0
  63. data/{app/models → lib}/spree/permission_sets/stock_transfer_management.rb +0 -0
  64. data/{app/models → lib}/spree/permission_sets/super_user.rb +0 -0
  65. data/{app/models → lib}/spree/permission_sets/user_display.rb +0 -0
  66. data/lib/spree/permission_sets/user_management.rb +21 -0
  67. data/lib/spree/permitted_attributes.rb +7 -4
  68. data/lib/spree/testing_support/factories/order_factory.rb +6 -2
  69. data/lib/spree/testing_support/factories/tracker_factory.rb +0 -1
  70. data/lib/spree/testing_support/order_walkthrough.rb +22 -19
  71. metadata +41 -27
  72. data/app/models/spree/permission_sets/user_management.rb +0 -12
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7a97651829c856767b16419843fcf2aa267271bc
4
- data.tar.gz: d875f33165835fb31607620cdc6d668e10b6babb
3
+ metadata.gz: ae8ca76f31ff2adc9e441466cdb0612a00327773
4
+ data.tar.gz: 025e77575cb92628ce8ecc1aceb3834b8e07c475
5
5
  SHA512:
6
- metadata.gz: cc874e200a655bd5ed0cdbb3e91ed51dc723391c875fce148b59a78c5b1b2dd27c15e53a08da8a7f909d790406a4055a60c20ea01cbc38760802c65ccd45f821
7
- data.tar.gz: 728d34af08ff76b34b2c8e69bd1cb532c8ec27762dbea4a5a10d3f7b0a6970383d2a8ab554adeea518f89e7624f00f91f6a02c9a79bd126bf36095acf94501b0
6
+ metadata.gz: 2bea81b1bd27228bfacf973daedec5e4db6c0b66758eaaeedd4f4d2fa720435fd9a32b63d857f885bedc103ffadf0ee3e42ddc7f92b6eb34f28517e05f6e9bb7
7
+ data.tar.gz: f846e9f87d9951c77ab83ae46af5668469d3b1e58f1fe744c40772b988513fb54527d80b8a1e3d1e417a879e190783d436b95efefb4b5083b35c571b3d1965c6
@@ -1,7 +1,18 @@
1
1
  module Spree
2
2
  class CartonMailer < BaseMailer
3
3
  # Send an email to customers to notify that an individual carton has been
4
- # shipped.
4
+ # shipped. If a carton contains items from multiple orders then this will be
5
+ # called with that carton one time for each order.
6
+ #
7
+ # @param carton [Spree::Carton] the shipped carton
8
+ # @param order [Spree::Order] one of the orders with items in the carton
9
+ # @param resend [Boolean] indicates whether the email is a 'resend' (e.g.
10
+ # triggered by the admin "resend" button)
11
+ # @return [Mail::Message]
12
+ #
13
+ # Note: The signature of this method has changed. The new (non-deprecated)
14
+ # signature is:
15
+ # def shipped_email(carton:, order:, resend: false)
5
16
  def shipped_email(options, deprecated_options={})
6
17
  if options.is_a?(Integer)
7
18
  ActiveSupport::Deprecation.warn "Calling shipped_email with a carton_id is DEPRECATED. Instead use CartonMailer.shipped_email(order: order, carton: carton)"
@@ -54,19 +54,28 @@ module Spree
54
54
  alias_method :ship_address, :default_address
55
55
  alias_method :ship_address_attributes=, :default_address_attributes=
56
56
 
57
+ # saves address in address book
58
+ # sets address to the default if automatic_default_address is set to true
59
+ # if address is nil, does nothing and returns nil
57
60
  def ship_address=(address)
58
61
  be_default = Spree::Config.automatic_default_address
59
- save_in_address_book(address.attributes, be_default)
62
+ save_in_address_book(address.attributes, be_default) if address
60
63
  end
61
64
 
65
+ # saves order.ship_address and order.bill_address in address book
66
+ # sets ship_address to the default if automatic_default_address is set to true
67
+ # sets bill_address to the default if automatic_default_address is set to true and there is no ship_address
68
+ # if one address is nil, does not save that address
62
69
  def persist_order_address(order)
63
- if Spree::Config.automatic_default_address
64
- save_in_address_book(order.ship_address.attributes, true)
65
- save_in_address_book(order.bill_address.attributes, order.ship_address.nil?)
66
- else
67
- save_in_address_book(order.ship_address.attributes)
68
- save_in_address_book(order.bill_address.attributes)
69
- end
70
+ save_in_address_book(
71
+ order.ship_address.attributes,
72
+ Spree::Config.automatic_default_address
73
+ ) if order.ship_address
74
+
75
+ save_in_address_book(
76
+ order.bill_address.attributes,
77
+ order.ship_address.nil? && Spree::Config.automatic_default_address
78
+ ) if order.bill_address
70
79
  end
71
80
 
72
81
  # Add an address to the user's list of saved addresses for future autofill
@@ -23,13 +23,10 @@ module Spree
23
23
  has_many :store_credit_events, through: :store_credits
24
24
  money_methods :total_available_store_credit
25
25
 
26
- def self.ransackable_associations(auth_object=nil)
27
- %w[addresses]
28
- end
26
+ include Spree::RansackableAttributes unless included_modules.include?(Spree::RansackableAttributes)
29
27
 
30
- def self.ransackable_attributes(auth_object=nil)
31
- %w[id email]
32
- end
28
+ self.whitelisted_ransackable_associations = %w[addresses]
29
+ self.whitelisted_ransackable_attributes = %w[id email]
33
30
  end
34
31
 
35
32
  # has_spree_role? simply needs to return true or false whether a user has a role or not.
@@ -51,7 +51,7 @@ module Spree
51
51
  def register_extension_abilities
52
52
  Ability.abilities.each do |clazz|
53
53
  ability = clazz.send(:new, user)
54
- @rules = rules + ability.send(:rules)
54
+ merge(ability)
55
55
  end
56
56
  end
57
57
 
@@ -16,7 +16,7 @@ module Spree
16
16
  class Adjustment < Spree::Base
17
17
  belongs_to :adjustable, polymorphic: true, touch: true
18
18
  belongs_to :source, polymorphic: true
19
- belongs_to :order, class_name: "Spree::Order"
19
+ belongs_to :order, class_name: 'Spree::Order', inverse_of: :all_adjustments
20
20
  belongs_to :promotion_code, :class_name => 'Spree::PromotionCode'
21
21
  belongs_to :adjustment_reason, class_name: 'Spree::AdjustmentReason', inverse_of: :adjustments
22
22
 
@@ -265,6 +265,17 @@ module Spree
265
265
  @promotion_chooser_class ||= Spree::PromotionChooser
266
266
  end
267
267
 
268
+ # Allows providing your own Mailer for shipped cartons.
269
+ #
270
+ # @!attribute [rw] carton_shipped_email_class
271
+ # @return [ActionMailer::Base] an object that responds to "shipped_email"
272
+ # (e.g. an ActionMailer with a "shipped_email" method) with the same
273
+ # signature as Spree::CartonMailer.shipped_email.
274
+ attr_writer :carton_shipped_email_class
275
+ def carton_shipped_email_class
276
+ @carton_shipped_email_class ||= Spree::CartonMailer
277
+ end
278
+
268
279
  def static_model_preferences
269
280
  @static_model_preferences ||= Spree::Preferences::StaticModelPreferences.new
270
281
  end
@@ -59,6 +59,11 @@ module Spree
59
59
  has_many :products, through: :variants
60
60
  has_many :variants, through: :line_items
61
61
  has_many :refunds, through: :payments
62
+ has_many :all_adjustments,
63
+ class_name: 'Spree::Adjustment',
64
+ foreign_key: :order_id,
65
+ dependent: :destroy,
66
+ inverse_of: :order
62
67
 
63
68
  has_many :order_stock_locations, class_name: "Spree::OrderStockLocation"
64
69
  has_many :stock_locations, through: :order_stock_locations
@@ -145,11 +150,6 @@ module Spree
145
150
  self.line_item_comparison_hooks.add(hook)
146
151
  end
147
152
 
148
- def all_adjustments
149
- Adjustment.where("order_id = :order_id OR (adjustable_id = :order_id AND adjustable_type = 'Spree::Order')",
150
- order_id: self.id)
151
- end
152
-
153
153
  # For compatiblity with Calculator::PriceSack
154
154
  def amount
155
155
  line_items.inject(0.0) { |sum, li| sum + li.amount }
@@ -400,7 +400,7 @@ module Spree
400
400
 
401
401
  # Helper methods for checkout steps
402
402
  def paid?
403
- payment_state == 'paid' || payment_state == 'credit_owed'
403
+ %w(paid credit_owed).include?(payment_state)
404
404
  end
405
405
 
406
406
  def available_payment_methods
@@ -126,6 +126,15 @@ module Spree
126
126
  order.update_totals
127
127
  order.persist_totals
128
128
  end
129
+
130
+ after_transition do |order, transition|
131
+ order.logger.debug "Order #{order.number} transitioned from #{transition.from} to #{transition.to} via #{transition.event}"
132
+ end
133
+
134
+
135
+ after_failure do |order, transition|
136
+ order.logger.debug "Order #{order.number} halted transition on event #{transition.event} state #{transition.from}: #{order.errors.full_messages.join}"
137
+ end
129
138
  end
130
139
 
131
140
  alias_method :save_state, :save
@@ -33,7 +33,10 @@ class Spree::OrderCapturing
33
33
  end
34
34
  end
35
35
  ensure
36
- @order.update!
36
+ # FIXME: Adding the inverse_of on the payments relation for orders -should- fix this,
37
+ # however it only appears to make it worse (calling with changes three times instead of once.
38
+ # Warrants an investigation. Reloading for now.
39
+ @order.reload.update!
37
40
  end
38
41
  end
39
42
  end
@@ -16,6 +16,11 @@ module Spree
16
16
  after_add_or_remove(line_item, options)
17
17
  end
18
18
 
19
+ def remove_line_item(line_item, options = {})
20
+ line_item.destroy!
21
+ after_add_or_remove(line_item, options)
22
+ end
23
+
19
24
  def update_cart(params)
20
25
  if order.update_attributes(params)
21
26
  unless order.completed?
@@ -87,7 +87,7 @@ class Spree::OrderShipping
87
87
 
88
88
  def send_shipment_emails(carton)
89
89
  carton.orders.each do |order|
90
- Spree::CartonMailer.shipped_email(order: order, carton: carton).deliver_later
90
+ Spree::Config.carton_shipped_email_class.shipped_email(order: order, carton: carton).deliver_later
91
91
  end
92
92
  end
93
93
  end
@@ -2,6 +2,9 @@ module Spree
2
2
  class Payment < Spree::Base
3
3
  include Spree::Payment::Processing
4
4
 
5
+ alias_attribute :identifier, :number
6
+ deprecate :identifier, :identifier=, deprecator: Spree::Deprecation
7
+
5
8
  IDENTIFIER_CHARS = (('A'..'Z').to_a + ('0'..'9').to_a - %w(0 1 I O)).freeze
6
9
  NON_RISKY_AVS_CODES = ['B', 'D', 'H', 'J', 'M', 'Q', 'T', 'V', 'X', 'Y'].freeze
7
10
  RISKY_AVS_CODES = ['A', 'C', 'E', 'F', 'G', 'I', 'K', 'L', 'N', 'O', 'P', 'R', 'S', 'U', 'W', 'Z'].freeze
@@ -255,8 +258,8 @@ module Spree
255
258
  # See https://github.com/spree/spree/issues/1998#issuecomment-12869105
256
259
  def set_unique_identifier
257
260
  begin
258
- self.identifier = generate_identifier
259
- end while self.class.exists?(identifier: self.identifier)
261
+ self.number = generate_identifier
262
+ end while self.class.exists?(number: self.number)
260
263
  end
261
264
 
262
265
  def generate_identifier
@@ -218,7 +218,7 @@ module Spree
218
218
 
219
219
  # The unique identifier to be passed in to the payment gateway
220
220
  def gateway_order_id
221
- "#{order.number}-#{self.identifier}"
221
+ "#{order.number}-#{self.number}"
222
222
  end
223
223
 
224
224
  def token_based?
@@ -25,7 +25,7 @@ module Spree
25
25
  has_many :classifications, dependent: :delete_all, inverse_of: :product
26
26
  has_many :taxons, through: :classifications, before_remove: :remove_taxon
27
27
 
28
- has_many :product_promotion_rules
28
+ has_many :product_promotion_rules, dependent: :destroy
29
29
  has_many :promotion_rules, through: :product_promotion_rules
30
30
 
31
31
  belongs_to :tax_category, class_name: 'Spree::TaxCategory'
@@ -16,7 +16,7 @@ module Spree
16
16
  has_many :order_promotions, class_name: "Spree::OrderPromotion"
17
17
  has_many :orders, through: :order_promotions
18
18
 
19
- has_many :codes, class_name: "Spree::PromotionCode", inverse_of: :promotion
19
+ has_many :codes, class_name: "Spree::PromotionCode", inverse_of: :promotion, dependent: :destroy
20
20
  alias_method :promotion_codes, :codes
21
21
 
22
22
  accepts_nested_attributes_for :promotion_actions, :promotion_rules
@@ -28,6 +28,7 @@ module Spree
28
28
  validates :usage_limit, numericality: { greater_than: 0, allow_nil: true }
29
29
  validates :per_code_usage_limit, numericality: { greater_than_or_equal_to: 0, allow_nil: true }
30
30
  validates :description, length: { maximum: 255 }
31
+ validate :apply_automatically_disallowed_with_codes_or_paths
31
32
 
32
33
  before_save :normalize_blank_values
33
34
 
@@ -228,5 +229,11 @@ module Spree
228
229
  def match_all?
229
230
  match_policy == "all"
230
231
  end
232
+
233
+ def apply_automatically_disallowed_with_codes_or_paths
234
+ return unless apply_automatically
235
+ errors.add(:apply_automatically, :disallowed_with_code) if codes.any?
236
+ errors.add(:apply_automatically, :disallowed_with_path) if path.present?
237
+ end
231
238
  end
232
239
  end
@@ -0,0 +1,36 @@
1
+ module Spree
2
+ class Promotion
3
+ module Rules
4
+ class FirstRepeatPurchaseSince < PromotionRule
5
+ preference :days_ago, :integer, default: 365
6
+ validates :preferred_days_ago, numericality: { only_integer: true, greater_than: 0 }
7
+
8
+ # This promotion is applicable to orders only.
9
+ def applicable?(promotable)
10
+ promotable.is_a?(Spree::Order)
11
+ end
12
+
13
+ # This is never eligible if the order does not have a user, and that user does not have any previous completed orders.
14
+ #
15
+ # This is eligible if the user's most recently completed order is more than the preferred days ago
16
+ # @param order [Spree::Order]
17
+ # @option options
18
+ def eligible?(order, options = {})
19
+ return false unless order.user
20
+
21
+ last_order = last_completed_order(order.user)
22
+ return false unless last_order
23
+
24
+ last_order.completed_at < preferred_days_ago.days.ago
25
+ end
26
+
27
+ private
28
+
29
+ def last_completed_order user
30
+ user.orders.complete.order(:completed_at).last
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+
@@ -6,7 +6,8 @@ module Spree
6
6
  # either come from assigned product group or are assingned directly to
7
7
  # the rule.
8
8
  class Product < PromotionRule
9
- has_many :product_promotion_rules, class_name: 'Spree::ProductPromotionRule', foreign_key: :promotion_rule_id
9
+ has_many :product_promotion_rules, dependent: :destroy, foreign_key: :promotion_rule_id,
10
+ class_name: 'Spree::ProductPromotionRule'
10
11
  has_many :products, class_name: 'Spree::Product', through: :product_promotion_rules
11
12
 
12
13
  MATCH_POLICIES = %w(any all none)
@@ -40,20 +40,7 @@ module Spree
40
40
  end
41
41
 
42
42
  def sale_promotions
43
- promo_table = Promotion.arel_table
44
- code_table = PromotionCode.arel_table
45
-
46
- promotion_code_join = promo_table.join(code_table, Arel::Nodes::OuterJoin).on(
47
- promo_table[:id].eq(code_table[:promotion_id])
48
- ).join_sources
49
-
50
- Promotion.active.includes(:promotion_rules).
51
- joins(promotion_code_join).
52
- where(
53
- code_table[:value].eq(nil).and(
54
- promo_table[:path].eq(nil)
55
- )
56
- ).distinct
43
+ Promotion.where(apply_automatically: true).active.includes(:promotion_rules)
57
44
  end
58
45
 
59
46
  def promotion_code(promotion)
@@ -2,7 +2,9 @@ module Spree
2
2
  class Prototype < Spree::Base
3
3
  has_and_belongs_to_many :properties, join_table: :spree_properties_prototypes
4
4
  has_and_belongs_to_many :option_types, join_table: :spree_option_types_prototypes
5
- has_and_belongs_to_many :taxons, join_table: :spree_taxons_prototypes
5
+
6
+ has_many :prototype_taxons, dependent: :destroy
7
+ has_many :taxons, through: :prototype_taxons
6
8
 
7
9
  validates :name, presence: true
8
10
  end
@@ -0,0 +1,6 @@
1
+ module Spree
2
+ class PrototypeTaxon < Spree::Base
3
+ belongs_to :prototype
4
+ belongs_to :taxon
5
+ end
6
+ end
@@ -6,7 +6,8 @@ module Spree
6
6
  has_many :classifications, -> { order(:position) }, dependent: :delete_all, inverse_of: :taxon
7
7
  has_many :products, through: :classifications
8
8
 
9
- has_and_belongs_to_many :prototypes, join_table: :spree_taxons_prototypes
9
+ has_many :prototype_taxons, dependent: :destroy
10
+ has_many :prototypes, through: :prototype_taxons
10
11
 
11
12
  before_create :set_permalink
12
13
 
@@ -1,7 +1,7 @@
1
1
  module Spree
2
2
  class Tracker < Spree::Base
3
3
  def self.current
4
- tracker = where(active: true, environment: Rails.env).first
4
+ tracker = where(active: true).first
5
5
  tracker.analytics_id.present? ? tracker : nil if tracker
6
6
  end
7
7
  end
@@ -21,7 +21,7 @@ module Spree
21
21
  private
22
22
 
23
23
  def ensure_stock_transfer_not_closed
24
- if stock_transfer_closed?
24
+ if stock_transfer.closed?
25
25
  errors.add(:base, Spree.t('errors.messages.cannot_modify_transfer_item_closed_stock_transfer'))
26
26
  end
27
27
  end
@@ -47,12 +47,8 @@ module Spree
47
47
  end
48
48
  end
49
49
 
50
- def stock_transfer_closed?
51
- stock_transfer.closed?
52
- end
53
-
54
50
  def check_stock?
55
- !stock_transfer_closed? && stock_transfer.source_location.check_stock_on_transfer?
51
+ !stock_transfer.shipped? && stock_transfer.source_location.check_stock_on_transfer?
56
52
  end
57
53
  end
58
54
  end
@@ -299,6 +299,11 @@ en:
299
299
  attributes:
300
300
  currency:
301
301
  must_match_order_currency: "Must match order currency"
302
+ spree/promotion:
303
+ attributes:
304
+ apply_automatically:
305
+ disallowed_with_code: Disallowed for promotions with a code
306
+ disallowed_with_path: Disallowed for promotions with a path
302
307
  spree/refund:
303
308
  attributes:
304
309
  amount:
@@ -589,6 +594,7 @@ en:
589
594
  cannot_rebuild_shipments_shipments_not_pending: Cannot rebuild shipments for an order with non-pending shipments.
590
595
  cannot_perform_operation: Cannot perform requested operation
591
596
  cannot_set_shipping_method_without_address: Cannot set shipping method until customer details are provided.
597
+ cannot_update_email: You do not have access to update this user's email address. <br />Please contact a superuser if you need to perform this action.
592
598
  capture: Capture
593
599
  capture_events: Capture events
594
600
  card_code: Card Code
@@ -764,7 +770,6 @@ en:
764
770
  enable_mail_delivery: Enable Mail Delivery
765
771
  end: End
766
772
  ending_in: Ending in
767
- environment: Environment
768
773
  error: error
769
774
  errors:
770
775
  messages:
@@ -1228,6 +1233,10 @@ en:
1228
1233
  description: Apply a promotion to every nth order a user has completed.
1229
1234
  name: Nth Order
1230
1235
  form_text: "Apply this promotion on the users Nth order: "
1236
+ first_repeat_purchase_since:
1237
+ description: Available only to user who have not purchased in a while
1238
+ name: First Repeat Purchase Since
1239
+ form_text: "Apply this promotion to users whose last order was more than X days ago: "
1231
1240
  promotions: Promotions
1232
1241
  promotion_successfully_created: Promotion has been successfully created!
1233
1242
  promotion_total_changed_before_complete: "One or more of the promotions on your order have become ineligible and were removed. Please check the new order amounts and try again."