spree_core 6.0.0.rc1 → 6.0.0.rc2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -3
  3. data/app/jobs/spree/search_provider/index_job.rb +4 -0
  4. data/app/jobs/spree/uploads/purge_unattached_job.rb +49 -0
  5. data/app/mailers/spree/base_mailer.rb +4 -3
  6. data/app/models/concerns/spree/admin_user_methods.rb +2 -0
  7. data/app/models/concerns/spree/calculated_adjustments.rb +41 -13
  8. data/app/models/concerns/spree/calculator/currency_amounts.rb +1 -1
  9. data/app/models/concerns/spree/calculator/tiers.rb +1 -14
  10. data/app/models/concerns/spree/canonical_decimals.rb +61 -0
  11. data/app/models/concerns/spree/customer_methods.rb +8 -4
  12. data/app/models/concerns/spree/derived_number.rb +11 -13
  13. data/app/models/concerns/spree/preference_schema/json_schema.rb +139 -0
  14. data/app/models/concerns/spree/preference_schema.rb +59 -21
  15. data/app/models/concerns/spree/product_scopes.rb +5 -1
  16. data/app/models/concerns/spree/purchase/payment_processing.rb +1 -1
  17. data/app/models/concerns/spree/purchase/purchase_order.rb +7 -2
  18. data/app/models/concerns/spree/purchase/store_credits.rb +36 -1
  19. data/app/models/concerns/spree/purchase/totals.rb +1 -8
  20. data/app/models/concerns/spree/ransackable_attributes.rb +46 -0
  21. data/app/models/concerns/spree/signed_id_attachments.rb +54 -0
  22. data/app/models/concerns/spree/stores/email_branding.rb +2 -1
  23. data/app/models/concerns/spree/stores/markets.rb +5 -2
  24. data/app/models/concerns/spree/typed_adjustment_line.rb +4 -5
  25. data/app/models/concerns/spree/typed_associations.rb +6 -18
  26. data/app/models/concerns/spree/vat_price_calculation.rb +5 -6
  27. data/app/models/spree/address.rb +1 -1
  28. data/app/models/spree/adjusters/largest_remainder.rb +14 -1
  29. data/app/models/spree/adjusters/promotion.rb +3 -4
  30. data/app/models/spree/allowed_origin.rb +3 -0
  31. data/app/models/spree/base.rb +29 -0
  32. data/app/models/spree/calculator/flat_percent_item_total.rb +1 -1
  33. data/app/models/spree/calculator/flat_rate.rb +2 -2
  34. data/app/models/spree/calculator/flexi_rate.rb +3 -3
  35. data/app/models/spree/calculator/percent_on_line_item.rb +1 -1
  36. data/app/models/spree/calculator/price_sack.rb +4 -4
  37. data/app/models/spree/calculator/shipping/digital_delivery.rb +2 -2
  38. data/app/models/spree/calculator/shipping/flat_percent_item_total.rb +6 -4
  39. data/app/models/spree/calculator/shipping/flat_rate.rb +4 -4
  40. data/app/models/spree/calculator/shipping/flexi_rate.rb +3 -3
  41. data/app/models/spree/calculator/shipping/per_item.rb +2 -2
  42. data/app/models/spree/calculator/shipping/price_sack.rb +4 -4
  43. data/app/models/spree/calculator/tiered_flat_rate.rb +2 -2
  44. data/app/models/spree/calculator/tiered_percent.rb +1 -1
  45. data/app/models/spree/catalog.rb +1 -0
  46. data/app/models/spree/catalog_price.rb +0 -7
  47. data/app/models/spree/catalog_price_tier.rb +0 -8
  48. data/app/models/spree/category.rb +3 -1
  49. data/app/models/spree/channel/gating.rb +2 -1
  50. data/app/models/spree/channel.rb +2 -0
  51. data/app/models/spree/claim.rb +1 -0
  52. data/app/models/spree/collection.rb +2 -0
  53. data/app/models/spree/commission_rate.rb +5 -1
  54. data/app/models/spree/commission_rules/category_rule.rb +2 -5
  55. data/app/models/spree/commission_rules/item_total_rule.rb +2 -2
  56. data/app/models/spree/commission_rules/seller_rule.rb +2 -5
  57. data/app/models/spree/company.rb +1 -0
  58. data/app/models/spree/custom_field_definition.rb +11 -12
  59. data/app/models/spree/customer_group.rb +1 -0
  60. data/app/models/spree/data_request.rb +1 -0
  61. data/app/models/spree/delivery_method.rb +6 -1
  62. data/app/models/spree/delivery_method_rules/channel_rule.rb +2 -5
  63. data/app/models/spree/delivery_method_rules/item_total_rule.rb +2 -2
  64. data/app/models/spree/delivery_profile.rb +1 -0
  65. data/app/models/spree/delivery_rate.rb +3 -3
  66. data/app/models/spree/delivery_zone.rb +1 -1
  67. data/app/models/spree/exchange.rb +1 -0
  68. data/app/models/spree/exports/report.rb +2 -2
  69. data/app/models/spree/file_upload.rb +155 -0
  70. data/app/models/spree/fulfillment.rb +13 -3
  71. data/app/models/spree/fulfillment_group.rb +6 -1
  72. data/app/models/spree/gateway/bogus.rb +2 -0
  73. data/app/models/spree/gateway.rb +1 -1
  74. data/app/models/spree/gift_card.rb +6 -7
  75. data/app/models/spree/gift_card_batch.rb +3 -4
  76. data/app/models/spree/import.rb +3 -0
  77. data/app/models/spree/integration.rb +1 -1
  78. data/app/models/spree/line_item.rb +2 -2
  79. data/app/models/spree/market.rb +7 -2
  80. data/app/models/spree/media.rb +1 -9
  81. data/app/models/spree/newsletter_subscriber.rb +1 -0
  82. data/app/models/spree/option_type.rb +2 -1
  83. data/app/models/spree/option_value.rb +2 -1
  84. data/app/models/spree/order.rb +5 -5
  85. data/app/models/spree/order_group.rb +7 -2
  86. data/app/models/spree/package_type.rb +1 -0
  87. data/app/models/spree/payment/gateway_options.rb +1 -1
  88. data/app/models/spree/payment.rb +3 -10
  89. data/app/models/spree/payment_method/check.rb +2 -0
  90. data/app/models/spree/payment_method/store_credit.rb +11 -9
  91. data/app/models/spree/payment_method.rb +42 -2
  92. data/app/models/spree/policy.rb +1 -0
  93. data/app/models/spree/price.rb +17 -14
  94. data/app/models/spree/price_adjustment_tier.rb +4 -0
  95. data/app/models/spree/price_list.rb +13 -7
  96. data/app/models/spree/price_rules/channel_rule.rb +2 -5
  97. data/app/models/spree/price_rules/customer_group_rule.rb +2 -5
  98. data/app/models/spree/price_rules/market_rule.rb +2 -5
  99. data/app/models/spree/price_rules/user_rule.rb +1 -3
  100. data/app/models/spree/product/channels.rb +9 -2
  101. data/app/models/spree/product.rb +23 -23
  102. data/app/models/spree/product_type.rb +1 -0
  103. data/app/models/spree/promotion/actions/create_line_items.rb +1 -1
  104. data/app/models/spree/promotion/rules/category.rb +1 -1
  105. data/app/models/spree/promotion/rules/channel.rb +2 -5
  106. data/app/models/spree/promotion/rules/country.rb +3 -4
  107. data/app/models/spree/promotion/rules/currency.rb +1 -1
  108. data/app/models/spree/promotion/rules/customer_group.rb +2 -5
  109. data/app/models/spree/promotion/rules/item_total.rb +2 -2
  110. data/app/models/spree/promotion/rules/market.rb +2 -5
  111. data/app/models/spree/promotion/rules/option_value.rb +2 -3
  112. data/app/models/spree/promotion/rules/product.rb +1 -1
  113. data/app/models/spree/promotion.rb +1 -0
  114. data/app/models/spree/purchase_order.rb +2 -0
  115. data/app/models/spree/refund.rb +13 -15
  116. data/app/models/spree/return.rb +1 -0
  117. data/app/models/spree/saved_report.rb +1 -0
  118. data/app/models/spree/search_provider/base.rb +4 -0
  119. data/app/models/spree/seller.rb +8 -1
  120. data/app/models/spree/seller_payout.rb +1 -0
  121. data/app/models/spree/seller_requirement.rb +1 -0
  122. data/app/models/spree/seller_requirement_submission.rb +1 -0
  123. data/app/models/spree/seller_transfer.rb +1 -0
  124. data/app/models/spree/shipping_label.rb +1 -0
  125. data/app/models/spree/stock/estimator.rb +1 -1
  126. data/app/models/spree/stock_level.rb +2 -0
  127. data/app/models/spree/stock_location.rb +1 -0
  128. data/app/models/spree/stock_transfer.rb +2 -0
  129. data/app/models/spree/store.rb +37 -12
  130. data/app/models/spree/store_credit.rb +5 -4
  131. data/app/models/spree/supplier.rb +1 -0
  132. data/app/models/spree/tax_category.rb +1 -0
  133. data/app/models/spree/tax_exemption_certificate.rb +1 -0
  134. data/app/models/spree/tax_provider/internal.rb +12 -9
  135. data/app/models/spree/tax_rate.rb +29 -6
  136. data/app/models/spree/taxon.rb +1 -1
  137. data/app/models/spree/update_status.rb +25 -0
  138. data/app/models/spree/variant.rb +6 -9
  139. data/app/models/spree/webhook_delivery.rb +1 -0
  140. data/app/models/spree/webhook_endpoint.rb +36 -0
  141. data/app/presenters/spree/csv/order_line_item_presenter.rb +10 -6
  142. data/app/presenters/spree/csv/price_list_price_presenter.rb +3 -8
  143. data/app/presenters/spree/csv/product_variant_presenter.rb +17 -8
  144. data/app/presenters/spree/csv/purchase_order_item_presenter.rb +1 -3
  145. data/app/services/spree/carts/update.rb +3 -3
  146. data/app/services/spree/imports/row_processors/product_variant.rb +12 -0
  147. data/app/services/spree/locales.rb +39 -5
  148. data/app/services/spree/orders/create.rb +3 -3
  149. data/app/services/spree/orders/discounts/create.rb +8 -8
  150. data/app/services/spree/prices/bulk_upsert.rb +32 -25
  151. data/app/services/spree/seeds/stores.rb +1 -1
  152. data/app/services/spree/stores/provision_defaults.rb +13 -16
  153. data/app/services/spree/update_check.rb +160 -0
  154. data/app/services/spree/uploads/assign_stores.rb +92 -0
  155. data/app/validators/spree/attachment_content_type_validator.rb +19 -2
  156. data/app/validators/spree/locale_codes_validator.rb +22 -0
  157. data/app/workflows/spree/fulfillments/create.rb +2 -2
  158. data/app/workflows/spree/payments/capture.rb +20 -5
  159. data/app/workflows/spree/payments/process.rb +3 -1
  160. data/app/workflows/spree/price_lists/update.rb +4 -4
  161. data/app/workflows/spree/purchase_orders/create.rb +3 -1
  162. data/app/workflows/spree/shipping_labels/record.rb +2 -2
  163. data/app/workflows/spree/stock_transfers/create.rb +3 -1
  164. data/config/currency_exponents.json +180 -0
  165. data/config/locales/content_locales.rb +9 -0
  166. data/config/locales/en.yml +10 -2
  167. data/db/migrate/20261009000001_change_money_columns_to_decimal_19_4.rb +116 -0
  168. data/db/migrate/20261010000001_add_store_id_to_active_storage_blobs.rb +12 -0
  169. data/lib/generators/spree/dummy/templates/rails/test.rb +21 -2
  170. data/lib/spree/core/configuration.rb +18 -0
  171. data/lib/spree/core/engine.rb +26 -0
  172. data/lib/spree/core/preferences/invalid_preferences.rb +18 -0
  173. data/lib/spree/core/preferences/json_conversion.rb +5 -1
  174. data/lib/spree/core/preferences/masking.rb +51 -1
  175. data/lib/spree/core/preferences/preferable.rb +212 -26
  176. data/lib/spree/core/preferences/preferable_class_methods.rb +121 -4
  177. data/lib/spree/core/ransack_configuration.rb +13 -2
  178. data/lib/spree/core/version.rb +1 -1
  179. data/lib/spree/core.rb +6 -3
  180. data/lib/spree/iso_data.rb +7 -0
  181. data/lib/spree/localized_number.rb +6 -0
  182. data/lib/spree/migrations.rb +4 -1
  183. data/lib/spree/money.rb +196 -15
  184. data/lib/spree/reporting/adapters/base.rb +7 -6
  185. data/lib/spree/reporting/registry.rb +13 -0
  186. data/lib/spree/testing_support/factories/product_factory.rb +9 -0
  187. data/lib/spree/upgrades/5_6_to_6_0/manifest.yml +22 -1
  188. data/lib/spree/uploads.rb +101 -0
  189. data/lib/tasks/markets.rake +2 -1
  190. data/lib/tasks/migrate_users_to_customers.rake +23 -18
  191. data/lib/tasks/money.rake +46 -0
  192. data/lib/tasks/seller_sample_data.rake +2 -2
  193. data/lib/tasks/store_settings.rake +13 -6
  194. data/lib/tasks/typed_adjustments_migration.rake +1 -1
  195. data/lib/tasks/uploads.rake +19 -0
  196. metadata +35 -6
  197. data/app/jobs/spree/products/touch_categories_job.rb +0 -15
  198. data/app/jobs/spree/products/touch_taxons_job.rb +0 -13
@@ -19,9 +19,10 @@ module Spree
19
19
  # @param expected_at [Date, nil]
20
20
  # @param reference [String, nil] the supplier's own order number
21
21
  # @param notes [String, nil]
22
+ # @param metadata [Hash, nil]
22
23
  # @param created_by [Object, nil]
23
24
  def perform(store:, supplier:, destination_location:, items: [], currency: nil,
24
- expected_at: nil, cancel_by: nil, reference: nil, notes: nil, created_by: nil)
25
+ expected_at: nil, cancel_by: nil, reference: nil, notes: nil, metadata: nil, created_by: nil)
25
26
  super
26
27
 
27
28
  step :build_purchase_order
@@ -50,6 +51,7 @@ module Spree
50
51
  created_by: created_by,
51
52
  status: Spree::PurchaseOrder.default_status
52
53
  )
54
+ @purchase_order.metadata = metadata if metadata
53
55
 
54
56
  Array(items).each do |item|
55
57
  @purchase_order.items.build(
@@ -13,7 +13,7 @@ module Spree
13
13
  attr_reader :shipping_label
14
14
 
15
15
  # @param owner [Spree::Fulfillment, Spree::Return]
16
- # @param file [ActionDispatch::Http::UploadedFile, String] the file, or a signed blob id from a direct upload
16
+ # @param file [ActionDispatch::Http::UploadedFile, String] the file, or a signed id from POST /files
17
17
  # @param tracking_number [String] the number printed on the label
18
18
  # @param carrier [String, nil] free text; detected from the number when omitted
19
19
  # @param service [String, nil]
@@ -69,7 +69,7 @@ module Spree
69
69
  failure(@shipping_label) unless @shipping_label.save
70
70
  end
71
71
 
72
- # A direct-upload blob carries the type the client declared, which the
72
+ # An uploaded blob carries the type the client declared, which the
73
73
  # attachment validator re-decides from the bytes; either way the
74
74
  # extension is the merchant-visible answer, so it wins when it names a
75
75
  # format Spree knows.
@@ -16,9 +16,10 @@ module Spree
16
16
  # may legitimately open empty and gain lines as the merchant packs
17
17
  # @param reference [String, nil] the merchant's own label for the trip
18
18
  # @param notes [String, nil]
19
+ # @param metadata [Hash, nil]
19
20
  # @param created_by [Object, nil]
20
21
  def perform(store:, source_location:, destination_location:, items: [],
21
- reference: nil, notes: nil, created_by: nil)
22
+ reference: nil, notes: nil, metadata: nil, created_by: nil)
22
23
  super
23
24
 
24
25
  step :build_transfer
@@ -46,6 +47,7 @@ module Spree
46
47
  created_by: created_by,
47
48
  status: Spree::StockTransfer.default_status
48
49
  )
50
+ @stock_transfer.metadata = metadata if metadata
49
51
 
50
52
  Array(items).each do |item|
51
53
  @stock_transfer.items.build(variant: item[:variant], quantity_shipped: item[:quantity_shipped])
@@ -0,0 +1,180 @@
1
+ {
2
+ "AED": 2,
3
+ "AFN": 2,
4
+ "ALL": 2,
5
+ "AMD": 2,
6
+ "ANG": 2,
7
+ "AOA": 2,
8
+ "ARS": 2,
9
+ "AUD": 2,
10
+ "AWG": 2,
11
+ "AZN": 2,
12
+ "BAM": 2,
13
+ "BBD": 2,
14
+ "BDT": 2,
15
+ "BGN": 2,
16
+ "BHD": 3,
17
+ "BIF": 0,
18
+ "BMD": 2,
19
+ "BND": 2,
20
+ "BOB": 2,
21
+ "BRL": 2,
22
+ "BSD": 2,
23
+ "BTN": 2,
24
+ "BWP": 2,
25
+ "BYN": 2,
26
+ "BYR": 0,
27
+ "BZD": 2,
28
+ "CAD": 2,
29
+ "CDF": 2,
30
+ "CHF": 2,
31
+ "CLF": 4,
32
+ "CLP": 0,
33
+ "CNY": 2,
34
+ "COP": 2,
35
+ "CRC": 2,
36
+ "CUC": 2,
37
+ "CUP": 2,
38
+ "CVE": 2,
39
+ "CZK": 2,
40
+ "DJF": 0,
41
+ "DKK": 2,
42
+ "DOP": 2,
43
+ "DZD": 2,
44
+ "EEK": 2,
45
+ "EGP": 2,
46
+ "ERN": 2,
47
+ "ETB": 2,
48
+ "EUR": 2,
49
+ "FJD": 2,
50
+ "FKP": 2,
51
+ "GBP": 2,
52
+ "GEL": 2,
53
+ "GHS": 2,
54
+ "GIP": 2,
55
+ "GMD": 2,
56
+ "GNF": 0,
57
+ "GTQ": 2,
58
+ "GYD": 2,
59
+ "HKD": 2,
60
+ "HNL": 2,
61
+ "HRK": 2,
62
+ "HTG": 2,
63
+ "HUF": 2,
64
+ "IDR": 2,
65
+ "ILS": 2,
66
+ "INR": 2,
67
+ "IQD": 3,
68
+ "IRR": 2,
69
+ "ISK": 0,
70
+ "JMD": 2,
71
+ "JOD": 3,
72
+ "JPY": 0,
73
+ "KES": 2,
74
+ "KGS": 2,
75
+ "KHR": 2,
76
+ "KMF": 0,
77
+ "KPW": 2,
78
+ "KRW": 0,
79
+ "KWD": 3,
80
+ "KYD": 2,
81
+ "KZT": 2,
82
+ "LAK": 2,
83
+ "LBP": 2,
84
+ "LKR": 2,
85
+ "LRD": 2,
86
+ "LSL": 2,
87
+ "LTL": 2,
88
+ "LVL": 2,
89
+ "LYD": 3,
90
+ "MAD": 2,
91
+ "MDL": 2,
92
+ "MGA": 2,
93
+ "MKD": 2,
94
+ "MMK": 2,
95
+ "MNT": 2,
96
+ "MOP": 2,
97
+ "MRO": 2,
98
+ "MRU": 2,
99
+ "MTL": 2,
100
+ "MUR": 2,
101
+ "MVR": 2,
102
+ "MWK": 2,
103
+ "MXN": 2,
104
+ "MYR": 2,
105
+ "MZN": 2,
106
+ "NAD": 2,
107
+ "NGN": 2,
108
+ "NIO": 2,
109
+ "NOK": 2,
110
+ "NPR": 2,
111
+ "NZD": 2,
112
+ "OMR": 3,
113
+ "PAB": 2,
114
+ "PEN": 2,
115
+ "PGK": 2,
116
+ "PHP": 2,
117
+ "PKR": 2,
118
+ "PLN": 2,
119
+ "PYG": 0,
120
+ "QAR": 2,
121
+ "RON": 2,
122
+ "RSD": 2,
123
+ "RUB": 2,
124
+ "RWF": 0,
125
+ "SAR": 2,
126
+ "SBD": 2,
127
+ "SCR": 2,
128
+ "SDG": 2,
129
+ "SEK": 2,
130
+ "SGD": 2,
131
+ "SHP": 2,
132
+ "SKK": 2,
133
+ "SLE": 2,
134
+ "SLL": 2,
135
+ "SOS": 2,
136
+ "SRD": 2,
137
+ "SSP": 2,
138
+ "STD": 2,
139
+ "STN": 2,
140
+ "SVC": 2,
141
+ "SYP": 2,
142
+ "SZL": 2,
143
+ "THB": 2,
144
+ "TJS": 2,
145
+ "TMM": 2,
146
+ "TMT": 2,
147
+ "TND": 3,
148
+ "TOP": 2,
149
+ "TRY": 2,
150
+ "TTD": 2,
151
+ "TWD": 2,
152
+ "TZS": 2,
153
+ "UAH": 2,
154
+ "UGX": 0,
155
+ "USD": 2,
156
+ "UYI": 0,
157
+ "UYU": 2,
158
+ "UYW": 4,
159
+ "UZS": 2,
160
+ "VED": 2,
161
+ "VEF": 2,
162
+ "VES": 2,
163
+ "VND": 0,
164
+ "VUV": 0,
165
+ "WST": 2,
166
+ "XAF": 0,
167
+ "XCD": 2,
168
+ "XCG": 2,
169
+ "XOF": 0,
170
+ "XPF": 0,
171
+ "YER": 2,
172
+ "ZAR": 2,
173
+ "ZMK": 2,
174
+ "ZMW": 2,
175
+ "ZWD": 2,
176
+ "ZWG": 2,
177
+ "ZWL": 2,
178
+ "ZWN": 2,
179
+ "ZWR": 2
180
+ }
@@ -0,0 +1,9 @@
1
+ # A store's content locales are the codes Spree::Locales::ALL lists,
2
+ # independent of the languages anything has translation files for. An API
3
+ # request sets I18n.locale (and so Mobility's) to its content locale, and both
4
+ # refuse a locale I18n does not know, so this registers every listed code:
5
+ # content can be stored and read in, say, Yoruba, while messages in it fall
6
+ # back along the i18n fallback chain. The backend ignores a locale with no
7
+ # keys at all, hence the one marker key. An app that limits
8
+ # `config.i18n.available_locales` keeps its own list.
9
+ Spree::Locales::ALL.to_h { |code| [code.to_sym, { spree_content_locale: true }] }
@@ -251,6 +251,7 @@ en:
251
251
  must_belong_to_same_store: must belong to the same store
252
252
  not_registered: is not a registered provider
253
253
  spree_content_type_not_allowed: is not a file type we accept
254
+ too_many_decimals: has more than %{count} decimal places
254
255
  models:
255
256
  spree/allowed_origin:
256
257
  attributes:
@@ -381,6 +382,7 @@ en:
381
382
  base:
382
383
  duplicate_quantity: each quantity break must have its own quantity
383
384
  invalid_quantity: each quantity break must start at a whole number of units
385
+ malformed_price: a price must be a number like 16.50
384
386
  negative_price: a price cannot be below zero
385
387
  nothing_to_apply_to: has no rules, no catalog and no products, so it would apply to nothing. Add one before activating.
386
388
  price_rises_with_quantity: a quantity break cannot cost more than the price a buyer pays below it
@@ -873,6 +875,7 @@ en:
873
875
  invalid_status: Invalid status
874
876
  invalid_storefront_access: is not a valid storefront access level
875
877
  items_not_a_list: items must be a list of objects
878
+ money_preference_too_many_decimals: has more than %{count} decimal places for %{currency}
876
879
  must_be_greater_than_min_amount: must be greater than or equal to the minimum
877
880
  no_pickup_point_provider: this delivery method has no pickup point provider configured
878
881
  not_cancellable: cannot be canceled once its goods have been dispatched
@@ -894,6 +897,7 @@ en:
894
897
  stock_level_immutable: cannot be changed after the hold is taken — release the hold and take it again
895
898
  store_association_can_not_be_changed: The store association can not be changed
896
899
  store_is_already_set: Store is already set
900
+ unknown_calculator_type: "%{type} is not a calculator offered here"
897
901
  unknown_custom_field_type: 'is not a known custom field type (expected one of: %{types})'
898
902
  unknown_scopes: 'contains unknown scopes: %{scopes}'
899
903
  unregistered_actor: is not a registered actor class
@@ -956,6 +960,7 @@ en:
956
960
  image: Image
957
961
  import: Import
958
962
  import_has_no_failed_rows: This import has no failed rows to retry.
963
+ import_invalid_number: "%{column} must be a number like 16.50, got %{value}."
959
964
  import_mailer:
960
965
  greeting: Hi %{user_name},
961
966
  import_done:
@@ -1000,6 +1005,7 @@ en:
1000
1005
  link: Link
1001
1006
  locale: Locale
1002
1007
  locale_not_supported_by_store: is not supported by this store
1008
+ locales_not_listed: 'includes locales Spree does not support: %{codes}'
1003
1009
  manual: Manual
1004
1010
  market: Market
1005
1011
  me_no_current_user: No admin user authenticated this request
@@ -1138,8 +1144,7 @@ en:
1138
1144
  phone: Phone
1139
1145
  pickup:
1140
1146
  store_pickup: Store Pickup
1141
- po_document_invalid: A purchase order document must be a PDF, image or Word file of %{size} or less.
1142
- po_document_invalid_signed_id: The purchase order document reference is not valid.
1147
+ po_document_invalid: A purchase order document must be a PDF, image, Word document, spreadsheet or CSV file of %{size} or less.
1143
1148
  po_document_missing: This order has no attached purchase order document.
1144
1149
  po_document_too_large: is larger than %{size}.
1145
1150
  po_document_upload_incomplete: The purchase order document was not uploaded completely. Please try attaching it again.
@@ -1672,6 +1677,7 @@ en:
1672
1677
  errors:
1673
1678
  already_purchased: This parcel already has an active shipping label
1674
1679
  file_fetch_failed: The label file could not be fetched from the carrier (HTTP %{status})
1680
+ file_renamed: Send the uploaded label's signed id as file_signed_id
1675
1681
  file_required: An uploaded label needs a file
1676
1682
  no_file_url: The carrier did not provide a file for this label
1677
1683
  not_refund_requested: Only a label awaiting the carrier's refund decision can be confirmed refunded
@@ -1791,6 +1797,8 @@ en:
1791
1797
  type: Type
1792
1798
  unable_to_connect_to_gateway: Unable to connect to gateway.
1793
1799
  updated_at: Updated at
1800
+ upload_reference_invalid: A signed reference in this request is invalid or has expired. For a file, upload it again.
1801
+ upload_wrong_visibility: must be uploaded with %{visibility} visibility
1794
1802
  url: URL
1795
1803
  user: User
1796
1804
  validation:
@@ -0,0 +1,116 @@
1
+ # Widens every money column to decimal(19,4): room for a dinar's third decimal,
2
+ # sub-cent unit prices and very large amounts. Every stored value fits exactly,
3
+ # so no value changes. A column already at (19,4) is skipped, so an install
4
+ # that widened its largest tables online ahead of the upgrade runs this as a
5
+ # no-op for those (see the 5.6 to 6.0 upgrade guide).
6
+ class ChangeMoneyColumnsToDecimal194 < ActiveRecord::Migration[8.1]
7
+ COLUMNS = {
8
+ spree_adjustments: %i[amount],
9
+ spree_carts: %i[additional_tax_total adjustment_total delivery_total discount_total fee_total included_tax_total
10
+ item_total non_taxable_adjustment_total payment_total taxable_adjustment_total total],
11
+ spree_catalog_order_minimums: %i[amount],
12
+ spree_claim_line_items: %i[additional_tax_total included_tax_total refund_amount],
13
+ spree_commission_lines: %i[amount tax_amount total],
14
+ spree_commission_rate_values: %i[amount max_amount min_amount],
15
+ spree_delivery_method_services: %i[markup_flat],
16
+ spree_delivery_methods: %i[markup_flat],
17
+ spree_delivery_rates: %i[cost],
18
+ spree_discounts: %i[amount value],
19
+ spree_exchange_line_items: %i[new_additional_tax_total new_included_tax_total original_additional_tax_total
20
+ original_included_tax_total],
21
+ spree_fees: %i[amount],
22
+ spree_fulfillments: %i[additional_tax_total adjustment_total cost discount_total included_tax_total
23
+ non_taxable_adjustment_total pre_tax_amount taxable_adjustment_total],
24
+ spree_gift_card_batches: %i[amount],
25
+ spree_gift_cards: %i[amount amount_authorized amount_used],
26
+ spree_line_items: %i[additional_tax_total adjustment_total cost_price discount_total included_tax_total
27
+ non_taxable_adjustment_total pre_tax_amount price taxable_adjustment_total],
28
+ spree_orders: %i[additional_tax_total adjustment_total commission_amount_total commission_tax_total commission_total
29
+ delivery_total discount_total fee_total included_tax_total item_total non_taxable_adjustment_total
30
+ payment_total taxable_adjustment_total total],
31
+ spree_payment_capture_events: %i[amount],
32
+ spree_payment_sessions: %i[amount],
33
+ spree_payment_splits: %i[authorized_amount captured_amount claimed_amount refunded_amount],
34
+ spree_payments: %i[amount],
35
+ spree_price_histories: %i[amount compare_at_amount],
36
+ spree_prices: %i[amount compare_at_amount],
37
+ spree_products: %i[revenue],
38
+ spree_products_stores: %i[revenue],
39
+ spree_purchase_order_items: %i[unit_cost],
40
+ spree_refunds: %i[amount tax_amount],
41
+ spree_reimbursement_credits: %i[amount],
42
+ spree_reimbursements: %i[total],
43
+ spree_return_items: %i[additional_tax_total included_tax_total pre_tax_amount],
44
+ spree_return_line_items: %i[additional_tax_total included_tax_total pre_tax_amount],
45
+ spree_seller_payouts: %i[amount],
46
+ spree_seller_transfers: %i[amount settled_amount],
47
+ spree_sellers: %i[minimum_payout_amount],
48
+ spree_shipping_labels: %i[cost],
49
+ spree_stock_movements: %i[unit_cost],
50
+ spree_store_credit_events: %i[amount user_total_amount],
51
+ spree_store_credits: %i[amount amount_authorized amount_used],
52
+ spree_tax_lines: %i[amount],
53
+ spree_variants: %i[cost_price]
54
+ }.freeze
55
+
56
+ # What `down` restores: decimal(10,2) unless listed.
57
+ ORIGINAL = {
58
+ 'spree_delivery_rates.cost' => [8, 2],
59
+ 'spree_store_credit_events.amount' => [8, 2],
60
+ 'spree_store_credit_events.user_total_amount' => [8, 2],
61
+ 'spree_store_credits.amount' => [8, 2],
62
+ 'spree_store_credits.amount_authorized' => [8, 2],
63
+ 'spree_store_credits.amount_used' => [8, 2],
64
+ 'spree_fulfillments.pre_tax_amount' => [12, 4],
65
+ 'spree_line_items.pre_tax_amount' => [12, 4],
66
+ 'spree_return_items.additional_tax_total' => [12, 4],
67
+ 'spree_return_items.included_tax_total' => [12, 4],
68
+ 'spree_return_items.pre_tax_amount' => [12, 4],
69
+ 'spree_products.revenue' => [16, 4]
70
+ }.freeze
71
+
72
+ def up
73
+ deliberately_rewriting { COLUMNS.each_key { |table| retype(table) { [19, 4] } } }
74
+ end
75
+
76
+ def down
77
+ deliberately_rewriting do
78
+ COLUMNS.each_key { |table| retype(table) { |column| ORIGINAL.fetch("#{table}.#{column}", [10, 2]) } }
79
+ end
80
+ end
81
+
82
+ private
83
+
84
+ # The rewrite is the point of this migration and the upgrade guide documents
85
+ # it, so an app running strong_migrations is not stopped by it.
86
+ def deliberately_rewriting(&block)
87
+ respond_to?(:safety_assured) ? safety_assured(&block) : yield
88
+ end
89
+
90
+ # One ALTER TABLE per table, so each is rewritten once rather than once per
91
+ # column. PostgreSQL keeps NOT NULL and the default through a type change,
92
+ # and Rails' MySQL adapter carries them over itself; passing them would add a
93
+ # SET NOT NULL that scans every row. SQLite rebuilds the table from the new
94
+ # definition, so it is told both.
95
+ def retype(table)
96
+ return unless table_exists?(table)
97
+
98
+ existing = connection.columns(table).index_by(&:name)
99
+ changes = COLUMNS[table].filter_map do |name|
100
+ column = existing[name.to_s]
101
+ next if column.nil?
102
+
103
+ precision, scale = yield(name)
104
+ [column, precision, scale] unless column.precision == precision && column.scale == scale
105
+ end
106
+ return if changes.empty?
107
+
108
+ change_table(table, bulk: true) do |definition|
109
+ changes.each do |column, precision, scale|
110
+ options = { precision: precision, scale: scale }
111
+ options.merge!(null: column.null, default: column.default) if connection.adapter_name.match?(/sqlite/i)
112
+ definition.change column.name, :decimal, **options
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,12 @@
1
+ # Records which store owns every uploaded file, so a file from one store cannot
2
+ # be attached in another and storage can be measured and cleaned up per store.
3
+ # Active Storage ignores columns it does not define. Existing rows are filled
4
+ # in by `spree:uploads:assign_stores` in the 6.0 upgrade.
5
+ class AddStoreIdToActiveStorageBlobs < ActiveRecord::Migration[8.1]
6
+ def change
7
+ return unless table_exists?(:active_storage_blobs)
8
+ return if column_exists?(:active_storage_blobs, :store_id)
9
+
10
+ add_reference :active_storage_blobs, :store, null: true, index: true
11
+ end
12
+ end
@@ -42,9 +42,28 @@ Dummy::Application.configure do
42
42
  # Print deprecation notices to the stderr
43
43
  config.active_support.deprecation = :stderr
44
44
 
45
- # Spree's own code must not call the deprecated Spree.t.
45
+ # Spree's own code and specs must not call these deprecated APIs. A spec
46
+ # covering one on purpose stubs Spree::Deprecation.warn or silences it.
46
47
  config.active_support.disallowed_deprecation = :raise
47
- config.active_support.disallowed_deprecation_warnings = [/Spree\.t is deprecated/]
48
+ config.active_support.disallowed_deprecation_warnings = [
49
+ /Spree\.t is deprecated/,
50
+ /Spree::Product#(master|variants_including_master) is deprecated/,
51
+ /Spree::(Order|Cart)#(shipment_total|promo_total|item_count|shipping_discount) is deprecated/,
52
+ /Spree::Order#finalize! is deprecated/,
53
+ /Spree::Payment#state=? is deprecated/,
54
+ /Spree::Payment#(process|authorize|purchase|capture|void_transaction)! is deprecated/,
55
+ /Spree::(Category|Product)#classifications is deprecated/,
56
+ /Spree::Product#taxons=? is deprecated/,
57
+ /Spree::Promotion::Rules::Category#taxon/,
58
+ /Spree::Fulfillment#(add_)?shipping_method is deprecated/,
59
+ /Spree::Stock::Package#shipping_methods is deprecated/,
60
+ /Calling Spree::(Carts::AddItem|StockReservations::(Reserve|Extend)) with order: is deprecated/,
61
+ /Spree::Config\[:admin_url\] is deprecated/,
62
+ /private_metadata=? is deprecated/,
63
+ /Spree::Pricing::Resolver is deprecated/,
64
+ /Spree::Invitation#accept! is deprecated/,
65
+ /`preference :\w+, in:` is deprecated/
66
+ ]
48
67
 
49
68
  config.active_job.queue_adapter = :test
50
69
 
@@ -119,6 +119,19 @@ module Spree
119
119
  # requirement, in bytes. Small on purpose: these are certificates and
120
120
  # registrations, and an unbounded upload is a way to fill a disk.
121
121
  preference :max_seller_document_upload_size, :integer, default: 20_971_520, env: 'SPREE_MAX_SELLER_DOCUMENT_UPLOAD_SIZE' # 20 MB
122
+
123
+ # Largest file the Admin and Seller APIs accept for upload straight to
124
+ # storage, in bytes. Digital products (courses, archives) can be large.
125
+ preference :max_upload_size, :integer, default: 5_368_709_120, env: 'SPREE_MAX_UPLOAD_SIZE' # 5 GB
126
+
127
+ # Largest file accepted as bytes in the upload request itself, in bytes.
128
+ # Lower, because those bytes pass through the application.
129
+ preference :max_multipart_upload_size, :integer, default: 10_485_760, env: 'SPREE_MAX_MULTIPART_UPLOAD_SIZE' # 10 MB
130
+
131
+ # Days an uploaded file that was never attached to anything is kept
132
+ # before the daily purge deletes it. Longer than an upload reference
133
+ # stays valid (one day), so nothing a client can still attach is lost.
134
+ preference :unattached_upload_retention_days, :integer, default: 2, env: 'SPREE_UNATTACHED_UPLOAD_RETENTION_DAYS'
122
135
  preference :non_expiring_credit_types, :array, default: [], deprecated: 'Nothing reads this in Spree 6 — store credits no longer carry a category, and expiry lives on Spree::GiftCard'
123
136
  preference :products_per_page, :integer, default: 12, deprecated: 'Nothing reads this in Spree 6 — pass per_page to the API instead'
124
137
  preference :restock_inventory, :boolean, default: true, deprecated: 'Restocking is decided per line item by Spree::ReturnLineItem#resellable'
@@ -137,10 +150,15 @@ module Spree
137
150
  preference :empty_cart_expiry_hours, :integer, default: 48, env: 'SPREE_EMPTY_CART_EXPIRY_HOURS'
138
151
  preference :show_variant_full_price, :boolean, default: false, deprecated: 'Nothing reads this in Spree 6 — a storefront decides how it renders prices'
139
152
  preference :show_products_without_price, :boolean, default: false, deprecated: 'Set it on the store instead'
153
+ # The daily update check also reports usage (see Spree::UpdateCheck).
154
+ # SPREE_TELEMETRY_DISABLED or DO_NOT_TRACK turns it off as well.
155
+ preference :telemetry_enabled, :boolean, default: true
140
156
  preference :tax_using_ship_address, :boolean, default: true, deprecated: 'Set it on the store instead'
141
157
  preference :track_inventory_levels, :boolean, default: true, deprecated: 'Set it on the store instead' # Determines whether to track on_hand values for variants / products.
142
158
  preference :track_price_history, :boolean, default: true, deprecated: 'Set it on the store instead' # Records price changes for Omnibus Directive compliance. Disable for non-EU stores.
143
159
  preference :price_history_retention_days, :integer, default: 30, deprecated: 'Set it on the store instead' # Days to retain price history records. Used by spree:price_history:prune rake task.
160
+ # SPREE_UPDATE_CHECK_DISABLED turns it off as well.
161
+ preference :update_check_enabled, :boolean, default: true
144
162
  preference :use_user_locale, :boolean, default: true, deprecated: true
145
163
 
146
164
  # Sets the path used for products, taxons and pages.
@@ -559,6 +559,25 @@ module Spree
559
559
  )
560
560
  end
561
561
 
562
+ # Deprecated constant aliases warn when their file loads. Leaving them
563
+ # out of eager loading keeps every boot quiet; they still load, and
564
+ # warn, the first time something references the old name.
565
+ DEPRECATED_ALIAS_FILES = %w[
566
+ app/models/concerns/spree/user_methods.rb
567
+ app/models/spree/asset.rb
568
+ app/models/spree/classification.rb
569
+ app/models/spree/promotion/rules/taxon.rb
570
+ app/models/spree/promotion_rule_taxon.rb
571
+ app/models/spree/return_authorization_reason.rb
572
+ app/models/spree/taxon.rb
573
+ app/services/spree/checkout/add_store_credit.rb
574
+ app/services/spree/checkout/remove_store_credit.rb
575
+ ].freeze
576
+
577
+ initializer 'spree.deprecated_aliases.skip_eager_load' do
578
+ Rails.autoloaders.main.do_not_eager_load(*DEPRECATED_ALIAS_FILES.map { |path| root.join(path) })
579
+ end
580
+
562
581
  # filter sensitive information during logging
563
582
  initializer 'spree.params.filter' do |app|
564
583
  app.config.filter_parameters += [
@@ -572,6 +591,13 @@ module Spree
572
591
  ]
573
592
  end
574
593
 
594
+ # Records which store owns every uploaded file, and reports attaching a
595
+ # file to another store's record (docs/plans/6.0-uploads-and-file-ownership.md).
596
+ initializer 'spree.active_storage.store_ownership' do
597
+ ActiveSupport.on_load(:active_storage_blob) { include Spree::Uploads::BlobStoreOwnership }
598
+ ActiveSupport.on_load(:active_storage_attachment) { include Spree::Uploads::AttachmentStoreCheck }
599
+ end
600
+
575
601
  initializer 'spree.core.checking_migrations' do |app|
576
602
  app.config.after_initialize do
577
603
  Migrations.new(config, engine_name).check unless Rails.env.test? || Spree::Config.disable_migration_check
@@ -0,0 +1,18 @@
1
+ module Spree
2
+ module Preferences
3
+ # Raised when a `preferences` payload does not match the schema its type
4
+ # declares. Each failure names the value by a JSON pointer into the
5
+ # payload, so a client can show it next to the field it belongs to.
6
+ class InvalidPreferences < StandardError
7
+ # @return [Array<Hash{Symbol => String}>] `{ pointer:, message: }` per failure
8
+ attr_reader :failures
9
+
10
+ # @param failures [Array<Hash{Symbol => String}>] pointers relative to the preferences object
11
+ # @param prefix [String] where the preferences object sits in the request
12
+ def initialize(failures, prefix: '/preferences')
13
+ @failures = failures.map { |failure| failure.merge(pointer: "#{prefix}#{failure[:pointer]}") }
14
+ super(@failures.map { |failure| "#{failure[:pointer]}: #{failure[:message]}" }.join('; '))
15
+ end
16
+ end
17
+ end
18
+ end
@@ -60,8 +60,12 @@ module Spree
60
60
  # @return [Array<String>]
61
61
  def preference_tables
62
62
  Rails.application.eager_load! if defined?(Rails.application) && Rails.application
63
+ # An anonymous class (a spec's throwaway model) has no name to derive a
64
+ # table from, and asking raises, so it is skipped.
63
65
  owned = ActiveRecord::Base.descendants.filter_map do |model|
64
- model.table_name if !model.abstract_class? && model.include?(Spree::Preferences::Preferable) && model.table_name
66
+ next if model.name.nil? || model.abstract_class? || !model.include?(Spree::Preferences::Preferable)
67
+
68
+ model.table_name
65
69
  end
66
70
 
67
71
  connection.tables.select { |table| owned.include?(table) && connection.column_exists?(table, :preferences) }.sort
@@ -47,6 +47,25 @@ module Spree
47
47
  value.is_a?(String) && value.start_with?(TOKEN)
48
48
  end
49
49
 
50
+ # Money is written to at least the decimals of the currency the record
51
+ # names (a calculator's `currency` preference), keeping up to four like
52
+ # a unit price, so a per-item amount below a cent survives a round trip.
53
+ # A money preference with no currency of its own, such as an order-total
54
+ # rule that applies to any currency, keeps its exact decimal.
55
+ def self.wire_value(preferable, key, type, value)
56
+ case type
57
+ when :password then mask(value)
58
+ when :money
59
+ # Stored as the JSON text of a decimal.
60
+ amount = value.is_a?(String) ? BigDecimal(value, exception: false) : value
61
+ return value unless amount.is_a?(Numeric)
62
+
63
+ currency = preferable.money_preference_currency(key)
64
+ currency.present? ? Spree::Money::Rounding.format(amount, currency, unit_price: true) : Spree::Money::Rounding.format_decimal(amount)
65
+ else value
66
+ end
67
+ end
68
+
50
69
  # Serializes a Preferable's `preferences` hash for the wire,
51
70
  # masking `:password` values. Keys are stringified to match the
52
71
  # wire shape expected by JSON clients — schema entries built by
@@ -59,14 +78,45 @@ module Spree
59
78
  def self.serialize(preferable)
60
79
  return {} if preferable.nil?
61
80
 
81
+ definitions = preferable.class.preference_definitions
62
82
  preferable.preference_schema.each_with_object({}) do |field, hash|
63
83
  # The stored value only, never the default — an unset secret must not
64
84
  # reveal what it would fall back to — read through the record,
65
85
  # because a secret lives in its own column.
66
86
  value = preferable.stored_preference(field[:key]) { nil }
67
- hash[field[:key_string] || field[:key].to_s] = field[:type] == :password ? mask(value) : value
87
+ definition = definitions.fetch(field[:key])
88
+ hash[field[:key_string] || field[:key].to_s] =
89
+ if definition[:of] == :id
90
+ prefixed_ids(value, definition)
91
+ elsif %i[password money].include?(field[:type])
92
+ wire_value(preferable, field[:key], field[:type], value)
93
+ elsif definition[:keys] == :currency && definition[:values] == :money
94
+ money_by_currency(preferable.wire_preference_value(value, definition))
95
+ else
96
+ preferable.wire_preference_value(value, definition)
97
+ end
68
98
  end
69
99
  end
100
+
101
+ # Amounts keyed by currency (`{ "EUR" => "12.5" }`), each written with
102
+ # the decimals of its own currency, as every money value is.
103
+ def self.money_by_currency(value)
104
+ return value unless value.is_a?(Hash)
105
+
106
+ value.to_h do |currency, amount|
107
+ decimal = BigDecimal(amount.to_s, exception: false)
108
+ [currency, decimal ? Spree::Money::Rounding.format(decimal, currency, unit_price: true) : amount]
109
+ end
110
+ end
111
+
112
+ # Ids are stored as raw primary keys and leave as prefixed ids, the form
113
+ # every write accepts.
114
+ def self.prefixed_ids(value, definition)
115
+ return value unless value.is_a?(Array)
116
+
117
+ model = Spree::Preferences::Preferable.preference_model(definition)
118
+ value.map { |id| Spree::PrefixedId.prefixed_id?(id.to_s) ? id : model.prefixed_id_for(id) }
119
+ end
70
120
  end
71
121
  end
72
122
  end