# CanteenManagementSystem
**Repository Path**: solve_id22/canteen-management-system
## Basic Information
- **Project Name**: CanteenManagementSystem
- **Description**: 餐饮管理系统
- **Primary Language**: C++
- **License**: GPL-3.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 6
- **Forks**: 4
- **Created**: 2022-05-23
- **Last Updated**: 2025-11-09
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 一:项目描述
**地大后勤集团餐饮部需要研发一套系统,用于对地大体系内所有餐饮部门进行运维管理。**
背景:在校园里面通常会设立食堂,有可能自己经营某种类型的餐食,也可能承包给各个个体户,丰富师生饮食。其收费与经营的基本思路如下所示:
请结合上图进行餐饮系统的设计与实现,要求必须考虑内容如下:
1. 就餐时价格如何确定,套餐、点餐、称重?
2. 如何付费,一卡通、二维码、收付款?
3. 考虑扩展性,将来可能增加的餐饮类型?(顿顿有你)
4. 校外人员、校内人员收费标准可否改变?
5. 过节时的加餐如何管理?
6. 学校后勤集团如何结算各服务窗口的租赁费用?
为平抑物价,学校后勤集团控制菜品价格。
- 在就餐时,窗口服务人员选择就餐者所订购饮食种类(如红烧牛肉面、两荤一素、餐食重量),发送至后台后,计算出价格,反应到打卡机上,就餐者刷卡购餐。
- 如窗口增加餐品类型,则由管理员制定菜品价格,然后将菜品名称、价格、窗口ID等输入到服务器中。窗口服务人员的打卡机上则显示出菜品名称,以便就餐者选择。
- 每周日晚上10点,饮食中心结算每个窗口的经营额,抽取租赁费用后,返还剩余金额。
---
# 二:需求分析
## 2.1业务流分析
**基本的注册和登陆业务:**
我们根据题目要求,主要分析出两种用户,一种是管理员用户(即题目中所提到的位裁片指定价格的管理员),另一种是普通商户用户(即题目中所提到的窗口服务人员)。我们分析两种用户的关联,结合食堂实际的情况,管理员属于食堂管理员(后勤集团的管理者,比如某某食堂的经理),服务人员属于(窗口所有者),两者的联系在于两者属于同一个组织(比如学一食堂),因此我们抽象出一个组织的实体。在登陆时主要就是对比数据库数据,注册时管理员可以选择注册时开设一个组织,而普通用户则不能选择开设组织。
**管理员对用户和窗口的管理:**
结合题目要求和我校实际情况,学校的后勤集团管理员能够控制菜品价格,控制窗口是否能够开设,控制某个店的老板能否参与某食堂的运营中来。我们认为一个可行的菜品价格控制方法不是管理员直接设置菜品价格(我们认为这种单向的操作并不是一个良好的决定),我们设计的想法是,窗口的服务人员(比如窗口的老板)向管理员提出一个申请菜品价格的申请,由管理员批准是否能够设置为该价格。
(在该部分考虑餐饮拓展即服务人员提出一个增加菜品的申请以及过节时加餐的处理即开设一个临时窗口,对应要求3和要求5)
**用餐收费流程:**
分析题目,窗口的服务人员选择好用餐者的订购的饮食的品种后,由后台直接计算价格并反映到打卡机上,我们的想法是由窗口服务人员设置好饮食品种(用餐者可见)后,传送到后台进行计算,在订单结算时,商户可以选择指定对某餐品进行打折(打折以吸引顾客)。分析后我们认为得设计一个收费器,用于响应用户付款,主要是向收费器接口发送收费命令,等待相应和超时处理。
(在该部分考虑了用餐价格的确定即后台计算,以及如何付费即用收费器判断,还考虑了不同人员的收费标准即打折,对应要求1,要求2和要求4)
**饮食中心结算:**
每周日晚上10点,饮食中心结算窗口经营额,抽取租赁费用,返还剩余金额。我们的想法是在服务器上部署一个定时脚本,到每周日晚上10点时自动触发。平时的时候,用户付费直接进入饮食中心账户(即组织的账户),结算时,饮食中心根据窗口订单情况,给商家进行结算。租赁费我们初步设想是分级的,类似交税,结算后,将钱转入商户账户(即窗口人员账户)。
(在该部分考虑了租赁费用的结算即定时脚本实现,对应要求6)
基于以上分析,分析并绘制出以下总体架构






## 2.2编程环境以及相关工具
**MySQL:**
- 优化的 SQL 查询算法,有效地提高查询速度。(效率高)
- 提供 TCP/IP、ODBC 和 JDBC 等多种数据库连接途径。(支持C++连接)提供多语言支持,常见的编码如中文的 GB 2312、BIG 5,日文的 Shift_JIS 等都可以用作数据表名和数据列名。(中文不会出错,不会乱码)
- 既能够作为一个单独的应用程序应用在客户端服务器网络环境中,也能够作为一个库而嵌入其他的软件中。(方便在程序中嵌入,同时可以在脚本中直接调用嵌入式库)
**Navicat:**
- 使用Navicat浏览和修改数据,插入、编辑、删除数据或复制和粘贴记录到数据表形式的数据编辑器,Navicat将运行相应的命令(例如INSERT 或UPDATE),免除写复杂的SQL。(便于操作)
- 可以保证快捷地输入无错误的代码。(高亮标识表明提示代码正确性)
- 运用精密的数据库设计和模型工具,可以用图形表达数据库。使用实体关系图表来显现数据库结构及关系,这样你就可以更容易塑造,建立和理解复杂的数据库。(可以快捷地建立出数据库地模型)
- 用户管理功能提升和管理每个用户的管理权限,不需输入任何命令,在数分钟内就能创建和编辑用户角色,借助这个精确控制的层面,可以在不影响数据库的安全性下,创建规则并让用户访问数据库。(连接方便,安全性高)
**Qt:**
- 需要的库,在其API中都可以找到,并且有很高的集成度,不仅速度快并且简单易用。(用户友好)
- Qt支持2D/3D图形渲染,并且支持OpenGL。(强大的图形库,便于ui设计)
- 模块化程度高,重用性好;使用相对安全的信号与槽机制来代替回调函数,各个函数之间的协调更加灵活。(便于程序设计,且安全性高)
- 通过更改编译步骤,是可以在其他嵌入式或者linux界面上被支持的(扩展性好)
**Clion:(MAC)**
- 非常好的智能感知功能,自动折叠、高亮、自动补全、类型推断都很好。Autofix工作的很好。(用户友好)
- 重构很方便,像inline函数、extract成员函数、常数,pull up/pull down、修改签名这些功能都有。调试功能很方便,可以自动解析STL容器。
- 继承了jetbrains系ide的很多优点,像方便的vim插件和keymap调整,滚动条预览,与VCS的紧密集成等等。
- 跨平台,支持CMake/gcc/clang/mingw/cygwin/gdb。虽然不多,但是其实基本上也够用了。(可以保证和其他成员不起冲突)
**Gitee:**
- 访问速度快,对国内用户比较友好
**SourceTree**
- 实用的git gui程序,各种分支迭代更新一目了然(可视化良好,可读性好)
- 操作简单快捷,不用刻意去记各种繁琐的命令(对用户友好)
- 可以在选项里直接配置diff和merge的工具,对于unity yaml merge来说极其好用。(功能强大)
**Visio**
- 模板库强大,基本覆盖所有学科的相关图的绘制(方便直接调用)
- 绘图专业,有利于提升工作效率(开发效率高)
- 生成格式多样,Visio能生成大约20多种不同格式的文件,与其他各类专业软件交互良好(便于与其他软件交互,接口广)
**Drawio**
- 开源免费(对学生党友好)
- 界面简介,美观,使用方便(美观)
- 占用空间小,轻便,不用担心存储空间的问题(小巧玲珑)
- 导出格式丰富,与其他软件交互良好(交互性不错)
# 三:总体设计
## 3.1系统架构设计
在当下的大部分系统中,流行的是MVC架构,MVC架构各个模块分工明确,在开发时各模块可以分离开发,相互协作,最终完成系统的搭建。
### MVC架构
mvc是一种架构模型,本身并没有新功能,只是对项目的一种规范,方便后期进行维护;mvc架构将模型(M),视图(V)和控制(C)割离开,这么做可以提高代码书写的效率和后期维护。
View(视图):简单来说,就是负责数据的可视化。
Controller(控制器):通常控制器用来从视图读取数据,并发送给对应的模型处理,再将结果反馈给视图显示。充当视图与模型之间数据交互的桥梁。
Model(模型): 模型代表一个存取数据的对象它也可以带有逻辑,在数据变化时更新控制器。
借用MVC架构的思想,我们结合系统需求,将数据库的操作与其他模块分离,同样使用Controller进行UI的响应,做出相应的操作,作用到数据库与界面之间。最终构建了DAO-Model-Controller-View的架构,其中DAO对数据进行操作,Model存储对象,Controller实现各逻辑控制界面现实,View就是UI与用户进行交互。
### 各模块关系
在总体上,DAO层与数据库进行打交道,实现各个表的增删改查,让其他层与数据库隔离,其他层只需要知道数据库中有那些表,数据的操作通过DAO层来进行交互,在Controller层中,实现系统的各主要功能,集成DAO中的数据操作进行封装打包,控制UI界面的展示,Controller层连接前端界面和数据访问层,在各层时间数据的传输通过模型来进行传递,根据系统设计需求建立各个实体类模型。各模块关系如图所示。

- DAO层:连接数据库,对数据库中的各个表进行增删改查,与数据库中的表进行交互,每个表有对应的DAO,根据Controller的不同需求,完成从数据库中的查询操作,删除操作,修改操作。
- Controller层:控制器,控制系统的整个功能的实现,功能集成与这一层装,调用DAO层中的各个数据表访问,完成前端页面展示需求数据的获取,将数据从数据中通过DAO层提取出返回到前端页面显示,而前端的操作,如数据的修改插入,也通过Controller层传递到DAO最终作用到数据库,完成数据的更新,在前端UI传回的数据,进行逻辑判断与处理,返回给前端并根据结果决定对数据库的操作。
- View层:View层的职责非常简单,就是显示界面,对数据的操作向Controller获取相应的行为,对用户进行一个回显,与用户打交道。
- Model层:在DAO、Controller和View层的交流中,需要对数据进行传输,而在系统中不同的对象数据不同,所以在Model层中就是建立实体类,对现实生活中的实体进行抽象到程序中进行表达,定义实体的相应应有的行为并实现。从数据流上就是,前端用户输入产生模型,通过Controller传输到DAO层,最终写入数据库,也可以是逆操作。
## 3.2数据库设计
### 关系模型
Bill : ['order_id', 'username', 'window_id', 'price', 'tradingTime', 'status', 'description']
User : ['type', 'name', 'phone', 'password', 'creditCard', 'sex', 'identityCard']
WindowInfo : ['id', 'name', 'address', 'organization_id', 'status']
collect_rent : ['id', 'org_id', 'win_id', 'turnover', 'salary', 'time', 'description']
discount : ['id', 'value', 'window_id', 'description', 'type']
discountDetail : ['discount_id', 'food_id', 'discription']
foodType : ['type_id', 'food_id', 'name', 'description']
joinApproval : ['id', 'organization_id', 'user_id', 'status']
organizationInfo : ['id', 'name', 'address', 'creditCard']
picture : ['id', 'size', 'content']
saleUnit : ['id', 'window_id', 'name', 'price', 'status', 'diningType', 'unit', 'image_id', 'description', 'type']
setFoodType : ['set_id', 'type_id', 'num', 'description']
windowApproval : ['window_id', 'user_id', 'id', 'status']
### 物理模型
#### Bill

#### **collect_rent**

#### **discount**

#### **discountDetail**

#### foodType

#### **joinAproval**

#### **organizationInfo**

#### **picture**

#### **saleUnit**

#### **setFoodType**

#### **User**

#### **windowApproval**

#### **WindowInfo**

# 四:模块设计
## 4.1概述
根据我们的架构设计,我们一共有四个大的模块,数据访问操作层DAO,控制层Controller,模型层Model,界面展示视图View。具体每个层的总体设计如下:
### DAO (Data Access Operation)
这一层主要为数据访问操作,和数据库进行交互,那么这个过程就需要与数据库建立连接,搭建一个桥梁。这个桥梁我们使用单例模型,系统每次加载,与数据库建立一次连接,在系统运行时,共用这个连接建立不同的会话进行数据库操作。
在该层中,定义每个类对应相应的数据表增删改查操作。共有7个DAO操作分别为:
①OrderDao:对订单表Bill进行增删改查,完成订单信息与数据库的交互。
②OrderRoleDao:对discount表和discountDetail表进行增删改查操作,完成打折信息对象的获取与存储。
③OrganizationDao:对组织机构表organizationInfo进行增删改查操作,完成组织机构对象的获取与存储。
④SaleDao:菜品对象的获取与生成,操作菜品基本表,根据工厂,生成不同的菜品对象。
⑤UserDao:对user表进行增删改查,完成用户对象的获取与存储。
⑥WindowDao:对windowInfo表进行增删改查,完成窗口数据的获取与存储。
⑦ApproveExam:对两个审批表joinApproval,WindowApproval进行增删改查操作,完成各个审批数据的获取与状态更改。
### Controller
Controller层主要实现与DAO层交互和View层交互,我们是设计是一个面向对象的Controller,每个Controller完成对应的对象操作,完成对象的数据需求和操作需求,对对象的各操作进行逻辑处理,响应的数据库以及数据返回给View层。在我们的系统中,我们主要抽象出了三个实体,分别为管理员,商家,窗口,系统中主要是这三个对象进行操作,完成整个系统的运作,在这个系统中还需要一个对象,就是支付系统,在系统中,我们抽象成了收费器对象,收费器对象调用其他平台完成支付。商家和管理员对于系统来说,可以抽象出一个上层对象为用户。具体描述如下:
①UserController:是ManagerController和MerchantController的父类,共同属性有User对象和登录方法。
②ManagerController:完成管理员的各个操作响应:登录,注册,查看加盟申请,查看窗口申请,查看菜品申请,以及各个申请响应的审批操作,进行通过DAO层写入到数据库。
③MerchantController:商家控制器,实现商家的主要功能,也就是用例图中的商家的每个对应的用例,登录,注册,查看当前拥有窗口,打开窗口,申请加入机构,申请开辟新窗口,查看已有组织,查看可以申请的窗口,申请窗口,对于商家来说,完成这几个功能即可完成需求。
④WindowController:窗口控制器,是商家打开窗口后产生的一个对象,在窗口控制器中,有自己的窗口说明信息,菜单,打折信息,以及当前订单,所属商户。行为有为订单添加优惠,为窗口添加菜品,为订单添加菜,订单结算功能,窗口控制器属于是食堂中的店员在进行操作,完成顾客的点单操作。
⑤Charge:收费器,更是一个容器,容器中存储了各个平台的支付对象,如二维码支付,扫码支付,打卡器支付,收费器收到收费指令后,向各个支付子系统进行发送指令,监听各支付状态,根据状态完成收费的状态控制。流程图如下图所示:

所有的子支付系统即需要实现支付接口,采用多线程,各个支付同步进行,完成支付的操作,类图描述如下图所示,所有的支付均需要集成于类ChargeBase,实现里面的接口,在run()函数中实现支付的操作,通过修改status状态来告知Charge收费成功状态。

### Model
Model层为实体对象的定义,在系统中,较为简单的对象有用户User,订单Order,窗口信息WindowInfo,组织机构信息OrganizationInfo,账单信息Bill,这几个只需要简单的结构体进行存储就可以完成相应的功能。具体定义于数据库中的表定义相同,详细请见4.4.1。
在Model中,需要考虑变化,在菜品上,食堂中的菜品多种多样,需要对各类对象进行抽象出上层实体,通过面向对象的多态,进行派生出多种多样的对象,抽象出上层售卖食物SaleBase,存储一个菜品的基本信息,并实现DaoBase接口,使得在衍生时,需要实现如何调用DAO层中的操作完成对应对象的生成与数据存储。在本次系统中,我们设计了三种售卖食物,单个售卖,称重售卖,以及套餐售卖,也就是三个类SaleUnit,SaleWeight,SaleSetMeal,继承于基类SaleBase,并有自己的特有属性与方法。详细描述见4.4.2
同时,窗口举办的优惠活动信息也具有多样性,有满减活动,有打折活动,不同的对象我们需要使用不同的类型来表示,抽象出上层实体,OrderRoleBase,对于满减活动或者打折活动需要继承基类OrderRoleBase,同时,打折活动也需要实现我们定义的序列化接口DaoBase,完成于数据库的对接,数据的持久化存储。详细描述见4.2.2。
在不同对象的生成,我们需要考虑使用工厂来实现不同对象的生成,在本次设计中,我们的类实现了序列化接口,工厂模式的主要职责是用于分配空间,所以我们采用了改进的简单工厂模式,在进行详细信息的查询时,通过序列化接口进行获取,加载各种不同的菜品。
## 4.2DAO(Data Access Operation)
### ApproveExa
```
bool selectOrgExamWindow(OrganizationInfo org,vector& wins,vector& users,vector& exam_ids);
```
查询某个组织下的所有待审批的窗口,一个窗口对应一个用户的审批。通过对 WindowInfo, User, windowApproval三个表的联合查询,获取到一个组织全部等待审批的窗口的信息和提交该申请对应的申请人的信息,以及每个审批在系统内部的审批编号(用于前端反馈处理结果)。
```
bool selectOrgJoin(OrganizationInfo org,vector& users,vector &exam_ids);
```
查询某个组织下的所有待审批的加盟申请对应的用户。
直接查询joinApproval中org_id为查询条件,同时status为待审批状态,调用UserDao查询用户的信息并返回。
```
bool selectOrgExamFood(OrganizationInfo org,vector& sales);
```
查询某个组织下的所有审批的菜品。
先通过windowDao查出org对应的所有窗口vec_win_info,然后查询每个窗口对应的所有菜品(调用saleDao中的查询函数),最后根据返回的菜品状态status,将status值为STATUS_exam(审批中)的菜品挑选出来返回。
```
bool selectExam(int exam_id,int& org_id,int& user_id);
```
加盟审批查询,根据exam_id查询,返回组织id和userid,单个查询。
根据给出的审查编号,查询joinapproval表,获取对应的组织id和用户id并返回。
```
bool alterJionExam(int exam_id,int status);
```
加盟审批状态修改 。
根据给出的审批id修改joinapproval表中的status字段为传递的参数(通过/不通过)
```
bool addJionExam(User user,OrganizationInfo org);
```
用户加入组织的请求。
通过给出的user信息和组织信息,提取出用户id和组织id,插入joinapproval表,表示用户要申请加入该组织。
```
bool addWindowExam(User user,WindowInfo win);
```
窗口申请。
通过给出的user信息和窗口信息,提取出用户id和窗口id,插入windowapproval表,表示用户要申请加入该窗口。
```
bool alterWindowExam(int exam_id ,int status);
```
窗口修改。
根据给出的审批id修改windowapproval表中的status字段为传递的参数(通过/不通过)
### **orderdao**
```
void insert(Bill bill);
```
将获取到的订单(bill对象)拆解出不同字段,插入bill表。
```
void selectDuring(WindowInfo win,QDate start,QDate end,vector &bills);
```
查询某个时间段内的指定窗口(id)订单信息。
根据窗口信息和开始结束时间,从bill表里面查询符合条件的所有订单信息并返回。
### **orderroledao**
```
vector selectAllRole(WindowInfo win);
```
查询指定窗口中的所有订单优惠。
通过给出的窗口信息提取出window_id,先查询discount表获取到当前窗口的所有优惠信息,然后查询discountDetail表获取优惠信息作用的具体商品列表,最终返回一个包含所有优惠信息的vector。
```
bool insert(OrderRoleBase* orderRole,WindowInfo win);
```
为某个窗口添加订单优惠信息。
通过windowInfo参数获取到要插入优惠信息的窗口id,然后解析待插入的所有优惠信息,插入discount表,再将详细的商品打折信息插入discount Detail表
```
bool del(OrderRoleBase* orderRole,WindowInfo win);
```
将某个窗口的优惠信息删除。
根据给出的窗口信息和优惠信息,先删除discountDetail表中所有符合的优惠详情信息,然后删除discount表中对应的一条记录。
### **organizationdao**
```
void addOrg(OrganizationInfo& org);
```
新增组织。
通过所给的组织信息,向organizationinfo表中插入一条记录。
```
bool alterOrg(OrganizationInfo org);
```
修改组织。
通过获取传入的组织信息,获取到组织名称、地址、账户等信息,然后将organizationinfo表中对应的记录更新为传入的新信息。
```
bool delOrg(OrganizationInfo org);
```
删除组织。删除指定的一个组织。
```
vector selectAll();
```
查询所有组织。
查询organizationinfo表中的所有信息,每一条记录构造成一个organization Info对象,最终返回一个vector。
```
vector selectOrg(User& user);
```
根据user查询所属的组织。
先根据userID查询joinapproval表获取到用户对应的组织,然后在organizationInfo表中查询该组织的信息。
### saledao
```
void insertSale(SaleBase* sale,WindowInfo win);
```
为窗口插入售卖的食物。
根据给出的商品信息和窗口信息,将商品上架到对应的窗口内,先把商品的图片信息插入picture表(如果不存在),再将商品信息插入saleUnit表(单品),如果是套餐的话就依次插入saleunit、fodType、setFoodType三个表。
```
void alterSale(SaleBase*& sale);
```
修改窗口中的某个食物。
直接将传入的新的食物信息插入saleunit表。
```
void delSale(SaleBase*& sale);
```
删除窗口中的某个食物。
先删除saleUnit表中的商品信息,然后再删除foodType表中的信息和setFoodtype表中的套餐信息,最后删除picture表中对应的图片信息。
```
void selectSale(SaleBase*& sale);
```
根据id,以及食物类型,查询单个数据。
先从saleUnit查询食物信息,再从picture表查找对应的图片信息。
```
vector selectFoodType(int foodType_id,string& name);
```
查询某个类型的所有食物id。
从foodType表中查找type_id 为传入商品id的全部食物的信息
```
vector selectAllSale(WindowInfo win);
```
查询指定窗口中的所有售卖食物。
从saleunit中查找商品的window_id为目标id的全部商品并且返回。
```
bool examFood(SaleBase* sale,int status);
```
菜品审批。
修改saleUnit表中的status值为传入的参数(通过/不通过)。
### userdao
```
bool getUser(User& user);
```
查找用户,根据用户名和密码在user表中查找用户,返回查找结果,用户名和密码对应成功返回true。
```
bool insertUser(User& user);
```
用户注册,在user表中插入用户信息,先检查用户是否存在,用户名存在则注册失败。
```
bool alterUser(User& user);
```
用户信息修改
```
bool delUser(User& user);
```
用户注销,需要删除所有与该用户相关的窗口等信息。
### windowdao
```
bool insertWindow(WindowInfo& win);
```
窗口添加;
根据窗口信息,在windowInfo表中插入一条记录。
```
bool alterWindow(WindowInfo win);
```
窗口修改;根据窗口信息,
将windowinfo表中id对应的一行的信息更新成新的。
```
bool delWindow(WindowInfo win);
```
窗口删除;
直接在windowInfo表中删除对应的一行即可。
```
bool selectWindow(WindowInfo& win);
```
根据窗口id查询整个窗口信息;
查询windowInfo表中id符合的一条记录并返回。
```
vector selectUserWindow(User user);
```
查询某个用户名下所有窗口信息,只需要有用户电话号码即可,逻辑上应该判断是否成功登录。
```
vector selectUserApplyWindow(User user);
```
查询用户申请的所有未通过的窗口;
查找WindowInfo表,找到所有id匹配并且statue为未通过(2)的记录并返回。
```
vector selectOrgWindow(OrganizationInfo org);
```
查询某个组织下的所有窗口信息;
查找WindowInfo表找到组织id匹配的所有记录,并返回。
```
vector selectSpareWindow(User user);
```
查询空闲窗口(user用户可以申请加入的窗口);
## 4.3Controller
### **managercontroller**
```
bool login(User& user);
```
用户登录,返回是否登录成功,以及用户信息查询查询,用户名密码错误则返回false;
```
bool registerUser(User& user,OrganizationInfo& org);
```
用户注册,存在则返回失败,成功返回true,注册信息需要带有组织信息;
先做合法性检查:电话号码11位、电话号码纯数字、用户类型要匹配、身份证出了X/x外只能有数字、性别只能为M/F。若检查都通过的话,就调用UserDao写入用户信息、调用OrganizationDao写入一条组织信息。
```
bool addWindow(WindowInfo& win);
```
添加窗口;
先调用windowDao来判断系统内是否有这个窗口的信息,如果没有再利用windowDao插入一个组织的信息
```
bool examJoin(int exam_id, int status);
```
审批加盟申请;
直接调用ApproveExam(Dao)的修改状态函数,将ststus字段改为审批后的状态(通过/不通过)。
```
bool selectOrgJoin(vector& users,vector &exam_ids);
```
查询组织下的所有待审批的加盟申请对应的用户,调用approveExam中的同名函数即可。
```
bool selectOrgExamWindow(vector& wins,vector& users,vector& exam_ids);窗口申请的信息查询,调用approveExam中的同名函数即可
```
```
bool examWindow(int exam_id,WindowInfo win,int status);
```
商家开窗口申请审批,调用ApproveExam中的窗口审批状态修改函数。
更改exam_id对应审批的窗口在windowInfo表中的状态(status)信息,(若状态为审批通过,则窗口为运营态,否则为空闲态)
```
bool selectOrgExamFood(vector& sales,vector& wins);
```
查询组织下的所有审批的菜品,调用approveExam中的同名函数即可
```
bool examFood(SaleBase* sale,int status);
```
菜品审批,相当于修改表中的status值,调用saleDao中的同名函数
### **merchantcontroller**
```
bool login(User& user);
```
用户登录,返回是否登录成功,以及用户信息查询查询,用户名密码错误则返回false
```
bool registerUser(User& user);
```
用户注册,存在则返回失败,成功返回true。
先做合法性检查:电话号码11位、电话号码纯数字、用户类型要匹配、身份证出了X/x外只能有数字、性别只能为M/F。若检查都通过的话,就调用UserDao写入用户信息。
```
bool getWindows(vector &wins);
```
查询用户的所有窗口,调用windowDao查询。
```
bool openWindow(WindowInfo& win,WindowController& wincol);
```
打开某个窗口,根据窗口id打开,返回所有的销售食物,以及窗口的优惠状况。
根据已有的windowInfo 查询窗口的所有信息,构造一个WindowController,返回回去即可。windowsinfo 的构造需要调用 类里面的 set函数,将各个字段置为想要的值,目标值用dao操作一个个查询出来。
```
bool joinOrg(OrganizationInfo org);
```
申请加入组织,调用approexam向申请表里面插入一条申请记录。
```
bool getAllOrg(vector& orgs);
```
查询所有可加入的组织。
```
bool getApplyWindow(vector& wins);
```
查询所有可以申请的窗口,调用windowDao函数。
```
bool applyWindow(WindowInfo win);
```
窗口申请,指定窗口id,调用approveExam中的addWindowExam函数插入。
### **windowcontroller**
```
void setWindow(WindowInfo win_){win=win_;}
```
为保护成员win赋值
```
void setSales(vector sales_){sales=sales_;};
```
为保护成员sale赋值
```
void setorderRoles(vector orderRoles_){orderRoles=orderRoles_;};
```
为保护成员orderrole赋值
```
void setUser(User user_){user=user_;}
```
为保护成员user赋值
```
WindowInfo getWindow(){return win;}
```
获取保护成员win的内容
```
vector getSales(){return sales;}
```
获取保护成员sales的内容
```
vector getOrderRoles(){return orderRoles;}
```
获取保护成员orderole的内容
```
void clearOrder(){order.clear();}
```
清除保护成员order的内容。
```
User getUser(){return user;}
```
获取保护成员user的内容
```
Order getOrder(){return order;}
```
获取保护成员order的内容
```
void addSelectSale(SaleBase* sale);
```
为订单添加食物,先把食物的状态改为审批中,然后调用orderdao的addfood函数将食物插入数据库。
```
void addSelectOrderRole(OrderRoleBase* orderRole);
```
为订单添加优惠。直接调用order.addFood即可。
```
void delSelectSale(unsigned int i);
```
删除第i个选择的食物。
```
void delAllOrderRole();
```
删除所有的优惠
```
void delOrderRole(unsigned int i);
```
删除第i个选择的优惠
```
bool checkOut();
```
结算,进行收费操作,数据库操作,返回收费是否成功
```
bool addSale(SaleBase* sale);
```
为窗口添加菜;调用saledao,将传入的菜品信息插入到当前对象对应的win id中。
```
bool addOrderRole(OrderRoleBase* _order);
```
为窗口添加优惠。调用orderroleDao,将传入的优惠信息插入到当前对象对应的win id中。
##4.4 model
Model中有简单的结构体,也有需要衍生的多样性对象,分为两小节分别详细描述如下。
### 简单对象
系统中,较为简单的对象有用户User,订单Order,窗口信息WindowInfo,组织机构信息OrganizationInfo,账单信息Bill,这几个只需要简单的结构体进行存储就可以完成相应的功能。具体定义于数据库中的表定义相同,具体属性描述如下:
- User中有如下属性:
```
string phone;//用户名与电话号码同,
string password;//密码
int type;//用户类型,0:商家 1:管理员
string identityCard;//身份证号
string name;//用户姓名
string sex;//用户性别
string creditCard;//银行卡号,用于账目流转
```
- Bill账单表中有如下属性:
```
int order_id;//内部订单id
string username;//商家用户名(电话号码)
int window_id;//窗口id double price;//价格
string tradingTime;//交易时间 int status;//交易状态
string description;//订单详细信息
```
- WindowInfo窗口信息有如下属性:
```
int id;
string name;//窗口名称
string address;//窗口地址
int organization_id;//所属组织id
int f_id;//设备id
int status;//窗口状态
```
- OrgnitionInfo中有如下属性:
```
int id;
string name; //组织机构名称
string address; //组织地址
string creditCard;//组织银行密码,用于流水走向
```
### 多样性对象
首先是菜品对象,菜品对象抽象出基类SaleBase,存储了一个菜品最基本的信息,不管是任何菜品,这些信息均需要全部包含,具体信息如下:
```
int id;//食物id
int window_id;//所属窗口id
string name;//食物名称
double price;//食物单价
int status;//食物状态 0:售卖中 1:停售 2:审批中
string diningType;//餐饮类型
string unit;//食物单位
QPixmap image;//食物图片
string desciption;//食物描述
```
对于每个食物的具体价格计算,重写 getPayable()函数进行返回不同的价格,单个售卖,重量售卖和套餐售卖衍生类与基类SaleBase的关系类图如下图所示:

通过这个设计模型,可以很好的增加食物类型的扩展性,在上层,对象的所有生成代码可以不用改变。有效的利用代码的复用性,通过实现DaoBase接口,完成对象与数据库的交互,每个子类的价格计算方式不同。对于函数成员:
```
virtual double getPayable()//获取用户选择规格重量等后的应付价格
int getClassType()//获取售卖类型
virtual string toString()//转换成字符串描述,在进行对外展示时,通过这个来进行展示
```
对于打折活动对象的抽象,抽象出OrderRoleBase对象,对象中设置虚函数设置如下:
```
double priceAdjustment(vector sales,double curPrice),
```
这个函数就是这个活动对象的核心,通过实现这个函数,实现不同的活动作用在价格上,生成最终不同的价格。
```
virtual string toString(),
```
这个函数用于描述优惠活动,
```
int id;
string description;//优惠描述
double value;//作用值
vector scope;//该优惠作用于哪些食物id上
int type;//优惠类型,有几种固定的取值
```
在本次活动中我们实现了两种优惠活动,一种是减价SubOrderRole,一种是打折MulOrderRole ,继承于基类OrderRoleBase对象。类图描述如下:

### 单工厂模式
在前面的设计中,涉及到不同对象的生成,虽然我们每个派生类都实现了我们定义的序列化接口,但是在对象的空间分配上,并没有做设计,所以我们需要设计一个工厂,来实现对象的内存空间分配,对象的具体数据生成,由对象自己内部进行生成。在设计工厂时,我们的对象生成较为简单,我们可以只通过简单工厂模式就可以生成,即根据type值的不同,直接生成相应的对象,但是当新增了新的类型时,我们需要修改这个部分代码,违反了开闭原则,所以我们进行了一个小改进,抽象出一个简单工厂父类,当父类中不认识type值的对象时,无法生成对象则直接生成空对象,新增的type由新的工厂继承于父类,重新生成,在类图上描述如下:

在子类的衍生中,先调用父类的生成函数,判断是否为空,若不为空,说明父类已经完成对象生成,若为空,说明是我们子类需要生成的对象,进行判断swich判断生成对象,分配空间即可,结合c++代码描述如下:
```c++
SaleBase* SaleFectory::CreateSale(int type)
{
Q_UNUSED(type);
return nullptr;
}
SaleBase* InitSaleFectory::CreateSale(int type)
{
SaleBase* sale=SaleFectory::CreateSale(type);
if(sale!=nullptr)
return sale;
switch (type) {
case SaleBase::CLASS_SALEBASE:
return new SaleBase;
case SaleBase::CLASS_SALEUNIT:
case SaleBase::Class_SIDEDISH://配菜和单品一样
return new SaleUnit;
case SaleBase::CLASS_SALEWEIGHT:
return new SaleWeight;
case SaleBase::CLASS_SALESETMEAL:
return new SaleSetMeal;
default:
return nullptr;
}
}
```
## 4.5服务器
**收租程序**
> 每周日晚上10点,饮食中心结算每个窗口的经营额,抽取租赁费用后,返还剩余金额。
### **收取规则:**
- 每周收取基础服务费`100`元。
- 营业额2万元以下收取`5%`的租赁费。
- 2万以上的部分收取`10%`的租赁费。
### 添加系统账户流水记录表collect_rent
一条记录表示,一个组织给一个商家发放一次工资。
| 字段 | 类型 | 说明 |
| ---------------- | ---- | ---------------- |
| 流水id | | 自增 |
| 组织id | | 参考org:id |
| 窗口id | | 参考win:id |
| 该周期营业额(总) | | |
| 发放给商家的金额 | | |
| 时间 | | 自动获取当前时间 |
| 说明 | | |
| | | |
### 执行流程
- 首先查询organization Info 找到所有的组织id
- 在bill表里面,遍历所有的组织id
- 遍历bill表里面隶属于组织id的window 对应的全部订单。
- 累加近一周的全部订单金额
- 计算要收取的服务费
- 写入
### 执行
使用crontab定时执行脚本:
- 运行 sudo vim /etc/crontab
- 添加下述内容:
```
0 22 * * 0 root python /home/xxx/xxx/collect_rent.py
```
# 五:UI设计
UI设计分为三个模块,分别是登录/注册界面;商家功能界面和管理员功能界面。
## 5.1登录/注册界面:
### 流程概览

### 登录
界面设计:界面中账号和密码输入框为QLineEdit类型;
用户类型选择为QCheckBox,设置其为互斥框,用户只能选一个;
登录/注册按钮为QPushButton,点击按钮进行相应功能实现。
运行程序,弹出登录/注册界面,用户选择功能;

用户输入账号(手机号)和密码,选择用户类型后登录。若未选择用户类型,弹出“类型未选择”窗口。按下登录按钮将获取到的账号,密码传给后端,后端执行登录函数,并接收来自后端返回的执行结果。如果弹出failed open窗口,表示未连接到数据库;弹出success框,表示数据库连接成功。如果账号密码输入错误,弹出“账号密码不匹配,请重新输入”提示;账号密码输入正确,则进入相应用户页面;
### 注册
界面设计:界面使用了QLineEdit、QComboBox、QCheckBox、QPushButton和Qstackwidget。
其中,QLineEdit获取用户输入的信息;QComboBox用作性别选择;QCheckBox获取用户类型;QPushButton获取用户相应请求;Qstackwidget用作显示不同的页面。
除了所有类型用户都需要输入的信息外,管理员额外需要输入组织名称、组织地址、组织卡号信息。使用QStackwidget实现如下:在widget中添加一个QStackwidget控件,在控件中添加两个页面,页面0:内容为空,界面1:显示管理员需要输入的其他信息。
初始打开注册界面时,显示界面0。对类型选择的QCheckBox设置状态改变槽函数,当选择管理员时,弹出界面1;选择商家时,弹出界面0。
 
执行逻辑:用户选择类型并填入相应信息,点击确定注册按钮,将数据传给后端,执行注册函数,接收后端返回的执行结果。如果返回结果是True,弹出“注册成功”窗口;如果是False,弹出“注册失败,信息输入不合法”窗口。如果注册成功,将返回登陆界面进行登录。
## 5.2商家功能界面:
### 流程概览


界面设计:界面上方是widget中包含三个QLabel,QLabel1显示固定文本“商家管理系统”;QLabel在商家管理界面打开时,显示商家名字;QLabel3在商家打开窗口时,显示窗口名称。包含5个QPushButton,分别是五个功能实现按钮。
界面总体使用QStackwidget实现六个页面的切换:
页面0是空白界面,在商家未选择功能时,显示此界面;界面1是打开窗口界面,界面2是添加菜品页面;界面3是窗口申请页面;界面4是加盟申请页面;界面5是添加折扣页面。
### 打开窗口

#### 打开窗口
用户点击打开窗口按钮,显示下图所示widget并向后端发出请求获取当前可开启窗口信息。widget由一个QTableWidget和一个QPushButton组成。QTableWidget显示从后端传来的当前可以开启的窗口的信息。

用户点击需要打开窗口的勾选框,并点击确定按钮,即可获取到商家所选择窗口的信息,向后端请求获取此窗口的菜品信息,并将其显示在页面1上。
菜品有三种类型:1.单品;2.套餐;3.不可选(例如:番茄酱),为单品时,右方选择框显示“添加”按钮;为套餐时,右方选择框显示“选套餐”按钮;为不可选时,右方选择框显示“自能看,不能选”文本。
界面1如下图所示:

#### 点餐
界面设计:点餐界面显示两个QTablewidget框,左方的框中内容为本窗口可点的菜品,在QTablewidget第六列中插入button,并在插入时对每个button进行添加槽函数;,使其可以能够实时获取商家点击了哪个按钮并在右侧对订单信息进行显示。添加套餐时首先对套餐的内容进行选择,选择完成后将订单信息显示在右侧QTablewidget框中。
商家打开窗口后,即可根据学生的需求进行点餐:
\1. 选择单品添加时,右方订单信息显示框会实时显示已点的菜品。
\2. 选择套餐时,首先弹出“点套餐,显示下一个窗口”提示,点击确定,跳转到选择套餐详细信息窗口。

#### 选择套餐内容窗口
界面设计:套餐界面总体上显示一个QListWidget和一个QPushButton。
List:List中显示不同类型的菜单的菜品,list的每一行都是一个QTableWidge,每一个菜单由一个table显示。
table:table内容设计为三行若干列,第一行展示菜品图片,第二行显示菜品名称,第三行放置添加菜品button;第一列显示该菜单的描述信息,从第二列开始动态添加各个菜品的选择信息,可添加若干个。
添加button:添加槽函数:功能1:每点击选择一个菜品,第一列的菜品份数显示减1,若为0则表示button不可选择点击;功能2:对QPushButton进行了重新封装,增加了button的位置信息,点击触发槽函数可以识别按钮“身份”,如“0行 0列”,从而对用户点击的按钮对应菜单进行匹配。
提交订单button:添加槽函数:将上述按钮的添加结果添加到订单中,并关闭套餐界面,返回点餐界面。

#### 提交订单

界面设计:界面包含两个tablewidget、一个label和一个button;
tablewidget1用来显示订单详细信息;tablewidget2有两列,第一列动态显示当前可选择的折扣,第二列是折扣勾选框。添加槽函数:当对勾选框状态发生变化时,动态获取当前选择的折扣信息,并将当前的折扣信息发送给后端,并发送重新计算价格的信息,将后端返回的价格动态显示在label上。
label:label内容格式为:“订单价格为:xx”,用来显示当前订单总价格。
button:对button按下添加槽函数,按下槽函数将当前订单中餐品信息、餐品份数及总价发送给后端,请求后端结算,接收后端发送的订单是否结算成功的结果,弹出相应的提示框。
### 添加菜品

#### 添加单品
点击添加菜品按钮,显示界面2。

界面设计:界面包含四个LineEdit、一个label,一个ComboBox,两个按钮。
LineEdit:LineEdit获取菜品的名称、单位、总价、描述信息。
ComboBox:ComboBox显示菜品类型的三个选项,分别是“称重,单价,组合”。添加槽函数:当选择“组合”时,弹出添加套餐界面。
label:label显示食物对应的图片。
button:“添加图片”按钮,添加槽函数,显示本机文件夹,选择jpg或png格式的图片,对图片进行裁剪及按比例缩放,将其显示在label中。
“确定”按钮,添加槽函数,获取当前界面上所有信息并组合成结构体,将结构体传给后端并发送添加菜品的请求。
#### 添加套餐
在(1)中的菜品类型中选择组合时,表示要添加套餐,弹出添加套餐界面。

界面设计:首先是右侧的Qtablewidget,其中显示了当前店内可点单的单品,商家对其中单品进行选择,对tablewidget中插入的checkbox添加槽函数,可以实时获取被勾选的框。
button:按下add按钮是在套餐中加入一类,例如盖饭中的“两荤两素”,其中“荤菜”就是一类,“素菜”是另一类。在添加荤菜时,勾选右侧的勾选框,然后选数量为2,类型名称填荤菜,点击add,即添加成功;添加素菜与此类似。
在套餐内容添加完成后,点击“套餐组合完成”,即可结束添加套餐,并将套餐信息发送给后端并请求添加套餐。
### 窗口申请

点击窗口申请显示界面4

界面设计:界面4包含tablewidget、一个button和一个Lineedit。
tablewidget:第一列插入checkbox控件供商家勾选,二三列显示当前可申请的窗口的地址和收租方式。
LineEdit:输入商家所设置的窗口名称。
button:添加槽函数,从界面获取勾选的窗口和窗口名称。点击按钮,即可将要申请的窗口的信息传给后端,并发送加入窗口的请求。
### 加盟申请


界面设计:界面4包含tablewidget、一个button。
tablewidget:第一列插入checkbox控件供商家勾选,二三列显示当前可申请的组织的名称和地址。
button:添加槽函数,从界面获取勾选的组织。点击按钮,即可将要加盟的组织的信息传给后端,并发送加盟的请求。
### 添加折扣


界面设计:包含一个ComboBox、一个spinbox和一个button。
ComboBox:供商家选择打折方式是满减还是打折。
spinbox:供商家选择折扣力度。
button:添加槽函数,点击按钮,获取界面的打折方式和折扣力度,将打折信息传给后端并发送添加打折的请求。根据后端返回的结果进行显示。
## 5.3管理员界面
### 流程概览


管理员登陆成功,右上方显示组织名称;有四个功能,加盟审批、窗口审批、菜品审批,添加窗口。分别为界面1,界面2,界面3,界面4。
### 加盟审批

界面设计:包含一个tablewidget和两个button。
tablewidget:tablewidget第一列插入ckeckbox类型勾选框,其余列显示当前需要审批的加盟的商家id,名称和审批id,一个管理员只能审批自己管理的组织。信息动态显示。
button:分别是审核通过和审核不通过,添加点击按钮槽函数,槽函数获取tablewidget中的被勾选的信息并将其传给后端,发送审核通过/不通过的请求。当点击按钮之后,完成审批,则tablewidget中此信息就会被删除。
### 窗口审批

界面设计:包含一个tablewidget和两个button。
tablewidget:tablewidget第一列插入ckeckbox类型勾选框,其余列显示当前需要审批的窗口位置、名称,商家姓名,商家电话和审批id,一个管理员只能审批自己管理的组织中的窗口。信息动态显示。
button:分别是审核通过和审核不通过,添加点击按钮槽函数,槽函数获取tablewidget中的被勾选的信息并将其传给后端,发送审核通过/不通过的请求。当点击按钮之后,完成审批,则tablewidget中此信息就会被删除。
### 菜品审批

界面设计:包含一个tablewidget和两个button。
tablewidget:tablewidget第一列插入ckeckbox类型勾选框,第二列插入label控件显示菜品图片,其余列显示当前需要添加的菜品的窗口名字,菜品名称,单价,计价方式,单位和菜品描述。一个管理员只能审批自己管理的窗口。信息动态显示。
button:分别是审核通过和审核不通过,添加点击按钮槽函数,槽函数获取tablewidget中的被勾选的信息并将其传给后端,发送审核通过/不通过的请求。当点击按钮之后,完成审批,则tablewidget中此信息就会被删除。
### 添加窗口

界面设计:包含一个LineEdit、一个ComboBox和一个button。
LineEdit:管理员输入窗口位置
ComboBox:管理员选择收租类型,有两种:日租、利润收租。
button:添加槽函数,点击确定,获取LineEdit和ComboBox的信息发送给后端,并请求添加窗口。根据返回结果进行显示。
# 六:代码实现
代码实现系统功能分为基于用户种类两个模块展现:商家模块与管理员模块。
## 6.1商家系统效果展现

### 登录模块效果展现
若用户没有尚未注册账号则点击注册进入注册界面,勾选商家提示框,填写相应信息,若填写无误则注册成功:

注册成功之后返回登陆界面,用户勾选商家提示框,填写相关信息,填写无误则登陆成功进入主界面,否则弹出报错信息,并返回登陆界面:

### 功能模块效果展现
用户登陆成功之后进入系统主界面,商家在此界面可选择打开窗口点菜、添加菜品、窗口申请、加盟申请、添加折等多种功能:
功能展示一:打开窗口点菜。

若选择套餐类型,则打开套餐详情页,提交订单后再返回点餐页面结算。

提交订单:

功能展示二:添加菜品。

若要添加套餐,则选择组和菜品类型,打开组合详情页面进行设置:

功能展示三:窗口申请。

功能展示四:加盟申请

功能展示五:添加折扣。

## 6.2管理员系统效果展现

### 登录模块效果展现
对于登录模块,管理员几乎与上文中的商家相同,唯一的不同点既是管理员在注册时需要输入对应组织相关信息:

### 功能模块效果展现
注册、登录完成之后管理员进入管理员系统主界面,管理员在此界面可选择加盟审批、菜品审批、窗口审批、添加窗口门面等功能。
l 功能展示一:加盟审批。

功能展示二:窗口审批。

功能展示三:菜品审批。

功能展示四:添加窗口门面。
