solidus_advanced_pricing 0.1.0 → 0.2.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3dd5e3bb515d712553025000029ebf739470339c09c0a615b70084a134ceff3c
4
- data.tar.gz: 065ce411b91483ac3e6b3ef13f09144d5015c986cc27fbbff9c1544bf4978355
3
+ metadata.gz: 1750fd578f0dd63061c1ad728e7418e11bd128ee4bd18d602b58bdc43d6d84c1
4
+ data.tar.gz: e56ded8723862a6ac3513dd4ac9df9a5f074a7a5dfcc41664d595a440c67cbf4
5
5
  SHA512:
6
- metadata.gz: 740e98329afaa1a757bde107a9ea231d22b4a3c92ae0840b0a3ea9462b0d6227b3d93acfc05a4c27a43ec285b4e0fc02b99056f3352950e6047ac7919a975c56
7
- data.tar.gz: e4426d9b9fac0bda2db9a87f228f424ced485a16003c5ec220dfa845756cdf87c637605f2c8e6fe0f4802149b9f16f6207efab762b43db73e0510f2ab25bea13
6
+ metadata.gz: 158337d85d20a1d2099954234f17a1adf6c3e5eef14369a60379bb690edfd9fd42874dd442a17980a89427f1d2d3cfff14f460350ed173cd3bde65a582954ee3
7
+ data.tar.gz: ca2d434c231a0e0ea9d9e9bc88730d39b66455a9296cce9809935337ff7d0b5fd7e54eb35471aed2456e3799a1ec4241a4f4d99db2d6fb7f5660d975061bd09d
data/CHANGELOG.md CHANGED
@@ -1,3 +1,68 @@
1
1
  # Changelog
2
2
 
3
- See https://github.com/solidusio-contrib/solidus_advanced_pricing/releases or OLD_CHANGELOG.md for older versions.
3
+ All notable changes to this project are documented here. The format is based on
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to
5
+ [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [0.2.0] - 2026-09-23
8
+
9
+ ### Added
10
+
11
+ - Price types can carry a **default role**. A price with no role of its own inherits its
12
+ type's role, so a type like `employee` can be targeted once instead of on every price.
13
+ A price's own role still wins when set. This is the recommended way to avoid publishing
14
+ a role-named price to everyone.
15
+ - `map` and `promotional` price types are now seeded, bringing the set to six.
16
+
17
+ ### Fixed
18
+
19
+ - **An untyped price could not be created in the admin.** The price type select had no
20
+ blank option, and defaulted to its first entry — so every price created through the UI
21
+ was silently typed `wholesale`. A regression from making `price_type_id` nullable in
22
+ 0.1.0.
23
+ - **Validity windows lost their time.** `valid_from` and `valid_to` are datetime columns,
24
+ but the form used Solidus' `.datepicker` class, which is initialised as flatpickr
25
+ without `enableTime` and can only produce a date. Any time an admin set was dropped to
26
+ midnight. Now rendered as native `datetime-local` inputs.
27
+ - Deleting a `Spree::Role` referenced by a price type's default role raised a raw foreign
28
+ key violation instead of being blocked with an error.
29
+
30
+ ### Changed
31
+
32
+ - **The price type is now locked once a price exists**, matching how core treats the
33
+ country field. Changing which pricing dimension an existing row belongs to reinterprets
34
+ historical data rather than correcting it.
35
+
36
+ ## [0.1.0] - 2026-09-23
37
+
38
+ Initial release.
39
+
40
+ ### Added
41
+
42
+ - **Price types** — an admin-managed model with a code, name and position. `price_type_id`
43
+ on `spree_prices` is nullable; `NULL` means the untyped base price.
44
+ - **Validity windows** — optional `valid_from` / `valid_to` on each price. `valid_to` is
45
+ exclusive.
46
+ - **Role targeting** — an optional `Spree::Role` per price. Blank means every customer,
47
+ guests included. Role is *eligibility only*, never specificity: a role-targeted price set
48
+ above the untargeted price never applies.
49
+ - **`admin_notes`** — internal commentary on a price, never rendered to customers.
50
+ - A `PriceSelector` and `PricingOptions` registered through
51
+ `Spree::Config.variant_price_selector_class`: filter by currency, type, window and role;
52
+ prefer the country-specific bucket over the any-country fallback; cheapest wins.
53
+ - `Spree::Variant#price_of_type` and `#base_price` for compare-at (strikethrough) display.
54
+ - `Spree::Variant.with_prices` now honours validity windows and role targeting, so a
55
+ variant whose only price has expired no longer counts as purchasable.
56
+ - Full legacy backend admin: price fields, table columns, a price types CRUD, and a
57
+ settings sidebar link. A price types index for `solidus_admin`.
58
+ - `GET /api/variants/:variant_id/prices` — admin-facing; `admin_notes` is exposed only to
59
+ a user who can update the price.
60
+
61
+ ### Security
62
+
63
+ - Pricing cache keys include the customer's pricing-relevant roles and a coarse time
64
+ bucket. Without the role component a role-targeted price would be cached and served to
65
+ guests.
66
+
67
+ [0.2.0]: https://github.com/fcpeuro/solidus_advanced_pricing/compare/v0.1.0...v0.2.0
68
+ [0.1.0]: https://github.com/fcpeuro/solidus_advanced_pricing/releases/tag/v0.1.0
data/README.md CHANGED
@@ -80,8 +80,8 @@ value for `price_of_type`.
80
80
 
81
81
  | Column | Type | Meaning |
82
82
  |---|---|---|
83
- | `price_type_id` | bigint, nullable | Belongs to `SolidusAdvancedPricing::PriceType`. `nil` means the untyped base price — exactly as `role_id: nil` means every customer. |
84
- | `role_id` | integer, nullable | Belongs to `Spree::Role`. `nil` means visible to everyone, including guests. |
83
+ | `price_type_id` | bigint, nullable | Belongs to `SolidusAdvancedPricing::PriceType`. `nil` means the untyped base price — exactly as `role_id: nil` means "inherit from the type" (see below). |
84
+ | `role_id` | integer, nullable | Belongs to `Spree::Role`. `nil` does **not** mean "public" — it means the price's visibility is inherited from its price type, if any. See [Effective role](#effective-role) below. |
85
85
  | `valid_from` | datetime, nullable | Window opens here. `nil` means always open. |
86
86
  | `valid_to` | datetime, nullable | Window closes here, **exclusive** — a price is valid up to but not including this instant, so a window ending at midnight and the next one starting at midnight don't both match. `nil` means never closes. |
87
87
  | `admin_notes` | text, nullable | Internal only. Never shown to customers; see [API](#api) for exactly who can see it. |
@@ -89,15 +89,61 @@ value for `price_of_type`.
89
89
  `price_type_id`, `role_id`, `valid_from` and `valid_to` are all optional. `valid_to`
90
90
  must be after `valid_from` when both are set.
91
91
 
92
- ## A price type is not access control
92
+ `SolidusAdvancedPricing::PriceType` also carries a nullable `role_id` of its own —
93
+ the type's *default* role. See [Effective role](#effective-role).
93
94
 
94
- **Setting a price's `price_type` to `employee` does nothing on its own.** Type and
95
- role are independent columns. A price typed `employee` with `role_id` left blank is
96
- visible to every customer, guests included — the name is just a label for the admin
97
- UI and reporting; it grants no eligibility by itself. Two of the six seeded types,
98
- `wholesale` and `employee`, exist specifically to invite this mistake.
95
+ ## Effective role
99
96
 
100
- If a price should only be available to a specific group, you must **also** set `role`:
97
+ A price's visibility to a customer is never decided by its own `role_id` alone. It is
98
+ decided by its **effective role**:
99
+
100
+ ```ruby
101
+ price.role_id || price.price_type&.role_id
102
+ ```
103
+
104
+ **`nil` on a price's own `role_id` means "inherit from the type," not "explicitly
105
+ public."** If the price has no type, or its type has no default role, the effective
106
+ role is `nil` and the price is visible to everyone, including guests — exactly as
107
+ before this feature existed. But once a price type carries a default role, every
108
+ price of that type inherits it unless the price sets its own `role_id`.
109
+
110
+ The deliberate consequence: **if the `employee` type has a default role, you cannot
111
+ make an employee-typed price public.** Its `role_id` is either blank (inherits the
112
+ type's role) or set to some other role (its own, more specific, targeting) — there is
113
+ no value that means "ignore the type's default and show this to everyone." That
114
+ tradeoff is the point. It turns this gem's biggest footgun — a price typed `employee`
115
+ with no role, visible to every customer including guests — into something a store
116
+ fixes once, on the type, instead of something every price author must remember.
117
+
118
+ Stores are **not** seeded with any type-level defaults; all six seeded types
119
+ (`wholesale`, `sale`, `clearance`, `employee`, `map`, `promotional`) ship with
120
+ `role_id: nil`, because the correct role for, say, `employee` doesn't exist in every
121
+ store. Set it yourself once eligibility should be automatic:
122
+
123
+ ```ruby
124
+ employee_role = Spree::Role.find_or_create_by!(name: "employee")
125
+ employee_type = SolidusAdvancedPricing::PriceType.find_by(code: "employee")
126
+ employee_type.update!(role: employee_role)
127
+
128
+ # Every price typed `employee`, existing or future, is now visible only to
129
+ # customers holding the `employee` role -- no per-price role_id required.
130
+ variant.prices.create!(amount: 45, currency: "USD", price_type: employee_type)
131
+ ```
132
+
133
+ ## A price type is not access control by default
134
+
135
+ **Setting a price's `price_type` to `employee` does nothing on its own unless the
136
+ type itself carries a default role.** Type and role are independent columns until a
137
+ store links them via the type's `role_id`. A price typed `employee`, with the type
138
+ having no default role and the price's own `role_id` left blank, is visible to every
139
+ customer, guests included — the type name alone is just a label for the admin UI and
140
+ reporting; it grants no eligibility by itself. Two of the six seeded types,
141
+ `wholesale` and `employee`, exist specifically to invite this mistake — and setting a
142
+ default role on the type (above) is the recommended way to close it for good, rather
143
+ than remembering to set `role_id` on every price of that type.
144
+
145
+ If you'd rather target one specific price without touching the type, set `role`
146
+ directly on it:
101
147
 
102
148
  ```ruby
103
149
  employee_role = Spree::Role.find_or_create_by!(name: "employee")
@@ -112,7 +158,9 @@ variant.prices.create!(
112
158
  ```
113
159
 
114
160
  With both fields set, a guest or any customer without the `employee` role still sees
115
- the ordinary price; only a customer holding that role sees $45.
161
+ the ordinary price; only a customer holding that role sees $45. This still works
162
+ exactly as before even once the type has its own default role — an explicit
163
+ `role_id` on the price always wins over the type's.
116
164
 
117
165
  ## How a price is chosen
118
166
 
@@ -121,9 +169,10 @@ in three steps:
121
169
 
122
170
  1. **Filter.** A price survives only if: its currency matches exactly; the current
123
171
  time falls inside its validity window (or it has none); and it's visible to the
124
- customer — `role_id` is blank, or the customer holds that role. If a price type is
125
- pinned (see [Admin vs. customer lookups](#admin-vs-customer-lookups) below),
126
- non-matching types are dropped here too.
172
+ customer — its [effective role](#effective-role) is blank, or the customer holds
173
+ that role. If a price type is pinned (see
174
+ [Admin vs. customer lookups](#admin-vs-customer-lookups) below), non-matching
175
+ types are dropped here too.
127
176
  2. **Country specificity.** Among what survives, a price matching the customer's
128
177
  country beats every country-agnostic (`country_iso: nil`) price — **even when the
129
178
  agnostic price is cheaper.** This matches core Solidus behavior.
@@ -30,8 +30,23 @@ module SolidusAdvancedPricing
30
30
  .where(arel_table[:valid_to].eq(nil).or(arel_table[:valid_to].gt(time)))
31
31
  }
32
32
 
33
+ # Tests the *effective* role (price's own role_id, falling back to its
34
+ # type's) via COALESCE over a LEFT JOIN, not just the price's column —
35
+ # a price whose type carries a default role must be excluded from a
36
+ # guest's listing even though the price row itself has role_id: nil.
37
+ # left_joins(:price_type) does not apply PriceType's `kept` default
38
+ # scope (verified empirically), so a retired type's default role still
39
+ # applies to its historical prices.
33
40
  base.scope :visible_to_roles, ->(role_ids) {
34
- where(role_id: [nil, *role_ids])
41
+ price_types_table = SolidusAdvancedPricing::PriceType.arel_table
42
+ effective_role_id = Arel::Nodes::NamedFunction.new(
43
+ "COALESCE", [arel_table[:role_id], price_types_table[:role_id]]
44
+ )
45
+
46
+ condition = effective_role_id.eq(nil)
47
+ condition = condition.or(effective_role_id.in(role_ids)) if role_ids.present?
48
+
49
+ left_joins(:price_type).where(condition)
35
50
  }
36
51
 
37
52
  base.scope :for_price_type, ->(price_type) {
@@ -41,6 +56,11 @@ module SolidusAdvancedPricing
41
56
  base.after_commit { SolidusAdvancedPricing::PriceTypeCache.clear }
42
57
  end
43
58
 
59
+ # nil on the price means "inherit from the type", not "public".
60
+ def effective_role_id
61
+ role_id || SolidusAdvancedPricing::PriceTypeCache.role_id_for(price_type_id)
62
+ end
63
+
44
64
  private
45
65
 
46
66
  def valid_to_after_valid_from
@@ -12,7 +12,9 @@ module SolidusAdvancedPricing
12
12
  private
13
13
 
14
14
  def prevent_destroying_referenced_role
15
- return unless ::Spree::Price.with_discarded.exists?(role_id: id)
15
+ referenced = ::Spree::Price.with_discarded.exists?(role_id: id) ||
16
+ SolidusAdvancedPricing::PriceType.with_discarded.exists?(role_id: id)
17
+ return unless referenced
16
18
 
17
19
  errors.add(:base, :referenced_by_prices)
18
20
  throw :abort
@@ -49,7 +49,9 @@ module SolidusAdvancedPricing
49
49
  end
50
50
 
51
51
  def visible_to?(price, customer_role_ids)
52
- price.role_id.nil? || customer_role_ids.include?(price.role_id)
52
+ effective = price.effective_role_id
53
+
54
+ effective.nil? || customer_role_ids.include?(effective)
53
55
  end
54
56
 
55
57
  # Country is specificity, not competition: a country-specific price beats the
@@ -21,6 +21,8 @@ module SolidusAdvancedPricing
21
21
  foreign_key: :price_type_id,
22
22
  inverse_of: :price_type
23
23
 
24
+ belongs_to :role, class_name: "::Spree::Role", optional: true
25
+
24
26
  # Not `dependent: :restrict_with_error` — that check is default-scoped and
25
27
  # misses discarded prices, which then trip the FK on DELETE.
26
28
  before_destroy :prevent_destroying_referenced_type
@@ -6,10 +6,15 @@ module SolidusAdvancedPricing
6
6
  # and, later, by Spree::Price.
7
7
  module PriceTypeCache
8
8
  class << self
9
+ # Union with type-level defaults: a role that appears only on a price type
10
+ # (never directly on a price) must still narrow in, or a customer holding
11
+ # only that role would be filtered out before selection ever runs.
9
12
  def pricing_role_ids
10
13
  return @pricing_role_ids if defined?(@pricing_role_ids)
11
14
 
12
- @pricing_role_ids = ::Spree::Price.distinct.pluck(:role_id).compact
15
+ @pricing_role_ids = (
16
+ ::Spree::Price.distinct.pluck(:role_id) + PriceType.with_discarded.distinct.pluck(:role_id)
17
+ ).compact.uniq
13
18
  end
14
19
 
15
20
  # id => position, for the selector's tie-breaker. A handful of rows that
@@ -35,10 +40,23 @@ module SolidusAdvancedPricing
35
40
  ids_by_code[code.to_s]
36
41
  end
37
42
 
43
+ # id => role_id, for the effective-role fallback. with_discarded matters —
44
+ # a retired type's default role must still apply to historical prices.
45
+ def role_ids
46
+ return @role_ids if defined?(@role_ids)
47
+
48
+ @role_ids = PriceType.with_discarded.pluck(:id, :role_id).to_h
49
+ end
50
+
51
+ def role_id_for(price_type_id)
52
+ role_ids[price_type_id]
53
+ end
54
+
38
55
  def clear
39
56
  remove_instance_variable(:@pricing_role_ids) if defined?(@pricing_role_ids)
40
57
  remove_instance_variable(:@positions) if defined?(@positions)
41
58
  remove_instance_variable(:@ids_by_code) if defined?(@ids_by_code)
59
+ remove_instance_variable(:@role_ids) if defined?(@role_ids)
42
60
  end
43
61
  end
44
62
  end
@@ -6,7 +6,10 @@ en:
6
6
  solidus_advanced_pricing:
7
7
  price_types: "Price Types"
8
8
  all_customers: "All customers"
9
- role_hint: "Leave blank for all customers. A price type name alone grants no access control — an employee-only price needs this field set."
9
+ base_price: "Base price (no type)"
10
+ price_type_hint: "Leave as Base price for the standard price. A type is a label for the admin and reporting — it grants no eligibility on its own."
11
+ role_hint: "Leave blank to inherit the price type's default role, if it has one; otherwise visible to all customers. A price type name alone grants no access control — an employee-only price needs this field, or the type, set."
12
+ price_type_role_hint: "Prices of this type default to this role unless a price sets its own. Leaving a price's own role blank means it inherits this default — it does not make that price public."
10
13
  admin_notes_hint: "Internal only. Never shown to customers."
11
14
  validity: "Validity"
12
15
  solidus_admin:
@@ -22,6 +25,8 @@ en:
22
25
  one: "Price Type"
23
26
  other: "Price Types"
24
27
  attributes:
28
+ solidus_advanced_pricing/price_type:
29
+ role_id: "Default role"
25
30
  spree/price:
26
31
  price_type_id: "Price Type"
27
32
  role_id: "Visible to role"
@@ -41,4 +46,4 @@ en:
41
46
  spree/role:
42
47
  attributes:
43
48
  base:
44
- referenced_by_prices: "This role is used to target one or more prices and cannot be deleted."
49
+ referenced_by_prices: "This role is used to target one or more prices or price types and cannot be deleted."
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddRoleToSolidusAdvancedPricingPriceTypes < ActiveRecord::Migration[7.0]
4
+ def change
5
+ # :integer, not :bigint — spree_roles.id is int and MySQL rejects mismatched FK precision.
6
+ add_column :solidus_advanced_pricing_price_types, :role_id, :integer
7
+ add_index :solidus_advanced_pricing_price_types, :role_id
8
+ add_foreign_key :solidus_advanced_pricing_price_types, :spree_roles, column: :role_id, on_delete: :restrict
9
+ end
10
+ end
@@ -56,6 +56,10 @@ module SolidusAdmin
56
56
  {
57
57
  header: :position,
58
58
  data: ->(price_type) { price_type.position }
59
+ },
60
+ {
61
+ header: :role_id,
62
+ data: ->(price_type) { price_type.role&.name || t("solidus_advanced_pricing.all_customers") }
59
63
  }
60
64
  ]
61
65
  end
@@ -7,7 +7,7 @@ module SolidusAdmin
7
7
  def resource_class = SolidusAdvancedPricing::PriceType
8
8
 
9
9
  def permitted_resource_params
10
- params.require(:price_type).permit(:name, :code, :position)
10
+ params.require(:price_type).permit(:name, :code, :position, :role_id)
11
11
  end
12
12
 
13
13
  # ResourcesController derives these from resource_class.model_name, which for
@@ -14,7 +14,7 @@ module Spree
14
14
  end
15
15
 
16
16
  def permitted_resource_params
17
- params.require(:price_type).permit(:name, :code, :position)
17
+ params.require(:price_type).permit(:name, :code, :position, :role_id)
18
18
  end
19
19
 
20
20
  # ResourceController derives these from model_class.model_name, which for a
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SolidusAdvancedPricing
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -17,4 +17,15 @@
17
17
  <%= f.number_field :position, class: 'fullwidth' %>
18
18
  <% end %>
19
19
  </div>
20
+ <div class="col-6">
21
+ <%= f.field_container :role do %>
22
+ <%= f.label :role_id, Spree::Role.model_name.human %>
23
+ <%= f.collection_select :role_id,
24
+ Spree::Role.order(:name),
25
+ :id, :name,
26
+ { include_blank: t('solidus_advanced_pricing.all_customers') },
27
+ { class: 'custom-select fullwidth' } %>
28
+ <span class="field-hint"><%= t('solidus_advanced_pricing.price_type_role_hint') %></span>
29
+ <% end %>
30
+ </div>
20
31
  </div>
@@ -12,6 +12,7 @@
12
12
  <th><%= SolidusAdvancedPricing::PriceType.human_attribute_name(:name) %></th>
13
13
  <th><%= SolidusAdvancedPricing::PriceType.human_attribute_name(:code) %></th>
14
14
  <th><%= SolidusAdvancedPricing::PriceType.human_attribute_name(:position) %></th>
15
+ <th><%= SolidusAdvancedPricing::PriceType.human_attribute_name(:role_id) %></th>
15
16
  <th class="actions"></th>
16
17
  </tr>
17
18
  </thead>
@@ -21,6 +22,7 @@
21
22
  <td><%= price_type.name %></td>
22
23
  <td><%= price_type.code %></td>
23
24
  <td><%= price_type.position %></td>
25
+ <td><%= price_type.role&.name || t('solidus_advanced_pricing.all_customers') %></td>
24
26
  <td class="actions">
25
27
  <%= link_to_edit price_type, no_text: true %>
26
28
  <%= link_to_delete price_type, no_text: true %>
@@ -5,8 +5,9 @@
5
5
  <%= f.collection_select :price_type_id,
6
6
  SolidusAdvancedPricing::PriceType.ordered,
7
7
  :id, :name,
8
- {},
9
- { class: 'custom-select fullwidth' } %>
8
+ { include_blank: t('solidus_advanced_pricing.base_price') },
9
+ { class: 'custom-select fullwidth', disabled: !f.object.new_record? } %>
10
+ <span class="field-hint"><%= t('solidus_advanced_pricing.price_type_hint') %></span>
10
11
  <% end %>
11
12
  </div>
12
13
 
@@ -25,14 +26,14 @@
25
26
  <div class="col-2">
26
27
  <%= f.field_container :valid_from do %>
27
28
  <%= f.label :valid_from %>
28
- <%= f.text_field :valid_from, value: f.object.valid_from&.iso8601, class: 'fullwidth datepicker' %>
29
+ <%= f.datetime_local_field :valid_from, class: 'fullwidth' %>
29
30
  <% end %>
30
31
  </div>
31
32
 
32
33
  <div class="col-2">
33
34
  <%= f.field_container :valid_to do %>
34
35
  <%= f.label :valid_to %>
35
- <%= f.text_field :valid_to, value: f.object.valid_to&.iso8601, class: 'fullwidth datepicker' %>
36
+ <%= f.datetime_local_field :valid_to, class: 'fullwidth' %>
36
37
  <% end %>
37
38
  </div>
38
39
 
@@ -10,12 +10,12 @@ Gem::Specification.new do |spec|
10
10
 
11
11
  spec.summary = "Price types, validity windows and role targeting for Solidus prices"
12
12
  spec.description = "Adds an admin-managed price type, an optional validity window, optional single-role targeting and internal admin notes to Spree::Price, selected through Solidus' own variant_price_selector_class seam."
13
- spec.homepage = "https://github.com/solidusio-contrib/solidus_advanced_pricing#readme"
13
+ spec.homepage = "https://github.com/fcpeuro/solidus_advanced_pricing#readme"
14
14
  spec.license = "BSD-3-Clause"
15
15
 
16
16
  spec.metadata["homepage_uri"] = spec.homepage
17
- spec.metadata["source_code_uri"] = "https://github.com/solidusio-contrib/solidus_advanced_pricing"
18
- spec.metadata["changelog_uri"] = "https://github.com/solidusio-contrib/solidus_advanced_pricing/blob/main/CHANGELOG.md"
17
+ spec.metadata["source_code_uri"] = "https://github.com/fcpeuro/solidus_advanced_pricing"
18
+ spec.metadata["changelog_uri"] = "https://github.com/fcpeuro/solidus_advanced_pricing/blob/main/CHANGELOG.md"
19
19
 
20
20
  spec.required_ruby_version = ">= 3.1"
21
21
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solidus_advanced_pricing
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrick McMorran
@@ -178,6 +178,7 @@ files:
178
178
  - db/migrate/20260915000001_create_solidus_advanced_pricing_price_types.rb
179
179
  - db/migrate/20260915000002_seed_solidus_advanced_pricing_price_types.rb
180
180
  - db/migrate/20260915000003_add_advanced_pricing_to_spree_prices.rb
181
+ - db/migrate/20260923000001_add_role_to_solidus_advanced_pricing_price_types.rb
181
182
  - docs/superpowers/specs/2026-09-14-solidus-advanced-pricing-design.md
182
183
  - lib/components/admin/solidus_admin/price_types/index/component.rb
183
184
  - lib/controllers/admin/solidus_admin/price_types_controller.rb
@@ -201,13 +202,13 @@ files:
201
202
  - lib/views/backend/spree/admin/prices/_advanced_columns.html.erb
202
203
  - lib/views/backend/spree/admin/prices/_advanced_fields.html.erb
203
204
  - solidus_advanced_pricing.gemspec
204
- homepage: https://github.com/solidusio-contrib/solidus_advanced_pricing#readme
205
+ homepage: https://github.com/fcpeuro/solidus_advanced_pricing#readme
205
206
  licenses:
206
207
  - BSD-3-Clause
207
208
  metadata:
208
- homepage_uri: https://github.com/solidusio-contrib/solidus_advanced_pricing#readme
209
- source_code_uri: https://github.com/solidusio-contrib/solidus_advanced_pricing
210
- changelog_uri: https://github.com/solidusio-contrib/solidus_advanced_pricing/blob/main/CHANGELOG.md
209
+ homepage_uri: https://github.com/fcpeuro/solidus_advanced_pricing#readme
210
+ source_code_uri: https://github.com/fcpeuro/solidus_advanced_pricing
211
+ changelog_uri: https://github.com/fcpeuro/solidus_advanced_pricing/blob/main/CHANGELOG.md
211
212
  rdoc_options: []
212
213
  require_paths:
213
214
  - lib