| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141 |
- import asyncio
- import json
- import secrets
- from datetime import UTC, datetime, timedelta
- from uuid import UUID, uuid4
- from anyio import Path
- from sqlalchemy.ext.asyncio import async_sessionmaker
- from zhifu.core.config import Settings
- from zhifu.core.database import create_engine
- from zhifu.modules.after_sales.models import BusinessEvent, ExchangeRule, OrderProjection
- from zhifu.modules.billing.models import BillingGrant
- from zhifu.modules.identity.access_models import Principal, StoreGrant
- from zhifu.modules.identity.auth import password_hasher
- from zhifu.modules.identity.models import Store, Tenant
- async def main():
- settings = Settings()
- if settings.environment == "production":
- raise ValueError("开发账户只能在 development 或 test 环境创建")
- engine = create_engine(settings)
- sessions = async_sessionmaker(engine, expire_on_commit=False)
- tenant_id = UUID("ac700000-2026-4000-8000-000000000001")
- store_id = UUID("ac700000-2026-4000-8000-000000000002")
- target = Path(".work/frontend-accounts.json")
- # 数据保留在独立开发商家中;重复运行只更新该商家的演示订单观察时间。
- existing = (
- json.loads(await target.read_text(encoding="utf-8")) if await target.exists() else None
- )
- password = existing["password"] if existing else secrets.token_urlsafe(18)
- roles = {
- "customer01": (
- "CUSTOMER",
- ["orders:read", "tickets:read", "after_sales:create",
- "after_sales:confirm", "after_sales:cancel"],
- ),
- "staff01": (
- "STAFF",
- ["orders:read", "tickets:read", "after_sales:create", "after_sales:cancel",
- "conversations:read", "conversations:reply", "conversations:manage"],
- ),
- "supervisor01": ("STAFF", ["orders:read", "tickets:read", "approvals:decide"]),
- "admin01": (
- "STAFF",
- ["orders:read", "tickets:read", "rules:write", "knowledge:write", "billing:read",
- "billing:request", "billing:approve", "stores:manage", "staff:manage",
- "channels:manage", "agent:publish"],
- ),
- "finance02": ("STAFF", ["billing:read", "billing:request", "billing:approve"]),
- }
- actors = {}
- now = datetime.now(UTC).replace(tzinfo=None)
- try:
- async with sessions.begin() as session:
- tenant = await session.get(Tenant, tenant_id.bytes)
- if tenant is None:
- session.add(Tenant(id=tenant_id.bytes, name="前端开发独立商家"))
- await session.flush()
- session.add(Store(
- id=store_id.bytes, tenant_id=tenant_id.bytes, name="前端开发配件店"
- ))
- await session.flush()
- elif existing is None:
- raise ValueError("开发商家已存在,但本地账户文件缺失;请使用已有开发账户")
- for index, (username, (kind, permissions)) in enumerate(roles.items(), start=10):
- actor_id = UUID(f"ac700000-2026-4000-8000-{index:012d}")
- actors[username] = str(actor_id)
- principal = await session.get(Principal, actor_id.bytes)
- if principal is None:
- session.add(Principal(
- id=actor_id.bytes, tenant_id=tenant_id.bytes, username=username,
- password_hash=password_hasher.hash(password), kind=kind,
- ))
- await session.flush()
- for permission in permissions:
- session.add(StoreGrant(
- tenant_id=tenant_id.bytes, principal_id=actor_id.bytes,
- store_id=store_id.bytes, permission=permission,
- ))
- if permission.startswith("billing:"):
- session.add(BillingGrant(
- tenant_id=tenant_id.bytes, principal_id=actor_id.bytes,
- permission=permission,
- ))
- elif username == "admin01":
- # 开发管理员补充本次管理接口权限,其他账户授权保持原有配置。
- for permission in (
- "stores:manage", "staff:manage", "channels:manage", "agent:publish"
- ):
- if await session.get(
- StoreGrant,
- (tenant_id.bytes, actor_id.bytes, store_id.bytes, permission),
- ) is None:
- session.add(StoreGrant(
- tenant_id=tenant_id.bytes, principal_id=actor_id.bytes,
- store_id=store_id.bytes, permission=permission,
- ))
- await session.flush()
- order_id = UUID("ac700000-2026-4000-8000-000000000003")
- order = await session.get(OrderProjection, order_id.bytes)
- if order is None:
- session.add(OrderProjection(
- id=order_id.bytes, tenant_id=tenant_id.bytes, store_id=store_id.bytes,
- subject_id=UUID(actors["customer01"]).bytes,
- source_system="frontend-development", source_version=1,
- item_id="keyboard-01", product_type="keyboard", available_quantity=10,
- signed_at=now - timedelta(days=1), observed_at=now,
- ))
- else:
- order.observed_at = now
- rule = await session.get(ExchangeRule, (tenant_id.bytes, store_id.bytes, 1))
- if rule is None:
- session.add(ExchangeRule(
- tenant_id=tenant_id.bytes, store_id=store_id.bytes, version=1,
- window_days=7, product_types=["keyboard"],
- citation="签收七天内的键盘故障支持申请换货,以当前审批与履约结果为准。",
- ))
- session.add(BusinessEvent(
- id=uuid4().bytes, tenant_id=tenant_id.bytes, actor_id=UUID(int=0).bytes,
- aggregate_id=tenant_id.bytes, aggregate_version=1,
- event_type="development.frontend_accounts", request_id=str(uuid4()),
- payload={"source": "prepare_frontend_accounts", "accounts": list(roles)},
- occurred_at=now,
- ))
- await target.parent.mkdir(exist_ok=True)
- await target.write_text(
- json.dumps(
- {"tenant_id": str(tenant_id), "store_id": str(store_id),
- "order_id": str(order_id), "password": password, "actors": actors},
- ensure_ascii=False, indent=2,
- ),
- encoding="utf-8",
- )
- print("开发账户信息已保存至 .work/frontend-accounts.json;未输出密码。")
- finally:
- await engine.dispose()
- asyncio.run(main())
|