spree_core 6.0.0.beta4 → 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.
- checksums.yaml +4 -4
- data/README.md +5 -5
- data/app/helpers/spree/images_helper.rb +0 -57
- data/app/jobs/spree/base_job.rb +21 -0
- data/app/jobs/spree/carts/reap_expired_job.rb +2 -2
- data/app/jobs/spree/collections/regenerate_time_based_job.rb +6 -3
- data/app/jobs/spree/orders/finalize_stale_drafts_job.rb +8 -5
- data/app/jobs/spree/payments/cancel_unused_sessions_job.rb +61 -0
- data/app/jobs/spree/search_provider/index_job.rb +4 -0
- data/app/jobs/spree/seller_payouts/sweep_seller_job.rb +2 -0
- data/app/jobs/spree/uploads/purge_unattached_job.rb +49 -0
- data/app/mailers/spree/admin_user_mailer.rb +4 -19
- data/app/mailers/spree/base_mailer.rb +97 -13
- data/app/mailers/spree/email_template_mailer.rb +27 -0
- data/app/mailers/spree/export_mailer.rb +3 -4
- data/app/mailers/spree/import_mailer.rb +3 -4
- data/app/mailers/spree/invitation_mailer.rb +24 -7
- data/app/mailers/spree/seller_user_mailer.rb +4 -16
- data/app/mailers/spree/webhook_mailer.rb +2 -2
- data/app/models/concerns/spree/acted_by.rb +1 -1
- data/app/models/concerns/spree/admin_user_methods.rb +15 -1
- data/app/models/concerns/spree/api_typed.rb +91 -0
- data/app/models/concerns/spree/calculated_adjustments.rb +41 -13
- data/app/models/concerns/spree/calculator/currency_amounts.rb +1 -1
- data/app/models/concerns/spree/calculator/tiers.rb +63 -0
- data/app/models/concerns/spree/canonical_decimals.rb +61 -0
- data/app/models/concerns/spree/customer_methods.rb +23 -18
- data/app/models/concerns/spree/derived_number.rb +11 -13
- data/app/models/concerns/spree/email_template_identity.rb +52 -0
- data/app/models/concerns/spree/parameterizable_name.rb +2 -0
- data/app/models/concerns/spree/payment_session_transitions.rb +8 -2
- data/app/models/concerns/spree/post_sale/taxation.rb +54 -0
- data/app/models/concerns/spree/post_sale/taxed_line.rb +51 -0
- data/app/models/concerns/spree/preference_schema/json_schema.rb +139 -0
- data/app/models/concerns/spree/preference_schema.rb +62 -32
- data/app/models/concerns/spree/product_scopes.rb +16 -2
- data/app/models/concerns/spree/provider_listing.rb +41 -0
- data/app/models/concerns/spree/publishable.rb +82 -14
- data/app/models/concerns/spree/purchase/addresses.rb +23 -10
- data/app/models/concerns/spree/purchase/currency.rb +1 -1
- data/app/models/concerns/spree/purchase/deprecated_aliases.rb +87 -0
- data/app/models/concerns/spree/purchase/gift_cards.rb +10 -4
- data/app/models/concerns/spree/purchase/locale.rb +1 -1
- data/app/models/concerns/spree/purchase/payment_processing.rb +8 -4
- data/app/models/concerns/spree/purchase/purchase_order.rb +9 -4
- data/app/models/concerns/spree/purchase/store_credits.rb +36 -1
- data/app/models/concerns/spree/purchase/totals.rb +49 -8
- data/app/models/concerns/spree/ransackable_attributes.rb +46 -0
- data/app/models/concerns/spree/receivable.rb +1 -1
- data/app/models/concerns/spree/searchable.rb +0 -4
- data/app/models/concerns/spree/secret_preferences.rb +59 -0
- data/app/models/concerns/spree/signed_id_attachments.rb +54 -0
- data/app/models/concerns/spree/single_store_resource.rb +1 -1
- data/app/models/concerns/spree/stores/email_branding.rb +32 -0
- data/app/models/concerns/spree/stores/markets.rb +5 -2
- data/app/models/concerns/spree/type_labels.rb +38 -0
- data/app/models/concerns/spree/typed_adjustment_line.rb +5 -6
- data/app/models/concerns/spree/typed_associations.rb +12 -18
- data/app/models/concerns/spree/vat_price_calculation.rb +5 -6
- data/app/models/spree/address.rb +12 -2
- data/app/models/spree/adjusters/largest_remainder.rb +14 -1
- data/app/models/spree/adjusters/promotion.rb +4 -19
- data/app/models/spree/allowed_origin.rb +3 -1
- data/app/models/spree/api_key.rb +5 -6
- data/app/models/spree/authentication/strategies/oidc_strategy.rb +5 -5
- data/app/models/spree/base.rb +99 -22
- data/app/models/spree/calculator/flat_percent_item_total.rb +2 -4
- data/app/models/spree/calculator/flat_rate.rb +3 -5
- data/app/models/spree/calculator/flexi_rate.rb +4 -6
- data/app/models/spree/calculator/percent_on_line_item.rb +2 -2
- data/app/models/spree/calculator/price_sack.rb +5 -7
- data/app/models/spree/calculator/shipping/digital_delivery.rb +3 -5
- data/app/models/spree/calculator/shipping/flat_percent_item_total.rb +7 -7
- data/app/models/spree/calculator/shipping/flat_rate.rb +5 -7
- data/app/models/spree/calculator/shipping/flexi_rate.rb +4 -6
- data/app/models/spree/calculator/shipping/per_item.rb +3 -5
- data/app/models/spree/calculator/shipping/price_sack.rb +5 -7
- data/app/models/spree/calculator/tiered_flat_rate.rb +7 -24
- data/app/models/spree/calculator/tiered_percent.rb +7 -25
- data/app/models/spree/cart.rb +78 -18
- data/app/models/spree/catalog.rb +1 -0
- data/app/models/spree/catalog_price.rb +0 -7
- data/app/models/spree/catalog_price_tier.rb +0 -8
- data/app/models/spree/category.rb +3 -1
- data/app/models/spree/channel/gating.rb +3 -2
- data/app/models/spree/channel.rb +6 -4
- data/app/models/spree/claim.rb +22 -1
- data/app/models/spree/claim_line_item.rb +41 -6
- data/app/models/spree/collection.rb +2 -1
- data/app/models/spree/collection_rule.rb +5 -21
- data/app/models/spree/commission_line.rb +1 -1
- data/app/models/spree/commission_rate.rb +7 -4
- data/app/models/spree/commission_rate_value.rb +1 -1
- data/app/models/spree/commission_rule.rb +4 -11
- data/app/models/spree/commission_rules/category_rule.rb +2 -5
- data/app/models/spree/commission_rules/item_total_rule.rb +2 -2
- data/app/models/spree/commission_rules/product_rule.rb +1 -1
- data/app/models/spree/commission_rules/seller_rule.rb +2 -5
- data/app/models/spree/company.rb +1 -0
- data/app/models/spree/company_invitation.rb +1 -0
- data/app/models/spree/consent_record.rb +0 -1
- data/app/models/spree/credit_card.rb +1 -1
- data/app/models/spree/current.rb +23 -0
- data/app/models/spree/custom_field.rb +2 -2
- data/app/models/spree/custom_field_definition.rb +20 -35
- data/app/models/spree/custom_fields/boolean.rb +1 -1
- data/app/models/spree/custom_fields/json.rb +1 -1
- data/app/models/spree/customer_group.rb +1 -0
- data/app/models/spree/data_feed/google.rb +0 -2
- data/app/models/spree/data_request.rb +2 -1
- data/app/models/spree/delivery_method.rb +36 -14
- data/app/models/spree/delivery_method_rule.rb +4 -11
- data/app/models/spree/delivery_method_rules/channel_rule.rb +2 -5
- data/app/models/spree/delivery_method_rules/item_total_rule.rb +2 -2
- data/app/models/spree/delivery_profile.rb +1 -0
- data/app/models/spree/delivery_rate.rb +7 -8
- data/app/models/spree/delivery_rate_provider/base.rb +1 -0
- data/app/models/spree/delivery_zone.rb +1 -2
- data/app/models/spree/delivery_zone_member.rb +4 -4
- data/app/models/spree/digital_asset.rb +7 -0
- data/app/models/spree/digital_asset_provider/base.rb +2 -0
- data/app/models/spree/digital_link.rb +2 -5
- data/app/models/spree/discount.rb +1 -1
- data/app/models/spree/email_template.rb +25 -0
- data/app/models/spree/email_template_draft.rb +13 -0
- data/app/models/spree/email_template_revision.rb +17 -0
- data/app/models/spree/email_templates/entry.rb +118 -0
- data/app/models/spree/email_templates/sample_record.rb +16 -0
- data/app/models/spree/emails/branding.rb +80 -0
- data/app/models/spree/exchange.rb +16 -1
- data/app/models/spree/exchange_line_item.rb +121 -5
- data/app/models/spree/export.rb +0 -1
- data/app/models/spree/exports/products.rb +10 -1
- data/app/models/spree/exports/report.rb +3 -3
- data/app/models/spree/fee.rb +4 -2
- data/app/models/spree/file_upload.rb +155 -0
- data/app/models/spree/fulfillment.rb +66 -30
- data/app/models/spree/fulfillment_changer.rb +2 -2
- data/app/models/spree/fulfillment_group.rb +6 -1
- data/app/models/spree/fulfillment_item.rb +0 -4
- data/app/models/spree/fulfillment_provider/base.rb +1 -0
- data/app/models/spree/gateway/bogus.rb +6 -0
- data/app/models/spree/gateway/custom_payment_source_method.rb +5 -0
- data/app/models/spree/gateway.rb +6 -2
- data/app/models/spree/gift_card.rb +18 -8
- data/app/models/spree/gift_card_batch.rb +3 -5
- data/app/models/spree/import.rb +5 -7
- data/app/models/spree/import_mapping.rb +1 -2
- data/app/models/spree/import_row.rb +2 -2
- data/app/models/spree/import_schemas/products.rb +1 -0
- data/app/models/spree/integration.rb +9 -19
- data/app/models/spree/invitation.rb +5 -5
- data/app/models/spree/line_item.rb +15 -4
- data/app/models/spree/locale.rb +11 -8
- data/app/models/spree/market.rb +8 -2
- data/app/models/spree/media.rb +1 -9
- data/app/models/spree/newsletter_subscriber.rb +4 -0
- data/app/models/spree/option_type.rb +2 -1
- data/app/models/spree/option_value.rb +2 -1
- data/app/models/spree/order.rb +60 -142
- data/app/models/spree/order_group.rb +18 -4
- data/app/models/spree/order_inventory.rb +10 -5
- data/app/models/spree/order_promotion.rb +1 -1
- data/app/models/spree/order_routing/has_strategy_preference.rb +1 -1
- data/app/models/spree/order_routing/strategy/base.rb +4 -2
- data/app/models/spree/order_routing/strategy/rules.rb +1 -1
- data/app/models/spree/order_routing_rule.rb +6 -22
- data/app/models/spree/package_type.rb +2 -2
- data/app/models/spree/payment/gateway_options.rb +15 -53
- data/app/models/spree/payment/processing.rb +2 -4
- data/app/models/spree/payment.rb +21 -13
- data/app/models/spree/payment_method/check.rb +2 -0
- data/app/models/spree/payment_method/session_instrumentation.rb +1 -0
- data/app/models/spree/payment_method/store_credit.rb +15 -13
- data/app/models/spree/payment_method.rb +78 -6
- data/app/models/spree/payment_session.rb +5 -1
- data/app/models/spree/payment_setup_session.rb +1 -0
- data/app/models/spree/payout_provider/base.rb +3 -1
- data/app/models/spree/pickup_point_provider/base.rb +2 -0
- data/app/models/spree/policy.rb +1 -0
- data/app/models/spree/price.rb +17 -14
- data/app/models/spree/price_adjustment_tier.rb +4 -0
- data/app/models/spree/price_list.rb +14 -9
- data/app/models/spree/price_rules/channel_rule.rb +3 -6
- data/app/models/spree/price_rules/customer_group_rule.rb +3 -6
- data/app/models/spree/price_rules/market_rule.rb +2 -5
- data/app/models/spree/price_rules/user_rule.rb +1 -3
- data/app/models/spree/product/channels.rb +9 -2
- data/app/models/spree/product.rb +54 -30
- data/app/models/spree/product_submission.rb +0 -12
- data/app/models/spree/product_type.rb +1 -1
- data/app/models/spree/promotion/actions/create_line_items.rb +28 -18
- data/app/models/spree/promotion/rules/category.rb +1 -5
- data/app/models/spree/promotion/rules/channel.rb +2 -9
- data/app/models/spree/promotion/rules/country.rb +3 -8
- data/app/models/spree/promotion/rules/currency.rb +1 -5
- data/app/models/spree/promotion/rules/customer_group.rb +2 -9
- data/app/models/spree/promotion/rules/first_order.rb +0 -4
- data/app/models/spree/promotion/rules/item_total.rb +6 -10
- data/app/models/spree/promotion/rules/market.rb +2 -9
- data/app/models/spree/promotion/rules/one_use_per_user.rb +0 -4
- data/app/models/spree/promotion/rules/option_value.rb +2 -6
- data/app/models/spree/promotion/rules/product.rb +1 -5
- data/app/models/spree/promotion/rules/user.rb +0 -4
- data/app/models/spree/promotion/rules/user_logged_in.rb +0 -4
- data/app/models/spree/promotion.rb +36 -6
- data/app/models/spree/promotion_action.rb +7 -14
- data/app/models/spree/promotion_action_line_item.rb +1 -1
- data/app/models/spree/promotion_handler/coupon.rb +59 -27
- data/app/models/spree/promotion_rule.rb +7 -14
- data/app/models/spree/purchase_order.rb +3 -0
- data/app/models/spree/quantity_rule.rb +1 -1
- data/app/models/spree/refund.rb +41 -18
- data/app/models/spree/return.rb +31 -2
- data/app/models/spree/return_line_item.rb +65 -14
- data/app/models/spree/role.rb +7 -7
- data/app/models/spree/saved_report.rb +2 -1
- data/app/models/spree/search_provider/base.rb +4 -0
- data/app/models/spree/seller.rb +10 -2
- data/app/models/spree/seller_payout.rb +8 -0
- data/app/models/spree/seller_requirement.rb +5 -12
- data/app/models/spree/seller_requirement_custom_field.rb +1 -2
- data/app/models/spree/seller_requirement_submission.rb +3 -2
- data/app/models/spree/seller_requirements/accept_terms.rb +2 -3
- data/app/models/spree/seller_transfer.rb +6 -0
- data/app/models/spree/shipping_label.rb +2 -0
- data/app/models/spree/stock/availability_validator.rb +2 -2
- data/app/models/spree/stock/estimator.rb +1 -1
- data/app/models/spree/stock/inventory_unit_builder.rb +1 -1
- data/app/models/spree/stock/packer.rb +2 -2
- data/app/models/spree/stock/splitter/weight.rb +1 -1
- data/app/models/spree/stock_level/custom_events.rb +6 -0
- data/app/models/spree/stock_level.rb +3 -1
- data/app/models/spree/stock_location.rb +1 -0
- data/app/models/spree/stock_movement.rb +1 -1
- data/app/models/spree/stock_receipt_item.rb +1 -1
- data/app/models/spree/stock_reservation.rb +2 -2
- data/app/models/spree/stock_transfer.rb +6 -2
- data/app/models/spree/store.rb +70 -19
- data/app/models/spree/store_credit.rb +13 -13
- data/app/models/spree/store_credit_event.rb +5 -5
- data/app/models/spree/supplier.rb +1 -0
- data/app/models/spree/tax_category.rb +1 -0
- data/app/models/spree/tax_exemption_certificate.rb +3 -0
- data/app/models/spree/tax_identifier.rb +3 -1
- data/app/models/spree/tax_line.rb +53 -3
- data/app/models/spree/tax_provider/base.rb +71 -14
- data/app/models/spree/tax_provider/internal.rb +70 -41
- data/app/models/spree/tax_provider/recorded_share.rb +114 -0
- data/app/models/spree/tax_rate.rb +30 -8
- data/app/models/spree/taxon.rb +1 -1
- data/app/models/spree/taxonomy.rb +0 -1
- data/app/models/spree/update_status.rb +25 -0
- data/app/models/spree/variant.rb +10 -12
- data/app/models/spree/variant_media.rb +1 -1
- data/app/models/spree/webhook_delivery.rb +1 -0
- data/app/models/spree/webhook_endpoint.rb +46 -13
- data/app/models/spree/wishlist.rb +1 -6
- data/app/models/spree/wishlist_item/custom_events.rb +6 -0
- data/app/presenters/spree/csv/customer_presenter.rb +1 -1
- data/app/presenters/spree/csv/newsletter_subscriber_presenter.rb +1 -1
- data/app/presenters/spree/csv/order_line_item_presenter.rb +10 -6
- data/app/presenters/spree/csv/price_list_price_presenter.rb +3 -8
- data/app/presenters/spree/csv/product_variant_presenter.rb +59 -20
- data/app/presenters/spree/csv/purchase_order_item_presenter.rb +1 -3
- data/app/serializers/spree/emails/base_serializer.rb +14 -0
- data/app/serializers/spree/emails/export_serializer.rb +13 -0
- data/app/serializers/spree/emails/import_serializer.rb +19 -0
- data/app/serializers/spree/emails/invitation_serializer.rb +28 -0
- data/app/serializers/spree/emails/store_serializer.rb +31 -0
- data/app/serializers/spree/emails/user_serializer.rb +12 -0
- data/app/serializers/spree/emails/webhook_endpoint_serializer.rb +16 -0
- data/app/services/spree/carts/create.rb +8 -2
- data/app/services/spree/carts/destroy.rb +2 -2
- data/app/services/spree/carts/remove_out_of_stock_items.rb +2 -2
- data/app/services/spree/carts/split_by_seller.rb +1 -1
- data/app/services/spree/carts/update.rb +22 -8
- data/app/services/spree/catalogs/set_product_terms.rb +1 -1
- data/app/services/spree/categories/add_products.rb +13 -31
- data/app/services/spree/categories/remove_products.rb +12 -51
- data/app/services/spree/checkout/advance.rb +1 -4
- data/app/services/spree/collections/add_products.rb +10 -23
- data/app/services/spree/collections/remove_products.rb +7 -43
- data/app/services/spree/coupon_codes/bulk_generate.rb +8 -14
- data/app/services/spree/data_requests/create.rb +1 -1
- data/app/services/spree/deliveries/destroy.rb +2 -2
- data/app/services/spree/email_templates/check.rb +58 -0
- data/app/services/spree/email_templates/no_sample_record.rb +7 -0
- data/app/services/spree/email_templates/preview.rb +99 -0
- data/app/services/spree/emails/file_system.rb +23 -0
- data/app/services/spree/emails/filters.rb +84 -0
- data/app/services/spree/emails/liquid_context.rb +48 -0
- data/app/services/spree/emails/rendered_email.rb +14 -0
- data/app/services/spree/emails/renderer.rb +125 -0
- data/app/services/spree/emails/template.rb +51 -0
- data/app/services/spree/emails/template_data.rb +23 -0
- data/app/services/spree/emails/template_resolver.rb +109 -0
- data/app/services/spree/emails/text_converter.rb +72 -0
- data/app/services/spree/emails/translation_inliner.rb +134 -0
- data/app/services/spree/imports/row_processors/price_list_price.rb +10 -10
- data/app/services/spree/imports/row_processors/product_variant.rb +77 -34
- data/app/services/spree/imports/row_processors/purchase_order.rb +13 -13
- data/app/services/spree/locales.rb +43 -10
- data/app/services/spree/newsletter/subscribe.rb +8 -12
- data/app/services/spree/orders/build_fulfillments.rb +3 -2
- data/app/services/spree/orders/create.rb +5 -4
- data/app/services/spree/orders/discounts/create.rb +14 -11
- data/app/services/spree/orders/discounts/destroy.rb +1 -0
- data/app/services/spree/orders/discounts/update.rb +1 -0
- data/app/services/spree/orders/fees/create.rb +1 -0
- data/app/services/spree/orders/fees/destroy.rb +1 -0
- data/app/services/spree/orders/fees/update.rb +1 -0
- data/app/services/spree/orders/remove_line_item.rb +13 -3
- data/app/services/spree/orders/update.rb +10 -6
- data/app/services/spree/orders/update_item.rb +6 -3
- data/app/services/spree/orders/update_statuses.rb +3 -1
- data/app/services/spree/prices/bulk_upsert.rb +32 -25
- data/app/services/spree/product_memberships.rb +61 -0
- data/app/services/spree/returns/eligibility_validator.rb +1 -1
- data/app/services/spree/search_provider/custom_field_schema.rb +2 -2
- data/app/services/spree/seeds/commission_rates.rb +1 -1
- data/app/services/spree/seeds/digital_delivery.rb +1 -1
- data/app/services/spree/seeds/payment_methods.rb +2 -2
- data/app/services/spree/seeds/saved_reports.rb +3 -3
- data/app/services/spree/seeds/stores.rb +1 -1
- data/app/services/spree/sellers/requirements.rb +1 -2
- data/app/services/spree/shipping_labels/confirm_refund.rb +1 -1
- data/app/services/spree/shipping_labels/store_file.rb +3 -3
- data/app/services/spree/stock_levels/correct.rb +3 -3
- data/app/services/spree/stock_reservations/reserve.rb +2 -3
- data/app/services/spree/store_credits/apply.rb +15 -13
- data/app/services/spree/stores/provision_defaults.rb +23 -25
- data/app/services/spree/update_check.rb +160 -0
- data/app/services/spree/uploads/assign_stores.rb +92 -0
- data/app/subscribers/spree/seller_onboarding_subscriber.rb +1 -1
- data/app/validators/spree/attachment_content_type_validator.rb +19 -2
- data/app/validators/spree/locale_codes_validator.rb +22 -0
- data/app/views/layouts/spree/base_mailer.html.erb +2 -43
- data/app/views/layouts/spree/base_mailer.liquid +79 -0
- data/app/views/spree/admin_user_mailer/password_reset_email.liquid +14 -0
- data/app/views/spree/export_mailer/export_done.liquid +12 -0
- data/app/views/spree/import_mailer/import_done.liquid +15 -0
- data/app/views/spree/invitation_mailer/invitation_accepted.liquid +9 -0
- data/app/views/spree/invitation_mailer/invitation_email.liquid +13 -0
- data/app/views/spree/seller_user_mailer/password_reset_email.liquid +14 -0
- data/app/views/spree/shared/_mailer_button.html.erb +4 -5
- data/app/views/spree/shared/_mailer_hero.html.erb +6 -5
- data/app/views/spree/shared/_summary_row.liquid +8 -0
- data/app/views/spree/webhook_mailer/endpoint_disabled.liquid +21 -0
- data/app/workflows/spree/carts/add_item.rb +4 -4
- data/app/workflows/spree/carts/complete.rb +13 -3
- data/app/workflows/spree/carts/merge.rb +1 -1
- data/app/workflows/spree/carts/recalculate_totals.rb +5 -0
- data/app/workflows/spree/carts/upsert_items.rb +18 -9
- data/app/workflows/spree/claims/approve.rb +5 -22
- data/app/workflows/spree/claims/cancel.rb +5 -21
- data/app/workflows/spree/claims/create.rb +15 -0
- data/app/workflows/spree/claims/deny.rb +7 -0
- data/app/workflows/spree/claims/resolve.rb +34 -31
- data/app/workflows/spree/customers/anonymize.rb +3 -4
- data/app/workflows/spree/customers/create.rb +1 -1
- data/app/workflows/spree/data_requests/fulfill.rb +2 -2
- data/app/workflows/spree/deliveries/update_tracking.rb +1 -1
- data/app/workflows/spree/email_templates/discard_draft.rb +20 -0
- data/app/workflows/spree/email_templates/publish.rb +70 -0
- data/app/workflows/spree/email_templates/restore_revision.rb +33 -0
- data/app/workflows/spree/email_templates/revert.rb +29 -0
- data/app/workflows/spree/email_templates/save_draft.rb +85 -0
- data/app/workflows/spree/exchanges/approve.rb +5 -22
- data/app/workflows/spree/exchanges/cancel.rb +5 -21
- data/app/workflows/spree/exchanges/create.rb +6 -0
- data/app/workflows/spree/exchanges/fulfill.rb +75 -51
- data/app/workflows/spree/exchanges/receive.rb +6 -65
- data/app/workflows/spree/fulfillments/cancel.rb +1 -1
- data/app/workflows/spree/fulfillments/create.rb +53 -49
- data/app/workflows/spree/fulfillments/fulfill.rb +23 -27
- data/app/workflows/spree/fulfillments/mark_delivered.rb +1 -1
- data/app/workflows/spree/fulfillments/replacements.rb +59 -0
- data/app/workflows/spree/gift_cards/apply.rb +2 -2
- data/app/workflows/spree/orders/add_item.rb +5 -1
- data/app/workflows/spree/orders/cancel.rb +3 -3
- data/app/workflows/spree/orders/complete.rb +16 -8
- data/app/workflows/spree/payments/capture.rb +20 -5
- data/app/workflows/spree/payments/process.rb +5 -3
- data/app/workflows/spree/post_sale/approval.rb +35 -0
- data/app/workflows/spree/post_sale/cancellation.rb +44 -0
- data/app/workflows/spree/post_sale/receipt.rb +92 -0
- data/app/workflows/spree/price_lists/update.rb +4 -4
- data/app/workflows/spree/products/nested_attributes.rb +1 -1
- data/app/workflows/spree/purchase_orders/cancel.rb +1 -1
- data/app/workflows/spree/purchase_orders/close.rb +3 -31
- data/app/workflows/spree/purchase_orders/create.rb +3 -1
- data/app/workflows/spree/purchase_orders/mark_draft.rb +2 -2
- data/app/workflows/spree/purchase_orders/mark_ordered.rb +2 -2
- data/app/workflows/spree/purchase_orders/receive.rb +1 -1
- data/app/workflows/spree/purchase_orders/update.rb +5 -34
- data/app/workflows/spree/receivables/closing_short.rb +46 -0
- data/app/workflows/spree/receivables/draft_editing.rb +48 -0
- data/app/workflows/spree/refunds/create.rb +5 -2
- data/app/workflows/spree/refunds/order_payments.rb +14 -2
- data/app/workflows/spree/refunds/tax_credit.rb +67 -0
- data/app/workflows/spree/returns/approve.rb +5 -22
- data/app/workflows/spree/returns/cancel.rb +5 -22
- data/app/workflows/spree/returns/create.rb +8 -0
- data/app/workflows/spree/returns/receive.rb +5 -76
- data/app/workflows/spree/returns/refund.rb +31 -8
- data/app/workflows/spree/seller_requirement_submissions/create.rb +1 -2
- data/app/workflows/spree/seller_transfers/reverse.rb +28 -1
- data/app/workflows/spree/shipping_labels/purchase.rb +8 -8
- data/app/workflows/spree/shipping_labels/record.rb +7 -7
- data/app/workflows/spree/shipping_labels/refund.rb +2 -2
- data/app/workflows/spree/stock_receipts/recording.rb +4 -4
- data/app/workflows/spree/stock_transfers/cancel.rb +2 -2
- data/app/workflows/spree/stock_transfers/close.rb +3 -30
- data/app/workflows/spree/stock_transfers/create.rb +3 -1
- data/app/workflows/spree/stock_transfers/mark_draft.rb +1 -1
- data/app/workflows/spree/stock_transfers/mark_in_transit.rb +3 -3
- data/app/workflows/spree/stock_transfers/mark_ready.rb +2 -2
- data/app/workflows/spree/stock_transfers/receive.rb +1 -1
- data/app/workflows/spree/stock_transfers/update.rb +5 -33
- data/config/currency_exponents.json +180 -0
- data/config/i18n-tasks.yml +32 -18
- data/config/initializers/alba.rb +7 -0
- data/config/language_names.csv +264 -0
- data/config/locales/ar.yml +811 -0
- data/config/locales/be.yml +855 -0
- data/config/locales/bg.yml +723 -0
- data/config/locales/ca.yml +689 -0
- data/config/locales/content_locales.rb +9 -0
- data/config/locales/cs.yml +855 -0
- data/config/locales/da.yml +771 -0
- data/config/locales/de-CH.yml +248 -0
- data/config/locales/de.yml +835 -0
- data/config/locales/el.yml +769 -0
- data/config/locales/en.yml +47 -505
- data/config/locales/es-CL.yml +301 -0
- data/config/locales/es-EC.yml +186 -0
- data/config/locales/es-MX.yml +334 -0
- data/config/locales/es.yml +831 -0
- data/config/locales/et.yml +644 -0
- data/config/locales/fa.yml +709 -0
- data/config/locales/fi.yml +771 -0
- data/config/locales/fr.yml +830 -0
- data/config/locales/hu.yml +751 -0
- data/config/locales/id.yml +756 -0
- data/config/locales/it.yml +833 -0
- data/config/locales/ja.yml +726 -0
- data/config/locales/km.yml +759 -0
- data/config/locales/ko.yml +575 -0
- data/config/locales/ku.yml +753 -0
- data/config/locales/lt.yml +821 -0
- data/config/locales/lv.yml +685 -0
- data/config/locales/nb.yml +824 -0
- data/config/locales/ne.yml +823 -0
- data/config/locales/nl.yml +826 -0
- data/config/locales/pl.yml +835 -0
- data/config/locales/pt-BR.yml +547 -0
- data/config/locales/pt.yml +636 -0
- data/config/locales/ro.yml +750 -0
- data/config/locales/ru.yml +834 -0
- data/config/locales/sk.yml +680 -0
- data/config/locales/sl-SI.yml +600 -0
- data/config/locales/sv.yml +827 -0
- data/config/locales/th.yml +745 -0
- data/config/locales/tr.yml +777 -0
- data/config/locales/uk.yml +836 -0
- data/config/locales/vi.yml +768 -0
- data/config/locales/zh-CN.yml +786 -0
- data/config/locales/zh-TW.yml +834 -0
- data/db/migrate/20260925000001_add_replacement_to_spree_fulfillment_items.rb +5 -0
- data/db/migrate/20261001000001_add_post_sale_tax.rb +30 -0
- data/db/migrate/20261001000001_convert_preferences_to_json.rb +166 -0
- data/db/migrate/20261001000001_create_spree_email_templates.rb +52 -0
- data/db/migrate/20261009000001_change_money_columns_to_decimal_19_4.rb +116 -0
- data/db/migrate/20261010000001_add_store_id_to_active_storage_blobs.rb +12 -0
- data/lib/generators/spree/dummy/dummy_generator.rb +0 -9
- data/lib/generators/spree/dummy/templates/rails/test.rb +26 -0
- data/lib/spree/checkout/default_requirements.rb +37 -13
- data/lib/spree/core/configuration.rb +19 -1
- data/lib/spree/core/controller_helpers/store.rb +1 -1
- data/lib/spree/core/dependencies.rb +5 -4
- data/lib/spree/core/emails/escaped_output.rb +60 -0
- data/lib/spree/core/engine.rb +72 -165
- data/lib/spree/core/permissions/default_catalog.rb +5 -0
- data/lib/spree/core/preferences/invalid_preferences.rb +18 -0
- data/lib/spree/core/preferences/json_conversion.rb +186 -0
- data/lib/spree/core/preferences/masking.rb +55 -2
- data/lib/spree/core/preferences/preferable.rb +343 -103
- data/lib/spree/core/preferences/preferable_class_methods.rb +188 -136
- data/lib/spree/core/previews/import_preview.rb +10 -0
- data/lib/spree/core/ransack_configuration.rb +13 -2
- data/lib/spree/core/tax/provider_error.rb +37 -0
- data/lib/spree/core/version.rb +1 -1
- data/lib/spree/core.rb +91 -373
- data/lib/spree/emails/editable_templates.rb +82 -0
- data/lib/spree/events/catalog.rb +189 -0
- data/lib/spree/events.rb +21 -1
- data/lib/spree/hooks.rb +0 -17
- data/lib/spree/i18n.rb +32 -15
- data/lib/spree/iso_data.rb +7 -0
- data/lib/spree/localized_number.rb +6 -0
- data/lib/spree/migrations.rb +4 -1
- data/lib/spree/money.rb +196 -15
- data/lib/spree/reporting/adapters/base.rb +7 -6
- data/lib/spree/reporting/adapters/live.rb +6 -6
- data/lib/spree/reporting/default_vocabulary.rb +22 -10
- data/lib/spree/reporting/hydration.rb +1 -1
- data/lib/spree/reporting/query.rb +8 -0
- data/lib/spree/reporting/registry.rb +13 -0
- data/lib/spree/reporting/schema.rb +10 -10
- data/lib/spree/testing_support/editable_email_templates.rb +61 -0
- data/lib/spree/testing_support/emails.rb +22 -0
- data/lib/spree/testing_support/factories/cart_factory.rb +1 -1
- data/lib/spree/testing_support/factories/email_template_factory.rb +28 -0
- data/lib/spree/testing_support/factories/order_factory.rb +2 -2
- data/lib/spree/testing_support/factories/product_factory.rb +9 -0
- data/lib/spree/testing_support/factories/refund_factory.rb +1 -1
- data/lib/spree/testing_support/factories/stock_factory.rb +1 -1
- data/lib/spree/testing_support/factories/variant_factory.rb +1 -1
- data/lib/spree/testing_support/i18n.rb +3 -94
- data/lib/spree/testing_support/lifecycle_events.rb +1 -1
- data/lib/spree/testing_support/order_walkthrough.rb +1 -1
- data/lib/spree/testing_support/preferences.rb +0 -5
- data/lib/spree/testing_support/store.rb +9 -1
- data/lib/spree/upgrades/5_6_to_6_0/manifest.yml +67 -1
- data/lib/spree/uploads.rb +101 -0
- data/lib/tasks/categories_and_collections.rake +3 -3
- data/lib/tasks/core.rake +6 -4
- data/lib/tasks/markets.rake +2 -1
- data/lib/tasks/media.rake +3 -3
- data/lib/tasks/migrate_users_to_customers.rake +23 -18
- data/lib/tasks/money.rake +46 -0
- data/lib/tasks/package_types.rake +1 -1
- data/lib/tasks/preferences_json.rake +101 -0
- data/lib/tasks/seller_sample_data.rake +2 -2
- data/lib/tasks/store_settings.rake +13 -6
- data/lib/tasks/typed_adjustments_migration.rake +1 -1
- data/lib/tasks/uploads.rake +19 -0
- data/spec/fixtures/mailer_views/custom_views_probe_mailer/welcome_email.html.erb +4 -0
- metadata +219 -46
- data/app/jobs/spree/products/touch_categories_job.rb +0 -15
- data/app/jobs/spree/products/touch_taxons_job.rb +0 -13
- data/app/models/acts_as_taggable_on/tag_decorator.rb +0 -10
- data/app/models/concerns/spree/has_image_alt_text.rb +0 -18
- data/app/models/spree/base_analytics_event_handler.rb +0 -54
- data/app/models/spree/invitations/store.rb +0 -6
- data/app/models/spree/preference.rb +0 -14
- data/app/models/spree/promotion_handler/page.rb +0 -25
- data/app/models/spree/promotion_handler/promotion_duplicator.rb +0 -60
- data/app/models/spree/stock/differentiator.rb +0 -45
- data/app/presenters/spree/filters/options_presenter.rb +0 -47
- data/app/services/spree/carts/empty.rb +0 -42
- data/app/services/spree/country_to_timezone.rb +0 -273
- data/app/services/spree/payments/create.rb +0 -92
- data/app/validators/spree/url_validator.rb +0 -23
- data/app/views/active_storage/blobs/_blob.html.erb +0 -14
- data/app/views/spree/admin_user_mailer/password_reset_email.html.erb +0 -8
- data/app/views/spree/export_mailer/export_done.html.erb +0 -7
- data/app/views/spree/import_mailer/import_done.html.erb +0 -12
- data/app/views/spree/invitation_mailer/invitation_accepted.html.erb +0 -4
- data/app/views/spree/invitation_mailer/invitation_email.html.erb +0 -12
- data/app/views/spree/seller_user_mailer/password_reset_email.html.erb +0 -8
- data/app/views/spree/shared/_base_mailer_footer.html.erb +0 -19
- data/app/views/spree/shared/_base_mailer_header.html.erb +0 -8
- data/app/views/spree/shared/_base_mailer_stylesheets.html.erb +0 -399
- data/app/views/spree/shared/_mailer_logo.html.erb +0 -41
- data/app/views/spree/shared/_purchased_items_styles.html.erb +0 -119
- data/app/views/spree/webhook_mailer/endpoint_disabled.html.erb +0 -51
- data/app/views/spree/webhook_mailer/endpoint_disabled.text.erb +0 -10
- data/app/workflows/spree/fulfillments/purchase_label.rb +0 -40
- data/app/workflows/spree/returns/purchase_label.rb +0 -32
- data/lib/spree/analytics.rb +0 -11
- data/lib/spree/core/preferences/scoped_store.rb +0 -34
- data/lib/spree/core/preferences/store.rb +0 -105
- data/lib/spree/translation_migrations.rb +0 -52
|
@@ -142,9 +142,9 @@ module Spree
|
|
|
142
142
|
def sort_option_label(name, field_type, direction)
|
|
143
143
|
suffix =
|
|
144
144
|
if field_type == 'number'
|
|
145
|
-
direction == :asc ?
|
|
145
|
+
direction == :asc ? I18n.t('spree.sort_low_to_high') : I18n.t('spree.sort_high_to_low')
|
|
146
146
|
else
|
|
147
|
-
direction == :asc ?
|
|
147
|
+
direction == :asc ? I18n.t('spree.sort_a_to_z') : I18n.t('spree.sort_z_to_a')
|
|
148
148
|
end
|
|
149
149
|
"#{name} (#{suffix})"
|
|
150
150
|
end
|
|
@@ -35,7 +35,7 @@ module Spree
|
|
|
35
35
|
return if store.commission_rates.exists?(code: DEFAULT_CODE)
|
|
36
36
|
|
|
37
37
|
rate = store.commission_rates.create!(
|
|
38
|
-
name:
|
|
38
|
+
name: I18n.t('spree.seed.commission_rates.marketplace_default'),
|
|
39
39
|
code: DEFAULT_CODE,
|
|
40
40
|
kind: 'percentage',
|
|
41
41
|
value: 0,
|
|
@@ -12,7 +12,7 @@ module Spree
|
|
|
12
12
|
profile = Spree::DeliveryProfiles::Digital.find_by(store: store) ||
|
|
13
13
|
Spree::DeliveryProfiles::Digital.create!(store: store, name: I18n.t('spree.seed.delivery_profiles.digital'))
|
|
14
14
|
|
|
15
|
-
digital_delivery_method = Spree::DeliveryMethod.find_or_initialize_by(name:
|
|
15
|
+
digital_delivery_method = Spree::DeliveryMethod.find_or_initialize_by(name: I18n.t('spree.digital.digital_delivery'), store: store)
|
|
16
16
|
|
|
17
17
|
digital_delivery_method.delivery_profile = profile
|
|
18
18
|
digital_delivery_method.storefront_visible = true
|
|
@@ -12,8 +12,8 @@ module Spree
|
|
|
12
12
|
)
|
|
13
13
|
return if payment_method.persisted?
|
|
14
14
|
|
|
15
|
-
payment_method.name =
|
|
16
|
-
payment_method.description =
|
|
15
|
+
payment_method.name = I18n.t('spree.store_credit_name')
|
|
16
|
+
payment_method.description = I18n.t('spree.store_credit_name')
|
|
17
17
|
payment_method.active = true
|
|
18
18
|
payment_method.save!
|
|
19
19
|
end
|
|
@@ -86,8 +86,8 @@ module Spree
|
|
|
86
86
|
next if known_names(report[:key]).intersect?(existing)
|
|
87
87
|
|
|
88
88
|
store.saved_reports.create!(
|
|
89
|
-
name:
|
|
90
|
-
description:
|
|
89
|
+
name: I18n.t("spree.reporting.seeds.#{report[:key]}.name"),
|
|
90
|
+
description: I18n.t("spree.reporting.seeds.#{report[:key]}.description"),
|
|
91
91
|
query: report[:query],
|
|
92
92
|
seeded: true
|
|
93
93
|
)
|
|
@@ -98,7 +98,7 @@ module Spree
|
|
|
98
98
|
def known_names(key)
|
|
99
99
|
@known_names ||= {}
|
|
100
100
|
@known_names[key] ||= Spree.available_locales.filter_map do |locale|
|
|
101
|
-
|
|
101
|
+
I18n.t("spree.reporting.seeds.#{key}.name", locale: locale, default: nil)&.downcase
|
|
102
102
|
end.to_set
|
|
103
103
|
end
|
|
104
104
|
end
|
|
@@ -61,8 +61,7 @@ module Spree
|
|
|
61
61
|
def record_blocking(errors)
|
|
62
62
|
blocking.each do |requirement|
|
|
63
63
|
errors.add(:requirements, :incomplete,
|
|
64
|
-
message:
|
|
65
|
-
default: "#{requirement.name} is not complete"))
|
|
64
|
+
message: I18n.t('spree.seller_requirements.incomplete', name: requirement.name))
|
|
66
65
|
end
|
|
67
66
|
end
|
|
68
67
|
|
|
@@ -14,7 +14,7 @@ module Spree
|
|
|
14
14
|
return success(shipping_label) if shipping_label.refunded?
|
|
15
15
|
|
|
16
16
|
unless shipping_label.refund_requested?
|
|
17
|
-
shipping_label.errors.add(:base, :not_refund_requested, message:
|
|
17
|
+
shipping_label.errors.add(:base, :not_refund_requested, message: I18n.t('spree.shipping_labels.errors.not_refund_requested'))
|
|
18
18
|
return failure(shipping_label)
|
|
19
19
|
end
|
|
20
20
|
|
|
@@ -27,7 +27,7 @@ module Spree
|
|
|
27
27
|
|
|
28
28
|
url = shipping_label.file_url
|
|
29
29
|
if url.blank?
|
|
30
|
-
shipping_label.errors.add(:file, :no_file_url, message:
|
|
30
|
+
shipping_label.errors.add(:file, :no_file_url, message: I18n.t('spree.shipping_labels.errors.no_file_url'))
|
|
31
31
|
return failure(shipping_label)
|
|
32
32
|
end
|
|
33
33
|
|
|
@@ -38,13 +38,13 @@ module Spree
|
|
|
38
38
|
# ordinary outcome here, not an exception the caller should handle:
|
|
39
39
|
# the purchase behind it is already a fact, and the job retries.
|
|
40
40
|
shipping_label.errors.add(:file, :file_fetch_failed, status: e.message,
|
|
41
|
-
message:
|
|
41
|
+
message: I18n.t('spree.shipping_labels.errors.file_fetch_failed', status: e.message))
|
|
42
42
|
return failure(shipping_label)
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
unless response.is_a?(Net::HTTPSuccess)
|
|
46
46
|
shipping_label.errors.add(:file, :file_fetch_failed, status: response.code,
|
|
47
|
-
message:
|
|
47
|
+
message: I18n.t('spree.shipping_labels.errors.file_fetch_failed', status: response.code))
|
|
48
48
|
return failure(shipping_label)
|
|
49
49
|
end
|
|
50
50
|
|
|
@@ -24,13 +24,13 @@ module Spree
|
|
|
24
24
|
# @return [Spree::ServiceModule::Result] the corrected stock level
|
|
25
25
|
def call(stock_level:, count_on_hand: nil, adjustment: nil, reason: nil)
|
|
26
26
|
if count_on_hand.present? && adjustment.present?
|
|
27
|
-
return failure(stock_level,
|
|
27
|
+
return failure(stock_level, I18n.t('spree.stock_level.errors.adjustment_exclusive_with_count_on_hand'))
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
target = parse(count_on_hand)
|
|
31
31
|
delta = parse(adjustment)
|
|
32
|
-
return failure(stock_level,
|
|
33
|
-
return failure(stock_level,
|
|
32
|
+
return failure(stock_level, I18n.t('spree.stock_level.errors.count_on_hand_not_an_integer')) if count_on_hand.present? && target.nil?
|
|
33
|
+
return failure(stock_level, I18n.t('spree.stock_level.errors.adjustment_not_an_integer')) if adjustment.present? && delta.nil?
|
|
34
34
|
|
|
35
35
|
# Locked around the read: the delta is worked out from the count this
|
|
36
36
|
# caller first saw, so two admins correcting the same level at once
|
|
@@ -36,9 +36,8 @@ module Spree
|
|
|
36
36
|
if available < line_item.quantity
|
|
37
37
|
raise InsufficientStockError.new(
|
|
38
38
|
line_item,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
default: '%{item} has only %{available} available',
|
|
39
|
+
I18n.t(
|
|
40
|
+
'spree.insufficient_stock_for_reservation',
|
|
42
41
|
item: line_item.variant.name,
|
|
43
42
|
available: [available, 0].max
|
|
44
43
|
)
|
|
@@ -13,7 +13,10 @@ module Spree
|
|
|
13
13
|
# credit payment for a negative amount.
|
|
14
14
|
remaining_total = [amount ? [amount, @order.outstanding_balance].min : @order.outstanding_balance, 0].max
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
# Mirrors Spree::GiftCards::Apply, which refuses a gift card once store
|
|
17
|
+
# credit is in use: the two are never combined on one order.
|
|
18
|
+
return failure(nil, I18n.t('spree.store_credit_using_gift_card_error')) if @order.gift_card.present?
|
|
19
|
+
return failure(nil, I18n.t('spree.error_user_does_not_have_any_store_credits')) unless spendable_store_credits.exists?
|
|
17
20
|
|
|
18
21
|
ApplicationRecord.transaction do
|
|
19
22
|
existing = @order.payments.store_credits.where(status: :checkout)
|
|
@@ -40,12 +43,10 @@ module Spree
|
|
|
40
43
|
# creating unnecessary invalid payment records on every recalculation.
|
|
41
44
|
def update_existing_payments(payments, remaining_total)
|
|
42
45
|
payments.each do |payment|
|
|
43
|
-
|
|
44
|
-
available = credit.amount_remaining + payment.amount
|
|
45
|
-
new_amount = [available, remaining_total].min
|
|
46
|
+
new_amount = store_credit_amount(payment.source, remaining_total)
|
|
46
47
|
|
|
47
48
|
if new_amount.positive?
|
|
48
|
-
payment.
|
|
49
|
+
payment.update_store_credit_amount!(new_amount)
|
|
49
50
|
remaining_total -= new_amount
|
|
50
51
|
else
|
|
51
52
|
payment.invalidate!
|
|
@@ -53,19 +54,14 @@ module Spree
|
|
|
53
54
|
end
|
|
54
55
|
|
|
55
56
|
# If there's still remaining total, apply from additional store credits
|
|
56
|
-
apply_store_credits(remaining_total) if remaining_total.positive?
|
|
57
|
+
apply_store_credits(remaining_total, except: payments.map(&:source_id)) if remaining_total.positive?
|
|
57
58
|
end
|
|
58
59
|
|
|
59
|
-
def apply_store_credits(remaining_total)
|
|
60
|
+
def apply_store_credits(remaining_total, except: [])
|
|
60
61
|
payment_method = Spree::PaymentMethod::StoreCredit.available.first
|
|
61
62
|
raise 'Store credit payment method could not be found' unless payment_method
|
|
62
63
|
|
|
63
|
-
|
|
64
|
-
# currency would be drawn against first and, being unusable, either
|
|
65
|
-
# overshoot the allowed amount or persist a payment that fails later
|
|
66
|
-
# at authorization. This matches what the order already counts as
|
|
67
|
-
# available (Purchase::StoreCredits#total_available_store_credit).
|
|
68
|
-
@order.customer.store_credits.for_store(@order.store).where(currency: @order.currency).oldest_first.each do |credit|
|
|
64
|
+
spendable_store_credits.where.not(id: except).oldest_first.each do |credit|
|
|
69
65
|
break unless remaining_total.positive?
|
|
70
66
|
next if credit.amount_remaining.zero?
|
|
71
67
|
|
|
@@ -75,6 +71,12 @@ module Spree
|
|
|
75
71
|
end
|
|
76
72
|
end
|
|
77
73
|
|
|
74
|
+
def spendable_store_credits
|
|
75
|
+
return Spree::StoreCredit.none unless @order.customer
|
|
76
|
+
|
|
77
|
+
@order.customer.store_credits.without_gift_card.for_store(@order.store).where(currency: @order.currency)
|
|
78
|
+
end
|
|
79
|
+
|
|
78
80
|
def create_store_credit_payment(payment_method, credit, amount)
|
|
79
81
|
@order.payments.create!(
|
|
80
82
|
source: credit,
|
|
@@ -18,8 +18,9 @@ module Spree
|
|
|
18
18
|
|
|
19
19
|
# @param store [Spree::Store] the store to provision, already persisted
|
|
20
20
|
# @param country [Spree::Country] where the shop ships from
|
|
21
|
-
# @param locale [String, nil] storefront locale
|
|
22
|
-
#
|
|
21
|
+
# @param locale [String, nil] storefront locale, a code from
|
|
22
|
+
# `Spree::Locales::ALL` (another spelling or region moves to the listed
|
|
23
|
+
# one); defaults to the country's own language, then English
|
|
23
24
|
# @param currency [String, nil] ISO 4217 code; defaults to the country's
|
|
24
25
|
# own currency. Merchants who ship from one country and price in
|
|
25
26
|
# another currency pass it explicitly.
|
|
@@ -55,22 +56,17 @@ module Spree
|
|
|
55
56
|
found&.iso_code || country.default_currency.presence || 'USD'
|
|
56
57
|
end
|
|
57
58
|
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
#
|
|
59
|
+
# A store's locale is a content locale, so it comes from
|
|
60
|
+
# `Spree::Locales::ALL` whether or not Spree ships translations for it:
|
|
61
|
+
# the storefront and dashboard carry their own. An explicit locale is
|
|
62
|
+
# kept when it is listed, or moved to its listed language (`en-IE`
|
|
63
|
+
# becomes `en`); one Spree cannot place, or none, gives the country's
|
|
64
|
+
# first official language Spree lists.
|
|
64
65
|
def resolve_locale(requested, country)
|
|
65
|
-
|
|
66
|
+
listed = Spree::Locales.listed(requested)
|
|
67
|
+
return listed if listed
|
|
66
68
|
|
|
67
|
-
|
|
68
|
-
return 'en' if derived.blank?
|
|
69
|
-
|
|
70
|
-
translated = Spree.available_locales.map { |locale| locale.to_s.split('-').first }.uniq
|
|
71
|
-
return derived if translated.size <= 1 || translated.include?(derived)
|
|
72
|
-
|
|
73
|
-
(country.official_locales & translated).first || 'en'
|
|
69
|
+
[country.default_locale, *country.official_locales].lazy.filter_map { |language| Spree::Locales.for_language(language) }.first || 'en'
|
|
74
70
|
end
|
|
75
71
|
|
|
76
72
|
# The store's own country/currency/locale columns are not the source of
|
|
@@ -118,12 +114,12 @@ module Spree
|
|
|
118
114
|
end
|
|
119
115
|
|
|
120
116
|
# Match on identity alone: folding the other attributes into the finder
|
|
121
|
-
# made re-running fail once anything edited them.
|
|
117
|
+
# made re-running fail once anything edited them. The default flag is
|
|
118
|
+
# that identity; the name is translated and may have been edited.
|
|
122
119
|
def provision_stock_location
|
|
123
|
-
# first_party, so a seller
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
where(name: Spree.t(:default_stock_location_name)).first_or_initialize
|
|
120
|
+
# first_party, so a seller's location is never adopted as the store's own.
|
|
121
|
+
location = store.stock_locations.first_party.where(default: true).
|
|
122
|
+
first_or_initialize(name: I18n.t('spree.default_stock_location_name'))
|
|
127
123
|
location.propagate_all_variants = false if location.new_record?
|
|
128
124
|
location.country_code = country.iso
|
|
129
125
|
if location.persisted? && location.will_save_change_to_country_code?
|
|
@@ -206,7 +202,7 @@ module Spree
|
|
|
206
202
|
weight_unit = store.preferred_weight_unit
|
|
207
203
|
|
|
208
204
|
store.package_types.create!(
|
|
209
|
-
name:
|
|
205
|
+
name: I18n.t('spree.package_types.default_name'),
|
|
210
206
|
kind: 'box',
|
|
211
207
|
default: true,
|
|
212
208
|
length: metric ? 30 : 12,
|
|
@@ -237,10 +233,12 @@ module Spree
|
|
|
237
233
|
end
|
|
238
234
|
return if store.stock_locations.first_party.where(pickup_enabled: true).none?
|
|
239
235
|
|
|
240
|
-
|
|
236
|
+
# Matched by its provider, not its translated (and editable) name.
|
|
237
|
+
delivery_method = store.delivery_methods.first_party.
|
|
238
|
+
where(fulfillment_provider: 'Spree::FulfillmentProvider::Pickup').
|
|
239
|
+
first_or_initialize(name: I18n.t('spree.pickup.store_pickup'))
|
|
241
240
|
delivery_method.delivery_profile = profile
|
|
242
241
|
delivery_method.storefront_visible = true
|
|
243
|
-
delivery_method.fulfillment_provider = 'Spree::FulfillmentProvider::Pickup'
|
|
244
242
|
delivery_method.calculator ||= Spree::Calculator::Shipping::FlatRate.new
|
|
245
243
|
delivery_method.calculator.preferences = { amount: 0, currency: currency }
|
|
246
244
|
delivery_method.save!
|
|
@@ -259,7 +257,7 @@ module Spree
|
|
|
259
257
|
calculator = delivery_method.calculator
|
|
260
258
|
next unless calculator.respond_to?(:preferred_currency)
|
|
261
259
|
next if calculator.preferred_currency == currency
|
|
262
|
-
next unless calculator.preferences[:amount]
|
|
260
|
+
next unless Spree::Money::Rounding.blank_or_zero?(calculator.preferences[:amount])
|
|
263
261
|
|
|
264
262
|
calculator.update!(preferences: calculator.preferences.merge(currency: currency))
|
|
265
263
|
end
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
require 'net/http'
|
|
2
|
+
|
|
3
|
+
module Spree
|
|
4
|
+
# Asks spreecloud.io which Spree releases are newer than the running one,
|
|
5
|
+
# and reports usage with the same request. The answer is cached for a day,
|
|
6
|
+
# so at most one admin request a day waits on it, for no more than the two
|
|
7
|
+
# one-second timeouts. A failed check is cached for an hour, so a passing
|
|
8
|
+
# outage costs one slow request an hour and never hides a release for long.
|
|
9
|
+
#
|
|
10
|
+
# The +version+, +environment+, +url+ and +install_id+ query keys are what
|
|
11
|
+
# Spree 5 sent, and the endpoint still serves 5.x installs with them. They
|
|
12
|
+
# are never renamed; new keys only ever go beside them.
|
|
13
|
+
class UpdateCheck
|
|
14
|
+
ENDPOINT = 'https://spreecloud.io/updates.json'.freeze
|
|
15
|
+
RELEASES_URL = 'https://github.com/spree/spree/releases/tag/'.freeze
|
|
16
|
+
RELEASE_TAG = /\Av\d+(\.[0-9a-z]+)*\z/
|
|
17
|
+
CACHE_TTL = 1.day
|
|
18
|
+
FAILURE_TTL = 1.hour
|
|
19
|
+
OPEN_TIMEOUT = 1
|
|
20
|
+
READ_TIMEOUT = 1
|
|
21
|
+
# The read timeout applies to each read, not the whole response, so a
|
|
22
|
+
# slowly trickling answer needs an overall deadline as well.
|
|
23
|
+
TOTAL_TIMEOUT = 2
|
|
24
|
+
# Order-of-magnitude buckets: enough to tell a hobby install from a large
|
|
25
|
+
# merchant, never an exact figure.
|
|
26
|
+
BUCKETS = {
|
|
27
|
+
0..0 => '0',
|
|
28
|
+
1..9 => '1-9',
|
|
29
|
+
10..99 => '10-99',
|
|
30
|
+
100..999 => '100-999',
|
|
31
|
+
1_000..9_999 => '1k-9k',
|
|
32
|
+
10_000..99_999 => '10k-99k'
|
|
33
|
+
}.freeze
|
|
34
|
+
LARGEST_BUCKET = '100k+'.freeze
|
|
35
|
+
|
|
36
|
+
class << self
|
|
37
|
+
# @return [Boolean] false when the operator turned the check off
|
|
38
|
+
def enabled?
|
|
39
|
+
Spree::Config.update_check_enabled && !env_flag?('SPREE_UPDATE_CHECK_DISABLED')
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# When false, the check still runs but sends the running version only.
|
|
43
|
+
#
|
|
44
|
+
# @return [Boolean]
|
|
45
|
+
def telemetry_enabled?
|
|
46
|
+
Spree::Config.telemetry_enabled &&
|
|
47
|
+
!env_flag?('SPREE_TELEMETRY_DISABLED') &&
|
|
48
|
+
!env_flag?('DO_NOT_TRACK')
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# The cached answer, fetched from the endpoint when there is none.
|
|
52
|
+
#
|
|
53
|
+
# @return [Spree::UpdateStatus]
|
|
54
|
+
def status
|
|
55
|
+
return Spree::UpdateStatus.new(current_version: Spree.version, enabled: false) unless enabled?
|
|
56
|
+
|
|
57
|
+
result = Rails.cache.read(cache_key) || refresh
|
|
58
|
+
|
|
59
|
+
Spree::UpdateStatus.new(current_version: Spree.version, checked_at: result[:checked_at], releases: result[:releases])
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# @return [Hash] the query string the endpoint receives
|
|
63
|
+
def query
|
|
64
|
+
return { version: Spree.version } unless telemetry_enabled?
|
|
65
|
+
|
|
66
|
+
{
|
|
67
|
+
version: Spree.version,
|
|
68
|
+
environment: Rails.env,
|
|
69
|
+
url: Spree::Store.default&.storefront_url,
|
|
70
|
+
install_id: Spree.install_id,
|
|
71
|
+
ruby: RUBY_VERSION,
|
|
72
|
+
rails: Rails.version,
|
|
73
|
+
database: ActiveRecord::Base.connection_db_config.adapter,
|
|
74
|
+
gems: Gem.loaded_specs.keys.grep(/\Aspree_/).sort.join(','),
|
|
75
|
+
dashboard: dashboard_served?,
|
|
76
|
+
stores: bucket(Spree::Store.count),
|
|
77
|
+
products: bucket(Spree::Product.count),
|
|
78
|
+
orders: bucket(Spree::Order.complete.count),
|
|
79
|
+
sellers: seller_bucket
|
|
80
|
+
}.compact
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def refresh
|
|
86
|
+
releases = fetch_releases
|
|
87
|
+
result = { releases: releases || [], checked_at: Time.current }
|
|
88
|
+
Rails.cache.write(cache_key, result, expires_in: releases ? CACHE_TTL : FAILURE_TTL)
|
|
89
|
+
result
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Nil when the check failed, for whatever reason: an update notice is
|
|
93
|
+
# never worth failing the admin's request over, including a database
|
|
94
|
+
# error while counting for the usage fields.
|
|
95
|
+
def fetch_releases
|
|
96
|
+
uri = URI(ENDPOINT)
|
|
97
|
+
uri.query = URI.encode_www_form(query)
|
|
98
|
+
|
|
99
|
+
response = Timeout.timeout(TOTAL_TIMEOUT) do
|
|
100
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: true, max_retries: 0,
|
|
101
|
+
open_timeout: OPEN_TIMEOUT, read_timeout: READ_TIMEOUT) do |http|
|
|
102
|
+
http.get(uri.request_uri)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
return unless response.is_a?(Net::HTTPSuccess)
|
|
106
|
+
|
|
107
|
+
newer_releases(JSON.parse(response.body))
|
|
108
|
+
rescue StandardError => e
|
|
109
|
+
Rails.error.report(e, handled: true, severity: :warning, source: 'spree.update_check')
|
|
110
|
+
nil
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# The endpoint already filters by version; this does it again and
|
|
114
|
+
# builds each link from the tag, so whatever comes back over the wire
|
|
115
|
+
# never reaches the dashboard as a URL. Release candidates are only
|
|
116
|
+
# offered to installs already running one.
|
|
117
|
+
def newer_releases(payload)
|
|
118
|
+
current = Gem::Version.new(Spree.version)
|
|
119
|
+
|
|
120
|
+
Array(payload).filter_map { |release|
|
|
121
|
+
tag = release.is_a?(Hash) ? release['tag_name'].to_s : ''
|
|
122
|
+
next unless tag.match?(RELEASE_TAG)
|
|
123
|
+
|
|
124
|
+
version = Gem::Version.new(tag.delete_prefix('v'))
|
|
125
|
+
next unless version > current
|
|
126
|
+
next if version.prerelease? && !current.prerelease?
|
|
127
|
+
|
|
128
|
+
{ version: version.to_s, url: "#{RELEASES_URL}#{tag}", published_at: parse_time(release['published_at']) }
|
|
129
|
+
}.sort_by { |release| Gem::Version.new(release[:version]) }.reverse
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def parse_time(value)
|
|
133
|
+
Time.iso8601(value.to_s)
|
|
134
|
+
rescue ArgumentError
|
|
135
|
+
nil
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def bucket(count)
|
|
139
|
+
BUCKETS.find { |range, _| range.cover?(count) }&.last || LARGEST_BUCKET
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def seller_bucket
|
|
143
|
+
count = Spree::Seller.count
|
|
144
|
+
bucket(count) if count.positive?
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def dashboard_served?
|
|
148
|
+
(defined?(Spree::Dashboard) && Spree::Dashboard.dist_path.present?) || Spree::Config.dashboard_url.present?
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def env_flag?(name)
|
|
152
|
+
ActiveModel::Type::Boolean.new.cast(ENV[name].presence) == true
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def cache_key
|
|
156
|
+
"spree/update_check/#{Spree.version}"
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Uploads
|
|
3
|
+
# Gives every attached file that has no store the store of the record it
|
|
4
|
+
# is attached to (the 6.0 upgrade, `spree:uploads:assign_stores`).
|
|
5
|
+
#
|
|
6
|
+
# Owners with a `store_id` column are handled with one set-based update per
|
|
7
|
+
# attachment type; the rest (a variant's digital file, a seller's
|
|
8
|
+
# submission) ask {Spree::Uploads.store_id_for} record by record. Files
|
|
9
|
+
# attached nowhere keep no store, and the daily purge leaves them alone.
|
|
10
|
+
#
|
|
11
|
+
# Safe to re-run: only files without a store are touched.
|
|
12
|
+
class AssignStores
|
|
13
|
+
prepend Spree::ServiceModule::Base
|
|
14
|
+
|
|
15
|
+
BATCH_SIZE = 1_000
|
|
16
|
+
|
|
17
|
+
# @return [Spree::ServiceModule::Result] a hash with `assigned`, the
|
|
18
|
+
# number of files given a store, and `conflicts`, the ids of files
|
|
19
|
+
# attached to records of more than one store, which are reported and
|
|
20
|
+
# left for a person to resolve
|
|
21
|
+
def call
|
|
22
|
+
assigned = record_types.sum do |record_type|
|
|
23
|
+
owner_class = record_type.safe_constantize
|
|
24
|
+
next 0 if owner_class.nil?
|
|
25
|
+
|
|
26
|
+
# Renditions and previews are blobs themselves: a set-based update
|
|
27
|
+
# would join the table it updates, which MySQL refuses.
|
|
28
|
+
if owner_class.column_names.include?('store_id') && !record_type.start_with?('ActiveStorage::')
|
|
29
|
+
assign_from_store_column(record_type, owner_class)
|
|
30
|
+
else
|
|
31
|
+
assign_record_by_record(record_type)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
success(assigned: assigned, conflicts: conflicting_blob_ids)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
# Renditions and previews take their store from their original file, so
|
|
41
|
+
# they go last, once the originals have one.
|
|
42
|
+
def record_types
|
|
43
|
+
ActiveStorage::Attachment.distinct.pluck(:record_type).
|
|
44
|
+
sort_by { |record_type| [record_type.start_with?('ActiveStorage::') ? 1 : 0, record_type] }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Correlated subqueries rather than UPDATE … FROM, which MySQL and SQLite
|
|
48
|
+
# spell differently. Several attachments of one file in different stores
|
|
49
|
+
# pick one; {#conflicting_blob_ids} reports them.
|
|
50
|
+
def assign_from_store_column(record_type, owner_class)
|
|
51
|
+
attachments_table = ActiveStorage::Attachment.table_name
|
|
52
|
+
owners_table = owner_class.table_name
|
|
53
|
+
owner_store = ActiveStorage::Attachment.
|
|
54
|
+
joins("INNER JOIN #{owners_table} ON #{owners_table}.id = #{attachments_table}.record_id").
|
|
55
|
+
where(record_type: record_type).
|
|
56
|
+
where("#{attachments_table}.blob_id = #{ActiveStorage::Blob.table_name}.id").
|
|
57
|
+
where.not(owners_table => { store_id: nil })
|
|
58
|
+
|
|
59
|
+
ActiveStorage::Blob.where(store_id: nil).
|
|
60
|
+
where(id: ActiveStorage::Attachment.where(record_type: record_type).select(:blob_id)).
|
|
61
|
+
where(owner_store.arel.exists).
|
|
62
|
+
update_all(["store_id = (#{owner_store.select("#{owners_table}.store_id").limit(1).to_sql})"])
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# One update per store and batch rather than per file.
|
|
66
|
+
def assign_record_by_record(record_type)
|
|
67
|
+
unassigned = ActiveStorage::Attachment.joins(:blob).
|
|
68
|
+
where(record_type: record_type, ActiveStorage::Blob.table_name => { store_id: nil })
|
|
69
|
+
|
|
70
|
+
unassigned.preload(:record).in_batches(of: BATCH_SIZE).sum do |batch|
|
|
71
|
+
batch.select(&:record).group_by { |attachment| Spree::Uploads.store_id_for(attachment.record) }.sum do |store_id, owned|
|
|
72
|
+
next 0 if store_id.nil?
|
|
73
|
+
|
|
74
|
+
ActiveStorage::Blob.where(id: owned.map(&:blob_id), store_id: nil).update_all(store_id: store_id)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def conflicting_blob_ids
|
|
80
|
+
resolvable_types = record_types.select(&:safe_constantize)
|
|
81
|
+
shared_blob_ids = ActiveStorage::Attachment.where(record_type: resolvable_types).group(:blob_id).having('COUNT(*) > 1').pluck(:blob_id)
|
|
82
|
+
|
|
83
|
+
shared_blob_ids.each_slice(BATCH_SIZE).flat_map do |blob_ids|
|
|
84
|
+
ActiveStorage::Attachment.where(blob_id: blob_ids, record_type: resolvable_types).includes(:record).group_by(&:blob_id).filter_map do |blob_id, blob_attachments|
|
|
85
|
+
store_ids = blob_attachments.filter_map { |attachment| attachment.record && Spree::Uploads.store_id_for(attachment.record) }
|
|
86
|
+
blob_id if store_ids.map(&:to_s).uniq.size > 1
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -44,6 +44,16 @@ module Spree
|
|
|
44
44
|
# identified outright and never reaches the tie-break at all.
|
|
45
45
|
CONTAINER_CONTENT_TYPES = %w[application/zip application/x-ole-storage].freeze
|
|
46
46
|
|
|
47
|
+
# Plain-text formats carry no signature at all, so the bytes can only prove
|
|
48
|
+
# what they are not. Marcel names anything it recognises — HTML, XML, SVG,
|
|
49
|
+
# scripts with a shebang, executables — and those never reach this
|
|
50
|
+
# tie-break. What is left is accepted under a text extension only when it
|
|
51
|
+
# is free of the control bytes every binary format carries. No particular
|
|
52
|
+
# encoding is required, since spreadsheet tools still export in legacy ones.
|
|
53
|
+
TEXT_CONTENT_TYPES = %w[text/csv].freeze
|
|
54
|
+
UNIDENTIFIED_CONTENT_TYPES = %w[application/octet-stream text/plain].freeze
|
|
55
|
+
BINARY_BYTES = /[\x00-\x08\x0B\x0E-\x1F\x7F]/n
|
|
56
|
+
|
|
47
57
|
# A misspelled or forgotten option would otherwise leave this validator
|
|
48
58
|
# silently accepting everything, on the one path whose whole purpose is
|
|
49
59
|
# refusing hostile files. An allowlist that resolves to empty at runtime is
|
|
@@ -116,9 +126,16 @@ module Spree
|
|
|
116
126
|
# read, which would hand the decision back to whoever uploaded the file.
|
|
117
127
|
detected = Marcel::MimeType.for(StringIO.new(head))
|
|
118
128
|
return true if permitted.include?(detected)
|
|
129
|
+
return permitted_plain_text?(blob, head, permitted) if UNIDENTIFIED_CONTENT_TYPES.include?(detected)
|
|
119
130
|
return false unless CONTAINER_CONTENT_TYPES.include?(detected)
|
|
120
131
|
|
|
121
|
-
permitted.include?(
|
|
132
|
+
permitted.include?(type_from_extension(blob))
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def permitted_plain_text?(blob, head, permitted)
|
|
136
|
+
text_type = type_from_extension(blob)
|
|
137
|
+
|
|
138
|
+
TEXT_CONTENT_TYPES.include?(text_type) && permitted.include?(text_type) && !head.b.match?(BINARY_BYTES)
|
|
122
139
|
end
|
|
123
140
|
|
|
124
141
|
# Asks the extension alone, rather than re-reading the bytes with a filename
|
|
@@ -129,7 +146,7 @@ module Spree
|
|
|
129
146
|
# bytes we read.
|
|
130
147
|
#
|
|
131
148
|
# @return [String, nil]
|
|
132
|
-
def
|
|
149
|
+
def type_from_extension(blob)
|
|
133
150
|
filename = blob.filename.to_s
|
|
134
151
|
return if File.extname(filename).blank?
|
|
135
152
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
# Rejects locale codes `Spree::Locales::ALL` does not list, in a single code
|
|
3
|
+
# or a comma-separated list, naming the ones it refuses. Reads the stored
|
|
4
|
+
# column, since a store answers `default_locale` from its default market,
|
|
5
|
+
# and checks only an attribute that is changing, so a record saved before
|
|
6
|
+
# the list was enforced can still be edited.
|
|
7
|
+
#
|
|
8
|
+
# @example
|
|
9
|
+
# validates_with Spree::LocaleCodesValidator, attributes: %i[default_locale supported_locales]
|
|
10
|
+
class LocaleCodesValidator < ActiveModel::Validator
|
|
11
|
+
def validate(record)
|
|
12
|
+
Array(options[:attributes]).each do |attribute|
|
|
13
|
+
next unless record.will_save_change_to_attribute?(attribute)
|
|
14
|
+
|
|
15
|
+
unlisted = Spree::Locales.unlisted(record.read_attribute(attribute))
|
|
16
|
+
next if unlisted.empty?
|
|
17
|
+
|
|
18
|
+
record.errors.add(attribute, I18n.t('spree.locales_not_listed', codes: unlisted.join(', ')))
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|