Quellcode durchsuchen

Merge branch 'debug' of http://8.136.199.33:3000/eta_server/eta_api into debug

xiziwen vor 5 Monaten
Ursprung
Commit
3f7a44d327

+ 1 - 1
controllers/data_manage/chart_info.go

@@ -1597,7 +1597,7 @@ func (this *ChartInfoController) ChartInfoDetailV2() {
 	var dateMax time.Time
 	if dateType == utils.DateTypeNYears {
 		for _, v := range mappingList {
-			if v.LatestDate != "" {
+			if v.LatestDate != "" && v.LatestDate != "0000-00-00" {
 				lastDateT, tErr := time.Parse(utils.FormatDate, v.LatestDate)
 				if tErr != nil {
 					br.Msg = "获取失败"

+ 4 - 0
controllers/data_manage/multiple_graph_config.go

@@ -513,6 +513,10 @@ func (this *ChartInfoController) MultipleGraphPreviewCurve() {
 			startDate = "2020-01-01"
 		case 11:
 			startDate = "2022-01-01"
+		case 12:
+			startDate = "2023-01-01"
+		case 13:
+			startDate = "2024-01-01"
 		}
 
 		edbInfoType := 0

+ 46 - 20
controllers/data_manage/predict_edb_info.go

@@ -427,31 +427,56 @@ func (this *PredictEdbInfoController) Add() {
 	ruleList := make([]request.RuleConfig, 0)
 	{
 		ruleMap := make(map[string]request.RuleConfig)
+		ruleEndNumSortMap := make(map[int]request.RuleConfig)
 		dateIntList := make([]int64, 0)
+		endNumList := make([]int, 0)
 		for _, v := range req.RuleList {
-			confEndDate, err := time.Parse(utils.FormatDate, v.EndDate)
-			if err != nil {
-				br.Msg = "配置项中时间异常,请重新选择"
-				br.ErrMsg = "配置项中时间异常,请重新选择,err:" + err.Error()
-				br.IsSendEmail = false
-				return
+			if req.EndDateType == 0 {
+				confEndDate, err := time.Parse(utils.FormatDate, v.EndDate)
+				if err != nil {
+					br.Msg = "配置项中时间异常,请重新选择"
+					br.ErrMsg = "配置项中时间异常,请重新选择,err:" + err.Error()
+					br.IsSendEmail = false
+					return
+				}
+				dateIntList = append(dateIntList, confEndDate.Unix())
+				ruleMap[v.EndDate] = v
+			} else {
+				if _, ok := ruleEndNumSortMap[v.EndNum]; ok {
+					br.Msg = "所选期数不能和其他时间段相同"
+					return
+				}
+				endNumList = append(endNumList, v.EndNum)
+				ruleEndNumSortMap[v.EndNum] = v
 			}
-			dateIntList = append(dateIntList, confEndDate.Unix())
-			ruleMap[v.EndDate] = v
 		}
-		sort.Slice(dateIntList, func(i, j int) bool {
-			return dateIntList[i] < dateIntList[j]
-		})
-		for _, dateInt := range dateIntList {
-			currDateTime := time.Unix(dateInt, 0)
-			item, ok := ruleMap[currDateTime.Format(utils.FormatDate)]
-			if !ok {
-				br.Msg = "配置项中时间异常,请重新选择"
-				br.ErrMsg = "配置项中时间异常,请重新选择"
-				br.IsSendEmail = false
-				return
+		if req.EndDateType == 0 {
+			sort.Slice(dateIntList, func(i, j int) bool {
+				return dateIntList[i] < dateIntList[j]
+			})
+			for _, dateInt := range dateIntList {
+				currDateTime := time.Unix(dateInt, 0)
+				item, ok := ruleMap[currDateTime.Format(utils.FormatDate)]
+				if !ok {
+					br.Msg = "配置项中时间异常,请重新选择"
+					br.ErrMsg = "配置项中时间异常,请重新选择"
+					br.IsSendEmail = false
+					return
+				}
+				ruleList = append(ruleList, item)
+			}
+		} else {
+			sort.Ints(endNumList)
+			for _, endNum := range endNumList {
+				item, ok := ruleEndNumSortMap[endNum]
+				if !ok {
+					br.Msg = "配置项中时间异常,请重新选择"
+					br.ErrMsg = "配置项中时间异常,请重新选择"
+					br.IsSendEmail = false
+					return
+				}
+				ruleList = append(ruleList, item)
 			}
-			ruleList = append(ruleList, item)
 		}
 	}
 	req.RuleList = ruleList
@@ -927,6 +952,7 @@ func (this *PredictEdbInfoController) Detail() {
 				ModifyTime:       v.ModifyTime,
 				CreateTime:       v.CreateTime,
 				CalculateList:    tmpPredictEdbConfCalculateMappingDetail,
+				EndNum:           v.EndNum,
 			}
 			predictEdbConfList = append(predictEdbConfList, tmp)
 		}

+ 9 - 8
controllers/material/material.go

@@ -925,6 +925,7 @@ func (this *MaterialController) MyChartSaveAsMaterial() {
 		return
 	}
 	existList := make([]*material.Material, 0)
+	existNameMap := make(map[string]struct{})
 	switch this.Lang {
 	case utils.LANG_EN:
 		existList, err = material.GetMaterialByNameEns(materialNames)
@@ -934,12 +935,9 @@ func (this *MaterialController) MyChartSaveAsMaterial() {
 			return
 		}
 		if len(existList) > 0 {
-			msg := "图片名称:"
 			for _, v := range existList {
-				msg += v.MaterialNameEn + " "
+				existNameMap[v.MaterialNameEn] = struct{}{}
 			}
-			br.Msg = fmt.Sprintf("%s 已存在", msg)
-			return
 		}
 	default:
 		// 判断文件名是否已存在
@@ -950,14 +948,17 @@ func (this *MaterialController) MyChartSaveAsMaterial() {
 			return
 		}
 		if len(existList) > 0 {
-			msg := "图片名称:"
 			for _, v := range existList {
-				msg += v.MaterialName + " "
+				existNameMap[v.MaterialName] = struct{}{}
 			}
-			br.Msg = fmt.Sprintf("%s 已存在", msg)
-			return
 		}
 	}
+	if len(existList) > 0 {
+		br.Msg = "图片名称已存在"
+		respData := materialService.GetMyChartExistMaterialNameListMsg(existNameMap, req.MaterialList)
+		br.Data = respData
+		return
+	}
 	if len(req.MaterialList) > 30 {
 		br.Msg = "最多支持选择30个图表"
 		return

+ 194 - 119
controllers/report_chapter.go

@@ -4,6 +4,7 @@ import (
 	"encoding/json"
 	"eta/eta_api/models"
 	"eta/eta_api/models/report"
+	"eta/eta_api/models/system"
 	"eta/eta_api/services"
 	"eta/eta_api/services/data"
 	"eta/eta_api/utils"
@@ -332,38 +333,12 @@ func (this *ReportController) EditDayWeekChapter() {
 	}
 
 	// 操作权限校验
-	{
-		// 如果不是创建人,那么就要去查看是否授权
-		if reportInfo.AdminId != sysUser.AdminId {
-			// 授权用户权限校验
-			chapterGrantObj := report.ReportChapterGrant{}
-			_, tmpErr := chapterGrantObj.GetGrantByIdAndAdmin(reportChapterInfo.ReportChapterId, sysUser.AdminId)
-			if tmpErr != nil {
-				if tmpErr.Error() == utils.ErrNoRow() {
-					br.Msg = "没有权限"
-					br.ErrMsg = "没有权限"
-					br.IsSendEmail = false
-					return
-				}
-				br.Msg = "获取章节id授权用户失败"
-				br.ErrMsg = "获取章节id授权用户失败, Err: " + tmpErr.Error()
-				return
-			}
-		}
-
-		// 标记更新中
-		{
-			markStatus, err := services.UpdateReportEditMark(reportChapterInfo.ReportId, reportChapterInfo.ReportChapterId, sysUser.AdminId, 1, sysUser.RealName, this.Lang)
-			if err != nil {
-				br.Msg = err.Error()
-				return
-			}
-			if markStatus.Status == 1 {
-				br.Msg = markStatus.Msg
-				br.IsSendEmail = false
-				return
-			}
-		}
+	hasAuth, msg, errMsg, isSendEmail := checkOpPermission(sysUser, reportInfo, reportChapterInfo, true, this.Lang)
+	if !hasAuth {
+		br.Msg = msg
+		br.ErrMsg = errMsg
+		br.IsSendEmail = isSendEmail
+		return
 	}
 
 	if reportInfo.State == 2 {
@@ -372,6 +347,13 @@ func (this *ReportController) EditDayWeekChapter() {
 		br.IsSendEmail = false
 		return
 	}
+	if reportChapterInfo.PublishState == 2 {
+		br.Msg = "该报告章节已发布,不允许编辑"
+		br.ErrMsg = "该报告章节已发布,不允许编辑"
+		br.IsSendEmail = false
+		return
+	}
+
 	// 报告的最后编辑人
 	reportInfo.LastModifyAdminId = sysUser.AdminId
 	reportInfo.LastModifyAdminName = sysUser.RealName
@@ -523,38 +505,12 @@ func (this *ReportController) DelChapter() {
 	}
 
 	// 操作权限校验
-	{
-		// 如果不是创建人,那么就要去查看是否授权
-		if reportInfo.AdminId != sysUser.AdminId {
-			// 授权用户权限校验
-			chapterGrantObj := report.ReportChapterGrant{}
-			_, tmpErr := chapterGrantObj.GetGrantByIdAndAdmin(reportChapterInfo.ReportChapterId, sysUser.AdminId)
-			if tmpErr != nil {
-				if tmpErr.Error() == utils.ErrNoRow() {
-					br.Msg = "没有权限"
-					br.ErrMsg = "没有权限"
-					br.IsSendEmail = false
-					return
-				}
-				br.Msg = "获取章节id授权用户失败"
-				br.ErrMsg = "获取章节id授权用户失败, Err: " + tmpErr.Error()
-				return
-			}
-		}
-
-		// 标记更新中
-		{
-			markStatus, err := services.UpdateReportEditMark(reportChapterInfo.ReportId, reportChapterInfo.ReportChapterId, sysUser.AdminId, 1, sysUser.RealName, this.Lang)
-			if err != nil {
-				br.Msg = err.Error()
-				return
-			}
-			if markStatus.Status == 1 {
-				br.Msg = markStatus.Msg
-				br.IsSendEmail = false
-				return
-			}
-		}
+	hasAuth, msg, errMsg, isSendEmail := checkOpPermission(sysUser, reportInfo, reportChapterInfo, true, this.Lang)
+	if !hasAuth {
+		br.Msg = msg
+		br.ErrMsg = errMsg
+		br.IsSendEmail = isSendEmail
+		return
 	}
 
 	if reportInfo.State == 2 {
@@ -565,7 +521,7 @@ func (this *ReportController) DelChapter() {
 	}
 
 	// 删除章节
-	err, errMsg := services.DelChapter(reportInfo, reportChapterInfo, sysUser)
+	err, errMsg = services.DelChapter(reportInfo, reportChapterInfo, sysUser)
 	if err != nil {
 		br.Msg = "删除失败"
 		if errMsg != "" {
@@ -622,7 +578,7 @@ func (this *ReportController) GetReportChapterList() {
 	}
 
 	// 权限校验
-	isAuth, err := services.CheckReportAuthByReportChapterInfo(sysUser.AdminId, reportInfo.AdminId, reportId)
+	isAuth, err := services.CheckReportAuthByReportId(sysUser, reportInfo.AdminId, reportId)
 	if err != nil {
 		br.Msg = "获取报告权限失败"
 		br.ErrMsg = "获取报告权限失败,Err:" + err.Error()
@@ -750,7 +706,7 @@ func (this *ReportController) GetReportChapterList() {
 			}
 
 			// 报告章节的操作权限
-			tmpChapterItem.IsAuth = services.CheckChapterAuthByAdminIdList(sysUser.AdminId, reportInfo.AdminId, tmpChapterIdGrandList)
+			tmpChapterItem.IsAuth = services.CheckChapterAuthByAdminIdList(sysUser, reportInfo.AdminId, tmpChapterIdGrandList)
 
 			resp = append(resp, tmpChapterItem)
 		}
@@ -804,7 +760,7 @@ func (this *ReportController) GetDayWeekChapter() {
 	}
 
 	// 权限校验
-	isAuth, err := services.CheckReportAuthByReportChapterInfo(sysUser.AdminId, reportInfo.AdminId, reportInfo.Id)
+	isAuth, err := services.CheckReportAuthByReportId(sysUser, reportInfo.AdminId, reportInfo.Id)
 	if err != nil {
 		br.Msg = "获取报告权限失败"
 		br.ErrMsg = "获取报告权限失败,Err:" + err.Error()
@@ -979,24 +935,12 @@ func (this *ReportController) EditChapterTrendTag() {
 	}
 
 	// 操作权限校验
-	{
-		// 如果不是创建人,那么就要去查看是否授权
-		if reportInfo.AdminId != sysUser.AdminId {
-			// 授权用户权限校验
-			chapterGrantObj := report.ReportChapterGrant{}
-			_, tmpErr := chapterGrantObj.GetGrantByIdAndAdmin(chapterInfo.ReportChapterId, sysUser.AdminId)
-			if tmpErr != nil {
-				if tmpErr.Error() == utils.ErrNoRow() {
-					br.Msg = "没有权限"
-					br.ErrMsg = "没有权限"
-					br.IsSendEmail = false
-					return
-				}
-				br.Msg = "获取章节id授权用户失败"
-				br.ErrMsg = "获取章节id授权用户失败, Err: " + tmpErr.Error()
-				return
-			}
-		}
+	hasAuth, msg, errMsg, isSendEmail := checkOpPermission(sysUser, reportInfo, chapterInfo, false, this.Lang)
+	if !hasAuth {
+		br.Msg = msg
+		br.ErrMsg = errMsg
+		br.IsSendEmail = isSendEmail
+		return
 	}
 
 	// 更新章节标签
@@ -1122,7 +1066,7 @@ func (this *ReportController) VoiceUpload() {
 	}
 
 	// 权限校验
-	isAuth, err := services.CheckChapterAuthByReportChapterInfo(this.SysUser.AdminId, reportInfo.AdminId, reportChapterInfo)
+	isAuth, err := services.CheckChapterAuthByReportChapterInfo(this.SysUser, reportInfo.AdminId, reportChapterInfo)
 	if err != nil {
 		br.Msg = "获取报告权限失败"
 		br.ErrMsg = "获取报告权限失败,Err:" + err.Error()
@@ -1550,38 +1494,12 @@ func (this *ReportController) EditChapterTitle() {
 	}
 
 	// 操作权限校验
-	{
-		// 如果不是创建人,那么就要去查看是否授权
-		if reportInfo.AdminId != sysUser.AdminId {
-			// 授权用户权限校验
-			chapterGrantObj := report.ReportChapterGrant{}
-			_, tmpErr := chapterGrantObj.GetGrantByIdAndAdmin(reportChapterInfo.ReportChapterId, sysUser.AdminId)
-			if tmpErr != nil {
-				if tmpErr.Error() == utils.ErrNoRow() {
-					br.Msg = "没有权限"
-					br.ErrMsg = "没有权限"
-					br.IsSendEmail = false
-					return
-				}
-				br.Msg = "获取章节id授权用户失败"
-				br.ErrMsg = "获取章节id授权用户失败, Err: " + tmpErr.Error()
-				return
-			}
-		}
-
-		// 标记更新中
-		{
-			markStatus, err := services.UpdateReportEditMark(reportChapterInfo.ReportId, reportChapterInfo.ReportChapterId, sysUser.AdminId, 1, sysUser.RealName, this.Lang)
-			if err != nil {
-				br.Msg = err.Error()
-				return
-			}
-			if markStatus.Status == 1 {
-				br.Msg = markStatus.Msg
-				br.IsSendEmail = false
-				return
-			}
-		}
+	hasAuth, msg, errMsg, isSendEmail := checkOpPermission(sysUser, reportInfo, reportChapterInfo, true, this.Lang)
+	if !hasAuth {
+		br.Msg = msg
+		br.ErrMsg = errMsg
+		br.IsSendEmail = isSendEmail
+		return
 	}
 
 	if reportInfo.State == 2 {
@@ -1632,3 +1550,160 @@ func (this *ReportController) EditChapterTitle() {
 	br.Success = true
 	br.Msg = "保存成功"
 }
+
+// CancelPublishReportChapter
+// @Title 取消发布章节
+// @Description 取消发布章节
+// @Param	request	body models.PublishReportChapterReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /chapter/publish/cancel [post]
+func (this *ReportController) CancelPublishReportChapter() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req models.PublishReportChapterReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ReportChapterId <= 0 {
+		br.Msg = "参数有误"
+		return
+	}
+
+	// 获取章节详情
+	chapterInfo, err := models.GetReportChapterInfoById(req.ReportChapterId)
+	if err != nil {
+		br.Msg = "章节信息有误"
+		br.ErrMsg = "获取章节信息失败, Err: " + err.Error()
+		return
+	}
+	// 章节发布状态校验
+	if chapterInfo.PublishState == 1 {
+		br.Msg = "该章节未发布,不能重复撤销"
+		br.ErrMsg = "该章节未发布,不能重复撤销"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 获取报告详情
+	reportInfo, err := models.GetReportByReportId(chapterInfo.ReportId)
+	if err != nil {
+		br.Msg = "查询报告有误"
+		br.ErrMsg = "查询报告信息失败, Err: " + err.Error()
+		return
+	}
+	if reportInfo.State == 2 {
+		br.Msg = "该报告已发布,不允许撤销章节"
+		br.ErrMsg = "该报告已发布,不允许撤销章节"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 更新章节信息
+	chapterInfo.PublishState = 1
+	chapterInfo.PublishTime = time.Time{}
+	chapterInfo.LastModifyAdminId = this.SysUser.AdminId
+	chapterInfo.LastModifyAdminName = this.SysUser.RealName
+	chapterInfo.ModifyTime = time.Now()
+
+	updateCols := make([]string, 0)
+	updateCols = append(updateCols, "PublishState", "PublishTime", "LastModifyAdminId", "LastModifyAdminName", "ModifyTime")
+	err = chapterInfo.UpdateChapter(updateCols)
+	if err != nil {
+		br.Msg = "发布失败"
+		br.ErrMsg = "报告章节内容保存失败, Err: " + err.Error()
+		return
+	}
+
+	// 更新章节ES
+	{
+		go services.UpdateReportChapterEs(chapterInfo.ReportChapterId)
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "撤销成功"
+}
+
+// checkOpPermission
+// @Description: 操作权限校验
+// @author: Roc
+// @datetime 2024-11-12 09:58:34
+// @param sysUser *system.Admin
+// @param reportInfo *models.Report
+// @param reportChapterInfo *models.ReportChapter
+// @param isMarkStatus bool
+// @param lang string
+// @return hasAuth bool
+// @return msg string
+// @return errMsg string
+// @return isSendEmail bool
+func checkOpPermission(sysUser *system.Admin, reportInfo *models.Report, reportChapterInfo *models.ReportChapter, isMarkStatus bool, lang string) (hasAuth bool, msg, errMsg string, isSendEmail bool) {
+	isSendEmail = true
+
+	// 权限校验
+	isAuth, err := services.CheckChapterAuthByReportChapterInfo(sysUser, reportInfo.AdminId, reportChapterInfo)
+	if err != nil {
+		msg = "获取报告权限失败"
+		errMsg = "获取报告权限失败,Err:" + err.Error()
+		return
+	}
+	if !isAuth {
+		msg = "没有权限"
+		errMsg = "没有权限"
+		isSendEmail = false
+		return
+	}
+
+	// 如果不是创建人,那么就要去查看是否授权
+	//if reportInfo.AdminId != sysUser.AdminId && !utils.IsAdminRole(sysUser.RoleTypeCode) {
+	//	// 授权用户权限校验
+	//	chapterGrantObj := report.ReportChapterGrant{}
+	//	_, tmpErr := chapterGrantObj.GetGrantByIdAndAdmin(reportChapterInfo.ReportChapterId, sysUser.AdminId)
+	//	if tmpErr != nil {
+	//		if tmpErr.Error() == utils.ErrNoRow() {
+	//			msg = "没有权限"
+	//			errMsg = "没有权限"
+	//			isSendEmail = false
+	//			return
+	//		}
+	//		msg = "获取章节id授权用户失败"
+	//		errMsg = "获取章节id授权用户失败, Err: " + tmpErr.Error()
+	//		return
+	//	}
+	//}
+
+	// 标记更新中
+	if isMarkStatus {
+		markStatus, err := services.UpdateReportEditMark(reportChapterInfo.ReportId, reportChapterInfo.ReportChapterId, sysUser.AdminId, 1, sysUser.RealName, lang)
+		if err != nil {
+			msg = err.Error()
+			errMsg = err.Error()
+			return
+		}
+		if markStatus.Status == 1 {
+			msg = markStatus.Msg
+			errMsg = markStatus.Msg
+			isSendEmail = false
+			return
+		}
+	}
+
+	// 有权限
+	hasAuth = true
+
+	return
+}

+ 95 - 15
controllers/report_v2.go

@@ -12,13 +12,14 @@ import (
 	"eta/eta_api/services/data"
 	"eta/eta_api/utils"
 	"fmt"
-	"github.com/rdlucklib/rdluck_tools/paging"
 	"html"
 	"io"
 	"os"
 	"strconv"
 	"strings"
 	"time"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
 )
 
 // ListReport
@@ -134,11 +135,17 @@ func (this *ReportController) ListReport() {
 		pars = append(pars, 1)
 		condition += `  AND a.state in (2,6) `
 	case 3:
-		condition += ` AND a.admin_id = ? `
-		pars = append(pars, this.SysUser.AdminId)
+		// 如果不是超管,那么就看自己有权限的
+		if !utils.IsAdminRole(this.SysUser.RoleTypeCode) {
+			condition += ` AND a.admin_id = ? `
+			pars = append(pars, this.SysUser.AdminId)
+		}
 	case 2:
-		condition += ` AND (a.admin_id = ? or b.admin_id = ?) `
-		pars = append(pars, this.SysUser.AdminId, this.SysUser.AdminId)
+		// 如果不是超管,那么就看自己有权限的
+		if !utils.IsAdminRole(this.SysUser.RoleTypeCode) {
+			condition += ` AND (a.admin_id = ? or b.admin_id = ?) `
+			pars = append(pars, this.SysUser.AdminId, this.SysUser.AdminId)
+		}
 	}
 
 	// 共享报告需要连表查询,所以需要单独写
@@ -897,18 +904,21 @@ func (this *ReportController) AuthorizedListReport() {
 	var err error
 	var total int
 
-	orCondition := `AND ( (a.is_public_publish = ? AND a.state in (2,6)) or a.admin_id = ? `
-	pars = append(pars, 1, this.SysUser.AdminId)
+	// 如果不是超管,那么只能看到有权限的报告
+	if !utils.IsAdminRole(this.SysUser.RoleTypeCode) {
+		orCondition := `AND ( (a.is_public_publish = ? AND a.state in (2,6)) or a.admin_id = ? `
+		pars = append(pars, 1, this.SysUser.AdminId)
 
-	// 当前用户有权限的报告id列表
-	num := len(grantReportIdList)
-	if num > 0 {
-		orCondition += ` OR a.id in (` + utils.GetOrmInReplace(num) + `)`
-		pars = append(pars, grantReportIdList)
-	}
-	orCondition += ` ) `
+		// 当前用户有权限的报告id列表
+		num := len(grantReportIdList)
+		if num > 0 {
+			orCondition += ` OR a.id in (` + utils.GetOrmInReplace(num) + `)`
+			pars = append(pars, grantReportIdList)
+		}
+		orCondition += ` ) `
 
-	condition += orCondition
+		condition += orCondition
+	}
 
 	total, err = models.GetReportListCountByAuthorized(condition, pars)
 	if err != nil {
@@ -1650,6 +1660,76 @@ func (this *ReportController) CancelApprove() {
 	br.Msg = "操作成功"
 }
 
+// ShareGenerate
+// @Title 获取复制链接
+// @Description 获取复制链接
+// @Param	request	body models.ReportShartLinkReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /share/generate [post]
+func (this *ReportCommonController) ShareGenerate() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	var req models.ReportShartUrlReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+
+	link, err := services.GetReportShareUrlToken(req)
+	if err != nil {
+		br.Msg = "复制链接失败"
+		br.ErrMsg = "获取复制链接失败, Err: " + err.Error()
+		return
+	}
+
+	resp := new(models.ReportShartUrlResp)
+	resp.UrlToken = link
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// ShareTransform
+// @Title 获取原始链接
+// @Description 获取原始链接
+// @Param	Token   query   string  true    "复制链接的token"
+// @Success 200 Ret=200 操作成功
+// @router /share/link [get]
+func (this *ReportCommonController) ShareTransform() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	token := this.GetString("Token")
+
+	link, msg, err := services.TransfromToOriginUrl(token)
+	if err != nil {
+		if msg == "" {
+			msg = "获取失败"
+		}
+		br.Msg = msg
+		br.ErrMsg = "获取复制链接失败, Err: " + err.Error()
+		return
+	}
+
+	resp := new(models.ReportShartOriginUrlResp)
+	resp.Url = link
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
 // init
 // @Description: 修复历史报告数据
 // @author: Roc

+ 60 - 6
controllers/residual_analysis/residual_analysis.go

@@ -43,7 +43,7 @@ func (this *ResidualAnalysisController) ResidualAnalysisPreview() {
 
 	resp, err := residual_analysis_service.ResidualAnalysisPreview(req)
 	if err != nil {
-		br.Ret = 408
+		br.Ret = 403
 		br.Msg = "获取失败"
 		br.ErrMsg = err.Error()
 		return
@@ -77,7 +77,7 @@ func (this *ResidualAnalysisController) ContrastPreview() {
 
 	IndexCode := this.GetString("IndexCode")
 	if IndexCode == "" {
-		br.Ret = 408
+		br.Ret = 403
 		br.Msg = "IndexCode不能为空"
 		br.ErrMsg = "IndexCode不能为空"
 		return
@@ -128,10 +128,16 @@ func (this *ResidualAnalysisController) SaveResidualAnalysis() {
 		return
 	}
 
+	if req.Source == 0 {
+		br.Msg = "来源不能为空"
+		br.ErrMsg = "来源不能为空"
+		return
+	}
+
 	err = residual_analysis_service.SaveResidualAnalysis(req, sysUser)
 	if err != nil {
-		br.Ret = 408
-		br.Msg = "获取失败"
+		br.Ret = 403
+		br.Msg = err.Error()
 		br.ErrMsg = err.Error()
 		return
 	}
@@ -169,9 +175,9 @@ func (this *ResidualAnalysisController) SaveResidualAnalysisConfig() {
 		return
 	}
 
-	err = residual_analysis_service.SaveResidualAnalysisConfig(req, sysUser)
+	configId, err := residual_analysis_service.SaveResidualAnalysisConfig(req, sysUser)
 	if err != nil {
-		br.Ret = 408
+		br.Ret = 403
 		br.Msg = "保存失败"
 		br.ErrMsg = err.Error()
 		return
@@ -180,6 +186,7 @@ func (this *ResidualAnalysisController) SaveResidualAnalysisConfig() {
 	br.Ret = 200
 	br.Success = true
 	br.Msg = "添加成功"
+	br.Data = configId
 }
 
 // ResidualAnalysisDetail
@@ -214,6 +221,53 @@ func (this *ResidualAnalysisController) ResidualAnalysisDetail() {
 
 	resp, err := residual_analysis_service.ResidualAnalysisDetail(EdbInfoId)
 	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = err.Error()
+		br.Ret = 403
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// CheckResidualAnalysisExist
+// @Title 校验残差指标是否存在
+// @Description 校验残差指标是否存在
+// @Success 200 {object}
+// @router /check/residual/analysis/exist [get]
+func (this *ResidualAnalysisController) CheckResidualAnalysisExist() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	configId, err := this.GetInt("ConfigId")
+	if err != nil {
+		br.Msg = "EdbInfoId参数异常!"
+		br.ErrMsg = "EdbInfoId参数解析失败,Err:" + err.Error()
+	}
+	if configId <= 0 {
+		br.Msg = "ConfigId参数异常!"
+		br.ErrMsg = "ConfigId参数异常!"
+	}
+
+	resp, err := residual_analysis_service.CheckResidualAnalysisExist(configId)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = err.Error()
+		br.Ret = 403
 		return
 	}
 

+ 2 - 1
go.mod

@@ -37,11 +37,11 @@ require (
 	github.com/nosixtools/solarlunar v0.0.0-20211112060703-1b6dea7b4a19
 	github.com/olivere/elastic/v7 v7.0.32
 	github.com/pdfcpu/pdfcpu v0.8.0
-	github.com/pingcap/errors v0.11.5-0.20221009092201-b66cddb77c32
 	github.com/qiniu/qmgo v1.1.8
 	github.com/rdlucklib/rdluck_tools v1.0.3
 	github.com/shopspring/decimal v1.4.0
 	github.com/silenceper/wechat/v2 v2.1.6
+	github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72
 	github.com/spf13/viper v1.19.0
 	github.com/tealeg/xlsx v1.0.5
 	github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/asr v1.0.973
@@ -114,6 +114,7 @@ require (
 	github.com/montanaflynn/stats v0.7.1 // indirect
 	github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
 	github.com/pelletier/go-toml/v2 v2.2.2 // indirect
+	github.com/pingcap/errors v0.11.5-0.20221009092201-b66cddb77c32 // indirect
 	github.com/pingcap/failpoint v0.0.0-20220801062533-2eaa32854a6c // indirect
 	github.com/pingcap/log v1.1.1-0.20230317032135-a0d097d16e22 // indirect
 	github.com/pingcap/tidb/pkg/parser v0.0.0-20231103042308-035ad5ccbe67 // indirect

+ 2 - 0
go.sum

@@ -491,6 +491,8 @@ github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYl
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
 github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
 github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
 github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
 github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=

+ 3 - 0
models/data_manage/edb_info.go

@@ -68,6 +68,7 @@ type EdbInfo struct {
 	IsJoinPermission int       `description:"是否加入权限管控,0:不加入;1:加入;默认:0"`
 	IsStaticData     int       `description:"是否是静态指标,0否,1是"`
 	SetUpdateTime    time.Time `description:"设置启用/禁用刷新状态的时间"`
+	EndDateType      int       `description:"预测指标截止日期类型:0:未来日期,1未来期数"`
 }
 
 type EdbInfoFullClassify struct {
@@ -464,6 +465,8 @@ type EdbInfoList struct {
 	IsStaticData     int                     `description:"是否是静态指标,0否,1是"`
 	MoveType         int                     `description:"移动方式:1:领先(默认),2:滞后"`
 	MoveFrequency    string                  `description:"移动频度"`
+	MinValue         float64                 `description:"最小值"`
+	MaxValue         float64                 `description:"最大值"`
 }
 
 type EdbDataInsertConfigItem struct {

+ 3 - 0
models/data_manage/predict_edb_conf.go

@@ -19,6 +19,7 @@ type PredictEdbConf struct {
 	EndDate          time.Time `description:"截止日期"`
 	ModifyTime       time.Time `description:"修改时间"`
 	CreateTime       time.Time `description:"添加时间"`
+	EndNum           int       `description:"截止期数"`
 }
 
 // PredictEdbConfDetail 预测规则 和 规则相关联的指标
@@ -35,6 +36,7 @@ type PredictEdbConfDetail struct {
 	ModifyTime       time.Time                               `description:"修改时间"`
 	CreateTime       time.Time                               `description:"添加时间"`
 	CalculateList    []*PredictEdbConfCalculateMappingDetail `description:"配置与指标的关联信息"`
+	EndNum           int                                     `description:"截止期数"`
 }
 
 // PredictEdbConfAndData 预测规则和其对应的动态数据
@@ -46,6 +48,7 @@ type PredictEdbConfAndData struct {
 	FixedValue       float64        `description:"固定值"`
 	Value            string         `description:"配置的值"`
 	EndDate          time.Time      `description:"截止日期"`
+	EndNum           int            `description:"截止期数"`
 	ModifyTime       time.Time      `description:"修改时间"`
 	CreateTime       time.Time      `description:"添加时间"`
 	DataList         []*EdbDataList `description:"动态数据"`

+ 8 - 7
models/data_manage/request/predict_edb_info.go

@@ -29,13 +29,13 @@ type AddPredictEdbInfoReq struct {
 	RuleType        int          `description:"预测规则,1:最新,2:固定值"`
 	FixedValue      float64      `description:"固定值"`
 	RuleList        []RuleConfig `description:"配置规则列表"`
-
-	DataDateType string  `description:"日期类型,枚举值:交易日、自然日"`
-	MaxValue     float64 `description:"最大值"`
-	MinValue     float64 `description:"最小值"`
-	EdbInfoId    int     `description:"指标ID"`
-	AdminId      int     `description:"添加人id"`
-	AdminName    string  `description:"添加人名称"`
+	DataDateType    string       `description:"日期类型,枚举值:交易日、自然日"`
+	MaxValue        float64      `description:"最大值"`
+	MinValue        float64      `description:"最小值"`
+	EdbInfoId       int          `description:"指标ID"`
+	AdminId         int          `description:"添加人id"`
+	AdminName       string       `description:"添加人名称"`
+	EndDateType     int          `description:"截止日期类型:0:未来日期,1未来期数"`
 }
 
 // RuleConfig 预测规则配置
@@ -45,6 +45,7 @@ type RuleConfig struct {
 	EmptyType    int                          `description:"空值处理类型(0查找前后35天,1不计算,2前值填充,3后值填充,4等于0)"`
 	MaxEmptyType int                          `description:"MAX、MIN公式空值处理类型(1、等于0;2、跳过空值)"`
 	EndDate      string                       `description:"截止日期"`
+	EndNum       int                          `description:"截止期数"`
 	EdbInfoIdArr []data_manage.EdbInfoFromTag `description:"指标信息"`
 }
 

+ 7 - 2
models/material/material.go

@@ -211,14 +211,14 @@ func GetMaterialInfoCountByClassifyIds(classifyIds []string) (count int, err err
 
 func GetMaterialByNames(materialNames []string) (items []*Material, err error) {
 	o := orm.NewOrmUsingDB("rddp")
-	sql := `SELECT * FROM material WHERE material_name in (` + utils.GetOrmInReplace(len(materialNames)) + `) limit 1`
+	sql := `SELECT * FROM material WHERE material_name in (` + utils.GetOrmInReplace(len(materialNames)) + `)`
 	_, err = o.Raw(sql, materialNames).QueryRows(&items)
 	return
 }
 
 func GetMaterialByNameEns(materialNames []string) (items []*Material, err error) {
 	o := orm.NewOrmUsingDB("rddp")
-	sql := `SELECT * FROM material WHERE material_name_en in (` + utils.GetOrmInReplace(len(materialNames)) + `) limit 1`
+	sql := `SELECT * FROM material WHERE material_name_en in (` + utils.GetOrmInReplace(len(materialNames)) + `)`
 	_, err = o.Raw(sql, materialNames).QueryRows(&items)
 	return
 }
@@ -243,3 +243,8 @@ func GetMaterialMaxSort() (sort int, err error) {
 	err = o.Raw(sql).QueryRow(&sort)
 	return
 }
+
+// MyChartSaveAsMaterialResp 添加素材的
+type MyChartSaveAsMaterialResp struct {
+	ExistList []*MyChartSaveAsMaterialItem
+}

+ 13 - 0
models/report.go

@@ -1565,3 +1565,16 @@ func FindReportListByCondition(condition string, pars []interface{}) (items []*R
 	}
 	return
 }
+
+type ReportShartUrlReq struct {
+	Url      string `description:"分享链接"`
+	ReportId int    `description:"报告ID"`
+}
+
+type ReportShartUrlResp struct {
+	UrlToken string `description:"分享链接token"`
+}
+
+type ReportShartOriginUrlResp struct {
+	Url string `description:"分享链接token"`
+}

+ 58 - 30
models/residual_analysis_model/calculate_residual_analysis_config.go

@@ -1,16 +1,16 @@
 package residual_analysis_model
 
 import (
-	"eta/eta_api/models/data_manage"
 	"github.com/beego/beego/v2/client/orm"
+	"time"
 )
 
 type CalculateResidualAnalysisConfig struct {
-	CalculateResidualAnalysisConfigId int    `orm:"column(calculate_residual_analysis_config_id);pk;auto" description:"自增id"`
-	Config                            string `orm:"column(config)" description:"计算参数配置"`
-	SysUserId                         int    `orm:"column(sys_user_id)" description:"操作人id"`
-	CreateTime                        string `orm:"column(create_time)" description:"创建时间"`
-	ModifyTime                        string `orm:"column(modify_time)" description:"修改时间"`
+	CalculateResidualAnalysisConfigId int       `orm:"column(calculate_residual_analysis_config_id);pk;auto" description:"自增id"`
+	Config                            string    `orm:"column(config)" description:"计算参数配置"`
+	SysUserId                         int       `orm:"column(sys_user_id)" description:"操作人id"`
+	CreateTime                        time.Time `orm:"column(create_time)" description:"创建时间"`
+	ModifyTime                        time.Time `orm:"column(modify_time)" description:"修改时间"`
 }
 
 func init() {
@@ -22,6 +22,8 @@ type ResidualAnalysisReq struct {
 	EdbInfoIdA       int     `description:"指标A"`
 	EdbInfoIdB       int     `description:"指标B"`
 	EdbInfoId        int     `description:"残差指标id"`
+	ConfigId         int     `description:"配置id"`
+	QueryType        int     `description:"查询类型区分 避免查询过慢 1-查询第一个表格 2-查询需要计算的表格"`
 	ResidualType     int     `description:"残差类型: 1-映射残差 2-拟合残差"`
 	DateType         int     `description:"时间类型 -1-自定义时间 0-至今 n-枚举时间(近n年)"`
 	StartDate        string  `description:"自定义开始日期"`
@@ -45,10 +47,14 @@ type ResidualAnalysisResp struct {
 	OriginalChartData ChartResp `description:"原始图数据"`
 	MappingChartData  ChartResp `description:"映射图数据"`
 	ResidualChartData ChartResp `description:"残差图数据"`
+	A                 float64   `description:"斜率"`
+	B                 float64   `description:"截距"`
+	R                 float64   `description:"相关系数"`
+	R2                float64   `description:"决定系数"`
 }
 
 type ChartResp struct {
-	ChartInfo   *ResidualAnalysisChartInfo
+	ChartInfo   ResidualAnalysisChartInfo
 	EdbInfoList []ResidualAnalysisChartEdbInfoMapping
 }
 
@@ -89,7 +95,7 @@ type ResidualAnalysisChartEdbInfoMapping struct {
 	ModifyTime          string  `description:"指标最后更新时间"`
 	MaxData             float64 `description:"上限"`
 	MinData             float64 `description:"下限"`
-	IsOrder             bool    `description:"true:正序,false:逆序"`
+	IsOrder             bool    `description:"true:逆序:,false:正序"`
 	IsAxis              int     `description:"1:左轴,0:右轴"`
 	EdbInfoType         int     `description:"1:标准指标,0:领先指标"`
 	EdbInfoCategoryType int     `description:"0:普通指标,1:预测指标"`
@@ -103,24 +109,29 @@ type ResidualAnalysisChartEdbInfoMapping struct {
 	ChartType           int     `description:"生成样式:1:曲线图,2:季节性图,3:面积图,4:柱状图,5:散点图,6:组合图,7:柱方图,8:商品价格曲线图,9:相关性图"`
 	LatestDate          string  `description:"数据最新日期"`
 	LatestValue         float64 `description:"数据最新值"`
-	MinValue            float64 `json:"-" description:"最小值"`
-	MaxValue            float64 `json:"-" description:"最大值"`
+	MinValue            float64 `description:"最小值"`
+	MaxValue            float64 `description:"最大值"`
 	DataList            interface{}
 }
 
 type ResidualAnalysisIndexSaveReq struct {
-	EdbCode      string                 `description:"指标编码"`
-	EdbName      string                 `description:"指标名称"`
-	EdbNameEn    string                 `description:"英文指标名称"`
-	EdbType      int                    `description:"指标类型:1:基础指标,2:计算指标"`
-	Frequency    string                 `description:"频率"`
-	FrequencyEn  string                 `description:"英文频率"`
-	Unit         string                 `description:"单位"`
-	UnitEn       string                 `description:"英文单位"`
-	ClassifyId   int                    `description:"分类id"`
-	ConfigId     int                    `description:"残差配置id"`
-	ResidualType int                    `orm:"column(residual_type)" description:"残差类型: 1-映射残差 2-拟合残差"`
-	DataList     []ResidualAnalysisData `description:"指标数据"`
+	EdbInfoIdA   int                       `description:"指标A"`
+	EdbInfoIdB   int                       `description:"指标B"`
+	Source       int                       `description:"99-映射残差 100-拟合残差"`
+	EdbCode      string                    `description:"指标编码"`
+	EdbName      string                    `description:"指标名称"`
+	EdbNameEn    string                    `description:"英文指标名称"`
+	EdbType      int                       `description:"指标类型:1:基础指标,2:计算指标"`
+	Frequency    string                    `description:"频率"`
+	FrequencyEn  string                    `description:"英文频率"`
+	Unit         string                    `description:"单位"`
+	UnitEn       string                    `description:"英文单位"`
+	Calendar     string                    `description:"公历/农历"`
+	ClassifyId   int                       `description:"分类id"`
+	ConfigId     int                       `description:"残差配置id"`
+	ResidualType int                       `orm:"column(residual_type)" description:"残差类型: 1-映射残差 2-拟合残差"`
+	IndexType    int                       `orm:"column(index_type)" description:"指标类型:1-映射指标 2-残差指标 3-因变量指标 4-自变量指标"`
+	DataList     []edbDataResidualAnalysis `description:"指标数据"`
 }
 
 // ResidualAnalysisConfigVo 残差分析配置vo
@@ -142,24 +153,41 @@ type ResidualAnalysisConfigVo struct {
 	ContrastIndexMax float64 `description:"对比指标上限"`
 }
 
+type DetailEdbInfoList struct {
+	EdbInfoId   int    `orm:"column(edb_info_id);pk"`
+	EdbInfoType int    `description:"指标类型,0:普通指标,1:预测指标"`
+	IndexType   int    `orm:"column(index_type)" description:"指标类型:1-映射指标 2-残差指标 3-因变量指标 4-自变量指标"`
+	SourceName  string `description:"来源名称"`
+	Source      int    `description:"来源id"`
+	EdbCode     string `description:"指标编码"`
+	EdbNameEn   string `description:"英文指标名称"`
+	EdbName     string `description:"指标名称"`
+	Frequency   string `description:"频率"`
+	FrequencyEn string `description:"英文频率"`
+	Unit        string `description:"单位"`
+	UnitEn      string `description:"英文单位"`
+	ClassifyId  int    `description:"分类id"`
+}
+
 // ResidualAnalysisDetailResp 详情响应接口
 type ResidualAnalysisDetailResp struct {
-	ConfigInfo  *CalculateResidualAnalysisConfig
-	EdbInfoList []*data_manage.EdbInfoList
+	ConfigInfo   *CalculateResidualAnalysisConfig
+	EdbInfoList  []*DetailEdbInfoList
+	ResidualType int `description:"残差类型: 1-映射残差 2-拟合残差"`
 }
 
 // GetResidualAnalysisConfigById 根据配置id查询配置信息
 func GetResidualAnalysisConfigById(configId int) (residualAnalysisConfig CalculateResidualAnalysisConfig, err error) {
-	o := orm.NewOrm()
-	sql := "SELECT * FROM residual_analysis_config WHERE calculate_residual_analysis_config_id=?"
+	o := orm.NewOrmUsingDB("data")
+	sql := "SELECT * FROM calculate_residual_analysis_config WHERE calculate_residual_analysis_config_id=?"
 	err = o.Raw(sql, configId).QueryRow(&residualAnalysisConfig)
 	return residualAnalysisConfig, nil
 }
 
 // UpdateResidualAnalysisConfig 更新配置信息
 func UpdateResidualAnalysisConfig(config CalculateResidualAnalysisConfig) (err error) {
-	o := orm.NewOrm()
-	_, err = o.Update(config)
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(&config)
 	if err != nil {
 		return err
 	}
@@ -168,8 +196,8 @@ func UpdateResidualAnalysisConfig(config CalculateResidualAnalysisConfig) (err e
 
 // SaveResidualAnalysisConfig 保存配置信息
 func SaveResidualAnalysisConfig(config CalculateResidualAnalysisConfig) (id int64, err error) {
-	o := orm.NewOrm()
-	id, err = o.Insert(config)
+	o := orm.NewOrmUsingDB("data")
+	id, err = o.Insert(&config)
 	if err != nil {
 		return 0, err
 	}

+ 13 - 9
models/residual_analysis_model/calculate_residual_analysis_config_mapping.go

@@ -1,14 +1,18 @@
 package residual_analysis_model
 
-import "github.com/beego/beego/v2/client/orm"
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
 
 type CalculateResidualAnalysisConfigMapping struct {
-	CalculateResidualAnalysisConfigMappingId int    `orm:"column(calculate_residual_analysis_config_mapping_id);pk;auto" description:"自增id"`
-	CalculateResidualAnalysisConfigId        int    `orm:"column(calculate_residual_analysis_config_id)" description:"残差分析配置id"`
-	EdbInfoId                                int64  `orm:"column(edb_info_id)" description:"指标id"`
-	ResidualType                             int    `orm:"column(residual_type)" description:"残差类型: 1-映射残差 2-拟合残差"`
-	CreateTime                               string `orm:"column(create_time)" description:"创建时间"`
-	ModifyTime                               string `orm:"column(modify_time)" description:"修改时间"`
+	CalculateResidualAnalysisConfigMappingId int       `orm:"column(calculate_residual_analysis_config_mapping_id);pk;auto" description:"自增id"`
+	CalculateResidualAnalysisConfigId        int       `orm:"column(calculate_residual_analysis_config_id)" description:"残差分析配置id"`
+	EdbInfoId                                int64     `orm:"column(edb_info_id)" description:"指标id"`
+	ResidualType                             int       `orm:"column(residual_type)" description:"残差类型: 1-映射残差 2-拟合残差"`
+	IndexType                                int       `orm:"column(index_type)" description:"指标类型:1-映射指标 2-残差指标 3-因变量指标 4-自变量指标"`
+	CreateTime                               time.Time `orm:"column(create_time)" description:"创建时间"`
+	ModifyTime                               time.Time `orm:"column(modify_time)" description:"修改时间"`
 }
 
 func init() {
@@ -37,8 +41,8 @@ func GetConfigMappingListByCondition(condition string, pars []interface{}) (item
 
 // SaveConfigMapping 保存指标和配置的映射关系
 func SaveConfigMapping(mapping CalculateResidualAnalysisConfigMapping) (id int64, err error) {
-	o := orm.NewOrm()
-	id, err = o.Insert(mapping)
+	o := orm.NewOrmUsingDB("data")
+	id, err = o.Insert(&mapping)
 	if err != nil {
 		return 0, err
 	}

+ 15 - 10
models/residual_analysis_model/edb_data_residual_analysis.go

@@ -1,18 +1,23 @@
 package residual_analysis_model
 
-import "github.com/beego/beego/v2/client/orm"
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
 
-type ResidualAnalysisData struct {
-	EdbDataId     int     `orm:"column(edb_data_id);pk;auto" description:"自增id"`
-	EdbInfoId     int     `orm:"column(edb_info_id)" description:"指标id"`
-	EdbCode       string  `orm:"column(edb_code)" description:"指标编码"`
-	DataTime      string  `orm:"column(data_time)" description:"数据日期"`
-	Value         float64 `orm:"column(value)" description:"数据值"`
-	DataTimeStamp int64   `orm:"column(data_timestamp)"`
+type edbDataResidualAnalysis struct {
+	EdbDataId     int       `orm:"column(edb_data_id);pk;auto" description:"自增id"`
+	EdbInfoId     int       `orm:"column(edb_info_id)" description:"指标id"`
+	EdbCode       string    `orm:"column(edb_code)" description:"指标编码"`
+	DataTime      string    `orm:"column(data_time)" description:"数据日期"`
+	Value         float64   `orm:"column(value)" description:"数据值"`
+	CreateTime    time.Time `orm:"column(create_time)" description:"创建时间"`
+	ModifyTime    time.Time `orm:"column(modify_time)" description:"修改时间"`
+	DataTimeStamp int64     `orm:"column(data_timestamp)"`
 }
 
 func init() {
-	orm.RegisterModel(new(ResidualAnalysisData))
+	orm.RegisterModel(new(edbDataResidualAnalysis))
 }
 
 // DeleteResidualAnalysisDataByEdbCode 根据指标编码删除数据
@@ -24,7 +29,7 @@ func DeleteResidualAnalysisDataByEdbCode(edbCode string) error {
 }
 
 // AddResidualAnalysisData 新增指标数据
-func AddResidualAnalysisData(dataList []ResidualAnalysisData) (num int64, err error) {
+func AddResidualAnalysisData(dataList []edbDataResidualAnalysis) (num int64, err error) {
 	o := orm.NewOrmUsingDB("data")
 	num, err = o.InsertMulti(len(dataList), dataList)
 	if err != nil {

+ 36 - 0
routers/commentsRouter.go

@@ -8134,6 +8134,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/residual_analysis:ResidualAnalysisController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/residual_analysis:ResidualAnalysisController"],
+        beego.ControllerComments{
+            Method: "CheckResidualAnalysisExist",
+            Router: `/check/residual/analysis/exist`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/residual_analysis:ResidualAnalysisController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/residual_analysis:ResidualAnalysisController"],
         beego.ControllerComments{
             Method: "ContrastPreview",
@@ -10609,6 +10618,24 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers:ReportCommonController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportCommonController"],
+        beego.ControllerComments{
+            Method: "ShareGenerate",
+            Router: `/share/generate`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers:ReportCommonController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportCommonController"],
+        beego.ControllerComments{
+            Method: "ShareTransform",
+            Router: `/share/link`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
         beego.ControllerComments{
             Method: "CheckDayWeekReportChapterVideo",
@@ -10690,6 +10717,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "CancelPublishReportChapter",
+            Router: `/chapter/publish/cancel`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
         beego.ControllerComments{
             Method: "EditChapterTitle",

+ 14 - 13
services/excel/lucky_sheet.go

@@ -8,6 +8,7 @@ import (
 	"fmt"
 	"os"
 	"reflect"
+	"regexp"
 	"strconv"
 	"strings"
 	"time"
@@ -1423,22 +1424,22 @@ func getExcelizeAlignmentConf(cellInfo LuckySheetDataValue) *excelize.Alignment
 
 // getColor 获取hex颜色
 func getColor(bgStr string) string {
-	if strings.Contains(bgStr, "(") {
-		arr := strings.Split(bgStr, ",")
-		if len(arr) != 3 {
+	isRgb := strings.HasPrefix(bgStr, "rgb")
+	re := regexp.MustCompile(`\(([^)]+)\)`)
+	matches := re.FindStringSubmatch(bgStr)
+	if len(matches) != 2 {
+		return bgStr
+	}
+	if isRgb {
+		arr := strings.Split(matches[1], ",")
+		if len(arr) < 3 {
 			return bgStr
 		}
-
-		// 第一位
-		tmpFirstArr := strings.Split(arr[0], "(")
-		arr[0] = tmpFirstArr[len(tmpFirstArr)-1]
-
-		// 最后一位
-		tmpLastArr := strings.Split(arr[2], ")")
-		arr[2] = tmpLastArr[0]
-
 		rgbArr := make([]int64, 0)
-		for _, v := range arr {
+		for i, v := range arr {
+			if i >= 3 {
+				continue
+			}
 			tmpInt, err := strconv.Atoi(utils.TrimStr(v))
 			if err != nil {
 				return bgStr

+ 13 - 0
services/material/material.go

@@ -580,3 +580,16 @@ func GetBatchSelectedMaterialList(classifyId int, keyword string, isShowMe bool,
 	}
 	return
 }
+
+func GetMyChartExistMaterialNameListMsg(nameList map[string]struct{}, reqList []*material.MyChartSaveAsMaterialItem) (nameResp material.MyChartSaveAsMaterialResp) {
+	existNameList := make([]*material.MyChartSaveAsMaterialItem, 0)
+	for _, v := range reqList {
+		if _, ok := nameList[v.MaterialName]; ok {
+			existNameList = append(existNameList, v)
+		}
+	}
+	nameResp = material.MyChartSaveAsMaterialResp{
+		ExistList: existNameList,
+	}
+	return
+}

+ 27 - 13
services/report_chapter.go

@@ -3,6 +3,7 @@ package services
 import (
 	"eta/eta_api/models"
 	"eta/eta_api/models/report"
+	"eta/eta_api/models/system"
 	"eta/eta_api/utils"
 	"fmt"
 	"time"
@@ -173,18 +174,23 @@ func moveReportChapter(reportChapter, prevReportChapter, nextReportChapter *mode
 // @Description: 根据管理员id列表,判断当前用户是否有章节权限
 // @author: Roc
 // @datetime 2024-06-13 11:03:10
-// @param adminId int
+// @param sysUser *system.Admin
 // @param createAdminId int
 // @param grantAdminIdList []int
 // @return isAuth bool
-func CheckChapterAuthByAdminIdList(adminId, createAdminId int, grantAdminIdList []int) (isAuth bool) {
+func CheckChapterAuthByAdminIdList(sysUser *system.Admin, createAdminId int, grantAdminIdList []int) (isAuth bool) {
 	// 如果是自己创建的报告,那么就有权限
-	if adminId == createAdminId {
+	if sysUser.AdminId == createAdminId {
+		isAuth = true
+		return
+	}
+	// 如果本人是超管,那么就有权限
+	if utils.IsAdminRole(sysUser.RoleTypeCode) {
 		isAuth = true
 		return
 	}
 	// 如果是授权用户,那么就有权限
-	if utils.IsCheckInList(grantAdminIdList, adminId) {
+	if utils.IsCheckInList(grantAdminIdList, sysUser.AdminId) {
 		isAuth = true
 		return
 	}
@@ -201,24 +207,32 @@ func CheckChapterAuthByAdminIdList(adminId, createAdminId int, grantAdminIdList
 // @param reportChapterInfo *models.ReportChapter
 // @return isAuth bool
 // @return err error
-func CheckChapterAuthByReportChapterInfo(adminId, createAdminId int, reportChapterInfo *models.ReportChapter) (isAuth bool, err error) {
+func CheckChapterAuthByReportChapterInfo(sysUser *system.Admin, createAdminId int, reportChapterInfo *models.ReportChapter) (isAuth bool, err error) {
 	// 如果是自己创建的报告,那么就有权限
-	if adminId == createAdminId {
+	if sysUser.AdminId == createAdminId {
 		isAuth = true
 		return
 	}
-
-	chapterGrantObj := report.ReportChapterGrant{}
-	chapterGrantList, err := chapterGrantObj.GetGrantListById(reportChapterInfo.ReportChapterId)
-	if err != nil {
+	// 如果本人是超管,那么就有权限
+	if utils.IsAdminRole(sysUser.RoleTypeCode) {
+		isAuth = true
 		return
 	}
 
-	for _, v := range chapterGrantList {
-		if v.AdminId == adminId {
-			isAuth = true
+	chapterGrantObj := report.ReportChapterGrant{}
+	item, err := chapterGrantObj.GetGrantByIdAndAdmin(reportChapterInfo.ReportChapterId, sysUser.AdminId)
+	if err != nil {
+		// 如果是没找到数据,那么就是无权限
+		if err.Error() == utils.ErrNoRow() {
+			err = nil
 			return
 		}
+		// sql报错了
+		return
+	}
+	// 用户id一致就有权限
+	if item.AdminId == sysUser.AdminId {
+		isAuth = true
 	}
 
 	return

+ 76 - 12
services/report_v2.go

@@ -10,13 +10,15 @@ import (
 	"eta/eta_api/models/system"
 	"eta/eta_api/utils"
 	"fmt"
-	"github.com/rdlucklib/rdluck_tools/file"
-	"github.com/rdlucklib/rdluck_tools/http"
 	"html"
 	"os"
 	"path"
 	"strconv"
 	"time"
+
+	"github.com/go-redis/redis/v8"
+	"github.com/rdlucklib/rdluck_tools/file"
+	"github.com/rdlucklib/rdluck_tools/http"
 )
 
 // AddReportAndChapter
@@ -975,7 +977,7 @@ func CheckReportAuthByAdminIdList(adminId, createAdminId int, grantAdminIdList [
 	return
 }
 
-// CheckReportAuthByReportChapterInfo
+// CheckReportAuthByReportId
 // @Description: 根据报告ID,判断当前用户是否有报告权限
 // @author: Roc
 // @datetime 2024-06-13 16:21:28
@@ -983,24 +985,32 @@ func CheckReportAuthByAdminIdList(adminId, createAdminId int, grantAdminIdList [
 // @param reportInfoId int
 // @return isAuth bool
 // @return err error
-func CheckReportAuthByReportChapterInfo(adminId, createAdminId int, reportInfoId int) (isAuth bool, err error) {
+func CheckReportAuthByReportId(sysUser *system.Admin, createAdminId int, reportInfoId int) (isAuth bool, err error) {
 	// 如果是自己创建的报告,那么就有权限
-	if adminId == createAdminId {
+	if sysUser.AdminId == createAdminId {
 		isAuth = true
 		return
 	}
-
-	obj := report.ReportGrant{}
-	chapterGrantList, err := obj.GetGrantListById(reportInfoId)
-	if err != nil {
+	// 如果本人是超管,那么就有权限
+	if utils.IsAdminRole(sysUser.RoleTypeCode) {
+		isAuth = true
 		return
 	}
 
-	for _, v := range chapterGrantList {
-		if v.AdminId == adminId {
-			isAuth = true
+	obj := report.ReportGrant{}
+	item, err := obj.GetGrantByIdAndAdmin(reportInfoId, sysUser.AdminId)
+	if err != nil {
+		// 如果是没找到数据,那么就是无权限
+		if err.Error() == utils.ErrNoRow() {
+			err = nil
 			return
 		}
+		// sql报错了
+		return
+	}
+	// 用户id一致就有权限
+	if item.AdminId == sysUser.AdminId {
+		isAuth = true
 	}
 
 	return
@@ -1497,3 +1507,57 @@ func GetReportWaterMarkPdf(reportInfo *models.Report, sysUser *system.Admin) {
 	waterMarkStr := fmt.Sprintf("%s - %s", sysUser.RealName, sysUser.Mobile)
 	GeneralWaterMarkPdf(filePath, waterMarkStr)
 }
+
+// GetReportShareUrlToken 获取报告分享链接token
+func GetReportShareUrlToken(req models.ReportShartUrlReq) (linkToken string, err error) {
+	cacheLinkKey := utils.CACHE_REPORT_SHARE_SHORT_Url + strconv.Itoa(req.ReportId)
+	linkToken, _ = utils.Rc.RedisString(cacheLinkKey)
+	if linkToken != "" && utils.Rc.IsExist(utils.CACHE_REPORT_SHARE_ORIGIN_Url+linkToken) {
+		return
+	}
+	var ok bool
+	// 冲突检测
+	for i := 0; i < 3; i++ {
+		linkToken = req.Url
+		if i > 0 {
+			linkToken += "_" + utils.GetRandDigit(3)
+		}
+		hashUrl := utils.MurmurHash64([]byte(linkToken))
+		linkToken = utils.ConvertNumToBase62(hashUrl)
+
+		ok = utils.Rc.IsExist(utils.CACHE_REPORT_SHARE_ORIGIN_Url + linkToken)
+		if !ok {
+			break
+		}
+	}
+	if !ok {
+		after := time.Now().AddDate(0, 0, 7)
+		err = utils.Rc.Put(cacheLinkKey, linkToken, time.Until(after))
+		if err != nil {
+			return
+		}
+		err = utils.Rc.Put(utils.CACHE_REPORT_SHARE_ORIGIN_Url+linkToken, req.Url, time.Until(after))
+		if err != nil {
+			return
+		}
+	}
+	return
+}
+
+func TransfromToOriginUrl(linkToken string) (originLink string, msg string, err error) {
+	cacheLinkKey := utils.CACHE_REPORT_SHARE_ORIGIN_Url + linkToken
+	originLink, err = utils.Rc.RedisString(cacheLinkKey)
+	if err != nil {
+		if err == redis.Nil {
+			msg = "链接已失效, 请重新获取"
+			return
+		}
+		msg = "获取链接失败"
+		return
+	}
+	if originLink == "" {
+		msg = "链接已失效, 请重新获取"
+		return
+	}
+	return
+}

+ 446 - 128
services/residual_analysis_service/residual_analysis_service.go

@@ -7,15 +7,13 @@ import (
 	"eta/eta_api/models/system"
 	"eta/eta_api/utils"
 	"fmt"
+	"math"
+	"strconv"
 	"time"
 )
 
 func ResidualAnalysisPreview(req residual_analysis_model.ResidualAnalysisReq) (residual_analysis_model.ResidualAnalysisResp, error) {
 
-	if req.DateType < 0 {
-		return residual_analysis_model.ResidualAnalysisResp{}, fmt.Errorf("时间类型错误", nil)
-	}
-
 	mappingList, err := data_manage.GetChartEdbMappingListByEdbInfoIdList([]int{req.EdbInfoIdA, req.EdbInfoIdB})
 	if err != nil {
 		return residual_analysis_model.ResidualAnalysisResp{}, fmt.Errorf("获取图表,指标信息失败,Err:%s", err.Error())
@@ -31,10 +29,10 @@ func ResidualAnalysisPreview(req residual_analysis_model.ResidualAnalysisReq) (r
 		}
 	}
 	if edbInfoMappingA == nil {
-		return residual_analysis_model.ResidualAnalysisResp{}, fmt.Errorf("指标A不存在", nil)
+		return residual_analysis_model.ResidualAnalysisResp{}, fmt.Errorf("指标A不存在")
 	}
 	if edbInfoMappingB == nil {
-		return residual_analysis_model.ResidualAnalysisResp{}, fmt.Errorf("指标B不存在", nil)
+		return residual_analysis_model.ResidualAnalysisResp{}, fmt.Errorf("指标B不存在")
 	}
 
 	// 时间处理
@@ -53,15 +51,6 @@ func ResidualAnalysisPreview(req residual_analysis_model.ResidualAnalysisReq) (r
 
 	resp := residual_analysis_model.ResidualAnalysisResp{}
 
-	// 图表基础信息
-	baseChartInfo := new(residual_analysis_model.ResidualAnalysisChartInfo)
-	baseChartInfo.Calendar = `公历`
-	baseChartInfo.Source = utils.CHART_SOURCE_DEFAULT
-	baseChartInfo.DateType = req.DateType
-	baseChartInfo.StartDate = startDate
-	baseChartInfo.EndDate = endDate
-	baseChartInfo.ChartType = utils.CHART_TYPE_CURVE
-
 	// 原始图表信息
 	originalEdbList := make([]residual_analysis_model.ResidualAnalysisChartEdbInfoMapping, 0)
 
@@ -69,16 +58,21 @@ func ResidualAnalysisPreview(req residual_analysis_model.ResidualAnalysisReq) (r
 	if err != nil {
 		return residual_analysis_model.ResidualAnalysisResp{}, err
 	}
-	baseChartInfo.ChartName = edbInfoMappingA.EdbName + "与" + edbInfoMappingB.EdbName
+
+	originalChartInfo := createChartInfoResp(req, startDate, endDate, edbInfoMappingA.EdbName+"与"+edbInfoMappingB.EdbName)
 
 	resp.OriginalChartData = residual_analysis_model.ChartResp{
-		ChartInfo:   baseChartInfo,
+		ChartInfo:   originalChartInfo,
 		EdbInfoList: originalEdbList,
 	}
+	// 如果只需要第一张图表的数据 直接返回,避免继续处理
+	if req.QueryType == 1 {
+		return resp, nil
+	}
 
 	dataAList, ok := edbInfoMappingA.DataList.([]*data_manage.EdbDataList)
 	if !ok {
-		return residual_analysis_model.ResidualAnalysisResp{}, fmt.Errorf("数据类型转换失败", nil)
+		return residual_analysis_model.ResidualAnalysisResp{}, fmt.Errorf("数据类型转换失败")
 	}
 	indexADataMap := map[string]*data_manage.EdbDataList{}
 	for _, indexData := range dataAList {
@@ -86,34 +80,54 @@ func ResidualAnalysisPreview(req residual_analysis_model.ResidualAnalysisReq) (r
 	}
 
 	// 映射图表信息
-	mappingEdbList, err := fillMappingChartInfo(req, edbInfoMappingA, edbInfoMappingB, originalEdbList, indexADataMap)
+	mappingEdbList, a, b, r, err := fillMappingChartInfo(req, edbInfoMappingA, edbInfoMappingB, originalEdbList, indexADataMap)
 	if err != nil {
 		return residual_analysis_model.ResidualAnalysisResp{}, err
 	}
-	baseChartInfo.ChartName = edbInfoMappingA.EdbName + "与" + edbInfoMappingB.EdbName + "映射" + edbInfoMappingA.EdbName
+
+	mappingChartInfo := createChartInfoResp(req, startDate, endDate, edbInfoMappingA.EdbName+"与"+edbInfoMappingB.EdbName+"映射"+edbInfoMappingA.EdbName)
 
 	resp.MappingChartData = residual_analysis_model.ChartResp{
-		ChartInfo:   baseChartInfo,
+		ChartInfo:   mappingChartInfo,
 		EdbInfoList: mappingEdbList,
 	}
 
 	// 残差图表信息
-	ResidualEdbList, err := fillResidualChartInfo(edbInfoMappingA, edbInfoMappingB, mappingEdbList)
+	residualEdbList, R2, err := fillResidualChartInfo(edbInfoMappingA, edbInfoMappingB, mappingEdbList)
 	if err != nil {
 		return residual_analysis_model.ResidualAnalysisResp{}, err
 	}
 
-	baseChartInfo.ChartName = edbInfoMappingA.EdbName + "与" + edbInfoMappingA.EdbName + "映射残差/" + edbInfoMappingB.EdbName
+	residualChartInfo := createChartInfoResp(req, startDate, endDate, edbInfoMappingA.EdbName+"与"+edbInfoMappingA.EdbName+"映射残差/"+edbInfoMappingB.EdbName)
 
-	resp.MappingChartData = residual_analysis_model.ChartResp{
-		ChartInfo:   baseChartInfo,
-		EdbInfoList: ResidualEdbList,
+	resp.ResidualChartData = residual_analysis_model.ChartResp{
+		ChartInfo:   residualChartInfo,
+		EdbInfoList: residualEdbList,
+	}
+
+	if req.ResidualType == 2 {
+		resp.A = a
+		resp.B = b
+		resp.R = r
+		resp.R2 = R2
 	}
 
 	return resp, nil
 }
 
-func fillResidualChartInfo(edbInfoMappingA *data_manage.ChartEdbInfoMapping, edbInfoMappingB *data_manage.ChartEdbInfoMapping, mappingEdbList []residual_analysis_model.ResidualAnalysisChartEdbInfoMapping) ([]residual_analysis_model.ResidualAnalysisChartEdbInfoMapping, error) {
+func createChartInfoResp(req residual_analysis_model.ResidualAnalysisReq, startDate, endDate, chartName string) residual_analysis_model.ResidualAnalysisChartInfo {
+	return residual_analysis_model.ResidualAnalysisChartInfo{
+		Calendar:  `公历`,
+		Source:    utils.CHART_SOURCE_DEFAULT,
+		DateType:  req.DateType,
+		StartDate: startDate,
+		EndDate:   endDate,
+		ChartType: utils.CHART_TYPE_CURVE,
+		ChartName: chartName,
+	}
+}
+
+func fillResidualChartInfo(edbInfoMappingA *data_manage.ChartEdbInfoMapping, edbInfoMappingB *data_manage.ChartEdbInfoMapping, mappingEdbList []residual_analysis_model.ResidualAnalysisChartEdbInfoMapping) ([]residual_analysis_model.ResidualAnalysisChartEdbInfoMapping, float64, error) {
 	// 计算公式 映射残差 = 因变量指标 - 映射指标
 	var edbInfoA, edbInfoB residual_analysis_model.ResidualAnalysisChartEdbInfoMapping
 	if mappingEdbList[0].EdbInfoId == edbInfoMappingA.EdbInfoId {
@@ -125,59 +139,98 @@ func fillResidualChartInfo(edbInfoMappingA *data_manage.ChartEdbInfoMapping, edb
 	}
 	dataAList, ok := edbInfoA.DataList.([]*data_manage.EdbDataList)
 	if !ok {
-		return nil, fmt.Errorf("数据类型转换失败", nil)
+		return nil, 0, fmt.Errorf("数据类型转换失败")
 	}
-	edbData := dataAList
+	edbData := make([]*data_manage.EdbDataList, len(dataAList))
+	for i, data := range dataAList {
+		edbData[i] = &data_manage.EdbDataList{
+			Value: data.Value, // 确保为每个元素创建一个新的对象
+		}
+	}
+
 	dataBList, ok := edbInfoB.DataList.([]*data_manage.EdbDataList)
 	if !ok {
-		return nil, fmt.Errorf("数据类型转换失败", nil)
+		return nil, 0, fmt.Errorf("数据类型转换失败")
 	}
 	var indexDataBMap = make(map[string]*data_manage.EdbDataList)
 	for _, data := range dataBList {
 		indexDataBMap[data.DataTime] = data
 	}
-	var valueB float64
+	// 求R2
+	var valueB, sumValueA, averageValueA, residualQuadraticSum, totalQuadraticSum, R2 float64
+
+	for _, indexData := range edbData {
+		// 因变量的值总和
+		sumValueA += indexData.Value
+	}
+	// 因变量平均值
+	averageValueA = sumValueA / float64(len(edbData))
+
 	for _, indexData := range edbData {
 		if dataB, ok := indexDataBMap[indexData.DataTime]; ok {
 			valueB = dataB.Value
 		} else {
 			valueB = 0
 		}
+
+		// 总因变量平方和
+		totalQuadraticSum += math.Pow(indexData.Value-averageValueA, 2)
+
+		// 补全残差值
 		indexData.Value = indexData.Value - valueB
+
+		// 残差平方和
+		residualQuadraticSum += math.Pow(indexData.Value, 2)
 	}
-	for _, mapping := range mappingEdbList {
+	// 计算R2 公式:R2=1-SSE/SST R2越大,越符合线性  R2 = 1 - 残差平方和/总平方和
+	R2 = 1 - residualQuadraticSum/totalQuadraticSum
+
+	mappingEdb := make([]residual_analysis_model.ResidualAnalysisChartEdbInfoMapping, len(mappingEdbList))
+	copy(mappingEdb, mappingEdbList)
+
+	for i, mapping := range mappingEdb {
 		if mapping.EdbInfoId != edbInfoMappingA.EdbInfoId {
-			mapping.DataList = edbData
-			mapping.EdbName = edbInfoMappingA.EdbName + "映射残差/" + edbInfoMappingB.EdbName
+			mappingEdb[i].DataList = edbData
+			mappingEdb[i].EdbName = edbInfoMappingA.EdbName + "映射残差/" + edbInfoMappingB.EdbName
+			mappingEdb[i].IsAxis = 1
+			mappingEdb[i].ChartColor = `#00F`
+			mappingEdb[i].IsOrder = false
+		} else {
+			mappingEdb[i].IsAxis = 0
+			mappingEdb[i].ChartColor = `#F00`
 		}
 	}
 
-	return mappingEdbList, nil
+	return mappingEdb, R2, nil
 }
 
-func fillMappingChartInfo(req residual_analysis_model.ResidualAnalysisReq, edbInfoMappingA *data_manage.ChartEdbInfoMapping, edbInfoMappingB *data_manage.ChartEdbInfoMapping, originalEdbList []residual_analysis_model.ResidualAnalysisChartEdbInfoMapping, indexADataMap map[string]*data_manage.EdbDataList) ([]residual_analysis_model.ResidualAnalysisChartEdbInfoMapping, error) {
+func fillMappingChartInfo(req residual_analysis_model.ResidualAnalysisReq, edbInfoMappingA *data_manage.ChartEdbInfoMapping, edbInfoMappingB *data_manage.ChartEdbInfoMapping, originalEdbList []residual_analysis_model.ResidualAnalysisChartEdbInfoMapping, indexADataMap map[string]*data_manage.EdbDataList) ([]residual_analysis_model.ResidualAnalysisChartEdbInfoMapping, float64, float64, float64, error) {
 	// 计算公式:Y=aX+b,Y为映射后的指标,X为自变量指标
 	// 正序:a=(L2-L1)/(R2-R1)	b=L2-R2*a
 	// 逆序:a=(L2-L1)/(R1-R2)	b=L2-R1*a
 	// L2:左轴下限 R2:右轴上限 L1:左轴上限 R1:右轴下限
-	var a, b float64
-	if req.IsOrder {
-		a = (req.LeftIndexMax - req.LeftIndexMin) / (req.RightIndexMax - req.RightIndexMin)
-		b = req.LeftIndexMax - req.RightIndexMax*a
-	} else {
-		a = (req.LeftIndexMax - req.LeftIndexMin) / (req.RightIndexMin - req.RightIndexMax)
-		b = req.LeftIndexMax - req.RightIndexMin*a
+	var a, b, r float64
+
+	// 映射残差 计算a,b
+	if req.ResidualType == 1 {
+		if req.IsOrder {
+			a = (req.LeftIndexMax - req.LeftIndexMin) / (req.RightIndexMin - req.RightIndexMax)
+			b = req.LeftIndexMax - req.RightIndexMin*a
+		} else {
+			a = (req.LeftIndexMax - req.LeftIndexMin) / (req.RightIndexMax - req.RightIndexMin)
+			b = req.LeftIndexMax - req.RightIndexMax*a
+		}
 	}
 
 	dataList, ok := edbInfoMappingB.DataList.([]*data_manage.EdbDataList)
 	if !ok {
-		return nil, fmt.Errorf("数据类型转换失败", nil)
+		return nil, a, b, r, fmt.Errorf("数据类型转换失败")
 	}
 
 	// 领先指标 dataList进行数据处理
 	if req.IndexType == 2 {
 		if req.LeadValue < 0 {
-			return nil, fmt.Errorf("领先值不能小于0", nil)
+			return nil, a, b, r, fmt.Errorf("领先值不能小于0")
 		} else if req.LeadValue > 0 {
 			for _, indexData := range dataList {
 				switch req.LeadFrequency {
@@ -196,14 +249,12 @@ func fillMappingChartInfo(req residual_analysis_model.ResidualAnalysisReq, edbIn
 		}
 	}
 
+	// 指标B数据补充
 	for index := 0; index < len(dataList)-1; index++ {
 		// 获取当前数据和下一个数据
 		beforeIndexData := dataList[index]
 		afterIndexData := dataList[index+1]
 
-		// 计算映射值
-		beforeIndexData.Value = a*beforeIndexData.Value + b
-
 		// 从最早时间开始,补充时间为自然日
 		if utils.IsMoreThanOneDay(beforeIndexData.DataTime, afterIndexData.DataTime) {
 			// 创建补充数据
@@ -217,27 +268,55 @@ func fillMappingChartInfo(req residual_analysis_model.ResidualAnalysisReq, edbIn
 			dataList = append(dataList, &replenishIndexData)
 		}
 	}
+
+	// 拟合残差 计算a,b
+	var coordinateList []utils.Coordinate
+	if req.ResidualType == 2 {
+		for _, indexData := range dataList {
+			if _, ok := indexADataMap[indexData.DataTime]; ok {
+
+				coordinate := utils.Coordinate{
+					X: indexData.Value,
+					Y: indexADataMap[indexData.DataTime].Value,
+				}
+				coordinateList = append(coordinateList, coordinate)
+			}
+		}
+		a, b = utils.GetLinearResult(coordinateList)
+		r = utils.CalculationDecisive(coordinateList)
+	}
+
 	// 根据指标A的时间key,在B的映射指标中筛选出对应的值
 	var dataBList []*data_manage.EdbDataList
+
 	for _, indexData := range dataList {
 		if _, ok := indexADataMap[indexData.DataTime]; ok {
-			dataBList = append(dataBList, indexData)
+			indexDataCopy := *indexData
+
+			// 计算指标B映射值
+			indexDataCopy.Value = math.Round((a*indexData.Value+b)*10000) / 10000
+
+			// 将副本添加到 dataBList
+			dataBList = append(dataBList, &indexDataCopy)
 		}
 	}
 
-	mappingEdbList := originalEdbList
-	for _, mapping := range mappingEdbList {
-		if mapping.EdbInfoId == req.EdbInfoIdB {
-			mapping.EdbInfoId = 0
-			mapping.EdbCode = ""
-			mapping.EdbName = edbInfoMappingB.EdbName + "映射" + edbInfoMappingA.EdbName
-			mapping.DataList = dataList
+	mappingEdbList := make([]residual_analysis_model.ResidualAnalysisChartEdbInfoMapping, len(originalEdbList))
+	copy(mappingEdbList, originalEdbList)
+
+	for i, mapping := range mappingEdbList {
+		if mapping.EdbInfoId != req.EdbInfoIdA {
+			mappingEdbList[i].EdbInfoId = 0
+			mappingEdbList[i].EdbCode = ""
+			mappingEdbList[i].IsAxis = 1
+			mappingEdbList[i].EdbName = edbInfoMappingB.EdbName + "映射" + edbInfoMappingA.EdbName
+			mappingEdbList[i].DataList = dataBList
 		}
 	}
-	return mappingEdbList, nil
+	return mappingEdbList, a, b, r, nil
 }
 
-func fillOriginalChart(req residual_analysis_model.ResidualAnalysisReq, mappingList []*data_manage.ChartEdbInfoMapping, startDate string, endDate string, edbInfoMappingA *data_manage.ChartEdbInfoMapping, edbInfoMappingB *data_manage.ChartEdbInfoMapping, OriginalEdbList []residual_analysis_model.ResidualAnalysisChartEdbInfoMapping) ([]residual_analysis_model.ResidualAnalysisChartEdbInfoMapping, error) {
+func fillOriginalChart(req residual_analysis_model.ResidualAnalysisReq, mappingList []*data_manage.ChartEdbInfoMapping, startDate string, endDate string, edbInfoMappingA *data_manage.ChartEdbInfoMapping, edbInfoMappingB *data_manage.ChartEdbInfoMapping, originalEdbList []residual_analysis_model.ResidualAnalysisChartEdbInfoMapping) ([]residual_analysis_model.ResidualAnalysisChartEdbInfoMapping, error) {
 	for _, v := range mappingList {
 		var edbInfoMapping residual_analysis_model.ResidualAnalysisChartEdbInfoMapping
 		edbInfoMapping.EdbInfoType = 1
@@ -272,11 +351,15 @@ func fillOriginalChart(req residual_analysis_model.ResidualAnalysisReq, mappingL
 		edbInfoMapping.Frequency = v.Frequency
 		edbInfoMapping.Source = v.Source
 		edbInfoMapping.SourceName = v.SourceName
+		edbInfoMapping.MinValue = v.MinValue
+		edbInfoMapping.MaxValue = v.MaxValue
+		edbInfoMapping.LatestDate = v.LatestDate
+		edbInfoMapping.LatestValue = v.LatestValue
 
 		edbInfoMapping.DataList = dataList
-		OriginalEdbList = append(OriginalEdbList, edbInfoMapping)
+		originalEdbList = append(originalEdbList, edbInfoMapping)
 	}
-	return OriginalEdbList, nil
+	return originalEdbList, nil
 }
 
 func ContrastPreview(indexCode string) (residual_analysis_model.ResidualAnalysisChartEdbInfoMapping, error) {
@@ -300,10 +383,14 @@ func ContrastPreview(indexCode string) (residual_analysis_model.ResidualAnalysis
 
 	var resp residual_analysis_model.ResidualAnalysisChartEdbInfoMapping
 	resp.EdbInfoId = edbInfo.EdbInfoId
+	resp.EdbCode = edbInfo.EdbCode
+	resp.ChartColor = "#F00"
 	resp.SourceName = edbInfo.SourceName
 	resp.EdbName = edbInfo.EdbName
 	resp.Unit = edbInfo.Unit
 	resp.Frequency = edbInfo.Frequency
+	resp.MinValue = edbInfo.MinValue
+	resp.MaxValue = edbInfo.MaxValue
 	resp.DataList = dataList
 	return resp, nil
 }
@@ -316,75 +403,196 @@ func SaveResidualAnalysis(req residual_analysis_model.ResidualAnalysisIndexSaveR
 		return err
 	}
 	if classifyCount <= 0 {
-		return fmt.Errorf("分类不存在", nil)
+		return fmt.Errorf("分类不存在")
+	}
+
+	// 校验名称是否重复
+	var condition string
+	var pars []interface{}
+
+	condition += " and source = ? AND edb_name=?"
+	pars = append(pars, req.Source, req.EdbName)
+
+	edbInfoByCondition, err := data_manage.GetEdbInfoByCondition(condition, pars)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		return err
+	}
+
+	// 获取指标数据最大值 最小值 最后更新时间 最后更新时间对应的值
+	var indexMax, indexMin, indexLatestValue float64
+	var indexLatestDate string
+	if len(req.DataList) > 0 {
+		latestTime, _ := time.Parse(utils.YearMonthDay, req.DataList[0].DataTime)
+		for _, data := range req.DataList {
+			// 比较最大值
+			if data.Value > indexMax {
+				indexMax = data.Value
+			}
+
+			// 比较最小值
+			if data.Value < indexMin {
+				indexMin = data.Value
+			}
+
+			// 比较最新时间和对应值
+			currentTime, err := time.Parse(utils.YearMonthDay, data.DataTime)
+			if err != nil {
+				// 时间解析失败,跳过此项
+				continue
+			}
+
+			// 如果当前时间更晚
+			if currentTime.After(latestTime) {
+				latestTime = currentTime
+				indexLatestDate = data.DataTime
+				indexLatestValue = data.Value
+			}
+		}
 	}
 
+	// 更新保存指标和配置的映射关系
+	mappingList, err := residual_analysis_model.GetConfigMappingListByConfigId(req.ConfigId)
+	if err != nil {
+		return err
+	}
+
+	// 判断是更新还是修改 看指标配置映射中,是否存在对应指标 存在 则更新 不存在 则新增
+	var edbInfoMapping residual_analysis_model.CalculateResidualAnalysisConfigMapping
+	for _, mapping := range mappingList {
+		if req.IndexType == mapping.IndexType && req.IndexType != 0 {
+			edbInfoMapping = mapping
+		}
+	}
+
+	var edbInfoId int64
+	var edbCode string
 	// 更新or新增
-	if req.EdbCode != "" {
+	if edbInfoMapping.EdbInfoId > 0 {
 		// 查询指标库指标
-		edbInfo, err := data_manage.GetEdbInfoByEdbCode(utils.DATA_SOURCE_RESIDUAL_ANALYSIS, req.EdbCode)
+		edbInfo, err := data_manage.GetEdbInfoById(int(edbInfoMapping.EdbInfoId))
 		if err != nil {
 			return err
 		}
 		if edbInfo == nil {
-			return fmt.Errorf("指标不存在", nil)
+			return fmt.Errorf("指标不存在")
 		}
-		// todo 须补充更新指标最大值,最小值,数据最新时间,
-		err = edbInfo.Update([]string{"min_value", "max_value", "latest_date", "latest_value"})
+		edbInfoId = int64(edbInfo.EdbInfoId)
+		edbCode = edbInfo.EdbCode
+
+		if edbInfoByCondition != nil && edbInfoByCondition.EdbInfoId != edbInfo.EdbInfoId {
+			return fmt.Errorf("指标名称重复")
+		}
+
+		// 须补充更新指标最大值,最小值,数据最新时间,数据最新值
+		edbInfo.MaxValue = indexMax
+		edbInfo.MinValue = indexMin
+		edbInfo.LatestDate = indexLatestDate
+		edbInfo.LatestValue = indexLatestValue
+		edbInfo.Unit = req.Unit
+		edbInfo.Frequency = req.Frequency
+		edbInfo.ClassifyId = req.ClassifyId
+		edbInfo.EdbName = req.EdbName
+		err = edbInfo.Update([]string{"min_value", "max_value", "latest_date", "latest_value", "unit", "frequency", "classify_id", "edb_name"})
 		if err != nil {
 			return err
 		}
 
 		// 删除对应得指标数据
-		err = residual_analysis_model.DeleteResidualAnalysisDataByEdbCode(req.EdbCode)
+		err = residual_analysis_model.DeleteResidualAnalysisDataByEdbCode(edbInfo.EdbCode)
 		if err != nil {
-			return fmt.Errorf("删除指标数据失败", nil)
+			return fmt.Errorf("删除指标数据失败")
+		}
+	} else {
+		if edbInfoByCondition != nil {
+			return fmt.Errorf("指标名称重复")
 		}
-	}
-	// 新增指标
-	edbCode, err := utils.GenerateEdbCode(1, "")
-	if err != nil {
-		return err
-	}
 
-	_, err = data_manage.AddEdbInfo(&data_manage.EdbInfo{
-		EdbCode:    edbCode,
-		EdbName:    req.EdbName,
-		EdbNameEn:  req.EdbNameEn,
-		EdbType:    req.EdbType,
-		Unit:       req.Unit,
-		UnitEn:     req.UnitEn,
-		Frequency:  req.Frequency,
-		Source:     utils.DATA_SOURCE_RESIDUAL_ANALYSIS,
-		SourceName: "残差分析",
-	})
-	if err != nil {
-		return err
+		// 新增指标
+		edbCode, err = utils.GenerateEdbCode(1, "")
+		if err != nil {
+			return err
+		}
+
+		timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
+
+		edbInfoId, err = data_manage.AddEdbInfo(&data_manage.EdbInfo{
+			EdbCode:         edbCode,
+			UniqueCode:      utils.MD5(utils.CHART_PREFIX + "_" + timestamp),
+			EdbName:         req.EdbName,
+			EdbNameEn:       req.EdbNameEn,
+			ClassifyId:      req.ClassifyId,
+			EdbType:         req.EdbType,
+			Unit:            req.Unit,
+			UnitEn:          req.UnitEn,
+			Frequency:       req.Frequency,
+			Source:          req.Source,
+			SourceName:      "残差分析",
+			Calendar:        req.Calendar,
+			SysUserRealName: sysUser.RealName,
+			SysUserId:       sysUser.AdminId,
+			LatestDate:      indexLatestDate,
+			LatestValue:     indexLatestValue,
+			MinValue:        indexMin,
+			MaxValue:        indexMax,
+			CreateTime:      time.Now(),
+			ModifyTime:      time.Now(),
+		})
+		if err != nil {
+			return err
+		}
+
+		// 新增指标配置关系
+		_, err = residual_analysis_model.SaveConfigMapping(residual_analysis_model.CalculateResidualAnalysisConfigMapping{
+			CalculateResidualAnalysisConfigId: req.ConfigId,
+			EdbInfoId:                         edbInfoId,
+			ResidualType:                      req.ResidualType,
+			IndexType:                         req.IndexType,
+			CreateTime:                        time.Now(),
+			ModifyTime:                        time.Now(),
+		})
+		if err != nil {
+			return err
+		}
 	}
 
 	// 新增数据
-	edbInfoId, err := residual_analysis_model.AddResidualAnalysisData(req.DataList)
-	if err != nil {
-		return err
+	for i := range req.DataList {
+		req.DataList[i].EdbDataId = 0
+		req.DataList[i].EdbInfoId = int(edbInfoId)
+		req.DataList[i].EdbCode = edbCode
 	}
-
-	// 更新保存指标和配置的映射关系
-	mappingList, err := residual_analysis_model.GetConfigMappingListByConfigId(req.ConfigId)
+	_, err = residual_analysis_model.AddResidualAnalysisData(req.DataList)
 	if err != nil {
 		return err
 	}
-	flag := true
+
+	// 新增自变量 因变量与配置得关系 配置中不存在该指标,则新增
+	var indexMap = make(map[int64]residual_analysis_model.CalculateResidualAnalysisConfigMapping)
 	for _, mapping := range mappingList {
-		if mapping.CalculateResidualAnalysisConfigId == req.ConfigId {
-			flag = false
-		}
+		indexMap[mapping.EdbInfoId] = mapping
 	}
 
-	if flag {
+	if _, ok := indexMap[int64(req.EdbInfoIdA)]; !ok {
 		_, err = residual_analysis_model.SaveConfigMapping(residual_analysis_model.CalculateResidualAnalysisConfigMapping{
 			CalculateResidualAnalysisConfigId: req.ConfigId,
-			EdbInfoId:                         edbInfoId,
+			EdbInfoId:                         int64(req.EdbInfoIdA),
 			ResidualType:                      req.ResidualType,
+			IndexType:                         3,
+			CreateTime:                        time.Now(),
+			ModifyTime:                        time.Now(),
+		})
+		if err != nil {
+			return err
+		}
+	}
+	if _, ok := indexMap[int64(req.EdbInfoIdB)]; !ok {
+		_, err = residual_analysis_model.SaveConfigMapping(residual_analysis_model.CalculateResidualAnalysisConfigMapping{
+			CalculateResidualAnalysisConfigId: req.ConfigId,
+			EdbInfoId:                         int64(req.EdbInfoIdB),
+			ResidualType:                      req.ResidualType,
+			IndexType:                         4,
+			CreateTime:                        time.Now(),
+			ModifyTime:                        time.Now(),
 		})
 		if err != nil {
 			return err
@@ -408,7 +616,7 @@ func ResidualAnalysisDetail(edbInfoId int) (residual_analysis_model.ResidualAnal
 	}
 
 	if len(mappingList) <= 0 {
-		return residual_analysis_model.ResidualAnalysisDetailResp{}, fmt.Errorf("指标不存在", nil)
+		return residual_analysis_model.ResidualAnalysisDetailResp{}, fmt.Errorf("指标不存在")
 	}
 
 	mapping := mappingList[0]
@@ -419,8 +627,19 @@ func ResidualAnalysisDetail(edbInfoId int) (residual_analysis_model.ResidualAnal
 	}
 
 	var edbInfoIdList []int64
+	var edbInfoMap = make(map[int64]residual_analysis_model.CalculateResidualAnalysisConfigMapping)
+	var mappgingFlag = false
+	var residualFlag = false
 	for _, v := range configMappingList {
 		edbInfoIdList = append(edbInfoIdList, v.EdbInfoId)
+
+		edbInfoMap[v.EdbInfoId] = v
+
+		if v.IndexType == 1 {
+			mappgingFlag = true
+		} else if v.IndexType == 2 {
+			residualFlag = true
+		}
 	}
 
 	condition = ""
@@ -442,15 +661,76 @@ func ResidualAnalysisDetail(edbInfoId int) (residual_analysis_model.ResidualAnal
 		return residual_analysis_model.ResidualAnalysisDetailResp{}, err
 	}
 
+	var edbInfoListResp []*residual_analysis_model.DetailEdbInfoList
+	var dependentEdbInfo residual_analysis_model.DetailEdbInfoList
+	var independentEdbInfo residual_analysis_model.DetailEdbInfoList
+	for _, edbInfo := range edbInfoList {
+		var indexType int
+		if _, ok := edbInfoMap[int64(edbInfo.EdbInfoId)]; ok {
+			indexType = edbInfoMap[int64(edbInfo.EdbInfoId)].IndexType
+		}
+
+		info := residual_analysis_model.DetailEdbInfoList{
+			EdbInfoId:   edbInfo.EdbInfoId,
+			EdbInfoType: edbInfo.EdbInfoType,
+			IndexType:   indexType,
+			SourceName:  edbInfo.SourceName,
+			Source:      edbInfo.Source,
+			EdbCode:     edbInfo.EdbCode,
+			EdbName:     edbInfo.EdbName,
+			EdbNameEn:   edbInfo.EdbNameEn,
+			Unit:        edbInfo.Unit,
+			UnitEn:      edbInfo.UnitEn,
+			Frequency:   edbInfo.Frequency,
+			FrequencyEn: edbInfo.FrequencyEn,
+			ClassifyId:  edbInfo.ClassifyId,
+		}
+		edbInfoListResp = append(edbInfoListResp, &info)
+
+		if indexType == 3 {
+			dependentEdbInfo = info
+		} else if indexType == 4 {
+			independentEdbInfo = info
+		}
+	}
+
+	// 补充表格中 映射指标或者残差指标
+	if mappgingFlag && !residualFlag {
+		info := residual_analysis_model.DetailEdbInfoList{
+			IndexType:   2,
+			EdbName:     independentEdbInfo.EdbName + "映射残差/" + dependentEdbInfo.EdbName,
+			EdbNameEn:   dependentEdbInfo.EdbNameEn,
+			Unit:        dependentEdbInfo.Unit,
+			UnitEn:      dependentEdbInfo.UnitEn,
+			Frequency:   dependentEdbInfo.Frequency,
+			FrequencyEn: dependentEdbInfo.FrequencyEn,
+			ClassifyId:  dependentEdbInfo.ClassifyId,
+		}
+		edbInfoListResp = append(edbInfoListResp, &info)
+	} else if !mappgingFlag && residualFlag {
+		info := residual_analysis_model.DetailEdbInfoList{
+			IndexType:   1,
+			EdbName:     dependentEdbInfo.EdbName + "映射" + independentEdbInfo.EdbName,
+			EdbNameEn:   dependentEdbInfo.EdbNameEn,
+			Unit:        dependentEdbInfo.Unit,
+			UnitEn:      dependentEdbInfo.UnitEn,
+			Frequency:   dependentEdbInfo.Frequency,
+			FrequencyEn: dependentEdbInfo.FrequencyEn,
+			ClassifyId:  dependentEdbInfo.ClassifyId,
+		}
+		edbInfoListResp = append(edbInfoListResp, &info)
+	}
+
 	resp := residual_analysis_model.ResidualAnalysisDetailResp{
-		ConfigInfo:  &configInfo,
-		EdbInfoList: edbInfoList,
+		ConfigInfo:   &configInfo,
+		EdbInfoList:  edbInfoListResp,
+		ResidualType: mapping.ResidualType,
 	}
 
 	return resp, nil
 }
 
-func SaveResidualAnalysisConfig(req residual_analysis_model.ResidualAnalysisReq, sysUser *system.Admin) error {
+func SaveResidualAnalysisConfig(req residual_analysis_model.ResidualAnalysisReq, sysUser *system.Admin) (int64, error) {
 
 	config := residual_analysis_model.ResidualAnalysisConfigVo{
 		DateType:         req.DateType,
@@ -473,42 +753,80 @@ func SaveResidualAnalysisConfig(req residual_analysis_model.ResidualAnalysisReq,
 	// 转换为json格式
 	configJson, err := json.Marshal(config)
 	if err != nil {
-		return err
+		return 0, err
 	}
 
-	var condition string
-	var pars []interface{}
 	// 新增or更新
-	if req.EdbInfoId > 0 {
-		condition += " and edb_info_id=?"
-		pars = append(pars, req.EdbInfoId)
-		configMappings, err := residual_analysis_model.GetConfigMappingListByCondition(condition, pars)
-		if err != nil {
-			return err
-		}
-		if len(configMappings) > 0 {
-			mapping := configMappings[0]
-			configInfo, err := residual_analysis_model.GetResidualAnalysisConfigById(mapping.CalculateResidualAnalysisConfigId)
+	/*
+		var condition string
+			var pars []interface{}
+		if req.EdbInfoId > 0 {
+			condition += " and edb_info_id=?"
+			pars = append(pars, req.EdbInfoId)
+			configMappings, err := residual_analysis_model.GetConfigMappingListByCondition(condition, pars)
 			if err != nil {
-				return err
+				return 0, err
 			}
-			configInfo.Config = string(configJson)
-			err = residual_analysis_model.UpdateResidualAnalysisConfig(configInfo)
-			if err != nil {
-				return err
+			if len(configMappings) > 0 {
+				mapping := configMappings[0]
+				configInfo, err := residual_analysis_model.GetResidualAnalysisConfigById(mapping.CalculateResidualAnalysisConfigId)
+				if err != nil {
+					return 0, err
+				}
+				configInfo.Config = string(configJson)
+				err = residual_analysis_model.UpdateResidualAnalysisConfig(configInfo)
+				if err != nil {
+					return 0, err
+				}
 			}
+		}*/
+	var configId int64
+	if req.ConfigId > 0 {
+		configInfo, err := residual_analysis_model.GetResidualAnalysisConfigById(req.ConfigId)
+		if err != nil {
+			return 0, err
+		}
+		if configInfo.CalculateResidualAnalysisConfigId == 0 {
+			return 0, fmt.Errorf("未找到配置信息")
+		}
+
+		configId = int64(configInfo.CalculateResidualAnalysisConfigId)
+		configInfo.Config = string(configJson)
+
+		err = residual_analysis_model.UpdateResidualAnalysisConfig(configInfo)
+		if err != nil {
+			return 0, err
+		}
+	} else {
+		analysisConfig := residual_analysis_model.CalculateResidualAnalysisConfig{
+			Config:     string(configJson),
+			SysUserId:  sysUser.AdminId,
+			CreateTime: time.Now(),
+			ModifyTime: time.Now(),
 		}
-	}
 
-	analysisConfig := residual_analysis_model.CalculateResidualAnalysisConfig{
-		Config:    string(configJson),
-		SysUserId: sysUser.AdminId,
+		configId, err = residual_analysis_model.SaveResidualAnalysisConfig(analysisConfig)
+		if err != nil {
+			return 0, err
+		}
 	}
 
-	_, err = residual_analysis_model.SaveResidualAnalysisConfig(analysisConfig)
+	return configId, nil
+}
+
+func CheckResidualAnalysisExist(configId int) (int64, error) {
+
+	configMappingList, err := residual_analysis_model.GetConfigMappingListByConfigId(configId)
 	if err != nil {
-		return err
+		return 0, err
 	}
 
-	return nil
+	var configMapping residual_analysis_model.CalculateResidualAnalysisConfigMapping
+	for _, mapping := range configMappingList {
+		if mapping.IndexType == 2 {
+			configMapping = mapping
+		}
+	}
+
+	return configMapping.EdbInfoId, nil
 }

+ 45 - 0
utils/common.go

@@ -34,6 +34,7 @@ import (
 	"github.com/PuerkitoBio/goquery"
 	"github.com/microcosm-cc/bluemonday"
 	"github.com/shopspring/decimal"
+	"github.com/spaolacci/murmur3"
 	xhtml "golang.org/x/net/html"
 )
 
@@ -2790,3 +2791,47 @@ func RoundNumber(num string, decimalPlaces int, hasPercent bool) string {
 	}
 	return numStr
 }
+
+// MurmurHash64 计算字符串的64位哈希值
+func MurmurHash64(val []byte) uint64 {
+	hash64 := murmur3.New64()
+	hash64.Write(val)
+	return hash64.Sum64()
+}
+
+const base62Chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+
+type TUint interface {
+	uint64 | uint32 | uint16 | uint8 | uint
+}
+
+// ConvertNumToBase62 转换数字为base62编码
+func ConvertNumToBase62[T TUint](num T) string {
+	if num == 0 {
+		return string(base62Chars[0])
+	}
+
+	var result []byte
+	for num > 0 {
+		remainder := num % 62
+		result = append(result, base62Chars[remainder])
+		num /= 62
+	}
+
+	// 反转结果
+	for i, j := 0, len(result)-1; i < j; i, j = i+1, j-1 {
+		result[i], result[j] = result[j], result[i]
+	}
+
+	return string(result)
+}
+
+// IsAdminRole
+// @Description: 判断是否管理员角色
+// @author: Roc
+// @datetime 2024-11-12 09:40:48
+// @param roleTypeCode string
+// @return bool
+func IsAdminRole(roleTypeCode string) bool {
+	return roleTypeCode == ROLE_TYPE_CODE_ADMIN
+}

+ 6 - 3
utils/constants.go

@@ -187,8 +187,9 @@ const (
 	DATA_SOURCE_TRADE_ANALYSIS                       = 92       // 持仓分析
 	DATA_SOURCE_USDA_FAS                             = 96       //美国农业部->96
 	DATA_SOURCE_RZD                                  = 97       // 睿姿得数据
-	DATA_SOURCE_CALCULATE_STL                                  = 93       // 泛糖科技 -> 93
-	DATA_SOURCE_RESIDUAL_ANALYSIS                    = 99       // 残差分析
+	DATA_SOURCE_CALCULATE_STL                        = 98       // 泛糖科技 -> 93
+	DATA_SOURCE_MAPPING_RESIDUAL                     = 99       // 映射残差
+	DATA_SOURCE_FIT_RESIDUAL                         = 100      // 拟合残差
 )
 
 // 数据刷新频率
@@ -252,7 +253,9 @@ const (
 	CACHE_CREATE_REPORT_IMGPDF_QUEUE = "eta_report:report_img_pdf_queue" // 生成报告长图PDF队列
 	CACHE_EDB_TERMINAL_CODE_URL      = "edb:terminal_code:edb_code:"     // 指标与终端关系的缓存
 
-	CACHE_KEY_REPLACE_EDB = "eta:replace_edb" //系统用户操作日志队列
+	CACHE_KEY_REPLACE_EDB         = "eta:replace_edb"                 //系统用户操作日志队列
+	CACHE_REPORT_SHARE_SHORT_Url  = "eta:report_share_url:report_id:" //报告短链映射key
+	CACHE_REPORT_SHARE_ORIGIN_Url = "eta:report_share_url:token:"     //短链与原始报告链接的映射key
 )
 
 // 模板消息推送类型