21 changed files with 2399 additions and 779 deletions
@ -1,144 +1,133 @@ |
|||||
 |
# 智慧教务系统 |
||||
|
|
||||
|
 |
||||
|
|
||||
|
## 项目概述 |
||||
|
|
||||
|
智慧教务系统是一套基于ThinkPHP 8和Vue3开发的现代化教育管理平台,专为教育培训机构、学校等教育组织设计。系统提供全面的学生管理、课程管理、排课管理、校区管理、场地管理、人员管理、合同管理等功能,帮助教育机构实现数字化转型,提高管理效率。 |
||||
|
|
||||
|
## 技术架构 |
||||
|
|
||||
|
### 后端技术栈 |
||||
|
- PHP 8 |
||||
|
- ThinkPHP 8 |
||||
|
- MySQL 数据库 |
||||
|
- Workman 高性能框架(消息队列、计划任务) |
||||
|
|
||||
:fa-quote-left: 如果对您有帮助,您可以点右上角 ⭐“Star” 收藏一下 ,获取第一时间更新,谢谢! :fa-quote-right: |
### 前端技术栈 |
||||
|
- Vue 3 |
||||
|
- TypeScript |
||||
### NIUSHOP 开源商城 V6 技术选型 |
- Element Plus |
||||
NIUSHOP V6 使用 **NIUCLOUD-ADMIN** 底层框架设计, 国内首家唯一支持TP8框架 ,前端采用市面最流行的技术栈 **Vite+TypeScript+Vue3+ElementPlus** ,后端采用 **THINKPHP8、PHP8** 语言搭建。配合 **Workman** 高性能框架实现消息队列,计划任务处理。内置集成用户权限、代码生成器、表单设计、云存储、短信发送、素材中心、微信及公众号、支付、模版消息推送Api模块一系列开箱即用功能,这是一款快速可以开发企业级应用的软件系统。 |
- Vite |
||||
|
|
||||
|
### 移动端技术栈 |
||||
### 设计理念 |
- UniApp |
||||
|
- Vue 3 |
||||
强大的多应用+插件组合设计理念,低耦合,高内聚 |
- TypeScript |
||||
|
|
||||
全新生态设计,多应用聚合+多插件组合运营模式全新升级 ,支持共同会员体系下商城,会员卡,上门服务等等多种商业模式随机组合,DIY装修出最强的软件系统 |
## 系统功能模块 |
||||
 |
|
||||
|
### 学生管理 |
||||
### 插件化,完全为开发者二次开发而生 |
- 学生信息管理 |
||||
V6底层采用插件化模式设计,可以做到多种插件共存,组合使用。比如您有一个项目是旅游的项目,这个项目的要求是,既有商城的功能,又有旅游项目的销售,还需要进行会员的管理,甚至于还要客服系统。传统的实现方式是,找多个源码,东拼西凑,二次开发,或者部署多套独立的系统,配合起来。而今天,使用V6,可以通过组装的方式,在一套体系中实现,随着发展,会有越来越多的各行各业的插件和应用上架。您对于项目的定制,可能只需要简单组装,装修页面,就可以最终实现功能交付。 |
- 学生档案管理 |
||||
 |
- 学生考勤管理 |
||||
|
- 学生成绩管理 |
||||
### 首创强大的一键云安装,云编译,云发布,升级引擎 |
|
||||
给我一个支点,必能撬动地球。V6简单方便的一键云安装,云编译工具,让您小白也能变大师。 |
### 课程管理 |
||||
V6内置在线升级功能,系统会全自动化帮您升级文件。产品的更新只需一键完成 。 |
- 课程信息管理 |
||||
HBUILDER, VSCODE,微信小程序开发工具,打包,上传,发布! V6强大的小程序一键傻瓜式发布系统,任何开发环境都不再需要搭建!鼠标一点完成小程序升级发布。 |
- 课程分类管理 |
||||
 |
- 课程资源管理 |
||||
 |
|
||||
 |
### 排课管理 |
||||
|
- 课表编排 |
||||
### NIUCLOUD-ADMIN 是什么? |
- 教师排课 |
||||
NIUCLOUD-ADMIN是一款快速开发通用管理后台框架,整体功能架构全部精心设计!代码干净整洁!低耦合,高质量!!!前后端API接口完全分离 :raised_hands: !!!前端采用最新技术 **Vite+TypeScript+Vue3+ElementPlus** ,后台采用PHP8、MYSQL8、THINKPHP8 全部最新技术栈,内置Workman高性能消息队列,计划任务处理,完全兼容容器路由运行技术。 内置代码生成器,插件生成器,一键云编译、一键云部署,集成用户权限、表单设计、云存储、短信发送、素材中心、微信及公众号、Api模块一系列开箱即用功能,是一款快速搭建开发企业级应用的软件系统。源码100%开源无加密!框架采用MIT协议,终身免费,商用免费! |
- 教室安排 |
||||
|
- 时间段管理 |
||||
请到官方网站了解更多 http://www.niucloud.com |
|
||||
|
### 校区管理 |
||||
### NIUSHOP V6 和 NIUCLOUD-ADMIN 的区别和关系怎样的? |
- 校区信息管理 |
||||
首先,NIUSHOP 产品系列是以商城系统(2016年立项研发,V1一直升级到V5版本, V6是完全从零研发的新产品)为主的独立的产品线。NIUCLOUD产品系列(从2022年底开始立项研发)是以NIUCLOUD-ADMIN框架(分单用户独立版、SAAS版)为根本,在此基础上发展各种应用插件,包括第三方开发者生态产品,主要以SAAS产品系列为主。而 NIUSHOP V6 是 使用 **NIUCLOUD-ADMIN** 框架单用户独立版设计的商城应用,以NIUSHOP品牌推广。一句话概括就是,单用户专业化系统以NIUSHOP品牌整体运营推广,SAAS版本插件和应用市场以及NIUCLOUD框架(单用户、SAAS)以NIUCLOUD品牌运营推广。NIUSHOP和NIUCLOUD都是牛之云科技有限公司投资研发运作。 |
- 校区资源配置 |
||||
|
|
||||
### NIUCLOUD-ADMIN 技术特点 |
### 场地管理 |
||||
|
- 教室管理 |
||||
- 支持composer快速安装扩展,支持 **redis** 缓存以及消息队列,支持多语言设计开发,采用严格的 **restful** 的api设计开发。 |
- 场地预约 |
||||
- 后台前后端分离采用 **element-plus、vue3.0、typescript、vite、pina** 等前端技术,同时使用i18n支持国际化多语言开发。 |
- 场地使用记录 |
||||
- 手机端采用uniapp前后端分离,使用 **uview、vue3.0、typescript、vite、pina** 前端技术,支持h5,微信小程序,支付宝小程序,抖音小程序等使用场景。 |
|
||||
- 支持安装多个应用多插件组合使用。 |
### 人员管理 |
||||
- 前端以及后端采用严格的多语言开发规范,包括前端展示,api接口返回,数据验证,错误返回等全部使用多语言设计规范,使开发者能够真生意义上实现多语言的开发需求。 |
- 教师管理 |
||||
- 框架已经搭建好常规系统的开发底层,具体的底层功能包括:管理员管理,权限管理,网站设置,计划任务管理,素材管理,会员管理,会员账户管理,微信公众号以及小程序管理,支付管理,第三方登录管理,消息管理,短信管理,文章管理,前端装修等全面的基础功能,这样开发者不需要开发基础的结构而专心开发业务。 |
- 职工管理 |
||||
- 内置支持微信/支付宝支付,微信公众号/小程序/短信消息管理,阿里云/腾讯云短信,七牛云/阿里云存储等基础的功能扩展,后续会根据实际业务不断扩展基础组件。 |
- 人员排班 |
||||
- 强大的代码生成器。开发者根据数据表可以一键生成基础的业务代码,包括:后台php业务代码以及对应的前端vue代码。 |
|
||||
- 手机端内置了自定义装修,同时提供了基础的开发组件,强大的DIY组件自定义功能,允许开发者按照规范开发第三方DIY组件及自定义页面实现业务需求 |
### 合同管理 |
||||
|
- 合同创建 |
||||
|
- 合同审批 |
||||
### 强者归来,选择NIUSHOP 开源商城 V6, 不止于此,未来无限可能 |
- 合同执行跟踪 |
||||
|
|
||||
酒香不怕巷子深,花香自有蝶飞来,NIUSHOP和NIUCLOUD开发者生态圈正在快速的膨胀发展,越来越多的开发者正在积极参与,统一的代码规范,统一的开发模式和思路,产品的二次开发和项目定制正在,规范化,积木化,快速简单化。只需用心细读一回代码,二次开发效率和质量完全得到保证!完全插件化的设计,多应用,多插件模式。随着生态的逐步完善,组合即用! 我们官方会努力帮大家搭建好基础服务平台,为所有的开发者,创业者,码农,互联网从业者,提供一个资源互换,信息共享,产品推广的生态圈。共享百万开发者产品,共享亿万市场资源。 |
### 学生课程管理 |
||||
|
- 课时管理(总课时、赠送课时) |
||||
|
- 课程有效期管理 |
||||
### 界面截图 :point_right: |
- 已用课时统计 |
||||
 |
- 单次课时设置 |
||||
|
|
||||
 |
### 用户权限管理 |
||||
|
- 用户管理 |
||||
 |
- 角色管理 |
||||
|
- 菜单权限管理 |
||||
 |
- 操作日志记录 |
||||
|
|
||||
 |
### 系统配置管理 |
||||
|
- 系统参数配置 |
||||
 |
- 字典管理 |
||||
|
- 附件管理 |
||||
### 操作指南 |
|
||||
[NIUSHOP官网地址](https://www.niushop.com) |
### 通知管理 |
||||
| [NIUCLOUD官网地址](https://www.niucloud.com) |
- 微信通知 |
||||
| [服务市场](https://www.niucloud.com) |
- 小程序通知 |
||||
| [使用手册](https://www.niucloud.com/doc) |
- 短信通知 |
||||
| [二开手册](https://www.niucloud.com/doc) |
- 通知日志记录 |
||||
| [开发视频](https://www.niucloud.com/doc) |
|
||||
| [API接口手册](https://api.niucloud.com/apidoc.html?target_id=001) |
### 计划任务管理 |
||||
| [论坛地址](https://bbs.niucloud.com) |
- 定时任务配置 |
||||
|
- 任务执行记录 |
||||
### V6安装教程 |
- 任务调度管理 |
||||
|
|
||||
- [安装指引说明](https://www.kancloud.cn/niushop/niushop_v6/3224842) |
## 系统特点 |
||||
- [宝塔安装部署V6](https://www.kancloud.cn/niushop/niushop_v6/3226724) |
|
||||
- [PHPStudy安装部署V6](https://www.kancloud.cn/niushop/niushop_v6/3226728) |
### 插件化设计 |
||||
|
系统采用插件化设计,支持多插件共存和组合使用,便于功能扩展和定制开发。 |
||||
|
|
||||
### 二次开发视频教程 |
### 多端支持 |
||||
|
同时支持PC管理端、H5移动端、微信小程序等多种终端,满足不同场景的使用需求。 |
||||
- [开发准备工作与创建插件](https://niucloud-document-video.oss-cn-beijing.aliyuncs.com/video/1-1.mp4) |
|
||||
- [插件目录整体说明](https://niucloud-document-video.oss-cn-beijing.aliyuncs.com/video/2-1.mp4) |
### 多语言支持 |
||||
- [插件安装与打包原理](https://niucloud-document-video.oss-cn-beijing.aliyuncs.com/video/8-1.mp4) |
系统内置多语言支持,包括前端展示、API接口返回、数据验证、错误提示等全方位的多语言设计。 |
||||
- [消息队列](https://niucloud-document-video.oss-cn-beijing.aliyuncs.com/video/9-1.mp4) |
|
||||
- [计划任务](https://niucloud-document-video.oss-cn-beijing.aliyuncs.com/video/10-1.mp4) |
### 高性能架构 |
||||
- [DIY自定义小组件和页面装修开发](https://niucloud-document-video.oss-cn-beijing.aliyuncs.com/video/11-1.mp4) |
采用ThinkPHP 8框架,结合Workman高性能消息队列和计划任务处理,保证系统的高效运行。 |
||||
- [支付接口开发](https://niucloud-document-video.oss-cn-beijing.aliyuncs.com/video/12-1.mp4) |
|
||||
- [插件升级包打包流程以及云编译](https://niucloud-document-video.oss-cn-beijing.aliyuncs.com/video/13-1.mp4) |
### 安全可靠 |
||||
|
完善的权限管理机制,详细的操作日志记录,确保系统数据安全和操作可追溯。 |
||||
|
|
||||
### 演示地址 |
## 数据库设计 |
||||
- 管理后台演示网址:[<a href='http://v6.site.niucloud.com/' target="_blank"> 查看 </a>] |
|
||||
<a href='http://v6.site.niucloud.com/' target="_blank">http://v6.site.niucloud.com 账号:admin 密码:123456 |
系统采用MySQL数据库,主要表类别包括: |
||||
|
|
||||
- H5前端演示网址:[<a href='https://v6.site.niucloud.com/wap/addon/shop/pages/index' target="_blank"> 查看 </a>] |
- 学生课程关联表(school_student_courses):记录学生选课信息、课时信息等 |
||||
<a href='https://v6.site.niucloud.com/wap/addon/shop/pages/index' target="_blank">https://v6.site.niucloud.com/wap/addon/shop/pages/index |
- 系统用户表(school_sys_user):管理系统用户信息 |
||||
|
- 系统角色表(school_sys_role):管理角色及权限信息 |
||||
### 加入开发者生态,一起助力成就程序员创业梦想!!! |
- 系统菜单表(school_sys_menu):管理系统菜单及权限 |
||||
|
- 系统配置表(school_sys_config):存储系统配置信息 |
||||
加入企业微信群技术交流,请扫描下面二维码 :point_down: |
- 系统字典表(school_sys_dict):管理系统字典数据 |
||||
|
- 系统通知表(school_sys_notice):管理系统通知模板 |
||||
 |
- 通知日志表(school_sys_notice_log):记录通知发送日志 |
||||
|
- 短信日志表(school_sys_notice_sms_log):记录短信发送日志 |
||||
|
- 计划任务表(school_sys_cron_task):管理系统定时任务 |
||||
### 产品LOGO |
- 计划任务日志表(school_sys_schedule_log):记录任务执行日志 |
||||
|
- 用户操作日志表(school_sys_user_log):记录用户操作日志 |
||||
 |
- 附件管理表(school_sys_attachment):管理系统附件 |
||||
|
|
||||
 |
## 版权信息 |
||||
|
|
||||
 |
版权所有 Copyright © 2023-2024 智慧教务系统 |
||||
|
|
||||
|
杭州盛宇网络科技有限公司提供技术支持 |
||||
### 开源使用须知 |
|
||||
|
|
||||
1.允许用于个人学习、毕业设计、教学案例、公益事业、商业使用; |
|
||||
|
|
||||
2.本框架应用源代码所有权和著作权归niucloud官方所有,基于niucloud-admin框架开发的应用,所有权和著作权归应用开发商所有。但必须明确声明是基于niucloud-admin框架开发,请自觉遵守,否则产生的一切任何后果责任由侵权者自负; |
|
||||
|
|
||||
3.禁止修改框架代码并再次发布框架衍生版等与niucloud-admin框架产生恶意竞争或对抗的行为; |
|
||||
|
|
||||
4.本框架源码全部开源;包括前端,后端,无任何加密; |
|
||||
|
|
||||
5.商用请仔细审查代码和漏洞,不得用于任一国家许可范围之外的商业应用,产生的一切任何后果责任自负; |
|
||||
|
|
||||
6.一切事物有个人喜好的标准,本开源代码意在分享,不喜勿喷。 |
|
||||
|
|
||||
|
|
||||
### 版权信息 |
|
||||
版权所有Copyright © 2015-2025 niucloud-admin 版权所有 |
|
||||
|
|
||||
All rights reserved。 |
|
||||
|
|
||||
杭州数字云动科技有限公司 |
|
||||
杭州牛之云科技有限公司 |
|
||||
|
|
||||
提供技术支持 |
|
||||
@ -0,0 +1,15 @@ |
|||||
|
<?php |
||||
|
|
||||
|
namespace app\command\ClientCommand; |
||||
|
|
||||
|
use think\console\Command; |
||||
|
use think\console\Input; |
||||
|
use think\console\Output; |
||||
|
|
||||
|
class TestCommand extends Command |
||||
|
{ |
||||
|
protected function execute(Input $input, Output $output) |
||||
|
{ |
||||
|
$output->writeln('test'); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
<?php |
||||
|
declare (strict_types = 1); |
||||
|
|
||||
|
namespace app\command; |
||||
|
|
||||
|
use app\job\transfer\schedule\CourseScheduleJob; |
||||
|
use app\job\transfer\schedule\ResourceAutoAllocation; |
||||
|
use think\console\Command; |
||||
|
use think\console\Input; |
||||
|
use think\console\input\Argument; |
||||
|
use think\console\input\Option; |
||||
|
use think\console\Output; |
||||
|
|
||||
|
class TestCommand extends Command |
||||
|
{ |
||||
|
protected function configure() |
||||
|
{ |
||||
|
// 指令配置 |
||||
|
$this->setName('testcommand') |
||||
|
->setDescription('the testcommand command'); |
||||
|
} |
||||
|
|
||||
|
protected function execute(Input $input, Output $output) |
||||
|
{ |
||||
|
// 指令输出 |
||||
|
$obj = new CourseScheduleJob(); |
||||
|
$obj->doJob(); |
||||
|
$output->writeln('testcommand'); |
||||
|
} |
||||
|
} |
||||
@ -1,20 +0,0 @@ |
|||||
<?php |
|
||||
|
|
||||
namespace app\job\custmer; |
|
||||
|
|
||||
use app\model\customer_resources\CustomerResources; |
|
||||
use app\model\resource_sharing\ResourceSharing; |
|
||||
use core\base\BaseJob; |
|
||||
|
|
||||
class ResourceAutoAllocation extends BaseJob |
|
||||
{ |
|
||||
public function doJob() |
|
||||
{ |
|
||||
//获取当天的资源列表模型CustomerResources |
|
||||
// CustomerResources::where('created_at') |
|
||||
//获取当天销售获取资源的数量ResourceSharing |
|
||||
// ResourceSharing::where('shared_at')-> |
|
||||
//遍历资源列表按照资源数量的最少的人员优先分配 |
|
||||
return true; |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,43 @@ |
|||||
|
<?php |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | Niucloud-admin 企业快速开发的多应用管理平台 |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | 官方网址:https://www.niucloud.com |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | niucloud团队 版权所有 开源版本可自由商用 |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | Author: Niucloud Team |
||||
|
// +---------------------------------------------------------------------- |
||||
|
|
||||
|
namespace app\job\schedule; |
||||
|
|
||||
|
use app\model\course_schedule\CourseSchedule; |
||||
|
use core\base\BaseJob; |
||||
|
use think\facade\Log; |
||||
|
|
||||
|
/** |
||||
|
* 队列异步调用定时任务 |
||||
|
*/ |
||||
|
class HandleCourseSchedule extends BaseJob |
||||
|
{ |
||||
|
public function doJob() |
||||
|
{ |
||||
|
Log::write('课程状态自动化任务开始' . date('Y-m-d h:i:s')); |
||||
|
$this->handleCourseStatus(); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
private function handleCourseStatus() |
||||
|
{ |
||||
|
$list = CourseSchedule::where('course_date','<',date('Y-m-d'))->select(); |
||||
|
if (!empty($list)) { |
||||
|
foreach ($list as $item) { |
||||
|
CourseSchedule::update([ |
||||
|
'status' => 'completed' |
||||
|
], [ |
||||
|
'id' => $item['id'] |
||||
|
]); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,829 @@ |
|||||
|
<?php |
||||
|
|
||||
|
namespace app\job\transfer\schedule; |
||||
|
|
||||
|
use app\model\course\Course; |
||||
|
use app\model\order_table\OrderTable; |
||||
|
use app\model\personnel\Personnel; |
||||
|
use app\model\resource_sharing\ResourceSharing; |
||||
|
use app\model\six_speed\SixSpeed; |
||||
|
use app\model\customer_resources\CustomerResources; |
||||
|
use app\service\admin\performance\PerformanceService; |
||||
|
use app\service\api\apiService\CommonService; |
||||
|
use core\base\BaseJob; |
||||
|
use think\facade\Db; |
||||
|
use think\facade\Log; |
||||
|
|
||||
|
/** |
||||
|
* 销售绩效核算 |
||||
|
* Class PerformanceCalculation |
||||
|
* @package app\job\transfer\schedule |
||||
|
*/ |
||||
|
class PerformanceCalculation extends BaseJob |
||||
|
{ |
||||
|
/** |
||||
|
* 员工工龄阶段 |
||||
|
*/ |
||||
|
const STAGE_TRIAL = '试用期1'; // 试用期 |
||||
|
const STAGE_REGULAR = '转正'; // 转正 |
||||
|
const STAGE_HALF_YEAR = '转正后半年'; // 转正后半年 |
||||
|
|
||||
|
/** |
||||
|
* 资源来源类型 |
||||
|
*/ |
||||
|
const SOURCE_INTERNAL_STAFF = 4; // 内部员工资源 |
||||
|
|
||||
|
/** |
||||
|
* 绩效服务类实例 |
||||
|
* @var PerformanceService |
||||
|
*/ |
||||
|
protected $performanceService; |
||||
|
|
||||
|
/** |
||||
|
* 构造函数 |
||||
|
*/ |
||||
|
public function __construct() |
||||
|
{ |
||||
|
parent::__construct(); |
||||
|
$this->performanceService = new PerformanceService(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 执行任务 |
||||
|
*/ |
||||
|
public function doJob() |
||||
|
{ |
||||
|
Log::write('开始执行销售绩效核算'); |
||||
|
|
||||
|
// 获取所有需要计算绩效的订单 |
||||
|
$orders = $this->getOrders(); |
||||
|
if (empty($orders)) { |
||||
|
Log::write('没有需要计算绩效的订单'); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 获取绩效配置 |
||||
|
$performanceConfig = $this->getPerformanceConfig(); |
||||
|
if (empty($performanceConfig)) { |
||||
|
Log::write('未找到绩效配置'); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 计算每个订单的绩效 |
||||
|
$results = []; |
||||
|
foreach ($orders as $order) { |
||||
|
// 首先判断是否为内部员工资源 |
||||
|
$isInternalStaffResource = $this->isInternalStaffResource($order); |
||||
|
|
||||
|
if ($isInternalStaffResource) { |
||||
|
// 处理内部员工资源的绩效计算 |
||||
|
$internalResult = $this->calculateInternalStaffPerformance($order, $performanceConfig); |
||||
|
if (!empty($internalResult)) { |
||||
|
$results[] = $internalResult; |
||||
|
} |
||||
|
} else { |
||||
|
// 判断是否为多人介入的订单 |
||||
|
$isMultiPersonInvolved = $this->isMultiPersonInvolved($order); |
||||
|
|
||||
|
if ($isMultiPersonInvolved) { |
||||
|
// 处理多人介入的绩效计算 |
||||
|
$multiResults = $this->calculateMultiPersonPerformance($order, $performanceConfig); |
||||
|
if (!empty($multiResults)) { |
||||
|
$results = array_merge($results, $multiResults); |
||||
|
} |
||||
|
} else { |
||||
|
// 处理单人的绩效计算 |
||||
|
$result = $this->calculateOrderPerformance($order, $performanceConfig); |
||||
|
$results[] = $result; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 保存绩效结果 |
||||
|
$this->savePerformanceResults($results); |
||||
|
|
||||
|
Log::write('销售绩效核算完成,共处理' . count($results) . '个订单'); |
||||
|
|
||||
|
return $results; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 判断是否为内部员工资源 |
||||
|
* @param array $order 订单信息 |
||||
|
* @return bool 是否为内部员工资源 |
||||
|
*/ |
||||
|
protected function isInternalStaffResource($order) |
||||
|
{ |
||||
|
if (empty($order['resource_id'])) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
// 查询资源表中的记录 |
||||
|
$resource = CustomerResources::where('id', $order['resource_id'])->find(); |
||||
|
|
||||
|
// 如果没有找到资源记录,则不是内部员工资源 |
||||
|
if (empty($resource)) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
// 判断资源来源是否为内部员工 |
||||
|
return isset($resource['source']) && $resource['source'] == self::SOURCE_INTERNAL_STAFF; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 计算内部员工资源的绩效 |
||||
|
* @param array $order 订单信息 |
||||
|
* @param array $config 绩效配置 |
||||
|
* @return array 绩效计算结果 |
||||
|
*/ |
||||
|
protected function calculateInternalStaffPerformance($order, $config) |
||||
|
{ |
||||
|
$staffId = $order['staff_id']; |
||||
|
$campusId = isset($order['campus_id']) ? $order['campus_id'] : 0; |
||||
|
$resourceId = isset($order['resource_id']) ? $order['resource_id'] : 0; |
||||
|
|
||||
|
// 获取员工信息 |
||||
|
$staff = Personnel::where('id', $staffId)->find(); |
||||
|
if (empty($staff)) { |
||||
|
Log::write('未找到员工信息,员工ID:' . $staffId); |
||||
|
return [ |
||||
|
'order_id' => $order['id'], |
||||
|
'staff_id' => $staffId, |
||||
|
'performance_amount' => 0, |
||||
|
'status' => 'failed', |
||||
|
'message' => '未找到员工信息' |
||||
|
]; |
||||
|
} |
||||
|
|
||||
|
// 获取课程类型前缀 |
||||
|
$courseType = $this->getCourseTypePrefix($order); |
||||
|
|
||||
|
// 获取内部员工绩效配置 |
||||
|
$xspjConfig = $config['XSPJ']; |
||||
|
$internalStaffKey = $courseType . '_internalStaff'; |
||||
|
$performanceAmount = isset($xspjConfig[$internalStaffKey]) ? floatval($xspjConfig[$internalStaffKey]) : 0; |
||||
|
|
||||
|
if ($performanceAmount <= 0) { |
||||
|
Log::write('未找到有效的内部员工绩效配置,订单ID:' . $order['id']); |
||||
|
return [ |
||||
|
'order_id' => $order['id'], |
||||
|
'staff_id' => $staffId, |
||||
|
'performance_amount' => 0, |
||||
|
'status' => 'failed', |
||||
|
'message' => '未找到有效的内部员工绩效配置' |
||||
|
]; |
||||
|
} |
||||
|
|
||||
|
// 判断订单类型(新订单或续费) |
||||
|
$isRenewal = $this->isRenewalOrder($order); |
||||
|
|
||||
|
// 获取新资源数和续费资源数 |
||||
|
$newResourceCount = $isRenewal ? 0 : 1; |
||||
|
$renewResourceCount = $isRenewal ? 1 : 0; |
||||
|
|
||||
|
// 保存使用的绩效配置和算法 |
||||
|
$performanceConfig = json_encode([ |
||||
|
'is_renewal' => $isRenewal, |
||||
|
'is_internal_staff' => true, |
||||
|
'performance_key' => $internalStaffKey |
||||
|
], JSON_UNESCAPED_UNICODE); |
||||
|
|
||||
|
$performanceAlgorithm = json_encode([ |
||||
|
'order_id' => $order['id'], |
||||
|
'resource_id' => $resourceId, |
||||
|
'internal_staff_resource' => true |
||||
|
], JSON_UNESCAPED_UNICODE); |
||||
|
|
||||
|
// 添加一条绩效汇总记录 |
||||
|
$this->addPerformanceSummary([ |
||||
|
'staff_id' => $staffId, |
||||
|
'resource_id' => $resourceId, |
||||
|
'order_id' => $order['id'], |
||||
|
'order_status' => PerformanceService::ORDER_STATUS_PENDING, |
||||
|
'performance_type' => PerformanceService::PERFORMANCE_TYPE_SALES, |
||||
|
'performance_value' => $performanceAmount, |
||||
|
'remarks' => '内部员工关单绩效' |
||||
|
]); |
||||
|
|
||||
|
return [ |
||||
|
'order_id' => $order['id'], |
||||
|
'staff_id' => $staffId, |
||||
|
'personnel_id' => $staffId, |
||||
|
'campus_id' => $campusId, |
||||
|
'resource_id' => $resourceId, |
||||
|
'is_renewal' => $isRenewal, |
||||
|
'performance_amount' => $performanceAmount, |
||||
|
'new_resource_count' => $newResourceCount, |
||||
|
'renew_resource_count' => $renewResourceCount, |
||||
|
'performance_date' => date('Y-m-d'), |
||||
|
'performance_config' => $performanceConfig, |
||||
|
'performance_algorithm' => $performanceAlgorithm, |
||||
|
'remarks' => '内部员工关单绩效', |
||||
|
'status' => 'success', |
||||
|
'created_at' => time(), |
||||
|
'updated_at' => time() |
||||
|
]; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取需要计算绩效的订单 |
||||
|
* @return array 订单列表 |
||||
|
*/ |
||||
|
protected function getOrders() |
||||
|
{ |
||||
|
// 这里可以根据实际需求筛选需要计算绩效的订单 |
||||
|
// 例如:只计算已完成的订单、特定时间段内的订单等 |
||||
|
$orders = OrderTable::with(['course', 'personnel']) |
||||
|
->where('order_status', 'completed') // 假设只计算已完成的订单 |
||||
|
->where(function ($query) { |
||||
|
$query->where('performance_calculated', 0) // 未计算过绩效的订单 |
||||
|
->whereOr('accounting_time', null); // 或者核算时间为空的订单 |
||||
|
}) |
||||
|
->select() |
||||
|
->toArray(); |
||||
|
|
||||
|
// 额外检查:过滤掉已经在绩效汇总表中存在的订单 |
||||
|
if (!empty($orders)) { |
||||
|
$orderIds = array_column($orders, 'id'); |
||||
|
$existingOrderIds = Db::name('school_performance_summary') |
||||
|
->whereIn('order_id', $orderIds) |
||||
|
->where('performance_type', PerformanceService::PERFORMANCE_TYPE_SALES) |
||||
|
->column('order_id'); |
||||
|
|
||||
|
if (!empty($existingOrderIds)) { |
||||
|
Log::write('发现' . count($existingOrderIds) . '个订单已在绩效汇总表中存在,将被跳过'); |
||||
|
$orders = array_filter($orders, function($order) use ($existingOrderIds) { |
||||
|
return !in_array($order['id'], $existingOrderIds); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Log::write('找到' . count($orders) . '个需要计算绩效的订单'); |
||||
|
return $orders; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取绩效配置 |
||||
|
* @return array 绩效配置 |
||||
|
*/ |
||||
|
protected function getPerformanceConfig() |
||||
|
{ |
||||
|
$commonService = new CommonService(); |
||||
|
|
||||
|
// 获取销售基础绩效配置 |
||||
|
$xsyjConfig = $commonService->getDictionary(['key' => 'XSYJ']); |
||||
|
|
||||
|
// 获取多人介入绩效配置 |
||||
|
$xspjConfig = $commonService->getDictionary(['key' => 'XSPJ']); |
||||
|
|
||||
|
// 获取课程基础绩效配置 |
||||
|
$courseTypeConfig = $commonService->getDictionary(['key' => 'course_type']); |
||||
|
|
||||
|
return [ |
||||
|
'XSYJ' => $xsyjConfig, |
||||
|
'XSPJ' => $xspjConfig, |
||||
|
'course_type' => $courseTypeConfig |
||||
|
]; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 判断是否为多人介入的订单 |
||||
|
* @param array $order 订单信息 |
||||
|
* @return bool 是否为多人介入 |
||||
|
*/ |
||||
|
protected function isMultiPersonInvolved($order) |
||||
|
{ |
||||
|
if (empty($order['resource_id']) || empty($order['staff_id'])) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
// 查询资源共享表中的记录 |
||||
|
$resourceSharing = ResourceSharing::where('resource_id', $order['resource_id']) |
||||
|
->where('shared_by', '<>', 0) |
||||
|
->order('id', 'asc') |
||||
|
->find(); |
||||
|
|
||||
|
// 如果没有找到资源共享记录,则不是多人介入 |
||||
|
if (empty($resourceSharing)) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
// 如果资源共享记录中的user_id与订单的staff_id不同,则是多人介入 |
||||
|
return $resourceSharing['user_id'] != $order['staff_id']; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 计算多人介入的绩效 |
||||
|
* @param array $order 订单信息 |
||||
|
* @param array $config 绩效配置 |
||||
|
* @return array 绩效计算结果数组 |
||||
|
*/ |
||||
|
protected function calculateMultiPersonPerformance($order, $config) |
||||
|
{ |
||||
|
$results = []; |
||||
|
$resourceId = $order['resource_id']; |
||||
|
$staffId = $order['staff_id']; |
||||
|
|
||||
|
// 查询资源共享表中的记录(资源归属人) |
||||
|
$resourceOwner = ResourceSharing::where('resource_id', $resourceId) |
||||
|
->where('shared_by', '<>', 0) |
||||
|
->order('id', 'asc') |
||||
|
->find(); |
||||
|
|
||||
|
if (empty($resourceOwner)) { |
||||
|
Log::write('未找到资源归属人,订单ID:' . $order['id']); |
||||
|
return []; |
||||
|
} |
||||
|
|
||||
|
$resourceOwnerId = $resourceOwner['user_id']; |
||||
|
|
||||
|
// 查询六速表中的访问记录 |
||||
|
$sixSpeed = SixSpeed::where('resource_id', $resourceId)->find(); |
||||
|
|
||||
|
// 确定介入类型 |
||||
|
$visitType = 'followUp'; // 默认为跟进 |
||||
|
$visitTypeDesc = '跟进关单'; |
||||
|
|
||||
|
if (!empty($sixSpeed)) { |
||||
|
if (!empty($sixSpeed['first_visit_time'])) { |
||||
|
$visitType = 'firstVisit'; |
||||
|
$visitTypeDesc = '一访关单'; |
||||
|
} elseif (!empty($sixSpeed['second_visit_time'])) { |
||||
|
$visitType = 'secondVisit'; |
||||
|
$visitTypeDesc = '二访关单'; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 获取课程类型前缀 |
||||
|
$courseType = $this->getCourseTypePrefix($order); |
||||
|
|
||||
|
// 获取多人介入绩效配置 |
||||
|
$xspjConfig = $config['XSPJ']; |
||||
|
$percentageKey = $courseType . '_' . $visitType; |
||||
|
$percentage = isset($xspjConfig[$percentageKey]) ? floatval($xspjConfig[$percentageKey]) : 0; |
||||
|
|
||||
|
if ($percentage <= 0) { |
||||
|
Log::write('未找到有效的多人介入绩效配置,订单ID:' . $order['id']); |
||||
|
return []; |
||||
|
} |
||||
|
|
||||
|
// 计算订单的基础绩效 |
||||
|
$basePerformance = $this->calculateOrderBasePerformance($order, $config); |
||||
|
|
||||
|
// 计算完成订单人的绩效 |
||||
|
$staffPerformance = $basePerformance * ($percentage / 100); |
||||
|
|
||||
|
// 计算资源归属人的绩效 |
||||
|
$ownerPerformance = $basePerformance * (1 - $percentage / 100); |
||||
|
|
||||
|
// 创建完成订单人的绩效记录 |
||||
|
$staffResult = $this->createPerformanceResult($order, $staffId, $staffPerformance, $visitTypeDesc . '(完成订单人)'); |
||||
|
$results[] = $staffResult; |
||||
|
|
||||
|
// 创建资源归属人的绩效记录 |
||||
|
$ownerResult = $this->createPerformanceResult($order, $resourceOwnerId, $ownerPerformance, $visitTypeDesc . '(资源归属人)'); |
||||
|
$results[] = $ownerResult; |
||||
|
|
||||
|
return $results; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取课程类型前缀 |
||||
|
* @param array $order 订单信息 |
||||
|
* @return string 课程类型前缀(xj, xf, qt) |
||||
|
*/ |
||||
|
protected function getCourseTypePrefix($order) |
||||
|
{ |
||||
|
// 判断订单类型 |
||||
|
if (isset($order['order_type'])) { |
||||
|
if ($order['order_type'] == 1) { |
||||
|
return 'xj'; // 新建 |
||||
|
} elseif ($order['order_type'] == 2) { |
||||
|
return 'xf'; // 续费 |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return 'qt'; // 其他 |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 计算订单的基础绩效金额 |
||||
|
* @param array $order 订单信息 |
||||
|
* @param array $config 绩效配置 |
||||
|
* @return float 基础绩效金额 |
||||
|
*/ |
||||
|
protected function calculateOrderBasePerformance($order, $config) |
||||
|
{ |
||||
|
// 获取员工信息 |
||||
|
$staff = Personnel::where('id', $order['staff_id'])->find(); |
||||
|
if (empty($staff)) { |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
// 获取课程信息 |
||||
|
$course = Course::where('id', $order['course_id'])->find(); |
||||
|
if (empty($course)) { |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
// 判断员工工龄阶段 |
||||
|
$employmentStage = $this->getEmploymentStage($staff); |
||||
|
|
||||
|
// 判断订单类型(新订单或续费) |
||||
|
$isRenewal = $this->isRenewalOrder($order); |
||||
|
|
||||
|
// 计算绩效 |
||||
|
return $this->calculatePerformance($order, $course, $employmentStage, $isRenewal, $config); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 创建绩效计算结果 |
||||
|
* @param array $order 订单信息 |
||||
|
* @param int $staffId 员工ID |
||||
|
* @param float $performance 绩效金额 |
||||
|
* @param string $remarks 备注 |
||||
|
* @return array 绩效计算结果 |
||||
|
*/ |
||||
|
protected function createPerformanceResult($order, $staffId, $performance, $remarks) |
||||
|
{ |
||||
|
$campusId = isset($order['campus_id']) ? $order['campus_id'] : 0; |
||||
|
$resourceId = isset($order['resource_id']) ? $order['resource_id'] : 0; |
||||
|
|
||||
|
// 获取员工信息 |
||||
|
$staff = Personnel::where('id', $staffId)->find(); |
||||
|
if (empty($staff)) { |
||||
|
return [ |
||||
|
'order_id' => $order['id'], |
||||
|
'staff_id' => $staffId, |
||||
|
'performance_amount' => 0, |
||||
|
'status' => 'failed', |
||||
|
'message' => '未找到员工信息' |
||||
|
]; |
||||
|
} |
||||
|
|
||||
|
// 判断员工工龄阶段 |
||||
|
$employmentStage = $this->getEmploymentStage($staff); |
||||
|
|
||||
|
// 判断订单类型(新订单或续费) |
||||
|
$isRenewal = $this->isRenewalOrder($order); |
||||
|
|
||||
|
// 获取新资源数和续费资源数 |
||||
|
$newResourceCount = $isRenewal ? 0 : 1; |
||||
|
$renewResourceCount = $isRenewal ? 1 : 0; |
||||
|
|
||||
|
// 保存使用的绩效配置和算法 |
||||
|
$performanceConfig = json_encode([ |
||||
|
'employment_stage' => $employmentStage, |
||||
|
'is_renewal' => $isRenewal, |
||||
|
'remarks' => $remarks |
||||
|
], JSON_UNESCAPED_UNICODE); |
||||
|
|
||||
|
$performanceAlgorithm = json_encode([ |
||||
|
'order_id' => $order['id'], |
||||
|
'resource_id' => $resourceId, |
||||
|
'multi_person_involved' => true |
||||
|
], JSON_UNESCAPED_UNICODE); |
||||
|
|
||||
|
// 添加一条绩效汇总记录 |
||||
|
$this->addPerformanceSummary([ |
||||
|
'staff_id' => $staffId, |
||||
|
'resource_id' => $resourceId, |
||||
|
'order_id' => $order['id'], |
||||
|
'order_status' => PerformanceService::ORDER_STATUS_PENDING, |
||||
|
'performance_type' => PerformanceService::PERFORMANCE_TYPE_SALES, |
||||
|
'performance_value' => $performance, |
||||
|
'remarks' => $remarks |
||||
|
]); |
||||
|
|
||||
|
return [ |
||||
|
'order_id' => $order['id'], |
||||
|
'staff_id' => $staffId, |
||||
|
'personnel_id' => $staffId, |
||||
|
'campus_id' => $campusId, |
||||
|
'resource_id' => $resourceId, |
||||
|
'employment_stage' => $employmentStage, |
||||
|
'is_renewal' => $isRenewal, |
||||
|
'performance_amount' => $performance, |
||||
|
'new_resource_count' => $newResourceCount, |
||||
|
'renew_resource_count' => $renewResourceCount, |
||||
|
'performance_date' => date('Y-m-d'), |
||||
|
'performance_config' => $performanceConfig, |
||||
|
'performance_algorithm' => $performanceAlgorithm, |
||||
|
'remarks' => $remarks, |
||||
|
'status' => 'success', |
||||
|
'created_at' => time(), |
||||
|
'updated_at' => time() |
||||
|
]; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 计算单个订单的绩效 |
||||
|
* @param array $order 订单信息 |
||||
|
* @param array $config 绩效配置 |
||||
|
* @return array 绩效计算结果 |
||||
|
*/ |
||||
|
protected function calculateOrderPerformance($order, $config) |
||||
|
{ |
||||
|
$staffId = $order['staff_id']; |
||||
|
$courseId = $order['course_id']; |
||||
|
$campusId = isset($order['campus_id']) ? $order['campus_id'] : 0; |
||||
|
$resourceId = isset($order['resource_id']) ? $order['resource_id'] : 0; |
||||
|
|
||||
|
// 获取员工信息 |
||||
|
$staff = Personnel::where('id', $staffId)->find(); |
||||
|
if (empty($staff)) { |
||||
|
Log::write('未找到员工信息,员工ID:' . $staffId); |
||||
|
return [ |
||||
|
'order_id' => $order['id'], |
||||
|
'staff_id' => $staffId, |
||||
|
'performance_amount' => 0, |
||||
|
'status' => 'failed', |
||||
|
'message' => '未找到员工信息' |
||||
|
]; |
||||
|
} |
||||
|
|
||||
|
// 获取课程信息 |
||||
|
$course = Course::where('id', $courseId)->find(); |
||||
|
if (empty($course)) { |
||||
|
Log::write('未找到课程信息,课程ID:' . $courseId); |
||||
|
return [ |
||||
|
'order_id' => $order['id'], |
||||
|
'staff_id' => $staffId, |
||||
|
'performance_amount' => 0, |
||||
|
'status' => 'failed', |
||||
|
'message' => '未找到课程信息' |
||||
|
]; |
||||
|
} |
||||
|
|
||||
|
// 判断员工工龄阶段 |
||||
|
$employmentStage = $this->getEmploymentStage($staff); |
||||
|
|
||||
|
// 判断订单类型(新订单或续费) |
||||
|
$isRenewal = $this->isRenewalOrder($order); |
||||
|
|
||||
|
// 计算绩效 |
||||
|
$performance = $this->calculatePerformance($order, $course, $employmentStage, $isRenewal, $config); |
||||
|
|
||||
|
// 获取新资源数和续费资源数 |
||||
|
$newResourceCount = $isRenewal ? 0 : 1; |
||||
|
$renewResourceCount = $isRenewal ? 1 : 0; |
||||
|
|
||||
|
// 保存使用的绩效配置和算法 |
||||
|
$performanceConfig = json_encode([ |
||||
|
'employment_stage' => $employmentStage, |
||||
|
'is_renewal' => $isRenewal, |
||||
|
'course_type' => $course['course_type'] |
||||
|
], JSON_UNESCAPED_UNICODE); |
||||
|
|
||||
|
$performanceAlgorithm = json_encode([ |
||||
|
'base_commission' => $isRenewal ? 'renewal_commission' : 'course_type_num', |
||||
|
'order_id' => $order['id'], |
||||
|
'course_id' => $courseId |
||||
|
], JSON_UNESCAPED_UNICODE); |
||||
|
|
||||
|
// 同时添加一条绩效汇总记录 |
||||
|
$this->addPerformanceSummary([ |
||||
|
'staff_id' => $staffId, |
||||
|
'resource_id' => $resourceId, |
||||
|
'order_id' => $order['id'], |
||||
|
'order_status' => PerformanceService::ORDER_STATUS_PENDING, |
||||
|
'performance_type' => PerformanceService::PERFORMANCE_TYPE_SALES, |
||||
|
'performance_value' => $performance, |
||||
|
'remarks' => '订单绩效自动计算' |
||||
|
]); |
||||
|
|
||||
|
return [ |
||||
|
'order_id' => $order['id'], |
||||
|
'staff_id' => $staffId, |
||||
|
'personnel_id' => $staffId, |
||||
|
'campus_id' => $campusId, |
||||
|
'course_id' => $courseId, |
||||
|
'employment_stage' => $employmentStage, |
||||
|
'is_renewal' => $isRenewal, |
||||
|
'performance_amount' => $performance, |
||||
|
'new_resource_count' => $newResourceCount, |
||||
|
'renew_resource_count' => $renewResourceCount, |
||||
|
'performance_date' => date('Y-m-d'), |
||||
|
'performance_config' => $performanceConfig, |
||||
|
'performance_algorithm' => $performanceAlgorithm, |
||||
|
'status' => 'success', |
||||
|
'created_at' => time(), |
||||
|
'updated_at' => time() |
||||
|
]; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 判断员工工龄阶段 |
||||
|
* @param object $staff 员工信息 |
||||
|
* @return string 工龄阶段 |
||||
|
*/ |
||||
|
protected function getEmploymentStage($staff) |
||||
|
{ |
||||
|
$joinTime = strtotime($staff['join_time']); |
||||
|
$now = time(); |
||||
|
$monthsDiff = floor(($now - $joinTime) / (30 * 24 * 60 * 60)); |
||||
|
|
||||
|
if ($monthsDiff < 3) { |
||||
|
return self::STAGE_TRIAL; // 试用期 |
||||
|
} else if ($monthsDiff < 6) { |
||||
|
return self::STAGE_REGULAR; // 转正 |
||||
|
} else { |
||||
|
return self::STAGE_HALF_YEAR; // 转正后半年 |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 判断是否为续费订单 |
||||
|
* @param array $order 订单信息 |
||||
|
* @return bool 是否为续费订单 |
||||
|
*/ |
||||
|
protected function isRenewalOrder($order) |
||||
|
{ |
||||
|
// 这里需要根据实际业务逻辑判断是否为续费订单 |
||||
|
// 假设订单中有一个字段标记是否为续费 |
||||
|
return isset($order['order_type']) && $order['order_type'] == 2; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 计算绩效金额 |
||||
|
* @param array $order 订单信息 |
||||
|
* @param object $course 课程信息 |
||||
|
* @param string $employmentStage 工龄阶段 |
||||
|
* @param bool $isRenewal 是否为续费订单 |
||||
|
* @param array $config 绩效配置 |
||||
|
* @return float 绩效金额 |
||||
|
*/ |
||||
|
protected function calculatePerformance($order, $course, $employmentStage, $isRenewal, $config) |
||||
|
{ |
||||
|
// 获取对应工龄阶段的配置 |
||||
|
$stageConfig = null; |
||||
|
foreach ($config['XSYJ'] as $item) { |
||||
|
if ($item['name'] == $employmentStage) { |
||||
|
$stageConfig = $item; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (empty($stageConfig)) { |
||||
|
Log::write('未找到对应工龄阶段的配置:' . $employmentStage); |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
// 获取课程类型配置 |
||||
|
$courseTypeConfig = null; |
||||
|
$courseType = $course['course_type']; |
||||
|
foreach ($config['course_type'] as $item) { |
||||
|
if ($item['value'] == $courseType) { |
||||
|
$courseTypeConfig = $item; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (empty($courseTypeConfig)) { |
||||
|
Log::write('未找到对应课程类型的配置:' . $courseType); |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
// 获取基础提成金额 |
||||
|
$baseCommission = $courseTypeConfig['num']; |
||||
|
|
||||
|
// 如果是续费订单,根据续费率计算提成 |
||||
|
if ($isRenewal) { |
||||
|
// 获取续费率 |
||||
|
$renewalRate = $this->getRenewalRate($order['staff_id']); |
||||
|
|
||||
|
// 查找适用的规则 |
||||
|
$rule = null; |
||||
|
foreach ($stageConfig['rules'] as $r) { |
||||
|
$minRate = floatval($r['renewal_standard_min']); |
||||
|
$maxRate = !empty($r['renewal_standard_max']) ? floatval($r['renewal_standard_max']) : PHP_FLOAT_MAX; |
||||
|
|
||||
|
if ($renewalRate >= $minRate && $renewalRate < $maxRate) { |
||||
|
$rule = $r; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (!empty($rule)) { |
||||
|
return floatval($rule['renewal_commission']); |
||||
|
} |
||||
|
} else { |
||||
|
// 新订单,直接返回基础提成 |
||||
|
return floatval($baseCommission); |
||||
|
} |
||||
|
|
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取员工的续费率 |
||||
|
* @param int $staffId 员工ID |
||||
|
* @return float 续费率 |
||||
|
*/ |
||||
|
protected function getRenewalRate($staffId) |
||||
|
{ |
||||
|
// 这里需要根据实际业务逻辑计算员工的续费率 |
||||
|
// 假设有一个表记录了员工的续费率 |
||||
|
$rate = 95.0; // 默认值 |
||||
|
|
||||
|
// TODO: 从数据库中获取实际续费率 |
||||
|
|
||||
|
return $rate; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 保存绩效计算结果 |
||||
|
* @param array $results 绩效计算结果 |
||||
|
*/ |
||||
|
protected function savePerformanceResults($results) |
||||
|
{ |
||||
|
if (empty($results)) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
Db::startTrans(); |
||||
|
|
||||
|
foreach ($results as $result) { |
||||
|
if ($result['status'] == 'success') { |
||||
|
// 先检查订单是否已经计算过绩效 |
||||
|
$existingRecord = Db::name('school_performance_summary') |
||||
|
->where('order_id', $result['order_id']) |
||||
|
->where('staff_id', $result['staff_id']) |
||||
|
->where('performance_type', PerformanceService::PERFORMANCE_TYPE_SALES) |
||||
|
->find(); |
||||
|
|
||||
|
if ($existingRecord) { |
||||
|
Log::write('订单ID:' . $result['order_id'] . ' 员工ID:' . $result['staff_id'] . ' 已存在绩效记录,跳过'); |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
// 更新订单表,标记已计算绩效并记录核算时间 |
||||
|
// 只有在处理完所有绩效记录后才更新订单表的核算状态 |
||||
|
if (!isset($result['is_multi_person']) || !$result['is_multi_person']) { |
||||
|
OrderTable::where('id', $result['order_id']) |
||||
|
->update([ |
||||
|
'performance_calculated' => 1, |
||||
|
'performance_amount' => $result['performance_amount'], |
||||
|
'accounting_time' => time() // 记录核算时间 |
||||
|
]); |
||||
|
} |
||||
|
|
||||
|
// 保存到绩效表 school_sales_performance |
||||
|
$performanceData = [ |
||||
|
'personnel_id' => $result['personnel_id'], |
||||
|
'campus_id' => $result['campus_id'], |
||||
|
'performance_amount' => $result['performance_amount'], |
||||
|
'new_resource_count' => $result['new_resource_count'], |
||||
|
'renew_resource_count' => $result['renew_resource_count'], |
||||
|
'performance_date' => $result['performance_date'], |
||||
|
'performance_config' => $result['performance_config'], |
||||
|
'performance_algorithm' => $result['performance_algorithm'], |
||||
|
'created_at' => $result['created_at'], |
||||
|
'updated_at' => $result['updated_at'] |
||||
|
]; |
||||
|
|
||||
|
Db::name('school_sales_performance')->insert($performanceData); |
||||
|
|
||||
|
Log::write('成功保存绩效记录,员工ID:' . $result['personnel_id'] . ',绩效金额:' . $result['performance_amount'] . ',核算时间:' . date('Y-m-d H:i:s', time())); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 更新所有已处理订单的状态 |
||||
|
$orderIds = array_unique(array_column(array_filter($results, function($result) { |
||||
|
return $result['status'] == 'success'; |
||||
|
}), 'order_id')); |
||||
|
|
||||
|
if (!empty($orderIds)) { |
||||
|
OrderTable::whereIn('id', $orderIds) |
||||
|
->update([ |
||||
|
'performance_calculated' => 1, |
||||
|
'accounting_time' => time() |
||||
|
]); |
||||
|
|
||||
|
Log::write('成功更新' . count($orderIds) . '个订单的核算状态'); |
||||
|
} |
||||
|
|
||||
|
Db::commit(); |
||||
|
Log::write('成功保存绩效计算结果'); |
||||
|
} catch (\Exception $e) { |
||||
|
Db::rollback(); |
||||
|
Log::write('保存绩效计算结果失败:' . $e->getMessage()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 添加绩效汇总记录 |
||||
|
* @param array $data 绩效数据 |
||||
|
* @return int|string 插入的ID |
||||
|
*/ |
||||
|
public function addPerformanceSummary(array $data) |
||||
|
{ |
||||
|
try { |
||||
|
return $this->performanceService->addPerformance($data); |
||||
|
} catch (\Exception $e) { |
||||
|
Log::write('添加绩效汇总记录失败:' . $e->getMessage()); |
||||
|
return 0; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,257 @@ |
|||||
|
<?php |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | Niucloud-admin 企业快速开发的多应用管理平台 |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | 官方网址:https://www.niucloud.com |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | niucloud团队 版权所有 开源版本可自由商用 |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | Author: Niucloud Team |
||||
|
// +---------------------------------------------------------------------- |
||||
|
|
||||
|
namespace app\service\admin\performance; |
||||
|
|
||||
|
use core\base\BaseService; |
||||
|
use think\facade\Db; |
||||
|
use think\facade\Log; |
||||
|
|
||||
|
/** |
||||
|
* 绩效服务类 |
||||
|
* Class PerformanceService |
||||
|
* @package app\service\admin\performance |
||||
|
*/ |
||||
|
class PerformanceService extends BaseService |
||||
|
{ |
||||
|
/** |
||||
|
* 绩效类型:销售绩效 |
||||
|
*/ |
||||
|
const PERFORMANCE_TYPE_SALES = 'sales'; |
||||
|
|
||||
|
/** |
||||
|
* 绩效类型:市场绩效 |
||||
|
*/ |
||||
|
const PERFORMANCE_TYPE_MARKETING = 'marketing'; |
||||
|
|
||||
|
/** |
||||
|
* 订单状态:待处理 |
||||
|
*/ |
||||
|
const ORDER_STATUS_PENDING = 'pending'; |
||||
|
|
||||
|
/** |
||||
|
* 订单状态:已完成 |
||||
|
*/ |
||||
|
const ORDER_STATUS_COMPLETED = 'completed'; |
||||
|
|
||||
|
/** |
||||
|
* 添加绩效记录 |
||||
|
* @param array $data 绩效数据 |
||||
|
* @return int|string 插入的ID |
||||
|
*/ |
||||
|
public function addPerformance(array $data) |
||||
|
{ |
||||
|
try { |
||||
|
// 必填字段验证 |
||||
|
$requiredFields = ['staff_id', 'resource_id', 'performance_type', 'performance_value']; |
||||
|
foreach ($requiredFields as $field) { |
||||
|
if (!isset($data[$field]) || empty($data[$field])) { |
||||
|
throw new \Exception('缺少必填字段:' . $field); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 默认值设置 |
||||
|
if (!isset($data['order_status'])) { |
||||
|
$data['order_status'] = self::ORDER_STATUS_PENDING; |
||||
|
} |
||||
|
|
||||
|
if (!isset($data['created_at'])) { |
||||
|
$data['created_at'] = time(); |
||||
|
} |
||||
|
|
||||
|
if (!isset($data['updated_at'])) { |
||||
|
$data['updated_at'] = time(); |
||||
|
} |
||||
|
|
||||
|
// 插入数据 |
||||
|
$id = Db::name('school_performance_summary')->insertGetId($data); |
||||
|
|
||||
|
Log::write('成功添加绩效记录,ID:' . $id . ',员工ID:' . $data['staff_id'] . ',绩效类型:' . $data['performance_type']); |
||||
|
|
||||
|
return $id; |
||||
|
} catch (\Exception $e) { |
||||
|
Log::write('添加绩效记录失败:' . $e->getMessage()); |
||||
|
throw $e; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 更新绩效记录 |
||||
|
* @param int $id 记录ID |
||||
|
* @param array $data 更新数据 |
||||
|
* @return bool 是否成功 |
||||
|
*/ |
||||
|
public function updatePerformance(int $id, array $data) |
||||
|
{ |
||||
|
try { |
||||
|
if (empty($id)) { |
||||
|
throw new \Exception('缺少绩效记录ID'); |
||||
|
} |
||||
|
|
||||
|
// 更新时间 |
||||
|
if (!isset($data['updated_at'])) { |
||||
|
$data['updated_at'] = time(); |
||||
|
} |
||||
|
|
||||
|
// 更新数据 |
||||
|
$result = Db::name('school_performance_summary')->where('id', $id)->update($data); |
||||
|
|
||||
|
Log::write('成功更新绩效记录,ID:' . $id); |
||||
|
|
||||
|
return $result !== false; |
||||
|
} catch (\Exception $e) { |
||||
|
Log::write('更新绩效记录失败:' . $e->getMessage()); |
||||
|
throw $e; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 批量添加绩效记录 |
||||
|
* @param array $dataList 绩效数据列表 |
||||
|
* @return int 插入的记录数 |
||||
|
*/ |
||||
|
public function addBatchPerformance(array $dataList) |
||||
|
{ |
||||
|
try { |
||||
|
if (empty($dataList)) { |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
// 设置默认值 |
||||
|
foreach ($dataList as &$data) { |
||||
|
if (!isset($data['order_status'])) { |
||||
|
$data['order_status'] = self::ORDER_STATUS_PENDING; |
||||
|
} |
||||
|
|
||||
|
if (!isset($data['created_at'])) { |
||||
|
$data['created_at'] = time(); |
||||
|
} |
||||
|
|
||||
|
if (!isset($data['updated_at'])) { |
||||
|
$data['updated_at'] = time(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 批量插入 |
||||
|
$count = Db::name('school_performance_summary')->insertAll($dataList); |
||||
|
|
||||
|
Log::write('成功批量添加绩效记录,数量:' . $count); |
||||
|
|
||||
|
return $count; |
||||
|
} catch (\Exception $e) { |
||||
|
Log::write('批量添加绩效记录失败:' . $e->getMessage()); |
||||
|
throw $e; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取绩效记录 |
||||
|
* @param int $id 记录ID |
||||
|
* @return array|null 绩效记录 |
||||
|
*/ |
||||
|
public function getPerformance(int $id) |
||||
|
{ |
||||
|
try { |
||||
|
if (empty($id)) { |
||||
|
throw new \Exception('缺少绩效记录ID'); |
||||
|
} |
||||
|
|
||||
|
$record = Db::name('school_performance_summary')->where('id', $id)->find(); |
||||
|
|
||||
|
return $record; |
||||
|
} catch (\Exception $e) { |
||||
|
Log::write('获取绩效记录失败:' . $e->getMessage()); |
||||
|
throw $e; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取员工绩效列表 |
||||
|
* @param int $staffId 员工ID |
||||
|
* @param string $performanceType 绩效类型 |
||||
|
* @param string $orderStatus 订单状态 |
||||
|
* @param string $startDate 开始日期 |
||||
|
* @param string $endDate 结束日期 |
||||
|
* @return array 绩效列表 |
||||
|
*/ |
||||
|
public function getStaffPerformanceList(int $staffId, string $performanceType = '', string $orderStatus = '', string $startDate = '', string $endDate = '') |
||||
|
{ |
||||
|
try { |
||||
|
$query = Db::name('school_performance_summary')->where('staff_id', $staffId); |
||||
|
|
||||
|
if (!empty($performanceType)) { |
||||
|
$query->where('performance_type', $performanceType); |
||||
|
} |
||||
|
|
||||
|
if (!empty($orderStatus)) { |
||||
|
$query->where('order_status', $orderStatus); |
||||
|
} |
||||
|
|
||||
|
if (!empty($startDate)) { |
||||
|
$startTime = strtotime($startDate . ' 00:00:00'); |
||||
|
$query->where('created_at', '>=', $startTime); |
||||
|
} |
||||
|
|
||||
|
if (!empty($endDate)) { |
||||
|
$endTime = strtotime($endDate . ' 23:59:59'); |
||||
|
$query->where('created_at', '<=', $endTime); |
||||
|
} |
||||
|
|
||||
|
$list = $query->order('created_at', 'desc')->select()->toArray(); |
||||
|
|
||||
|
return $list; |
||||
|
} catch (\Exception $e) { |
||||
|
Log::write('获取员工绩效列表失败:' . $e->getMessage()); |
||||
|
throw $e; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 计算员工绩效总额 |
||||
|
* @param int $staffId 员工ID |
||||
|
* @param string $performanceType 绩效类型 |
||||
|
* @param string $orderStatus 订单状态 |
||||
|
* @param string $startDate 开始日期 |
||||
|
* @param string $endDate 结束日期 |
||||
|
* @return float 绩效总额 |
||||
|
*/ |
||||
|
public function calculateStaffPerformanceTotal(int $staffId, string $performanceType = '', string $orderStatus = '', string $startDate = '', string $endDate = '') |
||||
|
{ |
||||
|
try { |
||||
|
$query = Db::name('school_performance_summary')->where('staff_id', $staffId); |
||||
|
|
||||
|
if (!empty($performanceType)) { |
||||
|
$query->where('performance_type', $performanceType); |
||||
|
} |
||||
|
|
||||
|
if (!empty($orderStatus)) { |
||||
|
$query->where('order_status', $orderStatus); |
||||
|
} |
||||
|
|
||||
|
if (!empty($startDate)) { |
||||
|
$startTime = strtotime($startDate . ' 00:00:00'); |
||||
|
$query->where('created_at', '>=', $startTime); |
||||
|
} |
||||
|
|
||||
|
if (!empty($endDate)) { |
||||
|
$endTime = strtotime($endDate . ' 23:59:59'); |
||||
|
$query->where('created_at', '<=', $endTime); |
||||
|
} |
||||
|
|
||||
|
$total = $query->sum('performance_value'); |
||||
|
|
||||
|
return floatval($total); |
||||
|
} catch (\Exception $e) { |
||||
|
Log::write('计算员工绩效总额失败:' . $e->getMessage()); |
||||
|
throw $e; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue