Files
2026-09-09 17:36:45 +08:00

206 lines
7.2 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use strict";
import mongoose from "mongoose";
import { pinyin } from "pinyin-pro";
/**
* Organization Schema
* 组织/机构基础信息,支持医院、诊所、体检中心等多种机构类型
*/
const OrganizationSchema = mongoose.Schema(
{
// 基础信息
name: { type: String, required: true, index: true, comment: "机构名称" },
shortName: { type: String, default: "", comment: "机构简称" },
pinyin: { type: String, default: "", index: true, comment: "机构名称全拼(小写无分隔,用于搜索)" },
pinyinFL: { type: String, default: "", index: true, comment: "机构名称拼音首字母(用于搜索)" },
code: { type: String, unique: true, sparse: true, comment: "机构编码(不填时字段缺省,避免与 unique sparse 索引冲突)" },
// 机构类型
type: {
type: String,
enum: ["hospital", "clinic", "checkup_center", "other"],
default: "hospital",
comment: "机构类型:医院、诊所、体检中心、其他",
},
// 等级/性质
level: {
type: String,
enum: ["三级甲等", "三级乙等", "二级甲等", "二级乙等", "一级", "未定级", ""],
default: "",
comment: "医院等级",
},
nature: {
type: String,
enum: ["public", "private", "joint", "other", ""],
default: "",
comment: "机构性质:公立、私立、合资、其他",
},
// 联系方式
contact: {
phone: { type: String, default: "", comment: "联系电话" },
email: { type: String, default: "", comment: "联系邮箱" },
website: { type: String, default: "", comment: "官方网站" },
},
// 地址信息
address: {
province: { type: String, default: "", comment: "省份" },
city: { type: String, default: "", comment: "城市" },
district: { type: String, default: "", comment: "区县" },
detail: { type: String, default: "", comment: "详细地址" },
longitude: { type: Number, default: 0, comment: "经度" },
latitude: { type: Number, default: 0, comment: "纬度" },
},
// 状态
status: {
type: String,
enum: ["active", "inactive", "suspended"],
default: "active",
comment: "机构状态:正常、停用、暂停",
},
// 简介/备注
description: { type: String, default: "", comment: "机构简介" },
remark: { type: String, default: "", comment: "备注" },
// 就医信息(agent-first:少字段、大文本;由 AI 助手录入维护,供查询与看病指导)
forAgent: {
updatedAt: { type: Date, comment: "最近更新时间" },
updatedBy: { type: String, default: "", comment: "最近更新人" },
// 医院级总览:门诊时间、挂号渠道、缴费、医保、异地备案、交通停车,一个文本块全包含
overview: { type: String, default: "", comment: "医院级信息全文(门诊/挂号/缴费/医保/交通)" },
// 医院/导引照片
photos: [{
url: { type: String, default: "", comment: "图片链接" },
caption: { type: String, default: "", comment: "说明(医院照片/导引照片 + 描述)" },
}],
// 科室:name 是锚点;detail 包含位置/电话/临床还是医技/就诊提示全文
departments: [{
name: { type: String, comment: "科室名称(锚点)" },
detail: { type: String, default: "", comment: "科室信息全文(位置/电话/类型/就诊提示)" },
photo: { type: String, default: "", comment: "科室位置导引照片链接" },
}],
// 医生:name+department 是锚点;detail 包含职称/擅长/号别/坐班停诊全文
doctors: [{
name: { type: String, comment: "医生姓名(锚点)" },
department: { type: String, comment: "所属科室(锚点)" },
detail: { type: String, default: "", comment: "医生信息全文(职称/擅长/号别/坐班停诊)" },
photo: { type: String, default: "", comment: "医生照片链接" },
}],
// 场景指南:初诊/复诊/住院/检查须知/老人服务等,键为场景名,值为文本,结构自由演化
guides: { type: mongoose.Schema.Types.Mixed, comment: "场景指南(键为场景名,值为文本)" },
},
// 元数据
meta: {
createtime: { type: Date, default: Date.now, comment: "创建时间" },
updatetime: { type: Date, default: Date.now, comment: "更新时间" },
},
},
{
minimize: false,
strict: false,
collection: "organization",
timestamps: false,
}
);
// ==================== 拼音生成 ====================
/**
* 机构名称 → 全拼(小写、无分隔,如 "北京协和医院" → "beijingxieheyiyuan"
*/
const genPinyin = (name) =>
pinyin(String(name), { toneType: "none", nonZh: "consecutive" }).toLowerCase().replace(/[^a-z0-9]/g, "");
/**
* 机构名称 → 拼音首字母(如 "北京协和医院" → "bjxyy"
*/
const genPinyinFL = (name) =>
pinyin(String(name), { pattern: "first", toneType: "none", nonZh: "consecutive" }).toLowerCase().replace(/[^a-z0-9]/g, "");
// 保存时自动生成/同步拼音字段
OrganizationSchema.pre("save", function (next) {
if (!this.name) return next();
if (this.isModified("name") || !this.pinyin || !this.pinyinFL) {
this.pinyin = genPinyin(this.name);
this.pinyinFL = genPinyinFL(this.name);
}
next();
});
// ==================== 静态方法 ====================
/**
* 根据名称/拼音/拼音首字母模糊查询机构
*/
OrganizationSchema.statics.findByName = async function (name, options = {}) {
const { page = 1, pageSize = 20 } = options;
const skip = (page - 1) * pageSize;
const escaped = String(name).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const re = { $regex: escaped, $options: "i" };
return await this.find({ $or: [{ name: re }, { pinyin: re }, { pinyinFL: re }] })
.skip(skip)
.limit(pageSize)
.exec();
};
/**
* 根据城市查询机构列表
*/
OrganizationSchema.statics.findByCity = async function (city, options = {}) {
const { page = 1, pageSize = 20 } = options;
const skip = (page - 1) * pageSize;
return await this.find({ "address.city": city })
.skip(skip)
.limit(pageSize)
.exec();
};
/**
* 根据ID查找机构
*/
OrganizationSchema.statics.findByOrgId = async function (id) {
return await this.findById(id).exec();
};
/**
* 创建机构
*/
OrganizationSchema.statics.createOrg = async function (data) {
data.meta = { createtime: Date.now(), updatetime: Date.now() };
const doc = new this(data);
return await doc.save();
};
/**
* 更新机构
*/
OrganizationSchema.statics.updateOrg = async function (id, update) {
if (update.name) {
update.pinyin = genPinyin(update.name);
update.pinyinFL = genPinyinFL(update.name);
}
update["meta.updatetime"] = Date.now();
return await this.findByIdAndUpdate(id, { $set: update }, { new: true }).exec();
};
// ==================== 索引定义 ====================
OrganizationSchema.index({ name: 1 });
OrganizationSchema.index({ code: 1 });
OrganizationSchema.index({ type: 1, status: 1 });
OrganizationSchema.index({ "address.city": 1, "address.district": 1 });
OrganizationSchema.index({ status: 1 });
export { OrganizationSchema };