v3数据表更改

This commit is contained in:
2026-07-01 14:36:50 +08:00
parent 638898b71e
commit aeb4ecfc84
46 changed files with 4553 additions and 918 deletions
+282
View File
@@ -0,0 +1,282 @@
# V3.1 Schema 迁移清单
> 生成自 `init_v3.sql` → `schema.v31.prisma`(已通过 `prisma validate`
> 旧版备份:`schema.legacy-v21.prisma`(当前运行中的 `schema.prisma`
## 激活步骤(P0
```bash
cd server/dukang-api
# 1. 备份并切换 schema
cp prisma/schema.prisma prisma/schema.legacy-v21.prisma # 若尚未备份
cp prisma/schema.v31.prisma prisma/schema.prisma
# 2. 开发库建议删库重建
mysql -u root -p -e "DROP DATABASE IF EXISTS dukang_haoke; CREATE DATABASE dukang_haoke ..."
# 或:mysql < prisma/init_v3.sql
# 3. 生成 Client
pnpm db:generate
npx prisma db push # 或 prisma migrate dev --name v31_init
# 4. 新 seed(待编写 seed-v31.ts
pnpm prisma:seed
```
---
## 表对照(27 张 v3.1
| v3.1 物理表 | Prisma Model | 旧表/Model | 变化 |
|-------------|--------------|------------|------|
| `common_wx_app_config` | `CommonWxAppConfig` | `wx_app_configs` / `WxAppConfig` | 重命名 |
| `common_resource` | `CommonResource` | — | **新增**(替代 `store_media`、裸 URL |
| `common_event` | `CommonEvent` | `benefit_ledgers`, `order_status_logs`, `store_audits`, `event_logs`, `operation_logs` | **合并** |
| `common_ticket` | `CommonTicket` | `after_sale_tickets`, `refunds`, `delivery_intercepts`, `alerts` | **合并** |
| `common_product_item` | `CommonProductItem` | `products` / `Product` | 重命名 + `cover_resource_id` |
| `common_store_category` | `CommonStoreCategory` | `store_categories` / `StoreCategory` | 重命名 |
| `common_promo_code` | `CommonPromoCode` | `promo_codes` / `PromoCode` | 重命名 + `qrcode_resource_id` |
| `common_city` | `CommonCity` | `cities` / `City` | 重命名 |
| `common_city_commission_rule` | `CommonCityCommissionRule` | `city_commission_rules` | 重命名 |
| `partner_partner` | `Partner` | `partners` | 重命名 + 合同字段内联 |
| `partner_account` | `PartnerAccount` | `partner_accounts` | 重命名 |
| `partner_bill` | `PartnerBill` | `partner_bills` | 状态枚举精简 |
| `hq_account` | `HqAccount` | `hq_accounts` | 重命名 |
| `user_user` | `User` | `users` | 重命名;`phone` 可空;含 `deviceKey`/合并字段 |
| `user_address` | `UserAddress` | `user_addresses` | 重命名 |
| `user_city_preference` | `UserCityPreference` | `user_city_preferences` | 重命名 |
| `user_promo_attribution` | `UserPromoAttribution` | `user_promo_attributions` | 重命名 |
| `store_store` | `Store` | `stores` | `cover_url``cover_resource_id` |
| `store_account` | `StoreAccount` | `store_accounts` | 重命名 |
| `user_order` | `Order` | `orders` | **无 order_items**;商品快照内嵌 |
| `user_order_delivery` | `OrderDelivery` | `order_deliveries` | 重命名 + `sign_photo_resource_id` |
| `user_benefit_coupon` | `BenefitCoupon` | `benefit_coupons` | 重命名 |
| `user_redeem_record` | `RedeemRecord` | `redeem_records` | 重命名 |
| `user_store_rating` | `StoreRating` | `store_ratings` | 重命名 |
| `store_payout` | `StorePayout` | `store_payouts` | 重命名 |
| `log_third_party` | `LogThirdParty` | `payments`, `sms_logs` | **合并** |
| `log_user_analytics` | `LogUserAnalytics` | `event_logs`(埋点部分) | **拆分** |
### 删除的表(v3.1 不再存在)
| 旧表 | 替代方案 |
|------|----------|
| `store_media` | `common_resource``owner_type=STORE` |
| `partner_contracts` | `partner_partner.contract_*` + `common_resource` CONTRACT |
| `order_items` | `user_order` 内嵌快照字段 |
| `order_status_logs` | `common_event(ORDER_STATUS)` |
| `payments` | `log_third_party` + `user_order.pay_*` |
| `refunds` | `common_ticket(REFUND)` |
| `delivery_intercepts` | `common_ticket(ALERT)` 或 RESHIPMENT |
| `benefit_ledgers` | `common_event(BENEFIT_LEDGER)` |
| `redeem_tokens` | **仅 Redis** |
| `order_commissions` | `partner_bill` 汇总(无明细表) |
| `partner_withdrawals` | 手册 v3.1 未包含(后续按需) |
| `store_audits` | `common_event(STORE_AUDIT)` |
| `after_sale_tickets` | `common_ticket` |
| `alerts` | `common_ticket(ALERT)` |
| `operation_logs` | `common_event(HQ_OPERATION)` |
| `event_logs` | `common_event` + `log_user_analytics` |
### preV1 扩展字段(已并入 v3.1
| 字段 | 表 | 说明 |
|------|-----|------|
| `device_key` / `phone_verified_at` / `merged_into_user_id` | `user_user` | 访客 JWT + 验机 + 账号合并 |
| `client_ip` / `ip_*` / `gps_*` | `user_order` | 下单位置快照 |
~~以下字段在切换后丢失~~**已保留**
---
## Prisma Client 调用变更速查
| 旧调用 | v3.1 调用 |
|--------|-----------|
| `prisma.product` | `prisma.commonProductItem` |
| `prisma.city` | `prisma.commonCity` |
| `prisma.cityCommissionRule` | `prisma.commonCityCommissionRule` |
| `prisma.storeCategory` | `prisma.commonStoreCategory` |
| `prisma.promoCode` | `prisma.commonPromoCode` |
| `prisma.orderDelivery` | `prisma.orderDelivery`(表名变 `user_order_delivery` |
| `prisma.benefitCoupon` | `prisma.benefitCoupon`(表 `user_benefit_coupon` |
| `prisma.benefitLedger` | `prisma.commonEvent``eventType=BENEFIT_LEDGER` |
| `prisma.redeemToken` | **删除**,改 Redis |
| `prisma.storeMedia` | `prisma.commonResource` |
| `prisma.storeAudit` | `prisma.commonEvent``eventType=STORE_AUDIT` |
| `prisma.payment` | `prisma.logThirdParty` |
| `prisma.orderStatusLog` | `prisma.commonEvent``eventType=ORDER_STATUS` |
| `prisma.orderItem` | **删除**,读写 `order` 快照字段 |
> `Partner`、`Store`、`User`、`Order` 等 Model 名保留,仅 `@@map` 物理表名变化。
---
## 后端模块影响面
### P0 — 基础设施
| 路径 | 影响 | 工作量 |
|------|------|--------|
| `prisma/schema.prisma` | 已切换为 v3.1 | ✅ |
| `prisma/schema.v31.prisma` | 与 `schema.prisma` 同步源 | ✅ |
| `prisma/seed-prev1.ts` | 保留为 `seed-legacy`;主 seed 为 `seed-v31.ts` | ✅ |
| `prisma/sync-benefit-to-price.ts` | `product``commonProductItem` | 低 |
| `packages/shared-types` | `ClientApp` 去掉 `USER_H5` 等;新增 Resource/Event 枚举 | 中 |
### P1 — common 模块(新建)
| 路径 | 说明 |
|------|------|
| `src/modules/common/common.module.ts` | **新建** |
| `src/modules/common/resource.service.ts` | OSS 凭证、登记、CRUD |
| `src/modules/common/resource.controller.ts` | `/common/resources/*` |
| `src/modules/common/event.service.ts` | 事件写入/查询/时间线 |
| `src/modules/common/ticket.service.ts` | 工单 CRUD |
### P2 — IAM
| 路径 | 关键改动 |
|------|----------|
| `modules/iam/auth.service.ts` | 去掉访客 `deviceKey` 流程;`user.phone` 必填;`prisma.user` 字段变更 |
| `modules/iam/user-address.service.ts` | 表名映射,逻辑基本不变 |
| `common/guards/phone-verified.guard.ts` | 适配新 User 模型 |
| `common/guards/super-admin.guard.ts` | 无大变 |
### P3 — catalog
| 路径 | 关键改动 |
|------|----------|
| `modules/catalog/catalog.service.ts` | `city``commonCity``product``commonProductItem`;返回 `coverResource.url` |
### P4 — trade(改动最大)
| 路径 | 关键改动 |
|------|----------|
| `modules/trade/trade.service.ts` | 下单写 `user_order` 快照(无 `orderItem`);支付写 `log_third_party`;状态变更写 `common_event`;去掉 IP/GPS 字段或扩展 |
| `integrations/pay/*` | 回调改查 `log_third_party` |
| `jobs/*`(配送 Mock | `orderDelivery` 字段对齐 |
### P5 — benefit
| 路径 | 关键改动 |
|------|----------|
| `modules/benefit/benefit.service.ts` | 发券逻辑保留;流水从 `benefitLedger.create``commonEvent.create(BENEFIT_LEDGER)`;读明细改查 `commonEvent` |
### P6 — redeem
| 路径 | 关键改动 |
|------|----------|
| `modules/redeem/redeem.service.ts` | **删除** `redeemToken` DB 写入,仅 Redis`cityCommissionRule``commonCityCommissionRule` |
### P7 — store
| 路径 | 关键改动 |
|------|----------|
| `modules/store/store.service.ts` | `storeAudit``commonEvent`;封面改 `coverResourceId``city``commonCity` |
### P8 — settlement
| 路径 | 关键改动 |
|------|----------|
| `modules/settlement/settlement.service.ts` | `partnerBill` 状态枚举变更;去掉 `orderCommission` 明细 |
### P9 — analytics
| 路径 | 关键改动 |
|------|----------|
| `modules/analytics/analytics.service.ts` | `eventLog``logUserAnalytics` |
### P10 — opsHQ 后台)
| 路径 | 关键改动 |
|------|----------|
| `modules/ops/admin-stores.service.ts` | `storeMedia``commonResource``coverUrl``coverResourceId``city``commonCity` |
| `modules/ops/admin-benefit.service.ts` | 流水列表改查 `commonEvent` |
| `modules/ops/admin-orders.service.ts` | 订单含内嵌商品快照;无 `items` include |
| `modules/ops/admin-dashboard.service.ts` | 统计字段:去掉 guest/merged 用户计数 |
| `modules/ops/admin-cities.service.ts` | `city``commonCity` |
| `modules/ops/admin-partners.service.ts` | 订单关联 `city.partnerId` 不变 |
| `modules/ops/admin-redeem.service.ts` | 表名映射 |
| `modules/ops/admin-users.service.ts` | 去掉 merged/guest 相关 |
| `admin-stores.controller.ts` | `/admin/store-media``/admin/resources` 或复用 common API |
---
## 前端影响面
| 应用 | 影响 |
|------|------|
| `apps/h5-user` | 登录流(phone 必填);商品图 URL 来源;订单详情无 items 数组 |
| `apps/h5-shop` | 门店详情封面 URL |
| `apps/h5-partner` | 录店上传走 `/common/resources` |
| `apps/admin-web` | 门店资源页改 `common_resource`;权益流水改 event;订单详情结构调整 |
| `packages/shared-types` | 枚举与 DTO 同步 |
---
## P1 进度(common 模块)
| 项 | 状态 |
|----|------|
| `modules/common/` Resource/Event/Ticket/ThirdPartyLog | ✅ |
| `common/event/event.helpers.ts` 权益/订单事件 | ✅ |
| 业务层改用 `commonEvent` / `commonResource` / `logThirdParty` | ✅ |
| `pnpm run build` | ✅ |
## P2–P6 进度(业务层 + 兼容层 + 后台)
| 项 | 状态 |
|----|------|
| P2 IAM + catalog 适配 v3.1 | ✅ |
| P3 trade + benefit(下单/发券/支付日志/事件) | ✅ |
| P4 redeem + storeRedis token + 封面资源) | ✅ |
| P5 ops 后台(订单/门店/权益/城市/合伙人) | ✅ |
| P6 兼容层 `v31-compat.ts`(订单 items、门店 coverUrl、流水/状态日志) | ✅ |
| `admin/products` CRUD + HQ 商品页 | ✅ |
| `sync-benefit-to-price.ts``commonProductItem` | ✅ |
| `schema.prisma``schema.v31.prisma` 同步 | ✅ |
| smoke 脚本 `scripts/smoke-prev1.mjs` | ✅ |
### 新增 HQ API
| 路径 | 说明 |
|------|------|
| `GET/POST /admin/products` | 商品列表/新建 |
| `GET/PUT /admin/products/:id` | 商品详情/更新 |
### 新增 API`/api/v1/common/*`
| 路径 | 说明 |
|------|------|
| `POST /common/resources/upload-token` | Mock OSS 直传凭证 |
| `POST/GET/PUT/DELETE /common/resources` | 资源 CRUD |
| `POST/GET /common/events` | 事件写入/查询 |
| `GET /common/events/timeline` | 时间线 |
| `POST/GET/PUT /common/tickets` | 工单 |
| `GET /common/third-party-logs` | HQ 只读支付/第三方日志 |
## 建议实施顺序
```
P0 schema 切换 + seed-v31
→ P1 common 模块(resource + event
→ P2 IAM + catalog(可登录、可看商品)
→ P3 trade + benefit(可下单发券)
→ P4 redeem + store(可核销)
→ P5 ops 后台 + settlement
→ P6 前端对齐 + smoke
```
**冻结规则**:P0~P1 期间不新增业务功能,只修迁移阻塞项。
---
## 文件索引
| 文件 | 说明 |
|------|------|
| `prisma/init_v3.sql` | DDL 源 |
| `prisma/schema.v31.prisma` | **新生成**,待激活 |
| `prisma/schema.legacy-v21.prisma` | 旧版备份 |
| `prisma/schema.prisma` | 当前运行版(**v3.1 已激活** |
+18 -1
View File
@@ -266,7 +266,10 @@ DROP TABLE IF EXISTS user_user;
CREATE TABLE user_user (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
user_no VARCHAR(20) NOT NULL,
phone VARCHAR(20) NOT NULL,
device_key VARCHAR(36) DEFAULT NULL COMMENT '访客设备标识',
phone VARCHAR(20) DEFAULT NULL COMMENT '验机后必填;访客可为NULL',
phone_verified_at DATETIME(3) DEFAULT NULL,
merged_into_user_id BIGINT UNSIGNED DEFAULT NULL COMMENT '合并入主账号',
wx_open_id VARCHAR(64) DEFAULT NULL,
wx_union_id VARCHAR(64) DEFAULT NULL,
nickname VARCHAR(64) DEFAULT NULL,
@@ -281,8 +284,10 @@ CREATE TABLE user_user (
PRIMARY KEY (id),
UNIQUE KEY uk_user_user_phone (phone),
UNIQUE KEY uk_user_user_no (user_no),
UNIQUE KEY uk_user_user_device_key (device_key),
KEY idx_user_user_source (source_type, source_ref_id),
KEY idx_user_user_referrer (referrer_user_id),
KEY idx_user_user_merged (merged_into_user_id),
KEY idx_user_user_wx_open (wx_open_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='C端用户';
@@ -426,6 +431,16 @@ CREATE TABLE user_order (
receiver_province VARCHAR(32) NOT NULL,
receiver_city VARCHAR(32) NOT NULL,
receiver_district VARCHAR(32) NOT NULL,
client_ip VARCHAR(45) DEFAULT NULL COMMENT '下单时客户端IP',
ip_province VARCHAR(32) DEFAULT NULL COMMENT 'IP解析省',
ip_city VARCHAR(32) DEFAULT NULL COMMENT 'IP解析市',
ip_district VARCHAR(32) DEFAULT NULL COMMENT 'IP解析区县',
gps_province VARCHAR(32) DEFAULT NULL COMMENT 'GPS解析省',
gps_city VARCHAR(32) DEFAULT NULL COMMENT 'GPS解析市',
gps_district VARCHAR(32) DEFAULT NULL COMMENT 'GPS解析区县',
gps_latitude DECIMAL(10,7) DEFAULT NULL,
gps_longitude DECIMAL(10,7) DEFAULT NULL,
gps_address VARCHAR(256) DEFAULT NULL COMMENT 'GPS逆地理地址',
pay_external_no VARCHAR(64) DEFAULT NULL COMMENT '微信交易号(冗余)',
paid_at DATETIME(3) DEFAULT NULL COMMENT '支付时间',
shipped_at DATETIME(3) DEFAULT NULL COMMENT '发货时间(冗余=user_order_delivery.shipping_at)',
@@ -442,6 +457,8 @@ CREATE TABLE user_order (
KEY idx_user_order_product (product_id),
KEY idx_user_order_barcode (barcode_69),
KEY idx_user_order_pay_external (pay_external_no),
KEY idx_user_order_ip_city (ip_city),
KEY idx_user_order_gps_city (gps_city),
CONSTRAINT fk_user_order_user FOREIGN KEY (user_id) REFERENCES user_user(id) ON DELETE RESTRICT,
CONSTRAINT fk_user_order_city FOREIGN KEY (city_id) REFERENCES common_city(id) ON DELETE RESTRICT,
CONSTRAINT fk_user_order_origin FOREIGN KEY (origin_order_id) REFERENCES user_order(id) ON DELETE SET NULL,
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+847
View File
@@ -0,0 +1,847 @@
// 杜康好客 · V3.1 数据模型(由 init_v3.sql 生成)
// 激活方式:确认后替换 schema.prisma,执行 prisma migrate / db push + seed-v31
// 旧版备份:schema.legacy-v21.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
// ─── 枚举(与 init_v3.sql COMMENT 对齐)────────────────
enum ClientApp {
USER_MINI
USER_H5
PARTNER_MINI
PARTNER_H5
HQ_MINI
HQ_WEB
SHOP_H5
}
enum ResourceOwnerType {
PRODUCT
STORE
PARTNER
USER
ORDER
PROMO
HQ
}
enum ResourceBizType {
COVER
ENV
CONTRACT
CAROUSEL
DETAIL
AVATAR
QRCODE
SIGN_PHOTO
VIDEO
}
enum ResourceMediaType {
IMAGE
VIDEO
FILE
}
enum ResourceStatus {
ACTIVE
DELETED
}
enum EventType {
STORE_AUDIT
ORDER_STATUS
BENEFIT_LEDGER
HQ_OPERATION
PROMO_TOUCH
}
enum ActorType {
USER
STORE
PARTNER
HQ
SYSTEM
}
enum TicketType {
REFUND
RESHIPMENT
ALERT
}
enum AromaType {
QINGXIANG
JIANGXIANG
NONGXIANG
}
enum ProductStatus {
DRAFT
ON_SALE
OFF_SALE
}
enum PromoCodeStatus {
ACTIVE
DISABLED
}
enum CityStatus {
PENDING
ACTIVE
PAUSED
}
enum PartnerStaffRole {
PARTNER
INTERNAL
PROMOTER
}
enum AccountStatus {
ACTIVE
DISABLED
}
enum HqAdminRole {
SUPER_ADMIN
OPS
FINANCE
CUSTOMER_SERVICE
}
enum PartnerBillStatus {
DRAFT
CONFIRMED
PAID
}
enum StoreStatus {
OPEN
PAUSED
CLOSED
}
enum UserSourceType {
ORGANIC
PROMO_CODE
SHARE_LINK
FRIEND_REFERRAL
OFFLINE_EVENT
OTHER
}
enum OrderType {
NORMAL
RESHIPMENT
}
enum OrderStatus {
PENDING_PAY
PENDING_SHIP
OUT_WAREHOUSE
SHIPPING
PENDING_RECEIVE
COMPLETED
CANCELLED
REFUNDING
REFUNDED
}
enum PayStatus {
UNPAID
PAYING
PAID
REFUNDING
REFUNDED
}
enum DeliveryType {
LOCAL
CROSS_CITY
}
enum FreightPayType {
FREE
COD
}
enum DeliveryProvider {
XFX
LOGISTICS
MANUAL
}
enum BenefitCouponStatus {
ACTIVE
USED_UP
VOID
}
enum StorePayoutStatus {
PENDING
PAID
}
enum ThirdPartyProvider {
WECHAT_PAY
WECHAT_REFUND
WECHAT_AUTH
WECHAT_MAP
XFX
SMS
LOGISTICS
}
enum ThirdPartyLogStatus {
PENDING
SUCCESS
FAILED
}
enum BenefitLedgerType {
GRANT
REDEEM
REFUND_VOID
ADJUST
}
// ─── COMMON ───────────────────────────────────────────
model CommonWxAppConfig {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
clientApp ClientApp @unique @map("client_app")
appId String @map("app_id") @db.VarChar(64)
appSecret String @map("app_secret") @db.VarChar(128)
mchId String? @map("mch_id") @db.VarChar(32)
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.DateTime(3)
@@map("common_wx_app_config")
}
model CommonResource {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
ownerType ResourceOwnerType @map("owner_type")
ownerId BigInt @map("owner_id") @db.UnsignedBigInt
bizType ResourceBizType @map("biz_type")
mediaType ResourceMediaType @default(IMAGE) @map("media_type")
ossBucket String @map("oss_bucket") @db.VarChar(64)
ossKey String @map("oss_key") @db.VarChar(256)
url String @db.VarChar(512)
fileName String? @map("file_name") @db.VarChar(128)
fileSize BigInt? @map("file_size") @db.UnsignedBigInt
mimeType String? @map("mime_type") @db.VarChar(64)
sortOrder Int @default(0) @map("sort_order")
status ResourceStatus @default(ACTIVE)
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.DateTime(3)
productCovers CommonProductItem[] @relation("ProductCover")
promoQrcodes CommonPromoCode[] @relation("PromoQrcode")
userAvatars User[] @relation("UserAvatar")
storeCovers Store[] @relation("StoreCover")
orderImages Order[] @relation("OrderProductImage")
deliveryPhotos OrderDelivery[] @relation("DeliverySignPhoto")
@@index([ownerType, ownerId, bizType])
@@index([status])
@@map("common_resource")
}
model CommonEvent {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
eventType EventType @map("event_type")
refType String @map("ref_type") @db.VarChar(32)
refId BigInt @map("ref_id") @db.UnsignedBigInt
actorType ActorType? @map("actor_type")
actorId BigInt? @map("actor_id") @db.UnsignedBigInt
status String? @db.VarChar(32)
param1 String? @db.VarChar(128)
param1Desc String? @map("param1_desc") @db.VarChar(64)
param2 String? @db.VarChar(128)
param2Desc String? @map("param2_desc") @db.VarChar(64)
param3 String? @db.VarChar(128)
param3Desc String? @map("param3_desc") @db.VarChar(64)
amount1 Decimal? @db.Decimal(10, 2)
amount2 Decimal? @db.Decimal(10, 2)
remark String? @db.VarChar(512)
extraJson Json? @map("extra_json")
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
@@index([refType, refId, eventType])
@@index([eventType, createdAt])
@@index([actorType, actorId])
@@map("common_event")
}
model CommonTicket {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
ticketNo String @unique @map("ticket_no") @db.VarChar(32)
ticketType TicketType @map("ticket_type")
status String @default("PENDING") @db.VarChar(32)
refType String @map("ref_type") @db.VarChar(32)
refId BigInt @map("ref_id") @db.UnsignedBigInt
operatorType ActorType? @map("operator_type")
operatorId BigInt? @map("operator_id") @db.UnsignedBigInt
param1 String? @db.VarChar(128)
param1Desc String? @map("param1_desc") @db.VarChar(64)
param2 String? @db.VarChar(128)
param2Desc String? @map("param2_desc") @db.VarChar(64)
param3 String? @db.VarChar(128)
param3Desc String? @map("param3_desc") @db.VarChar(64)
remark String? @db.VarChar(512)
extraJson Json? @map("extra_json")
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
completedAt DateTime? @map("completed_at") @db.DateTime(3)
@@index([refType, refId])
@@index([ticketType, status])
@@map("common_ticket")
}
model CommonProductItem {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
skuCode String @unique @map("sku_code") @db.VarChar(32)
barcode69 String @unique @map("barcode_69") @db.VarChar(32)
name String @db.VarChar(128)
subtitle String? @db.VarChar(256)
aromaType AromaType @map("aroma_type")
spec String @db.VarChar(128)
price Decimal @db.Decimal(10, 2)
benefitAmount Decimal? @map("benefit_amount") @db.Decimal(10, 2)
status ProductStatus @default(DRAFT)
sortOrder Int @default(0) @map("sort_order")
coverResourceId BigInt? @map("cover_resource_id") @db.UnsignedBigInt
detailContent Json? @map("detail_content")
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.DateTime(3)
coverResource CommonResource? @relation("ProductCover", fields: [coverResourceId], references: [id], onDelete: SetNull)
orders Order[]
@@index([status, aromaType])
@@map("common_product_item")
}
model CommonStoreCategory {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
code String @unique @db.VarChar(32)
name String @db.VarChar(64)
sort Int @default(0)
stores Store[]
@@map("common_store_category")
}
model CommonPromoCode {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
code String @unique @db.VarChar(32)
name String @db.VarChar(128)
status PromoCodeStatus @default(ACTIVE)
qrcodeResourceId BigInt? @map("qrcode_resource_id") @db.UnsignedBigInt
scanCount Int @default(0) @map("scan_count")
orderCount Int @default(0) @map("order_count")
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
qrcodeResource CommonResource? @relation("PromoQrcode", fields: [qrcodeResourceId], references: [id], onDelete: SetNull)
attributions UserPromoAttribution[]
orders Order[]
@@map("common_promo_code")
}
model CommonCity {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
code String @unique @db.VarChar(16)
name String @db.VarChar(64)
province String @db.VarChar(32)
status CityStatus @default(PENDING)
partnerId BigInt? @map("partner_id") @db.UnsignedBigInt
localMinQty Int @default(2) @map("local_min_qty")
crossMinQty Int @default(6) @map("cross_min_qty")
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.DateTime(3)
partner Partner? @relation(fields: [partnerId], references: [id], onDelete: SetNull)
commissionRule CommonCityCommissionRule?
stores Store[]
orders Order[]
@@index([partnerId])
@@map("common_city")
}
model CommonCityCommissionRule {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
cityId BigInt @unique @map("city_id") @db.UnsignedBigInt
orderCommissionRate Decimal @default(0) @map("order_commission_rate") @db.Decimal(5, 4)
redeemCommissionRate Decimal @default(0) @map("redeem_commission_rate") @db.Decimal(5, 4)
partnerProfitRate Decimal @default(0.35) @map("partner_profit_rate") @db.Decimal(5, 4)
storeSettlementRate Decimal @default(0.60) @map("store_settlement_rate") @db.Decimal(5, 4)
updatedAt DateTime @updatedAt @map("updated_at") @db.DateTime(3)
city CommonCity @relation(fields: [cityId], references: [id], onDelete: Cascade)
@@map("common_city_commission_rule")
}
// ─── PARTNER ──────────────────────────────────────────
model Partner {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
companyName String @map("company_name") @db.VarChar(128)
address String @db.VarChar(256)
contactPhone String @map("contact_phone") @db.VarChar(20)
contractNo String? @map("contract_no") @db.VarChar(64)
contractSignedAt DateTime? @map("contract_signed_at") @db.DateTime(3)
contractExpireAt DateTime? @map("contract_expire_at") @db.DateTime(3)
bankAccountName String? @map("bank_account_name") @db.VarChar(64)
bankAccountNo String? @map("bank_account_no") @db.VarChar(32)
bankBranch String? @map("bank_branch") @db.VarChar(128)
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.DateTime(3)
cities CommonCity[]
accounts PartnerAccount[]
stores Store[]
bills PartnerBill[]
@@index([contactPhone])
@@map("partner_partner")
}
model PartnerAccount {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
partnerId BigInt @map("partner_id") @db.UnsignedBigInt
phone String @unique @db.VarChar(20)
name String @db.VarChar(64)
wxOpenId String? @map("wx_open_id") @db.VarChar(64)
wxUnionId String? @map("wx_union_id") @db.VarChar(64)
isPrimary Int @default(0) @map("is_primary") @db.TinyInt
parentAccountId BigInt? @map("parent_account_id") @db.UnsignedBigInt
staffRole PartnerStaffRole? @map("staff_role")
status AccountStatus @default(ACTIVE)
lastLoginAt DateTime? @map("last_login_at") @db.DateTime(3)
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.DateTime(3)
partner Partner @relation(fields: [partnerId], references: [id], onDelete: Restrict)
parent PartnerAccount? @relation("PartnerAccountHierarchy", fields: [parentAccountId], references: [id], onDelete: SetNull)
children PartnerAccount[] @relation("PartnerAccountHierarchy")
@@index([partnerId])
@@index([parentAccountId])
@@index([wxOpenId])
@@map("partner_account")
}
model PartnerBill {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
billNo String @unique @map("bill_no") @db.VarChar(32)
partnerId BigInt @map("partner_id") @db.UnsignedBigInt
periodStart DateTime @map("period_start") @db.DateTime(3)
periodEnd DateTime @map("period_end") @db.DateTime(3)
orderCommission Decimal @default(0) @map("order_commission") @db.Decimal(10, 2)
redeemCommission Decimal @default(0) @map("redeem_commission") @db.Decimal(10, 2)
totalAmount Decimal @map("total_amount") @db.Decimal(10, 2)
status PartnerBillStatus @default(DRAFT)
confirmedAt DateTime? @map("confirmed_at") @db.DateTime(3)
paidAt DateTime? @map("paid_at") @db.DateTime(3)
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
partner Partner @relation(fields: [partnerId], references: [id], onDelete: Restrict)
@@index([partnerId, status])
@@map("partner_bill")
}
// ─── HQ ───────────────────────────────────────────────
model HqAccount {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
phone String @unique @db.VarChar(20)
name String @db.VarChar(64)
adminRole HqAdminRole @default(OPS) @map("admin_role")
wxOpenId String? @map("wx_open_id") @db.VarChar(64)
wxUnionId String? @map("wx_union_id") @db.VarChar(64)
status AccountStatus @default(ACTIVE)
lastLoginAt DateTime? @map("last_login_at") @db.DateTime(3)
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.DateTime(3)
@@map("hq_account")
}
// ─── USER ─────────────────────────────────────────────
model User {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
userNo String @unique @map("user_no") @db.VarChar(20)
deviceKey String? @unique @map("device_key") @db.VarChar(36)
phone String? @unique @db.VarChar(20)
phoneVerifiedAt DateTime? @map("phone_verified_at") @db.DateTime(3)
mergedIntoUserId BigInt? @map("merged_into_user_id") @db.UnsignedBigInt
wxOpenId String? @map("wx_open_id") @db.VarChar(64)
wxUnionId String? @map("wx_union_id") @db.VarChar(64)
nickname String? @db.VarChar(64)
avatarResourceId BigInt? @map("avatar_resource_id") @db.UnsignedBigInt
status Int @default(1) @db.TinyInt
sourceType UserSourceType @default(ORGANIC) @map("source_type")
sourceRefId BigInt? @map("source_ref_id") @db.UnsignedBigInt
sourceLabel String? @map("source_label") @db.VarChar(128)
referrerUserId BigInt? @map("referrer_user_id") @db.UnsignedBigInt
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.DateTime(3)
mergedInto User? @relation("UserMerge", fields: [mergedIntoUserId], references: [id], onDelete: SetNull)
mergedFrom User[] @relation("UserMerge")
referrer User? @relation("UserReferrer", fields: [referrerUserId], references: [id], onDelete: SetNull)
referrers User[] @relation("UserReferrer")
avatar CommonResource? @relation("UserAvatar", fields: [avatarResourceId], references: [id], onDelete: SetNull)
addresses UserAddress[]
cityPreference UserCityPreference?
promoTouch UserPromoAttribution?
orders Order[]
benefitCoupons BenefitCoupon[]
redeemRecords RedeemRecord[]
@@index([sourceType, sourceRefId])
@@index([referrerUserId])
@@index([mergedIntoUserId])
@@index([wxOpenId])
@@map("user_user")
}
model UserAddress {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
userId BigInt @map("user_id") @db.UnsignedBigInt
receiverName String @map("receiver_name") @db.VarChar(32)
phone String @db.VarChar(20)
province String @db.VarChar(32)
city String @db.VarChar(32)
district String @db.VarChar(32)
detail String @db.VarChar(256)
latitude Decimal? @db.Decimal(10, 7)
longitude Decimal? @db.Decimal(10, 7)
isDefault Int @default(0) @map("is_default") @db.TinyInt
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.DateTime(3)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId])
@@map("user_address")
}
model UserCityPreference {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
userId BigInt @unique @map("user_id") @db.UnsignedBigInt
selectedCityCode String? @map("selected_city_code") @db.VarChar(16)
selectedDistrict String? @map("selected_district") @db.VarChar(32)
locateCityCode String? @map("locate_city_code") @db.VarChar(16)
locateDistrict String? @map("locate_district") @db.VarChar(32)
updatedAt DateTime @updatedAt @map("updated_at") @db.DateTime(3)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@map("user_city_preference")
}
model UserPromoAttribution {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
userId BigInt @unique @map("user_id") @db.UnsignedBigInt
promoCodeId BigInt @map("promo_code_id") @db.UnsignedBigInt
channelName String @map("channel_name") @db.VarChar(128)
firstTouchAt DateTime @map("first_touch_at") @db.DateTime(3)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
promoCode CommonPromoCode @relation(fields: [promoCodeId], references: [id], onDelete: Restrict)
@@index([promoCodeId])
@@map("user_promo_attribution")
}
// ─── STORE ────────────────────────────────────────────
model Store {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
cityId BigInt @map("city_id") @db.UnsignedBigInt
partnerId BigInt @map("partner_id") @db.UnsignedBigInt
categoryId BigInt? @map("category_id") @db.UnsignedBigInt
name String @db.VarChar(128)
phone String @db.VarChar(20)
province String @db.VarChar(32)
cityName String @map("city_name") @db.VarChar(32)
district String @db.VarChar(32)
address String @db.VarChar(256)
latitude Decimal? @db.Decimal(10, 7)
longitude Decimal? @db.Decimal(10, 7)
intro String? @db.Text
coverResourceId BigInt? @map("cover_resource_id") @db.UnsignedBigInt
avgPrice Decimal? @map("avg_price") @db.Decimal(10, 2)
rating Decimal? @db.Decimal(3, 2)
tags Json?
status StoreStatus @default(PAUSED)
openTime String? @map("open_time") @db.VarChar(8)
closeTime String? @map("close_time") @db.VarChar(8)
bankAccountName String? @map("bank_account_name") @db.VarChar(64)
bankAccountNo String? @map("bank_account_no") @db.VarChar(32)
bankBranch String? @map("bank_branch") @db.VarChar(128)
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.DateTime(3)
cityRef CommonCity @relation(fields: [cityId], references: [id], onDelete: Restrict)
partner Partner @relation(fields: [partnerId], references: [id], onDelete: Restrict)
category CommonStoreCategory? @relation(fields: [categoryId], references: [id], onDelete: SetNull)
coverResource CommonResource? @relation("StoreCover", fields: [coverResourceId], references: [id], onDelete: SetNull)
account StoreAccount?
redeemRecords RedeemRecord[]
ratings StoreRating[]
payouts StorePayout[]
@@index([cityId, status])
@@index([partnerId])
@@map("store_store")
}
model StoreAccount {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
storeId BigInt @unique @map("store_id") @db.UnsignedBigInt
phone String @unique @db.VarChar(20)
name String @db.VarChar(64)
wxOpenId String? @map("wx_open_id") @db.VarChar(64)
wxUnionId String? @map("wx_union_id") @db.VarChar(64)
status AccountStatus @default(ACTIVE)
lastLoginAt DateTime? @map("last_login_at") @db.DateTime(3)
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.DateTime(3)
store Store @relation(fields: [storeId], references: [id], onDelete: Cascade)
@@map("store_account")
}
// ─── ORDER ────────────────────────────────────────────
model Order {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
orderNo String @unique @map("order_no") @db.VarChar(32)
orderType OrderType @default(NORMAL) @map("order_type")
userId BigInt @map("user_id") @db.UnsignedBigInt
cityId BigInt @map("city_id") @db.UnsignedBigInt
status OrderStatus @default(PENDING_PAY)
payStatus PayStatus @default(UNPAID) @map("pay_status")
deliveryType DeliveryType @map("delivery_type")
originOrderId BigInt? @map("origin_order_id") @db.UnsignedBigInt
promoCodeId BigInt? @map("promo_code_id") @db.UnsignedBigInt
channelSource String? @map("channel_source") @db.VarChar(128)
productId BigInt @map("product_id") @db.UnsignedBigInt
barcode69 String @map("barcode_69") @db.VarChar(32)
productName String @map("product_name") @db.VarChar(128)
productSpec String @map("product_spec") @db.VarChar(128)
imageResourceId BigInt? @map("image_resource_id") @db.UnsignedBigInt
quantity Int
listUnitPrice Decimal @map("list_unit_price") @db.Decimal(10, 2)
listAmount Decimal @map("list_amount") @db.Decimal(10, 2)
discountAmount Decimal @default(0) @map("discount_amount") @db.Decimal(10, 2)
productAmount Decimal @map("product_amount") @db.Decimal(10, 2)
freightAmount Decimal @default(0) @map("freight_amount") @db.Decimal(10, 2)
freightPayType FreightPayType? @map("freight_pay_type")
payAmount Decimal @map("pay_amount") @db.Decimal(10, 2)
benefitAmount Decimal @default(0) @map("benefit_amount") @db.Decimal(10, 2)
receiverName String @map("receiver_name") @db.VarChar(32)
receiverPhone String @map("receiver_phone") @db.VarChar(20)
receiverAddress String @map("receiver_address") @db.Text
receiverProvince String @map("receiver_province") @db.VarChar(32)
receiverCity String @map("receiver_city") @db.VarChar(32)
receiverDistrict String @map("receiver_district") @db.VarChar(32)
clientIp String? @map("client_ip") @db.VarChar(45)
ipProvince String? @map("ip_province") @db.VarChar(32)
ipCity String? @map("ip_city") @db.VarChar(32)
ipDistrict String? @map("ip_district") @db.VarChar(32)
gpsProvince String? @map("gps_province") @db.VarChar(32)
gpsCity String? @map("gps_city") @db.VarChar(32)
gpsDistrict String? @map("gps_district") @db.VarChar(32)
gpsLatitude Decimal? @map("gps_latitude") @db.Decimal(10, 7)
gpsLongitude Decimal? @map("gps_longitude") @db.Decimal(10, 7)
gpsAddress String? @map("gps_address") @db.VarChar(256)
payExternalNo String? @map("pay_external_no") @db.VarChar(64)
paidAt DateTime? @map("paid_at") @db.DateTime(3)
shippedAt DateTime? @map("shipped_at") @db.DateTime(3)
completedAt DateTime? @map("completed_at") @db.DateTime(3)
cancelledAt DateTime? @map("cancelled_at") @db.DateTime(3)
payExpireAt DateTime? @map("pay_expire_at") @db.DateTime(3)
remark String? @db.VarChar(512)
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.DateTime(3)
user User @relation(fields: [userId], references: [id], onDelete: Restrict)
city CommonCity @relation(fields: [cityId], references: [id], onDelete: Restrict)
originOrder Order? @relation("OrderReshipment", fields: [originOrderId], references: [id], onDelete: SetNull)
reshipments Order[] @relation("OrderReshipment")
promoCode CommonPromoCode? @relation(fields: [promoCodeId], references: [id], onDelete: SetNull)
product CommonProductItem @relation(fields: [productId], references: [id], onDelete: Restrict)
imageResource CommonResource? @relation("OrderProductImage", fields: [imageResourceId], references: [id], onDelete: SetNull)
delivery OrderDelivery?
benefitCoupon BenefitCoupon?
@@index([userId, status])
@@index([cityId, createdAt])
@@index([productId])
@@index([barcode69])
@@index([payExternalNo])
@@index([ipCity])
@@index([gpsCity])
@@map("user_order")
}
model OrderDelivery {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
orderId BigInt @unique @map("order_id") @db.UnsignedBigInt
provider DeliveryProvider
providerOrderNo String? @map("provider_order_no") @db.VarChar(64)
trackingNo String? @map("tracking_no") @db.VarChar(64)
outWarehouseAt DateTime? @map("out_warehouse_at") @db.DateTime(3)
shippingAt DateTime? @map("shipping_at") @db.DateTime(3)
deliveredAt DateTime? @map("delivered_at") @db.DateTime(3)
signPhotoResourceId BigInt? @map("sign_photo_resource_id") @db.UnsignedBigInt
updatedAt DateTime @updatedAt @map("updated_at") @db.DateTime(3)
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
signPhotoResource CommonResource? @relation("DeliverySignPhoto", fields: [signPhotoResourceId], references: [id], onDelete: SetNull)
@@map("user_order_delivery")
}
// ─── BENEFIT & REDEEM ─────────────────────────────────
model BenefitCoupon {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
couponNo String @unique @map("coupon_no") @db.VarChar(32)
userId BigInt @map("user_id") @db.UnsignedBigInt
orderId BigInt @unique @map("order_id") @db.UnsignedBigInt
totalAmount Decimal @map("total_amount") @db.Decimal(10, 2)
usedAmount Decimal @default(0) @map("used_amount") @db.Decimal(10, 2)
balance Decimal @db.Decimal(10, 2)
status BenefitCouponStatus @default(ACTIVE)
sourceProduct String @map("source_product") @db.VarChar(128)
version Int @default(0)
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.DateTime(3)
user User @relation(fields: [userId], references: [id], onDelete: Restrict)
order Order @relation(fields: [orderId], references: [id], onDelete: Restrict)
redeemRecords RedeemRecord[]
@@index([userId, status])
@@map("user_benefit_coupon")
}
model RedeemRecord {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
redeemNo String @unique @map("redeem_no") @db.VarChar(32)
userId BigInt @map("user_id") @db.UnsignedBigInt
couponId BigInt @map("coupon_id") @db.UnsignedBigInt
storeId BigInt @map("store_id") @db.UnsignedBigInt
amount Decimal @db.Decimal(10, 2)
settleAmount Decimal @map("settle_amount") @db.Decimal(10, 2)
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
user User @relation(fields: [userId], references: [id], onDelete: Restrict)
coupon BenefitCoupon @relation(fields: [couponId], references: [id], onDelete: Restrict)
store Store @relation(fields: [storeId], references: [id], onDelete: Restrict)
rating StoreRating?
payout StorePayout?
@@index([storeId, createdAt])
@@map("user_redeem_record")
}
model StoreRating {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
redeemRecordId BigInt @unique @map("redeem_record_id") @db.UnsignedBigInt
storeId BigInt @map("store_id") @db.UnsignedBigInt
serviceScore Int @map("service_score") @db.TinyInt
envScore Int @map("env_score") @db.TinyInt
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
redeemRecord RedeemRecord @relation(fields: [redeemRecordId], references: [id], onDelete: Cascade)
store Store @relation(fields: [storeId], references: [id], onDelete: Restrict)
@@map("user_store_rating")
}
model StorePayout {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
redeemRecordId BigInt @unique @map("redeem_record_id") @db.UnsignedBigInt
storeId BigInt @map("store_id") @db.UnsignedBigInt
redeemAmount Decimal @map("redeem_amount") @db.Decimal(10, 2)
payoutAmount Decimal @map("payout_amount") @db.Decimal(10, 2)
settlementRate Decimal @map("settlement_rate") @db.Decimal(5, 4)
status StorePayoutStatus @default(PENDING)
expectedPayAt DateTime @map("expected_pay_at") @db.DateTime(3)
paidAt DateTime? @map("paid_at") @db.DateTime(3)
batchNo String? @map("batch_no") @db.VarChar(32)
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
redeemRecord RedeemRecord @relation(fields: [redeemRecordId], references: [id], onDelete: Restrict)
store Store @relation(fields: [storeId], references: [id], onDelete: Restrict)
@@index([storeId, status])
@@map("store_payout")
}
// ─── LOG ──────────────────────────────────────────────
model LogThirdParty {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
provider ThirdPartyProvider
scene String @db.VarChar(64)
refType String? @map("ref_type") @db.VarChar(32)
refId BigInt? @map("ref_id") @db.UnsignedBigInt
requestUrl String? @map("request_url") @db.VarChar(512)
requestBody Json? @map("request_body")
responseBody Json? @map("response_body")
externalNo String? @map("external_no") @db.VarChar(128)
amount Decimal? @db.Decimal(10, 2)
status ThirdPartyLogStatus @default(PENDING)
errorMessage String? @map("error_message") @db.VarChar(512)
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
@@index([refType, refId])
@@index([provider, scene, createdAt])
@@index([externalNo])
@@map("log_third_party")
}
model LogUserAnalytics {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
userId BigInt? @map("user_id") @db.UnsignedBigInt
sessionId String? @map("session_id") @db.VarChar(64)
eventName String @map("event_name") @db.VarChar(64)
clientApp ClientApp? @map("client_app")
pagePath String? @map("page_path") @db.VarChar(128)
refType String? @map("ref_type") @db.VarChar(32)
refId BigInt? @map("ref_id") @db.UnsignedBigInt
keyword String? @db.VarChar(128)
sourceType String? @map("source_type") @db.VarChar(32)
sourceRefId BigInt? @map("source_ref_id") @db.UnsignedBigInt
extraJson Json? @map("extra_json")
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
@@index([userId, createdAt])
@@index([eventName, createdAt])
@@index([sessionId])
@@index([refType, refId])
@@map("log_user_analytics")
}
+248
View File
@@ -0,0 +1,248 @@
import { PrismaClient, ResourceBizType, ResourceMediaType, ResourceOwnerType } from '@prisma/client';
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.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.partnerAccount.deleteMany();
await prisma.commonCityCommissionRule.deleteMany();
await prisma.commonCity.deleteMany();
await prisma.partner.deleteMany();
await prisma.commonProductItem.deleteMany();
await prisma.commonStoreCategory.deleteMany();
await prisma.commonPromoCode.deleteMany();
await prisma.commonResource.deleteMany();
await prisma.hqAccount.deleteMany();
const partner = await prisma.partner.create({
data: {
companyName: '郑州城市合伙人',
address: '河南省郑州市金水区',
contactPhone: '13700000001',
bankAccountName: '郑州合伙人公司',
bankAccountNo: '6222021234567890',
bankBranch: '工商银行郑州分行',
},
});
const city = await prisma.commonCity.create({
data: {
code: '410100',
name: '郑州市',
province: '河南省',
status: 'ACTIVE',
partnerId: partner.id,
localMinQty: 2,
crossMinQty: 6,
},
});
await prisma.commonCityCommissionRule.create({
data: {
cityId: city.id,
orderCommissionRate: 0.05,
redeemCommissionRate: 0.03,
partnerProfitRate: 0.35,
storeSettlementRate: 0.6,
},
});
const categories = await Promise.all([
prisma.commonStoreCategory.create({ data: { code: 'HOTPOT', name: '火锅', sort: 1 } }),
prisma.commonStoreCategory.create({ data: { code: 'LOCAL', name: '地方菜', sort: 2 } }),
]);
const productDefs = [
{ skuCode: 'QX-001', name: '杜康·白水古酿 500ml', subtitle: '清香型 52度 礼盒装', price: 599, sortOrder: 1, img: 'https://picsum.photos/seed/dukang1/400/400' },
{ skuCode: 'QX-002', name: '杜康·年份陈酿(十年)', subtitle: '清香型 42度 纯粮酿造', price: 880, sortOrder: 2, img: 'https://picsum.photos/seed/dukang2/400/400' },
{ skuCode: 'QX-003', name: '杜康·御享1号 珍藏版', subtitle: '高端定制 限量发售', price: 1299, sortOrder: 3, img: 'https://picsum.photos/seed/dukang3/400/400' },
{ skuCode: 'QX-004', name: '杜康·经典传承', subtitle: '清香型 纯粮固态', price: 399, sortOrder: 4, img: 'https://picsum.photos/seed/dukang4/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: def.skuCode === 'QX-002' ? '500ml | 42度' : def.skuCode === 'QX-004' ? '500ml | 46度' : '500ml | 52度',
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);
}
await prisma.partnerAccount.create({
data: {
partnerId: partner.id,
phone: '13700000001',
name: '郑州合伙人主账号',
isPrimary: 1,
staffRole: 'PARTNER',
},
});
const storeDefs = [
{
name: '郑州老城店',
phone: '0371-88880001',
district: '金水区',
address: '花园路100号',
intro: '正宗河南菜,欢迎核销好客权益',
img: 'https://picsum.photos/seed/store1/400/300',
categoryId: categories[0].id,
accountPhone: '13900000001',
accountName: '老城店店长',
},
{
name: '郑州美食城店',
phone: '0371-88880002',
district: '二七区',
address: '大学路200号',
intro: '地方特色餐饮',
img: 'https://picsum.photos/seed/store2/400/300',
categoryId: categories[1].id,
accountPhone: '13900000002',
accountName: '美食城店长',
},
];
for (const def of storeDefs) {
const store = await prisma.store.create({
data: {
cityId: city.id,
partnerId: partner.id,
categoryId: def.categoryId,
name: def.name,
phone: def.phone,
province: '河南省',
cityName: '郑州市',
district: def.district,
address: def.address,
intro: def.intro,
status: 'OPEN',
openTime: '10:00',
closeTime: '22:00',
bankAccountName: def.name,
bankAccountNo: '6222029876543210',
bankBranch: '建设银行郑州分行',
},
});
const cover = await createMockResource(ResourceOwnerType.STORE, store.id, ResourceBizType.COVER, def.img);
await prisma.store.update({ where: { id: store.id }, data: { coverResourceId: cover.id } });
await prisma.storeAccount.create({
data: { storeId: store.id, phone: def.accountPhone, name: def.accountName },
});
}
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',
name: '总部管理员',
adminRole: 'SUPER_ADMIN',
},
});
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()}`,
partnerId: partner.id,
periodStart,
periodEnd,
orderCommission: 1200,
redeemCommission: 800,
totalAmount: 2000,
status: 'CONFIRMED',
confirmedAt: now,
},
});
console.log('Seed complete:', {
city: city.name,
products: products.length,
stores: storeDefs.length,
testPhones: {
user: '13800000001',
store: '13900000001',
partner: '13700000001',
hq: '13600000001',
},
});
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});
@@ -1,5 +1,5 @@
/**
* 将 products.benefit_amount 同步为与 price 相同(全额好客权益)。
* 将 common_product_item.benefit_amount 同步为与 price 相同(全额好客权益)。
* 用法:pnpm db:sync-benefit
*/
import { PrismaClient } from '@prisma/client';
@@ -7,13 +7,13 @@ import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
const products = await prisma.product.findMany({
const products = await prisma.commonProductItem.findMany({
select: { id: true, skuCode: true, name: true, price: true, benefitAmount: true },
orderBy: { sortOrder: 'asc' },
});
if (products.length === 0) {
console.log('No products found. Run pnpm db:seed first.');
console.log('No products found. Run pnpm prisma:seed first.');
return;
}
@@ -23,7 +23,7 @@ async function main() {
const price = Number(p.price);
const before = p.benefitAmount != null ? Number(p.benefitAmount) : null;
await prisma.product.update({
await prisma.commonProductItem.update({
where: { id: p.id },
data: { benefitAmount: p.price },
});