标签:机器 ajax 根据 record field teacher margin 先来 excel
本节内容
业务痛点分析
项目需求讨论
使用场景分析
表结构设计
我2013年刚加入老男孩教育的时候,学校就一间教室,2个招生老师,招了学生后,招生老师就在自己的excel表里记录一下,每月算工资时,就按这个表来统计招生提成, 后来学校大了点,教室变成了3间,招生老师变成了4个人,招的学生也开始多了起来,不过大家依然是招了学生就纪录在自己的excel里,但问题开始出现了,我们的招生多是通过网络qq聊天在线咨询,好多客户需要聊很久才能转化成学员,经常会出现一个客户跟一个招生老师聊过后,过了一段时间却在另一个招生老师那报名了。 于是争执就出现了,A销售说这个人是先找我咨询的,我帮他回答了大多数问题,报名提成却算成B的,不公平。B销售也会讲,这个客户找他咨询时并未说自己之前跟其它销售聊过呀,所以对B来讲,这就是个新客户呀,并且是在B这报的名, 凭什么要分提成给A呀。为了解决这个问题,于是想出一个办法,搞了一个机器做共享服务器,共享服务器上搞了个统一的客户信息excel表,大家每人聊过的客户都在这个表里纪录, 这样每个人录入前,先在表格里全局查一下,这个客户的qq号是不是已经在表里了,这样就能避免冲突了。 但用过共享文件的应该都遇到过一个问题就是,为保证数据一致性,同一时间系统只允许一个用户修改数据, 但我们的每个招生老师一上班,就会打开这个共享文件,随时聊的学生,随时会录入到表里。但这个限制使的大家,只要一有人录入信息,就得要求其它销售人员都把文件先关闭,录入信息后其它人才能再用,这搞的大家很难受。 所以销售人员经常让我帮开发个系统,可以解决一些类似这样的问题。
另外, 我们在讲课时, 为了保证和检验学员的学习效果, 每节课讲完时都会给学生部置一个作业,要求他必须在下节上课前完成并提交作业,老师会给学员统一批作业,作业成绩也纪录在excel表中,就像下图一样。每个老师维护自己班级的作业,校长如果想看各个班级的成绩,还要跟每个老师要学员成绩表,很不方便, 当时校长就跟我讲,学员的成绩\出勤情况等要是也能统一管理就好了。
于是我心里也一直纪着这个事,想帮学校开发一个学员开发一个类似CRM(客户关系管理软件)的系统,集销售管理\学员管理等功能于一体。但平常要上班,周未还要讲课,一直没时间,终于有一次,我要去美帝度假,飞机要飞13个小时才到纽约,在飞机上突然想,反正也是没事干, 不如就开发这个东西吧, 于是一路未眠,狂写了近千行代码,飞机的上的美女空乘都心疼的多给我倒了2杯咖啡,哈哈,下飞机时,已是两眼痛红,不过,这个学员管理系统的雏形也做出来了,接下来几天在纽约又完善了下细节,于是就开始上线让大家用了。到现在,这个系统在公司已经用了一年多了,大家每天都在使用,之前的痛点都解决了,特别是销售人员, 工作已经离不开这个系统了,自己做的东西帮别人解决具体问题,心里还是蛮开森的!
个人觉得这个学员管理系统的挺适合新手练习的,难度适中,用户的知识点也比较综合,所以把学校里的系统简化了一下,提练成了一个教学项目,下面我们就一起来做一个喽。
噢,对了,最后说一下这个项目涉及到的知识点,请确保你以充分掌握了下面所列知识点再学习此项目噢!
Django
JQuery
BootStrap
Ajax
HTMS\CSS
首先给我们的项目起个名字吧,这个系统要同时支持销售管理\学员管理\讲师管理等功能,功能比较杂,不能称之为严格意上的CRM,因为CRM一般只包括销售管理,but anyway, who cares,我们就叫它”老男孩crm管理系统”吧,
起好了名字,开始动手写之前, 肯定要把需求想清楚,需求想不清楚就开始写的话,等于给自己挖坑,你肯定不想出现写了5千行代码后,突然发现,需求搞错了,还要重新推到重来的事情吧。所以,现在静下心,把需求想明白,画好思维导图,跟同事多讨论应用场景,做了各方面论证后,再开始动工噢。
我们的系统的用户分3种,销售\学生\讲师,这3个角色关注的事情是不同的, 销售只关注招了多少学员,讲师关注自己管理的班级学习成绩怎样,学员只关注自己的学习成绩。 因此在想需求时,你要从每个角色出发,看他关心的是什么, 于是我画出了这个思维导图
根据思维导图,我们总结出以下具体需求:
为了更加理清我们的项目开发需求,在动手写代码前,建议再有一个业务场景分析的步骤,即从用户角度写出你使用这个项目的具体场景,我们这里分为销售、讲师、学员3个角色,我分别给每个角色列出了几个使用场景:
一. 销售:
二. 讲师:
三. 学员:
好啦, 业务场景也分析完了,接下来我们终于可以开始搞事情啦!
在设计表结构前,咱们先创建好一个项目吧,项目名就叫PerfectCRM,app名叫crm
我这里先列出来我们大体需要的表,尔等先过目一下,然后我们再分别为什么需要每个具体的表,及这个表里要存什么样的数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
from django.db import models # Create your models here. class Customer(models.Model): ‘‘‘存储所有客户信息‘‘‘ pass class Enrollment(models.Model): ‘‘‘存储已报名学员的信息‘‘‘ pass class CustomerFollowUp(models.Model): ‘‘‘存储客户的后续跟进信息‘‘‘ pass class ClassList(models.Model): ‘‘‘存储班级信息‘‘‘ pass class Course(models.Model): ‘‘‘存储所开设课程的信息‘‘‘ pass class CourseRecord(models.Model): ‘‘‘存储各班级的上课记录‘‘‘ pass class StudyRecord(models.Model): ‘‘‘存储所有学员的详细的学习成绩情况‘‘‘ pass class UserProfile(models.Model): ‘‘‘存储所有讲师\销售人员\ 学员 用户信息‘‘‘ pass class Role(models.Model): ‘‘‘角色信息‘‘‘ pass class Branch(models.Model): ‘‘‘存储所有校区‘‘‘ pass |
接下来分别解释每张表
1. Customer表, 主要给销售人员用, 存储所有客户信息,里面要记录客户来源\姓名\qq \客户来源\咨询的内容等
class Customer(models.Model): ‘‘‘存储所有客户信息‘‘‘ #客户在咨询时,多是通过qq,所以这里就把qq号做为唯一标记客户的值,不能重复 qq = models.CharField(max_length=64,unique=True,help_text=u‘QQ号必须唯一‘) qq_name = models.CharField(u‘QQ名称‘,max_length=64,blank=True,null=True) #客户只要没报名,你没理由要求人家必须告诉你真实姓名及其它更多私人信息呀 name = models.CharField(u‘姓名‘,max_length=32,blank=True,null=True) sex_type = ((‘male‘,u‘男‘),(‘female‘,u‘女‘)) sex = models.CharField(u"性别",choices=sex_type,default=‘male‘,max_length=32) birthday = models.DateField(u‘出生日期‘,max_length=64,blank=True,null=True,help_text="格式yyyy-mm-dd") phone = models.BigIntegerField(u‘手机号‘,blank=True,null=True) email = models.EmailField(u‘常用邮箱‘,blank=True,null=True) id_num = models.CharField(u‘身份证号‘,blank=True,null=True,max_length=64) source_type = ((‘qq‘,u"qq群"), (‘referral‘,u"内部转介绍"), (‘website‘,u"官方网站"), (‘baidu_ads‘,u"百度广告"), (‘qq_class‘,u"腾讯课堂"), (‘school_propaganda‘,u"高校宣讲"), (‘51cto‘,u"51cto"), (‘others‘,u"其它"), ) #这个客户来源渠道是为了以后统计各渠道的客户量\成单量,先分类出来 source = models.CharField(u‘客户来源‘,max_length=64, choices=source_type,default=‘qq‘) #我们的很多新客户都是老学员转介绍来了,如果是转介绍的,就在这里纪录是谁介绍的他,前提这个介绍人必须是我们的老学员噢,要不然系统里找不到 referral_from = models.ForeignKey(‘self‘,verbose_name=u"转介绍自学员",help_text=u"若此客户是转介绍自内部学员,请在此处选择内部\学员姓名",blank=True,null=True,related_name="internal_referral") #已开设的课程单独搞了张表,客户想咨询哪个课程,直接在这里关联就可以 course = models.ForeignKey("Course",verbose_name=u"咨询课程") class_type_choices = ((‘online‘, u‘网络班‘), (‘offline_weekend‘, u‘面授班(周末)‘,), (‘offline_fulltime‘, u‘面授班(脱产)‘,), ) class_type = models.CharField(u"班级类型",max_length=64,choices=class_type_choices) customer_note = models.TextField(u"客户咨询内容详情",help_text=u"客户咨询的大概情况,客户个人信息备注等...") work_status_choices = ((‘employed‘,‘在职‘),(‘unemployed‘,‘无业‘)) work_status = models.CharField(u"职业状态",choices=work_status_choices,max_length=32,default=‘employed‘) company = models.CharField(u"目前就职公司",max_length=64,blank=True,null=True) salary = models.CharField(u"当前薪资",max_length=64,blank=True,null=True) status_choices = ((‘signed‘,u"已报名"),(‘unregistered‘,u"未报名")) status = models.CharField(u"状态",choices=status_choices,max_length=64,default=u"unregistered",help_text=u"选择客户此时的状态") #课程顾问很得要噢,每个招生老师录入自己的客户 consultant = models.ForeignKey("UserProfile",verbose_name=u"课程顾问") date = models.DateField(u"咨询日期",auto_now_add=True) def __str__(self): return u"QQ:%s -- Name:%s" %(self.qq,self.name) class Meta: #这个是用来在admin页面上展示的,因为默认显示的是表名,加上这个就变成中文啦 verbose_name = u‘客户信息表‘ verbose_name_plural = u"客户信息表"
2. 学员报名表,这里为什么要把客户信息表 和 这个学员报名表分开呢? 因内一个学员可以报多个课程 ,每个课程 都需要单独记录学习成绩呀什么的,所以每报一个课程 ,就在这里生成 一条相应的报名记录
class Enrollment(models.Model): ‘‘‘存储学员报名的信息‘‘‘ #所有报名的学生 肯定是来源于客户信息表的,先咨询,后报名嘛 customer = models.ForeignKey(Customer) school = models.ForeignKey(‘Branch‘, verbose_name=‘校区‘) #选择他报的班级,班级是关联课程的,比如python开发10期 course_grade = models.ForeignKey("ClassList", verbose_name="所报班级") why_us = models.TextField("为什么报名老男孩", max_length=1024, default=None, blank=True, null=True) your_expectation = models.TextField("学完想达到的具体期望", max_length=1024, blank=True, null=True) contract_agreed = models.BooleanField("我已认真阅读完培训协议并同意全部协议内容") contract_approved = models.BooleanField("审批通过", help_text=u"在审阅完学员的资料无误后勾选此项,合同即生效") enrolled_date = models.DateTimeField(auto_now_add=True, auto_created=True, verbose_name="报名日期") memo = models.TextField(‘备注‘, blank=True, null=True) def __str__(self): return "<%s 课程:%s>" %(self.customer ,self.course_grade) class Meta: verbose_name = ‘学员报名表‘ verbose_name_plural = "学员报名表" unique_together = ("customer", "course_grade") #这里为什么要做个unique_together联合唯一?因为老男孩有很多个课程, 学生学完了一个觉得好的话,以后还可以再报其它班级, #每报一个班级,就得单独创建一条报名记录,所以这里想避免重复数据的话,就得搞个"客户 + 班级"的联合唯一喽
3. 客户跟进表,这张表的意义很容易理解, 一个客户今天咨询后,你录入到了客户信息表,但他没报名, 所以过了一段时间,销售还得再跟他聊聊吧,聊完后,结果又没报,那也得纪录下来吧,因为每个销售每天要聊很多人,你不纪录的话, 可能下次再跟这个人聊时,你早已忘记上次聊了什么了, 这样会让客户觉得这个销售不专业,相反,如果把每次跟进的内容都纪录下来, 过了几个月,这个客户再跟你聊时,竟然发现,你还记得他的所有情况,他就觉得你很重视他,说不定一感动就报名了,哈哈。 所以,一条客户信息可能会对应多条跟进记录,是个1对多的关系,必须单独搞张表来记录
class CustomerFollowUp(models.Model): ‘‘‘存储客户的后续跟进信息‘‘‘ customer = models.ForeignKey(Customer,verbose_name=u"所咨询客户") note = models.TextField(u"跟进内容...") status_choices = ((1,u"近期无报名计划"), (2,u"2个月内报名"), (3,u"1个月内报名"), (4,u"2周内报名"), (5,u"1周内报名"), (6,u"2天内报名"), (7,u"已报名"), (8,u"已交全款"), ) status = models.IntegerField(u"状态",choices=status_choices,help_text=u"选择客户此时的状态") consultant = models.ForeignKey("UserProfile",verbose_name=u"跟踪人") date = models.DateField(u"跟进日期",auto_now_add=True) def __str__(self): return u"%s, %s" %(self.customer,self.status) class Meta: verbose_name = u‘客户咨询跟进记录‘ verbose_name_plural = u"客户咨询跟进记录"
4. 班级表, 学生以班级为单位管理,这个表被学员表反向关联, 即每个学员报名时需要选择班级
class ClassList(models.Model): ‘‘‘存储班级信息‘‘‘ #创建班级时需要选择这个班所学的课程 branch = models.ForeignKey("Branch",verbose_name="校区") course = models.ForeignKey("Course",verbose_name=u"课程") class_type_choices = ((0,‘面授‘),(1,‘随到随学网络‘)) class_type = models.SmallIntegerField(choices=class_type_choices,default=0) total_class_nums = models.PositiveIntegerField("课程总节次",default=10) semester = models.IntegerField(u"学期") price = models.IntegerField(u"学费", default=10000) start_date = models.DateField(u"开班日期") graduate_date = models.DateField(u"结业日期", blank=True, null=True) #选择这个班包括的讲师,可以是多个 teachers = models.ManyToManyField("UserProfile", verbose_name=u"讲师") def __str__(self): return "%s(%s)" % (self.course, self.semester) class Meta: verbose_name = u‘班级列表‘ verbose_name_plural = u"班级列表" #为避免重复创建班级,课程名+学期做联合唯一 unique_together = ("course", "semester") #自定义方法,反向查找每个班级学员的数量,在后台admin里 list_display加上这个"get_student_num"就可以看到 def get_student_num(self): return "%s" % self.customer_set.select_related().count() get_student_num.short_description = u‘学员数量‘
5. 课程表,存储课程介绍\大纲等基本信息
class Course(models.Model): ‘‘‘存储所开设课程的信息‘‘‘ name = models.CharField(u"课程名",max_length=64,unique=True) description = models.TextField("课程描述") outline = models.TextField("课程大纲") period = models.IntegerField("课程周期(Month)") def __str__(self): return self.name
6. 上课纪录表,每个班级都要上很多次课,讲师每上一次课的纪录都要纪录下来,以后可以方便统计讲师工资什么的
class CourseRecord(models.Model): ‘‘‘存储各班级的上课记录‘‘‘ #讲师创建上课纪录时要选择是上哪个班的课 course = models.ForeignKey(ClassList, verbose_name="班级(课程)") day_num = models.IntegerField("节次", help_text="此处填写第几节课或第几天课程...,必须为数字") date = models.DateField(auto_now_add=True, verbose_name="上课日期") teacher = models.ForeignKey("UserProfile", verbose_name="讲师") has_homework = models.BooleanField(default=True, verbose_name="本节有作业") homework_title = models.CharField(max_length=128,blank=True,null=True) homework_requirement = models.TextField(blank=True,null=True) def __str__(self): return "%s 第%s天" % (self.course, self.day_num) class Meta: verbose_name = u‘上课纪录‘ verbose_name_plural = u"上课纪录" unique_together = (‘course‘, ‘day_num‘)
7. 学员学习纪录表,思考一下,如果你想实现纪录每位学员的详细学习纪录,即精确到每节课的成绩\出勤情况,怎么办?其实很简单, 先来看一下此时班级\上课纪录\学员学习纪录的关系图:
class StudyRecord(models.Model): ‘‘‘存储所有学员的详细的学习成绩情况‘‘‘ student = models.ForeignKey("Customer",verbose_name=u"学员") course_record = models.ForeignKey(CourseRecord, verbose_name=u"第几天课程") record_choices = ((‘checked‘, u"已签到"), (‘late‘,u"迟到"), (‘noshow‘,u"缺勤"), (‘leave_early‘,u"早退"), ) record = models.CharField(u"上课纪录",choices=record_choices,default="checked",max_length=64) score_choices = ((100, ‘A+‘), (90,‘A‘), (85,‘B+‘), (80,‘B‘), (70,‘B-‘), (60,‘C+‘), (50,‘C‘), (40,‘C-‘), (-50,‘D‘), (0,‘N/A‘), (-100,‘COPY‘), (-1000,‘FAIL‘), ) score = models.IntegerField(u"本节成绩",choices=score_choices,default=-1) date = models.DateTimeField(auto_now_add=True) note = models.CharField(u"备注",max_length=255,blank=True,null=True) def __str__(self): return u"%s,学员:%s,纪录:%s, 成绩:%s" %(self.course_record,self.student.name,self.record,self.get_score_display()) class Meta: verbose_name = u‘学员学习纪录‘ verbose_name_plural = u"学员学习纪录" #一个学员,在同一节课只可能出现一次,所以这里把course_record + student 做成联合唯一 unique_together = (‘course_record‘,‘student‘)
可以看出,一个班级对应多节课,每节课又对应多个学生的出勤和成绩,我们已经通过班级表(”ClassList”)和上课纪录表(”CourseRecord”)存储了班级信息和每节上课纪录,想存学生的每节课学习纪录的话只需要再搞一个表就可以了。
8. 用户表,存储销售、讲师账户信息
这里我们用django自带的认证系统,并对其进行自定制
class UserProfile(auth.AbstractBaseUser, auth.PermissionsMixin): email = models.EmailField( verbose_name=‘email address‘, max_length=255, unique=True, ) password = models.CharField(_(‘password‘), max_length=128, help_text=mark_safe(‘‘‘<a class=‘btn-link‘ href=‘password‘>重置密码</a>‘‘‘)) is_active = models.BooleanField(default=True) is_admin = models.BooleanField(default=False) is_staff = models.BooleanField( verbose_name=‘staff status‘, default=True, help_text=‘Designates whether the user can log into this admin site.‘, ) name = models.CharField(max_length=32) #role = models.ForeignKey("Role",verbose_name="权限角色") branch = models.ForeignKey("Branch",verbose_name="所属校区",blank=True,null=True) roles = models.ManyToManyField(‘Role‘,blank=True) memo = models.TextField(‘备注‘, blank=True, null=True, default=None) date_joined = models.DateTimeField(blank=True, null=True, auto_now_add=True) USERNAME_FIELD = ‘email‘ # REQUIRED_FIELDS = [‘name‘,‘token‘,‘department‘,‘tel‘,‘mobile‘,‘memo‘] REQUIRED_FIELDS = [‘name‘] def get_full_name(self): # The user is identified by their email address return self.email def get_short_name(self): # The user is identified by their email address return self.email def __str__(self): # __str__ on Python 2 return self.email # def has_perm(self, perm, obj=None): # "Does the user have a specific permission?" # # Simplest possible answer: Yes, always # return True def has_perms(self, perm, obj=None): "Does the user have a specific permission?" # Simplest possible answer: Yes, always return True def has_module_perms(self, app_label): "Does the user have permissions to view the app `app_label`?" # Simplest possible answer: Yes, always return True @property def is_superuser(self): "Is the user a member of staff?" # Simplest possible answer: All admins are staff return self.is_admin class Meta: verbose_name = ‘用户信息‘ verbose_name_plural = u"用户信息" objects = auth.UserManager() class Meta: verbose_name = ‘CRM账户‘ verbose_name_plural = ‘CRM账户‘
9. 角色表,用于角色划分,用于权限管理,权限功能我们后面会实现,这里只在表里先存个简单的角色名
class Role(models.Model): ‘‘‘角色信息‘‘‘ name = models.CharField(max_length=32,unique=True) menus = models.ManyToManyField(‘FirstLayerMenu‘,blank=True) def __str__(self): return self.name
10. 校区表,存储不同校区
class Branch(models.Model): ‘‘‘存储所有校区‘‘‘ name = models.CharField(max_length=64,unique=True) def __str__(self): return self.name
11. 菜单表,不同的角色看到的菜单不同, 我们支持动态菜单 ,所以需要把菜单 以及 和角色的关联存下来
class FirstLayerMenu(models.Model): ‘‘‘第一层侧边栏菜单‘‘‘ name = models.CharField(‘菜单名‘,max_length=64) url_type_choices = ((0,‘related_name‘),(1,‘absolute_url‘)) url_type = models.SmallIntegerField(choices=url_type_choices,default=0) url_name = models.CharField(max_length=64,unique=True) order = models.SmallIntegerField(default=0,verbose_name=‘菜单排序‘) sub_menus = models.ManyToManyField(‘SubMenu‘,blank=True) def __str__(self): return self.name
12. 二级菜单表, 还可以支持2级子菜单
class SubMenu(models.Model): ‘‘‘第二层侧边栏菜单‘‘‘ name = models.CharField(‘二层菜单名‘, max_length=64) url_type_choices = ((0,‘related_name‘),(1,‘absolute_url‘)) url_type = models.SmallIntegerField(choices=url_type_choices,default=0) url_name = models.CharField(max_length=64, unique=True) order = models.SmallIntegerField(default=0, verbose_name=‘菜单排序‘) def __str__(self): return self.name
13. 缴费记录
class PaymentRecord(models.Model): enrollment = models.ForeignKey("Enrollment") pay_type_choices = ((‘deposit‘, u"订金/报名费"), (‘tution‘, u"学费"), (‘refund‘, u"退款"), ) pay_type = models.CharField("费用类型", choices=pay_type_choices, max_length=64, default="deposit") paid_fee = models.IntegerField("费用数额", default=0) note = models.TextField("备注",blank=True, null=True) date = models.DateTimeField("交款日期", auto_now_add=True) consultant = models.ForeignKey(UserProfile, verbose_name="负责老师", help_text="谁签的单就选谁") def __str__(self): return "%s, 类型:%s,数额:%s" %(self.enrollment.customer, self.pay_type, self.paid_fee) class Meta: verbose_name = ‘交款纪录‘ verbose_name_plural = "交款纪录"
好啦,表基本都建完了,接下来同步数据库,不过在同步数据库前,要改一下settings.py, 因为我们自定义了django的认证表,所以需要明确的告诉django,用我们改过的过来做默认的认证系统。
在settings.py中添加以下行,格式为 app.modelname
1
|
AUTH_USER_MODEL = ‘crm.UserProfile‘ |
最后一步,同步数据库!
1
2
|
python3 manage.py makemigrations python3 manage.py migrate |
伸个懒腰,出去撸一发,敬请期待下篇....
标签:机器 ajax 根据 record field teacher margin 先来 excel
原文地址:http://www.cnblogs.com/tongchengbin/p/7652082.html