基於Django的教育培訓CRM系統(一):表結構設計

起步

在文華學院讀大二時,接觸到一名華中科技大學的學長,他當時在搞少兒編程教育的創業項目,他邀請我作爲兼職講師加入到他們的團隊裏,我答應了。我們的團隊當時還比較小,專攻線下市場,後來隨着規模的擴大,管理上就有點力不從心。我那時正好在學習Django,所以就打算做一套系統,供我們的項目團隊使用,雖然後面離開了這個團隊,系統也一直沒有交付,但還是有所收穫。

需求分析

作爲一個供教育培訓機構使用的CRM系統,前期主要是調研這個系統的需求是什麼,前期梳理表結構如下,後期可以根據市場需要自行擴展:

作爲招生人員(銷售):

  1. 銷售註冊,登錄系統
  2. 銷售添加客戶信息,成爲銷售的私戶
  3. 銷售固定時間跟進客戶
  4. 客戶報名
  5. 銷售審覈報名,學生進行繳費
  6. 銷售將錢交給財務,財務對繳費記錄進行審覈
  7. 自動修改學生狀態
  8. 續費通知

作爲班主任:

  1. 每天創建課程記錄,記錄每天上課的情況
  2. 再根據課程記錄,生成學生的學習記錄,修改考勤情況
  3. 調班/退班
  4. 教學情況統計

作爲講師:

  1. 查看/申請變更排課日程
  2. 課時費用結算

作爲學員(少兒嘛,一般是家長看):

  1. 查看課程表
  2. 查看學習情況

作爲機構管理者:

  1. 運營分析報表

  • 用戶表
  • 客戶表
  • 跟進記錄表
  • 報名記錄表
  • 班級表
  • 校區表
  • 合同表
  • 繳費記錄表
  • 課程記錄表
  • 學習記錄表
  • 部門表

在這裏插入圖片描述


表結構設計

class UserProfile(AbstractBaseUser, PermissionsMixin):
    """
    用戶表
    """
    username = models.EmailField(
        max_length=255,
        unique=True,
    )
    is_staff = models.BooleanField(
        _('staff status'),
        default=False,
        help_text=_('Designates whether the user can log into this admin site.'),
    )
    is_admin = models.BooleanField(default=False)
    name = models.CharField('名字', max_length=32)
    department = models.ForeignKey('Department', default=None, blank=True, null=True)
    mobile = models.CharField('手機', max_length=32, default=None, blank=True, null=True)

    memo = models.TextField('備註', blank=True, null=True, default=None)
    date_joined = models.DateTimeField(auto_now_add=True)

    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = ['name']

    class Meta:
        verbose_name = '賬戶信息'
        verbose_name_plural = "賬戶信息"

    def get_full_name(self):
        # The user is identified by their email address
        return self.name

    def get_short_name(self):
        # The user is identified by their email address
        return self.username

    def __str__(self):  # __unicode__ on Python 2
        return self.username

    def has_perm(self, perm, obj=None):
        #     "Does the user have a specific permission?"
        # Simplest possible answer: Yes, always

        if self.is_active and self.is_superuser:
            return True
        return _user_has_perm(self, perm, obj)

    def has_perms(self, perm_list, obj=None):
        #     "Does the user have a specific permission?"
        # Simplest possible answer: Yes, always
        for perm in perm_list:
            if not self.has_perm(perm, obj):
                return False
        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
        if self.is_active and self.is_superuser:
            return True

        return _user_has_module_perms(self, app_label)

    objects = UserManager()
class Customer(models.Model):
    """
    客戶表
    """
    qq = models.CharField('QQ', max_length=64, unique=True, help_text='QQ號必須唯一')
    qq_name = models.CharField('QQ暱稱', max_length=64, blank=True, null=True)
    name = models.CharField('姓名', max_length=32, blank=True, null=True, help_text='學員報名後,請改爲真實姓名')
    sex_type = (('male', '男'), ('female', '女'))
    sex = models.CharField("性別", choices=sex_type, max_length=16, default='male', blank=True, null=True)
    birthday = models.DateField('出生日期', default=None, help_text="格式yyyy-mm-dd", blank=True, null=True)
    phone = models.BigIntegerField('手機號', blank=True, null=True)
    source = models.CharField('客戶來源', max_length=64, choices=source_type, default='qq')
    introduce_from = models.ForeignKey('self', verbose_name="轉介紹自學員", blank=True, null=True)
    course = MultiSelectField("諮詢課程", choices=course_choices)
    class_type = models.CharField("班級類型", max_length=64, choices=class_type_choices, default='fulltime')
    customer_note = models.TextField("客戶備註", blank=True, null=True, )
    status = models.CharField("狀態", choices=enroll_status_choices, max_length=64, default="unregistered",
                              help_text="選擇客戶此時的狀態")
    network_consult_note = models.TextField(blank=True, null=True, verbose_name='網絡諮詢師諮詢內容')
    date = models.DateTimeField("諮詢日期", auto_now_add=True)
    last_consult_date = models.DateField("最後跟進日期", auto_now_add=True)
    next_date = models.DateField("預計再次跟進時間", blank=True, null=True)
    network_consultant = models.ForeignKey('UserProfile', blank=True, null=True, verbose_name='諮詢師',
                                           related_name='network_consultant')
    consultant = models.ForeignKey('UserProfile', verbose_name="銷售", related_name='customers', blank=True, null=True, )
    class_list = models.ManyToManyField('ClassList', verbose_name="意向班級",blank=True )
class ConsultRecord(models.Model):
    """
    跟進記錄表
    """
    customer = models.ForeignKey('Customer', verbose_name="所諮詢客戶")
    note = models.TextField(verbose_name="跟進內容...")
    status = models.CharField("跟進狀態", max_length=8, choices=seek_status_choices, help_text="選擇客戶此時的狀態")
    consultant = models.ForeignKey("UserProfile", verbose_name="跟進人", related_name='records')
    date = models.DateTimeField("跟進日期", auto_now_add=True)
    delete_status = models.BooleanField(verbose_name='刪除狀態', default=False)
class Enrollment(models.Model):
    """
    報名表
    """

    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("我已認真閱讀完培訓協議並同意全部協議內容", default=False)
    contract_approved = models.BooleanField("審批通過", help_text="在審閱完學員的資料無誤後勾選此項,合同即生效", default=False)
    enrolled_date = models.DateTimeField(auto_now_add=True, verbose_name="報名日期")
    memo = models.TextField('備註', blank=True, null=True)
    delete_status = models.BooleanField(verbose_name='刪除狀態', default=False)
    customer = models.ForeignKey('Customer', verbose_name='客戶名稱')
    school = models.ForeignKey('Campuses',verbose_name='校區')
    enrolment_class = models.ForeignKey("ClassList", verbose_name="所報班級")

    class Meta:
        unique_together = ('enrolment_class', 'customer')
class ClassList(models.Model):
    """
    班級表
    """
    course = models.CharField("課程名稱", max_length=64, choices=course_choices)
    semester = models.IntegerField("學期")
    campuses = models.ForeignKey('Campuses', verbose_name="校區")
    price = models.IntegerField("學費", default=10000)
    memo = models.CharField('說明', blank=True, null=True, max_length=100)
    start_date = models.DateField("開班日期")
    graduate_date = models.DateField("結業日期", blank=True, null=True)
    contract = models.ForeignKey('ContractTemplate', verbose_name="選擇合同模版", blank=True, null=True)
    teachers = models.ManyToManyField('UserProfile', verbose_name="老師")
    class_type = models.CharField(choices=class_type_choices, max_length=64, verbose_name='班額及類型', blank=True,
                                  null=True)

    class Meta:
        unique_together = ("course", "semester", 'campuses')
class Campuses(models.Model):
    """
    校區表
    """
    name = models.CharField(verbose_name='校區', max_length=64)
    address = models.CharField(verbose_name='詳細地址', max_length=512, blank=True, null=True)
class ContractTemplate(models.Model):
    """
    合同模板表
    """
    name = models.CharField("合同名稱", max_length=128, unique=True)
    content = models.TextField("合同內容")
    date = models.DateField(auto_now=True)
class PaymentRecord(models.Model):
    """
    繳費記錄表
    """
    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)
    course = models.CharField("課程名", choices=course_choices, max_length=64, blank=True, null=True, default='N/A')
    class_type = models.CharField("班級類型", choices=class_type_choices, max_length=64, blank=True, null=True,
                                  default='N/A')
    enrolment_class = models.ForeignKey('ClassList', verbose_name='所報班級', blank=True, null=True)
    customer = models.ForeignKey('Customer', verbose_name="客戶")
    consultant = models.ForeignKey('UserProfile', verbose_name="銷售")
    delete_status = models.BooleanField(verbose_name='刪除狀態', default=False)

    status_choices = (
        (1, '未審覈'),
        (2, '已審覈'),
    )
    status = models.IntegerField(verbose_name='審覈', default=1, choices=status_choices)

    confirm_date = models.DateTimeField(verbose_name="確認日期", null=True, blank=True)
    confirm_user = models.ForeignKey(verbose_name="確認人", to='UserProfile', related_name='confirms', null=True,
                                     blank=True)
class CourseRecord(models.Model):
    """課程記錄表"""
    day_num = models.IntegerField("節次", help_text="此處填寫第幾節課或第幾天課程...,必須爲數字")
    date = models.DateField(auto_now_add=True, verbose_name="上課日期")
    course_title = models.CharField('本節課程標題', max_length=64, blank=True, null=True)
    course_memo = models.TextField('本節課程內容', max_length=300, blank=True, null=True)
    has_homework = models.BooleanField(default=True, verbose_name="本節有作業")
    homework_title = models.CharField('本節作業標題', max_length=64, blank=True, null=True)
    homework_memo = models.TextField('作業描述', max_length=500, blank=True, null=True)
    scoring_point = models.TextField('得分點', max_length=300, blank=True, null=True)
    re_class = models.ForeignKey('ClassList', verbose_name="班級")
    teacher = models.ForeignKey('UserProfile', verbose_name="班主任")

    class Meta:
        unique_together = ('re_class', 'day_num')
class StudyRecord(models.Model):
    """
    學習記錄
    """

    attendance = models.CharField("考勤", choices=attendance_choices, default="checked", max_length=64)
    score = models.IntegerField("本節成績", choices=score_choices, default=-1)
    homework_note = models.CharField(max_length=255, verbose_name='作業批語', blank=True, null=True)
    date = models.DateTimeField(auto_now_add=True)
    note = models.CharField("備註", max_length=255, blank=True, null=True)
    homework = models.FileField(verbose_name='作業文件', blank=True, null=True, default=None)
    course_record = models.ForeignKey('CourseRecord', verbose_name="某節課程")
    student = models.ForeignKey('Customer', verbose_name="學員")

    class Meta:
        unique_together = ('course_record', 'student')
class Department(models.Model):
    """
    部門表
    """
    name = models.CharField(max_length=32, verbose_name="部門名稱")
    count = models.IntegerField(verbose_name="人數", default=0)
    """
    字段選擇元組
    """
course_choices = (('Block', '智能積木'),
                  ('Scratch', 'scratch編程'),
                  ('Car', '智能小車'),
                  ('Python', 'python編程'),
                  ('Html', 'HTML網頁編程'),
                  ('Aircraft ', '無人機'),)

class_type_choices = (('fri', '週五班',),
                      ('sat', '週六班'),
                      ('sun', '週日班',),)

source_type = (('qq', "qq羣"),
               ('referral', "內部轉介紹"),
               ('website', "官方網站"),
               ('baidu_ads', "百度推廣"),
               ('office_direct', "直接上門"),
               ('WoM', "口碑"),
               ('public_class', "公開課"),
               ('website_luffy', "智酷官網"),
               ('others', "其它"),)

enroll_status_choices = (('signed', "已報名"),
                         ('unregistered', "未報名"),
                         ('studying', '學習中'),
                         ('paid_in_full', "學費已交齊"))

seek_status_choices = (('A', '近期無報名計劃'), ('B', '1個月內報名'), ('C', '2周內報名'), ('D', '1周內報名'),
                       ('E', '定金'), ('F', '到班'), ('G', '全款'), ('H', '無效'),)
pay_type_choices = (('deposit', "訂金/報名費"),
                    ('tuition', "學費"),
                    ('transfer', "轉班"),
                    ('dropout', "退學"),
                    ('refund', "退款"),)

attendance_choices = (('checked', "已簽到"),
                      ('vacate', "請假"),
                      ('late', "遲到"),
                      ('absence', "缺勤"),
                      ('leave_early', "早退"),)

score_choices = ((100, 'A+'),
                 (90, 'A'),
                 (85, 'B+'),
                 (80, 'B'),
                 (70, 'B-'),
                 (60, 'C+'),
                 (50, 'C'),
                 (40, 'C-'),
                 (0, ' D'),
                 (-1, 'N/A'),
                 (-100, 'COPY'),
                 (-1000, 'FAIL'),)
class UserManager(BaseUserManager):
    """
    系統配置相關
    """
    use_in_migrations = True

    def _create_user(self, username, password, **extra_fields):
        """
        Creates and saves a User with the given username, email and password.
        """
        if not username:
            raise ValueError('The given username must be set')
        username = self.normalize_email(username)
        username = self.model.normalize_username(username)
        user = self.model(username=username, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_user(self, username, password=None, **extra_fields):
        extra_fields.setdefault('is_staff', False)
        extra_fields.setdefault('is_superuser', False)
        return self._create_user(username, password, **extra_fields)

    def create_superuser(self, username, password, **extra_fields):
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)

        if extra_fields.get('is_staff') is not True:
            raise ValueError('Superuser must have is_staff=True.')
        if extra_fields.get('is_superuser') is not True:
            raise ValueError('Superuser must have is_superuser=True.')

        return self._create_user(username, password, **extra_fields)


# A few helper functions for common logic between User and AnonymousUser.
def _user_get_all_permissions(user, obj):
    permissions = set()
    for backend in auth.get_backends():
        if hasattr(backend, "get_all_permissions"):
            permissions.update(backend.get_all_permissions(user, obj))
    return permissions


def _user_has_perm(user, perm, obj):
    """
    A backend can raise `PermissionDenied` to short-circuit permission checking.
    """
    for backend in auth.get_backends():
        if not hasattr(backend, 'has_perm'):
            continue
        try:
            if backend.has_perm(user, perm, obj):
                return True
        except PermissionDenied:
            return False
    return False


def _user_has_module_perms(user, app_label):
    """
    A backend can raise `PermissionDenied` to short-circuit permission checking.
    """
    for backend in auth.get_backends():
        if not hasattr(backend, 'has_module_perms'):
            continue
        try:
            if backend.has_module_perms(user, app_label):
                return True
        except PermissionDenied:
            return False
    return False
發佈了41 篇原創文章 · 獲贊 9 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章