feat: 优化需求

This commit is contained in:
huxuejian 2026-10-22 15:30:30 +08:00
parent a88b0edd77
commit 0c369655b7
10 changed files with 126 additions and 24 deletions

View File

@ -26,3 +26,5 @@ CONFIG_PRESCRIPTION_PDF_BASE_URL=https://patient.youcan365.com/hn
## 查询药品信息的时候 带上corpId (测试数据 药品信息存在多家机构, 但是线上各自独立部署, 所以测试环境带corpId区分下) ## 查询药品信息的时候 带上corpId (测试数据 药品信息存在多家机构, 但是线上各自独立部署, 所以测试环境带corpId区分下)
CONFIG_SEARCH_DRUG_WITH_CORPID=YES CONFIG_SEARCH_DRUG_WITH_CORPID=YES
## 生成的咨询订单挂号id编号前缀, 也用于后续生成处方的编号 (his正式环境和测试环境同一个 生成序号规则相同, 产生同编号的话有问题, 所以加个前缀区分下)
CONFIG_CONSULT_REGISTER_ID_PREFIX=Test

View File

@ -37,4 +37,6 @@ CONFIG_PRESCRIPTION_PDF_BASE_URL=https://patient.youcan365.com/hn
## 查询药品信息的时候 带上corpId (测试数据 药品信息存在多家机构, 但是线上各自独立部署, 所以测试环境带corpId区分下) ## 查询药品信息的时候 带上corpId (测试数据 药品信息存在多家机构, 但是线上各自独立部署, 所以测试环境带corpId区分下)
CONFIG_SEARCH_DRUG_WITH_CORPID=YES CONFIG_SEARCH_DRUG_WITH_CORPID=YES
## 生成的咨询订单挂号id编号前缀, 也用于后续生成处方的编号 (his正式环境和测试环境同一个 生成序号规则相同, 产生同编号的话有问题, 所以加个前缀区分下)
CONFIG_CONSULT_REGISTER_ID_PREFIX=Test

View File

@ -498,7 +498,7 @@ function getFailureNotificationExt(error) {
? `HIS上传错误:${cleanHisUploadFailureMessage(rawReason) || "未知错误"}` ? `HIS上传错误:${cleanHisUploadFailureMessage(rawReason) || "未知错误"}`
: errorSource === ERROR_SOURCES.RATIONAL_DRUG : errorSource === ERROR_SOURCES.RATIONAL_DRUG
? `合理用药错误:${rawReason || "未知错误"}` ? `合理用药错误:${rawReason || "未知错误"}`
: `系统错误:${rawReason || "未知错误"}`; : `系统错误:${rawReason || "未知错误"}`;
return { errorSource, reason }; return { errorSource, reason };
} }
@ -845,6 +845,7 @@ async function resolvePrescriptionDrugs(db, order) {
dosage, dosage,
dosage_unit: dosageUnit.name, dosage_unit: dosageUnit.name,
dosage_unit_code: dosageUnit.code, dosage_unit_code: dosageUnit.code,
dose_time_name: requested.dose_time_name || "",
drugName: master.name, drugName: master.name,
specification: master.specification || "", specification: master.specification || "",
frequencyCode: frequency.code, frequencyCode: frequency.code,

View File

@ -1027,6 +1027,7 @@ describe("automatic prescription assembly", () => {
dosage_form: "片剂", dosage_form: "片剂",
days: 3, days: 3,
dosage_unit: "mg", dosage_unit: "mg",
dose_time_name: "餐后",
administration_method: "口服", administration_method: "口服",
freq: "每日两次", freq: "每日两次",
unit: "盒", unit: "盒",
@ -1083,6 +1084,7 @@ describe("automatic prescription assembly", () => {
_id: drugId.toString(), _id: drugId.toString(),
erpId: "ERP-1", erpId: "ERP-1",
dosage_unit_code: "MG", dosage_unit_code: "MG",
dose_time_name: "餐后",
frequencyCode: "BID", frequencyCode: "BID",
usageCode: "PO", usageCode: "PO",
quantity: 1, quantity: 1,

View File

@ -10,6 +10,7 @@ const { encryptOrderFields, decryptOrderFields, decryptOrderList } = require("./
const { getRequiredDrugAccessLevels, doctorMeetsDrugAccessLevels } = require("../drug-access-level"); const { getRequiredDrugAccessLevels, doctorMeetsDrugAccessLevels } = require("../drug-access-level");
const doctorOrPharmacistOfflineMessage = "当前医生或药师不在线,暂无法提交问诊单,请稍后再试"; const doctorOrPharmacistOfflineMessage = "当前医生或药师不在线,暂无法提交问诊单,请稍后再试";
const registerIdPrefix = process.env.CONFIG_CONSULT_REGISTER_ID_PREFIX || '';
const hisPayStatus = { const hisPayStatus = {
0: "已支付", 0: "已支付",
@ -158,7 +159,6 @@ async function addConsultOrder(item) {
async function addOnlineConsultOrder(params) { async function addOnlineConsultOrder(params) {
try { try {
params.registerId = await generateRegisterId(db);
const requiredDrugAccessLevels = await getRequiredDrugAccessLevels(db, params.drugs); const requiredDrugAccessLevels = await getRequiredDrugAccessLevels(db, params.drugs);
const storeApi = require("../drug-store"); const storeApi = require("../drug-store");
const { success, list, message } = await storeApi( const { success, list, message } = await storeApi(
@ -187,6 +187,7 @@ async function addOnlineConsultOrder(params) {
params.drugStoreName = store.name; params.drugStoreName = store.name;
params.drugStoreNo = store.login_no; params.drugStoreNo = store.login_no;
const doctorCode = params.doctorCode.trim(); const doctorCode = params.doctorCode.trim();
params.registerId = await generateRegisterId(db);
// 指定医生 // 指定医生
if (doctorCode) { if (doctorCode) {
const doctor = await db.collection("hlw-doctor").findOne( const doctor = await db.collection("hlw-doctor").findOne(
@ -261,7 +262,9 @@ async function addOnlineConsultOrder(params) {
errorInfo: imAccountResult.errorInfo, errorInfo: imAccountResult.errorInfo,
}; };
} }
params.medorg_order_no = common.generateUniqueStr('HLW', 12); const registerId = await generateRegisterId(db);
params.registerId = registerId
params.medorg_order_no = registerId;
// 加密敏感字段 // 加密敏感字段
const encryptedParams = encryptOrderFields(params); const encryptedParams = encryptOrderFields(params);
const { insertedId } = await db const { insertedId } = await db
@ -2378,5 +2381,5 @@ async function generateRegisterId(db, type = 'consult-register-id') {
} }
); );
const sequence = result.sequence; const sequence = result.sequence;
return `${day}${String(sequence).padStart(4, '0')}`; return `${registerIdPrefix}${day}${String(sequence).padStart(4, '0')}`;
} }

View File

@ -130,10 +130,10 @@ exports.formatUploadRxData = function (data, opt) {
appr_flag: i.forceSelfPay === 'Y' ? '2' : '1', appr_flag: i.forceSelfPay === 'Y' ? '2' : '1',
dose_time_name: i.dose_time_name, dose_time_name: i.dose_time_name,
} }
if (item.appr_flag === '2') { // if (item.appr_flag === '2') {
//[his要求]当appr_flag=2时必填,如果这个药品维护了限制使用范围的,传1,否则就传2 // //[his要求]当appr_flag=2时必填,如果这个药品维护了限制使用范围的,传1,否则就传2
item.self_pay_rea = i.limitUsageScope === 'Y' ? '1' : '2' // item.self_pay_rea = i.limitUsageScope === 'Y' ? '1' : '2'
} // }
return item return item
}), }),
icds: data.diagnosisList.map((i) => ({ icds: data.diagnosisList.map((i) => ({

View File

@ -2234,6 +2234,7 @@ async function getYbReviewResult(ctx, db) {
name: 1, name: 1,
dosage_form: 1, dosage_form: 1,
specification: 1, specification: 1,
forceSelfPay: 1
}, },
} }
) )
@ -2264,6 +2265,7 @@ async function getYbReviewResult(ctx, db) {
dosage: drug.dosage, dosage: drug.dosage,
dosage_unit: drug.dosage_unit, dosage_unit: drug.dosage_unit,
dose_time_name: drug.dose_time_name, dose_time_name: drug.dose_time_name,
forceSelfPay: dbDrug.forceSelfPay
}; };
}); });

View File

@ -31,6 +31,18 @@ function validateAccountId(accountId) {
return { success: true, accountId: accountId.trim() }; return { success: true, accountId: accountId.trim() };
} }
function isValidBirthday(birthday) {
if (!/^\d{4}-\d{2}-\d{2}$/.test(birthday)) return false;
const [year, month, day] = birthday.split('-').map(Number);
const date = new Date(Date.UTC(year, month - 1, day));
return (
date.getUTCFullYear() === year
&& date.getUTCMonth() + 1 === month
&& date.getUTCDate() === day
&& date.getTime() <= Date.now()
);
}
function validatePatient(item) { function validatePatient(item) {
const accountResult = validateAccountId(item && item.accountId); const accountResult = validateAccountId(item && item.accountId);
if (!accountResult.success) return accountResult; if (!accountResult.success) return accountResult;
@ -38,10 +50,27 @@ function validatePatient(item) {
const name = typeof item.name === 'string' ? item.name.trim() : ''; const name = typeof item.name === 'string' ? item.name.trim() : '';
const certNo = typeof item.certNo === 'string' ? item.certNo.trim() : ''; const certNo = typeof item.certNo === 'string' ? item.certNo.trim() : '';
const mobile = typeof item.mobile === 'string' ? item.mobile.trim() : ''; const mobile = typeof item.mobile === 'string' ? item.mobile.trim() : '';
let birthday = typeof item.birthday === 'string' ? item.birthday.trim() : '';
let sex = typeof item.sex === 'string' ? item.sex.trim() : '';
if (!name) return { success: false, message: '姓名不能为空' }; if (!name) return { success: false, message: '姓名不能为空' };
if (certNo && !validator.isChinaId(certNo)) { let certInfo = null;
return { success: false, message: '证件号格式不正确' }; if (certNo) {
certInfo = validator.getChinaIdInfo(certNo);
if (!certInfo) return { success: false, message: '证件号格式不正确' };
birthday = birthday || certInfo.birthday;
sex = sex || certInfo.sex;
}
if (!birthday) return { success: false, message: '出生日期不能为空' };
if (!isValidBirthday(birthday)) {
return { success: false, message: '出生日期格式不正确' };
}
if (!sex) return { success: false, message: '性别不能为空' };
if (!['男', '女'].includes(sex)) {
return { success: false, message: '性别格式不正确' };
}
if (certInfo && (birthday !== certInfo.birthday || sex !== certInfo.sex)) {
return { success: false, message: '出生日期或性别与证件号信息不一致' };
} }
if (!mobile) return { success: false, message: '手机号不能为空' }; if (!mobile) return { success: false, message: '手机号不能为空' };
if (!validator.isMobile(mobile)) { if (!validator.isMobile(mobile)) {
@ -62,6 +91,8 @@ function validatePatient(item) {
name, name,
certNo, certNo,
mobile, mobile,
birthday,
sex,
address, address,
}, },
data: { data: {
@ -69,6 +100,8 @@ function validatePatient(item) {
anotherIdNo: certNo ? Sm4Util.encryptDataForSm3(certNo) : '', anotherIdNo: certNo ? Sm4Util.encryptDataForSm3(certNo) : '',
anotherName: Sm4Util.encryptDataForSm3(name), anotherName: Sm4Util.encryptDataForSm3(name),
anotherMobile: Sm4Util.encryptDataForSm3(mobile), anotherMobile: Sm4Util.encryptDataForSm3(mobile),
birthday,
sex,
address, address,
}, },
}; };
@ -90,14 +123,16 @@ async function addHlwPatient(item, db) {
} }
} }
// if (validation.origin.certNo) { // if (validation.origin.certNo) {
const res = await hnHis({ // const res = await hnHis({
type: 'addHisCustomer', // type: 'addHisCustomer',
name: validation.origin.name, // name: validation.origin.name,
idCard: validation.origin.certNo, // idCard: validation.origin.certNo,
mobile: validation.origin.mobile, // mobile: validation.origin.mobile,
address: validation.origin.address, // address: validation.origin.address,
}) // birthday: validation.origin.birthday,
if (!res.success) return res; // sex: validation.origin.sex,
// })
// if (!res.success) return res;
// } // }
const result = await db.collection(COLLECTION_NAME).insertOne({ const result = await db.collection(COLLECTION_NAME).insertOne({
@ -150,7 +185,7 @@ async function getHlwPatientList(item, db) {
.find({ accountId: validation.accountId, disabled: false }) .find({ accountId: validation.accountId, disabled: false })
.toArray(); .toArray();
const data = patients.map(toPatientData); const data = patients.map((patient) => toPatientData(patient, true));
return { success: true, message: '查询就诊人列表成功', data }; return { success: true, message: '查询就诊人列表成功', data };
} catch (error) { } catch (error) {
@ -181,13 +216,39 @@ async function getHlwPatient(item, db) {
} }
} }
function toPatientData(patient) { function calculateAge(birthday) {
if (!isValidBirthday(birthday)) return '';
const [birthYear, birthMonth, birthDay] = birthday.split('-').map(Number);
const today = new Date();
let age = today.getFullYear() - birthYear;
const hasHadBirthdayThisYear = (
today.getMonth() + 1 > birthMonth
|| (today.getMonth() + 1 === birthMonth && today.getDate() >= birthDay)
);
if (!hasHadBirthdayThisYear) age -= 1;
return age > 0 ? age : 1;
}
function toPatientData(patient, normalizeDemographics = false) {
const certNo = Sm4Util.decryptDataForSm3(patient.anotherIdNo);
const certInfo = normalizeDemographics && certNo
? validator.getChinaIdInfo(certNo)
: null;
// 证件号信息仅用于本次响应,不回写数据库中的历史就诊人数据。
const birthday = certInfo ? certInfo.birthday : (patient.birthday || '');
const sex = certInfo ? certInfo.sex : (patient.sex || '');
return { return {
id: patient._id.toString(), id: patient._id.toString(),
accountId: patient.accountId, accountId: patient.accountId,
name: Sm4Util.decryptDataForSm3(patient.anotherName), name: Sm4Util.decryptDataForSm3(patient.anotherName),
certNo: Sm4Util.decryptDataForSm3(patient.anotherIdNo), certNo,
mobile: Sm4Util.decryptDataForSm3(patient.anotherMobile), mobile: Sm4Util.decryptDataForSm3(patient.anotherMobile),
birthday,
sex,
...(normalizeDemographics ? { age: calculateAge(birthday) } : {}),
address: patient.address || '', address: patient.address || '',
createTime: patient.createTime, createTime: patient.createTime,
}; };

View File

@ -77,8 +77,11 @@ async function addHisCustomer(item) {
const mdtrt_cert_no = typeof item.mdtrt_cert_no === 'string' ? item.mdtrt_cert_no.trim() : ''; const mdtrt_cert_no = typeof item.mdtrt_cert_no === 'string' ? item.mdtrt_cert_no.trim() : '';
const ecToken = typeof item.ecToken === 'string' ? item.ecToken.trim() : ''; const ecToken = typeof item.ecToken === 'string' ? item.ecToken.trim() : '';
const card_sn = typeof item.card_sn === 'string' ? item.card_sn.trim() : ''; const card_sn = typeof item.card_sn === 'string' ? item.card_sn.trim() : '';
const birthday = typeof item.birthday === 'string' ? item.birthday.trim() : '';
const sex = typeof item.sex === 'string' ? item.sex.trim() : '';
if (!name || !mobile) { if (!name || !mobile) {
return { success: false, message: '姓名、证件号、手机号不能为空' } return { success: false, message: '姓名、手机号不能为空' }
} }
const payload = { const payload = {
type: 'addHisCustomer', type: 'addHisCustomer',
@ -88,6 +91,10 @@ async function addHisCustomer(item) {
address, address,
buildType: '1', // 1.自费建档 2.电子凭证建档 3.线下医保 buildType: '1', // 1.自费建档 2.电子凭证建档 3.线下医保
}; };
if (!idCard) {
payload.brdy = birthday;
payload.gend = sex === '男' ? 1 : 2
}
// 电子凭证建档 医保插件授权建档 // 电子凭证建档 医保插件授权建档
if (psnToken) { if (psnToken) {
payload.psnToken = psnToken; payload.psnToken = psnToken;

View File

@ -173,10 +173,32 @@ function isChinaId(s) {
return true return true
} }
function getChinaIdInfo(s) {
if (typeof s !== 'string' || !isChinaId(s)) return null;
const sId = idTransfer(s);
const birthday = `${sId.substring(6, 10)}-${sId.substring(10, 12)}-${sId.substring(12, 14)}`;
const [year, month, day] = birthday.split('-').map(Number);
const date = new Date(Date.UTC(year, month - 1, day));
if (
date.getUTCFullYear() !== year
|| date.getUTCMonth() + 1 !== month
|| date.getUTCDate() !== day
) {
return null;
}
return {
birthday,
sex: Number(sId.charAt(16)) % 2 === 0 ? '女' : '男',
};
}
module.exports = { module.exports = {
verifyString, verifyString,
verifyNumber, verifyNumber,
verifyListItem, verifyListItem,
isMobile, isMobile,
isChinaId isChinaId,
} getChinaIdInfo,
}