1
This commit is contained in:
@@ -28,8 +28,11 @@ class ConversionLogic
|
|||||||
$pageNo = max(1, (int)($params['page_no'] ?? 1));
|
$pageNo = max(1, (int)($params['page_no'] ?? 1));
|
||||||
$pageSize = max(1, min(100, (int)($params['page_size'] ?? 15)));
|
$pageSize = max(1, min(100, (int)($params['page_size'] ?? 15)));
|
||||||
|
|
||||||
|
$allocationParams = self::buildAllocationScopeParams($dimension, $params);
|
||||||
$entities = self::loadEntities($dimension, $params);
|
$entities = self::loadEntities($dimension, $params);
|
||||||
$entityIds = array_keys($entities);
|
$entityIds = array_keys($entities);
|
||||||
|
$allocationEntities = self::loadEntities($dimension, $allocationParams);
|
||||||
|
$allocationEntityIds = array_keys($allocationEntities);
|
||||||
|
|
||||||
if ($entityIds === []) {
|
if ($entityIds === []) {
|
||||||
$result = [
|
$result = [
|
||||||
@@ -56,8 +59,10 @@ class ConversionLogic
|
|||||||
}
|
}
|
||||||
|
|
||||||
$adminToDeptIds = self::loadAdminDeptMap();
|
$adminToDeptIds = self::loadAdminDeptMap();
|
||||||
|
self::hydrateFanStats($allocationEntities, $dimension, $allocationEntityIds, $adminToDeptIds, $startTimestamp, $endTimestamp, $mediaChannel);
|
||||||
|
$allocationAddFansCount = self::sumEntityAddFans(array_values($allocationEntities));
|
||||||
self::hydrateFanStats($entities, $dimension, $entityIds, $adminToDeptIds, $startTimestamp, $endTimestamp, $mediaChannel);
|
self::hydrateFanStats($entities, $dimension, $entityIds, $adminToDeptIds, $startTimestamp, $endTimestamp, $mediaChannel);
|
||||||
self::hydrateAppointmentStats($entities, $dimension, $entityIds, $adminToDeptIds, $startDate, $endDate, $mediaChannel);
|
self::hydrateAppointmentStats($entities, $dimension, $entityIds, $adminToDeptIds, $startTimestamp, $endTimestamp, $startDate, $endDate, $mediaChannel);
|
||||||
self::hydrateOrderAndAmountStats($entities, $dimension, $entityIds, $adminToDeptIds, $startTimestamp, $endTimestamp, $mediaChannel);
|
self::hydrateOrderAndAmountStats($entities, $dimension, $entityIds, $adminToDeptIds, $startTimestamp, $endTimestamp, $mediaChannel);
|
||||||
$globalAccountCost = self::hydrateAccountCostStats($entities, $startDate, $endDate, $mediaChannelCode);
|
$globalAccountCost = self::hydrateAccountCostStats($entities, $startDate, $endDate, $mediaChannelCode);
|
||||||
|
|
||||||
@@ -67,13 +72,14 @@ class ConversionLogic
|
|||||||
isset($params['dept_id']) ? (int)$params['dept_id'] : 0,
|
isset($params['dept_id']) ? (int)$params['dept_id'] : 0,
|
||||||
$pageNo,
|
$pageNo,
|
||||||
$pageSize,
|
$pageSize,
|
||||||
$filterEmptyEntities
|
$filterEmptyEntities,
|
||||||
|
$allocationAddFansCount
|
||||||
);
|
);
|
||||||
|
|
||||||
$result = [
|
$result = [
|
||||||
'dimension' => $dimension,
|
'dimension' => $dimension,
|
||||||
'date_range' => [$startDate, $endDate],
|
'date_range' => [$startDate, $endDate],
|
||||||
'summary' => self::buildSummary($allRows, $globalAccountCost),
|
'summary' => self::buildSummary($allRows),
|
||||||
'charts' => self::buildCharts($chartRows),
|
'charts' => self::buildCharts($chartRows),
|
||||||
'lists' => $pagedRows,
|
'lists' => $pagedRows,
|
||||||
'count' => count($allRows),
|
'count' => count($allRows),
|
||||||
@@ -82,7 +88,7 @@ class ConversionLogic
|
|||||||
'extend' => [
|
'extend' => [
|
||||||
'dimension' => $dimension,
|
'dimension' => $dimension,
|
||||||
'date_range' => [$startDate, $endDate],
|
'date_range' => [$startDate, $endDate],
|
||||||
'summary' => self::buildSummary($allRows, $globalAccountCost),
|
'summary' => self::buildSummary($allRows),
|
||||||
'charts' => self::buildCharts($chartRows),
|
'charts' => self::buildCharts($chartRows),
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
@@ -93,14 +99,14 @@ class ConversionLogic
|
|||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
$rows = self::finalizeRows($entities, $filterEmptyEntities);
|
$rows = self::finalizeRows($entities, $filterEmptyEntities, $allocationAddFansCount);
|
||||||
$count = count($rows);
|
$count = count($rows);
|
||||||
$offset = ($pageNo - 1) * $pageSize;
|
$offset = ($pageNo - 1) * $pageSize;
|
||||||
|
|
||||||
$result = [
|
$result = [
|
||||||
'dimension' => $dimension,
|
'dimension' => $dimension,
|
||||||
'date_range' => [$startDate, $endDate],
|
'date_range' => [$startDate, $endDate],
|
||||||
'summary' => self::buildSummary($rows, $globalAccountCost),
|
'summary' => self::buildSummary($rows),
|
||||||
'charts' => self::buildCharts($rows),
|
'charts' => self::buildCharts($rows),
|
||||||
'lists' => array_slice($rows, $offset, $pageSize),
|
'lists' => array_slice($rows, $offset, $pageSize),
|
||||||
'count' => $count,
|
'count' => $count,
|
||||||
@@ -109,7 +115,7 @@ class ConversionLogic
|
|||||||
'extend' => [
|
'extend' => [
|
||||||
'dimension' => $dimension,
|
'dimension' => $dimension,
|
||||||
'date_range' => [$startDate, $endDate],
|
'date_range' => [$startDate, $endDate],
|
||||||
'summary' => self::buildSummary($rows, $globalAccountCost),
|
'summary' => self::buildSummary($rows),
|
||||||
'charts' => self::buildCharts($rows),
|
'charts' => self::buildCharts($rows),
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
@@ -120,6 +126,15 @@ class ConversionLogic
|
|||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static function buildAllocationScopeParams(string $dimension, array $params): array
|
||||||
|
{
|
||||||
|
$allocationParams = $params;
|
||||||
|
|
||||||
|
unset($allocationParams['dept_id'], $allocationParams['assistant_id'], $allocationParams['doctor_id']);
|
||||||
|
|
||||||
|
return $allocationParams;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<string, mixed>
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
@@ -432,6 +447,8 @@ class ConversionLogic
|
|||||||
string $dimension,
|
string $dimension,
|
||||||
array $entityIds,
|
array $entityIds,
|
||||||
array $adminToDeptIds,
|
array $adminToDeptIds,
|
||||||
|
int $startTimestamp,
|
||||||
|
int $endTimestamp,
|
||||||
string $startDate,
|
string $startDate,
|
||||||
string $endDate,
|
string $endDate,
|
||||||
?array $mediaChannel
|
?array $mediaChannel
|
||||||
@@ -447,8 +464,13 @@ class ConversionLogic
|
|||||||
->group("{$sourceExpr}, a.patient_id");
|
->group("{$sourceExpr}, a.patient_id");
|
||||||
|
|
||||||
if ($mediaChannel !== null) {
|
if ($mediaChannel !== null) {
|
||||||
$query->leftJoin('qywx_external_contact q', 'q.external_userid = u.external_userid');
|
$legacyChannelValues = MediaChannelService::getLegacyAppointmentChannelValues($mediaChannel);
|
||||||
MediaChannelService::applyFollowUsersChannelFilter($query, 'q.follow_users', $mediaChannel);
|
if ($legacyChannelValues !== []) {
|
||||||
|
$query->whereIn('a.channels', $legacyChannelValues);
|
||||||
|
} else {
|
||||||
|
$query->leftJoin('qywx_external_contact q', 'q.external_userid = u.external_userid');
|
||||||
|
MediaChannelService::applyFollowUsersChannelFilter($query, 'q.follow_users', $mediaChannel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$rows = $query->select()->toArray();
|
$rows = $query->select()->toArray();
|
||||||
@@ -470,13 +492,65 @@ class ConversionLogic
|
|||||||
$interviewCount = (int)($row['interview_count'] ?? 0);
|
$interviewCount = (int)($row['interview_count'] ?? 0);
|
||||||
foreach ($mappedEntityIds as $entityId) {
|
foreach ($mappedEntityIds as $entityId) {
|
||||||
$entities[$entityId]['appointment_total_count'] += $appointmentCount;
|
$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;
|
$entities[$entityId]['interview_count'] += $interviewCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self::hydratePaidAppointmentStats($entities, $dimension, $entityIds, $adminToDeptIds, $startTimestamp, $endTimestamp, $mediaChannel);
|
||||||
|
|
||||||
|
foreach ($entities as &$entity) {
|
||||||
|
$appointmentTotalCount = (int)($entity['appointment_total_count'] ?? 0);
|
||||||
|
$paidAppointmentCount = (int)($entity['paid_appointment_count'] ?? 0);
|
||||||
|
$entity['free_appointment_count'] = max($appointmentTotalCount - $paidAppointmentCount, 0);
|
||||||
|
}
|
||||||
|
unset($entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<int, array<string, mixed>> $entities
|
||||||
|
* @param int[] $entityIds
|
||||||
|
* @param array<int, int[]> $adminToDeptIds
|
||||||
|
* @param array<string, mixed>|null $mediaChannel
|
||||||
|
*/
|
||||||
|
private static function hydratePaidAppointmentStats(
|
||||||
|
array &$entities,
|
||||||
|
string $dimension,
|
||||||
|
array $entityIds,
|
||||||
|
array $adminToDeptIds,
|
||||||
|
int $startTimestamp,
|
||||||
|
int $endTimestamp,
|
||||||
|
?array $mediaChannel
|
||||||
|
): void {
|
||||||
|
$startDateTime = date('Y-m-d H:i:s', $startTimestamp);
|
||||||
|
$endDateTime = date('Y-m-d H:i:s', $endTimestamp);
|
||||||
|
$query = Db::name('order')
|
||||||
|
->alias('o')
|
||||||
|
->whereNull('o.delete_time')
|
||||||
|
->where('o.status', 2)
|
||||||
|
->where('o.order_type', 1)
|
||||||
|
->where('o.amount', 5)
|
||||||
|
->whereRaw('(o.payment_time IS NOT NULL AND o.payment_time <> "" AND o.payment_time >= ? AND o.payment_time <= ?)', [$startDateTime, $endDateTime])
|
||||||
|
->fieldRaw('o.creator_id AS source_admin_id, COUNT(*) AS paid_appointment_count')
|
||||||
|
->group('o.creator_id');
|
||||||
|
|
||||||
|
if ($mediaChannel !== null) {
|
||||||
|
$query->leftJoin('qywx_external_contact q', 'q.external_userid = o.payer_external_userid');
|
||||||
|
MediaChannelService::applyFollowUsersChannelFilter($query, 'q.follow_users', $mediaChannel);
|
||||||
|
}
|
||||||
|
|
||||||
|
$rows = $query->select()->toArray();
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$sourceAdminId = (int)($row['source_admin_id'] ?? 0);
|
||||||
|
$mappedEntityIds = self::mapEntityIds($dimension, $sourceAdminId, $entityIds, $adminToDeptIds);
|
||||||
|
if ($mappedEntityIds === []) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$count = (int)($row['paid_appointment_count'] ?? 0);
|
||||||
|
foreach ($mappedEntityIds as $entityId) {
|
||||||
|
$entities[$entityId]['paid_appointment_count'] += $count;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -506,7 +580,9 @@ class ConversionLogic
|
|||||||
->group($completedSourceExpr);
|
->group($completedSourceExpr);
|
||||||
|
|
||||||
if ($mediaChannel !== null) {
|
if ($mediaChannel !== null) {
|
||||||
$completedQuery->leftJoin('qywx_external_contact q', 'q.external_userid = dg.external_userid');
|
$completedQuery
|
||||||
|
->leftJoin('order o', 'o.id = po.linked_pay_order_id')
|
||||||
|
->leftJoin('qywx_external_contact q', 'q.external_userid = o.payer_external_userid');
|
||||||
MediaChannelService::applyFollowUsersChannelFilter($completedQuery, 'q.follow_users', $mediaChannel);
|
MediaChannelService::applyFollowUsersChannelFilter($completedQuery, 'q.follow_users', $mediaChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -540,7 +616,9 @@ class ConversionLogic
|
|||||||
->group($businessSourceExpr);
|
->group($businessSourceExpr);
|
||||||
|
|
||||||
if ($mediaChannel !== null) {
|
if ($mediaChannel !== null) {
|
||||||
$businessQuery->leftJoin('qywx_external_contact q2', 'q2.external_userid = dg.external_userid');
|
$businessQuery
|
||||||
|
->leftJoin('order o2', 'o2.id = po.linked_pay_order_id')
|
||||||
|
->leftJoin('qywx_external_contact q2', 'q2.external_userid = o2.payer_external_userid');
|
||||||
MediaChannelService::applyFollowUsersChannelFilter($businessQuery, 'q2.follow_users', $mediaChannel);
|
MediaChannelService::applyFollowUsersChannelFilter($businessQuery, 'q2.follow_users', $mediaChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -616,11 +694,17 @@ class ConversionLogic
|
|||||||
* @param array<int, array<string, mixed>> $entities
|
* @param array<int, array<string, mixed>> $entities
|
||||||
* @return array<int, array<string, mixed>>
|
* @return array<int, array<string, mixed>>
|
||||||
*/
|
*/
|
||||||
private static function finalizeRows(array $entities, bool $excludeEmpty = false): array
|
private static function finalizeRows(array $entities, bool $excludeEmpty = false, ?int $allocationAddFansCount = null): array
|
||||||
{
|
{
|
||||||
$rows = [];
|
$rows = [];
|
||||||
$globalAccountCost = 0.0;
|
$filteredEntities = $excludeEmpty
|
||||||
foreach ($entities as $entity) {
|
? array_values(array_filter($entities, static fn (array $entity): bool => self::hasBusinessMetrics($entity)))
|
||||||
|
: array_values($entities);
|
||||||
|
|
||||||
|
$globalAccountCost = self::extractGlobalAccountCost($filteredEntities);
|
||||||
|
$globalAddFansCount = $allocationAddFansCount ?? self::sumEntityAddFans($filteredEntities);
|
||||||
|
|
||||||
|
foreach ($filteredEntities as $entity) {
|
||||||
$paidAppointmentCount = (int)($entity['paid_appointment_count'] ?? 0);
|
$paidAppointmentCount = (int)($entity['paid_appointment_count'] ?? 0);
|
||||||
$freeAppointmentCount = (int)($entity['free_appointment_count'] ?? 0);
|
$freeAppointmentCount = (int)($entity['free_appointment_count'] ?? 0);
|
||||||
$appointmentTotalCount = (int)($entity['appointment_total_count'] ?? 0);
|
$appointmentTotalCount = (int)($entity['appointment_total_count'] ?? 0);
|
||||||
@@ -630,9 +714,7 @@ class ConversionLogic
|
|||||||
$completedOrderCount = (int)$entity['completed_order_count'];
|
$completedOrderCount = (int)$entity['completed_order_count'];
|
||||||
$businessOrderAmount = round((float)$entity['business_order_amount'], 2);
|
$businessOrderAmount = round((float)$entity['business_order_amount'], 2);
|
||||||
$completedOrderAmount = round((float)$entity['completed_order_amount'], 2);
|
$completedOrderAmount = round((float)$entity['completed_order_amount'], 2);
|
||||||
$accountCost = round((float)$entity['account_cost'], 2);
|
$effectiveAccountCost = self::allocateAccountCost($globalAccountCost, $globalAddFansCount, $addFansCount);
|
||||||
$globalAccountCost = max($globalAccountCost, round((float)($entity['_global_account_cost'] ?? 0), 2));
|
|
||||||
$effectiveAccountCost = $globalAccountCost > 0 ? $globalAccountCost : $accountCost;
|
|
||||||
|
|
||||||
$entity['paid_appointment_count'] = $paidAppointmentCount;
|
$entity['paid_appointment_count'] = $paidAppointmentCount;
|
||||||
$entity['free_appointment_count'] = $freeAppointmentCount;
|
$entity['free_appointment_count'] = $freeAppointmentCount;
|
||||||
@@ -649,9 +731,8 @@ class ConversionLogic
|
|||||||
$entity['avg_unit_price'] = self::safeDivideMoney($completedOrderAmount, $completedOrderCount);
|
$entity['avg_unit_price'] = self::safeDivideMoney($completedOrderAmount, $completedOrderCount);
|
||||||
$entity['cash_cost'] = self::safeDivideMoney($effectiveAccountCost, $addFansCount);
|
$entity['cash_cost'] = self::safeDivideMoney($effectiveAccountCost, $addFansCount);
|
||||||
$entity['roi'] = self::safeDivideRatio($completedOrderAmount, $effectiveAccountCost);
|
$entity['roi'] = self::safeDivideRatio($completedOrderAmount, $effectiveAccountCost);
|
||||||
|
if ($globalAccountCost > 0) {
|
||||||
if ($excludeEmpty && !self::hasBusinessMetrics($entity)) {
|
$entity['_global_account_cost'] = $globalAccountCost;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$rows[] = $entity;
|
$rows[] = $entity;
|
||||||
@@ -670,13 +751,6 @@ class ConversionLogic
|
|||||||
return $right['add_fans_count'] <=> $left['add_fans_count'];
|
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;
|
return $rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -684,7 +758,7 @@ class ConversionLogic
|
|||||||
* @param array<int, array<string, mixed>> $entities
|
* @param array<int, array<string, mixed>> $entities
|
||||||
* @return array{0: array<int, array<string, mixed>>, 1: array<int, array<string, mixed>>, 2: array<int, array<string, mixed>>}
|
* @return array{0: array<int, array<string, mixed>>, 1: array<int, array<string, mixed>>, 2: array<int, array<string, mixed>>}
|
||||||
*/
|
*/
|
||||||
private static function buildDeptTreeRows(array $entities, int $selectedDeptId, int $pageNo, int $pageSize, bool $excludeEmpty = false): array
|
private static function buildDeptTreeRows(array $entities, int $selectedDeptId, int $pageNo, int $pageSize, bool $excludeEmpty = false, ?int $allocationAddFansCount = null): array
|
||||||
{
|
{
|
||||||
$childrenByPid = [];
|
$childrenByPid = [];
|
||||||
foreach ($entities as $entity) {
|
foreach ($entities as $entity) {
|
||||||
@@ -744,7 +818,7 @@ class ConversionLogic
|
|||||||
return $left <=> $right;
|
return $left <=> $right;
|
||||||
});
|
});
|
||||||
|
|
||||||
$rootRows = array_map(static fn (int $deptId): array => self::finalizeDeptNode($buildNode($deptId)), $rootIds);
|
$rootRows = array_map($buildNode, $rootIds);
|
||||||
if ($excludeEmpty) {
|
if ($excludeEmpty) {
|
||||||
$rootRows = array_values(array_filter(array_map(
|
$rootRows = array_values(array_filter(array_map(
|
||||||
static fn (array $row): ?array => self::pruneDeptNode($row),
|
static fn (array $row): ?array => self::pruneDeptNode($row),
|
||||||
@@ -757,17 +831,28 @@ class ConversionLogic
|
|||||||
if ($selectedDeptId > 0) {
|
if ($selectedDeptId > 0) {
|
||||||
$selectedNode = self::findDeptNode($rootRows, $selectedDeptId);
|
$selectedNode = self::findDeptNode($rootRows, $selectedDeptId);
|
||||||
if ($selectedNode !== null) {
|
if ($selectedNode !== null) {
|
||||||
$allRows = [$selectedNode];
|
$allRowsRaw = [$selectedNode];
|
||||||
$chartRows = !empty($selectedNode['children']) ? $selectedNode['children'] : [$selectedNode];
|
$chartRowsRaw = !empty($selectedNode['children']) ? $selectedNode['children'] : [$selectedNode];
|
||||||
} else {
|
} else {
|
||||||
$allRows = $rootRows;
|
$allRowsRaw = $rootRows;
|
||||||
$chartRows = $virtualRoot !== null ? ($virtualRoot['children'] ?? [$virtualRoot]) : ($realRootRows ?: $rootRows);
|
$chartRowsRaw = $virtualRoot !== null ? ($virtualRoot['children'] ?? [$virtualRoot]) : ($realRootRows ?: $rootRows);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$allRows = $rootRows;
|
$allRowsRaw = $rootRows;
|
||||||
$chartRows = $virtualRoot !== null ? ($virtualRoot['children'] ?? [$virtualRoot]) : ($realRootRows ?: $rootRows);
|
$chartRowsRaw = $virtualRoot !== null ? ($virtualRoot['children'] ?? [$virtualRoot]) : ($realRootRows ?: $rootRows);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$globalAccountCost = self::extractGlobalAccountCost($allRowsRaw);
|
||||||
|
$globalAddFansCount = $allocationAddFansCount ?? self::sumNodeAddFans($allRowsRaw);
|
||||||
|
$allRows = array_map(
|
||||||
|
static fn (array $row): array => self::finalizeDeptNode($row, $globalAccountCost, $globalAddFansCount),
|
||||||
|
$allRowsRaw
|
||||||
|
);
|
||||||
|
$chartRows = array_map(
|
||||||
|
static fn (array $row): array => self::finalizeDeptNode($row, $globalAccountCost, $globalAddFansCount),
|
||||||
|
$chartRowsRaw
|
||||||
|
);
|
||||||
|
|
||||||
$offset = ($pageNo - 1) * $pageSize;
|
$offset = ($pageNo - 1) * $pageSize;
|
||||||
|
|
||||||
return [$allRows, array_slice($allRows, $offset, $pageSize), $chartRows];
|
return [$allRows, array_slice($allRows, $offset, $pageSize), $chartRows];
|
||||||
@@ -825,9 +910,12 @@ class ConversionLogic
|
|||||||
* @param array<string, mixed> $node
|
* @param array<string, mixed> $node
|
||||||
* @return array<string, mixed>
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
private static function finalizeDeptNode(array $node): array
|
private static function finalizeDeptNode(array $node, float $globalAccountCost, int $globalAddFansCount): array
|
||||||
{
|
{
|
||||||
$node['children'] = array_map(static fn (array $child): array => self::finalizeDeptNode($child), $node['children'] ?? []);
|
$node['children'] = array_map(
|
||||||
|
static fn (array $child): array => self::finalizeDeptNode($child, $globalAccountCost, $globalAddFansCount),
|
||||||
|
$node['children'] ?? []
|
||||||
|
);
|
||||||
$paidAppointmentCount = (int)($node['paid_appointment_count'] ?? 0);
|
$paidAppointmentCount = (int)($node['paid_appointment_count'] ?? 0);
|
||||||
$freeAppointmentCount = (int)($node['free_appointment_count'] ?? 0);
|
$freeAppointmentCount = (int)($node['free_appointment_count'] ?? 0);
|
||||||
$appointmentTotalCount = (int)($node['appointment_total_count'] ?? 0);
|
$appointmentTotalCount = (int)($node['appointment_total_count'] ?? 0);
|
||||||
@@ -837,9 +925,7 @@ class ConversionLogic
|
|||||||
$completedOrderCount = (int)$node['completed_order_count'];
|
$completedOrderCount = (int)$node['completed_order_count'];
|
||||||
$businessOrderAmount = round((float)$node['business_order_amount'], 2);
|
$businessOrderAmount = round((float)$node['business_order_amount'], 2);
|
||||||
$completedOrderAmount = round((float)$node['completed_order_amount'], 2);
|
$completedOrderAmount = round((float)$node['completed_order_amount'], 2);
|
||||||
$accountCost = round((float)$node['account_cost'], 2);
|
$effectiveAccountCost = self::allocateAccountCost($globalAccountCost, $globalAddFansCount, $addFansCount);
|
||||||
$globalAccountCost = round((float)($node['_global_account_cost'] ?? 0), 2);
|
|
||||||
$effectiveAccountCost = $globalAccountCost > 0 ? $globalAccountCost : $accountCost;
|
|
||||||
|
|
||||||
$node['paid_appointment_count'] = $paidAppointmentCount;
|
$node['paid_appointment_count'] = $paidAppointmentCount;
|
||||||
$node['free_appointment_count'] = $freeAppointmentCount;
|
$node['free_appointment_count'] = $freeAppointmentCount;
|
||||||
@@ -868,7 +954,7 @@ class ConversionLogic
|
|||||||
* @param array<int, array<string, mixed>> $rows
|
* @param array<int, array<string, mixed>> $rows
|
||||||
* @return array<string, mixed>
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
private static function buildSummary(array $rows, float $forcedAccountCost = 0.0): array
|
private static function buildSummary(array $rows): array
|
||||||
{
|
{
|
||||||
$summary = [
|
$summary = [
|
||||||
'add_fans_count' => 0,
|
'add_fans_count' => 0,
|
||||||
@@ -883,8 +969,6 @@ class ConversionLogic
|
|||||||
'completed_order_count' => 0,
|
'completed_order_count' => 0,
|
||||||
'account_cost' => 0.0,
|
'account_cost' => 0.0,
|
||||||
];
|
];
|
||||||
$globalAccountCost = 0.0;
|
|
||||||
|
|
||||||
foreach ($rows as $row) {
|
foreach ($rows as $row) {
|
||||||
$summary['add_fans_count'] += (int)$row['add_fans_count'];
|
$summary['add_fans_count'] += (int)$row['add_fans_count'];
|
||||||
$summary['total_open_count'] += (int)$row['total_open_count'];
|
$summary['total_open_count'] += (int)$row['total_open_count'];
|
||||||
@@ -896,11 +980,9 @@ class ConversionLogic
|
|||||||
$summary['completed_order_count'] += (int)$row['completed_order_count'];
|
$summary['completed_order_count'] += (int)$row['completed_order_count'];
|
||||||
$summary['business_order_amount'] = round($summary['business_order_amount'] + (float)($row['business_order_amount'] ?? 0), 2);
|
$summary['business_order_amount'] = round($summary['business_order_amount'] + (float)($row['business_order_amount'] ?? 0), 2);
|
||||||
$summary['completed_order_amount'] = round($summary['completed_order_amount'] + (float)$row['completed_order_amount'], 2);
|
$summary['completed_order_amount'] = round($summary['completed_order_amount'] + (float)$row['completed_order_amount'], 2);
|
||||||
$globalAccountCost = max($globalAccountCost, round((float)($row['_global_account_cost'] ?? 0), 2));
|
$summary['account_cost'] = round($summary['account_cost'] + (float)($row['account_cost'] ?? 0), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
$summary['account_cost'] = max($globalAccountCost, round($forcedAccountCost, 2));
|
|
||||||
|
|
||||||
$summary['paid_appointment_rate'] = self::percent($summary['paid_appointment_count'], $summary['add_fans_count']);
|
$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['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']);
|
$summary['interview_rate'] = self::percent($summary['interview_count'], $summary['appointment_total_count']);
|
||||||
@@ -914,6 +996,54 @@ class ConversionLogic
|
|||||||
return $summary;
|
return $summary;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<int, array<string, mixed>> $entities
|
||||||
|
*/
|
||||||
|
private static function extractGlobalAccountCost(array $entities): float
|
||||||
|
{
|
||||||
|
$globalAccountCost = 0.0;
|
||||||
|
foreach ($entities as $entity) {
|
||||||
|
$globalAccountCost = max($globalAccountCost, round((float)($entity['_global_account_cost'] ?? 0), 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $globalAccountCost;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<int, array<string, mixed>> $entities
|
||||||
|
*/
|
||||||
|
private static function sumEntityAddFans(array $entities): int
|
||||||
|
{
|
||||||
|
$total = 0;
|
||||||
|
foreach ($entities as $entity) {
|
||||||
|
$total += (int)($entity['add_fans_count'] ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $total;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<int, array<string, mixed>> $nodes
|
||||||
|
*/
|
||||||
|
private static function sumNodeAddFans(array $nodes): int
|
||||||
|
{
|
||||||
|
$total = 0;
|
||||||
|
foreach ($nodes as $node) {
|
||||||
|
$total += (int)($node['add_fans_count'] ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $total;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function allocateAccountCost(float $globalAccountCost, int $globalAddFansCount, int $rowAddFansCount): float
|
||||||
|
{
|
||||||
|
if ($globalAccountCost <= 0 || $globalAddFansCount <= 0 || $rowAddFansCount <= 0) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return round(($globalAccountCost / $globalAddFansCount) * $rowAddFansCount, 2);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<string, mixed> $row
|
* @param array<string, mixed> $row
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ namespace app\common\service\qywx;
|
|||||||
|
|
||||||
use app\common\model\QywxExternalContact;
|
use app\common\model\QywxExternalContact;
|
||||||
use app\common\model\QywxMediaChannel;
|
use app\common\model\QywxMediaChannel;
|
||||||
use think\db\Query;
|
|
||||||
use think\facade\Db;
|
use think\facade\Db;
|
||||||
|
use think\db\Query;
|
||||||
|
|
||||||
class MediaChannelService
|
class MediaChannelService
|
||||||
{
|
{
|
||||||
@@ -75,6 +75,35 @@ class MediaChannelService
|
|||||||
return (string) ($channel['channel_name'] ?? '');
|
return (string) ($channel['channel_name'] ?? '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 挂号老数据渠道:doctor_appointment.channels 对应 dict_data(type_value=channels) 的 value。
|
||||||
|
*
|
||||||
|
* @param array<string, mixed>|null $channel
|
||||||
|
* @return int[]
|
||||||
|
*/
|
||||||
|
public static function getLegacyAppointmentChannelValues(?array $channel): array
|
||||||
|
{
|
||||||
|
if ($channel === null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$names = array_values(array_unique(array_filter([
|
||||||
|
trim((string) ($channel['channel_name'] ?? '')),
|
||||||
|
trim((string) ($channel['source_tag_name'] ?? '')),
|
||||||
|
])));
|
||||||
|
|
||||||
|
if ($names === []) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$rows = Db::name('dict_data')
|
||||||
|
->where('type_value', 'channels')
|
||||||
|
->whereIn('name', $names)
|
||||||
|
->column('value');
|
||||||
|
|
||||||
|
return array_values(array_filter(array_map('intval', $rows), static fn (int $value): bool => $value > 0));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<string, mixed>|null $channel
|
* @param array<string, mixed>|null $channel
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user