Files
dukang/server/dukang-api/prisma/seed-v31.ts
T
jacy bde85a7b82
CI / verify (pull_request) Has been cancelled
仓库管理配置 小飞侠配置
2026-07-16 21:40:25 +08:00

934 lines
16 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.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.deleteMany();
await prisma.store.deleteMany();
await prisma.partnerBill.deleteMany();
await prisma.cityWarehouse.deleteMany();
await prisma.fulfillmentProvider.deleteMany();
await prisma.partnerAccount.deleteMany();
await prisma.commonCity.deleteMany();
await prisma.commonProductItem.deleteMany();
await prisma.commonProductDetailTemplate.deleteMany();
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,
},
});
const xfxProvider = await prisma.fulfillmentProvider.create({
data: {
code: 'XFX',
name: '小飞侠',
type: 'API',
status: 'ACTIVE',
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: 'API_AUTO',
fulfillmentProviderId: xfxProvider.id,
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 categories = await Promise.all([
prisma.commonStoreCategory.create({ data: { code: 'HOTPOT', name: '火锅', sort: 1 } }),
prisma.commonStoreCategory.create({ data: { code: 'LOCAL', name: '地方菜', sort: 2 } }),
]);
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,
},
});
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: 'CONFIRMED',
confirmedAt: now,
},
});
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,
testPhones: {
user: '13800000001',
partner: '13700000001',
partnerStaff: '13700000002',
hq: '13600000001',
},
});
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});