Merge branch 'feature/tag' into feature/en

This commit is contained in:
2025-03-27 14:06:25 +08:00
14 changed files with 465 additions and 41 deletions
+181 -35
View File
@@ -5,6 +5,7 @@ namespace app\controller;
use app\model\Channel;
use app\model\Customer;
use app\model\Follow;
use app\service\UserService;
use app\util\Util;
use config\constants\Constants;
use support\Log;
@@ -12,6 +13,13 @@ use support\Request;
class CustomerController
{
/**
* 不需要登录的方法
*/
protected $noNeedLogin = ['collect'];
/**
* 客户分级
* @param Request $request
@@ -73,6 +81,41 @@ class CustomerController
}
/**
* 待跟进客户列表
* @param Request $request
* @return \support\Response
*/
public function waitFollow(Request $request)
{
$userId = $request->user['id'];
$filter = Util::getParam($request, 'filter');
$page = $request->input('page', 1);
$perPage = $request->input('per_page', 10);
$where = [
['user_id', '=', $userId],
['follow_next', '<>', 0],
];
if($filter == 0){
$where[] = ['follow_next', '<=', time() + 3600 * 24];
}elseif($filter == 1){
$where[] = ['follow_next', '<=', time() + 3600 * 24*3];
}elseif($filter == 2){
$where[] = ['follow_next', '<=', time() + 3600 * 24*7];
}
$customer = Customer::where($where)
->whereNotNull('follow_next')
->orderBy('follow_next')
->paginate($perPage, ['*'], 'page', $page);
foreach ($customer as &$item) {
$item['follow_remaining'] = $item->follow_next-time();
$item['follow_next'] = date('Y-m-d H:i:s',$item->follow_next);
}
return Util::page($customer);
}
/**
* 客户列表
* @param Request $request
@@ -83,9 +126,9 @@ class CustomerController
$page = $request->input('page', 1);
$perPage = $request->input('per_page', 10);
$follow = $request->input('follow',''); //跟进状态
$keyword = $request->input('keyword',''); //follow_last
$follow_last = $request->input('follow_last',''); //最后跟进:1:当天跟进客户,2:近三天跟进客户,3:近七天跟进客户
$follow = $request->input('follow', ''); //跟进状态
$keyword = $request->input('keyword', ''); //follow_last
$follow_last = $request->input('follow_last', ''); //最后跟进:1:当天跟进客户,2:近三天跟进客户,3:近七天跟进客户
$where = [];
@@ -93,31 +136,30 @@ class CustomerController
if (Util::isEmpty($userId)) {
return Util::fail(['user_id' => $userId], trans('user_none'));
}
$where[] =['user_id',$userId] ;
$where[] = ['user_id', $userId];
//验证权限
// if ($request->user['role'] != 1) {
// $where[] =['user_id',$userId] ;
// }
$grade = [];
if (!Util::isEmpty($follow) && $follow == 0) {
$where[] = ['grade','<=', 2];
$where[] = ['grade', '<=', 2];
} elseif (!Util::isEmpty($follow) && $follow == 1) {
$grade = [3, 4];
$grade = [3, 4];
} elseif (!Util::isEmpty($follow) && $follow == 2) {
$where[] = ['grade','=', 5];
$where[] = ['grade', '=', 5];
}
if (!Util::isEmpty($follow_last) && $follow_last == 0) {
$where[] = ['follow_last','>', strtotime('-1 day')];
}elseif (!Util::isEmpty($follow_last) && $follow_last == 1) {
$where[] = ['follow_last','>', strtotime('-3 day')];
}elseif (!Util::isEmpty($follow_last) && $follow_last == 2) {
$where[] = ['follow_last','>', strtotime('-7 day')];
$where[] = ['follow_last', '>', strtotime('-1 day')];
} elseif (!Util::isEmpty($follow_last) && $follow_last == 1) {
$where[] = ['follow_last', '>', strtotime('-3 day')];
} elseif (!Util::isEmpty($follow_last) && $follow_last == 2) {
$where[] = ['follow_last', '>', strtotime('-7 day')];
}
// 执行分页查询
// 执行分页查询 //关联标签tags //关联标签及分组tags.group
$query = Customer::where($where)
->orderBy('id', 'desc');
if (!Util::isEmpty($keyword)) {
@@ -131,16 +173,46 @@ class CustomerController
$query->whereIn('grade', $grade);
}
// return Util::success($query->toSql().serialize($where),$follow);
$recycle = Util::getParam($request,'recycle', '');
if ($recycle == 'recycle') {
$query->onlyTrashed();
}
$data = $query->paginate($perPage, ['*'], 'page', $page);
foreach ($data->items() as &$value) {
$value = Customer::getCustomer($value);
$value = Customer::getCustomer($value, $request);
}
return Util::page($data);
}
/**
* 客户恢复
* @param Request $request
* @return \support\Response
*/
public function restore(Request $request)
{
$id = Util::getParam($request,'id', '');
if (Util::isEmpty($id)) {
return Util::fail([], trans('id_empty'));
}
$userId = $request->user['id'];
$where = [
'id' => $id,
'user_id' => $userId,
];
$res = Customer::where($where)->restore();
if($res){
return Util::success($res, trans('success'));
}else{
return Util::fail([], trans('fail'));
}
}
/**
* 客户详情
* @param Request $request
@@ -153,7 +225,7 @@ class CustomerController
return Util::fail([], 'id' . trans('not_null'));
}
$res = Customer::select('id', 'user_id', 'grade', 'channel_id', 'car_type_id', 'country_id', 'magnitude', 'email', 'phone', 'WhatsApp', 'contacts', 'describe', 'address', 'lat', 'lng', 'follow_next', 'follow_remark', 'sex')
$res = Customer::with('tags')->select('id', 'user_id', 'grade', 'channel_id', 'car_type_id', 'country_id', 'magnitude', 'email', 'phone', 'WhatsApp', 'contacts', 'describe', 'address', 'lat', 'lng', 'follow_next', 'follow_remark', 'sex')
->where('id', $id)
->first();
if ($res) {
@@ -162,7 +234,7 @@ class CustomerController
return Util::fail([], '记录不存在');
}
$res = Customer::getCustomer($res);
$res = Customer::getCustomer($res, $request, true);
return Util::success($res);
}
@@ -191,7 +263,7 @@ class CustomerController
$email = Util::getParam($request, 'email');
$phone = Util::getParam($request, 'phone');
if (Util::isEmpty($contacts) && Util::isEmpty($email) && Util::isEmpty($phone)) {
return Util::fail([], trans('contacts_not_null'));
return Util::fail([], trans('contacts_not_null'));
}
$data = [
@@ -271,10 +343,11 @@ class CustomerController
$data = [
'follow_next' => Util::getParam($request, 'follow_next', 0),
'follow_remark' => Util::getParam($request, 'follow_remark', 0),
'warn' => 0,
];
$res = Customer::where('id', $id)
->update($data);
->update($data);
if ($res) {
return Util::success($res, trans('save_success'));
@@ -291,8 +364,22 @@ class CustomerController
public function delete(Request $request)
{
$id = Util::getParam($request, 'id');
$customer = Customer::where('id', $id)->first();
$res = $customer->delete();
$force = Util::getParam($request, 'force','');
$userId = $request->user['id'];
$where = [
'id' => $id,
'user_id' => $userId,
];
$customer = Customer::where($where)->withTrashed()->first();
if(empty($customer)){
return Util::fail('', trans('fail'));
}
if($force == 'force'){
$res = $customer->forceDelete();
}else{
$res = $customer->delete();
}
if ($res) {
return Util::success($res, trans('success'));
} else {
@@ -313,19 +400,19 @@ class CustomerController
}
$userId = $request->user['id'];
$created_at = $request->user['created_at'];
$day = ceil((time()-$created_at+1)/60/60/24);
$day = ceil((time() - $created_at + 1) / 60 / 60 / 24);
$where[] = ['user_id', '=', $userId];
$start = strtotime($created.'-01');
$start = strtotime($created . '-01');
$year = date('Y', $start);
$month = date('n',$start); //获取月份
$end = mktime(00, 00, 00, $month+1, 1, $year);
$month = date('n', $start); //获取月份
$end = mktime(00, 00, 00, $month + 1, 1, $year);
$where[] = ['created_at', '>=', $start];
$where[] = ['created_at', '<', $end];
$customer = Customer::where($where)->count();
//初步接触
$whereGrade = $where;
$whereGrade[] = ['grade','<=', 2];
$whereGrade[] = ['grade', '<=', 2];
$customerInit = Customer::where($whereGrade)->count();
//跟进中
@@ -333,7 +420,7 @@ class CustomerController
//已成交
$whereGrade = $where;
$whereGrade[] = ['grade','=', 5];
$whereGrade[] = ['grade', '=', 5];
$customerDeal = Customer::where($whereGrade)->count();
//拜访数
@@ -347,13 +434,13 @@ class CustomerController
$follow = Follow::where($whereVisit)->distinct('customer_id')->count();
$res = [
'customer'=>$customer, //客户数
'visit'=>$visit, //拜访
'follow'=>$follow, //跟进
'day'=>$day, //注册天数
'customer_init'=>$customerInit, //初步接触
'customero_fllow'=>$customeroFllow, //跟进中
'customer_deal'=>$customerDeal, //已成交
'customer' => $customer, //客户数
'visit' => $visit, //拜访
'follow' => $follow, //跟进
'day' => $day, //注册天数
'customer_init' => $customerInit, //初步接触
'customero_fllow' => $customeroFllow, //跟进中
'customer_deal' => $customerDeal, //已成交
];
if ($res) {
return Util::success($res, trans('success'));
@@ -362,4 +449,63 @@ class CustomerController
}
}
/**
* 线索分配
* @param Request $request
* @return \support\Response
*/
public function collect(Request $request)
{
Log::info('send follow start',$request->all());
$user_sn = Util::getParam($request, 'user_sn'); //用户sn
$user_email = Util::getParam($request, 'user_email'); //邮箱
$sn = Util::getParam($request, 'order_no'); //客户sn验证客户唯一性
$channel_id = Util::getParam($request, 'channel_id'); //渠道
$country_id = Util::getParam($request, 'country_id'); //国家
$magnitude = Util::getParam($request, 'magnitude'); //商机
$describe = Util::getParam($request, 'describe'); //客户详情
$phone = Util::getParam($request, 'phone'); //用户详情
$email = Util::getParam($request, 'email'); //用户详情
$WhatsApp = Util::getParam($request, 'WhatsApp');
$contacts = Util::getParam($request, 'contacts'); //联系人
if (Util::isEmpty($user_sn)) {
return Util::fail([], '用户sn不能为空');
}
if (Util::isEmpty($user_email)) {
return Util::fail([], '用户邮箱不能为空');
}
if (Util::isEmpty($sn)) {
return Util::fail([], '客户sn不能为空');
}
$user = UserService::updateSn($user_sn, $user_email);
if(!$user){
return Util::fail($user, $user_email.'用户不存在');
}
$res = Customer::firstOrCreate(
['sn' => $sn],
[
'user_id' => $user->id,
'channel_id' => $channel_id,
'country_id' => $country_id,
'magnitude' => $magnitude,
'describe' => $describe,
'phone' => $phone,
'email' => $email,
'WhatsApp' => $WhatsApp,
'contacts' => $contacts,
]
);
if ($res) {
return Util::success($res, trans('success'));
} else {
return Util::fail($res, trans('fail'));
}
}
}
+2
View File
@@ -146,6 +146,8 @@ class FollowController
$customer = Customer::find($customer_id);
$customer->follow_last = strtotime($res->created_at);
$customer->follow_next = null;
$customer->warn = 0;
$customer->save();
if ($res) {
+65
View File
@@ -0,0 +1,65 @@
<?php
namespace app\controller;
use app\model\CustomerTag;
use app\model\TagGroup;
use app\util\Util;
use support\Request;
class TagGroupController
{
// 列表
public function list(Request $request)
{
$list = TagGroup::with('tags')->get();
$languageArr = config('translation.fallback_locale');
$language = $request->user['language'];
foreach ($list as &$item) {
$item['names'] = unserialize($item['names']);
if (in_array($language, $languageArr)) {
$item['name'] = $item['names'][$language];
}
foreach ($item['tags'] as &$tag) {
$tag['names'] = unserialize($tag['names']);
if (in_array($language, $languageArr)) {
$item['name'] = $item['names'][$language];
}
}
}
return Util::success($list);
}
/**
* 更新客户标签关系
* @param Request $request
* @return \support\Response
*/
public function updatePivot(Request $request)
{
$customerId = Util::getParam($request, 'customer_id');
$groupId = Util::getParam($request, 'group_id');
$tagId = Util::getParam($request, 'tag_id');
if (Util::isEmpty($customerId)) {
return Util::fail([], trans('customer_id') . trans('not_null'));
}
if (Util::isEmpty($groupId)) {
return Util::fail([], trans('tag_group_id') . trans('not_null'));
}
if(empty($tagId)){
$res = CustomerTag::where(['customer_id' => $customerId, 'group_id' => $groupId])->delete();
}else{
$res = CustomerTag::updateOrCreate(['customer_id' => $customerId, 'group_id' => $groupId], ['tag_id' => $tagId]);
}
if($res){
return Util::success($res, trans('success'));
}else{
return Util::fail($res, trans('fail'));
}
}
}
+1 -1
View File
@@ -115,7 +115,7 @@ class UploadController
}
$result = Storage::adapter('oss')
->path('vehicle_album/'.date('Ymd'))
->path('lingke_crm/'.date('Ymd'))
->size(1024*1024*5)
->extYes(['image/jpeg','image/png'])
->extNo(['image/gif'])
+1 -1
View File
@@ -154,7 +154,7 @@ class UserController
'role' => 1,
'password' => $hashedPassword,
'status' => 0,
'avatar' => 'lingke_crm/lingke.png'
'avatar' => 'lingke_crm/logo.png'
];
+9 -2
View File
@@ -54,11 +54,15 @@ class AuthMiddleware implements MiddlewareInterface
// 从 Header 获取 Token
$token = $request->header('Authorization');
$language = Util::getParam($request, 'language');
$language = $language=='zh_CN'?:'en';
//需要登录并且未提供token
if ($isLogin && (!$token || !str_starts_with($token, 'Bearer '))) {
return Util::fail('','请先登录',401);
locale($language);
return Util::fail('',trans('user_login'),401);
// throw new BusinessException('未提供 Token'.$isLogin, 401);
}elseif(!$isLogin){
locale($language);
return $handler($request);
}
@@ -68,8 +72,11 @@ class AuthMiddleware implements MiddlewareInterface
// 验证 Token 并获取用户ID
$decoded = JwtUtil::verifyToken($token);
$userId = $decoded->sub;
if (!isset($decoded->sub)) {
return Util::fail('', trans('user_login'),401);
}
$userId = $decoded->sub;
$user = User::select(['id','name','sex','role','avatar','email','password','language','created_at'])
->find($userId);
if(!$user){
+24 -1
View File
@@ -59,11 +59,18 @@ class Customer extends Model
return $date->format(Carbon::parse($date)->toDateTimeString());
}
public static function getCustomer($customer)
/**
* @param $customer
* @param $request
* @param $tag 是否返回客户标签
* @return mixed
*/
public static function getCustomer($customer, $request, $tag = false)
{
$customer['grade_txt'] = Constants::GRADE[$customer['grade']];
$customer['magnitude_txt'] = Constants::MAGNITUDE[$customer['magnitude']];
$customer['sex_txt'] = Constants::SEX[$customer['sex']];
$language = $request->user['language'] ?: 'en';
if ($customer['grade']<=2) {
$customer['follow'] = 0;
@@ -94,6 +101,22 @@ class Customer extends Model
$customer['channel_name'] = empty($customer['channel_id'])?'':Channel::where(['id'=>$customer['channel_id']])->value('name');
// $customer['last_follow'] = $last_follow;
if ($tag) {
foreach ($customer['tags'] as &$tag) {
$tag['names'] = unserialize($tag['names']);
$tag['name'] = $tag['names'][$language];
}
}
return $customer;
}
/**
* 客户的标签
*/
public function tags()
{
return $this->belongsToMany(Tag::class, 'customer_tag', 'customer_id', 'tag_id')
->withTimestamps(); // 如果需要记录关联时间
}
}
+36
View File
@@ -0,0 +1,36 @@
<?php
namespace app\model;
use app\util\Util;
use support\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class CustomerTag extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'customer_tag';
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'id';
//设置黑名单
protected $guarded = ['id', 'created_at'];
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = false;
}
+55
View File
@@ -0,0 +1,55 @@
<?php
namespace app\model;
use app\util\Util;
use config\constants\Constants;
use DateTimeInterface;
use Illuminate\Support\Carbon;
use support\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Tag extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'tag';
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'id';
//设置黑名单
protected $guarded = ['id','created_at','updated_at'];
// 指定日期字段存储格式为时间戳
protected $dateFormat = 'U';
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = true;
use SoftDeletes;
protected $dates = ['deleted_at'];
// 定义反向多对一关联
public function group()
{
return $this->belongsTo(TagGroup::class, 'group_id', 'id');
}
public function customers()
{
return $this->belongsToMany(Customer::class, 'customer_tag', 'tag_id', 'customer_id');
}
}
+50
View File
@@ -0,0 +1,50 @@
<?php
namespace app\model;
use app\util\Util;
use config\constants\Constants;
use DateTimeInterface;
use Illuminate\Support\Carbon;
use support\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class TagGroup extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'tag_group';
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'id';
//设置黑名单
protected $guarded = ['id','created_at','updated_at'];
// 指定日期字段存储格式为时间戳
protected $dateFormat = 'U';
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = true;
use SoftDeletes;
protected $dates = ['deleted_at'];
// 定义与 Tag 的一对多关系
public function tags()
{
return $this->hasMany(Tag::class, 'group_id', 'id');
}
}
+32
View File
@@ -0,0 +1,32 @@
<?php
namespace app\service;
use app\model\User;
use app\model\UserSn;
class UserService
{
/**
* 更新用户sn
* @param $sn
* @param $email
*/
public static function updateSn($sn, $email)
{
$user = User::first()
->where('email',$email)
->first();
return $user;
if(!$user){
return false;
}
if($user->sn != $sn){
UserSn::where(['sn' => $user->sn])->update(['user_id' => 0]);
UserSn::where(['sn' => $sn])->update(['user_id' => $user->id]);
$user->sn = $sn;
$user->save();
}
return $user;
}
}
+1 -1
View File
@@ -22,7 +22,7 @@ return [
'webman' => [
'handler' => Http::class,
'listen' => getenv('WEBMAN_LISTEN'),
'count' => cpu_count() * 1,
'count' => 8,
'user' => '',
'group' => '',
'reusePort' => false,
+4
View File
@@ -6,6 +6,7 @@ return [
'login_success' => ' login success',
'password_error' => ' password error',
'user_none' => ' user none',
'user_login' => ' Please log in first',
'not_null' => ' not null',
'code_error' => ' code error',
'format_error' => ' format error',
@@ -26,4 +27,7 @@ return [
'email_consistent' => ' Inconsistent email addresses',
'sign_not_null' => ' No affiliated company has been set',
'password_short' => ' Password cannot be less than 6 characters',
'id_empty' => ' ID cannot be empty',
'customer_id' => ' customer ID',
'tag_group_id' => ' tag group ID',
];
+4
View File
@@ -6,6 +6,7 @@ return [
'login_success' => ' 登录成功',
'password_error' => ' 密码错误',
'user_none' => ' 用户不存在',
'user_login' => ' 请先登录',
'not_null' => ' 不能为空',
'code_error' => ' 验证码错误',
'format_error' => ' 格式错误',
@@ -26,4 +27,7 @@ return [
'email_consistent' => ' 邮箱不一致',
'sign_not_null' => ' 未设置所属公司',
'password_short' => ' 密码不能少于6位',
'id_empty' => ' id不能为空',
'customer_id' => ' 客户ID',
'tag_group_id' => ' 标签组ID',
];