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
|
@@ -30,40 +30,43 @@ module Spree
|
|
|
30
30
|
sweep_internal_duty_tax_lines(owner) if full_estimate
|
|
31
31
|
return if items.empty?
|
|
32
32
|
|
|
33
|
-
rates =
|
|
33
|
+
rates = rates_for(owner)
|
|
34
34
|
|
|
35
35
|
items.group_by(&:class).each do |klass, group|
|
|
36
36
|
# Replace-all set semantics per estimate: stale lines die with the
|
|
37
37
|
# address/items change that triggered the re-estimate.
|
|
38
|
-
Spree::TaxLine.where(
|
|
38
|
+
Spree::TaxLine.where(Spree::TaxLine.adjustable_key_for(klass) => group.map(&:id)).delete_all
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
+
items.each { |item| tax_item(owner, item, rates, exemptions) }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Gives back each credited unit's share of the TaxLines the sale actually
|
|
45
|
+
# charged (see Spree::TaxProvider::RecordedShare). TaxRate rows are not
|
|
46
|
+
# versioned, so this engine can never recompute at a past date —
|
|
47
|
+
# +tax_date+ is accepted and ignored, and the rows are the only record
|
|
48
|
+
# of what the customer paid.
|
|
49
|
+
def estimate_refund(order, items, amounts: nil, tax_date: nil)
|
|
50
|
+
Spree::TaxProvider::RecordedShare.new(order: order, items: items, amounts: amounts).call
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Taxed like any line of the order, under the replacement's own category
|
|
54
|
+
# and at the order's tax address. +tax_date+ is ignored for the reason
|
|
55
|
+
# given on #estimate.
|
|
56
|
+
#
|
|
57
|
+
# An exemption is read as it applied to the line being replaced: a
|
|
58
|
+
# certificate's per-line carve-out names that line, and the exchange line
|
|
59
|
+
# replacing it would otherwise match no carve-out and come out exempt.
|
|
60
|
+
def estimate_replacement(order, items, tax_date: nil, tax_identifier: nil, exemptions: [])
|
|
61
|
+
return if items.empty?
|
|
62
|
+
|
|
63
|
+
Spree::TaxLine.charges.where(exchange_line_item_id: items.map(&:id)).delete_all
|
|
64
|
+
|
|
65
|
+
rates = rates_for(order)
|
|
41
66
|
items.each do |item|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
# A matched rate always produces a row, zero-amount ones included:
|
|
45
|
-
# a zero rate is a treatment ("this is zero-rated"), which reporting
|
|
46
|
-
# and e-invoicing both need to see. No matched rate means no opinion,
|
|
47
|
-
# and writes nothing.
|
|
48
|
-
taxed = relevant_rates.map do |rate|
|
|
49
|
-
jurisdiction = jurisdiction_for(rate, owner)
|
|
50
|
-
[rate, jurisdiction, exemption_for(item, exemptions, jurisdiction)]
|
|
51
|
-
end
|
|
67
|
+
next unless item.credited_quantity.to_i.positive?
|
|
52
68
|
|
|
53
|
-
|
|
54
|
-
# basis is pre-tax.
|
|
55
|
-
exempt_rates = taxed.select { |_rate, _jurisdiction, exemption| exemption }.map(&:first)
|
|
56
|
-
store_pre_tax_amount(item, relevant_rates - exempt_rates)
|
|
57
|
-
|
|
58
|
-
taxed.each do |rate, jurisdiction, exemption|
|
|
59
|
-
if exemption
|
|
60
|
-
write_tax_line(owner, item, rate, 0, 'customer_exempt', jurisdiction,
|
|
61
|
-
data: exemption_data(exemption, item))
|
|
62
|
-
else
|
|
63
|
-
write_tax_line(owner, item, rate, compute_tax(rate, item, relevant_rates),
|
|
64
|
-
reason_for(rate), jurisdiction)
|
|
65
|
-
end
|
|
66
|
-
end
|
|
69
|
+
tax_item(order, item, rates, exemptions, exemption_subject: item.line_item || item)
|
|
67
70
|
end
|
|
68
71
|
end
|
|
69
72
|
|
|
@@ -107,12 +110,35 @@ module Spree
|
|
|
107
110
|
owner.tax_lines.where(fee_id: duty_fee_ids, provider_id: 'internal').delete_all
|
|
108
111
|
end
|
|
109
112
|
|
|
110
|
-
def
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
def rates_for(owner)
|
|
114
|
+
Spree::TaxRate.for_store(owner.store).for_jurisdiction(owner.tax_country&.iso, owner.tax_address&.state_code).to_a
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def tax_item(owner, item, rates, exemptions, exemption_subject: item)
|
|
118
|
+
relevant_rates = rates.select { |rate| rate.tax_category_id == tax_category_id_for(item, owner) }
|
|
119
|
+
|
|
120
|
+
# A matched rate always produces a row, zero-amount ones included:
|
|
121
|
+
# a zero rate is a treatment ("this is zero-rated"), which reporting
|
|
122
|
+
# and e-invoicing both need to see. No matched rate means no opinion,
|
|
123
|
+
# and writes nothing.
|
|
124
|
+
taxed = relevant_rates.map do |rate|
|
|
125
|
+
jurisdiction = jurisdiction_for(rate, owner)
|
|
126
|
+
[rate, jurisdiction, exemption_for(exemption_subject, exemptions, jurisdiction)]
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# An exempt item has no tax to back out of its price, so its whole
|
|
130
|
+
# basis is pre-tax.
|
|
131
|
+
exempt_rates = taxed.select { |_rate, _jurisdiction, exemption| exemption }.map(&:first)
|
|
132
|
+
store_pre_tax_amount(item, relevant_rates - exempt_rates)
|
|
133
|
+
|
|
134
|
+
taxed.each do |rate, jurisdiction, exemption|
|
|
135
|
+
if exemption
|
|
136
|
+
write_tax_line(owner, item, rate, 0, 'customer_exempt', jurisdiction,
|
|
137
|
+
data: exemption_data(exemption, exemption_subject))
|
|
138
|
+
else
|
|
139
|
+
write_tax_line(owner, item, rate, compute_tax(rate, item, relevant_rates, owner.currency),
|
|
140
|
+
reason_for(rate), jurisdiction)
|
|
141
|
+
end
|
|
116
142
|
end
|
|
117
143
|
end
|
|
118
144
|
|
|
@@ -143,16 +169,19 @@ module Spree
|
|
|
143
169
|
item.respond_to?(:taxable_basis) ? item.taxable_basis : item.amount
|
|
144
170
|
end
|
|
145
171
|
|
|
146
|
-
|
|
172
|
+
# Rounded per line, half up, to the order's currency — the internal
|
|
173
|
+
# provider's choice; other providers round as their jurisdictions require.
|
|
174
|
+
def compute_tax(rate, item, relevant_rates, currency)
|
|
147
175
|
basis = taxable_basis(item)
|
|
148
176
|
|
|
149
|
-
if rate.included_in_price
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
177
|
+
tax = if rate.included_in_price
|
|
178
|
+
included_sum = relevant_rates.select(&:included_in_price).sum(&:amount)
|
|
179
|
+
included_sum = rate.amount if included_sum.zero?
|
|
180
|
+
basis / (1 + included_sum) * rate.amount
|
|
181
|
+
else
|
|
182
|
+
basis * rate.amount
|
|
183
|
+
end
|
|
184
|
+
Spree::Money::Rounding.to_currency(tax, currency)
|
|
156
185
|
end
|
|
157
186
|
|
|
158
187
|
# Pre-tax amounts are stored so refund and reporting code can weigh
|
|
@@ -209,7 +238,7 @@ module Spree
|
|
|
209
238
|
owner_key = owner.is_a?(Spree::Order) ? :order : :cart
|
|
210
239
|
Spree::TaxLine.create!(
|
|
211
240
|
{
|
|
212
|
-
|
|
241
|
+
Spree::TaxLine.adjustable_key_for(item.class) => item.id,
|
|
213
242
|
owner_key => owner,
|
|
214
243
|
tax_rate: rate,
|
|
215
244
|
amount: amount,
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module TaxProvider
|
|
3
|
+
# The tax a return, claim or exchange line gives back, read off what the
|
|
4
|
+
# sale actually charged: each credited unit's part of every TaxLine on the
|
|
5
|
+
# original line, row by row.
|
|
6
|
+
#
|
|
7
|
+
# Read from the rows rather than recomputed from TaxRate configuration,
|
|
8
|
+
# because the customer is owed back what they paid. Rates are not
|
|
9
|
+
# versioned, so a rate edited since the sale would otherwise refund a
|
|
10
|
+
# figure nobody was charged.
|
|
11
|
+
#
|
|
12
|
+
# Internal's answer to +estimate_refund+. Public so any other provider can
|
|
13
|
+
# answer with it too, rather than quoting the credit itself.
|
|
14
|
+
class RecordedShare
|
|
15
|
+
# @param order [Spree::Order]
|
|
16
|
+
# @param items [Array<Spree::ReturnLineItem, Spree::ClaimLineItem, Spree::ExchangeLineItem>]
|
|
17
|
+
# @param amounts [Hash{Object => BigDecimal}, nil] see Base#estimate_refund
|
|
18
|
+
def initialize(order:, items:, amounts: nil)
|
|
19
|
+
@order = order
|
|
20
|
+
@items = Array(items)
|
|
21
|
+
@amounts = amounts
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Replaces each item's credit rows.
|
|
25
|
+
#
|
|
26
|
+
# @return [void]
|
|
27
|
+
def call
|
|
28
|
+
items.each do |item|
|
|
29
|
+
key = Spree::TaxLine.adjustable_key_for(item.class)
|
|
30
|
+
Spree::TaxLine.credits.where(key => item.id).delete_all
|
|
31
|
+
credit(item, key)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
attr_reader :order, :items, :amounts
|
|
38
|
+
|
|
39
|
+
def credit(item, key)
|
|
40
|
+
share = share_of_line(item)
|
|
41
|
+
return if share.zero?
|
|
42
|
+
|
|
43
|
+
sources = item.line_item.tax_lines.order(:id).to_a
|
|
44
|
+
sources.zip(shares_for(sources, share)).each do |source, cents|
|
|
45
|
+
write(item, key, source, cents)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# The part of the original line this item gives back: its credited units,
|
|
50
|
+
# and of those only the share of their worth the customer is refunded.
|
|
51
|
+
def share_of_line(item)
|
|
52
|
+
quantity = item.line_item&.quantity.to_i
|
|
53
|
+
units = item.credited_quantity.to_i
|
|
54
|
+
return 0 if quantity.zero? || units.zero?
|
|
55
|
+
|
|
56
|
+
Rational(units, quantity) * refunded_fraction(item)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def refunded_fraction(item)
|
|
60
|
+
return 1 if amounts.nil?
|
|
61
|
+
|
|
62
|
+
worth = item.credited_worth.to_d
|
|
63
|
+
return 0 unless worth.positive?
|
|
64
|
+
|
|
65
|
+
[Rational(amounts.fetch(item, 0).to_d) / Rational(worth), 1].min
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# The rows' total share rounded once, then divided by largest remainder,
|
|
69
|
+
# so the credit lands on the cent and each rate keeps its part of it. A
|
|
70
|
+
# row is never credited past what earlier credits left of it, or three
|
|
71
|
+
# single-unit returns of one line could give back a cent more than it
|
|
72
|
+
# charged.
|
|
73
|
+
def shares_for(sources, share)
|
|
74
|
+
weights = sources.map { |source| to_minor_units(source.amount) }
|
|
75
|
+
return Array.new(sources.size, 0) if weights.sum.zero?
|
|
76
|
+
|
|
77
|
+
target = (Rational(weights.sum) * share).round(half: :up)
|
|
78
|
+
allotted = Spree::Adjusters::LargestRemainder.largest_remainder_shares(target, weights)
|
|
79
|
+
|
|
80
|
+
sources.zip(allotted).map { |source, cents| [cents, remaining_cents(source)].min }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def remaining_cents(source)
|
|
84
|
+
credited = Spree::TaxLine.credits.where(original_tax_line_id: source.id).sum(:amount)
|
|
85
|
+
[to_minor_units(source.amount) - to_minor_units(credited), 0].max
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Zero-amount rows are copied too: a zero-rated or exempt sale is a
|
|
89
|
+
# treatment the credit note has to repeat.
|
|
90
|
+
def write(item, key, source, cents)
|
|
91
|
+
Spree::TaxLine.create!(
|
|
92
|
+
key => item.id,
|
|
93
|
+
order: order,
|
|
94
|
+
credit: true,
|
|
95
|
+
original_tax_line: source,
|
|
96
|
+
tax_rate_id: source.tax_rate_id,
|
|
97
|
+
amount: Spree::Money::Rounding.from_minor_units(cents, order.currency),
|
|
98
|
+
rate: source.rate,
|
|
99
|
+
label: source.label,
|
|
100
|
+
included: source.included,
|
|
101
|
+
provider_id: source.provider_id,
|
|
102
|
+
taxability_reason: source.taxability_reason,
|
|
103
|
+
country_code: source.country_code,
|
|
104
|
+
state_code: source.state_code,
|
|
105
|
+
data: source.data
|
|
106
|
+
)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def to_minor_units(amount)
|
|
110
|
+
Spree::Money::Rounding.to_minor_units(amount, order.currency)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -15,6 +15,10 @@ module Spree
|
|
|
15
15
|
include Spree::HasCustomFields
|
|
16
16
|
include Spree::Metadata
|
|
17
17
|
include Spree::SingleStoreResource
|
|
18
|
+
include Spree::CanonicalDecimals
|
|
19
|
+
|
|
20
|
+
# A fraction (0.07125), held to the column's five decimals.
|
|
21
|
+
canonical_decimals :amount, places: 5
|
|
18
22
|
|
|
19
23
|
with_options inverse_of: :tax_rates do
|
|
20
24
|
belongs_to :tax_category,
|
|
@@ -56,17 +60,36 @@ module Spree
|
|
|
56
60
|
->(category) { where(tax_category_id: category.try(:id)) }
|
|
57
61
|
scope :included_in_price, -> { where(included_in_price: true) }
|
|
58
62
|
|
|
59
|
-
self.whitelisted_ransackable_attributes = %w[amount country_code state_code tax_category_id included_in_price name]
|
|
63
|
+
self.whitelisted_ransackable_attributes = %w[amount rate country_code state_code tax_category_id included_in_price name]
|
|
64
|
+
ransack_alias :rate, :amount
|
|
65
|
+
search_by :name
|
|
60
66
|
|
|
61
|
-
#
|
|
62
|
-
|
|
63
|
-
|
|
67
|
+
# The rate as a fraction (0.23); the column is +amount+.
|
|
68
|
+
alias_attribute :rate, :amount
|
|
69
|
+
|
|
70
|
+
# The rate as a percentage (23), what a merchant types.
|
|
71
|
+
#
|
|
72
|
+
# @return [BigDecimal, nil]
|
|
73
|
+
def rate_percent
|
|
74
|
+
amount && amount * 100
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# @param value [String, Numeric, nil]
|
|
78
|
+
def rate_percent=(value)
|
|
79
|
+
parsed = Spree::Money::Rounding.parse_decimal(value)
|
|
80
|
+
self.amount = parsed && parsed / 100
|
|
81
|
+
end
|
|
64
82
|
|
|
65
|
-
|
|
83
|
+
# @deprecated Use {#rate_percent}; removed in 6.1.
|
|
84
|
+
def amount_percentage
|
|
85
|
+
Spree::Deprecation.warn('Spree::TaxRate#amount_percentage is deprecated and will be removed in Spree 6.1. Use #rate_percent instead.')
|
|
86
|
+
rate_percent
|
|
66
87
|
end
|
|
67
88
|
|
|
89
|
+
# @deprecated Use {#rate_percent=}; removed in 6.1.
|
|
68
90
|
def amount_percentage=(value)
|
|
69
|
-
|
|
91
|
+
Spree::Deprecation.warn('Spree::TaxRate#amount_percentage= is deprecated and will be removed in Spree 6.1. Use #rate_percent= instead.')
|
|
92
|
+
self.rate_percent = value
|
|
70
93
|
end
|
|
71
94
|
|
|
72
95
|
# The included-in-price rate to back out of a gross price.
|
|
@@ -100,8 +123,7 @@ module Spree
|
|
|
100
123
|
#
|
|
101
124
|
# @return [String]
|
|
102
125
|
def adjustment_label
|
|
103
|
-
|
|
104
|
-
scope: 'adjustment_labels.tax_rates',
|
|
126
|
+
I18n.t "spree.adjustment_labels.tax_rates.#{included_in_price? ? :including_tax : :excluding_tax}",
|
|
105
127
|
name: name.presence || tax_category.name,
|
|
106
128
|
amount: amount_for_label
|
|
107
129
|
end
|
data/app/models/spree/taxon.rb
CHANGED
|
@@ -10,7 +10,7 @@ module Spree
|
|
|
10
10
|
# prefix (ctg), and model_name are all Spree::Category. Only the constant differs,
|
|
11
11
|
# so is_a?, STI, polymorphic *_type strings, and class_name: references keep
|
|
12
12
|
# resolving to Spree::Category. The warning below fires through Spree's deprecator
|
|
13
|
-
# when this file loads (
|
|
13
|
+
# when this file loads (on first reference; it is kept out of eager loading).
|
|
14
14
|
Taxon = Category
|
|
15
15
|
|
|
16
16
|
Spree::Deprecation.warn('Spree::Taxon is deprecated and will be removed in Spree 6.1. Use Spree::Category instead.')
|
|
@@ -12,7 +12,6 @@ module Spree
|
|
|
12
12
|
|
|
13
13
|
has_many :taxons, class_name: 'Spree::Category', inverse_of: :taxonomy
|
|
14
14
|
has_one :root, -> { where parent_id: nil }, class_name: 'Spree::Category', dependent: :destroy
|
|
15
|
-
belongs_to :store, class_name: 'Spree::Store'
|
|
16
15
|
|
|
17
16
|
default_scope { order("#{table_name}.position, #{table_name}.created_at") }
|
|
18
17
|
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
# What {Spree::UpdateCheck} last learned about newer releases. Never
|
|
3
|
+
# persisted.
|
|
4
|
+
class UpdateStatus
|
|
5
|
+
include ActiveModel::Model
|
|
6
|
+
include ActiveModel::Attributes
|
|
7
|
+
|
|
8
|
+
attribute :current_version, :string
|
|
9
|
+
attribute :enabled, :boolean, default: true
|
|
10
|
+
# Nil until the first check has finished.
|
|
11
|
+
attribute :checked_at, :datetime
|
|
12
|
+
# Newer releases, newest first: hashes with +version+, +url+ and +published_at+.
|
|
13
|
+
attribute :releases, default: -> { [] }
|
|
14
|
+
|
|
15
|
+
# @return [Hash, nil]
|
|
16
|
+
def latest_release
|
|
17
|
+
releases.first
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @return [Boolean]
|
|
21
|
+
def update_available?
|
|
22
|
+
latest_release.present?
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
data/app/models/spree/variant.rb
CHANGED
|
@@ -8,6 +8,7 @@ module Spree
|
|
|
8
8
|
include Spree::MemoizedData
|
|
9
9
|
include Spree::HasCustomFields
|
|
10
10
|
include Spree::Metadata
|
|
11
|
+
include Spree::CanonicalDecimals
|
|
11
12
|
include Spree::HasExternalReferences
|
|
12
13
|
include Spree::Searchable
|
|
13
14
|
include Spree::StorePreferences
|
|
@@ -231,6 +232,10 @@ module Spree
|
|
|
231
232
|
|
|
232
233
|
scope :not_deleted, -> { where("#{Spree::Variant.quoted_table_name}.deleted_at IS NULL") }
|
|
233
234
|
|
|
235
|
+
# The variants of a product relation, as a subquery. Unordered, because a
|
|
236
|
+
# listing's sort would otherwise land inside the IN.
|
|
237
|
+
scope :for_products, ->(products) { where(product_id: products.reorder(nil).select(Spree::Product.arel_table[:id])) }
|
|
238
|
+
|
|
234
239
|
# Variants the shop sells in this currency. Base prices only: a price
|
|
235
240
|
# list's rows are what one audience pays under an agreement, and a ladder
|
|
236
241
|
# holds several rows for one variant, so a contract-only variant is not
|
|
@@ -285,17 +290,9 @@ module Spree
|
|
|
285
290
|
relation.where(conditions.reduce(:or)).distinct
|
|
286
291
|
end
|
|
287
292
|
|
|
288
|
-
#
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
# FIXME: cost price should be represented with DisplayMoney class
|
|
292
|
-
LOCALIZED_NUMBERS = %w(cost_price weight depth width height)
|
|
293
|
-
|
|
294
|
-
LOCALIZED_NUMBERS.each do |m|
|
|
295
|
-
define_method("#{m}=") do |argument|
|
|
296
|
-
self[m] = Spree::LocalizedNumber.parse(argument) if argument.present?
|
|
297
|
-
end
|
|
298
|
-
end
|
|
293
|
+
# A blank value leaves these as they were rather than clearing them.
|
|
294
|
+
canonical_decimals :cost_price, places: Spree::Money::Rounding::UNIT_PRICE_DECIMALS, ignore_blank: true
|
|
295
|
+
canonical_decimals :weight, :depth, :width, :height, places: nil, ignore_blank: true
|
|
299
296
|
|
|
300
297
|
accepts_nested_attributes_for(
|
|
301
298
|
:stock_levels,
|
|
@@ -330,6 +327,7 @@ module Spree
|
|
|
330
327
|
carton_package_type_id carton_weight cartons_per_pallet]
|
|
331
328
|
self.whitelisted_ransackable_scopes = %i(product_name_or_sku_cont search_by_product_name_or_sku search
|
|
332
329
|
available_at_stock_location)
|
|
330
|
+
self.ransackable_scope_types = { 'available_at_stock_location' => 'id' }
|
|
333
331
|
self.private_ransackable_attributes = { store: %w[cost_price cost_currency deleted_at] }
|
|
334
332
|
|
|
335
333
|
def self.product_name_or_sku_cont(query)
|
|
@@ -665,7 +663,7 @@ module Spree
|
|
|
665
663
|
def price_in(currency)
|
|
666
664
|
currency = currency&.upcase
|
|
667
665
|
|
|
668
|
-
price = if prices
|
|
666
|
+
price = if read_association_in_memory?(:prices) && prices.any?
|
|
669
667
|
prices.detect { |p| p.currency == currency && p.price_list_id.nil? }
|
|
670
668
|
else
|
|
671
669
|
prices.base_prices.find_by(currency: currency)
|
|
@@ -35,7 +35,7 @@ module Spree
|
|
|
35
35
|
return if asset.product&.id == variant.product_id
|
|
36
36
|
|
|
37
37
|
errors.add(:asset, :variant_product_mismatch,
|
|
38
|
-
message:
|
|
38
|
+
message: I18n.t('spree.errors.messages.variant_product_mismatch'))
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
def refresh_variant_thumbnail
|
|
@@ -25,6 +25,7 @@ module Spree
|
|
|
25
25
|
|
|
26
26
|
# Ransack configuration
|
|
27
27
|
self.whitelisted_ransackable_attributes = %w[event_name response_code execution_time success delivered_at created_at]
|
|
28
|
+
search_by :event_name
|
|
28
29
|
|
|
29
30
|
# Event subjects whose permission is not found by class name alone.
|
|
30
31
|
PAYLOAD_SUBJECT_CLASSES = {
|
|
@@ -13,7 +13,6 @@ module Spree
|
|
|
13
13
|
|
|
14
14
|
encrypts :secret_key, deterministic: true if Rails.configuration.active_record.encryption.include?(:primary_key)
|
|
15
15
|
|
|
16
|
-
belongs_to :store, class_name: 'Spree::Store'
|
|
17
16
|
has_many :webhook_deliveries, class_name: 'Spree::WebhookDelivery', dependent: :destroy_async
|
|
18
17
|
|
|
19
18
|
validates :url, presence: true
|
|
@@ -31,11 +30,45 @@ module Spree
|
|
|
31
30
|
before_save :clear_disabled_state_when_reactivated
|
|
32
31
|
|
|
33
32
|
self.whitelisted_ransackable_attributes = %w[name url active]
|
|
33
|
+
search_by :url
|
|
34
34
|
|
|
35
35
|
scope :active, -> { where(active: true) }
|
|
36
36
|
scope :inactive, -> { where(active: false) }
|
|
37
37
|
scope :enabled, -> { active.where(disabled_at: nil) }
|
|
38
38
|
|
|
39
|
+
# Whether +url+ targets a host the operator trusts despite it resolving to
|
|
40
|
+
# a private address — e.g. a storefront Service in the same Kubernetes
|
|
41
|
+
# cluster. Set via +Spree::Api::Config.webhooks_allowed_internal_hosts+
|
|
42
|
+
# (SPREE_WEBHOOKS_ALLOWED_INTERNAL_HOSTS): an entry matches that exact
|
|
43
|
+
# host, or with a leading dot (".svc.cluster.local") any host under it.
|
|
44
|
+
# Matching is case-insensitive. Operator config only, so a store admin
|
|
45
|
+
# can never point a webhook at an internal address.
|
|
46
|
+
#
|
|
47
|
+
# @param url [String]
|
|
48
|
+
# @return [Boolean]
|
|
49
|
+
def self.allowed_internal_host?(url)
|
|
50
|
+
host = URI.parse(url.to_s).hostname&.downcase
|
|
51
|
+
return false if host.blank?
|
|
52
|
+
|
|
53
|
+
allowed_internal_hosts.any? do |entry|
|
|
54
|
+
entry.start_with?('.') ? host.end_with?(entry) : host == entry
|
|
55
|
+
end
|
|
56
|
+
rescue URI::InvalidURIError
|
|
57
|
+
false
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Spree::Api::Config only exists when spree_api is loaded; without it no
|
|
61
|
+
# webhook is ever delivered, so there is nothing to allow.
|
|
62
|
+
def self.allowed_internal_hosts
|
|
63
|
+
return [] unless defined?(Spree::Api::Config)
|
|
64
|
+
|
|
65
|
+
Array(Spree::Api::Config.webhooks_allowed_internal_hosts).filter_map do |entry|
|
|
66
|
+
entry = entry.to_s.strip.downcase
|
|
67
|
+
entry if entry.present? && entry != '.'
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
private_class_method :allowed_internal_hosts
|
|
71
|
+
|
|
39
72
|
# Returns the plaintext `secret_key` only on the create response.
|
|
40
73
|
#
|
|
41
74
|
# `@reveal_secret_in_response` is set by the `after_create` callback above
|
|
@@ -50,18 +83,16 @@ module Spree
|
|
|
50
83
|
# Number of consecutive failed deliveries before auto-disabling
|
|
51
84
|
AUTO_DISABLE_THRESHOLD = 15
|
|
52
85
|
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
# Check if this endpoint is subscribed to a specific event
|
|
86
|
+
# Check if this endpoint is subscribed to a specific event.
|
|
87
|
+
#
|
|
88
|
+
# Events whose payload carries a live credential (a password reset token)
|
|
89
|
+
# reach only an endpoint that names them — never through `*` or a pattern —
|
|
90
|
+
# since receiving one is as good as holding the account.
|
|
60
91
|
#
|
|
61
92
|
# @param event_name [String] the event name to check
|
|
62
93
|
# @return [Boolean]
|
|
63
94
|
def subscribed_to?(event_name)
|
|
64
|
-
return subscriptions.to_a.include?(event_name) if
|
|
95
|
+
return subscriptions.to_a.include?(event_name) if Spree::Events.catalog.credential?(event_name)
|
|
65
96
|
return true if subscriptions.blank? || subscriptions.include?('*')
|
|
66
97
|
|
|
67
98
|
subscriptions.any? do |subscription|
|
|
@@ -74,11 +105,11 @@ module Spree
|
|
|
74
105
|
end
|
|
75
106
|
end
|
|
76
107
|
|
|
77
|
-
#
|
|
108
|
+
# The credential-carrying events this endpoint names.
|
|
78
109
|
#
|
|
79
|
-
# @return [
|
|
80
|
-
def
|
|
81
|
-
(subscriptions.to_a
|
|
110
|
+
# @return [Array<Spree::Events::Catalog::Entry>]
|
|
111
|
+
def credential_subscriptions
|
|
112
|
+
Spree::Events.catalog.credential_events(subscriptions.to_a)
|
|
82
113
|
end
|
|
83
114
|
|
|
84
115
|
# Returns all events this endpoint is subscribed to
|
|
@@ -168,6 +199,8 @@ module Spree
|
|
|
168
199
|
end
|
|
169
200
|
|
|
170
201
|
def url_must_not_resolve_to_private_ip
|
|
202
|
+
return if self.class.allowed_internal_host?(url)
|
|
203
|
+
|
|
171
204
|
uri = URI.parse(url)
|
|
172
205
|
blacklist = SsrfFilter::IPV4_BLACKLIST + SsrfFilter::IPV6_BLACKLIST
|
|
173
206
|
addresses = Resolv.getaddresses(uri.host)
|
|
@@ -6,15 +6,10 @@ module Spree
|
|
|
6
6
|
|
|
7
7
|
publishes_lifecycle_events
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
has_secure_token on: :save
|
|
11
|
-
else
|
|
12
|
-
has_secure_token
|
|
13
|
-
end
|
|
9
|
+
has_secure_token on: :save
|
|
14
10
|
|
|
15
11
|
belongs_to :customer, class_name: "::#{Spree.customer_class}", touch: true
|
|
16
12
|
include Spree::DeprecatedCustomerAlias
|
|
17
|
-
belongs_to :store, class_name: 'Spree::Store'
|
|
18
13
|
|
|
19
14
|
has_many :wishlist_items, class_name: 'Spree::WishlistItem', dependent: :destroy
|
|
20
15
|
has_many :wished_items, class_name: 'Spree::WishlistItem', inverse_of: :wishlist, deprecated: true
|
|
@@ -11,6 +11,12 @@ module Spree
|
|
|
11
11
|
LEGACY_EVENT_PREFIX = 'wished_item'
|
|
12
12
|
LEGACY_EVENT_SUFFIXES = %w[created updated deleted].freeze
|
|
13
13
|
|
|
14
|
+
def self.included(base)
|
|
15
|
+
LEGACY_EVENT_SUFFIXES.each do |suffix|
|
|
16
|
+
base.publishes_event("#{LEGACY_EVENT_PREFIX}.#{suffix}", deprecated_alias_of: "#{base.event_prefix}.#{suffix}")
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
14
20
|
# Wraps publishing rather than the three callbacks that call it, so the
|
|
15
21
|
# decisions Publishable makes — whether events are enabled at all,
|
|
16
22
|
# whether a touch-only update is worth announcing — are made once and the
|
|
@@ -35,7 +35,7 @@ module Spree
|
|
|
35
35
|
customer.first_name,
|
|
36
36
|
customer.last_name,
|
|
37
37
|
customer.email,
|
|
38
|
-
customer.accepts_email_marketing ?
|
|
38
|
+
customer.accepts_email_marketing ? I18n.t('spree.say_yes') : I18n.t('spree.say_no'),
|
|
39
39
|
customer.address&.company,
|
|
40
40
|
customer.address&.address1,
|
|
41
41
|
customer.address&.address2,
|
|
@@ -25,7 +25,7 @@ module Spree
|
|
|
25
25
|
newsletter_subscriber.email,
|
|
26
26
|
newsletter_subscriber.customer&.full_name,
|
|
27
27
|
newsletter_subscriber.customer_id,
|
|
28
|
-
newsletter_subscriber.verified? ?
|
|
28
|
+
newsletter_subscriber.verified? ? I18n.t('spree.say_yes') : I18n.t('spree.say_no'),
|
|
29
29
|
newsletter_subscriber.verified_at,
|
|
30
30
|
newsletter_subscriber.created_at,
|
|
31
31
|
newsletter_subscriber.updated_at
|
|
@@ -96,16 +96,16 @@ module Spree
|
|
|
96
96
|
index.zero? ? order.email : nil,
|
|
97
97
|
index.zero? ? order.status : nil,
|
|
98
98
|
index.zero? ? order.currency : nil,
|
|
99
|
-
index.zero? ? order.item_total
|
|
100
|
-
index.zero? ? order.
|
|
101
|
-
index.zero? ? order.tax_total
|
|
99
|
+
index.zero? ? money(order.item_total) : nil,
|
|
100
|
+
index.zero? ? money(order.delivery_total) : nil,
|
|
101
|
+
index.zero? ? money(order.tax_total) : nil,
|
|
102
102
|
index.zero? ? order.included_tax_total.positive? : nil,
|
|
103
103
|
index.zero? ? (order.discount_total.negative? || line_item.discount_total.negative?) : nil,
|
|
104
104
|
index.zero? ? order.has_free_shipping? : nil,
|
|
105
|
-
index.zero? ? order.discount_total.abs : nil,
|
|
105
|
+
index.zero? ? money(order.discount_total.abs) : nil,
|
|
106
106
|
index.zero? ? order.promo_code : nil,
|
|
107
|
-
index.zero? ? order.payments.store_credits.sum(:amount).abs : nil,
|
|
108
|
-
index.zero? ? order.total
|
|
107
|
+
index.zero? ? money(order.payments.store_credits.sum(:amount).abs) : nil,
|
|
108
|
+
index.zero? ? money(order.total) : nil,
|
|
109
109
|
index.zero? ? order.delivery_method&.name : nil,
|
|
110
110
|
index.zero? ? order.total_weight.to_f : nil,
|
|
111
111
|
index.zero? ? order.payments.valid&.first&.display_source_name : nil,
|
|
@@ -160,6 +160,10 @@ module Spree
|
|
|
160
160
|
|
|
161
161
|
private
|
|
162
162
|
|
|
163
|
+
def money(amount)
|
|
164
|
+
Spree::Money::Rounding.format(amount, order.currency)
|
|
165
|
+
end
|
|
166
|
+
|
|
163
167
|
# Drops the cells whose headers were omitted, matching each value to its
|
|
164
168
|
# header by position so the row and the header list stay aligned.
|
|
165
169
|
def reject_omitted(csv)
|
|
@@ -26,15 +26,10 @@ module Spree
|
|
|
26
26
|
|
|
27
27
|
private
|
|
28
28
|
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
# localized because the file is read back by the import under the
|
|
32
|
-
# store's locale, not the exporting admin's, and a comma written here
|
|
33
|
-
# would re-import as a hundred times the price.
|
|
29
|
+
# A plain decimal ("18.00", never "$18.00" or "18,00"): the import reads
|
|
30
|
+
# the file back and accepts nothing else.
|
|
34
31
|
def amount_string(amount)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
format("%.#{Spree::Money::Rounding.precision(price.currency)}f", amount)
|
|
32
|
+
Spree::Money::Rounding.format(amount, price.currency, unit_price: true)
|
|
38
33
|
end
|
|
39
34
|
end
|
|
40
35
|
end
|