CRM项目总结

       

    CRM项目总结

 

 

     一:开发背景

 

  在公司日益扩大的过程中,不可避免的会伴随着更多问题出现。

  对外 : 如何更好的管理客户与公司的关系?如何更及时的了解客户日益发展的需求变化?公司的产品是否真的符合客户需求?以及公司新产品信息是否更有针对性的及时推送给客户?

  对内 : 公司发展壮大,部门越来越多,如何明确每个部门的权限?如何合理的调配公司人员?如何合理的分配客户资源?如何精确的处理绩效考核?以及更重要的在销售管理中,如何更及时的了解是什么阻碍了公司的发展?是什么影响了销售的业绩?销售的服务是否让客户满意?

面对这么多的问题,这个时候来一套完整的CRM就显得很有必要了。

 

二:开发周期

 

  1. 计划在两个月内完成项目的初步设计以及功能的初步实现。
  2. 基础功能完成后,会预留两个月时间来完成bug修复以及微调根据公司业务发展的业务需求。
  3. 项目上线后,开发人员持续跟进项目。根据公司业务发展和管理体系实时调整。

 

三:功能

 

  1 from django.db import models
  2 from rbac import models as rbac_model
  3 # Create your models here.
  4 
  5 
  6 class Department(models.Model):
  7     """
  8     部门表
  9     市场部     1000
 10     销售      1001
 11     """
 12     title = models.CharField(verbose_name='部门名称', max_length=16)
 13     code = models.IntegerField(verbose_name='部门编号',unique=True,null=False)
 14 
 15     def __str__(self):
 16         return self.title
 17 
 18 
 19 class UserInfo(models.Model):
 20     """
 21     员工表
 22     """
 23     auth = models.OneToOneField(verbose_name='用户权限', to=rbac_model.User,null=True,blank=True)
 24     name = models.CharField(verbose_name='员工姓名', max_length=16)
 25     username = models.CharField(verbose_name='用户名', max_length=32)
 26     password = models.CharField(verbose_name='密码', max_length=64)
 27     email = models.EmailField(verbose_name='邮箱', max_length=64)
 28     openid = models.CharField(verbose_name='微信唯一ID', max_length=64, null=True, blank=True)
 29     depart = models.ForeignKey(verbose_name='部门', to="Department",to_field="code")
 30 
 31     def __str__(self):
 32         return self.name
 33 
 34 
 35 class Course(models.Model):
 36     """
 37     课程表
 38     """
 39     name = models.CharField(verbose_name='课程名称', max_length=32)
 40 
 41     def __str__(self):
 42         return self.name
 43 
 44 
 45 class School(models.Model):
 46     """
 47     校区表
 48 
 49     """
 50     title = models.CharField(verbose_name='校区名称', max_length=32)
 51 
 52     def __str__(self):
 53         return self.title
 54 
 55 
 56 class ClassList(models.Model):
 57     """
 58     班级表
 59     如:
 60         烧饼  打饼班  1期  10000  2017-11-11  2018-5-11
 61     """
 62     school = models.ForeignKey(verbose_name='校区', to='School')
 63     course = models.ForeignKey(verbose_name='课程名称', to='Course')
 64 
 65     semester = models.IntegerField(verbose_name="班级(期)")
 66     price = models.IntegerField(verbose_name="学费")
 67     start_date = models.DateField(verbose_name="开班日期")
 68     graduate_date = models.DateField(verbose_name="结业日期", null=True, blank=True)
 69     memo = models.CharField(verbose_name='说明', max_length=256, blank=True, null=True, )
 70     teachers = models.ManyToManyField(verbose_name='任课老师', to='UserInfo', related_name='teach_classes',limit_choices_to={'depart_id__in':[1003,1004]})
 71     tutor = models.ForeignKey(verbose_name='班主任', to='UserInfo', related_name='classes',limit_choices_to={'depart_id':1002})
 72 
 73     def __str__(self):
 74         return "{0}({1}期)".format(self.course.name, self.semester)
 75 
 76 
 77 class Customer(models.Model):
 78     """
 79     客户表
 80     """
 81     qq = models.CharField(verbose_name='qq', max_length=64, unique=True, help_text='QQ号必须唯一')
 82 
 83     name = models.CharField(verbose_name='学生姓名', max_length=16)
 84     gender_choices = ((1, ''), (2, ''))
 85     gender = models.SmallIntegerField(verbose_name='性别', choices=gender_choices)
 86 
 87     education_choices = (
 88         (1, '重点大学'),
 89         (2, '普通本科'),
 90         (3, '独立院校'),
 91         (4, '民办本科'),
 92         (5, '大专'),
 93         (6, '民办专科'),
 94         (7, '高中'),
 95         (8, '其他')
 96     )
 97     education = models.IntegerField(verbose_name='学历', choices=education_choices, blank=True, null=True, )
 98     graduation_school = models.CharField(verbose_name='毕业学校', max_length=64, blank=True, null=True)
 99     major = models.CharField(verbose_name='所学专业', max_length=64, blank=True, null=True)
100 
101     experience_choices = [
102         (1, '在校生'),
103         (2, '应届毕业'),
104         (3, '半年以内'),
105         (4, '半年至一年'),
106         (5, '一年至三年'),
107         (6, '三年至五年'),
108         (7, '五年以上'),
109     ]
110     experience = models.IntegerField(verbose_name='工作经验', blank=True, null=True, choices=experience_choices)
111     work_status_choices = [
112         (1, '在职'),
113         (2, '无业')
114     ]
115     work_status = models.IntegerField(verbose_name="职业状态", choices=work_status_choices, default=1, blank=True,
116                                       null=True)
117     company = models.CharField(verbose_name="目前就职公司", max_length=64, blank=True, null=True)
118     salary = models.CharField(verbose_name="当前薪资", max_length=64, blank=True, null=True)
119 
120     source_choices = [
121         (1, "qq群"),
122         (2, "内部转介绍"),
123         (3, "官方网站"),
124         (4, "百度推广"),
125         (5, "360推广"),
126         (6, "搜狗推广"),
127         (7, "腾讯课堂"),
128         (8, "广点通"),
129         (9, "高校宣讲"),
130         (10, "渠道代理"),
131         (11, "51cto"),
132         (12, "智汇推"),
133         (13, "网盟"),
134         (14, "DSP"),
135         (15, "SEO"),
136         (16, "其它"),
137     ]
138     source = models.SmallIntegerField('客户来源', choices=source_choices, default=1)
139     referral_from = models.ForeignKey(
140         'self',
141         blank=True,
142         null=True,
143         verbose_name="转介绍自学员",
144         help_text="若此客户是转介绍自内部学员,请在此处选择内部学员姓名",
145         related_name="internal_referral"
146     )
147     course = models.ManyToManyField(verbose_name="咨询课程", to="Course")
148 
149     status_choices = [
150         (1, "已报名"),
151         (2, "未报名")
152     ]
153     status = models.IntegerField(
154         verbose_name="状态",
155         choices=status_choices,
156         default=2,
157         help_text=u"选择客户此时的状态"
158     )
159     consultant = models.ForeignKey(verbose_name="课程顾问", to='UserInfo', related_name='consultant',limit_choices_to={'depart_id':1005})
160     date = models.DateField(verbose_name="咨询日期", auto_now_add=True)
161 
162     recv_date = models.DateField(verbose_name='接单时间',null=True,blank=True)
163 
164     last_consult_date = models.DateField(verbose_name="最后跟进日期", auto_now_add=True)
165 
166     def __str__(self):
167         return "姓名:{0},QQ:{1}".format(self.name, self.qq, )
168 
169 
170 class CustomerDistribution(models.Model):
171     """客户分配表"""
172     user = models.ForeignKey(verbose_name="当前客户顾问",to='UserInfo',limit_choices_to={'depart_id':1005},related_name="cds",null=True,blank=True)
173     customer = models.ForeignKey(verbose_name="客户",to="Customer",related_name="dealers",null=True,blank=True)
174     ctime = models.DateField(auto_now_add=True,null=True,blank=True)
175     status_choices = (
176         (1,'正在跟进'),
177         (2,'已成单'),
178         (3,'三天未跟进'),
179         (4,'十五天未成单'),
180     )
181     status = models.IntegerField(verbose_name="状态",choices=status_choices,default=1)
182     memo = models.CharField(verbose_name="更多信息",max_length=255,null=True,blank=True)
183 
184 
185 class SaleRank(models.Model):
186     """销售权重与分配"""
187     user = models.ForeignKey(to="UserInfo",verbose_name='课程顾问',limit_choices_to={'depart':1005})
188     num = models.IntegerField(verbose_name='数量')
189     weight = models.IntegerField(verbose_name='权重')
190     def __str__(self):
191         return '权重:{0}?;num:{1}'.format(self.weight,self.num)
192 
193 
194 class ConsultRecord(models.Model):
195     """
196     客户跟进记录
197     """
198     customer = models.ForeignKey(verbose_name="所咨询客户", to='Customer')
199     consultant = models.ForeignKey(verbose_name="跟踪人", to='UserInfo',limit_choices_to={'depart_id':1005})
200     date = models.DateField(verbose_name="跟进日期", auto_now_add=True)
201     note = models.TextField(verbose_name="跟进内容...")
202 
203 
204 class PaymentRecord(models.Model):
205     """
206     缴费记录
207     """
208     customer = models.ForeignKey(Customer, verbose_name="客户")
209 
210     class_list = models.ForeignKey(verbose_name="班级", to="ClassList", blank=True, null=True)
211 
212     pay_type_choices = [
213         (1, "订金/报名费"),
214         (2, "学费"),
215         (3, "转班"),
216         (4, "退学"),
217         (5, "退款"),
218     ]
219     pay_type = models.IntegerField(verbose_name="费用类型", choices=pay_type_choices, default=1)
220     paid_fee = models.IntegerField(verbose_name="费用数额", default=0)
221     turnover = models.IntegerField(verbose_name="成交金额", blank=True, null=True)
222     quote = models.IntegerField(verbose_name="报价金额", blank=True, null=True)
223     note = models.TextField(verbose_name="备注", blank=True, null=True)
224     date = models.DateTimeField(verbose_name="交款日期", auto_now_add=True)
225     consultant = models.ForeignKey(verbose_name="负责老师", to='UserInfo', help_text="谁签的单就选谁")
226 
227 
228 class Student(models.Model):
229     """
230     学生表(已报名)
231     """
232     customer = models.OneToOneField(verbose_name='客户信息', to='Customer')
233 
234     username = models.CharField(verbose_name='用户名', max_length=32)
235     password = models.CharField(verbose_name='密码', max_length=64)
236     emergency_contract = models.CharField(max_length=32, blank=True, null=True, verbose_name='紧急联系人')
237     class_list = models.ManyToManyField(verbose_name="已报班级", to='ClassList', blank=True)
238 
239     company = models.CharField(verbose_name='公司', max_length=128, blank=True, null=True)
240     location = models.CharField(max_length=64, verbose_name='所在区域', blank=True, null=True)
241     position = models.CharField(verbose_name='岗位', max_length=64, blank=True, null=True)
242     salary = models.IntegerField(verbose_name='薪资', blank=True, null=True)
243     welfare = models.CharField(verbose_name='福利', max_length=256, blank=True, null=True)
244     date = models.DateField(verbose_name='入职时间', help_text='格式yyyy-mm-dd', blank=True, null=True)
245     memo = models.CharField(verbose_name='备注', max_length=256, blank=True, null=True)
246 
247     def __str__(self):
248         return self.username
249 
250 
251 class CourseRecord(models.Model):
252     """
253     上课记录表
254     """
255     class_obj = models.ForeignKey(verbose_name="班级", to="ClassList")
256     day_num = models.IntegerField(verbose_name="节次", help_text=u"此处填写第几节课或第几天课程...,必须为数字")
257     teacher = models.ForeignKey(verbose_name="讲师", to='UserInfo',limit_choices_to={'depart_id__in':[1003,1004]})
258     date = models.DateField(verbose_name="上课日期", auto_now_add=True)
259 
260     course_title = models.CharField(verbose_name='本节课程标题', max_length=64, blank=True, null=True)
261     course_memo = models.TextField(verbose_name='本节课程内容概要', blank=True, null=True)
262     has_homework = models.BooleanField(default=True, verbose_name="本节有作业")
263     homework_title = models.CharField(verbose_name='本节作业标题', max_length=64, blank=True, null=True)
264     homework_memo = models.TextField(verbose_name='作业描述', max_length=500, blank=True, null=True)
265     exam = models.TextField(verbose_name='踩分点', max_length=300, blank=True, null=True)
266 
267     def __str__(self):
268         return "{0} day{1}".format(self.class_obj, self.day_num)
269 
270 
271 class StudyRecord(models.Model):
272     course_record = models.ForeignKey(verbose_name="第几天课程", to="CourseRecord")
273     student = models.ForeignKey(verbose_name="学员", to='Student')
274     record_choices = (('checked', "已签到"),
275                       ('vacate', "请假"),
276                       ('late', "迟到"),
277                       ('noshow', "缺勤"),
278                       ('leave_early', "早退"),
279                       )
280     record = models.CharField("上课纪录", choices=record_choices, default="checked", max_length=64)
281     score_choices = ((100, 'A+'),
282                      (90, 'A'),
283                      (85, 'B+'),
284                      (80, 'B'),
285                      (70, 'B-'),
286                      (60, 'C+'),
287                      (50, 'C'),
288                      (40, 'C-'),
289                      (0, ' D'),
290                      (-1, 'N/A'),
291                      (-100, 'COPY'),
292                      (-1000, 'FAIL'),
293                      )
294     score = models.IntegerField("本节成绩", choices=score_choices, default=-1)
295     homework_note = models.CharField(verbose_name='作业评语', max_length=255, blank=True, null=True)
296     note = models.CharField(verbose_name="备注", max_length=255, blank=True, null=True)
297 
298     homework = models.FileField(verbose_name='作业文件', blank=True, null=True, default=None)
299     stu_memo = models.TextField(verbose_name='学员备注', blank=True, null=True)
300     date = models.DateTimeField(verbose_name='提交日期', auto_now_add=True)
301 
302     def __str__(self):
303         return "{0}-{1}".format(self.course_record, self.student)
CRM表结构

 

 

  1. 销售

    a)  综合一段时间的销售业绩。根据每个销售的权重比,自动分配客户资源,最大化客户转化率。

               i.  实现机制:

          根据权重表中数据合理分配客户资源,配合客户表和客户分配表。

          分配时,按权重降序排序。按照分配人员数量,生成单次循环列表。每次分配列表结束,重新获取分配列表。

          在客户资源分配时,使用到redis来配合完成。好处是:减少数据库查询消耗和I/O开销,提高数据处理速度。并且使用事务绑定,建立回滚机制,确保合理分配。

 1 import redis
 2 from app01 import models
 3 from oomph_2.settings import SALE_ID_LIST, SALE_ID_LIST_ORIGIN, SALE_ID_RESET
 4 
 5 POOL = redis.ConnectionPool(host='192.168.20.150',port=6379)
 6 # host是服务端的,这个port是redis固定的,只要是连接redis的这个port就固定是6379,密码是服务端设置的。
 7 CONN = redis.Redis(connection_pool=POOL)
 8 
 9 
10 class AutoSale(object):
11 
12     @classmethod
13     def fetch_users(cls):
14         # [obj(销售顾问id,num),obj(销售顾问id,num),obj(销售顾问id,num),obj(销售顾问id,num),]
15         sales = models.SaleRank.objects.all().order_by('-weight')
16         print(sales)
17         sale_id_list = []
18         count = 0
19         while True:
20             flag = False
21             for row in sales:
22                 if count < row.num:
23                     sale_id_list.append(row.user_id)
24                     flag = True
25             count += 1
26             if not flag:
27                 break
28         print('SALE_ID_LIST===',sale_id_list)
29         if sale_id_list:
30             CONN.rpush(SALE_ID_LIST,*sale_id_list)        # 用来操作的数据
31             CONN.rpush(SALE_ID_LIST_ORIGIN,*sale_id_list) # 保留一份源数据
32             return True
33         return False
34 
35 
36     @classmethod
37     def get_sale_id(cls):
38         # 查看原来数据是否存在
39         
40         sale_id_origin_count = CONN.llen(SALE_ID_LIST_ORIGIN)
41         if not sale_id_origin_count:
42             # 取数据库中获取数据,并且赋值list_origin,pop数据
43             status = cls.fetch_users()
44             if not status:
45                 return None
46             user_id = CONN.lpop(SALE_ID_LIST)
47             if user_id:
48                 return user_id
49 
50             reset = CONN.get(SALE_ID_RESET)
51             if reset:
52                 CONN.delete(SALE_ID_LIST_ORIGIN)
53                 status = cls.fetch_users()
54                 if not status:
55                     return None
56                 CONN.delete(SALE_ID_RESET)
57                 return CONN.lpop(SALE_ID_LIST)
58             else:
59                 ct = CONN.llen(SALE_ID_LIST_ORIGIN)
60                 for i in range(ct):
61                     v = CONN.lindex(SALE_ID_LIST_ORIGIN,i)
62                     CONN.rpush(SALE_ID_LIST,v)
63                 return CONN.lpop('sale_list_id')
64         # if cls.rollback_list:
65         #     """
66         #     可能存在已经取到了销售,也分配了,可是在写进数据库的过程中出现了问题,
67         #     在customer.py里面,我们使用了事务,虽然事务回滚了,可是因为我们使用的是生成器,所以自己写了rollback方法
68         #     """
69         #     return cls.rollback_list.pop()
70         #
71         # if not cls.users:
72         #     cls.fetch_users()
73         # # 如果没有课程顾问,就返回None
74         # if not cls.users:
75         #     return None
76         #
77         # if not cls.iter_users:
78         #     cls.iter_users = iter(cls.users)
79         # try:
80         #     user_id = next(cls.iter_users)
81         # except StopIteration as e:
82         #     if cls.reset_status:
83         #         cls.fetch_users()
84         #         cls.reset_status = False
85         #     cls.iter_users = iter(cls.users)
86         #     user_id = cls.get_sale_id()
87         # return user_id
88     @classmethod
89     def reset(cls):
90         CONN.set(SALE_ID_RESET,1)
91 
92     @classmethod
93     def rollback(cls,nid):
94         # cls.rollback_list.insert(0,nid)
95         CONN.lpush(SALE_ID_LIST,nid)   # callback的id,往前面放
自动分配

    

    b)       销售与客户关系处理。

  该功能下,包括销售人员得到任务后对应的客户状态改为开始接洽,记录起始时间,订单状态从公司资源更改为销售人员的个人资源,其他人在订单转移前不可接触订单信息。
销售人员在跟进订单时,每一次与客户接洽都会在数据库中生成一条记录。
  若订单在十五日内被销售人员转化成功,则将该客户的状态由待转化变为转化成功,并在正式客户表中生成该客户的记录。在销售人员的订单记录中将这笔订单的状态改为转化成功。
若当前与客户接洽的销售人员三天未跟进订单或是在十五天内未促成交易。则相关订单信息会被移动到公司公共资源中,并且原先跟进订单的销售人员不可以选择继续跟进(直至该订单再次被移入公司公共资源)。原销售人员的订单跟进记录中会显示有一单未能转化,并显示原因(重新接手该订单后即使转化成功,本条记录不会被覆盖)。

  在我的客户的视图中,是可以查看当前销售所有的销售记录(已成单,未成单,以及未成单的订单流失原因)。

    实现机制:

      包含公共资源,抢单功能,当前销售个人客户界面,单条和批量导入客户信息。

      

import json,datetime
from django.conf.urls import url
from django.db.models import Q
from django.http import StreamingHttpResponse
from django.urls import reverse
from django.utils.safestring import mark_safe
from django.shortcuts import render,HttpResponse,redirect
from django.forms import ModelForm
from app01 import models
from cccccccccccccc import AutoSale
from stark.service import v1
from utils import message
from django.db import transaction
from app01.stark import BasePermissionclass SingleModelForm(ModelForm):class Meta:model = models.Customerexclude = ['consultant','status','recv_date','last_consult_date']class CustomerConfig(BasePermission,v1.StarkConfig):order_by = ['-status']def display_gender(self,obj=None,is_header=None):if is_header:return '性别'return obj.get_gender_display()def display_education(self,obj=None,is_header=None):if is_header:return '学历'return obj.get_education_display()def display_source(self,obj=None,is_header=None):if is_header:return '客户来源'return obj.get_source_display()def display_course(self,obj=None,is_header=None):if is_header:return '咨询课程'html = []obj_list = obj.course.all()for obj1 in obj_list:temp = "<a style='display:inline-block;padding:3px 5px;border:2px solid red;margin:2px;' href='/stark/app01/customer/%s/%s/dc/'>%s <span class='glyphicon glyphicon-trash'></span></a>" %(obj.pk,obj1.pk,obj1.name)html.append(temp)return mark_safe(''.join(html))def display_status(self,obj=None,is_header=None):"""客户状态是可以点击修改的:param obj::param is_header::return:"""if is_header:return '客户状态'# print('obj......',obj)   姓名:yy,QQ:123456return obj.get_status_display()def record(self,obj=None,is_header=None):"""客户跟进记录,http://127.0.0.1:8000/stark/app01/consultrecord/?customer=1"""if is_header:return '客户跟进记录'return mark_safe("<a href='/stark/app01/consultrecord/?customer=%s'>查看跟进记录</a>"%(obj.pk))list_display = ['name','referral_from',display_gender,display_education,display_source,display_course,display_status,record]edit_link = ['name']def delete_course(self,request,customer_id,course_id):"""删除当前用户感兴趣的课程:param request::param customer_id::param course_id::return:"""# print('self.model_class=,=',self.model_class.objects.all())# < QuerySet[ < Customer: 姓名:骚伟, QQ: 123456 >] >customer_obj = self.model_class.objects.filter(pk=customer_id).first()# 在多对多字段中可以remove,
        customer_obj.course.remove(course_id)# ####################作业:  删除完成跳转回来的时候,带着走的时候的url# self.request.GET# self._query_param_key# 构造QueryDict# urlencode()return redirect(self.get_list_url())def extra_url(self):app_model_name = (self.model_class._meta.app_label,self.model_class._meta.model_name,)patterns = [url(r'^(\d+)/(\d+)/dc/$', self.wrap(self.delete_course), name="%s_%s_dc" %app_model_name),url(r'^public/$', self.wrap(self.public_view), name="%s_%s_public" %app_model_name),url(r'^user/$', self.wrap(self.user_view), name="%s_%s_user" %app_model_name),url(r'^(\d+)/competition/$', self.wrap(self.competition_view),name = "%s_%s_competition" % app_model_name),url(r'^single/$', self.wrap(self.single_view), name="%s_%s_single" % app_model_name),url(r'^multi/$', self.wrap(self.multi_view), name="%s_%s_multi" % app_model_name),# url(r'^loadfiles/$', self.wrap(self.get_loadfiles_view), name="%s_%s_loadfiles" % app_model_name),
        ]return patternsdef public_view(self,request):"""公共客户资源, 未报名 & (15天未接单 or 三天未跟进)"""current_user_id = 9# 当前日期current_date = datetime.datetime.now().date()# 最后接单时间no_deal = current_date - datetime.timedelta(days=15)# 最后跟进日期no_follow_date = current_date - datetime.timedelta(days=3)"""公共客户"""# 方法一:# customer_list = models.Customer.objects.filter(Q(recv_date__lt=no_deal)|Q(last_consult_date__lt=no_follow),status=2)# customer_list = models.Customer.objects.filter(Q(recv_date__lt=no_deal)|Q(last_consult_date__lt=no_follow_date),status=2)# print('9999999999',customer_list)# 方法二:con = Q()q1 = Q(status=2)q2 = Q()q2.connector = 'OR'q2.children.append(('recv_date__lt', no_deal))q2.children.append(('last_consult_date__lt', no_follow_date))con.add(q1,'AND')con.add(q2,'AND')customer_list = models.Customer.objects.filter(con)return render(request, 'public_view.html', {'customer_list':customer_list, "current_user_id":current_user_id})def competition_view(self,request,cid):"""抢单表:param request::param cid: customer_id 来自于公共资源里面的抢单选项,public_view:return:"""current_user_id = 9 # 这个是从session中拿的"""抢单之后,它会修改客户表里面的: recv_date & last_consult_date & 课程顾问可以抢单的前提条件是: 之前的顾问不是自己,状态必须是未报名,并且满足3/15的要求"""current_date = datetime.datetime.now().date()no_deal = current_date - datetime.timedelta(days=15) # 最后接单日期no_follow_date = current_date - datetime.timedelta(days=3) # 最后跟进日期# 更新数据row_count = models.Customer.objects.filter(Q(recv_date__lt=no_deal) | Q(last_consult_date__lt=no_follow_date),status=2,id=cid).exclude(consultant_id=current_user_id).update(recv_date=current_date,last_consult_date=current_date,consultant_id=current_user_id)if not row_count:return HttpResponse('配嘛...')# 如果存在的话,我们就把它添加到客户分配表CustomerDistribution里面models.CustomerDistribution.objects.create(ctime=current_date,customer_id=cid,user_id=current_user_id)return HttpResponse('嗯嗯,归你归你')def user_view(self,request):"""当前登录用户的所有的客户(在我这成单的,以及我正在跟进的)"""current_user_id = 9customer_list = models.CustomerDistribution.objects.filter(user_id=current_user_id).order_by('status')return render(request, 'user_view.html', {'customer_list':customer_list})def single_view(self,request):"""单条录入客户信息"""if request.method == 'GET':form = SingleModelForm()return render(request, 'single_view.html', {'form':form})else:current_date = datetime.datetime.now().date()form = SingleModelForm(request.POST)if form.is_valid():print(form.cleaned_data)sale_id = AutoSale.get_sale_id()if not sale_id:return HttpResponse('暂无课程顾问,请添加后再分配')try:with transaction.atomic():# 客户表保存form.instance.consultant_id = sale_idform.instance.recv_date = current_dateform.instance.last_consult_date = current_datenew_customer = form.save()  #  将数据添加到客户表。这就算创建完成了# 将关系添加到客户分配表models.CustomerDistribution.objects.create(customer=new_customer,ctime=current_date,user_id=sale_id)# 发送邮件信息# message.send_message('17701335022@163.com','saofei','fk','fk you')except Exception as e:# 创建客户和分配销售异常
                    AutoSale.rollback(sale_id)return HttpResponse('录入异常')return  HttpResponse('录入成功')else:return render(request, 'single_view.html', {'form':form})def multi_view(self,request):"""批量导入"""if request.method == 'GET':return render(request, 'multi_view.html')else:from django.core.files.uploadedfile import InMemoryUploadedFilefile_obj = request.FILES.get('exfile')# 老方法: 对应218# print(file_obj,type(file_obj))# with open('submit.xlsx','wb') as f :#     for chunk in file_obj:#         f.write(chunk)import xlrd# 不再创建xlsx文件workbook = xlrd.open_workbook(file_contents=file_obj.read())# 老方法: 先写再读# workbook = xlrd.open_workbook('submit.xlsx')sheet = workbook.sheet_by_index(0) # 这个当前sheet索引为0的 表单页# print(sheet.nrows) # 当前共有多少行maps = {0 : 'name',1 : 'qq',}row_dict = {}for index in range(1,sheet.nrows): # 第一行是标题,从索引为1的开始拿数据row = sheet.row(index)# [text: 'zz', number: 1123.0]# [text: 'aa', number: 23.0]# 拿到的是列表,可是我们要转化成字典# {text: 'zz', number: 1123.0}for i in range(len(maps)):key = maps[i]cell = row[i]row_dict[key] = cell.valueprint(row_dict)# 获取客户id,录入客户表,录入分配表current_date = datetime.datetime.now().date()sale_id = AutoSale.get_sale_id()print('236',sale_id)if not sale_id:return HttpResponse('暂无课程顾问,请添加后再执行操作!')try:with transaction.atomic():# 客户表保存new_customer = models.Customer.objects.create(**row_dict,consultant_id=sale_id,recv_date=current_date,last_consult_date=current_date)print(new_customer)# 将关系添加到客户分配表models.CustomerDistribution.objects.create(customer=new_customer,ctime=current_date,user_id=sale_id)# 发送邮件信息# message.send_message('17701335022@163.com','saofei','fk','fk you')print(new_customer)except Exception as e:# 创建客户和分配销售异常
                    AutoSale.rollback(sale_id)return HttpResponse('录入异常')return  HttpResponse('录入成功')else:return render(request, 'multi_view.html')# # 为用户提供模版# def download_file(request):#     # do something#
    #     the_file_name='11.png'             #显示在弹出对话框中的默认的下载文件名#     filename='media/uploads/11.png'    #要下载的文件路径#     response=StreamingHttpResponse(readFile(filename))#     response['Content-Type']='application/octet-stream'#     response['Content-Disposition']='attachment;filename="{0}"'.format(the_file_name)#     return response# def readFile(filename,chunk_size=512):#     with open(filename,'rb') as f:#         while True:#             c=f.read(chunk_size)#             if c:#                 yield c#             else:#                 break

v1.site.register(models.Customer,CustomerConfig)
公共资源、抢单、当前个人客户、单条和批量导入(信息提醒、使用excel导入客户记录)

 

2. 管理

  涉及到两张表,StudyRecord & CourseRecord。参照表结构,查看相关字段。

   a)  在成功的将客户转化为学员后,根据其所填选的具体信息如:校区,课程,以及初填信息。并且初始化学员账号信息,方便学员可以登录该系统查看自己的学分以及考勤记录(此处用到了Highcharts,任课老师也可查看学生相关信息)。

  

 1 {% load staticfiles %}
 2 <!DOCTYPE html>
 3 <html lang="en">
 4 <head>
 5     <meta charset="UTF-8">
 6     <title>Title</title>
 7 </head>
 8 <body>
 9 
10 <h2>所在班级</h2>
11 <ul class="fa-list-ul" id="classList" sid="{{ obj.sid }}">
12     {% for obj in class_list %}
13         <li cid="{{ obj.id }}">{{ obj.course.name }}({{ obj.semester }}期)</li>
14     {% endfor %}
15 </ul>
16 <h2>成绩图</h2>
17 <div id="container" style="width:600px;height:400px"></div>
18 
19 <script src="{% static '/stark/bootstrap/js/jquery-3.1.1.js' %}"></script>
20 <script src="{% static 'highcharts.js' %}"></script>
21 <script>
22 $(function () {
23     $('#classList li').click(function () {
24         var cid = $(this).attr('cid');
25         var sid = $('#classList').attr('sid');
26         $.ajax({
27             url :' /stark/app01/student/chart',
28             type : 'get',
29             data : {'cid':cid,'sid':sid},
30             dataType : 'JSON',
31             success:function (arg) {
32                         var config = {
33                             chart: {
34                                 type: 'column'
35                             },
36                             title: {
37                                 text: '学生成绩'
38                             },
39                             subtitle: {
40                                 text: '数据截止 2017-03,来源: <a href="https://en.wikipedia.org/wiki/List_of_cities_proper_by_population">Wikipedia</a>'
41                             },
42                             xAxis: {
43                                 type: 'category',
44                                 labels: {
45                                     rotation: -45,
46                                     style: {
47                                         fontSize: '13px',
48                                         fontFamily: 'Verdana, sans-serif'
49                                     }
50                                 }
51                             },
52                             yAxis: {
53                                 min: 0,
54                                 title: {
55                                     text: '分数'
56                                 }
57                             },
58                             legend: {
59                                 enabled: false
60                             },
61                             tooltip: {
62                                 pointFormat: '成绩: <b>{point.y} </b>'
63                             },
64                             series: [{
65                                 name: '成绩',
66                                 data: [],
67                                 dataLabels: {
68                                     enabled: true,
69                                     rotation: -90,
70                                     color: '#FFFFFF',
71                                     align: 'right',
72                                     format: '{point.y}', // one decimal
73                                     y: 10, // 10 pixels down from the top
74                                     style: {
75                                         fontSize: '13px',
76                                         fontFamily: 'Verdana, sans-serif'
77                                     }
78                                 }
79                             }]
80                         };
81 
82                         config.series[0].data =arg.data;
83 
84                         //$('#container').highcharts(config);
85                         Highcharts.chart('container',config);
86 
87                     }
88                 })
89             })
90         })
91 
92 
93 </script>
94 </body>
95 </html>
scores_view

 

   b) 出勤记录:任课教师可初始化学员出勤信息,并支持批量修改。

    学员哪天进入班级,就从哪天开始生成记录。考勤与成绩挂钩。

    实现机制:

      

 1     def mutil_init(self,request):
 2         """自定义批量初始化方法"""
 3         # 上课记录id列表
 4         pk_list = request.POST.getlist('pk')
 5         # 上课记录对象列表
 6         record_list = models.CourseRecord.objects.filter(id__in=pk_list)
 7         # print(record_list)
 8         # # 这种是,遍历每一个学生,查看是否存在记录。
 9         # for record in record_list:
10         #     student_list = models.Student.objects.filter(class_list=record.class_obj)
11         #     bulk_list = []
12         #     for student in student_list:
13         #         exists = models.StudyRecord.objects.filter(student=student,course_record=record).exists()
14         #         if exists:
15         #             continue
16         #         bulk_list.append(models.StudyRecord(student=student,course_record=record))
17         #     models.StudyRecord.objects.bulk_create(bulk_list)
18 
19         # 下面这种是,只要有当天的学习记录,后面不管还有没有学生来,都不能添加
20         for record in record_list:
21             if models.StudyRecord.objects.filter(course_record=record).exists():
22                 continue
23             student_list = models.Student.objects.filter(class_list=record.class_obj)
24             # 为每一个学生创建dayn的学习记录
25             bulk_list = []
26             for student in student_list:
27                 bulk_list.append(models.StudyRecord(student=student,course_record=record))
28             models.StudyRecord.objects.bulk_create(bulk_list)
29         # return redirect('/stark/app01/courserecord/')
30         return HttpResponse('初始化成功!')
31 
32     show_actions = True
33     mutil_init.short_desc = "学生初始化"
34     actions = [mutil_init,] # 因为这个是批量操作,咱们需要写点方法,里面是我们要实现的东西,所以函数
批量初始化

  c) 录入成绩:

    这里是通过点击在课程记录页面,我们手动生成的查看成绩的a标签,进入到成绩录入界面的。通过当前点击的课程记录的id,拿到学习记录对象,再进行编辑。

    并且,因为要使前端拿到每个学习记录id,我们就需要想办法将值传过去。

    在这使用了type来动态创建类。

    fields字段,写了一个score_学习记录id,还有一个home_note_学习记录id。该方法下,其实是由默认值的,so 在传值的时候回有initial。并且post请求时,是用的update

 1     def score_list(self,request,record_id):
 2         """
 3         录入成绩页面
 4         :param request:
 5         :param record_id: 老师上课记录ID
 6         :return:
 7         """
 8         if request.method == 'GET':
 9             from django.forms import Form
10             from django.forms import fields
11             from django.forms import widgets
12             ##方式一
13             # study_record_list = models.StudyRecord.objects.filter(course_record_id=record_id)  #这一天上课的所有的学生的学习记录
14             # score_choices = models.StudyRecord.score_choices
15             # return render(request,"score_list.html",{"study_record_list":study_record_list,"score_choices":score_choices})
16 
17             # 改款
18             # class TestForm(Form):
19             #     score = fields.ChoiceField(choices=models.StudyRecord.record_choices)
20             #     homeword_note = fields.CharField(widget=widgets.Textarea())
21 
22             # 因为前端要拿到id和对象,所以使用type来创建,来自定义字段
23             study_record_list = models.StudyRecord.objects.filter(course_record_id=record_id)
24             data = []
25             for obj in study_record_list:
26                 TestForm = type('TempForm',(Form,),{
27                     'score_%s'%obj.pk : fields.ChoiceField(choices=models.StudyRecord.score_choices),
28                     'homework_note_%s'%obj.pk : fields.CharField(widget=widgets.Textarea())
29                 })
30                 data.append({'obj':obj,'form':TestForm(initial={'score_%s' %obj.pk:obj.score,'homework_note_%s' %obj.pk:obj.homework_note})})
31             return render(request,'score_list.html',{'data':data})
32         else:
33             data_dict = {}
34             """
35             构造这样的字典,目的是保存更新数据库里面的数据,字典的结构的
36             {
37                 3:{"score":2,"homework_note":2}
38                 4:{"score":4,"homework_note":4}
39             }
40             """
41             for key, value in request.POST.items():
42                 if key == "csrfmiddlewaretoken":
43                     continue
44                 name, nid = key.rsplit('_', 1)
45                 if nid in data_dict:
46                     data_dict[nid][name] = value
47                 else:
48                     data_dict[nid] = {name: value}
49 
50             for nid, update_dict in data_dict.items():
51                 print(data_dict.items())
52                 models.StudyRecord.objects.filter(id=nid).update(**update_dict)
53 
54             return redirect(request.path_info)
score_list
 1 {% load staticfiles %}
 2 <!DOCTYPE html>
 3 <html lang="en">
 4 <head>
 5     <meta charset="UTF-8">
 6     <title>成绩录入</title>
 7     <link rel="stylesheet" href="{% static 'stark/bootstrap/css/bootstrap.css' %}">
 8 </head>
 9 <body>
10 <h2>自定义成绩录入</h2>
11 <form method="post">
12     {% csrf_token %}
13     <table class="table">
14         {% for row in data %}
15             <tr>
16             <td>{{ row.obj.course_record }}</td>
17             <td>{{ row.obj.student }}</td>
18             <td>{{ row.obj.get_record_display }}</td>
19             {% for field in row.form %}
20                 <td>{{ field }}</td>
21             {% endfor %}
22             </tr>
23         {% endfor %}
24 
25     </table>
26     <input type="submit" value="保存">
27 </form>
28 
29 </body>
30 </html>
score_list.HTML

 

3. 会议室预定

  开发背景:公司在壮大,各部门大小事也变得更多。你们部门要不要开个会互相解决一群大龄男青年的问题嘛。要的话,你要不要有次序的来用会议室嘛?要的话,你是不是要预定一下嘛?要的话,会议室预定要不要做一个嘛。。。来,交给程序来完成。

  

会议室预定表结构设计

  具体实现:    记得要绑定事务!!!

       第一步: 获取当天的预定信息。

       第二步: 生成会议室信息。 在生成会议室信息里面,需要区分当前预定是否是自己预定的。自己订的可以取消,但是不能取消非自己预定的会议室信息。

       第三步提交: 点击提交后,首先要判断日期是否正确,只能预定大于当前时间之后的会议室信息。并且,要区分一下是新增还是修改。

       第四部删除: 这里需要用到Q查询,组合多个条件来实现。

   

 1 # 删除会议室预定信息
 2             from django.db.models import Q
 3             remove_booking = Q()
 4             for room_id, time_id_list in post_data['DEL'].items():
 5                 for time_id in time_id_list:
 6                     temp = Q()
 7                     temp.connector = 'AND'
 8                     temp.children.append(('user_id',2,))
 9                     temp.children.append(('date', choice_date))
10                     temp.children.append(('room_id', room_id,))
11                     temp.children.append(('time_id', time_id,))
12 
13                     remove_booking.add(temp, 'OR')
14             if remove_booking:
15                 models.Book.objects.filter(remove_booking).delete()

 

4: 调查问卷

参照:Django_调查问卷

 

5:最骚的东西:RBAC   会单独总结。

  已经在写了。

  链接:RBAC   编写流程在ReadMe里面。

 

转载于:https://www.cnblogs.com/ugfly/p/8199269.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/255755.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

js变量和数据类型

转载于:https://www.cnblogs.com/songyinan/p/6181421.html

(九)模板方法模式详解(包含与类加载器不得不说的故事)

作者&#xff1a;zuoxiaolong8810&#xff08;左潇龙&#xff09;&#xff0c;转载请注明出处&#xff0c;特别说明&#xff1a;本博文来自博主原博客&#xff0c;为保证新博客中博文的完整性&#xff0c;特复制到此留存&#xff0c;如需转载请注明新博客地址即可。 模板方法模…

阿里云openapi接口使用,PHP,视频直播

1.下载sdk放入项目文件夹中 核心就是aliyun-php-sdk-core&#xff0c;它的配置文件会自动加载相应的类 2.引入文件 include_once LIB_PATH . ORG/aliyun-openapi/aliyun-php-sdk-core/Config.php; 3.配置客户端对象,需要Access Key ID,Access Key Secret $iClientProfile Defa…

Android之网络编程利用PHP操作MySql插入数据(四)

因为最近在更新我的项目&#xff0c;就想着把自己在项目中用到的一些的简单的与网络交互的方法总结一下&#xff0c;所以最近Android网络编程方面的博文会比较多一些&#xff0c;我尽量以最简单的方法给大家分享&#xff0c;让大家明白易懂。如果有什么不对的地方&#xff0c;还…

【常用网址】——opencv等

opencv官网Releases - OpenCVhttps://opencv.org/releases/

(五):C++分布式实时应用框架——微服务架构的演进

C分布式实时应用框架——微服务架构的演进 技术交流合作QQ群&#xff1a;436466587 欢迎讨论交流 上一篇&#xff1a;(四)&#xff1a;C分布式实时应用框架——状态中心模块 版权声明:本文版权及所用技术归属smartguys团队所有&#xff0c;对于抄袭&#xff0c;非经同意转载等…

Robot Application Builder

软件开发工具包 Robot Application Builder是安装在PC机&#xff08;Windows 2000或Windows XP操作系统&#xff09;上的一种独立开发工具&#xff0c;可用于创建运行于ABB FlexPendant示教器或PC机上的定制化操作界面。为此&#xff0c;该软件包由以下两部分组成&#xff1a;…

asp.net model 验证和取出 ErrorMessage 信息

为什么80%的码农都做不了架构师&#xff1f;>>> public class Users{public int Id { get; set; }public string Name { get; set; }[Required(ErrorMessage "邮箱不能为空")][EmailAddressAttribute(ErrorMessage "邮箱格式不正确")]public…

【pyqt5学习——信号与槽】实例计时器(解决界面卡顿问题)

目录 一、方法一&#xff1a;另开线程 1、什么是信号与槽 1&#xff09;GUI控件&#xff08;信号&#xff09;与槽 2&#xff09;自定义信号与槽 2、实战1&#xff1a;计时器&#xff08;不自定义信号槽和不使用多线程&#xff09; 1&#xff09;界面设计——利用qt-desi…

【数据库学习笔记】——创建数据库连接对象connection

目录 connect函数的参数 创建连接对象连接MySQL代码 连接对象常见属性与方法 事务名词解释 课程视频链接&#xff1a; 第14节 Python操作数据库_哔哩哔哩_bilibili666https://www.bilibili.com/video/BV1q54y147KX?fromsearch&seid968950907021994347&spm_id_from3…

【数据库学习笔记】——cursor游标对象

目录 1、创建cursor对象 2、cursor对象常用方法 3、操作数据库的常见流程&#xff08;五部曲&#xff09; 课程视频链接&#xff1a; 第14节 Python操作数据库_哔哩哔哩_bilibili666https://www.bilibili.com/video/BV1q54y147KX?p2&spm_id_frompageDriver 1、创建cu…

基于ARM核AT75C220在指纹识别系统中应用

纹识别技术近年来逐渐成熟&#xff0c;在门禁、安防和金融等方面得到了越来越广泛的应用。典型的指纹识别系统是以指纹传感器和DSP处理器为核心构成。指纹传感器采集指纹图像&#xff0c;DSP处理器实时实现指纹识别算法。同时&#xff0c;通常的指纹识别系统还具有较强的通信能…

【数据库学习笔记】——创建数据库文件

目录 1、数据库基础知识 2、创建数据库文件 1、数据库基础知识 什么是数据库&#xff1f;——数据库是存放多个数据表的仓库&#xff0c;数据表可以理解为是二维数组 如上表所示&#xff0c;每个表由行列组成&#xff1a; 字段&#xff1a;每一列称之为一个字段&#xff0c;每…

浅谈工业机器人的运动停止

德系的工业机器人系统中&#xff0c;对于机器人停止运动&#xff0c;定义了3种模式&#xff0c;比如 KUKA 的工业机器人分别定义了 Stop 0 &#xff0c;Stop 1&#xff0c;Stop 2 (*注1)。这种定义模式是与机器人的机械结构和电气结构相关联的。 对于此&#xff0c;读者应该先了…

【数据库学习】——数据库可视化--Navicat下载安装连接教程

目录 进入网站 下载可视化软件 安装 双击桌面图标&#xff0c;选择试用 连接数据库 查看数据库中的表&#xff0c;如下所示 常用数据库有&#xff1a; MySQL、sqlite等 进入网站 Navicat | 产品https://www.navicat.com.cn/products 下载可视化软件 这里以数据库sql…

C# 格式化字符串

原文地址&#xff1a;http://www.cnblogs.com/zyh-nhy/archive/2007/10/11/921240.html 1 前言如果你熟悉Microsoft Foundation Classes&#xff08;MFC&#xff09;的CString&#xff0c;Windows Template Library&#xff08;WTL&#xff09;的CString或者Standard Template …

【数据库学习笔记】——操作sqlite(增删改查)以及cursor的方法介绍

目录 1、sqlite数据库介绍 1&#xff09;常见的数据库操作 2&#xff09;数据操作常见步骤 2、向数据表中增加数据&#xff08;insert into&#xff09; 1&#xff09;向数据表中添加一条记录 2&#xff09;向数据表中一次性添加多条记录 ​ 3、修改数据表中已有的数据 1)…

【sqlite常用操作SQL语句】

目录 1、创建一个新的数据表 2、在已有的数据表中增加一个新的字段&#xff08;列&#xff09; 3、 在已有的数据表中增加一条新的记录&#xff08;行&#xff09; 1、创建一个新的数据表 "create table user(id int(11) primary key, name varchar(20))" 含义&…

centos6虚拟机复制后修改网卡

方法1&#xff1a; 使用vmware创建centos6.4虚拟机&#xff0c; 创建完成后复制该虚拟机&#xff0c; 打开复制的虚拟机发现网卡名字是eth1&#xff0c;而网卡配置文件为eth0&#xff0c;mac地址变了 这时修改网卡配置文件&#xff0c; 删除uuid&#xff0c;修改deivce为eth1&a…

【pyinstaller打包pyqt5编写的项目为exe(脱离环境可运行)】

目录 下载pyinstaller库 0、pyinstaller语句介绍 1、单个py文件打包成exe 1)只有py文件 假设只有一个py文件&#xff1a;pyinstaller -F xxx.py 加上图标&#xff1a;pyinstaller -F xxx.py -i xxx.ico 取消命令行窗口:pyinstaller -F -w xxx.py -i xxx.ico 2) 不但有py…