feat(r3): complete order and payment flow
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
-- R1 product fields and R3 order idempotency fields.
|
||||
ALTER TABLE "Product"
|
||||
ADD COLUMN IF NOT EXISTS "originalPrice" DECIMAL(10,2),
|
||||
ADD COLUMN IF NOT EXISTS "leadTime" TEXT NOT NULL DEFAULT '7-10 个工作日',
|
||||
ADD COLUMN IF NOT EXISTS "subtitle" TEXT,
|
||||
ADD COLUMN IF NOT EXISTS "tone" INTEGER[] NOT NULL DEFAULT ARRAY[]::INTEGER[],
|
||||
ADD COLUMN IF NOT EXISTS "tags" TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[],
|
||||
ADD COLUMN IF NOT EXISTS "specs" JSONB,
|
||||
ADD COLUMN IF NOT EXISTS "mask" JSONB,
|
||||
ADD COLUMN IF NOT EXISTS "story" TEXT,
|
||||
ADD COLUMN IF NOT EXISTS "scene" TEXT,
|
||||
ADD COLUMN IF NOT EXISTS "iconImg" TEXT,
|
||||
ADD COLUMN IF NOT EXISTS "sort" INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
ALTER TABLE "Order"
|
||||
ADD COLUMN IF NOT EXISTS "requestId" TEXT,
|
||||
ADD COLUMN IF NOT EXISTS "paidAt" TIMESTAMP(3);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "Order_userId_requestId_key" ON "Order"("userId", "requestId");
|
||||
@@ -0,0 +1,11 @@
|
||||
-- 待付款订单统一在服务端以创建后 30 分钟为截止时间,避免只由客户端倒计时控制。
|
||||
ALTER TYPE "OrderStatus" ADD VALUE IF NOT EXISTS 'PAYMENT_EXPIRED';
|
||||
|
||||
ALTER TABLE "Order" ADD COLUMN IF NOT EXISTS "paymentExpiresAt" TIMESTAMP(3);
|
||||
|
||||
-- 为已有待付款订单补齐截止时间;历史订单会按创建时间计算并在下一次读取时自动过期。
|
||||
UPDATE "Order"
|
||||
SET "paymentExpiresAt" = "createdAt" + INTERVAL '30 minutes'
|
||||
WHERE "paymentExpiresAt" IS NULL AND "status" = 'PENDING';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "Order_paymentExpiresAt_idx" ON "Order"("paymentExpiresAt");
|
||||
@@ -56,8 +56,19 @@ model Product {
|
||||
name String
|
||||
categoryId String?
|
||||
price Decimal @db.Decimal(10, 2)
|
||||
originalPrice Decimal? @db.Decimal(10, 2)
|
||||
leadTime String @default("7-10 个工作日")
|
||||
subtitle String?
|
||||
tone Int[]
|
||||
tags String[]
|
||||
specs Json?
|
||||
mask Json?
|
||||
story String?
|
||||
scene String?
|
||||
iconImg String?
|
||||
images String[]
|
||||
description String?
|
||||
sort Int @default(0)
|
||||
status ProductStatus @default(DRAFT)
|
||||
category Category? @relation(fields: [categoryId], references: [id])
|
||||
orderItems OrderItem[]
|
||||
@@ -127,6 +138,10 @@ model Order {
|
||||
status OrderStatus @default(PENDING)
|
||||
totalAmount Decimal @db.Decimal(10, 2)
|
||||
addressSnapshot Json
|
||||
requestId String?
|
||||
paidAt DateTime?
|
||||
// 待付款订单的固定截止时刻;创建后 30 分钟不可再支付。
|
||||
paymentExpiresAt DateTime?
|
||||
// 关联的设计清单(R4 下单后 WCD 派单据此读取 designData)
|
||||
designListId String?
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
@@ -139,7 +154,9 @@ model Order {
|
||||
|
||||
@@index([userId])
|
||||
@@index([status])
|
||||
@@index([paymentExpiresAt])
|
||||
@@index([designListId])
|
||||
@@unique([userId, requestId])
|
||||
}
|
||||
|
||||
enum OrderStatus {
|
||||
@@ -149,6 +166,7 @@ enum OrderStatus {
|
||||
SHIPPED
|
||||
COMPLETED
|
||||
CANCELLED
|
||||
PAYMENT_EXPIRED
|
||||
}
|
||||
|
||||
model OrderItem {
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
const products = [
|
||||
{ id: 'notebook-small', name: '微雕笔记本(小)', price: 12, originalPrice: 18, leadTime: '3-5个工作日', iconImg: '/icon/书本.png', images: ['/img/book_small/The1.jpg'], mask: { shape: 'rect', width: 300, height: 420, borderRadius: 8 }, tags: ['伴手礼', '学生', '手账'], sort: 1 },
|
||||
{ id: 'notebook-large', name: '微雕笔记本(大)', price: 45, originalPrice: 58, leadTime: '3-5个工作日', iconImg: '/icon/书本_大.png', images: ['/img/book_big/The1.jpg', '/img/book_big/The2.jpg'], mask: { shape: 'rect', width: 340, height: 480, borderRadius: 8 }, tags: ['毕业礼', '团建', '企业定制'], sort: 2 },
|
||||
{ id: 'coaster', name: '铜质杯垫', price: 25, originalPrice: 35, leadTime: '5-7个工作日', iconImg: '/icon/杯子.png', images: ['/img/cup/The1.jpg'], mask: { shape: 'circle', width: 280, height: 280 }, tags: ['乔迁礼', '黄铜', '桌面美学'], sort: 3 },
|
||||
{ id: 'penbox', name: '竹制笔盒', price: 75, originalPrice: 98, leadTime: '7-10个工作日', iconImg: '/icon/笔盒.png', images: ['/img/penbox/The1.jpg', '/img/penbox/The2.jpg', '/img/penbox/The3.jpg'], mask: { shape: 'rect', width: 320, height: 160, borderRadius: 12 }, tags: ['文房', '书法', '天然材质'], sort: 4 },
|
||||
{ id: 'booklamp', name: '书本型灯', price: 45, originalPrice: 68, leadTime: '5-7个工作日', iconImg: '/icon/书灯.png', images: ['/img/booklight/The1.jpg', '/img/booklight/The2.jpg'], mask: { shape: 'rect', width: 320, height: 240, borderRadius: 4 }, tags: ['情侣礼', '夜灯', '创意礼物'], sort: 5 },
|
||||
];
|
||||
|
||||
async function main() {
|
||||
const category = await prisma.category.upsert({ where: { id: 'cat-custom' }, update: { name: '定制商品', sort: 1 }, create: { id: 'cat-custom', name: '定制商品', sort: 1 } });
|
||||
for (const product of products) {
|
||||
await prisma.product.upsert({ where: { id: product.id }, update: { ...product, categoryId: category.id, status: 'ON_SALE', tone: [28, 22, 18] }, create: { ...product, categoryId: category.id, status: 'ON_SALE', tone: [28, 22, 18] } });
|
||||
}
|
||||
console.log(`Seed 完成:${products.length} 个在售商品已写入`);
|
||||
}
|
||||
|
||||
main().catch((error) => { console.error(error); process.exitCode = 1; }).finally(() => prisma.$disconnect());
|
||||
+19
-29
@@ -3,36 +3,26 @@ import { PrismaClient } from '@prisma/client';
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
// 分类
|
||||
const apparel = await prisma.category.upsert({
|
||||
where: { id: 'cat-apparel' },
|
||||
update: {},
|
||||
create: { id: 'cat-apparel', name: '服饰', sort: 1 },
|
||||
const category = await prisma.category.upsert({
|
||||
where: { id: 'cat-custom' },
|
||||
update: { name: '定制商品', sort: 1 },
|
||||
create: { id: 'cat-custom', name: '定制商品', sort: 1 },
|
||||
});
|
||||
|
||||
const tshirt = await prisma.category.upsert({
|
||||
where: { id: 'cat-tshirt' },
|
||||
update: {},
|
||||
create: { id: 'cat-tshirt', name: 'T恤', parentId: apparel.id, sort: 1 },
|
||||
});
|
||||
|
||||
// 示例商品
|
||||
await prisma.product.upsert({
|
||||
where: { id: 'prod-demo-tshirt' },
|
||||
update: {},
|
||||
create: {
|
||||
id: 'prod-demo-tshirt',
|
||||
name: '示例定制 T恤',
|
||||
categoryId: tshirt.id,
|
||||
price: 99.0,
|
||||
images: [],
|
||||
description: '用于本地开发联调的示例商品,可在此设计清单中上传自定义图案。',
|
||||
status: 'ON_SALE',
|
||||
},
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Seed 完成:分类 + 示例商品已写入');
|
||||
const products = [
|
||||
{ id: 'notebook-small', name: '微雕笔记本(小)', price: 12, originalPrice: 18, leadTime: '3-5个工作日', iconImg: '/icon/书本.png', images: ['/img/book_small/The1.jpg'], mask: { shape: 'rect', width: 300, height: 420, borderRadius: 8 }, tags: ['伴手礼', '学生', '手账'], sort: 1 },
|
||||
{ id: 'notebook-large', name: '微雕笔记本(大)', price: 45, originalPrice: 58, leadTime: '3-5个工作日', iconImg: '/icon/书本_大.png', images: ['/img/book_big/The1.jpg', '/img/book_big/The2.jpg'], mask: { shape: 'rect', width: 340, height: 480, borderRadius: 8 }, tags: ['毕业礼', '团建', '企业定制'], sort: 2 },
|
||||
{ id: 'coaster', name: '铜质杯垫', price: 25, originalPrice: 35, leadTime: '5-7个工作日', iconImg: '/icon/杯子.png', images: ['/img/cup/The1.jpg'], mask: { shape: 'circle', width: 280, height: 280 }, tags: ['乔迁礼', '黄铜', '桌面美学'], sort: 3 },
|
||||
{ id: 'penbox', name: '竹制笔盒', price: 75, originalPrice: 98, leadTime: '7-10个工作日', iconImg: '/icon/笔盒.png', images: ['/img/penbox/The1.jpg', '/img/penbox/The2.jpg', '/img/penbox/The3.jpg'], mask: { shape: 'rect', width: 320, height: 160, borderRadius: 12 }, tags: ['文房', '书法', '天然材质'], sort: 4 },
|
||||
{ id: 'booklamp', name: '书本型灯', price: 45, originalPrice: 68, leadTime: '5-7个工作日', iconImg: '/icon/书灯.png', images: ['/img/booklight/The1.jpg', '/img/booklight/The2.jpg'], mask: { shape: 'rect', width: 320, height: 240, borderRadius: 4 }, tags: ['情侣礼', '夜灯', '创意礼物'], sort: 5 },
|
||||
];
|
||||
for (const product of products) {
|
||||
await prisma.product.upsert({
|
||||
where: { id: product.id },
|
||||
update: { ...product, categoryId: category.id, status: 'ON_SALE', tone: [28, 22, 18] },
|
||||
create: { ...product, categoryId: category.id, status: 'ON_SALE', tone: [28, 22, 18] },
|
||||
});
|
||||
}
|
||||
console.log(`Seed 完成:${products.length} 个在售商品已写入`);
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user