feat(ops): add global test whitelist and exclude test accounts from settlement

Unify product/store visibility on HQ whitelist, mark isTest snapshots, and fix SUPER_ADMIN access for the new module.
This commit is contained in:
2026-08-07 15:46:23 +08:00
parent 10fa361983
commit b626db5d84
47 changed files with 1823 additions and 308 deletions
+34
View File
@@ -812,6 +812,21 @@ model CommonProductVisibilityPhone {
@@map("common_product_visibility_phone")
}
/// 全局测试白名单手机号(测试账号 + 限测商品/门店可见)
model CommonTestWhitelistPhone {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
phone String @unique @db.VarChar(20)
note String? @db.VarChar(256)
createdByHqId BigInt? @map("created_by_hq_id") @db.UnsignedBigInt
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.DateTime(3)
createdBy HqAccount? @relation("TestWhitelistCreatedBy", fields: [createdByHqId], references: [id], onDelete: SetNull)
@@index([createdAt])
@@map("common_test_whitelist_phone")
}
model CommonProductDetailTemplate {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
code String @unique @db.VarChar(32)
@@ -1043,6 +1058,8 @@ model PartnerAccount {
bankBranch String? @map("bank_branch") @db.VarChar(128)
weeklyStoreTarget Int? @default(20) @map("weekly_store_target")
managedWarehouseId BigInt? @unique @map("managed_warehouse_id") @db.UnsignedBigInt
/// 测试合伙人账号
isTest Boolean @default(false) @map("is_test")
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.DateTime(3)
@@ -1059,6 +1076,7 @@ model PartnerAccount {
@@index([parentAccountId])
@@index([wxOpenId])
@@index([contactPhone])
@@index([isTest])
@@map("partner_account")
}
@@ -1102,6 +1120,7 @@ model HqAccount {
updatedAt DateTime @updatedAt @map("updated_at") @db.DateTime(3)
permissions HqAccountPermission[]
testWhitelistPhones CommonTestWhitelistPhone[] @relation("TestWhitelistCreatedBy")
@@map("hq_account")
}
@@ -1140,6 +1159,8 @@ model User {
nickname String? @db.VarChar(64)
avatarResourceId BigInt? @map("avatar_resource_id") @db.UnsignedBigInt
status Int @default(1) @db.TinyInt
/// 测试账号:命中全局测试白名单手机号
isTest Boolean @default(false) @map("is_test")
sourceType UserSourceType @default(ORGANIC) @map("source_type")
sourceRefId BigInt? @map("source_ref_id") @db.UnsignedBigInt
sourceLabel String? @map("source_label") @db.VarChar(128)
@@ -1166,6 +1187,7 @@ model User {
@@index([referrerUserId])
@@index([mergedIntoUserId])
@@index([wxOpenId])
@@index([isTest])
@@map("user_user")
}
@@ -1253,6 +1275,8 @@ model Store {
visibilityWhitelistEnabled Boolean @default(false) @map("visibility_whitelist_enabled")
/// FIN-001:允许未出账手动提现的白名单门店
withdrawWhitelistEnabled Boolean @default(false) @map("withdraw_whitelist_enabled")
/// 测试门店:不计结算 / HQ 可手动标记
isTest Boolean @default(false) @map("is_test")
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.DateTime(3)
@@ -1274,6 +1298,7 @@ model Store {
@@index([cityId, status])
@@index([partnerAccountId])
@@index([auditStatus, createdAt])
@@index([isTest])
@@map("store_store")
}
@@ -1344,6 +1369,8 @@ model StoreAccount {
bankBranch String? @map("bank_branch") @db.VarChar(128)
status AccountStatus @default(ACTIVE)
lastLoginAt DateTime? @map("last_login_at") @db.DateTime(3)
/// 测试门店账号(商户)
isTest Boolean @default(false) @map("is_test")
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.DateTime(3)
@@ -1354,6 +1381,7 @@ model StoreAccount {
withdrawRequests StoreWithdrawRequest[]
@@index([parentAccountId])
@@index([isTest])
@@map("store_account")
}
@@ -1432,6 +1460,8 @@ model Order {
fulfillmentHold Boolean @default(false) @map("fulfillment_hold")
fulfillmentHoldReason String? @map("fulfillment_hold_reason") @db.VarChar(64)
remark String? @db.VarChar(512)
/// 测试订单快照(下单时取自 User.isTest
isTest Boolean @default(false) @map("is_test")
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.DateTime(3)
@@ -1456,6 +1486,7 @@ model Order {
@@index([gpsCity])
@@index([fulfillmentWarehouseId])
@@index([proxyPartnerAccountId])
@@index([isTest])
@@map("user_order")
}
@@ -1548,6 +1579,8 @@ model RedeemRecord {
settleAmount Decimal @map("settle_amount") @db.Decimal(10, 2)
/// SCAN=qrcode, PHONE=phone
channel RedeemChannel @default(SCAN)
/// 测试核销快照(User.isTest || Store.isTest
isTest Boolean @default(false) @map("is_test")
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
user User @relation(fields: [userId], references: [id], onDelete: Restrict)
@@ -1560,6 +1593,7 @@ model RedeemRecord {
@@index([storeId, createdAt])
@@index([storeId, channel, createdAt])
@@index([isTest])
@@map("user_redeem_record")
}
+44
View File
@@ -61,6 +61,16 @@ async function main() {
await prisma.storePayout.deleteMany();
await prisma.storeWithdrawPayoutItem.deleteMany();
await prisma.storeWithdrawRequest.deleteMany();
await prisma.storeBill.deleteMany();
await prisma.redeemPendingRecord.deleteMany();
await prisma.logStoreAnalytics.deleteMany();
await prisma.storeRating.deleteMany();
await prisma.redeemRecord.deleteMany();
@@ -83,16 +93,28 @@ async function main() {
await prisma.user.deleteMany();
await prisma.storeAccount.updateMany({ data: { parentAccountId: null } });
await prisma.storeAccount.deleteMany();
await prisma.store.deleteMany();
await prisma.partnerBill.deleteMany();
await prisma.logisticsBillItem.deleteMany();
await prisma.logisticsPrepaidLedger.deleteMany();
await prisma.logisticsBill.deleteMany();
await prisma.wineryBillItem.deleteMany();
await prisma.wineryBill.deleteMany();
await prisma.cityWarehouse.deleteMany();
await prisma.fulfillmentProvider.deleteMany();
await prisma.partnerAccount.updateMany({ data: { parentAccountId: null } });
await prisma.partnerAccount.deleteMany();
await prisma.commonCity.deleteMany();
@@ -101,6 +123,7 @@ async function main() {
await prisma.commonProductDetailTemplate.deleteMany();
await prisma.commonStoreCategory.updateMany({ data: { parentId: null } });
await prisma.commonStoreCategory.deleteMany();
await prisma.commonPromoCode.deleteMany();
@@ -914,6 +937,25 @@ async function main() {
const testWhitelistPhones = [
'13800000001',
'13700000001',
'13700000002',
'13910000001',
'13910000002',
];
for (const phone of testWhitelistPhones) {
await prisma.commonTestWhitelistPhone.upsert({
where: { phone },
create: { phone, note: 'seed 测试账号' },
update: {},
});
await prisma.user.updateMany({ where: { phone }, data: { isTest: true } });
await prisma.storeAccount.updateMany({ where: { phone }, data: { isTest: true } });
await prisma.partnerAccount.updateMany({ where: { phone }, data: { isTest: true } });
await prisma.store.updateMany({ where: { phone }, data: { isTest: true } });
}
console.log('Seed complete:', {
city: city.name,
@@ -928,6 +970,8 @@ async function main() {
stores: createdStores.length,
testWhitelistPhones,
testPhones: {
user: '13800000001',