diff --git a/backend/app/controllers/spree/admin/price_list_items_controller.rb b/backend/app/controllers/spree/admin/price_list_items_controller.rb new file mode 100644 index 00000000000..224d784347a --- /dev/null +++ b/backend/app/controllers/spree/admin/price_list_items_controller.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module Spree + module Admin + class PriceListItemsController < ResourceController + belongs_to 'spree/price_list', find_by: :id + before_action :set_price_list + + def new + @price_list_item = @price_list.price_list_items.build + @price_list_item.build_price + end + + private + + def set_price_list + @price_list = Spree::PriceList.find(params[:price_list_id]) + end + + def location_after_save + edit_admin_price_list_path(@price_list) + end + + def price_list_item_params + params.require(:price_list_item).permit(:price_list_id, price_attributes: [:amount, :currency, :variant_id]) + end + end + end +end diff --git a/backend/app/controllers/spree/admin/price_lists_controller.rb b/backend/app/controllers/spree/admin/price_lists_controller.rb new file mode 100644 index 00000000000..09609bc17fb --- /dev/null +++ b/backend/app/controllers/spree/admin/price_lists_controller.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Spree + module Admin + class PriceListsController < ResourceController + private + + def location_after_save + edit_admin_price_list_path(@price_list) + end + end + end +end diff --git a/backend/app/controllers/spree/admin/user_groups_controller.rb b/backend/app/controllers/spree/admin/user_groups_controller.rb index 81cf1828579..de20678f555 100644 --- a/backend/app/controllers/spree/admin/user_groups_controller.rb +++ b/backend/app/controllers/spree/admin/user_groups_controller.rb @@ -3,6 +3,13 @@ module Spree module Admin class UserGroupsController < ResourceController + before_action :load_data, except: :index + + private + + def load_data + @price_list = Spree::PriceList.order(:name) + end end end end diff --git a/backend/app/views/spree/admin/price_list_items/_form.html.erb b/backend/app/views/spree/admin/price_list_items/_form.html.erb new file mode 100644 index 00000000000..3fe7dbf6a1a --- /dev/null +++ b/backend/app/views/spree/admin/price_list_items/_form.html.erb @@ -0,0 +1,33 @@ +
| <%= Spree::PriceList.human_attribute_name(:name) %> | +<%= Spree::UserGroup.model_name.human.pluralize %> | ++ |
|---|---|---|
| <%= link_to price_list.try(:name), edit_admin_price_list_path(price_list) %> | ++ + <%= price_list.user_groups.count %> <%= Spree::UserGroup.model_name.human.pluralize %> + + | ++ <% if can?(:update, price_list) %> + <%= link_to_edit price_list, no_text: true %> + <% end %> + + <% if can?(:destroy, price_list) %> + <%= link_to_delete price_list, no_text: true %> + <% end %> + | +
| <%= Spree::UserGroup.human_attribute_name(:group_name) %> | +<%= Spree::PriceList.model_name.human %> | |
|---|---|---|
| <%= link_to user_group.try(:group_name), edit_admin_user_group_path(user_group) %> | +<%= user_group.price_list.try(:name) %> |
<% if can?(:update, user_group) %>
<%= link_to_edit user_group, no_text: true %>
diff --git a/backend/config/routes.rb b/backend/config/routes.rb
index 656d822c730..5d999d06d3f 100644
--- a/backend/config/routes.rb
+++ b/backend/config/routes.rb
@@ -16,6 +16,10 @@
resources :zones
+ resources :price_lists do
+ resources :price_list_items, only: [:destroy, :edit, :update, :new, :create]
+ end
+
resources :user_groups
resources :tax_categories
diff --git a/backend/lib/spree/backend_configuration.rb b/backend/lib/spree/backend_configuration.rb
index 690baa26838..7d8e603ee77 100644
--- a/backend/lib/spree/backend_configuration.rb
+++ b/backend/lib/spree/backend_configuration.rb
@@ -202,6 +202,13 @@ def menu_items
condition: -> { can? :admin, Spree::Product },
match_path: '/products',
),
+ MenuItem.new(
+ label: :price_lists,
+ condition: -> {
+ can?(:admin, Spree::PriceList)
+ },
+ match_path: '/price_lists'
+ ),
MenuItem.new(
label: :option_types,
condition: -> { can? :admin, Spree::OptionType },
diff --git a/core/app/models/spree/price.rb b/core/app/models/spree/price.rb
index 36a957468db..d6759a61557 100644
--- a/core/app/models/spree/price.rb
+++ b/core/app/models/spree/price.rb
@@ -9,6 +9,9 @@ class Price < Spree::Base
belongs_to :variant, -> { with_discarded }, class_name: 'Spree::Variant', touch: true, optional: true
belongs_to :country, class_name: "Spree::Country", foreign_key: "country_iso", primary_key: "iso", optional: true
+ has_one :price_list_item, class_name: 'Spree::PriceListItem'
+ has_one :price_list, through: :price_list_item, class_name: 'Spree::PriceList'
+
delegate :product, to: :variant
delegate :tax_rates, to: :variant
diff --git a/core/app/models/spree/price_list.rb b/core/app/models/spree/price_list.rb
new file mode 100644
index 00000000000..cfbfa2cfac8
--- /dev/null
+++ b/core/app/models/spree/price_list.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Spree
+ class PriceList < Spree::Base
+ has_many :price_list_items, class_name: 'Spree::PriceListItem', dependent: :destroy
+ has_many :prices, through: :price_list_items, class_name: 'Spree::Price'
+ has_many :user_groups, class_name: 'Spree::UserGroup', dependent: :destroy
+ belongs_to :country, class_name: "Spree::Country", foreign_key: "country_iso", primary_key: "iso", optional: true
+
+ validates :currency, inclusion: { in: ::Money::Currency.all.map(&:iso_code), message: :invalid_code }
+ validates :country, presence: true, unless: -> { for_any_country? }
+
+ validates :name, presence: true
+
+ def for_any_country?
+ country_iso.nil?
+ end
+
+ def country_iso=(country_iso)
+ self[:country_iso] = country_iso.presence
+ end
+ end
+end
diff --git a/core/app/models/spree/price_list_item.rb b/core/app/models/spree/price_list_item.rb
new file mode 100644
index 00000000000..be129317edb
--- /dev/null
+++ b/core/app/models/spree/price_list_item.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Spree
+ class PriceListItem < Spree::Base
+ belongs_to :price_list, class_name: 'Spree::PriceList'
+ belongs_to :price, class_name: 'Spree::Price', dependent: :destroy
+
+ validates :price_list, presence: true
+ validates :price, presence: true
+
+ accepts_nested_attributes_for :price
+ end
+end
diff --git a/core/app/models/spree/user_group.rb b/core/app/models/spree/user_group.rb
index 89e69250cb2..96d4f1ddcf7 100644
--- a/core/app/models/spree/user_group.rb
+++ b/core/app/models/spree/user_group.rb
@@ -4,6 +4,7 @@ module Spree
class UserGroup < Spree::Base
has_many :users, class_name: Spree::UserClassHandle.new
has_one :store, class_name: 'Spree::Store', foreign_key: 'default_cart_user_group_id'
+ belongs_to :price_list, class_name: 'Spree::PriceList', optional: true
validates :group_name, presence: true
end
diff --git a/core/config/locales/en.yml b/core/config/locales/en.yml
index 1864ac24615..929e85596fa 100644
--- a/core/config/locales/en.yml
+++ b/core/config/locales/en.yml
@@ -479,6 +479,10 @@ en:
attributes:
currency:
invalid_code: is not a valid currency code
+ spree/price_list:
+ attributes:
+ currency:
+ invalid_code: is not a valid currency code
spree/promotion:
attributes:
apply_automatically:
@@ -647,6 +651,9 @@ en:
spree/price:
one: Price
other: Prices
+ spree/price_list:
+ one: Price List
+ other: Price Lists
spree/product:
one: Product
other: Products
@@ -874,6 +881,16 @@ en:
source_forms:
storecredit:
not_supported: Creating store credit payments via the admin is not currently supported.
+ price_list_items:
+ edit:
+ edit_price: Edit Price
+ new:
+ new_price: New Price
+ price_lists:
+ edit:
+ new_price: Add New Price
+ price_list_info:
+ price_details: Price Details
prices:
any_country: Any Country
edit:
@@ -952,6 +969,8 @@ en:
orders: Orders
overview: Overview
payments: Payments
+ price_list: Price List
+ price_lists: Price Lists
products: Products
properties: Property Types
rma: RMA
@@ -1155,6 +1174,7 @@ en:
confirm_delete: Confirm Deletion
confirm_order: Confirm Order
confirm_password: Password Confirmation
+ contain_taxes: Tax Included
continue: Continue
continue_shopping: Continue shopping
cost_currency: Cost Currency
@@ -1616,6 +1636,9 @@ en:
country: 'This determines in what country the price is valid. Default: Any Country' master_variant: Changing master variant prices will not change variant prices below, but will be used to populate all new variants options: These options are used to create variants in the variants table. They can be changed in the variants tab + spree/price_list: + country: 'This determines in what country the price is valid. Default: Any Country' + currency: This determines which currency will be used for the price list's prices. Please, be aware that this will be used while adding prices to the price list's. spree/product: available_on: This sets the availability date for the product. If this value is not set, or it is set to a date in the future, then the product is not available on the storefront. discontinue_on: This sets the discontinue date for the product. If this value is set to a date, then the product is not available on the storefront from that day on anymore. @@ -1794,6 +1817,7 @@ en: new_order_completed: New Order Completed new_payment: New Payment new_payment_method: New Payment Method + new_price_list: New Price List new_product: New Product new_property: New Property Type new_refund: New Refund diff --git a/core/db/migrate/20250409090546_create_price_lists.rb b/core/db/migrate/20250409090546_create_price_lists.rb new file mode 100644 index 00000000000..63f831259c0 --- /dev/null +++ b/core/db/migrate/20250409090546_create_price_lists.rb @@ -0,0 +1,12 @@ +class CreatePriceLists < ActiveRecord::Migration[7.0] + def change + create_table :spree_price_lists do |t| + t.string :name + t.string :country_iso, limit: 2 + t.string :currency + t.boolean :contain_taxes, default: false + t.index ["country_iso"], name: "index_spree_price_lists_on_country_iso" + t.timestamps + end + end +end diff --git a/core/db/migrate/20250409090608_create_price_list_items.rb b/core/db/migrate/20250409090608_create_price_list_items.rb new file mode 100644 index 00000000000..762b9c9a0cd --- /dev/null +++ b/core/db/migrate/20250409090608_create_price_list_items.rb @@ -0,0 +1,9 @@ +class CreatePriceListItems < ActiveRecord::Migration[7.0] + def change + create_table :spree_price_list_items do |t| + t.references :price_list, foreign_key: { to_table: :spree_price_lists } + t.references :price, type: :integer, foreign_key: { to_table: :spree_prices } + t.timestamps + end + end +end diff --git a/core/db/migrate/20250409090748_add_contain_taxes_to_prices.rb b/core/db/migrate/20250409090748_add_contain_taxes_to_prices.rb new file mode 100644 index 00000000000..2ca150aa122 --- /dev/null +++ b/core/db/migrate/20250409090748_add_contain_taxes_to_prices.rb @@ -0,0 +1,5 @@ +class AddContainTaxesToPrices < ActiveRecord::Migration[7.0] + def change + add_column :spree_prices, :contain_taxes, :boolean, default: false + end +end diff --git a/core/db/migrate/20250409093925_add_price_list_to_user_group.rb b/core/db/migrate/20250409093925_add_price_list_to_user_group.rb new file mode 100644 index 00000000000..d19b53fe30a --- /dev/null +++ b/core/db/migrate/20250409093925_add_price_list_to_user_group.rb @@ -0,0 +1,7 @@ +class AddPriceListToUserGroup < ActiveRecord::Migration[7.0] + def change + change_table :spree_user_groups do |t| + t.references :price_list, foreign_key: { to_table: :spree_price_lists } + end + end +end |