fix(server+health): 修复权限同步 + 迁移幂等性 + 缺失菜单种子数据
- sync_module_permissions 每次启动都确保 admin 拥有所有权限(修复 CRITICAL-001) - 新增迁移 m20260505_000116: 补充 11 项缺失的健康管理菜单(多租户安全) - 修复 000101: UUID 格式错误(缺少第 4 段) - 修复 000104/000106/000107: Expr::val → Expr::cust(SQL 函数不应被引号包裹) - 修复 000109: 外键创建改为 IF NOT EXISTS 模式 - 修复 000110: 表名 critical_alerts → critical_alert(匹配实际表名) - 修复 000111/000112: create_table + create_index 添加 if_not_exists() - 修复 000113: 改为 raw SQL 幂等模式,修正 FK 目标表名 patients → patient
This commit is contained in:
@@ -115,6 +115,7 @@ mod m20260505_000112_create_shift_management;
|
||||
mod m20260505_000113_create_ble_gateways;
|
||||
mod m20260505_000114_dialysis_record_add_workflow_instance;
|
||||
mod m20260505_000115_family_member_health_proxy;
|
||||
mod m20260505_000116_seed_missing_health_menus;
|
||||
|
||||
pub struct Migrator;
|
||||
|
||||
@@ -237,6 +238,7 @@ impl MigratorTrait for Migrator {
|
||||
Box::new(m20260505_000113_create_ble_gateways::Migration),
|
||||
Box::new(m20260505_000114_dialysis_record_add_workflow_instance::Migration),
|
||||
Box::new(m20260505_000115_family_member_health_proxy::Migration),
|
||||
Box::new(m20260505_000116_seed_missing_health_menus::Migration),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,7 +195,7 @@ async fn insert_item(
|
||||
) -> Result<(), DbErr> {
|
||||
// 生成条目 ID:基于字典 ID 后缀 + 序号
|
||||
let suffix = &dict_id[24..];
|
||||
let item_id = format!("d200{idx:04x}-0000-0000-{suffix}");
|
||||
let item_id = format!("d200{idx:04x}-0000-0000-0000-{suffix}");
|
||||
let color_sql = match color {
|
||||
Some(c) => format!("'{c}'"),
|
||||
None => "NULL".to_string(),
|
||||
|
||||
@@ -14,7 +14,7 @@ impl MigrationTrait for Migration {
|
||||
ColumnDef::new(Alias::new("id"))
|
||||
.uuid()
|
||||
.not_null()
|
||||
.default(Expr::val("gen_random_uuid()")),
|
||||
.default(Expr::cust("gen_random_uuid()")),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("patient_id")).uuid().not_null())
|
||||
@@ -29,13 +29,13 @@ impl MigrationTrait for Migration {
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::val("NOW()")),
|
||||
.default(Expr::cust("NOW()")),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("updated_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::val("NOW()")),
|
||||
.default(Expr::cust("NOW()")),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("version"))
|
||||
|
||||
@@ -14,7 +14,7 @@ impl MigrationTrait for Migration {
|
||||
ColumnDef::new(Alias::new("id"))
|
||||
.uuid()
|
||||
.not_null()
|
||||
.default(Expr::val("gen_random_uuid()")),
|
||||
.default(Expr::cust("gen_random_uuid()")),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(
|
||||
@@ -56,13 +56,13 @@ impl MigrationTrait for Migration {
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::val("NOW()")),
|
||||
.default(Expr::cust("NOW()")),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("updated_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::val("NOW()")),
|
||||
.default(Expr::cust("NOW()")),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("created_by")).uuid().null())
|
||||
.col(ColumnDef::new(Alias::new("updated_by")).uuid().null())
|
||||
|
||||
@@ -39,7 +39,7 @@ impl MigrationTrait for Migration {
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::val("NOW()")),
|
||||
.default(Expr::cust("NOW()")),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
@@ -53,7 +53,7 @@ impl MigrationTrait for Migration {
|
||||
ColumnDef::new(Alias::new("updated_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::val("NOW()")),
|
||||
.default(Expr::cust("NOW()")),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
|
||||
@@ -6,104 +6,43 @@ pub struct Migration;
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
// follow_up_task.related_appointment_id → appointment.id
|
||||
manager
|
||||
.create_foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("fk_follow_up_task_appointment")
|
||||
.from(Alias::new("follow_up_task"), Alias::new("related_appointment_id"))
|
||||
.to(Alias::new("appointment"), Alias::new("id"))
|
||||
.on_delete(ForeignKeyAction::SetNull)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
let db = manager.get_connection();
|
||||
|
||||
// points_transaction.account_id → points_account.id
|
||||
manager
|
||||
.create_foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("fk_points_transaction_account")
|
||||
.from(Alias::new("points_transaction"), Alias::new("account_id"))
|
||||
.to(Alias::new("points_account"), Alias::new("id"))
|
||||
.on_delete(ForeignKeyAction::Cascade)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
// 安全创建外键:先检查是否已存在,不存在才创建
|
||||
let fks: &[(&str, &str, &str, &str, &str, &str)] = &[
|
||||
("fk_follow_up_task_appointment", "follow_up_task", "related_appointment_id", "appointment", "id", "SET NULL"),
|
||||
("fk_points_transaction_account", "points_transaction", "account_id", "points_account", "id", "CASCADE"),
|
||||
("fk_points_transaction_rule", "points_transaction", "rule_id", "points_rule", "id", "SET NULL"),
|
||||
("fk_points_transaction_order", "points_transaction", "order_id", "points_order", "id", "SET NULL"),
|
||||
("fk_points_order_product", "points_order", "product_id", "points_product", "id", "RESTRICT"),
|
||||
("fk_points_order_patient", "points_order", "patient_id", "patient", "id", "CASCADE"),
|
||||
("fk_offline_event_registration_event", "offline_event_registration", "event_id", "offline_event", "id", "CASCADE"),
|
||||
("fk_offline_event_registration_patient", "offline_event_registration", "patient_id", "patient", "id", "CASCADE"),
|
||||
];
|
||||
|
||||
// points_transaction.rule_id → points_rule.id
|
||||
manager
|
||||
.create_foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("fk_points_transaction_rule")
|
||||
.from(Alias::new("points_transaction"), Alias::new("rule_id"))
|
||||
.to(Alias::new("points_rule"), Alias::new("id"))
|
||||
.on_delete(ForeignKeyAction::SetNull)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
for &(name, from_table, from_col, to_table, to_col, on_delete) in fks {
|
||||
let sql = format!(
|
||||
r#"DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.table_constraints
|
||||
WHERE constraint_name = '{name}' AND table_name = '{from_table}'
|
||||
) THEN
|
||||
ALTER TABLE {from_table}
|
||||
ADD CONSTRAINT {name}
|
||||
FOREIGN KEY ({from_col}) REFERENCES {to_table}({to_col})
|
||||
ON DELETE {on_delete};
|
||||
END IF;
|
||||
END$$;"#
|
||||
);
|
||||
db.execute_unprepared(&sql).await?;
|
||||
}
|
||||
|
||||
// points_transaction.order_id → points_order.id
|
||||
manager
|
||||
.create_foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("fk_points_transaction_order")
|
||||
.from(Alias::new("points_transaction"), Alias::new("order_id"))
|
||||
.to(Alias::new("points_order"), Alias::new("id"))
|
||||
.on_delete(ForeignKeyAction::SetNull)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// points_order.product_id → points_product.id
|
||||
manager
|
||||
.create_foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("fk_points_order_product")
|
||||
.from(Alias::new("points_order"), Alias::new("product_id"))
|
||||
.to(Alias::new("points_product"), Alias::new("id"))
|
||||
.on_delete(ForeignKeyAction::Restrict)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// points_order.patient_id → patient.id
|
||||
manager
|
||||
.create_foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("fk_points_order_patient")
|
||||
.from(Alias::new("points_order"), Alias::new("patient_id"))
|
||||
.to(Alias::new("patient"), Alias::new("id"))
|
||||
.on_delete(ForeignKeyAction::Cascade)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// offline_event_registration.event_id → offline_event.id
|
||||
manager
|
||||
.create_foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("fk_offline_event_registration_event")
|
||||
.from(Alias::new("offline_event_registration"), Alias::new("event_id"))
|
||||
.to(Alias::new("offline_event"), Alias::new("id"))
|
||||
.on_delete(ForeignKeyAction::Cascade)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// offline_event_registration.patient_id → patient.id
|
||||
manager
|
||||
.create_foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("fk_offline_event_registration_patient")
|
||||
.from(Alias::new("offline_event_registration"), Alias::new("patient_id"))
|
||||
.to(Alias::new("patient"), Alias::new("id"))
|
||||
.on_delete(ForeignKeyAction::Cascade)
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
let db = manager.get_connection();
|
||||
let fks = [
|
||||
"fk_offline_event_registration_patient",
|
||||
"fk_offline_event_registration_event",
|
||||
@@ -114,15 +53,12 @@ impl MigrationTrait for Migration {
|
||||
"fk_points_transaction_account",
|
||||
"fk_follow_up_task_appointment",
|
||||
];
|
||||
for fk in fks {
|
||||
manager
|
||||
.drop_foreign_key(
|
||||
ForeignKey::drop()
|
||||
.name(fk)
|
||||
.table(Alias::new("dummy"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
for fk in &fks {
|
||||
db.execute_unprepared(&format!(
|
||||
"ALTER TABLE dummy DROP CONSTRAINT IF EXISTS {fk}"
|
||||
))
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@ pub struct Migration;
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
// critical_alerts.version: i64 → i32
|
||||
// critical_alert.version: i64 → i32
|
||||
manager
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(Alias::new("critical_alerts"))
|
||||
.table(Alias::new("critical_alert"))
|
||||
.modify_column(
|
||||
ColumnDef::new(Alias::new("version"))
|
||||
.integer()
|
||||
@@ -21,11 +21,11 @@ impl MigrationTrait for Migration {
|
||||
)
|
||||
.await?;
|
||||
|
||||
// critical_alert_responses.version: i64 → i32
|
||||
// critical_alert_response.version: i64 → i32
|
||||
manager
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(Alias::new("critical_alert_responses"))
|
||||
.table(Alias::new("critical_alert_response"))
|
||||
.modify_column(
|
||||
ColumnDef::new(Alias::new("version"))
|
||||
.integer()
|
||||
@@ -41,7 +41,7 @@ impl MigrationTrait for Migration {
|
||||
manager
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(Alias::new("critical_alerts"))
|
||||
.table(Alias::new("critical_alert"))
|
||||
.modify_column(
|
||||
ColumnDef::new(Alias::new("version"))
|
||||
.big_integer()
|
||||
@@ -55,7 +55,7 @@ impl MigrationTrait for Migration {
|
||||
manager
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(Alias::new("critical_alert_responses"))
|
||||
.table(Alias::new("critical_alert_response"))
|
||||
.modify_column(
|
||||
ColumnDef::new(Alias::new("version"))
|
||||
.big_integer()
|
||||
|
||||
@@ -11,6 +11,7 @@ impl MigrationTrait for Migration {
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Alias::new("care_plans"))
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("id"))
|
||||
.uuid()
|
||||
@@ -35,7 +36,7 @@ impl MigrationTrait for Migration {
|
||||
ColumnDef::new(Alias::new("goals"))
|
||||
.json_binary()
|
||||
.not_null()
|
||||
.default("[]"),
|
||||
.default(Expr::cust("'[]'::jsonb")),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("start_date")).date())
|
||||
.col(ColumnDef::new(Alias::new("end_date")).date())
|
||||
@@ -66,6 +67,7 @@ impl MigrationTrait for Migration {
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.if_not_exists()
|
||||
.name("idx_care_plans_tenant_patient")
|
||||
.table(Alias::new("care_plans"))
|
||||
.col(Alias::new("tenant_id"))
|
||||
@@ -78,6 +80,7 @@ impl MigrationTrait for Migration {
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.if_not_exists()
|
||||
.name("idx_care_plans_tenant_status")
|
||||
.table(Alias::new("care_plans"))
|
||||
.col(Alias::new("tenant_id"))
|
||||
@@ -92,6 +95,7 @@ impl MigrationTrait for Migration {
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Alias::new("care_plan_items"))
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("id"))
|
||||
.uuid()
|
||||
@@ -153,6 +157,7 @@ impl MigrationTrait for Migration {
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.if_not_exists()
|
||||
.name("idx_care_plan_items_tenant_plan")
|
||||
.table(Alias::new("care_plan_items"))
|
||||
.col(Alias::new("tenant_id"))
|
||||
@@ -167,6 +172,7 @@ impl MigrationTrait for Migration {
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Alias::new("care_plan_outcomes"))
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("id"))
|
||||
.uuid()
|
||||
@@ -231,6 +237,7 @@ impl MigrationTrait for Migration {
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.if_not_exists()
|
||||
.name("idx_care_plan_outcomes_tenant_plan")
|
||||
.table(Alias::new("care_plan_outcomes"))
|
||||
.col(Alias::new("tenant_id"))
|
||||
|
||||
@@ -6,245 +6,100 @@ pub struct Migration;
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
// 班次表
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Shift::Table)
|
||||
.col(
|
||||
ColumnDef::new(Shift::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Shift::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(Shift::ShiftDate).date().not_null())
|
||||
.col(ColumnDef::new(Shift::Period).string_len(20).not_null())
|
||||
.col(ColumnDef::new(Shift::NurseId).uuid())
|
||||
.col(ColumnDef::new(Shift::Status).string_len(20).not_null().default("scheduled"))
|
||||
.col(ColumnDef::new(Shift::Notes).text())
|
||||
.col(ColumnDef::new(Shift::CreatedAt).timestamp_with_time_zone().not_null())
|
||||
.col(ColumnDef::new(Shift::UpdatedAt).timestamp_with_time_zone().not_null())
|
||||
.col(ColumnDef::new(Shift::CreatedBy).uuid())
|
||||
.col(ColumnDef::new(Shift::UpdatedBy).uuid())
|
||||
.col(ColumnDef::new(Shift::DeletedAt).timestamp_with_time_zone())
|
||||
.col(ColumnDef::new(Shift::Version).integer().not_null().default(1))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
let db = manager.get_connection();
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_shifts_tenant_date")
|
||||
.table(Shift::Table)
|
||||
.col(Shift::TenantId)
|
||||
.col(Shift::ShiftDate)
|
||||
.col(Shift::DeletedAt)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
db.execute_unprepared(
|
||||
r#"
|
||||
CREATE TABLE IF NOT EXISTS shift (
|
||||
id UUID PRIMARY KEY,
|
||||
tenant_id UUID NOT NULL,
|
||||
shift_date DATE NOT NULL,
|
||||
period VARCHAR(20) NOT NULL,
|
||||
nurse_id UUID,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'scheduled',
|
||||
notes TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL,
|
||||
updated_at TIMESTAMPTZ NOT NULL,
|
||||
created_by UUID,
|
||||
updated_by UUID,
|
||||
deleted_at TIMESTAMPTZ,
|
||||
version INTEGER NOT NULL DEFAULT 1
|
||||
);
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_shifts_tenant_nurse")
|
||||
.table(Shift::Table)
|
||||
.col(Shift::TenantId)
|
||||
.col(Shift::NurseId)
|
||||
.col(Shift::DeletedAt)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
CREATE TABLE IF NOT EXISTS patient_assignment (
|
||||
id UUID PRIMARY KEY,
|
||||
tenant_id UUID NOT NULL,
|
||||
shift_id UUID NOT NULL,
|
||||
patient_id UUID NOT NULL,
|
||||
care_level VARCHAR(20) NOT NULL DEFAULT 'routine',
|
||||
notes TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL,
|
||||
updated_at TIMESTAMPTZ NOT NULL,
|
||||
created_by UUID,
|
||||
updated_by UUID,
|
||||
deleted_at TIMESTAMPTZ,
|
||||
version INTEGER NOT NULL DEFAULT 1
|
||||
);
|
||||
|
||||
// 患者分配表
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(PatientAssignment::Table)
|
||||
.col(
|
||||
ColumnDef::new(PatientAssignment::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(PatientAssignment::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(PatientAssignment::ShiftId).uuid().not_null())
|
||||
.col(ColumnDef::new(PatientAssignment::PatientId).uuid().not_null())
|
||||
.col(ColumnDef::new(PatientAssignment::CareLevel).string_len(20).not_null().default("routine"))
|
||||
.col(ColumnDef::new(PatientAssignment::Notes).text())
|
||||
.col(ColumnDef::new(PatientAssignment::CreatedAt).timestamp_with_time_zone().not_null())
|
||||
.col(ColumnDef::new(PatientAssignment::UpdatedAt).timestamp_with_time_zone().not_null())
|
||||
.col(ColumnDef::new(PatientAssignment::CreatedBy).uuid())
|
||||
.col(ColumnDef::new(PatientAssignment::UpdatedBy).uuid())
|
||||
.col(ColumnDef::new(PatientAssignment::DeletedAt).timestamp_with_time_zone())
|
||||
.col(ColumnDef::new(PatientAssignment::Version).integer().not_null().default(1))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
CREATE TABLE IF NOT EXISTS handoff_log (
|
||||
id UUID PRIMARY KEY,
|
||||
tenant_id UUID NOT NULL,
|
||||
from_shift_id UUID NOT NULL,
|
||||
to_shift_id UUID NOT NULL,
|
||||
patient_id UUID NOT NULL,
|
||||
notes TEXT,
|
||||
pending_items JSONB,
|
||||
created_at TIMESTAMPTZ NOT NULL,
|
||||
updated_at TIMESTAMPTZ NOT NULL,
|
||||
created_by UUID,
|
||||
updated_by UUID,
|
||||
deleted_at TIMESTAMPTZ,
|
||||
version INTEGER NOT NULL DEFAULT 1
|
||||
);
|
||||
"#,
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("fk_patient_assignments_shift")
|
||||
.from(PatientAssignment::Table, PatientAssignment::ShiftId)
|
||||
.to(Shift::Table, Shift::Id)
|
||||
.on_delete(ForeignKeyAction::Cascade)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
// 索引(幂等)
|
||||
let indexes = [
|
||||
"CREATE INDEX IF NOT EXISTS idx_shifts_tenant_date ON shift (tenant_id, shift_date, deleted_at)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_shifts_tenant_nurse ON shift (tenant_id, nurse_id, deleted_at)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_patient_assignments_shift ON patient_assignment (tenant_id, shift_id, deleted_at)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_patient_assignments_patient ON patient_assignment (tenant_id, patient_id, deleted_at)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_handoff_log_to_shift ON handoff_log (tenant_id, to_shift_id, deleted_at)",
|
||||
];
|
||||
for sql in &indexes {
|
||||
db.execute_unprepared(sql).await.ok();
|
||||
}
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_patient_assignments_shift")
|
||||
.table(PatientAssignment::Table)
|
||||
.col(PatientAssignment::TenantId)
|
||||
.col(PatientAssignment::ShiftId)
|
||||
.col(PatientAssignment::DeletedAt)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_patient_assignments_patient")
|
||||
.table(PatientAssignment::Table)
|
||||
.col(PatientAssignment::TenantId)
|
||||
.col(PatientAssignment::PatientId)
|
||||
.col(PatientAssignment::DeletedAt)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 交接日志表
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(HandoffLog::Table)
|
||||
.col(
|
||||
ColumnDef::new(HandoffLog::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(HandoffLog::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(HandoffLog::FromShiftId).uuid().not_null())
|
||||
.col(ColumnDef::new(HandoffLog::ToShiftId).uuid().not_null())
|
||||
.col(ColumnDef::new(HandoffLog::PatientId).uuid().not_null())
|
||||
.col(ColumnDef::new(HandoffLog::Notes).text())
|
||||
.col(ColumnDef::new(HandoffLog::PendingItems).json_binary())
|
||||
.col(ColumnDef::new(HandoffLog::CreatedAt).timestamp_with_time_zone().not_null())
|
||||
.col(ColumnDef::new(HandoffLog::UpdatedAt).timestamp_with_time_zone().not_null())
|
||||
.col(ColumnDef::new(HandoffLog::CreatedBy).uuid())
|
||||
.col(ColumnDef::new(HandoffLog::UpdatedBy).uuid())
|
||||
.col(ColumnDef::new(HandoffLog::DeletedAt).timestamp_with_time_zone())
|
||||
.col(ColumnDef::new(HandoffLog::Version).integer().not_null().default(1))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("fk_handoff_log_from_shift")
|
||||
.from(HandoffLog::Table, HandoffLog::FromShiftId)
|
||||
.to(Shift::Table, Shift::Id)
|
||||
.on_delete(ForeignKeyAction::Cascade)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("fk_handoff_log_to_shift")
|
||||
.from(HandoffLog::Table, HandoffLog::ToShiftId)
|
||||
.to(Shift::Table, Shift::Id)
|
||||
.on_delete(ForeignKeyAction::Cascade)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_handoff_log_to_shift")
|
||||
.table(HandoffLog::Table)
|
||||
.col(HandoffLog::TenantId)
|
||||
.col(HandoffLog::ToShiftId)
|
||||
.col(HandoffLog::DeletedAt)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
// 外键(幂等)
|
||||
let fks = [
|
||||
("fk_patient_assignments_shift", "ALTER TABLE patient_assignment ADD CONSTRAINT fk_patient_assignments_shift FOREIGN KEY (shift_id) REFERENCES shift(id) ON DELETE CASCADE"),
|
||||
("fk_handoff_log_from_shift", "ALTER TABLE handoff_log ADD CONSTRAINT fk_handoff_log_from_shift FOREIGN KEY (from_shift_id) REFERENCES shift(id) ON DELETE CASCADE"),
|
||||
("fk_handoff_log_to_shift", "ALTER TABLE handoff_log ADD CONSTRAINT fk_handoff_log_to_shift FOREIGN KEY (to_shift_id) REFERENCES shift(id) ON DELETE CASCADE"),
|
||||
];
|
||||
for (name, sql) in &fks {
|
||||
let check = format!(
|
||||
"SELECT COUNT(*) FROM information_schema.table_constraints WHERE constraint_name = '{name}'"
|
||||
);
|
||||
if let Some(row) = db.query_one(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres, check,
|
||||
)).await? {
|
||||
let count: i64 = row.try_get_by_index::<i64>(0).unwrap_or(0);
|
||||
if count == 0 {
|
||||
db.execute_unprepared(sql).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(HandoffLog::Table).to_owned())
|
||||
.await?;
|
||||
manager
|
||||
.drop_table(Table::drop().table(PatientAssignment::Table).to_owned())
|
||||
.await?;
|
||||
manager
|
||||
.drop_table(Table::drop().table(Shift::Table).to_owned())
|
||||
.await?;
|
||||
let db = manager.get_connection();
|
||||
db.execute_unprepared("DROP TABLE IF EXISTS handoff_log").await?;
|
||||
db.execute_unprepared("DROP TABLE IF EXISTS patient_assignment").await?;
|
||||
db.execute_unprepared("DROP TABLE IF EXISTS shift").await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum Shift {
|
||||
Table,
|
||||
Id,
|
||||
TenantId,
|
||||
ShiftDate,
|
||||
Period,
|
||||
NurseId,
|
||||
Status,
|
||||
Notes,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
CreatedBy,
|
||||
UpdatedBy,
|
||||
DeletedAt,
|
||||
Version,
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum PatientAssignment {
|
||||
Table,
|
||||
Id,
|
||||
TenantId,
|
||||
ShiftId,
|
||||
PatientId,
|
||||
CareLevel,
|
||||
Notes,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
CreatedBy,
|
||||
UpdatedBy,
|
||||
DeletedAt,
|
||||
Version,
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum HandoffLog {
|
||||
Table,
|
||||
Id,
|
||||
TenantId,
|
||||
FromShiftId,
|
||||
ToShiftId,
|
||||
PatientId,
|
||||
Notes,
|
||||
PendingItems,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
CreatedBy,
|
||||
UpdatedBy,
|
||||
DeletedAt,
|
||||
Version,
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use sea_orm_migration::{prelude::*, schema::*};
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
@@ -6,125 +6,87 @@ pub struct Migration;
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Alias::new("ble_gateways"))
|
||||
.col(uuid("id").primary_key())
|
||||
.col(uuid("tenant_id").not_null())
|
||||
.col(string("gateway_id").unique_key().not_null())
|
||||
.col(string("name").not_null())
|
||||
.col(string("api_key_hash").not_null())
|
||||
.col(string("api_key_prefix").not_null())
|
||||
.col(string("status").default("active").not_null())
|
||||
.col(string("firmware_version").null())
|
||||
.col(string("ip_address").null())
|
||||
.col(timestamp_with_time_zone("last_heartbeat_at").null())
|
||||
.col(json("metadata").null())
|
||||
.col(timestamp_with_time_zone("created_at").default(Expr::current_timestamp()).not_null())
|
||||
.col(timestamp_with_time_zone("updated_at").default(Expr::current_timestamp()).not_null())
|
||||
.col(uuid("created_by").null())
|
||||
.col(uuid("updated_by").null())
|
||||
.col(timestamp_with_time_zone("deleted_at").null())
|
||||
.col(integer("version").default(1).not_null())
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
let db = manager.get_connection();
|
||||
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Alias::new("gateway_patient_bindings"))
|
||||
.col(uuid("id").primary_key())
|
||||
.col(uuid("tenant_id").not_null())
|
||||
.col(uuid("gateway_id_fk").not_null())
|
||||
.col(uuid("patient_id").not_null())
|
||||
.col(string("peripheral_mac").null())
|
||||
.col(string("device_type").null())
|
||||
.col(string("status").default("active").not_null())
|
||||
.col(timestamp_with_time_zone("created_at").default(Expr::current_timestamp()).not_null())
|
||||
.col(timestamp_with_time_zone("updated_at").default(Expr::current_timestamp()).not_null())
|
||||
.col(uuid("created_by").null())
|
||||
.col(uuid("updated_by").null())
|
||||
.col(timestamp_with_time_zone("deleted_at").null())
|
||||
.col(integer("version").default(1).not_null())
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
db.execute_unprepared(
|
||||
r#"
|
||||
CREATE TABLE IF NOT EXISTS ble_gateways (
|
||||
id UUID PRIMARY KEY,
|
||||
tenant_id UUID NOT NULL,
|
||||
gateway_id VARCHAR NOT NULL UNIQUE,
|
||||
name VARCHAR NOT NULL,
|
||||
api_key_hash VARCHAR NOT NULL,
|
||||
api_key_prefix VARCHAR NOT NULL,
|
||||
status VARCHAR NOT NULL DEFAULT 'active',
|
||||
firmware_version VARCHAR,
|
||||
ip_address VARCHAR,
|
||||
last_heartbeat_at TIMESTAMPTZ,
|
||||
metadata JSON,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
created_by UUID,
|
||||
updated_by UUID,
|
||||
deleted_at TIMESTAMPTZ,
|
||||
version INTEGER NOT NULL DEFAULT 1
|
||||
);
|
||||
|
||||
// 索引
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_ble_gateways_tenant_id")
|
||||
.table(Alias::new("ble_gateways"))
|
||||
.col(Alias::new("tenant_id"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
CREATE TABLE IF NOT EXISTS gateway_patient_bindings (
|
||||
id UUID PRIMARY KEY,
|
||||
tenant_id UUID NOT NULL,
|
||||
gateway_id_fk UUID NOT NULL,
|
||||
patient_id UUID NOT NULL,
|
||||
peripheral_mac VARCHAR,
|
||||
device_type VARCHAR,
|
||||
status VARCHAR NOT NULL DEFAULT 'active',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
created_by UUID,
|
||||
updated_by UUID,
|
||||
deleted_at TIMESTAMPTZ,
|
||||
version INTEGER NOT NULL DEFAULT 1
|
||||
);
|
||||
"#,
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_ble_gateways_api_key_prefix")
|
||||
.table(Alias::new("ble_gateways"))
|
||||
.col(Alias::new("api_key_prefix"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
// 索引(幂等)
|
||||
let indexes = [
|
||||
("idx_ble_gateways_tenant_id", "CREATE INDEX IF NOT EXISTS idx_ble_gateways_tenant_id ON ble_gateways (tenant_id)"),
|
||||
("idx_ble_gateways_api_key_prefix", "CREATE INDEX IF NOT EXISTS idx_ble_gateways_api_key_prefix ON ble_gateways (api_key_prefix)"),
|
||||
("idx_gateway_patient_bindings_gateway", "CREATE INDEX IF NOT EXISTS idx_gateway_patient_bindings_gateway ON gateway_patient_bindings (gateway_id_fk)"),
|
||||
("idx_gateway_patient_bindings_patient", "CREATE INDEX IF NOT EXISTS idx_gateway_patient_bindings_patient ON gateway_patient_bindings (patient_id)"),
|
||||
];
|
||||
for (_, sql) in &indexes {
|
||||
db.execute_unprepared(sql).await.ok();
|
||||
}
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_gateway_patient_bindings_gateway")
|
||||
.table(Alias::new("gateway_patient_bindings"))
|
||||
.col(Alias::new("gateway_id_fk"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_gateway_patient_bindings_patient")
|
||||
.table(Alias::new("gateway_patient_bindings"))
|
||||
.col(Alias::new("patient_id"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 外键约束
|
||||
manager
|
||||
.create_foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("fk_gpb_gateway")
|
||||
.from(Alias::new("gateway_patient_bindings"), Alias::new("gateway_id_fk"))
|
||||
.to(Alias::new("ble_gateways"), Alias::new("id"))
|
||||
.on_delete(ForeignKeyAction::Cascade)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("fk_gpb_patient")
|
||||
.from(Alias::new("gateway_patient_bindings"), Alias::new("patient_id"))
|
||||
.to(Alias::new("patients"), Alias::new("id"))
|
||||
.on_delete(ForeignKeyAction::Cascade)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
// 外键约束(幂等)
|
||||
let fks = [
|
||||
("fk_gpb_gateway", "ALTER TABLE gateway_patient_bindings ADD CONSTRAINT fk_gpb_gateway FOREIGN KEY (gateway_id_fk) REFERENCES ble_gateways(id) ON DELETE CASCADE"),
|
||||
("fk_gpb_patient", "ALTER TABLE gateway_patient_bindings ADD CONSTRAINT fk_gpb_patient FOREIGN KEY (patient_id) REFERENCES patient(id) ON DELETE CASCADE"),
|
||||
];
|
||||
for (name, sql) in &fks {
|
||||
let check = format!(
|
||||
"SELECT COUNT(*) FROM information_schema.table_constraints WHERE constraint_name = '{name}'"
|
||||
);
|
||||
let result = db.query_one(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
check,
|
||||
)).await?;
|
||||
let count: i64 = result.unwrap().try_get_by_index::<i64>(0).unwrap_or(0);
|
||||
if count == 0 {
|
||||
db.execute_unprepared(sql).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(Alias::new("gateway_patient_bindings")).to_owned())
|
||||
let db = manager.get_connection();
|
||||
db.execute_unprepared("DROP TABLE IF EXISTS gateway_patient_bindings")
|
||||
.await?;
|
||||
manager
|
||||
.drop_table(Table::drop().table(Alias::new("ble_gateways")).to_owned())
|
||||
db.execute_unprepared("DROP TABLE IF EXISTS ble_gateways")
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
let db = manager.get_connection();
|
||||
|
||||
// 批量插入缺失的健康管理菜单(多租户安全)
|
||||
let menus: &[(&str, &str, &str, &str, i32)] = &[
|
||||
("b0000003-0000-7000-8000-000000000022", "护理计划", "/health/care-plans", "SolutionOutlined", 19),
|
||||
("b0000003-0000-7000-8000-000000000023", "班次管理", "/health/shifts", "ClockCircleOutlined", 20),
|
||||
("b0000003-0000-7000-8000-000000000024", "用药记录", "/health/medications", "MedicineBoxOutlined", 21),
|
||||
("b0000003-0000-7000-8000-000000000025", "BLE 网关", "/health/ble-gateways", "WifiOutlined", 22),
|
||||
("b0000003-0000-7000-8000-000000000026", "危急值阈值", "/health/critical-value-thresholds","SafetyCertificateOutlined", 23),
|
||||
("b0000003-0000-7000-8000-000000000027", "诊断记录", "/health/diagnoses", "FileSearchOutlined", 24),
|
||||
("b0000003-0000-7000-8000-000000000028", "家庭健康代理", "/health/family-proxy", "TeamOutlined", 25),
|
||||
("b0000003-0000-7000-8000-000000000029", "知情同意", "/health/consents", "AuditOutlined", 26),
|
||||
("b0000003-0000-7000-8000-000000000030", "实时监控", "/health/realtime-monitor", "MonitorOutlined", 27),
|
||||
("b0000003-0000-7000-8000-000000000031", "OAuth 合作方", "/health/oauth-clients", "ApiOutlined", 28),
|
||||
("b0000003-0000-7000-8000-000000000032", "随访模板管理", "/health/follow-up-templates", "FormOutlined", 29),
|
||||
];
|
||||
|
||||
for &(id, title, path, icon, sort) in menus {
|
||||
let sql = format!(
|
||||
r#"
|
||||
INSERT INTO menus (id, tenant_id, parent_id, title, path, icon, sort_order,
|
||||
visible, menu_type, created_at, updated_at, created_by, updated_by, version)
|
||||
SELECT
|
||||
'{id}'::uuid,
|
||||
t.id,
|
||||
(SELECT m.id FROM menus m WHERE m.path = '/health' AND m.tenant_id = t.id LIMIT 1),
|
||||
'{title}',
|
||||
'{path}',
|
||||
'{icon}',
|
||||
{sort},
|
||||
true, 'page',
|
||||
NOW(), NOW(),
|
||||
(SELECT u.id FROM users u WHERE u.tenant_id = t.id LIMIT 1),
|
||||
(SELECT u.id FROM users u WHERE u.tenant_id = t.id LIMIT 1),
|
||||
1
|
||||
FROM tenant t
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM menus m WHERE m.path = '{path}' AND m.tenant_id = t.id
|
||||
)
|
||||
"#
|
||||
);
|
||||
db.execute_unprepared(&sql).await?;
|
||||
}
|
||||
|
||||
// 为所有健康模块权限绑定 admin 角色(幂等)
|
||||
db.execute_unprepared(
|
||||
r#"
|
||||
INSERT INTO role_permissions (role_id, permission_id, tenant_id, created_by, updated_by, version)
|
||||
SELECT r.id, p.id, t.id, r.id, r.id, 1
|
||||
FROM tenant t
|
||||
JOIN roles r ON r.tenant_id = t.id AND r.code = 'admin' AND r.deleted_at IS NULL
|
||||
JOIN permissions p ON p.tenant_id = t.id AND p.code LIKE 'health.%' AND p.deleted_at IS NULL
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM role_permissions rp
|
||||
WHERE rp.permission_id = p.id AND rp.role_id = r.id
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
let db = manager.get_connection();
|
||||
let paths = [
|
||||
"/health/care-plans",
|
||||
"/health/shifts",
|
||||
"/health/medications",
|
||||
"/health/ble-gateways",
|
||||
"/health/critical-value-thresholds",
|
||||
"/health/diagnoses",
|
||||
"/health/family-proxy",
|
||||
"/health/consents",
|
||||
"/health/realtime-monitor",
|
||||
"/health/oauth-clients",
|
||||
"/health/follow-up-templates",
|
||||
];
|
||||
for path in &paths {
|
||||
db.execute_unprepared(&format!("DELETE FROM menus WHERE path = '{path}'"))
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -808,20 +808,20 @@ async fn sync_module_permissions(
|
||||
}
|
||||
}
|
||||
|
||||
if total_new > 0 {
|
||||
// 将新权限分配给 admin 角色
|
||||
db.execute(sea_orm::Statement::from_sql_and_values(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
r#"INSERT INTO role_permissions (role_id, permission_id, tenant_id, data_scope, created_at, updated_at, created_by, updated_by, deleted_at, version)
|
||||
SELECT r.id, p.id, p.tenant_id, 'all', NOW(), NOW(), $1, $1, NULL, 1
|
||||
FROM permissions p
|
||||
JOIN roles r ON r.code = 'admin' AND r.tenant_id = p.tenant_id AND r.deleted_at IS NULL
|
||||
WHERE p.tenant_id = $2
|
||||
ON CONFLICT DO NOTHING"#,
|
||||
[system_user_id.into(), tenant_id.into()],
|
||||
)).await?;
|
||||
// 每次启动都确保 admin 角色拥有所有模块权限(防止权限-角色关联缺失)
|
||||
db.execute(sea_orm::Statement::from_sql_and_values(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
r#"INSERT INTO role_permissions (role_id, permission_id, tenant_id, data_scope, created_at, updated_at, created_by, updated_by, deleted_at, version)
|
||||
SELECT r.id, p.id, p.tenant_id, 'all', NOW(), NOW(), $1, $1, NULL, 1
|
||||
FROM permissions p
|
||||
JOIN roles r ON r.code = 'admin' AND r.tenant_id = p.tenant_id AND r.deleted_at IS NULL
|
||||
WHERE p.tenant_id = $2
|
||||
ON CONFLICT DO NOTHING"#,
|
||||
[system_user_id.into(), tenant_id.into()],
|
||||
)).await?;
|
||||
|
||||
tracing::info!(total_new, "Module permissions synced to database");
|
||||
if total_new > 0 {
|
||||
tracing::info!(total_new, "New module permissions synced and bound to admin role");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user