feat: 支持商品管理

This commit is contained in:
richarjiang
2026-09-16 22:49:47 +08:00
parent 58c6d2092a
commit 95b9f6511c
30 changed files with 2800 additions and 5 deletions

View File

@@ -0,0 +1,74 @@
-- CreateTable
CREATE TABLE `products` (
`id` VARCHAR(191) NOT NULL,
`title` VARCHAR(80) NOT NULL,
`description` TEXT NOT NULL,
`cover_url` VARCHAR(1000) NOT NULL,
`price` INTEGER NOT NULL,
`stock` INTEGER NOT NULL DEFAULT 0,
`sold` INTEGER NOT NULL DEFAULT 0,
`is_active` BOOLEAN NOT NULL DEFAULT false,
`sort_order` INTEGER NOT NULL DEFAULT 0,
`version` INTEGER NOT NULL DEFAULT 0,
`created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updated_at` DATETIME(3) NOT NULL,
INDEX `products_is_active_sort_order_idx`(`is_active`, `sort_order`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `product_orders` (
`id` VARCHAR(191) NOT NULL,
`order_no` VARCHAR(32) NOT NULL,
`request_id` VARCHAR(80) NOT NULL,
`user_id` VARCHAR(191) NOT NULL,
`product_id` VARCHAR(191) NOT NULL,
`title` VARCHAR(80) NOT NULL,
`cover_url` VARCHAR(1000) NOT NULL,
`unit_price` INTEGER NOT NULL,
`quantity` INTEGER NOT NULL,
`amount` INTEGER NOT NULL,
`contact_name` VARCHAR(40) NOT NULL,
`contact_phone` VARCHAR(20) NOT NULL,
`status` ENUM('PENDING', 'PAID', 'COMPLETED', 'CANCELLED') NOT NULL DEFAULT 'PENDING',
`pickup_code` VARCHAR(12) NOT NULL,
`wx_transaction_id` VARCHAR(191) NULL,
`expires_at` DATETIME(3) NOT NULL,
`paid_at` DATETIME(3) NULL,
`completed_at` DATETIME(3) NULL,
`completed_by` VARCHAR(191) NULL,
`created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updated_at` DATETIME(3) NOT NULL,
UNIQUE INDEX `product_orders_order_no_key`(`order_no`),
UNIQUE INDEX `product_orders_wx_transaction_id_key`(`wx_transaction_id`),
INDEX `product_orders_status_expires_at_idx`(`status`, `expires_at`),
INDEX `product_orders_user_id_created_at_idx`(`user_id`, `created_at`),
INDEX `product_orders_product_id_status_idx`(`product_id`, `status`),
UNIQUE INDEX `product_orders_user_id_request_id_key`(`user_id`, `request_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `product_stock_changes` (
`id` VARCHAR(191) NOT NULL,
`product_id` VARCHAR(191) NOT NULL,
`delta` INTEGER NOT NULL,
`reason` VARCHAR(200) NOT NULL,
`operator_id` VARCHAR(191) NOT NULL,
`created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
INDEX `product_stock_changes_product_id_created_at_idx`(`product_id`, `created_at`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- AddForeignKey
ALTER TABLE `product_orders` ADD CONSTRAINT `product_orders_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `product_orders` ADD CONSTRAINT `product_orders_product_id_fkey` FOREIGN KEY (`product_id`) REFERENCES `products`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `product_stock_changes` ADD CONSTRAINT `product_stock_changes_product_id_fkey` FOREIGN KEY (`product_id`) REFERENCES `products`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -73,6 +73,7 @@ model User {
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
productOrders ProductOrder[]
bodyMetrics BodyMetric[]
memberNotes MemberNote[]
progressPhotos ProgressPhoto[]
@@ -620,3 +621,73 @@ model GrowthShareCard {
@@index([userId, createdAt])
@@map("growth_share_cards")
}
// Inventory counts sellable units; pending orders reserve units immediately.
enum ProductOrderStatus {
PENDING
PAID
COMPLETED
CANCELLED
}
model Product {
id String @id @default(uuid())
title String @db.VarChar(80)
description String @db.Text
coverUrl String @map("cover_url") @db.VarChar(1000)
price Int
stock Int @default(0)
sold Int @default(0)
isActive Boolean @default(false) @map("is_active")
sortOrder Int @default(0) @map("sort_order")
version Int @default(0)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
orders ProductOrder[]
stockChanges ProductStockChange[]
@@index([isActive, sortOrder])
@@map("products")
}
model ProductOrder {
id String @id @default(uuid())
orderNo String @unique @map("order_no") @db.VarChar(32)
requestId String @map("request_id") @db.VarChar(80)
userId String @map("user_id")
productId String @map("product_id")
title String @db.VarChar(80)
coverUrl String @map("cover_url") @db.VarChar(1000)
unitPrice Int @map("unit_price")
quantity Int
amount Int
contactName String @map("contact_name") @db.VarChar(40)
contactPhone String @map("contact_phone") @db.VarChar(20)
status ProductOrderStatus @default(PENDING)
pickupCode String @map("pickup_code") @db.VarChar(12)
wxTransactionId String? @unique @map("wx_transaction_id")
expiresAt DateTime @map("expires_at")
paidAt DateTime? @map("paid_at")
completedAt DateTime? @map("completed_at")
completedBy String? @map("completed_by")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id])
product Product @relation(fields: [productId], references: [id])
@@unique([userId, requestId])
@@index([status, expiresAt])
@@index([userId, createdAt])
@@index([productId, status])
@@map("product_orders")
}
model ProductStockChange {
id String @id @default(uuid())
productId String @map("product_id")
delta Int
reason String @db.VarChar(200)
operatorId String @map("operator_id")
createdAt DateTime @default(now()) @map("created_at")
product Product @relation(fields: [productId], references: [id])
@@index([productId, createdAt])
@@map("product_stock_changes")
}