后台定价保存不再清空 3D 段

- payload 的 model3d 缺席或为 null 统一表示沿用当前 3D 定价,显式给出时才覆盖
- 保存路径先取当前快照合并,避免改图 / 视频价格让 3D 提交一直失败关闭
- 补用例覆盖「省略保持」与「显式覆盖」两种语义
This commit is contained in:
2026-09-22 15:27:19 +08:00
parent ee2d505eb3
commit 050909a0b7
2 changed files with 61 additions and 1 deletions
@@ -110,6 +110,17 @@ impl EditorGenerationPricingConfig {
.price(query)
}
/// 后台 payload 的「可省略」语义:`model3d` 缺席或为 null 表示沿用当前 3D 段。
///
/// 3D 段目前只由文件配置提供(后台记录形状还表达不了 endpoint × 模型版本 × 是否带贴图
/// 的底价与 add-on 叠加),因此保存图 / 视频价格时绝不能把它清成 None。
pub(crate) fn with_previous_model3d(mut self, previous: &Self) -> Self {
if self.model3d.is_none() {
self.model3d = previous.model3d.clone();
}
self
}
pub(crate) fn image_model_mud_points(
&self,
model: Option<&str>,
+50 -1
View File
@@ -510,7 +510,9 @@ fn editor_generation_pricing_from_record(
}
// TODO Tripo 3D 定价:后台记录形状还只有 `模型 → 档位 → 单价`,表达不了
// endpoint × 模型版本 × 是否有贴图的底价与 add-on 叠加,因此 3D 段暂由文件配置提供,
// 后台改价不会覆盖它;等记录形状补齐后再改为从 SpacetimeDB 读取。
// 后台 payload 的 `model3d` 可空:省略 / null 一律沿用当前值(见保存路径的
// `with_previous_model3d`),后台改价不会覆盖它;
// 等记录形状补齐后再改为从 SpacetimeDB 读取。
let config = EditorGenerationPricingConfig {
models,
model3d: legacy_fallback.model3d.clone(),
@@ -797,6 +799,12 @@ impl AppState {
admin_user_id: String,
next: EditorGenerationPricingConfig,
) -> Result<EditorGenerationPricingConfig, EditorGenerationPricingError> {
// 后台 payload 的 `model3d` 允许缺席或为 null:两种情况都表示沿用当前 3D 定价。
// 3D 段现在只由文件配置提供,若不合并,改一次图 / 视频价格就会把它清成 None,
// 3D 提交会一直失败关闭到进程重启。
let previous = self.editor_generation_pricing_store.snapshot()?;
let next = next.with_previous_model3d(&previous);
#[cfg(test)]
{
let _ = admin_user_id;
@@ -2778,6 +2786,47 @@ mod tests {
use spacetime_client::SpacetimeClientStage;
use super::*;
use crate::tripo3d::pricing::Model3dAddOn;
/// 后台 payload 省略 / 置空 3D 段时必须沿用当前 3D 定价:
/// 改一次图 / 视频价格不能把文件提供的 3D 段清掉。
#[tokio::test]
async fn saving_pricing_without_model3d_keeps_existing_model3d() {
let state = AppState::new(AppConfig::default()).expect("state should build");
let before = state
.editor_generation_pricing()
.await
.expect("默认定价必须可读");
let default_model3d = before.model3d.clone().expect("默认配置必须带 3D 定价段");
let mut omitted = before.clone();
omitted.model3d = None;
let saved = state
.save_editor_generation_pricing("admin:test".to_string(), omitted)
.await
.expect("省略 3D 段的保存必须成功");
assert_eq!(saved.model3d.as_ref(), Some(&default_model3d));
// 显式给出 3D 段时按 payload 生效,而不是无脑沿用旧值。
let mut custom = default_model3d.clone();
custom.add_on_prices.insert(Model3dAddOn::QuadMesh, 7);
let mut explicit = before.clone();
explicit.model3d = Some(custom.clone());
let saved = state
.save_editor_generation_pricing("admin:test".to_string(), explicit)
.await
.expect("显式 3D 段的保存必须成功");
assert_eq!(saved.model3d.as_ref(), Some(&custom));
assert_eq!(
state
.editor_generation_pricing()
.await
.expect("定价必须可读")
.model3d
.as_ref(),
Some(&custom)
);
}
#[test]
fn template_library_credentials_never_mix_dedicated_and_general_pairs() {