代下单功能

This commit is contained in:
2026-07-12 11:40:04 +08:00
parent d949301bc1
commit de01d36cdb
46 changed files with 2657 additions and 253 deletions
+17 -8
View File
@@ -136,16 +136,25 @@ CREATE TABLE common_store_category (
DROP TABLE IF EXISTS common_promo_code;
CREATE TABLE common_promo_code (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
code VARCHAR(32) NOT NULL,
name VARCHAR(128) NOT NULL,
status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE',
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
code VARCHAR(32) NOT NULL,
name VARCHAR(128) NOT NULL,
scene VARCHAR(32) NOT NULL DEFAULT 'ONLINE_LINK',
qrcode_id VARCHAR(64) NOT NULL COMMENT '二维码唯一识别码',
status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE',
owner_user_id BIGINT UNSIGNED DEFAULT NULL COMMENT '关联用户(统计/归因)',
remark VARCHAR(256) DEFAULT NULL,
qrcode_resource_id BIGINT UNSIGNED DEFAULT NULL COMMENT '小程序码 common_resource.id',
scan_count INT NOT NULL DEFAULT 0,
order_count INT NOT NULL DEFAULT 0,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
scan_count INT NOT NULL DEFAULT 0,
order_count INT NOT NULL DEFAULT 0,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (id),
UNIQUE KEY uk_common_promo_code_code (code)
UNIQUE KEY uk_common_promo_code_code (code),
UNIQUE KEY uk_common_promo_code_qrcode_id (qrcode_id),
KEY idx_common_promo_code_owner (owner_user_id),
KEY idx_common_promo_code_scene_status (scene, status),
CONSTRAINT fk_common_promo_code_owner_user FOREIGN KEY (owner_user_id) REFERENCES user_user(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='推广码';
-- ===================== PARTNER(城市合伙人主账号 + 子账号) =============================
@@ -0,0 +1,28 @@
-- 推广码表扩展:scene / qrcode_id / owner_user_id / remark / updated_at
-- 已有数据会先回填 qrcode_id 再设 NOT NULL
ALTER TABLE common_promo_code
ADD COLUMN IF NOT EXISTS scene VARCHAR(32) NOT NULL DEFAULT 'ONLINE_LINK' AFTER name,
ADD COLUMN IF NOT EXISTS qrcode_id VARCHAR(64) NULL AFTER scene,
ADD COLUMN IF NOT EXISTS owner_user_id BIGINT UNSIGNED NULL AFTER status,
ADD COLUMN IF NOT EXISTS remark VARCHAR(256) NULL AFTER owner_user_id,
ADD COLUMN IF NOT EXISTS updated_at DATETIME(3) NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) AFTER created_at;
UPDATE common_promo_code
SET qrcode_id = 'a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456'
WHERE code = 'DKHQ001' AND (qrcode_id IS NULL OR qrcode_id = '');
UPDATE common_promo_code
SET qrcode_id = 'b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef12345678'
WHERE code = 'DKDEMO1' AND (qrcode_id IS NULL OR qrcode_id = '');
UPDATE common_promo_code
SET qrcode_id = LOWER(REPLACE(UUID(), '-', ''))
WHERE qrcode_id IS NULL OR qrcode_id = '';
UPDATE common_promo_code
SET updated_at = created_at
WHERE updated_at IS NULL;
ALTER TABLE common_promo_code
MODIFY qrcode_id VARCHAR(64) NOT NULL;
@@ -0,0 +1,106 @@
/**
* 一次性迁移:common_promo_code 扩展字段(scene / qrcode_id / owner_user_id / remark / updated_at
* 用法:cd server/dukang-api && npx ts-node --transpile-only prisma/migrate-promo-code-v31.ts
*/
import { PrismaClient } from '@prisma/client';
import { randomBytes } from 'crypto';
const prisma = new PrismaClient();
async function columnExists(table: string, column: string): Promise<boolean> {
const rows = await prisma.$queryRawUnsafe<Array<{ Field: string }>>(
`SHOW COLUMNS FROM ${table} LIKE '${column}'`,
);
return rows.length > 0;
}
async function addColumn(sql: string) {
try {
await prisma.$executeRawUnsafe(sql);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
if (!msg.includes('Duplicate column')) throw e;
}
}
async function main() {
if (!(await columnExists('common_promo_code', 'scene'))) {
await addColumn(
`ALTER TABLE common_promo_code ADD COLUMN scene VARCHAR(32) NOT NULL DEFAULT 'ONLINE_LINK' AFTER name`,
);
}
if (!(await columnExists('common_promo_code', 'qrcode_id'))) {
await addColumn(`ALTER TABLE common_promo_code ADD COLUMN qrcode_id VARCHAR(64) NULL AFTER scene`);
}
if (!(await columnExists('common_promo_code', 'owner_user_id'))) {
await addColumn(
`ALTER TABLE common_promo_code ADD COLUMN owner_user_id BIGINT UNSIGNED NULL AFTER status`,
);
}
if (!(await columnExists('common_promo_code', 'remark'))) {
await addColumn(`ALTER TABLE common_promo_code ADD COLUMN remark VARCHAR(256) NULL AFTER owner_user_id`);
}
if (!(await columnExists('common_promo_code', 'updated_at'))) {
await addColumn(
`ALTER TABLE common_promo_code ADD COLUMN updated_at DATETIME(3) NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) AFTER created_at`,
);
}
const rows = await prisma.$queryRawUnsafe<Array<{ id: bigint; code: string; qrcode_id: string | null }>>(
`SELECT id, code, qrcode_id FROM common_promo_code`,
);
const preset: Record<string, string> = {
DKHQ001: 'a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456',
DKDEMO1: 'b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef12345678',
};
for (const row of rows) {
if (row.qrcode_id) continue;
const qrcodeId = preset[row.code] ?? randomBytes(32).toString('hex');
await prisma.$executeRawUnsafe(
`UPDATE common_promo_code SET qrcode_id = ? WHERE id = ?`,
qrcodeId,
row.id,
);
}
await prisma.$executeRawUnsafe(`UPDATE common_promo_code SET updated_at = created_at WHERE updated_at IS NULL`);
await prisma.$executeRawUnsafe(`ALTER TABLE common_promo_code MODIFY qrcode_id VARCHAR(64) NOT NULL`);
try {
await prisma.$executeRawUnsafe(
`ALTER TABLE common_promo_code ADD UNIQUE KEY uk_common_promo_code_qrcode_id (qrcode_id)`,
);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
if (!msg.includes('Duplicate key name')) throw e;
}
try {
await prisma.$executeRawUnsafe(
`ALTER TABLE common_promo_code ADD KEY idx_common_promo_code_owner (owner_user_id)`,
);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
if (!msg.includes('Duplicate key name')) throw e;
}
try {
await prisma.$executeRawUnsafe(
`ALTER TABLE common_promo_code ADD KEY idx_common_promo_code_scene_status (scene, status)`,
);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
if (!msg.includes('Duplicate key name')) throw e;
}
console.log('migrate-promo-code-v31: OK');
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(() => prisma.$disconnect());
+18
View File
@@ -100,6 +100,14 @@ enum PromoCodeStatus {
DISABLED
}
enum PromoCodeScene {
ONLINE_LINK
OFFLINE_PICKUP
PARTNER_CHANNEL
EVENT
OTHER
}
enum CityStatus {
PENDING
ACTIVE
@@ -168,6 +176,7 @@ enum UserSourceType {
enum OrderType {
NORMAL
RESHIPMENT
PROXY
}
enum OrderStatus {
@@ -392,16 +401,24 @@ model CommonPromoCode {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
code String @unique @db.VarChar(32)
name String @db.VarChar(128)
scene PromoCodeScene @default(ONLINE_LINK)
qrcodeId String @unique @map("qrcode_id") @db.VarChar(64)
status PromoCodeStatus @default(ACTIVE)
ownerUserId BigInt? @map("owner_user_id") @db.UnsignedBigInt
remark String? @db.VarChar(256)
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)
updatedAt DateTime @updatedAt @map("updated_at") @db.DateTime(3)
ownerUser User? @relation("PromoOwnerUser", fields: [ownerUserId], references: [id], onDelete: SetNull)
qrcodeResource CommonResource? @relation("PromoQrcode", fields: [qrcodeResourceId], references: [id], onDelete: SetNull)
attributions UserPromoAttribution[]
orders Order[]
@@index([ownerUserId])
@@index([scene, status])
@@map("common_promo_code")
}
@@ -587,6 +604,7 @@ model User {
addresses UserAddress[]
cityPreference UserCityPreference?
promoTouch UserPromoAttribution?
ownedPromoCodes CommonPromoCode[] @relation("PromoOwnerUser")
orders Order[]
benefitCoupons BenefitCoupon[]
redeemRecords RedeemRecord[]
+8
View File
@@ -589,6 +589,10 @@ async function main() {
name: '总部品鉴会',
scene: 'EVENT',
qrcodeId: 'a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456',
status: 'ACTIVE',
},
@@ -605,6 +609,10 @@ async function main() {
name: '郑州品鉴会演示',
scene: 'OFFLINE_PICKUP',
qrcodeId: 'b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef12345678',
status: 'ACTIVE',
},