feat: 技术支持工单/企微权限/开发版本管理/消息推送等迭代

This commit is contained in:
2026-08-04 21:32:13 +08:00
parent c8ea5a3119
commit 9d96c73246
1341 changed files with 0 additions and 195605 deletions
-964
View File
@@ -1,964 +0,0 @@
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,
},
});
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,
},
});
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,
},
});
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();
});