spree_core 6.0.0.beta4 → 6.0.0.rc1
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 +2 -2
- 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/seller_payouts/sweep_seller_job.rb +2 -0
- data/app/mailers/spree/admin_user_mailer.rb +4 -19
- data/app/mailers/spree/base_mailer.rb +96 -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 +13 -1
- data/app/models/concerns/spree/api_typed.rb +91 -0
- data/app/models/concerns/spree/calculator/tiers.rb +76 -0
- data/app/models/concerns/spree/customer_methods.rb +15 -14
- 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.rb +4 -12
- data/app/models/concerns/spree/product_scopes.rb +11 -1
- 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 +7 -3
- data/app/models/concerns/spree/purchase/purchase_order.rb +2 -2
- data/app/models/concerns/spree/purchase/totals.rb +49 -1
- 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/single_store_resource.rb +1 -1
- data/app/models/concerns/spree/stores/email_branding.rb +31 -0
- data/app/models/concerns/spree/type_labels.rb +38 -0
- data/app/models/concerns/spree/typed_adjustment_line.rb +1 -1
- data/app/models/concerns/spree/typed_associations.rb +6 -0
- data/app/models/spree/address.rb +11 -1
- data/app/models/spree/adjusters/promotion.rb +1 -15
- data/app/models/spree/allowed_origin.rb +0 -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 +70 -22
- data/app/models/spree/calculator/flat_percent_item_total.rb +1 -3
- data/app/models/spree/calculator/flat_rate.rb +1 -3
- data/app/models/spree/calculator/flexi_rate.rb +1 -3
- data/app/models/spree/calculator/percent_on_line_item.rb +1 -1
- data/app/models/spree/calculator/price_sack.rb +1 -3
- data/app/models/spree/calculator/shipping/digital_delivery.rb +1 -3
- data/app/models/spree/calculator/shipping/flat_percent_item_total.rb +1 -3
- data/app/models/spree/calculator/shipping/flat_rate.rb +1 -3
- data/app/models/spree/calculator/shipping/flexi_rate.rb +1 -3
- data/app/models/spree/calculator/shipping/per_item.rb +1 -3
- data/app/models/spree/calculator/shipping/price_sack.rb +1 -3
- data/app/models/spree/calculator/tiered_flat_rate.rb +6 -23
- data/app/models/spree/calculator/tiered_percent.rb +7 -25
- data/app/models/spree/cart.rb +78 -18
- data/app/models/spree/channel/gating.rb +1 -1
- data/app/models/spree/channel.rb +4 -4
- data/app/models/spree/claim.rb +21 -1
- data/app/models/spree/claim_line_item.rb +41 -6
- data/app/models/spree/collection.rb +0 -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 +2 -3
- 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/product_rule.rb +1 -1
- 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 +9 -23
- data/app/models/spree/custom_fields/boolean.rb +1 -1
- data/app/models/spree/custom_fields/json.rb +1 -1
- data/app/models/spree/data_feed/google.rb +0 -2
- data/app/models/spree/data_request.rb +1 -1
- data/app/models/spree/delivery_method.rb +30 -13
- data/app/models/spree/delivery_method_rule.rb +4 -11
- data/app/models/spree/delivery_rate.rb +4 -5
- data/app/models/spree/delivery_rate_provider/base.rb +1 -0
- data/app/models/spree/delivery_zone.rb +0 -1
- 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 +15 -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 +1 -1
- data/app/models/spree/fee.rb +4 -2
- data/app/models/spree/fulfillment.rb +53 -27
- data/app/models/spree/fulfillment_changer.rb +2 -2
- 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 +4 -0
- data/app/models/spree/gateway/custom_payment_source_method.rb +5 -0
- data/app/models/spree/gateway.rb +5 -1
- data/app/models/spree/gift_card.rb +12 -1
- data/app/models/spree/gift_card_batch.rb +0 -1
- data/app/models/spree/import.rb +2 -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 +8 -18
- data/app/models/spree/invitation.rb +5 -5
- data/app/models/spree/line_item.rb +13 -2
- data/app/models/spree/locale.rb +11 -8
- data/app/models/spree/market.rb +1 -0
- data/app/models/spree/newsletter_subscriber.rb +3 -0
- data/app/models/spree/order.rb +56 -138
- data/app/models/spree/order_group.rb +11 -2
- 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 +1 -2
- data/app/models/spree/payment/gateway_options.rb +14 -52
- data/app/models/spree/payment/processing.rb +2 -4
- data/app/models/spree/payment.rb +20 -5
- data/app/models/spree/payment_method/session_instrumentation.rb +1 -0
- data/app/models/spree/payment_method/store_credit.rb +4 -4
- data/app/models/spree/payment_method.rb +36 -4
- 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/price_list.rb +1 -2
- data/app/models/spree/price_rules/channel_rule.rb +1 -1
- data/app/models/spree/price_rules/customer_group_rule.rb +1 -1
- data/app/models/spree/product.rb +31 -7
- data/app/models/spree/product_submission.rb +0 -12
- data/app/models/spree/product_type.rb +0 -1
- data/app/models/spree/promotion/actions/create_line_items.rb +27 -17
- data/app/models/spree/promotion/rules/category.rb +0 -4
- data/app/models/spree/promotion/rules/channel.rb +0 -4
- data/app/models/spree/promotion/rules/country.rb +0 -4
- data/app/models/spree/promotion/rules/currency.rb +0 -4
- data/app/models/spree/promotion/rules/customer_group.rb +0 -4
- data/app/models/spree/promotion/rules/first_order.rb +0 -4
- data/app/models/spree/promotion/rules/item_total.rb +4 -8
- data/app/models/spree/promotion/rules/market.rb +0 -4
- data/app/models/spree/promotion/rules/one_use_per_user.rb +0 -4
- data/app/models/spree/promotion/rules/option_value.rb +0 -3
- data/app/models/spree/promotion/rules/product.rb +0 -4
- 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 +35 -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 +1 -0
- data/app/models/spree/quantity_rule.rb +1 -1
- data/app/models/spree/refund.rb +28 -3
- data/app/models/spree/return.rb +30 -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 +1 -1
- data/app/models/spree/seller.rb +2 -1
- data/app/models/spree/seller_payout.rb +7 -0
- data/app/models/spree/seller_requirement.rb +4 -12
- data/app/models/spree/seller_requirement_custom_field.rb +1 -2
- data/app/models/spree/seller_requirement_submission.rb +2 -2
- data/app/models/spree/seller_requirements/accept_terms.rb +2 -3
- data/app/models/spree/seller_transfer.rb +5 -0
- data/app/models/spree/shipping_label.rb +1 -0
- data/app/models/spree/stock/availability_validator.rb +2 -2
- 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 +1 -1
- 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 +4 -2
- data/app/models/spree/store.rb +33 -7
- data/app/models/spree/store_credit.rb +8 -9
- data/app/models/spree/store_credit_event.rb +5 -5
- data/app/models/spree/tax_exemption_certificate.rb +2 -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 +59 -33
- data/app/models/spree/tax_provider/recorded_share.rb +114 -0
- data/app/models/spree/tax_rate.rb +1 -2
- data/app/models/spree/taxonomy.rb +0 -1
- data/app/models/spree/variant.rb +4 -3
- data/app/models/spree/variant_media.rb +1 -1
- data/app/models/spree/webhook_endpoint.rb +10 -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 +1 -1
- data/app/presenters/spree/csv/product_variant_presenter.rb +42 -12
- 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 +19 -5
- 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 +65 -34
- data/app/services/spree/imports/row_processors/purchase_order.rb +13 -13
- data/app/services/spree/locales.rb +4 -5
- 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 +2 -1
- data/app/services/spree/orders/discounts/create.rb +7 -4
- 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/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/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 +15 -14
- data/app/subscribers/spree/seller_onboarding_subscriber.rb +1 -1
- 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 +51 -47
- 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/process.rb +2 -2
- 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/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/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 +5 -5
- 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/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/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/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 +37 -503
- 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/lib/generators/spree/dummy/dummy_generator.rb +0 -9
- data/lib/generators/spree/dummy/templates/rails/test.rb +7 -0
- data/lib/spree/checkout/default_requirements.rb +37 -13
- data/lib/spree/core/configuration.rb +1 -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 +48 -167
- data/lib/spree/core/permissions/default_catalog.rb +5 -0
- data/lib/spree/core/preferences/json_conversion.rb +182 -0
- data/lib/spree/core/preferences/masking.rb +4 -1
- data/lib/spree/core/preferences/preferable.rb +137 -83
- data/lib/spree/core/preferences/preferable_class_methods.rb +81 -146
- data/lib/spree/core/previews/import_preview.rb +10 -0
- data/lib/spree/core/tax/provider_error.rb +37 -0
- data/lib/spree/core/version.rb +1 -1
- data/lib/spree/core.rb +85 -370
- 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/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/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/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 +45 -0
- data/lib/tasks/categories_and_collections.rake +3 -3
- data/lib/tasks/core.rake +6 -4
- data/lib/tasks/media.rake +3 -3
- data/lib/tasks/package_types.rake +1 -1
- data/lib/tasks/preferences_json.rake +101 -0
- data/spec/fixtures/mailer_views/custom_views_probe_mailer/welcome_email.html.erb +4 -0
- metadata +188 -44
- 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
|
@@ -24,15 +24,15 @@ module Spree
|
|
|
24
24
|
def display_action
|
|
25
25
|
case action
|
|
26
26
|
when Spree::StoreCredit::CAPTURE_ACTION
|
|
27
|
-
|
|
27
|
+
I18n.t('spree.store_credit.captured')
|
|
28
28
|
when Spree::StoreCredit::AUTHORIZE_ACTION
|
|
29
|
-
|
|
29
|
+
I18n.t('spree.store_credit.authorized')
|
|
30
30
|
when Spree::StoreCredit::ALLOCATION_ACTION
|
|
31
|
-
|
|
31
|
+
I18n.t('spree.store_credit.allocated')
|
|
32
32
|
when Spree::StoreCredit::ELIGIBLE_ACTION
|
|
33
|
-
|
|
33
|
+
I18n.t('spree.store_credit.eligible')
|
|
34
34
|
when Spree::StoreCredit::VOID_ACTION, Spree::StoreCredit::CREDIT_ACTION
|
|
35
|
-
|
|
35
|
+
I18n.t('spree.store_credit.credit')
|
|
36
36
|
end
|
|
37
37
|
end
|
|
38
38
|
|
|
@@ -9,6 +9,8 @@ module Spree
|
|
|
9
9
|
# valid in one state and not the next is the ordinary case, and the country
|
|
10
10
|
# and state columns say exactly where it holds.
|
|
11
11
|
class TaxExemptionCertificate < Spree.base_class
|
|
12
|
+
publishes_event :verified
|
|
13
|
+
|
|
12
14
|
has_prefix_id :cert
|
|
13
15
|
|
|
14
16
|
include Spree::HasStatus
|
|
@@ -36,6 +36,8 @@ module Spree
|
|
|
36
36
|
# directly. It is a habit of the gems that use it, not a rule this model
|
|
37
37
|
# applies.
|
|
38
38
|
class TaxIdentifier < Spree.base_class
|
|
39
|
+
publishes_event :number_changed
|
|
40
|
+
|
|
39
41
|
has_prefix_id :txi
|
|
40
42
|
|
|
41
43
|
# Statuses the platform records rather than the buyer chooses: nil means
|
|
@@ -150,7 +152,7 @@ module Spree
|
|
|
150
152
|
# The kind as the dashboard shows it, falling back to the raw key so an
|
|
151
153
|
# extension's kind reads as itself rather than blank.
|
|
152
154
|
def kind_label
|
|
153
|
-
|
|
155
|
+
I18n.t("spree.tax_identifier_kinds.#{kind}", default: kind.to_s.humanize)
|
|
154
156
|
end
|
|
155
157
|
|
|
156
158
|
# The class registered for this kind, or nil when nothing is registered and
|
|
@@ -4,6 +4,11 @@ module Spree
|
|
|
4
4
|
# per estimate. Snapshot columns (+rate+, +label+, +provider_id+) keep rows
|
|
5
5
|
# self-describing after a TaxRate is deleted or when an external provider
|
|
6
6
|
# computed them.
|
|
7
|
+
#
|
|
8
|
+
# Rows on a return, claim or exchange line are post-sale: the tax given back
|
|
9
|
+
# (+credit+), or the tax on an exchange's replacement. They are owned by the
|
|
10
|
+
# order like every other row but never count towards its totals — the order
|
|
11
|
+
# reads only {.sale} rows.
|
|
7
12
|
class TaxLine < Spree.base_class
|
|
8
13
|
include Spree::TypedAdjustmentLine
|
|
9
14
|
|
|
@@ -35,6 +40,13 @@ module Spree
|
|
|
35
40
|
belongs_to :line_item, class_name: 'Spree::LineItem', optional: true
|
|
36
41
|
belongs_to :fulfillment, class_name: 'Spree::Fulfillment', optional: true
|
|
37
42
|
belongs_to :fee, class_name: 'Spree::Fee', optional: true
|
|
43
|
+
belongs_to :return_line_item, class_name: 'Spree::ReturnLineItem', optional: true
|
|
44
|
+
belongs_to :claim_line_item, class_name: 'Spree::ClaimLineItem', optional: true
|
|
45
|
+
belongs_to :exchange_line_item, class_name: 'Spree::ExchangeLineItem', optional: true
|
|
46
|
+
|
|
47
|
+
# The sale row a credit gives back — what caps repeated credits against one
|
|
48
|
+
# line, and the original a credit note refers to.
|
|
49
|
+
belongs_to :original_tax_line, class_name: 'Spree::TaxLine', optional: true
|
|
38
50
|
|
|
39
51
|
# Tax included in price vs additional. Per-row (mixed regimes on one order).
|
|
40
52
|
attribute :included, :boolean, default: false
|
|
@@ -46,20 +58,50 @@ module Spree
|
|
|
46
58
|
inclusion: { in: ->(tax_line) { tax_line.class.taxability_reasons } },
|
|
47
59
|
allow_nil: true
|
|
48
60
|
validate :exactly_one_adjustable
|
|
61
|
+
validate :post_sale_owned_by_order
|
|
49
62
|
|
|
50
63
|
# Tax reporting is the reason the treatment columns exist — "which country's
|
|
51
64
|
# tax was this, and which sales were reverse-charged" has to be answerable.
|
|
52
65
|
self.whitelisted_ransackable_attributes = %w[taxability_reason country_code state_code included provider_id]
|
|
53
66
|
|
|
67
|
+
POST_SALE_KEYS = %i[return_line_item_id claim_line_item_id exchange_line_item_id].freeze
|
|
68
|
+
|
|
69
|
+
ADJUSTABLE_KEYS = {
|
|
70
|
+
'Spree::LineItem' => :line_item_id,
|
|
71
|
+
'Spree::Fulfillment' => :fulfillment_id,
|
|
72
|
+
'Spree::Fee' => :fee_id,
|
|
73
|
+
'Spree::ReturnLineItem' => :return_line_item_id,
|
|
74
|
+
'Spree::ClaimLineItem' => :claim_line_item_id,
|
|
75
|
+
'Spree::ExchangeLineItem' => :exchange_line_item_id
|
|
76
|
+
}.freeze
|
|
77
|
+
|
|
54
78
|
scope :included_in_price, -> { where(included: true) }
|
|
55
79
|
scope :additional, -> { where(included: false) }
|
|
56
80
|
scope :for_line_items, -> { where.not(line_item_id: nil) }
|
|
57
81
|
scope :for_fulfillments, -> { where.not(fulfillment_id: nil) }
|
|
58
82
|
scope :for_fees, -> { where.not(fee_id: nil) }
|
|
83
|
+
scope :sale, -> { where(POST_SALE_KEYS.index_with(nil)) }
|
|
84
|
+
scope :post_sale, -> { where.not(POST_SALE_KEYS.index_with(nil)) }
|
|
85
|
+
scope :credits, -> { where(credit: true) }
|
|
86
|
+
scope :charges, -> { where(credit: false) }
|
|
87
|
+
|
|
88
|
+
# The foreign key a row uses to point at an item of this class.
|
|
89
|
+
#
|
|
90
|
+
# @param klass [Class]
|
|
91
|
+
# @return [Symbol]
|
|
92
|
+
# @raise [ArgumentError] for a class that is not taxed
|
|
93
|
+
def self.adjustable_key_for(klass)
|
|
94
|
+
ADJUSTABLE_KEYS.fetch(klass.name) { raise ArgumentError, "#{klass} is not taxable" }
|
|
95
|
+
end
|
|
59
96
|
|
|
60
|
-
# @return [Spree::LineItem, Spree::Fulfillment, Spree::Fee,
|
|
97
|
+
# @return [Spree::LineItem, Spree::Fulfillment, Spree::Fee, Spree::ReturnLineItem,
|
|
98
|
+
# Spree::ClaimLineItem, Spree::ExchangeLineItem, nil]
|
|
61
99
|
def adjustable
|
|
62
|
-
line_item || fulfillment || fee
|
|
100
|
+
line_item || fulfillment || fee || return_line_item || claim_line_item || exchange_line_item
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def post_sale?
|
|
104
|
+
POST_SALE_KEYS.any? { |key| self[key].present? }
|
|
63
105
|
end
|
|
64
106
|
|
|
65
107
|
def included?
|
|
@@ -73,7 +115,15 @@ module Spree
|
|
|
73
115
|
private
|
|
74
116
|
|
|
75
117
|
def exactly_one_adjustable
|
|
76
|
-
|
|
118
|
+
adjustables = [line_item, fulfillment, fee, return_line_item, claim_line_item, exchange_line_item]
|
|
119
|
+
errors.add(:base, :exactly_one_adjustable, message: I18n.t('spree.errors.messages.exactly_one_adjustable')) unless adjustables.compact.one?
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Returns, claims and exchanges only exist on placed orders.
|
|
123
|
+
def post_sale_owned_by_order
|
|
124
|
+
return unless post_sale? && order.nil?
|
|
125
|
+
|
|
126
|
+
errors.add(:base, :exactly_one_of_cart_or_order, message: I18n.t('spree.errors.messages.exactly_one_of_cart_or_order'))
|
|
77
127
|
end
|
|
78
128
|
end
|
|
79
129
|
end
|
|
@@ -3,12 +3,16 @@ module Spree
|
|
|
3
3
|
# Contract for tax providers — the only sanctioned writers of
|
|
4
4
|
# {Spree::TaxLine} rows. +estimate+ must use replace-all set semantics per
|
|
5
5
|
# target item (delete the item's stale lines, insert fresh ones); that is
|
|
6
|
-
# what keeps tax correct after address or item changes
|
|
7
|
-
#
|
|
6
|
+
# what keeps tax correct after address or item changes, and the post-sale
|
|
7
|
+
# +estimate_refund+ / +estimate_replacement+ follow the same rule.
|
|
8
|
+
# Commit/void/refund/commit_replacement are lifecycle no-ops for providers
|
|
9
|
+
# without a remote ledger.
|
|
8
10
|
#
|
|
9
11
|
# Providers are stateless and constructed without arguments, so anything
|
|
10
12
|
# request-specific arrives as an argument rather than through the instance.
|
|
11
13
|
class Base
|
|
14
|
+
extend Spree::ApiTyped
|
|
15
|
+
|
|
12
16
|
# Domains this provider cannot handle, so a merchant is warned when
|
|
13
17
|
# pairing it with a market instead of silently under-collecting. Declared
|
|
14
18
|
# on the class because core reads it while presenting the choice, before
|
|
@@ -47,8 +51,8 @@ module Spree
|
|
|
47
51
|
unsupported_capabilities.map do |capability|
|
|
48
52
|
{
|
|
49
53
|
key: capability.to_s,
|
|
50
|
-
label:
|
|
51
|
-
description:
|
|
54
|
+
label: I18n.t("spree.tax_capabilities.#{capability}.label", default: capability.to_s.humanize),
|
|
55
|
+
description: I18n.t("spree.tax_capabilities.#{capability}.description", default: nil)
|
|
52
56
|
}.compact
|
|
53
57
|
end
|
|
54
58
|
end
|
|
@@ -61,7 +65,7 @@ module Spree
|
|
|
61
65
|
# @return [Hash]
|
|
62
66
|
def self.to_api_hash(store)
|
|
63
67
|
{
|
|
64
|
-
id:
|
|
68
|
+
id: api_type,
|
|
65
69
|
name: display_name,
|
|
66
70
|
available: available_for_store?(store),
|
|
67
71
|
unsupported_capabilities: unsupported_capability_details,
|
|
@@ -128,19 +132,72 @@ module Spree
|
|
|
128
132
|
nil
|
|
129
133
|
end
|
|
130
134
|
|
|
135
|
+
# Works out the tax given back on lines of a return, claim or exchange,
|
|
136
|
+
# and writes it as credit TaxLine rows on them. The customer is refunded
|
|
137
|
+
# exactly what these rows hold, so this — not +refund+ — decides the
|
|
138
|
+
# figure.
|
|
139
|
+
#
|
|
140
|
+
# Each item credits its +credited_quantity+, and zero means no rows.
|
|
141
|
+
# Replace-all per item, as +estimate+: the rows are rewritten when the
|
|
142
|
+
# warehouse counts what arrived and again when the money goes back.
|
|
143
|
+
#
|
|
144
|
+
# A provider may quote the credit, for example at the original
|
|
145
|
+
# +tax_date+, or give back the recorded share of what the sale charged —
|
|
146
|
+
# {Spree::TaxProvider::RecordedShare}, which Internal uses. Either way
|
|
147
|
+
# it answers in the same rows.
|
|
148
|
+
#
|
|
149
|
+
# @param order [Spree::Order] a placed order
|
|
150
|
+
# @param items [Array<Spree::ReturnLineItem, Spree::ClaimLineItem, Spree::ExchangeLineItem>]
|
|
151
|
+
# @param amounts [Hash{Object => BigDecimal}, nil] the money refunded per
|
|
152
|
+
# item when it is less than the item's worth — a restocking fee, a
|
|
153
|
+
# part refund. Tax is then credited in the same proportion. Nil
|
|
154
|
+
# credits every item's full worth.
|
|
155
|
+
# @param tax_date [Time, nil] the date whose rates apply; nil = order.completed_at
|
|
156
|
+
# @return [void]
|
|
157
|
+
# @raise [Spree::Tax::ProviderError] when the credit could not be worked out
|
|
158
|
+
def estimate_refund(order, items, amounts: nil, tax_date: nil)
|
|
159
|
+
raise NotImplementedError, "Please implement 'estimate_refund' in your tax provider: #{self.class.name}"
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Taxes the replacement an exchange sends out, as a new sale, and writes
|
|
163
|
+
# it as charge TaxLine rows on the exchange lines. Never touches the
|
|
164
|
+
# order's own rows, which stay as the sale was placed. Replace-all per
|
|
165
|
+
# item; each item is taxed on its +taxable_basis+ under its
|
|
166
|
+
# +tax_category_id+, and one whose +credited_quantity+ is zero ships
|
|
167
|
+
# nothing and ends with no rows.
|
|
168
|
+
#
|
|
169
|
+
# @param order [Spree::Order] a placed order
|
|
170
|
+
# @param items [Array<Spree::ExchangeLineItem>]
|
|
171
|
+
# @param tax_date [Time, nil] date whose rates apply; nil = now
|
|
172
|
+
# @param tax_identifier [Spree::TaxIdentifier, nil] the order's frozen registration
|
|
173
|
+
# @param exemptions [Array] exemption evidence to apply
|
|
174
|
+
# @return [void]
|
|
175
|
+
# @raise [Spree::Tax::ProviderError] when the tax could not be worked out
|
|
176
|
+
def estimate_replacement(order, items, tax_date: nil, tax_identifier: nil, exemptions: [])
|
|
177
|
+
raise NotImplementedError, "Please implement 'estimate_replacement' in your tax provider: #{self.class.name}"
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Files an exchange's replacement as a sale. Called once the exchange is
|
|
181
|
+
# fulfilled; must be idempotent, keyed on the exchange. No-op for a
|
|
182
|
+
# provider without a remote ledger.
|
|
183
|
+
#
|
|
184
|
+
# @param order [Spree::Order]
|
|
185
|
+
# @param items [Array<Spree::ExchangeLineItem>]
|
|
186
|
+
# @return [void]
|
|
187
|
+
def commit_replacement(order, items); end
|
|
188
|
+
|
|
131
189
|
# Reports a partial credit against the committed document, keyed to the
|
|
132
|
-
# original transaction rather than voiding and re-committing it.
|
|
190
|
+
# original transaction rather than voiding and re-committing it. Called
|
|
191
|
+
# after the money has moved.
|
|
133
192
|
#
|
|
134
|
-
#
|
|
135
|
-
#
|
|
136
|
-
#
|
|
137
|
-
#
|
|
138
|
-
#
|
|
139
|
-
# return is marked refunded once, so nothing later corrects it. Where the
|
|
140
|
-
# amount is short, allocate proportionally.
|
|
193
|
+
# By then each item's credit rows hold what went back (see
|
|
194
|
+
# +estimate_refund+), and a provider must credit no more than they do:
|
|
195
|
+
# the customer was repaid that tax and no more. +amount+ is the money
|
|
196
|
+
# refunded, which may be less than the items are worth when an admin kept
|
|
197
|
+
# a restocking fee or refunded a goodwill figure of their own.
|
|
141
198
|
#
|
|
142
199
|
# @param order [Spree::Order]
|
|
143
|
-
# @param return_items [Array<Spree::ReturnLineItem>]
|
|
200
|
+
# @param return_items [Array<Spree::ReturnLineItem, Spree::ClaimLineItem, Spree::ExchangeLineItem>]
|
|
144
201
|
# @param amount [BigDecimal, nil] the refund issued; nil = the lines' full worth
|
|
145
202
|
# @param tax_date [Time, nil] the original supply date; nil = order.completed_at
|
|
146
203
|
# @return [void]
|
|
@@ -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),
|
|
140
|
+
reason_for(rate), jurisdiction)
|
|
141
|
+
end
|
|
116
142
|
end
|
|
117
143
|
end
|
|
118
144
|
|
|
@@ -209,7 +235,7 @@ module Spree
|
|
|
209
235
|
owner_key = owner.is_a?(Spree::Order) ? :order : :cart
|
|
210
236
|
Spree::TaxLine.create!(
|
|
211
237
|
{
|
|
212
|
-
|
|
238
|
+
Spree::TaxLine.adjustable_key_for(item.class) => item.id,
|
|
213
239
|
owner_key => owner,
|
|
214
240
|
tax_rate: rate,
|
|
215
241
|
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
|
|
@@ -100,8 +100,7 @@ module Spree
|
|
|
100
100
|
#
|
|
101
101
|
# @return [String]
|
|
102
102
|
def adjustment_label
|
|
103
|
-
|
|
104
|
-
scope: 'adjustment_labels.tax_rates',
|
|
103
|
+
I18n.t "spree.adjustment_labels.tax_rates.#{included_in_price? ? :including_tax : :excluding_tax}",
|
|
105
104
|
name: name.presence || tax_category.name,
|
|
106
105
|
amount: amount_for_label
|
|
107
106
|
end
|
|
@@ -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
|
data/app/models/spree/variant.rb
CHANGED
|
@@ -231,6 +231,10 @@ module Spree
|
|
|
231
231
|
|
|
232
232
|
scope :not_deleted, -> { where("#{Spree::Variant.quoted_table_name}.deleted_at IS NULL") }
|
|
233
233
|
|
|
234
|
+
# The variants of a product relation, as a subquery. Unordered, because a
|
|
235
|
+
# listing's sort would otherwise land inside the IN.
|
|
236
|
+
scope :for_products, ->(products) { where(product_id: products.reorder(nil).select(Spree::Product.arel_table[:id])) }
|
|
237
|
+
|
|
234
238
|
# Variants the shop sells in this currency. Base prices only: a price
|
|
235
239
|
# list's rows are what one audience pays under an agreement, and a ladder
|
|
236
240
|
# holds several rows for one variant, so a contract-only variant is not
|
|
@@ -285,9 +289,6 @@ module Spree
|
|
|
285
289
|
relation.where(conditions.reduce(:or)).distinct
|
|
286
290
|
end
|
|
287
291
|
|
|
288
|
-
# Backward compatibility alias — remove in Spree 6.0
|
|
289
|
-
scope :multi_search, ->(*args) { search(*args) }
|
|
290
|
-
|
|
291
292
|
# FIXME: cost price should be represented with DisplayMoney class
|
|
292
293
|
LOCALIZED_NUMBERS = %w(cost_price weight depth width height)
|
|
293
294
|
|
|
@@ -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
|
|
@@ -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
|
|
@@ -50,18 +49,16 @@ module Spree
|
|
|
50
49
|
# Number of consecutive failed deliveries before auto-disabling
|
|
51
50
|
AUTO_DISABLE_THRESHOLD = 15
|
|
52
51
|
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
# Check if this endpoint is subscribed to a specific event
|
|
52
|
+
# Check if this endpoint is subscribed to a specific event.
|
|
53
|
+
#
|
|
54
|
+
# Events whose payload carries a live credential (a password reset token)
|
|
55
|
+
# reach only an endpoint that names them — never through `*` or a pattern —
|
|
56
|
+
# since receiving one is as good as holding the account.
|
|
60
57
|
#
|
|
61
58
|
# @param event_name [String] the event name to check
|
|
62
59
|
# @return [Boolean]
|
|
63
60
|
def subscribed_to?(event_name)
|
|
64
|
-
return subscriptions.to_a.include?(event_name) if
|
|
61
|
+
return subscriptions.to_a.include?(event_name) if Spree::Events.catalog.credential?(event_name)
|
|
65
62
|
return true if subscriptions.blank? || subscriptions.include?('*')
|
|
66
63
|
|
|
67
64
|
subscriptions.any? do |subscription|
|
|
@@ -74,11 +71,11 @@ module Spree
|
|
|
74
71
|
end
|
|
75
72
|
end
|
|
76
73
|
|
|
77
|
-
#
|
|
74
|
+
# The credential-carrying events this endpoint names.
|
|
78
75
|
#
|
|
79
|
-
# @return [
|
|
80
|
-
def
|
|
81
|
-
(subscriptions.to_a
|
|
76
|
+
# @return [Array<Spree::Events::Catalog::Entry>]
|
|
77
|
+
def credential_subscriptions
|
|
78
|
+
Spree::Events.catalog.credential_events(subscriptions.to_a)
|
|
82
79
|
end
|
|
83
80
|
|
|
84
81
|
# Returns all events this endpoint is subscribed to
|
|
@@ -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
|