@@ -0,0 +1,89 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE `orders` ADD COLUMN `contract_accepted_at` DATETIME(3) NULL;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `contract_parties` (
|
||||
`id` VARCHAR(191) NOT NULL,
|
||||
`business_name` VARCHAR(100) NOT NULL DEFAULT '',
|
||||
`brand_name` VARCHAR(60) NOT NULL DEFAULT '',
|
||||
`credit_code` VARCHAR(18) NOT NULL DEFAULT '',
|
||||
`operator_name` VARCHAR(30) NOT NULL DEFAULT '',
|
||||
`phone` VARCHAR(20) NOT NULL DEFAULT '',
|
||||
`address` VARCHAR(200) NOT NULL DEFAULT '',
|
||||
`premises_type` VARCHAR(10) NOT NULL DEFAULT 'LEASED',
|
||||
`lease_end_date` DATE NULL,
|
||||
`standard_lesson_price` INTEGER NOT NULL DEFAULT 0,
|
||||
`signature_image` MEDIUMBLOB NULL,
|
||||
`signature_sha256` VARCHAR(64) NULL,
|
||||
`signature_updated_at` DATETIME(3) NULL,
|
||||
`updated_at` DATETIME(3) NOT NULL,
|
||||
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `membership_contracts` (
|
||||
`id` VARCHAR(191) NOT NULL,
|
||||
`contract_no` VARCHAR(32) NOT NULL,
|
||||
`user_id` VARCHAR(191) NOT NULL,
|
||||
`membership_id` VARCHAR(191) NOT NULL,
|
||||
`order_id` VARCHAR(191) NULL,
|
||||
`source` ENUM('PURCHASE', 'LEGACY') NOT NULL,
|
||||
`status` ENUM('PENDING_SIGN', 'SIGNED', 'VOIDED') NOT NULL DEFAULT 'PENDING_SIGN',
|
||||
`require_before_booking` BOOLEAN NOT NULL DEFAULT false,
|
||||
`template_version` VARCHAR(32) NOT NULL,
|
||||
`terms` JSON NOT NULL,
|
||||
`party_name` VARCHAR(30) NULL,
|
||||
`party_phone` VARCHAR(20) NULL,
|
||||
`document` JSON NULL,
|
||||
`content_sha256` VARCHAR(64) NULL,
|
||||
`signature_image` MEDIUMBLOB NULL,
|
||||
`signature_sha256` VARCHAR(64) NULL,
|
||||
`party_b_signature_image` MEDIUMBLOB NULL,
|
||||
`party_b_name` VARCHAR(100) NULL,
|
||||
`signed_at` DATETIME(3) NULL,
|
||||
`signer_ip` VARCHAR(64) NULL,
|
||||
`signer_user_agent` VARCHAR(300) NULL,
|
||||
`signer_device` VARCHAR(200) NULL,
|
||||
`voided_at` DATETIME(3) NULL,
|
||||
`voided_by` VARCHAR(191) NULL,
|
||||
`void_reason` VARCHAR(200) NULL,
|
||||
`created_by` VARCHAR(191) NULL,
|
||||
`created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`updated_at` DATETIME(3) NOT NULL,
|
||||
|
||||
UNIQUE INDEX `membership_contracts_contract_no_key`(`contract_no`),
|
||||
UNIQUE INDEX `membership_contracts_order_id_key`(`order_id`),
|
||||
INDEX `membership_contracts_user_id_status_idx`(`user_id`, `status`),
|
||||
INDEX `membership_contracts_membership_id_status_idx`(`membership_id`, `status`),
|
||||
INDEX `membership_contracts_status_created_at_idx`(`status`, `created_at`),
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `contract_events` (
|
||||
`id` VARCHAR(191) NOT NULL,
|
||||
`contract_id` VARCHAR(191) NOT NULL,
|
||||
`action` VARCHAR(20) NOT NULL,
|
||||
`actor_id` VARCHAR(191) NULL,
|
||||
`ip` VARCHAR(64) NULL,
|
||||
`user_agent` VARCHAR(300) NULL,
|
||||
`detail` JSON NULL,
|
||||
`created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
|
||||
INDEX `contract_events_contract_id_created_at_idx`(`contract_id`, `created_at`),
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `membership_contracts` ADD CONSTRAINT `membership_contracts_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `membership_contracts` ADD CONSTRAINT `membership_contracts_membership_id_fkey` FOREIGN KEY (`membership_id`) REFERENCES `memberships`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `membership_contracts` ADD CONSTRAINT `membership_contracts_order_id_fkey` FOREIGN KEY (`order_id`) REFERENCES `orders`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `contract_events` ADD CONSTRAINT `contract_events_contract_id_fkey` FOREIGN KEY (`contract_id`) REFERENCES `membership_contracts`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
@@ -58,6 +58,17 @@ enum InviteReferralStatus {
|
||||
QUALIFIED
|
||||
}
|
||||
|
||||
enum MembershipContractStatus {
|
||||
PENDING_SIGN
|
||||
SIGNED
|
||||
VOIDED
|
||||
}
|
||||
|
||||
enum MembershipContractSource {
|
||||
PURCHASE
|
||||
LEGACY
|
||||
}
|
||||
|
||||
// ===== Models =====
|
||||
|
||||
model User {
|
||||
@@ -95,6 +106,7 @@ model User {
|
||||
trainingPlans TrainingPlan[]
|
||||
reassessmentTodos ReassessmentTodo[]
|
||||
growthShareCards GrowthShareCard[]
|
||||
membershipContracts MembershipContract[]
|
||||
|
||||
@@map("users")
|
||||
}
|
||||
@@ -163,6 +175,7 @@ model Membership {
|
||||
bookings Booking[]
|
||||
orders Order[]
|
||||
inviteRewardGrants InviteRewardGrant[]
|
||||
contracts MembershipContract[]
|
||||
|
||||
@@index([userId])
|
||||
@@index([userId, cardTypeId])
|
||||
@@ -273,6 +286,7 @@ model Order {
|
||||
status OrderStatus @default(PENDING)
|
||||
wxTransactionId String? @map("wx_transaction_id")
|
||||
paidAt DateTime? @map("paid_at")
|
||||
contractAcceptedAt DateTime? @map("contract_accepted_at")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
@@ -280,6 +294,7 @@ model Order {
|
||||
cardType CardType @relation(fields: [cardTypeId], references: [id])
|
||||
membership Membership? @relation(fields: [membershipId], references: [id])
|
||||
inviteReferrals InviteReferral[]
|
||||
contract MembershipContract?
|
||||
|
||||
@@index([userId])
|
||||
@@index([status])
|
||||
@@ -287,6 +302,84 @@ model Order {
|
||||
@@map("orders")
|
||||
}
|
||||
|
||||
// Single-row party B profile; signed contracts keep their own snapshot.
|
||||
model ContractParty {
|
||||
id String @id @default(uuid())
|
||||
businessName String @default("") @map("business_name") @db.VarChar(100)
|
||||
brandName String @default("") @map("brand_name") @db.VarChar(60)
|
||||
creditCode String @default("") @map("credit_code") @db.VarChar(18)
|
||||
operatorName String @default("") @map("operator_name") @db.VarChar(30)
|
||||
phone String @default("") @db.VarChar(20)
|
||||
address String @default("") @db.VarChar(200)
|
||||
premisesType String @default("LEASED") @map("premises_type") @db.VarChar(10)
|
||||
leaseEndDate DateTime? @map("lease_end_date") @db.Date
|
||||
standardLessonPrice Int @default(0) @map("standard_lesson_price")
|
||||
signatureImage Bytes? @map("signature_image") @db.MediumBlob
|
||||
signatureSha256 String? @map("signature_sha256") @db.VarChar(64)
|
||||
signatureUpdatedAt DateTime? @map("signature_updated_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
@@map("contract_parties")
|
||||
}
|
||||
|
||||
// terms is frozen at issuance; document, hashes and signatures are frozen at signing.
|
||||
model MembershipContract {
|
||||
id String @id @default(uuid())
|
||||
contractNo String @unique @map("contract_no") @db.VarChar(32)
|
||||
userId String @map("user_id")
|
||||
membershipId String @map("membership_id")
|
||||
orderId String? @unique @map("order_id")
|
||||
source MembershipContractSource
|
||||
status MembershipContractStatus @default(PENDING_SIGN)
|
||||
requireBeforeBooking Boolean @default(false) @map("require_before_booking")
|
||||
templateVersion String @map("template_version") @db.VarChar(32)
|
||||
terms Json
|
||||
partyName String? @map("party_name") @db.VarChar(30)
|
||||
partyPhone String? @map("party_phone") @db.VarChar(20)
|
||||
document Json?
|
||||
contentSha256 String? @map("content_sha256") @db.VarChar(64)
|
||||
signatureImage Bytes? @map("signature_image") @db.MediumBlob
|
||||
signatureSha256 String? @map("signature_sha256") @db.VarChar(64)
|
||||
partyBSignatureImage Bytes? @map("party_b_signature_image") @db.MediumBlob
|
||||
partyBName String? @map("party_b_name") @db.VarChar(100)
|
||||
signedAt DateTime? @map("signed_at")
|
||||
signerIp String? @map("signer_ip") @db.VarChar(64)
|
||||
signerUserAgent String? @map("signer_user_agent") @db.VarChar(300)
|
||||
signerDevice String? @map("signer_device") @db.VarChar(200)
|
||||
voidedAt DateTime? @map("voided_at")
|
||||
voidedBy String? @map("voided_by")
|
||||
voidReason String? @map("void_reason") @db.VarChar(200)
|
||||
createdBy String? @map("created_by")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
membership Membership @relation(fields: [membershipId], references: [id])
|
||||
order Order? @relation(fields: [orderId], references: [id])
|
||||
events ContractEvent[]
|
||||
|
||||
@@index([userId, status])
|
||||
@@index([membershipId, status])
|
||||
@@index([status, createdAt])
|
||||
@@map("membership_contracts")
|
||||
}
|
||||
|
||||
model ContractEvent {
|
||||
id String @id @default(uuid())
|
||||
contractId String @map("contract_id")
|
||||
action String @db.VarChar(20)
|
||||
actorId String? @map("actor_id")
|
||||
ip String? @db.VarChar(64)
|
||||
userAgent String? @map("user_agent") @db.VarChar(300)
|
||||
detail Json?
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
contract MembershipContract @relation(fields: [contractId], references: [id])
|
||||
|
||||
@@index([contractId, createdAt])
|
||||
@@map("contract_events")
|
||||
}
|
||||
|
||||
model InviteReferral {
|
||||
id String @id @default(uuid())
|
||||
inviterId String @map("inviter_id")
|
||||
|
||||
Reference in New Issue
Block a user