CRM——销售与客户
一、销售与客户——表结构
1、客户类型
(1)公共客户(公共资源)
必备条件:没有报名;
在必备条件满足的情况下,满足以下任意条件都是公共客户:
3天没有跟进;15天没有成单。
(2)我的客户
原销售——三江:
2018-5-18 12:00 龙泰 男 2018-5-18 正在跟进
2018-5-19 0:0 龙泰 男 2018-5-19 15天未成单
抢单销售——暴雨:
2018-5-19 12:00 龙泰 男 2018-5-19 正在跟进
2018-5-20 0:0 龙泰 男 2018-5-20 正在跟进
2018-5-21 0:0 龙泰 男 2018-5-21 正在跟进
2018-5-22 0:0 龙泰 男 2018-5-22 正在跟进
2018-5-23 0:0 龙泰 男 2018-5-23 3天未成单
抢单销售——大傻:
2018-5-23 0:0 龙泰 男 2018-5-23 正在跟进
2、添加客户分布表(CustomerDistrbute)
(1)为什么创建客户分布表,为什么不能直接用Customer?
因为:销售可以查看,自己的客户是否已过期,是否正在跟进,月底可以算业绩!不能说没谈成,就没有业绩!!
(2)更新models.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | class CustomerDistrbute(models.Model): """ customer:客户 consultant:跟进人 date:日期 status:状态 meno: """ customer = models.ForeignKey( "Customer" , related_name = "customers" , on_delete = models.CASCADE) consultant = models.ForeignKey(verbose_name = "课程顾问" , to = "UserInfo" , limit_choices_to = { "depart_id" : 1001 }, on_delete = models.CASCADE) date = models.DateField() status_choices = ( ( 1 , '正在跟进' ), ( 2 , '已报名' ), ( 3 , '三天未跟进' ), ( 4 , '15天未成单' ), ) status = models.IntegerField(choices = status_choices, default = 1 ) meno = models.CharField(max_length = 255 ) def __str__( self ): return self .customer.name + ":" + self .consultant.name |
注意:__str__函数返回客户名和顾问名
(3)再次执行数据库迁移加载新表到数据库中
1 2 | manage.py@CRM_demo > makemigrations manage.py@CRM_demo > migrate |
3、新的表结构
二、公共客户
继续配置Customer自定义配置类,扩展公共客户路由、视图、模板。
1、添加public客户url
1 2 3 4 5 6 7 | class CustomerConfig(ModelStark): def extra_url( self ): """扩展路由""" temp = [] temp.append(url((r "cancel_course/(\d+)/(\d+)" ), self .cancel_course)) temp.append(url(r "public" , self .public_customer)) return temp |
2、定制公共客户视图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | def public_customer( self , request): """公共客户""" # 未报名、且三天未跟进或15天未成单 from django.db.models import Q import datetime now = datetime.datetime.now() # datetime.datetime:表示日期时间 # datetime.timedelta:表示时间间隔,即两个时间点之间的长度 delta_day3 = datetime.timedelta(days = 3 ) delta_day15 = datetime.timedelta(days = 15 ) # 三天未跟进:now-last_consult_date>3 ===> last_consult_date < now - 3 # 15天未成单:now-recv_data > 15 ====> recv_data < now - 15 user_id = 2 # 课程顾问吴三江 customer_list = Customer.objects. filter (Q(last_consult_date__lt = now - delta_day3) | Q(recv_date__lt = now - delta_day15), status = 2 ).exclude(consultant = user_id) print (customer_list) # <QuerySet [<Customer: 小东北>, <Customer: 泰哥>]> return render(request, "public.html" , locals ()) |
注意:
(1)datetime模块的使用
datetime.datetime:表示的是日期时间。
datetime.timedelta:表示时间间隔,即两个时间点之间的长度
1 2 3 | now = datetime.datetime.now() # 当前日期 delta_day3 = datetime.timedelta(days = 3 ) # 时间间隔三天 delta_day15 = datetime.timedelta(days = 15 ) # 时间间隔15天 |
(2)根据公共客户条件筛选客户
1 2 3 4 5 | # 三天未跟进:now-last_consult_date>3 ===> last_consult_date < now - 3 # 15天未成单:now-recv_data > 15 ====> recv_data < now - 15 # Q查询 last_consult_date__lt recv_date__lt customer_list = Customer.objects. filter (Q(last_consult_date__lt = now - delta_day3) | Q(recv_date__lt = now - delta_day15), status = 2 ) print (customer_list) # <QuerySet [<Customer: 小东北>, <Customer: 泰哥>]> |
3、public.html基础设计渲染

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.css"> </head> <body> <h3>公共客户</h3> <div class="container"> <div class="row"> <div class="col-md-6"> <table class="table table-bordered table-striped"> <thead> <tr> <th>ID</th> <th>姓名</th> <th>QQ</th> <th>课程顾问</th> </tr> </thead> <tbody> {% for customer in customer_list %} <tr> <td>{{ forloop.counter }}</td> <td>{{ customer.name }}</td> <td>{{ customer.qq }}</td> <td>{{ customer.consultant }}</td> </tr> {% endfor %} </tbody> </table> </div> </div> </div> </body> </html>
显示效果如下所示:
4、exclude排除——公共客户原课程顾问不可见
filter按条件筛选,exclude按条件排除。
1 2 3 | user_id = 2 # 课程顾问吴三江 customer_list = Customer.objects. filter (Q(last_consult_date__lt = now - delta_day3) | Q(recv_date__lt = now - delta_day15), status = 2 ).exclude(consultant = user_id) print (customer_list) # <QuerySet [<Customer: 泰哥>]> |
显示效果:
5、在public.html渲染当前客户跟进记录

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.css"> </head> <body> <h3>公共客户</h3> <div class="container"> <div class="row"> <div class="col-md-6"> <table class="table table-bordered table-striped"> <thead> <tr> <th>ID</th> <th>姓名</th> <th>QQ</th> <th>课程顾问</th> <th>跟进详情</th> </tr> </thead> <tbody> {% for customer in customer_list %} <tr> <td>{{ forloop.counter }}</td> <td>{{ customer.name }}</td> <td>{{ customer.qq }}</td> <td>{{ customer.consultant }}</td> <td><a href="/stark/crm/consultrecord/?customer={{ customer.pk }}">跟进记录</a></td> </tr> {% endfor %} </tbody> </table> </div> </div> </div> </body> </html>
(1)渲染跟进记录的a标签时,添加href地址:
1 | < td >< a href="/stark/crm/consultrecord/?customer={{ customer.pk }}">跟进记录</ a ></ td > |
(2)显示效果:
点击泰哥的跟进记录,调整到客户记录页面,并过滤泰哥的记录:
三、确认跟进
1、增加确认跟进url和我的客户url
1 2 3 4 5 6 7 8 9 | class CustomerConfig(ModelStark): def extra_url( self ): """扩展路由""" temp = [] temp.append(url((r "cancel_course/(\d+)/(\d+)" ), self .cancel_course)) temp.append(url(r "public/" , self .public_customer)) temp.append(url(r "further/(\d+)" , self .further)) temp.append(url(r "mycustomer/" , self .mycustomer)) return temp |
2、更改课程顾问和对应的时间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | def further( self , request, customer_id): """跟进客户""" user_id = 2 # 这里是模拟登陆状态requet.session.get("user_id") import datetime from django.db.models import Q now = datetime.datetime.now() delta_day3 = datetime.timedelta(days = 3 ) delta_day15 = datetime.timedelta(days = 15 ) # 为符合条件客户更改课程顾问,避免多人同时或连续跟进 ret = Customer.objects. filter (pk = customer_id). filter (Q(last_consult_date__lt = now - delta_day3) | Q(recv_date__lt = now - delta_day15), status = 2 ).update(consultant = user_id, last_consult_date = now, recv_date = now) if not ret: # 没有更新 return HttpResponse( "已经被跟进了" ) # 添加一条记录 状态均是正在跟进状态 CustomerDistrbute.objects.create(customer_id = customer_id, consultant_id = user_id, date = now, status = 1 ) return HttpResponse( "跟进成功!" ) |
注意:
(1)更改课程需要提前过滤
先进行过滤是为了防止多个用户同时抢单,最后给了最后一个抢单的人,先过滤再抢单,后抢单的人已经找不到之前看到的客户,提示已经被跟进了。
(2)创建一条客户分布记录
1 2 3 4 5 6 | CustomerDistrbute.objects.create( customer_id = customer_id, consultant_id = user_id, date = now, status = 1 ) |
(3)ret是update的返回值
update返回值返回的是更新的数据条数。
3、页面布局

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.css"> </head> <body> <h3>公共客户</h3> <div class="container"> <div class="row"> <div class="col-md-6"> <table class="table table-bordered table-striped"> <thead> <tr> <th>ID</th> <th>姓名</th> <th>QQ</th> <th>课程顾问</th> <th>跟进详情</th> <th>确认跟进</th> </tr> </thead> <tbody> {% for customer in customer_list %} <tr> <td>{{ forloop.counter }}</td> <td>{{ customer.name }}</td> <td>{{ customer.qq }}</td> <td>{{ customer.consultant }}</td> <td><a href="/stark/crm/consultrecord/?customer={{ customer.pk }}">跟进记录</a></td> <td><a href="/stark/crm/customer/further/{{ customer.pk }}">确认跟进</a></td> </tr> {% endfor %} </tbody> </table> </div> </div> </div> </body> </html>

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h3>我的客户</h3> <ul> {% for customer_distribute in customer_distribute_list %} <li> {# 一个客户有多种分布状态 #} {{ customer_distribute.customer }}--{{ customer_distribute.date }}--{{ customer_distribute.get_status_display }} </li> {% endfor %} </ul> </body> </html>
4、测试验证
两个浏览器同时访问:
先跟进的浏览器:
后跟进的浏览器:
四、我的客户
1、新增url
1 | temp.append(url(r "mycustomer/" , self .mycustomer)) |
2、我的客户视图
1 2 3 4 5 6 7 | class CustomerConfig(ModelStark): def mycustomer( self , request): """我的客户""" user_id = 2 customer_distribute_list = CustomerDistrbute.objects. filter (consultant = user_id) return render(request, "mycustomer.html" , locals ()) |
注意:
(1)在客户分布表查询
不能在Customer表查询,这查询到的只是正在跟踪客户的信息。之前的跟踪过得信息不会显示。需要在客户分布表查询。
(2)模拟登陆销售
在mycustomer视图函数下配置user_id,值等于哪个顾问的pk值就是模拟的谁登陆。
3、模板和展示效果
mycustomer.html见跟进记录那一章。
展示效果如下所示:
五、定时任务监控放入公共客户
每天晚上12点检测CustomerDistribute哪个客户过期,一旦过期就调整为公共客户。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术