教育行业CRM项目开发
项目开发流程
需求分析
存储所有的客户咨询信息
    避免重复数据
    客户多次跟踪记录
    客户来源分析、成单率分析
    每个销售只能修改自己的客户信息
    报名流程开发
    
    班级管理
    学员成绩,出勤管理
    问卷调查
校区管理
课程管理
        课程大纲、周期、价格、代课老师
讲师的上课记录
学员就业情况
知识库
权限管理(学员、讲师、老板....查看权限不同)
    角色
            销售
            销售主管
            讲师
学员
管理员
思维导图
根据思维导图,我们总结出以下具体需求:
- 把用户分为销售\学员\讲师\管理员4种角色,每个角色关心的事情不同,所以有要不同的用户视图,即销售人员登陆后只需要看他与他工作相关的就可以,讲师关心的事情不需要在销售视图里显示
- 销售人员可以录入客户信息,和后续跟进信息,因为一个客户可能要跟进好几次才会报名。 可以多种条件过滤查询用户,可以查看自己的销售业绩报表
- 学员主要2个功能,查询成绩\提交作业
- 讲师的主要功能是管理班级\ 批作业\上课点名
- 这个系统里存放着大量的客户信息\学员数据等,都是公司的机密信息,设计系统时就要考虑到权限问题,不同的角色要有不同的权限,同一角色的不同用户也要允许有不同的权限
业务场景分析(用户使用的场景)
为了更加理清我们的项目开发需求,在动手写代码前,建议再有一个业务场景分析的步骤,即从用户角度写出你使用这个项目的具体场景,我们这里分为销售、讲师、学员、管理员4个角色,我分别给每个角色列出了几个使用场景:
销售
    1、销售人员A刚从 百度推广 聊了一个客户,录入crm系统,咨询了python全栈开发课程,但没报名
    2、销售B 从 QQ群聊了客户,且报名了py全栈5期课程,给用户发送了报名连接,待用户填写完毕后,把他添加到python fullstack
    3、销售C 打电话给之前的一个客户,说服他报名linux 36期课程,但没说服成功,更新了跟踪记录
    4、销售d 聊了一个客户,录入时发现 此客户已存在,不能录入,随后通知相应负责人 跟进
    5、销售B 从客户库里过滤出了 所有 超过一个月未跟踪的客户,进行跟踪
    6、销售主管 查看了部门 本月的销售报表,包括来源分析,班级报名数量分析,销售额环比,同比
学员
    1、客户A填写了销售发来的 报名连接,上传了个人证件信息,提交,过了一下会,发现收到了一个邮件,告知他报名python 5期课程成功,并帮他开通了学员账号,
    2、学员A登录了学员系统,看到了自己的合同,报名班级,课程大纲
    3、学员A 提交了PY 5期的 第三节课的作业
    4、学员A查看了自己在py5期的 学习成绩,排名
    5、学员A录入了一条 转介绍信息
    6、学员A 在线 搜索一个问题,发现没有答案,于是提交了一个问题。
讲师
    1、登录了CRM系统,查看了自己管理的班级列表
    2、进入了python 5期,创建了第3节的上课记录,填入了本节内容,作业需求
    3、为python 5 的第三节课 进行点名,发现小东北迟到了,标记他为迟到状态
    4、批量下载了所有学员的py 5期第二节的作业,给每个人在线 批改了成绩 + 批注 
    
管理员
    1、创建了,课程Linux,python,
    2、创建了校区 北京,上海
    3、创建了班级python  fullstack 35 和 Linux 36
    4、创建了账号A、B、C、D
    5、创建了销售,讲师,学员三个角色,并把ABCD分配到销售角色里
    6、设置了销售可以操作的权限
原型图
  类似示例(非本项目真实实例)
开发工具选型
    python
    Django
    mysql
    jquery
    bootstrap
    linux
    nginx
    pycharm
创建项目
    设计表结构
    写代码
设计表结构
表结构示意图
表结构代码
from django.db import models
from django.contrib.auth.models import User # Create your models here. class UserProfile(models.Model):
"""用户信息表"""
user = models.OneToOneField(User)
name = models.CharField(max_length=64,verbose_name="姓名")
role = models.ManyToManyField("Role",blank=True,null=True) def __str__(self): #__unicode__
return self.name class Role(models.Model):
"""角色表"""
name = models.CharField(max_length=64,unique=True)
menus = models.ManyToManyField("Menus",blank=True)
def __str__(self):
return self.name class CustomerInfo(models.Model):
"""客户信息表"""
name = models.CharField(max_length=64,default=None)
contact_type_choices = ((0,'qq'),(1,'微信'),(2,'手机'))
contact_type = models.SmallIntegerField(choices=contact_type_choices,default=0)
contact = models.CharField(max_length=64,unique=True)
source_choices = ((0,'QQ群'),
(1,'51CTO'),
(2,'百度推广'),
(3,'知乎'),
(4,'转介绍'),
(5,'其它'),
)
source = models.SmallIntegerField(choices=source_choices)
referral_from = models.ForeignKey("self",blank=True,null=True,verbose_name="转介绍")
consult_courses = models.ManyToManyField("Course",verbose_name="咨询课程")
consult_content = models.TextField(verbose_name="咨询内容")
status_choices = ((0,'未报名'),(1,'已报名'),(2,'已退学'))
status = models.SmallIntegerField(choices=status_choices)
consultant = models.ForeignKey("UserProfile",verbose_name="课程顾问")
date = models.DateField(auto_now_add=True) def __str__(self):
return self.name class Student(models.Model):
"""学员表"""
customer = models.ForeignKey("CustomerInfo")
class_grades = models.ManyToManyField("ClassList") def __str__(self):
return self.customer class CustomerFollowUp(models.Model):
"""客户跟踪记录表"""
customer = models.ForeignKey("CustomerInfo")
content = models.TextField(verbose_name="跟踪内容")
user = models.ForeignKey("UserProfile",verbose_name="跟进人")
status_choices = ((0,'近期无报名计划'),
(1,'一个月内报名'),
(2,'2周内内报名'),
(3,'已报名'),
)
status = models.SmallIntegerField(choices=status_choices)
date = models.DateField(auto_now_add=True)
def __str__(self):
return self.content class Course(models.Model):
"""课程表"""
name = models.CharField(verbose_name='课程名称',max_length=64,unique=True)
price = models.PositiveSmallIntegerField()
period = models.PositiveSmallIntegerField(verbose_name="课程周期(月)",default=5)
outline = models.TextField(verbose_name="大纲") def __str__(self):
return self.name class ClassList(models.Model):
"""班级列表"""
branch = models.ForeignKey("Branch")
course = models.ForeignKey("Course")
class_type_choices = ((0,'脱产'),(1,'周末'),(2,'网络班'))
class_type = models.SmallIntegerField(choices=class_type_choices,default=0)
semester = models.SmallIntegerField(verbose_name="学期")
teachers = models.ManyToManyField("UserProfile",verbose_name="讲师")
start_date = models.DateField("开班日期")
graduate_date = models.DateField("毕业日期",blank=True,null=True)
def __str__(self): return "%s(%s)期" %(self.course.name,self.semester) class Meta:
unique_together = ('branch','class_type','course','semester') class CourseRecord(models.Model):
"""上课记录"""
class_grade = models.ForeignKey("ClassList",verbose_name="上课班级")
day_num = models.PositiveSmallIntegerField(verbose_name="课程节次")
teacher = models.ForeignKey("UserProfile")
title = models.CharField("本节主题",max_length=64)
content = models.TextField("本节内容")
has_homework = models.BooleanField("本节有作业",default=True)
homework = models.TextField("作业需求",blank=True,null=True)
date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return "%s第(%s)节" %(self.class_grade,self.day_num) class Meta:
unique_together = ('class_grade','day_num') class StudyRecord(models.Model):
"""学习记录表"""
course_record = models.ForeignKey("CourseRecord")
student = models.ForeignKey("Student") score_choices = ((100,"A+"),
(90,"A"),
(85,"B+"),
(80,"B"),
(75,"B-"),
(70,"C+"),
(60,"C"),
(40,"C-"),
(-50,"D"),
(0,"N/A"), #not avaliable
(-100,"COPY"), #not avaliable
)
score = models.SmallIntegerField(choices=score_choices,default=0)
show_choices = ((0,'缺勤'),
(1,'已签到'),
(2,'迟到'),
(3,'早退'),
)
show_status = models.SmallIntegerField(choices=show_choices,default=1)
note = models.TextField("成绩备注",blank=True,null=True) date = models.DateTimeField(auto_now_add=True) def __str__(self): return "%s %s %s" %(self.course_record,self.student,self.score) class Branch(models.Model):
"""校区"""
name = models.CharField(max_length=64,unique=True)
addr = models.CharField(max_length=128,blank=True,null=True)
def __str__(self):
return self.name class Menus(models.Model):
"""动态菜单"""
name = models.CharField(max_length=64)
url_type_choices = ((0,'absolute'),(1,'dynamic'))
url_type = models.SmallIntegerField(choices=url_type_choices,default=0)
url_name = models.CharField(max_length=128) def __str__(self):
return self.name class Meta:
unique_together = ('name','url_name')
附crm个人项目笔记(笔记不太容易看懂,稍微有点乱,仅供参考):
个人笔记1:
(笔记1:实现登录页面及后台数据传输到前端并显示)
首先创建好表结构
然后:
一、
    登录页面
1、验证:
导入authenticate
后台获取用户名密码认证(用django自定义认证方式,user=authenticate(username=username,password=password))
注:user是个对象
跳转页面(redirect("url"))
2、真正登录
页面显示当前用户名(模板语言(request.user),后台获取的所有数据封装到了request中)
存在问题:未登陆时候显示匿名用户,当再次登陆成功之后仍然显示匿名用户
解决
    导入login
    把user封装到request.user
    login(request.user)
有错误:写个errormessage传到前端
二、退出(登出)(清楚一下session)
下拉框(boottrap)(改成隐藏的 class dropdown)
logout 返回到url (logout)
def    acc_logout
    logout(request)清空
    然后返回登陆页面
    
    
另外:未登陆不让进入url,
解决:用django自带的lonin_require
    在具体的页面导入lonin_require,加上装饰器
    
    当直接访问页面时会出现跳转到django自带的页面出错
    可以在settings写上:LOGIN_URL = '/login/'
    会跳转到当前url:login    
    获取next/url,(request.GET.get('next','/'))登陆成功可以直接跳转相应url:crm
    
    
    
    
    
三、创建动态菜单
        先找到用户关联的所有角色
        再找到每个角色里所有菜单
        
        在index里面写,每个用户都有动态菜单
        
        拿到角色(用user反向查userprofile的角色)
        反向查:
            one to one :request.user.userprofile.roll.all 后面跟小写表名
            one to one :request.user.userprofile.roll.select_related 后面跟小写表名
            FK :request.user.userprofile_set 后面跟小写表名
        
        完全一样:request.user.userprofile.roll.select_related == request.user.userprofile.roll.all
        
        然后循环拿到的所有角色
            循环所有的菜单
            去取menu.name,做判断绝对的还是动态的url
自定制djangoAdmin显示    list_display.....
过滤字段:list_filter  = [   ....]
搜索配置:search_fields = [     ]存在外键的搜索相应字段...__....
前端信息展示
写一个公共的实现增删改查功能   类似djangoadmin
公共页面,不同角色显示不同数据,传值不同,url不同
做一个独立APP     kingadmin        独立的templates     独立的static
注意:另相同名字templates 的配置 可以改名字....
kingadmin
登录login
把注册的app都列出来(没东西不显示)
先不用:
(自己写一个配置文件,且有东西就在页面上显示,及后面显示数据的自定义
用:
收集到所有app,然后过滤
动态导入配置文件(以后移动到其他项目也不用修改)
from djando import conf
.....
print(conf.settings.INSTALLED_APPS)
自定制显示设置:kingadmin.py
每个项目创建一个自定制的文件:kingadmin.py
根据字符串动态反射kingadmin.py文件,执行里面的代码
用__import__(...)导入Kingadmin
创建一个模块sites,生成全局字典,sites
在sites的register里面先检查app是否存在,不存在添加到全局字典,获取app_name,表名...添加到全局字典
(每个app实例化全局字典对象,其实是:第一次调用实例化一次,后面每次调用都是拿到那个对象,根本没有实例化,(python里面的优化))
触发:执行每个app里的admin程序,admin注册全局的字典,其他的视图也要从
全局字典读数据,因为触发了这个程序,其他视图才会在字典里面,所以要程序一启动就触发这个程序,重新建立一个模块app_setup
取出全局字典数据返回前端
点击取出的数据显示详细:url:app名字/表名        (取出指定model里的数据返回给前端)
怎么取?反射根据字符串导入 OR 从视图可以取到adminclass        通过adminclass和model绑定即可取到model里的数据
解决获取数据的自定义默认数据问题:
创建一个积累admin_base    让adminclass继承baseadmin,解决传值默认数据,还有好处:之后给表扩展增加功能,直接在基类里面一写就可以
避免多个model共享同一个BaseKingadmin对象,可以进行admin_class实例化
然后就获取到相应值了,创建一个页面(tabl_obj_list),返回到页面
接下来把格式化的展示:
在前端自定义标签:
    创建templatetags包        register.simple_tag            反射
choice字段的显示:先判断是否字段是choice字段:拿到每个字段                直接取a.get_status_diaplay()
models.CustomerInfo.meta.fields 获取model所有字段对象
models.CustomerInfo.meta.fields.SmallIntegerField('status')取一个字段的对象
查看字段类型:a.get_internal_type()
过滤字段时候,在后端views里面定义一个过滤函数get_filter_result()...
登录
动态菜单
增删改查公共模板    后端传数据 前端展示不同
    程序自动找到kingadmin下的文件,执行
    
笔记2:
(实现分页、排序及搜索功能)
分页:
(为什么做分页)
分页功能:减少一次从数据库读入过多数据
从数据库搜第5页的数据,每页是10条
select * from tablename limit offset 50 limit 10  从第50条开始往后搜索10条
select * from tablename limit limit 5,10  从第50条开始往后搜索10条
用django自带分页
字段排序:
a = models.custerinfo.objects.all()
先按name排序再按status排序
a.order_by("name").order_by("status")
系统实现:按单条件排序
前端:排序加上一个超链接,按字段索引排,用forloop.counter0,加0表示从0开始
全局排序,先排序再分页
小问题解决:
筛选过后再排序:进行拼接筛选的字段 
    分页问题:在render分页时候重新加上拼接
搜索:
    多个条件搜索,用OR的关系
    from django .db.models import Q
    q = Q()
    q.connector = "OR"
    q.children.append(("consult_name",'33'))
    models.Costumer.objects.filter(q)
    q.append......
    
    先搜索后排序
教育行业CRM项目开发的更多相关文章
- 读《31天学会CRM项目开发》记录2 - 企业信息管理系统
		在信息技术的快速推动下,企业如果依然利用传统的管理方式,以人为主,那效率便会大打折扣.在此背景下,企业信息化系统得 到了高速发展.如我们常见的ERP系统.MES系统,都是提高公司运行效率,降低运营以及 ... 
- 读《31天学会CRM项目开发》记录1 - 认识软件开发
		今天闲来无事,心中又对软件开发充满了向往和憧憬.一直认为实践是检验真知的唯一标准,也是快速提升的绝密方法,是巩固基础加深基础的好去处.故在JD上搜了下软件开发,看到了这本<31天学会CRM项目开 ... 
- crm项目开发之架构设计
		CRM customer relationship management 客户管理系统 1. 干什么用的? 管理客户 维护客户关系 2. 谁去使用? 销售 班主任 项目经理 3. 需求: 1. 登录 ... 
- 读《31天学会CRM项目开发》记录4  - WEB服务配置
		好几天没有更新记录了,因为最近都在看本书的基础内容,然后跟着练习.等看到数据库部分,就晕菜了,只能草草浏览一遍,想在后面的实战中再加强. 下面是对IIS 和ASP.NET的配置! 一.什么是IIS? ... 
- 读《31天学会CRM项目开发》记录3 - CRM解决方案
		一. 二.CRM系统设计方案 CRM技术部分设计方案主要包括:服务器端设计方案.客户端设计方案.数据库设计方案.应用系统框架设计方案. 1.服务器端设计方案 配备平台:IIS7和SQL Server2 ... 
- 教育行业app开发新契机,在线教育要从B端出发
		近几年移动互联网教育风生水起,在运营模式上的开拓也各不相同,随着移动互联网进入下半场,好未来.新东方.猿题库.学霸君等,都在加速三四线地区布局,以及教育行业app开发和升级. 在移动互联网下半场,用户 ... 
- VR外包AR外包公司(虚拟现实外包公司)承接虚拟现实项目开发(企业、教育、游戏)
		VR外包AR外包公司(虚拟现实外包公司)承接虚拟现实项目开发(企业.教育.游戏) 可公对公签正规合同,开发票. 我们是北京的公司.专业团队,成员为专业 VR/AR 产品公司一线开发人员,有大型产品开发 ... 
- Java高级项目实战之CRM系统01:CRM系统概念和分类、企业项目开发流程
		1. CRM系统介绍 CRM系统即客户关系管理系统, 顾名思义就是管理公司与客户之间的关系. 是一种以"客户关系一对一理论"为基础,旨在改善企业与客户之间关系的新型管理机制.客户关 ... 
- CRM系统推动教育行业数字化转型
		目前,教育培训的潜在市场规模巨大,并且保持着迅猛的发展态势.同时,随着众多外资企业不断涌入中国市场,与国内大大小小的培训机构展开竞争,所以教育行业的竞争也是非常的激烈.传统的教育行业亟待数字化转型,才 ... 
随机推荐
- 如何单页面不引用移动端的适配 (postcss)
			由于pc端移动端同时开发所以同时有vant跟elementui,我的pc端登录界面直接引用之前项目做的 因为postcss全局引用,全局的px会自动转换自适应,然后页面的布局就呈现了放大的趋势, 查阅 ... 
- 完了!TCP出了大事!
			前情回顾:<非中间人就不能劫持TCP了吗?> 不速之客 夜黑风高,乌云蔽月. 两位不速之客,身着黑衣,一高一矮,潜入Linux帝国. 这一潜就是一个多月,直到他们收到了一条消息······ ... 
- 6.15 省选模拟赛 老魔杖 博弈论 SG函数
			这道题确实没有一个很好的解决办法 唯一的正解可能就是打表找规律 或者 直接猜结论了吧. 尽管如此 在此也给最终结论一个完整的证明. 对于70分 容易发现状态数量不大 可以进行暴力dp求SG函数. 原本 ... 
- 5.15 牛客挑战赛40 B 小V的序列 关于随机均摊分析 二进制
			LINK:小V的序列 考试的时候 没想到正解 于是自闭. 题意很简单 就是 给出一个序列a 每次询问一个x 问序列中是否存在y 使得x^y的二进制位位1的个数<=3. 容易想到 暴力枚举. 第一 ... 
- 剑指 Offer 53 - II. 0~n-1中缺失的数字
			本题 题目链接 题目描述 我的题解 二分法 思路分析 排序数组中的搜索问题,首先想到二分法 当nums[center] > center 时,缺少的数在左区间 当nums[center] = c ... 
- [NewLife.Net]单机400万长连接压力测试
			目标 对网络库NewLife.Net进行单机百万级长连接测试,并持续收发数据,检测网络库稳定性. [2020年8月1日晚上22点] 先上源码:https://github.com/NewLifeX/N ... 
- 实验06——java自动封箱、自动拆箱
			package cn.tedu.demo; /** * @author 赵瑞鑫 E-mail:1922250303@qq.com * @version 1.0 * @创建时间:2020年7月17日 上 ... 
- AsyncTask被废弃了,换Coroutine吧
			本文主要是学习笔记,有版权问题还请告知删文 鸣谢:guolin@第一行代码(第三版) 你是否也在最近的代码中看见了 AsyncTask 被一条横杠划掉了 这表明--他要被Google放弃了 Googl ... 
- python5.3二进制文件的读写
			fh=open(r"C:\1.png","rb")#转换成二进制数据data=fh.read()#对二进制数据进行读取 fh1=open(r"C:\2 ... 
- 每日一道 LeetCode (10):搜索插入位置
			每天 3 分钟,走上算法的逆袭之路. 前文合集 每日一道 LeetCode 前文合集 代码仓库 GitHub: https://github.com/meteor1993/LeetCode Gitee ... 
