import { Prisma, PrismaClient } from '@prisma/client'; export const SEED_TEST_PHONES = [ '13800000001', '13700000001', '13700000002', '13910000001', '13910000002', ] as const; export type TestDataScope = { whitelistPhones: { id: bigint; phone: string; note: string | null }[]; testUserIds: bigint[]; testStoreAccountIds: bigint[]; deletableTestStoreAccountIds: bigint[]; promotedTestStoreAccountIds: bigint[]; testPartnerIds: bigint[]; deletableTestPartnerIds: bigint[]; promotedTestPartnerIds: bigint[]; testStoreIds: bigint[]; deletableTestStoreIds: bigint[]; promotedTestStoreIds: bigint[]; testOrderIds: bigint[]; testRedeemIds: bigint[]; limitedProductIds: bigint[]; limitedProductsWithFormalOrders: { productId: bigint; name: string; formalOrderCount: number }[]; }; export type CrossRisk = { code: string; message: string; details: unknown[]; }; export async function collectTestDataScope(prisma: PrismaClient): Promise { const whitelistPhones = await prisma.commonTestWhitelistPhone.findMany({ orderBy: { createdAt: 'asc' }, select: { id: true, phone: true, note: true }, }); const [testUsers, testStoreAccounts, testPartners, testStores] = await Promise.all([ prisma.user.findMany({ where: { isTest: true }, select: { id: true } }), prisma.storeAccount.findMany({ where: { isTest: true }, select: { id: true } }), prisma.partnerAccount.findMany({ where: { isTest: true }, select: { id: true } }), prisma.store.findMany({ where: { isTest: true }, select: { id: true } }), ]); const testUserIds = testUsers.map((r) => r.id); const testStoreAccountIds = testStoreAccounts.map((r) => r.id); const testPartnerIds = testPartners.map((r) => r.id); const testStoreIds = testStores.map((r) => r.id); const testOrders = await prisma.order.findMany({ where: { OR: [ { isTest: true }, ...(testUserIds.length ? [{ userId: { in: testUserIds } }] : []), ], }, select: { id: true }, }); let testOrderIds = testOrders.map((r) => r.id); if (testOrderIds.length) { const linkedReshipments = await prisma.order.findMany({ where: { originOrderId: { in: testOrderIds } }, select: { id: true }, }); testOrderIds = [...new Set([...testOrderIds, ...linkedReshipments.map((r) => r.id)])]; } const testRedeems = await prisma.redeemRecord.findMany({ where: { OR: [ { isTest: true }, ...(testUserIds.length ? [{ userId: { in: testUserIds } }] : []), ], }, select: { id: true }, }); const testRedeemIds = testRedeems.map((r) => r.id); const storesWithFormalRedeems = testStoreIds.length ? await prisma.redeemRecord.findMany({ where: { storeId: { in: testStoreIds }, isTest: false, ...(testUserIds.length ? { userId: { notIn: testUserIds } } : {}), }, select: { storeId: true }, distinct: ['storeId'], }) : []; const promotedTestStoreIds = storesWithFormalRedeems.map((r) => r.storeId); const promotedSet = new Set(promotedTestStoreIds.map(String)); const deletableTestStoreIds = testStoreIds.filter((id) => !promotedSet.has(id.toString())); const partnerStores = testPartnerIds.length ? await prisma.store.findMany({ where: { partnerAccountId: { in: testPartnerIds } }, select: { id: true, partnerAccountId: true, isTest: true }, }) : []; const deletableTestPartnerIds: bigint[] = []; const promotedTestPartnerIds: bigint[] = []; for (const partnerId of testPartnerIds) { const stores = partnerStores.filter((s) => s.partnerAccountId === partnerId); const hasRetainedStore = stores.some((s) => !deletableTestStoreIds.some((id) => id === s.id)); if (hasRetainedStore || stores.length === 0) promotedTestPartnerIds.push(partnerId); else deletableTestPartnerIds.push(partnerId); } const accountBindings = testStoreAccountIds.length ? await prisma.storeAccountStore.findMany({ where: { storeAccountId: { in: testStoreAccountIds } }, select: { storeAccountId: true, storeId: true }, }) : []; const deletableTestStoreAccountIds: bigint[] = []; const promotedTestStoreAccountIds: bigint[] = []; for (const accountId of testStoreAccountIds) { const bindings = accountBindings.filter((b) => b.storeAccountId === accountId); const hasRetainedStore = bindings.some((b) => !deletableTestStoreIds.some((id) => id === b.storeId)); if (hasRetainedStore || bindings.length === 0) promotedTestStoreAccountIds.push(accountId); else deletableTestStoreAccountIds.push(accountId); } const limitedProducts = await prisma.commonProductItem.findMany({ where: { visibilityWhitelistEnabled: true }, select: { id: true, name: true }, }); const limitedProductIds = limitedProducts.map((p) => p.id); const limitedProductsWithFormalOrders: TestDataScope['limitedProductsWithFormalOrders'] = []; for (const product of limitedProducts) { const formalOrderCount = await prisma.order.count({ where: { productId: product.id, isTest: false, ...(testUserIds.length ? { userId: { notIn: testUserIds } } : {}), }, }); if (formalOrderCount > 0) { limitedProductsWithFormalOrders.push({ productId: product.id, name: product.name, formalOrderCount, }); } } return { whitelistPhones, testUserIds, testStoreAccountIds, deletableTestStoreAccountIds, promotedTestStoreAccountIds, testPartnerIds, deletableTestPartnerIds, promotedTestPartnerIds, testStoreIds, deletableTestStoreIds, promotedTestStoreIds, testOrderIds, testRedeemIds, limitedProductIds, limitedProductsWithFormalOrders, }; } export async function detectCrossRisks( prisma: PrismaClient, scope: TestDataScope, ): Promise { const risks: CrossRisk[] = []; if (scope.testPartnerIds.length) { const formalStoresUnderTestPartner = await prisma.store.findMany({ where: { partnerAccountId: { in: scope.testPartnerIds }, isTest: false, }, select: { id: true, name: true, phone: true, partnerAccountId: true }, }); if (formalStoresUnderTestPartner.length) { risks.push({ code: 'TEST_PARTNER_FORMAL_STORES', message: '测试合伙人名下仍有正式门店', details: formalStoresUnderTestPartner, }); } } if (scope.testStoreIds.length) { const formalRedeemsAtTestStore = await prisma.redeemRecord.findMany({ where: { storeId: { in: scope.testStoreIds }, isTest: false, ...(scope.testUserIds.length ? { userId: { notIn: scope.testUserIds } } : {}), }, select: { id: true, redeemNo: true, storeId: true, userId: true, amount: true }, take: 50, }); if (formalRedeemsAtTestStore.length) { risks.push({ code: 'TEST_STORE_FORMAL_REDEEMS', message: '测试门店上有正式用户核销;这些门店将改为取消测试标记并保留,不删除', details: formalRedeemsAtTestStore, }); } } if (scope.testOrderIds.length) { const paidWineryItems = await prisma.wineryBillItem.findMany({ where: { orderId: { in: scope.testOrderIds }, wineryBill: { status: 'PAID' }, }, select: { id: true, orderNo: true, wineryBill: { select: { billNo: true, status: true } }, }, take: 50, }); if (paidWineryItems.length) { risks.push({ code: 'PAID_WINERY_BILL_TEST_ORDERS', message: '酒厂账单已打款且含测试订单', details: paidWineryItems, }); } const paidLogisticsItems = await prisma.logisticsBillItem.findMany({ where: { orderId: { in: scope.testOrderIds }, logisticsBill: { status: 'PAID' }, }, select: { id: true, orderNo: true, logisticsBill: { select: { billNo: true, status: true } }, }, take: 50, }); if (paidLogisticsItems.length) { risks.push({ code: 'PAID_LOGISTICS_BILL_TEST_ORDERS', message: '物流账单已打款且含测试订单', details: paidLogisticsItems, }); } } if (scope.testRedeemIds.length) { const paidPayouts = await prisma.storePayout.findMany({ where: { redeemRecordId: { in: scope.testRedeemIds }, status: 'PAID', }, select: { id: true, redeemRecordId: true, storeBill: { select: { billNo: true, status: true } }, }, take: 50, }); if (paidPayouts.length) { risks.push({ code: 'PAID_STORE_PAYOUT_TEST_REDEEMS', message: '门店打款已支付且含测试核销', details: paidPayouts, }); } } if (scope.limitedProductsWithFormalOrders.length) { risks.push({ code: 'LIMITED_PRODUCT_FORMAL_ORDERS', message: '限测商品已有正式订单,将跳过删除', details: scope.limitedProductsWithFormalOrders, }); } return risks; } export async function countTestData(prisma: PrismaClient) { const [ whitelistCount, testUsers, testStoreAccounts, testPartners, testStores, testOrders, testRedeems, limitedProducts, visibilityStores, visibilityProducts, ] = await Promise.all([ prisma.commonTestWhitelistPhone.count(), prisma.user.count({ where: { isTest: true } }), prisma.storeAccount.count({ where: { isTest: true } }), prisma.partnerAccount.count({ where: { isTest: true } }), prisma.store.count({ where: { isTest: true } }), prisma.order.count({ where: { isTest: true } }), prisma.redeemRecord.count({ where: { isTest: true } }), prisma.commonProductItem.count({ where: { visibilityWhitelistEnabled: true } }), prisma.store.count({ where: { visibilityWhitelistEnabled: true } }), prisma.commonProductItem.count({ where: { visibilityWhitelistEnabled: true } }), ]); return { whitelistCount, testUsers, testStoreAccounts, testPartners, testStores, testOrders, testRedeems, limitedProducts, visibilityStores, visibilityProducts, }; } export function idsEmpty(ids: bigint[]): boolean { return ids.length === 0; } export function inIds(ids: bigint[]): Prisma.BigIntFilter | undefined { return ids.length ? { in: ids } : undefined; }