更新
This commit is contained in:
@@ -132,9 +132,42 @@ class AuthMiddleware
|
||||
return true;
|
||||
}
|
||||
|
||||
// 处方库列表:消费者开方页/处方库页导入共用,复用开方或处方库菜单权限
|
||||
if ($accessUri === 'tcm.prescriptionlibrary/lists'
|
||||
&& $this->matchPrescriptionLibraryListsPermission($adminUris)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处方库 lists:与开方、处方库维护菜单权限互通(避免开方页「从处方库导入」403)
|
||||
*/
|
||||
private function matchPrescriptionLibraryListsPermission(array $adminUris): bool
|
||||
{
|
||||
$aliases = [
|
||||
'tcm.prescriptionlibrary/lists',
|
||||
'tcm.prescription/lists',
|
||||
'tcm.prescription/add',
|
||||
'tcm.prescription/edit',
|
||||
'tcm.prescription/detail',
|
||||
'cf.prescription/lists',
|
||||
'cf.prescription/add',
|
||||
'cf.prescription/edit',
|
||||
'cf.prescription/read',
|
||||
'cf.prescription/del',
|
||||
'cf.prescription/audit',
|
||||
'wcf.prescription/lists',
|
||||
'wcf.prescription/read',
|
||||
'wcf.prescription/add',
|
||||
'wcf.prescription/edit',
|
||||
'wcf.prescription/delete',
|
||||
];
|
||||
|
||||
return count(array_intersect($aliases, $adminUris)) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 面诊进度专用:auth.admin/lists、doctor.appointment/lists + progress_board=1,不校验菜单权限
|
||||
*/
|
||||
|
||||
@@ -21,7 +21,7 @@ class PrescriptionLibraryLists extends BaseAdminDataLists implements ListsSearch
|
||||
{
|
||||
return [
|
||||
'%like%' => ['prescription_name'],
|
||||
'=' => ['is_public', 'creator_id']
|
||||
'=' => ['is_public', 'creator_id', 'formula_type']
|
||||
];
|
||||
}
|
||||
|
||||
@@ -34,6 +34,16 @@ class PrescriptionLibraryLists extends BaseAdminDataLists implements ListsSearch
|
||||
return $query;
|
||||
}
|
||||
|
||||
// 开方页导入专用参数(勿与搜索项 creator_id 混用,否则无法 OR 出他人公开模板)
|
||||
$prescribingCreatorId = (int) ($this->params['prescribing_creator_id'] ?? 0);
|
||||
if ($prescribingCreatorId > 0
|
||||
&& PrescriptionLibraryLogic::canListLibraryForCreator($this->adminId, $this->adminInfo, $prescribingCreatorId)) {
|
||||
return $query->where(function ($q) use ($prescribingCreatorId) {
|
||||
$q->where('creator_id', $prescribingCreatorId)
|
||||
->whereOr('is_public', 1);
|
||||
});
|
||||
}
|
||||
|
||||
return $query->where(function ($q) {
|
||||
$q->where('creator_id', $this->adminId)
|
||||
->whereOr('is_public', 1);
|
||||
@@ -46,7 +56,7 @@ class PrescriptionLibraryLists extends BaseAdminDataLists implements ListsSearch
|
||||
public function lists(): array
|
||||
{
|
||||
$field = [
|
||||
'id', 'prescription_name', 'herbs', 'is_public',
|
||||
'id', 'prescription_name', 'formula_type', 'herbs', 'is_public',
|
||||
'creator_id', 'creator_name', 'create_time', 'update_time'
|
||||
];
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\logic\tcm;
|
||||
|
||||
use app\common\cache\AdminAuthCache;
|
||||
use app\common\logic\BaseLogic;
|
||||
use app\common\model\auth\AdminRole;
|
||||
use app\common\model\tcm\PrescriptionLibrary;
|
||||
@@ -33,12 +34,64 @@ class PrescriptionLibraryLogic extends BaseLogic
|
||||
return count(array_intersect($myRoles, $allowRoles)) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 是否具备消费者/诊间开方相关菜单权限(用于处方库列表鉴权别名)
|
||||
*/
|
||||
public static function hasPrescriptionOperatePermission(int $adminId): bool
|
||||
{
|
||||
$cache = new AdminAuthCache($adminId);
|
||||
$uris = $cache->getAdminUri() ?? [];
|
||||
$normalized = array_map(static fn ($item) => strtolower((string) $item), $uris);
|
||||
$allowed = [
|
||||
'tcm.prescription/lists',
|
||||
'tcm.prescription/add',
|
||||
'tcm.prescription/edit',
|
||||
'tcm.prescription/detail',
|
||||
'cf.prescription/lists',
|
||||
'cf.prescription/add',
|
||||
'cf.prescription/edit',
|
||||
'cf.prescription/read',
|
||||
'cf.prescription/del',
|
||||
'cf.prescription/audit',
|
||||
'wcf.prescription/lists',
|
||||
'wcf.prescription/read',
|
||||
'wcf.prescription/add',
|
||||
'wcf.prescription/edit',
|
||||
'wcf.prescription/delete',
|
||||
'tcm.prescriptionlibrary/lists',
|
||||
];
|
||||
|
||||
return count(array_intersect($allowed, $normalized)) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 开方页按医师 creator_id 拉取处方库:本人 / 超管角色 / 有开方菜单权限(医助代开方)
|
||||
*/
|
||||
public static function canListLibraryForCreator(int $adminId, array $adminInfo, int $targetCreatorId): bool
|
||||
{
|
||||
if ($targetCreatorId <= 0) {
|
||||
return false;
|
||||
}
|
||||
if ($targetCreatorId === $adminId) {
|
||||
return true;
|
||||
}
|
||||
if (self::canManageAllPrescriptions($adminId, $adminInfo)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return self::hasPrescriptionOperatePermission($adminId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 添加处方库
|
||||
*/
|
||||
public static function add(array $params): ?int
|
||||
{
|
||||
try {
|
||||
$params['formula_type'] = in_array($params['formula_type'] ?? '', ['主方', '辅方'], true)
|
||||
? $params['formula_type']
|
||||
: '主方';
|
||||
|
||||
// 处理药材数据
|
||||
if (isset($params['herbs']) && is_array($params['herbs'])) {
|
||||
$params['herbs'] = json_encode($params['herbs'], JSON_UNESCAPED_UNICODE);
|
||||
@@ -69,6 +122,12 @@ class PrescriptionLibraryLogic extends BaseLogic
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($params['formula_type'])) {
|
||||
$params['formula_type'] = in_array($params['formula_type'], ['主方', '辅方'], true)
|
||||
? $params['formula_type']
|
||||
: '主方';
|
||||
}
|
||||
|
||||
// 处理药材数据
|
||||
if (isset($params['herbs']) && is_array($params['herbs'])) {
|
||||
$params['herbs'] = json_encode($params['herbs'], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
@@ -159,6 +159,35 @@ class PrescriptionLogic
|
||||
return $ts ? date('Y-m-d', $ts) : date('Y-m-d');
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 辅方用法 JSON(与主方 dosage/times/days 字段结构一致)
|
||||
*/
|
||||
private static function normalizeAuxUsage(array $params): ?array
|
||||
{
|
||||
$raw = $params['aux_usage'] ?? null;
|
||||
if ($raw === null || $raw === '' || $raw === []) {
|
||||
return null;
|
||||
}
|
||||
if (is_string($raw)) {
|
||||
$decoded = json_decode($raw, true);
|
||||
$raw = is_array($decoded) ? $decoded : null;
|
||||
}
|
||||
if (!is_array($raw)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'dosage_amount' => isset($raw['dosage_amount']) && $raw['dosage_amount'] !== ''
|
||||
? (float) $raw['dosage_amount']
|
||||
: null,
|
||||
'dosage_bag_count' => self::normalizeDosageBagCount($raw['dosage_bag_count'] ?? 1),
|
||||
'need_decoction' => (int) ($raw['need_decoction'] ?? 0),
|
||||
'bags_per_dose' => isset($raw['bags_per_dose']) ? (int) $raw['bags_per_dose'] : 1,
|
||||
'times_per_day' => (int) ($raw['times_per_day'] ?? 3),
|
||||
'usage_days' => (int) ($raw['usage_days'] ?? 7),
|
||||
];
|
||||
}
|
||||
|
||||
private static function normalizeDosageBagCount($raw): int
|
||||
{
|
||||
$count = (int) $raw;
|
||||
@@ -273,6 +302,7 @@ class PrescriptionLogic
|
||||
'dose_unit' => $params['dose_unit'] ?? '剂',
|
||||
'usage_days' => (int)($params['usage_days'] ?? 7),
|
||||
'times_per_day' => (int)($params['times_per_day'] ?? 2),
|
||||
'aux_usage' => self::normalizeAuxUsage($params),
|
||||
'usage_instruction' => $params['usage_instruction'] ?? '水煎服一日二次',
|
||||
'usage_time' => $params['usage_time'] ?? '饭前',
|
||||
'usage_way' => $params['usage_way'] ?? '温水送服',
|
||||
@@ -398,6 +428,9 @@ class PrescriptionLogic
|
||||
'dose_unit' => $params['dose_unit'] ?? $prescription->dose_unit,
|
||||
'usage_days' => (int)($params['usage_days'] ?? $prescription->usage_days),
|
||||
'times_per_day' => (int)($params['times_per_day'] ?? $prescription->times_per_day ?? 2),
|
||||
'aux_usage' => array_key_exists('aux_usage', $params)
|
||||
? self::normalizeAuxUsage($params)
|
||||
: $prescription->aux_usage,
|
||||
'usage_instruction' => $params['usage_instruction'] ?? $prescription->usage_instruction,
|
||||
'usage_time' => $params['usage_time'] ?? $prescription->usage_time,
|
||||
'usage_way' => $params['usage_way'] ?? $prescription->usage_way,
|
||||
|
||||
@@ -1413,6 +1413,7 @@ class PrescriptionOrderLogic
|
||||
|
||||
$payload['official_urls'] = ExpressTrackService::officialUrls($num);
|
||||
$payload['tracking_number'] = $num;
|
||||
$payload['order_id'] = $id;
|
||||
$payload['express_company_used'] = $ec;
|
||||
|
||||
return $payload;
|
||||
@@ -2556,7 +2557,7 @@ class PrescriptionOrderLogic
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出用:诊单医助在 admin_dept 中的部门路径(多部门「;」、路径内「 / 」)
|
||||
* 导出用:管理员在 admin_dept 中的部门路径(多部门「;」、路径内「 / 」;业务订单导出取 creator_id)
|
||||
*/
|
||||
public static function formatAssistantDeptPathForExport(int $assistantAdminId): string
|
||||
{
|
||||
@@ -2846,21 +2847,21 @@ class PrescriptionOrderLogic
|
||||
$item['export_patient_gender'] = $g === 1 ? '男' : '女';
|
||||
$item['export_patient_age'] = (string) ($dg['age'] ?? '');
|
||||
$item['export_patient_phone'] = (string) ($dg['phone'] ?? '');
|
||||
$rxAst = \is_array($rx) ? (int) ($rx['assistant_id'] ?? 0) : 0;
|
||||
$diagAst = (int) ($dg['assistant_id'] ?? 0);
|
||||
$astId = self::resolveAssistantAdminIdForPrescriptionOrderRow($rxAst, $diagAst);
|
||||
if ($astId > 0) {
|
||||
if (!isset($assistantDeptCache[$astId])) {
|
||||
$assistantDeptCache[$astId] = self::formatAssistantDeptPathForExport($astId);
|
||||
}
|
||||
$item['export_assistant_dept'] = $assistantDeptCache[$astId];
|
||||
} else {
|
||||
$item['export_assistant_dept'] = '';
|
||||
}
|
||||
} else {
|
||||
$item['export_patient_gender'] = '';
|
||||
$item['export_patient_age'] = '';
|
||||
$item['export_patient_phone'] = '';
|
||||
}
|
||||
|
||||
// 导出「医助名字 / 医助部门」:与详情 order_creator 一致,按业务订单 creator_id(非诊单二诊 assistant_id)
|
||||
$creatorId = (int) ($item['creator_id'] ?? 0);
|
||||
$item['assistant_name'] = (string) ($item['creator_name'] ?? '');
|
||||
if ($creatorId > 0) {
|
||||
if (!isset($assistantDeptCache[$creatorId])) {
|
||||
$assistantDeptCache[$creatorId] = self::formatAssistantDeptPathForExport($creatorId);
|
||||
}
|
||||
$item['export_assistant_dept'] = $assistantDeptCache[$creatorId];
|
||||
} else {
|
||||
$item['export_assistant_dept'] = '';
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ class PrescriptionLibraryValidate extends BaseValidate
|
||||
protected $rule = [
|
||||
'id' => 'require|number',
|
||||
'prescription_name' => 'require|max:100',
|
||||
'formula_type' => 'in:主方,辅方',
|
||||
'herbs' => 'require|array',
|
||||
'is_public' => 'in:0,1'
|
||||
];
|
||||
@@ -23,6 +24,7 @@ class PrescriptionLibraryValidate extends BaseValidate
|
||||
'id.number' => '处方ID必须为数字',
|
||||
'prescription_name.require' => '处方名称不能为空',
|
||||
'prescription_name.max' => '处方名称最多100个字符',
|
||||
'formula_type.in' => '处方类型只能是主方或辅方',
|
||||
'herbs.require' => '药材列表不能为空',
|
||||
'herbs.array' => '药材列表格式错误',
|
||||
'is_public.in' => '是否公开参数错误'
|
||||
@@ -33,7 +35,7 @@ class PrescriptionLibraryValidate extends BaseValidate
|
||||
*/
|
||||
public function sceneAdd()
|
||||
{
|
||||
return $this->only(['prescription_name', 'herbs', 'is_public']);
|
||||
return $this->only(['prescription_name', 'formula_type', 'herbs', 'is_public']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -41,7 +43,7 @@ class PrescriptionLibraryValidate extends BaseValidate
|
||||
*/
|
||||
public function sceneEdit()
|
||||
{
|
||||
return $this->only(['id', 'prescription_name', 'herbs', 'is_public']);
|
||||
return $this->only(['id', 'prescription_name', 'formula_type', 'herbs', 'is_public']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -41,7 +41,7 @@ class PrescriptionValidate extends BaseValidate
|
||||
'patient_name', 'gender', 'age',
|
||||
'visit_no', 'prescription_date', 'tongue', 'tongue_image', 'pulse',
|
||||
'pulse_condition', 'clinical_diagnosis', 'herbs', 'dose_count', 'dose_unit',
|
||||
'usage_days', 'times_per_day', 'usage_instruction', 'usage_time', 'usage_way', 'dietary_taboo',
|
||||
'usage_days', 'times_per_day', 'aux_usage', 'usage_instruction', 'usage_time', 'usage_way', 'dietary_taboo',
|
||||
'usage_notes', 'doctor_name', 'is_shared', 'visible_role_ids',
|
||||
'diagnosis_id', 'appointment_id', 'audit_status',
|
||||
]);
|
||||
@@ -54,7 +54,7 @@ class PrescriptionValidate extends BaseValidate
|
||||
'patient_name', 'gender', 'age',
|
||||
'visit_no', 'prescription_date', 'tongue', 'tongue_image', 'pulse',
|
||||
'pulse_condition', 'clinical_diagnosis', 'herbs', 'dose_count', 'dose_unit',
|
||||
'usage_days', 'times_per_day', 'usage_instruction', 'usage_time', 'usage_way', 'dietary_taboo',
|
||||
'usage_days', 'times_per_day', 'aux_usage', 'usage_instruction', 'usage_time', 'usage_way', 'dietary_taboo',
|
||||
'usage_notes', 'doctor_name', 'is_shared', 'visible_role_ids', 'diagnosis_id',
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ use think\console\Output;
|
||||
* php think gancao:sync-logistics 默认拉 100 单
|
||||
* php think gancao:sync-logistics --limit=200 自定义拉取上限
|
||||
* php think gancao:sync-logistics --order-id=123 只跑指定订单
|
||||
* php think gancao:sync-logistics -t SF1234567890 按快递单号走快递100 查询并落库
|
||||
* php think gancao:sync-logistics --detail 打印每单详细
|
||||
*
|
||||
* 建议 crontab(每 30 分钟执行一次):
|
||||
@@ -40,6 +41,7 @@ class GancaoSyncLogisticsRoute extends Command
|
||||
->setDescription('同步甘草订单的物流路由(GET_TASK_ROUTE_LIST)到本地物流追踪表')
|
||||
->addOption('limit', 'l', Option::VALUE_OPTIONAL, '本次最多处理多少条订单', 100)
|
||||
->addOption('order-id', null, Option::VALUE_OPTIONAL, '只同步指定 prescription_order.id', null)
|
||||
->addOption('tracking-number', 't', Option::VALUE_REQUIRED, '按快递单号走快递100 查询并落库')
|
||||
->addOption('detail', 'd', Option::VALUE_NONE, '打印每单详细结果');
|
||||
}
|
||||
|
||||
@@ -48,26 +50,43 @@ class GancaoSyncLogisticsRoute extends Command
|
||||
$limit = max(1, (int) $input->getOption('limit'));
|
||||
$onlyOrderId = $input->getOption('order-id');
|
||||
$onlyOrderId = $onlyOrderId !== null ? (int) $onlyOrderId : null;
|
||||
$trackingNumber = trim((string) $input->getOption('tracking-number'));
|
||||
$verbose = (bool) $input->getOption('detail');
|
||||
|
||||
if ($trackingNumber !== '' && $onlyOrderId !== null) {
|
||||
$output->error('请勿同时使用 --tracking-number 与 --order-id');
|
||||
return 1;
|
||||
}
|
||||
|
||||
$output->writeln('========================================');
|
||||
$output->writeln('甘草物流路由同步');
|
||||
$output->writeln($trackingNumber !== '' ? '快递100 物流查询' : '甘草物流路由同步');
|
||||
$output->writeln('========================================');
|
||||
|
||||
if (!GancaoScmRecipelService::isConfigured()) {
|
||||
if ($trackingNumber === '' && !GancaoScmRecipelService::isConfigured()) {
|
||||
$output->error('甘草 SCM 未配置:' . GancaoScmRecipelService::whyNotConfigured());
|
||||
return 1;
|
||||
}
|
||||
|
||||
$start = microtime(true);
|
||||
$output->writeln('开始同步...(limit=' . $limit . ($onlyOrderId ? ', order_id=' . $onlyOrderId : '') . ')');
|
||||
if ($trackingNumber !== '') {
|
||||
$output->writeln('开始查询...(快递100,tracking_number=' . $trackingNumber . ')');
|
||||
} else {
|
||||
$output->writeln('开始同步...(limit=' . $limit . ($onlyOrderId ? ', order_id=' . $onlyOrderId : '') . ')');
|
||||
}
|
||||
|
||||
try {
|
||||
$stats = GancaoLogisticsRouteService::syncBatch($limit, $onlyOrderId);
|
||||
$recon = ExpressTrackingService::reconcileAssistantReleaseForShippedPrescriptionOrders(200);
|
||||
$stats['reconcile_cleared'] = (int) ($recon['cleared'] ?? 0);
|
||||
$stats['reconcile_scanned'] = (int) ($recon['scanned'] ?? 0);
|
||||
$stats['reconcile_lines'] = $recon['lines'] ?? [];
|
||||
if ($trackingNumber !== '') {
|
||||
$stats = ExpressTrackingService::queryKuaidiByTrackingNumber($trackingNumber);
|
||||
$stats['reconcile_cleared'] = 0;
|
||||
$stats['reconcile_scanned'] = 0;
|
||||
$stats['reconcile_lines'] = [];
|
||||
} else {
|
||||
$stats = GancaoLogisticsRouteService::syncBatch($limit, $onlyOrderId);
|
||||
$recon = ExpressTrackingService::reconcileAssistantReleaseForShippedPrescriptionOrders(2000);
|
||||
$stats['reconcile_cleared'] = (int) ($recon['cleared'] ?? 0);
|
||||
$stats['reconcile_scanned'] = (int) ($recon['scanned'] ?? 0);
|
||||
$stats['reconcile_lines'] = $recon['lines'] ?? [];
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$output->error('同步异常:' . $e->getMessage());
|
||||
return 1;
|
||||
@@ -81,7 +100,7 @@ class GancaoSyncLogisticsRoute extends Command
|
||||
foreach ($stats['details'] as $row) {
|
||||
$tag = !empty($row['success']) ? '[OK]' : '[FAIL]';
|
||||
$line = sprintf(
|
||||
'%s order_id=%s order_no=%s app_order_no=%s tn=%s state=%s traces=+%s msg=%s',
|
||||
'%s order_id=%s order_no=%s app_order_no=%s tn=%s state=%s traces=+%s source=%s msg=%s',
|
||||
$tag,
|
||||
$row['order_id'] ?? '',
|
||||
$row['order_no'] ?? '',
|
||||
@@ -89,6 +108,7 @@ class GancaoSyncLogisticsRoute extends Command
|
||||
$row['tracking_number'] ?? '',
|
||||
$row['state'] ?? '',
|
||||
$row['traces'] ?? 0,
|
||||
$row['source'] ?? '',
|
||||
$row['message'] ?? ''
|
||||
);
|
||||
$output->writeln($line);
|
||||
|
||||
@@ -18,7 +18,7 @@ class Prescription extends BaseModel
|
||||
protected $deleteTime = 'delete_time';
|
||||
protected $dateFormat = false;
|
||||
|
||||
protected $json = ['herbs', 'case_record'];
|
||||
protected $json = ['herbs', 'case_record', 'aux_usage'];
|
||||
protected $jsonAssoc = true;
|
||||
|
||||
// 字段类型转换
|
||||
|
||||
@@ -45,6 +45,7 @@ class ExpressTrackService
|
||||
$carrier = $resolved['carrier'];
|
||||
$kuaidiCom = $resolved['kuaidi_com'];
|
||||
$label = $resolved['label'];
|
||||
$comCandidates = self::kuaidiComCandidates($kuaidiCom, $num);
|
||||
|
||||
$officialUrl = self::buildOfficialUrl($carrier, $num);
|
||||
|
||||
@@ -75,6 +76,74 @@ class ExpressTrackService
|
||||
return $out;
|
||||
}
|
||||
|
||||
$lastFail = null;
|
||||
foreach ($comCandidates as $tryCom) {
|
||||
$tryOut = self::queryKuaidiOnce($cfg, $tryCom, $num, $phoneForKuaidi, $carrier, $label);
|
||||
if (!empty($tryOut['traces']) || ($tryOut['state'] ?? '') !== '') {
|
||||
return $tryOut;
|
||||
}
|
||||
$lastFail = $tryOut;
|
||||
}
|
||||
|
||||
return $lastFail ?? $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据运单号形态纠正承运商(避免 express_tracking 误存 sf 导致京东单查不出)
|
||||
*/
|
||||
public static function normalizeExpressCompanyCode(string $trackingNumber, string $storedCompany = 'auto'): string
|
||||
{
|
||||
$byNumber = self::detectCarrierFromNumber($trackingNumber);
|
||||
if ($byNumber === null) {
|
||||
$ec = strtolower(trim($storedCompany));
|
||||
|
||||
return in_array($ec, ['sf', 'jd', 'jt', 'jtexpress', 'auto'], true) ? $ec : 'auto';
|
||||
}
|
||||
|
||||
$ec = strtolower(trim($storedCompany));
|
||||
$byEc = self::carrierFromExpressCode($ec);
|
||||
if ($byEc !== null && $byEc['carrier'] !== $byNumber['carrier']) {
|
||||
return $byNumber['carrier'];
|
||||
}
|
||||
|
||||
return $byNumber['carrier'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $cfg
|
||||
* @return array{
|
||||
* carrier: string,
|
||||
* carrier_label: string,
|
||||
* kuaidi_com: string,
|
||||
* traces: list<array{time:string,context:string}>,
|
||||
* state: string,
|
||||
* state_text: string,
|
||||
* source: string,
|
||||
* hint: string,
|
||||
* official_url: string
|
||||
* }
|
||||
*/
|
||||
private static function queryKuaidiOnce(
|
||||
array $cfg,
|
||||
string $kuaidiCom,
|
||||
string $num,
|
||||
string $phoneForKuaidi,
|
||||
string $carrier,
|
||||
string $label
|
||||
): array {
|
||||
$officialUrl = self::buildOfficialUrl($carrier, $num);
|
||||
$out = [
|
||||
'carrier' => $carrier,
|
||||
'carrier_label' => $label,
|
||||
'kuaidi_com' => $kuaidiCom,
|
||||
'traces' => [],
|
||||
'state' => '',
|
||||
'state_text' => '',
|
||||
'source' => 'kuaidi100',
|
||||
'hint' => '',
|
||||
'official_url' => $officialUrl,
|
||||
];
|
||||
|
||||
$paramArr = [
|
||||
'com' => $kuaidiCom,
|
||||
'num' => $num,
|
||||
@@ -98,7 +167,7 @@ class ExpressTrackService
|
||||
$raw = self::httpPostForm($url, $postBody);
|
||||
if ($raw === null || $raw === '') {
|
||||
$out['hint'] = '快递100接口无响应,请稍后重试或使用官网查询';
|
||||
Log::warning('ExpressTrackService kuaidi100 empty response', ['num' => $num]);
|
||||
Log::warning('ExpressTrackService kuaidi100 empty response', ['num' => $num, 'com' => $kuaidiCom]);
|
||||
|
||||
return $out;
|
||||
}
|
||||
@@ -106,7 +175,7 @@ class ExpressTrackService
|
||||
$json = json_decode($raw, true);
|
||||
if (!is_array($json)) {
|
||||
$out['hint'] = '快递100返回异常,请使用官网查询';
|
||||
Log::warning('ExpressTrackService kuaidi100 invalid json', ['raw' => mb_substr($raw, 0, 500)]);
|
||||
Log::warning('ExpressTrackService kuaidi100 invalid json', ['raw' => mb_substr($raw, 0, 500), 'com' => $kuaidiCom]);
|
||||
|
||||
return $out;
|
||||
}
|
||||
@@ -114,19 +183,16 @@ class ExpressTrackService
|
||||
if (isset($json['result']) && $json['result'] === false) {
|
||||
$msg = (string) ($json['message'] ?? '查询失败');
|
||||
$returnCode = (string) ($json['returnCode'] ?? '');
|
||||
|
||||
// 特殊处理"找不到对应公司"错误
|
||||
if ($msg === '找不到对应公司' || $returnCode === '400') {
|
||||
$out['hint'] = '快递100暂不支持该快递公司或编码错误,请使用下方官网链接查询';
|
||||
} else {
|
||||
$out['hint'] = $msg;
|
||||
}
|
||||
|
||||
Log::info('ExpressTrackService kuaidi100 business fail', [
|
||||
'message' => $msg,
|
||||
'returnCode' => $returnCode,
|
||||
'num' => $num,
|
||||
'com' => $kuaidiCom
|
||||
'com' => $kuaidiCom,
|
||||
]);
|
||||
|
||||
return $out;
|
||||
@@ -138,7 +204,7 @@ class ExpressTrackService
|
||||
}
|
||||
if (($json['message'] ?? '') !== 'ok' && $data === []) {
|
||||
$out['hint'] = (string) ($json['message'] ?? '未查到轨迹');
|
||||
Log::info('ExpressTrackService kuaidi100 no data', ['json' => $json]);
|
||||
Log::info('ExpressTrackService kuaidi100 no data', ['json' => $json, 'com' => $kuaidiCom]);
|
||||
|
||||
return $out;
|
||||
}
|
||||
@@ -159,12 +225,35 @@ class ExpressTrackService
|
||||
$out['traces'] = $traces;
|
||||
$out['state'] = (string) ($json['state'] ?? '');
|
||||
$out['state_text'] = self::stateText($out['state']);
|
||||
$out['source'] = 'kuaidi100';
|
||||
$out['hint'] = $traces === [] ? '暂无轨迹节点,单号可能尚未揽收' : '';
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
private static function kuaidiComCandidates(string $primaryCom, string $num): array
|
||||
{
|
||||
$list = [$primaryCom];
|
||||
$byNumber = self::detectCarrierFromNumber($num);
|
||||
if ($byNumber !== null && !in_array($byNumber['kuaidi_com'], $list, true)) {
|
||||
$list[] = $byNumber['kuaidi_com'];
|
||||
}
|
||||
if (preg_match('/^JDVE/i', strtoupper($num)) && !in_array('jd', $list, true)) {
|
||||
$list[] = 'jd';
|
||||
}
|
||||
if (preg_match('/^(JD|JDV|JDK|JDEX)/i', strtoupper($num))) {
|
||||
foreach (['jingdong', 'jd'] as $c) {
|
||||
if (!in_array($c, $list, true)) {
|
||||
$list[] = $c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_values(array_unique(array_filter($list, static fn ($c) => $c !== '' && $c !== 'auto')));
|
||||
}
|
||||
|
||||
/**
|
||||
* 快递100「phone」入参:有手动覆盖且不少于 4 位时用覆盖;否则用订单收货号码。
|
||||
* 对 11 位及以上数字取后 11 位作为手机号(去掉可能的前缀符号位)。
|
||||
@@ -191,32 +280,75 @@ class ExpressTrackService
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{carrier: string, kuaidi_com: string, label: string}
|
||||
* @return array{carrier: string, kuaidi_com: string, label: string}|null
|
||||
*/
|
||||
private static function resolveCarrier(string $expressCompany, string $num): array
|
||||
private static function carrierFromExpressCode(string $expressCompany): ?array
|
||||
{
|
||||
$ec = strtolower(trim($expressCompany));
|
||||
if ($ec === 'sf') {
|
||||
if ($ec === 'sf' || $ec === 'shunfeng') {
|
||||
return ['carrier' => 'sf', 'kuaidi_com' => self::KUAIDI_COM_SF, 'label' => '顺丰速运'];
|
||||
}
|
||||
if ($ec === 'jd') {
|
||||
if ($ec === 'jd' || $ec === 'jingdong') {
|
||||
return ['carrier' => 'jd', 'kuaidi_com' => self::KUAIDI_COM_JD, 'label' => '京东快递'];
|
||||
}
|
||||
if ($ec === 'jt' || $ec === 'jtexpress') {
|
||||
return ['carrier' => 'jt', 'kuaidi_com' => self::KUAIDI_COM_JT, 'label' => '极兔速递'];
|
||||
}
|
||||
|
||||
$u = strtoupper($num);
|
||||
if (preg_match('/^SF\d/i', $num)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{carrier: string, kuaidi_com: string, label: string}|null
|
||||
*/
|
||||
private static function detectCarrierFromNumber(string $num): ?array
|
||||
{
|
||||
$n = trim($num);
|
||||
if ($n === '') {
|
||||
return null;
|
||||
}
|
||||
$u = strtoupper($n);
|
||||
if (preg_match('/^SF\d/i', $n)) {
|
||||
return ['carrier' => 'sf', 'kuaidi_com' => self::KUAIDI_COM_SF, 'label' => '顺丰速运(单号识别)'];
|
||||
}
|
||||
if (preg_match('/^(JD|JDV|JDK|JDEX|JD[A-Z0-9])/i', $u)) {
|
||||
if (preg_match('/^JDVE/i', $u)) {
|
||||
return ['carrier' => 'jd', 'kuaidi_com' => self::KUAIDI_COM_JD, 'label' => '京东快递(单号识别)'];
|
||||
}
|
||||
if (preg_match('/^JT\d{13}$/i', $num)) {
|
||||
if (preg_match('/^(JDK|JDV|JDEX)/i', $u) || preg_match('/^JD[A-Z0-9]{10,}/i', $u)) {
|
||||
return ['carrier' => 'jd', 'kuaidi_com' => self::KUAIDI_COM_JD, 'label' => '京东物流(单号识别)'];
|
||||
}
|
||||
if (preg_match('/^JT\d{13}$/i', $n)) {
|
||||
return ['carrier' => 'jt', 'kuaidi_com' => self::KUAIDI_COM_JT, 'label' => '极兔速递(单号识别)'];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{carrier: string, kuaidi_com: string, label: string}
|
||||
*/
|
||||
private static function resolveCarrier(string $expressCompany, string $num): array
|
||||
{
|
||||
$byNumber = self::detectCarrierFromNumber($num);
|
||||
$byEc = self::carrierFromExpressCode($expressCompany);
|
||||
|
||||
if ($byNumber !== null && $byEc !== null && $byEc['carrier'] !== $byNumber['carrier']) {
|
||||
Log::info('ExpressTrackService carrier mismatch, prefer tracking number', [
|
||||
'express_company' => $expressCompany,
|
||||
'tracking_number' => $num,
|
||||
'stored_carrier' => $byEc['carrier'],
|
||||
'detected_carrier' => $byNumber['carrier'],
|
||||
]);
|
||||
|
||||
return $byNumber;
|
||||
}
|
||||
if ($byEc !== null) {
|
||||
return $byEc;
|
||||
}
|
||||
if ($byNumber !== null) {
|
||||
return $byNumber;
|
||||
}
|
||||
|
||||
return ['carrier' => 'auto', 'kuaidi_com' => 'auto', 'label' => '自动识别'];
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,10 @@ class ExpressTrackingService
|
||||
|
||||
$tracking->order_id = (int) ($params['order_id'] ?? 0);
|
||||
$tracking->order_type = (string) ($params['order_type'] ?? 'prescription');
|
||||
$tracking->express_company = (string) ($params['express_company'] ?? 'auto');
|
||||
$tracking->express_company = ExpressTrackService::normalizeExpressCompanyCode(
|
||||
$trackingNumber,
|
||||
(string) ($params['express_company'] ?? 'auto')
|
||||
);
|
||||
$tracking->recipient_phone = (string) ($params['recipient_phone'] ?? '');
|
||||
$tracking->recipient_name = (string) ($params['recipient_name'] ?? '');
|
||||
$tracking->recipient_address = (string) ($params['recipient_address'] ?? '');
|
||||
@@ -78,6 +81,177 @@ class ExpressTrackingService
|
||||
return $tracking;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按快递单号走快递100 查询并落库(CLI:gancao:sync-logistics -t)
|
||||
*
|
||||
* @return array{total:int, success:int, failed:int, skipped:int, assistant_cleared:int, assistant_lines:list<string>, details:array<int, array<string,mixed>>}
|
||||
*/
|
||||
public static function queryKuaidiByTrackingNumber(string $trackingNumber): array
|
||||
{
|
||||
$stats = [
|
||||
'total' => 0,
|
||||
'success' => 0,
|
||||
'failed' => 0,
|
||||
'skipped' => 0,
|
||||
'assistant_cleared' => 0,
|
||||
'assistant_skipped_assign_log' => 0,
|
||||
'assistant_lines' => [],
|
||||
'details' => [],
|
||||
];
|
||||
|
||||
$tn = trim($trackingNumber);
|
||||
if ($tn === '') {
|
||||
$stats['failed'] = 1;
|
||||
$stats['details'][] = [
|
||||
'success' => false,
|
||||
'tracking_number' => '',
|
||||
'message' => '快递单号不能为空',
|
||||
];
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
$tracking = self::resolveTrackingByNumber($tn);
|
||||
if (!$tracking) {
|
||||
$stats['failed'] = 1;
|
||||
$stats['details'][] = [
|
||||
'success' => false,
|
||||
'tracking_number' => $tn,
|
||||
'message' => '未找到该快递单号对应的业务订单或物流追踪记录',
|
||||
];
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
$stats['total'] = 1;
|
||||
self::backfillRecipientPhoneFromPrescriptionOrder($tracking);
|
||||
self::syncTrackingExpressCompanyFromNumber($tracking);
|
||||
|
||||
try {
|
||||
$result = self::queryAndUpdate((int) $tracking->id, false);
|
||||
$ok = self::isKuaidiQuerySuccessful($result);
|
||||
$detail = [
|
||||
'order_id' => (int) $tracking->order_id,
|
||||
'tracking_number' => (string) $tracking->tracking_number,
|
||||
'success' => $ok,
|
||||
'message' => trim((string) ($result['hint'] ?? '')) ?: 'ok',
|
||||
'traces' => count($result['traces'] ?? []),
|
||||
'state' => (string) ($result['state_text'] ?? $tracking->current_state_text ?? ''),
|
||||
'source' => (string) ($result['source'] ?? ''),
|
||||
];
|
||||
if ($ok) {
|
||||
$stats['success'] = 1;
|
||||
} else {
|
||||
$stats['failed'] = 1;
|
||||
}
|
||||
$stats['details'][] = $detail;
|
||||
} catch (\Throwable $e) {
|
||||
$stats['failed'] = 1;
|
||||
$stats['details'][] = [
|
||||
'order_id' => (int) $tracking->order_id,
|
||||
'tracking_number' => (string) $tracking->tracking_number,
|
||||
'success' => false,
|
||||
'message' => '异常:' . $e->getMessage(),
|
||||
];
|
||||
Log::error('queryKuaidiByTrackingNumber failed', [
|
||||
'tracking_number' => $tn,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保 express_tracking 存在(不发起查询)
|
||||
*/
|
||||
private static function resolveTrackingByNumber(string $trackingNumber): ?ExpressTracking
|
||||
{
|
||||
$tn = trim($trackingNumber);
|
||||
if ($tn === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$tracking = ExpressTracking::whereRaw('TRIM(`tracking_number`) = ?', [$tn])
|
||||
->whereNull('delete_time')
|
||||
->find();
|
||||
|
||||
$order = PrescriptionOrder::whereNull('delete_time')
|
||||
->whereRaw('TRIM(`tracking_number`) = ?', [$tn])
|
||||
->order('id', 'desc')
|
||||
->find();
|
||||
|
||||
if ($tracking && $order) {
|
||||
$dirty = false;
|
||||
if ((int) $tracking->order_id !== (int) $order->id) {
|
||||
$tracking->order_id = (int) $order->id;
|
||||
$dirty = true;
|
||||
}
|
||||
if (trim((string) ($tracking->recipient_phone ?? '')) === '') {
|
||||
$tracking->recipient_phone = (string) ($order->recipient_phone ?? '');
|
||||
$dirty = true;
|
||||
}
|
||||
if ($dirty) {
|
||||
$tracking->update_time = time();
|
||||
$tracking->save();
|
||||
}
|
||||
|
||||
return $tracking;
|
||||
}
|
||||
|
||||
if ($tracking) {
|
||||
self::backfillRecipientPhoneFromPrescriptionOrder($tracking);
|
||||
|
||||
return $tracking;
|
||||
}
|
||||
|
||||
if (!$order) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$now = time();
|
||||
$tracking = new ExpressTracking();
|
||||
$tracking->tracking_number = mb_substr($tn, 0, 80);
|
||||
$tracking->create_time = $now;
|
||||
$tracking->order_id = (int) $order->id;
|
||||
$tracking->order_type = 'prescription';
|
||||
$tracking->express_company = ExpressTrackService::normalizeExpressCompanyCode(
|
||||
$tn,
|
||||
(string) ($order->express_company ?? 'auto')
|
||||
);
|
||||
$tracking->recipient_phone = (string) ($order->recipient_phone ?? '');
|
||||
$tracking->recipient_name = (string) ($order->recipient_name ?? '');
|
||||
$tracking->recipient_address = (string) ($order->shipping_address ?? '');
|
||||
$tracking->next_update_time = $now;
|
||||
$tracking->update_time = $now;
|
||||
$tracking->save();
|
||||
|
||||
return $tracking;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed>|null $result
|
||||
*/
|
||||
private static function isKuaidiQuerySuccessful(?array $result): bool
|
||||
{
|
||||
if (!is_array($result)) {
|
||||
return false;
|
||||
}
|
||||
$source = (string) ($result['source'] ?? '');
|
||||
if ($source !== 'kuaidi100') {
|
||||
return false;
|
||||
}
|
||||
$hint = trim((string) ($result['hint'] ?? ''));
|
||||
if ($hint === '') {
|
||||
return true;
|
||||
}
|
||||
if (str_contains($hint, '暂无轨迹')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询并更新物流信息
|
||||
*
|
||||
@@ -96,13 +270,14 @@ class ExpressTrackingService
|
||||
}
|
||||
|
||||
self::backfillRecipientPhoneFromPrescriptionOrder($tracking);
|
||||
self::syncTrackingExpressCompanyFromNumber($tracking);
|
||||
|
||||
$startTime = microtime(true);
|
||||
|
||||
// 调用快递100查询(顺丰等单号需带收件人手机号后四位,见 ExpressTrackService)
|
||||
$result = ExpressTrackService::query(
|
||||
$tracking->express_company,
|
||||
$tracking->tracking_number,
|
||||
(string) $tracking->express_company,
|
||||
(string) $tracking->tracking_number,
|
||||
(string) $tracking->recipient_phone
|
||||
);
|
||||
|
||||
@@ -547,6 +722,27 @@ class ExpressTrackingService
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 运单号与 express_company 不一致时按单号纠正(如京东单误存 sf)
|
||||
*/
|
||||
private static function syncTrackingExpressCompanyFromNumber(ExpressTracking $tracking): void
|
||||
{
|
||||
$num = trim((string) $tracking->tracking_number);
|
||||
if ($num === '') {
|
||||
return;
|
||||
}
|
||||
$normalized = ExpressTrackService::normalizeExpressCompanyCode(
|
||||
$num,
|
||||
(string) ($tracking->express_company ?? 'auto')
|
||||
);
|
||||
if ($normalized === (string) $tracking->express_company) {
|
||||
return;
|
||||
}
|
||||
$tracking->express_company = $normalized;
|
||||
$tracking->update_time = time();
|
||||
$tracking->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存轨迹明细
|
||||
*/
|
||||
|
||||
@@ -470,54 +470,62 @@ final class GancaoLogisticsRouteService
|
||||
|
||||
$orders = $q->order('id', 'desc')->limit($limit)->select();
|
||||
foreach ($orders as $order) {
|
||||
$stats['total']++;
|
||||
try {
|
||||
$r = self::syncOne($order);
|
||||
$detail = [
|
||||
'order_id' => (int) $order->id,
|
||||
'order_no' => (string) $order->order_no,
|
||||
'app_order_no' => (string) $order->gancao_reciperl_order_no,
|
||||
'tracking_number' => (string) $order->tracking_number,
|
||||
'success' => $r['success'],
|
||||
'message' => $r['message'],
|
||||
'traces' => $r['traces_count'],
|
||||
'state' => $r['state'],
|
||||
];
|
||||
if ($r['success']) {
|
||||
$stats['success']++;
|
||||
if (!empty($r['assistant_sync']) && is_array($r['assistant_sync'])) {
|
||||
$sync = $r['assistant_sync'];
|
||||
$act = (string) ($sync['action'] ?? '');
|
||||
if ($act === 'cleared') {
|
||||
$stats['assistant_cleared']++;
|
||||
$stats['assistant_lines'][] = sprintf(
|
||||
'[移除医助+指派日志][甘草路由] 诊单=%d 业务订单=%d 运单=%s 原医助ID=%s 履约状态=%d',
|
||||
(int) ($sync['diagnosis_id'] ?? 0),
|
||||
(int) ($sync['prescription_order_id'] ?? 0),
|
||||
(string) ($sync['tracking_number'] ?? ''),
|
||||
(string) ($sync['former_assistant_id'] ?? ''),
|
||||
(int) ($sync['fulfillment_status'] ?? 0)
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$stats['failed']++;
|
||||
}
|
||||
$stats['details'][] = $detail;
|
||||
} catch (\Throwable $e) {
|
||||
$stats['failed']++;
|
||||
$stats['details'][] = [
|
||||
'order_id' => (int) $order->id,
|
||||
'success' => false,
|
||||
'message' => '异常:' . $e->getMessage(),
|
||||
];
|
||||
Log::error('Gancao route sync exception: ' . $e->getMessage(), [
|
||||
'order_id' => (int) $order->id,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
}
|
||||
self::appendSyncOneResult($stats, $order);
|
||||
}
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{total:int, success:int, failed:int, skipped:int, assistant_cleared:int, assistant_skipped_assign_log:int, assistant_lines:list<string>, details:array<int, array<string,mixed>>} $stats
|
||||
*/
|
||||
private static function appendSyncOneResult(array &$stats, PrescriptionOrder $order): void
|
||||
{
|
||||
$stats['total']++;
|
||||
try {
|
||||
$r = self::syncOne($order);
|
||||
$detail = [
|
||||
'order_id' => (int) $order->id,
|
||||
'order_no' => (string) $order->order_no,
|
||||
'app_order_no' => (string) $order->gancao_reciperl_order_no,
|
||||
'tracking_number' => (string) $order->tracking_number,
|
||||
'success' => $r['success'],
|
||||
'message' => $r['message'],
|
||||
'traces' => $r['traces_count'],
|
||||
'state' => $r['state'],
|
||||
];
|
||||
if ($r['success']) {
|
||||
$stats['success']++;
|
||||
if (!empty($r['assistant_sync']) && is_array($r['assistant_sync'])) {
|
||||
$sync = $r['assistant_sync'];
|
||||
$act = (string) ($sync['action'] ?? '');
|
||||
if ($act === 'cleared') {
|
||||
$stats['assistant_cleared']++;
|
||||
$stats['assistant_lines'][] = sprintf(
|
||||
'[移除医助+指派日志][甘草路由] 诊单=%d 业务订单=%d 运单=%s 原医助ID=%s 履约状态=%d',
|
||||
(int) ($sync['diagnosis_id'] ?? 0),
|
||||
(int) ($sync['prescription_order_id'] ?? 0),
|
||||
(string) ($sync['tracking_number'] ?? ''),
|
||||
(string) ($sync['former_assistant_id'] ?? ''),
|
||||
(int) ($sync['fulfillment_status'] ?? 0)
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$stats['failed']++;
|
||||
}
|
||||
$stats['details'][] = $detail;
|
||||
} catch (\Throwable $e) {
|
||||
$stats['failed']++;
|
||||
$stats['details'][] = [
|
||||
'order_id' => (int) $order->id,
|
||||
'success' => false,
|
||||
'message' => '异常:' . $e->getMessage(),
|
||||
];
|
||||
Log::error('Gancao route sync exception: ' . $e->getMessage(), [
|
||||
'order_id' => (int) $order->id,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
CREATE TABLE IF NOT EXISTS `zyt_prescription_library` (
|
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`prescription_name` varchar(100) NOT NULL DEFAULT '' COMMENT '处方名称',
|
||||
`formula_type` varchar(20) NOT NULL DEFAULT '主方' COMMENT '处方类型:主方、辅方',
|
||||
`herbs` text COMMENT '药材列表JSON:[{name, dosage}]',
|
||||
`is_public` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否公开:0-仅自己可见 1-所有人可见',
|
||||
`creator_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建人ID',
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
-- 处方:辅方用法(用量、袋数、代煎、每天几次、服用天数等)
|
||||
ALTER TABLE `zyt_tcm_prescription`
|
||||
ADD COLUMN `aux_usage` json DEFAULT NULL COMMENT '辅方用法JSON' AFTER `times_per_day`;
|
||||
@@ -0,0 +1,3 @@
|
||||
-- 处方库:处方类型(主方/辅方)
|
||||
ALTER TABLE `zyt_prescription_library`
|
||||
ADD COLUMN `formula_type` varchar(20) NOT NULL DEFAULT '主方' COMMENT '处方类型:主方、辅方' AFTER `prescription_name`;
|
||||
@@ -0,0 +1,50 @@
|
||||
-- 消费者开方页「从处方库导入」:补充 tcm.prescriptionLibrary/lists 接口权限
|
||||
-- 执行后需在「权限管理」为相关角色勾选,或依赖 AuthMiddleware 别名(见 PrescriptionLibraryLogic)
|
||||
|
||||
SET @cf_rx_menu_id := (
|
||||
SELECT id FROM zyt_system_menu
|
||||
WHERE type = 'C'
|
||||
AND (
|
||||
component = 'consumer/prescription/index'
|
||||
OR component LIKE '%consumer/prescription/index%'
|
||||
)
|
||||
LIMIT 1
|
||||
);
|
||||
|
||||
INSERT INTO zyt_system_menu (
|
||||
pid, type, name, icon, sort, perms, paths, component,
|
||||
selected, params, is_cache, is_show, is_disable, create_time, update_time
|
||||
)
|
||||
SELECT
|
||||
@cf_rx_menu_id, 'A', '处方库列表(开方导入)', '', 54,
|
||||
'tcm.prescriptionLibrary/lists', '', '',
|
||||
'', '', 0, 1, 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()
|
||||
FROM DUAL
|
||||
WHERE @cf_rx_menu_id IS NOT NULL
|
||||
AND NOT EXISTS (SELECT 1 FROM zyt_system_menu WHERE perms = 'tcm.prescriptionLibrary/lists');
|
||||
|
||||
SET @wcf_rx_menu_id := (
|
||||
SELECT id FROM zyt_system_menu
|
||||
WHERE type = 'C'
|
||||
AND (
|
||||
component = 'consumer/prescription/list'
|
||||
OR component LIKE '%consumer/prescription/list%'
|
||||
)
|
||||
LIMIT 1
|
||||
);
|
||||
|
||||
INSERT INTO zyt_system_menu (
|
||||
pid, type, name, icon, sort, perms, paths, component,
|
||||
selected, params, is_cache, is_show, is_disable, create_time, update_time
|
||||
)
|
||||
SELECT
|
||||
@wcf_rx_menu_id, 'A', '处方库列表(开方导入)', '', 54,
|
||||
'tcm.prescriptionLibrary/lists', '', '',
|
||||
'', '', 0, 1, 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()
|
||||
FROM DUAL
|
||||
WHERE @wcf_rx_menu_id IS NOT NULL
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM zyt_system_menu
|
||||
WHERE perms = 'tcm.prescriptionLibrary/lists'
|
||||
AND pid = @wcf_rx_menu_id
|
||||
);
|
||||
Reference in New Issue
Block a user