ATTV1 实现考勤统一入口与企微同步
这个提交包含在:
@@ -0,0 +1,158 @@
|
||||
CREATE TYPE "AttendanceCorrectionKind" AS ENUM ('missed_punch', 'out', 'overtime');
|
||||
|
||||
CREATE TYPE "AttendanceCorrectionStatus" AS ENUM ('reviewing', 'approved', 'rejected', 'cancelled');
|
||||
|
||||
CREATE TABLE "wecom_checkin_sync_runs" (
|
||||
"id" UUID NOT NULL,
|
||||
"start_date" DATE NOT NULL,
|
||||
"end_date" DATE NOT NULL,
|
||||
"status" VARCHAR(24) NOT NULL DEFAULT 'running',
|
||||
"employee_count" INTEGER NOT NULL DEFAULT 0,
|
||||
"record_count" INTEGER NOT NULL DEFAULT 0,
|
||||
"error_code" VARCHAR(80),
|
||||
"error_message" TEXT,
|
||||
"triggered_by" UUID,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "wecom_checkin_sync_runs_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "wecom_checkin_records" (
|
||||
"id" UUID NOT NULL,
|
||||
"sync_run_id" UUID NOT NULL,
|
||||
"employee_id" UUID NOT NULL,
|
||||
"external_user_id" VARCHAR(128) NOT NULL,
|
||||
"checkin_time" TIMESTAMP(3) NOT NULL,
|
||||
"checkin_date" DATE NOT NULL,
|
||||
"checkin_type" VARCHAR(40) NOT NULL,
|
||||
"exception_type" VARCHAR(80),
|
||||
"source_type" VARCHAR(40),
|
||||
"result" VARCHAR(80),
|
||||
"raw_hash" VARCHAR(64) NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "wecom_checkin_records_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "attendance_corrections" (
|
||||
"id" UUID NOT NULL,
|
||||
"owner_id" UUID NOT NULL,
|
||||
"department_id" UUID,
|
||||
"kind" "AttendanceCorrectionKind" NOT NULL,
|
||||
"status" "AttendanceCorrectionStatus" NOT NULL DEFAULT 'reviewing',
|
||||
"work_date" DATE NOT NULL,
|
||||
"start_at" TIMESTAMP(3),
|
||||
"end_at" TIMESTAMP(3),
|
||||
"missed_punch_at" TIMESTAMP(3),
|
||||
"reason" TEXT NOT NULL,
|
||||
"location_summary" TEXT,
|
||||
"compensation_policy" TEXT,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"created_by" UUID,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_by" UUID,
|
||||
CONSTRAINT "attendance_corrections_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
ALTER TABLE "workflow_instances"
|
||||
ADD COLUMN "attendance_correction_id" UUID;
|
||||
|
||||
CREATE UNIQUE INDEX "wecom_checkin_records_user_time_type_ux"
|
||||
ON "wecom_checkin_records"("external_user_id", "checkin_time", "checkin_type");
|
||||
|
||||
CREATE INDEX "idx_wecom_checkin_sync_runs_status_created"
|
||||
ON "wecom_checkin_sync_runs"("status", "created_at");
|
||||
|
||||
CREATE INDEX "idx_wecom_checkin_sync_runs_range"
|
||||
ON "wecom_checkin_sync_runs"("start_date", "end_date");
|
||||
|
||||
CREATE INDEX "idx_wecom_checkin_sync_runs_triggered_by"
|
||||
ON "wecom_checkin_sync_runs"("triggered_by");
|
||||
|
||||
CREATE INDEX "idx_wecom_checkin_records_employee_date"
|
||||
ON "wecom_checkin_records"("employee_id", "checkin_date");
|
||||
|
||||
CREATE INDEX "idx_wecom_checkin_records_sync_run"
|
||||
ON "wecom_checkin_records"("sync_run_id");
|
||||
|
||||
CREATE INDEX "idx_attendance_corrections_owner_date"
|
||||
ON "attendance_corrections"("owner_id", "work_date");
|
||||
|
||||
CREATE INDEX "idx_attendance_corrections_department_status"
|
||||
ON "attendance_corrections"("department_id", "status");
|
||||
|
||||
CREATE INDEX "idx_attendance_corrections_status_updated"
|
||||
ON "attendance_corrections"("status", "updated_at");
|
||||
|
||||
CREATE INDEX "workflow_instances_attendance_correction_id_idx"
|
||||
ON "workflow_instances"("attendance_correction_id");
|
||||
|
||||
ALTER TABLE "wecom_checkin_sync_runs"
|
||||
ADD CONSTRAINT "wecom_checkin_sync_runs_triggered_by_fkey"
|
||||
FOREIGN KEY ("triggered_by") REFERENCES "employees"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "wecom_checkin_records"
|
||||
ADD CONSTRAINT "wecom_checkin_records_sync_run_id_fkey"
|
||||
FOREIGN KEY ("sync_run_id") REFERENCES "wecom_checkin_sync_runs"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "wecom_checkin_records"
|
||||
ADD CONSTRAINT "wecom_checkin_records_employee_id_fkey"
|
||||
FOREIGN KEY ("employee_id") REFERENCES "employees"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "attendance_corrections"
|
||||
ADD CONSTRAINT "attendance_corrections_owner_id_fkey"
|
||||
FOREIGN KEY ("owner_id") REFERENCES "employees"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "attendance_corrections"
|
||||
ADD CONSTRAINT "attendance_corrections_department_id_fkey"
|
||||
FOREIGN KEY ("department_id") REFERENCES "departments"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "workflow_instances"
|
||||
ADD CONSTRAINT "workflow_instances_attendance_correction_id_fkey"
|
||||
FOREIGN KEY ("attendance_correction_id") REFERENCES "attendance_corrections"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
INSERT INTO "feature_flags" (
|
||||
"id",
|
||||
"flag_key",
|
||||
"override_mode",
|
||||
"rollout_type",
|
||||
"department_ids",
|
||||
"note",
|
||||
"version",
|
||||
"created_at",
|
||||
"updated_at"
|
||||
) VALUES (
|
||||
'9902678f-58af-4326-b2ce-04690a0b5fa9'::uuid,
|
||||
'attendance_unified_v1',
|
||||
'force_off',
|
||||
'global',
|
||||
'[]'::jsonb,
|
||||
'bootstrap default: force_off',
|
||||
1,
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP
|
||||
) ON CONFLICT ("flag_key") DO NOTHING;
|
||||
|
||||
INSERT INTO "workflow_definitions" (
|
||||
"id",
|
||||
"name",
|
||||
"entity_type",
|
||||
"definition_json",
|
||||
"version",
|
||||
"is_active",
|
||||
"created_at",
|
||||
"created_by"
|
||||
) VALUES (
|
||||
'113c2ac4-a58b-470b-bd46-c9a3cdc34e97'::uuid,
|
||||
'考勤修正审批流程(V1)',
|
||||
'attendance_correction',
|
||||
'{"steps":[{"id":"attendance_correction_approval_manager","name":"直属经理审批","type":"approval","assignee_rule":{"type":"department_manager"}}]}'::jsonb,
|
||||
1,
|
||||
true,
|
||||
CURRENT_TIMESTAMP,
|
||||
NULL
|
||||
) ON CONFLICT ("entity_type", "version") DO UPDATE
|
||||
SET
|
||||
"name" = EXCLUDED."name",
|
||||
"definition_json" = EXCLUDED."definition_json",
|
||||
"is_active" = true;
|
||||
@@ -344,6 +344,19 @@ enum EmployeePresenceSource {
|
||||
admin_override
|
||||
}
|
||||
|
||||
enum AttendanceCorrectionKind {
|
||||
missed_punch
|
||||
out
|
||||
overtime
|
||||
}
|
||||
|
||||
enum AttendanceCorrectionStatus {
|
||||
reviewing
|
||||
approved
|
||||
rejected
|
||||
cancelled
|
||||
}
|
||||
|
||||
enum EmployeeContractType {
|
||||
labor
|
||||
nda
|
||||
@@ -534,6 +547,7 @@ model Department {
|
||||
leave_requests LeaveRequest[]
|
||||
purchase_requests PurchaseRequest[]
|
||||
travel_requests TravelRequest[]
|
||||
attendance_corrections AttendanceCorrection[]
|
||||
workflow_instances WorkflowInstance[]
|
||||
budget_pools BudgetPool[]
|
||||
travel_orders TravelOrder[]
|
||||
@@ -604,6 +618,9 @@ model Employee {
|
||||
payment_batch_receipts_uploaded PaymentBatchReceiptAttachment[] @relation("PaymentBatchReceiptAttachmentUploader")
|
||||
leave_requests LeaveRequest[]
|
||||
leave_ledgers LeaveLedger[]
|
||||
wecom_checkin_records WecomCheckinRecord[]
|
||||
wecom_checkin_sync_runs WecomCheckinSyncRun[] @relation("WecomCheckinSyncRunTrigger")
|
||||
attendance_corrections AttendanceCorrection[]
|
||||
owned_cost_centers CostCenter[] @relation("CostCenterOwner")
|
||||
purchase_requests_applicant PurchaseRequest[] @relation("PurchaseRequestApplicant")
|
||||
travel_requests_applicant TravelRequest[] @relation("TravelRequestApplicant")
|
||||
@@ -1926,6 +1943,83 @@ model AttendancePolicy {
|
||||
@@map("attendance_policies")
|
||||
}
|
||||
|
||||
model WecomCheckinSyncRun {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
start_date DateTime @db.Date
|
||||
end_date DateTime @db.Date
|
||||
status String @default("running") @db.VarChar(24)
|
||||
employee_count Int @default(0)
|
||||
record_count Int @default(0)
|
||||
error_code String? @db.VarChar(80)
|
||||
error_message String?
|
||||
triggered_by String? @db.Uuid
|
||||
|
||||
created_at DateTime @default(now())
|
||||
updated_at DateTime @default(now()) @updatedAt
|
||||
|
||||
trigger Employee? @relation("WecomCheckinSyncRunTrigger", fields: [triggered_by], references: [id], onDelete: SetNull)
|
||||
records WecomCheckinRecord[]
|
||||
|
||||
@@index([status, created_at], map: "idx_wecom_checkin_sync_runs_status_created")
|
||||
@@index([start_date, end_date], map: "idx_wecom_checkin_sync_runs_range")
|
||||
@@index([triggered_by], map: "idx_wecom_checkin_sync_runs_triggered_by")
|
||||
@@map("wecom_checkin_sync_runs")
|
||||
}
|
||||
|
||||
model WecomCheckinRecord {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
sync_run_id String @db.Uuid
|
||||
employee_id String @db.Uuid
|
||||
external_user_id String @db.VarChar(128)
|
||||
checkin_time DateTime
|
||||
checkin_date DateTime @db.Date
|
||||
checkin_type String @db.VarChar(40)
|
||||
exception_type String? @db.VarChar(80)
|
||||
source_type String? @db.VarChar(40)
|
||||
result String? @db.VarChar(80)
|
||||
raw_hash String @db.VarChar(64)
|
||||
|
||||
created_at DateTime @default(now())
|
||||
updated_at DateTime @default(now()) @updatedAt
|
||||
|
||||
sync_run WecomCheckinSyncRun @relation(fields: [sync_run_id], references: [id], onDelete: Cascade)
|
||||
employee Employee @relation(fields: [employee_id], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([external_user_id, checkin_time, checkin_type], map: "wecom_checkin_records_user_time_type_ux")
|
||||
@@index([employee_id, checkin_date], map: "idx_wecom_checkin_records_employee_date")
|
||||
@@index([sync_run_id], map: "idx_wecom_checkin_records_sync_run")
|
||||
@@map("wecom_checkin_records")
|
||||
}
|
||||
|
||||
model AttendanceCorrection {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
owner_id String @db.Uuid
|
||||
department_id String? @db.Uuid
|
||||
kind AttendanceCorrectionKind
|
||||
status AttendanceCorrectionStatus @default(reviewing)
|
||||
work_date DateTime @db.Date
|
||||
start_at DateTime?
|
||||
end_at DateTime?
|
||||
missed_punch_at DateTime?
|
||||
reason String
|
||||
location_summary String?
|
||||
compensation_policy String?
|
||||
|
||||
created_at DateTime @default(now())
|
||||
created_by String? @db.Uuid
|
||||
updated_at DateTime @default(now()) @updatedAt
|
||||
updated_by String? @db.Uuid
|
||||
|
||||
owner Employee @relation(fields: [owner_id], references: [id], onDelete: Cascade)
|
||||
department Department? @relation(fields: [department_id], references: [id], onDelete: SetNull)
|
||||
workflow_instances WorkflowInstance[]
|
||||
|
||||
@@index([owner_id, work_date], map: "idx_attendance_corrections_owner_date")
|
||||
@@index([department_id, status], map: "idx_attendance_corrections_department_status")
|
||||
@@index([status, updated_at], map: "idx_attendance_corrections_status_updated")
|
||||
@@map("attendance_corrections")
|
||||
}
|
||||
|
||||
model WeeklyReport {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
employee_id String @db.Uuid
|
||||
@@ -2868,6 +2962,7 @@ model WorkflowInstance {
|
||||
leave_request_id String? @db.Uuid
|
||||
purchase_request_id String? @db.Uuid
|
||||
travel_request_id String? @db.Uuid
|
||||
attendance_correction_id String? @db.Uuid
|
||||
submitter_id String @db.Uuid
|
||||
current_status WorkflowInstanceStatus @default(pending)
|
||||
current_step_id String
|
||||
@@ -2885,6 +2980,7 @@ model WorkflowInstance {
|
||||
leave_request LeaveRequest? @relation(fields: [leave_request_id], references: [id], onDelete: Cascade)
|
||||
purchase_request PurchaseRequest? @relation(fields: [purchase_request_id], references: [id], onDelete: Cascade)
|
||||
travel_request TravelRequest? @relation(fields: [travel_request_id], references: [id], onDelete: Cascade)
|
||||
attendance_correction AttendanceCorrection? @relation(fields: [attendance_correction_id], references: [id], onDelete: Cascade)
|
||||
submitter Employee @relation("WorkflowInstanceSubmitter", fields: [submitter_id], references: [id], onDelete: Restrict)
|
||||
department Department? @relation(fields: [context_department_id], references: [id], onDelete: SetNull)
|
||||
|
||||
@@ -2900,6 +2996,7 @@ model WorkflowInstance {
|
||||
@@index([leave_request_id])
|
||||
@@index([purchase_request_id])
|
||||
@@index([travel_request_id])
|
||||
@@index([attendance_correction_id])
|
||||
@@index([current_status])
|
||||
@@index([submitter_id])
|
||||
@@index([context_department_id])
|
||||
|
||||
在新工单中引用
屏蔽一个用户