From 4f45ecbd633f63899ed17d1cb122fb5abcfc3952 Mon Sep 17 00:00:00 2001 From: ShengLong <452591453@qq.com> Date: Tue, 21 Apr 2026 17:24:51 +0800 Subject: [PATCH] update --- admin/src/api/finance.ts | 20 ++ admin/src/views/finance/account_cost/edit.vue | 127 +++++++++++++ .../src/views/finance/account_cost/index.vue | 122 ++++++++++++ admin/src/views/stats/conversion/index.vue | 48 ++++- .../finance/AccountCostController.php | 47 +++++ .../lists/finance/AccountCostLists.php | 59 ++++++ .../logic/finance/AccountCostLogic.php | 68 +++++++ .../adminapi/logic/stats/ConversionLogic.php | 178 ++++++++---------- .../validate/finance/AccountCostValidate.php | 53 ++++++ .../app/common/model/finance/AccountCost.php | 20 ++ .../add_conversion_stats_menu.sql | 8 +- .../1.9.20260421/add_account_cost_menu.sql | 15 ++ .../create_account_cost_table.sql | 15 ++ 13 files changed, 675 insertions(+), 105 deletions(-) create mode 100644 admin/src/views/finance/account_cost/edit.vue create mode 100644 admin/src/views/finance/account_cost/index.vue create mode 100644 server/app/adminapi/controller/finance/AccountCostController.php create mode 100644 server/app/adminapi/lists/finance/AccountCostLists.php create mode 100644 server/app/adminapi/logic/finance/AccountCostLogic.php create mode 100644 server/app/adminapi/validate/finance/AccountCostValidate.php create mode 100644 server/app/common/model/finance/AccountCost.php create mode 100644 server/sql/1.9.20260421/add_account_cost_menu.sql create mode 100644 server/sql/1.9.20260421/create_account_cost_table.sql diff --git a/admin/src/api/finance.ts b/admin/src/api/finance.ts index 7e0ed536..e8f79630 100644 --- a/admin/src/api/finance.ts +++ b/admin/src/api/finance.ts @@ -39,3 +39,23 @@ export function refundLog(params?: any) { export function refundStat(params?: any) { return request.get({ url: '/finance.refund/stat', params }) } + +// 账户消耗列表 +export function accountCostLists(params?: any) { + return request.get({ url: '/finance.account_cost/lists', params }) +} + +// 账户消耗新增 +export function accountCostAdd(params?: any) { + return request.post({ url: '/finance.account_cost/add', params }) +} + +// 账户消耗编辑 +export function accountCostEdit(params?: any) { + return request.post({ url: '/finance.account_cost/edit', params }) +} + +// 账户消耗详情 +export function accountCostDetail(params?: any) { + return request.get({ url: '/finance.account_cost/detail', params }) +} diff --git a/admin/src/views/finance/account_cost/edit.vue b/admin/src/views/finance/account_cost/edit.vue new file mode 100644 index 00000000..93e37527 --- /dev/null +++ b/admin/src/views/finance/account_cost/edit.vue @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + diff --git a/admin/src/views/finance/account_cost/index.vue b/admin/src/views/finance/account_cost/index.vue new file mode 100644 index 00000000..d6b49189 --- /dev/null +++ b/admin/src/views/finance/account_cost/index.vue @@ -0,0 +1,122 @@ + + + + + + 当前筛选天数 + {{ pager.extend.days_count ?? 0 }} + + + 累计账户消耗 (元) + ¥{{ pager.extend.total_amount ?? '0.00' }} + + + + + + + + + + + + + + 查询 + 重置 + + + + + + + + + + + 新增 + + + + + + + ¥{{ row.amount }} + + + + + + + + + 编辑 + + + + + + + + + + + + + + + diff --git a/admin/src/views/stats/conversion/index.vue b/admin/src/views/stats/conversion/index.vue index 3b66c666..066cac5b 100644 --- a/admin/src/views/stats/conversion/index.vue +++ b/admin/src/views/stats/conversion/index.vue @@ -326,6 +326,7 @@ const summaryCards: MetricCard[] = [ { key: 'completed_order_count', label: '接诊诊单', type: 'count' }, { key: 'completed_order_amount', label: '诊单金额', type: 'money' }, { key: 'account_cost', label: '账户消耗', type: 'money' }, + { key: 'cash_cost', label: '现金成本', type: 'money' }, { key: 'roi', label: 'ROI', type: 'ratio' } ] @@ -346,7 +347,6 @@ const tableColumns: MetricColumn[] = [ { key: 'interview_receive_rate', label: '面诊接诊率', type: 'percent', minWidth: 130 }, { key: 'open_receive_rate', label: '开口接诊率', type: 'percent', placeholder: true, minWidth: 120 }, { key: 'avg_unit_price', label: '平均单价', type: 'money', minWidth: 110 }, - { key: 'account_cost', label: '账户消耗', type: 'money', minWidth: 120 }, { key: 'cash_cost', label: '现金成本', type: 'money', minWidth: 110 }, { key: 'roi', label: 'ROI', type: 'ratio', minWidth: 90 } ] @@ -421,12 +421,31 @@ const rankingChartOption = computed(() => ({ const amountPieOption = computed(() => ({ tooltip: { trigger: 'item' }, - legend: { bottom: 0 }, + legend: { + bottom: 0, + type: 'scroll', + pageIconColor: '#409eff', + pageTextStyle: { + color: '#909399', + }, + }, series: [ { name: '诊单金额', type: 'pie', - radius: ['42%', '72%'], + radius: ['42%', '70%'], + center: ['50%', '42%'], + avoidLabelOverlap: true, + minAngle: 3, + label: { + show: true, + formatter: '{b}', + overflow: 'truncate', + width: 90, + }, + labelLayout: { + hideOverlap: true, + }, data: overview.charts.amount_share } ] @@ -434,12 +453,31 @@ const amountPieOption = computed(() => ({ const fanPieOption = computed(() => ({ tooltip: { trigger: 'item' }, - legend: { bottom: 0 }, + legend: { + bottom: 0, + type: 'scroll', + pageIconColor: '#409eff', + pageTextStyle: { + color: '#909399', + }, + }, series: [ { name: '加粉数', type: 'pie', - radius: ['42%', '72%'], + radius: ['42%', '70%'], + center: ['50%', '42%'], + avoidLabelOverlap: true, + minAngle: 3, + label: { + show: true, + formatter: '{b}', + overflow: 'truncate', + width: 90, + }, + labelLayout: { + hideOverlap: true, + }, data: overview.charts.fan_share } ] diff --git a/server/app/adminapi/controller/finance/AccountCostController.php b/server/app/adminapi/controller/finance/AccountCostController.php new file mode 100644 index 00000000..d10132de --- /dev/null +++ b/server/app/adminapi/controller/finance/AccountCostController.php @@ -0,0 +1,47 @@ +dataLists(new AccountCostLists()); + } + + public function add() + { + $params = (new AccountCostValidate())->post()->goCheck('add'); + $result = AccountCostLogic::add($params, $this->adminId, (string) ($this->adminInfo['name'] ?? '')); + if ($result === false) { + return $this->fail(AccountCostLogic::getError()); + } + + return $this->success('添加成功', [], 1, 1); + } + + public function edit() + { + $params = (new AccountCostValidate())->post()->goCheck('edit'); + $result = AccountCostLogic::edit($params, $this->adminId, (string) ($this->adminInfo['name'] ?? '')); + if ($result === false) { + return $this->fail(AccountCostLogic::getError()); + } + + return $this->success('编辑成功', [], 1, 1); + } + + public function detail() + { + $params = (new AccountCostValidate())->goCheck('detail'); + + return $this->data(AccountCostLogic::detail((int) $params['id'])); + } +} diff --git a/server/app/adminapi/lists/finance/AccountCostLists.php b/server/app/adminapi/lists/finance/AccountCostLists.php new file mode 100644 index 00000000..a893ee65 --- /dev/null +++ b/server/app/adminapi/lists/finance/AccountCostLists.php @@ -0,0 +1,59 @@ + ['remark', 'creator_name', 'updater_name'], + ]; + } + + private function baseQuery() + { + $query = AccountCost::where($this->searchWhere); + + if (!empty($this->params['start_date']) && !empty($this->params['end_date'])) { + $query->whereBetween('cost_date', [$this->params['start_date'], $this->params['end_date']]); + } elseif (!empty($this->params['start_date'])) { + $query->where('cost_date', '>=', $this->params['start_date']); + } elseif (!empty($this->params['end_date'])) { + $query->where('cost_date', '<=', $this->params['end_date']); + } + + return $query; + } + + public function lists(): array + { + return $this->baseQuery() + ->order(['cost_date' => 'desc', 'id' => 'desc']) + ->limit($this->limitOffset, $this->limitLength) + ->select() + ->toArray(); + } + + public function count(): int + { + return (int) $this->baseQuery()->count(); + } + + public function extend(): array + { + $baseQuery = $this->baseQuery(); + + return [ + 'total_amount' => round((float) $baseQuery->sum('amount'), 2), + 'days_count' => (int) $baseQuery->count(), + ]; + } +} diff --git a/server/app/adminapi/logic/finance/AccountCostLogic.php b/server/app/adminapi/logic/finance/AccountCostLogic.php new file mode 100644 index 00000000..a906cb40 --- /dev/null +++ b/server/app/adminapi/logic/finance/AccountCostLogic.php @@ -0,0 +1,68 @@ +count() > 0) { + self::setError('该日期的账户消耗已存在,请直接编辑'); + + return false; + } + + AccountCost::create([ + 'cost_date' => $costDate, + 'amount' => round((float) $params['amount'], 2), + 'remark' => (string) ($params['remark'] ?? ''), + 'creator_id' => $adminId, + 'creator_name' => $adminName, + 'updater_id' => $adminId, + 'updater_name' => $adminName, + ]); + + return true; + } catch (\Throwable $e) { + self::setError($e->getMessage()); + + return false; + } + } + + public static function edit(array $params, int $adminId, string $adminName): bool + { + try { + $model = AccountCost::find($params['id']); + if (!$model) { + self::setError('记录不存在'); + + return false; + } + + $model->amount = round((float) $params['amount'], 2); + $model->remark = (string) ($params['remark'] ?? ''); + $model->updater_id = $adminId; + $model->updater_name = $adminName; + $model->save(); + + return true; + } catch (\Throwable $e) { + self::setError($e->getMessage()); + + return false; + } + } + + public static function detail(int $id): array + { + return AccountCost::findOrEmpty($id)->toArray(); + } +} diff --git a/server/app/adminapi/logic/stats/ConversionLogic.php b/server/app/adminapi/logic/stats/ConversionLogic.php index e3cc3f49..cc369e66 100644 --- a/server/app/adminapi/logic/stats/ConversionLogic.php +++ b/server/app/adminapi/logic/stats/ConversionLogic.php @@ -6,7 +6,6 @@ namespace app\adminapi\logic\stats; use app\adminapi\logic\dept\DeptLogic; use app\adminapi\logic\tcm\DiagnosisLogic; -use think\facade\Cache; use think\facade\Db; class ConversionLogic @@ -17,18 +16,6 @@ class ConversionLogic public static function overview(array $params = []): array { $includeFilters = (int)($params['include_filters'] ?? 0) === 1; - $cacheParams = $params; - ksort($cacheParams); - $cacheKey = 'conversion_stats_overview_' . md5(json_encode($cacheParams, JSON_UNESCAPED_UNICODE)); - $cached = Cache::get($cacheKey); - if (is_array($cached)) { - if ($includeFilters) { - $cached['extend']['filters'] = self::buildFilterOptions(); - } - - return $cached; - } - $dimension = self::normalizeDimension((string)($params['dimension'] ?? 'dept')); [$startTimestamp, $endTimestamp, $startDate, $endDate] = self::resolveTimeRange($params); $pageNo = max(1, (int)($params['page_no'] ?? 1)); @@ -57,7 +44,6 @@ class ConversionLogic if ($includeFilters) { $result['extend']['filters'] = self::buildFilterOptions(); } - Cache::set($cacheKey, $result, 60); return $result; } @@ -65,9 +51,9 @@ class ConversionLogic $adminToDeptIds = self::loadAdminDeptMap(); self::hydrateFanStats($entities, $dimension, $entityIds, $adminToDeptIds, $startTimestamp, $endTimestamp); self::hydrateAppointmentStats($entities, $dimension, $entityIds, $adminToDeptIds, $startDate, $endDate); - self::finalizeAppointmentStats($entities); self::hydratePaidAuditAmountStats($entities, $dimension, $entityIds, $adminToDeptIds, $startTimestamp, $endTimestamp); self::hydrateCompletedOrderStats($entities, $dimension, $entityIds, $adminToDeptIds, $startTimestamp, $endTimestamp); + self::hydrateAccountCostStats($entities, $startDate, $endDate); if ($dimension === 'dept') { [$allRows, $pagedRows, $chartRows] = self::buildDeptTreeRows( @@ -96,7 +82,6 @@ class ConversionLogic if ($includeFilters) { $result['extend']['filters'] = self::buildFilterOptions(); } - Cache::set($cacheKey, $result, 60); return $result; } @@ -124,7 +109,6 @@ class ConversionLogic if ($includeFilters) { $result['extend']['filters'] = self::buildFilterOptions(); } - Cache::set($cacheKey, $result, 60); return $result; } @@ -134,20 +118,11 @@ class ConversionLogic */ private static function buildFilterOptions(): array { - $cacheKey = 'conversion_stats_filters'; - $cached = Cache::get($cacheKey); - if (is_array($cached)) { - return $cached; - } - - $filters = [ + return [ 'departments' => DeptLogic::getAllData(), 'assistants' => DiagnosisLogic::getAssistants(), 'doctors' => DiagnosisLogic::getDoctors(), ]; - Cache::set($cacheKey, $filters, 600); - - return $filters; } private static function normalizeDimension(string $dimension): string @@ -316,7 +291,6 @@ class ConversionLogic 'completed_order_amount' => 0.0, 'completed_order_count' => 0, 'account_cost' => 0.0, - '_appointment_groups' => [], ]; } @@ -364,6 +338,7 @@ class ConversionLogic ->select() ->toArray(); + $countsByAdmin = []; foreach ($rows as $row) { $adminIds = self::extractFollowAdminIds($row, $wxUserIdToAdminIds); if ($adminIds === []) { @@ -371,9 +346,13 @@ class ConversionLogic } foreach ($adminIds as $adminId) { - foreach (self::mapEntityIds($dimension, $adminId, $entityIds, $adminToDeptIds) as $entityId) { - $entities[$entityId]['add_fans_count']++; - } + $countsByAdmin[$adminId] = ($countsByAdmin[$adminId] ?? 0) + 1; + } + } + + foreach ($countsByAdmin as $adminId => $count) { + foreach (self::mapEntityIds($dimension, (int) $adminId, $entityIds, $adminToDeptIds) as $entityId) { + $entities[$entityId]['add_fans_count'] += (int) $count; } } } @@ -457,10 +436,15 @@ class ConversionLogic string $startDate, string $endDate ): void { + $sourceExpr = $dimension === 'doctor' ? 'a.doctor_id' : 'u.assistant_id'; $rows = Db::name('doctor_appointment') - ->where('appointment_date', '>=', $startDate) - ->where('appointment_date', '<=', $endDate) - ->field('patient_id, doctor_id, assistant_id, status') + ->alias('a') + ->leftJoin('tcm_diagnosis u', 'a.patient_id = u.id') + ->where('a.appointment_date', '>=', $startDate) + ->where('a.appointment_date', '<=', $endDate) + ->whereRaw('(u.id IS NULL OR u.delete_time IS NULL)') + ->fieldRaw("{$sourceExpr} AS source_admin_id, a.patient_id AS diagnosis_id, COUNT(*) AS appointment_count, SUM(CASE WHEN a.status = 3 THEN 1 ELSE 0 END) AS interview_count") + ->group("{$sourceExpr}, a.patient_id") ->select() ->toArray(); @@ -470,61 +454,26 @@ class ConversionLogic continue; } - $sourceAdminId = $dimension === 'doctor' - ? (int)($row['doctor_id'] ?? 0) - : (int)($row['assistant_id'] ?? 0); + $sourceAdminId = (int)($row['source_admin_id'] ?? 0); $mappedEntityIds = self::mapEntityIds($dimension, $sourceAdminId, $entityIds, $adminToDeptIds); if ($mappedEntityIds === []) { continue; } + $appointmentCount = (int)($row['appointment_count'] ?? 0); + $interviewCount = (int)($row['interview_count'] ?? 0); foreach ($mappedEntityIds as $entityId) { - $entities[$entityId]['appointment_total_count']++; - if (!isset($entities[$entityId]['_appointment_groups'][$diagnosisId])) { - $entities[$entityId]['_appointment_groups'][$diagnosisId] = [ - 'count' => 0, - 'has_completed' => false, - ]; - } - $entities[$entityId]['_appointment_groups'][$diagnosisId]['count']++; - if ((int)($row['status'] ?? 0) === 3) { - $entities[$entityId]['_appointment_groups'][$diagnosisId]['has_completed'] = true; - $entities[$entityId]['interview_count']++; + $entities[$entityId]['appointment_total_count'] += $appointmentCount; + $entities[$entityId]['paid_appointment_count'] += 1; + if ($appointmentCount > 1) { + $entities[$entityId]['free_appointment_count'] += ($appointmentCount - 1); } + $entities[$entityId]['interview_count'] += $interviewCount; } } } - /** - * @param array> $entities - */ - private static function finalizeAppointmentStats(array &$entities): void - { - foreach ($entities as &$entity) { - $groups = $entity['_appointment_groups'] ?? []; - $paid = 0; - $free = 0; - foreach ($groups as $group) { - $count = (int)($group['count'] ?? 0); - if ($count <= 0) { - continue; - } - $paid += 1; - // 与问诊列表总数口径对齐: - // 同一诊单第一条记为付费挂号,其余挂号记录都记为免费挂号, - // 否则“重复挂号但未完成”的记录会被漏掉,导致付费+免费 < 挂号总数。 - if ($count > 1) { - $free += ($count - 1); - } - } - $entity['paid_appointment_count'] = $paid; - $entity['free_appointment_count'] = $free; - unset($entity['_appointment_groups']); - } - unset($entity); - } - /** * @param array> $entities * @param int[] $entityIds @@ -538,6 +487,7 @@ class ConversionLogic int $startTimestamp, int $endTimestamp ): void { + $sourceExpr = $dimension === 'doctor' ? 'rx.creator_id' : 'rx.assistant_id'; $rows = Db::name('tcm_prescription_order') ->alias('po') ->leftJoin('tcm_prescription rx', 'rx.id = po.prescription_id') @@ -546,29 +496,47 @@ class ConversionLogic ->where('po.prescription_audit_status', 1) ->where('po.payment_slip_audit_status', 1) ->where('po.update_time', 'between', [$startTimestamp, $endTimestamp]) - ->field('po.amount, po.internal_cost, rx.assistant_id, rx.creator_id') + ->fieldRaw("{$sourceExpr} AS source_admin_id, COUNT(*) AS order_count") + ->group($sourceExpr) ->select() ->toArray(); foreach ($rows as $row) { - $sourceAdminId = $dimension === 'doctor' - ? (int)($row['creator_id'] ?? 0) - : (int)($row['assistant_id'] ?? 0); + $sourceAdminId = (int)($row['source_admin_id'] ?? 0); $mappedEntityIds = self::mapEntityIds($dimension, $sourceAdminId, $entityIds, $adminToDeptIds); if ($mappedEntityIds === []) { continue; } - $internalCost = round((float)($row['internal_cost'] ?? 0), 2); + $orderCount = (int)($row['order_count'] ?? 0); foreach ($mappedEntityIds as $entityId) { - $entities[$entityId]['completed_order_count']++; - $entities[$entityId]['account_cost'] = round($entities[$entityId]['account_cost'] + $internalCost, 2); + $entities[$entityId]['completed_order_count'] += $orderCount; } } } + /** + * 账户消耗:来源于独立维护表 zyt_account_cost,为全局日维度数据。 + * 由于当前没有部门/医助/医生拆分来源,只在汇总层使用,不向各明细行分摊。 + * + * @param array> $entities + */ + private static function hydrateAccountCostStats(array &$entities, string $startDate, string $endDate): void + { + $totalAmount = round((float) Db::name('account_cost') + ->where('cost_date', '>=', $startDate) + ->where('cost_date', '<=', $endDate) + ->sum('amount'), 2); + + foreach ($entities as &$entity) { + $entity['account_cost'] = 0.0; + $entity['_global_account_cost'] = $totalAmount; + } + unset($entity); + } + /** * 诊单金额:按支付单审核通过(payment_slip_audit_status=1)的业务订单 amount 汇总 * @@ -584,27 +552,27 @@ class ConversionLogic int $startTimestamp, int $endTimestamp ): void { + $sourceExpr = $dimension === 'doctor' ? 'rx.creator_id' : 'rx.assistant_id'; $rows = Db::name('tcm_prescription_order') ->alias('po') ->leftJoin('tcm_prescription rx', 'rx.id = po.prescription_id') ->whereNull('po.delete_time') ->where('po.payment_slip_audit_status', 1) ->where('po.update_time', 'between', [$startTimestamp, $endTimestamp]) - ->field('po.amount, rx.assistant_id, rx.creator_id') + ->fieldRaw("{$sourceExpr} AS source_admin_id, SUM(po.amount) AS total_amount") + ->group($sourceExpr) ->select() ->toArray(); foreach ($rows as $row) { - $sourceAdminId = $dimension === 'doctor' - ? (int)($row['creator_id'] ?? 0) - : (int)($row['assistant_id'] ?? 0); + $sourceAdminId = (int)($row['source_admin_id'] ?? 0); $mappedEntityIds = self::mapEntityIds($dimension, $sourceAdminId, $entityIds, $adminToDeptIds); if ($mappedEntityIds === []) { continue; } - $amount = round((float)($row['amount'] ?? 0), 2); + $amount = round((float)($row['total_amount'] ?? 0), 2); foreach ($mappedEntityIds as $entityId) { $entities[$entityId]['completed_order_amount'] = round($entities[$entityId]['completed_order_amount'] + $amount, 2); } @@ -641,6 +609,7 @@ class ConversionLogic private static function finalizeRows(array $entities): array { $rows = []; + $globalAccountCost = 0.0; foreach ($entities as $entity) { $paidAppointmentCount = (int)($entity['paid_appointment_count'] ?? 0); $freeAppointmentCount = (int)($entity['free_appointment_count'] ?? 0); @@ -651,12 +620,14 @@ class ConversionLogic $completedOrderCount = (int)$entity['completed_order_count']; $completedOrderAmount = round((float)$entity['completed_order_amount'], 2); $accountCost = round((float)$entity['account_cost'], 2); + $globalAccountCost = max($globalAccountCost, round((float)($entity['_global_account_cost'] ?? 0), 2)); + $effectiveAccountCost = $globalAccountCost > 0 ? $globalAccountCost : $accountCost; $entity['paid_appointment_count'] = $paidAppointmentCount; $entity['free_appointment_count'] = $freeAppointmentCount; $entity['appointment_total_count'] = $appointmentTotalCount; $entity['completed_order_amount'] = $completedOrderAmount; - $entity['account_cost'] = $accountCost; + $entity['account_cost'] = $effectiveAccountCost; $entity['paid_appointment_rate'] = self::percent($paidAppointmentCount, $addFansCount); $entity['open_appointment_rate'] = self::percent($paidAppointmentCount, $totalOpenCount); $entity['interview_rate'] = self::percent($interviewCount, $appointmentTotalCount); @@ -664,8 +635,8 @@ class ConversionLogic $entity['interview_receive_rate'] = self::percent($completedOrderCount, $interviewCount); $entity['open_receive_rate'] = self::percent($completedOrderCount, $totalOpenCount); $entity['avg_unit_price'] = self::safeDivideMoney($completedOrderAmount, $completedOrderCount); - $entity['cash_cost'] = self::safeDivideMoney($accountCost, $addFansCount); - $entity['roi'] = self::safeDivideRatio($completedOrderAmount, $accountCost); + $entity['cash_cost'] = self::safeDivideMoney($effectiveAccountCost, $addFansCount); + $entity['roi'] = self::safeDivideRatio($completedOrderAmount, $effectiveAccountCost); $rows[] = $entity; } @@ -683,6 +654,13 @@ class ConversionLogic return $right['add_fans_count'] <=> $left['add_fans_count']; }); + if ($globalAccountCost > 0 && $rows !== []) { + foreach ($rows as &$row) { + $row['_global_account_cost'] = $globalAccountCost; + } + unset($row); + } + return $rows; } @@ -815,12 +793,14 @@ class ConversionLogic $completedOrderCount = (int)$node['completed_order_count']; $completedOrderAmount = round((float)$node['completed_order_amount'], 2); $accountCost = round((float)$node['account_cost'], 2); + $globalAccountCost = round((float)($node['_global_account_cost'] ?? 0), 2); + $effectiveAccountCost = $globalAccountCost > 0 ? $globalAccountCost : $accountCost; $node['paid_appointment_count'] = $paidAppointmentCount; $node['free_appointment_count'] = $freeAppointmentCount; $node['appointment_total_count'] = $appointmentTotalCount; $node['completed_order_amount'] = $completedOrderAmount; - $node['account_cost'] = $accountCost; + $node['account_cost'] = $effectiveAccountCost; $node['paid_appointment_rate'] = self::percent($paidAppointmentCount, $addFansCount); $node['open_appointment_rate'] = self::percent($paidAppointmentCount, $totalOpenCount); $node['interview_rate'] = self::percent($interviewCount, $appointmentTotalCount); @@ -828,8 +808,11 @@ class ConversionLogic $node['interview_receive_rate'] = self::percent($completedOrderCount, $interviewCount); $node['open_receive_rate'] = self::percent($completedOrderCount, $totalOpenCount); $node['avg_unit_price'] = self::safeDivideMoney($completedOrderAmount, $completedOrderCount); - $node['cash_cost'] = self::safeDivideMoney($accountCost, $addFansCount); - $node['roi'] = self::safeDivideRatio($completedOrderAmount, $accountCost); + $node['cash_cost'] = self::safeDivideMoney($effectiveAccountCost, $addFansCount); + $node['roi'] = self::safeDivideRatio($completedOrderAmount, $effectiveAccountCost); + if ($globalAccountCost > 0) { + $node['_global_account_cost'] = $globalAccountCost; + } unset($node['sort'], $node['pid']); return $node; @@ -853,6 +836,7 @@ class ConversionLogic 'completed_order_count' => 0, 'account_cost' => 0.0, ]; + $globalAccountCost = 0.0; foreach ($rows as $row) { $summary['add_fans_count'] += (int)$row['add_fans_count']; @@ -864,9 +848,11 @@ class ConversionLogic $summary['interview_count'] += (int)$row['interview_count']; $summary['completed_order_count'] += (int)$row['completed_order_count']; $summary['completed_order_amount'] = round($summary['completed_order_amount'] + (float)$row['completed_order_amount'], 2); - $summary['account_cost'] = round($summary['account_cost'] + (float)$row['account_cost'], 2); + $globalAccountCost = max($globalAccountCost, round((float)($row['_global_account_cost'] ?? 0), 2)); } + $summary['account_cost'] = $globalAccountCost; + $summary['paid_appointment_rate'] = self::percent($summary['paid_appointment_count'], $summary['add_fans_count']); $summary['open_appointment_rate'] = self::percent($summary['paid_appointment_count'], $summary['total_open_count']); $summary['interview_rate'] = self::percent($summary['interview_count'], $summary['appointment_total_count']); diff --git a/server/app/adminapi/validate/finance/AccountCostValidate.php b/server/app/adminapi/validate/finance/AccountCostValidate.php new file mode 100644 index 00000000..5bac0f46 --- /dev/null +++ b/server/app/adminapi/validate/finance/AccountCostValidate.php @@ -0,0 +1,53 @@ + 'require|checkExists', + 'cost_date' => 'require|dateFormat:Y-m-d', + 'amount' => 'require|float|egt:0', + 'remark' => 'max:255', + ]; + + protected $message = [ + 'id.require' => '参数缺失', + 'cost_date.require' => '请选择日期', + 'cost_date.dateFormat' => '日期格式错误', + 'amount.require' => '请输入账户消耗金额', + 'amount.float' => '账户消耗金额格式错误', + 'amount.egt' => '账户消耗金额不能小于0', + 'remark.max' => '备注不能超过255个字符', + ]; + + public function sceneAdd() + { + return $this->remove('id', true); + } + + public function sceneEdit() + { + return $this->remove('cost_date', true); + } + + public function sceneDetail() + { + return $this->only(['id']); + } + + public function checkExists($value) + { + $model = AccountCost::find($value); + if (!$model) { + return '记录不存在'; + } + + return true; + } +} diff --git a/server/app/common/model/finance/AccountCost.php b/server/app/common/model/finance/AccountCost.php new file mode 100644 index 00000000..a4413975 --- /dev/null +++ b/server/app/common/model/finance/AccountCost.php @@ -0,0 +1,20 @@ +