fix(test-round): 测试清单一批 bug 修复 + 团队月限额持久化

后端:
- accounts: 团队级月限额持久化(Team.monthly_credit_limit 三态 + PATCH/GET
  /api/auth/team/settings/,刷新不丢,PMC#12);头像上传 500→502 可读错误;
  设备下线真失效(删并重建 token,旧 token 401)
- assets: 审核类目加 model_portrait,消除三视图"无需审核"误报
- ai/projects/products: 模特已有三视图复用、产品三视图同步回商品库
  (metadata.view=three_view)、真人模特三视图回写

前端:
- 商品图删除判断改用真实图片数;团队成员弹窗禁点外部关闭
- 图片预览骨架铺满占位;平台套图左侧栏折叠改导航同款;收件箱长文换行对齐
- 团队月限额改真落库(乐观更新+失败回滚)

测试:新增 TeamSettingsTests(5 条),accounts 全套 36 tests 通过;前端 build 0 error

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
zyc
2026-06-29 18:50:22 +08:00
co-authored by Claude Opus 4.8
parent ca0c3e2300
commit b0b3ac2348
20 changed files with 476 additions and 57 deletions
+49
View File
@@ -596,9 +596,58 @@ class ProjectViewSet(TeamScopedViewSetMixin, ModelViewSet):
from apps.assets.review import submit_asset_for_review
transaction.on_commit(lambda a=asset: submit_asset_for_review(a))
# ZWQ#4 · 沿用模特库已有三视图:选了「已带三视图」的模特(官方/自有)作角色时,
# 把该模特的三视图也挂进项目(triview_of=本组采用立绘 id),前端即判定「已有三视图」、
# 不再要求点「AI 生成三视图」。仅对人物组生效;asset_id 传的是模特的 portrait_asset(原始 id),
# 故按原始 portrait_asset 反查 Model;官方模特三视图同立绘一样克隆进本团队再挂(team 隔离)。
if asset.category in (Asset.Category.PERSON, Asset.Category.MODEL_PORTRAIT):
self._attach_existing_model_triview(project, group, asset, source_portrait_id=asset_id, user=request.user)
promote_base_asset_stage_if_ready(project)
return Response(BaseAssetGroupSerializer(group).data)
def _attach_existing_model_triview(self, project, portrait_group, portrait_asset, *, source_portrait_id, user):
"""若被采用的立绘来自「已带三视图」的模特,把该三视图挂进项目并采用(triview_of=立绘 id)。
幂等:已存在该立绘的三视图组就跳过,不重复挂。best-effort,出错不阻断主采用流程。"""
from apps.assets.models import Model as ModelEntity
# source_portrait_id = 前端传的资产 id(模特的原始 portrait_asset);官方模特会被克隆,
# 故 portrait_asset 可能是克隆体。两边都查一遍找到对应 Model。
candidate_ids = {str(portrait_asset.id)}
if source_portrait_id:
candidate_ids.add(str(source_portrait_id))
model = (
ModelEntity.objects.filter(portrait_asset_id__in=candidate_ids, is_deleted=False)
.exclude(triview_asset__isnull=True)
.select_related("triview_asset")
.first()
)
if model is None or model.triview_asset is None:
return
portrait_key = str(portrait_asset.id)
# 已有该立绘的三视图组?幂等跳过。
existing = next(
(g for g in project.base_asset_groups.filter(kind=BaseAssetGroup.Kind.PERSON)
if (g.metadata or {}).get("triview_of") == portrait_key),
None,
)
if existing is not None:
return
tri_asset = model.triview_asset
# 官方模特三视图跨团队 → 克隆进本团队再挂(与立绘同款);本团队三视图直接用。
if tri_asset.team_id != project.team_id:
tri_asset = _clone_asset_into_team(tri_asset, team=project.team, user=user)
tri_group = BaseAssetGroup.objects.create(
project=project, kind=BaseAssetGroup.Kind.PERSON, prompt="",
metadata={"label": "·三视图", "triview_of": portrait_key, "adopt": "adopted"},
)
tri_group.candidate_assets.add(tri_asset)
tri_group.adopted_asset = tri_asset
tri_group.save(update_fields=["adopted_asset", "updated_at"])
if tri_asset.category in Asset.REVIEW_CATEGORIES and tri_asset.review_status != "active":
from apps.assets.review import submit_asset_for_review
transaction.on_commit(lambda a=tri_asset: submit_asset_for_review(a))
@action(detail=True, methods=["post"], url_path="generate-triview")
def generate_triview(self, request, pk=None):
"""流程步骤4 · 据某一版立绘资产生成它配套的三视图(image_edit 锁角色一致性,三视图绑定该立绘)。"""