Files
daiyongkang 76f266645d Initial commit: 风影 OA 全栈源码(API/Web/Flutter/IM)
含 Phase 1.1 IM seq 排序、断线重连、多端已读同步与微信式语音转文字 UI。
排除 node_modules、构建产物、安装包与 .env 密钥。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-02 10:04:03 +00:00

1422 lines
44 KiB
Plaintext

generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(uuid())
tenantId String @default("1")
username String
passwordHash String
displayName String
status String @default("ACTIVE")
employeeId String?
mustChangePassword Boolean @default(true)
profileCompletedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
employee Employee? @relation(fields: [employeeId], references: [id])
roles UserRole[]
refreshTokens RefreshToken[]
todos TodoTask[] @relation("TodoAssignee")
approvals ApprovalTask[] @relation("ApprovalAssignee")
bidCreated BidCase[] @relation("BidCreator")
bidReviews BidReviewer[]
bidActions BidActionLog[]
bidAssignments BidAssignee[]
bidDocVersions BidDocVersion[]
contractsCreated Contract[] @relation("ContractCreator")
contractReviews ContractReviewer[]
contractActions ContractActionLog[]
expensesCreated ExpenseClaim[] @relation("ExpenseApplicant")
expenseDecided ExpenseClaim[] @relation("ExpenseDecider")
expensesPaid ExpenseClaim[] @relation("ExpensePayer")
loansCreated LoanRecord[] @relation("LoanApplicant")
loanDecided LoanRecord[] @relation("LoanDecider")
invoicesCreated InvoiceRecord[] @relation("InvoiceCreator")
timesheets Timesheet[]
assignedTasks ProjectTask[] @relation("TaskAssignee")
projectAcceptances ProjectAcceptance[]
sealApplied SealRequest[] @relation("SealApplicant")
sealDecided SealRequest[] @relation("SealDecider")
credentialBorrows CredentialBorrow[]
employmentEvents EmploymentEvent[] @relation("EventActor")
attendanceImports AttendanceMonth[] @relation("AttendanceImporter")
leaveDecided LeaveRequest[] @relation("LeaveDecider")
payrollRuns PayrollRun[] @relation("PayrollCreator")
workReports WorkReport[]
workReportsTo WorkReport[] @relation("ReportTo")
workReportsCc WorkReport[] @relation("ReportCc")
operationLogs OperationLog[]
purchases PurchaseRequest[] @relation("PurchaseApplicant")
purchaseDecided PurchaseRequest[] @relation("PurchaseDecider")
paymentsCreated PaymentRecord[] @relation("PaymentCreator")
paymentsReceived PaymentRecord[] @relation("PaymentPayee")
projectChanges ProjectChange[] @relation("ChangeActor")
assetsHeld AssetOccupancy[] @relation("AssetHolder")
laborContractsCreated LaborContract[] @relation("LaborContractCreator")
workAssigned WorkAssignment[] @relation("WorkAssigner")
workReceived WorkAssignment[] @relation("WorkAssignee")
assetsRegistered RegisteredAsset[] @relation("AssetRegistrar")
officeApplies OfficeApply[] @relation("OfficeApplicant")
officeApplyDecided OfficeApply[] @relation("OfficeDecider")
noticesCreated Notice[] @relation("NoticeAuthor")
pushDevices PushDevice[]
@@unique([tenantId, username])
}
model PushDevice {
id String @id @default(uuid())
userId String
vendor String
regId String
brand String?
model String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([vendor, regId])
@@index([userId, vendor])
}
model RefreshToken {
id String @id @default(uuid())
userId String
tokenHash String
clientKind String @default("web")
expiresAt DateTime
revokedAt DateTime?
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId, clientKind])
}
model AuthQrTicket {
id String @id @default(uuid())
ticket String @unique
status String @default("pending")
userId String?
confirmCodeHash String?
confirmCodeIssuedAt DateTime?
confirmCodeAttempts Int @default(0)
expiresAt DateTime
consumedAt DateTime?
createdAt DateTime @default(now())
@@index([status, expiresAt])
}
model Department {
id String @id @default(uuid())
tenantId String @default("1")
parentId String?
name String
code String
deptType String
categoryCode String?
sortNo Int @default(0)
parent Department? @relation("DeptTree", fields: [parentId], references: [id])
children Department[] @relation("DeptTree")
employees Employee[]
projectTasks ProjectTask[]
eventsFrom EmploymentEvent[] @relation("EventFromDept")
eventsTo EmploymentEvent[] @relation("EventToDept")
@@unique([tenantId, code])
}
model Employee {
id String @id @default(uuid())
tenantId String @default("1")
name String
employeeNo String
departmentId String
title String?
restSchedule String @default("SINGLE")
employmentStatus String @default("REGULAR")
hiredAt DateTime?
internStartAt DateTime?
probationEndAt DateTime?
leftAt DateTime?
managerId String?
managerIds String?
gender String?
email String?
mobile String?
idNo String?
idType String?
education String?
birthday DateTime?
ethnicity String?
maritalStatus String?
hukouAddress String?
liveAddress String?
emergencyName String?
emergencyPhone String?
idIssuer String?
idValidFrom DateTime?
idValidTo DateTime?
personCategory String?
ccTo String?
shortMobile String?
workMobile String?
school String?
major String?
graduatedAt DateTime?
professionalTitle String?
certificates String?
companyName String?
jobRole String?
jobLevel String?
attendGroup String?
probationMonths Int?
contractStartAt DateTime?
contractEndAt DateTime?
recruitSource String?
landline String?
landlineExt String?
groupShortNo String?
socialNo String?
housingFundNo String?
familyNote String?
jobType String?
ccDaily String?
ccWeekly String?
ccMonthly String?
bankName String?
bankAccount String?
remark String?
baseSalary Decimal @default(0) @db.Decimal(18, 2)
postSalary Decimal @default(0) @db.Decimal(18, 2)
attendanceBonus Decimal @default(0) @db.Decimal(18, 2)
phoneSubsidy Decimal @default(0) @db.Decimal(18, 2)
transportSubsidy Decimal @default(0) @db.Decimal(18, 2)
mealSubsidy Decimal @default(0) @db.Decimal(18, 2)
probationSalary Decimal @default(0) @db.Decimal(18, 2)
perfBase Decimal @default(0) @db.Decimal(18, 2)
socialBase Decimal @default(0) @db.Decimal(18, 2)
compLeaveHours Decimal @default(0) @db.Decimal(10, 2)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
department Department @relation(fields: [departmentId], references: [id])
manager Employee? @relation("EmpManager", fields: [managerId], references: [id])
reports Employee[] @relation("EmpManager")
users User[]
events EmploymentEvent[]
attendance AttendanceMonth[]
punches AttendancePunch[]
leaves LeaveRequest[]
payrollItems PayrollItem[]
laborContracts LaborContract[]
@@unique([tenantId, employeeNo])
@@index([departmentId])
@@index([managerId])
}
model LaborContract {
id String @id @default(uuid())
tenantId String @default("1")
employeeId String
contractNo String?
startAt DateTime?
endAt DateTime?
remark String?
createdById String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
createdBy User? @relation("LaborContractCreator", fields: [createdById], references: [id])
@@index([employeeId])
@@index([tenantId, createdAt])
}
model EmploymentEvent {
id String @id @default(uuid())
tenantId String @default("1")
employeeId String
kind String
fromStatus String?
toStatus String?
fromDeptId String?
toDeptId String?
effectiveAt DateTime
comment String?
actorId String?
createdAt DateTime @default(now())
employee Employee @relation(fields: [employeeId], references: [id])
fromDept Department? @relation("EventFromDept", fields: [fromDeptId], references: [id])
toDept Department? @relation("EventToDept", fields: [toDeptId], references: [id])
actor User? @relation("EventActor", fields: [actorId], references: [id])
@@index([employeeId])
@@index([effectiveAt])
}
model AttendanceMonth {
id String @id @default(uuid())
tenantId String @default("1")
employeeId String
yearMonth String
workDays Decimal @default(0) @db.Decimal(6, 2)
leaveDays Decimal @default(0) @db.Decimal(6, 2)
sickDays Decimal @default(0) @db.Decimal(6, 2)
overtimeHours Decimal @default(0) @db.Decimal(6, 2)
compLeaveDays Decimal @default(0) @db.Decimal(6, 2)
remark String?
importedAt DateTime @default(now())
importedById String?
employee Employee @relation(fields: [employeeId], references: [id])
importedBy User? @relation("AttendanceImporter", fields: [importedById], references: [id])
@@unique([employeeId, yearMonth])
@@index([yearMonth])
}
model AttendancePunch {
id String @id @default(uuid())
tenantId String @default("1")
employeeId String
kind String
punchedAt DateTime @default(now())
remark String?
lat Decimal? @db.Decimal(10, 7)
lng Decimal? @db.Decimal(10, 7)
address String?
accuracy Float?
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
@@index([employeeId, punchedAt])
}
model LeaveRequest {
id String @id @default(uuid())
tenantId String @default("1")
employeeId String
kind String
startAt DateTime
endAt DateTime
days Decimal @db.Decimal(6, 2)
reason String
toDeptId String?
targetTitle String?
status String @default("PENDING")
comment String?
decidedById String?
decidedAt DateTime?
createdAt DateTime @default(now())
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
decidedBy User? @relation("LeaveDecider", fields: [decidedById], references: [id])
@@index([employeeId])
@@index([status])
}
model HrSetting {
id String @id @default("default")
tenantId String @default("1") @unique
idMask Boolean @default(true)
autoAccount Boolean @default(false)
probationMonths Int @default(3)
defaultRest String @default("SINGLE")
workStart String @default("09:00")
workEnd String @default("18:00")
updatedAt DateTime @updatedAt
}
model AttendanceRule {
id String @id @default(uuid())
tenantId String @default("1")
name String
restSchedule String
workStart String
workEnd String
lateGraceMinutes Int @default(0)
punchPlace String?
punchAddress String?
punchLat Decimal? @db.Decimal(10, 7)
punchLng Decimal? @db.Decimal(10, 7)
punchRadiusMeters Int @default(300)
enabled Boolean @default(true)
sortNo Int @default(0)
remark String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([tenantId, name])
}
model PayrollRun {
id String @id @default(uuid())
tenantId String @default("1")
yearMonth String
status String @default("DRAFT")
createdById String?
createdAt DateTime @default(now())
confirmedAt DateTime?
createdBy User? @relation("PayrollCreator", fields: [createdById], references: [id])
items PayrollItem[]
@@unique([tenantId, yearMonth])
}
model PayrollItem {
id String @id @default(uuid())
runId String
employeeId String
prorationFactor Decimal @default(1) @db.Decimal(8, 6)
scheduledDays Decimal @default(0) @db.Decimal(6, 2)
dailyRate Decimal @default(0) @db.Decimal(18, 2)
basePay Decimal @default(0) @db.Decimal(18, 2)
performancePay Decimal @default(0) @db.Decimal(18, 2)
performanceRatio Decimal @default(100) @db.Decimal(5, 2)
leaveDeduct Decimal @default(0) @db.Decimal(18, 2)
sickDeduct Decimal @default(0) @db.Decimal(18, 2)
socialPersonal Decimal @default(0) @db.Decimal(18, 2)
housingFund Decimal @default(0) @db.Decimal(18, 2)
tax Decimal @default(0) @db.Decimal(18, 2)
grossPay Decimal @default(0) @db.Decimal(18, 2)
netPay Decimal @default(0) @db.Decimal(18, 2)
overtimeHours Decimal @default(0) @db.Decimal(6, 2)
compLeaveUsedDays Decimal @default(0) @db.Decimal(6, 2)
compLeaveHours Decimal @default(0) @db.Decimal(10, 2)
run PayrollRun @relation(fields: [runId], references: [id], onDelete: Cascade)
employee Employee @relation(fields: [employeeId], references: [id])
@@unique([runId, employeeId])
}
model Role {
id String @id @default(uuid())
tenantId String @default("1")
name String
code String
description String @default("")
dataScope String @default("ALL")
aclCustom Boolean @default(false)
permissions RolePermission[]
users UserRole[]
@@unique([tenantId, code])
}
model Permission {
id String @id @default(uuid())
parentId String?
name String
code String @unique
permType String
path String?
icon String?
sortNo Int @default(0)
visible Boolean @default(true)
parent Permission? @relation("PermTree", fields: [parentId], references: [id])
children Permission[] @relation("PermTree")
roles RolePermission[]
}
model RolePermission {
roleId String
permissionId String
role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
permission Permission @relation(fields: [permissionId], references: [id], onDelete: Cascade)
@@id([roleId, permissionId])
}
model UserRole {
userId String
roleId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
@@id([userId, roleId])
}
model DictItem {
id String @id @default(uuid())
tenantId String @default("1")
dictType String
label String
value String
sortNo Int @default(0)
@@unique([tenantId, dictType, value])
}
model NumberRule {
id String @id @default(uuid())
tenantId String @default("1")
code String
name String
prefix String
padLength Int @default(5)
@@unique([tenantId, code])
}
model SealItem {
id String @id @default(uuid())
tenantId String @default("1")
name String
sealType String @default("公章")
keeper String @default("行政")
legalEntityId String?
status String @default("IN_STOCK")
createdAt DateTime @default(now())
legalEntity LegalEntity? @relation(fields: [legalEntityId], references: [id])
}
model LegalEntity {
id String @id @default(uuid())
tenantId String @default("1")
name String
shortName String
creditNo String?
isDefault Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
bidCases BidCase[]
contracts Contract[]
sealItems SealItem[]
assets RegisteredAsset[]
@@unique([tenantId, name])
}
model BusinessParty {
id String @id @default(uuid())
tenantId String @default("1")
name String
creditNo String?
level String @default("B")
riskFlag Boolean @default(false)
remark String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
contacts PartyContact[]
bidCases BidCase[]
@@unique([tenantId, name])
}
model PartyContact {
id String @id @default(uuid())
tenantId String @default("1")
partyId String
name String
phone String?
title String?
party BusinessParty @relation(fields: [partyId], references: [id], onDelete: Cascade)
}
model Qualification {
id String @id @default(uuid())
tenantId String @default("1")
name String
code String
expiresAt DateTime?
keeper String?
createdAt DateTime @default(now())
borrows CredentialBorrow[]
}
model SealRequest {
id String @id @default(uuid())
tenantId String @default("1")
sealType String
reason String
bidCaseId String?
contractId String?
applicantId String?
takeOut Boolean @default(false)
returnAt DateTime?
returnedAt DateTime?
decidedAt DateTime?
decidedById String?
rejectComment String?
status String @default("PENDING")
createdAt DateTime @default(now())
contract Contract? @relation(fields: [contractId], references: [id])
bidCase BidCase? @relation(fields: [bidCaseId], references: [id])
applicant User? @relation("SealApplicant", fields: [applicantId], references: [id])
decidedBy User? @relation("SealDecider", fields: [decidedById], references: [id])
}
model CredentialBorrow {
id String @id @default(uuid())
tenantId String @default("1")
itemName String
borrower String
borrowerId String?
qualificationId String?
bidCaseId String?
dueAt DateTime?
returnedAt DateTime?
remark String?
status String @default("BORROWED")
createdAt DateTime @default(now())
borrowerUser User? @relation(fields: [borrowerId], references: [id])
qualification Qualification? @relation(fields: [qualificationId], references: [id])
bidCase BidCase? @relation(fields: [bidCaseId], references: [id])
}
model BidCase {
id String @id @default(uuid())
tenantId String @default("1")
bidNo String
name String
externalNo String?
projectType String
sourceType String @default("FLOW")
source String?
qualificationNeed String?
legalEntityId String?
tenderMethod String?
applyStartAt DateTime?
applyEndAt DateTime?
applyMethod String?
openAt DateTime?
openMethod String?
openPlace String?
securityLevel String @default("INTERNAL")
projectNature String?
partyId String?
contactName String?
contactPhone String?
bondMethod String?
bondStatus String?
bondPaidAt DateTime?
delivererName String?
priceCap String?
quoteAmount String?
summary String?
remark String?
status String @default("TECH_INITIAL")
nasPath String?
fetchedAt DateTime?
reviewCycle Int @default(1)
printCost Decimal? @db.Decimal(18, 2)
printNote String?
printedAt DateTime?
costDisposition String?
resultNote String?
winnerName String?
winnerAmount Decimal? @db.Decimal(18, 2)
winnerNote String?
openedResultAt DateTime?
needSurvey Boolean @default(false)
createdById String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
legalEntity LegalEntity? @relation(fields: [legalEntityId], references: [id])
party BusinessParty? @relation(fields: [partyId], references: [id])
createdBy User? @relation("BidCreator", fields: [createdById], references: [id])
reviewers BidReviewer[]
actionLogs BidActionLog[]
assignees BidAssignee[]
versions BidDocVersion[]
expenses ExpenseClaim[]
loans LoanRecord[]
contracts Contract[]
bonds BondRecord[]
sealRequests SealRequest[]
credentialBorrows CredentialBorrow[]
@@index([tenantId, bidNo])
}
model BidReviewer {
id String @id @default(uuid())
bidCaseId String
userId String
stage String
cycle Int @default(1)
result String @default("PENDING")
comment String?
decidedAt DateTime?
bidCase BidCase @relation(fields: [bidCaseId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id])
@@unique([bidCaseId, userId, stage, cycle])
@@index([bidCaseId])
}
model BidAssignee {
id String @id @default(uuid())
bidCaseId String
userId String
role String
createdAt DateTime @default(now())
bidCase BidCase @relation(fields: [bidCaseId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id])
@@unique([bidCaseId, userId, role])
@@index([bidCaseId])
}
model BidDocVersion {
id String @id @default(uuid())
bidCaseId String
versionNo Int
nasPath String
note String?
submittedById String
createdAt DateTime @default(now())
bidCase BidCase @relation(fields: [bidCaseId], references: [id], onDelete: Cascade)
submittedBy User @relation(fields: [submittedById], references: [id])
@@unique([bidCaseId, versionNo])
@@index([bidCaseId])
}
model BidActionLog {
id String @id @default(uuid())
bidCaseId String
actorId String
action String
fromStatus String?
toStatus String?
comment String?
createdAt DateTime @default(now())
bidCase BidCase @relation(fields: [bidCaseId], references: [id], onDelete: Cascade)
actor User @relation(fields: [actorId], references: [id])
@@index([bidCaseId])
}
model TodoTask {
id String @id @default(uuid())
tenantId String @default("1")
title String
bizType String
bizId String?
assigneeId String
dueAt DateTime?
status String @default("OPEN")
createdAt DateTime @default(now())
assignee User @relation("TodoAssignee", fields: [assigneeId], references: [id])
}
model ApprovalTask {
id String @id @default(uuid())
tenantId String @default("1")
title String
bizType String
bizId String?
assigneeId String
status String @default("PENDING")
createdAt DateTime @default(now())
assignee User @relation("ApprovalAssignee", fields: [assigneeId], references: [id])
}
model WorkReport {
id String @id @default(uuid())
tenantId String @default("1")
authorId String
toUserId String?
ccUserId String?
period String
periodType String @default("DAY")
periodStart DateTime?
hours Decimal @default(0) @db.Decimal(8, 2)
content String @default("")
status String @default("DRAFT")
submittedAt DateTime?
createdAt DateTime @default(now())
author User @relation(fields: [authorId], references: [id])
toUser User? @relation("ReportTo", fields: [toUserId], references: [id])
ccUser User? @relation("ReportCc", fields: [ccUserId], references: [id])
@@index([authorId])
@@index([toUserId])
@@index([ccUserId])
}
model Contract {
id String @id @default(uuid())
tenantId String @default("1")
contractNo String?
name String
kind String @default("OTHER")
bidCaseId String?
legalEntityId String?
amount Decimal @default(0) @db.Decimal(18, 2)
status String @default("DRAFT")
partyA String?
partyB String?
nasPath String?
remark String?
signedAt DateTime?
submittedAt DateTime?
approvedAt DateTime?
createdById String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
bidCase BidCase? @relation(fields: [bidCaseId], references: [id])
legalEntity LegalEntity? @relation(fields: [legalEntityId], references: [id])
createdBy User? @relation("ContractCreator", fields: [createdById], references: [id])
projects Project[]
milestones PaymentMilestone[]
reviewers ContractReviewer[]
actionLogs ContractActionLog[]
sealRequests SealRequest[]
invoices InvoiceRecord[]
bonds BondRecord[]
@@unique([tenantId, contractNo])
}
model ContractReviewer {
id String @id @default(uuid())
contractId String
userId String
result String @default("PENDING")
comment String?
decidedAt DateTime?
contract Contract @relation(fields: [contractId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id])
@@unique([contractId, userId])
@@index([contractId])
}
model ContractActionLog {
id String @id @default(uuid())
contractId String
actorId String
action String
fromStatus String?
toStatus String?
comment String?
createdAt DateTime @default(now())
contract Contract @relation(fields: [contractId], references: [id], onDelete: Cascade)
actor User @relation(fields: [actorId], references: [id])
@@index([contractId])
}
model PaymentMilestone {
id String @id @default(uuid())
contractId String
name String
ratio Decimal @db.Decimal(5, 2)
dueAt DateTime?
trigger String?
contract Contract @relation(fields: [contractId], references: [id], onDelete: Cascade)
invoice InvoiceRecord?
}
model Project {
id String @id @default(uuid())
tenantId String @default("1")
projectNo String
name String
partyName String?
contractId String?
projectType String @default("")
amount Decimal @default(0) @db.Decimal(18, 2)
status String @default("ACTIVE")
presalesCost Decimal @default(0) @db.Decimal(18, 2)
hourRate Decimal @default(200) @db.Decimal(18, 2)
internalAcceptedAt DateTime?
customerAcceptedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
contract Contract? @relation(fields: [contractId], references: [id])
tasks ProjectTask[]
timesheets Timesheet[]
expenses ExpenseClaim[]
loans LoanRecord[]
bonds BondRecord[]
budgets ProjectBudget[]
acceptances ProjectAcceptance[]
acceptMaterials ProjectAcceptMaterial[]
purchases PurchaseRequest[]
changes ProjectChange[]
assets AssetOccupancy[]
officeApplies OfficeApply[]
@@unique([tenantId, projectNo])
}
model ProjectBudget {
id String @id @default(uuid())
projectId String
category String
amount Decimal @db.Decimal(18, 2)
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
@@unique([projectId, category])
}
model ProjectAcceptance {
id String @id @default(uuid())
projectId String
kind String
result String
comment String?
actorId String
decidedAt DateTime @default(now())
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
actor User @relation(fields: [actorId], references: [id])
@@index([projectId])
}
model ProjectAcceptMaterial {
id String @id @default(uuid())
projectId String
name String
remark String?
required Boolean @default(true)
sortNo Int @default(0)
createdAt DateTime @default(now())
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
@@index([projectId])
}
model ProjectTask {
id String @id @default(uuid())
projectId String
name String
department String?
departmentId String?
assigneeId String?
sortNo Int @default(0)
dueAt DateTime?
status String @default("OPEN")
createdAt DateTime @default(now())
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
dept Department? @relation(fields: [departmentId], references: [id])
assignee User? @relation("TaskAssignee", fields: [assigneeId], references: [id])
timesheets Timesheet[]
@@index([projectId])
}
model Timesheet {
id String @id @default(uuid())
tenantId String @default("1")
projectId String
taskId String?
userId String
workDate DateTime
hours Decimal @db.Decimal(6, 2)
costAmount Decimal @default(0) @db.Decimal(18, 2)
note String?
createdAt DateTime @default(now())
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
task ProjectTask? @relation(fields: [taskId], references: [id], onDelete: SetNull)
user User @relation(fields: [userId], references: [id])
@@index([projectId])
@@index([userId])
}
model ExpenseClaim {
id String @id @default(uuid())
tenantId String @default("1")
claimNo String
applicantId String
costType String
budgetCategory String?
bidCaseId String?
projectId String?
loanId String?
expenseCategory String?
purpose String?
amount Decimal @default(0) @db.Decimal(18, 2)
status String @default("DRAFT")
remark String?
submittedAt DateTime?
decidedAt DateTime?
decidedById String?
rejectComment String?
prepaid Boolean @default(false)
payerId String?
createdAt DateTime @default(now())
bidCase BidCase? @relation(fields: [bidCaseId], references: [id])
project Project? @relation(fields: [projectId], references: [id])
loan LoanRecord? @relation(fields: [loanId], references: [id])
applicant User @relation("ExpenseApplicant", fields: [applicantId], references: [id])
decidedBy User? @relation("ExpenseDecider", fields: [decidedById], references: [id])
payer User? @relation("ExpensePayer", fields: [payerId], references: [id])
lines ExpenseLine[]
@@unique([tenantId, claimNo])
@@index([applicantId])
@@index([loanId])
@@index([payerId])
}
model ExpenseLine {
id String @id @default(uuid())
tenantId String @default("1")
claimId String
kind String
amount Decimal @db.Decimal(18, 2)
invoiceNo String?
occurAt DateTime?
remark String?
sortNo Int @default(0)
createdAt DateTime @default(now())
claim ExpenseClaim @relation(fields: [claimId], references: [id], onDelete: Cascade)
@@index([claimId])
}
model LoanRecord {
id String @id @default(uuid())
tenantId String @default("1")
loanNo String
applicantId String
costType String
bidCaseId String?
projectId String?
amount Decimal @db.Decimal(18, 2)
offsetAmount Decimal @default(0) @db.Decimal(18, 2)
purpose String
status String @default("DRAFT")
remark String?
submittedAt DateTime?
decidedAt DateTime?
decidedById String?
createdAt DateTime @default(now())
bidCase BidCase? @relation(fields: [bidCaseId], references: [id])
project Project? @relation(fields: [projectId], references: [id])
applicant User @relation("LoanApplicant", fields: [applicantId], references: [id])
decidedBy User? @relation("LoanDecider", fields: [decidedById], references: [id])
expenses ExpenseClaim[]
@@unique([tenantId, loanNo])
@@index([applicantId])
}
model BondRecord {
id String @id @default(uuid())
tenantId String @default("1")
bondNo String
bidCaseId String?
projectId String?
contractId String?
bondType String
amount Decimal @db.Decimal(18, 2)
paidAt DateTime?
dueBackAt DateTime?
returnedAt DateTime?
status String @default("UNPAID")
remark String?
createdAt DateTime @default(now())
bidCase BidCase? @relation(fields: [bidCaseId], references: [id], onDelete: SetNull)
project Project? @relation(fields: [projectId], references: [id])
contract Contract? @relation(fields: [contractId], references: [id])
@@unique([tenantId, bondNo])
}
model InvoiceRecord {
id String @id @default(uuid())
tenantId String @default("1")
invoiceNo String?
contractId String
milestoneId String @unique
amount Decimal @db.Decimal(18, 2)
status String @default("TO_ISSUE")
dueAt DateTime?
issuedAt DateTime?
receivedAt DateTime?
remark String?
createdById String?
createdAt DateTime @default(now())
contract Contract @relation(fields: [contractId], references: [id])
milestone PaymentMilestone @relation(fields: [milestoneId], references: [id])
createdBy User? @relation("InvoiceCreator", fields: [createdById], references: [id])
}
model FileAsset {
id String @id @default(uuid())
tenantId String @default("1")
bizType String
bizId String
fileName String
mimeType String
size Int
storageKey String
uploadedById String
createdAt DateTime @default(now())
@@index([bizType, bizId])
@@index([uploadedById])
}
model PurchaseRequest {
id String @id @default(uuid())
tenantId String @default("1")
title String
projectId String?
amount Decimal @default(0) @db.Decimal(18, 2)
status String @default("DRAFT")
remark String?
applicantId String?
decidedById String?
decidedAt DateTime?
createdAt DateTime @default(now())
project Project? @relation(fields: [projectId], references: [id])
applicant User? @relation("PurchaseApplicant", fields: [applicantId], references: [id])
decidedBy User? @relation("PurchaseDecider", fields: [decidedById], references: [id])
officeApplies OfficeApply[]
}
model AssetOccupancy {
id String @id @default(uuid())
tenantId String @default("1")
assetName String
holder String
holderId String?
projectId String?
registeredAssetId String?
status String @default("IN_USE")
dueAt DateTime?
returnedAt DateTime?
createdAt DateTime @default(now())
project Project? @relation(fields: [projectId], references: [id])
holderUser User? @relation("AssetHolder", fields: [holderId], references: [id])
registeredAsset RegisteredAsset? @relation(fields: [registeredAssetId], references: [id])
officeApplies OfficeApply[] @relation("ApplyOccupancy")
@@index([registeredAssetId])
}
model ProjectChange {
id String @id @default(uuid())
tenantId String @default("1")
projectId String
summary String
amountDelta Decimal @default(0) @db.Decimal(18, 2)
status String @default("PENDING")
comment String?
actorId String?
createdAt DateTime @default(now())
decidedAt DateTime?
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
actor User? @relation("ChangeActor", fields: [actorId], references: [id])
@@index([projectId])
}
model PaymentRecord {
id String @id @default(uuid())
tenantId String @default("1")
payNo String
kind String
refId String?
amount Decimal @db.Decimal(18, 2)
status String @default("TODO")
remark String?
paidAt DateTime?
createdById String?
payeeUserId String?
payeeName String?
payeeKind String?
bankName String?
bankAccount String?
payChannel String @default("MANUAL")
bankStatus String @default("NONE")
bankTxnNo String?
bankPayload String?
createdAt DateTime @default(now())
createdBy User? @relation("PaymentCreator", fields: [createdById], references: [id])
payeeUser User? @relation("PaymentPayee", fields: [payeeUserId], references: [id])
@@unique([tenantId, payNo])
@@index([refId])
@@index([payeeUserId])
}
model OperationLog {
id String @id @default(uuid())
tenantId String @default("1")
userId String?
username String?
action String
path String
detail String?
success Boolean @default(true)
requestId String?
ip String?
createdAt DateTime @default(now())
user User? @relation(fields: [userId], references: [id])
@@index([createdAt])
@@index([action])
}
model ImConversation {
id String @id @default(uuid())
tenantId String @default("1")
type String @default("direct")
name String @default("")
ownerId String?
userAId String?
userBId String?
lastText String @default("")
lastAt DateTime @default(now())
nextSeq Int @default(1)
createdAt DateTime @default(now())
@@unique([userAId, userBId])
@@index([lastAt])
@@index([type, lastAt])
}
model ImConversationMember {
id String @id @default(uuid())
conversationId String
userId String
role String @default("member")
joinedAt DateTime @default(now())
leftAt DateTime?
@@unique([conversationId, userId])
@@index([userId])
}
model ImMessage {
id String @id @default(uuid())
tenantId String @default("1")
conversationId String
seq Int @default(0)
fromUserId String
toUserId String @default("")
kind String @default("chat")
contentType String @default("text")
body String
meta String @default("")
fingerprint String
createdAt DateTime @default(now())
@@unique([fingerprint])
@@index([conversationId, createdAt])
@@index([conversationId, seq])
@@index([toUserId, createdAt])
}
model ImReadCursor {
id String @id @default(uuid())
userId String
conversationId String
lastReadAt DateTime @default(now())
lastReadSeq Int @default(0)
@@unique([userId, conversationId])
}
model ImMessageReceipt {
id String @id @default(uuid())
messageId String
userId String
readAt DateTime @default(now())
@@unique([messageId, userId])
@@index([messageId])
}
model ImCall {
id String @id @default(uuid())
tenantId String @default("1")
conversationId String?
kind String
status String @default("ringing")
initiatorId String
title String @default("")
createdAt DateTime @default(now())
endedAt DateTime?
@@index([status, createdAt])
}
model ImCallMember {
id String @id @default(uuid())
callId String
userId String
status String @default("invited")
joinedAt DateTime?
leftAt DateTime?
@@unique([callId, userId])
}
model ImCallSignal {
id String @id @default(uuid())
callId String
fromUserId String
toUserId String
kind String
payload String
createdAt DateTime @default(now())
@@index([callId, createdAt])
@@index([toUserId, createdAt])
}
model SmsSendLog {
id String @id @default(uuid())
tenantId String @default("1")
phones String
signName String
templateCode String
templateParam String?
eventKey String?
bizId String?
code String
message String?
createdById String?
createdAt DateTime @default(now())
@@index([createdAt])
@@index([eventKey])
}
model SmsEventBind {
id String @id @default(uuid())
tenantId String @default("1")
eventKey String @unique
enabled Boolean @default(true)
templateCode String @default("")
signName String?
updatedAt DateTime @updatedAt
}
model SmsOtp {
id String @id @default(uuid())
phone String
codeHash String
purpose String
expiresAt DateTime
usedAt DateTime?
createdAt DateTime @default(now())
@@index([phone, purpose])
}
model WorkAssignment {
id String @id @default(uuid())
tenantId String @default("1")
title String
content String @default("")
assigneeId String
dueAt DateTime?
status String @default("OPEN")
createdById String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
assignee User @relation("WorkAssignee", fields: [assigneeId], references: [id])
createdBy User @relation("WorkAssigner", fields: [createdById], references: [id])
@@index([assigneeId])
@@index([createdById])
}
model RegisteredAsset {
id String @id @default(uuid())
tenantId String @default("1")
kind String
name String
code String?
spec String?
keeper String?
location String?
amount Decimal? @db.Decimal(18, 2)
remark String?
status String @default("ACTIVE")
usage String @default("GENERAL")
legalEntityId String?
createdById String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
legalEntity LegalEntity? @relation(fields: [legalEntityId], references: [id])
createdBy User? @relation("AssetRegistrar", fields: [createdById], references: [id])
occupancies AssetOccupancy[]
officeApplies OfficeApply[]
inboundFrom OfficeApply[] @relation("ApplyInbound")
@@index([kind])
@@index([legalEntityId])
}
model OfficeApply {
id String @id @default(uuid())
tenantId String @default("1")
kind String
title String
reason String?
status String @default("PENDING")
assetKind String?
registeredAssetId String?
amount Decimal? @db.Decimal(18, 2)
destination String?
startAt DateTime?
endAt DateTime?
projectId String?
purchaseId String?
occupancyId String?
inboundAssetId String?
applicantId String
decidedById String?
decidedAt DateTime?
remark String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
applicant User @relation("OfficeApplicant", fields: [applicantId], references: [id])
decidedBy User? @relation("OfficeDecider", fields: [decidedById], references: [id])
registeredAsset RegisteredAsset? @relation(fields: [registeredAssetId], references: [id])
project Project? @relation(fields: [projectId], references: [id])
purchase PurchaseRequest? @relation(fields: [purchaseId], references: [id])
occupancy AssetOccupancy? @relation("ApplyOccupancy", fields: [occupancyId], references: [id])
inboundAsset RegisteredAsset? @relation("ApplyInbound", fields: [inboundAssetId], references: [id])
@@index([applicantId])
@@index([status])
@@index([kind])
}
model Notice {
id String @id @default(uuid())
tenantId String @default("1")
title String
content String
status String @default("PUBLISHED")
kind String @default("ANNOUNCEMENT")
audience String @default("ALL")
departmentIds String[] @default([])
userIds String[] @default([])
createdById String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
createdBy User @relation("NoticeAuthor", fields: [createdById], references: [id])
@@index([status])
@@index([kind])
@@index([createdAt])
}