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
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
module Spree
|
|
2
2
|
# Shared shape of the typed adjustment rows (TaxLine, Discount, Fee):
|
|
3
3
|
# dual-FK cart/order owner (exactly one), consolidated +metadata+ JSON
|
|
4
|
-
# column, money display and
|
|
4
|
+
# column, money display and strict amount parsing.
|
|
5
5
|
module TypedAdjustmentLine
|
|
6
6
|
extend ActiveSupport::Concern
|
|
7
7
|
|
|
8
8
|
included do
|
|
9
|
+
include Spree::CanonicalDecimals
|
|
10
|
+
|
|
11
|
+
canonical_decimals :amount
|
|
9
12
|
attribute :metadata, default: -> { {} }
|
|
10
13
|
|
|
11
14
|
belongs_to :order, class_name: 'Spree::Order', optional: true
|
|
@@ -38,10 +41,6 @@ module Spree
|
|
|
38
41
|
end
|
|
39
42
|
end
|
|
40
43
|
|
|
41
|
-
def amount=(amount)
|
|
42
|
-
self[:amount] = Spree::LocalizedNumber.parse(amount)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
44
|
def currency
|
|
46
45
|
owner&.currency
|
|
47
46
|
end
|
|
@@ -49,7 +48,7 @@ module Spree
|
|
|
49
48
|
private
|
|
50
49
|
|
|
51
50
|
def exactly_one_owner
|
|
52
|
-
errors.add(:base, :exactly_one_of_cart_or_order, message:
|
|
51
|
+
errors.add(:base, :exactly_one_of_cart_or_order, message: I18n.t('spree.errors.messages.exactly_one_of_cart_or_order')) unless [order, cart].compact.one?
|
|
53
52
|
end
|
|
54
53
|
end
|
|
55
54
|
end
|
|
@@ -53,7 +53,7 @@ module Spree
|
|
|
53
53
|
|
|
54
54
|
def reconcile_typed_association(association, rows)
|
|
55
55
|
collection = public_send(association)
|
|
56
|
-
kept_ids = rows.filter_map { |row| save_typed_association_row(collection, row) }
|
|
56
|
+
kept_ids = rows.each_with_index.filter_map { |row, index| save_typed_association_row(collection, row, "/#{association.to_s.split('_').last}/#{index}") }
|
|
57
57
|
if kept_ids.any? || rows.empty?
|
|
58
58
|
collection.where.not(id: kept_ids).destroy_all
|
|
59
59
|
# `where.not(...).destroy_all` deletes the dropped rows from the DB but
|
|
@@ -64,7 +64,8 @@ module Spree
|
|
|
64
64
|
end
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
# @param pointer [String] where the row sits in the request, e.g. `/rules/1`
|
|
68
|
+
def save_typed_association_row(collection, row, pointer)
|
|
68
69
|
record = find_or_build_typed_association_row(collection, row)
|
|
69
70
|
return nil unless record
|
|
70
71
|
|
|
@@ -78,12 +79,10 @@ module Spree
|
|
|
78
79
|
deferred_ids, scalar_attrs = attrs.partition { |k, _| record.new_record? && k.to_s.end_with?('_ids') }
|
|
79
80
|
record.assign_attributes(scalar_attrs.to_h) if scalar_attrs.any?
|
|
80
81
|
|
|
81
|
-
preferences
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
record.set_preference(key.to_sym, decode_preference_value(key, value))
|
|
82
|
+
record.assign_preferences(preferences, pointer: "#{pointer}/preferences") if preferences
|
|
83
|
+
if calculator.present? && record.respond_to?(:assign_calculator_attributes)
|
|
84
|
+
record.assign_calculator_attributes(calculator, pointer: "#{pointer}/calculator")
|
|
85
85
|
end
|
|
86
|
-
record.assign_calculator_attributes(calculator) if calculator.present? && record.respond_to?(:assign_calculator_attributes)
|
|
87
86
|
|
|
88
87
|
# Always save — `record.changed?` doesn't reflect preferences
|
|
89
88
|
# (serialized hash) or calculator association changes.
|
|
@@ -93,17 +92,12 @@ module Spree
|
|
|
93
92
|
record.save! if record.changed?
|
|
94
93
|
|
|
95
94
|
record.id
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
return value unless key.to_s.end_with?('_ids') && value.is_a?(Array)
|
|
103
|
-
|
|
104
|
-
value.map do |v|
|
|
105
|
-
Spree::PrefixedId.prefixed_id?(v) ? Spree::PrefixedId.decode_prefixed_id(v) : v
|
|
106
|
-
end
|
|
95
|
+
rescue ActiveRecord::RecordInvalid => e
|
|
96
|
+
# A row saved from the owner's `after_save` fails inside the owner's own
|
|
97
|
+
# save, and a plain `save` swallows the exception — so its errors are
|
|
98
|
+
# copied onto the owner, or the caller learns only that something failed.
|
|
99
|
+
e.record.errors.full_messages.each { |message| errors.add(:base, message) }
|
|
100
|
+
raise
|
|
107
101
|
end
|
|
108
102
|
|
|
109
103
|
def find_or_build_typed_association_row(collection, row)
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
module Spree
|
|
2
2
|
# Restates a VAT-inclusive price for a customer in another country: the
|
|
3
3
|
# home-country VAT comes out, the destination's goes on.
|
|
4
|
+
#
|
|
5
|
+
# The including class answers +currency+, which the restated amount is
|
|
6
|
+
# rounded to.
|
|
4
7
|
module VatPriceCalculation
|
|
5
8
|
# Cheapest test first: a domestic sale needs no restatement whatever the
|
|
6
9
|
# provider is, so the provider lookup (a constantize and an instantiation) is
|
|
@@ -11,7 +14,7 @@ module Spree
|
|
|
11
14
|
return amount if amount.nil? || !outside_default_vat_zone?(price_options)
|
|
12
15
|
return amount unless restatement_available?(price_options)
|
|
13
16
|
|
|
14
|
-
|
|
17
|
+
Spree::Money::Rounding.to_currency(add_foreign_vat_for(amount, price_options), currency)
|
|
15
18
|
end
|
|
16
19
|
|
|
17
20
|
private
|
|
@@ -66,7 +69,7 @@ module Spree
|
|
|
66
69
|
lookup[:country] = country_from(price_options)
|
|
67
70
|
end
|
|
68
71
|
|
|
69
|
-
Spree::TaxRate.included_tax_amount_for(lookup).
|
|
72
|
+
Spree::TaxRate.included_tax_amount_for(lookup).to_d
|
|
70
73
|
end
|
|
71
74
|
|
|
72
75
|
# +current_price_options+ is documented as an override point, so an app may
|
|
@@ -92,9 +95,5 @@ module Spree
|
|
|
92
95
|
|
|
93
96
|
@default_tax_country = Spree::Current.store&.default_country
|
|
94
97
|
end
|
|
95
|
-
|
|
96
|
-
def round_to_two_places(amount)
|
|
97
|
-
Spree::Money::Rounding.quantize(amount, 2)
|
|
98
|
-
end
|
|
99
98
|
end
|
|
100
99
|
end
|
data/app/models/spree/address.rb
CHANGED
|
@@ -7,7 +7,7 @@ module Spree
|
|
|
7
7
|
include Spree::HasCustomFields
|
|
8
8
|
include Spree::Metadata
|
|
9
9
|
|
|
10
|
-
serialize :preferences,
|
|
10
|
+
serialize :preferences, coder: Spree::Metadata::HashSerializer, default: {}
|
|
11
11
|
|
|
12
12
|
NO_ZIPCODE_ISO_CODES ||= [
|
|
13
13
|
'AO', 'AG', 'AW', 'BS', 'BZ', 'BJ', 'BM', 'BO', 'BW', 'BF', 'BI', 'CM', 'CF', 'KM', 'CG',
|
|
@@ -27,6 +27,10 @@ module Spree
|
|
|
27
27
|
# those attributes depending of the logic of their applications
|
|
28
28
|
ADDRESS_FIELDS = %w(first_name last_name company address1 address2 city state postal_code country phone)
|
|
29
29
|
|
|
30
|
+
# The columns a delivery quote reads. A change to anything else (a phone
|
|
31
|
+
# number, the recipient's name) leaves the parcel going where it was.
|
|
32
|
+
DESTINATION_FIELDS = %w(address1 address2 city state_code postal_code country_code).freeze
|
|
33
|
+
|
|
30
34
|
# Beyond identity and ownership, three groups are excluded because they
|
|
31
35
|
# cannot tell two addresses apart. Latitude and longitude are derived from
|
|
32
36
|
# the address, so an entry whose geocoding has already run would otherwise
|
|
@@ -202,7 +206,7 @@ module Spree
|
|
|
202
206
|
scope.first
|
|
203
207
|
end
|
|
204
208
|
|
|
205
|
-
self.whitelisted_ransackable_attributes = ADDRESS_FIELDS + %w[country_code state_code]
|
|
209
|
+
self.whitelisted_ransackable_attributes = ADDRESS_FIELDS - %w[country state] + %w[country_code state_code]
|
|
206
210
|
self.whitelisted_ransackable_associations = %w[owner]
|
|
207
211
|
|
|
208
212
|
def self.required_fields
|
|
@@ -379,6 +383,12 @@ module Spree
|
|
|
379
383
|
attributes.except(*EXCLUDED_KEYS_FOR_COMPARISON)
|
|
380
384
|
end
|
|
381
385
|
|
|
386
|
+
# @return [Boolean] whether the last save moved this address somewhere
|
|
387
|
+
# else, as opposed to correcting who receives the parcel
|
|
388
|
+
def saved_change_to_destination?
|
|
389
|
+
saved_changes.keys.intersect?(DESTINATION_FIELDS)
|
|
390
|
+
end
|
|
391
|
+
|
|
382
392
|
# A country alone doesn't make an address — a new one is pre-filled with the
|
|
383
393
|
# store's default, so both the code and the association are ignored here.
|
|
384
394
|
def empty?
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
module Spree
|
|
2
2
|
module Adjusters
|
|
3
|
-
# Largest-remainder apportionment of an integer
|
|
3
|
+
# Largest-remainder apportionment of an integer total, in the currency's
|
|
4
|
+
# minor units, across weights.
|
|
4
5
|
# Shared by promotion order-level distribution and manual admin discounts
|
|
5
6
|
# so both split identically (sum of shares always equals the total).
|
|
6
7
|
module LargestRemainder
|
|
@@ -19,6 +20,18 @@ module Spree
|
|
|
19
20
|
end
|
|
20
21
|
shares
|
|
21
22
|
end
|
|
23
|
+
|
|
24
|
+
# Splits an amount across weights in the currency's minor units, so the
|
|
25
|
+
# shares are amounts that sum exactly to the one given.
|
|
26
|
+
#
|
|
27
|
+
# @param amount [BigDecimal]
|
|
28
|
+
# @param weights [Array<Numeric>] proportional bases (sum must be > 0)
|
|
29
|
+
# @param currency [String]
|
|
30
|
+
# @return [Array<BigDecimal>]
|
|
31
|
+
def apportion(amount, weights, currency)
|
|
32
|
+
units = Spree::Money::Rounding.to_minor_units(amount, currency)
|
|
33
|
+
largest_remainder_shares(units, weights).map { |share| Spree::Money::Rounding.from_minor_units(share, currency) }
|
|
34
|
+
end
|
|
22
35
|
end
|
|
23
36
|
end
|
|
24
37
|
end
|
|
@@ -65,22 +65,8 @@ module Spree
|
|
|
65
65
|
end
|
|
66
66
|
end
|
|
67
67
|
|
|
68
|
-
# The owner's PERSISTED coupon code keeps its promotion in candidacy
|
|
69
|
-
# even before it ever applied — the discount activates on the exact
|
|
70
|
-
# recalculation where the cart first qualifies, and deactivates the
|
|
71
|
-
# same way (Shopify-parity for cart-level discount codes). In-memory
|
|
72
|
-
# assignments deliberately don't participate: unsaved codes belong to
|
|
73
|
-
# the explicit PromotionHandler::Coupon path.
|
|
74
68
|
def coupon_promotions
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
code = order.read_attribute(:coupon_code)
|
|
78
|
-
return [] if code.blank?
|
|
79
|
-
|
|
80
|
-
promotion = order.store.promotions.active.with_coupon_code(code)
|
|
81
|
-
return [] if promotion.nil? || promotion.usage_limit_exceeded?(order)
|
|
82
|
-
|
|
83
|
-
[promotion]
|
|
69
|
+
Spree::Promotion.held_by_saved_coupon_code(order)
|
|
84
70
|
end
|
|
85
71
|
|
|
86
72
|
# A coupon promotion that produced rows becomes an applied promotion —
|
|
@@ -154,7 +140,7 @@ module Spree
|
|
|
154
140
|
end
|
|
155
141
|
end
|
|
156
142
|
|
|
157
|
-
# Largest-remainder distribution (in
|
|
143
|
+
# Largest-remainder distribution (in minor units) of the order-wide winner over
|
|
158
144
|
# each line's remaining discounted base, so shares always sum exactly to
|
|
159
145
|
# the promotion amount and no line goes below zero.
|
|
160
146
|
def distribute_over_line_items(chosen)
|
|
@@ -163,11 +149,10 @@ module Spree
|
|
|
163
149
|
bases_sum = bases.sum
|
|
164
150
|
return if bases_sum <= 0
|
|
165
151
|
|
|
166
|
-
|
|
167
|
-
shares = Spree::Adjusters::LargestRemainder.largest_remainder_shares(total_cents.round, bases)
|
|
152
|
+
shares = Spree::Adjusters::LargestRemainder.apportion([chosen[:amount].abs, bases_sum].min, bases, order.currency)
|
|
168
153
|
|
|
169
154
|
line_items.each_with_index do |line_item, index|
|
|
170
|
-
amount = -
|
|
155
|
+
amount = -shares[index]
|
|
171
156
|
next if amount.zero?
|
|
172
157
|
|
|
173
158
|
persist_discount(line_item, chosen, amount)
|
|
@@ -10,7 +10,9 @@ module Spree
|
|
|
10
10
|
|
|
11
11
|
include Spree::SingleStoreResource
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
self.whitelisted_ransackable_attributes = %w[origin]
|
|
14
|
+
search_by :origin
|
|
15
|
+
|
|
14
16
|
|
|
15
17
|
validates :origin, presence: true
|
|
16
18
|
validates :origin, uniqueness: { scope: [:store_id, *spree_base_uniqueness_scope] }
|
data/app/models/spree/api_key.rb
CHANGED
|
@@ -44,7 +44,6 @@ module Spree
|
|
|
44
44
|
publishable? ? token : @plaintext_token
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
-
belongs_to :store, class_name: 'Spree::Store'
|
|
48
47
|
# Optional single-channel binding for publishable keys. A bound key
|
|
49
48
|
# server-assigns the request's channel (see Api::V3::ChannelResolution),
|
|
50
49
|
# making channel identity unforgeable instead of client-asserted via the
|
|
@@ -162,23 +161,23 @@ module Spree
|
|
|
162
161
|
return if invalid.empty?
|
|
163
162
|
|
|
164
163
|
errors.add(:scopes, :unknown_scopes, scopes: invalid.join(', '),
|
|
165
|
-
message:
|
|
164
|
+
message: I18n.t('spree.errors.messages.unknown_scopes', scopes: invalid.join(', ')))
|
|
166
165
|
end
|
|
167
166
|
|
|
168
167
|
def scopes_immutable
|
|
169
|
-
errors.add(:scopes, :api_key_scopes_immutable, message:
|
|
168
|
+
errors.add(:scopes, :api_key_scopes_immutable, message: I18n.t('spree.api_key_scopes_immutable'))
|
|
170
169
|
end
|
|
171
170
|
|
|
172
171
|
def channel_binding_valid
|
|
173
172
|
if secret?
|
|
174
|
-
errors.add(:channel, :api_key_channel_publishable_only, message:
|
|
173
|
+
errors.add(:channel, :api_key_channel_publishable_only, message: I18n.t('spree.api_key_channel_publishable_only'))
|
|
175
174
|
elsif channel && channel.store_id != store_id
|
|
176
|
-
errors.add(:channel, :api_key_channel_must_belong_to_store, message:
|
|
175
|
+
errors.add(:channel, :api_key_channel_must_belong_to_store, message: I18n.t('spree.api_key_channel_must_belong_to_store'))
|
|
177
176
|
end
|
|
178
177
|
end
|
|
179
178
|
|
|
180
179
|
def channel_immutable
|
|
181
|
-
errors.add(:channel, :api_key_channel_immutable, message:
|
|
180
|
+
errors.add(:channel, :api_key_channel_immutable, message: I18n.t('spree.api_key_channel_immutable'))
|
|
182
181
|
end
|
|
183
182
|
|
|
184
183
|
# Generates the token on creation. For publishable keys, stores the raw token
|
|
@@ -129,7 +129,7 @@ module Spree
|
|
|
129
129
|
# Password login is not available through an OIDC provider — the browser
|
|
130
130
|
# redirect is the only entry point.
|
|
131
131
|
def authenticate
|
|
132
|
-
failure(
|
|
132
|
+
failure(I18n.t('spree.errors.messages.oidc_requires_redirect'))
|
|
133
133
|
end
|
|
134
134
|
|
|
135
135
|
# Completes the login started by +#authorization_url+: exchanges the
|
|
@@ -138,13 +138,13 @@ module Spree
|
|
|
138
138
|
# @return [Spree::ServiceModule::Result]
|
|
139
139
|
def callback
|
|
140
140
|
code = params[:code]
|
|
141
|
-
return authentication_failure(
|
|
141
|
+
return authentication_failure(I18n.t('spree.errors.messages.oidc_missing_code')) if code.blank?
|
|
142
142
|
|
|
143
143
|
claims = verify_id_token(exchange_code_for_tokens(code).fetch('id_token'))
|
|
144
144
|
resolve_user(claims)
|
|
145
145
|
rescue StandardError => e
|
|
146
146
|
Rails.logger.error("[Spree] OIDC callback failed for #{config[:label] || config[:issuer]}: #{e.message}")
|
|
147
|
-
authentication_failure(
|
|
147
|
+
authentication_failure(I18n.t('spree.errors.messages.oidc_authentication_failed'))
|
|
148
148
|
end
|
|
149
149
|
|
|
150
150
|
# True when the last +#callback+ failed because no account here is
|
|
@@ -168,7 +168,7 @@ module Spree
|
|
|
168
168
|
# every member of a corporate tenant would become store staff.
|
|
169
169
|
def resolve_user(claims)
|
|
170
170
|
subject = claims['sub']
|
|
171
|
-
return authentication_failure(
|
|
171
|
+
return authentication_failure(I18n.t('spree.errors.messages.oidc_authentication_failed')) if subject.blank?
|
|
172
172
|
|
|
173
173
|
identity = Spree::UserIdentity.find_by(
|
|
174
174
|
provider: registry_key,
|
|
@@ -191,7 +191,7 @@ module Spree
|
|
|
191
191
|
|
|
192
192
|
def not_provisioned_failure
|
|
193
193
|
@account_not_provisioned = true
|
|
194
|
-
failure(
|
|
194
|
+
failure(I18n.t('spree.errors.messages.oidc_account_not_provisioned'))
|
|
195
195
|
end
|
|
196
196
|
|
|
197
197
|
# Only an email the provider asserts as verified may claim an existing
|
data/app/models/spree/base.rb
CHANGED
|
@@ -9,6 +9,7 @@ class Spree::Base < ApplicationRecord
|
|
|
9
9
|
include Spree::HasNumber
|
|
10
10
|
include Spree::HasIsoGeography
|
|
11
11
|
include Spree::TypedAssociations
|
|
12
|
+
include Spree::SignedIdAttachments
|
|
12
13
|
include Spree::ValidatesStoreUniqueness
|
|
13
14
|
|
|
14
15
|
# Extra writable attributes contributed by extensions, appended to the v3
|
|
@@ -23,24 +24,20 @@ class Spree::Base < ApplicationRecord
|
|
|
23
24
|
# rather than assigning, so extensions don't clobber each other.
|
|
24
25
|
class_attribute :additional_permitted_attributes, instance_writer: false, default: [].freeze
|
|
25
26
|
|
|
27
|
+
# The Seller API's counterpart, kept separate because sellers write a
|
|
28
|
+
# narrower set than operators: an attribute an extension makes writable for
|
|
29
|
+
# admins stays admin-only unless it is also declared here.
|
|
30
|
+
#
|
|
31
|
+
# Spree::Product.additional_seller_permitted_attributes += [:brand_id]
|
|
32
|
+
class_attribute :additional_seller_permitted_attributes, instance_writer: false, default: [].freeze
|
|
33
|
+
|
|
26
34
|
# Backfills preferences added to the class after this row was last saved, so
|
|
27
|
-
# a reader never sees nil for a newly defined preference.
|
|
28
|
-
# unconditionally would dirty every record on load
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
# `with_lock` refuse the record ("unpersisted changes").
|
|
35
|
+
# a reader never sees nil for a newly defined preference. Only missing keys
|
|
36
|
+
# are assigned: assigning unconditionally would dirty every record on load,
|
|
37
|
+
# which is enough to make `with_lock` refuse the record ("unpersisted
|
|
38
|
+
# changes").
|
|
32
39
|
after_initialize do
|
|
33
|
-
if has_attribute?(:preferences) && !preferences.nil?
|
|
34
|
-
missing = default_preferences.except(*preferences.keys)
|
|
35
|
-
self.preferences = preferences.merge(missing) if missing.any?
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# only for backwards compatibility with Kaminari
|
|
40
|
-
if defined?(Kaminari) && Kaminari.config.page_method_name != :page
|
|
41
|
-
def self.page(num)
|
|
42
|
-
send Kaminari.config.page_method_name, num
|
|
43
|
-
end
|
|
40
|
+
backfill_default_preferences if has_attribute?(:preferences) && !preferences.nil?
|
|
44
41
|
end
|
|
45
42
|
|
|
46
43
|
self.abstract_class = true
|
|
@@ -72,6 +69,18 @@ class Spree::Base < ApplicationRecord
|
|
|
72
69
|
true
|
|
73
70
|
end
|
|
74
71
|
|
|
72
|
+
# Whether to answer from an association's rows rather than a query of its
|
|
73
|
+
# own (a SUM, an EXISTS). True when the rows are loaded already, or when
|
|
74
|
+
# this record came from a list loaded with +preload_associations_lazily+:
|
|
75
|
+
# loading them then fetches the rows for the whole list in one query, where
|
|
76
|
+
# an aggregate would run once per record.
|
|
77
|
+
#
|
|
78
|
+
# @param name [Symbol] the association
|
|
79
|
+
# @return [Boolean]
|
|
80
|
+
def read_association_in_memory?(name)
|
|
81
|
+
association(name).loaded? || lazy_preload_context.present?
|
|
82
|
+
end
|
|
83
|
+
|
|
75
84
|
# @see Spree.mysql? — the single definition; this reads it so a model can
|
|
76
85
|
# branch without reaching for the connection itself.
|
|
77
86
|
def mysql_adapter?
|
|
@@ -115,9 +124,10 @@ class Spree::Base < ApplicationRecord
|
|
|
115
124
|
# read through the parent (a plain `Spree::Export` row, a factory that sets
|
|
116
125
|
# `type` as an attribute, a query on the base relation).
|
|
117
126
|
#
|
|
118
|
-
# Resolves against
|
|
119
|
-
# unrecognized column value
|
|
120
|
-
# constantized
|
|
127
|
+
# Resolves against the class's type registry where it has one, so an
|
|
128
|
+
# unrecognized column value (an extension no longer installed) is never
|
|
129
|
+
# constantized — it is shortened the same way instead, never sent as a class
|
|
130
|
+
# name.
|
|
121
131
|
#
|
|
122
132
|
# @param type [String, nil] value of the STI `type` column
|
|
123
133
|
# @return [String]
|
|
@@ -125,9 +135,49 @@ class Spree::Base < ApplicationRecord
|
|
|
125
135
|
return api_type if type.blank?
|
|
126
136
|
|
|
127
137
|
type = type.to_s
|
|
128
|
-
|
|
138
|
+
api_type_registry.find { |klass| klass.to_s == type }&.api_type || type.demodulize.underscore
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Inverse of {.api_type_for}: the STI `type` column value for a wire
|
|
142
|
+
# shorthand, or nil when no registered subclass answers to it. Lookup is
|
|
143
|
+
# registry-driven, so a request can never name a class outside the family.
|
|
144
|
+
#
|
|
145
|
+
# @param api_type [String, nil] e.g. `"orders"`
|
|
146
|
+
# @return [String, nil] e.g. `"Spree::Exports::Orders"`
|
|
147
|
+
def self.class_name_for_api_type(api_type)
|
|
148
|
+
return nil if api_type.blank?
|
|
149
|
+
|
|
150
|
+
api_type_registry.find { |klass| klass.api_type == api_type.to_s }&.to_s
|
|
151
|
+
end
|
|
129
152
|
|
|
130
|
-
|
|
153
|
+
# The subclasses a typed family accepts — `registered_subclasses` for the
|
|
154
|
+
# preference-schema families, `available_types` for exports and imports.
|
|
155
|
+
#
|
|
156
|
+
# @return [Array<Class>]
|
|
157
|
+
def self.api_type_registry
|
|
158
|
+
if subclass_registry
|
|
159
|
+
registered_subclasses
|
|
160
|
+
elsif respond_to?(:available_types)
|
|
161
|
+
Array(available_types)
|
|
162
|
+
else
|
|
163
|
+
[]
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# How a filter on one of this model's columns turns the short name a client
|
|
168
|
+
# sends (`purchase_order`) into the class name the column stores, or nil
|
|
169
|
+
# when the column does not store class names. Covers the STI column of a
|
|
170
|
+
# typed family and polymorphic `*_type` columns; a model with another such
|
|
171
|
+
# column overrides this.
|
|
172
|
+
#
|
|
173
|
+
# @param attribute [String]
|
|
174
|
+
# @return [Proc, nil] short name → stored class name
|
|
175
|
+
def self.api_type_resolver(attribute)
|
|
176
|
+
if attribute == inheritance_column && api_type_registry.any?
|
|
177
|
+
->(api_type) { class_name_for_api_type(api_type) }
|
|
178
|
+
elsif reflect_on_all_associations(:belongs_to).any? { |reflection| reflection.polymorphic? && reflection.foreign_type == attribute }
|
|
179
|
+
->(api_type) { Spree::Base.polymorphic_type_for(api_type) }
|
|
180
|
+
end
|
|
131
181
|
end
|
|
132
182
|
|
|
133
183
|
# Shorthand for a *polymorphic* `*_type` column (`owner_type`,
|
|
@@ -138,10 +188,37 @@ class Spree::Base < ApplicationRecord
|
|
|
138
188
|
#
|
|
139
189
|
# @param type [String, Class, nil] value of the polymorphic type column
|
|
140
190
|
# @return [String, nil]
|
|
191
|
+
#
|
|
192
|
+
# The configured customer and admin user classes always answer `customer`
|
|
193
|
+
# and `admin_user`, so a host app's own `User` class does not change the
|
|
194
|
+
# contract.
|
|
141
195
|
def self.polymorphic_api_type(type)
|
|
142
196
|
return nil if type.blank?
|
|
143
197
|
|
|
144
|
-
type.to_s
|
|
198
|
+
type = type.to_s
|
|
199
|
+
return 'customer' if type == Spree.customer_class(constantize: false)
|
|
200
|
+
return 'admin_user' if type == Spree.admin_user_class(constantize: false)
|
|
201
|
+
|
|
202
|
+
type.demodulize.underscore
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Inverse of {.polymorphic_api_type}: the class name a polymorphic `*_type`
|
|
206
|
+
# column stores for a wire shorthand. Matches against `candidates` when the
|
|
207
|
+
# caller knows what the column may reference (always prefer that); otherwise
|
|
208
|
+
# against the configured customer and admin user classes, then the
|
|
209
|
+
# `Spree::` model of that name. Never constantizes the input.
|
|
210
|
+
#
|
|
211
|
+
# @param api_type [String, nil] e.g. `"purchase_order"`
|
|
212
|
+
# @param candidates [Array<Class, String>, nil] classes the column may hold
|
|
213
|
+
# @return [String, nil] e.g. `"Spree::PurchaseOrder"`
|
|
214
|
+
def self.polymorphic_type_for(api_type, candidates = nil)
|
|
215
|
+
token = api_type.to_s
|
|
216
|
+
return nil unless token.match?(/\A[a-z][a-z0-9_]*\z/)
|
|
217
|
+
|
|
218
|
+
candidates ||= [Spree.customer_class(constantize: false),
|
|
219
|
+
Spree.admin_user_class(constantize: false),
|
|
220
|
+
"Spree::#{token.camelize}"]
|
|
221
|
+
candidates.map(&:to_s).find { |name| polymorphic_api_type(name) == token }
|
|
145
222
|
end
|
|
146
223
|
|
|
147
224
|
# Prefixed id for a polymorphic reference, encoded from the columns without
|
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
require_dependency 'spree/calculator'
|
|
2
|
-
|
|
3
1
|
module Spree
|
|
4
2
|
class Calculator::FlatPercentItemTotal < Calculator
|
|
5
3
|
preference :flat_percent, :decimal, default: 0
|
|
6
4
|
|
|
7
5
|
def self.description
|
|
8
|
-
|
|
6
|
+
I18n.t('spree.flat_percent')
|
|
9
7
|
end
|
|
10
8
|
|
|
11
9
|
def compute(object)
|
|
12
|
-
computed_amount = (object.amount * preferred_flat_percent / 100
|
|
10
|
+
computed_amount = Spree::Money::Rounding.to_currency(object.amount * preferred_flat_percent / 100, object.try(:currency))
|
|
13
11
|
|
|
14
12
|
# We don't want to cause the promotion adjustments to push the order into a negative total.
|
|
15
13
|
if computed_amount > object.amount
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
require_dependency 'spree/calculator'
|
|
2
|
-
|
|
3
1
|
module Spree
|
|
4
2
|
class Calculator::FlatRate < Calculator
|
|
5
|
-
preference :amount, :
|
|
6
|
-
preference :currency, :string, default: -> { Spree::Store.default.default_currency }
|
|
3
|
+
preference :amount, :money, default: 0
|
|
4
|
+
preference :currency, :string, format: :currency, default: -> { Spree::Store.default.default_currency }
|
|
7
5
|
preference :apply_only_on_full_priced_items, :boolean, default: false
|
|
8
6
|
|
|
9
7
|
def self.description
|
|
10
|
-
|
|
8
|
+
I18n.t('spree.flat_rate_per_order')
|
|
11
9
|
end
|
|
12
10
|
|
|
13
11
|
def compute(object = nil)
|
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
require_dependency 'spree/calculator'
|
|
2
|
-
|
|
3
1
|
module Spree
|
|
4
2
|
class Calculator::FlexiRate < Calculator
|
|
5
|
-
preference :first_item, :
|
|
6
|
-
preference :additional_item, :
|
|
3
|
+
preference :first_item, :money, default: 0.0
|
|
4
|
+
preference :additional_item, :money, default: 0.0
|
|
7
5
|
preference :max_items, :integer, default: 0
|
|
8
|
-
preference :currency, :string, default: -> { Spree::Store.default.default_currency }
|
|
6
|
+
preference :currency, :string, format: :currency, default: -> { Spree::Store.default.default_currency }
|
|
9
7
|
preference :apply_only_on_full_priced_items, :boolean, default: false
|
|
10
8
|
|
|
11
9
|
def self.description
|
|
12
|
-
|
|
10
|
+
I18n.t('spree.flexible_rate')
|
|
13
11
|
end
|
|
14
12
|
|
|
15
13
|
def self.available?(_object)
|
|
@@ -5,13 +5,13 @@ module Spree
|
|
|
5
5
|
preference :apply_only_on_full_priced_items, :boolean, default: false
|
|
6
6
|
|
|
7
7
|
def self.description
|
|
8
|
-
|
|
8
|
+
I18n.t('spree.percent_per_item')
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def compute(object)
|
|
12
12
|
return 0 if preferred_apply_only_on_full_priced_items && object.variant.compare_at_amount_in(object.currency).present?
|
|
13
13
|
|
|
14
|
-
computed_amount = (object.amount * preferred_percent / 100
|
|
14
|
+
computed_amount = Spree::Money::Rounding.to_currency(object.amount * preferred_percent / 100, object.currency)
|
|
15
15
|
|
|
16
16
|
# We don't want to cause the promotion adjustments to push the order into a negative total.
|
|
17
17
|
if computed_amount > object.amount
|
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
require_dependency 'spree/calculator'
|
|
2
|
-
|
|
3
1
|
module Spree
|
|
4
2
|
class Calculator::PriceSack < Calculator
|
|
5
|
-
preference :minimal_amount, :
|
|
6
|
-
preference :normal_amount, :
|
|
7
|
-
preference :discount_amount, :
|
|
8
|
-
preference :currency, :string, default: -> { Spree::Store.default.default_currency }
|
|
3
|
+
preference :minimal_amount, :money, default: 0
|
|
4
|
+
preference :normal_amount, :money, default: 0
|
|
5
|
+
preference :discount_amount, :money, default: 0
|
|
6
|
+
preference :currency, :string, format: :currency, default: -> { Spree::Store.default.default_currency }
|
|
9
7
|
|
|
10
8
|
def self.description
|
|
11
|
-
|
|
9
|
+
I18n.t('spree.price_sack')
|
|
12
10
|
end
|
|
13
11
|
|
|
14
12
|
# as object we always get line items, as calculable we have Coupon, ShippingMethod
|
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
require_dependency 'spree/shipping_calculator'
|
|
2
|
-
|
|
3
1
|
module Spree
|
|
4
2
|
module Calculator::Shipping
|
|
5
3
|
class DigitalDelivery < ShippingCalculator
|
|
6
4
|
include Spree::Calculator::CurrencyAmounts
|
|
7
5
|
|
|
8
|
-
preference :amount, :
|
|
9
|
-
preference :currency, :string, default: -> { Spree::Store.default.default_currency }
|
|
6
|
+
preference :amount, :money, default: 0
|
|
7
|
+
preference :currency, :string, format: :currency, default: -> { Spree::Store.default.default_currency }
|
|
10
8
|
|
|
11
9
|
def self.description
|
|
12
|
-
|
|
10
|
+
I18n.t('spree.digital.digital_delivery')
|
|
13
11
|
end
|
|
14
12
|
|
|
15
13
|
def compute_package(package = nil)
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
require_dependency 'spree/shipping_calculator'
|
|
2
|
-
|
|
3
1
|
module Spree
|
|
4
2
|
module Calculator::Shipping
|
|
5
3
|
class FlatPercentItemTotal < ShippingCalculator
|
|
6
4
|
preference :flat_percent, :decimal, default: 0
|
|
7
5
|
|
|
8
6
|
def self.description
|
|
9
|
-
|
|
7
|
+
I18n.t('spree.flat_percent')
|
|
10
8
|
end
|
|
11
9
|
|
|
12
10
|
def compute_package(package)
|
|
13
|
-
compute_from_price(total(package.contents))
|
|
11
|
+
compute_from_price(total(package.contents), package.currency)
|
|
14
12
|
end
|
|
15
13
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
# @param price [BigDecimal]
|
|
15
|
+
# @param currency [String, nil] the amount is rounded to its minor unit
|
|
16
|
+
# @return [BigDecimal]
|
|
17
|
+
def compute_from_price(price, currency = nil)
|
|
18
|
+
Spree::Money::Rounding.to_currency(price * BigDecimal(preferred_flat_percent.to_s) / 100, currency)
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
end
|