ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

汽车预销售管理系统开发实战:从业务建模到Flask/Django实现

汽车预销售管理系统开发实战:从业务建模到Flask/Django实现 1. 汽车预销售管理到底在管什么先捋清业务再写代码很多人第一次听到汽车预销售管理系统这个名字以为就是一套把订单录入数据库的CRUD系统实际做进去才发现完全不是那么回事。这个系统真正管的核心不是车而是人——从客户第一次进店留资、销售电话回访、试驾预约、报价洽谈到最终交定金签预订单的整个漏斗过程。汽车单价高、决策周期长平均一个客户要跟进三到五次才会成交这个过程中产生的线索、跟进记录、洽谈历史、价格承诺都是门店最核心的资产。如果这些还靠Excel和销售的个人备忘录来管理那销售一离职客户资源就跟着丢了。这套系统的价值就是把这段过程线上化、标准化、角色化。1.1 预销售和正式销售系统不是一回事先把这个概念掰扯清楚。很多汽贸公司、4S店其实已经有ERP或者DMS系统了DMS管的是库存、售后、配件、财务结算它的业务起点是车已经卖出去之后或者说车辆资源已经分配到门店之后。但预销售管的是这辆车还没卖出去之前的阶段包括潜客管理、意向跟进、试驾安排、价格谈判、定金收取、订单审批。这两个系统的数据模型、操作角色、流程节点是完全不同的。DMS里的订单是已经生效的销售单预销售系统里的预订单则是一张可能生效、也可能随时取消的意向单。用DMS去管潜客跟进你会发现根本没法记录今天给客户打了回访电话、客户说下周再试驾这种过程性信息用Excel去管预销售又会遇到数据错乱、多人同时编辑、无法统计转化率的问题。所以专门做一套预销售管理系统不是重复造轮子而是补上业务链条里最容易漏水的那一段。1.2 四个角色分别管哪一段这套系统设计了四个角色系统管理员、销售顾问、客户、门店经理兼财务。这个划分不是凭空想出来的而是参照实际门店的岗位设置来的每个角色的权限边界非常清晰系统管理员管账号、管车型基础数据、管权限分配、看系统日志。他不参与具体的销售业务但他能看到全系统的数据分布和操作记录负责日常维护和数据修正。销售顾问录入潜客信息、写跟进记录、约试驾、创建预订单、发起折扣申请、跟踪审批结果。他是系统里操作最频繁的角色几乎所有业务数据都是他产生的。客户注册登录后可以浏览车型库、提交购车意向、查看自己预订单的审批进度和定金缴纳状态。客户看到的内容严格限制在自己的数据范围内。门店经理兼财务审批销售顾问提交的预订单和折扣方案、管理定金收款记录、查看销售日报和月报。在规模不大的门店里经理和财务经常是同一人所以我把这两个岗位合并成一个角色来设计。这四个角色的协作关系可以用一条业务线串起来销售顾问录入潜客并跟进客户在线提交意向或由销售代录双方洽谈后创建预订单提交给门店经理审批审批通过后客户缴纳定金预订单生效最后转入交付排期。管理员在整个过程中承担的是搭台子的角色不直接参与业务流转。1.3 预订单的状态机设计预订单是整个系统的核心实体它的状态流转我一开始就设计成了状态机而不是简单的一个字段随便改。完整的流转链路是这样的意向登记 → 报价洽谈 → 提交预订 → 经理审批 → 定金缴纳 → 订单生效 → 交付排期 → 完成或取消其中提交预订之后会分叉审批通过进入定金缴纳审批驳回退回报价洽谈定金缴纳后如果客户反悔可以进入已退款关闭交付完成才是终态。把这套状态机画成一张表放在项目文档里后端代码里再用枚举字段去约束这样就不会出现一个订单同时是待审批又是已缴纳这种脏数据。状态流转的每一步都应该记录操作人、操作时间和审批意见方便后面查账和纠纷追溯。2. 选Flask还是选Django两条路线的取舍与项目结构技术选型是这类项目最容易被问的问题。我在做这套系统时其实两个框架都写了版本底层业务模型完全共享只是Web层实现方式不同。这里把我的取舍过程和两种结构的差异讲清楚你自己做的时候可以根据团队情况选一条路线深入。2.1 框架选型不是面子工程Flask和Django之争本质上是灵活自由和自带全套之争。Flask的优势是轻、小、自由。你只需要一个Python文件就能跑起一个服务路由、视图、请求处理完全由你自己控制很适合想彻底搞懂HTTP请求链路的人。但代价是你需要自己组装ORM、登录处理、表单校验和管理后台这套自由其实是用开发时间换来的。对于汽车预销售这种角色多、权限细、页面多的管理系统Flask会让你在中后期不断重复造轮子。Django的优势则是全家桶。它自带Admin后台、ORM、Auth认证、表单系统、模板引擎、迁移机制对于管理类系统来说这些内置能力几乎是为业务量身定做的。尤其是Django的Admin只要把模型定义好后台自动生成一套可用的增删改查界面管理员角色的需求基本不用写代码就能满足。它的缺点是对新手来说魔法太重——一个中间件、一个settings配置可能就能让你排查一整晚。我的建议是如果项目周期紧、角色权限复杂、需要管理后台优先Django如果你想把这个项目当成练手、彻底吃透Web框架底层逻辑或者后续要把它改成接口服务提供给小程序端选Flask。两条路都不是错的关键是知道自己在牺牲什么。2.2 Flask版本的项目结构我是这样组织Flask版本的用了应用工厂模式加上Blueprint避免把路由全堆在一个文件里car_pre_sale/ ├── app.py # 入口文件创建app并注册蓝图 ├── config.py # 配置文件数据库连接、密钥、时区 ├── extensions.py # db SQLAlchemy() 独立出来避免循环引用 ├── models/ │ ├── user.py # 用户模型含角色字段 │ ├── customer.py # 潜客模型 │ ├── car.py # 车型模型 │ ├── pre_order.py # 预订单模型 │ └── follow_record.py # 跟进记录模型 ├── views/ │ ├── auth_view.py # 登录、注册、登出 │ ├── sales_view.py # 销售顾问端路由 │ ├── customer_view.py # 客户端口路由 │ ├── manager_view.py # 经理审批端路由 │ └── admin_view.py # 管理员配置端路由 ├── decorators.py # 角色权限装饰器 ├── templates/ └── static/用Blueprint的好处是每个角色对应一组路由边界一眼就能看清楚。extensions.py单独拎出来是因为SQLAlchemy的实例必须在models和views之间共享直接放在app.py里很容易出现循环引用报错。2.3 Django版本的项目结构Django版本我建议按业务模块拆分app而不是按角色拆分。一个角色可能横跨多个业务模块按角色拆分会导致同一个业务逻辑散落好几个app里。我实际使用的拆分方式是这样的# 创建项目 django-admin startproject car_pre_sale # 进入项目目录后创建四个app python manage.py startapp accounts # 用户、角色、权限 python manage.py startapp models_manage # 车型库、门店基础数据 python manage.py startapp sales # 潜客管理、跟进记录、预订单 python manage.py startapp reports # 统计报表、销售排行accounts这个app放自定义User模型用AUTH_USER_MODEL指向它。models_manage管车型和门店这类基础数据sales是业务的中心reports处理各种统计查询。这和Flask按角色拆views的思路不一样但最终效果是等价的——Django的权限中间件负责把角色约束到视图上业务的按模块组织反而更符合Django的哲学。2.4 数据库核心表五张表撑起整个业务无论选择哪个框架数据模型是一模一样的。我花了一晚上设计最终定下来五张核心表整个系统所有功能都围绕这五张表展开表名关键字段作用usersid, username, password_hash, role, phone, store_id所有角色的登录账号role区分角色carsid, brand, model_name, config_level, guide_price, store_id车型库供销售报价和客户浏览customersid, name, phone, source, intent_level, owner_sales_id, status潜客主数据归属某个销售顾问跟进pre_ordersid, order_no, customer_id, car_id, sales_id, amount, discount, deposit, status, approval_status预订单核心业务实体follow_recordsid, customer_id, sales_id, content, next_follow_date, create_time每一次电话、回访、试驾的过程记录pre_orders表我单独多说一句amount是合同总价discount是优惠金额deposit是实际收到的定金这三笔钱必须分开存不能用总额加备注那种偷懒做法。因为财务对账时定金是否到账、优惠是否在权限内是两个完全独立的校验维度。把这几个字段合并后面做报表时一定会抓瞎。3. 角色权限体系最先写、也是最容易被绕过的地方权限是所有多角色系统的地基。地基没打好后面每个视图函数都得提心吊胆地加判断。我在项目里最先写的不是任何业务功能而是用户登录和角色鉴权。这里把Flask和Django的两种实现都放出来。3.1 登录态与Token/Cookie机制不管哪个框架思路都是用户在登录页面提交用户名密码服务端验证通过后签发一个登录凭证浏览器在后续每次请求时带上这个凭证服务端再根据凭证识别出当前是哪个用户在操作。Flask里我用的是Flask-Login加Session。登录成功之后把user_id写进session后面的请求通过current_user获取当前用户。它还负责处理记住我、登出等操作比自己手写session省很多事from flask_login import login_user, login_required, current_user auth_bp.route(/login, methods[POST]) def login(): user User.query.filter_by(usernamerequest.json.get(username)).first() if user and check_password_hash(user.password_hash, request.json.get(password)): login_user(user) return {code: 0, msg: 登录成功, role: user.role} return {code: 1, msg: 账号或密码错误}Django这边更省心它自带AuthenticationMiddleware登录视图直接用auth.login(request, user)就行。Django在Cookie里会话的管理机制比Flask更完善Session数据存在服务端Cookie里只放session_id安全性和时效性都有人帮你管。这里也顺带回答一下django cookie设置token这类问题在需要接口鉴权的场景下Django的rest_framework_simplejwt会比原生session更合适本项目中普通页面用内置session足够但如果后续要接小程序建议换JWT。3.2 Flask的角色装饰器Flask里做角色控制我写了一个装饰器挂在需要限制角色的视图函数上面from functools import wraps from flask_login import current_user def role_required(*allowed_roles): def decorator(func): wraps(func) def wrapper(*args, **kwargs): if not current_user.is_authenticated: return {code: 401, msg: 未登录}, 401 if current_user.role not in allowed_roles: return {code: 403, msg: 无权限访问}, 403 return func(*args, **kwargs) return wrapper return decorator # 使用示例只有销售顾问才能创建预订单 sales_bp.route(/pre_orders, methods[POST]) role_required(sales) def create_pre_order(): ...这个装饰器把是否登录和是否角色匹配两道校验合在一起比在每个视图里重复写if判断干净得多。注意装饰器的顺序role_required必须在route装饰器下面否则拿不到请求上下文。3.3 Django的中间件角色控制Django如果没有用DRF直接用内置的LoginRequiredMixin和UserPassesTestMixin就能解决大部分场景。但项目里角色数量固定且权限边界清晰我更推荐写一个中间件做统一拦截class RoleAccessMiddleware: def __init__(self, get_response): self.get_response get_response def __call__(self, request): # 放行登录页、静态文件、Admin if request.path.startswith(/static) or request.path in [/login, /register]: return self.get_response(request) if not request.user.is_authenticated: return redirect(/login) # 这里维护一个URL前缀和允许角色的映射 allowed_map { /sales/: [sales], /manager/: [manager], /admin/: [admin], /customer/: [customer], } for prefix, roles in allowed_map.items(): if request.path.startswith(prefix) and request.user.role not in roles: return HttpResponseForbidden(无权限访问) return self.get_response(request)中间件的优势是全局生效天然防漏。我踩过一个坑用装饰器控制权限时总有某个视图函数忘了挂装饰器结果接口裸奔。中间件从入口统一把控新写一个视图只要URL前缀规范就不会漏掉鉴权。3.4 数据级隔离销售不能看别人的客户和底价角色权限只是第一层真正的关键是数据过滤。同一个销售顾问只能看自己的潜客和预订单门店经理能看整个门店的数据管理员能看所有门店的数据。这是数据级隔离需要在每一个查询语句里加上归属条件。Flask里我封装了一个方法def get_visible_customers(user): if user.role admin: return Customer.query.all() if user.role manager: return Customer.query.filter_by(store_iduser.store_id).all() return Customer.query.filter_by(owner_sales_iduser.id).all()Django里同样逻辑用ORM表达from django.db.models import Q def get_visible_customers(user): if user.role admin: return Customer.objects.all() if user.role manager: return Customer.objects.filter(store_iduser.store_id) return Customer.objects.filter(owner_sales_iduser.id)这里要特别强调不能先在视图层查出所有数据再在模板或前端里过滤。那样数据已经传输出去了一旦有接口被绕过就直接泄露全部客户信息。必须在ORM层面就把数据范围卡死。还有个细节——客户底价和优惠权限。我让汽车模型里有一个base_price和min_price销售顾问报价时只能看到指导价和折扣上限底价只有经理能看。查询时通过字段选择的API比如只select需要的列或者序列化时过滤掉敏感字段保证销售顾问在前端页面里根本拿不到底价数据。4. 预销售核心功能编码从录入到审批的完整闭环权限框架搭好之后业务功能就是按图索骥。我按主业务流程的顺序把几个最关键模块的实现拆开讲Django版本和Flask版本就混着说因为思路完全一致代码我也以更工程化的Django为主。4.1 客户留资与意向登记尽量让录信息这件事不烦人销售顾问每天最烦的就是录客户信息。设计这个模块时我的原则是必填字段不超过四个——姓名、电话、意向车型、来源渠道。其他信息比如预算、家庭人数、换车原因全部做成选填标签让销售在跟进过程中慢慢补充。在Django里建模型时就把这些考虑进去class Customer(models.Model): name models.CharField(max_length50) phone models.CharField(max_length20, db_indexTrue) source models.CharField(max_length20, choicesSOURCE_CHOICES) # 自然到店/线上留资/朋友介绍 intent_level models.CharField(max_length10, choicesINTENT_CHOICES) # 高/中/低 budget models.DecimalField(max_digits10, decimal_places2, nullTrue, blankTrue) owner_sales models.ForeignKey(User, on_deletemodels.PROTECT, related_namecustomers) status models.CharField(max_length20, defaultpotential) # potential/following/ordered/lost created_at models.DateTimeField(auto_now_addTrue)注意几个设计细节。phone字段加db_index因为销售经常用电话搜索客户不加索引后面数据到几万条会很慢。owner_sales用PROTECT而不是CASCADE防止销售顾问的账号被误删时连带把客户数据都删了。intent_level用固定choices而不是随便填字符串是为了统计时不被脏数据干扰。跟进记录和潜客是主从关系一个客户可以有多条跟进记录。每个跟进记录要写下一次联系时间class FollowRecord(models.Model): customer models.ForeignKey(Customer, on_deletemodels.CASCADE, related_namefollows) sales models.ForeignKey(User, on_deletemodels.PROTECT) content models.TextField(max_length500) next_follow_date models.DateTimeField(nullTrue, blankTrue) created_at models.DateTimeField(auto_now_addTrue)有next_follow_date这个字段我就可以在首页给销售做一个今日待跟进客户列表这是我做过之后发现使用率最高的一个功能。否则记录的跟进再多不提醒等于白记。4.2 创建预订单金额、定金与交付日期的处理预订单是整个系统里字段最多的模型。这里给一个核心模型class PreOrder(models.Model): STATUS_CHOICES [ (pending_approval, 待审批), (approved, 已通过), (rejected, 已驳回), (deposit_paid, 已交定金), (completed, 已完成), (canceled, 已取消), ] order_no models.CharField(max_length20, uniqueTrue, editableFalse) customer models.ForeignKey(Customer, on_deletemodels.PROTECT) car models.ForeignKey(Car, on_deletemodels.PROTECT) sales models.ForeignKey(User, on_deletemodels.PROTECT, related_namepre_orders) amount models.DecimalField(max_digits10, decimal_places2) # 成交总价 discount models.DecimalField(max_digits10, decimal_places2, default0) # 优惠金额 deposit models.DecimalField(max_digits10, decimal_places2, default0) # 定金 delivery_date models.DateField(nullTrue, blankTrue) status models.CharField(max_length20, choicesSTATUS_CHOICES, defaultpending_approval) remark models.TextField(blankTrue) created_at models.DateTimeField(auto_now_addTrue) approved_by models.ForeignKey(User, on_deletemodels.SET_NULL, nullTrue, related_nameapproved_orders) approved_at models.DateTimeField(nullTrue, blankTrue)order_no的生成规则我推荐用年月日门店编号随机数比如PD20250618001不能直接用自增主键当订单号因为外部客户会看到这个单号订单号带业务含义方便对账。deposit字段和amount分开存这样财务能非常清晰地看到每张单子实际收了多少钱。创建预订单的时候有一个业务规则必须处理如果discount超过销售顾问的折扣权限这张单子就必须进入经理审批流程如果没超过可以直接进入已通过状态。这个规则不是写在页面上的而是写在后端创建订单的service里def create_pre_order(data, sales_user): car Car.objects.get(pkdata[car_id]) discount_amount decimal.Decimal(data[discount]) # 判断优惠金额是否超出销售权限 if discount_amount sales_user.max_discount: status pending_approval else: status approved order PreOrder.objects.create(..., statusstatus) return order这里把谁有权限、能批多少折扣直接配置在User模型上比写在代码里硬编码灵活得多。不同级别的销售顾问可申请的优惠上限不同。4.3 审批流的实现状态驱动加事务保护经理审批是这个系统里最容易写出Bug的地方。审批这个动作涉及两个操作改预订单状态、记录审批人信息。这两个操作必须放在同一个事务里否则就可能在状态已经变成已通过但审批人字段还没写上的瞬间被其他查询读到半成品数据。Django里用transaction.atomicfrom django.db import transaction transaction.atomic def approve_order(order_id, manager_user, approveTrue): order PreOrder.objects.select_for_update().get(pkorder_id) if approve: order.status approved else: order.status rejected order.approved_by manager_user order.approved_at timezone.now() order.save()select_for_update()是数据库层面的行锁它保证两个经理同时点审批的时候只有第一个能拿到这条订单的锁第二个必须等第一个提交之后才能执行。如果没有这把锁极端情况下就会出现一条订单被两个经理同时审批、状态被覆盖的混乱场景。审批通过后客户会看到订单状态从待审批变成已通过接下来需要在客户端的订单详情页展示去交定金的按钮。到这一步预销售的核心链路就算走通了录入意向、跟进洽谈、生成预订单、经理审批、客户缴定金。4.4 财务统计与销售排行别把报表写在查询里硬算报表这块我最开始是在视图函数里偷偷写一堆for循环凑数据结果导致页面加载奇慢。后来统一改成用Django的聚合查询from django.db.models import Sum, Count def get_sales_report(store): report (PreOrder.objects .filter(store_idstore.id, status__in[approved, deposit_paid, completed]) .values(sales__username) .annotate( order_countCount(id), total_amountSum(amount), total_depositSum(deposit) ) .order_by(-total_amount)) return report这种查询在数据库层面就完成分组聚合而不是把几万条记录全加载进Python再循环。实际项目里这个查询从最初的3秒压缩到了200毫秒以内。销售漏斗的统计稍微复杂一点从潜客总数、有效跟进数、预订单数到成交订单数每一层因为口径不同需要单独写查询再合并展示。我在reports这个app里单独开了一个模块放这些统计逻辑没有把它们散落在各个视图里。几个月后回头看这个决定让后续加报表功能时省了很多事。5. 开发中踩过的几个大坑附完整排查链路这套系统从零到一开发过程中我踩了不少坑。有些坑回看时觉得蠢但每一个都没有在任何教程里直接告诉你。我把它们完整列出来包括排查思路希望能帮你少浪费几个通宵。5.1 坑一改表结构后外键关联数据全部报错PreOrder表一开始设计的car_id是一个字符串字段用来存类似奥迪A6L2023款这种手动填写的车型名。后来我觉得这样不正规把字段改成了外键指向Car表。改完之后测试环境里所有关联查询开始报Cannot add or update a child row: a foreign key constraint fails。排查链路我先查代码发现ORM模型已经更新但数据库表结构没有跟着变。Django本地跑migrate理论上会自动生成迁移文件但因为是手动改的模型迁移文件没有同步。确认之后我重新生成了迁移文件并执行迁移问题解决。这件事之后我养成了一个习惯任何模型字段变更第一件事就是检查迁移文件到位没有不要直接改数据库。5.2 坑二权限校验只写了页面层接口被直接绕过我最初以为只要在前端把没有权限的操作入口隐藏掉比如不给销售显示审批按钮就安全了。结果内部测试时我直接通过接口工具post请求到管理端的审批接口发现居然能成功——后端视图函数根本没有做角色校验。当时是忘了在审批视图上加装饰器。排查链路抓包确认请求确实到达了视图再定位到审批路由发现上面只有mark_as_approved函数名没有挂任何装饰器。补上装饰器后再用普通销售账号post一次收到403验证通过。这个坑直接促使我在Flask版里把角色校验从装饰器升级为全局拦截在Django版里写中间件统一处理。前端隐藏入口只是锦上添花真正的防线必须在后端。5.3 坑三状态字段用中文同一状态两种写法导致统计错乱在中期做报表时我发现了数据错乱的问题统计预订单状态数量时发现已通过和已审核两行其实是同一种状态数量被拆开了。原因是创建订单和审核订单时用了两套中文常量一个视图写的是已通过另一个视图写的是已审核存进数据库就成了两种状态。排查链路先在报表页面看到异常分组然后直接查数据库里pre_orders表status字段的distinct值很快发现了问题。解决方式是把所有状态改成英文枚举统一放一个常量类里维护前端展示时才从枚举字典映射成中文。这个坑的核心教训是数据库里永远不要存中文状态要存稳定不变的机器码展示层负责翻译。5.4 坑四时区问题导致统计报表差了8小时上线后销售反馈说今天的新增客户在报表里看不出来仔细一看发现昨天下午6点以后新增的客户全部算到了今天。翻数据库发现created_at时间比北京时间晚了8小时。根源是服务器时区默认是UTCDjango的TIME_ZONE没有配置成Asia/Shanghai。排查链路先在页面和数据库里对比一条记录的时间确认差8小时检查settings.py的TIME_ZONE发现还是默认值修改为TIME_ZONE Asia/Shanghai同时USE_TZ保持True。为了避免这类问题我后来统一规范数据库存UTC时间展示时转换到本地时区统计口径统一按本地时区处理。这里不用靠猜查系统时间和代码配置即可。5.5 坑五并发提交预订单重复单号和数据冲突预订单的order_no最开始用时间戳加自增id拼接测试时用两台设备同时对同一个客户提交预订单结果出现了两条完全一样的订单记录。原因是创建订单的请求并发访问时两个进程同时读取了当前的自增值。排查链路复现并发场景后用数据库日志确认是缺少唯一约束。解决方式是在order_no字段上加了uniqueTrue同时创建订单时在外层加try except捕获IntegrityError并重试。业务层上我还在创建订单视图入口加了一个基于客户ID时间窗口的幂等开关同一个客户5秒内重复提交直接拦截。两个机制一起上之后再也没有出现过重复订单。6. 从开发机到服务器部署上线与后续扩展写完代码只是第一步真正让系统跑起来是另一回事。这里记录几个部署相关的要点都是实用经验不是网上那些千篇一律的教程。6.1 本地运行环境的搭建建议全程用Python 3.8以上的版本我实际用的是Python 3.8稳定且兼容性好。确保环境一致有条件就用venv或conda建一个独立环境然后安装项目依赖。Flask版本需要安装flask、flask-login、flask-sqlalchemy、flask-wtfDjango版本需要django和djangorestframework如果要做接口。初始化数据库时Django直接把migrate跑一遍Flask版本需要自己在Python交互环境里执行db.create_all()或者写一个init_db.py脚本。这里推荐把初始数据做成种子脚本包括管理员账号、几个测试用车型后续每次重置环境都能一键恢复。6.2 服务器部署的几个关键点服务器部署这块有四个点容易被忽略第一Django模式下必须执行collectstatic收集静态文件否则前端样式全部丢失Flask如果用了模板继承静态文件路径也要在nginx里单独配置。第二DEBUG必须设为False否则访问者能直接看到完整的Python堆栈错误里面可能包含数据库信息和文件路径这是严重的安全隐患。第三生产环境用gunicornDjango和Flask都适用跑Web服务配置合适的workers数量workers建议按照CPU核数的两倍加一调整比如2核的云服务器就配置为5。不要用只针对测试环境的development server。第四nginx做反向代理同时把上传的文件放在一个单独目录里单独处理因为后面客户可能会上传驾驶证照片、身份证照片这类资料需要有一个专门的存储路径。6.3 后续还可以这样扩展这套系统跑稳定之后我给自己列了几个扩展计划走一步看一步一是接入短信验证码客户提交意向时做手机号校验减少无效线索二是做小程序端让客户能在微信里随时看订单状态这需要后端提供一套JSON接口Django这边用DRF、Flask那边用RESTful路由都可以三是把试驾预约做成日历视图让销售和客户直接在系统里约时间避免电话反复确认四是做门店大屏的数据看板把销售漏斗、定金总额、本周新客数这些指标滚动播放。这些扩展基本都不用动核心数据表结构只需要在现有模型上加字段、加接口就行这也是一开始设计时把状态机、角色权限和数据隔离做扎实带来的好处。最后分享一个实际感受这套系统开发过程中最耗时的不是业务功能而是角色边界和数据权限的反复推敲。很多问题是销售能不能看这个字段经理审批时要不要看底价管理员能不能改已审批的订单这类细节。做这类多角色管理系统建议开工前花几天把每个角色能看、能改、能审批的数据完整列成一张权限清单宁可前期慢一点也不要开发到一半来回返工。
返回列表