From 4295683f0474dd183309225f9673144254548f54 Mon Sep 17 00:00:00 2001 From: liuyun Date: Thu, 15 Oct 2026 00:20:26 +0800 Subject: [PATCH] =?UTF-8?q?feat(c3):=20record/item/photo/abnormal=20+=20se?= =?UTF-8?q?ttlement/report=20=E6=95=B0=E6=8D=AE=E6=A8=A1=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../20261015001939_c3_record/migration.sql | 133 ++++++++++++++++++ prisma/schema.prisma | 133 ++++++++++++++++++ src/prisma/prisma.service.ts | 2 +- 3 files changed, 267 insertions(+), 1 deletion(-) create mode 100644 prisma/migrations/20261015001939_c3_record/migration.sql diff --git a/prisma/migrations/20261015001939_c3_record/migration.sql b/prisma/migrations/20261015001939_c3_record/migration.sql new file mode 100644 index 0000000..d8a40dc --- /dev/null +++ b/prisma/migrations/20261015001939_c3_record/migration.sql @@ -0,0 +1,133 @@ +-- CreateTable +CREATE TABLE `ho_record` ( + `id` VARCHAR(36) NOT NULL, + `tenant_code` VARCHAR(64) NOT NULL, + `order_id` VARCHAR(36) NOT NULL, + `elder_id` VARCHAR(36) NOT NULL, + `assignee_id` VARCHAR(36) NOT NULL, + `checkin_time` DATETIME(3) NULL, + `checkout_time` DATETIME(3) NULL, + `duration_min` INTEGER NULL, + `checkin_lat` DECIMAL(10, 6) NULL, + `checkin_lng` DECIMAL(10, 6) NULL, + `locate_status` VARCHAR(20) NOT NULL DEFAULT 'ok', + `record_status` VARCHAR(20) NOT NULL DEFAULT 'serving', + `voice_file` VARCHAR(200) NULL, + `offline_flag` BOOLEAN NOT NULL DEFAULT false, + `client_md5` VARCHAR(32) NULL, + `submit_time` DATETIME(3) NULL, + `actual_duration_tier` VARCHAR(10) NULL, + `biz_line` VARCHAR(20) NOT NULL DEFAULT 'ltci', + `is_prepaid` BOOLEAN NOT NULL DEFAULT false, + `billable` BOOLEAN NOT NULL DEFAULT true, + `source_prepaid_of` VARCHAR(36) NULL, + `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `created_by` VARCHAR(36) NULL, + `updated_at` DATETIME(3) NOT NULL, + `deleted_at` DATETIME(3) NULL, + + INDEX `ho_record_tenant_code_order_id_idx`(`tenant_code`, `order_id`), + INDEX `ho_record_tenant_code_elder_id_idx`(`tenant_code`, `elder_id`), + INDEX `ho_record_tenant_code_assignee_id_checkin_time_idx`(`tenant_code`, `assignee_id`, `checkin_time`), + UNIQUE INDEX `ho_record_tenant_code_client_md5_key`(`tenant_code`, `client_md5`), + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; + +-- CreateTable +CREATE TABLE `ho_record_item` ( + `id` VARCHAR(36) NOT NULL, + `tenant_code` VARCHAR(64) NOT NULL, + `record_id` VARCHAR(36) NOT NULL, + `svc_id` VARCHAR(36) NOT NULL, + `svc_name_snapshot` VARCHAR(100) NOT NULL, + `exec_result` VARCHAR(20) NOT NULL, + `undo_reason` VARCHAR(200) NULL, + `extra_flag` BOOLEAN NOT NULL DEFAULT false, + `photo_required` BOOLEAN NOT NULL DEFAULT false, + `is_refused_photo` BOOLEAN NOT NULL DEFAULT false, + `refuse_reason` VARCHAR(200) NULL, + + INDEX `ho_record_item_record_id_idx`(`record_id`), + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; + +-- CreateTable +CREATE TABLE `ho_record_photo` ( + `id` VARCHAR(36) NOT NULL, + `tenant_code` VARCHAR(64) NOT NULL, + `record_id` VARCHAR(36) NOT NULL, + `svc_id` VARCHAR(36) NULL, + `file_path` VARCHAR(255) NOT NULL, + `file_size` INTEGER NOT NULL, + `visible_to_family` BOOLEAN NOT NULL DEFAULT false, + `upload_batch` VARCHAR(36) NULL, + `keep_until` DATETIME(3) NULL, + `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + + INDEX `ho_record_photo_record_id_idx`(`record_id`), + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; + +-- CreateTable +CREATE TABLE `ho_record_abnormal` ( + `id` VARCHAR(36) NOT NULL, + `tenant_code` VARCHAR(64) NOT NULL, + `record_id` VARCHAR(36) NULL, + `order_id` VARCHAR(36) NULL, + `elder_id` VARCHAR(36) NOT NULL, + `reporter_id` VARCHAR(36) NOT NULL, + `abnormal_type` VARCHAR(20) NOT NULL, + `abnormal_desc` TEXT NULL, + `voice_file` VARCHAR(200) NULL, + `has_photo` BOOLEAN NOT NULL DEFAULT false, + `visit_time` DATETIME(3) NULL, + `visit_lat` DECIMAL(10, 6) NULL, + `visit_lng` DECIMAL(10, 6) NULL, + `notify_status` VARCHAR(20) NOT NULL DEFAULT 'pending', + `handle_status` VARCHAR(20) NOT NULL DEFAULT 'open', + `handle_result` VARCHAR(200) NULL, + `link_plan_change` BOOLEAN NOT NULL DEFAULT false, + `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `created_by` VARCHAR(36) NULL, + + INDEX `ho_record_abnormal_tenant_code_elder_id_idx`(`tenant_code`, `elder_id`), + INDEX `ho_record_abnormal_tenant_code_notify_status_idx`(`tenant_code`, `notify_status`), + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; + +-- CreateTable +CREATE TABLE `settlement` ( + `id` VARCHAR(36) NOT NULL, + `tenant_code` VARCHAR(64) NOT NULL, + `settle_period` VARCHAR(20) NOT NULL, + `amount_total` DECIMAL(12, 2) NULL, + `amount_passed` DECIMAL(12, 2) NULL, + `amount_rejected` DECIMAL(12, 2) NULL, + `amount_fund` DECIMAL(12, 2) NULL, + `amount_self_pay` DECIMAL(12, 2) NULL, + `submit_date` DATE NULL, + `pay_date` DATE NULL, + `exception_reason` VARCHAR(200) NULL, + `handle_deadline` DATE NULL, + `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; + +-- CreateTable +CREATE TABLE `report` ( + `id` VARCHAR(36) NOT NULL, + `tenant_code` VARCHAR(64) NOT NULL, + `record_id` VARCHAR(36) NOT NULL, + `order_id` VARCHAR(36) NOT NULL, + `report_status` VARCHAR(20) NOT NULL DEFAULT 'pending', + `report_time` DATETIME(3) NULL, + `official_no` VARCHAR(50) NULL, + `loc_code` VARCHAR(12) NOT NULL, + `nat_code_snapshot` JSON NULL, + `adapter_result` VARCHAR(200) NULL, + `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; + diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 97bf63e..0321bb5 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -591,3 +591,136 @@ model ElderSvc { @@index([tenantCode, elderId]) @@map("ltc_elder_svc") } + +/// 履约留痕(主) +model Record { + id String @id @default(uuid(7)) @db.VarChar(36) + tenantCode String @map("tenant_code") @db.VarChar(64) + orderId String @map("order_id") @db.VarChar(36) + elderId String @map("elder_id") @db.VarChar(36) + assigneeId String @map("assignee_id") @db.VarChar(36) + checkinTime DateTime? @map("checkin_time") + checkoutTime DateTime? @map("checkout_time") + durationMin Int? @map("duration_min") + checkinLat Decimal? @map("checkin_lat") @db.Decimal(10, 6) + checkinLng Decimal? @map("checkin_lng") @db.Decimal(10, 6) + locateStatus String @default("ok") @map("locate_status") @db.VarChar(20) + recordStatus String @default("serving") @map("record_status") @db.VarChar(20) + voiceFile String? @map("voice_file") @db.VarChar(200) + offlineFlag Boolean @default(false) @map("offline_flag") + clientMd5 String? @map("client_md5") @db.VarChar(32) + submitTime DateTime? @map("submit_time") + actualDurationTier String? @map("actual_duration_tier") @db.VarChar(10) + bizLine String @default("ltci") @map("biz_line") @db.VarChar(20) + isPrepaid Boolean @default(false) @map("is_prepaid") + billable Boolean @default(true) @map("billable") + sourcePrepaidOf String? @map("source_prepaid_of") @db.VarChar(36) + createdAt DateTime @default(now()) @map("created_at") + createdBy String? @map("created_by") @db.VarChar(36) + updatedAt DateTime @updatedAt @map("updated_at") + deletedAt DateTime? @map("deleted_at") + + @@unique([tenantCode, clientMd5]) + @@index([tenantCode, orderId]) + @@index([tenantCode, elderId]) + @@index([tenantCode, assigneeId, checkinTime]) + @@map("ho_record") +} + +/// 留痕项目明细 +model RecordItem { + id String @id @default(uuid(7)) @db.VarChar(36) + tenantCode String @map("tenant_code") @db.VarChar(64) + recordId String @map("record_id") @db.VarChar(36) + svcId String @map("svc_id") @db.VarChar(36) + svcNameSnapshot String @map("svc_name_snapshot") @db.VarChar(100) + execResult String @map("exec_result") @db.VarChar(20) + undoReason String? @map("undo_reason") @db.VarChar(200) + extraFlag Boolean @default(false) @map("extra_flag") + photoRequired Boolean @default(false) @map("photo_required") + isRefusedPhoto Boolean @default(false) @map("is_refused_photo") + refuseReason String? @map("refuse_reason") @db.VarChar(200) + + @@index([recordId]) + @@map("ho_record_item") +} + +/// 留痕照片 +model RecordPhoto { + id String @id @default(uuid(7)) @db.VarChar(36) + tenantCode String @map("tenant_code") @db.VarChar(64) + recordId String @map("record_id") @db.VarChar(36) + svcId String? @map("svc_id") @db.VarChar(36) + filePath String @map("file_path") @db.VarChar(255) + fileSize Int @map("file_size") + visibleToFamily Boolean @default(false) @map("visible_to_family") + uploadBatch String? @map("upload_batch") @db.VarChar(36) + keepUntil DateTime? @map("keep_until") + createdAt DateTime @default(now()) @map("created_at") + + @@index([recordId]) + @@map("ho_record_photo") +} + +/// 异常上报 +model RecordAbnormal { + id String @id @default(uuid(7)) @db.VarChar(36) + tenantCode String @map("tenant_code") @db.VarChar(64) + recordId String? @map("record_id") @db.VarChar(36) + orderId String? @map("order_id") @db.VarChar(36) + elderId String @map("elder_id") @db.VarChar(36) + reporterId String @map("reporter_id") @db.VarChar(36) + abnormalType String @map("abnormal_type") @db.VarChar(20) + abnormalDesc String? @map("abnormal_desc") @db.Text + voiceFile String? @map("voice_file") @db.VarChar(200) + hasPhoto Boolean @default(false) @map("has_photo") + visitTime DateTime? @map("visit_time") + visitLat Decimal? @map("visit_lat") @db.Decimal(10, 6) + visitLng Decimal? @map("visit_lng") @db.Decimal(10, 6) + notifyStatus String @default("pending") @map("notify_status") @db.VarChar(20) + handleStatus String @default("open") @map("handle_status") @db.VarChar(20) + handleResult String? @map("handle_result") @db.VarChar(200) + linkPlanChange Boolean @default(false) @map("link_plan_change") + createdAt DateTime @default(now()) @map("created_at") + createdBy String? @map("created_by") @db.VarChar(36) + + @@index([tenantCode, elderId]) + @@index([tenantCode, notifyStatus]) + @@map("ho_record_abnormal") +} + +/// 结算表(二期预留,建空表不写业务数据) +model Settlement { + id String @id @default(uuid(7)) @db.VarChar(36) + tenantCode String @map("tenant_code") @db.VarChar(64) + settlePeriod String @map("settle_period") @db.VarChar(20) + amountTotal Decimal? @map("amount_total") @db.Decimal(12, 2) + amountPassed Decimal? @map("amount_passed") @db.Decimal(12, 2) + amountRejected Decimal? @map("amount_rejected") @db.Decimal(12, 2) + amountFund Decimal? @map("amount_fund") @db.Decimal(12, 2) + amountSelfPay Decimal? @map("amount_self_pay") @db.Decimal(12, 2) + submitDate DateTime? @map("submit_date") @db.Date + payDate DateTime? @map("pay_date") @db.Date + exceptionReason String? @map("exception_reason") @db.VarChar(200) + handleDeadline DateTime? @map("handle_deadline") @db.Date + createdAt DateTime @default(now()) @map("created_at") + + @@map("settlement") +} + +/// 上报表(二期预留,建空表不写业务数据) +model Report { + id String @id @default(uuid(7)) @db.VarChar(36) + tenantCode String @map("tenant_code") @db.VarChar(64) + recordId String @map("record_id") @db.VarChar(36) + orderId String @map("order_id") @db.VarChar(36) + reportStatus String @default("pending") @map("report_status") @db.VarChar(20) + reportTime DateTime? @map("report_time") + officialNo String? @map("official_no") @db.VarChar(50) + locCode String @map("loc_code") @db.VarChar(12) + natCodeSnapshot Json? @map("nat_code_snapshot") + adapterResult String? @map("adapter_result") @db.VarChar(200) + createdAt DateTime @default(now()) @map("created_at") + + @@map("report") +} diff --git a/src/prisma/prisma.service.ts b/src/prisma/prisma.service.ts index 136b263..334416c 100644 --- a/src/prisma/prisma.service.ts +++ b/src/prisma/prisma.service.ts @@ -5,7 +5,7 @@ import { getRequestContext } from '../common/context/request-context.js'; /** 不带 tenantCode 的模型(租户表自身)。 */ const TENANT_FREE = new Set(['Tenant', 'Agency', 'SvcNational', 'SvcLocal', 'SvcMapping', 'LocConfig']); /** 软删模型:读操作统一注入 deletedAt IS NULL。 */ -const SOFT_DELETE = new Set(['User', 'Role', 'Resource', 'Org', 'DictType', 'DictItem', 'Config', 'Notice', 'Staff', 'SvcOrg', 'Elder', 'Plan', 'Order', 'ElderSvc']); +const SOFT_DELETE = new Set(['User', 'Role', 'Resource', 'Org', 'DictType', 'DictItem', 'Config', 'Notice', 'Staff', 'SvcOrg', 'Elder', 'Plan', 'Order', 'ElderSvc', 'Record']); const READ_OPS = new Set(['findMany', 'findFirst', 'findUnique', 'count', 'aggregate', 'groupBy']); /** 没有 where 子句的操作(create 的租户由 service 在 data 里显式写入)。 */ const NO_WHERE_OPS = new Set(['create', 'createMany']);