数据库表和后端初步搭建

This commit is contained in:
Your Name 2025-07-29 22:04:06 +08:00
parent ee3b1228dd
commit 9c64dc8dd4
14 changed files with 5283 additions and 13 deletions

View File

@ -0,0 +1,337 @@
import { Prisma } from "@fenghuo/db";
import { BaseService } from "../base/base.service";
import { prisma } from '@fenghuo/db';
import { ObjectType, FamilyRelation, ApplicationStatus } from "@fenghuo/common";
import { z } from "zod"; // 添加 z 导入
import { CreateApplicationSchema, ApprovalSchema } from './accommodation.trpc';
/**
*
*/
export class AccommodationService extends BaseService<Prisma.ApplicationDelegate> {
constructor() {
super(prisma, ObjectType.APPLICATION as any)
}
/**
*
*/
async createApplication(params: z.infer<typeof CreateApplicationSchema>) {
try {
return await this.prisma.$transaction(async (tx) => {
// 1. 根据申请人信息查找匹配的 Profile
const matchedProfile = await this.findMatchingProfile(params);
// 2. 创建申请记录
const application = await tx.application.create({
data: {
// 申请人信息
applicantName: params.applicantName,
applicantDuty: params.applicantDuty,
applicantBirthday: params.applicantBirthday,
applicantHireDate: params.applicantHireDate,
applicantMarriageDate: params.applicantMarriageDate,
applicantPhone: params.applicantPhone,
// 关联到 Profile如果找到匹配的
profileId: matchedProfile?.id || null,
// 申请信息
applyDays: params.applyDays,
plannedCheckIn: params.plannedCheckIn,
plannedCheckOut: params.plannedCheckOut,
reason: params.reason,
status: ApplicationStatus.PENDING,
// 嵌套创建家属信息
familyMembers: {
create: params.familyMembers.map(member => ({
name: member.name,
birthDate: member.birthDate,
relation: member.relation,
nativePlace: member.nativePlace,
employer: member.employer,
address: member.address
}))
}
},
include: {
familyMembers: true,
profile: {
include: {
organization: true
}
}
}
});
return application;
});
} catch (error) {
throw new Error(`创建申请失败: ${error.message}`);
}
}
/**
*
*/
async approveApplication(params: z.infer<typeof ApprovalSchema>) {
try {
const { applicationId, role, opinion, status } = params;
// 构建更新数据
const updateData: Prisma.ApplicationUpdateInput = {};
switch (role) {
case 'group':
updateData.groupOpinion = opinion;
break;
case 'hr':
updateData.hrOpinion = opinion;
break;
case 'barracks':
updateData.barracksOpinion = opinion;
break;
case 'guarantee':
updateData.guaranteeOpinion = opinion;
break;
case 'leader':
updateData.leaderOpinion = opinion;
if (status) {
updateData.status = status;
}
break;
default:
throw new Error('无效的审批角色');
}
const application = await this.prisma.application.update({
where: { id: applicationId },
data: updateData,
include: {
familyMembers: true,
profile: {
include: {
organization: true
}
}
}
});
return application;
} catch (error) {
throw new Error(`审批申请失败: ${error.message}`);
}
}
/**
*
*/
async getApplicationStats(profileId: string, year: number) {
try {
const startDate = new Date(year, 0, 1);
const endDate = new Date(year + 1, 0, 1);
const applications = await this.prisma.application.findMany({
where: {
profileId,
status: ApplicationStatus.APPROVED,
createdAt: {
gte: startDate,
lt: endDate
},
deletedAt: null
},
select: {
applyDays: true,
plannedCheckIn: true,
plannedCheckOut: true
}
});
const totalDays = applications.reduce((sum, app) => sum + app.applyDays, 0);
return {
totalApplications: applications.length,
totalDays,
applications: applications.map(app => ({
days: app.applyDays,
checkIn: app.plannedCheckIn,
checkOut: app.plannedCheckOut
}))
};
} catch (error) {
throw new Error(`获取统计信息失败: ${error.message}`);
}
}
/**
*
*/
async getApplicationById(id: string) {
try {
const application = await this.prisma.application.findUnique({
where: { id },
include: {
familyMembers: true,
profile: {
include: {
organization: true
}
}
}
});
if (!application) {
throw new Error('申请不存在');
}
return application;
} catch (error) {
throw new Error(`获取申请详情失败: ${error.message}`);
}
}
/**
*
*/
async getApplicationsWithPagination(params: {
page?: number;
pageSize?: number;
where?: Prisma.ApplicationWhereInput;
orderBy?: Prisma.ApplicationOrderByWithRelationInput;
}) {
try {
const result = await this.findManyWithPagination(params);
const itemsWithRelations = await Promise.all(
result.items.map(async (item) => {
return await this.prisma.application.findUnique({
where: { id: item.id },
include: {
familyMembers: true,
profile: {
select: {
id: true,
name: true,
organization: {
select: {
id: true,
name: true
}
}
}
}
}
});
})
);
return {
...result,
items: itemsWithRelations
};
} catch (error) {
throw new Error(`获取申请列表失败: ${error.message}`);
}
}
/**
*
*/
async deleteApplication(id: string) {
try {
return await this.softDeleteByIds([id]);
} catch (error) {
throw new Error(`删除申请失败: ${error.message}`);
}
}
/**
*
*/
async deleteApplications(ids: string[]) {
try {
return await this.softDeleteByIds(ids);
} catch (error) {
throw new Error(`批量删除申请失败: ${error.message}`);
}
}
/**
*
*/
async restoreApplication(id: string) {
try {
return await this.restoreByIds([id]);
} catch (error) {
throw new Error(`恢复申请失败: ${error.message}`);
}
}
/**
*
*/
async updateApplication(id: string, data: Prisma.ApplicationUpdateInput) {
try {
return await this.updateById(id, data);
} catch (error) {
throw new Error(`更新申请失败: ${error.message}`);
}
}
/**
* Profile
* + -> ->
*/
private async findMatchingProfile(params: z.infer<typeof CreateApplicationSchema>) {
if (!params.applicantName || !params.applicantDuty) {
return null;
}
try {
// 第一步:用姓名和职别进行匹配
const whereConditions: Prisma.ProfileWhereInput = {
name: params.applicantName,
dutyName: params.applicantDuty, // 使用职别名称匹配
deletedAt: null
};
const matchingProfiles = await this.prisma.profile.findMany({
where: whereConditions,
include: {
organization: true
}
});
// 如果只有一个匹配结果,直接返回
if (matchingProfiles.length === 1) {
return matchingProfiles[0];
}
// 如果有多个匹配结果,用出生年月进一步筛选
if (matchingProfiles.length > 1 && params.applicantBirthday) {
const exactMatch = matchingProfiles.find(profile =>
profile.birthday &&
profile.birthday.getTime() === params.applicantBirthday!.getTime()
);
if (exactMatch) {
return exactMatch;
}
}
// 如果仍然有多个结果或没有匹配,返回第一个(或者返回 null
return matchingProfiles.length > 0 ? matchingProfiles[0] : null;
} catch (error) {
// 匹配失败不影响申请创建,返回 null
console.warn('Profile 匹配失败:', error);
return null;
}
}
}
// 导出服务实例
export const accommodationService = new AccommodationService();

View File

@ -0,0 +1,201 @@
import { accommodationService } from './accommodation.service';
import { protectedProcedure, publicProcedure, router } from "../../trpc/base";
import { z, ZodType } from "zod";
import { Prisma } from "@fenghuo/db";
import { FamilyRelation, ApplicationStatus } from "@fenghuo/common";
// Prisma 类型的 Zod Schema
const ApplicationCreateArgsSchema: ZodType<Prisma.ApplicationCreateArgs> = z.any();
const ApplicationUpdateArgsSchema: ZodType<Prisma.ApplicationUpdateArgs> = z.any();
const ApplicationUpdateInputSchema: ZodType<Prisma.ApplicationUpdateInput> = z.any();
const ApplicationWhereInputSchema: ZodType<Prisma.ApplicationWhereInput> = z.any();
const ApplicationSelectSchema: ZodType<Prisma.ApplicationSelect> = z.any();
const ApplicationOrderByWithRelationInputSchema: ZodType<Prisma.ApplicationOrderByWithRelationInput> = z.any();
const ApplicationIncludeSchema: ZodType<Prisma.ApplicationInclude> = z.any();
// 自定义输入验证 Schema
export const CreateApplicationSchema = z.object({
// 申请人信息
applicantName: z.string().min(1, '申请人姓名不能为空'),
applicantDuty: z.string().min(1, '申请人职别不能为空'),
applicantBirthday: z.date().optional(),
applicantHireDate: z.date().optional(),
applicantMarriageDate: z.date().optional(),
applicantPhone: z.string().optional(),
applicantIdNum: z.string().optional(),
// 家属信息
familyMembers: z.array(z.object({
name: z.string().min(1, '家属姓名不能为空'),
birthDate: z.date().optional(),
relation: z.enum([
FamilyRelation.SPOUSE,
FamilyRelation.CHILD,
FamilyRelation.PARENT,
FamilyRelation.OTHER
]),
nativePlace: z.string().optional(),
employer: z.string().optional(),
address: z.string().optional()
})).min(1, '至少需要一个家属信息'),
// 申请信息
applyDays: z.number().min(1, '申请天数必须大于0'),
plannedCheckIn: z.date(),
plannedCheckOut: z.date(),
reason: z.string().optional()
}).refine(data => data.plannedCheckOut > data.plannedCheckIn, {
message: '退房时间必须晚于入住时间',
path: ['plannedCheckOut']
});
export const ApprovalSchema = z.object({
applicationId: z.string().min(1, '申请ID不能为空'),
role: z.enum(['group', 'hr', 'barracks', 'guarantee', 'leader']),
opinion: z.string().min(1, '审批意见不能为空'),
status: z.enum([
ApplicationStatus.PENDING,
ApplicationStatus.APPROVED,
ApplicationStatus.REJECTED
]).optional()
});
const StatsQuerySchema = z.object({
profileId: z.string().min(1, 'Profile ID不能为空'),
year: z.number().min(2000).max(2100)
});
export const accommodationRouter = router({
// 创建申请
createApplication: protectedProcedure
.input(CreateApplicationSchema)
.mutation(async ({ input }) => {
return accommodationService.createApplication(input);
}),
// 获取申请详情
getApplicationById: protectedProcedure
.input(z.object({
id: z.string().min(1, '申请ID不能为空')
}))
.query(async ({ input }) => {
return accommodationService.getApplicationById(input.id);
}),
// 获取申请列表(带分页)
getApplications: protectedProcedure
.input(z.object({
page: z.number().optional(),
pageSize: z.number().optional(),
where: ApplicationWhereInputSchema.optional(),
orderBy: ApplicationOrderByWithRelationInputSchema.optional(),
}))
.query(async ({ input }) => {
return accommodationService.getApplicationsWithPagination(input);
}),
// 审批申请
approveApplication: protectedProcedure
.input(ApprovalSchema)
.mutation(async ({ input }) => {
return accommodationService.approveApplication(input);
}),
// 获取申请统计
getApplicationStats: protectedProcedure
.input(StatsQuerySchema)
.query(async ({ input }) => {
return accommodationService.getApplicationStats(input.profileId, input.year);
}),
// 更新申请
updateApplication: protectedProcedure
.input(z.object({
id: z.string().min(1, '申请ID不能为空'),
data: ApplicationUpdateInputSchema
}))
.mutation(async ({ input }) => {
return accommodationService.updateApplication(input.id, input.data);
}),
// 删除申请(软删除)
deleteApplication: protectedProcedure
.input(z.object({
id: z.string().min(1, '申请ID不能为空')
}))
.mutation(async ({ input }) => {
return accommodationService.deleteApplication(input.id);
}),
// 批量删除申请
deleteApplications: protectedProcedure
.input(z.object({
ids: z.array(z.string()).min(1, '至少选择一个申请')
}))
.mutation(async ({ input }) => {
return accommodationService.deleteApplications(input.ids);
}),
// 恢复申请
restoreApplication: protectedProcedure
.input(z.object({
id: z.string().min(1, '申请ID不能为空')
}))
.mutation(async ({ input }) => {
return accommodationService.restoreApplication(input.id);
}),
// 基础的 CRUD 操作(使用 Prisma 原生参数)
create: protectedProcedure
.input(ApplicationCreateArgsSchema)
.mutation(async ({ input }) => {
return accommodationService.create(input);
}),
update: protectedProcedure
.input(ApplicationUpdateArgsSchema)
.mutation(async ({ input }) => {
return accommodationService.update(input);
}),
findFirst: protectedProcedure
.input(z.object({
where: ApplicationWhereInputSchema.optional(),
select: ApplicationSelectSchema.optional(),
include: ApplicationIncludeSchema.optional(),
}))
.query(async ({ input }) => {
return accommodationService.findFirst(input);
}),
findManyWithPagination: protectedProcedure
.input(z.object({
page: z.number().optional(),
pageSize: z.number().optional(),
where: ApplicationWhereInputSchema.optional(),
select: ApplicationSelectSchema.optional(),
orderBy: ApplicationOrderByWithRelationInputSchema.optional(),
}))
.query(async ({ input }) => {
return accommodationService.findManyWithPagination(input);
}),
// 软删除相关操作
softDeleteByIds: protectedProcedure
.input(z.object({
ids: z.array(z.string()),
data: ApplicationUpdateInputSchema.nullish(),
}))
.mutation(async ({ input }) => {
return accommodationService.softDeleteByIds(input.ids, input.data);
}),
restoreByIds: protectedProcedure
.input(z.object({
ids: z.array(z.string()),
data: ApplicationUpdateInputSchema.nullish(),
}))
.mutation(async ({ input }) => {
return accommodationService.restoreByIds(input.ids, input.data);
}),
});

View File

@ -13,6 +13,8 @@ export enum ObjectType {
FILE_VERSION = 'file_version', FILE_VERSION = 'file_version',
PROFILE = 'profile', PROFILE = 'profile',
RESOURCE = 'resource', RESOURCE = 'resource',
APPLICATION = 'application',
ACCOMMODATION = 'accommodation',
} }
export enum UserActionType { export enum UserActionType {
@ -147,3 +149,27 @@ export enum FileVersionChangeType {
AUTO_SAVE = 'auto_save', AUTO_SAVE = 'auto_save',
COLLABORATION = 'collaboration' COLLABORATION = 'collaboration'
} }
// 家属关系枚举
export enum FamilyRelation {
SPOUSE = '配偶', // 配偶
CHILD = '子女', // 子女
PARENT = '父母', // 父母
OTHER = '其他' // 其他关系
}
// 申请状态枚举
export enum ApplicationStatus {
PENDING = 'pending', // 待审批
APPROVED = 'approved', // 已同意
REJECTED = 'rejected' // 已拒绝
}
// 审批角色枚举
export enum ApprovalRole {
GROUP = 'group', // 小组
HR = 'hr', // 人力科
BARRACKS = 'barracks', // 营房
GUARANTEE = 'guarantee', // 保障部
LEADER = 'leader' // 领导
}

File diff suppressed because one or more lines are too long

View File

@ -405,6 +405,46 @@ exports.Prisma.SsoProviderScalarFieldEnum = {
updatedAt: 'updatedAt' updatedAt: 'updatedAt'
}; };
exports.Prisma.FamilyMemberScalarFieldEnum = {
id: 'id',
name: 'name',
birthDate: 'birthDate',
relation: 'relation',
nativePlace: 'nativePlace',
employer: 'employer',
address: 'address',
applicationId: 'applicationId',
createdAt: 'createdAt',
updatedAt: 'updatedAt',
deletedAt: 'deletedAt'
};
exports.Prisma.ApplicationScalarFieldEnum = {
id: 'id',
applicantName: 'applicantName',
applicantDuty: 'applicantDuty',
applicantBirthday: 'applicantBirthday',
applicantHireDate: 'applicantHireDate',
applicantMarriageDate: 'applicantMarriageDate',
applicantPhone: 'applicantPhone',
profileId: 'profileId',
applyDays: 'applyDays',
plannedCheckIn: 'plannedCheckIn',
plannedCheckOut: 'plannedCheckOut',
reason: 'reason',
usedDaysThisYear: 'usedDaysThisYear',
remainingDays: 'remainingDays',
groupOpinion: 'groupOpinion',
hrOpinion: 'hrOpinion',
barracksOpinion: 'barracksOpinion',
guaranteeOpinion: 'guaranteeOpinion',
leaderOpinion: 'leaderOpinion',
status: 'status',
createdAt: 'createdAt',
updatedAt: 'updatedAt',
deletedAt: 'deletedAt'
};
exports.Prisma.SortOrder = { exports.Prisma.SortOrder = {
asc: 'asc', asc: 'asc',
desc: 'desc' desc: 'desc'
@ -453,7 +493,9 @@ exports.Prisma.ModelName = {
oauthApplication: 'oauthApplication', oauthApplication: 'oauthApplication',
oauthAccessToken: 'oauthAccessToken', oauthAccessToken: 'oauthAccessToken',
oauthConsent: 'oauthConsent', oauthConsent: 'oauthConsent',
ssoProvider: 'ssoProvider' ssoProvider: 'ssoProvider',
FamilyMember: 'FamilyMember',
Application: 'Application'
}; };
/** /**

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
{ {
"name": "prisma-client-e6300ea92a36b9e59626dd91d6c26072231c307b248909be09ae2c1e4aaf4b03", "name": "prisma-client-d4221045c8bd0b0530fb2223c54e752adff39e629694496fc8f8784c88fb8239",
"main": "index.js", "main": "index.js",
"types": "index.d.ts", "types": "index.d.ts",
"browser": "index-browser.js", "browser": "index-browser.js",

View File

@ -304,6 +304,9 @@ model Profile {
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
deletedAt DateTime? deletedAt DateTime?
// 关联关系 - 临时住房申请相关
applications Application[]
@@index([organizationId, deletedAt]) // 组织人员查询优化 @@index([organizationId, deletedAt]) // 组织人员查询优化
@@index([hireDate]) @@index([hireDate])
@@index([level]) @@index([level])
@ -563,3 +566,74 @@ model ssoProvider {
@@index([issuer]) @@index([issuer])
@@map("sso_providers") @@map("sso_providers")
} }
// ===临时住房申请系统===
// 家属信息表
model FamilyMember {
id String @id @default(cuid())
name String // 家属姓名
birthDate DateTime? @map("birth_date") // 出生年月
relation String // 与申请人关系(配偶、子女、父母、其他)
nativePlace String? @map("native_place") // 籍贯
employer String? // 工作单位
address String? // 实际居住地
// 关联关系 - 直接关联申请表
applicationId String @map("application_id") // 关联的申请ID
application Application @relation(fields: [applicationId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
@@index([applicationId, deletedAt]) // 申请家属查询优化
@@index([relation]) // 关系类型查询优化
@@map("family_members")
}
// 临时住房申请表
model Application {
id String @id @default(cuid())
// 申请人信息(冗余存储,用于审计)
applicantName String @map("applicant_name") // 申请人姓名
applicantDuty String @map("applicant_duty") // 申请人职别
applicantBirthday DateTime? @map("applicant_birthday") // 申请人出生年月
applicantHireDate DateTime? @map("applicant_hire_date") // 申请人入职时间
applicantMarriageDate DateTime? @map("applicant_marriage_date") // 申请人结婚时间
applicantPhone String? @map("applicant_phone") // 申请人联系方式
// 关联到 Profile用于数据管理和统计
profileId String? @map("profile_id") // 关联的员工ID可选
profile Profile? @relation(fields: [profileId], references: [id], onDelete: Cascade)
// 申请信息
applyDays Int @map("apply_days") // 申请住用天数
plannedCheckIn DateTime @map("planned_check_in") // 计划入住时间
plannedCheckOut DateTime @map("planned_check_out") // 计划退房时间
reason String? // 申请理由
usedDaysThisYear Int @default(0) @map("used_days_this_year") // 本年度已住用天数
remainingDays Int @default(0) @map("remaining_days") // 剩余住用天数
// 审批意见
groupOpinion String? @map("group_opinion") // yinlian意见
hrOpinion String? @map("hr_opinion") // 人力科意见
barracksOpinion String? @map("barracks_opinion") // 营房意见
guaranteeOpinion String? @map("guarantee_opinion") // 保障部意见
leaderOpinion String? @map("leader_opinion") // 领导意见
status String @default("pending") // 申请状态:待审批、已同意、已拒绝
// 关联关系
familyMembers FamilyMember[] // 该申请的所有家属
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
@@index([profileId, deletedAt]) // 员工申请查询优化
@@index([status, deletedAt]) // 状态查询优化
@@index([plannedCheckIn, plannedCheckOut]) // 时间范围查询优化
@@index([applicantName]) // 申请人姓名查询优化
@@map("applications")
}

View File

@ -405,6 +405,46 @@ exports.Prisma.SsoProviderScalarFieldEnum = {
updatedAt: 'updatedAt' updatedAt: 'updatedAt'
}; };
exports.Prisma.FamilyMemberScalarFieldEnum = {
id: 'id',
name: 'name',
birthDate: 'birthDate',
relation: 'relation',
nativePlace: 'nativePlace',
employer: 'employer',
address: 'address',
applicationId: 'applicationId',
createdAt: 'createdAt',
updatedAt: 'updatedAt',
deletedAt: 'deletedAt'
};
exports.Prisma.ApplicationScalarFieldEnum = {
id: 'id',
applicantName: 'applicantName',
applicantDuty: 'applicantDuty',
applicantBirthday: 'applicantBirthday',
applicantHireDate: 'applicantHireDate',
applicantMarriageDate: 'applicantMarriageDate',
applicantPhone: 'applicantPhone',
profileId: 'profileId',
applyDays: 'applyDays',
plannedCheckIn: 'plannedCheckIn',
plannedCheckOut: 'plannedCheckOut',
reason: 'reason',
usedDaysThisYear: 'usedDaysThisYear',
remainingDays: 'remainingDays',
groupOpinion: 'groupOpinion',
hrOpinion: 'hrOpinion',
barracksOpinion: 'barracksOpinion',
guaranteeOpinion: 'guaranteeOpinion',
leaderOpinion: 'leaderOpinion',
status: 'status',
createdAt: 'createdAt',
updatedAt: 'updatedAt',
deletedAt: 'deletedAt'
};
exports.Prisma.SortOrder = { exports.Prisma.SortOrder = {
asc: 'asc', asc: 'asc',
desc: 'desc' desc: 'desc'
@ -453,7 +493,9 @@ exports.Prisma.ModelName = {
oauthApplication: 'oauthApplication', oauthApplication: 'oauthApplication',
oauthAccessToken: 'oauthAccessToken', oauthAccessToken: 'oauthAccessToken',
oauthConsent: 'oauthConsent', oauthConsent: 'oauthConsent',
ssoProvider: 'ssoProvider' ssoProvider: 'ssoProvider',
FamilyMember: 'FamilyMember',
Application: 'Application'
}; };
/** /**

View File

@ -0,0 +1,74 @@
-- AlterTable
ALTER TABLE "profiles" ADD COLUMN "marriageDate" TIMESTAMP(3),
ADD COLUMN "phone" TEXT;
-- CreateTable
CREATE TABLE "family_members" (
"id" TEXT NOT NULL,
"profile_id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"birthDate" TIMESTAMP(3),
"relation" TEXT NOT NULL,
"nativePlace" TEXT,
"employer" TEXT,
"address" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3),
CONSTRAINT "family_members_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "applications" (
"id" TEXT NOT NULL,
"profile_id" TEXT NOT NULL,
"family_member_id" TEXT NOT NULL,
"applyDays" INTEGER NOT NULL,
"plannedCheckIn" TIMESTAMP(3) NOT NULL,
"plannedCheckOut" TIMESTAMP(3) NOT NULL,
"reason" TEXT,
"usedDaysThisYear" INTEGER NOT NULL DEFAULT 0,
"remainingDays" INTEGER NOT NULL DEFAULT 0,
"groupOpinion" TEXT,
"hrOpinion" TEXT,
"barracksOpinion" TEXT,
"guaranteeOpinion" TEXT,
"leaderOpinion" TEXT,
"status" TEXT NOT NULL DEFAULT 'pending',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3),
CONSTRAINT "applications_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "family_members_profile_id_deletedAt_idx" ON "family_members"("profile_id", "deletedAt");
-- CreateIndex
CREATE INDEX "family_members_relation_idx" ON "family_members"("relation");
-- CreateIndex
CREATE INDEX "applications_profile_id_deletedAt_idx" ON "applications"("profile_id", "deletedAt");
-- CreateIndex
CREATE INDEX "applications_family_member_id_deletedAt_idx" ON "applications"("family_member_id", "deletedAt");
-- CreateIndex
CREATE INDEX "applications_status_deletedAt_idx" ON "applications"("status", "deletedAt");
-- CreateIndex
CREATE INDEX "applications_plannedCheckIn_plannedCheckOut_idx" ON "applications"("plannedCheckIn", "plannedCheckOut");
-- CreateIndex
CREATE INDEX "profiles_phone_idx" ON "profiles"("phone");
-- AddForeignKey
ALTER TABLE "family_members" ADD CONSTRAINT "family_members_profile_id_fkey" FOREIGN KEY ("profile_id") REFERENCES "profiles"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "applications" ADD CONSTRAINT "applications_profile_id_fkey" FOREIGN KEY ("profile_id") REFERENCES "profiles"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "applications" ADD CONSTRAINT "applications_family_member_id_fkey" FOREIGN KEY ("family_member_id") REFERENCES "family_members"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -0,0 +1,49 @@
/*
Warnings:
- You are about to drop the column `applyDays` on the `applications` table. All the data in the column will be lost.
- You are about to drop the column `barracksOpinion` on the `applications` table. All the data in the column will be lost.
- You are about to drop the column `groupOpinion` on the `applications` table. All the data in the column will be lost.
- You are about to drop the column `guaranteeOpinion` on the `applications` table. All the data in the column will be lost.
- You are about to drop the column `hrOpinion` on the `applications` table. All the data in the column will be lost.
- You are about to drop the column `plannedCheckIn` on the `applications` table. All the data in the column will be lost.
- You are about to drop the column `plannedCheckOut` on the `applications` table. All the data in the column will be lost.
- You are about to drop the column `birthDate` on the `family_members` table. All the data in the column will be lost.
- You are about to drop the column `nativePlace` on the `family_members` table. All the data in the column will be lost.
- You are about to drop the column `marriageDate` on the `profiles` table. All the data in the column will be lost.
- Added the required column `apply_days` to the `applications` table without a default value. This is not possible if the table is not empty.
- Added the required column `planned_check_in` to the `applications` table without a default value. This is not possible if the table is not empty.
- Added the required column `planned_check_out` to the `applications` table without a default value. This is not possible if the table is not empty.
*/
-- DropIndex
DROP INDEX "applications_plannedCheckIn_plannedCheckOut_idx";
-- AlterTable
ALTER TABLE "applications" DROP COLUMN "applyDays",
DROP COLUMN "barracksOpinion",
DROP COLUMN "groupOpinion",
DROP COLUMN "guaranteeOpinion",
DROP COLUMN "hrOpinion",
DROP COLUMN "plannedCheckIn",
DROP COLUMN "plannedCheckOut",
ADD COLUMN "apply_days" INTEGER NOT NULL,
ADD COLUMN "barracks_opinion" TEXT,
ADD COLUMN "group_opinion" TEXT,
ADD COLUMN "guarantee_opinion" TEXT,
ADD COLUMN "hr_opinion" TEXT,
ADD COLUMN "planned_check_in" TIMESTAMP(3) NOT NULL,
ADD COLUMN "planned_check_out" TIMESTAMP(3) NOT NULL;
-- AlterTable
ALTER TABLE "family_members" DROP COLUMN "birthDate",
DROP COLUMN "nativePlace",
ADD COLUMN "birth_date" TIMESTAMP(3),
ADD COLUMN "native_place" TEXT;
-- AlterTable
ALTER TABLE "profiles" DROP COLUMN "marriageDate",
ADD COLUMN "marriage_date" TIMESTAMP(3);
-- CreateIndex
CREATE INDEX "applications_planned_check_in_planned_check_out_idx" ON "applications"("planned_check_in", "planned_check_out");

View File

@ -0,0 +1,62 @@
/*
Warnings:
- You are about to drop the column `family_member_id` on the `applications` table. All the data in the column will be lost.
- You are about to drop the column `leaderOpinion` on the `applications` table. All the data in the column will be lost.
- You are about to drop the column `remainingDays` on the `applications` table. All the data in the column will be lost.
- You are about to drop the column `usedDaysThisYear` on the `applications` table. All the data in the column will be lost.
- You are about to drop the column `profile_id` on the `family_members` table. All the data in the column will be lost.
- You are about to drop the column `marriage_date` on the `profiles` table. All the data in the column will be lost.
- You are about to drop the column `phone` on the `profiles` table. All the data in the column will be lost.
- Added the required column `applicant_duty` to the `applications` table without a default value. This is not possible if the table is not empty.
- Added the required column `applicant_name` to the `applications` table without a default value. This is not possible if the table is not empty.
- Added the required column `application_id` to the `family_members` table without a default value. This is not possible if the table is not empty.
*/
-- DropForeignKey
ALTER TABLE "applications" DROP CONSTRAINT "applications_family_member_id_fkey";
-- DropForeignKey
ALTER TABLE "family_members" DROP CONSTRAINT "family_members_profile_id_fkey";
-- DropIndex
DROP INDEX "applications_family_member_id_deletedAt_idx";
-- DropIndex
DROP INDEX "family_members_profile_id_deletedAt_idx";
-- DropIndex
DROP INDEX "profiles_phone_idx";
-- AlterTable
ALTER TABLE "applications" DROP COLUMN "family_member_id",
DROP COLUMN "leaderOpinion",
DROP COLUMN "remainingDays",
DROP COLUMN "usedDaysThisYear",
ADD COLUMN "applicant_birthday" TIMESTAMP(3),
ADD COLUMN "applicant_duty" TEXT NOT NULL,
ADD COLUMN "applicant_hire_date" TIMESTAMP(3),
ADD COLUMN "applicant_marriage_date" TIMESTAMP(3),
ADD COLUMN "applicant_name" TEXT NOT NULL,
ADD COLUMN "applicant_phone" TEXT,
ADD COLUMN "leader_opinion" TEXT,
ADD COLUMN "remaining_days" INTEGER NOT NULL DEFAULT 0,
ADD COLUMN "used_days_this_year" INTEGER NOT NULL DEFAULT 0,
ALTER COLUMN "profile_id" DROP NOT NULL;
-- AlterTable
ALTER TABLE "family_members" DROP COLUMN "profile_id",
ADD COLUMN "application_id" TEXT NOT NULL;
-- AlterTable
ALTER TABLE "profiles" DROP COLUMN "marriage_date",
DROP COLUMN "phone";
-- CreateIndex
CREATE INDEX "applications_applicant_name_idx" ON "applications"("applicant_name");
-- CreateIndex
CREATE INDEX "family_members_application_id_deletedAt_idx" ON "family_members"("application_id", "deletedAt");
-- AddForeignKey
ALTER TABLE "family_members" ADD CONSTRAINT "family_members_application_id_fkey" FOREIGN KEY ("application_id") REFERENCES "applications"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -304,6 +304,9 @@ model Profile {
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
deletedAt DateTime? deletedAt DateTime?
// 关联关系 - 临时住房申请相关
applications Application[]
@@index([organizationId, deletedAt]) // 组织人员查询优化 @@index([organizationId, deletedAt]) // 组织人员查询优化
@@index([hireDate]) @@index([hireDate])
@@index([level]) @@index([level])
@ -563,3 +566,74 @@ model ssoProvider {
@@index([issuer]) @@index([issuer])
@@map("sso_providers") @@map("sso_providers")
} }
// ===临时住房申请系统===
// 家属信息表
model FamilyMember {
id String @id @default(cuid())
name String // 家属姓名
birthDate DateTime? @map("birth_date") // 出生年月
relation String // 与申请人关系(配偶、子女、父母、其他)
nativePlace String? @map("native_place") // 籍贯
employer String? // 工作单位
address String? // 实际居住地
// 关联关系 - 直接关联申请表
applicationId String @map("application_id") // 关联的申请ID
application Application @relation(fields: [applicationId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
@@index([applicationId, deletedAt]) // 申请家属查询优化
@@index([relation]) // 关系类型查询优化
@@map("family_members")
}
// 临时住房申请表
model Application {
id String @id @default(cuid())
// 申请人信息(冗余存储,用于审计)
applicantName String @map("applicant_name") // 申请人姓名
applicantDuty String @map("applicant_duty") // 申请人职别
applicantBirthday DateTime? @map("applicant_birthday") // 申请人出生年月
applicantHireDate DateTime? @map("applicant_hire_date") // 申请人入职时间
applicantMarriageDate DateTime? @map("applicant_marriage_date") // 申请人结婚时间
applicantPhone String? @map("applicant_phone") // 申请人联系方式
// 关联到 Profile用于数据管理和统计
profileId String? @map("profile_id") // 关联的员工ID可选
profile Profile? @relation(fields: [profileId], references: [id], onDelete: Cascade)
// 申请信息
applyDays Int @map("apply_days") // 申请住用天数
plannedCheckIn DateTime @map("planned_check_in") // 计划入住时间
plannedCheckOut DateTime @map("planned_check_out") // 计划退房时间
reason String? // 申请理由
usedDaysThisYear Int @default(0) @map("used_days_this_year") // 本年度已住用天数
remainingDays Int @default(0) @map("remaining_days") // 剩余住用天数
// 审批意见
groupOpinion String? @map("group_opinion") // yinlian意见
hrOpinion String? @map("hr_opinion") // 人力科意见
barracksOpinion String? @map("barracks_opinion") // 营房意见
guaranteeOpinion String? @map("guarantee_opinion") // 保障部意见
leaderOpinion String? @map("leader_opinion") // 领导意见
status String @default("pending") // 申请状态:待审批、已同意、已拒绝
// 关联关系
familyMembers FamilyMember[] // 该申请的所有家属
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
@@index([profileId, deletedAt]) // 员工申请查询优化
@@index([status, deletedAt]) // 状态查询优化
@@index([plannedCheckIn, plannedCheckOut]) // 时间范围查询优化
@@index([applicantName]) // 申请人姓名查询优化
@@map("applications")
}