Răsfoiți Sursa

add:绩效管理

zoie 1 lună în urmă
părinte
comite
5b689b8fbc

+ 17 - 2
conf/app.conf

@@ -10,7 +10,7 @@ Sys_Name = "ERP_SALARY"
 NatsServer_Url = "127.0.0.1:4223"
 NatsSubj_Prefix = "Test_"
 # Mysql 线上
-MysqlServer_UrlPort = "127.0.0.1:3316"
+MysqlServer_UrlPort = "36.137.156.216:3306"
 MysqlServer_Database = "erp_salary_test"
 MysqlServer_Username = "erp_salary_test"
 MysqlServer_Password = "784HzbWpj5LbzBHN"
@@ -18,7 +18,7 @@ MysqlServer_MaxIdleConnections = 100
 MysqlServer_MaxOpenConnections = 200
 
 # Redis
-Redis_address = "127.0.0.1:6378"
+Redis_address = "36.137.156.216:6379"
 Redis_password = ""
 Redis_dbNum = "1"
 
@@ -39,3 +39,18 @@ ReimburseApprovalUrl = "/reimburse"
 MyReimburseUrl = "/reimburseMy"
 # 财务uuid
 FinanceUuid = "NeGSMvREXA8xDZWo6q1YjLHTm0dJg7zc"
+ColdVerify_OpenApi_Host = "http://8.148.211.203:91/apiserver"
+
+# 日志配置
+# 0-控制台输出 1-文件输出 2-文件和控制台输出
+adapter_type = 0
+# 文件最多保存多少天
+maxdays = 7
+# 日志级别 (0-紧急 1-报警 2-严重错误 3-错误 4-警告 5-注意 6-信息 7-调试)
+level = 7
+# SQL日志级别 (1-静音 2-错误 3-警告 4-信息). 注意: sql日志只在level大于等于5级别才会输出。
+sqlloglevel = 4
+# 慢SQL阈值(毫秒)。慢SQL会在sqlloglevel大于等于3时输出。
+slow_threshold = 200
+# 每个文件保存的最大行数
+maxlines = 10000

+ 16 - 0
conf/app_prod.conf

@@ -39,3 +39,19 @@ ReimburseApprovalUrl = "/reimburse"
 MyReimburseUrl = "/reimburseMy"
 # 财务uuid
 FinanceUuid = "NeGSMvREXA8xDZWo6q1YjLHTm0dJg7zc"
+
+# 日志配置
+# 0-控制台输出 1-文件输出 2-文件和控制台输出
+adapter_type = 0
+# 文件最多保存多少天
+maxdays = 7
+# 日志级别 (0-紧急 1-报警 2-严重错误 3-错误 4-警告 5-注意 6-信息 7-调试)
+level = 7
+# SQL日志级别 (1-静音 2-错误 3-警告 4-信息). 注意: sql日志只在level大于等于5级别才会输出。
+sqlloglevel = 4
+# 慢SQL阈值(毫秒)。慢SQL会在sqlloglevel大于等于3时输出。
+slow_threshold = 200
+# 每个文件保存的最大行数
+maxlines = 10000
+
+ColdVerify_OpenApi_Host = "https://coldverify-s.coldbaozhida.com/apiserver"

+ 2 - 0
conf/config.go

@@ -37,3 +37,5 @@ var MySalaryNewsUrl, _ = beego.AppConfig.String("MySalaryNewsUrl")
 var ReimburseApprovalUrl, _ = beego.AppConfig.String("ReimburseApprovalUrl")
 var MyReimburseUrl, _ = beego.AppConfig.String("MyReimburseUrl")
 var FinanceUuid, _ = beego.AppConfig.String("FinanceUuid")
+
+var ColdVerify_OpenApi_Host, _ = beego.AppConfig.String("ColdVerify_OpenApi_Host")

+ 1146 - 0
controllers/Performance.go

@@ -0,0 +1,1146 @@
+package controllers
+
+import (
+	"ERP_salary/Nats/NatsServer"
+	"ERP_salary/conf"
+	"ERP_salary/dto"
+	"ERP_salary/logs"
+	"ERP_salary/models/Account"
+	"ERP_salary/models/Performance"
+	"ERP_salary/services"
+	"encoding/json"
+	"fmt"
+	"io"
+	"math"
+	"net/http"
+	"net/url"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/robfig/cron/v3"
+	"github.com/xuri/excelize/v2"
+
+	beego "github.com/beego/beego/v2/server/web"
+	userlibs "gogs.baozhida.cn/zoie/ERP_libs/User"
+	"gogs.baozhida.cn/zoie/ERP_libs/lib"
+)
+
+type PerformanceController struct {
+	beego.Controller
+	User userlibs.User
+}
+
+// calculateTotalScore 计算总得分百分比
+func calculateTotalScore(perf Performance.Perf) float64 {
+	if perf.T_assess_points == 0 {
+		return 0
+	}
+	return (perf.T_workload / float64(perf.T_assess_points)) * 100
+}
+
+// calculatePerfTotal 计算应发绩效
+func calculatePerfTotal(perf Performance.Perf) float64 {
+	score := calculateTotalScore(perf)
+	if score >= 100 {
+		return perf.T_perf
+	}
+	return (score / 100) * perf.T_perf
+}
+
+func (c *PerformanceController) Prepare() {
+	c.User = *Account.User_r
+}
+func (c *PerformanceController) Points_List() {
+
+	s := services.PerformancePoints{}
+	reqData := dto.PerformancePointsPageReq{}
+	if err := c.ParseForm(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "参数错误"}
+		c.ServeJSON()
+		return
+	}
+	if err := Validate(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: err.Error()}
+		c.ServeJSON()
+		return
+	}
+
+	R_List, R_cnt := s.GetPage(&reqData)
+
+	var r_jsons lib.R_JSONS
+	r_jsons.Num = R_cnt
+	r_jsons.Data = R_List
+	r_jsons.Page = reqData.Page
+	r_jsons.Page_size = int(math.Ceil(float64(R_cnt) / float64(reqData.PageSize)))
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!", Data: r_jsons}
+	c.ServeJSON()
+	return
+}
+func (c *PerformanceController) Points_Add() {
+
+	s := services.PerformancePoints{}
+	reqData := dto.PerformancePointsInsertReq{}
+	if err := c.ParseForm(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "参数错误"}
+		c.ServeJSON()
+		return
+	}
+	if err := Validate(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: err.Error()}
+		c.ServeJSON()
+		return
+	}
+
+	Id, err := s.Insert(&reqData)
+	if err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "添加失败"}
+		c.ServeJSON()
+		return
+	}
+
+	NatsServer.AddUserLogs(c.User.T_uuid, "绩效点", "添加", reqData)
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!", Data: Id}
+	c.ServeJSON()
+	return
+}
+func (c *PerformanceController) Points_Edit() {
+	s := services.PerformancePoints{}
+	reqData := dto.PerformancePointsUpdateReq{}
+	if err := c.ParseForm(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "参数错误"}
+		c.ServeJSON()
+		return
+	}
+	if err := Validate(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: err.Error()}
+		c.ServeJSON()
+		return
+	}
+
+	if err := s.Update(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "修改失败"}
+		c.ServeJSON()
+		return
+	}
+
+	NatsServer.AddUserLogs(c.User.T_uuid, "绩效点", "修改", reqData)
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!"}
+	c.ServeJSON()
+	return
+}
+func (c *PerformanceController) Points_Del() {
+
+	s := services.PerformancePoints{}
+	reqData := dto.PerformancePointsDeleteReq{}
+	if err := c.ParseForm(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "参数错误"}
+		c.ServeJSON()
+		return
+	}
+	if err := Validate(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: err.Error()}
+		c.ServeJSON()
+		return
+	}
+
+	if err := s.Delete(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: err.Error()}
+		c.ServeJSON()
+		return
+	}
+
+	NatsServer.AddUserLogs(c.User.T_uuid, "绩效点", "删除", strconv.Itoa(reqData.T_id))
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!"}
+	c.ServeJSON()
+	return
+}
+
+func (c *PerformanceController) Target_List() {
+
+	s := services.PerformanceTarget{}
+	reqData := dto.PerformanceTargetPageReq{}
+	if err := c.ParseForm(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "参数错误"}
+		c.ServeJSON()
+		return
+	}
+	if err := Validate(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: err.Error()}
+		c.ServeJSON()
+		return
+	}
+
+	R_List, R_cnt := s.GetPage(&reqData)
+
+	var r_jsons lib.R_JSONS
+	r_jsons.Num = R_cnt
+	r_jsons.Data = R_List
+	r_jsons.Page = reqData.Page
+	r_jsons.Page_size = int(math.Ceil(float64(R_cnt) / float64(reqData.PageSize)))
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!", Data: r_jsons}
+	c.ServeJSON()
+	return
+}
+func (c *PerformanceController) Target_Add() {
+
+	s := services.PerformanceTarget{}
+	reqData := dto.PerformanceTargetInsertReq{}
+	if err := c.ParseForm(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "参数错误"}
+		c.ServeJSON()
+		return
+	}
+	if err := Validate(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: err.Error()}
+		c.ServeJSON()
+		return
+	}
+
+	Id, err := s.Insert(&reqData)
+	if err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "添加失败"}
+		c.ServeJSON()
+		return
+	}
+
+	NatsServer.AddUserLogs(c.User.T_uuid, "绩效指标", "添加", reqData)
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!", Data: Id}
+	c.ServeJSON()
+	return
+}
+func (c *PerformanceController) Target_Edit() {
+	s := services.PerformanceTarget{}
+	reqData := dto.PerformanceTargetUpdateReq{}
+	if err := c.ParseForm(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "参数错误"}
+		c.ServeJSON()
+		return
+	}
+	if err := Validate(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: err.Error()}
+		c.ServeJSON()
+		return
+	}
+
+	if err := s.Update(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "修改失败"}
+		c.ServeJSON()
+		return
+	}
+	NatsServer.AddUserLogs(c.User.T_uuid, "绩效指标", "修改", reqData)
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!"}
+	c.ServeJSON()
+	return
+}
+func (c *PerformanceController) Target_Del() {
+
+	s := services.PerformanceTarget{}
+	reqData := dto.PerformanceTargetDeleteReq{}
+	if err := c.ParseForm(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "参数错误"}
+		c.ServeJSON()
+		return
+	}
+	if err := Validate(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: err.Error()}
+		c.ServeJSON()
+		return
+	}
+
+	if err := s.Delete(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: err.Error()}
+		c.ServeJSON()
+		return
+	}
+
+	NatsServer.AddUserLogs(c.User.T_uuid, "绩效指标", "删除", strconv.Itoa(reqData.T_id))
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!"}
+	c.ServeJSON()
+	return
+}
+
+// 绩效-用户
+func (c *PerformanceController) List() {
+	s := services.Performance{}
+	reqData := dto.PerformancePageReq{}
+	if err := c.ParseForm(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "参数错误"}
+		c.ServeJSON()
+		return
+	}
+	if err := Validate(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: err.Error()}
+		c.ServeJSON()
+		return
+	}
+
+	userList, _ := NatsServer.Read_User_List_All()
+	Account.Read_User_All_Map(userList)
+	R_List, R_cnt := s.GetManagerPage(&reqData)
+
+	var r_jsons lib.R_JSONS
+	r_jsons.Num = R_cnt
+	r_jsons.Data = R_List
+	r_jsons.Page = reqData.Page
+	r_jsons.Page_size = int(math.Ceil(float64(R_cnt) / float64(reqData.PageSize)))
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!", Data: r_jsons}
+	c.ServeJSON()
+	return
+}
+func (c *PerformanceController) Excel() {
+	s := services.Performance{}
+	reqData := dto.PerformancePageReq{}
+	if err := c.ParseForm(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "参数错误"}
+		c.ServeJSON()
+		return
+	}
+	if err := Validate(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: err.Error()}
+		c.ServeJSON()
+		return
+	}
+
+	reqData.PageSize = 9999
+	userList, _ := NatsServer.Read_User_List_All()
+	Account.Read_User_All_Map(userList)
+	R_List, R_cnt := s.GetManagerPage(&reqData)
+
+	// 创建Excel文件
+	f := excelize.NewFile()
+	defer func() {
+		if err := f.Close(); err != nil {
+			logs.Error("关闭Excel文件失败: %s", err)
+		}
+	}()
+
+	// 设置工作表名称
+	sheetName := "绩效管理"
+	index, err := f.NewSheet(sheetName)
+	if err != nil {
+		logs.Error("创建工作表失败: %s", err)
+		c.Data["json"] = lib.JSONS{Code: 500, Msg: "创建Excel文件失败"}
+		c.ServeJSON()
+		return
+	}
+	f.SetActiveSheet(index)
+
+	// 设置表头
+	headers := []string{"序号", "姓名", "工资级别", "绩效工资(元)", "所属月份", "工作量", "考核工作量", "得分", "应发绩效"}
+	for i, header := range headers {
+		cell := fmt.Sprintf("%c1", 'A'+i)
+		f.SetCellValue(sheetName, cell, header)
+	}
+
+	// 设置表头样式
+	headerStyle, err := f.NewStyle(&excelize.Style{
+		Font: &excelize.Font{
+			Bold: true,
+		},
+		Alignment: &excelize.Alignment{
+			Horizontal: "center",
+			Vertical:   "center",
+		},
+		Fill: excelize.Fill{
+			Type:    "pattern",
+			Color:   []string{"#E6F3FF"},
+			Pattern: 1,
+		},
+	})
+	if err == nil {
+		f.SetCellStyle(sheetName, "A1", fmt.Sprintf("%c1", 'A'+len(headers)-1), headerStyle)
+	}
+
+	// 填充数据
+	for i, perf := range R_List {
+		row := i + 2 // 从第2行开始(第1行是表头)
+
+		// 序号
+		f.SetCellValue(sheetName, fmt.Sprintf("A%d", row), i+1)
+		// 姓名
+		f.SetCellValue(sheetName, fmt.Sprintf("B%d", row), perf.T_submit_name)
+		// 工资级别
+		f.SetCellValue(sheetName, fmt.Sprintf("C%d", row), perf.Target.T_name)
+		// 绩效工资(元)
+		f.SetCellValue(sheetName, fmt.Sprintf("D%d", row), perf.T_perf)
+		// 所属月份
+		f.SetCellValue(sheetName, fmt.Sprintf("E%d", row), perf.T_date)
+		// 工作量
+		f.SetCellValue(sheetName, fmt.Sprintf("F%d", row), perf.T_workload)
+		// 考核工作量
+		f.SetCellValue(sheetName, fmt.Sprintf("G%d", row), perf.T_assess_points)
+		// 得分
+		score := calculateTotalScore(perf)
+		f.SetCellValue(sheetName, fmt.Sprintf("H%d", row), fmt.Sprintf("%.1f%%", score))
+		// 应发绩效
+		perfTotal := calculatePerfTotal(perf)
+		f.SetCellValue(sheetName, fmt.Sprintf("I%d", row), fmt.Sprintf("%.2f", perfTotal))
+	}
+
+	// 设置列宽
+	f.SetColWidth(sheetName, "A", "A", 8)  // 序号
+	f.SetColWidth(sheetName, "B", "B", 12) // 姓名
+	f.SetColWidth(sheetName, "C", "C", 12) // 工资级别
+	f.SetColWidth(sheetName, "D", "D", 15) // 绩效工资(元)
+	f.SetColWidth(sheetName, "E", "E", 12) // 所属月份
+	f.SetColWidth(sheetName, "F", "F", 12) // 工作量
+	f.SetColWidth(sheetName, "G", "G", 15) // 考核工作量
+	f.SetColWidth(sheetName, "H", "H", 12) // 得分
+	f.SetColWidth(sheetName, "I", "I", 15) // 应发绩效
+
+	// 设置数据区域样式
+	dataStyle, err := f.NewStyle(&excelize.Style{
+		Alignment: &excelize.Alignment{
+			Horizontal: "center",
+			Vertical:   "center",
+		},
+		Border: []excelize.Border{
+			{Type: "left", Color: "CCCCCC", Style: 1},
+			{Type: "top", Color: "CCCCCC", Style: 1},
+			{Type: "bottom", Color: "CCCCCC", Style: 1},
+			{Type: "right", Color: "CCCCCC", Style: 1},
+		},
+	})
+	if err == nil && len(R_List) > 0 {
+		f.SetCellStyle(sheetName, "A2", fmt.Sprintf("I%d", len(R_List)+1), dataStyle)
+	}
+
+	// 生成文件名
+	fileName := fmt.Sprintf("绩效管理_%s.xlsx", time.Now().Format("20060102_150405"))
+
+	// 设置响应头
+	c.Ctx.Output.Header("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
+	c.Ctx.Output.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", fileName))
+	c.Ctx.Output.Header("Content-Transfer-Encoding", "binary")
+
+	// 写入响应
+	if err := f.Write(c.Ctx.ResponseWriter); err != nil {
+		logs.Error("写入Excel文件失败: %s", err)
+		c.Data["json"] = lib.JSONS{Code: 500, Msg: "导出Excel失败"}
+		c.ServeJSON()
+		return
+	}
+
+	logs.Println(fmt.Sprintf("成功导出绩效管理Excel文件,共%d条记录", R_cnt))
+}
+func (c *PerformanceController) User_List() {
+	s := services.Performance{}
+	reqData := dto.PerformanceUserPageReq{}
+	if err := c.ParseForm(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "参数错误"}
+		c.ServeJSON()
+		return
+	}
+	if err := Validate(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: err.Error()}
+		c.ServeJSON()
+		return
+	}
+
+	userList, _ := NatsServer.Read_User_List_All()
+	Account.Read_User_All_Map(userList)
+	reqData.T_submit = c.User.T_uuid
+	R_List, R_cnt := s.GetPage(&reqData)
+
+	var r_jsons lib.R_JSONS
+	r_jsons.Num = R_cnt
+	r_jsons.Data = R_List
+	r_jsons.Page = reqData.Page
+	r_jsons.Page_size = int(math.Ceil(float64(R_cnt) / float64(reqData.PageSize)))
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!", Data: r_jsons}
+	c.ServeJSON()
+	return
+}
+func (c *PerformanceController) User_Excel() {
+	s := services.Performance{}
+	reqData := dto.PerformanceUserPageReq{}
+	if err := c.ParseForm(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "参数错误"}
+		c.ServeJSON()
+		return
+	}
+	if err := Validate(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: err.Error()}
+		c.ServeJSON()
+		return
+	}
+
+	reqData.PageSize = 9999
+	reqData.T_submit = c.User.T_uuid
+	userList, _ := NatsServer.Read_User_List_All()
+	Account.Read_User_All_Map(userList)
+	R_List, R_cnt := s.GetPage(&reqData)
+
+	// 创建Excel文件
+	f := excelize.NewFile()
+	defer func() {
+		if err := f.Close(); err != nil {
+			logs.Error("关闭Excel文件失败: %s", err)
+		}
+	}()
+
+	// 设置工作表名称
+	sheetName := "绩效管理"
+	index, err := f.NewSheet(sheetName)
+	if err != nil {
+		logs.Error("创建工作表失败: %s", err)
+		c.Data["json"] = lib.JSONS{Code: 500, Msg: "创建Excel文件失败"}
+		c.ServeJSON()
+		return
+	}
+	f.SetActiveSheet(index)
+
+	// 设置表头
+	headers := []string{"序号", "姓名", "工资级别", "绩效工资(元)", "所属月份", "工作量", "考核工作量", "得分", "应发绩效"}
+	for i, header := range headers {
+		cell := fmt.Sprintf("%c1", 'A'+i)
+		f.SetCellValue(sheetName, cell, header)
+	}
+
+	// 设置表头样式
+	headerStyle, err := f.NewStyle(&excelize.Style{
+		Font: &excelize.Font{
+			Bold: true,
+		},
+		Alignment: &excelize.Alignment{
+			Horizontal: "center",
+			Vertical:   "center",
+		},
+		Fill: excelize.Fill{
+			Type:    "pattern",
+			Color:   []string{"#E6F3FF"},
+			Pattern: 1,
+		},
+	})
+	if err == nil {
+		f.SetCellStyle(sheetName, "A1", fmt.Sprintf("%c1", 'A'+len(headers)-1), headerStyle)
+	}
+
+	// 填充数据
+	for i, perf := range R_List {
+		row := i + 2 // 从第2行开始(第1行是表头)
+
+		// 序号
+		f.SetCellValue(sheetName, fmt.Sprintf("A%d", row), i+1)
+		// 姓名
+		f.SetCellValue(sheetName, fmt.Sprintf("B%d", row), perf.T_submit_name)
+		// 工资级别
+		f.SetCellValue(sheetName, fmt.Sprintf("C%d", row), perf.Target.T_name)
+		// 绩效工资(元)
+		f.SetCellValue(sheetName, fmt.Sprintf("D%d", row), perf.T_perf)
+		// 所属月份
+		f.SetCellValue(sheetName, fmt.Sprintf("E%d", row), perf.T_date)
+		// 工作量
+		f.SetCellValue(sheetName, fmt.Sprintf("F%d", row), perf.T_workload)
+		// 考核工作量
+		f.SetCellValue(sheetName, fmt.Sprintf("G%d", row), perf.T_assess_points)
+		// 得分
+		score := calculateTotalScore(perf)
+		f.SetCellValue(sheetName, fmt.Sprintf("H%d", row), fmt.Sprintf("%.1f%%", score))
+		// 应发绩效
+		perfTotal := calculatePerfTotal(perf)
+		f.SetCellValue(sheetName, fmt.Sprintf("I%d", row), fmt.Sprintf("%.2f", perfTotal))
+	}
+
+	// 设置列宽
+	f.SetColWidth(sheetName, "A", "A", 8)  // 序号
+	f.SetColWidth(sheetName, "B", "B", 12) // 姓名
+	f.SetColWidth(sheetName, "C", "C", 12) // 工资级别
+	f.SetColWidth(sheetName, "D", "D", 15) // 绩效工资(元)
+	f.SetColWidth(sheetName, "E", "E", 12) // 所属月份
+	f.SetColWidth(sheetName, "F", "F", 12) // 工作量
+	f.SetColWidth(sheetName, "G", "G", 15) // 考核工作量
+	f.SetColWidth(sheetName, "H", "H", 12) // 得分
+	f.SetColWidth(sheetName, "I", "I", 15) // 应发绩效
+
+	// 设置数据区域样式
+	dataStyle, err := f.NewStyle(&excelize.Style{
+		Alignment: &excelize.Alignment{
+			Horizontal: "center",
+			Vertical:   "center",
+		},
+		Border: []excelize.Border{
+			{Type: "left", Color: "CCCCCC", Style: 1},
+			{Type: "top", Color: "CCCCCC", Style: 1},
+			{Type: "bottom", Color: "CCCCCC", Style: 1},
+			{Type: "right", Color: "CCCCCC", Style: 1},
+		},
+	})
+	if err == nil && len(R_List) > 0 {
+		f.SetCellStyle(sheetName, "A2", fmt.Sprintf("I%d", len(R_List)+1), dataStyle)
+	}
+
+	// 生成文件名
+	fileName := fmt.Sprintf("绩效管理_%s.xlsx", time.Now().Format("20060102_150405"))
+
+	// 设置响应头
+	c.Ctx.Output.Header("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
+	c.Ctx.Output.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", fileName))
+	c.Ctx.Output.Header("Content-Transfer-Encoding", "binary")
+
+	// 写入响应
+	if err := f.Write(c.Ctx.ResponseWriter); err != nil {
+		logs.Error("写入Excel文件失败: %s", err)
+		c.Data["json"] = lib.JSONS{Code: 500, Msg: "导出Excel失败"}
+		c.ServeJSON()
+		return
+	}
+
+	logs.Println(fmt.Sprintf("成功导出绩效管理Excel文件,共%d条记录", R_cnt))
+}
+func (c *PerformanceController) Submit_User() {
+	s := services.Performance{}
+
+	page, _ := c.GetInt("page")
+	if page < 1 {
+		page = 1
+	}
+	page_z, _ := c.GetInt("page_z")
+	if page_z < 1 {
+		page_z = conf.Page_size
+	}
+
+	T_name := c.GetString("T_name")
+	userList, _ := NatsServer.Read_User_List_All()
+	Account.Read_User_All_Map(userList)
+
+	uuidList := s.GetSubmitUserPage()
+
+	R_List, R_cnt, err := NatsServer.Read_User_List_T_uuid(T_name, uuidList, page, page_z)
+	if err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "查询失败"}
+		c.ServeJSON()
+		return
+	}
+
+	var U_List []userlibs.User
+	for _, user := range R_List {
+		U_List = append(U_List, user)
+	}
+
+	var r_jsons lib.R_JSONS
+	r_jsons.Num = R_cnt
+	r_jsons.Data = U_List
+	r_jsons.Page = page
+	r_jsons.Page_size = int(math.Ceil(float64(R_cnt) / float64(page_z)))
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!", Data: r_jsons}
+	c.ServeJSON()
+	return
+}
+func (c *PerformanceController) Add() {
+	s := services.Performance{}
+
+	reqData := dto.PerformanceInsertReq{}
+
+	if err := c.ParseForm(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "参数错误"}
+		c.ServeJSON()
+		return
+	}
+	if err := json.Unmarshal([]byte(reqData.Points), &reqData.PointList); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "绩效明细 参数错误"}
+		c.ServeJSON()
+		return
+	}
+	if err := Validate(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: err.Error()}
+		c.ServeJSON()
+		return
+	}
+	reqData.T_submit = c.User.T_uuid
+	// 如传入绩效点,则校验非空
+	if len(reqData.PointList) == 0 {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "绩效明细不能为空"}
+		c.ServeJSON()
+		return
+	}
+
+	Id, err := s.Insert(&reqData)
+	if err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: err.Error()}
+		c.ServeJSON()
+		return
+	}
+
+	NatsServer.AddUserLogs(c.User.T_uuid, "绩效考核", "添加", reqData)
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!", Data: Id}
+	c.ServeJSON()
+	return
+}
+func (c *PerformanceController) Edit() {
+	s := services.Performance{}
+	reqData := dto.PerformanceUpdateReq{}
+	if err := c.ParseForm(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "参数错误"}
+		c.ServeJSON()
+		return
+	}
+	if err := json.Unmarshal([]byte(reqData.Points), &reqData.PointList); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "绩效明细 参数错误"}
+		c.ServeJSON()
+		return
+	}
+	if err := Validate(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: err.Error()}
+		c.ServeJSON()
+		return
+	}
+
+	if err := s.Update(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: err.Error()}
+		c.ServeJSON()
+		return
+	}
+
+	NatsServer.AddUserLogs(c.User.T_uuid, "绩效考核", "修改", reqData)
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!"}
+	c.ServeJSON()
+	return
+}
+func (c *PerformanceController) Edit_Audit() {
+	s := services.Performance{}
+	reqData := dto.PerformanceUpdateAuditReq{}
+	if err := c.ParseForm(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "参数错误"}
+		c.ServeJSON()
+		return
+	}
+	if err := Validate(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: err.Error()}
+		c.ServeJSON()
+		return
+	}
+
+	if err := s.UpdateAudit(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: err.Error()}
+		c.ServeJSON()
+		return
+	}
+
+	NatsServer.AddUserLogs(c.User.T_uuid, "绩效考核", "修改状态", reqData)
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!"}
+	c.ServeJSON()
+	return
+}
+func (c *PerformanceController) Del() {
+	s := services.Performance{}
+	reqData := dto.PerformanceDeleteReq{}
+	if err := c.ParseForm(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "参数错误"}
+		c.ServeJSON()
+		return
+	}
+	if err := Validate(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: err.Error()}
+		c.ServeJSON()
+		return
+	}
+
+	if err := s.Delete(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: err.Error()}
+		c.ServeJSON()
+		return
+	}
+
+	NatsServer.AddUserLogs(c.User.T_uuid, "绩效考核", "删除", strconv.Itoa(reqData.T_id))
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!"}
+	c.ServeJSON()
+	return
+}
+
+// GetPerformanceDetail 根据ID获取绩效详情
+func (c *PerformanceController) GetPerformanceDetail() {
+	s := services.Performance{}
+
+	reqData := dto.PerformanceGetReq{}
+	if err := c.ParseForm(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "参数错误"}
+		c.ServeJSON()
+		return
+	}
+
+	if reqData.T_id <= 0 {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "ID参数无效"}
+		c.ServeJSON()
+		return
+	}
+
+	perf, err := s.Get(reqData)
+	if err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "获取绩效详情失败: " + err.Error()}
+		c.ServeJSON()
+		return
+	}
+
+	// 获取关联的绩效点详情
+	pointList, err := s.GetPerfPointsByPerfId(reqData.T_id)
+	if err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "获取绩效点详情失败: " + err.Error()}
+		c.ServeJSON()
+		return
+	}
+	// 组装返回数据
+	result := map[string]interface{}{
+		"perf":      perf,
+		"pointList": pointList,
+	}
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!", Data: result}
+	c.ServeJSON()
+	return
+}
+
+func Cron_Percentage() {
+
+	//创建一个定时任务对象
+	c := cron.New(cron.WithSeconds())
+	//给对象增加定时任务 - 改为每月1日凌晨0点0分执行
+	c.AddFunc("0 0 1 * *", SyncVerifyPercentage)
+
+	//启动定时任务
+	c.Start()
+	defer c.Stop()
+
+	//查询语句,阻塞,让main函数不退出,保持程序运行
+	select {}
+
+}
+
+func SyncVerifyPercentage() {
+	urls := "/openapi/task/list"
+	signature, timestamp := lib.GenColdVerifySignature()
+
+	// 构建请求数据
+	var start_time, end_time string
+
+	start_time = lib.GetFirstDayOfLastMonth() + " 00:00:00"
+	end_time = lib.GetLastDayOfLastMonth() + " 23:59:59"
+
+	// 使用标准HTTP库替换resty
+	formData := url.Values{}
+	formData.Set("T_reporting_pass_start_time", start_time)
+	formData.Set("T_reporting_pass_end_time", end_time)
+	formData.Set("X-API-KEY", lib.ColdVerify_OpenApi_Key)
+	formData.Set("X-API-SIGNATURE", signature)
+	formData.Set("X-API-TIMESTAMP", timestamp)
+
+	// 构建完整URL
+	fullURL := conf.ColdVerify_OpenApi_Host + urls
+
+	// 创建HTTP客户端
+	client := &http.Client{
+		Timeout: 30 * time.Second,
+	}
+
+	// 创建POST请求
+	req, err := http.NewRequest("POST", fullURL, strings.NewReader(formData.Encode()))
+	if err != nil {
+		logs.Error("创建HTTP请求失败: %v", err)
+		return
+	}
+
+	// 设置请求头
+	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+
+	// 发送请求
+	resp, err := client.Do(req)
+	if err != nil {
+		logs.Error("请求冷链验证任务列表接口失败: %v", err)
+		return
+	}
+	defer resp.Body.Close()
+
+	// 读取响应体
+	body, err := io.ReadAll(resp.Body)
+	if err != nil {
+		logs.Error("读取响应体失败: %v", err)
+		return
+	}
+
+	// 检查响应状态码
+	if resp.StatusCode != http.StatusOK {
+		logs.Error("请求冷链验证任务列表接口失败,状态码: %d", resp.StatusCode)
+		return
+	}
+
+	// 解析响应
+	type R_JSONS struct {
+		//必须的大写开头
+		Data []Performance.VerifyTask
+		Code int64
+		Msg  string
+	}
+	var res R_JSONS
+	if err = json.Unmarshal(body, &res); err != nil {
+		logs.Error("解析响应数据失败: %v", err)
+		return
+	}
+
+	pointsMap := GetPerformancePointsMap()
+	coldVerifyUUIDMap := GetColdVerifyUUIDMap()
+
+	// 分别为报告编写人员和数据采集人员创建统计映射
+	reportingStat := map[string]map[int]int{}  // 报告编写人员统计
+	collectionStat := map[string]map[int]int{} // 数据采集人员统计
+
+	// 遍历所有任务,分别统计报告编写人员和数据采集人员的工作量
+	for _, task := range res.Data {
+		verifyItem := GetPerformancePoints(Performance.DeviceTypeMap[task.T_device_type], task.T_verify_type, pointsMap)
+		if verifyItem.Id == 0 {
+			continue
+		}
+		// 报告编写人员统计
+		reportingUserUUID := coldVerifyUUIDMap[task.T_reporting]
+		if reportingUserUUID != "" {
+			updateWorkloadStat(reportingStat, reportingUserUUID, verifyItem.Id)
+		}
+
+		// 数据采集人员统计
+		collectionUserUUID := coldVerifyUUIDMap[task.T_collection]
+		if collectionUserUUID != "" {
+			updateWorkloadStat(collectionStat, collectionUserUUID, verifyItem.Id)
+		}
+	}
+	Performance.Read_PerformanceTarget_All_Map()
+	// 保存统计结果到数据库
+	saveReportingStatsToDB(reportingStat, "reporting")
+	saveCollectionStatsToDB(collectionStat, "collection")
+}
+
+// updateWorkloadStat 更新工作量统计的辅助函数
+func updateWorkloadStat(stat map[string]map[int]int, userUUID string, pointID int) {
+	// 如果UUID为空则跳过
+	if userUUID == "" {
+		return
+	}
+
+	// 初始化用户映射表
+	if stat[userUUID] == nil {
+		stat[userUUID] = make(map[int]int)
+	}
+
+	// 增加工作量计数
+	stat[userUUID][pointID]++
+}
+
+// saveReportingStatsToDB 保存报告编写人员统计结果到数据库
+func saveReportingStatsToDB(stat map[string]map[int]int, workType string) {
+	s := services.Performance{}
+
+	// 获取当前日期(用于T_date字段)
+	currentDate := time.Now().Format("2006-01")
+
+	// 遍历统计结果并保存到数据库
+	for userUUID, points := range stat {
+		// 计算总工作量
+		totalWorkload := 0.0
+		totalPoints := 0 // 总绩效点
+
+		// 收集所有绩效点信息用于创建PerfPoint列表
+		var pointList []Performance.PerfPoint
+
+		for pointID, count := range points {
+			// 获取绩效点信息以计算工作量
+			pointsInfo := getPerformancePointsInfo(pointID)
+			if pointsInfo.Id > 0 {
+				// 计算工作量:T_points_numerator/T_points_denominator*统计出来的工作量
+				workload := float64(pointsInfo.T_points_numerator) / float64(pointsInfo.T_points_denominator) * float64(count)
+				totalWorkload += workload
+
+				// 累计总绩效点和总数量
+				totalPoints += pointsInfo.T_points_numerator
+
+				// 创建绩效点记录
+				point := Performance.PerfPoint{
+					T_performance_points_id: pointID,
+					T_quantity:              count,
+					T_points_numerator:      pointsInfo.T_points_numerator,
+					T_points_denominator:    pointsInfo.T_points_denominator,
+					T_type:                  workType,
+					T_remark:                fmt.Sprintf("报告编写工作量统计: %d项", count),
+				}
+				pointList = append(pointList, point)
+			}
+		}
+		User := Account.Read_User_Get(userUUID)
+		target := Performance.Read_PerformanceTarget_Get(User.T_verify_perf_target)
+		// 创建Perf记录
+		perf := Performance.Perf{
+			T_date:                  currentDate,
+			T_submit:                userUUID,
+			T_workload:              totalWorkload,
+			T_audit:                 1,                      // 待提交状态
+			T_State:                 1,                      // 正常状态
+			T_assess_points:         target.T_assess_points, // 考核绩效点
+			T_perf:                  target.T_perf,          // 考核绩效点
+			T_performance_target_id: target.Id,              // 考核绩效点
+		}
+
+		// 保存到数据库
+		_, err := s.InsertFromStats(&perf, pointList)
+		if err != nil {
+			logs.Error("保存报告编写人员统计结果失败: %v", err)
+		} else {
+			logs.Info("成功保存报告编写人员 %s 的统计结果", userUUID)
+		}
+	}
+}
+
+// saveCollectionStatsToDB 保存数据采集人员统计结果到数据库
+func saveCollectionStatsToDB(stat map[string]map[int]int, workType string) {
+	s := services.Performance{}
+
+	// 获取当前日期(用于T_date字段)
+	currentDate := time.Now().AddDate(0, -1, 0).Format("2006-01")
+
+	// 遍历统计结果并保存到数据库
+	for userUUID, points := range stat {
+		// 计算总工作量
+		totalWorkload := 0.0
+		totalPoints := 0 // 总绩效点
+		totalCount := 0  // 总数量
+
+		// 收集所有绩效点信息用于创建PerfPoint列表
+		var pointList []Performance.PerfPoint
+
+		for pointID, count := range points {
+			// 获取绩效点信息以计算工作量
+			pointsInfo := getPerformancePointsInfo(pointID)
+			if pointsInfo.Id > 0 {
+				// 计算工作量:T_points_numerator/T_points_denominator*统计出来的工作量
+				workload := float64(pointsInfo.T_points_numerator) / float64(pointsInfo.T_points_denominator) * float64(count)
+				totalWorkload += workload
+
+				// 累计总绩效点和总数量
+				totalPoints += pointsInfo.T_points_numerator
+				totalCount += count
+
+				// 创建绩效点记录
+				point := Performance.PerfPoint{
+					T_performance_points_id: pointID,
+					T_quantity:              count,
+					T_points_numerator:      pointsInfo.T_points_numerator,
+					T_points_denominator:    pointsInfo.T_points_denominator,
+					T_type:                  workType,
+					T_remark:                fmt.Sprintf("数据采集工作量统计: %d项", count),
+				}
+				pointList = append(pointList, point)
+			}
+		}
+
+		User := Account.Read_User_Get(userUUID)
+		target := Performance.Read_PerformanceTarget_Get(User.T_verify_perf_target)
+		// 创建Perf记录
+		perf := Performance.Perf{
+			T_date:                  currentDate,
+			T_submit:                userUUID,
+			T_workload:              totalWorkload,
+			T_audit:                 1,                      // 待提交状态
+			T_State:                 1,                      // 正常状态
+			T_assess_points:         target.T_assess_points, // 考核绩效点
+			T_perf:                  target.T_perf,          // 考核绩效
+			T_performance_target_id: target.Id,              // 工资级别id
+
+		}
+
+		// 保存到数据库
+		_, err := s.InsertFromStats(&perf, pointList)
+		if err != nil {
+			logs.Error("保存数据采集人员统计结果失败: %v", err)
+		} else {
+			logs.Info(fmt.Sprintf("成功保存数据采集人员 %s 的统计结果", userUUID))
+		}
+	}
+}
+
+// getPerformancePointsInfo 获取绩效点信息
+func getPerformancePointsInfo(pointID int) Performance.PerformancePoints {
+	s := services.PerformancePoints{}
+
+	points, err := s.Get(pointID)
+	if err != nil {
+		logs.Error("获取绩效点信息失败: %v", err)
+		return Performance.PerformancePoints{}
+	}
+
+	return points
+}
+
+func GetPerformancePointsMap() map[string]Performance.PerformancePoints {
+	s := services.PerformancePoints{}
+	reqData := dto.PerformancePointsPageReq{}
+	reqData.PageSize = 9999
+	performancePointsList, _ := s.GetPage(&reqData)
+	var pointsMap = make(map[string]Performance.PerformancePoints) // 实施
+	for _, item := range performancePointsList {
+		pointsMap[item.T_name] = item
+
+	}
+	return pointsMap
+}
+
+func GetColdVerifyUUIDMap() (uuidMap map[string]string) {
+	uuidMap = make(map[string]string)
+	userList, _ := NatsServer.Read_User_List_All()
+	for _, user := range userList {
+		uuidMap[user.T_verify_cold_uuid] = user.T_uuid
+	}
+	return uuidMap
+}
+
+// 获取实施提成金额
+func GetPerformancePoints(T_device_type, T_verify_type string, pointsMap map[string]Performance.PerformancePoints) (money Performance.PerformancePoints) {
+	if strings.Contains(T_device_type, "箱") {
+		return pointsMap["箱"]
+	}
+	if strings.Contains(T_device_type, "车") {
+		if strings.Contains(T_verify_type, "空载") {
+			return pointsMap["车(空载)"]
+		} else {
+			return pointsMap["车(满载)"]
+		}
+	}
+	if strings.Contains(T_device_type, "柜") {
+		if strings.Contains(T_verify_type, "空载") {
+			return pointsMap["库(空载)"]
+		} else {
+			return pointsMap["库(满载)"]
+		}
+	}
+	if strings.Contains(T_device_type, "库") {
+		if strings.Contains(T_verify_type, "空载") {
+			return pointsMap["库(空载)"]
+		} else {
+			return pointsMap["库(满载)"]
+		}
+	}
+	if strings.Contains(T_device_type, "系统") {
+		return pointsMap["系统"]
+	}
+	if strings.Contains(T_device_type, "位置") {
+		return pointsMap["位置"]
+	}
+	if strings.Contains(T_device_type, "巡检") {
+		return pointsMap["巡检"]
+	}
+	if strings.Contains(T_device_type, "培训") {
+		return pointsMap["培训"]
+	}
+	return Performance.PerformancePoints{}
+}

+ 34 - 0
controllers/base_controller.go

@@ -0,0 +1,34 @@
+package controllers
+
+import (
+	vd "github.com/bytedance/go-tagexpr/v2/validator"
+)
+
+func Validate(reqData interface{}) error {
+
+	//valueType := reflect.ValueOf(reqData).Elem().Type() // 获取结构体类型
+	//newStruct := reflect.New(valueType).Interface()     // 创建新的结构体
+	//
+	//v := validation.Validation{}
+	//status, err := v.Valid(newStruct)
+	//if err != nil {
+	//	return err
+	//}
+	//if !status {
+	//	for _, err := range v.Errors {
+	//		// 获取 newStruct 指向的真实结构体的类型
+	//		structType := reflect.Indirect(reflect.ValueOf(newStruct)).Type()
+	//		field, _ := structType.FieldByName(err.Field)
+	//		alias := field.Tag.Get("alias")
+	//		message := strings.Replace(err.Message, err.Field, alias, 1)
+	//		return errors.New(message)
+	//	}
+	//}
+	//
+	//reflect.ValueOf(reqData).Elem().Set(reflect.ValueOf(newStruct).Elem())
+	//return nil
+	if err := vd.Validate(reqData); err != nil {
+		return err
+	}
+	return nil
+}

+ 116 - 0
dto/Performance.go

@@ -0,0 +1,116 @@
+package dto
+
+import (
+	models "ERP_salary/models/Performance"
+)
+
+// PerformancePageReq 列表或者搜索使用结构体
+type PerformancePageReq struct {
+	Pagination `search:"-"`
+	T_date     string `form:"T_date" search:"type:contains;column:t_date;table:perf"`  // 月份
+	T_submit   string `form:"T_submit" search:"type:exact;column:t_submit;table:perf"` // 负责人
+	T_audit    string `form:"T_audit" search:"type:exact;column:t_audit;table:perf"`   // 审核状态
+	PerformanceOrder
+}
+type PerformanceOrder struct {
+	DateOrder string `search:"type:order;column:t_date;table:perf" form:"T_date" default:"desc"`
+	IdOrder   string `search:"type:order;column:id;table:perf" form:"T_id" default:"desc"`
+}
+
+func (m *PerformancePageReq) GetNeedSearch() interface{} {
+	return *m
+}
+
+// PerformancePageReq 列表或者搜索使用结构体
+type PerformanceUserPageReq struct {
+	Pagination `search:"-"`
+	T_date     string `form:"T_date" search:"type:contains;column:t_date;table:perf"`  // 月份
+	T_submit   string `form:"T_submit" search:"type:exact;column:t_submit;table:perf"` // 负责人
+	T_audit    string `form:"T_audit" search:"type:exact;column:t_audit;table:perf"`   // 审核状态
+	PerformanceOrder
+}
+
+func (m *PerformanceUserPageReq) GetNeedSearch() interface{} {
+	return *m
+}
+
+type PerformanceSubmitUserPageRes struct {
+	T_submit string
+	T_name   string
+}
+
+// PerformanceInsertReq 增使用的结构体
+type PerformanceInsertReq struct {
+	T_date     string  `form:"T_date" vd:"len($)>0;msg:'所属月份不能为空'"` // 所属月份
+	T_submit   string  `form:"T_submit"`                            // 员工
+	T_workload float64 `form:"T_workload"`                          // 工作量
+
+	Points    string             `json:"Points"`    // 绩效点
+	PointList []models.PerfPoint `json:"PointList"` //
+
+}
+
+func (s *PerformanceInsertReq) Generate(model *models.Perf) {
+	model.T_date = s.T_date
+	model.T_submit = s.T_submit
+	model.T_workload = s.T_workload
+	model.T_audit = 1
+	model.T_State = 1
+	// 注意:T_points 和 T_count 字段在统计场景下由系统计算,不在手动插入时设置
+}
+
+// PerformanceUpdateReq 改使用的结构体
+type PerformanceUpdateReq struct {
+	T_id       int     `form:"T_id"  example:"1"`
+	T_date     string  `form:"T_date" vd:"len($)>0;msg:'所属月份不能为空'"` // 所属月份
+	T_workload float64 `form:"T_workload"`                          // 工作量
+
+	Points    string             `json:"Points"`    // 绩效点
+	PointList []models.PerfPoint `json:"PointList"` //
+}
+
+func (s *PerformanceUpdateReq) Generate(model *models.Perf) {
+	model.Id = s.T_id
+	model.T_date = s.T_date
+	model.T_workload = s.T_workload
+}
+
+func (s *PerformanceUpdateReq) GetId() interface{} {
+	return s.T_id
+}
+
+type PerformanceUpdateAuditReq struct {
+	T_id    int `form:"T_id"  example:"1"`
+	T_audit int `form:"T_audit" vd:"$>0;msg:'状态不能为空'"` // 所属月份
+}
+
+func (s *PerformanceUpdateAuditReq) GetId() interface{} {
+	return s.T_id
+}
+
+// PerformanceGetReq 获取单个的结构体
+type PerformanceGetReq struct {
+	T_id int `form:"T_id"`
+}
+
+func (s *PerformanceGetReq) GetId() interface{} {
+	return s.T_id
+}
+
+// PerformanceDeleteReq 删除的结构体
+type PerformanceDeleteReq struct {
+	T_id int `form:"T_id"`
+}
+
+func (s *PerformanceDeleteReq) GetId() interface{} {
+	return s.T_id
+}
+
+// PerformanceDetailReq 获取绩效详情的结构体
+type PerformanceDetailReq struct {
+	T_id int `form:"T_id" vd:"$>0;msg:'ID不能为空且必须大于0'"`
+}
+
+func (s *PerformanceDetailReq) GetId() interface{} {
+	return s.T_id
+}

+ 91 - 0
dto/PerformancePoints.go

@@ -0,0 +1,91 @@
+package dto
+
+import (
+	models "ERP_salary/models/Performance"
+	"strconv"
+	"strings"
+)
+
+// PerformancePointsPageReq 列表或者搜索使用结构体
+type PerformancePointsPageReq struct {
+	Pagination `search:"-"`
+	T_name     string `form:"T_name" search:"type:contains;column:t_name;table:performance_points" example:""` // 名称
+}
+
+func (m *PerformancePointsPageReq) GetNeedSearch() interface{} {
+	return *m
+}
+
+// PerformancePointsInsertReq 增使用的结构体
+type PerformancePointsInsertReq struct {
+	T_name   string `form:"T_name" example:"试用期"  vd:"len($)>0;msg:'名称不能为空'"`     // 名称
+	T_points string `form:"T_points" example:"100" vd:"len($)>0;msg:'绩效点数量不能为空'"` // 绩效点数量
+	T_remark string `form:"T_remark" example:"备注"`                                // 备注
+}
+
+func (s *PerformancePointsInsertReq) Generate(model *models.PerformancePoints) {
+	model.T_name = s.T_name
+	T_points_numerator, T_points_denominator := 1, 1
+	T_points := s.T_points
+	if len(T_points) > 0 {
+		if strings.Contains(T_points, "/") {
+			T_points_numerator, _ = strconv.Atoi(T_points[:strings.Index(T_points, "/")])
+			T_points_denominator, _ = strconv.Atoi(T_points[strings.Index(T_points, "/")+1:])
+		} else {
+			T_points_numerator, _ = strconv.Atoi(T_points)
+		}
+	}
+	model.T_points_numerator = T_points_numerator
+	model.T_points_denominator = T_points_denominator
+	model.T_remark = s.T_remark
+	model.T_State = 1
+}
+
+// PerformancePointsUpdateReq 改使用的结构体
+type PerformancePointsUpdateReq struct {
+	T_id     int    `form:"T_id"  example:"1"`
+	T_name   string `form:"T_name" example:"服务类型"  vd:"len($)>0;msg:'名称不能为空'"`    // 名称
+	T_points string `form:"T_points" example:"100" vd:"len($)>0;msg:'绩效点数量不能为空'"` // 绩效点数量
+	T_remark string `form:"T_remark" example:"备注"`                                // 备注
+}
+
+func (s *PerformancePointsUpdateReq) Generate(model *models.PerformancePoints) {
+	model.Id = s.T_id
+	model.T_name = s.T_name
+	T_points_numerator, T_points_denominator := 1, 1
+	T_points := s.T_points
+	if len(T_points) > 0 {
+		if strings.Contains(T_points, "/") {
+			T_points_numerator, _ = strconv.Atoi(T_points[:strings.Index(T_points, "/")])
+			T_points_denominator, _ = strconv.Atoi(T_points[strings.Index(T_points, "/")+1:])
+		} else {
+			T_points_numerator, _ = strconv.Atoi(T_points)
+		}
+	}
+	model.T_points_numerator = T_points_numerator
+	model.T_points_denominator = T_points_denominator
+	model.T_remark = s.T_remark
+	model.T_State = 1
+}
+
+func (s *PerformancePointsUpdateReq) GetId() interface{} {
+	return s.T_id
+}
+
+// PerformancePointsGetReq 获取单个的结构体
+type PerformancePointsGetReq struct {
+	T_id int `form:"T_id"`
+}
+
+func (s *PerformancePointsGetReq) GetId() interface{} {
+	return s.T_id
+}
+
+// PerformancePointsDeleteReq 删除的结构体
+type PerformancePointsDeleteReq struct {
+	T_id int `form:"T_id"`
+}
+
+func (s *PerformancePointsDeleteReq) GetId() interface{} {
+	return s.T_id
+}

+ 79 - 0
dto/PerformanceTarget.go

@@ -0,0 +1,79 @@
+package dto
+
+import (
+	models "ERP_salary/models/Performance"
+)
+
+// PerformanceTargetPageReq 列表或者搜索使用结构体
+type PerformanceTargetPageReq struct {
+	Pagination `search:"-"`
+	T_name     string `form:"T_name" search:"type:contains;column:t_name;table:performance_target" example:""` // 名称
+}
+
+func (m *PerformanceTargetPageReq) GetNeedSearch() interface{} {
+	return *m
+}
+
+// PerformanceTargetInsertReq 增使用的结构体
+type PerformanceTargetInsertReq struct {
+	T_name          string  `form:"T_name" example:"工资级别"  vd:"len($)>0;msg:'工资级别不能为空'"`   // 工资级别
+	T_operator      string  `form:"T_operator" example:"≥"  vd:"len($)>0;msg:'运算符不能为空'"`        // 运算符 <,≥
+	T_assess_points int     `form:"T_assess_points" example:"60"  vd:"$>0;msg:'月考核工具量不能为空'"` // 月考试绩效点数量
+	T_base          float64 `form:"T_base" example:"2000"  vd:"$>0;msg:'无责底薪不能为空'"`            // 无责底薪
+	T_post          float64 `form:"T_post" example:"1400"  vd:"$>0;msg:'补助不能为空'"`                // 补助
+	T_perf          float64 `form:"T_perf" example:"800"  vd:"$>0;msg:'绩效金额不能为空'"`             // 绩效金额
+
+}
+
+func (s *PerformanceTargetInsertReq) Generate(model *models.PerformanceTarget) {
+	model.T_name = s.T_name
+	model.T_operator = s.T_operator
+	model.T_assess_points = s.T_assess_points
+	model.T_base = s.T_base
+	model.T_post = s.T_post
+	model.T_perf = s.T_perf
+	model.T_State = 1
+}
+
+// PerformanceTargetUpdateReq 改使用的结构体
+type PerformanceTargetUpdateReq struct {
+	T_id            int     `form:"T_id"  example:"1"`
+	T_name          string  `form:"T_name" example:"工资级别"  vd:"len($)>0;msg:'服务类型不能为空'"`   //服务类型
+	T_operator      string  `form:"T_operator" example:"≥"  vd:"len($)>0;msg:'运算符不能为空'"`        // 运算符 <,≥
+	T_assess_points int     `form:"T_assess_points" example:"60"  vd:"$>0;msg:'月考核工具量不能为空'"` // 月考试绩效点数量
+	T_base          float64 `form:"T_base" example:"2000"  vd:"len($)>0;msg:'无责底薪不能为空'"`       // 无责底薪
+	T_post          float64 `form:"T_post" example:"1400"  vd:"len($)>0;msg:'补助不能为空'"`           // 补助
+	T_perf          float64 `form:"T_perf" example:"800"  vd:"len($)>0;msg:'绩效金额不能为空'"`        // 绩效金额
+}
+
+func (s *PerformanceTargetUpdateReq) Generate(model *models.PerformanceTarget) {
+	model.Id = s.T_id
+	model.T_name = s.T_name
+	model.T_operator = s.T_operator
+	model.T_assess_points = s.T_assess_points
+	model.T_base = s.T_base
+	model.T_post = s.T_post
+	model.T_perf = s.T_perf
+}
+
+func (s *PerformanceTargetUpdateReq) GetId() interface{} {
+	return s.T_id
+}
+
+// PerformanceTargetGetReq 获取单个的结构体
+type PerformanceTargetGetReq struct {
+	T_id int `form:"T_id"`
+}
+
+func (s *PerformanceTargetGetReq) GetId() interface{} {
+	return s.T_id
+}
+
+// PerformanceTargetDeleteReq 删除的结构体
+type PerformanceTargetDeleteReq struct {
+	T_id int `form:"T_id"`
+}
+
+func (s *PerformanceTargetDeleteReq) GetId() interface{} {
+	return s.T_id
+}

+ 76 - 0
dto/Stock.go

@@ -0,0 +1,76 @@
+package dto
+
+import (
+	"gogs.baozhida.cn/zoie/ERP_libs/lib"
+	"sort"
+)
+
+// 入库产品mingxi
+
+type StockProduct struct {
+	T_product_id  int
+	T_num         int
+	T_relation_sn []string
+}
+
+type StockProductRelationSn struct {
+	T_product_id         int
+	T_delete_relation_sn []string
+	T_add_relation_sn    []string
+}
+
+// 比较两个StockProduct列表是否相等
+func StockProductListsEqual(a, b []StockProduct) (isEqual bool, needDelete, needAdd, needEdit map[int]StockProduct, snDiff map[int]StockProductRelationSn) {
+	needDelete = make(map[int]StockProduct)
+	needAdd = make(map[int]StockProduct)
+	needEdit = make(map[int]StockProduct)
+	snDiff = make(map[int]StockProductRelationSn)
+	isEqual = false
+	// 原始数据
+	aMap := make(map[int]StockProduct)
+	aProductList := []int{}
+	for _, item := range a {
+		sort.Strings(item.T_relation_sn)
+		aMap[item.T_product_id] = item
+		aProductList = append(aProductList, item.T_product_id)
+	}
+	// 修改提交的数据
+	bMap := make(map[int]StockProduct)
+	bProductList := []int{}
+	for _, item := range b {
+		sort.Strings(item.T_relation_sn)
+		bMap[item.T_product_id] = item
+		bProductList = append(bProductList, item.T_product_id)
+	}
+
+	// 查询需要删除的产品 和需要添加的产品
+	_, commonIds, needDeleteIds, needAddIds := lib.IntListCompare(aProductList, bProductList)
+
+	for _, product_id := range commonIds {
+		equal, _, T_delete_relation_sn, T_add_relation_sn := lib.StringListCompare(aMap[product_id].T_relation_sn, bMap[product_id].T_relation_sn)
+		snDiff[product_id] = StockProductRelationSn{
+			T_product_id:         product_id,
+			T_delete_relation_sn: T_delete_relation_sn,
+			T_add_relation_sn:    T_add_relation_sn,
+		}
+		if bMap[product_id].T_num != aMap[product_id].T_num && len(bMap[product_id].T_relation_sn) == 0 {
+			needEdit[product_id] = bMap[product_id]
+		}
+
+		if !equal {
+			isEqual = false
+		}
+	}
+
+	if len(needDelete) == 0 && len(needAdd) == 0 && len(needEdit) == 0 && isEqual {
+		isEqual = true
+	}
+	for _, id := range needDeleteIds {
+		needDelete[id] = aMap[id]
+	}
+	for _, id := range needAddIds {
+		needAdd[id] = bMap[id]
+	}
+
+	return isEqual, needDelete, needAdd, needEdit, snDiff
+}

+ 22 - 0
dto/error.go

@@ -0,0 +1,22 @@
+package dto
+
+import (
+	"errors"
+	"github.com/go-sql-driver/mysql"
+)
+
+var (
+	GetFailedErr    = errors.New("查询失败")
+	CreateFailedErr = errors.New("添加失败")
+	UpdateFailedErr = errors.New("更新失败")
+	DeleteFailedErr = errors.New("删除失败")
+	GetNotFoundErr  = errors.New("数据不存在")
+)
+
+func IsUniqueIndexErr(err error) bool {
+	uniqueErr := &mysql.MySQLError{}
+	if ok := errors.As(err, &uniqueErr); ok {
+		return uniqueErr.Number == 1062
+	}
+	return false
+}

+ 20 - 0
dto/pagination.go

@@ -0,0 +1,20 @@
+package dto
+
+type Pagination struct {
+	Page     int `form:"page" example:"1"`    // 页数
+	PageSize int `form:"page_z" example:"10"` // 每页条数
+}
+
+func (m *Pagination) GetPageIndex() int {
+	if m.Page <= 0 {
+		m.Page = 1
+	}
+	return m.Page
+}
+
+func (m *Pagination) GetPageSize() int {
+	if m.PageSize <= 0 {
+		m.PageSize = 10
+	}
+	return m.PageSize
+}

+ 108 - 0
dto/search.go

@@ -0,0 +1,108 @@
+package dto
+
+import (
+	"ERP_salary/dto/search"
+	"fmt"
+	"gorm.io/gorm"
+	"strings"
+)
+
+var (
+	Source string
+	Driver string
+	DBName string
+)
+
+type GeneralDelDto struct {
+	Id  int   `uri:"id" json:"id" validate:"required"`
+	Ids []int `json:"ids"`
+}
+
+func (g GeneralDelDto) GetIds() []int {
+	ids := make([]int, 0)
+	if g.Id != 0 {
+		ids = append(ids, g.Id)
+	}
+	if len(g.Ids) > 0 {
+		for _, id := range g.Ids {
+			if id > 0 {
+				ids = append(ids, id)
+			}
+		}
+	} else {
+		if g.Id > 0 {
+			ids = append(ids, g.Id)
+		}
+	}
+	if len(ids) <= 0 {
+		//方式全部删除
+		ids = append(ids, 0)
+	}
+	return ids
+}
+
+type GeneralGetDto struct {
+	Id int `uri:"id" json:"id" validate:"required"`
+}
+
+func MakeCondition(q interface{}) func(db *gorm.DB) *gorm.DB {
+	return func(db *gorm.DB) *gorm.DB {
+		condition := &search.GormCondition{
+			GormPublic: search.GormPublic{},
+			Join:       make([]*search.GormJoin, 0),
+		}
+		search.ResolveSearchQuery(Driver, q, condition)
+		for _, join := range condition.Join {
+			if join == nil {
+				continue
+			}
+			db = db.Joins(join.JoinOn)
+			for k, v := range join.Where {
+				db = db.Where(k, v...)
+			}
+			for k, v := range join.Or {
+				db = db.Or(k, v...)
+			}
+			for _, o := range join.Order {
+				db = db.Order(o)
+			}
+		}
+		for k, v := range condition.Where {
+			db = db.Where(k, v...)
+		}
+
+		for k, v := range condition.Or {
+			db = db.Or(k, v...)
+		}
+		var orContains string
+		for k, v := range condition.OrContains {
+			orContains += fmt.Sprintf(" OR %v '%v'", k, v[0])
+		}
+		if len(orContains) > 0 {
+			db = db.Where(strings.TrimLeft(orContains, " OR"))
+		}
+		for _, o := range condition.Order {
+			db = db.Order(o)
+		}
+		return db
+	}
+}
+
+func Paginate(pageSize, pageIndex int) func(db *gorm.DB) *gorm.DB {
+	return func(db *gorm.DB) *gorm.DB {
+		if pageSize == 9999 {
+			return db
+		}
+		offset := (pageIndex - 1) * pageSize
+		if offset < 0 {
+			offset = 0
+		}
+		return db.Offset(offset).Limit(pageSize)
+	}
+}
+
+func WithNormalState() func(db *gorm.DB) *gorm.DB {
+	return func(db *gorm.DB) *gorm.DB {
+		return db.Where("t__state = 1")
+	}
+}

+ 34 - 0
dto/search/README.md

@@ -0,0 +1,34 @@
+## Search库
+
+|type|描述|query示例|
+|:---|:---|:---|
+|exact/iexact|等于|status=1|
+|contains/icontanins|包含|name=n|
+|gt/gte|大于/大于等于|age=18|
+|lt/lte|小于/小于等于|age=18|
+|startswith/istartswith|以…起始|content=hell|
+|endswith/iendswith|以…结束|content=world|
+|in|in查询|status[]=0&status[]=1|
+|isnull|isnull查询|startTime=1|
+|order|排序|sort=asc/sort=desc|
+
+e.g.
+```
+type ApplicationQuery struct {
+	Id       string    `search:"type:icontains;column:id;table:receipt" form:"id"`
+	Domain   string    `search:"type:icontains;column:domain;table:receipt" form:"domain"`
+	Version  string    `search:"type:exact;column:version;table:receipt" form:"version"`
+	Status   []int     `search:"type:in;column:status;table:receipt" form:"status"`
+	Start    time.Time `search:"type:gte;column:created_at;table:receipt" form:"start"`
+	End      time.Time `search:"type:lte;column:created_at;table:receipt" form:"end"`
+	TestJoin `search:"type:left;on:id:receipt_id;table:receipt_goods;join:receipts"`
+	ApplicationOrder
+}
+type ApplicationOrder struct {
+	IdOrder string `search:"type:order;column:id;table:receipt" form"id_order"`
+}
+
+type TestJoin struct {
+	PaymentAccount string `search:"type:icontains;column:payment_account;table:receipts" form:"payment_account"`
+}
+```

+ 212 - 0
dto/search/Stock_test.go

@@ -0,0 +1,212 @@
+package search
+
+import (
+	"fmt"
+	"reflect"
+	"sort"
+	"testing"
+)
+
+// 对比两个列表数据是否相同
+func StringListCompare(list1, list2 []string) (isEqual bool, common, onlyList1, onlyList2 []string) {
+	if len(list1) != len(list2) {
+		isEqual = false
+	}
+
+	// 创建map来存储list1列表中的元素
+	AMap := make(map[string]int)
+	for _, item := range list1 {
+		AMap[item]++
+	}
+
+	// 检查B列表中的元素是否在AMap中
+	for _, item := range list2 {
+		if count, exists := AMap[item]; !exists || count == 0 {
+			isEqual = false
+		}
+		AMap[item]--
+	}
+	if !isEqual {
+		// 创建一个map来存储A列表中的元素
+		list1Map := make(map[string]struct{})
+		for _, a := range list1 {
+			list1Map[a] = struct{}{}
+		}
+
+		// 查找list2列表中存在但list1列表中不存在的元素
+		for _, v := range list2 {
+			if _, exists := list1Map[v]; !exists {
+				onlyList2 = append(onlyList2, v)
+			} else {
+				common = append(common, v)
+			}
+		}
+
+		// 创建一个map来存储B列表中的元素
+		list2Map := make(map[string]struct{})
+		for _, b := range list2 {
+			list2Map[b] = struct{}{}
+		}
+
+		// 查找list1列表中存在但list2列表中不存在的元素
+		for _, a := range list1 {
+			if _, exists := list2Map[a]; !exists {
+				onlyList1 = append(onlyList1, a)
+			}
+		}
+
+	}
+
+	isEqual = true
+	return
+}
+func IntListCompare(list1, list2 []int) (isEqual bool, common, onlyList1, onlyList2 []int) {
+	if len(list1) != len(list2) {
+		isEqual = false
+	}
+
+	// 创建map来存储list1列表中的元素
+	AMap := make(map[int]int)
+	for _, item := range list1 {
+		AMap[item]++
+	}
+
+	// 检查B列表中的元素是否在AMap中
+	for _, item := range list2 {
+		if count, exists := AMap[item]; !exists || count == 0 {
+			isEqual = false
+		}
+		AMap[item]--
+	}
+	if !isEqual {
+		// 创建一个map来存储A列表中的元素
+		list1Map := make(map[int]struct{})
+		for _, a := range list1 {
+			list1Map[a] = struct{}{}
+		}
+
+		// 查找list2列表中存在但list1列表中不存在的元素
+		for _, v := range list2 {
+			if _, exists := list1Map[v]; !exists {
+				onlyList2 = append(onlyList2, v)
+			} else {
+				common = append(common, v)
+			}
+		}
+
+		// 创建一个map来存储B列表中的元素
+		list2Map := make(map[int]struct{})
+		for _, b := range list2 {
+			list2Map[b] = struct{}{}
+		}
+
+		// 查找list1列表中存在但list2列表中不存在的元素
+		for _, a := range list1 {
+			if _, exists := list2Map[a]; !exists {
+				onlyList1 = append(onlyList1, a)
+			}
+		}
+
+	}
+
+	isEqual = true
+	return
+}
+
+type StockProduct struct {
+	T_product_id  int
+	T_num         int
+	T_relation_sn []string
+}
+
+type StockProductRelationSn struct {
+	T_product_id         int
+	T_delete_relation_sn []string
+	T_add_relation_sn    []string
+}
+
+// 比较两个StockProduct列表是否相等
+func StockProductListsEqual(a, b []StockProduct) (isEqual bool, cDiff map[int]StockProductRelationSn) {
+	cDiff = make(map[int]StockProductRelationSn)
+	isEqual = true
+	// 创建一个map来存储a列表中的StockProduct
+	aMap := make(map[int]StockProduct)
+	aProductList := []int{}
+	for _, item := range a {
+		sort.Strings(item.T_relation_sn)
+		aMap[item.T_product_id] = item
+		aProductList = append(aProductList, item.T_product_id)
+	}
+	bMap := make(map[int]StockProduct)
+	bProductList := []int{}
+	for _, item := range b {
+		sort.Strings(item.T_relation_sn)
+		bMap[item.T_product_id] = item
+		bProductList = append(bProductList, item.T_product_id)
+	}
+
+	// 查找两个元素相同
+	_, common, needDelete, needAdd := IntListCompare(aProductList, bProductList)
+	fmt.Println("needDelete:", needDelete)
+	fmt.Println("needAdd:", needAdd)
+	for _, product_id := range common {
+		//_, _, aDiff[product_id], bDiff[product_id] = StringListCompare(aMap[product_id].T_relation_sn, bMap[product_id].T_relation_sn)
+		equal, _, T_delete_relation_sn, T_add_relation_sn := StringListCompare(aMap[product_id].T_relation_sn, bMap[product_id].T_relation_sn)
+		cDiff[product_id] = StockProductRelationSn{
+			T_product_id:         product_id,
+			T_delete_relation_sn: T_delete_relation_sn,
+			T_add_relation_sn:    T_add_relation_sn,
+		}
+		fmt.Println("equal:", equal)
+
+		if !equal {
+			isEqual = false
+		}
+	}
+
+	if len(needDelete) == 0 && len(needAdd) == 0 && isEqual {
+		isEqual = true
+	} else {
+		isEqual = false
+	}
+
+	return isEqual, cDiff
+}
+
+// 比较两个StockProduct是否相等
+func stockProductEqual(a, b StockProduct) bool {
+	return a.T_product_id == b.T_product_id && a.T_num == b.T_num && reflect.DeepEqual(a.T_relation_sn, b.T_relation_sn)
+}
+
+func TestStockProductListsEqual(t *testing.T) {
+	// 定义两个StockProduct列表
+	A := []StockProduct{
+		{T_product_id: 1, T_num: 10, T_relation_sn: []string{"20220101", "20230202"}},
+		{T_product_id: 2, T_num: 20, T_relation_sn: []string{"20220303", "20230404"}},
+	}
+	B := []StockProduct{
+		//{T_product_id: 1, T_num: 10, T_relation_sn: []string{"20220101", "20230202"}},
+		//{T_product_id: 2, T_num: 20, T_relation_sn: []string{"20220303", "20230404"}},
+	}
+	//B := []StockProduct{
+	//	{T_product_id: 1, T_num: 10, T_relation_sn: []string{"20220101", "202302"}},
+	//	{T_product_id: 2, T_num: 20, T_relation_sn: []string{"20230404", "20220303"}},
+	//	{T_product_id: 3, T_num: 20, T_relation_sn: []string{"20240404", "20240303"}},
+	//}
+
+	//查找两个StockProduct列表中T_relation_sn不相同的元素
+	//aDiff, bDiff := StockProductListsEqual(A, B)
+	////打印结果
+	//fmt.Println("A列表中T_relation_sn不相同的元素:", aDiff)
+	//fmt.Println("B列表中T_relation_sn不相同的元素:", bDiff)
+	isEqual, aDiff := StockProductListsEqual(A, B)
+	//打印结果
+	fmt.Println("是否相等:", isEqual)
+	fmt.Println("A列表中T_relation_sn不相同的元素:", aDiff)
+
+	//_, c, d, a := StringListCompare([]string{"20220101", "20230202"}, []string{"20230202", "20220103"})
+	//fmt.Println("共同元素:", c)
+	//fmt.Println("仅A列表中:", d)
+	//fmt.Println("仅B列表中:", a)
+
+}

+ 117 - 0
dto/search/condition.go

@@ -0,0 +1,117 @@
+package search
+
+import "strings"
+
+type Condition interface {
+	SetWhere(k string, v []interface{})
+	SetOr(k string, v []interface{})
+	SetOrContains(k string, v []interface{})
+	SetOrder(k string)
+	SetJoinOn(t, on string) Condition
+}
+
+type GormCondition struct {
+	GormPublic
+	Join []*GormJoin
+}
+
+type GormPublic struct {
+	Where      map[string][]interface{}
+	Order      []string
+	Or         map[string][]interface{}
+	OrContains map[string][]interface{}
+}
+
+type GormJoin struct {
+	Type   string
+	JoinOn string
+	GormPublic
+}
+
+func (e *GormJoin) SetJoinOn(t, on string) Condition {
+	return nil
+}
+
+func (e *GormPublic) SetWhere(k string, v []interface{}) {
+	if e.Where == nil {
+		e.Where = make(map[string][]interface{})
+	}
+	e.Where[k] = v
+}
+
+func (e *GormPublic) SetOr(k string, v []interface{}) {
+	if e.Or == nil {
+		e.Or = make(map[string][]interface{})
+	}
+	e.Or[k] = v
+}
+func (e *GormPublic) SetOrContains(k string, v []interface{}) {
+	if e.OrContains == nil {
+		e.OrContains = make(map[string][]interface{})
+	}
+	e.OrContains[k] = v
+}
+
+func (e *GormPublic) SetOrder(k string) {
+	if e.Order == nil {
+		e.Order = make([]string, 0)
+	}
+	e.Order = append(e.Order, k)
+}
+
+func (e *GormCondition) SetJoinOn(t, on string) Condition {
+	if e.Join == nil {
+		e.Join = make([]*GormJoin, 0)
+	}
+	join := &GormJoin{
+		Type:       t,
+		JoinOn:     on,
+		GormPublic: GormPublic{},
+	}
+	e.Join = append(e.Join, join)
+	return join
+}
+
+type resolveSearchTag struct {
+	Type   string
+	Column string
+	Table  string
+	On     []string
+	Join   string
+}
+
+// makeTag 解析search的tag标签
+func makeTag(tag string) *resolveSearchTag {
+	r := &resolveSearchTag{}
+	tags := strings.Split(tag, ";")
+	var ts []string
+	for _, t := range tags {
+		ts = strings.Split(t, ":")
+		if len(ts) == 0 {
+			continue
+		}
+		switch ts[0] {
+		case "type":
+			if len(ts) > 1 {
+				r.Type = ts[1]
+			}
+		case "column":
+			if len(ts) > 1 {
+				r.Column = ts[1]
+			}
+		case "table":
+			if len(ts) > 1 {
+				r.Table = ts[1]
+			}
+		case "on":
+			if len(ts) > 1 {
+				r.On = ts[1:]
+			}
+		case "join":
+			if len(ts) > 1 {
+				r.Join = ts[1]
+			}
+		}
+	}
+	return r
+}

+ 103 - 0
dto/search/query.go

@@ -0,0 +1,103 @@
+package search
+
+import (
+	"fmt"
+	"reflect"
+	"strings"
+)
+
+const (
+	// FromQueryTag tag标记
+	FromQueryTag = "search"
+	// OrderDefaultTag tag标记
+	DefaultTag = "default"
+	// Mysql 数据库标识
+	Mysql = "mysql"
+	// Postgres 数据库标识
+	Postgres = "postgres"
+)
+
+// ResolveSearchQuery 解析
+/**
+ * 	exact / iexact 等于
+ * 	contains / icontains 包含
+ *	gt / gte 大于 / 大于等于
+ *	lt / lte 小于 / 小于等于
+ *	startswith / istartswith 以…起始
+ *	endswith / iendswith 以…结束
+ *	in
+ *	isnull
+ *  order 排序		e.g. order[key]=desc     order[key]=asc
+ */
+func ResolveSearchQuery(driver string, q interface{}, condition Condition) {
+	qType := reflect.TypeOf(q)
+	qValue := reflect.ValueOf(q)
+	var tag string
+	var ok bool
+	var t *resolveSearchTag
+	for i := 0; i < qType.NumField(); i++ {
+		tag, ok = "", false
+		tag, ok = qType.Field(i).Tag.Lookup(FromQueryTag)
+		if !ok {
+			//递归调用
+			ResolveSearchQuery(driver, qValue.Field(i).Interface(), condition)
+			continue
+		}
+		switch tag {
+		case "-":
+			continue
+		}
+
+		defaultTag, defaultOk := qType.Field(i).Tag.Lookup(DefaultTag)
+
+		if qValue.Field(i).IsZero() && !defaultOk {
+			continue
+		}
+		t = makeTag(tag)
+		//解析
+		switch t.Type {
+		case "left":
+			//左关联
+			join := condition.SetJoinOn(t.Type, fmt.Sprintf(
+				"left join `%s` on `%s`.`%s` = `%s`.`%s`",
+				t.Join,
+				t.Join,
+				t.On[0],
+				t.Table,
+				t.On[1],
+			))
+			ResolveSearchQuery(driver, qValue.Field(i).Interface(), join)
+		case "exact", "iexact":
+			condition.SetWhere(fmt.Sprintf("`%s`.`%s` = ?", t.Table, t.Column), []interface{}{qValue.Field(i).Interface()})
+		case "contains":
+			condition.SetWhere(fmt.Sprintf("`%s`.`%s` like ?", t.Table, t.Column), []interface{}{"%" + qValue.Field(i).String() + "%"})
+		case "orcontains":
+			condition.SetOrContains(fmt.Sprintf("`%s`.`%s` like ", t.Table, t.Column), []interface{}{"%" + qValue.Field(i).String() + "%"})
+		case "gt":
+			condition.SetWhere(fmt.Sprintf("`%s`.`%s` > ?", t.Table, t.Column), []interface{}{qValue.Field(i).Interface()})
+		case "gte":
+			condition.SetWhere(fmt.Sprintf("`%s`.`%s` >= ?", t.Table, t.Column), []interface{}{qValue.Field(i).Interface()})
+		case "lt":
+			condition.SetWhere(fmt.Sprintf("`%s`.`%s` < ?", t.Table, t.Column), []interface{}{qValue.Field(i).Interface()})
+		case "lte":
+			condition.SetWhere(fmt.Sprintf("`%s`.`%s` <= ?", t.Table, t.Column), []interface{}{qValue.Field(i).Interface()})
+		case "startswith":
+			condition.SetWhere(fmt.Sprintf("`%s`.`%s` like ?", t.Table, t.Column), []interface{}{qValue.Field(i).String() + "%"})
+		case "endswith":
+			condition.SetWhere(fmt.Sprintf("`%s`.`%s` like ?", t.Table, t.Column), []interface{}{"%" + qValue.Field(i).String()})
+		case "in":
+			condition.SetWhere(fmt.Sprintf("`%s`.`%s` in (?)", t.Table, t.Column), []interface{}{qValue.Field(i).Interface()})
+		case "isnull":
+			if !(qValue.Field(i).IsZero() && qValue.Field(i).IsNil()) {
+				condition.SetWhere(fmt.Sprintf("`%s`.`%s` isnull", t.Table, t.Column), make([]interface{}, 0))
+			}
+		case "order":
+			switch strings.ToLower(qValue.Field(i).String()) {
+			case "desc", "asc":
+				condition.SetOrder(fmt.Sprintf("`%s`.`%s` %s", t.Table, t.Column, qValue.Field(i).String()))
+			default:
+				condition.SetOrder(fmt.Sprintf("`%s`.`%s` %s", t.Table, t.Column, defaultTag))
+			}
+		}
+	}
+}

+ 51 - 0
dto/search/query_test.go

@@ -0,0 +1,51 @@
+package search
+
+import (
+	"fmt"
+	"testing"
+	"time"
+
+	. "github.com/smartystreets/goconvey/convey"
+)
+
+type ApplicationQuery struct {
+	Id       string    `search:"type:icontains;column:id;table:receipt" form:"id"`
+	Domain   string    `search:"type:icontains;column:domain;table:receipt" form:"domain"`
+	Version  string    `search:"type:exact;column:version;table:receipt" form:"version"`
+	Status   []int     `search:"type:in;column:status;table:receipt" form:"status"`
+	Start    time.Time `search:"type:gte;column:created_at;table:receipt" form:"start"`
+	End      time.Time `search:"type:lte;column:created_at;table:receipt" form:"end"`
+	TestJoin `search:"type:left;on:id:receipt_id;table:receipt_goods;join:receipts"`
+	NotNeed  string `search:"-"`
+	ApplicationOrder
+}
+
+type ApplicationOrder struct {
+	IdOrder string `search:"type:order;column:id;table:receipt" form:"id_order"`
+}
+
+type TestJoin struct {
+	PaymentAccount string `search:"type:icontains;column:payment_account;table:receipts" form:"payment_account"`
+}
+
+func TestResolveSearchQuery(t *testing.T) {
+	// Only pass t into top-level Convey calls
+	Convey("Given some integer with a starting value", t, func() {
+		d := ApplicationQuery{
+			Id:               "aaa",
+			Domain:           "bbb",
+			Version:          "ccc",
+			Status:           []int{1, 2},
+			Start:            time.Now().Add(-8 * time.Hour),
+			End:              time.Now(),
+			ApplicationOrder: ApplicationOrder{IdOrder: "desc"},
+			TestJoin:         TestJoin{PaymentAccount: "1212"},
+		}
+		condition := &GormCondition{
+			GormPublic: GormPublic{},
+			Join:       make([]*GormJoin, 0),
+		}
+		ResolveSearchQuery("mysql", d, condition)
+		fmt.Println(condition)
+	})
+}

+ 16 - 5
go.mod

@@ -1,23 +1,32 @@
 module ERP_salary
 
-go 1.19
+go 1.23.0
+
+toolchain go1.23.9
 
 require (
 	github.com/astaxie/beego v1.12.3
 	github.com/beego/beego/v2 v2.1.0
-	github.com/go-sql-driver/mysql v1.7.1
+	github.com/go-sql-driver/mysql v1.8.1
 	github.com/nats-io/nats.go v1.23.0
 	github.com/shopspring/decimal v1.3.1
 	github.com/vmihailenco/msgpack/v5 v5.3.5
-	gogs.baozhida.cn/zoie/ERP_libs v0.0.0-20241029034120-31ca607abb2e
+	github.com/xuri/excelize/v2 v2.9.0
+	gogs.baozhida.cn/zoie/ERP_libs v0.0.0-20250925092557-0180097d6c03
 )
 
 require (
+	filippo.io/edwards25519 v1.1.0 // indirect
+	github.com/andeya/ameda v1.5.3 // indirect
+	github.com/andeya/goutil v1.0.1 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
+	github.com/bytedance/go-tagexpr/v2 v2.9.11 // indirect
 	github.com/cespare/xxhash/v2 v2.2.0 // indirect
 	github.com/golang/protobuf v1.5.3 // indirect
 	github.com/gomodule/redigo v2.0.0+incompatible // indirect
 	github.com/hashicorp/golang-lru v0.5.4 // indirect
+	github.com/jinzhu/inflection v1.0.0 // indirect
+	github.com/jinzhu/now v1.1.5 // indirect
 	github.com/klauspost/compress v1.15.15 // indirect
 	github.com/lib/pq v1.10.9 // indirect
 	github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
@@ -28,6 +37,7 @@ require (
 	github.com/nats-io/jwt/v2 v2.3.0 // indirect
 	github.com/nats-io/nkeys v0.3.0 // indirect
 	github.com/nats-io/nuid v1.0.1 // indirect
+	github.com/nyaruka/phonenumbers v1.0.55 // indirect
 	github.com/phpdave11/gofpdi v1.0.14-0.20211212211723-1f10f9844311 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/prometheus/client_golang v1.16.0 // indirect
@@ -37,12 +47,11 @@ require (
 	github.com/qiniu/go-sdk/v7 v7.14.0 // indirect
 	github.com/richardlehane/mscfb v1.0.4 // indirect
 	github.com/richardlehane/msoleps v1.0.4 // indirect
+	github.com/robfig/cron/v3 v3.0.1 // indirect
 	github.com/shiena/ansicolor v0.0.0-20230509054315-a9deabde6e02 // indirect
 	github.com/signintech/gopdf v0.16.1 // indirect
-	github.com/stretchr/testify v1.8.4 // indirect
 	github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
 	github.com/xuri/efp v0.0.0-20250227110027-3491fafc2b79 // indirect
-	github.com/xuri/excelize/v2 v2.9.0 // indirect
 	github.com/xuri/nfp v0.0.0-20250226145837-86d5fc24b2ba // indirect
 	golang.org/x/crypto v0.37.0 // indirect
 	golang.org/x/net v0.39.0 // indirect
@@ -52,4 +61,6 @@ require (
 	golang.org/x/time v0.1.0 // indirect
 	google.golang.org/protobuf v1.31.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
+	gorm.io/driver/mysql v1.6.0 // indirect
+	gorm.io/gorm v1.30.0 // indirect
 )

+ 35 - 0
go.sum

@@ -1,3 +1,5 @@
+filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
+filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
@@ -6,6 +8,10 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
 github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=
+github.com/andeya/ameda v1.5.3 h1:SvqnhQPZwwabS8HQTRGfJwWPl2w9ZIPInHAw9aE1Wlk=
+github.com/andeya/ameda v1.5.3/go.mod h1:FQDHRe1I995v6GG+8aJ7UIUToEmbdTJn/U26NCPIgXQ=
+github.com/andeya/goutil v1.0.1 h1:eiYwVyAnnK0dXU5FJsNjExkJW4exUGn/xefPt3k4eXg=
+github.com/andeya/goutil v1.0.1/go.mod h1:jEG5/QnnhG7yGxwFUX6Q+JGMif7sjdHmmNVjn7nhJDo=
 github.com/astaxie/beego v1.12.3 h1:SAQkdD2ePye+v8Gn1r4X6IKZM1wd28EyUOVQ3PDSOOQ=
 github.com/astaxie/beego v1.12.3/go.mod h1:p3qIm0Ryx7zeBHLljmd7omloyca1s4yu1a8kM1FkpIA=
 github.com/beego/beego/v2 v2.1.0 h1:Lk0FtQGvDQCx5V5yEu4XwDsIgt+QOlNjt5emUa3/ZmA=
@@ -17,6 +23,8 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce
 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
+github.com/bytedance/go-tagexpr/v2 v2.9.11 h1:jJgmoDKPKacGl0llPYbYL/+/2N+Ng0vV0ipbnVssXHY=
+github.com/bytedance/go-tagexpr/v2 v2.9.11/go.mod h1:UAyKh4ZRLBPGsyTRFZoPqTni1TlojMdOJXQnEIPCX84=
 github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
@@ -50,6 +58,8 @@ github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8w
 github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
 github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
+github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
+github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -77,6 +87,10 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
 github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
 github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
+github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
+github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
@@ -125,6 +139,8 @@ github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV
 github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
 github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/nyaruka/phonenumbers v1.0.55 h1:bj0nTO88Y68KeUQ/n3Lo2KgK7lM1hF7L9NFuwcCl3yg=
+github.com/nyaruka/phonenumbers v1.0.55/go.mod h1:sDaTZ/KPX5f8qyV9qN+hIm+4ZBARJrupC6LuhshJq1U=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
@@ -168,6 +184,8 @@ github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7
 github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
 github.com/richardlehane/msoleps v1.0.4 h1:WuESlvhX3gH2IHcd8UqyCuFY5yiq/GR/yqaSM/9/g00=
 github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
+github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
+github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
 github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
 github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
@@ -186,14 +204,19 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd
 github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 github.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
 github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
+github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
+github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
 github.com/ugorji/go v0.0.0-20171122102828-84cb69a8af83/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
 github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
 github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
@@ -209,6 +232,12 @@ github.com/xuri/nfp v0.0.0-20250226145837-86d5fc24b2ba/go.mod h1:WwHg+CVyzlv/TX9
 github.com/yuin/gopher-lua v0.0.0-20171031051903-609c9cd26973/go.mod h1:aEV29XrmTYFr3CiRxZeGHpkvbwq+prZduBqMaascyCU=
 gogs.baozhida.cn/zoie/ERP_libs v0.0.0-20241029034120-31ca607abb2e h1:sUbmO6gD9GcYjW1abZ2Brfw5xay2a3R6KlSVy7+QE4I=
 gogs.baozhida.cn/zoie/ERP_libs v0.0.0-20241029034120-31ca607abb2e/go.mod h1:wS/rgL1FkdfMsCwKpwG04dZtG4SOFcBADi4lxHN7UeM=
+gogs.baozhida.cn/zoie/ERP_libs v0.0.0-20250901061433-cb5c3676aa11 h1:aMuM32qXdOojinJW9w9rm0tSopDvD5eecI14YXnwMdA=
+gogs.baozhida.cn/zoie/ERP_libs v0.0.0-20250901061433-cb5c3676aa11/go.mod h1:wS/rgL1FkdfMsCwKpwG04dZtG4SOFcBADi4lxHN7UeM=
+gogs.baozhida.cn/zoie/ERP_libs v0.0.0-20250925072832-09c80fde6e5c h1:HiZhes2UNlHdhdzJkeOrpoczeW4WwrPrekCxAi2CVo8=
+gogs.baozhida.cn/zoie/ERP_libs v0.0.0-20250925072832-09c80fde6e5c/go.mod h1:wS/rgL1FkdfMsCwKpwG04dZtG4SOFcBADi4lxHN7UeM=
+gogs.baozhida.cn/zoie/ERP_libs v0.0.0-20250925092557-0180097d6c03 h1:EDX10TTz5AWSVjPVvX7LiYYPn1lXVlc34u35qeB0c1E=
+gogs.baozhida.cn/zoie/ERP_libs v0.0.0-20250925092557-0180097d6c03/go.mod h1:wS/rgL1FkdfMsCwKpwG04dZtG4SOFcBADi4lxHN7UeM=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -217,6 +246,7 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
 golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
+golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -268,6 +298,7 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
 google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
@@ -290,3 +321,7 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
+gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=
+gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
+gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=

+ 84 - 0
initialize/db.go

@@ -0,0 +1,84 @@
+package initialize
+
+import (
+	"ERP_salary/conf"
+	_ "github.com/go-sql-driver/mysql"
+
+	"fmt"
+	"log"
+	"time"
+
+	"gorm.io/gorm/logger"
+
+	"github.com/beego/beego/v2/core/logs"
+	beego "github.com/beego/beego/v2/server/web"
+	"gorm.io/driver/mysql"
+	"gorm.io/gorm"
+)
+
+var DB *gorm.DB
+
+var Err error
+
+// 重写gorm日志的Writer
+type Writer struct {
+}
+
+func (w Writer) Printf(format string, args ...interface{}) {
+	// log.Infof(format, args...)
+	logs.Notice(format, args...)
+}
+
+// 设置psql
+func init() {
+	log.Println("连接数据库...")
+
+	sqlloglevel, err := beego.AppConfig.Int("sqlloglevel")
+	if err != nil {
+		log.Fatalf("无法获取sqlloglevel: %v", err)
+	}
+	slow_threshold, err := beego.AppConfig.Int("slow_threshold")
+	if err != nil {
+		log.Fatalf("无法获取slow_threshold: %v", err)
+	}
+
+	dataSource := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
+		conf.MysqlServer_Username,
+		conf.MysqlServer_Password,
+		conf.MysqlServer_UrlPort,
+		conf.MysqlServer_Database,
+	)
+
+	//设置gorm日志规则
+	newLogger := logger.New(
+		// log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer 单独设置grom日志输出
+		Writer{}, // beego日志info输出
+		logger.Config{
+			SlowThreshold:             time.Duration(slow_threshold) * time.Millisecond, // Slow SQL threshold
+			LogLevel:                  logger.LogLevel(sqlloglevel),                     // Log level
+			IgnoreRecordNotFoundError: true,                                             // Ignore ErrRecordNotFound error for logger
+			Colorful:                  true,                                             // Disable color
+		},
+	)
+
+	DB, Err = gorm.Open(mysql.Open(dataSource), &gorm.Config{
+		DisableForeignKeyConstraintWhenMigrating: true, // 禁用外键
+		Logger:                                   newLogger,
+		DisableAutomaticPing:                     true,
+	})
+
+	if Err != nil {
+		log.Fatalf("连接数据库失败: %v", Err)
+	}
+
+	SqlDB, err2 := DB.DB()
+	if err2 != nil {
+		log.Fatalf("连接数据库失败: %v", err2)
+	}
+
+	SqlDB.SetMaxIdleConns(conf.MysqlServer_MaxIdleConnections)
+	SqlDB.SetMaxOpenConns(conf.MysqlServer_MaxOpenConnections)
+	SqlDB.SetConnMaxLifetime(time.Hour)
+
+	log.Println("连接数据库完成...")
+}

+ 71 - 0
initialize/log.go

@@ -0,0 +1,71 @@
+package initialize
+
+import (
+	"encoding/json"
+	"github.com/beego/beego/v2/core/logs"
+	beego "github.com/beego/beego/v2/server/web"
+	"log"
+)
+
+func init() {
+	log.SetFlags(log.Lshortfile | log.Ltime | log.Ldate)
+	log.Println("系统日志初始化...")
+
+	maxdays, err := beego.AppConfig.Int("maxdays")
+	if err != nil {
+		log.Println("无法获取maxdays:", err)
+	}
+
+	level, err := beego.AppConfig.Int("level")
+	if err != nil {
+		log.Println("无法获取level:", err)
+	}
+
+	maxlines, err := beego.AppConfig.Int("maxlines")
+	if err != nil {
+		log.Println("无法获取maxlines:", err)
+	}
+
+	dataSource := &struct {
+		Filename string `json:"filename"`
+		Level    int    `json:"level"`
+		Maxlines int    `json:"maxlines"`
+		Maxsize  int    `json:"maxsize"`
+		Daily    bool   `json:"daily"`
+		Maxdays  int    `json:"maxdays"`
+		Color    bool   `json:"color"`
+	}{
+		Filename: "logs/logx/logx.log",
+		Level:    level,
+		Maxlines: maxlines,
+		Maxsize:  0,
+		Daily:    true,
+		Maxdays:  maxdays,
+		Color:    true,
+	}
+	dataSourceBytes, err := json.Marshal(dataSource)
+	if err != nil {
+		log.Println("无法创建dataSource:", err)
+	}
+	logs.SetLevel(level)
+	adapter_type, err := beego.AppConfig.Int("adapter_type")
+	if err != nil {
+		log.Println("无法获取adapter_type:", err)
+	}
+	// 日志输出选择
+	switch adapter_type {
+	case 0:
+		logs.SetLogger(logs.AdapterConsole)
+	case 1:
+		logs.Reset()
+		logs.SetLogger(logs.AdapterFile, string(dataSourceBytes))
+	case 2:
+		logs.SetLogger(logs.AdapterFile, string(dataSourceBytes))
+	default:
+		logs.SetLogger(logs.AdapterConsole)
+	}
+	// 是否记录日志的调用层级 默认是logs.SetLogFuncCallDepth(2)
+	logs.EnableFuncCallDepth(true)
+	logs.Async()
+	log.Println("系统日志初始化完成")
+}

+ 64 - 0
initialize/time.go

@@ -0,0 +1,64 @@
+package initialize
+
+import (
+	"database/sql/driver"
+	"fmt"
+	"time"
+)
+
+const timeFormat = "2006-01-02 15:04:05"
+const timezone = "Asia/Shanghai"
+const dateFormat = "2006-01-02"
+
+// 全局定义
+type Time time.Time
+
+func (t Time) MarshalJSON() ([]byte, error) {
+	b := make([]byte, 0, len(timeFormat)+2)
+	if time.Time(t).IsZero() {
+		b = append(b, '"')
+		b = append(b, '"')
+		return b, nil
+	}
+
+	b = append(b, '"')
+	b = time.Time(t).AppendFormat(b, timeFormat)
+	b = append(b, '"')
+	return b, nil
+}
+
+func (t *Time) UnmarshalJSON(data []byte) (err error) {
+	now, err := time.ParseInLocation(`"`+timeFormat+`"`, string(data), time.Local)
+	*t = Time(now)
+	return
+}
+
+func (t Time) String() string {
+	if time.Time(t).IsZero() {
+		return ""
+	}
+	return time.Time(t).Format(timeFormat)
+}
+
+func (t Time) Local() time.Time {
+	loc, _ := time.LoadLocation(timezone)
+	return time.Time(t).In(loc)
+}
+
+func (t Time) Value() (driver.Value, error) {
+	var zeroTime time.Time
+	var ti = time.Time(t)
+	if ti.UnixNano() == zeroTime.UnixNano() {
+		return nil, nil
+	}
+	return ti, nil
+}
+
+func (t *Time) Scan(v interface{}) error {
+	value, ok := v.(time.Time)
+	if ok {
+		*t = Time(value)
+		return nil
+	}
+	return fmt.Errorf("can not convert %v to timestamp", v)
+}

+ 4 - 1
main.go

@@ -3,8 +3,11 @@ package main
 import (
 	_ "ERP_salary/Nats"
 	"ERP_salary/conf"
+	"ERP_salary/controllers"
 	"ERP_salary/logs"
+	_ "ERP_salary/models"
 	_ "ERP_salary/models/Account"
+	_ "ERP_salary/models/Performance"
 	_ "ERP_salary/models/Salary"
 	_ "ERP_salary/routers"
 	"fmt"
@@ -54,7 +57,7 @@ func main() {
 	beego.BConfig.WebConfig.AutoRender = false
 	beego.BConfig.RecoverPanic = true
 	beego.BConfig.RecoverFunc = RecoverPanic
-
+	go controllers.Cron_Percentage()
 	beego.Run()
 
 }

+ 9 - 0
models/Account/User.go

@@ -28,6 +28,15 @@ func Read_User_T_name_Get(T_uuid string) string {
 	}
 }
 
+func Read_User_Get(T_uuid string) userlibs.User {
+	v, ok := AdminMap.Load(T_uuid)
+	if ok {
+		return v.(userlibs.User)
+	} else {
+		return userlibs.User{}
+	}
+}
+
 func Read_User_T_dept_Get(T_uuid string) string {
 	v, ok := AdminMap.Load(T_uuid)
 	if ok {

+ 92 - 0
models/Performance/Performance.go

@@ -0,0 +1,92 @@
+package Performance
+
+import (
+	db "ERP_salary/initialize"
+	"time"
+)
+
+var (
+	DeviceTypeMap = map[string]string{
+		"X":  "箱",
+		"G":  "柜",
+		"C":  "车",
+		"K":  "库",
+		"XT": "系统",
+		"WZ": "位置",
+		"PX": "培训",
+		"XJ": "巡检",
+		"QT": "其他",
+	}
+)
+
+type Perf struct {
+	Id                      int     `json:"Id" gorm:"primaryKey;autoIncrement;comment:主键编码"` // 主键编码
+	T_date                  string  `json:"T_date" gorm:"size:128"`                          // 所属月份
+	T_submit                string  `json:"T_submit" gorm:"size:128"`                        // 员工
+	T_workload              float64 `json:"T_workload" gorm:"type:decimal(10,1)"`            // 工作量
+	T_audit                 int     `json:"T_audit" gorm:"size:4"`                           // 审核状态 待提交1 已提交2 已打款3
+	T_assess_points         int     `json:"T_assess_points" gorm:"size:4"`
+	T_perf                  float64 `json:"T_perf" gorm:"type:decimal(10,1)"` // 绩效金额
+	T_performance_target_id int     `json:"T_performance_target_id" gorm:"size:11;comment:工资级别id"`
+
+	T_State    int     `json:"T_State" gorm:"column:t__state;size(2);default(1)"`                  // 0 删除(伪删除)   1 正常
+	CreateTime db.Time `json:"CreateTime" gorm:"column:create_time;autoCreateTime;comment:创建时间"`   // 创建时间
+	UpdateTime db.Time `json:"UpdateTime" gorm:"column:update_time;autoUpdateTime;comment:最后更新时间"` // 最后更新时间
+
+	T_submit_name string `json:"T_submit_name" gorm:"-"` // 员工名称
+
+	// 关联的绩效点列表
+	PointList []PerfPoint       `json:"PointList" gorm:"foreignKey:T_performance_id"`
+	Target    PerformanceTarget `json:"Target" gorm:"foreignKey:T_performance_target_id"`
+}
+
+// 绩效点详情
+type PerfPoint struct {
+	Id                      int     `json:"Id" gorm:"primaryKey;autoIncrement;comment:主键编码"`
+	T_performance_id        int     `json:"T_performance_id" gorm:"size:11;comment:绩效记录ID"`
+	T_performance_points_id int     `json:"T_performance_points_id" gorm:"size:11;comment:绩效点ID"`
+	T_quantity              int     `json:"T_quantity" gorm:"size:11;comment:数量"`
+	T_points_numerator      int     `json:"T_points_numerator" gorm:"size:4;comment:绩效点分子"`
+	T_points_denominator    int     `json:"T_points_denominator" gorm:"size:4;comment:绩效点分母"`
+	T_type                  string  `json:"T_type" gorm:"size:50;comment:工作类型"` // 工作类型: reporting 或 collection
+	T_remark                string  `json:"T_remark" gorm:"size:text;comment:备注"`
+	T_State                 int     `json:"T_State" gorm:"column:t__state;size(2);default(1)"`                  // 0 删除(伪删除)   1 正常
+	CreateTime              db.Time `json:"CreateTime" gorm:"column:create_time;autoCreateTime;comment:创建时间"`   // 创建时间
+	UpdateTime              db.Time `json:"UpdateTime" gorm:"column:update_time;autoUpdateTime;comment:最后更新时间"` // 最后更新时间
+
+	// 关联的绩效点信息
+	PerformancePoints PerformancePoints `json:"PerformancePoints" gorm:"foreignKey:T_performance_points_id"`
+}
+
+func (e *PerfPoint) TableName() string {
+	return "perf_point"
+}
+
+type VerifyTask struct {
+	Id                int    `orm:"column(ID);size(11);auto;pk"`
+	T_Distributor_id  string `orm:"size(256);null"`     // 分销商id
+	T_task_id         string `orm:"size(256);null"`     // 任务ID
+	T_uuid            string `orm:"size(256);null"`     // 用户 UUID
+	T_name            string `orm:"size(256);null"`     // 标题
+	T_scheme          string `orm:"size(256);null"`     // 实施方案 负责人UUID
+	T_collection      string `orm:"size(256);null"`     // 数据采集 负责人UUID
+	T_reporting       string `orm:"size(256);null"`     // 报告编写 负责人UUID
+	T_delivery        string `orm:"size(256);null"`     // 交付审核 负责人UUID
+	T_scheme_state    int    `orm:"size(2);default(0)"` // 实施方案 状态 0 未完成 1 已完成(客户通过) 2已退回(客户) 3已通过(负责人) 4已退回(负责人) 5已提交
+	T_reporting_state int    `orm:"size(2);default(0)"` // 报告编写 状态 0 未完成 1 已完成(客户通过) 2已退回(客户) 3已通过(负责人) 4已退回(负责人) 5已提交
+	T_State           int    `orm:"size(2);default(1)"` // 0 删除   1 正常
+
+	T_device_type string `orm:"size(256);null"` // 设备类型
+
+	T_verify_type         string `orm:"size(256);null"` // 验证类型
+	T_reporting_pass_time string `orm:"size(256);null"` // 验证报告负责人通过时间
+
+	T_device_quantity int `orm:"size(2);default(0)"` // 终端数量
+
+	CreateTime time.Time `orm:"column(create_time);type(timestamp);null;auto_now_add"` //auto_now_add 第一次保存时才设置时间
+	UpdateTime time.Time `orm:"column(update_time);type(timestamp);null;auto_now"`     //auto_now 每次 model 保存时都会对时间自动更新
+}
+
+func (e *Perf) TableName() string {
+	return "perf"
+}

+ 35 - 0
models/Performance/PerformancePoints.go

@@ -0,0 +1,35 @@
+package Performance
+
+import (
+	db "ERP_salary/initialize"
+)
+
+// 绩效点
+type PerformancePoints struct {
+	Id                   int     `json:"Id" gorm:"primaryKey;autoIncrement;comment:主键编码"`
+	T_name               string  `json:"T_name" gorm:"size:128"`             //  绩效点名称
+	T_points_numerator   int     `json:"T_points_numerator" gorm:"size:4"`   //  绩效点分子
+	T_points_denominator int     `json:"T_points_denominator" gorm:"size:4"` //  绩效点分母
+	T_remark             string  `json:"T_remark" gorm:"size:text"`
+	T_State              int     `json:"T_State" gorm:"column:t__state;size(2);default(1)"`                  // 0 删除(伪删除)   1 正常
+	CreateTime           db.Time `json:"CreateTime" gorm:"column:create_time;autoCreateTime;comment:创建时间"`   // 创建时间
+	UpdateTime           db.Time `json:"UpdateTime" gorm:"column:update_time;autoUpdateTime;comment:最后更新时间"` // 最后更新时间
+}
+
+func (e *PerformancePoints) TableName() string {
+	return "performance_points"
+}
+
+type PerformancePointsOmit struct {
+	Id                   int    `json:"Id"`                   // 主键编码
+	T_name               string `json:"T_name"`               //  绩效点名称
+	T_points_numerator   int    `json:"T_points_numerator"`   //  绩效点分子
+	T_points_denominator int    `json:"T_points_denominator"` //  绩效点分母
+	T_remark             string `json:"T_remark"`
+	T_State              int    `json:"T_State"` // 0 删除(伪删除)   1 正常
+
+}
+
+func (e *PerformancePointsOmit) TableName() string {
+	return "performance_points"
+}

+ 60 - 0
models/Performance/PerformanceTarget.go

@@ -0,0 +1,60 @@
+package Performance
+
+import (
+	"ERP_salary/logs"
+	"github.com/beego/beego/v2/adapter/orm"
+	"gogs.baozhida.cn/zoie/ERP_libs/lib"
+	"sync"
+	"time"
+)
+
+// 绩效点
+type PerformanceTarget struct {
+	Id              int       `json:"Id" gorm:"primaryKey;autoIncrement;comment:主键编码"`
+	T_name          string    `json:"T_name" gorm:"size:128"`                               // 绩效点名称
+	T_operator      string    `json:"T_operator" gorm:"size:128"`                           // 运算符 <,≥
+	T_assess_points int       `json:"T_assess_points" gorm:"size:4"`                        // 月考试绩效点数量
+	T_base          float64   `json:"T_base" gorm:"type:decimal(10,1)"`                     // 无责底薪
+	T_post          float64   `json:"T_post" gorm:"type:decimal(10,1)"`                     // 补助
+	T_perf          float64   `json:"T_perf" gorm:"type:decimal(10,1)"`                     // 绩效金额
+	T_State         int       `json:"T_State" gorm:"column:t__state;size(2);default(1)"`    // 0删除  1 正常
+	CreateTime      time.Time `orm:"column(create_time);type(timestamp);null;auto_now_add"` // auto_now_add 第一次保存时才设置时间
+	UpdateTime      time.Time `orm:"column(update_time);type(timestamp);null;auto_now"`     // auto_now 每次 model 保存时都会对时间自动更新
+}
+
+func (e *PerformanceTarget) TableName() string {
+	return "performance_target"
+}
+
+var PerformanceTarget_list *sync.Map
+
+func init() {
+	//注册模型
+	PerformanceTarget_list = new(sync.Map)
+}
+
+// 获取全部
+func Read_PerformanceTarget_All_Map() {
+	logs.Println("=========== 初始化考核指标 =========")
+
+	o := orm.NewOrm()
+	var r []PerformanceTarget
+	qs := o.QueryTable(new(PerformanceTarget))
+	_, err := qs.Filter("T_State", 1).All(&r)
+	if err != nil {
+		logs.Error(lib.FuncName(), err)
+	}
+
+	for _, v := range r {
+		PerformanceTarget_list.Store(v.Id, v)
+	}
+
+}
+func Read_PerformanceTarget_Get(Id int) PerformanceTarget {
+	v, ok := PerformanceTarget_list.Load(Id)
+	if ok {
+		return v.(PerformanceTarget)
+	} else {
+		return PerformanceTarget{}
+	}
+}

+ 26 - 0
models/init.go

@@ -0,0 +1,26 @@
+package models
+
+import (
+	db "ERP_salary/initialize"
+	"ERP_salary/models/Performance"
+	_ "github.com/go-sql-driver/mysql"
+	"log"
+)
+
+func init() {
+	AutoMigrateDB()
+}
+
+func AutoMigrateDB() {
+	//自动迁移模式
+	err := db.DB.Set("gorm:table_options", "charset=utf8mb4").
+		AutoMigrate(
+			&Performance.Perf{},
+			&Performance.PerfPoint{},
+			&Performance.PerformancePoints{},
+			&Performance.PerformanceTarget{},
+		)
+	if err != nil {
+		log.Fatalf("migrate db fail: %v", err)
+	}
+}

+ 39 - 0
routers/performance.go

@@ -0,0 +1,39 @@
+package routers
+
+import (
+	"ERP_salary/controllers"
+
+	beego "github.com/beego/beego/v2/server/web"
+)
+
+func init() {
+
+	performancePoints := beego.NewNamespace("/PerformancePoints",
+		beego.NSRouter("/List", &controllers.PerformanceController{}, "*:Points_List"), // 绩效点列表
+		beego.NSRouter("/Add", &controllers.PerformanceController{}, "*:Points_Add"),   // 获取绩效点
+		beego.NSRouter("/Edit", &controllers.PerformanceController{}, "*:Points_Edit"), // 添加绩效点
+		beego.NSRouter("/Del", &controllers.PerformanceController{}, "*:Points_Del"),   // 删除绩效点
+	)
+
+	performanceTarget := beego.NewNamespace("/PerformanceTarget",
+		beego.NSRouter("/List", &controllers.PerformanceController{}, "*:Target_List"), // 绩效指标列表
+		beego.NSRouter("/Add", &controllers.PerformanceController{}, "*:Target_Add"),   // 获取绩效指标
+		beego.NSRouter("/Edit", &controllers.PerformanceController{}, "*:Target_Edit"), // 添加绩效指标
+		beego.NSRouter("/Del", &controllers.PerformanceController{}, "*:Target_Del"),   // 删除绩效指标
+	)
+
+	performance := beego.NewNamespace("/Performance",
+		beego.NSRouter("/List", &controllers.PerformanceController{}, "*:List"),                   // 绩效列表
+		beego.NSRouter("/Excel", &controllers.PerformanceController{}, "*:Excel"),                 // 绩效列表
+		beego.NSRouter("/User_List", &controllers.PerformanceController{}, "*:User_List"),         // 绩效列表
+		beego.NSRouter("/User_Excel", &controllers.PerformanceController{}, "*:User_Excel"),       // 绩效列表
+		beego.NSRouter("/Add", &controllers.PerformanceController{}, "*:Add"),                     // 获取绩效
+		beego.NSRouter("/Edit", &controllers.PerformanceController{}, "*:Edit"),                   // 添加绩效
+		beego.NSRouter("/Edit_Audit", &controllers.PerformanceController{}, "*:Edit_Audit"),       // 修改绩效状态
+		beego.NSRouter("/Del", &controllers.PerformanceController{}, "*:Del"),                     // 删除绩效
+		beego.NSRouter("/Detail", &controllers.PerformanceController{}, "*:GetPerformanceDetail"), // 绩效详情
+		beego.NSRouter("/Submit_User", &controllers.PerformanceController{}, "*:Submit_User"),     // 用户搜索列表
+	)
+
+	beego.AddNamespace(performancePoints, performanceTarget, performance)
+}

+ 371 - 0
services/Performance.go

@@ -0,0 +1,371 @@
+package services
+
+import (
+	"ERP_salary/dto"
+	db "ERP_salary/initialize"
+	"ERP_salary/logs"
+	"ERP_salary/models/Account"
+	models "ERP_salary/models/Performance"
+	"errors"
+
+	"gogs.baozhida.cn/zoie/ERP_libs/lib"
+)
+
+type Performance struct {
+}
+
+func (e *Performance) GetPage(c *dto.PerformanceUserPageReq) (list []models.Perf, cnt int64) {
+
+	var err error
+
+	err = db.DB.Model(&models.Perf{}).
+		Scopes(
+			dto.MakeCondition(c.GetNeedSearch()),
+			dto.Paginate(c.GetPageSize(), c.GetPageIndex()),
+			dto.WithNormalState(),
+		).
+		Preload("PointList", "t__state = ?", 1).
+		Preload("PointList.PerformancePoints").
+		Preload("Target", "t__state = ?", 1).
+		Find(&list).Limit(-1).Offset(-1).
+		Count(&cnt).Error
+
+	if err != nil {
+		logs.Error("db error: %s ", err)
+		return
+	}
+	for i := 0; i < len(list); i++ {
+		list[i].T_submit_name = Account.Read_User_T_name_Get(list[i].T_submit)
+	}
+
+	return
+}
+
+func (e *Performance) GetManagerPage(c *dto.PerformancePageReq) (list []models.Perf, cnt int64) {
+
+	var err error
+
+	err = db.DB.Model(&models.Perf{}).
+		Scopes(
+			dto.MakeCondition(c.GetNeedSearch()),
+			dto.Paginate(c.GetPageSize(), c.GetPageIndex()),
+			dto.WithNormalState(),
+		).
+		Where("t_audit in (?)", []int{2, 3}).
+		Preload("PointList", "t__state = ?", 1).
+		Preload("PointList.PerformancePoints").
+		Preload("Target", "t__state = ?", 1).
+		Find(&list).Limit(-1).Offset(-1).
+		Count(&cnt).Error
+
+	if err != nil {
+		logs.Error("db error: %s ", err)
+		return
+	}
+	for i := 0; i < len(list); i++ {
+		list[i].T_submit_name = Account.Read_User_T_name_Get(list[i].T_submit)
+	}
+
+	return
+}
+func (e *Performance) GetSubmitUserPage() (list []string) {
+
+	var err error
+
+	err = db.DB.Model(&models.Perf{}).
+		Select("Distinct t_submit").
+		Scopes(
+			dto.WithNormalState(),
+		).
+		Where("t_audit in (?)", []int{2, 3}).
+		Find(&list).Error
+
+	if err != nil {
+		logs.Error("db error: %s ", err)
+		return
+	}
+	return
+}
+
+// GetPerfPointsByPerfId 根据绩效ID获取关联的绩效点列表
+func (e *Performance) GetPerfPointsByPerfId(perfId int) (pointList []models.PerfPoint, err error) {
+	err = db.DB.Model(&models.PerfPoint{}).
+		Scopes(dto.WithNormalState()).
+		Where("t_performance_id = ?", perfId).
+		Find(&pointList).Error
+
+	if err != nil {
+		logs.Error("获取绩效点列表失败: %s", err)
+		return
+	}
+
+	// 获取绩效点详细信息
+	for i := range pointList {
+		var performancePoints models.PerformancePoints
+		err = db.DB.Model(&models.PerformancePoints{}).
+			Scopes(dto.WithNormalState()).
+			Where("id = ?", pointList[i].T_performance_points_id).
+			First(&performancePoints).Error
+
+		if err == nil {
+			pointList[i].PerformancePoints = performancePoints
+		}
+	}
+
+	return
+}
+
+// InsertFromStats 从统计结果插入绩效记录
+func (e *Performance) InsertFromStats(perf *models.Perf, pointList []models.PerfPoint) (id int, err error) {
+	tx := db.DB.Begin()
+	defer func() {
+		if err != nil {
+			tx.Rollback()
+		} else {
+			tx.Commit()
+		}
+	}()
+
+	// 创建绩效主记录
+	err = tx.Create(perf).Error
+	if err != nil {
+		logs.Error("创建绩效主记录失败: %s", err)
+		return
+	}
+
+	// 设置绩效点的关联ID
+	for i := range pointList {
+		pointList[i].T_performance_id = perf.Id
+		pointList[i].T_State = 1
+	}
+
+	// 批量创建绩效点记录
+	if len(pointList) > 0 {
+		err = tx.Create(&pointList).Error
+		if err != nil {
+			logs.Error("创建绩效点记录失败: %s", err)
+			return
+		}
+	}
+
+	id = perf.Id
+	return
+}
+
+// Get 获取单个绩效记录
+func (e *Performance) Get(c dto.PerformanceGetReq) (r models.Perf, err error) {
+	err = db.DB.Scopes(dto.WithNormalState()).Preload("Target", "t__state = ?", 1).First(&r, c.GetId()).Error
+	if err != nil {
+		logs.Error("db error: %s", err)
+		return
+	}
+	return
+}
+
+// 添加
+func (e *Performance) Insert(c *dto.PerformanceInsertReq) (id int, err error) {
+	var data models.Perf
+
+	// 开启事务
+	tx := db.DB.Begin()
+	defer func() {
+		if err != nil {
+			tx.Rollback()
+		} else {
+			tx.Commit()
+		}
+	}()
+
+	// 生成主表数据
+	c.Generate(&data)
+
+	// 唯一性校验:同一月份+同一用户不可重复
+	{
+		var cnt int64
+		query := db.DB.Model(&models.Perf{}).
+			Scopes(dto.WithNormalState()).
+			Where("t_date = ? AND t_submit = ?", data.T_date, data.T_submit)
+		err = query.Count(&cnt).Error
+		if err != nil {
+			logs.Error("db error: %s", err)
+			return
+		}
+		if cnt > 0 {
+			return 0, errors.New("当月该用户已添加过该绩效")
+		}
+	}
+
+	// 读取员工考核指标,设置考核绩效点
+	user := Account.Read_User_Get(data.T_submit)
+	if user.T_verify_perf_target == 0 {
+		return 0, errors.New("用户未配置工资级别,请联系管理员配置!")
+	}
+	targetSvc := PerformanceTarget{}
+	target, _ := targetSvc.Get(user.T_verify_perf_target)
+	if target.Id > 0 {
+		data.T_assess_points = target.T_assess_points
+		data.T_perf = target.T_perf
+		data.T_performance_target_id = target.Id
+	}
+
+	// 根据绩效点明细计算工作量
+	if len(c.PointList) > 0 {
+		totalWorkload := 0.0
+		for _, p := range c.PointList {
+			if p.T_points_denominator != 0 {
+				totalWorkload += float64(p.T_points_numerator) / float64(p.T_points_denominator) * float64(p.T_quantity)
+			}
+		}
+		data.T_workload = totalWorkload
+	}
+
+	// 创建绩效主记录
+	err = tx.Create(&data).Error
+	if err != nil {
+		logs.Error("db error: %s", err)
+		return
+	}
+
+	// 批量创建绩效点明细
+	if len(c.PointList) > 0 {
+		for i := range c.PointList {
+			c.PointList[i].T_performance_id = data.Id
+			c.PointList[i].T_State = 1
+		}
+		err = tx.Create(&c.PointList).Error
+		if err != nil {
+			logs.Error("db error: %s", err)
+			return
+		}
+	}
+
+	id = data.Id
+	return
+
+}
+
+// 修改
+func (e *Performance) Update(c *dto.PerformanceUpdateReq) error {
+	var performance = models.Perf{}
+	err := db.DB.Scopes(dto.WithNormalState()).First(&performance, c.GetId()).Error
+	if err != nil {
+		logs.Error("db error: %s", err)
+		return dto.GetFailedErr
+	}
+	// 已打款 不可修改
+	if performance.T_audit == 3 {
+		logs.Error("db error: %s", err)
+		return errors.New("已打款,不可修改")
+	}
+
+	tx := db.DB.Begin()
+	defer func() {
+		if err != nil {
+			tx.Rollback()
+		} else {
+			tx.Commit()
+		}
+	}()
+	// 先根据请求生成需要更新的主记录字段
+	c.Generate(&performance)
+
+	// 如 T_submit 可能变更,则刷新考核指标
+	user := Account.Read_User_Get(performance.T_submit)
+	targetSvc := PerformanceTarget{}
+	target, _ := targetSvc.Get(user.T_verify_perf_target)
+	if target.Id > 0 {
+		performance.T_assess_points = target.T_assess_points
+		performance.T_perf = target.T_perf
+		performance.T_performance_target_id = target.Id
+	}
+
+	// 重新计算工作量(基于新绩效点列表)
+	if len(c.PointList) > 0 {
+		totalWorkload := 0.0
+		for _, p := range c.PointList {
+			if p.T_points_denominator != 0 {
+				totalWorkload += float64(p.T_points_numerator) / float64(p.T_points_denominator) * float64(p.T_quantity)
+			}
+		}
+		performance.T_workload = totalWorkload
+	}
+
+	// 保存主记录
+	err = tx.Save(&performance).Error
+	if err != nil {
+		logs.Error("db error: %s", err)
+		return err
+	}
+
+	// 软删除旧的绩效点
+	if err = tx.Model(&models.PerfPoint{}).
+		Where("t_performance_id = ? AND t__state = ?", performance.Id, 1).
+		Update("t__state", 0).Error; err != nil {
+		logs.Error("db error: %s", err)
+		return err
+	}
+
+	// 插入新的绩效点列表
+	if len(c.PointList) > 0 {
+		for i := range c.PointList {
+			c.PointList[i].T_performance_id = performance.Id
+			c.PointList[i].T_State = 1
+		}
+		if err = tx.Create(&c.PointList).Error; err != nil {
+			logs.Error("db error: %s", err)
+			return err
+		}
+	}
+
+	return nil
+
+}
+func (e *Performance) UpdateAudit(c *dto.PerformanceUpdateAuditReq) error {
+	var performance = models.Perf{}
+	err := db.DB.Scopes(dto.WithNormalState()).First(&performance, c.GetId()).Error
+	if err != nil {
+		logs.Error("db error: %s", err)
+		return dto.GetFailedErr
+	}
+	// 已打款 不可修改
+	if performance.T_audit == 1 && c.T_audit == 3 {
+		logs.Error("db error: %s", err)
+		return errors.New("未提交,不可修改")
+	}
+
+	tx := db.DB.Begin()
+	defer func() {
+		if err != nil {
+			tx.Rollback()
+		} else {
+			tx.Commit()
+		}
+	}()
+	performance.T_audit = c.T_audit
+	// 保存主记录
+	err = tx.Save(&performance).Error
+	if err != nil {
+		logs.Error("db error: %s", err)
+		return err
+	}
+
+	return nil
+
+}
+
+// 删除
+func (e *Performance) Delete(c *dto.PerformanceDeleteReq) error {
+	var performance = models.Perf{}
+	err := db.DB.Scopes(dto.WithNormalState()).First(&performance, c.GetId()).Error
+	if err != nil {
+		logs.Error("db error: %s", err)
+		return dto.GetFailedErr
+	}
+	performance.T_State = 0
+	err = db.DB.Save(&performance).Error
+	if err != nil {
+		logs.Error(lib.FuncName(), err)
+		return dto.DeleteFailedErr
+	}
+	return nil
+}

+ 116 - 0
services/PerformancePoints.go

@@ -0,0 +1,116 @@
+package services
+
+import (
+	"ERP_salary/dto"
+	db "ERP_salary/initialize"
+	"ERP_salary/logs"
+	models "ERP_salary/models/Performance"
+	"errors"
+	"gogs.baozhida.cn/zoie/ERP_libs/lib"
+	"gorm.io/gorm"
+)
+
+type PerformancePoints struct {
+}
+
+func (e *PerformancePoints) GetPage(c *dto.PerformancePointsPageReq) (list []models.PerformancePoints, cnt int64) {
+
+	var err error
+
+	err = db.DB.Model(&models.PerformancePoints{}).
+		Scopes(
+			dto.MakeCondition(c.GetNeedSearch()),
+			dto.Paginate(c.GetPageSize(), c.GetPageIndex()),
+			dto.WithNormalState(),
+		).
+		Find(&list).Limit(-1).Offset(-1).
+		Count(&cnt).Error
+
+	if err != nil {
+		logs.Error("db error: %s ", err)
+		return
+	}
+	return
+}
+
+func (e *PerformancePoints) Get(Id int) (r models.PerformancePoints, err error) {
+	err = db.DB.Scopes(dto.WithNormalState()).First(&r, Id).Error
+	if err != nil {
+		logs.Error("db error: %s", err)
+		return
+	}
+	return
+}
+
+// 添加
+func (e *PerformancePoints) Insert(c *dto.PerformancePointsInsertReq) (id int, err error) {
+	var data models.PerformancePoints
+	err = db.DB.Scopes(dto.WithNormalState()).Where("t_name = ?", c.T_name).First(&data).Error
+	if err != nil {
+		// 没有则创建
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			c.Generate(&data)
+			err = db.DB.Create(&data).Error
+			if err != nil {
+				logs.Error("db error: %s", err)
+				return
+			}
+			id = data.Id
+			return
+		}
+		logs.Error("db error: %s", err)
+		return
+	}
+	id = data.Id
+	return
+}
+
+// 修改
+func (e *PerformancePoints) Update(c *dto.PerformancePointsUpdateReq) error {
+	var performancePoints = models.PerformancePoints{}
+	err := db.DB.Scopes(dto.WithNormalState()).First(&performancePoints, c.GetId()).Error
+	if err != nil {
+		logs.Error("db error: %s", err)
+		return dto.GetNotFoundErr
+	}
+	c.Generate(&performancePoints)
+	err = db.DB.Save(&performancePoints).Error
+	if err != nil {
+		logs.Error("db error: %s", err)
+		return dto.UpdateFailedErr
+	}
+	return nil
+
+}
+
+// 删除
+func (e *PerformancePoints) Delete(c *dto.PerformancePointsDeleteReq) error {
+	var performancePoints = models.PerformancePoints{}
+	err := db.DB.Scopes(dto.WithNormalState()).First(&performancePoints, c.GetId()).Error
+	if err != nil {
+		logs.Error("db error: %s", err)
+		return dto.GetNotFoundErr
+	}
+
+	var ServiceItemCount int64
+	err = db.DB.Model(&models.PerformancePoints{}).
+		Scopes(dto.WithNormalState()).
+		Where("t_service_type_id = ?", performancePoints.Id).
+		Count(&ServiceItemCount).Error
+	if err != nil {
+		logs.Error("db error: %s", err)
+		return dto.GetFailedErr
+	}
+
+	if ServiceItemCount > 0 {
+		return errors.New("存在已关联的服务内容,无法删除!")
+	}
+
+	performancePoints.T_State = 0
+	err = db.DB.Save(&performancePoints).Error
+	if err != nil {
+		logs.Error(lib.FuncName(), err)
+		return dto.DeleteFailedErr
+	}
+	return nil
+}

+ 116 - 0
services/PerformanceTarget.go

@@ -0,0 +1,116 @@
+package services
+
+import (
+	"ERP_salary/dto"
+	db "ERP_salary/initialize"
+	"ERP_salary/logs"
+	models "ERP_salary/models/Performance"
+	"errors"
+	"gogs.baozhida.cn/zoie/ERP_libs/lib"
+	"gorm.io/gorm"
+)
+
+type PerformanceTarget struct {
+}
+
+func (e *PerformanceTarget) GetPage(c *dto.PerformanceTargetPageReq) (list []models.PerformanceTarget, cnt int64) {
+
+	var err error
+
+	err = db.DB.Model(&models.PerformanceTarget{}).
+		Scopes(
+			dto.MakeCondition(c.GetNeedSearch()),
+			dto.Paginate(c.GetPageSize(), c.GetPageIndex()),
+			dto.WithNormalState(),
+		).
+		Find(&list).Limit(-1).Offset(-1).
+		Count(&cnt).Error
+
+	if err != nil {
+		logs.Error("db error: %s ", err)
+		return
+	}
+	return
+}
+
+func (e *PerformanceTarget) Get(Id int) (r models.PerformanceTarget, err error) {
+	err = db.DB.Scopes(dto.WithNormalState()).First(&r, Id).Error
+	if err != nil {
+		logs.Error("db error: %s", err)
+		return
+	}
+	return
+}
+
+// 添加
+func (e *PerformanceTarget) Insert(c *dto.PerformanceTargetInsertReq) (id int, err error) {
+	var data models.PerformanceTarget
+	err = db.DB.Scopes(dto.WithNormalState()).Where("t_name = ?", c.T_name).First(&data).Error
+	if err != nil {
+		// 没有则创建
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			c.Generate(&data)
+			err = db.DB.Create(&data).Error
+			if err != nil {
+				logs.Error("db error: %s", err)
+				return
+			}
+			id = data.Id
+			return
+		}
+		logs.Error("db error: %s", err)
+		return
+	}
+	id = data.Id
+	return
+}
+
+// 修改
+func (e *PerformanceTarget) Update(c *dto.PerformanceTargetUpdateReq) error {
+	var PerformanceTarget = models.PerformanceTarget{}
+	err := db.DB.Scopes(dto.WithNormalState()).First(&PerformanceTarget, c.GetId()).Error
+	if err != nil {
+		logs.Error("db error: %s", err)
+		return dto.GetNotFoundErr
+	}
+	c.Generate(&PerformanceTarget)
+	err = db.DB.Save(&PerformanceTarget).Error
+	if err != nil {
+		logs.Error("db error: %s", err)
+		return dto.UpdateFailedErr
+	}
+	return nil
+
+}
+
+// 删除
+func (e *PerformanceTarget) Delete(c *dto.PerformanceTargetDeleteReq) error {
+	var PerformanceTarget = models.PerformanceTarget{}
+	err := db.DB.Scopes(dto.WithNormalState()).First(&PerformanceTarget, c.GetId()).Error
+	if err != nil {
+		logs.Error("db error: %s", err)
+		return dto.GetNotFoundErr
+	}
+
+	var ServiceItemCount int64
+	err = db.DB.Model(&models.PerformanceTarget{}).
+		Scopes(dto.WithNormalState()).
+		Where("t_service_type_id = ?", PerformanceTarget.Id).
+		Count(&ServiceItemCount).Error
+	if err != nil {
+		logs.Error("db error: %s", err)
+		return dto.GetFailedErr
+	}
+
+	if ServiceItemCount > 0 {
+		return errors.New("存在已关联的服务内容,无法删除!")
+	}
+
+	PerformanceTarget.T_State = 0
+	err = db.DB.Save(&PerformanceTarget).Error
+	if err != nil {
+		logs.Error(lib.FuncName(), err)
+		return dto.DeleteFailedErr
+	}
+	return nil
+}