v0.2.0:20 — Multi-policy scope for discount codes
A discount code can now apply to a subset of policies on a product (e.g. "Patron and Pro but not Creator") instead of being limited to exactly one policy or the entire product. - Migration 0018 adds `applies_to_policy_ids_json` (nullable JSON array of policy ids). Legacy `applies_to_policy_id` stays as the singular fallback when the JSON column is empty/NULL. - `DiscountCode::allowed_policy_ids()` helper unifies multi + singular into one Vec. Purchase + preview scope checks consult it. - `find_applicable_featured_discount` now narrows multi-policy candidates in Rust (small candidate set; index-friendly SQL would require json_each, deferred). - Admin API: `POST /v1/admin/discount-codes` accepts `policy_slugs` (array) alongside the existing `policy_slug` (singular). Multi wins when both are present. PATCH does not allow scope edits — same rule as the singular field (disable + recreate to re-scope). - UI: pill multi-select replaces the policy dropdown on the create form. Edit modal's scope label renders the comma-separated list. UI + schema both back-compat: existing codes keep working unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -292,13 +292,16 @@ pub async fn start(
|
||||
));
|
||||
}
|
||||
}
|
||||
// If the code is restricted to a specific policy and a tier was
|
||||
// selected, they must match. If no tier was selected, the code is
|
||||
// implicitly applied to the product's default policy at issuance
|
||||
// time, which we accept here (v0.1.0:27+).
|
||||
if let Some(restricted_pid) = &code.applies_to_policy_id {
|
||||
// If the code is restricted to one or more policies and a tier
|
||||
// was selected, the chosen tier must be in the allowed set.
|
||||
// `allowed_policy_ids()` unifies the multi-policy column (0018)
|
||||
// and the legacy singular column. If no tier was selected, the
|
||||
// code is implicitly applied to the product's default policy at
|
||||
// issuance time, which we accept here (v0.1.0:27+).
|
||||
let allowed = code.allowed_policy_ids();
|
||||
if !allowed.is_empty() {
|
||||
if let Some(chosen) = &chosen_policy {
|
||||
if restricted_pid != &chosen.id {
|
||||
if !allowed.iter().any(|p| *p == chosen.id) {
|
||||
return Err(AppError::BadRequest(
|
||||
"discount code does not apply to the selected tier".into(),
|
||||
));
|
||||
|
||||
Reference in New Issue
Block a user