feat: AIHR 智能人力资源管理系统初始提交
- 员工花名册管理(加密存储、导入导出) - 薪酬管理(发薪批次、薪酬模版、加班费计算、工资条) - 社保公积金(多城市配置、版本管理、基数调整) - 解聘管理(6步流程、证据链、工作交接) - AI 助手(合同审查、风险预测、RAG 知识库) - Dashboard 仪表盘 - 设置与通知
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
-- 解聘流程状态机:仅新增列,不删除/修改现有列
|
||||
-- PostgreSQL 语法,安全执行不会丢失数据
|
||||
|
||||
ALTER TABLE "TerminationRecord" ADD COLUMN IF NOT EXISTS "status" TEXT NOT NULL DEFAULT 'DRAFT';
|
||||
ALTER TABLE "TerminationRecord" ADD COLUMN IF NOT EXISTS "currentStep" INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE "TerminationRecord" ADD COLUMN IF NOT EXISTS "compensationBreakdown" JSONB;
|
||||
ALTER TABLE "TerminationRecord" ADD COLUMN IF NOT EXISTS "checklistOverrides" JSONB;
|
||||
ALTER TABLE "TerminationRecord" ADD COLUMN IF NOT EXISTS "handoverItems" JSONB;
|
||||
ALTER TABLE "TerminationRecord" ADD COLUMN IF NOT EXISTS "approvedBy" TEXT;
|
||||
ALTER TABLE "TerminationRecord" ADD COLUMN IF NOT EXISTS "approvedAt" TIMESTAMP(3);
|
||||
ALTER TABLE "TerminationRecord" ADD COLUMN IF NOT EXISTS "approvalComment" TEXT;
|
||||
ALTER TABLE "TerminationRecord" ADD COLUMN IF NOT EXISTS "updatedBy" TEXT;
|
||||
ALTER TABLE "TerminationRecord" ADD COLUMN IF NOT EXISTS "updatedAt" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP;
|
||||
|
||||
-- 创建索引
|
||||
CREATE INDEX IF NOT EXISTS "TerminationRecord_orgId_status_idx" ON "TerminationRecord"("orgId", "status");
|
||||
@@ -0,0 +1,931 @@
|
||||
-- 启用 pgvector 扩展(RAG 知识库需要 vector 类型)
|
||||
CREATE EXTENSION IF NOT EXISTS vector;
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "Plan" AS ENUM ('FREE', 'PRO', 'ENTERPRISE');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "Role" AS ENUM ('ADMIN', 'HR', 'VIEWER');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "EmployeeStatus" AS ENUM ('ACTIVE', 'RESIGNED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "ContractType" AS ENUM ('FIXED', 'UNFIXED', 'UNSIGNED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "SignMethod" AS ENUM ('PAPER', 'ELECTRONIC');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "RiskType" AS ENUM ('CONTRACT', 'SALARY', 'TERMINATION', 'MONTHLY', 'ONBOARDING');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "RiskLevel" AS ENUM ('HIGH', 'MEDIUM', 'LOW');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "PayrollBatchType" AS ENUM ('REGULAR', 'TERMINATION', 'BONUS', 'SEVERANCE');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "PayrollBatchStatus" AS ENUM ('DRAFT', 'ARCHIVED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "PayslipItemType" AS ENUM ('INPUT', 'CALCULATED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "PayslipStatus" AS ENUM ('PENDING', 'PUBLISHED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "RiskStatus" AS ENUM ('PENDING', 'RESOLVED', 'IGNORED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "TerminationReason" AS ENUM ('NEGOTIATED', 'FAULT', 'NONFAULT', 'LAYOFF', 'EXPIRED', 'RESIGNATION');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "RiskAssessment" AS ENUM ('SAFE', 'WARNING', 'DANGER');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "OnboardingStatus" AS ENUM ('PENDING', 'APPROVED', 'REJECTED', 'CANCELLED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "ContractConfirmStatus" AS ENUM ('UNCONFIRMED', 'CONFIRMED', 'EXPIRED');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Organization" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"plan" "Plan" NOT NULL DEFAULT 'FREE',
|
||||
"maxEmployees" INTEGER NOT NULL DEFAULT 20,
|
||||
"city" TEXT,
|
||||
"payrollFrequency" INTEGER NOT NULL DEFAULT 1,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Organization_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "User" (
|
||||
"id" TEXT NOT NULL,
|
||||
"orgId" TEXT NOT NULL,
|
||||
"phone" TEXT NOT NULL,
|
||||
"email" TEXT,
|
||||
"passwordHash" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"role" "Role" NOT NULL DEFAULT 'ADMIN',
|
||||
"disabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"lastLoginAt" TIMESTAMP(3),
|
||||
|
||||
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Employee" (
|
||||
"id" TEXT NOT NULL,
|
||||
"orgId" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"department" TEXT NOT NULL,
|
||||
"hireDate" TIMESTAMP(3) NOT NULL,
|
||||
"monthlySalary" TEXT NOT NULL,
|
||||
"status" "EmployeeStatus" NOT NULL DEFAULT 'ACTIVE',
|
||||
"gender" TEXT,
|
||||
"phone" TEXT,
|
||||
"idCardNumber" TEXT,
|
||||
"idCardHash" TEXT,
|
||||
"emergencyContact" TEXT,
|
||||
"emergencyPhone" TEXT,
|
||||
"address" TEXT,
|
||||
"bankAccount" TEXT,
|
||||
"bankName" TEXT,
|
||||
"passwordHash" TEXT,
|
||||
"isPregnant" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isInMedicalPeriod" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isWorkInjured" BOOLEAN NOT NULL DEFAULT false,
|
||||
"socialInsBase" DOUBLE PRECISION,
|
||||
"housingFundBase" DOUBLE PRECISION,
|
||||
"socialInsStartMonth" TEXT,
|
||||
"socialInsEndMonth" TEXT,
|
||||
"housingFundStartMonth" TEXT,
|
||||
"housingFundEndMonth" TEXT,
|
||||
"specialDeduction" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"city" TEXT,
|
||||
"createdBy" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Employee_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "LaborContract" (
|
||||
"id" TEXT NOT NULL,
|
||||
"orgId" TEXT NOT NULL,
|
||||
"employeeId" TEXT NOT NULL,
|
||||
"signDate" TIMESTAMP(3),
|
||||
"startDate" TIMESTAMP(3) NOT NULL,
|
||||
"endDate" TIMESTAMP(3),
|
||||
"contractType" "ContractType" NOT NULL,
|
||||
"signMethod" "SignMethod" NOT NULL DEFAULT 'PAPER',
|
||||
"contractYears" INTEGER NOT NULL DEFAULT 3,
|
||||
"probationMonths" INTEGER NOT NULL DEFAULT 0,
|
||||
"probationSalary" INTEGER NOT NULL DEFAULT 0,
|
||||
"renewalCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"attachmentName" TEXT,
|
||||
"attachmentUrl" TEXT,
|
||||
"electronicContractNo" TEXT,
|
||||
"electronicContractUrl" TEXT,
|
||||
"createdBy" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "LaborContract_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "OvertimeRecord" (
|
||||
"id" TEXT NOT NULL,
|
||||
"orgId" TEXT NOT NULL,
|
||||
"employeeId" TEXT NOT NULL,
|
||||
"month" TEXT NOT NULL,
|
||||
"weekdayHours" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"weekendHours" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"holidayHours" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"weekdayPay" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"weekendPay" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"holidayPay" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"totalPay" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"batchId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "OvertimeRecord_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "TerminationRecord" (
|
||||
"id" TEXT NOT NULL,
|
||||
"orgId" TEXT NOT NULL,
|
||||
"employeeId" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL DEFAULT 'TERMINATION',
|
||||
"reason" "TerminationReason" NOT NULL,
|
||||
"terminationDate" TIMESTAMP(3) NOT NULL,
|
||||
"resignationReason" TEXT,
|
||||
"compensation" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"socialInsEndMonth" TEXT,
|
||||
"housingFundEndMonth" TEXT,
|
||||
"riskLevel" "RiskAssessment" NOT NULL DEFAULT 'SAFE',
|
||||
"checklist" JSONB NOT NULL,
|
||||
"remark" TEXT,
|
||||
"createdBy" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "TerminationRecord_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "RiskItem" (
|
||||
"id" TEXT NOT NULL,
|
||||
"orgId" TEXT NOT NULL,
|
||||
"employeeId" TEXT,
|
||||
"type" "RiskType" NOT NULL,
|
||||
"level" "RiskLevel" NOT NULL,
|
||||
"status" "RiskStatus" NOT NULL DEFAULT 'PENDING',
|
||||
"title" TEXT NOT NULL,
|
||||
"description" TEXT NOT NULL,
|
||||
"actionUrl" TEXT,
|
||||
"resolvedAt" TIMESTAMP(3),
|
||||
"resolvedBy" TEXT,
|
||||
"remark" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "RiskItem_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "AuditLog" (
|
||||
"id" TEXT NOT NULL,
|
||||
"orgId" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"action" TEXT NOT NULL,
|
||||
"entity" TEXT NOT NULL,
|
||||
"entityId" TEXT,
|
||||
"detail" JSONB,
|
||||
"ip" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "AuditLog_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "SocialInsuranceConfig" (
|
||||
"id" TEXT NOT NULL,
|
||||
"orgId" TEXT NOT NULL,
|
||||
"city" TEXT NOT NULL DEFAULT '北京',
|
||||
"pensionOrg" DOUBLE PRECISION NOT NULL DEFAULT 16,
|
||||
"pensionEmp" DOUBLE PRECISION NOT NULL DEFAULT 8,
|
||||
"medicalOrg" DOUBLE PRECISION NOT NULL DEFAULT 9.8,
|
||||
"medicalEmp" DOUBLE PRECISION NOT NULL DEFAULT 2,
|
||||
"unemploymentOrg" DOUBLE PRECISION NOT NULL DEFAULT 0.5,
|
||||
"unemploymentEmp" DOUBLE PRECISION NOT NULL DEFAULT 0.5,
|
||||
"injuryOrg" DOUBLE PRECISION NOT NULL DEFAULT 0.2,
|
||||
"maternityOrg" DOUBLE PRECISION NOT NULL DEFAULT 0.8,
|
||||
"baseMin" DOUBLE PRECISION NOT NULL DEFAULT 6326,
|
||||
"baseMax" DOUBLE PRECISION NOT NULL DEFAULT 33891,
|
||||
"effectiveFrom" TEXT NOT NULL,
|
||||
"effectiveTo" TEXT,
|
||||
"isCurrent" BOOLEAN NOT NULL DEFAULT true,
|
||||
"adjustmentDone" BOOLEAN NOT NULL DEFAULT false,
|
||||
"createdBy" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "SocialInsuranceConfig_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "HousingFundConfig" (
|
||||
"id" TEXT NOT NULL,
|
||||
"orgId" TEXT NOT NULL,
|
||||
"city" TEXT NOT NULL DEFAULT '北京',
|
||||
"housingOrg" DOUBLE PRECISION NOT NULL DEFAULT 12,
|
||||
"housingEmp" DOUBLE PRECISION NOT NULL DEFAULT 12,
|
||||
"baseMin" DOUBLE PRECISION NOT NULL DEFAULT 6326,
|
||||
"baseMax" DOUBLE PRECISION NOT NULL DEFAULT 33891,
|
||||
"effectiveFrom" TEXT NOT NULL,
|
||||
"effectiveTo" TEXT,
|
||||
"isCurrent" BOOLEAN NOT NULL DEFAULT true,
|
||||
"adjustmentDone" BOOLEAN NOT NULL DEFAULT false,
|
||||
"createdBy" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "HousingFundConfig_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "NotificationSetting" (
|
||||
"id" TEXT NOT NULL,
|
||||
"orgId" TEXT NOT NULL,
|
||||
"contractExpiry" BOOLEAN NOT NULL DEFAULT true,
|
||||
"expiryDays" INTEGER NOT NULL DEFAULT 30,
|
||||
"contractUnsigned" BOOLEAN NOT NULL DEFAULT true,
|
||||
"overtimeAlert" BOOLEAN NOT NULL DEFAULT true,
|
||||
"payslipReady" BOOLEAN NOT NULL DEFAULT true,
|
||||
"payrollDay" INTEGER NOT NULL DEFAULT 10,
|
||||
"socialInsDay" INTEGER NOT NULL DEFAULT 15,
|
||||
"housingFundDay" INTEGER NOT NULL DEFAULT 15,
|
||||
"taxDay" INTEGER NOT NULL DEFAULT 15,
|
||||
"wechatWebhook" TEXT,
|
||||
"emailNotify" BOOLEAN NOT NULL DEFAULT false,
|
||||
"email" TEXT,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "NotificationSetting_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "OvertimeConfig" (
|
||||
"id" TEXT NOT NULL,
|
||||
"orgId" TEXT NOT NULL,
|
||||
"weekdayRate" DOUBLE PRECISION NOT NULL DEFAULT 1.5,
|
||||
"weekendRate" DOUBLE PRECISION NOT NULL DEFAULT 2.0,
|
||||
"holidayRate" DOUBLE PRECISION NOT NULL DEFAULT 3.0,
|
||||
"monthlyDays" DOUBLE PRECISION NOT NULL DEFAULT 21.75,
|
||||
"dailyHours" DOUBLE PRECISION NOT NULL DEFAULT 8,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "OvertimeConfig_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "NotificationLog" (
|
||||
"id" TEXT NOT NULL,
|
||||
"orgId" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"content" TEXT NOT NULL,
|
||||
"channel" TEXT NOT NULL,
|
||||
"status" TEXT NOT NULL DEFAULT 'SENT',
|
||||
"employeeId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "NotificationLog_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "EmployeeAttachment" (
|
||||
"id" TEXT NOT NULL,
|
||||
"orgId" TEXT NOT NULL,
|
||||
"employeeId" TEXT NOT NULL,
|
||||
"fileName" TEXT NOT NULL,
|
||||
"fileType" TEXT NOT NULL,
|
||||
"fileUrl" TEXT NOT NULL,
|
||||
"fileSize" INTEGER NOT NULL DEFAULT 0,
|
||||
"uploadedBy" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "EmployeeAttachment_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "DisciplinaryRecord" (
|
||||
"id" TEXT NOT NULL,
|
||||
"orgId" TEXT NOT NULL,
|
||||
"employeeId" TEXT NOT NULL,
|
||||
"violationDate" TIMESTAMP(3) NOT NULL,
|
||||
"violationType" TEXT NOT NULL,
|
||||
"description" TEXT NOT NULL,
|
||||
"severity" TEXT NOT NULL DEFAULT 'WARNING',
|
||||
"action" TEXT NOT NULL DEFAULT 'ORAL_WARNING',
|
||||
"actionDetail" TEXT,
|
||||
"employeeAck" BOOLEAN NOT NULL DEFAULT false,
|
||||
"ackDate" TIMESTAMP(3),
|
||||
"ackMethod" TEXT,
|
||||
"witness" TEXT,
|
||||
"attachmentUrl" TEXT,
|
||||
"createdBy" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "DisciplinaryRecord_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "AttendanceRecord" (
|
||||
"id" TEXT NOT NULL,
|
||||
"orgId" TEXT NOT NULL,
|
||||
"employeeId" TEXT NOT NULL,
|
||||
"date" TIMESTAMP(3) NOT NULL,
|
||||
"checkInTime" TEXT,
|
||||
"checkOutTime" TEXT,
|
||||
"status" TEXT NOT NULL DEFAULT 'NORMAL',
|
||||
"lateMinutes" INTEGER NOT NULL DEFAULT 0,
|
||||
"earlyMinutes" INTEGER NOT NULL DEFAULT 0,
|
||||
"workHours" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"overtimeHours" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"remark" TEXT,
|
||||
"createdBy" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "AttendanceRecord_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "TrainingRecord" (
|
||||
"id" TEXT NOT NULL,
|
||||
"orgId" TEXT NOT NULL,
|
||||
"employeeId" TEXT NOT NULL,
|
||||
"trainingDate" TIMESTAMP(3) NOT NULL,
|
||||
"topic" TEXT NOT NULL,
|
||||
"content" TEXT,
|
||||
"trainer" TEXT,
|
||||
"duration" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"ackStatus" TEXT NOT NULL DEFAULT 'PENDING',
|
||||
"ackDate" TIMESTAMP(3),
|
||||
"attachmentUrl" TEXT,
|
||||
"remark" TEXT,
|
||||
"createdBy" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "TrainingRecord_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "PerformanceRecord" (
|
||||
"id" TEXT NOT NULL,
|
||||
"orgId" TEXT NOT NULL,
|
||||
"employeeId" TEXT NOT NULL,
|
||||
"period" TEXT NOT NULL,
|
||||
"score" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"grade" TEXT NOT NULL DEFAULT 'B',
|
||||
"result" TEXT NOT NULL DEFAULT 'QUALIFIED',
|
||||
"summary" TEXT,
|
||||
"improvementPlan" TEXT,
|
||||
"employeeAck" BOOLEAN NOT NULL DEFAULT false,
|
||||
"ackDate" TIMESTAMP(3),
|
||||
"reviewer" TEXT,
|
||||
"createdBy" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "PerformanceRecord_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Payslip" (
|
||||
"id" TEXT NOT NULL,
|
||||
"orgId" TEXT NOT NULL,
|
||||
"employeeId" TEXT NOT NULL,
|
||||
"month" TEXT NOT NULL,
|
||||
"baseSalary" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"overtimePay" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"weekdayOvertimePay" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"weekendOvertimePay" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"holidayOvertimePay" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"allowance" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"deduction" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"bonus" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"totalPay" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"socialEmp" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"housingEmp" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"tax" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"netPay" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"ytdIncome" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"ytdTaxDeducted" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"ytdSocialEmp" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"ytdHousingEmp" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"status" "PayslipStatus" NOT NULL DEFAULT 'PENDING',
|
||||
"confirmedAt" TIMESTAMP(3),
|
||||
"confirmedIp" TEXT,
|
||||
"publishedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Payslip_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "PayrollBatch" (
|
||||
"id" TEXT NOT NULL,
|
||||
"orgId" TEXT NOT NULL,
|
||||
"month" TEXT NOT NULL,
|
||||
"batchNo" INTEGER NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"type" "PayrollBatchType" NOT NULL DEFAULT 'REGULAR',
|
||||
"status" "PayrollBatchStatus" NOT NULL DEFAULT 'DRAFT',
|
||||
"employeeCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"totalPay" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"totalNetPay" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"totalSocialOrg" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"totalSocialEmp" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"totalHousingOrg" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"totalHousingEmp" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"totalTax" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"remark" TEXT,
|
||||
"createdBy" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"archivedAt" TIMESTAMP(3),
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "PayrollBatch_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "BatchEntry" (
|
||||
"id" TEXT NOT NULL,
|
||||
"batchId" TEXT NOT NULL,
|
||||
"orgId" TEXT NOT NULL,
|
||||
"employeeId" TEXT NOT NULL,
|
||||
"baseSalary" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"overtimePay" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"allowance" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"deduction" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"bonus" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"socialEmp" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"socialOrg" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"housingEmp" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"housingOrg" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"tax" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"totalPay" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"netPay" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"riskWarnings" JSONB,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "BatchEntry_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "PayslipItem" (
|
||||
"id" TEXT NOT NULL,
|
||||
"orgId" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"code" TEXT NOT NULL,
|
||||
"type" "PayslipItemType" NOT NULL DEFAULT 'INPUT',
|
||||
"formula" TEXT,
|
||||
"order" INTEGER NOT NULL DEFAULT 0,
|
||||
"isDefault" BOOLEAN NOT NULL DEFAULT true,
|
||||
"isEditable" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "PayslipItem_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "SalaryChangeRecord" (
|
||||
"id" TEXT NOT NULL,
|
||||
"orgId" TEXT NOT NULL,
|
||||
"employeeId" TEXT NOT NULL,
|
||||
"oldSalary" DOUBLE PRECISION NOT NULL,
|
||||
"newSalary" DOUBLE PRECISION NOT NULL,
|
||||
"effectiveDate" TIMESTAMP(3) NOT NULL,
|
||||
"effectiveMonth" TEXT NOT NULL,
|
||||
"endMonth" TEXT,
|
||||
"changeType" TEXT NOT NULL DEFAULT 'SALARY_CHANGE',
|
||||
"reason" TEXT,
|
||||
"createdBy" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "SalaryChangeRecord_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "EmployeeSocialInsRecord" (
|
||||
"id" TEXT NOT NULL,
|
||||
"orgId" TEXT NOT NULL,
|
||||
"employeeId" TEXT NOT NULL,
|
||||
"city" TEXT NOT NULL DEFAULT '北京',
|
||||
"startMonth" TEXT NOT NULL,
|
||||
"endMonth" TEXT,
|
||||
"base" DOUBLE PRECISION NOT NULL,
|
||||
"changeType" TEXT NOT NULL,
|
||||
"changeRefId" TEXT,
|
||||
"remark" TEXT,
|
||||
"createdBy" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "EmployeeSocialInsRecord_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "EmployeeHousingFundRecord" (
|
||||
"id" TEXT NOT NULL,
|
||||
"orgId" TEXT NOT NULL,
|
||||
"employeeId" TEXT NOT NULL,
|
||||
"city" TEXT NOT NULL DEFAULT '北京',
|
||||
"startMonth" TEXT NOT NULL,
|
||||
"endMonth" TEXT,
|
||||
"base" DOUBLE PRECISION NOT NULL,
|
||||
"changeType" TEXT NOT NULL,
|
||||
"changeRefId" TEXT,
|
||||
"remark" TEXT,
|
||||
"createdBy" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "EmployeeHousingFundRecord_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "EmployeeDepartmentRecord" (
|
||||
"id" TEXT NOT NULL,
|
||||
"orgId" TEXT NOT NULL,
|
||||
"employeeId" TEXT NOT NULL,
|
||||
"oldDepartment" TEXT NOT NULL,
|
||||
"newDepartment" TEXT NOT NULL,
|
||||
"effectiveMonth" TEXT NOT NULL,
|
||||
"endMonth" TEXT,
|
||||
"reason" TEXT,
|
||||
"changeType" TEXT NOT NULL,
|
||||
"createdBy" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "EmployeeDepartmentRecord_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "OnboardingLink" (
|
||||
"id" TEXT NOT NULL,
|
||||
"orgId" TEXT NOT NULL,
|
||||
"token" TEXT NOT NULL,
|
||||
"employeeName" TEXT,
|
||||
"phone" TEXT,
|
||||
"status" "OnboardingStatus" NOT NULL DEFAULT 'PENDING',
|
||||
"formData" JSONB,
|
||||
"expiresAt" TIMESTAMP(3) NOT NULL,
|
||||
"usedAt" TIMESTAMP(3),
|
||||
"createdBy" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "OnboardingLink_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "ContractConfirmLink" (
|
||||
"id" TEXT NOT NULL,
|
||||
"orgId" TEXT NOT NULL,
|
||||
"contractId" TEXT NOT NULL,
|
||||
"token" TEXT NOT NULL,
|
||||
"status" "ContractConfirmStatus" NOT NULL DEFAULT 'UNCONFIRMED',
|
||||
"confirmedAt" TIMESTAMP(3),
|
||||
"confirmedIp" TEXT,
|
||||
"expiresAt" TIMESTAMP(3) NOT NULL,
|
||||
"createdBy" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "ContractConfirmLink_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "AIConversation" (
|
||||
"id" TEXT NOT NULL,
|
||||
"orgId" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL DEFAULT '新对话',
|
||||
"messages" JSONB NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "AIConversation_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "AIReviewRecord" (
|
||||
"id" TEXT NOT NULL,
|
||||
"orgId" TEXT NOT NULL,
|
||||
"employeeId" TEXT,
|
||||
"type" TEXT NOT NULL,
|
||||
"input" TEXT NOT NULL,
|
||||
"result" TEXT NOT NULL,
|
||||
"createdBy" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "AIReviewRecord_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "rag_knowledge" (
|
||||
"id" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"content" TEXT NOT NULL,
|
||||
"source" TEXT NOT NULL,
|
||||
"category" TEXT NOT NULL,
|
||||
"embedding" vector(1536),
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "rag_knowledge_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_phone_key" ON "User"("phone");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Employee_orgId_idCardHash_key" ON "Employee"("orgId", "idCardHash");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "OvertimeRecord_employeeId_month_key" ON "OvertimeRecord"("employeeId", "month");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "RiskItem_orgId_status_idx" ON "RiskItem"("orgId", "status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "RiskItem_orgId_type_idx" ON "RiskItem"("orgId", "type");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AuditLog_orgId_createdAt_idx" ON "AuditLog"("orgId", "createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "SocialInsuranceConfig_orgId_isCurrent_idx" ON "SocialInsuranceConfig"("orgId", "isCurrent");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "SocialInsuranceConfig_orgId_city_effectiveFrom_key" ON "SocialInsuranceConfig"("orgId", "city", "effectiveFrom");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "HousingFundConfig_orgId_isCurrent_idx" ON "HousingFundConfig"("orgId", "isCurrent");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "HousingFundConfig_orgId_city_effectiveFrom_key" ON "HousingFundConfig"("orgId", "city", "effectiveFrom");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "NotificationSetting_orgId_key" ON "NotificationSetting"("orgId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "OvertimeConfig_orgId_key" ON "OvertimeConfig"("orgId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "NotificationLog_orgId_createdAt_idx" ON "NotificationLog"("orgId", "createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "EmployeeAttachment_orgId_employeeId_idx" ON "EmployeeAttachment"("orgId", "employeeId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "DisciplinaryRecord_orgId_employeeId_idx" ON "DisciplinaryRecord"("orgId", "employeeId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AttendanceRecord_orgId_employeeId_idx" ON "AttendanceRecord"("orgId", "employeeId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "AttendanceRecord_employeeId_date_key" ON "AttendanceRecord"("employeeId", "date");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "TrainingRecord_orgId_employeeId_idx" ON "TrainingRecord"("orgId", "employeeId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "PerformanceRecord_orgId_employeeId_idx" ON "PerformanceRecord"("orgId", "employeeId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "PerformanceRecord_employeeId_period_key" ON "PerformanceRecord"("employeeId", "period");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Payslip_orgId_month_idx" ON "Payslip"("orgId", "month");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Payslip_orgId_status_idx" ON "Payslip"("orgId", "status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Payslip_employeeId_month_key" ON "Payslip"("employeeId", "month");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "PayrollBatch_orgId_month_idx" ON "PayrollBatch"("orgId", "month");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "PayrollBatch_orgId_status_idx" ON "PayrollBatch"("orgId", "status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "PayrollBatch_orgId_month_batchNo_key" ON "PayrollBatch"("orgId", "month", "batchNo");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "BatchEntry_orgId_employeeId_idx" ON "BatchEntry"("orgId", "employeeId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "BatchEntry_batchId_employeeId_key" ON "BatchEntry"("batchId", "employeeId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "PayslipItem_orgId_code_key" ON "PayslipItem"("orgId", "code");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "SalaryChangeRecord_orgId_employeeId_idx" ON "SalaryChangeRecord"("orgId", "employeeId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "SalaryChangeRecord_employeeId_effectiveMonth_endMonth_idx" ON "SalaryChangeRecord"("employeeId", "effectiveMonth", "endMonth");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "EmployeeSocialInsRecord_orgId_employeeId_idx" ON "EmployeeSocialInsRecord"("orgId", "employeeId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "EmployeeSocialInsRecord_employeeId_startMonth_endMonth_idx" ON "EmployeeSocialInsRecord"("employeeId", "startMonth", "endMonth");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "EmployeeSocialInsRecord_orgId_city_idx" ON "EmployeeSocialInsRecord"("orgId", "city");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "EmployeeHousingFundRecord_orgId_employeeId_idx" ON "EmployeeHousingFundRecord"("orgId", "employeeId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "EmployeeHousingFundRecord_employeeId_startMonth_endMonth_idx" ON "EmployeeHousingFundRecord"("employeeId", "startMonth", "endMonth");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "EmployeeDepartmentRecord_orgId_employeeId_idx" ON "EmployeeDepartmentRecord"("orgId", "employeeId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "EmployeeDepartmentRecord_employeeId_effectiveMonth_endMonth_idx" ON "EmployeeDepartmentRecord"("employeeId", "effectiveMonth", "endMonth");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "OnboardingLink_token_key" ON "OnboardingLink"("token");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "OnboardingLink_orgId_status_idx" ON "OnboardingLink"("orgId", "status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "ContractConfirmLink_token_key" ON "ContractConfirmLink"("token");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ContractConfirmLink_orgId_status_idx" ON "ContractConfirmLink"("orgId", "status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AIConversation_orgId_userId_idx" ON "AIConversation"("orgId", "userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AIReviewRecord_orgId_employeeId_idx" ON "AIReviewRecord"("orgId", "employeeId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "rag_knowledge_category_idx" ON "rag_knowledge"("category");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "User" ADD CONSTRAINT "User_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Employee" ADD CONSTRAINT "Employee_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "LaborContract" ADD CONSTRAINT "LaborContract_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "LaborContract" ADD CONSTRAINT "LaborContract_employeeId_fkey" FOREIGN KEY ("employeeId") REFERENCES "Employee"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "OvertimeRecord" ADD CONSTRAINT "OvertimeRecord_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "OvertimeRecord" ADD CONSTRAINT "OvertimeRecord_employeeId_fkey" FOREIGN KEY ("employeeId") REFERENCES "Employee"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "TerminationRecord" ADD CONSTRAINT "TerminationRecord_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "TerminationRecord" ADD CONSTRAINT "TerminationRecord_employeeId_fkey" FOREIGN KEY ("employeeId") REFERENCES "Employee"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "RiskItem" ADD CONSTRAINT "RiskItem_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "RiskItem" ADD CONSTRAINT "RiskItem_employeeId_fkey" FOREIGN KEY ("employeeId") REFERENCES "Employee"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "AuditLog" ADD CONSTRAINT "AuditLog_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "SocialInsuranceConfig" ADD CONSTRAINT "SocialInsuranceConfig_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "HousingFundConfig" ADD CONSTRAINT "HousingFundConfig_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "NotificationSetting" ADD CONSTRAINT "NotificationSetting_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "OvertimeConfig" ADD CONSTRAINT "OvertimeConfig_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "NotificationLog" ADD CONSTRAINT "NotificationLog_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "EmployeeAttachment" ADD CONSTRAINT "EmployeeAttachment_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "EmployeeAttachment" ADD CONSTRAINT "EmployeeAttachment_employeeId_fkey" FOREIGN KEY ("employeeId") REFERENCES "Employee"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "DisciplinaryRecord" ADD CONSTRAINT "DisciplinaryRecord_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "DisciplinaryRecord" ADD CONSTRAINT "DisciplinaryRecord_employeeId_fkey" FOREIGN KEY ("employeeId") REFERENCES "Employee"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "AttendanceRecord" ADD CONSTRAINT "AttendanceRecord_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "AttendanceRecord" ADD CONSTRAINT "AttendanceRecord_employeeId_fkey" FOREIGN KEY ("employeeId") REFERENCES "Employee"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "TrainingRecord" ADD CONSTRAINT "TrainingRecord_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "TrainingRecord" ADD CONSTRAINT "TrainingRecord_employeeId_fkey" FOREIGN KEY ("employeeId") REFERENCES "Employee"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "PerformanceRecord" ADD CONSTRAINT "PerformanceRecord_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "PerformanceRecord" ADD CONSTRAINT "PerformanceRecord_employeeId_fkey" FOREIGN KEY ("employeeId") REFERENCES "Employee"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Payslip" ADD CONSTRAINT "Payslip_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Payslip" ADD CONSTRAINT "Payslip_employeeId_fkey" FOREIGN KEY ("employeeId") REFERENCES "Employee"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "PayrollBatch" ADD CONSTRAINT "PayrollBatch_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "BatchEntry" ADD CONSTRAINT "BatchEntry_batchId_fkey" FOREIGN KEY ("batchId") REFERENCES "PayrollBatch"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "BatchEntry" ADD CONSTRAINT "BatchEntry_employeeId_fkey" FOREIGN KEY ("employeeId") REFERENCES "Employee"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "PayslipItem" ADD CONSTRAINT "PayslipItem_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "SalaryChangeRecord" ADD CONSTRAINT "SalaryChangeRecord_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "SalaryChangeRecord" ADD CONSTRAINT "SalaryChangeRecord_employeeId_fkey" FOREIGN KEY ("employeeId") REFERENCES "Employee"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "EmployeeSocialInsRecord" ADD CONSTRAINT "EmployeeSocialInsRecord_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "EmployeeSocialInsRecord" ADD CONSTRAINT "EmployeeSocialInsRecord_employeeId_fkey" FOREIGN KEY ("employeeId") REFERENCES "Employee"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "EmployeeHousingFundRecord" ADD CONSTRAINT "EmployeeHousingFundRecord_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "EmployeeHousingFundRecord" ADD CONSTRAINT "EmployeeHousingFundRecord_employeeId_fkey" FOREIGN KEY ("employeeId") REFERENCES "Employee"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "EmployeeDepartmentRecord" ADD CONSTRAINT "EmployeeDepartmentRecord_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "EmployeeDepartmentRecord" ADD CONSTRAINT "EmployeeDepartmentRecord_employeeId_fkey" FOREIGN KEY ("employeeId") REFERENCES "Employee"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "OnboardingLink" ADD CONSTRAINT "OnboardingLink_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ContractConfirmLink" ADD CONSTRAINT "ContractConfirmLink_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ContractConfirmLink" ADD CONSTRAINT "ContractConfirmLink_contractId_fkey" FOREIGN KEY ("contractId") REFERENCES "LaborContract"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "AIConversation" ADD CONSTRAINT "AIConversation_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "AIReviewRecord" ADD CONSTRAINT "AIReviewRecord_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "AIReviewRecord" ADD CONSTRAINT "AIReviewRecord_employeeId_fkey" FOREIGN KEY ("employeeId") REFERENCES "Employee"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "postgresql"
|
||||
@@ -0,0 +1,826 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
// ========== 枚举 ==========
|
||||
|
||||
enum Plan {
|
||||
FREE
|
||||
PRO
|
||||
ENTERPRISE
|
||||
}
|
||||
|
||||
enum Role {
|
||||
ADMIN
|
||||
HR
|
||||
VIEWER
|
||||
}
|
||||
|
||||
enum EmployeeStatus {
|
||||
ACTIVE
|
||||
RESIGNED
|
||||
}
|
||||
|
||||
enum ContractType {
|
||||
FIXED
|
||||
UNFIXED
|
||||
UNSIGNED
|
||||
}
|
||||
|
||||
enum SignMethod {
|
||||
PAPER
|
||||
ELECTRONIC
|
||||
}
|
||||
|
||||
enum RiskType {
|
||||
CONTRACT
|
||||
SALARY
|
||||
TERMINATION
|
||||
MONTHLY
|
||||
ONBOARDING
|
||||
}
|
||||
|
||||
enum RiskLevel {
|
||||
HIGH
|
||||
MEDIUM
|
||||
LOW
|
||||
}
|
||||
|
||||
enum PayrollBatchType {
|
||||
REGULAR // 常规发薪
|
||||
TERMINATION // 离职结算
|
||||
BONUS // 年终奖/奖金
|
||||
SEVERANCE // 补偿金按月发放(无社保,个税按政策处理)
|
||||
}
|
||||
|
||||
enum PayrollBatchStatus {
|
||||
DRAFT // 草稿(可编辑)
|
||||
ARCHIVED // 归档(已发薪,锁定)
|
||||
}
|
||||
|
||||
enum PayslipItemType {
|
||||
INPUT // 手工输入项(计算依据)
|
||||
CALCULATED // 计算项(公式自动计算)
|
||||
}
|
||||
|
||||
enum PayslipStatus {
|
||||
PENDING // 待发布
|
||||
PUBLISHED // 已发布到员工端
|
||||
}
|
||||
|
||||
enum RiskStatus {
|
||||
PENDING
|
||||
RESOLVED
|
||||
IGNORED
|
||||
}
|
||||
|
||||
enum TerminationReason {
|
||||
NEGOTIATED
|
||||
FAULT
|
||||
NONFAULT
|
||||
LAYOFF
|
||||
EXPIRED
|
||||
RESIGNATION
|
||||
}
|
||||
|
||||
enum RiskAssessment {
|
||||
SAFE
|
||||
WARNING
|
||||
DANGER
|
||||
}
|
||||
|
||||
enum OnboardingStatus {
|
||||
PENDING
|
||||
APPROVED
|
||||
REJECTED
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
enum ContractConfirmStatus {
|
||||
UNCONFIRMED
|
||||
CONFIRMED
|
||||
EXPIRED
|
||||
}
|
||||
|
||||
// ========== 核心表 ==========
|
||||
|
||||
model Organization {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
plan Plan @default(FREE)
|
||||
maxEmployees Int @default(20)
|
||||
city String?
|
||||
payrollFrequency Int @default(1) // 每月发薪次数(1=一次一批)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
users User[]
|
||||
employees Employee[]
|
||||
contracts LaborContract[]
|
||||
overtimeRecords OvertimeRecord[]
|
||||
terminations TerminationRecord[]
|
||||
riskItems RiskItem[]
|
||||
auditLogs AuditLog[]
|
||||
payslips Payslip[]
|
||||
payrollBatches PayrollBatch[]
|
||||
payslipItems PayslipItem[]
|
||||
salaryChangeRecords SalaryChangeRecord[]
|
||||
onboardingLinks OnboardingLink[]
|
||||
confirmLinks ContractConfirmLink[]
|
||||
aiConversations AIConversation[]
|
||||
aiReviewRecords AIReviewRecord[]
|
||||
socialInsuranceConfig SocialInsuranceConfig[]
|
||||
housingFundConfigs HousingFundConfig[]
|
||||
socialInsRecords EmployeeSocialInsRecord[]
|
||||
housingFundRecords EmployeeHousingFundRecord[]
|
||||
departmentRecords EmployeeDepartmentRecord[]
|
||||
notificationSetting NotificationSetting?
|
||||
overtimeConfig OvertimeConfig?
|
||||
notificationLogs NotificationLog[]
|
||||
employeeAttachments EmployeeAttachment[]
|
||||
disciplinaryRecords DisciplinaryRecord[]
|
||||
attendanceRecords AttendanceRecord[]
|
||||
trainingRecords TrainingRecord[]
|
||||
performanceRecords PerformanceRecord[]
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(cuid())
|
||||
orgId String
|
||||
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
|
||||
phone String @unique
|
||||
email String?
|
||||
passwordHash String
|
||||
name String
|
||||
role Role @default(ADMIN)
|
||||
disabled Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
lastLoginAt DateTime?
|
||||
}
|
||||
|
||||
// ========== 业务表 ==========
|
||||
|
||||
model Employee {
|
||||
id String @id @default(cuid())
|
||||
orgId String
|
||||
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
|
||||
name String
|
||||
department String
|
||||
hireDate DateTime
|
||||
monthlySalary String // AES-256 加密存储
|
||||
status EmployeeStatus @default(ACTIVE)
|
||||
gender String?
|
||||
phone String?
|
||||
idCardNumber String? // AES-256 加密存储
|
||||
idCardHash String? // SHA-256 哈希,用于按身份证号查询匹配
|
||||
emergencyContact String?
|
||||
emergencyPhone String?
|
||||
address String?
|
||||
bankAccount String? // AES-256 加密存储
|
||||
bankName String?
|
||||
passwordHash String? // 员工端登录密码
|
||||
isPregnant Boolean @default(false)
|
||||
isInMedicalPeriod Boolean @default(false)
|
||||
isWorkInjured Boolean @default(false)
|
||||
// 薪税扩展
|
||||
socialInsBase Float? // 社保缴费基数(便捷字段,由Record同步)
|
||||
housingFundBase Float? // 公积金缴费基数(便捷字段,由Record同步)
|
||||
socialInsStartMonth String? // 当前社保开始年月(便捷字段)
|
||||
socialInsEndMonth String? // 当前社保截止年月(便捷字段,null=在保)
|
||||
housingFundStartMonth String? // 当前公积金开始年月(便捷字段)
|
||||
housingFundEndMonth String? // 当前公积金截止年月(便捷字段)
|
||||
specialDeduction Float @default(0) // 专项附加扣除(子女教育、赡养老人等,员工portal端填报)
|
||||
city String? // 员工社保参保城市
|
||||
createdBy String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
contracts LaborContract[]
|
||||
overtimeRecords OvertimeRecord[]
|
||||
terminations TerminationRecord[]
|
||||
riskItems RiskItem[]
|
||||
payslips Payslip[]
|
||||
salaryChanges SalaryChangeRecord[]
|
||||
batchEntries BatchEntry[]
|
||||
attachments EmployeeAttachment[]
|
||||
disciplinaryRecords DisciplinaryRecord[]
|
||||
attendanceRecords AttendanceRecord[]
|
||||
trainingRecords TrainingRecord[]
|
||||
performanceRecords PerformanceRecord[]
|
||||
socialInsRecords EmployeeSocialInsRecord[]
|
||||
housingFundRecords EmployeeHousingFundRecord[]
|
||||
departmentRecords EmployeeDepartmentRecord[]
|
||||
aiReviewRecords AIReviewRecord[]
|
||||
|
||||
@@unique([orgId, idCardHash])
|
||||
}
|
||||
|
||||
model LaborContract {
|
||||
id String @id @default(cuid())
|
||||
orgId String
|
||||
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
|
||||
employeeId String
|
||||
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
|
||||
signDate DateTime?
|
||||
startDate DateTime
|
||||
endDate DateTime?
|
||||
contractType ContractType
|
||||
signMethod SignMethod @default(PAPER)
|
||||
contractYears Int @default(3)
|
||||
probationMonths Int @default(0)
|
||||
probationSalary Int @default(0)
|
||||
renewalCount Int @default(0)
|
||||
attachmentName String?
|
||||
attachmentUrl String?
|
||||
electronicContractNo String?
|
||||
electronicContractUrl String?
|
||||
createdBy String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
confirmLinks ContractConfirmLink[]
|
||||
}
|
||||
|
||||
model OvertimeRecord {
|
||||
id String @id @default(cuid())
|
||||
orgId String
|
||||
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
|
||||
employeeId String
|
||||
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
|
||||
month String // YYYY-MM
|
||||
weekdayHours Float @default(0)
|
||||
weekendHours Float @default(0)
|
||||
holidayHours Float @default(0)
|
||||
weekdayPay Float @default(0)
|
||||
weekendPay Float @default(0)
|
||||
holidayPay Float @default(0)
|
||||
totalPay Float @default(0)
|
||||
batchId String? // 关联的发薪批次(加入后锁定,不可重复加入)
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@unique([employeeId, month])
|
||||
}
|
||||
|
||||
model TerminationRecord {
|
||||
id String @id @default(cuid())
|
||||
orgId String
|
||||
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
|
||||
employeeId String
|
||||
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
|
||||
type String @default("TERMINATION") // TERMINATION=公司解聘, RESIGNATION=员工主动离职
|
||||
reason TerminationReason
|
||||
terminationDate DateTime
|
||||
resignationReason String? // 主动离职原因(type=RESIGNATION时使用)
|
||||
compensation Float @default(0)
|
||||
socialInsEndMonth String? // 社保截止缴费年月 YYYY-MM
|
||||
housingFundEndMonth String? // 公积金截止缴费年月 YYYY-MM
|
||||
riskLevel RiskAssessment @default(SAFE)
|
||||
checklist Json
|
||||
remark String?
|
||||
createdBy String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
// 流程状态机
|
||||
status String @default("DRAFT") // DRAFT|PENDING_APPROVAL|APPROVED|EXECUTING|COMPLETED|REJECTED|CANCELLED
|
||||
currentStep Int @default(0) // 当前完成到第几步
|
||||
// 补偿金分项明细 + 调整记录
|
||||
compensationBreakdown Json? // { severance, noticePay, doublePay, other, adjustments: [{field, from, to, reason}] }
|
||||
// 合规检查覆盖记录
|
||||
checklistOverrides Json? // { key: { checked: bool, overrideReason: string } }
|
||||
// 工作交接清单
|
||||
handoverItems Json? // [{ key, label, done, remark }]
|
||||
// 审批信息
|
||||
approvedBy String?
|
||||
approvedAt DateTime?
|
||||
approvalComment String?
|
||||
updatedBy String?
|
||||
|
||||
@@index([orgId, status])
|
||||
}
|
||||
|
||||
model RiskItem {
|
||||
id String @id @default(cuid())
|
||||
orgId String
|
||||
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
|
||||
employeeId String?
|
||||
employee Employee? @relation(fields: [employeeId], references: [id], onDelete: SetNull)
|
||||
type RiskType
|
||||
level RiskLevel
|
||||
status RiskStatus @default(PENDING)
|
||||
title String
|
||||
description String
|
||||
actionUrl String?
|
||||
resolvedAt DateTime?
|
||||
resolvedBy String?
|
||||
remark String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([orgId, status])
|
||||
@@index([orgId, type])
|
||||
}
|
||||
|
||||
model AuditLog {
|
||||
id String @id @default(cuid())
|
||||
orgId String
|
||||
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
|
||||
userId String
|
||||
action String
|
||||
entity String
|
||||
entityId String?
|
||||
detail Json?
|
||||
ip String?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([orgId, createdAt])
|
||||
}
|
||||
|
||||
// ========== 社保 & 通知 & 附件 ==========
|
||||
|
||||
model SocialInsuranceConfig {
|
||||
id String @id @default(cuid())
|
||||
orgId String
|
||||
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
|
||||
city String @default("北京")
|
||||
pensionOrg Float @default(16) // 养老保险 企业比例 %
|
||||
pensionEmp Float @default(8) // 养老保险 个人比例 %
|
||||
medicalOrg Float @default(9.8) // 医疗保险 企业比例 %
|
||||
medicalEmp Float @default(2) // 医疗保险 个人比例 %
|
||||
unemploymentOrg Float @default(0.5) // 失业保险 企业比例 %
|
||||
unemploymentEmp Float @default(0.5) // 失业保险 个人比例 %
|
||||
injuryOrg Float @default(0.2) // 工伤保险 企业比例 %
|
||||
maternityOrg Float @default(0.8) // 生育保险 企业比例 %
|
||||
baseMin Float @default(6326) // 社保缴费基数下限
|
||||
baseMax Float @default(33891) // 社保缴费基数上限
|
||||
effectiveFrom String // 生效月份 YYYY-MM
|
||||
effectiveTo String? // 失效月份 YYYY-MM(null=当前有效)
|
||||
isCurrent Boolean @default(true) // 是否当前生效版本
|
||||
adjustmentDone Boolean @default(false) // 是否已执行过社保基数调整
|
||||
createdBy String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([orgId, city, effectiveFrom])
|
||||
@@index([orgId, isCurrent])
|
||||
}
|
||||
|
||||
model HousingFundConfig {
|
||||
id String @id @default(cuid())
|
||||
orgId String
|
||||
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
|
||||
city String @default("北京")
|
||||
housingOrg Float @default(12) // 公积金 企业比例 %
|
||||
housingEmp Float @default(12) // 公积金 个人比例 %
|
||||
baseMin Float @default(6326) // 公积金缴费基数下限
|
||||
baseMax Float @default(33891) // 公积金缴费基数上限
|
||||
effectiveFrom String // 生效月份 YYYY-MM
|
||||
effectiveTo String? // 失效月份 YYYY-MM(null=当前有效)
|
||||
isCurrent Boolean @default(true) // 是否当前生效版本
|
||||
adjustmentDone Boolean @default(false) // 是否已执行过公积金基数调整
|
||||
createdBy String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([orgId, city, effectiveFrom])
|
||||
@@index([orgId, isCurrent])
|
||||
}
|
||||
|
||||
model NotificationSetting {
|
||||
id String @id @default(cuid())
|
||||
orgId String @unique
|
||||
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
|
||||
contractExpiry Boolean @default(true)
|
||||
expiryDays Int @default(30)
|
||||
contractUnsigned Boolean @default(true)
|
||||
overtimeAlert Boolean @default(true)
|
||||
payslipReady Boolean @default(true)
|
||||
// 月度事务提醒日(每月几号)
|
||||
payrollDay Int @default(10) // 发薪日
|
||||
socialInsDay Int @default(15) // 社保缴纳日
|
||||
housingFundDay Int @default(15) // 公积金缴纳日
|
||||
taxDay Int @default(15) // 个税申报日
|
||||
wechatWebhook String?
|
||||
emailNotify Boolean @default(false)
|
||||
email String?
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model OvertimeConfig {
|
||||
id String @id @default(cuid())
|
||||
orgId String @unique
|
||||
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
|
||||
weekdayRate Float @default(1.5) // 工作日加班倍率
|
||||
weekendRate Float @default(2.0) // 休息日加班倍率
|
||||
holidayRate Float @default(3.0) // 法定节假日加班倍率
|
||||
monthlyDays Float @default(21.75) // 月计薪天数
|
||||
dailyHours Float @default(8) // 每日工时
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model NotificationLog {
|
||||
id String @id @default(cuid())
|
||||
orgId String
|
||||
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
|
||||
type String // CONTRACT_EXPIRY / CONTRACT_UNSIGNED / OVERTIME / PAYSLIP
|
||||
title String
|
||||
content String
|
||||
channel String // WECHAT / EMAIL / IN_APP
|
||||
status String @default("SENT") // SENT / FAILED
|
||||
employeeId String?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([orgId, createdAt])
|
||||
}
|
||||
|
||||
model EmployeeAttachment {
|
||||
id String @id @default(cuid())
|
||||
orgId String
|
||||
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
|
||||
employeeId String
|
||||
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
|
||||
fileName String
|
||||
fileType String // ID_CARD / BANK_CARD / CONTRACT_SCAN / EDUCATION / OTHER
|
||||
fileUrl String
|
||||
fileSize Int @default(0)
|
||||
uploadedBy String
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([orgId, employeeId])
|
||||
}
|
||||
|
||||
// ========== 仲裁证据链 ==========
|
||||
|
||||
model DisciplinaryRecord {
|
||||
id String @id @default(cuid())
|
||||
orgId String
|
||||
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
|
||||
employeeId String
|
||||
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
|
||||
violationDate DateTime
|
||||
violationType String // LATE/ABSENT/INSUBORDINATION/MISCONDUCT/VIOLATE_POLICY/OTHER
|
||||
description String
|
||||
severity String @default("WARNING") // WARNING/SERIOUS/SEVERE
|
||||
action String @default("ORAL_WARNING") // ORAL_WARNING/WRITTEN_WARNING/DEDUCTION/DEMOTION/TERMINATION
|
||||
actionDetail String?
|
||||
employeeAck Boolean @default(false) // 员工是否签字确认
|
||||
ackDate DateTime?
|
||||
ackMethod String? // SIGN/ELECTRONIC/REFUSED
|
||||
witness String? // 见证人
|
||||
attachmentUrl String?
|
||||
createdBy String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([orgId, employeeId])
|
||||
}
|
||||
|
||||
model AttendanceRecord {
|
||||
id String @id @default(cuid())
|
||||
orgId String
|
||||
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
|
||||
employeeId String
|
||||
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
|
||||
date DateTime
|
||||
checkInTime String? // HH:mm
|
||||
checkOutTime String? // HH:mm
|
||||
status String @default("NORMAL") // NORMAL/LATE/EARLY_LEAVE/ABSENT/LEAVE/BUSINESS_TRIP
|
||||
lateMinutes Int @default(0)
|
||||
earlyMinutes Int @default(0)
|
||||
workHours Float @default(0)
|
||||
overtimeHours Float @default(0)
|
||||
remark String?
|
||||
createdBy String
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@unique([employeeId, date])
|
||||
@@index([orgId, employeeId])
|
||||
}
|
||||
|
||||
model TrainingRecord {
|
||||
id String @id @default(cuid())
|
||||
orgId String
|
||||
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
|
||||
employeeId String
|
||||
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
|
||||
trainingDate DateTime
|
||||
topic String // 培训主题/制度名称
|
||||
content String? // 培训内容摘要
|
||||
trainer String?
|
||||
duration Float @default(0) // 培训时长(小时)
|
||||
ackStatus String @default("PENDING") // PENDING/SIGNED/REFUSED
|
||||
ackDate DateTime?
|
||||
attachmentUrl String? // 签收单扫描件
|
||||
remark String?
|
||||
createdBy String
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([orgId, employeeId])
|
||||
}
|
||||
|
||||
model PerformanceRecord {
|
||||
id String @id @default(cuid())
|
||||
orgId String
|
||||
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
|
||||
employeeId String
|
||||
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
|
||||
period String // 考核周期 YYYY-MM 或 YYYY-Q1
|
||||
score Float @default(0) // 考核得分
|
||||
grade String @default("B") // A/B/C/D
|
||||
result String @default("QUALIFIED") // EXCELLENT/QUALIFIED/NEED_IMPROVE/UNQUALIFIED
|
||||
summary String? // 考核评语
|
||||
improvementPlan String? // 改进计划(不胜任时)
|
||||
employeeAck Boolean @default(false)
|
||||
ackDate DateTime?
|
||||
reviewer String?
|
||||
createdBy String
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@unique([employeeId, period])
|
||||
@@index([orgId, employeeId])
|
||||
}
|
||||
|
||||
// ========== 员工端表 ==========
|
||||
|
||||
model Payslip {
|
||||
id String @id @default(cuid())
|
||||
orgId String
|
||||
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
|
||||
employeeId String
|
||||
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
|
||||
month String // YYYY-MM
|
||||
// 薪酬构成
|
||||
baseSalary Float @default(0)
|
||||
overtimePay Float @default(0)
|
||||
weekdayOvertimePay Float @default(0)
|
||||
weekendOvertimePay Float @default(0)
|
||||
holidayOvertimePay Float @default(0)
|
||||
allowance Float @default(0)
|
||||
deduction Float @default(0)
|
||||
bonus Float @default(0) // 奖金/年终奖
|
||||
totalPay Float @default(0) // 应发合计
|
||||
// 扣除项
|
||||
socialEmp Float @default(0) // 个人社保
|
||||
housingEmp Float @default(0) // 个人公积金
|
||||
tax Float @default(0) // 个人所得税
|
||||
netPay Float @default(0) // 实发工资 = totalPay - socialEmp - housingEmp - tax
|
||||
// 累计预扣法
|
||||
ytdIncome Float @default(0) // 当年累计收入
|
||||
ytdTaxDeducted Float @default(0) // 当年累计已扣税
|
||||
ytdSocialEmp Float @default(0) // 当年累计个人社保
|
||||
ytdHousingEmp Float @default(0) // 当年累计个人公积金
|
||||
// 状态
|
||||
status PayslipStatus @default(PENDING) // PENDING → PUBLISHED
|
||||
confirmedAt DateTime?
|
||||
confirmedIp String?
|
||||
publishedAt DateTime? // 工资条发布到员工端的时间
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([employeeId, month])
|
||||
@@index([orgId, month])
|
||||
@@index([orgId, status])
|
||||
}
|
||||
|
||||
model PayrollBatch {
|
||||
id String @id @default(cuid())
|
||||
orgId String
|
||||
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
|
||||
month String // YYYY-MM
|
||||
batchNo Int // 批次序号(1, 2, 3...)
|
||||
name String // 批次名称
|
||||
type PayrollBatchType @default(REGULAR)
|
||||
status PayrollBatchStatus @default(DRAFT)
|
||||
employeeCount Int @default(0)
|
||||
totalPay Float @default(0)
|
||||
totalNetPay Float @default(0)
|
||||
totalSocialOrg Float @default(0)
|
||||
totalSocialEmp Float @default(0)
|
||||
totalHousingOrg Float @default(0)
|
||||
totalHousingEmp Float @default(0)
|
||||
totalTax Float @default(0)
|
||||
remark String?
|
||||
createdBy String
|
||||
createdAt DateTime @default(now())
|
||||
archivedAt DateTime?
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
entries BatchEntry[]
|
||||
|
||||
@@unique([orgId, month, batchNo])
|
||||
@@index([orgId, month])
|
||||
@@index([orgId, status])
|
||||
}
|
||||
|
||||
model BatchEntry {
|
||||
id String @id @default(cuid())
|
||||
batchId String
|
||||
batch PayrollBatch @relation(fields: [batchId], references: [id], onDelete: Cascade)
|
||||
orgId String
|
||||
employeeId String
|
||||
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
|
||||
// 薪酬项(可编辑的输入项)
|
||||
baseSalary Float @default(0)
|
||||
overtimePay Float @default(0)
|
||||
allowance Float @default(0)
|
||||
deduction Float @default(0)
|
||||
bonus Float @default(0)
|
||||
// 自动计算项
|
||||
socialEmp Float @default(0)
|
||||
socialOrg Float @default(0)
|
||||
housingEmp Float @default(0)
|
||||
housingOrg Float @default(0)
|
||||
tax Float @default(0)
|
||||
totalPay Float @default(0) // 应发合计
|
||||
netPay Float @default(0) // 实发工资
|
||||
// 风险提示
|
||||
riskWarnings Json?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([batchId, employeeId])
|
||||
@@index([orgId, employeeId])
|
||||
}
|
||||
|
||||
model PayslipItem {
|
||||
id String @id @default(cuid())
|
||||
orgId String
|
||||
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
|
||||
name String // 显示名称
|
||||
code String // 字段代码
|
||||
type PayslipItemType @default(INPUT)
|
||||
formula String? // 计算公式(CALCULATED 类型),如 "baseSalary + overtimePay + allowance - deduction"
|
||||
order Int @default(0)
|
||||
isDefault Boolean @default(true) // 系统预置项不可删除
|
||||
isEditable Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([orgId, code])
|
||||
}
|
||||
|
||||
model SalaryChangeRecord {
|
||||
id String @id @default(cuid())
|
||||
orgId String
|
||||
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
|
||||
employeeId String
|
||||
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
|
||||
oldSalary Float
|
||||
newSalary Float
|
||||
effectiveDate DateTime // 生效日期
|
||||
effectiveMonth String // 生效年月 YYYY-MM(从 effectiveDate 转换)
|
||||
endMonth String? // 失效年月 YYYY-MM(null=至今有效,被新版本覆盖时设置)
|
||||
changeType String @default("SALARY_CHANGE") // ONBOARDING=入职, REHIRE=重新入职, SALARY_CHANGE=调薪
|
||||
reason String?
|
||||
createdBy String
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([orgId, employeeId])
|
||||
@@index([employeeId, effectiveMonth, endMonth])
|
||||
}
|
||||
|
||||
model EmployeeSocialInsRecord {
|
||||
id String @id @default(cuid())
|
||||
orgId String
|
||||
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
|
||||
employeeId String
|
||||
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
|
||||
city String @default("北京") // 参保城市
|
||||
startMonth String // 开始缴费年月 YYYY-MM
|
||||
endMonth String? // 截止缴费年月 YYYY-MM(null=至今有效)
|
||||
base Float // 缴费基数
|
||||
changeType String // ONBOARDING=入职, REHIRE=重新入职, ADJUST=调基, TERMINATION=离职/解聘
|
||||
changeRefId String? // 关联的 TerminationRecord ID(离职/解聘时)
|
||||
remark String?
|
||||
createdBy String
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([orgId, employeeId])
|
||||
@@index([employeeId, startMonth, endMonth])
|
||||
@@index([orgId, city])
|
||||
}
|
||||
|
||||
model EmployeeHousingFundRecord {
|
||||
id String @id @default(cuid())
|
||||
orgId String
|
||||
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
|
||||
employeeId String
|
||||
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
|
||||
city String @default("北京") // 参保城市
|
||||
startMonth String // 开始缴费年月 YYYY-MM
|
||||
endMonth String? // 截止缴费年月 YYYY-MM(null=至今有效)
|
||||
base Float // 缴费基数
|
||||
changeType String // ONBOARDING=入职, REHIRE=重新入职, ADJUST=调基, TERMINATION=离职/解聘
|
||||
changeRefId String? // 关联的 TerminationRecord ID(离职/解聘时)
|
||||
remark String?
|
||||
createdBy String
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([orgId, employeeId])
|
||||
@@index([employeeId, startMonth, endMonth])
|
||||
}
|
||||
|
||||
model EmployeeDepartmentRecord {
|
||||
id String @id @default(cuid())
|
||||
orgId String
|
||||
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
|
||||
employeeId String
|
||||
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
|
||||
oldDepartment String // 调整前部门
|
||||
newDepartment String // 调整后部门
|
||||
effectiveMonth String // 生效年月 YYYY-MM
|
||||
endMonth String? // 失效年月 YYYY-MM(null=至今有效)
|
||||
reason String? // 调部门原因
|
||||
changeType String // ONBOARDING=入职, REHIRE=重新入职, TRANSFER=调部门
|
||||
createdBy String
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([orgId, employeeId])
|
||||
@@index([employeeId, effectiveMonth, endMonth])
|
||||
}
|
||||
|
||||
model OnboardingLink {
|
||||
id String @id @default(cuid())
|
||||
orgId String
|
||||
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
|
||||
token String @unique
|
||||
employeeName String?
|
||||
phone String?
|
||||
status OnboardingStatus @default(PENDING)
|
||||
formData Json?
|
||||
expiresAt DateTime
|
||||
usedAt DateTime?
|
||||
createdBy String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([orgId, status])
|
||||
}
|
||||
|
||||
model ContractConfirmLink {
|
||||
id String @id @default(cuid())
|
||||
orgId String
|
||||
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
|
||||
contractId String
|
||||
contract LaborContract @relation(fields: [contractId], references: [id], onDelete: Cascade)
|
||||
token String @unique
|
||||
status ContractConfirmStatus @default(UNCONFIRMED)
|
||||
confirmedAt DateTime?
|
||||
confirmedIp String?
|
||||
expiresAt DateTime
|
||||
createdBy String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([orgId, status])
|
||||
}
|
||||
|
||||
// ========== AI 会话 & 审查记录 ==========
|
||||
|
||||
model AIConversation {
|
||||
id String @id @default(cuid())
|
||||
orgId String
|
||||
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
|
||||
userId String
|
||||
title String @default("新对话")
|
||||
messages Json // [{ role, content }]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([orgId, userId])
|
||||
}
|
||||
|
||||
model AIReviewRecord {
|
||||
id String @id @default(cuid())
|
||||
orgId String
|
||||
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
|
||||
employeeId String?
|
||||
employee Employee? @relation(fields: [employeeId], references: [id], onDelete: SetNull)
|
||||
type String // REVIEW=合同审查, CASE=案例匹配
|
||||
input String // 用户输入的合同文本或争议情形
|
||||
result String // AI 返回的审查/分析结果
|
||||
createdBy String
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([orgId, employeeId])
|
||||
}
|
||||
|
||||
// ========== RAG 知识库 ==========
|
||||
|
||||
model RagKnowledge {
|
||||
id String @id
|
||||
title String
|
||||
content String
|
||||
source String
|
||||
category String
|
||||
embedding Unsupported("vector(1536)")?
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
@@index([category])
|
||||
@@map("rag_knowledge")
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
import prisma from '../src/lib/prisma'
|
||||
|
||||
const EID = 'cmrx61v6d001oqqcwb2pu2tih'
|
||||
const ORGID = 'cmrx61v3l0000qqcwo3dr3h95'
|
||||
const UID = 'cmrx61v5u0002qqcwqf4vlyth'
|
||||
|
||||
async function main() {
|
||||
// 加班记录
|
||||
const otMonths = [
|
||||
{ month: '2025-03', wh: 8, weh: 4, hh: 0, wp: 600, wep: 600, hp: 0, pay: 1200 },
|
||||
{ month: '2025-06', wh: 12, weh: 8, hh: 0, wp: 1200, wep: 1200, hp: 0, pay: 2400 },
|
||||
{ month: '2025-09', wh: 6, weh: 0, hh: 8, wp: 600, wep: 0, hp: 1200, pay: 1800 },
|
||||
]
|
||||
for (const o of otMonths) {
|
||||
const existing = await prisma.overtimeRecord.findUnique({ where: { employeeId_month: { employeeId: EID, month: o.month } } })
|
||||
if (!existing) {
|
||||
await prisma.overtimeRecord.create({ data: { orgId: ORGID, employeeId: EID, month: o.month, weekdayHours: o.wh, weekendHours: o.weh, holidayHours: o.hh, weekdayPay: o.wp, weekendPay: o.wep, holidayPay: o.hp, totalPay: o.pay } })
|
||||
}
|
||||
}
|
||||
console.log('加班记录: 完成')
|
||||
|
||||
// 违纪记录
|
||||
const discRecords = [
|
||||
{ violationDate: new Date('2025-05-12'), violationType: 'LATE', description: '月度迟到超过5次,影响团队考勤', severity: 'WARNING', action: 'ORAL_WARNING', actionDetail: '口头警告并谈话', employeeAck: true, ackDate: new Date('2025-05-13'), ackMethod: 'SIGN', witness: '王强' },
|
||||
{ violationDate: new Date('2025-09-20'), violationType: 'ABSENT', description: '未经请假擅自旷工1天', severity: 'SERIOUS', action: 'DEDUCTION', actionDetail: '扣款200元', employeeAck: true, ackDate: new Date('2025-09-21'), ackMethod: 'SIGN', witness: '王强' },
|
||||
]
|
||||
for (const d of discRecords) {
|
||||
const existing = await prisma.disciplinaryRecord.findFirst({ where: { employeeId: EID, violationDate: d.violationDate } })
|
||||
if (!existing) {
|
||||
await prisma.disciplinaryRecord.create({ data: { orgId: ORGID, employeeId: EID, createdBy: UID, ...d } })
|
||||
}
|
||||
}
|
||||
console.log('违纪记录: 完成')
|
||||
|
||||
// 考勤记录 - 最近10个工作日
|
||||
const attendance = [
|
||||
{ date: '2026-07-10', status: 'NORMAL', late: 0, early: 0 },
|
||||
{ date: '2026-07-11', status: 'NORMAL', late: 0, early: 0 },
|
||||
{ date: '2026-07-14', status: 'NORMAL', late: 0, early: 0 },
|
||||
{ date: '2026-07-15', status: 'NORMAL', late: 0, early: 0 },
|
||||
{ date: '2026-07-16', status: 'LATE', late: 25, early: 0 },
|
||||
{ date: '2026-07-17', status: 'NORMAL', late: 0, early: 0 },
|
||||
{ date: '2026-07-18', status: 'NORMAL', late: 0, early: 0 },
|
||||
{ date: '2026-07-21', status: 'NORMAL', late: 0, early: 0 },
|
||||
{ date: '2026-07-22', status: 'EARLY_LEAVE', late: 0, early: 30 },
|
||||
{ date: '2026-07-23', status: 'NORMAL', late: 0, early: 0 },
|
||||
]
|
||||
for (const a of attendance) {
|
||||
const existing = await prisma.attendanceRecord.findUnique({ where: { employeeId_date: { employeeId: EID, date: new Date(a.date) } } })
|
||||
if (!existing) {
|
||||
await prisma.attendanceRecord.create({ data: { orgId: ORGID, employeeId: EID, createdBy: UID, date: new Date(a.date), checkInTime: '09:00', checkOutTime: '18:00', status: a.status, lateMinutes: a.late, earlyMinutes: a.early, workHours: 8, overtimeHours: 0 } })
|
||||
}
|
||||
}
|
||||
console.log('考勤记录: 完成')
|
||||
|
||||
// 培训签收记录
|
||||
const trainings = [
|
||||
{ trainingDate: new Date('2025-03-15'), topic: '《员工手册》培训', content: '公司规章制度、考勤制度、奖惩条例', trainer: '赵敏', duration: 2, ackStatus: 'SIGNED', ackDate: new Date('2025-03-15'), remark: '新员工入职培训' },
|
||||
{ trainingDate: new Date('2025-06-20'), topic: '销售技巧与合规培训', content: '销售话术规范、客户信息保护、合同签订注意事项', trainer: '王强', duration: 4, ackStatus: 'SIGNED', ackDate: new Date('2025-06-20') },
|
||||
{ trainingDate: new Date('2026-01-10'), topic: '2026年度规章制度更新培训', content: '新版考勤制度、绩效考核办法、安全生产规范', trainer: '赵敏', duration: 3, ackStatus: 'PENDING', remark: '待员工签收确认' },
|
||||
]
|
||||
for (const t of trainings) {
|
||||
const existing = await prisma.trainingRecord.findFirst({ where: { employeeId: EID, trainingDate: t.trainingDate } })
|
||||
if (!existing) {
|
||||
await prisma.trainingRecord.create({ data: { orgId: ORGID, employeeId: EID, createdBy: UID, ...t } })
|
||||
}
|
||||
}
|
||||
console.log('培训记录: 完成')
|
||||
|
||||
// 绩效记录
|
||||
const performances = [
|
||||
{ period: '2025-Q1', score: 82, grade: 'B', result: 'QUALIFIED', summary: '销售业绩达标,客户维护良好,需提升新客户开发能力', improvementPlan: '', employeeAck: true, ackDate: new Date('2025-04-10'), reviewer: '王强' },
|
||||
{ period: '2025-Q2', score: 75, grade: 'B', result: 'QUALIFIED', summary: '业绩略有下滑,新客户开发不足,团队协作有待加强', improvementPlan: '', employeeAck: true, ackDate: new Date('2025-07-08'), reviewer: '王强' },
|
||||
{ period: '2025-Q3', score: 68, grade: 'C', result: 'NEED_IMPROVE', summary: '连续3个月未完成销售目标,客户投诉1次', improvementPlan: '调岗至客户维护岗,加强销售技巧培训1个月', employeeAck: true, ackDate: new Date('2025-10-15'), reviewer: '王强' },
|
||||
{ period: '2025-Q4', score: 78, grade: 'B', result: 'QUALIFIED', summary: '改进后业绩回升,客户满意度提升', improvementPlan: '', employeeAck: false, reviewer: '王强' },
|
||||
]
|
||||
for (const p of performances) {
|
||||
const existing = await prisma.performanceRecord.findUnique({ where: { employeeId_period: { employeeId: EID, period: p.period } } })
|
||||
if (!existing) {
|
||||
await prisma.performanceRecord.create({ data: { orgId: ORGID, employeeId: EID, createdBy: UID, ...p } })
|
||||
}
|
||||
}
|
||||
console.log('绩效记录: 完成')
|
||||
|
||||
// 附件
|
||||
const attachments = [
|
||||
{ fileName: '吴芳身份证扫描件.pdf', fileType: 'ID_CARD', fileUrl: 'data:application/pdf;base64,placeholder', fileSize: 102400 },
|
||||
{ fileName: '吴芳银行卡复印件.jpg', fileType: 'BANK_CARD', fileUrl: 'data:image/jpeg;base64,placeholder', fileSize: 51200 },
|
||||
{ fileName: '吴芳劳动合同扫描件.pdf', fileType: 'CONTRACT_SCAN', fileUrl: 'data:application/pdf;base64,placeholder', fileSize: 204800 },
|
||||
{ fileName: '吴芳学历证书.jpg', fileType: 'EDUCATION', fileUrl: 'data:image/jpeg;base64,placeholder', fileSize: 81920 },
|
||||
]
|
||||
for (const a of attachments) {
|
||||
const existing = await prisma.employeeAttachment.findFirst({ where: { employeeId: EID, fileName: a.fileName } })
|
||||
if (!existing) {
|
||||
await prisma.employeeAttachment.create({ data: { ...a, orgId: ORGID, employeeId: EID, uploadedBy: UID } })
|
||||
}
|
||||
}
|
||||
console.log('附件: 完成')
|
||||
|
||||
// 验证
|
||||
const emp = await prisma.employee.findFirst({
|
||||
where: { id: EID },
|
||||
include: { contracts: true, payslips: true, overtimeRecords: true, disciplinaryRecords: true, attendanceRecords: true, trainingRecords: true, performanceRecords: true, terminations: true, attachments: true }
|
||||
})
|
||||
if (emp) {
|
||||
console.log('--- 吴芳完整档案数据统计 ---')
|
||||
console.log('contracts:', emp.contracts.length)
|
||||
console.log('payslips:', emp.payslips.length)
|
||||
console.log('overtimeRecords:', emp.overtimeRecords.length)
|
||||
console.log('disciplinaryRecords:', emp.disciplinaryRecords.length)
|
||||
console.log('attendanceRecords:', emp.attendanceRecords.length)
|
||||
console.log('trainingRecords:', emp.trainingRecords.length)
|
||||
console.log('performanceRecords:', emp.performanceRecords.length)
|
||||
console.log('terminations:', emp.terminations.length)
|
||||
console.log('attachments:', emp.attachments.length)
|
||||
}
|
||||
await prisma.$disconnect()
|
||||
}
|
||||
|
||||
main().catch(console.error)
|
||||
@@ -0,0 +1,298 @@
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
import bcrypt from 'bcryptjs'
|
||||
import { encrypt } from '../src/lib/crypto'
|
||||
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
// 社保计算(与 payroll.service.ts 一致)
|
||||
function calcSocial(base: number, config: any) {
|
||||
const actualBase = Math.min(Math.max(base, config.baseMin), config.baseMax)
|
||||
const socialEmp = actualBase * (config.pensionEmp + config.medicalEmp + config.unemploymentEmp) / 100
|
||||
const socialOrg = actualBase * (config.pensionOrg + config.medicalOrg + config.unemploymentOrg + config.injuryOrg + config.maternityOrg) / 100
|
||||
return { socialEmp: Math.round(socialEmp * 100) / 100, socialOrg: Math.round(socialOrg * 100) / 100 }
|
||||
}
|
||||
function calcHousing(base: number, config: any) {
|
||||
const actualBase = Math.min(Math.max(base, config.baseMin), config.baseMax)
|
||||
const housingEmp = actualBase * config.housingEmp / 100
|
||||
const housingOrg = actualBase * config.housingOrg / 100
|
||||
return { housingEmp: Math.round(housingEmp * 100) / 100, housingOrg: Math.round(housingOrg * 100) / 100 }
|
||||
}
|
||||
function calcTax(taxableIncome: number): number {
|
||||
if (taxableIncome <= 0) return 0
|
||||
let tax = 0
|
||||
if (taxableIncome <= 36000) tax = taxableIncome * 0.03
|
||||
else if (taxableIncome <= 144000) tax = taxableIncome * 0.10 - 2520
|
||||
else if (taxableIncome <= 300000) tax = taxableIncome * 0.20 - 16920
|
||||
else if (taxableIncome <= 420000) tax = taxableIncome * 0.25 - 31920
|
||||
else if (taxableIncome <= 660000) tax = taxableIncome * 0.30 - 52920
|
||||
else if (taxableIncome <= 960000) tax = taxableIncome * 0.35 - 85920
|
||||
else tax = taxableIncome * 0.45 - 181920
|
||||
return Math.max(0, Math.round(tax * 100) / 100)
|
||||
}
|
||||
|
||||
// 9名员工完整数据
|
||||
const EMPLOYEES = [
|
||||
{ name: '张伟', gender: '男', dept: '技术部', phone: '13900000001', idCard: '310101199001011234', salary: 18000, hireDate: '2023-03-01', socialBase: 18000, housingBase: 18000, specialDeduction: 2000, contractType: 'FIXED', years: 3, probation: 3, probationSalary: 14400, bank: '工商银行', account: '6222021234567890001', emergency: '张父', emergencyPhone: '13800001001', address: '上海市浦东新区张江路100号' },
|
||||
{ name: '李娜', gender: '女', dept: '技术部', phone: '13900000002', idCard: '310102199203052345', salary: 15000, hireDate: '2023-06-15', socialBase: 15000, housingBase: 15000, specialDeduction: 1000, contractType: 'FIXED', years: 3, probation: 2, probationSalary: 12000, bank: '建设银行', account: '6227001234567890002', emergency: '李母', emergencyPhone: '13800001002', address: '上海市徐汇区漕河泾50号', pregnant: true },
|
||||
{ name: '王强', gender: '男', dept: '销售部', phone: '13900000003', idCard: '310103198812103456', salary: 12000, hireDate: '2024-01-10', socialBase: 12000, housingBase: 12000, specialDeduction: 3000, contractType: 'FIXED', years: 3, probation: 3, probationSalary: 9600, bank: '招商银行', account: '6225881234567890003', emergency: '王妻', emergencyPhone: '13800001003', address: '上海市闵行区莘庄路200号' },
|
||||
{ name: '赵敏', gender: '女', dept: '人事部', phone: '13900000004', idCard: '310104199506154567', salary: 10000, hireDate: '2022-09-01', socialBase: 10000, housingBase: 10000, specialDeduction: 1500, contractType: 'UNFIXED', years: 0, probation: 0, probationSalary: 0, bank: '农业银行', account: '6228481234567890004', emergency: '赵父', emergencyPhone: '13800001004', address: '上海市黄浦区南京东路300号' },
|
||||
{ name: '陈刚', gender: '男', dept: '销售部', phone: '13900000005', idCard: '310105199907205678', salary: 8000, hireDate: '2024-07-01', socialBase: 8000, housingBase: 8000, specialDeduction: 0, contractType: 'FIXED', years: 3, probation: 2, probationSalary: 6400, bank: '中国银行', account: '6217001234567890005', emergency: '陈母', emergencyPhone: '13800001005', address: '上海市杨浦区五角场400号' },
|
||||
{ name: '刘洋', gender: '男', dept: '技术部', phone: '13900000006', idCard: '310106198504016789', salary: 22000, hireDate: '2021-04-01', socialBase: 33891, housingBase: 33891, specialDeduction: 4000, contractType: 'UNFIXED', years: 0, probation: 0, probationSalary: 0, bank: '交通银行', account: '6222601234567890006', emergency: '刘妻', emergencyPhone: '13800001006', address: '上海市长宁区中山公园500号' },
|
||||
{ name: '周婷', gender: '女', dept: '财务部', phone: '13900000007', idCard: '310107199311157890', salary: 13000, hireDate: '2023-11-15', socialBase: 13000, housingBase: 13000, specialDeduction: 2500, contractType: 'FIXED', years: 3, probation: 2, probationSalary: 10400, bank: '浦发银行', account: '6225161234567890007', emergency: '周父', emergencyPhone: '13800001007', address: '上海市静安区南京西路600号' },
|
||||
{ name: '孙磊', gender: '男', dept: '技术部', phone: '13900000008', idCard: '310108199008018901', salary: 16000, hireDate: '2022-06-01', socialBase: 16000, housingBase: 16000, specialDeduction: 1000, contractType: 'FIXED', years: 3, probation: 3, probationSalary: 12800, bank: '民生银行', account: '6226161234567890008', emergency: '孙母', emergencyPhone: '13800001008', address: '上海市虹口区四川北路700号' },
|
||||
{ name: '吴芳', gender: '女', dept: '销售部', phone: '13900000009', idCard: '310109199702159012', salary: 9000, hireDate: '2025-02-15', socialBase: 9000, housingBase: 9000, specialDeduction: 500, contractType: 'FIXED', years: 3, probation: 2, probationSalary: 7200, bank: '光大银行', account: '6226621234567890009', emergency: '吴夫', emergencyPhone: '13800001009', address: '上海市宝山区牡丹江路800号' },
|
||||
]
|
||||
|
||||
async function main() {
|
||||
// 1. 清空所有数据(按依赖顺序删除)
|
||||
console.log('清空现有数据...')
|
||||
await prisma.notificationLog.deleteMany()
|
||||
await prisma.auditLog.deleteMany()
|
||||
await prisma.batchEntry.deleteMany()
|
||||
await prisma.payrollBatch.deleteMany()
|
||||
await prisma.payslipItem.deleteMany()
|
||||
await prisma.salaryChangeRecord.deleteMany()
|
||||
await prisma.payslip.deleteMany()
|
||||
await prisma.overtimeRecord.deleteMany()
|
||||
await prisma.terminationRecord.deleteMany()
|
||||
await prisma.riskItem.deleteMany()
|
||||
await prisma.employeeAttachment.deleteMany()
|
||||
await prisma.disciplinaryRecord.deleteMany()
|
||||
await prisma.attendanceRecord.deleteMany()
|
||||
await prisma.trainingRecord.deleteMany()
|
||||
await prisma.performanceRecord.deleteMany()
|
||||
await prisma.laborContract.deleteMany()
|
||||
await prisma.contractConfirmLink.deleteMany()
|
||||
await prisma.onboardingLink.deleteMany()
|
||||
await prisma.employee.deleteMany()
|
||||
await prisma.socialInsuranceConfig.deleteMany()
|
||||
await prisma.notificationSetting.deleteMany()
|
||||
await prisma.user.deleteMany()
|
||||
await prisma.organization.deleteMany()
|
||||
console.log('数据已清空')
|
||||
|
||||
// 2. 创建企业
|
||||
const org = await prisma.organization.create({
|
||||
data: {
|
||||
name: '智云科技有限公司',
|
||||
plan: 'PRO',
|
||||
maxEmployees: 50,
|
||||
city: '上海',
|
||||
payrollFrequency: 1,
|
||||
},
|
||||
})
|
||||
console.log('企业已创建:', org.name)
|
||||
|
||||
// 3. 创建管理员
|
||||
const passwordHash = await bcrypt.hash('12345678', 10)
|
||||
const admin = await prisma.user.create({
|
||||
data: {
|
||||
orgId: org.id,
|
||||
phone: '13800000001',
|
||||
name: '管理员',
|
||||
passwordHash,
|
||||
role: 'ADMIN',
|
||||
},
|
||||
})
|
||||
console.log('管理员已创建:', admin.phone)
|
||||
|
||||
// 4. 创建社保配置(上海标准)
|
||||
await prisma.socialInsuranceConfig.create({
|
||||
data: {
|
||||
orgId: org.id,
|
||||
city: '上海',
|
||||
pensionOrg: 16,
|
||||
pensionEmp: 8,
|
||||
medicalOrg: 9.8,
|
||||
medicalEmp: 2,
|
||||
unemploymentOrg: 0.5,
|
||||
unemploymentEmp: 0.5,
|
||||
injuryOrg: 0.2,
|
||||
maternityOrg: 0.8,
|
||||
baseMin: 7384,
|
||||
baseMax: 36921,
|
||||
effectiveFrom: '2025-07',
|
||||
createdBy: admin.id,
|
||||
},
|
||||
})
|
||||
console.log('社保配置已创建')
|
||||
|
||||
// 4.5 创建公积金配置(上海标准)
|
||||
await prisma.housingFundConfig.create({
|
||||
data: {
|
||||
orgId: org.id,
|
||||
city: '上海',
|
||||
housingOrg: 7,
|
||||
housingEmp: 7,
|
||||
baseMin: 7384,
|
||||
baseMax: 36921,
|
||||
effectiveFrom: '2025-07',
|
||||
createdBy: admin.id,
|
||||
},
|
||||
})
|
||||
console.log('公积金配置已创建')
|
||||
|
||||
// 5. 创建通知设置
|
||||
await prisma.notificationSetting.create({
|
||||
data: {
|
||||
orgId: org.id,
|
||||
contractExpiry: true,
|
||||
expiryDays: 30,
|
||||
contractUnsigned: true,
|
||||
overtimeAlert: true,
|
||||
payslipReady: true,
|
||||
payrollDay: 10,
|
||||
socialInsDay: 15,
|
||||
housingFundDay: 15,
|
||||
taxDay: 15,
|
||||
},
|
||||
})
|
||||
|
||||
// 6. 创建薪酬模版(预置项)
|
||||
const defaultItems: { name: string; code: string; type: 'INPUT' | 'CALCULATED'; formula: string | null; order: number; isDefault: boolean; isEditable: boolean }[] = [
|
||||
{ name: '基本工资', code: 'baseSalary', type: 'INPUT', formula: null, order: 1, isDefault: true, isEditable: true },
|
||||
{ name: '加班费', code: 'overtimePay', type: 'CALCULATED', formula: 'weekdayOvertimePay + weekendOvertimePay + holidayOvertimePay', order: 2, isDefault: true, isEditable: false },
|
||||
{ name: '津贴补贴', code: 'allowance', type: 'INPUT', formula: null, order: 3, isDefault: true, isEditable: true },
|
||||
{ name: '奖金', code: 'bonus', type: 'INPUT', formula: null, order: 4, isDefault: true, isEditable: true },
|
||||
{ name: '扣款', code: 'deduction', type: 'INPUT', formula: null, order: 5, isDefault: true, isEditable: true },
|
||||
{ name: '应发合计', code: 'totalPay', type: 'CALCULATED', formula: 'baseSalary + overtimePay + allowance + bonus - deduction', order: 6, isDefault: true, isEditable: false },
|
||||
{ name: '个人社保', code: 'socialEmp', type: 'CALCULATED', formula: 'SOCIAL_EMP', order: 7, isDefault: true, isEditable: false },
|
||||
{ name: '个人公积金', code: 'housingEmp', type: 'CALCULATED', formula: 'HOUSING_EMP', order: 8, isDefault: true, isEditable: false },
|
||||
{ name: '个人所得税', code: 'tax', type: 'CALCULATED', formula: 'TAX', order: 9, isDefault: true, isEditable: false },
|
||||
{ name: '实发工资', code: 'netPay', type: 'CALCULATED', formula: 'totalPay - socialEmp - housingEmp - tax', order: 10, isDefault: true, isEditable: false },
|
||||
]
|
||||
for (const item of defaultItems) {
|
||||
await prisma.payslipItem.create({
|
||||
data: { orgId: org.id, ...item },
|
||||
})
|
||||
}
|
||||
console.log('薪酬模版已创建')
|
||||
|
||||
// 7. 创建9名员工 + 合同
|
||||
for (let i = 0; i < EMPLOYEES.length; i++) {
|
||||
const e = EMPLOYEES[i]
|
||||
const emp = await prisma.employee.create({
|
||||
data: {
|
||||
orgId: org.id,
|
||||
name: e.name,
|
||||
department: e.dept,
|
||||
hireDate: new Date(e.hireDate),
|
||||
monthlySalary: encrypt(String(e.salary)),
|
||||
phone: e.phone,
|
||||
idCardNumber: encrypt(e.idCard),
|
||||
gender: e.gender,
|
||||
socialInsBase: e.socialBase,
|
||||
housingFundBase: e.housingBase,
|
||||
specialDeduction: e.specialDeduction,
|
||||
bankName: e.bank,
|
||||
bankAccount: encrypt(e.account),
|
||||
emergencyContact: e.emergency,
|
||||
emergencyPhone: e.emergencyPhone,
|
||||
address: e.address,
|
||||
isPregnant: e.pregnant || false,
|
||||
createdBy: admin.id,
|
||||
},
|
||||
})
|
||||
|
||||
// 创建合同
|
||||
const startDate = new Date(e.hireDate)
|
||||
const endDate = e.contractType === 'FIXED'
|
||||
? new Date(startDate.getFullYear() + e.years, startDate.getMonth(), startDate.getDate() - 1)
|
||||
: null
|
||||
|
||||
await prisma.laborContract.create({
|
||||
data: {
|
||||
orgId: org.id,
|
||||
employeeId: emp.id,
|
||||
signDate: new Date(e.hireDate),
|
||||
startDate,
|
||||
endDate,
|
||||
contractType: e.contractType as any,
|
||||
signMethod: 'PAPER',
|
||||
contractYears: e.years,
|
||||
probationMonths: e.probation,
|
||||
probationSalary: e.probationSalary,
|
||||
createdBy: admin.id,
|
||||
},
|
||||
})
|
||||
console.log(`员工 ${i + 1}/9 已创建: ${e.name} - ${e.dept} - ¥${e.salary}/月`)
|
||||
}
|
||||
|
||||
// 8. 生成 1-6 月历史工资条(已发布),使 7 月累计预扣个税有 YTD 数据
|
||||
console.log('\n生成 1-6 月历史工资条...')
|
||||
const socialConfig = await prisma.socialInsuranceConfig.findFirst({ where: { orgId: org.id, isCurrent: true } })
|
||||
const housingConfig = await prisma.housingFundConfig.findFirst({ where: { orgId: org.id, isCurrent: true } })
|
||||
const allEmployees = await prisma.employee.findMany({ where: { orgId: org.id } })
|
||||
|
||||
for (const emp of allEmployees) {
|
||||
// 跳过 2026 年之后入职的员工
|
||||
const hireYear = emp.hireDate.getFullYear()
|
||||
if (hireYear > 2026) continue
|
||||
const hireMonth = hireYear === 2026 ? emp.hireDate.getMonth() + 1 : 1
|
||||
|
||||
let ytdIncome = 0, ytdSocialEmp = 0, ytdHousingEmp = 0, ytdTaxDeducted = 0
|
||||
|
||||
for (let m = 1; m <= 6; m++) {
|
||||
if (m < hireMonth) continue
|
||||
const monthStr = `2026-${String(m).padStart(2, '0')}`
|
||||
const baseSalary = emp.socialInsBase || 0 // 用社保基数作为基本工资(简化)
|
||||
const social = calcSocial(emp.socialInsBase || baseSalary, socialConfig)
|
||||
const housing = calcHousing(emp.housingFundBase || baseSalary, housingConfig || socialConfig)
|
||||
const totalPay = baseSalary
|
||||
const specialDeduction = emp.specialDeduction * m
|
||||
|
||||
ytdIncome += totalPay
|
||||
ytdSocialEmp += social.socialEmp
|
||||
ytdHousingEmp += housing.housingEmp
|
||||
|
||||
const ytdTaxableIncome = Math.max(0, ytdIncome - 5000 * m - ytdSocialEmp - ytdHousingEmp - specialDeduction)
|
||||
const ytdTax = calcTax(ytdTaxableIncome)
|
||||
const monthTax = Math.max(0, Math.round((ytdTax - ytdTaxDeducted) * 100) / 100)
|
||||
ytdTaxDeducted += monthTax
|
||||
|
||||
const netPay = Math.round((totalPay - social.socialEmp - housing.housingEmp - monthTax) * 100) / 100
|
||||
|
||||
await prisma.payslip.create({
|
||||
data: {
|
||||
org: { connect: { id: org.id } },
|
||||
employee: { connect: { id: emp.id } },
|
||||
month: monthStr,
|
||||
baseSalary,
|
||||
overtimePay: 0,
|
||||
allowance: 0,
|
||||
deduction: 0,
|
||||
bonus: 0,
|
||||
totalPay,
|
||||
socialEmp: social.socialEmp,
|
||||
housingEmp: housing.housingEmp,
|
||||
tax: monthTax,
|
||||
netPay,
|
||||
ytdIncome,
|
||||
ytdTaxDeducted,
|
||||
ytdSocialEmp,
|
||||
ytdHousingEmp,
|
||||
status: 'PUBLISHED',
|
||||
publishedAt: new Date(`${monthStr}-10T10:00:00Z`),
|
||||
confirmedAt: new Date(`${monthStr}-12T10:00:00Z`),
|
||||
},
|
||||
})
|
||||
}
|
||||
console.log(` ${emp.name}: 1-6月工资条已生成`)
|
||||
}
|
||||
|
||||
console.log('\n===== 示例数据创建完成 =====')
|
||||
console.log(`企业: ${org.name}`)
|
||||
console.log(`管理员: 13800000001 / 密码: 12345678`)
|
||||
console.log(`员工: ${EMPLOYEES.length} 人`)
|
||||
console.log('社保配置: 上海标准')
|
||||
console.log('薪酬模版: 10项预置')
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error(e)
|
||||
process.exit(1)
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect()
|
||||
})
|
||||
Reference in New Issue
Block a user