更新
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -957,8 +957,13 @@ class ConversionLogic
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggregate add-contact events by WeCom user once, then project that raw
|
||||
* snapshot to departments, members and virtual buckets in PHP.
|
||||
* 区间有效加粉:按企微员工聚合后,再投影到部门/成员/虚拟桶。
|
||||
*
|
||||
* 口径(对齐企微客户列表,而非原始回调条数):
|
||||
* - 统计区间内 add_external_contact,按 (user_id, external_userid) 去重;
|
||||
* - 加粉之后、统计结束前须有 msg_audit_approved(排除未完成链路的幽灵事件);
|
||||
* - 加粉之后、统计结束前若出现 del_external_contact(客户从企微侧删除)则不计。
|
||||
* 不扣减 del_follow_user:企微客户列表中改名带「删」的客户往往仍在列表中。
|
||||
*
|
||||
* @param array<string, mixed>|null $mediaChannel
|
||||
* @param int[]|null $adminIds null means all active/unbound WeCom users
|
||||
@@ -981,7 +986,7 @@ class ConversionLogic
|
||||
}
|
||||
}
|
||||
|
||||
$baseKey = self::requestRowsCacheKey('fans', [
|
||||
$baseKey = self::requestRowsCacheKey('fans-effective-v2', [
|
||||
$startTimestamp,
|
||||
$endTimestamp,
|
||||
self::mediaChannelCacheKey($mediaChannel),
|
||||
@@ -1019,11 +1024,30 @@ class ConversionLogic
|
||||
}
|
||||
}
|
||||
|
||||
$eventTable = config('database.connections.mysql.prefix') . 'qywx_external_contact_event';
|
||||
$query = Db::name('qywx_external_contact_event')
|
||||
->alias('e')
|
||||
->where('e.change_type', 'add_external_contact')
|
||||
->where('e.event_time', 'between', [$startTimestamp, $endTimestamp])
|
||||
->fieldRaw('e.user_id, COUNT(*) AS add_fans_count')
|
||||
->whereRaw(
|
||||
'EXISTS (SELECT 1 FROM `' . $eventTable . '` audit_e'
|
||||
. ' WHERE audit_e.user_id = e.user_id'
|
||||
. ' AND audit_e.external_userid = e.external_userid'
|
||||
. ' AND audit_e.change_type = ?'
|
||||
. ' AND audit_e.event_time >= e.event_time'
|
||||
. ' AND audit_e.event_time <= ?)',
|
||||
['msg_audit_approved', $endTimestamp]
|
||||
)
|
||||
->whereRaw(
|
||||
'NOT EXISTS (SELECT 1 FROM `' . $eventTable . '` del_e'
|
||||
. ' WHERE del_e.user_id = e.user_id'
|
||||
. ' AND del_e.external_userid = e.external_userid'
|
||||
. ' AND del_e.change_type = ?'
|
||||
. ' AND del_e.event_time >= e.event_time'
|
||||
. ' AND del_e.event_time <= ?)',
|
||||
['del_external_contact', $endTimestamp]
|
||||
)
|
||||
->fieldRaw('e.user_id, COUNT(DISTINCT e.external_userid) AS add_fans_count')
|
||||
->group('e.user_id');
|
||||
if ($workWechatUserIds !== null) {
|
||||
$query->whereIn('e.user_id', $workWechatUserIds);
|
||||
@@ -1604,6 +1628,8 @@ class ConversionLogic
|
||||
$entity['interview_rate'] = self::percent($interviewCount, $appointmentTotalCount);
|
||||
$entity['receive_rate'] = self::receiveRate($completedOrderCount, $addFansCount, $interviewCount, $dimension);
|
||||
$entity['interview_receive_rate'] = self::percent($completedOrderCount, $interviewCount);
|
||||
// 面诊接诊率:面诊 / 挂号
|
||||
$entity['interview_paid_rate'] = self::percent($interviewCount, $paidAppointmentCount);
|
||||
$entity['open_receive_rate'] = self::percent($completedOrderCount, $totalOpenCount);
|
||||
$entity['avg_unit_price'] = self::safeDivideMoney($completedOrderAmount, $completedOrderCount);
|
||||
$entity['cash_cost'] = self::safeDivideMoney($effectiveAccountCost, $addFansCount);
|
||||
@@ -1837,6 +1863,7 @@ class ConversionLogic
|
||||
$node['interview_rate'] = self::percent($interviewCount, $appointmentTotalCount);
|
||||
$node['receive_rate'] = self::receiveRate($completedOrderCount, $addFansCount, $interviewCount, 'dept');
|
||||
$node['interview_receive_rate'] = self::percent($completedOrderCount, $interviewCount);
|
||||
$node['interview_paid_rate'] = self::percent($interviewCount, $paidAppointmentCount);
|
||||
$node['open_receive_rate'] = self::percent($completedOrderCount, $totalOpenCount);
|
||||
$node['avg_unit_price'] = self::safeDivideMoney($completedOrderAmount, $completedOrderCount);
|
||||
$node['cash_cost'] = self::safeDivideMoney($effectiveAccountCost, $addFansCount);
|
||||
@@ -2258,6 +2285,7 @@ class ConversionLogic
|
||||
'interview_rate' => 0.0,
|
||||
'receive_rate' => 0.0,
|
||||
'interview_receive_rate' => 0.0,
|
||||
'interview_paid_rate' => 0.0,
|
||||
'open_receive_rate' => 0.0,
|
||||
'avg_unit_price' => 0.0,
|
||||
'cash_cost' => 0.0,
|
||||
@@ -2324,6 +2352,7 @@ class ConversionLogic
|
||||
'member'
|
||||
),
|
||||
'interview_receive_rate' => self::percent($completedOrderCount, $interviewCount),
|
||||
'interview_paid_rate' => self::percent($interviewCount, $paidAppointmentCount),
|
||||
'open_receive_rate' => self::percent($completedOrderCount, $totalOpenCount),
|
||||
'avg_unit_price' => self::safeDivideMoney($completedOrderAmount, $completedOrderCount),
|
||||
'cash_cost' => self::safeDivideMoney($effectiveAccountCost, $addFansCount),
|
||||
@@ -2500,6 +2529,7 @@ class ConversionLogic
|
||||
$dimension
|
||||
);
|
||||
$summary['interview_receive_rate'] = self::percent($summary['completed_order_count'], $summary['interview_count']);
|
||||
$summary['interview_paid_rate'] = self::percent($summary['interview_count'], $summary['paid_appointment_count']);
|
||||
$summary['open_receive_rate'] = self::percent($summary['completed_order_count'], $summary['total_open_count']);
|
||||
$summary['avg_unit_price'] = self::safeDivideMoney($summary['completed_order_amount'], $summary['completed_order_count']);
|
||||
$summary['cash_cost'] = self::safeDivideMoney($summary['account_cost'], $summary['add_fans_count']);
|
||||
|
||||
Reference in New Issue
Block a user