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
|
@@ -2,13 +2,19 @@ module Spree
|
|
|
2
2
|
module Exchanges
|
|
3
3
|
# Ships the replacements and settles the difference in price.
|
|
4
4
|
#
|
|
5
|
+
# Both halves of the swap carry tax: the units that came back are credited
|
|
6
|
+
# the tax the customer paid on them, and the replacements are taxed as a
|
|
7
|
+
# new sale. The difference between the two, tax included, is what settles.
|
|
8
|
+
#
|
|
5
9
|
# The money branch mirrors Returns::Refund: a credit the customer is owed
|
|
6
10
|
# can be store credit (an internal ledger write, inside the transaction)
|
|
7
|
-
# or a gateway refund (outside it). A balance the customer owes is
|
|
8
|
-
# for the merchant to collect — charging a stored card
|
|
9
|
-
# authorization is not something core should do silently.
|
|
11
|
+
# or a gateway refund (outside it). A balance the customer owes is put on
|
|
12
|
+
# the order as a fee for the merchant to collect — charging a stored card
|
|
13
|
+
# without a fresh authorization is not something core should do silently.
|
|
10
14
|
class Fulfill < Spree::Workflow
|
|
11
15
|
include Spree::Refunds::OrderPayments
|
|
16
|
+
include Spree::Refunds::TaxCredit
|
|
17
|
+
include Spree::Fulfillments::Replacements
|
|
12
18
|
|
|
13
19
|
hooks :validate, :before_settle, :after_fulfill
|
|
14
20
|
|
|
@@ -24,17 +30,22 @@ module Spree
|
|
|
24
30
|
|
|
25
31
|
@fulfillments = []
|
|
26
32
|
@refunds = []
|
|
27
|
-
step :
|
|
33
|
+
step :ensure_received
|
|
34
|
+
step :settle_tax
|
|
35
|
+
step :ensure_refund_method
|
|
28
36
|
run_hooks :validate
|
|
29
37
|
|
|
30
38
|
ApplicationRecord.transaction do
|
|
31
39
|
step :build_replacement_fulfillments
|
|
32
40
|
run_hooks :before_settle
|
|
33
41
|
step :issue_store_credit if credit_due? && internal_refund?
|
|
42
|
+
step :charge_balance if balance_owed?
|
|
34
43
|
step :mark_fulfilled
|
|
35
44
|
end
|
|
36
45
|
|
|
37
46
|
external_step :refund_at_gateway if credit_due? && !internal_refund?
|
|
47
|
+
external_step :refund_tax
|
|
48
|
+
external_step :commit_replacement
|
|
38
49
|
|
|
39
50
|
step :recalculate_order
|
|
40
51
|
run_hooks :after_fulfill
|
|
@@ -54,6 +65,10 @@ module Spree
|
|
|
54
65
|
settled_difference.negative?
|
|
55
66
|
end
|
|
56
67
|
|
|
68
|
+
def balance_owed?
|
|
69
|
+
settled_difference.positive?
|
|
70
|
+
end
|
|
71
|
+
|
|
57
72
|
def credit_amount
|
|
58
73
|
settled_difference.abs
|
|
59
74
|
end
|
|
@@ -63,64 +78,44 @@ module Spree
|
|
|
63
78
|
# exchange that asked for more than arrived would pay credit for goods
|
|
64
79
|
# nobody returned.
|
|
65
80
|
def settled_difference
|
|
66
|
-
@settled_difference ||=
|
|
67
|
-
|
|
81
|
+
@settled_difference ||= received_lines.sum(0.to_d) { |line| line.settled_charge - line.settled_credit }
|
|
82
|
+
end
|
|
68
83
|
|
|
69
|
-
|
|
70
|
-
|
|
84
|
+
def received_lines
|
|
85
|
+
@received_lines ||= exchange.exchange_line_items.select { |line| line.received_quantity.to_i.positive? }
|
|
71
86
|
end
|
|
72
87
|
|
|
73
|
-
def
|
|
74
|
-
if credit_due? && !Spree::RefundMethods.valid?(refund_method)
|
|
75
|
-
# :base, not :refund_method — it is a workflow argument, not an
|
|
76
|
-
# attribute, and ActiveModel raises when an error names one that
|
|
77
|
-
# does not exist on the record.
|
|
78
|
-
exchange.errors.add(:base, :invalid_refund_method,
|
|
79
|
-
message: Spree.t('errors.messages.invalid_refund_method'))
|
|
80
|
-
failure(exchange)
|
|
81
|
-
end
|
|
88
|
+
def ensure_received
|
|
82
89
|
failure(exchange, :not_received) unless exchange.received?
|
|
83
90
|
end
|
|
84
91
|
|
|
92
|
+
# Rewrites both halves' rows for the units that arrived, before any money
|
|
93
|
+
# moves, so the difference settled is the one the rows record.
|
|
94
|
+
def settle_tax
|
|
95
|
+
with_tax_provider(exchange) { exchange.settle_tax! }
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def ensure_refund_method
|
|
99
|
+
return unless credit_due? && !Spree::RefundMethods.valid?(refund_method)
|
|
100
|
+
|
|
101
|
+
# :base, not :refund_method — it is a workflow argument, not an
|
|
102
|
+
# attribute, and ActiveModel raises when an error names one that
|
|
103
|
+
# does not exist on the record.
|
|
104
|
+
exchange.errors.add(:base, :invalid_refund_method,
|
|
105
|
+
message: I18n.t('spree.errors.messages.invalid_refund_method'))
|
|
106
|
+
failure(exchange)
|
|
107
|
+
end
|
|
108
|
+
|
|
85
109
|
# Only lines that actually came back are replaced — a customer who
|
|
86
110
|
# returned two of three items gets two replacements.
|
|
87
111
|
def build_replacement_fulfillments
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
exchange.order.fulfillment_items.new(
|
|
92
|
-
variant: line.new_variant,
|
|
93
|
-
quantity: line.received_quantity,
|
|
94
|
-
line_item: line.line_item,
|
|
95
|
-
order: exchange.order,
|
|
96
|
-
status: 'on_hand'
|
|
97
|
-
)
|
|
112
|
+
items = received_lines.map do |line|
|
|
113
|
+
{ variant: line.new_variant, quantity: line.received_quantity, line_item: line.line_item }
|
|
98
114
|
end
|
|
99
115
|
|
|
100
|
-
failure(exchange, :nothing_to_fulfill) if
|
|
116
|
+
failure(exchange, :nothing_to_fulfill) if items.empty?
|
|
101
117
|
|
|
102
|
-
@fulfillments =
|
|
103
|
-
if @fulfillments.flat_map(&:fulfillment_items).sum(&:quantity) != units.sum(&:quantity)
|
|
104
|
-
failure(exchange, :replacement_out_of_stock)
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
exchange.order.fulfillments += @fulfillments
|
|
108
|
-
exchange.order.save!
|
|
109
|
-
@fulfillments.each { |fulfillment| allocate_replacement_stock(fulfillment) }
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
# The replacement is promised the moment it exists, exactly as placement
|
|
113
|
-
# promises an order's own fulfillments. Without this the fulfillment holds
|
|
114
|
-
# nothing, and dispatch then writes no movement at all — an unallocated
|
|
115
|
-
# fulfillment is indistinguishable from one created before typed
|
|
116
|
-
# movements, so the goods would leave the shelf untouched and unrecorded.
|
|
117
|
-
def allocate_replacement_stock(fulfillment)
|
|
118
|
-
fulfillment.manifest.each do |item|
|
|
119
|
-
next unless item.variant.track_inventory?
|
|
120
|
-
next unless item.quantity.positive?
|
|
121
|
-
|
|
122
|
-
fulfillment.stock_location.allocate(item.variant, item.quantity, fulfillment)
|
|
123
|
-
end
|
|
118
|
+
@fulfillments = build_replacements(exchange, items)
|
|
124
119
|
end
|
|
125
120
|
|
|
126
121
|
def issue_store_credit
|
|
@@ -138,10 +133,39 @@ module Spree
|
|
|
138
133
|
order: exchange.order,
|
|
139
134
|
amount: credit_amount,
|
|
140
135
|
record: exchange,
|
|
141
|
-
refunder: refunder
|
|
136
|
+
refunder: refunder,
|
|
137
|
+
tax_amount: received_lines.sum(0.to_d, &:settled_tax)
|
|
142
138
|
)
|
|
143
139
|
end
|
|
144
140
|
|
|
141
|
+
# Puts what the customer owes on the order, which then shows it as due
|
|
142
|
+
# for the merchant to collect through the order's payments.
|
|
143
|
+
def charge_balance
|
|
144
|
+
result = Spree.order_fee_create_service.call(
|
|
145
|
+
order: exchange.order,
|
|
146
|
+
attributes: {
|
|
147
|
+
kind: 'exchange',
|
|
148
|
+
amount: settled_difference,
|
|
149
|
+
label: I18n.t('spree.exchange_fee_label', number: exchange.number),
|
|
150
|
+
metadata: { 'exchange_id' => exchange.prefixed_id }
|
|
151
|
+
}
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
failure(exchange, result.error.value) if result.failure?
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def refund_tax
|
|
158
|
+
file_tax_credit(exchange, received_lines, received_lines.sum(0.to_d, &:settled_credit))
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Reported rather than raised, as the credit is: the goods have shipped.
|
|
162
|
+
def commit_replacement
|
|
163
|
+
order = exchange.order
|
|
164
|
+
order.tax_provider.commit_replacement(order, received_lines)
|
|
165
|
+
rescue Spree::Tax::ProviderError => error
|
|
166
|
+
Rails.error.report(error, handled: true, context: tax_report_context(exchange), source: 'spree.post_sale')
|
|
167
|
+
end
|
|
168
|
+
|
|
145
169
|
def mark_fulfilled
|
|
146
170
|
exchange.update!(status: 'fulfilled', fulfilled_at: Time.current)
|
|
147
171
|
end
|
|
@@ -3,6 +3,8 @@ module Spree
|
|
|
3
3
|
# Records what came back. Same partial-receipt shape as returns: the
|
|
4
4
|
# warehouse enters actual quantities and flags anything unsellable.
|
|
5
5
|
class Receive < Spree::Workflow
|
|
6
|
+
include Spree::PostSale::Receipt
|
|
7
|
+
|
|
6
8
|
hooks :validate, :before_restock, :after_receive
|
|
7
9
|
|
|
8
10
|
# @param exchange [Spree::Exchange]
|
|
@@ -10,76 +12,15 @@ module Spree
|
|
|
10
12
|
# @param received_by [Object, nil]
|
|
11
13
|
def perform(exchange:, items: nil, received_by: nil)
|
|
12
14
|
super
|
|
13
|
-
|
|
14
|
-
step :ensure_receivable
|
|
15
|
-
step :normalize_items
|
|
16
|
-
run_hooks :validate
|
|
17
|
-
|
|
18
|
-
ApplicationRecord.transaction do
|
|
19
|
-
step :record_receipt
|
|
20
|
-
run_hooks :before_restock
|
|
21
|
-
step :restock_resellable_items
|
|
22
|
-
step :mark_received
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
run_hooks :after_receive
|
|
26
|
-
exchange.publish_event('exchange.received')
|
|
27
|
-
success(exchange.reload)
|
|
15
|
+
receive
|
|
28
16
|
end
|
|
29
17
|
|
|
30
18
|
private
|
|
31
19
|
|
|
32
|
-
def
|
|
33
|
-
failure(exchange, :not_approved) unless exchange.approved?
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def normalize_items
|
|
37
|
-
@normalized_items =
|
|
38
|
-
if items.nil?
|
|
39
|
-
exchange.exchange_line_items.map do |line|
|
|
40
|
-
{ exchange_line_item: line, quantity: line.quantity, resellable: true }
|
|
41
|
-
end
|
|
42
|
-
else
|
|
43
|
-
items.map { |item| normalize_item(item) }
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
failure(exchange, :no_items_received) if @normalized_items.sum { |item| item[:quantity] }.zero?
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def normalize_item(item)
|
|
50
|
-
line = item[:exchange_line_item]
|
|
51
|
-
quantity = item[:quantity].to_i
|
|
52
|
-
|
|
53
|
-
failure(exchange, :item_not_on_exchange) unless line&.exchange_id == exchange.id
|
|
54
|
-
failure(exchange, :invalid_quantity) if quantity.negative? || quantity > line.quantity
|
|
55
|
-
|
|
56
|
-
{ exchange_line_item: line, quantity: quantity, resellable: item.fetch(:resellable, true) }
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def record_receipt
|
|
60
|
-
@normalized_items.each do |item|
|
|
61
|
-
item[:exchange_line_item].update!(
|
|
62
|
-
received_quantity: item[:quantity],
|
|
63
|
-
resellable: item[:resellable]
|
|
64
|
-
)
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def restock_resellable_items
|
|
69
|
-
@normalized_items.each do |item|
|
|
70
|
-
next unless item[:resellable]
|
|
71
|
-
next unless item[:quantity].positive?
|
|
72
|
-
|
|
73
|
-
exchange.stock_location.restock(
|
|
74
|
-
item[:exchange_line_item].original_variant,
|
|
75
|
-
item[:quantity],
|
|
76
|
-
exchange
|
|
77
|
-
)
|
|
78
|
-
end
|
|
79
|
-
end
|
|
20
|
+
def post_sale_record = exchange
|
|
80
21
|
|
|
81
|
-
def
|
|
82
|
-
|
|
22
|
+
def restock_variant(line)
|
|
23
|
+
line.original_variant
|
|
83
24
|
end
|
|
84
25
|
end
|
|
85
26
|
end
|
|
@@ -45,7 +45,7 @@ module Spree
|
|
|
45
45
|
def ensure_cancelable
|
|
46
46
|
return if fulfillment.can_cancel?
|
|
47
47
|
|
|
48
|
-
failure(fulfillment,
|
|
48
|
+
failure(fulfillment, I18n.t('spree.fulfillments.errors.cannot_cancel'))
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
# Withdraws the promise this fulfillment held. Nothing physical comes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module Spree
|
|
2
2
|
module Fulfillments
|
|
3
|
-
# Manually creates a fulfillment (Spree::
|
|
3
|
+
# Manually creates a fulfillment (Spree::Fulfillment) on a completed order,
|
|
4
4
|
# bypassing order routing. Moves the requested quantities of each line
|
|
5
5
|
# item's not-yet-shipped inventory units out of their current shipments
|
|
6
6
|
# into the new fulfillment, mirroring externally-managed fulfillment
|
|
@@ -13,12 +13,19 @@ module Spree
|
|
|
13
13
|
# new fulfillment, unless the caller provides its own +cost+ /
|
|
14
14
|
# +delivery_method+.
|
|
15
15
|
#
|
|
16
|
+
# Order-wide, replacement units sent by an exchange or a claim are never
|
|
17
|
+
# moved: they belong to the record that sent them, not to the goods the
|
|
18
|
+
# customer bought on the line they are filed under. Given a
|
|
19
|
+
# +source_fulfillment+, units move only out of that one, replacements
|
|
20
|
+
# included — splitting a parcel, or re-sending a canceled one, moves what
|
|
21
|
+
# that parcel holds.
|
|
22
|
+
#
|
|
16
23
|
# The resulting cost is frozen only for fulfillments registered with
|
|
17
|
-
# status: 'shipped' (rate refresh skips shipped shipments).
|
|
18
|
-
#
|
|
19
|
-
# re-prices
|
|
20
|
-
# totals recalculation
|
|
21
|
-
#
|
|
24
|
+
# status: 'shipped' (rate refresh skips shipped shipments). A pending new
|
|
25
|
+
# fulfillment participates in the standard rate machinery — this workflow
|
|
26
|
+
# re-prices it from the delivery method calculators before the final
|
|
27
|
+
# totals recalculation. Only the new fulfillment is re-priced; the order's
|
|
28
|
+
# other fulfillments keep their cost.
|
|
22
29
|
#
|
|
23
30
|
# In the workflow tier for its hooks and its transaction discipline:
|
|
24
31
|
# 3PL and courier integrations need to contribute provider payload and
|
|
@@ -35,9 +42,11 @@ module Spree
|
|
|
35
42
|
# @param order [Spree::Order] completed order to fulfill
|
|
36
43
|
# @param stock_location [Spree::StockLocation] location the fulfillment ships from
|
|
37
44
|
# @param items [Array<Hash>, nil] `[{ line_item: Spree::LineItem, quantity: Integer }]`;
|
|
38
|
-
# nil
|
|
45
|
+
# nil moves every not-yet-shipped unit, drawn as +source_fulfillment+ describes
|
|
46
|
+
# @param source_fulfillment [Spree::Fulfillment, nil] the only fulfillment units are moved from;
|
|
47
|
+
# nil takes the customer's bought units from every unshipped fulfillment on the order
|
|
39
48
|
# @param tracking [String, nil] carrier tracking number
|
|
40
|
-
# @param delivery_method [Spree::
|
|
49
|
+
# @param delivery_method [Spree::DeliveryMethod, nil] carrier; stored as the selected rate.
|
|
41
50
|
# Defaults to the delivery method of the drained source fulfillment(s)
|
|
42
51
|
# @param cost [String, Numeric, nil] explicit shipping cost (e.g. the 3PL's price).
|
|
43
52
|
# Defaults to the summed cost of the drained source fulfillment(s), keeping the
|
|
@@ -47,7 +56,7 @@ module Spree
|
|
|
47
56
|
# @param status [String, nil] pass 'shipped' to register an already-shipped fulfillment
|
|
48
57
|
# @param metadata [Hash, nil] metadata stored on the fulfillment
|
|
49
58
|
# @return [Spree::ServiceModule::Result] the created shipment on success
|
|
50
|
-
def perform(order:, stock_location:, items: nil, tracking: nil, delivery_method: nil, cost: nil, status: nil, metadata: nil)
|
|
59
|
+
def perform(order:, stock_location:, items: nil, source_fulfillment: nil, tracking: nil, delivery_method: nil, cost: nil, status: nil, metadata: nil)
|
|
51
60
|
super
|
|
52
61
|
|
|
53
62
|
step :ensure_valid_status
|
|
@@ -83,7 +92,7 @@ module Spree
|
|
|
83
92
|
def ensure_valid_status
|
|
84
93
|
return if status.nil? || status == 'shipped'
|
|
85
94
|
|
|
86
|
-
failure(nil,
|
|
95
|
+
failure(nil, I18n.t('spree.fulfillments.errors.invalid_status'))
|
|
87
96
|
end
|
|
88
97
|
|
|
89
98
|
# parse_cost returns a failure Result for an unparseable value; step
|
|
@@ -93,8 +102,8 @@ module Spree
|
|
|
93
102
|
end
|
|
94
103
|
|
|
95
104
|
def ensure_order_fulfillable
|
|
96
|
-
failure(nil,
|
|
97
|
-
failure(nil,
|
|
105
|
+
failure(nil, I18n.t('spree.fulfillments.errors.order_not_completed')) unless order.completed?
|
|
106
|
+
failure(nil, I18n.t('spree.fulfillments.errors.order_canceled')) if order.canceled?
|
|
98
107
|
end
|
|
99
108
|
|
|
100
109
|
def build_fulfillment
|
|
@@ -126,38 +135,32 @@ module Spree
|
|
|
126
135
|
mark_shipped(fulfillment) if status == 'shipped'
|
|
127
136
|
end
|
|
128
137
|
|
|
129
|
-
def reprice_and_recalculate
|
|
130
|
-
reprice_pending_fulfillments(order.reload)
|
|
131
|
-
order.recalculate_totals!
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
# Moved here from the old updater completed-order branch: pending/ready
|
|
135
|
-
# fulfillments re-price from backoffice-visible delivery methods;
|
|
136
|
-
# fulfilled ones keep their frozen cost.
|
|
137
|
-
#
|
|
138
138
|
# A caller that priced this parcel itself is honoured rather than
|
|
139
|
-
# re-quoted.
|
|
140
|
-
def
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
next if @requested_cost && candidate.id == fulfillment.id
|
|
145
|
-
|
|
146
|
-
candidate.refresh_rates(Spree::DeliveryMethod::BACKOFFICE)
|
|
147
|
-
candidate.update_amounts
|
|
139
|
+
# re-quoted, and a shipped one keeps its frozen cost.
|
|
140
|
+
def reprice_and_recalculate
|
|
141
|
+
unless @requested_cost || fulfillment.fulfilled?
|
|
142
|
+
fulfillment.reload.refresh_rates(Spree::DeliveryMethod::BACKOFFICE)
|
|
143
|
+
fulfillment.update_amounts
|
|
148
144
|
end
|
|
145
|
+
|
|
146
|
+
order.reload.recalculate_totals!
|
|
149
147
|
end
|
|
150
148
|
|
|
151
149
|
# Units that can still be moved into a manual fulfillment: on-hand or
|
|
152
|
-
# backordered units sitting in shipments that haven't shipped
|
|
153
|
-
#
|
|
154
|
-
#
|
|
155
|
-
#
|
|
156
|
-
#
|
|
157
|
-
#
|
|
158
|
-
#
|
|
150
|
+
# backordered units sitting in shipments that haven't shipped — the
|
|
151
|
+
# source's own units when one is named, otherwise the customer's bought
|
|
152
|
+
# units across the order. A canceled shipment's units are included —
|
|
153
|
+
# cancellation kills that attempt, not the obligation to ship, so the
|
|
154
|
+
# goods are still owed and a fresh fulfillment is the only way left to
|
|
155
|
+
# send them (the canceled record itself can never be fulfilled). Loaded
|
|
156
|
+
# in one query, bought before replacement (a request names only the
|
|
157
|
+
# line) and on-hand first so moved units stay shippable, grouped by line
|
|
158
|
+
# item for both validation and moving.
|
|
159
159
|
def fulfillable_units(order)
|
|
160
|
-
order.fulfillment_items
|
|
160
|
+
units = order.fulfillment_items
|
|
161
|
+
units = source_fulfillment ? units.where(fulfillment_id: source_fulfillment.id).order(:replacement) : units.where(replacement: false)
|
|
162
|
+
|
|
163
|
+
units.
|
|
161
164
|
on_hand_or_backordered.
|
|
162
165
|
joins(:fulfillment).
|
|
163
166
|
merge(Spree::Fulfillment.ready_or_pending.or(Spree::Fulfillment.canceled)).
|
|
@@ -174,12 +177,12 @@ module Spree
|
|
|
174
177
|
quantity = available_for.call(line_item)
|
|
175
178
|
{ line_item: line_item, quantity: quantity } if quantity.positive?
|
|
176
179
|
end
|
|
177
|
-
failure(nil,
|
|
180
|
+
failure(nil, I18n.t('spree.fulfillments.errors.no_items_to_fulfill')) if derived.empty?
|
|
178
181
|
|
|
179
182
|
return derived
|
|
180
183
|
end
|
|
181
184
|
|
|
182
|
-
failure(nil,
|
|
185
|
+
failure(nil, I18n.t('spree.fulfillments.errors.no_items_to_fulfill')) if items.empty?
|
|
183
186
|
|
|
184
187
|
# Merge duplicate line item entries, then validate quantities.
|
|
185
188
|
merged = items.group_by { |item| item[:line_item].id }.values.map do |grouped|
|
|
@@ -191,14 +194,14 @@ module Spree
|
|
|
191
194
|
quantity = item[:quantity]
|
|
192
195
|
|
|
193
196
|
unless quantity.positive?
|
|
194
|
-
failure(nil,
|
|
197
|
+
failure(nil, I18n.t('spree.fulfillments.errors.invalid_quantity', item: line_item.prefixed_id))
|
|
195
198
|
end
|
|
196
199
|
|
|
197
200
|
available = available_for.call(line_item)
|
|
198
201
|
if quantity > available
|
|
199
202
|
failure(
|
|
200
203
|
nil,
|
|
201
|
-
|
|
204
|
+
I18n.t('spree.fulfillments.errors.insufficient_quantity',
|
|
202
205
|
item: line_item.prefixed_id, requested: quantity, available: available)
|
|
203
206
|
)
|
|
204
207
|
end
|
|
@@ -212,7 +215,7 @@ module Spree
|
|
|
212
215
|
# carries each moved unit's allocation across with it so the promise
|
|
213
216
|
# follows the fulfillment that will ship it.
|
|
214
217
|
#
|
|
215
|
-
# @return [Array<Spree::
|
|
218
|
+
# @return [Array<Spree::Fulfillment>] the shipments units were taken from
|
|
216
219
|
def move_units(order, fulfillment, requested, units_by_line_item)
|
|
217
220
|
source_shipments = []
|
|
218
221
|
stock_moves = Hash.new(0)
|
|
@@ -231,7 +234,8 @@ module Spree
|
|
|
231
234
|
status: unit.status,
|
|
232
235
|
variant_id: unit.variant_id,
|
|
233
236
|
line_item_id: unit.line_item_id,
|
|
234
|
-
order_id: order.id
|
|
237
|
+
order_id: order.id,
|
|
238
|
+
replacement: unit.replacement
|
|
235
239
|
)
|
|
236
240
|
target.pending = unit.pending
|
|
237
241
|
# The quantity column has a database default of 1, so a fresh
|
|
@@ -282,7 +286,7 @@ module Spree
|
|
|
282
286
|
# deleted along with the shipment. The delivery method lookup costs
|
|
283
287
|
# queries, so it is skipped when the caller provided its own.
|
|
284
288
|
#
|
|
285
|
-
# @return [Hash] `{ cost: BigDecimal, delivery_method: Spree::
|
|
289
|
+
# @return [Hash] `{ cost: BigDecimal, delivery_method: Spree::DeliveryMethod or nil }`
|
|
286
290
|
def destroy_drained_shipments(source_shipments, capture_delivery_method:)
|
|
287
291
|
inherited = { cost: 0, delivery_method: nil }
|
|
288
292
|
|
|
@@ -325,11 +329,11 @@ module Spree
|
|
|
325
329
|
return if cost.blank?
|
|
326
330
|
|
|
327
331
|
parsed = cost.is_a?(String) ? BigDecimal(cost.strip) : cost
|
|
328
|
-
failure(nil,
|
|
332
|
+
failure(nil, I18n.t('spree.fulfillments.errors.invalid_cost')) if parsed.negative?
|
|
329
333
|
|
|
330
334
|
parsed
|
|
331
335
|
rescue ArgumentError
|
|
332
|
-
failure(nil,
|
|
336
|
+
failure(nil, I18n.t('spree.fulfillments.errors.invalid_cost'))
|
|
333
337
|
end
|
|
334
338
|
|
|
335
339
|
# Registers an externally-completed fulfillment: backorders are filled
|
|
@@ -56,9 +56,12 @@ module Spree
|
|
|
56
56
|
|
|
57
57
|
# The split has to land before the provider is involved: the label is
|
|
58
58
|
# for the actual parcel, and a partial shipment's parcel only exists
|
|
59
|
-
# after the split.
|
|
60
|
-
|
|
59
|
+
# after the split. requires_new: #call rescues a failure, so joining a
|
|
60
|
+
# caller's open transaction (the API's order lock) would let it commit
|
|
61
|
+
# a split the shelf check refused.
|
|
62
|
+
ApplicationRecord.transaction(requires_new: true) do
|
|
61
63
|
step :split_off_requested_units
|
|
64
|
+
step :ensure_shelf_can_cover_dispatch
|
|
62
65
|
step :apply_tracking
|
|
63
66
|
end
|
|
64
67
|
|
|
@@ -67,7 +70,7 @@ module Spree
|
|
|
67
70
|
# box, hand it over), and the ordering that lets the shipped email
|
|
68
71
|
# carry the tracking number the provider discovered instead of racing
|
|
69
72
|
# it. No-op when a label was already bought through
|
|
70
|
-
# Spree::
|
|
73
|
+
# Spree::ShippingLabels::Purchase (the flow where a failure is
|
|
71
74
|
# loud), or when the provider has no dispatch mechanics (Manual). A
|
|
72
75
|
# failure here still degrades to "no label yet" — a carrier outage
|
|
73
76
|
# must never stop a merchant recording a parcel that physically left.
|
|
@@ -102,11 +105,10 @@ module Spree
|
|
|
102
105
|
end
|
|
103
106
|
|
|
104
107
|
def ensure_fulfillable
|
|
105
|
-
failure(@source,
|
|
108
|
+
failure(@source, I18n.t('spree.fulfillments.errors.cannot_fulfill')) unless @source.can_fulfill?
|
|
106
109
|
|
|
107
110
|
ensure_order_placed
|
|
108
111
|
ensure_ready_to_hand_over
|
|
109
|
-
ensure_shelf_can_cover_dispatch
|
|
110
112
|
ensure_requested_units_available
|
|
111
113
|
end
|
|
112
114
|
|
|
@@ -116,7 +118,7 @@ module Spree
|
|
|
116
118
|
def ensure_order_placed
|
|
117
119
|
return unless @source.order&.draft?
|
|
118
120
|
|
|
119
|
-
failure(@source,
|
|
121
|
+
failure(@source, I18n.t('spree.fulfillments.errors.order_draft'))
|
|
120
122
|
end
|
|
121
123
|
|
|
122
124
|
# What the pending/ready statuses used to encode, asked once at the
|
|
@@ -132,7 +134,7 @@ module Spree
|
|
|
132
134
|
return if order.nil?
|
|
133
135
|
|
|
134
136
|
if @source.fulfillment_items.any?(&:backordered?)
|
|
135
|
-
failure(@source,
|
|
137
|
+
failure(@source, I18n.t('spree.fulfillments.errors.backordered_units'))
|
|
136
138
|
end
|
|
137
139
|
|
|
138
140
|
# An order that owes nothing is ready to hand over, which the old
|
|
@@ -152,7 +154,7 @@ module Spree
|
|
|
152
154
|
pending = order.settlement_pending_payments
|
|
153
155
|
return if pending.any? && pending.all? { |payment| payment.payment_method&.capture_at_checkout? == false }
|
|
154
156
|
|
|
155
|
-
failure(@source,
|
|
157
|
+
failure(@source, I18n.t('spree.fulfillments.errors.order_not_paid'))
|
|
156
158
|
end
|
|
157
159
|
|
|
158
160
|
# The shelf has to be able to cover what is about to leave it. A dispatch
|
|
@@ -163,7 +165,10 @@ module Spree
|
|
|
163
165
|
#
|
|
164
166
|
# Refused here rather than at the stock write so it renders as a 422 the
|
|
165
167
|
# merchant can act on, instead of a validation exception surfacing from
|
|
166
|
-
# inside the dispatch transaction.
|
|
168
|
+
# inside the dispatch transaction. Checked after the split, which the
|
|
169
|
+
# refusal rolls back, so a partial dispatch is measured on the units that
|
|
170
|
+
# actually moved: a line's request can move a replacement of another
|
|
171
|
+
# variant.
|
|
167
172
|
def ensure_shelf_can_cover_dispatch
|
|
168
173
|
return if force
|
|
169
174
|
|
|
@@ -173,12 +178,12 @@ module Spree
|
|
|
173
178
|
variants = Spree::Variant.with_deleted.where(id: quantities.keys).index_by(&:id)
|
|
174
179
|
|
|
175
180
|
quantities.each do |variant_id, quantity|
|
|
176
|
-
stock_level = @
|
|
181
|
+
stock_level = @fulfillment.stock_location.stock_level(variant_id)
|
|
177
182
|
next if stock_level.nil? || stock_level.count_on_hand >= quantity
|
|
178
183
|
|
|
179
184
|
failure(
|
|
180
185
|
@source,
|
|
181
|
-
|
|
186
|
+
I18n.t('spree.fulfillments.errors.insufficient_stock_on_hand',
|
|
182
187
|
item: variants[variant_id]&.name,
|
|
183
188
|
on_hand: stock_level.count_on_hand,
|
|
184
189
|
requested: quantity)
|
|
@@ -186,28 +191,18 @@ module Spree
|
|
|
186
191
|
end
|
|
187
192
|
end
|
|
188
193
|
|
|
189
|
-
# What each variant will actually ship:
|
|
190
|
-
# by
|
|
191
|
-
# split carries min(promise, moved quantity), so the two agree.
|
|
194
|
+
# What each variant will actually ship: the parcel's outstanding promise,
|
|
195
|
+
# capped by the units it holds.
|
|
192
196
|
#
|
|
193
197
|
# @return [Hash{Integer => Integer}]
|
|
194
198
|
def dispatch_quantity_by_variant
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
allocated = @source.allocated_quantities
|
|
199
|
+
allocated = @fulfillment.allocated_quantities
|
|
198
200
|
return {} if allocated.empty?
|
|
199
201
|
|
|
200
|
-
|
|
201
|
-
if @requested.empty?
|
|
202
|
-
held
|
|
203
|
-
else
|
|
204
|
-
@requested.each_with_object(Hash.new(0)) do |item, totals|
|
|
205
|
-
totals[item[:line_item].variant_id] += item[:quantity]
|
|
206
|
-
end
|
|
207
|
-
end
|
|
202
|
+
held = @fulfillment.fulfillment_items.group(:variant_id).sum(:quantity)
|
|
208
203
|
|
|
209
204
|
allocated.each_with_object({}) do |(variant_id, promised), totals|
|
|
210
|
-
quantity = [promised,
|
|
205
|
+
quantity = [promised, held[variant_id].to_i].min
|
|
211
206
|
totals[variant_id] = quantity if quantity.positive?
|
|
212
207
|
end
|
|
213
208
|
end
|
|
@@ -226,7 +221,7 @@ module Spree
|
|
|
226
221
|
|
|
227
222
|
failure(
|
|
228
223
|
@source,
|
|
229
|
-
|
|
224
|
+
I18n.t('spree.fulfillments.errors.not_in_fulfillment',
|
|
230
225
|
item: item[:line_item].prefixed_id, requested: item[:quantity], available: held)
|
|
231
226
|
)
|
|
232
227
|
end
|
|
@@ -244,6 +239,7 @@ module Spree
|
|
|
244
239
|
order: @source.order,
|
|
245
240
|
stock_location: @source.stock_location,
|
|
246
241
|
items: @requested,
|
|
242
|
+
source_fulfillment: @source,
|
|
247
243
|
delivery_method: @source.delivery_method,
|
|
248
244
|
# Delivery was bought once, at checkout. Charging the rate again
|
|
249
245
|
# would bill the customer for the warehouse's decision.
|
|
@@ -45,7 +45,7 @@ module Spree
|
|
|
45
45
|
def ensure_deliverable
|
|
46
46
|
return if fulfillment.can_mark_delivered?
|
|
47
47
|
|
|
48
|
-
failure(fulfillment,
|
|
48
|
+
failure(fulfillment, I18n.t('spree.fulfillments.errors.cannot_mark_delivered'))
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
# A human saying "it arrived" outranks a carrier that never sent the
|