diff --git a/src/api/abnormal.ts b/src/api/abnormal.ts new file mode 100644 index 0000000..20e8b78 --- /dev/null +++ b/src/api/abnormal.ts @@ -0,0 +1,119 @@ +import { http } from '@/api/http' +import type { PageResult, TagType } from '@/types/crud' + +/** + * 异常上报(ho_record_abnormal)API。 + * 字段锚点见 shared/data-model spec(ho_record_abnormal)。 + * - 时间字段(visit_time/created_at)后端经 TransformInterceptor 序列化为毫秒时间戳; + * - visit_lat/visit_lng 为 Decimal; + * - 上报(POST /abnormal)为护理员 APP 端能力,admin 仅调用列表。 + */ + +/** 异常分类(9 宫格 + 未遇/扑空),值与 tenant-template DICTS abnormal_type 对齐。 */ +export const ABNORMAL_TYPES = [ + 'pressure_sore', + 'choking', + 'fall', + 'fever', + 'skin_break', + 'mood_abnormal', + 'eat_difficulty', + 'excretion_abnormal', + 'other', + 'not_met', +] as const + +export interface LabeledValue { + label: string + value: string +} + +export const ABNORMAL_TYPE_OPTIONS: LabeledValue[] = [ + { label: '压疮', value: 'pressure_sore' }, + { label: '呛咳', value: 'choking' }, + { label: '跌倒', value: 'fall' }, + { label: '发热', value: 'fever' }, + { label: '皮肤破损', value: 'skin_break' }, + { label: '情绪异常', value: 'mood_abnormal' }, + { label: '进食困难', value: 'eat_difficulty' }, + { label: '排泄异常', value: 'excretion_abnormal' }, + { label: '其他', value: 'other' }, + { label: '未遇(扑空)', value: 'not_met' }, +] + +export const ABNORMAL_TYPE_LABEL_MAP: Record = { + pressure_sore: '压疮', + choking: '呛咳', + fall: '跌倒', + fever: '发热', + skin_break: '皮肤破损', + mood_abnormal: '情绪异常', + eat_difficulty: '进食困难', + excretion_abnormal: '排泄异常', + other: '其他', + not_met: '未遇(扑空)', +} + +export const NOTIFY_STATUS_OPTIONS: LabeledValue[] = [ + { label: '待通知', value: 'pending' }, + { label: '已通知', value: 'notified' }, +] + +export const HANDLE_STATUS_OPTIONS: LabeledValue[] = [ + { label: '待处理', value: 'open' }, + { label: '处理中', value: 'handling' }, + { label: '已关闭', value: 'closed' }, +] + +export const NOTIFY_STATUS_TAG_MAP: Record = { + pending: { label: '待通知', type: 'info' }, + notified: { label: '已通知', type: 'success' }, +} + +export const HANDLE_STATUS_TAG_MAP: Record = { + open: { label: '待处理', type: 'warning' }, + handling: { label: '处理中', type: 'info' }, + closed: { label: '已关闭', type: 'success' }, +} + +/** 异常上报记录(ho_record_abnormal)。 */ +export interface RecordAbnormal { + id: string + tenantCode: string + recordId: string | null + orderId: string | null + elderId: string + reporterId: string + abnormalType: string + abnormalDesc: string | null + voiceFile: string | null + hasPhoto: boolean + visitTime: number | null + visitLat: number | string | null + visitLng: number | string | null + notifyStatus: string + handleStatus: string + handleResult: string | null + linkPlanChange: boolean + createdAt: number + createdBy: string | null +} + +export interface ListAbnormalQuery { + page: number + pageSize: number + abnormalType?: string + notifyStatus?: string + handleStatus?: string + elderId?: string +} + +/** 异常列表(后台 B4,分页)。 */ +export function listAbnormals(query: ListAbnormalQuery, signal?: AbortSignal): Promise> { + const params: Record = { page: query.page, pageSize: query.pageSize } + if (query.abnormalType) params.abnormalType = query.abnormalType + if (query.notifyStatus) params.notifyStatus = query.notifyStatus + if (query.handleStatus) params.handleStatus = query.handleStatus + if (query.elderId) params.elderId = query.elderId + return http.get>('/abnormal', { params, signal }) +} diff --git a/src/api/record.ts b/src/api/record.ts new file mode 100644 index 0000000..d45a309 --- /dev/null +++ b/src/api/record.ts @@ -0,0 +1,133 @@ +import { http } from '@/api/http' +import type { TagType } from '@/types/crud' +import type { RecordAbnormal } from '@/api/abnormal' + +/** + * 履约留痕(ho_record / ho_record_item / ho_record_photo)API。 + * 字段锚点见 shared/data-model spec(ho_record*)。 + * - 时间字段(checkin_time/checkout_time/submit_time/keep_until/created_at/updated_at)后端经 TransformInterceptor 序列化为毫秒时间戳; + * - checkin_lat/checkin_lng 为 Decimal,序列化为字符串或数值; + * - 签到/签退/项目/照片/离线补传为护理员 APP 端能力,admin 仅调用列表/详情/垫付台账/结转。 + */ + +/** 留痕状态(record_status)。 */ +export const RECORD_STATUSES = ['serving', 'submitted'] as const + +/** 项目执行结果(exec_result)。 */ +export const EXEC_RESULTS = ['done', 'undo_not_done', 'undo_refused'] as const + +/** 定位状态(locate_status)。 */ +export const LOCATE_STATUSES = ['ok', 'failed', 'denied'] as const + +export const RECORD_STATUS_TAG_MAP: Record = { + serving: { label: '服务中', type: 'info' }, + submitted: { label: '已提交', type: 'success' }, +} + +export const EXEC_RESULT_TAG_MAP: Record = { + done: { label: '已完成', type: 'success' }, + undo_not_done: { label: '本次未做', type: 'warning' }, + undo_refused: { label: '拒做', type: 'danger' }, +} + +export const LOCATE_STATUS_TAG_MAP: Record = { + ok: { label: '定位成功', type: 'success' }, + failed: { label: '定位失败', type: 'danger' }, + denied: { label: '拒绝定位', type: 'warning' }, +} + +/** 留痕项目明细(ho_record_item)。 */ +export interface RecordItem { + id: string + recordId: string + svcId: string + svcNameSnapshot: string + execResult: string + undoReason: string | null + extraFlag: boolean + photoRequired: boolean + isRefusedPhoto: boolean + refuseReason: string | null +} + +/** 留痕照片(ho_record_photo)。 */ +export interface RecordPhoto { + id: string + recordId: string + svcId: string | null + filePath: string + fileSize: number + visibleToFamily: boolean + uploadBatch: string | null + keepUntil: number | null + createdAt: number +} + +/** 履约留痕(ho_record)。Decimal 定位字段序列化为字符串/数值。 */ +export interface ServiceRecord { + id: string + tenantCode: string + orderId: string + elderId: string + assigneeId: string + checkinTime: number | null + checkoutTime: number | null + durationMin: number | null + checkinLat: number | string | null + checkinLng: number | string | null + locateStatus: string + recordStatus: string + voiceFile: string | null + offlineFlag: boolean + clientMd5: string | null + submitTime: number | null + actualDurationTier: string | null + bizLine: string + isPrepaid: boolean + billable: boolean + sourcePrepaidOf: string | null + createdAt: number + createdBy: string | null + updatedAt: number + deletedAt: number | null +} + +/** 留痕详情:record + items + photos + abnormals。 */ +export interface ServiceRecordDetail extends ServiceRecord { + items: RecordItem[] + photos: RecordPhoto[] + abnormals: RecordAbnormal[] +} + +export interface ListRecordQuery { + elderId?: string + assigneeId?: string + /** 服务日期(YYYY-MM-DD,按 checkin_time 过滤)。 */ + serviceDate?: string + recordStatus?: string +} + +/** 留痕列表(后台不分页,返回全量数组)。 */ +export function listRecords(query: ListRecordQuery, signal?: AbortSignal): Promise { + const params: Record = {} + if (query.elderId) params.elderId = query.elderId + if (query.assigneeId) params.assigneeId = query.assigneeId + if (query.serviceDate) params.serviceDate = query.serviceDate + if (query.recordStatus) params.recordStatus = query.recordStatus + return http.get('/record', { params, signal }) +} + +/** 留痕详情。 */ +export function getRecord(id: string): Promise { + return http.get(`/record/${id}`) +} + +/** 垫付台账:某老人 is_prepaid=1 的留痕列表。 */ +export function listPrepaidRecords(elderId: string): Promise { + return http.get('/record/prepaid-list', { params: { elderId } }) +} + +/** 结转垫付期服务:勾选垫付留痕 → 复制为正式计费记录。 */ +export function prepaidCarryover(recordIds: string[]): Promise { + return http.post('/record/prepaid/carryover', { recordIds }) +} diff --git a/src/components/elder-detail.vue b/src/components/elder-detail.vue index 77ed263..16ed0b3 100644 --- a/src/components/elder-detail.vue +++ b/src/components/elder-detail.vue @@ -50,6 +50,47 @@ 制定计划 + 垫付台账 +
+
+ + 结转垫付期服务 + + 勾选待结转的垫付留痕(is_prepaid),结转后生成正式计费记录 +
+ + + + + + + + + + + + + + + + + + + +
+
状态变更 @@ -86,6 +127,7 @@ import { ApiError } from '@/api/http' import { changeElderStatus, getElder, getStatusLog, type Elder, type ElderStatusLog } from '@/api/elder' import { listLocal, listNewNational, listOrg } from '@/api/catalog' import { getElderSvc, putElderSvc } from '@/api/elder-svc' +import { listPrepaidRecords, prepaidCarryover, type ServiceRecord } from '@/api/record' import { showDialog } from '@/utils/dialog' import { ASSESS_LEVEL_OPTIONS, @@ -124,6 +166,12 @@ const svcSaving = ref(false) const l4Projects = ref([]) const svcForm = reactive({ careMode: '', svcIds: [] as string[] }) +// ---- 垫付台账 ---- +const prepaidRecords = ref([]) +const prepaidLoading = ref(false) +const prepaidSelection = ref([]) +const carryingOver = ref(false) + /** 终态(dead/terminated):后端 IRREVERSIBLE 源态守卫,禁止从终态再变更 → 隐藏整个状态变更区块。 */ const isTerminal = computed(() => { const s = elder.value?.treatStatus @@ -146,6 +194,7 @@ async function load(): Promise { loading.value = false } await loadSvcLibrary() + await loadPrepaid() } /** 个人项目库:当前勾选(快照)+ 本机构 L4 项目(名称 join L2/L1 目录)。 */ @@ -200,6 +249,41 @@ async function saveSvcLibrary(): Promise { } } +/** 垫付台账:is_prepaid=1 的留痕,供勾选结转。 */ +async function loadPrepaid(): Promise { + prepaidLoading.value = true + try { + prepaidRecords.value = await listPrepaidRecords(props.elderId) + prepaidSelection.value = [] + } catch (e) { + if (e instanceof ApiError) ElMessage.error(e.message) + } finally { + prepaidLoading.value = false + } +} + +function onPrepaidSelectionChange(rows: ServiceRecord[]): void { + prepaidSelection.value = rows +} + +/** 结转垫付期服务:勾选 → POST /record/prepaid/carryover(复制为正式计费记录)。 */ +async function carryover(): Promise { + if (prepaidSelection.value.length === 0) { + ElMessage.warning('请先勾选待结转的垫付留痕') + return + } + carryingOver.value = true + try { + await prepaidCarryover(prepaidSelection.value.map((r) => r.id)) + ElMessage.success('结转成功') + await loadPrepaid() + } catch (e) { + if (e instanceof ApiError) ElMessage.error(e.message) + } finally { + carryingOver.value = false + } +} + /** 制定计划:弹窗,从个人项目库带入 → 生成 plan_item 初稿(可改)。 */ async function openPlanForm(): Promise { const plan = await showDialog({ diff --git a/src/pages/ltc/qc/index.vue b/src/pages/ltc/qc/index.vue index 8e47764..2f14557 100644 --- a/src/pages/ltc/qc/index.vue +++ b/src/pages/ltc/qc/index.vue @@ -1,7 +1,149 @@ + + diff --git a/src/pages/ltc/record/index.vue b/src/pages/ltc/record/index.vue index 9647e5d..84ee690 100644 --- a/src/pages/ltc/record/index.vue +++ b/src/pages/ltc/record/index.vue @@ -1,7 +1,281 @@ + +