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 fff05e9dd..e37eed3dc 100644 --- a/server-rs/crates/spacetime-module/src/runtime/active/profile.rs +++ b/server-rs/crates/spacetime-module/src/runtime/active/profile.rs @@ -4192,6 +4192,18 @@ mod tests { assert_eq!(same_period.row.cycle_index, 1); assert_eq!(same_period.row.cycle_remaining_points, 120); + // 期号水位为 0(新列默认值)但账期窗口已存在:只补水期号,不重发本期额度。 + let mut stale_watermark = row(); + stale_watermark.cycle_index = 0; + let backfilled = advance_profile_membership_cycle_to( + stale_watermark, + Timestamp::from_micros_since_unix_epoch(anchor + 10 * day), + 400, + ); + assert!(!backfilled.advanced, "期号水位缺失不应被当成换期"); + assert_eq!(backfilled.row.cycle_index, 1); + assert_eq!(backfilled.row.cycle_remaining_points, 120); + let next_period = advance_profile_membership_cycle_to( row(), Timestamp::from_micros_since_unix_epoch(anchor + 28 * day), @@ -9424,7 +9436,13 @@ fn advance_profile_membership_cycle_to( // 起点取已落库的期号(水位下限),保证只前进、不后退。 let mut cycle_index = row.cycle_index.max(1); let mut window = membership_cycle_window(anchor_micros, cycle_index); - let mut advanced = row.cycle_resets_at.is_none() || row.cycle_index == 0; + // `cycle_resets_at` 缺失 = 账期窗口尚未初始化,需要写库补种并发放本期额度; + // `cycle_index == 0` 只是期号水位没写(新列默认值),不能据此判定越界、重发额度。 + let mut advanced = row.cycle_resets_at.is_none(); + if row.cycle_index == 0 { + // 仅补写期号水位:账期额度只应在真正越过边界时重置。 + row.cycle_index = 1; + } // 仅当 `at` 已越过当前窗口结束时刻才推进;时钟回拨时条件不成立,整行保持原状。 while window.1 <= at_micros && cycle_index < max_index { cycle_index = cycle_index.saturating_add(1);