feat: 手动充值、邀请码注册与后台管理增强
新增玩家手动充值全流程(收款方式配置、充值下单/审核、钱包上分), 支持邀请码注册、邀请历史与专属返水率;完善后台代理/玩家管理与响应式操作栏, 并补充前台注册、充值页及多语言错误码。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "payment_methods" (
|
||||
"id" BIGSERIAL NOT NULL,
|
||||
"method_type" VARCHAR(20) NOT NULL,
|
||||
"bank_name" VARCHAR(128),
|
||||
"account_holder" VARCHAR(128),
|
||||
"account_number" VARCHAR(128),
|
||||
"usdt_address" VARCHAR(256),
|
||||
"qr_code_url" VARCHAR(500),
|
||||
"display_name" VARCHAR(128),
|
||||
"sort_order" INTEGER NOT NULL DEFAULT 0,
|
||||
"is_active" BOOLEAN NOT NULL DEFAULT true,
|
||||
"show_on_player" BOOLEAN NOT NULL DEFAULT true,
|
||||
"created_by" BIGINT,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "payment_methods_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "deposit_orders" (
|
||||
"id" BIGSERIAL NOT NULL,
|
||||
"order_no" VARCHAR(64) NOT NULL,
|
||||
"player_id" BIGINT NOT NULL,
|
||||
"payment_method_id" BIGINT NOT NULL,
|
||||
"method_type" VARCHAR(20) NOT NULL,
|
||||
"amount" DECIMAL(18,4) NOT NULL,
|
||||
"screenshot_url" VARCHAR(500) NOT NULL,
|
||||
"status" VARCHAR(20) NOT NULL DEFAULT 'PENDING',
|
||||
"approved_amount" DECIMAL(18,4),
|
||||
"reviewer_id" BIGINT,
|
||||
"reviewed_at" TIMESTAMP(3),
|
||||
"reject_reason" VARCHAR(500),
|
||||
"remark" VARCHAR(500),
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "deposit_orders_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "payment_methods_method_type_is_active_idx" ON "payment_methods"("method_type", "is_active");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "deposit_orders_order_no_key" ON "deposit_orders"("order_no");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "deposit_orders_player_id_idx" ON "deposit_orders"("player_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "deposit_orders_status_idx" ON "deposit_orders"("status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "deposit_orders_created_at_idx" ON "deposit_orders"("created_at");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "deposit_orders" ADD CONSTRAINT "deposit_orders_player_id_fkey" FOREIGN KEY ("player_id") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "deposit_orders" ADD CONSTRAINT "deposit_orders_payment_method_id_fkey" FOREIGN KEY ("payment_method_id") REFERENCES "payment_methods"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@@ -0,0 +1 @@
|
||||
-- This is an empty migration.
|
||||
@@ -0,0 +1,11 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "users" ADD COLUMN "invite_code" VARCHAR(16);
|
||||
|
||||
-- Backfill existing admins and agents with deterministic unique codes
|
||||
UPDATE "users"
|
||||
SET "invite_code" = UPPER(SUBSTR(MD5("id"::text || ':invite'), 1, 8))
|
||||
WHERE "user_type" IN ('ADMIN', 'AGENT')
|
||||
AND "invite_code" IS NULL;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "users_invite_code_key" ON "users"("invite_code");
|
||||
@@ -0,0 +1,8 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "users" ADD COLUMN "invite_sponsor_id" BIGINT;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "users_invite_sponsor_id_idx" ON "users"("invite_sponsor_id");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "users" ADD CONSTRAINT "users_invite_sponsor_id_fkey" FOREIGN KEY ("invite_sponsor_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,56 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "user_invites" (
|
||||
"id" BIGSERIAL NOT NULL,
|
||||
"code" VARCHAR(16) NOT NULL,
|
||||
"sponsor_id" BIGINT NOT NULL,
|
||||
"status" VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
|
||||
"register_count" INTEGER NOT NULL DEFAULT 0,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"revoked_at" TIMESTAMP(3),
|
||||
|
||||
CONSTRAINT "user_invites_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "user_invites_code_key" ON "user_invites"("code");
|
||||
CREATE INDEX "user_invites_sponsor_id_idx" ON "user_invites"("sponsor_id");
|
||||
CREATE INDEX "user_invites_status_idx" ON "user_invites"("status");
|
||||
CREATE INDEX "user_invites_created_at_idx" ON "user_invites"("created_at");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "user_invites" ADD CONSTRAINT "user_invites_sponsor_id_fkey" FOREIGN KEY ("sponsor_id") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "users" ADD COLUMN "used_invite_id" BIGINT;
|
||||
CREATE INDEX "users_used_invite_id_idx" ON "users"("used_invite_id");
|
||||
ALTER TABLE "users" ADD CONSTRAINT "users_used_invite_id_fkey" FOREIGN KEY ("used_invite_id") REFERENCES "user_invites"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- Backfill existing invite codes into history
|
||||
INSERT INTO "user_invites" ("code", "sponsor_id", "status", "register_count", "created_at")
|
||||
SELECT u."invite_code", u."id", 'ACTIVE', 0, COALESCE(u."updated_at", u."created_at")
|
||||
FROM "users" u
|
||||
WHERE u."invite_code" IS NOT NULL
|
||||
AND u."user_type" IN ('ADMIN', 'AGENT')
|
||||
ON CONFLICT ("code") DO NOTHING;
|
||||
|
||||
-- Link players to invite records when possible
|
||||
UPDATE "users" p
|
||||
SET "used_invite_id" = ui."id"
|
||||
FROM "user_invites" ui
|
||||
WHERE p."invite_sponsor_id" = ui."sponsor_id"
|
||||
AND p."user_type" = 'PLAYER'
|
||||
AND p."used_invite_id" IS NULL
|
||||
AND ui."status" = 'ACTIVE'
|
||||
AND ui."code" = (
|
||||
SELECT s."invite_code" FROM "users" s WHERE s."id" = p."invite_sponsor_id"
|
||||
);
|
||||
|
||||
UPDATE "user_invites" ui
|
||||
SET "register_count" = sub.cnt
|
||||
FROM (
|
||||
SELECT "used_invite_id" AS id, COUNT(*)::int AS cnt
|
||||
FROM "users"
|
||||
WHERE "used_invite_id" IS NOT NULL
|
||||
GROUP BY "used_invite_id"
|
||||
) sub
|
||||
WHERE ui."id" = sub.id;
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Per-invite cashback rate (admin can set when generating)
|
||||
ALTER TABLE "user_invites"
|
||||
ADD COLUMN "cashback_rate" DECIMAL(8, 4);
|
||||
@@ -17,9 +17,12 @@ model User {
|
||||
parentId BigInt? @map("parent_id")
|
||||
agentLevel Int? @map("agent_level")
|
||||
locale String @default("en-US") @db.VarChar(10)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
deletedAt DateTime? @map("deleted_at")
|
||||
inviteCode String? @unique @map("invite_code") @db.VarChar(16)
|
||||
inviteSponsorId BigInt? @map("invite_sponsor_id")
|
||||
usedInviteId BigInt? @map("used_invite_id")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
deletedAt DateTime? @map("deleted_at")
|
||||
|
||||
auth UserAuth?
|
||||
wallet Wallet?
|
||||
@@ -27,15 +30,41 @@ model User {
|
||||
adminRole AdminUserRole?
|
||||
bets Bet[]
|
||||
preferences UserPreference?
|
||||
depositOrders DepositOrder[] @relation("PlayerDepositOrders")
|
||||
|
||||
parent User? @relation("UserHierarchy", fields: [parentId], references: [id])
|
||||
children User[] @relation("UserHierarchy")
|
||||
parent User? @relation("UserHierarchy", fields: [parentId], references: [id])
|
||||
children User[] @relation("UserHierarchy")
|
||||
inviteSponsor User? @relation("InviteSponsor", fields: [inviteSponsorId], references: [id])
|
||||
invitedPlayers User[] @relation("InviteSponsor")
|
||||
usedInvite UserInvite? @relation("UsedInvite", fields: [usedInviteId], references: [id])
|
||||
invites UserInvite[] @relation("UserInvites")
|
||||
|
||||
@@index([userType])
|
||||
@@index([parentId])
|
||||
@@index([inviteSponsorId])
|
||||
@@index([usedInviteId])
|
||||
@@map("users")
|
||||
}
|
||||
|
||||
model UserInvite {
|
||||
id BigInt @id @default(autoincrement())
|
||||
code String @unique @db.VarChar(16)
|
||||
sponsorId BigInt @map("sponsor_id")
|
||||
status String @default("ACTIVE") @db.VarChar(20)
|
||||
registerCount Int @default(0) @map("register_count")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
revokedAt DateTime? @map("revoked_at")
|
||||
cashbackRate Decimal? @map("cashback_rate") @db.Decimal(8, 4)
|
||||
|
||||
sponsor User @relation("UserInvites", fields: [sponsorId], references: [id])
|
||||
registrations User[] @relation("UsedInvite")
|
||||
|
||||
@@index([sponsorId])
|
||||
@@index([status])
|
||||
@@index([createdAt])
|
||||
@@map("user_invites")
|
||||
}
|
||||
|
||||
model UserAuth {
|
||||
id BigInt @id @default(autoincrement())
|
||||
userId BigInt @unique @map("user_id")
|
||||
@@ -613,6 +642,56 @@ model UploadedFile {
|
||||
@@map("uploaded_files")
|
||||
}
|
||||
|
||||
// ============ Manual Deposit / Recharge ============
|
||||
|
||||
model PaymentMethod {
|
||||
id BigInt @id @default(autoincrement())
|
||||
methodType String @map("method_type") @db.VarChar(20)
|
||||
bankName String? @map("bank_name") @db.VarChar(128)
|
||||
accountHolder String? @map("account_holder") @db.VarChar(128)
|
||||
accountNumber String? @map("account_number") @db.VarChar(128)
|
||||
usdtAddress String? @map("usdt_address") @db.VarChar(256)
|
||||
qrCodeUrl String? @map("qr_code_url") @db.VarChar(500)
|
||||
displayName String? @map("display_name") @db.VarChar(128)
|
||||
sortOrder Int @default(0) @map("sort_order")
|
||||
isActive Boolean @default(true) @map("is_active")
|
||||
showOnPlayer Boolean @default(true) @map("show_on_player")
|
||||
createdBy BigInt? @map("created_by")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
depositOrders DepositOrder[]
|
||||
|
||||
@@index([methodType, isActive])
|
||||
@@map("payment_methods")
|
||||
}
|
||||
|
||||
model DepositOrder {
|
||||
id BigInt @id @default(autoincrement())
|
||||
orderNo String @unique @map("order_no") @db.VarChar(64)
|
||||
playerId BigInt @map("player_id")
|
||||
paymentMethodId BigInt @map("payment_method_id")
|
||||
methodType String @map("method_type") @db.VarChar(20)
|
||||
amount Decimal @db.Decimal(18, 4)
|
||||
screenshotUrl String @map("screenshot_url") @db.VarChar(500)
|
||||
status String @default("PENDING") @db.VarChar(20)
|
||||
approvedAmount Decimal? @map("approved_amount") @db.Decimal(18, 4)
|
||||
reviewerId BigInt? @map("reviewer_id")
|
||||
reviewedAt DateTime? @map("reviewed_at")
|
||||
rejectReason String? @map("reject_reason") @db.VarChar(500)
|
||||
remark String? @db.VarChar(500)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
player User @relation("PlayerDepositOrders", fields: [playerId], references: [id])
|
||||
paymentMethod PaymentMethod @relation(fields: [paymentMethodId], references: [id])
|
||||
|
||||
@@index([playerId])
|
||||
@@index([status])
|
||||
@@index([createdAt])
|
||||
@@map("deposit_orders")
|
||||
}
|
||||
|
||||
// ============ System Config & Audit ============
|
||||
|
||||
model SystemConfig {
|
||||
|
||||
Reference in New Issue
Block a user