diff --git a/server-rs/crates/spacetime-module/src/runtime/active/profile.rs b/server-rs/crates/spacetime-module/src/runtime/active/profile.rs index 869fc7af6..f028340c5 100644 --- a/server-rs/crates/spacetime-module/src/runtime/active/profile.rs +++ b/server-rs/crates/spacetime-module/src/runtime/active/profile.rs @@ -8855,6 +8855,7 @@ pub fn init_profile_membership_plan_catalog(ctx: &ReducerContext) { fn ensure_default_profile_recharge_product_config(ctx: &ReducerContext) { if ctx.db.profile_recharge_product_config().count() > 0 { migrate_legacy_default_profile_recharge_product_config(ctx); + converge_default_profile_recharge_product_config(ctx); return; } @@ -8961,6 +8962,64 @@ fn migrate_legacy_default_profile_recharge_product_config(ctx: &ReducerContext) } } +/// 存量库 / 冲突迁移回灌后把充点商品收敛到当前六档目录。 +/// +/// 迁移目的:冲突迁移会清库发布再回灌旧 `profile_recharge_product_config`;旧库里的 +/// `member_*` 会员商品行会被原样带回,而新增的 `points_1280` / `points_3280` 不在旧包里, +/// [`migrate_legacy_default_profile_recharge_product_config`] 又只归一旧行、不补缺档。 +/// 这里在读取路径幂等补齐缺失档位并删除已退役的会员商品配置行。 +fn converge_default_profile_recharge_product_config(ctx: &ReducerContext) { + let now = ctx.timestamp; + for (sort_order, product) in runtime_profile_recharge_point_products() + .into_iter() + .enumerate() + { + let product_id = product.product_id.clone(); + if ctx + .db + .profile_recharge_product_config() + .product_id() + .find(&product_id) + .is_some() + { + continue; + } + ctx.db + .profile_recharge_product_config() + .insert(ProfileRechargeProductConfig { + product_id: product.product_id, + title: product.title, + price_cents: product.price_cents, + kind: product.kind, + points_amount: product.points_amount, + bonus_points: product.bonus_points, + duration_days: product.duration_days, + badge_label: product.badge_label, + description: product.description, + enabled: true, + sort_order: sort_order as i32, + created_by: PROFILE_RECHARGE_PRODUCT_SYSTEM_USER_ID.to_string(), + created_at: now, + updated_by: PROFILE_RECHARGE_PRODUCT_SYSTEM_USER_ID.to_string(), + updated_at: now, + }); + } + + let retired_membership_product_ids = ctx + .db + .profile_recharge_product_config() + .iter() + .filter(|row| row.kind == RuntimeProfileRechargeProductKind::Membership) + .map(|row| row.product_id) + .collect::>(); + for product_id in retired_membership_product_ids { + ctx.db + .profile_recharge_product_config() + .product_id() + .delete(&product_id); + } +} + fn profile_recharge_product_config_rows( ctx: &ReducerContext, include_disabled: bool,