Files
dukang/server/dukang-api/prisma/seed-v31.ts
T
jacy a01217539c
CI / verify (pull_request) Waiting to run
v3.5.4版本提交
2026-08-21 15:48:15 +08:00

1029 lines
18 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { PrismaClient, ResourceBizType, ResourceMediaType, ResourceOwnerType } from '@prisma/client';
import { hashPassword } from '../src/common/crypto/password.util';
import { DEFAULT_PRODUCT_DETAIL_TEMPLATES } from './seeds/product-detail-templates.default';
const prisma = new PrismaClient();
async function createMockResource(
ownerType: ResourceOwnerType,
ownerId: bigint,
bizType: ResourceBizType,
url: string,
) {
return prisma.commonResource.create({
data: {
ownerType,
ownerId,
bizType,
mediaType: ResourceMediaType.IMAGE,
ossBucket: 'mock-dukang',
ossKey: `mock/${ownerType.toLowerCase()}/${ownerId}/${bizType.toLowerCase()}`,
url,
},
});
}
async function main() {
console.log('Seeding v3.1 data...');
await prisma.logUserAnalytics.deleteMany();
await prisma.logPartnerAnalytics.deleteMany();
await prisma.logThirdParty.deleteMany();
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();
await prisma.benefitCoupon.deleteMany();
await prisma.orderDelivery.deleteMany();
await prisma.order.deleteMany();
await prisma.commonEvent.deleteMany();
await prisma.commonTicket.deleteMany();
await prisma.userPromoAttribution.deleteMany();
await prisma.userCityPreference.deleteMany();
await prisma.userAddress.deleteMany();
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();
await prisma.commonProductItem.deleteMany();
await prisma.commonProductDetailTemplate.deleteMany();
await prisma.commonStoreCategory.updateMany({ data: { parentId: null } });
await prisma.commonStoreCategory.deleteMany();
await prisma.commonPromoCode.deleteMany();
await prisma.commonResource.deleteMany();
await prisma.hqAccount.deleteMany();
const city = await prisma.commonCity.create({
data: {
code: '410100',
name: '郑州市',
province: '河南省',
status: 'ACTIVE',
localMinQty: 2,
crossMinQty: 6,
},
});
const primaryAccount = await prisma.partnerAccount.create({
data: {
phone: '13700000001',
name: '郑州合伙人主账号',
isPrimary: 1,
staffRole: 'PARTNER',
status: 'ACTIVE',
cityId: city.id,
scopeType: 'CITY_WIDE',
orderCommissionRate: 0,
redeemCommissionRate: 0.03,
bindingStatus: 'ACTIVE',
companyName: '郑州城市合伙人',
address: '河南省郑州市金水区',
contactPhone: '13700000001',
bankAccountName: '郑州合伙人公司',
bankAccountNo: '6222021234567890',
bankBranch: '工商银行郑州分行',
weeklyStoreTarget: 20,
},
});
await prisma.fulfillmentProvider.create({
data: {
code: 'XFX',
name: '小飞侠',
type: 'API',
status: 'ACTIVE',
settlementMethod: 'PREPAID',
pricingRulesJson: JSON.stringify({
baseBottles: 2,
baseFee: 6,
extraBottleFee: 2,
boxBottles: 6,
boxFee: 14,
}),
capabilitiesJson: JSON.stringify({
createShipment: true,
getTrack: true,
callback: true,
cancel: true,
}),
configJson: JSON.stringify({
apiUrl: 'https://beta.51xiaoju.cn/app/api/interface.do',
mchId: '',
apiKey: '',
signType: 'MD5',
}),
},
});
const warehouse = await prisma.cityWarehouse.create({
data: {
cityId: city.id,
name: '郑州中央仓',
address: '河南省郑州市金水区物流园1号',
contactName: '仓管张',
contactPhone: '13700000009',
managerType: 'PARTNER',
partnerAccountId: primaryAccount.id,
status: 'ACTIVE',
fulfillmentMode: 'MANUAL',
fulfillmentProviderId: null,
lng: 113.665,
lat: 34.757,
},
});
await prisma.partnerAccount.update({
where: { id: primaryAccount.id },
data: { managedWarehouseId: warehouse.id },
});
await prisma.partnerAccount.create({
data: {
phone: '13700000002',
name: '拓店员小李',
isPrimary: 0,
parentAccountId: primaryAccount.id,
staffRole: 'INTERNAL',
permissions: ['store:create', 'store:view'],
status: 'DISABLED',
},
});
const { DEFAULT_STORE_CATEGORY_TREE } = await import('../src/modules/store/store-category.defaults');
const categoryByCode = new Map<string, { id: bigint }>();
for (const root of DEFAULT_STORE_CATEGORY_TREE) {
const parent = await prisma.commonStoreCategory.create({
data: {
code: root.code,
name: root.name,
sort: root.sort,
parentId: null,
status: 'ACTIVE',
},
});
categoryByCode.set(root.code, parent);
for (const child of root.children) {
const row = await prisma.commonStoreCategory.create({
data: {
code: child.code,
name: child.name,
sort: child.sort,
parentId: parent.id,
status: 'ACTIVE',
},
});
categoryByCode.set(child.code, row);
}
}
const categories = [
categoryByCode.get('HOTPOT')!,
categoryByCode.get('LOCAL')!,
];
for (const tpl of DEFAULT_PRODUCT_DETAIL_TEMPLATES) {
await prisma.commonProductDetailTemplate.create({
data: {
code: tpl.code,
name: tpl.name,
description: tpl.description,
aromaType: tpl.aromaType as 'QINGXIANG' | 'JIANGXIANG' | 'NONGXIANG' | null,
storyTitle: tpl.storyTitle,
storyText: tpl.storyText,
features: tpl.features,
detailImageUrls: [],
suggestedDetailImageCount: tpl.suggestedDetailImageCount,
sortOrder: tpl.sortOrder,
status: 'ACTIVE',
},
});
}
const productDefs = [
{ skuCode: 'JZ-10', name: '酒祖杜康(国标特级10', subtitle: '清香型 53度', price: 128, sortOrder: 1, img: 'https://picsum.photos/seed/jiuzu10/400/400' },
{ skuCode: 'JZ-15', name: '酒祖杜康(国标特级15', subtitle: '清香型 53度', price: 168, sortOrder: 2, img: 'https://picsum.photos/seed/jiuzu15/400/400' },
{ skuCode: 'JZ-20', name: '酒祖杜康(国标特级20', subtitle: '清香型 53度', price: 298, sortOrder: 3, img: 'https://picsum.photos/seed/jiuzu20/400/400' },
{ skuCode: 'JZ-30', name: '酒祖杜康(国标特级30', subtitle: '清香型 53度', price: 498, sortOrder: 4, img: 'https://picsum.photos/seed/jiuzu30/400/400' },
];
const products = [];
for (const [i, def] of productDefs.entries()) {
const product = await prisma.commonProductItem.create({
data: {
skuCode: def.skuCode,
barcode69: `69000000000${i + 1}`,
name: def.name,
subtitle: def.subtitle,
aromaType: 'QINGXIANG',
spec: '500ml | 53度',
price: def.price,
benefitAmount: def.price,
status: 'ON_SALE',
sortOrder: def.sortOrder,
},
});
await prisma.commonProductSku.create({
data: {
productId: product.id,
skuCode: def.skuCode,
barcode69: `69000000000${i + 1}`,
specKey: '',
specText: '500ml | 53度',
price: def.price,
benefitAmount: def.price,
status: 'ON_SALE',
allowOnSitePickup: false,
allowOnlinePurchase: true,
allowCrossCityDelivery: true,
saleUnit: 'BOTTLE',
bottlesPerUnit: 1,
isDefault: true,
sortOrder: 0,
},
});
const cover = await createMockResource(ResourceOwnerType.PRODUCT, product.id, ResourceBizType.COVER, def.img);
await prisma.commonProductItem.update({
where: { id: product.id },
data: { coverResourceId: cover.id },
});
products.push(product);
}
const storeDefs = [
{
name: '郑州老城店',
phone: '13910000001',
district: '金水区',
address: '花园路100号',
intro: '正宗河南菜,欢迎核销好客权益',
img: 'https://picsum.photos/seed/store1/400/300',
categoryId: categories[0].id,
settlementRate: 0.6,
withAccount: true,
},
{
name: '郑州美食城店',
phone: '13910000002',
district: '二七区',
address: '大学路200号',
intro: '地方特色餐饮',
img: 'https://picsum.photos/seed/store2/400/300',
categoryId: categories[1].id,
settlementRate: 0.65,
withAccount: false,
},
];
const createdStores: { id: bigint; name: string }[] = [];
for (const def of storeDefs) {
const store = await prisma.store.create({
data: {
cityId: city.id,
partnerAccountId: primaryAccount.id,
categoryId: def.categoryId,
name: def.name,
phone: def.phone,
province: '河南省',
cityName: '郑州市',
district: def.district,
address: def.address,
intro: def.intro,
settlementRate: def.settlementRate,
status: 'OPEN',
openTime: '10:00',
closeTime: '22:00',
},
});
const cover = await createMockResource(ResourceOwnerType.STORE, store.id, ResourceBizType.COVER, def.img);
await prisma.store.update({ where: { id: store.id }, data: { coverResourceId: cover.id } });
createdStores.push({ id: store.id, name: def.name });
}
// 一号两店:主账号 13910000001 绑定老城店 + 美食城店,便于测选店
const multiStorePrimary = await prisma.storeAccount.create({
data: {
phone: '13910000001',
name: '郑州老城店主',
isPrimary: 1,
bankAccountName: '郑州老城店主',
bankAccountNo: '6222029876543210',
bankBranch: '建设银行郑州分行',
bindings: {
create: createdStores.map((s) => ({ storeId: s.id })),
},
},
});
// 子账号样例:仅绑定第一家店
await prisma.storeAccount.create({
data: {
phone: '13910000011',
name: '老城店收银员',
isPrimary: 0,
parentAccountId: multiStorePrimary.id,
staffRole: 'CASHIER',
permissions: ['redeem', 'records'],
status: 'ACTIVE',
bindings: {
create: [{ storeId: createdStores[0].id }],
},
},
});
const weekStart = (() => {
const d = new Date();
d.setHours(12, 0, 0, 0);
const day = d.getDay();
const diff = day === 0 ? 6 : day - 1;
d.setDate(d.getDate() - diff);
d.setHours(10, 0, 0, 0);
return d;
})();
const newWeekStore = await prisma.store.create({
data: {
cityId: city.id,
partnerAccountId: primaryAccount.id,
categoryId: categories[0].id,
name: '本周新签体验店',
phone: '13910000003',
province: '河南省',
cityName: '郑州市',
district: '中原区',
address: '建设路88号',
intro: '本周新签约门店',
settlementRate: 0.6,
status: 'OPEN',
openTime: '10:00',
closeTime: '22:00',
createdAt: new Date(weekStart.getTime() + 2 * 24 * 60 * 60 * 1000),
},
});
createdStores.push({ id: newWeekStore.id, name: newWeekStore.name });
await prisma.storeAccount.create({
data: {
phone: '13910000003',
name: '本周新签体验店',
isPrimary: 1,
bankAccountName: '本周新签体验店',
bankAccountNo: '6222029876543211',
bankBranch: '农业银行郑州分行',
bindings: { create: [{ storeId: newWeekStore.id }] },
},
});
await prisma.user.create({
data: {
userNo: 'DK88293401',
phone: '13800000001',
phoneVerifiedAt: new Date(),
nickname: '测试用户',
cityPreference: {
create: {
selectedCityCode: '410100',
selectedDistrict: '郑州市',
locateCityCode: '410100',
locateDistrict: '金水区',
},
},
},
});
await prisma.hqAccount.create({
data: {
phone: '13600000001',
loginName: 'admin',
passwordHash: hashPassword('dukang@123!'),
name: '总部管理员',
adminRole: 'SUPER_ADMIN',
},
});
await prisma.commonPromoCode.create({
data: {
code: 'DKHQ001',
name: '总部品鉴会',
scene: 'EVENT',
qrcodeId: 'a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456',
status: 'ACTIVE',
},
});
await prisma.commonPromoCode.create({
data: {
code: 'DKDEMO1',
name: '郑州品鉴会演示',
scene: 'OFFLINE_PICKUP',
qrcodeId: 'b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef12345678',
status: 'ACTIVE',
},
});
const user = await prisma.user.findUniqueOrThrow({ where: { phone: '13800000001' } });
const product = products[0];
const orderDefs = [
{ dayOffset: 0, payAmount: 1198, quantity: 2 },
{ dayOffset: 1, payAmount: 599, quantity: 1 },
{ dayOffset: 2, payAmount: 1760, quantity: 2 },
{ dayOffset: 4, payAmount: 1299, quantity: 1 },
{ dayOffset: 5, payAmount: 880, quantity: 1 },
];
const coupons: { id: bigint; balance: number }[] = [];
for (const [index, def] of orderDefs.entries()) {
const paidAt = new Date(weekStart.getTime() + def.dayOffset * 24 * 60 * 60 * 1000 + 14 * 60 * 60 * 1000);
const listAmount = def.payAmount;
const order = await prisma.order.create({
data: {
orderNo: `WK${Date.now()}${index}`,
userId: user.id,
cityId: city.id,
status: 'COMPLETED',
payStatus: 'PAID',
deliveryType: 'LOCAL',
productId: product.id,
barcode69: product.barcode69,
productName: product.name,
productSpec: product.spec,
quantity: def.quantity,
listUnitPrice: product.price,
listAmount,
productAmount: listAmount,
payAmount: listAmount,
benefitAmount: listAmount,
receiverName: '测试用户',
receiverPhone: user.phone!,
receiverAddress: '郑州市金水区测试路1号',
receiverProvince: '河南省',
receiverCity: '郑州市',
receiverDistrict: '金水区',
paidAt,
completedAt: paidAt,
partnerAccountIdAtPay: primaryAccount.id,
orderCommissionRateAtPay: 0,
},
});
const coupon = await prisma.benefitCoupon.create({
data: {
couponNo: `CPN${Date.now()}${index}`,
userId: user.id,
orderId: order.id,
totalAmount: listAmount,
balance: listAmount,
sourceProduct: product.name,
},
});
coupons.push({ id: coupon.id, balance: listAmount });
}
const redeemDefs = [
{ storeIndex: 0, amount: 42800, dayOffset: 1, settlementRate: 0.6 },
{ storeIndex: 1, amount: 38400, dayOffset: 2, settlementRate: 0.65 },
{ storeIndex: 2, amount: 31200, dayOffset: 3, settlementRate: 0.6 },
];
for (const [index, def] of redeemDefs.entries()) {
const coupon = coupons[index];
const store = createdStores[def.storeIndex];
const createdAt = new Date(weekStart.getTime() + def.dayOffset * 24 * 60 * 60 * 1000 + 16 * 60 * 60 * 1000);
const settleAmount = Math.round(def.amount * def.settlementRate * 100) / 100;
await prisma.redeemRecord.create({
data: {
redeemNo: `RD${Date.now()}${index}`,
userId: user.id,
couponId: coupon.id,
storeId: store.id,
amount: def.amount,
settleAmount,
createdAt,
},
});
await prisma.benefitCoupon.update({
where: { id: coupon.id },
data: {
usedAmount: def.amount,
balance: Math.max(0, coupon.balance - def.amount),
},
});
}
const now = new Date();
const periodStart = new Date(now.getFullYear(), now.getMonth(), 1);
const periodEnd = new Date(now.getFullYear(), now.getMonth() + 1, 0);
await prisma.partnerBill.create({
data: {
billNo: `PB${Date.now()}`,
partnerAccountId: primaryAccount.id,
periodStart,
periodEnd,
orderCommission: 1200,
redeemCommission: 800,
totalAmount: 2000,
status: 'UNPAID',
confirmedAt: now,
},
});
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,
warehouse: warehouse.name,
primaryAccount: primaryAccount.phone,
detailTemplates: DEFAULT_PRODUCT_DETAIL_TEMPLATES.length,
products: products.length,
stores: createdStores.length,
testWhitelistPhones,
testPhones: {
user: '13800000001',
partner: '13700000001',
partnerStaff: '13700000002',
hq: '13600000001',
},
});
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});