更新
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\controller\finance;
|
||||
|
||||
use app\adminapi\controller\BaseAdminController;
|
||||
use app\adminapi\logic\finance\DeptPerformanceTargetLogic;
|
||||
use app\adminapi\validate\finance\DeptPerformanceTargetValidate;
|
||||
|
||||
/**
|
||||
* 制定业绩:按部门、按自然月维护目标金额(元)
|
||||
*
|
||||
* - GET finance.dept_performance_target/monthMatrix
|
||||
* - POST finance.dept_performance_target/batchSave
|
||||
*/
|
||||
class DeptPerformanceTargetController extends BaseAdminController
|
||||
{
|
||||
public function monthMatrix()
|
||||
{
|
||||
$params = (new DeptPerformanceTargetValidate())->goCheck('monthMatrix');
|
||||
$ym = (string) $params['year_month'];
|
||||
|
||||
return $this->data(DeptPerformanceTargetLogic::monthMatrix($ym));
|
||||
}
|
||||
|
||||
public function batchSave()
|
||||
{
|
||||
$params = (new DeptPerformanceTargetValidate())->post()->goCheck('batchSave');
|
||||
$ym = (string) $params['year_month'];
|
||||
$items = $params['items'] ?? [];
|
||||
if (!is_array($items)) {
|
||||
return $this->fail('目标数据格式错误');
|
||||
}
|
||||
|
||||
$result = DeptPerformanceTargetLogic::batchSave(
|
||||
$ym,
|
||||
$items,
|
||||
$this->adminId,
|
||||
(string) ($this->adminInfo['name'] ?? '')
|
||||
);
|
||||
if ($result === false) {
|
||||
return $this->fail(DeptPerformanceTargetLogic::getError());
|
||||
}
|
||||
|
||||
return $this->success('保存成功', DeptPerformanceTargetLogic::monthMatrix($ym), 1, 1);
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,14 @@ class CustomerController extends BaseAdminController
|
||||
return $this->data($stats);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 获取标签维度统计(按 group 分组、按客户数倒序,供前端筛选下拉 + 标签面板共用)
|
||||
*/
|
||||
public function tagStats()
|
||||
{
|
||||
return $this->data(CustomerLogic::getTagStats());
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 今日进入分布(用于页面"今日新增"卡片的迷你柱/最近进入时间/渠道 Top5)
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\controller\stats;
|
||||
|
||||
use app\adminapi\controller\BaseAdminController;
|
||||
use app\adminapi\logic\stats\YejiStatsLogic;
|
||||
|
||||
/**
|
||||
* 业绩看板(部门 × 时间区间)控制器
|
||||
*
|
||||
* - GET stats.yeji-stats/overview 单区间
|
||||
* - GET stats.yeji-stats/multi 多区间一次返回(默认 月/周/今日/昨日 四张表)
|
||||
* - GET stats.yeji-stats/dept-options 部门下拉
|
||||
* - GET stats.yeji-stats/leaderboard 医助排行榜(按展示部门分表)
|
||||
*/
|
||||
class YejiStatsController extends BaseAdminController
|
||||
{
|
||||
public function overview()
|
||||
{
|
||||
// 业绩看板涉及多张大表 + 标签穿透计算,单 30s 上限不够;放宽到 120s 兜底
|
||||
@set_time_limit(120);
|
||||
$params = $this->request->get();
|
||||
|
||||
return $this->data(YejiStatsLogic::overview($params));
|
||||
}
|
||||
|
||||
/**
|
||||
* 一次返回 4 个时间区间的数据,对应前端 4 张表。
|
||||
* 默认区间(不传 ranges 时):当月、当周(周一→今天)、今日、昨日。
|
||||
* ranges 也可由前端自定义传入:[{label:..., start_date:..., end_date:...}]
|
||||
*/
|
||||
public function multi()
|
||||
{
|
||||
// 4 个区间叠加,30s 上限对部分线上数据量来说过紧,放宽到 120s
|
||||
@set_time_limit(120);
|
||||
$params = $this->request->get();
|
||||
$rangesRaw = $params['ranges'] ?? null;
|
||||
|
||||
$today = date('Y-m-d');
|
||||
$monthStart = date('Y-m-01');
|
||||
// 周一为起点(PHP date('N') 1=周一)
|
||||
$weekStart = date('Y-m-d', strtotime('monday this week'));
|
||||
$yesterday = date('Y-m-d', strtotime('-1 day'));
|
||||
|
||||
$ranges = [];
|
||||
if (is_array($rangesRaw) && $rangesRaw !== []) {
|
||||
foreach ($rangesRaw as $r) {
|
||||
if (!is_array($r)) {
|
||||
continue;
|
||||
}
|
||||
$ranges[] = [
|
||||
'label' => (string) ($r['label'] ?? ''),
|
||||
'start_date' => (string) ($r['start_date'] ?? $today),
|
||||
'end_date' => (string) ($r['end_date'] ?? ($r['start_date'] ?? $today)),
|
||||
];
|
||||
}
|
||||
}
|
||||
if ($ranges === []) {
|
||||
$ranges = [
|
||||
['label' => '本月(' . date('n') . '月)', 'start_date' => $monthStart, 'end_date' => $today],
|
||||
['label' => '本周(' . substr($weekStart, 5) . '~' . substr($today, 5) . ')', 'start_date' => $weekStart, 'end_date' => $today],
|
||||
['label' => '今日(' . substr($today, 5) . ')', 'start_date' => $today, 'end_date' => $today],
|
||||
['label' => '昨日(' . substr($yesterday, 5) . ')', 'start_date' => $yesterday, 'end_date' => $yesterday],
|
||||
];
|
||||
}
|
||||
|
||||
$base = [];
|
||||
if (isset($params['dept_ids'])) {
|
||||
$base['dept_ids'] = $params['dept_ids'];
|
||||
}
|
||||
if (isset($params['tag_id'])) {
|
||||
$base['tag_id'] = $params['tag_id'];
|
||||
}
|
||||
if (isset($params['channel_code'])) {
|
||||
$base['channel_code'] = $params['channel_code'];
|
||||
}
|
||||
|
||||
return $this->data([
|
||||
'ranges' => $ranges,
|
||||
'tables' => YejiStatsLogic::overviewBatch($ranges, $base),
|
||||
]);
|
||||
}
|
||||
|
||||
public function deptOptions()
|
||||
{
|
||||
return $this->data(YejiStatsLogic::deptOptions());
|
||||
}
|
||||
|
||||
/** 渠道(标签)下拉,按 source_group_name 分组 */
|
||||
public function channelOptions()
|
||||
{
|
||||
return $this->data(YejiStatsLogic::channelOptions());
|
||||
}
|
||||
|
||||
/** 医助排行榜:与 overview 同筛选;日期由前端传入(多表模式下前端传「今日」单区间) */
|
||||
public function leaderboard()
|
||||
{
|
||||
@set_time_limit(120);
|
||||
$params = $this->request->get();
|
||||
|
||||
return $this->data(YejiStatsLogic::assistantLeaderboards($params));
|
||||
}
|
||||
}
|
||||
@@ -183,6 +183,7 @@ class PrescriptionOrderController extends BaseAdminController
|
||||
(int) $params['id'],
|
||||
(string) ($params['express_company'] ?? 'auto'),
|
||||
(string) ($params['tracking_number'] ?? ''),
|
||||
(string) ($params['ship_mode'] ?? 'gancao'),
|
||||
$this->adminId,
|
||||
$this->adminInfo
|
||||
);
|
||||
|
||||
@@ -9,6 +9,7 @@ use app\adminapi\logic\qywx\CustomerLogic;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\model\auth\Admin;
|
||||
use app\common\model\QywxExternalContact;
|
||||
use think\facade\Db;
|
||||
|
||||
/**
|
||||
* 企业微信客户列表
|
||||
@@ -26,6 +27,32 @@ class CustomerLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[] 入参 tag_ids 规范化后的非空字符串数组(实际为 string[])
|
||||
*/
|
||||
private function normalizeTagIds(): array
|
||||
{
|
||||
$raw = $this->params['tag_ids'] ?? null;
|
||||
if ($raw === null || $raw === '') {
|
||||
return [];
|
||||
}
|
||||
if (is_string($raw)) {
|
||||
$raw = explode(',', $raw);
|
||||
}
|
||||
if (!is_array($raw)) {
|
||||
return [];
|
||||
}
|
||||
$ids = [];
|
||||
foreach ($raw as $v) {
|
||||
$s = trim((string) $v);
|
||||
if ($s !== '') {
|
||||
$ids[] = $s;
|
||||
}
|
||||
}
|
||||
|
||||
return array_values(array_unique($ids));
|
||||
}
|
||||
|
||||
private function baseQuery()
|
||||
{
|
||||
$query = QywxExternalContact::where($this->searchWhere);
|
||||
@@ -34,6 +61,22 @@ class CustomerLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
$query->whereLike('follow_users', '%' . $kw . '%');
|
||||
}
|
||||
|
||||
// 标签筛选:JOIN 关系表按 tag_id 过滤;多个标签为 OR(命中任一即返回)。
|
||||
// 走 zyt_qywx_external_contact_tag.idx_tag 索引,比 LIKE follow_users 快得多
|
||||
$tagIds = $this->normalizeTagIds();
|
||||
if ($tagIds !== []) {
|
||||
$matchedExtIds = Db::name('qywx_external_contact_tag')
|
||||
->whereIn('tag_id', $tagIds)
|
||||
->group('external_userid')
|
||||
->column('external_userid');
|
||||
if ($matchedExtIds === []) {
|
||||
// 没人命中:直接给一个不可能成立的条件,避免下面命中所有客户
|
||||
$query->whereRaw('1=0');
|
||||
} else {
|
||||
$query->whereIn('external_userid', $matchedExtIds);
|
||||
}
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
@@ -91,6 +134,10 @@ class CustomerLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
$followAdminIds = json_decode($item['follow_admin_ids'] ?? '[]', true);
|
||||
$item['follow_admin_ids'] = is_array($followAdminIds) ? $followAdminIds : [];
|
||||
|
||||
// 解析标签 JSON 数组(值由 CustomerLogic::extractFollowUserTags 写入;按 tag_id 去重)
|
||||
$tags = json_decode((string) ($item['tags'] ?? '[]'), true);
|
||||
$item['tags'] = is_array($tags) ? $tags : [];
|
||||
|
||||
$fromDb = (int) ($item['external_first_add_time'] ?? 0);
|
||||
$fromJson = CustomerLogic::minFollowCreatetime($followUsers);
|
||||
$item['external_first_add_time'] = $fromDb > 0 ? $fromDb : $fromJson;
|
||||
|
||||
@@ -17,6 +17,100 @@ use app\common\model\tcm\PrescriptionOrder;
|
||||
class PrescriptionLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
{
|
||||
use HasDataScopeFilter;
|
||||
|
||||
/**
|
||||
* 识别「有业务订单且处方药材为空白/重复」的处方 ID(用于全局置顶排序)
|
||||
*
|
||||
* @param array<int,int|string> $candidateIds
|
||||
* @return array<int,int>
|
||||
*/
|
||||
private function collectRiskPrescriptionIds(array $candidateIds): array
|
||||
{
|
||||
$candidateIds = array_values(array_unique(array_filter(array_map('intval', $candidateIds), static function (int $id): bool {
|
||||
return $id > 0;
|
||||
})));
|
||||
if ($candidateIds === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// 仅在「存在有效业务订单」的处方里做风险判定
|
||||
$orderRxIds = PrescriptionOrder::whereIn('prescription_id', $candidateIds)
|
||||
->whereNull('delete_time')
|
||||
->where('fulfillment_status', '<>', 4)
|
||||
->column('prescription_id');
|
||||
$orderRxIds = array_values(array_unique(array_filter(array_map('intval', $orderRxIds), static function (int $id): bool {
|
||||
return $id > 0;
|
||||
})));
|
||||
if ($orderRxIds === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$rows = Prescription::whereIn('id', $orderRxIds)
|
||||
->whereNull('delete_time')
|
||||
->field(['id', 'herbs'])
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$riskIds = [];
|
||||
foreach ($rows as $row) {
|
||||
$rid = (int) ($row['id'] ?? 0);
|
||||
if ($rid <= 0) {
|
||||
continue;
|
||||
}
|
||||
if ($this->isRiskHerbs($row['herbs'] ?? null)) {
|
||||
$riskIds[] = $rid;
|
||||
}
|
||||
}
|
||||
|
||||
return array_values(array_unique($riskIds));
|
||||
}
|
||||
|
||||
/**
|
||||
* 药材风险判定:空白 or 重复(按名称,忽略空格与大小写)
|
||||
*/
|
||||
private function isRiskHerbs($herbsRaw): bool
|
||||
{
|
||||
$herbs = [];
|
||||
if (is_array($herbsRaw)) {
|
||||
$herbs = $herbsRaw;
|
||||
} elseif (is_string($herbsRaw) && $herbsRaw !== '') {
|
||||
$decoded = json_decode($herbsRaw, true);
|
||||
if (is_array($decoded)) {
|
||||
$herbs = $decoded;
|
||||
}
|
||||
}
|
||||
|
||||
$names = [];
|
||||
foreach ($herbs as $h) {
|
||||
if (!is_array($h)) {
|
||||
continue;
|
||||
}
|
||||
$name = trim((string) ($h['name'] ?? ''));
|
||||
if ($name !== '') {
|
||||
$names[] = $name;
|
||||
}
|
||||
}
|
||||
|
||||
// 空白药材
|
||||
if ($names === []) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 重复药材
|
||||
$seen = [];
|
||||
foreach ($names as $name) {
|
||||
$key = strtolower(preg_replace('/\s+/', '', $name) ?? '');
|
||||
if ($key === '') {
|
||||
continue;
|
||||
}
|
||||
if (isset($seen[$key])) {
|
||||
return true;
|
||||
}
|
||||
$seen[$key] = true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* @notes 搜索条件
|
||||
*/
|
||||
@@ -142,8 +236,16 @@ class PrescriptionLists extends BaseAdminDataLists implements ListsSearchInterfa
|
||||
$this->applyCreatorIdsFilter($query);
|
||||
$this->applyDataScopeByOwnerColumns($query, ['creator_id', 'assistant_id']);
|
||||
|
||||
$lists = $query
|
||||
->whereNull('delete_time')
|
||||
// 全局置顶:有业务订单且处方药材为空白/重复
|
||||
$candidateIds = (clone $query)->whereNull('delete_time')->column('id');
|
||||
$riskIds = $this->collectRiskPrescriptionIds(is_array($candidateIds) ? $candidateIds : []);
|
||||
|
||||
$listQuery = $query->whereNull('delete_time');
|
||||
if ($riskIds !== []) {
|
||||
$riskIdStr = implode(',', array_map('intval', $riskIds));
|
||||
$listQuery->orderRaw("CASE WHEN id IN ({$riskIdStr}) THEN 0 ELSE 1 END ASC");
|
||||
}
|
||||
$lists = $listQuery
|
||||
->order('id', 'desc')
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->select()
|
||||
|
||||
@@ -71,13 +71,7 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
|
||||
$this->applyDoctorAssistantFilters($query);
|
||||
$this->applyPatientKeywordFilter($query);
|
||||
if (!PrescriptionOrderLogic::canSeeAllPrescriptionOrders($this->adminInfo)) {
|
||||
$diagIds = Diagnosis::where('assistant_id', $this->adminId)->whereNull('delete_time')->column('id');
|
||||
$query->where(function ($q) use ($diagIds) {
|
||||
$q->where('creator_id', $this->adminId);
|
||||
if ($diagIds !== []) {
|
||||
$q->whereOr('diagnosis_id', 'in', $diagIds);
|
||||
}
|
||||
});
|
||||
$query->where('creator_id', $this->adminId);
|
||||
}
|
||||
$this->applyDataScopeForPrescriptionOrder($query);
|
||||
}
|
||||
@@ -145,13 +139,7 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
|
||||
return $query;
|
||||
}
|
||||
if (!PrescriptionOrderLogic::canSeeAllPrescriptionOrders($this->adminInfo)) {
|
||||
$diagIds = Diagnosis::where('assistant_id', $this->adminId)->whereNull('delete_time')->column('id');
|
||||
$query->where(function ($q) use ($diagIds) {
|
||||
$q->where('creator_id', $this->adminId);
|
||||
if ($diagIds !== []) {
|
||||
$q->whereOr('diagnosis_id', 'in', $diagIds);
|
||||
}
|
||||
});
|
||||
$query->where('creator_id', $this->adminId);
|
||||
}
|
||||
$this->applyDataScopeForPrescriptionOrder($query);
|
||||
|
||||
@@ -319,9 +307,13 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
|
||||
'stats_order_amount' => $s['order_amount'],
|
||||
'stats_order_amount_not_cancelled' => $s['order_amount_not_cancelled'],
|
||||
'stats_order_amount_cancelled' => $s['order_amount_cancelled'],
|
||||
/** 业绩:业务订单金额,剔除履约已取消(fulfillment_status=4),其余状态全部计入 */
|
||||
'stats_order_amount_performance' => $s['order_amount_not_cancelled'],
|
||||
'stats_linked_pay_amount' => $s['linked_pay_amount'],
|
||||
'stats_linked_pay_amount_not_cancelled' => $s['linked_pay_amount_not_cancelled'],
|
||||
'stats_linked_pay_amount_cancelled' => $s['linked_pay_amount_cancelled'],
|
||||
/** 业绩-关联实付:剔除履约已取消订单的关联支付金额 */
|
||||
'stats_linked_pay_amount_performance' => $s['linked_pay_amount_not_cancelled'],
|
||||
'stats_time_start' => $s['label_start'],
|
||||
'stats_time_end' => $s['label_end'],
|
||||
/** 统计口径:all=支付审核白名单全量;assistant=医助仅诊单协助业绩;restricted=与列表同域 */
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\logic\finance;
|
||||
|
||||
use app\adminapi\logic\dept\DeptLogic;
|
||||
use app\common\logic\BaseLogic;
|
||||
use app\common\model\dept\Dept;
|
||||
use app\common\model\finance\DeptPerformanceTarget;
|
||||
use think\facade\Db;
|
||||
|
||||
class DeptPerformanceTargetLogic extends BaseLogic
|
||||
{
|
||||
/**
|
||||
* 指定月份:保留部门树层级 + 合并当月已有目标
|
||||
*
|
||||
* @return array{year_month: string, rows: array<int, array<string, mixed>>, total_target: float}
|
||||
*/
|
||||
public static function monthMatrix(string $yearMonth): array
|
||||
{
|
||||
$tree = DeptLogic::getAllData();
|
||||
|
||||
$rowsDb = DeptPerformanceTarget::where('year_month', $yearMonth)
|
||||
->select()
|
||||
->toArray();
|
||||
$targets = [];
|
||||
foreach ($rowsDb as $r) {
|
||||
$targets[(int) ($r['dept_id'] ?? 0)] = $r;
|
||||
}
|
||||
|
||||
$rows = self::attachTargetsToTree(is_array($tree) ? $tree : [], $targets);
|
||||
$total = self::sumTargetsInTree($rows);
|
||||
|
||||
return [
|
||||
'year_month' => $yearMonth,
|
||||
'rows' => $rows,
|
||||
'total_target' => round($total, 2),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, array{dept_id?:int|float|string, target_amount?:int|float|string, remark?:string}> $items
|
||||
*/
|
||||
public static function batchSave(string $yearMonth, array $items, int $adminId, string $adminName): bool
|
||||
{
|
||||
try {
|
||||
Db::transaction(function () use ($yearMonth, $items, $adminId, $adminName): void {
|
||||
foreach ($items as $item) {
|
||||
if (!is_array($item)) {
|
||||
continue;
|
||||
}
|
||||
$deptId = (int) ($item['dept_id'] ?? 0);
|
||||
if ($deptId <= 0) {
|
||||
continue;
|
||||
}
|
||||
$dept = Dept::where('id', $deptId)->whereNull('delete_time')->find();
|
||||
if (!$dept) {
|
||||
continue;
|
||||
}
|
||||
$deptName = trim((string) ($dept->name ?? ''));
|
||||
$amt = round((float) ($item['target_amount'] ?? 0), 2);
|
||||
$remark = mb_substr(trim((string) ($item['remark'] ?? '')), 0, 255);
|
||||
|
||||
if ($amt <= 0) {
|
||||
DeptPerformanceTarget::where('dept_id', $deptId)
|
||||
->where('year_month', $yearMonth)
|
||||
->delete();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$row = DeptPerformanceTarget::where('dept_id', $deptId)
|
||||
->where('year_month', $yearMonth)
|
||||
->find();
|
||||
if ($row) {
|
||||
$row->dept_name = $deptName;
|
||||
$row->target_amount = $amt;
|
||||
$row->remark = $remark;
|
||||
$row->updater_id = $adminId;
|
||||
$row->updater_name = $adminName;
|
||||
$row->save();
|
||||
} else {
|
||||
DeptPerformanceTarget::create([
|
||||
'dept_id' => $deptId,
|
||||
'dept_name' => $deptName,
|
||||
'year_month' => $yearMonth,
|
||||
'target_amount' => $amt,
|
||||
'remark' => $remark,
|
||||
'creator_id' => $adminId,
|
||||
'creator_name' => $adminName,
|
||||
'updater_id' => $adminId,
|
||||
'updater_name' => $adminName,
|
||||
]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
self::setError($e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, array<string, mixed>> $nodes DeptLogic::getAllData 子树
|
||||
* @param array<int, array<string, mixed>> $targets keyed by dept_id
|
||||
*
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private static function attachTargetsToTree(array $nodes, array $targets, string $prefix = ''): array
|
||||
{
|
||||
$out = [];
|
||||
foreach ($nodes as $n) {
|
||||
if (!is_array($n)) {
|
||||
continue;
|
||||
}
|
||||
$id = (int) ($n['id'] ?? 0);
|
||||
$name = trim((string) ($n['name'] ?? ''));
|
||||
if ($id <= 0) {
|
||||
continue;
|
||||
}
|
||||
$path = $prefix === '' ? $name : $prefix . ' / ' . $name;
|
||||
$t = $targets[$id] ?? null;
|
||||
$amt = $t ? round((float) $t['target_amount'], 2) : 0.0;
|
||||
$node = [
|
||||
'dept_id' => $id,
|
||||
'dept_name' => $name,
|
||||
'dept_path' => $path,
|
||||
'target_id' => $t ? (int) $t['id'] : 0,
|
||||
'target_amount' => $amt,
|
||||
'remark' => $t ? (string) ($t['remark'] ?? '') : '',
|
||||
];
|
||||
$rawChildren = $n['children'] ?? [];
|
||||
$childList = is_array($rawChildren) && $rawChildren !== []
|
||||
? self::attachTargetsToTree($rawChildren, $targets, $path)
|
||||
: [];
|
||||
if ($childList !== []) {
|
||||
$node['children'] = $childList;
|
||||
}
|
||||
$out[] = $node;
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, array<string, mixed>> $nodes
|
||||
*/
|
||||
private static function sumTargetsInTree(array $nodes): float
|
||||
{
|
||||
$s = 0.0;
|
||||
foreach ($nodes as $n) {
|
||||
$s += (float) ($n['target_amount'] ?? 0);
|
||||
$ch = $n['children'] ?? [];
|
||||
if (is_array($ch) && $ch !== []) {
|
||||
$s += self::sumTargetsInTree($ch);
|
||||
}
|
||||
}
|
||||
|
||||
return $s;
|
||||
}
|
||||
}
|
||||
@@ -734,6 +734,11 @@ class CustomerLogic extends BaseLogic
|
||||
'delete_time' => $now,
|
||||
'update_time' => $now,
|
||||
]);
|
||||
|
||||
// 客户已删 → 关系表硬删(无 delete_time 列;统计场景不需要保留)
|
||||
Db::name('qywx_external_contact_tag')
|
||||
->where('external_userid', $externalUserId)
|
||||
->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -782,16 +787,23 @@ class CustomerLogic extends BaseLogic
|
||||
->where('id', (int) $row['id'])
|
||||
->update([
|
||||
'follow_users' => json_encode([], JSON_UNESCAPED_UNICODE),
|
||||
'tags' => '[]',
|
||||
'delete_time' => $now,
|
||||
'update_time' => $now,
|
||||
]);
|
||||
|
||||
// 整客户已无人跟进 → 关系表清空
|
||||
Db::name('qywx_external_contact_tag')
|
||||
->where('external_userid', $externalUserId)
|
||||
->delete();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$minCreate = self::minFollowCreatetime($kept);
|
||||
$update = [
|
||||
'follow_users' => json_encode($kept, JSON_UNESCAPED_UNICODE),
|
||||
'tags' => self::extractFollowUserTags($kept),
|
||||
'update_time' => $now,
|
||||
];
|
||||
if ($minCreate > 0) {
|
||||
@@ -802,6 +814,9 @@ class CustomerLogic extends BaseLogic
|
||||
Db::name('qywx_external_contact')
|
||||
->where('id', (int) $row['id'])
|
||||
->update($update);
|
||||
|
||||
// 关系表按剩余 kept follow_users 同步(自动清掉离开员工那行 + 保留其他员工的标签)
|
||||
self::syncContactTagsRelation($externalUserId, $kept);
|
||||
}
|
||||
|
||||
private static function upsertOneExternalContactBundle(
|
||||
@@ -843,6 +858,7 @@ class CustomerLogic extends BaseLogic
|
||||
'corp_full_name' => $externalContact['corp_full_name'] ?? '',
|
||||
'external_profile' => json_encode($externalContact['external_profile'] ?? [], JSON_UNESCAPED_UNICODE),
|
||||
'follow_users' => json_encode($followUsers, JSON_UNESCAPED_UNICODE),
|
||||
'tags' => self::extractFollowUserTags($followUsers),
|
||||
'follow_admin_ids' => self::resolveFollowAdminIds($followUsers),
|
||||
'external_first_add_time' => self::minFollowCreatetime($followUsers),
|
||||
'create_time' => $now,
|
||||
@@ -851,11 +867,168 @@ class CustomerLogic extends BaseLogic
|
||||
];
|
||||
|
||||
self::upsertExternalContactRow($row, $syncCount, $newCount, $updateCount);
|
||||
|
||||
// 同步「客户↔员工↔标签」关系表(用于检索/统计;列表筛选/聚合不必再解析 follow_users JSON)
|
||||
self::syncContactTagsRelation((string) $row['external_userid'], $followUsers);
|
||||
|
||||
if (($syncCount % 25) === 0) {
|
||||
usleep(8000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步「客户↔员工↔标签」关系表(zyt_qywx_external_contact_tag)。
|
||||
*
|
||||
* 行粒度:(external_userid, follow_user_id, tag_id) 三元组——同一标签由不同员工打则各占一行。
|
||||
* 用于检索/统计/聚合:按标签筛客户、统计某标签客户数、多标签 AND/OR、按分组漏斗等。
|
||||
*
|
||||
* 写入策略(保留 create_time,仅在改名/分组变化时刷 tag_name/group_name/type):
|
||||
* 1. 计算目标三元组集合
|
||||
* 2. 删除"该 external_userid 下"不在新集合里的旧关系(含跟进人离开 / 标签移除)
|
||||
* 3. 对每个新三元组 INSERT ... ON DUPLICATE KEY UPDATE 刷新冗余字段
|
||||
*
|
||||
* @param array<int, mixed> $followUsers /externalcontact/get 返回的 follow_user[]
|
||||
* @internal 仅供 UPSERT / 回填命令复用
|
||||
*/
|
||||
public static function syncContactTagsRelation(string $externalUserId, array $followUsers): void
|
||||
{
|
||||
$externalUserId = trim($externalUserId);
|
||||
if ($externalUserId === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. 拍平为 (follow_user_id, tag_id) → 行 的字典;保持 follow_user_id 维度区分
|
||||
$rows = [];
|
||||
foreach ($followUsers as $fu) {
|
||||
if (!is_array($fu)) {
|
||||
continue;
|
||||
}
|
||||
$followUserId = trim((string) ($fu['userid'] ?? ''));
|
||||
$tags = $fu['tags'] ?? [];
|
||||
if (!is_array($tags)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($tags as $t) {
|
||||
if (!is_array($t)) {
|
||||
continue;
|
||||
}
|
||||
$tagId = trim((string) ($t['tag_id'] ?? ''));
|
||||
if ($tagId === '') {
|
||||
continue;
|
||||
}
|
||||
$key = $followUserId . '|' . $tagId;
|
||||
$rows[$key] = [
|
||||
'follow_user_id' => mb_substr($followUserId, 0, 64),
|
||||
'tag_id' => mb_substr($tagId, 0, 64),
|
||||
'tag_name' => mb_substr((string) ($t['tag_name'] ?? ''), 0, 128),
|
||||
'group_name' => mb_substr((string) ($t['group_name'] ?? ''), 0, 128),
|
||||
'type' => isset($t['type']) ? (int) $t['type'] : 1,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$now = time();
|
||||
|
||||
// 2. 删除该客户下不在目标集合里的旧关系
|
||||
if ($rows === []) {
|
||||
Db::name('qywx_external_contact_tag')
|
||||
->where('external_userid', $externalUserId)
|
||||
->delete();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// 用 (follow_user_id || tag_id) 拼字符串过滤;记录少时 PHP 拉出来比 SQL CONCAT 更高效
|
||||
$existing = Db::name('qywx_external_contact_tag')
|
||||
->where('external_userid', $externalUserId)
|
||||
->field(['id', 'follow_user_id', 'tag_id'])
|
||||
->select()
|
||||
->toArray();
|
||||
$deleteIds = [];
|
||||
foreach ($existing as $ex) {
|
||||
$key = (string) ($ex['follow_user_id'] ?? '') . '|' . (string) ($ex['tag_id'] ?? '');
|
||||
if (!isset($rows[$key])) {
|
||||
$deleteIds[] = (int) $ex['id'];
|
||||
}
|
||||
}
|
||||
if ($deleteIds !== []) {
|
||||
Db::name('qywx_external_contact_tag')
|
||||
->whereIn('id', $deleteIds)
|
||||
->delete();
|
||||
}
|
||||
|
||||
// 3. UPSERT 新关系(命中唯一键时只刷冗余字段 + update_time,create_time 保持原值)
|
||||
foreach ($rows as $r) {
|
||||
try {
|
||||
Db::name('qywx_external_contact_tag')
|
||||
->duplicate(['tag_name', 'group_name', 'type', 'update_time'])
|
||||
->insert([
|
||||
'external_userid' => $externalUserId,
|
||||
'follow_user_id' => $r['follow_user_id'],
|
||||
'tag_id' => $r['tag_id'],
|
||||
'tag_name' => $r['tag_name'],
|
||||
'group_name' => $r['group_name'],
|
||||
'type' => $r['type'],
|
||||
'create_time' => $now,
|
||||
'update_time' => $now,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('qywx external contact tag upsert failed: ' . $e->getMessage(), [
|
||||
'ext' => $externalUserId,
|
||||
'follow_user_id' => $r['follow_user_id'],
|
||||
'tag_id' => $r['tag_id'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 把所有 follow_user[].tags 合并去重(按 tag_id),返回 JSON 字符串。
|
||||
*
|
||||
* 一个客户可能同时被多个员工跟进,每个员工各自打的 tag 在各自的 follow_user.tags 里;
|
||||
* 这里按 tag_id 唯一去重,保留 tag_name / group_name / type 完整结构,便于后台直接渲染/筛选,
|
||||
* 不必再解析 follow_users 大 JSON。
|
||||
*
|
||||
* 来源结构示例:
|
||||
* "tags":[{"group_name":"自媒体1","tag_name":"自媒体1","type":1,"tag_id":"etRr2..."}]
|
||||
*
|
||||
* @internal 仅供 UPSERT / 回填命令复用,不属于业务对外 API
|
||||
* @param array<int, mixed> $followUsers
|
||||
*/
|
||||
public static function extractFollowUserTags(array $followUsers): string
|
||||
{
|
||||
$bag = [];
|
||||
foreach ($followUsers as $fu) {
|
||||
if (!is_array($fu)) {
|
||||
continue;
|
||||
}
|
||||
$tags = $fu['tags'] ?? [];
|
||||
if (!is_array($tags)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($tags as $t) {
|
||||
if (!is_array($t)) {
|
||||
continue;
|
||||
}
|
||||
$tagId = trim((string) ($t['tag_id'] ?? ''));
|
||||
if ($tagId === '') {
|
||||
continue;
|
||||
}
|
||||
if (isset($bag[$tagId])) {
|
||||
continue;
|
||||
}
|
||||
$bag[$tagId] = [
|
||||
'group_name' => (string) ($t['group_name'] ?? ''),
|
||||
'tag_name' => (string) ($t['tag_name'] ?? ''),
|
||||
'type' => isset($t['type']) ? (int) $t['type'] : 1,
|
||||
'tag_id' => $tagId,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return json_encode(array_values($bag), JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将企微 follow_user[].userid 与后台 admin.work_wechat_userid 对齐,得到 zyt_admin.id 列表 JSON。
|
||||
*
|
||||
@@ -922,6 +1095,7 @@ class CustomerLogic extends BaseLogic
|
||||
'corp_full_name',
|
||||
'external_profile',
|
||||
'follow_users',
|
||||
'tags',
|
||||
'follow_admin_ids',
|
||||
'external_first_add_time',
|
||||
'update_time',
|
||||
@@ -948,6 +1122,90 @@ class CustomerLogic extends BaseLogic
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 标签维度统计:用于「按标签筛客户」下拉选项 + 「按标签看分布」面板
|
||||
*
|
||||
* 数据源:zyt_qywx_external_contact_tag(关系表)+ zyt_qywx_external_contact(剔除已软删客户)
|
||||
* 输出按 group_name 分组、组内按客户数倒序,每条带 tag_id / tag_name / customer_count
|
||||
*
|
||||
* @return array{
|
||||
* total_tags: int,
|
||||
* total_relations: int,
|
||||
* total_tagged_customers: int,
|
||||
* groups: array<int, array{group_name: string, customer_count: int, tags: array<int, array{tag_id: string, tag_name: string, customer_count: int}>}>
|
||||
* }
|
||||
*/
|
||||
public static function getTagStats(): array
|
||||
{
|
||||
// 关系表里 external_userid 可能指向已被软删的客户;这里 INNER JOIN 主表过滤未删除的
|
||||
$rows = Db::name('qywx_external_contact_tag')
|
||||
->alias('ect')
|
||||
->join('qywx_external_contact ec', 'ec.external_userid = ect.external_userid', 'INNER')
|
||||
->whereNull('ec.delete_time')
|
||||
->field([
|
||||
'ect.tag_id',
|
||||
'ect.tag_name',
|
||||
'ect.group_name',
|
||||
'COUNT(DISTINCT ect.external_userid) AS customer_count',
|
||||
])
|
||||
->group('ect.tag_id, ect.tag_name, ect.group_name')
|
||||
->order('customer_count', 'desc')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$groupMap = [];
|
||||
$customersUnion = [];
|
||||
foreach ($rows as $r) {
|
||||
$g = (string) ($r['group_name'] ?? '');
|
||||
if (!isset($groupMap[$g])) {
|
||||
$groupMap[$g] = [
|
||||
'group_name' => $g,
|
||||
'customer_count' => 0,
|
||||
'tags' => [],
|
||||
];
|
||||
}
|
||||
$cnt = (int) ($r['customer_count'] ?? 0);
|
||||
$groupMap[$g]['tags'][] = [
|
||||
'tag_id' => (string) ($r['tag_id'] ?? ''),
|
||||
'tag_name' => (string) ($r['tag_name'] ?? ''),
|
||||
'customer_count' => $cnt,
|
||||
];
|
||||
}
|
||||
|
||||
// 组内已经按 customer_count desc 排好序了(外面 SQL 排序结果);
|
||||
// 组本身按"组内最大 customer_count"倒序排(最热门标签所在组优先)
|
||||
foreach ($groupMap as &$g) {
|
||||
$g['customer_count'] = $g['tags'][0]['customer_count'] ?? 0;
|
||||
}
|
||||
unset($g);
|
||||
|
||||
$groups = array_values($groupMap);
|
||||
usort($groups, static function ($a, $b) {
|
||||
return $b['customer_count'] <=> $a['customer_count'];
|
||||
});
|
||||
|
||||
// 全表 distinct 客户数(带 tag 的客户)
|
||||
$taggedCustomers = (int) Db::name('qywx_external_contact_tag')
|
||||
->alias('ect')
|
||||
->join('qywx_external_contact ec', 'ec.external_userid = ect.external_userid', 'INNER')
|
||||
->whereNull('ec.delete_time')
|
||||
->group('ect.external_userid')
|
||||
->count();
|
||||
|
||||
$totalRelations = (int) Db::name('qywx_external_contact_tag')
|
||||
->alias('ect')
|
||||
->join('qywx_external_contact ec', 'ec.external_userid = ect.external_userid', 'INNER')
|
||||
->whereNull('ec.delete_time')
|
||||
->count();
|
||||
|
||||
return [
|
||||
'total_tags' => count($rows),
|
||||
'total_relations' => $totalRelations,
|
||||
'total_tagged_customers' => $taggedCustomers,
|
||||
'groups' => $groups,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 获取统计信息
|
||||
*/
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -471,6 +471,27 @@ class PrescriptionLogic
|
||||
->count() > 0;
|
||||
$arr['has_prescription_order'] = $hasBizOrder ? 1 : 0;
|
||||
|
||||
/**
|
||||
* 处方笺展示所需的"收件信息/订单号/合计"等字段,
|
||||
* 来源是该处方关联的「业务处方订单」(zyt_tcm_prescription_order)。
|
||||
* 取最近一条未删除且未取消的订单:
|
||||
* - recipient_name / recipient_phone / shipping_address 用于「收件信息」一行
|
||||
* - order_no 用于处方笺右上角「编号」
|
||||
* - amount 作为「合计」金额回填(前端再做 0 兜底展示)
|
||||
*/
|
||||
$latestPo = PrescriptionOrder::where('prescription_id', $id)
|
||||
->whereNull('delete_time')
|
||||
->where('fulfillment_status', '<>', 4)
|
||||
->order('id', 'desc')
|
||||
->find();
|
||||
if ($latestPo) {
|
||||
$arr['recipient_name'] = (string) ($latestPo->recipient_name ?? '');
|
||||
$arr['recipient_phone'] = (string) ($latestPo->recipient_phone ?? '');
|
||||
$arr['shipping_address'] = (string) ($latestPo->shipping_address ?? '');
|
||||
$arr['order_no'] = (string) ($latestPo->order_no ?? '');
|
||||
$arr['order_amount'] = round((float) ($latestPo->amount ?? 0), 2);
|
||||
}
|
||||
|
||||
return $arr;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ use app\adminapi\logic\auth\AuthLogic;
|
||||
use app\adminapi\logic\order\OrderLogic;
|
||||
use app\common\model\Order;
|
||||
use app\common\model\tcm\Diagnosis;
|
||||
use app\common\model\tcm\DiagnosisAssignLog;
|
||||
use app\common\model\tcm\Prescription;
|
||||
use app\common\model\tcm\PrescriptionOrder;
|
||||
use app\common\model\tcm\PrescriptionOrderLog;
|
||||
@@ -117,12 +118,8 @@ class PrescriptionOrderLogic
|
||||
if (self::canSeeAllPrescriptionOrders($adminInfo)) {
|
||||
return true;
|
||||
}
|
||||
if ((int) $row->creator_id === $adminId) {
|
||||
return true;
|
||||
}
|
||||
$aid = (int) Diagnosis::where('id', (int) $row->diagnosis_id)->whereNull('delete_time')->value('assistant_id');
|
||||
|
||||
return $aid === $adminId;
|
||||
return (int) $row->creator_id === $adminId;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -948,7 +945,7 @@ class PrescriptionOrderLogic
|
||||
*
|
||||
* @return array<string,mixed>|false
|
||||
*/
|
||||
public static function ship(int $id, string $expressCompany, string $trackingNumber, int $adminId, array $adminInfo)
|
||||
public static function ship(int $id, string $expressCompany, string $trackingNumber, string $shipMode, int $adminId, array $adminInfo)
|
||||
{
|
||||
self::$error = '';
|
||||
|
||||
@@ -976,6 +973,9 @@ class PrescriptionOrderLogic
|
||||
return false;
|
||||
}
|
||||
|
||||
$shipMode = self::normalizeShipMode($shipMode);
|
||||
$shipModeText = $shipMode === 'direct' ? '药房直发' : '甘草药方发';
|
||||
|
||||
$order->express_company = self::normalizeExpressCompany($expressCompany);
|
||||
$order->tracking_number = $trackingNumber;
|
||||
// 仅履约中(2)时才推进到已发货(5),已发货(5)则只更新快递信息保持状态
|
||||
@@ -994,9 +994,21 @@ class PrescriptionOrderLogic
|
||||
}
|
||||
|
||||
if ($isFirstShip) {
|
||||
self::writeLog((int) $order->id, $adminId, $adminInfo, 'ship', '确认发货,运单:' . $trackingNumber . '(' . $expressCompany . ')');
|
||||
self::writeLog(
|
||||
(int) $order->id,
|
||||
$adminId,
|
||||
$adminInfo,
|
||||
'ship',
|
||||
'确认发货(' . $shipModeText . '),运单:' . $trackingNumber . '(' . $expressCompany . ')'
|
||||
);
|
||||
} else {
|
||||
self::writeLog((int) $order->id, $adminId, $adminInfo, 'fill_tracking', '更新发货信息,运单:' . $trackingNumber . '(' . $expressCompany . ')');
|
||||
self::writeLog(
|
||||
(int) $order->id,
|
||||
$adminId,
|
||||
$adminInfo,
|
||||
'fill_tracking',
|
||||
'更新发货信息(' . $shipModeText . '),运单:' . $trackingNumber . '(' . $expressCompany . ')'
|
||||
);
|
||||
}
|
||||
|
||||
$out = $order->toArray();
|
||||
@@ -1006,6 +1018,16 @@ class PrescriptionOrderLogic
|
||||
return $out;
|
||||
}
|
||||
|
||||
private static function normalizeShipMode(string $shipMode): string
|
||||
{
|
||||
$s = strtolower(trim($shipMode));
|
||||
if ($s === 'direct') {
|
||||
return 'direct';
|
||||
}
|
||||
|
||||
return 'gancao';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string,mixed>|false
|
||||
*/
|
||||
@@ -1771,7 +1793,7 @@ class PrescriptionOrderLogic
|
||||
|
||||
/**
|
||||
* 完成订单时把关联诊单的 assistant_id 清空,让患者回到「待分配」状态
|
||||
* 仅当该患者没有其它进行中的处方订单时才清空,避免误覆盖
|
||||
* 仅当该患者没有其它进行中的处方订单且无指派日志时才清空,避免误覆盖
|
||||
*
|
||||
* @return string 追加到操作日志的描述(无变更则返回空串)
|
||||
*/
|
||||
@@ -1791,6 +1813,14 @@ class PrescriptionOrderLogic
|
||||
return '';
|
||||
}
|
||||
|
||||
// 存在医助指派日志时,保留当前医助分配,不自动清空
|
||||
$assignLogCount = DiagnosisAssignLog::where('diagnosis_id', $diagnosisId)
|
||||
->whereNull('delete_time')
|
||||
->count();
|
||||
if ($assignLogCount > 0) {
|
||||
return ';检测到该患者已有医助指派记录,保留当前医助分配(assistant_id=' . $oldAssistant . ')';
|
||||
}
|
||||
|
||||
// 同一诊单下若仍有未完成/未取消的处方订单,则不清空
|
||||
$pendingCount = PrescriptionOrder::where('diagnosis_id', $diagnosisId)
|
||||
->whereNull('delete_time')
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\validate\finance;
|
||||
|
||||
use app\common\validate\BaseValidate;
|
||||
|
||||
class DeptPerformanceTargetValidate extends BaseValidate
|
||||
{
|
||||
protected $rule = [
|
||||
'year_month' => 'require|regex:/^\d{4}-\d{2}$/',
|
||||
'items' => 'require|array',
|
||||
];
|
||||
|
||||
protected $message = [
|
||||
'year_month.require' => '请选择月份',
|
||||
'year_month.regex' => '月份格式须为 YYYY-MM',
|
||||
'items.require' => '请提交目标数据',
|
||||
'items.array' => '目标数据格式错误',
|
||||
];
|
||||
|
||||
public function sceneMonthMatrix(): DeptPerformanceTargetValidate
|
||||
{
|
||||
return $this->only(['year_month']);
|
||||
}
|
||||
|
||||
public function sceneBatchSave(): DeptPerformanceTargetValidate
|
||||
{
|
||||
return $this->only(['year_month', 'items']);
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ class PrescriptionOrderValidate extends BaseValidate
|
||||
'service_package' => 'max:100',
|
||||
'tracking_number' => 'max:80',
|
||||
'express_company' => 'max:20',
|
||||
'ship_mode' => 'in:gancao,direct',
|
||||
'fee_type' => 'require|in:1,2,3,4,5,6,7',
|
||||
'amount' => 'require|float',
|
||||
'order_type' => 'require|in:1,2,3,4,5,6,7',
|
||||
@@ -60,7 +61,7 @@ class PrescriptionOrderValidate extends BaseValidate
|
||||
'auditPrescription' => ['id', 'action', 'remark'],
|
||||
'auditPayment' => ['id', 'action', 'remark'],
|
||||
'withdraw' => ['id'],
|
||||
'ship' => ['id', 'tracking_number', 'express_company'],
|
||||
'ship' => ['id', 'tracking_number', 'express_company', 'ship_mode'],
|
||||
'logs' => ['id'],
|
||||
'paidPayOrders' => ['diagnosis_id'],
|
||||
'addPayOrder' => ['id', 'order_type', 'pay_amount', 'pay_remark'],
|
||||
|
||||
Reference in New Issue
Block a user