瀏覽代碼

2.0发版

kobe6258 5 月之前
父節點
當前提交
86185b22c9

+ 12 - 6
controllers/analyst.go

@@ -174,11 +174,14 @@ func (this *AnalystController) List() {
 	analystList := make([]*models.AnalystView, 0)
 	for _, v := range list {
 		analystList = append(analystList, &models.AnalystView{
-			Id:           v.Id,
-			Name:         v.Name,
-			HeadImgURL:   v.HeadImgUrl,
-			Introduction: v.Introduction,
-			CreatedTime:  v.CreatedTime.Format(time.DateTime),
+			Id:                      v.Id,
+			Name:                    v.Name,
+			HeadImgURL:              v.HeadImgUrl,
+			Position:                v.Position,
+			InvestmentCertificate:   v.InvestmentCertificate,
+			ProfessionalCertificate: v.ProfessionalCertificate,
+			Introduction:            v.Introduction,
+			CreatedTime:             v.CreatedTime.Format(time.DateTime),
 		})
 
 	}
@@ -237,8 +240,11 @@ func (this *AnalystController) Edit() {
 		analyst.HeadImgUrl = req.HeadImgUrl
 	}
 	analyst.Introduction = req.Introduction
+	analyst.Position = req.Position
+	analyst.ProfessionalCertificate = req.ProfessionalCertificate
+	analyst.InvestmentCertificate = req.InvestmentCertificate
 	analyst.UpdatedTime = time.Now()
-	err = analyst.Update([]string{"HeadImgURL", "Introduction", "UpdatedTime"})
+	err = analyst.Update([]string{"HeadImgURL", "Introduction", "Position", "InvestmentCertificate", "ProfessionalCertificate", "UpdatedTime"})
 	if err != nil {
 		br.Msg = "编辑失败"
 		br.ErrMsg = "编辑失败, 系统错误,Err:" + err.Error()

+ 2 - 2
controllers/cutomer_product_risk_mapping.go

@@ -15,7 +15,7 @@ type CustomerProductRiskMappingController struct {
 	BaseAuthController
 }
 
-func sortStr(sort string) string {
+func sortStrFunc(sort string) string {
 	if sort == "desc" {
 		return "desc"
 	}
@@ -37,7 +37,7 @@ func (this *CustomerProductRiskMappingController) GetMappingList() {
 		this.ServeJSON()
 	}()
 	sort := this.GetString("Sort")
-	sort = sortStr(sort)
+	sort = sortStrFunc(sort)
 	list, err := models.GetMappingList(sort)
 	if err != nil {
 		br.Msg = "获取失败"

+ 854 - 0
controllers/order.go

@@ -0,0 +1,854 @@
+package controllers
+
+import (
+	"encoding/json"
+	"eta/eta_mini_crm_ht/models"
+	"eta/eta_mini_crm_ht/models/request"
+	"eta/eta_mini_crm_ht/models/response"
+	"eta/eta_mini_crm_ht/utils"
+	"fmt"
+	"github.com/google/uuid"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"github.com/xuri/excelize/v2"
+	"math/rand"
+	"net/http"
+	"net/url"
+	"strconv"
+	"strings"
+	"time"
+)
+
+var (
+	productCols = map[string]utils.ExcelColMapping{
+		"A": {"订单编号", "OrderID"},
+		"B": {"姓名", "RealName"},
+		"C": {"手机号", "Mobile"},
+		"D": {"商品名称", "ProductName"},
+		"E": {"商品类型", "ProductType"},
+		"F": {"商品价格", "TotalAmount"},
+		"G": {"有效期", "ValidDate"},
+		"H": {"订单状态", "Status"},
+		"I": {"支付渠道", "PaymentWay"},
+		"J": {"支付金额", "PaymentAmount"},
+		"K": {"售后状态", "RefundStatus"},
+		"L": {"付款时间", "PaymentTime"},
+		"M": {"下单时间", "CreatedTime"},
+	}
+
+	tradeCols = map[string]utils.ExcelColMapping{
+		"A": {"支付订单", "TransactionID"},
+		"B": {"订单编号", "OrderID"},
+		"C": {"姓名", "RealName"},
+		"D": {"手机号", "Mobile"},
+		"E": {"商品名称", "ProductName"},
+		"F": {"支付金额", "Amount"},
+		"G": {"支付状态", "PaymentStatus"},
+		"H": {"支付渠道", "PaymentWay"},
+		"I": {"支付账号", "PaymentAccount"},
+		"J": {"收款方", "MerchantID"},
+		"K": {"完成支付时间", "DealTime"},
+		"L": {"创建时间", "CreatedTime"},
+	}
+
+	refundCols = map[string]utils.ExcelColMapping{
+		"A": {"退款订单", "TransactionId"},
+		"B": {"订单编号", "OrderID"},
+		"C": {"姓名", "RealName"},
+		"D": {"手机号", "Mobile"},
+		"E": {"退款金额", "Amount"},
+		"F": {"退回账号", "PaymentAccount"},
+		"G": {"退款状态", "PaymentStatus"},
+		"H": {"完成退款时间", "DealTime"},
+		"I": {"创建时间", "CreatedTime"},
+	}
+	ProductOrderStatus = map[models.OrderStatus]string{
+		"pending":    "待支付",
+		"processing": "支付中",
+		"paid":       "已支付",
+		"closed":     "已关闭",
+		"refund":     "售后",
+	}
+
+	TradeOrderStatus = map[models.PaymentStatus]string{
+		"pending": "待支付",
+		"failed":  "支付失败",
+		"done":    "支付成功",
+	}
+
+	RefundOrderStatus = map[models.PaymentStatus]string{
+		"pending": "退款中",
+		"failed":  "退款失败",
+		"done":    "退款成功",
+	}
+	RefundStatusMap = map[models.RefundStatus]string{
+		"pending":    "待退款",
+		"processing": "退款中",
+		"failed":     "退款失败",
+		"success":    "退款成功",
+		"canceled":   "已取消",
+	}
+	ProductTypeMap = map[models.MerchantProductType]string{
+		"report":  "报告",
+		"video":   "视频",
+		"audio":   "音频",
+		"package": "套餐",
+	}
+	PaymentWayMap = map[models.PaymentWay]string{
+		"wechat": "微信",
+		"alipay": "支付宝",
+	}
+)
+
+type OrderController struct {
+	BaseAuthController
+}
+
+// ProductOrderList
+// @Title 商品订单列表
+// @Description 商品订单列表
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   ClassifyIds   query   string  true       "二级分类id,可多选用英文,隔开"
+// @Param   KeyWord   query   string  true       "报告标题/创建人"
+// @Param   SortType   query   string  true       "排序方式"
+// @Success 200 {object} models.ReportAuthorResp
+// @router /productOrderList [get]
+func (this *OrderController) ProductOrderList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	sortType := this.GetString("SortType")
+	KeyWord := this.GetString("KeyWord")
+	TemplateUserId, _ := this.GetInt("TemplateUserId", 0)
+	PaymentDate := this.GetString("PaymentDate")
+	PaymentWay := this.GetString("PaymentWay")
+	CreatedDate := this.GetString("CreatedDate")
+	ProductType := this.GetString("ProductType")
+	RefundStatus := this.GetString("RefundStatus")
+	OrderStatus := this.GetString("OrderStatus")
+	var condition string
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	if KeyWord != "" {
+		condition += " AND (product_name like '%" + KeyWord + "%' or real_name like '%" + KeyWord + "%'or order_id like '%" + KeyWord + "%' or mobile like '%" + KeyWord + "%')"
+	}
+	sortCondition := " ORDER BY created_time "
+	if sortType == "" {
+		sortType = "DESC"
+	}
+	if CreatedDate != "" {
+		condition += " AND Date(created_time) = '" + CreatedDate + "'"
+	}
+	if PaymentDate != "" {
+		condition += " AND Date(payment_time) = '" + PaymentDate + "'"
+	}
+	if PaymentWay != "" {
+		condition += " AND payment_way='" + PaymentWay + "'"
+	}
+	if TemplateUserId > 0 {
+		condition += fmt.Sprintf(" AND template_user_id=%d", TemplateUserId)
+	}
+	if OrderStatus != "" {
+		switch OrderStatus {
+		case "pending":
+			condition += " AND status='pending'"
+		case "paid":
+			condition += " AND status='paid'"
+		case "closed":
+			condition += " AND status='closed'"
+		case "refund":
+			condition += " AND status='refund'"
+			if RefundStatus != "" {
+				switch RefundStatus {
+				case "pending":
+					condition += " AND refund_status='pending'"
+				case "processing":
+					condition += " AND refund_status='processing'"
+				case "failed":
+					condition += " AND refund_status='failed'"
+				case "success":
+					condition += " AND refund_status='success'"
+				}
+			}
+		default:
+			br.Msg = "无效的订单状态"
+			br.ErrMsg = "无效的订单状态:" + OrderStatus
+			return
+		}
+	}
+	if ProductType != "" {
+		switch ProductType {
+		case "report":
+			condition += " AND product_type='" + string(models.ProductReport) + "'"
+		case "audio":
+			condition += " AND product_type='" + string(models.ProductAudio) + "'"
+		case "video":
+			condition += " AND product_type='" + string(models.ProductVideo) + "'"
+		case "package":
+			condition += " AND product_type='" + string(models.ProductPackage) + "'"
+		default:
+			br.Msg = "无效的产品类型"
+			br.ErrMsg = "无效的产品类型:" + ProductType
+			return
+		}
+	}
+
+	sortCondition = sortCondition + sortType
+	total, err := models.GetProductOrderCountByCondition(condition)
+	if err != nil {
+		br.Msg = "获取商品列表失败"
+		br.ErrMsg = "获取商品列表失败,Err:" + err.Error()
+		return
+	}
+
+	startSize := utils.StartIndex(currentIndex, pageSize)
+	List, err := models.GetProductOrderByCondition(condition, sortCondition, startSize, pageSize)
+	if err != nil {
+		br.Msg = "获取商品列表失败"
+		br.ErrMsg = "获取商品列表失败,Err:" + err.Error()
+		return
+	}
+	var ListView []*models.ProductOrderView
+	for _, orderItem := range List {
+		view := &models.ProductOrderView{
+			OrderID:       orderItem.OrderId,
+			RealName:      orderItem.RealName,
+			Mobile:        fmt.Sprintf("+%s %s", orderItem.AreaCode, orderItem.Mobile),
+			ProductType:   ProductTypeMap[orderItem.ProductType],
+			ProductName:   orderItem.ProductName,
+			TotalAmount:   fmt.Sprintf("¥%s", orderItem.TotalAmount),
+			TradeNO:       orderItem.TradeNo,
+			RefundAmount:  orderItem.RefundAmount,
+			RefundTradeId: orderItem.RefundTradeId,
+			PaymentWay:    PaymentWayMap[orderItem.PaymentWay],
+			Status:        ProductOrderStatus[orderItem.Status],
+			RefundStatus:  RefundStatusMap[orderItem.RefundStatus],
+			Remark:        orderItem.Remark,
+			ValidDuration: orderItem.ValidDuration,
+			CreatedTime:   orderItem.CreatedTime.Format(time.DateTime),
+		}
+		if orderItem.TradeNo != "" {
+			view.PaymentTime = orderItem.PaymentTime.Format(time.DateTime)
+			tradeOrder, tradeErr := models.GetTradeOrderByNo(orderItem.TradeNo)
+			if tradeErr != nil {
+				utils.FileLog.Error("获取支付订单失败,支付订单号:" + orderItem.TradeNo + ",err:" + tradeErr.Error())
+			} else {
+				view.PaymentAmount = fmt.Sprintf("¥%s", tradeOrder.Amount)
+			}
+		}
+		if orderItem.Status == models.OrderStatusRefund && orderItem.RefundStatus == models.RefundStatusSuccess {
+			view.RefundFinishTime = orderItem.RefundFinishTime.Format(time.DateTime)
+		}
+		ListView = append(ListView, view)
+	}
+	page := paging.GetPaging(currentIndex, pageSize, total)
+	resp := new(response.ProductOrderListResp)
+	resp.List = ListView
+	resp.Paging = page
+	br.Ret = 200
+	br.Success = true
+	br.Data = resp
+	br.Msg = "获取成功"
+}
+
+// TradeOrderList
+// @Title 支付订单列表
+// @Description 支付订单列表
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   ClassifyIds   query   string  true       "二级分类id,可多选用英文,隔开"
+// @Param   KeyWord   query   string  true       "报告标题/创建人"
+// @Param   SortType   query   string  true       "排序方式"
+// @Success 200 {object} models.ReportAuthorResp
+// @router /tradeOrderList [get]
+func (this *OrderController) TradeOrderList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	sortType := this.GetString("SortType")
+	KeyWord := this.GetString("KeyWord")
+	DealDate := this.GetString("DealDate")
+	PaymentWay := this.GetString("PaymentWay")
+	CreatedDate := this.GetString("CreatedDate")
+	OrderStatus := this.GetString("OrderStatus")
+	IsRefund, _ := this.GetBool("IsRefund", false)
+	var condition string
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	if IsRefund {
+		condition += " AND payment_type ='" + string(models.PaymentTypeRefund) + "'"
+	} else {
+		condition += " AND payment_type ='" + string(models.PaymentTypePay) + "'"
+	}
+	if KeyWord != "" {
+		condition += " AND (product_name like '%" + KeyWord + "%' or real_name like '%" + KeyWord + "%' or product_order_id like '%" + KeyWord + "%' or mobile like '%" + KeyWord + "%')"
+	}
+	sortCondition := " ORDER BY created_time "
+	if sortType == "" {
+		sortType = "DESC"
+	}
+	if CreatedDate != "" {
+		condition += " AND Date(created_time) = '" + CreatedDate + "'"
+	}
+	if DealDate != "" {
+		condition += " AND Date(deal_time) = '" + DealDate + "'"
+	}
+	if PaymentWay != "" {
+		condition += " AND payment_way='" + PaymentWay + "'"
+	}
+
+	if OrderStatus != "" {
+		switch OrderStatus {
+		case "pending":
+			condition += " AND payment_status='pending'"
+		case "done":
+			condition += " AND payment_status='done'"
+		case "failed":
+			condition += " AND payment_status='failed'"
+		default:
+			br.Msg = "无效的支付订单状态"
+			br.ErrMsg = "无效的支付订单状态:" + OrderStatus
+			return
+		}
+	}
+	sortCondition = sortCondition + sortType
+	total, err := models.GetTradeOrderCountByCondition(condition)
+	if err != nil {
+		br.Msg = "获取支付明细列表失败"
+		br.ErrMsg = "获取支付明细列表失败,Err:" + err.Error()
+		return
+	}
+
+	startSize := utils.StartIndex(currentIndex, pageSize)
+	List, err := models.GetTradeOrderByCondition(condition, sortCondition, startSize, pageSize)
+	if err != nil {
+		br.Msg = "获取支付明细列表失败"
+		br.ErrMsg = "获取支付明细列表失败,Err:" + err.Error()
+		return
+	}
+	var ListView []*models.TradeOrderView
+	for i := 0; i < len(List); i++ {
+		order := List[i]
+		view := &models.TradeOrderView{
+			RealName:       order.RealName,
+			Mobile:         fmt.Sprintf("+%s %s", order.AreaCode, order.Mobile),
+			ProductName:    order.ProductName,
+			Amount:         fmt.Sprintf("¥%s", order.Amount),
+			TransactionID:  order.TransactionId,
+			ProductOrderID: order.ProductOrderId,
+			PaymentWay:     PaymentWayMap[order.PaymentWay],
+			PaymentAccount: order.PaymentAccount,
+			MerchantID:     order.MerchantId,
+
+			CreatedTime: order.CreatedTime.Format(time.DateTime),
+		}
+		if order.PaymentStatus == models.PaymentStatusDone {
+			view.DealTime = order.DealTime.Format(time.DateTime)
+		}
+		if IsRefund {
+			view.PaymentStatus = RefundOrderStatus[order.PaymentStatus]
+		} else {
+			view.PaymentStatus = TradeOrderStatus[order.PaymentStatus]
+		}
+		ListView = append(ListView, view)
+	}
+	page := paging.GetPaging(currentIndex, pageSize, total)
+	resp := new(response.TradeOrderListResp)
+	resp.List = ListView
+	resp.Paging = page
+	br.Ret = 200
+	br.Success = true
+	br.Data = resp
+	br.Msg = "获取成功"
+}
+
+// ExportProductOrder
+// @Title 临时用户列表
+// @Description 临时用户列表
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   Keyword   query   string  false       "手机号"
+// @Param   SortParam   query   string  false       "排序字段参数,用来排序的字段, 枚举值:0:注册时间,1:阅读数,2:最近一次阅读时间"
+// @Param   SortType   query   string  true       "如何排序,是正序还是倒序,0:倒序,1:正序"
+// @Success 200 {object} response.TemplateUserListResp
+// @router /productOrder/export [get]
+func (this *OrderController) ExportProductOrder() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	sortType := this.GetString("SortType")
+	KeyWord := this.GetString("KeyWord")
+
+	PaymentDate := this.GetString("PaymentDate")
+	PaymentWay := this.GetString("PaymentWay")
+	CreatedDate := this.GetString("CreatedDate")
+	ProductType := this.GetString("ProductType")
+	RefundStatus := this.GetString("RefundStatus")
+	OrderStatus := this.GetString("OrderStatus")
+	var condition string
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	if KeyWord != "" {
+		condition += " AND (product_name like '%" + KeyWord + "%' or real_name like '%" + KeyWord + "%' order_id like '%" + KeyWord + "%' or mobile like '%" + KeyWord + "%')"
+	}
+	sortCondition := " ORDER BY created_time "
+	if sortType == "" {
+		sortType = "DESC"
+	}
+	if CreatedDate != "" {
+		condition += " AND Date(created_time) = '" + CreatedDate + "'"
+	}
+	if PaymentDate != "" {
+		condition += " AND Date(payment_time) = '" + PaymentDate + "'"
+	}
+	if PaymentWay != "" {
+		condition += " AND payment_way='" + PaymentWay + "'"
+	}
+
+	if OrderStatus != "" {
+		switch OrderStatus {
+		case "pending":
+			condition += " AND status='pending'"
+		case "paid":
+			condition += " AND status='paid'"
+		case "closed":
+			condition += " AND status='closed'"
+		case "refund":
+			condition += " AND status='refund'"
+			if RefundStatus != "" {
+				switch RefundStatus {
+				case "pending":
+					condition += " AND refund_status='pending'"
+				case "processing":
+					condition += " AND refund_status='processing'"
+				case "failed":
+					condition += " AND refund_status='failed'"
+				case "success":
+					condition += " AND refund_status='success'"
+				}
+			}
+		default:
+			br.Msg = "无效的订单状态"
+			br.ErrMsg = "无效的订单状态:" + OrderStatus
+			return
+		}
+	}
+	if ProductType != "" {
+		switch ProductType {
+		case "report":
+			condition += " AND product_type='" + string(models.ProductReport) + "'"
+		case "audio":
+			condition += " AND product_type='" + string(models.ProductAudio) + "'"
+		case "video":
+			condition += " AND product_type='" + string(models.ProductVideo) + "'"
+		case "package":
+			condition += " AND product_type='" + string(models.ProductPackage) + "'"
+		default:
+			br.Msg = "无效的产品类型"
+			br.ErrMsg = "无效的产品类型:" + ProductType
+			return
+		}
+	}
+	sortCondition = sortCondition + sortType
+	List, err := models.GetProductOrderListByCondition(condition, sortCondition)
+	if err != nil {
+		br.Msg = "导出商品订单失败"
+		br.ErrMsg = "导出商品订单失败,Err:" + err.Error()
+		return
+	}
+	var ListView []models.ProductOrderView
+	for _, orderItem := range List {
+		view := models.ProductOrderView{
+			OrderID:       orderItem.OrderId,
+			RealName:      orderItem.RealName,
+			Mobile:        fmt.Sprintf("+%s %s", orderItem.AreaCode, orderItem.Mobile),
+			ProductType:   ProductTypeMap[orderItem.ProductType],
+			ProductName:   orderItem.ProductName,
+			TotalAmount:   fmt.Sprintf("¥%s", orderItem.TotalAmount),
+			TradeNO:       orderItem.TradeNo,
+			RefundAmount:  orderItem.RefundAmount,
+			RefundTradeId: orderItem.RefundTradeId,
+			PaymentWay:    PaymentWayMap[orderItem.PaymentWay],
+			PaymentTime:   orderItem.PaymentTime.Format(time.DateTime),
+			Status:        ProductOrderStatus[orderItem.Status],
+			RefundStatus:  RefundStatusMap[orderItem.RefundStatus],
+			Remark:        orderItem.Remark,
+			CreatedTime:   orderItem.CreatedTime.Format(time.DateTime),
+			ValidDuration: orderItem.ValidDuration,
+		}
+		if orderItem.TradeNo != "" {
+			view.PaymentTime = orderItem.PaymentTime.Format(time.DateTime)
+			tradeOrder, tradeErr := models.GetTradeOrderByNo(orderItem.TradeNo)
+			if tradeErr != nil {
+				utils.FileLog.Error("获取支付订单失败,支付订单号:" + orderItem.TradeNo + ",err:" + tradeErr.Error())
+			} else {
+				view.PaymentAmount = fmt.Sprintf("¥%s", tradeOrder.Amount)
+			}
+		}
+		//if orderItem.Status == models.OrderStatusPaid {
+		//	access, accessErr := models.GetAccess(orderItem.ProductId, orderItem.TemplateUserId)
+		//	if accessErr != nil {
+		//		utils.FileLog.Error("获取用户订阅记录失败,templateUserId:" + string(rune(orderItem.TemplateUserId)) + "productId:" + string(rune(orderItem.ProductId)) + ",err:" + accessErr.Error())
+		//	} else {
+		//		if access.ProductType == models.ProductPackage {
+		//			view.ValidDuration = fmt.Sprintf("%s~%s", access.BeginDate.Format(time.DateOnly), access.EndDate.Format(time.DateOnly))
+		//		} else {
+		//			view.ValidDuration = "永久有效"
+		//		}
+		//	}
+		//}
+		if orderItem.Status == models.OrderStatusRefund && orderItem.RefundStatus == models.RefundStatusSuccess {
+			view.RefundFinishTime = orderItem.RefundFinishTime.Format(time.DateTime)
+		}
+		ListView = append(ListView, view)
+	}
+	year, month, day := time.Now().Date()
+	yearStr := strconv.Itoa(year)[2:]
+	fileName := fmt.Sprintf("商品订单%s.%d.%d.xlsx", yearStr, month, day)
+	file, err := utils.ExportExcel("商品订单", productCols, ListView)
+	_ = this.downloadExcelFile(file, fileName)
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "下载成功"
+}
+
+// ExportTradeOrder
+// @Title 临时用户列表
+// @Description 临时用户列表
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   Keyword   query   string  false       "手机号"
+// @Param   SortParam   query   string  false       "排序字段参数,用来排序的字段, 枚举值:0:注册时间,1:阅读数,2:最近一次阅读时间"
+// @Param   SortType   query   string  true       "如何排序,是正序还是倒序,0:倒序,1:正序"
+// @Success 200 {object} response.TemplateUserListResp
+// @router /tradeOrder/export [get]
+func (this *OrderController) ExportTradeOrder() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	sortType := this.GetString("SortType")
+	KeyWord := this.GetString("KeyWord")
+	DealDate := this.GetString("DealDate")
+	PaymentWay := this.GetString("PaymentWay")
+	CreatedDate := this.GetString("CreatedDate")
+	OrderStatus := this.GetString("OrderStatus")
+	IsRefund, _ := this.GetBool("IsRefund", false)
+	var condition string
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	if IsRefund {
+		condition += " AND payment_type ='" + string(models.PaymentTypeRefund) + "'"
+	} else {
+		condition += " AND payment_type ='" + string(models.PaymentTypePay) + "'"
+	}
+	if KeyWord != "" {
+		condition += " AND (product_name like '%" + KeyWord + "%' or real_name like '%" + KeyWord + "%' or product_order_id like '%" + KeyWord + "%' or mobile like '%" + KeyWord + "%')"
+	}
+	sortCondition := " ORDER BY created_time "
+	if sortType == "" {
+		sortType = "DESC"
+	}
+	if CreatedDate != "" {
+		condition += " AND Date(created_time) = '" + CreatedDate + "'"
+	}
+	if DealDate != "" {
+		condition += " AND Date(deal_time) = '" + DealDate + "'"
+	}
+	if PaymentWay != "" {
+		condition += " AND payment_way='" + PaymentWay + "'"
+	}
+
+	if OrderStatus != "" {
+		switch OrderStatus {
+		case "pending":
+			condition += " AND payment_status='pending'"
+		case "done":
+			condition += " AND payment_status='done'"
+		case "failed":
+			condition += " AND payment_status='failed'"
+		default:
+			br.Msg = "无效的支付订单状态"
+			br.ErrMsg = "无效的支付订单状态:" + OrderStatus
+			return
+		}
+	}
+	sortCondition = sortCondition + sortType
+	List, err := models.GetTradeOrderListByCondition(condition, sortCondition)
+	if err != nil {
+		br.Msg = "获取支付明细列表失败"
+		br.ErrMsg = "获取支付明细列表失败,Err:" + err.Error()
+		return
+	}
+	var ListView []models.TradeOrderView
+	for i := 0; i < len(List); i++ {
+		order := List[i]
+		view := models.TradeOrderView{
+			RealName:       order.RealName,
+			Mobile:         fmt.Sprintf("+%s %s", order.AreaCode, order.Mobile),
+			ProductName:    order.ProductName,
+			Amount:         fmt.Sprintf("¥%s", order.Amount),
+			TransactionID:  order.TransactionId,
+			ProductOrderID: order.ProductOrderId,
+			PaymentWay:     PaymentWayMap[order.PaymentWay],
+			PaymentAccount: order.PaymentAccount,
+			MerchantID:     order.MerchantId,
+			CreatedTime:    order.CreatedTime.Format(time.DateTime),
+		}
+		if order.PaymentStatus == models.PaymentStatusDone {
+			view.DealTime = order.DealTime.Format(time.DateTime)
+		}
+		if IsRefund {
+			view.PaymentStatus = RefundOrderStatus[order.PaymentStatus]
+		} else {
+			view.PaymentStatus = TradeOrderStatus[order.PaymentStatus]
+		}
+		ListView = append(ListView, view)
+	}
+	year, month, day := time.Now().Date()
+	yearStr := strconv.Itoa(year)[2:]
+	if IsRefund {
+		fileName := fmt.Sprintf("退款明细%s.%d.%d.xlsx", yearStr, month, day)
+		file, _ := utils.ExportExcel("退款明细", refundCols, ListView)
+		_ = this.downloadExcelFile(file, fileName)
+	} else {
+		fileName := fmt.Sprintf("支付明细%s.%d.%d.xlsx", yearStr, month, day)
+		file, _ := utils.ExportExcel("支付明细", tradeCols, ListView)
+		_ = this.downloadExcelFile(file, fileName)
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "下载成功"
+}
+
+// encodeChineseFilename 将中文文件名编码为 ISO-8859-1
+func (this *OrderController) downloadExcelFile(file *excelize.File, filename string) (err error) {
+	// 对文件名进行 ISO-8859-1 编码
+	fn := url.QueryEscape(filename)
+	if filename == fn {
+		fn = "filename=" + fn
+	} else {
+		fn = "filename=" + filename + "; filename*=utf-8''" + fn
+	}
+	this.Ctx.ResponseWriter.Header().Set("Access-Control-Expose-Headers", "Content-Disposition")
+	this.Ctx.ResponseWriter.Header().Set("Content-Disposition", "attachment; "+fn)
+	this.Ctx.ResponseWriter.Header().Set("Content-Description", "File Transfer")
+	this.Ctx.ResponseWriter.Header().Set("Content-Type", "application/octet-stream")
+	this.Ctx.ResponseWriter.Header().Set("Content-Transfer-Encoding", "binary")
+	this.Ctx.ResponseWriter.Header().Set("Expires", "0")
+	this.Ctx.ResponseWriter.Header().Set("Cache-Control", "must-revalidate")
+	this.Ctx.ResponseWriter.Header().Set("Pragma", "public")
+	this.Ctx.ResponseWriter.Header().Set("File-Name", filename)
+	// 写入文件
+	if err = file.Write(this.Ctx.ResponseWriter); err != nil {
+		utils.FileLog.Error("导出excel文件失败:", err)
+		http.Error(this.Ctx.ResponseWriter, "导出excel文件失败", http.StatusInternalServerError)
+	}
+	return
+}
+
+// Refund
+// @Title 退款
+// @Description 退款
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   ClassifyIds   query   string  true       "二级分类id,可多选用英文,隔开"
+// @Param   KeyWord   query   string  true       "报告标题/创建人"
+// @Param   SortType   query   string  true       "排序方式"
+// @Success 200 {object} models.ReportAuthorResp
+// @router /refund [post]
+func (this *OrderController) Refund() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req request.RefundReq
+	if err := json.Unmarshal(this.Ctx.Input.RequestBody, &req); err != nil {
+		br.Msg = "参数解析失败"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ProductOrderNo == "" {
+		br.Msg = "商品订单号不能为空"
+		br.ErrMsg = "商品订单号不能为空"
+		return
+	}
+	productOrder, err := models.GetProductOrderByID(req.ProductOrderNo)
+	if err != nil {
+		br.Msg = "获取商品订单失败"
+		br.ErrMsg = "获取商品订单失败,err:" + err.Error()
+		return
+	}
+	if productOrder.Status == models.OrderStatusPending {
+		br.Msg = "退款失败,"
+		br.ErrMsg = "退款失败,退款状态异常,当前订单已关闭"
+		return
+	}
+	if productOrder.Status == models.OrderStatusRefund && productOrder.RefundStatus != models.RefundStatusFailed {
+		br.Msg = "退款失败,"
+		br.ErrMsg = "退款失败,当前订单退款处理中"
+		return
+	}
+	tradeOrder, err := models.GetTradeOrderByNo(productOrder.TradeNo)
+	if err != nil {
+		br.Msg = "退款失败,获取原订单失败"
+		br.ErrMsg = "退款失败,获取原订单失败,err:" + err.Error()
+		return
+	}
+	if tradeOrder.PaymentType != models.PaymentTypePay {
+		br.Msg = "退款失败,原订单非支付订单"
+		br.ErrMsg = "退款失败,原订单非支付订单"
+		return
+	}
+	if tradeOrder.PaymentStatus != models.PaymentStatusDone {
+		br.Msg = "退款失败,原订单未完成支付"
+		br.ErrMsg = "退款失败,原订单未完成支付"
+		return
+	}
+	uuidStr := strings.ReplaceAll(uuid.New().String(), "-", "")
+	key := fmt.Sprintf("refund_lock_%s", productOrder.OrderId)
+	defer func() {
+		utils.ReleaseLock(key, uuidStr)
+	}()
+	if utils.AcquireLock(key, 10, uuidStr) {
+		aa := GenerateProductOrderNo()
+		refundOrder := &models.TradeOrder{
+			TransactionId:    aa,
+			OrgTransactionId: productOrder.TradeNo,
+			ProductOrderId:   productOrder.OrderId,
+			PaymentAccount:   tradeOrder.PaymentAccount,
+			PaymentWay:       tradeOrder.PaymentWay,
+			ProductName:      productOrder.ProductName,
+			Mobile:           productOrder.Mobile,
+			AreaCode:         productOrder.AreaCode,
+			RealName:         productOrder.RealName,
+			Amount:           tradeOrder.Amount,
+			Currency:         tradeOrder.Currency,
+			UserId:           tradeOrder.UserId,
+			TemplateUserId:   tradeOrder.TemplateUserId,
+			PaymentType:      models.PaymentTypeRefund,
+			PaymentStatus:    models.PaymentStatusPending,
+			CreatedTime:      time.Now(),
+		}
+		productOrder.RefundStatus = models.RefundStatusProcessing
+		productOrder.Status = models.OrderStatusRefund
+		productOrder.RefundTradeId = refundOrder.TransactionId
+		productOrder.Remark = req.Remark
+		productOrder.RefundAmount = tradeOrder.Amount
+		err = refundOrder.Refund(productOrder)
+		if err != nil {
+			br.Msg = "退款失败"
+			br.ErrMsg = "退款失败,,Err:" + err.Error()
+			return
+		}
+		refundflow := models.RefundDealFlow{
+			ProductOrderNo: productOrder.OrderId,
+			RefundOrderNo:  refundOrder.TransactionId,
+			OperatorUserId: this.SysUser.SysUserId,
+			CreatedTime:    time.Now(),
+		}
+		//增加一条退款流水
+		_ = refundflow.Insert()
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "退款处理成功"
+	} else {
+		br.Msg = "退款失败,当前订单正在退款中"
+		br.ErrMsg = "退款失败,当前订单正在退款中"
+		return
+	}
+}
+func GenerateProductOrderNo() string {
+	timestamp := time.Now().UnixNano() / 1000000 // 毫秒级时间戳
+	// 生成随机数
+	rand.New(rand.NewSource(time.Now().UnixNano()))
+	randomPart := rand.Intn(999999)
+	// 格式化订单号
+	orderNumber := fmt.Sprintf("R%d%06d", timestamp, randomPart)
+	return orderNumber
+}
+
+// RefundDetail
+// @Title 退款详情
+// @Description 退款详情
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   ClassifyIds   query   string  true       "二级分类id,可多选用英文,隔开"
+// @Param   KeyWord   query   string  true       "报告标题/创建人"
+// @Param   SortType   query   string  true       "排序方式"
+// @Success 200 {object} models.ReportAuthorResp
+// @router /refundDetail [get]
+func (this *OrderController) RefundDetail() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	ProductOrderNo := this.GetString("ProductOrderNo")
+	if ProductOrderNo == "" {
+		br.Msg = "商品订单号不能为空"
+		br.ErrMsg = "商品订单号不能为空"
+		return
+	}
+	productOrder, err := models.GetProductOrderByID(ProductOrderNo)
+	if err != nil {
+		br.Msg = "获取商品订单失败"
+		br.ErrMsg = "获取商品订单失败,err:" + err.Error()
+		return
+	}
+	if productOrder.Status != models.OrderStatusRefund && productOrder.RefundStatus != models.RefundStatusSuccess {
+		br.Msg = "当前订单未完成退款"
+		br.ErrMsg = "当前订单未完成退款"
+		return
+	}
+	refundOrder, err := models.GetTradeOrderByNo(productOrder.RefundTradeId)
+	if err != nil {
+		br.Msg = "获取退款订单失败"
+		br.ErrMsg = "获取退款订单失败,err:" + err.Error()
+		return
+	}
+	refundResp := response.RefundResp{
+		Account:          refundOrder.PaymentAccount,
+		RealName:         productOrder.RealName,
+		RefundAmount:     productOrder.RefundAmount,
+		RefundFinishTime: productOrder.RefundFinishTime.Format(time.DateTime),
+		Remark:           productOrder.Remark,
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Data = refundResp
+	br.Msg = "退款详情获取成功"
+	return
+}

+ 138 - 46
controllers/product.go

@@ -13,6 +13,8 @@ import (
 	"github.com/rdlucklib/rdluck_tools/paging"
 	"github.com/shopspring/decimal"
 	"math/rand"
+	"os"
+	"path"
 	"strconv"
 	"strings"
 	"sync"
@@ -75,11 +77,10 @@ func (this *ProductController) UnSetProductList() {
 	if KeyWord != "" {
 		switch ProductType {
 		case "report":
-			condition += " AND title like '%?%'"
+			condition += " AND title like '%" + KeyWord + "%' "
 		case "media":
-			condition += " AND media_name like '%?%'"
+			condition += " AND media_name like '%" + KeyWord + "%' "
 		}
-		pars = append(pars, KeyWord)
 	}
 	sortCondition := " ORDER BY published_time "
 	if sortType == "" {
@@ -117,13 +118,13 @@ func (this *ProductController) UnSetProductList() {
 			defer wg.Done()
 			switch product.ProductType {
 			case "report":
-				product.RiskLevel, _, _ = services.GetRiskLevel("report", product.SourceId)
+				product.RiskLevel, _, product.PermissionNames, _ = services.GetRiskLevel("report", product.SourceId)
 				product.ProductType = "报告"
 			case "video":
-				product.RiskLevel, _, _ = services.GetRiskLevel("media", product.SourceId)
+				product.RiskLevel, _, product.PermissionNames, _ = services.GetRiskLevel("media", product.SourceId)
 				product.ProductType = "视频"
 			case "audio":
-				product.RiskLevel, _, _ = services.GetRiskLevel("media", product.SourceId)
+				product.RiskLevel, _, product.PermissionNames, _ = services.GetRiskLevel("media", product.SourceId)
 				product.ProductType = "音频"
 			}
 		}(product)
@@ -183,24 +184,29 @@ func (this *ProductController) AddProduct() {
 				//br.ErrMsg = "默认套餐封面获取失败,请上传封面,Err:" + err.Error()
 				//return
 			} else {
-				var rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
-				index := rnd.Intn(len(imageList))
-				product.CoverSrc = imageList[index].SrcUrl
+				if len(imageList) > 0 {
+					var rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
+					index := rnd.Intn(len(imageList))
+					product.CoverSrc = imageList[index].Id
+				}
 			}
 		} else {
-			product.CoverSrc = req.CoverSrc
+			product.CoverUrl = req.CoverSrc
 		}
 
 	}
 	switch req.Type {
 	case "report":
-		product.RiskLevel, product.Title, err = services.GetRiskLevel("report", req.SourceId)
+		_, product.Title, _, err = services.GetRiskLevel("report", req.SourceId)
+		product.IsPermanent = true
 	case "audio":
-		product.RiskLevel, product.Title, err = services.GetRiskLevel("audio", req.SourceId)
+		_, product.Title, _, err = services.GetRiskLevel("audio", req.SourceId)
+		product.IsPermanent = true
 	case "video":
-		product.RiskLevel, product.Title, err = services.GetRiskLevel("video", req.SourceId)
+		_, product.Title, _, err = services.GetRiskLevel("video", req.SourceId)
+		product.IsPermanent = true
 	case "package":
-		product.RiskLevel, permissionName, err = services.GetRiskLevel("package", req.SourceId)
+		_, permissionName, _, err = services.GetRiskLevel("package", req.SourceId)
 	default:
 		br.Msg = "产品类型错误"
 		br.ErrMsg = "获取产品列表失败,Err:产品类型错误"
@@ -217,16 +223,7 @@ func (this *ProductController) AddProduct() {
 		}
 		return
 	}
-	if product.RiskLevel == "" {
-		br.Msg = "新增产品错误"
-		br.ErrMsg = "未获取到风险等级"
-		return
-	}
-	if !checkProductRiskLevel(product.RiskLevel) {
-		br.Msg = "产品风险等级不合法"
-		br.ErrMsg = "产品风险等级不合法:" + product.RiskLevel
-		return
-	}
+
 	if product.Title == "" {
 		br.Msg = "产品名称不能为空"
 		br.ErrMsg = "产品名称不能为空"
@@ -382,7 +379,7 @@ func (this *ProductController) DeleteProduct() {
 	}
 	product := models.MerchantProduct{
 		Id:          req.ProductId,
-		Deleted:     true,
+		Deleted:     req.ProductId,
 		UpdatedTime: time.Now(),
 	}
 	err = product.Delete()
@@ -416,6 +413,7 @@ func (this *ProductController) ProductList() {
 	pageSize, _ := this.GetInt("PageSize")
 	currentIndex, _ := this.GetInt("CurrentIndex")
 	sortType := this.GetString("SortType")
+	sortColumn := this.GetString("SortColumn")
 	KeyWord := this.GetString("KeyWord")
 	CreatedTime := this.GetString("CreatedTime")
 	UpdatedTime := this.GetString("UpdatedTime")
@@ -432,7 +430,13 @@ func (this *ProductController) ProductList() {
 	if KeyWord != "" {
 		condition += " AND title like '%" + KeyWord + "%'"
 	}
-	sortCondition := " ORDER BY created_time "
+	var sortCondition string
+	if sortColumn == "" {
+		sortCondition = " ORDER BY created_time "
+	} else {
+		sortCondition = " ORDER BY " + sortColumn + " "
+	}
+
 	if sortType == "" {
 		sortType = "DESC"
 	}
@@ -468,6 +472,8 @@ func (this *ProductController) ProductList() {
 				br.ErrMsg = "无效的产品类型:" + ProductType
 				return
 			}
+		} else {
+			condition += " AND type !=  '" + string(models.ProductPackage) + "'"
 		}
 
 	} else {
@@ -495,13 +501,28 @@ func (this *ProductController) ProductList() {
 			ProductName:   product.Title,
 			ProductType:   CNProductMap[product.Type],
 			PublishedTime: product.CreatedTime.Format(time.DateTime),
-			RiskLevel:     product.RiskLevel,
 			Price:         fmt.Sprintf("¥%s", product.Price),
 			SaleStatus:    CNSaleStatusMap[product.SaleStatus],
+			CoverSrc:      product.CoverUrl,
+			ValidDays:     product.ValidDays,
+			Creator:       product.Creator,
+			IsPermanent:   product.IsPermanent,
+			Description:   product.Description,
+			SourceId:      product.SourceId,
+		}
+		if product.CoverUrl == "" && product.CoverSrc > 0 {
+			image, imageErr := models.GetImageById(product.CoverSrc)
+			if err != nil {
+				utils.FileLog.Warn("获取图片资源失败,err:%s,imageId:%d", imageErr, product.CoverSrc)
+			} else {
+				view.CoverSrc = image.SrcUrl
+			}
 		}
 		if !product.UpdatedTime.IsZero() {
 			view.UpdatedTime = product.UpdatedTime.Format(time.DateTime)
 		}
+		view.RiskLevel, _, _, _ = services.GetRiskLevel(string(product.Type), product.SourceId)
+
 		ListView = append(ListView, view)
 	}
 	page := paging.GetPaging(currentIndex, pageSize, total)
@@ -538,7 +559,7 @@ func (this *ProductController) ProductRisk() {
 		br.ErrMsg = "无效的产品ID:" + strconv.Itoa(SourceId)
 		return
 	}
-	riskLevel, _, err := services.GetRiskLevel(ProductType, SourceId)
+	riskLevel, _, _, err := services.GetRiskLevel(ProductType, SourceId)
 	if err != nil {
 		utils.FileLog.Error("查询产品风险等级失败", err.Error)
 		return
@@ -597,24 +618,26 @@ func (this *ProductController) EditProduct() {
 				//br.ErrMsg = "默认套餐封面获取失败,请上传封面,Err:" + err.Error()
 				//return
 			} else {
-				var rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
-				index := rnd.Intn(len(imageList))
-				product.CoverSrc = imageList[index].SrcUrl
+				if len(imageList) > 0 {
+					var rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
+					index := rnd.Intn(len(imageList))
+					product.CoverSrc = imageList[index].Id
+				}
 			}
 		} else {
-			product.CoverSrc = req.CoverSrc
+			product.CoverUrl = req.CoverSrc
 		}
 
 	}
 	switch req.Type {
 	case "report":
-		product.RiskLevel, product.Title, err = services.GetRiskLevel("report", req.SourceId)
+		_, product.Title, _, err = services.GetRiskLevel("report", req.SourceId)
 	case "audio":
-		product.RiskLevel, product.Title, err = services.GetRiskLevel("audio", req.SourceId)
+		_, product.Title, _, err = services.GetRiskLevel("audio", req.SourceId)
 	case "video":
-		product.RiskLevel, product.Title, err = services.GetRiskLevel("video", req.SourceId)
+		_, product.Title, _, err = services.GetRiskLevel("video", req.SourceId)
 	case "package":
-		product.RiskLevel, _, err = services.GetRiskLevel("package", req.SourceId)
+		_, _, _, err = services.GetRiskLevel("package", req.SourceId)
 	default:
 		br.Msg = "产品类型错误"
 		br.ErrMsg = "获取产品列表失败,Err:产品类型错误"
@@ -630,16 +653,7 @@ func (this *ProductController) EditProduct() {
 		}
 		return
 	}
-	if product.RiskLevel == "" {
-		br.Msg = "编辑产品失败"
-		br.ErrMsg = "未获取到风险等级"
-		return
-	}
-	if !checkProductRiskLevel(product.RiskLevel) {
-		br.Msg = "产品风险等级不合法"
-		br.ErrMsg = "产品风险等级不合法:" + product.RiskLevel
-		return
-	}
+
 	if product.Title == "" {
 		br.Msg = "产品名称不能为空"
 		br.ErrMsg = "产品名称不能为空"
@@ -657,6 +671,7 @@ func (this *ProductController) EditProduct() {
 		br.ErrMsg = "产品价格不能小于0"
 		return
 	}
+	product.Id = req.ProductId
 	product.SaleStatus = models.OnSale
 	product.CreatedTime = time.Now()
 	product.Price = req.Price
@@ -681,3 +696,80 @@ func (this *ProductController) EditProduct() {
 	br.Msg = "编辑产品成功"
 	return
 }
+
+// UploadFile @Title 上传图片
+// @Description 上传图片
+// @Param   File   query   file  true       "文件"
+// @Success 200 {object} models.ReportAuthorResp
+// @router /uploadFile [post]
+func (this *ProductController) UploadFile() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	f, h, err := this.GetFile("File")
+	if err != nil {
+		br.Msg = "获取资源信息失败"
+		br.ErrMsg = "获取资源信息失败,Err:" + err.Error()
+		return
+	}
+	defer f.Close()
+	size, err := strconv.Atoi(utils.UPLOAD_IMG_SIZE)
+	if err != nil {
+		size = 100
+	}
+	if h.Size > 1024*1024*int64(size) {
+		br.Msg = fmt.Sprintf("图片大小不能超过%dK", size)
+		br.ErrMsg = "图片上传失败,Err:" + err.Error()
+		return
+	}
+	ext := path.Ext(h.Filename)
+
+	dateDir := time.Now().Format("20060102")
+	uploadDir := utils.STATIC_DIR + "ht/product" + dateDir
+	err = os.MkdirAll(uploadDir, utils.DIR_MOD)
+	if err != nil {
+		br.Msg = "存储目录创建失败"
+		br.ErrMsg = "存储目录创建失败,Err:" + err.Error()
+		return
+	}
+	randStr := utils.GetRandStringNoSpecialChar(28)
+	fileName := randStr + ext
+	fpath := uploadDir + "/" + fileName
+	err = this.SaveToFile("File", fpath)
+	if err != nil {
+		br.Msg = "图片上传失败"
+		br.ErrMsg = "图片上传失败,Err:" + err.Error()
+		return
+	}
+	audioUploadDir := utils.RESOURCE_DIR + "img/"
+	savePdfToOssPath := audioUploadDir + time.Now().Format("200601/20060102/")
+	audioName := utils.GetRandStringNoSpecialChar(28)
+	savePdfToOssPath += audioName + ext
+
+	defer func() {
+		err = os.Remove(fpath)
+		fmt.Sprintf("删除文件失败:%v", err)
+	}()
+	ossClient := services.NewOssClient()
+	if ossClient == nil {
+		br.Msg = "图片上传失败"
+		br.ErrMsg = "初始化OSS服务失败"
+		return
+	}
+	mp3Url, err := ossClient.UploadFile("", fpath, savePdfToOssPath)
+	if err != nil {
+		br.Msg = "图片上传失败"
+		br.ErrMsg = "图片上传失败,Err:" + err.Error()
+		return
+	}
+	base := path.Base(h.Filename)
+	resp := new(response.MediaUploadResp)
+	resp.Url = mp3Url
+	resp.FileName = base
+	br.Data = resp
+	br.Msg = "上传成功"
+	br.Ret = 200
+	br.Success = true
+}

+ 91 - 0
controllers/sys_config.go

@@ -0,0 +1,91 @@
+package controllers
+
+import (
+	"encoding/json"
+	"eta/eta_mini_crm_ht/models"
+	"fmt"
+)
+
+type SysConfigController struct {
+	BaseAuthController
+}
+
+type Config struct {
+	ConfigId   int
+	ConfigType string
+	Json       bool
+}
+
+const (
+	ConfigTypeInt  = "int"
+	ConfigTypeStr  = "string"
+	ConfigTypeByte = "byte"
+)
+const (
+	// configCode
+	PaymentWayCode string = "paymentWay"
+)
+
+// SysConfigMap 用于存储错误码和错误信息的映射
+var SysConfigMap = map[string]*Config{
+	PaymentWayCode: {ConfigId: 1003, ConfigType: ConfigTypeStr, Json: true},
+}
+
+func GetConfig(code string) *Config {
+	return SysConfigMap[code]
+}
+
+// GetConfig
+// @Title 系统用户详情信息
+// @Description 用户详情信息
+// @Param   SysUserId   query   int  true       "系统用户id"
+// @Success 200 {object} models.LoginResp
+// @router /config [get]
+func (this *SysConfigController) GetConfig() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	ConfigKey := this.GetString("ConfigKey")
+
+	if ConfigKey == "" {
+		br.Msg = "系统参数错误"
+		br.ErrMsg = fmt.Sprintf("系统参数错误 <%d>", ConfigKey)
+		return
+	}
+	configMap := SysConfigMap[ConfigKey]
+	configId := configMap.ConfigId
+	if configId == 0 {
+		br.Msg = "系统参数错误"
+		br.ErrMsg = fmt.Sprintf("系统参数错误,系统配置不存在")
+		return
+	}
+	config, err := models.GetConfig(configId)
+	if err != nil {
+		br.Msg = "系统参数获取失败"
+		br.ErrMsg = "系统参数获取失败,Err" + err.Error()
+		return
+	}
+	var value interface{}
+	switch config.ConfigType {
+	case ConfigTypeInt:
+		value = config.ByteValue
+	case ConfigTypeByte:
+		value = config.IntValue
+	case ConfigTypeStr:
+		if configMap.Json {
+			var jsonMap map[string]interface{}
+			err = json.Unmarshal([]byte(config.StrValue), &jsonMap)
+			value = jsonMap
+		} else {
+			value = config.StrValue
+		}
+
+	}
+
+	br.Data = value
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}

+ 334 - 0
controllers/user.go

@@ -1,12 +1,17 @@
 package controllers
 
 import (
+	"encoding/json"
 	"eta/eta_mini_crm_ht/models"
+	"eta/eta_mini_crm_ht/models/request"
 	"eta/eta_mini_crm_ht/models/response"
 	"eta/eta_mini_crm_ht/services"
 	"eta/eta_mini_crm_ht/utils"
 	"fmt"
 	"github.com/rdlucklib/rdluck_tools/paging"
+	"github.com/xuri/excelize/v2"
+	"net/http"
+	"net/url"
 	"strconv"
 	"strings"
 	"time"
@@ -16,6 +21,25 @@ type UserController struct {
 	BaseAuthController
 }
 
+var (
+	templateCols = map[string]utils.ExcelColMapping{
+		"A": {"手机号", "Mobile"},
+		"B": {"最近一次阅读时间", "LastReadTime"},
+		"C": {"累计阅读次数", "ReadCount"},
+		"D": {"注册时间", "CreatedTime"},
+	}
+
+	userCols = map[string]utils.ExcelColMapping{
+		"A": {"姓名", "RealName"},
+		"B": {"手机号", "Mobile"},
+		"C": {"公司名称", "CompanyName"},
+		"D": {"注册时间", "CreatedTime"},
+		"E": {"是否关注公众号", "FollowingGzhStr"},
+		"F": {"最近一次阅读时间", "LastReadTime"},
+		"G": {"累计阅读次数", "ReadCount"},
+	}
+)
+
 // TemplateList
 // @Title 临时用户列表
 // @Description 临时用户列表
@@ -371,3 +395,313 @@ func transSourceType(productType string) (sourceType models.SourceType) {
 		return ""
 	}
 }
+
+// ExportTemplateUsers
+// @Title 临时用户列表
+// @Description 临时用户列表
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   Keyword   query   string  false       "手机号"
+// @Param   SortParam   query   string  false       "排序字段参数,用来排序的字段, 枚举值:0:注册时间,1:阅读数,2:最近一次阅读时间"
+// @Param   SortType   query   string  true       "如何排序,是正序还是倒序,0:倒序,1:正序"
+// @Success 200 {object} response.TemplateUserListResp
+// @router /temporary/export [get]
+func (this *UserController) ExportTemplateUsers() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	keyword := this.GetString("Keyword")
+	sortParamInt, _ := this.GetInt("SortParam", 0)
+	sortTypeInt, _ := this.GetInt("SortType", 0)
+	var sortStr = ``
+	var condition string
+	var pars []interface{}
+
+	sortParamMap := map[int]string{0: "created_time", 1: "read_count", 2: "last_read_time"}
+	sortTypeMap := map[int]string{0: "desc", 1: "asc"}
+	sortParam, ok := sortParamMap[sortParamInt]
+	if !ok {
+		br.Msg = "错误的排序字段参数"
+		br.ErrMsg = fmt.Sprint("错误的排序字段:", sortParamInt)
+		return
+	}
+	sortType, ok := sortTypeMap[sortTypeInt]
+	if !ok {
+		br.Msg = "错误的排序字段"
+		br.ErrMsg = fmt.Sprint("错误的排序字段:", sortTypeInt)
+		return
+	}
+	sortStr = fmt.Sprintf("%s %s,updated_time desc ", sortParam, sortType)
+
+	if keyword != "" {
+		condition += ` AND mobile LIKE ? `
+		pars = utils.GetLikeKeywordPars(pars, keyword, 1)
+	}
+
+	userList, err := models.GetTemplateUserListByCondition(condition, pars, sortStr)
+	if err != nil {
+		br.Msg = "查询用户失败"
+		br.Msg = "查询用户失败,系统错误,Err:" + err.Error()
+		return
+	}
+	list := make([]models.TemplateUsersItem, 0)
+	for _, v := range userList {
+		list = append(list, v.ToItem())
+	}
+	year, month, day := time.Now().Date()
+	yearStr := strconv.Itoa(year)[2:]
+	fileName := fmt.Sprintf("临时用户表%s.%d.%d.xlsx", yearStr, month, day)
+	file, err := utils.ExportExcel("临时用户表", templateCols, list)
+	_ = this.downloadExcelFile(file, fileName)
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "下载成功"
+}
+
+// encodeChineseFilename 将中文文件名编码为 ISO-8859-1
+func (this *UserController) downloadExcelFile(file *excelize.File, filename string) (err error) {
+	// 对文件名进行 ISO-8859-1 编码
+	fn := url.QueryEscape(filename)
+	if filename == fn {
+		fn = "filename=" + fn
+	} else {
+		fn = "filename=" + filename + "; filename*=utf-8''" + fn
+	}
+	this.Ctx.ResponseWriter.Header().Set("Access-Control-Expose-Headers", "Content-Disposition")
+	this.Ctx.ResponseWriter.Header().Set("Content-Disposition", "attachment; "+fn)
+	this.Ctx.ResponseWriter.Header().Set("Content-Description", "File Transfer")
+	this.Ctx.ResponseWriter.Header().Set("Content-Type", "application/octet-stream")
+	this.Ctx.ResponseWriter.Header().Set("Content-Transfer-Encoding", "binary")
+	this.Ctx.ResponseWriter.Header().Set("Expires", "0")
+	this.Ctx.ResponseWriter.Header().Set("Cache-Control", "must-revalidate")
+	this.Ctx.ResponseWriter.Header().Set("Pragma", "public")
+	this.Ctx.ResponseWriter.Header().Set("File-Name", filename)
+	// 写入文件
+	if err = file.Write(this.Ctx.ResponseWriter); err != nil {
+		utils.FileLog.Error("导出excel文件失败:", err)
+		http.Error(this.Ctx.ResponseWriter, "导出excel文件失败", http.StatusInternalServerError)
+	}
+	return
+}
+
+// ExportOfficialUsers
+// @Title 正式用户列表
+// @Description 正式用户列表
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   Keyword   query   string  false       "手机号"
+// @Param   SortParam   query   string  false       "排序字段参数,用来排序的字段, 枚举值:0:注册时间,1:阅读数,2:最近一次阅读时间"
+// @Param   SortType   query   string  true       "如何排序,是正序还是倒序,0:倒序,1:正序"
+// @Success 200 {object} response.TemplateUserListResp
+// @router /official/export [get]
+func (this *UserController) ExportOfficialUsers() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	keyword := this.GetString("Keyword")
+	FollowingGzh := this.GetString("FollowingGzh")
+	RegisterBeginDate := this.GetString("RegisterBeginDate")
+	RegisterEndDate := this.GetString("RegisterEndDate")
+
+	sortParamInt, _ := this.GetInt("SortParam", 0)
+	sortTypeInt, _ := this.GetInt("SortType", 0)
+
+	var sortStr = ``
+	var condition string
+	var pars []interface{}
+
+	sortParamMap := map[int]string{0: "created_time", 1: "read_count", 2: "last_read_time"}
+	sortTypeMap := map[int]string{0: "desc", 1: "asc"}
+	sortParam, ok := sortParamMap[sortParamInt]
+	if !ok {
+		br.Msg = "错误的排序字段参数"
+		br.ErrMsg = fmt.Sprint("错误的排序字段:", sortParamInt)
+		return
+	}
+	sortType, ok := sortTypeMap[sortTypeInt]
+	if !ok {
+		br.Msg = "错误的排序字段"
+		br.ErrMsg = fmt.Sprintf("错误的排序字段:%v", sortTypeInt)
+		return
+	}
+	sortStr = fmt.Sprintf("%s %s,updated_time desc ", sortParam, sortType)
+	if FollowingGzh != "" {
+		switch FollowingGzh {
+		case "true":
+			condition += ` AND following_gzh=1 `
+		case "false":
+			condition += ` AND following_gzh=0 `
+		default:
+			br.Msg = "关注公众号字段非法字段"
+			br.ErrMsg = fmt.Sprintf("错误的关注公众号字段:%s", FollowingGzh)
+			return
+		}
+	}
+	if RegisterBeginDate != "" || RegisterEndDate != "" {
+		var beginDate, endDate time.Time
+		var parseErr error
+		if RegisterBeginDate != "" {
+			beginDate, parseErr = time.Parse(time.DateOnly, RegisterBeginDate)
+			if parseErr != nil {
+				br.Msg = "注册时间开始字段非法字段"
+				br.ErrMsg = fmt.Sprintf("错误的注册时间开始字段:%s", RegisterBeginDate)
+				return
+			}
+		}
+		if RegisterEndDate != "" {
+			endDate, parseErr = time.Parse(time.DateOnly, RegisterEndDate)
+			if parseErr != nil {
+				br.Msg = "注册时间结束字段非法字段"
+				br.ErrMsg = fmt.Sprintf("错误的注册时间结束字段:%s", RegisterEndDate)
+
+				return
+			}
+		}
+
+		if RegisterBeginDate != "" {
+			if RegisterEndDate != "" {
+				if beginDate.After(endDate) {
+					br.Msg = "结束时间不能早于开始时间"
+					br.ErrMsg = fmt.Sprintf("错误的注册时间结束字段:开始时间:%s,结束时间:%s", RegisterBeginDate, RegisterEndDate)
+					return
+				}
+				condition += ` AND DATE_FORMAT(created_time,'%Y-%m-%d') BETWEEN ? AND ?`
+				pars = append(pars, RegisterBeginDate, RegisterEndDate)
+			} else {
+				condition += ` AND DATE_FORMAT(created_time,'%Y-%m-%d') >= ?`
+				pars = append(pars, RegisterBeginDate)
+			}
+		} else {
+			condition += ` AND DATE_FORMAT(created_time,'%Y-%m-%d') <= ?`
+			pars = append(pars, RegisterEndDate)
+		}
+	}
+	if keyword != "" {
+		condition += ` AND ( mobile LIKE ? or real_name like ?)`
+		pars = utils.GetLikeKeywordPars(pars, keyword, 2)
+	}
+	userList, err := models.GetPageOfficialUserByCondition(condition, pars, sortStr)
+
+	if err != nil {
+		br.Msg = "查询用户失败"
+		br.Msg = "查询用户失败,系统错误,Err:" + err.Error()
+		return
+	}
+	year, month, day := time.Now().Date()
+	yearStr := strconv.Itoa(year)[2:]
+	fileName := fmt.Sprintf("用户列表%s.%d.%d.xlsx", yearStr, month, day)
+	file, err := utils.ExportExcel("用户列表", userCols, userList)
+	_ = this.downloadExcelFile(file, fileName)
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// MessageList  获取未读消息
+// @Summary 获取未读消息
+// @Description 获取未读消息
+// @Success 200 {object} controllers.BaseResponse
+// @router /message [get]
+func (u *UserController) MessageList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		u.Data["json"] = br
+		u.ServeJSON()
+	}()
+	pageSize, _ := u.GetInt("PageSize")
+	currentIndex, _ := u.GetInt("CurrentIndex")
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	} else if pageSize > utils.PageSize100 {
+		pageSize = utils.PageSize100
+	}
+	startSize := utils.StartIndex(currentIndex, pageSize)
+	total, err := models.GetMessageListCount(u.SysUser.SysUserId)
+	if err != nil {
+		br.Msg = "查询退款通知失败"
+		br.Msg = "查询退款通知失败,Err:" + err.Error()
+		return
+	}
+	messages, err := models.GetMessageList(u.SysUser.SysUserId, startSize, pageSize)
+	if err != nil {
+		br.Msg = "查询退款通知失败"
+		br.Msg = "查询退款通知失败,Err:" + err.Error()
+		return
+	}
+	var list []*models.UserMessageView
+	for _, message := range messages {
+		list = append(list, message.ToView())
+	}
+	resp := new(response.MessageListResp)
+	page := paging.GetPaging(currentIndex, pageSize, total)
+	resp.Paging = page
+	resp.List = list
+	br.Ret = 200
+	br.Success = true
+	br.Data = resp
+	br.Msg = "获取成功"
+}
+
+// ReadMessage  获取未读消息
+// @Summary 获取未读消息
+// @Description 获取未读消息
+// @Success 200 {object} controllers.BaseResponse
+// @router /readMessage [post]
+func (u *UserController) ReadMessage() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		u.Data["json"] = br
+		u.ServeJSON()
+	}()
+	var req request.ReadMessageReq
+	if err := json.Unmarshal(u.Ctx.Input.RequestBody, &req); err != nil {
+		br.Msg = "参数解析失败"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.MessageId <= 0 {
+		br.Msg = "消息Id非法"
+		br.Msg = "已读退款通知失败,消息Id非法"
+		return
+	}
+	if models.ReadMessage(u.SysUser.SysUserId, req.MessageId) {
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "已读成功"
+		return
+	} else {
+		br.Msg = "已读退款通知失败"
+		br.ErrMsg = "已读退款通知失败"
+		return
+	}
+}
+
+// ReadMessages  获取未读消息
+// @Summary 获取未读消息
+// @Description 获取未读消息
+// @Success 200 {object} controllers.BaseResponse
+// @router /readMessages [post]
+func (u *UserController) ReadMessages() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		u.Data["json"] = br
+		u.ServeJSON()
+	}()
+	if models.ReadMessages(u.SysUser.SysUserId) {
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "已读成功"
+		return
+	} else {
+		br.Msg = "批量已读退款通知失败"
+		br.ErrMsg = "批量已读退款通知失败"
+		return
+	}
+}

+ 10 - 4
go.mod

@@ -17,6 +17,7 @@ require (
 	github.com/shopspring/decimal v1.3.1
 	github.com/silenceper/wechat/v2 v2.1.6
 	github.com/tcolgate/mp3 v0.0.0-20170426193717-e79c5a46d300
+	github.com/xuri/excelize/v2 v2.9.0
 	google.golang.org/grpc v1.65.0
 	google.golang.org/protobuf v1.34.2
 )
@@ -43,12 +44,15 @@ require (
 	github.com/mitchellh/mapstructure v1.5.0 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
+	github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
 	github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/prometheus/client_golang v1.19.0 // indirect
 	github.com/prometheus/client_model v0.5.0 // indirect
 	github.com/prometheus/common v0.48.0 // indirect
 	github.com/prometheus/procfs v0.12.0 // indirect
+	github.com/richardlehane/mscfb v1.0.4 // indirect
+	github.com/richardlehane/msoleps v1.0.4 // indirect
 	github.com/rs/xid v1.5.0 // indirect
 	github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect
 	github.com/sirupsen/logrus v1.9.0 // indirect
@@ -57,10 +61,12 @@ require (
 	github.com/tidwall/match v1.1.1 // indirect
 	github.com/tidwall/pretty v1.2.0 // indirect
 	github.com/valyala/bytebufferpool v1.0.0 // indirect
-	golang.org/x/crypto v0.26.0 // indirect
-	golang.org/x/net v0.28.0 // indirect
-	golang.org/x/sys v0.24.0 // indirect
-	golang.org/x/text v0.17.0 // indirect
+	github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d // indirect
+	github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 // indirect
+	golang.org/x/crypto v0.28.0 // indirect
+	golang.org/x/net v0.30.0 // indirect
+	golang.org/x/sys v0.26.0 // indirect
+	golang.org/x/text v0.19.0 // indirect
 	golang.org/x/time v0.6.0 // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20240820151423-278611b39280 // indirect
 	gopkg.in/ini.v1 v1.67.0 // indirect

+ 4 - 23
models/chart_permission.go

@@ -115,34 +115,15 @@ func GetByPermissionIdsByClassifyId(classify int) (permissionIds []int, err erro
 	return
 }
 
-func GetChartPermissionList() (items []*ChartPermission, err error) {
-	o := orm.NewOrmUsingDB("rddp")
-	sql := `SELECT * FROM chart_permission WHERE enabled=1 AND product_id=1 ORDER BY sort, chart_permission_id ASC`
-	_, err = o.Raw(sql).QueryRows(&items)
-	return
-}
-
-//	func GetPermissionNames(ids []int) (items []string, err error) {
-//		o := orm.NewOrmUsingDB("rddp")
-//		condition := " AND chart_permission_id in (" + utils.GetOrmReplaceHolder(len(ids)) + ")"
-//		sql := `SELECT distinct  permission_name FROM chart_permission WHERE enabled=1 AND product_id=1` + condition
-//		_, err = o.Raw(sql, ids).QueryRows(&items)
-//		return
-//	}
 func GetPermissionNameById(id int) (items string, err error) {
-	o := orm.NewOrmUsingDB("rddp")
-	sql := `SELECT distinct  permission_name FROM chart_permission WHERE enabled=1 AND product_id=1 and chart_permission_id=?`
+	o := orm.NewOrm()
+	sql := `SELECT name FROM permissions WHERE permission_id=?`
 	err = o.Raw(sql, id).QueryRow(&items)
 	return
 }
-func GetChartPermissionByParentId(parentId int) (items []*ChartPermissionList, err error) {
-	o := orm.NewOrmUsingDB("rddp")
-	sql := `SELECT * FROM chart_permission WHERE enabled=1 AND chart_permission_id=? order parent_id asc ,sort asc`
-	_, err = o.Raw(sql, parentId).QueryRows(&items)
-	return
-}
+
 func GetClassifyIdsByPermissionIds(condition string, pars []interface{}) (classifyIds []int, err error) {
-	o := orm.NewOrmUsingDB("rddp")
+	o := orm.NewOrm()
 	sql := "select distinct classify_id from permission_classify_mapping where 1=1 "
 	if condition != "" {
 		sql += condition

+ 3 - 0
models/db.go

@@ -53,5 +53,8 @@ func init() {
 		new(CustomerProductRiskMapping),
 		new(UserSourceClickFlow),
 		new(MerchantProduct),
+		new(ProductOrder),
+		new(TradeOrder),
+		new(RefundDealFlow),
 	)
 }

+ 29 - 20
models/financial_analyst.go

@@ -14,16 +14,19 @@ const (
 )
 
 type CrmFinancialAnalyst struct {
-	Id           int           `description:"主键"`
-	ETAId        int           `description:"eta_id"`
-	HTId         int           `description:"ht_id"`
-	Name         string        `description:"姓名"`
-	HeadImgUrl   string        `description:"头像"`
-	Introduction string        `description:"介绍"`
-	Status       AnalystStatus `description:"状态"`
-	Deleted      bool          `description:"是否删除"`
-	CreatedTime  time.Time     `description:"创建时间"`
-	UpdatedTime  time.Time     `description:"更新时间"`
+	Id                      int           `description:"主键"`
+	ETAId                   int           `description:"eta_id"`
+	HTId                    int           `description:"ht_id"`
+	Name                    string        `description:"姓名"`
+	HeadImgUrl              string        `description:"头像"`
+	Position                string        `description:"职位"`
+	InvestmentCertificate   string        `description:"投资咨询资格证"`
+	ProfessionalCertificate string        `description:"从业资格证"`
+	Introduction            string        `description:"介绍"`
+	Status                  AnalystStatus `description:"状态"`
+	Deleted                 bool          `description:"是否删除"`
+	CreatedTime             time.Time     `description:"创建时间"`
+	UpdatedTime             time.Time     `description:"更新时间"`
 }
 
 func (m *CrmFinancialAnalyst) TableName() string {
@@ -31,11 +34,14 @@ func (m *CrmFinancialAnalyst) TableName() string {
 }
 
 type AnalystView struct {
-	Id           int    `json:"Id"`
-	Name         string `json:"Name"`
-	HeadImgURL   string `json:"HeadImgURL"`
-	Introduction string `json:"Introduction"`
-	CreatedTime  string `json:"CreatedTime"`
+	Id                      int    `json:"Id"`
+	Name                    string `json:"Name"`
+	HeadImgURL              string `json:"HeadImgURL"`
+	Position                string `json:"Position"`
+	InvestmentCertificate   string `json:"InvestmentCertificate"`
+	ProfessionalCertificate string `json:"ProfessionalCertificate"`
+	Introduction            string `json:"Introduction"`
+	CreatedTime             string `json:"CreatedTime"`
 }
 
 func GetAnalystByName(name string) (analyst CrmFinancialAnalyst, err error) {
@@ -47,11 +53,14 @@ func GetAnalystByName(name string) (analyst CrmFinancialAnalyst, err error) {
 
 func (a *CrmFinancialAnalyst) ToView() *AnalystView {
 	return &AnalystView{
-		Id:           a.Id,
-		Name:         a.Name,
-		HeadImgURL:   a.HeadImgUrl,
-		Introduction: a.Introduction,
-		CreatedTime:  a.CreatedTime.Format(time.DateTime),
+		Id:                      a.Id,
+		Name:                    a.Name,
+		HeadImgURL:              a.HeadImgUrl,
+		Position:                a.Position,
+		InvestmentCertificate:   a.InvestmentCertificate,
+		ProfessionalCertificate: a.ProfessionalCertificate,
+		Introduction:            a.Introduction,
+		CreatedTime:             a.CreatedTime.Format(time.DateTime),
 	}
 }
 func GetAnalystCount(condition string) (count int, err error) {

+ 3 - 8
models/image_sources.go

@@ -64,19 +64,14 @@ func (i *ImageSource) Delete() (err error) {
 
 func GetImageByPermissionId(permissionId int) (items []ImageSource, err error) {
 	o := orm.NewOrm()
-	sql := "select id from image_sources where permission_id = ?"
-	_, err = o.Raw(sql, permissionId).QueryRows(&items)
-	return
-}
-func GetImageIdByPermissionId(permissionId int) (items []int, err error) {
-	o := orm.NewOrm()
-	sql := "select id from image_sources where permission_id = ?"
+	sql := "select id,src_url from image_sources where permission_id = ? "
 	_, err = o.Raw(sql, permissionId).QueryRows(&items)
 	return
 }
+
 func GetImageById(Id int) (item *ImageSource, err error) {
 	o := orm.NewOrm()
-	sql := "select id from image_sources where id = ?"
+	sql := "select id,src_url from image_sources where id = ?"
 	err = o.Raw(sql, Id).QueryRow(&item)
 	return
 }

+ 6 - 1
models/media.go

@@ -315,6 +315,11 @@ func UpdateMedia(m *Media) (updateId int64, err error) {
 		_ = tx.Rollback()
 		return
 	}
+	err = DeleteMappingsById(m.Id)
+	if err != nil {
+		_ = tx.Rollback()
+		return
+	}
 	ids := strings.Split(m.PermissionIds, ",")
 	for _, item := range ids {
 		x, _ := strconv.Atoi(item)
@@ -344,7 +349,7 @@ func FilterMediaIdIdsBySourceId(mediaType MediaType, ids []int) (mediaIds []int,
 
 func GetMediaIdsByCondition(condition string, pars []interface{}) (mediaIds []int, err error) {
 	o := orm.NewOrm()
-	sql := `SELECT distinct id FROM media WHERE ` + condition
+	sql := `SELECT distinct id FROM media WHERE 1=1 ` + condition
 	_, err = o.Raw(sql, pars).QueryRows(&mediaIds)
 	return
 }

+ 5 - 6
models/merchant_product.go

@@ -22,16 +22,16 @@ type MerchantProduct struct {
 	Id          int                 `description:"column:id;primary_key;autoIncrement;comment:主键"`
 	SourceId    int                 `description:"column:source_id;type:int(11);comment:单品或者套餐对应的主键"`
 	Title       string              `description:"column:title;type:varchar(255);comment:标题"`
-	CoverSrc    string              `description:"column:cover_src;type:varchar(255);comment:封面图片"`
+	CoverSrc    int                 `description:"column:cover_src;type:int(11);comment:封面图片资源库id"`
+	CoverUrl    string              `description:"column:cover_url;type:varchar(255);comment:封面图片url"`
 	Description string              `description:"column:description;type:varchar(255);comment:描述"`
 	Price       string              `description:"column:price;type:decimal(10,2);comment:价格"`
-	RiskLevel   string              `description:"column:risk_level;type:varchar(255);not null;comment:风险等级"`
 	Type        MerchantProductType `description:"column:type;type:enum('report','video','audio','package');not null;comment:类型"`
 	IsPermanent bool                `description:"column:is_permanent;type:int(1);not null;default:0;comment:是否永久"`
 	ValidDays   int                 `description:"column:valid_days;type:int(11);comment:有效期天数"`
 	Creator     string              `description:"创建人"`
 	SaleStatus  SaleStatus          `description:"column:sale_status;type:enum('on_sale','off_sale');not null;default:'on_sale';comment:上架/下架状态"`
-	Deleted     bool                `description:"column:deleted;type:tinyint(1);not null;default:0;comment:是否删除"`
+	Deleted     int                 `description:"column:deleted;type:tinyint(1);not null;default:0;comment:是否删除"`
 	CreatedTime time.Time           `description:"column:created_time;type:datetime;comment:创建时间"`
 	UpdatedTime time.Time           `description:"column:updated_time;type:datetime;comment:更新时间;default:CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP"`
 }
@@ -85,11 +85,10 @@ func (mp *MerchantProduct) EditProduct() (err error) {
 	o := orm.NewOrm()
 	mp.UpdatedTime = time.Now()
 	if mp.Type == ProductPackage {
-		_, err = o.Update(mp, "updated_time", "risk_level", "price", "cover_src", "description", "valid_days", "title")
+		_, err = o.Update(mp, "updated_time", "price", "cover_src", "description", "valid_days", "title")
 	} else {
-		_, err = o.Update(mp, "updated_time", "risk_level", "price")
+		_, err = o.Update(mp, "updated_time", "price")
 	}
-
 	return
 }
 func (mp *MerchantProduct) UpdateProductSaleStatus() (err error) {

+ 8 - 1
models/meta_info.go

@@ -11,6 +11,7 @@ type MetaType string
 
 const (
 	UserNoticeType    MetaType   = "USER_NOTICE"
+	SysUserNoticeType MetaType   = "SYS_USER_NOTICE"
 	InitStatusType    StatusType = "INIT"
 	PendingStatusType StatusType = "PENDING"
 	FinishStatusType  StatusType = "FINISH"
@@ -19,6 +20,7 @@ const (
 	ReportSourceType SourceType = "REPORT"
 	VideoSourceType  SourceType = "VIDEO"
 	AudioSourceType  SourceType = "AUDIO"
+	RefundSourceType SourceType = "REFUND"
 )
 
 // MetaInfo 表示 meta_infos 表的模型
@@ -28,7 +30,7 @@ type MetaInfo struct {
 	From        string     `description:"from"`
 	To          string     `description:"column:to"`
 	SourceType  SourceType `description:"source_type"`
-	MetaType    MetaType   `gorm:"column:meta_type;type:enum('USER_NOTICE')"`
+	MetaType    MetaType   `gorm:"column:meta_type;type:enum('USER_NOTICE','SYS_USER_NOTICE')"`
 	Status      StatusType `gorm:"column:status;type:enum('INIT','PENDING','FINISH','FAILED')"`
 	CreatedTime time.Time
 	UpdatedTime time.Time
@@ -40,6 +42,11 @@ type MetaData struct {
 	AuthorName    string `json:"authorName"`
 	PublishedTime string `json:"publishedTime"`
 }
+type RefundMetaData struct {
+	ProductOrderNo string
+	RealName       string
+	Result         string
+}
 
 func (m *MetaInfo) TableName() string {
 	return "meta_infos"

+ 2 - 2
models/permission.go

@@ -109,9 +109,9 @@ func FilterPermissionIdsWithRiskLevel(ids []int) (permissionIds []int, err error
 	if len(ids) == 0 {
 		sql = `SELECT distinct permission_id FROM permissions  where risk_level!='' order by permission_id asc`
 	} else {
-		sql = `SELECT distinct permission_id FROM permissions  where risk_level!='' and permission_id in (?) order by permission_id asc`
+		sql = `SELECT distinct permission_id FROM permissions  where risk_level!='' and permission_id in (` + utils.GetOrmReplaceHolder(len(ids)) + `) order by permission_id asc`
 	}
-	_, err = o.Raw(sql, permissionIds).QueryRows(&permissionIds)
+	_, err = o.Raw(sql, ids).QueryRows(&permissionIds)
 	return
 }
 

+ 120 - 0
models/product_order.go

@@ -0,0 +1,120 @@
+package models
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+type OrderStatus string
+type RefundStatus string
+type PaymentWay string
+
+const (
+	//订单状态
+	OrderStatusPending    OrderStatus = "pending"
+	OrderStatusProcessing OrderStatus = "processing"
+	OrderStatusPaid       OrderStatus = "paid"
+	OrderStatusClosed     OrderStatus = "closed"
+	OrderStatusRefund     OrderStatus = "refund"
+	//退款状态
+	RefundStatusSuccess    RefundStatus = "success"
+	RefundStatusPending    RefundStatus = "pending"
+	RefundStatusFailed     RefundStatus = "failed"
+	RefundStatusProcessing RefundStatus = "processing"
+	WechatPayWay           PaymentWay   = "wechat"
+	AliPayWay              PaymentWay   = "alipay"
+
+	detailColumn = "id,order_id,user_id,template_user_id,product_id,status,created_time,updated_time,total_amount"
+)
+
+type ProductOrderView struct {
+	OrderID          string
+	RealName         string
+	Mobile           string
+	ProductType      string
+	ProductName      string
+	TotalAmount      string
+	ValidDuration    string
+	PaymentAmount    string
+	TradeNO          string
+	RefundTradeId    string
+	RefundAmount     string
+	PaymentWay       string
+	PaymentTime      string
+	Status           string
+	RefundStatus     string
+	RefundFinishTime string
+	Remark           string
+	CreatedTime      string
+}
+
+type ProductOrder struct {
+	Id               int                 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
+	OrderId          string              `gorm:"column:order_id;size:255;comment:'订单编号'" json:"order_id"`
+	UserId           int                 `gorm:"column:user_id;default:null;comment:'用户id'" json:"user_id"`
+	TemplateUserId   int                 `gorm:"column:template_user_id;default:null;comment:'临时用户id'" json:"template_user_id"`
+	RealName         string              `gorm:"column:real_name;default:null;comment:'临时用户id'" json:"real_name"`
+	AreaCode         string              `gorm:"column:area_code;default:null;comment:'临时用户id'" json:"area_code"`
+	Mobile           string              `gorm:"column:mobile;default:null;comment:'临时用户id'" json:"mobile"`
+	ProductId        int                 `gorm:"column:product_id;default:null;comment:'产品id'" json:"product_id"`
+	ProductType      MerchantProductType `gorm:"column:product_type;default:null;comment:'产品类型'" json:"product_type"`
+	ProductName      string              `gorm:"column:product_name;default:null;comment:'商品名称'" json:"product_name"`
+	TotalAmount      string              `gorm:"column:total_amount;size:255;default:null;comment:'金额'" json:"total_amount"`
+	TradeId          int                 `gorm:"column:trade_id;default:null;comment:'支付订单'" json:"trade_id"`
+	TradeNo          string              `gorm:"column:trade_no;default:null;comment:'支付订单'" json:"trade_no"`
+	RefundTradeId    string              `column:"refund_trade_id;null;comment:'退款金额'" json:"refund_trade_id"`
+	RefundAmount     string              `gorm:"column:refund_amount;size:255;default:null;comment:'退款金额'" json:"refund_amount"`
+	PaymentAmount    string              `gorm:"column:payment_amount;size:255;default:null;comment:'支付金额'" json:"payment_amount"`
+	PaymentWay       PaymentWay          `gorm:"column:payment_way;enum('wechat','alipay');default:null;comment:'支付渠道'"`
+	PaymentTime      time.Time           `gorm:"column:payment_time;default:null;comment:'支付时间'" json:"payment_time"`
+	ExpiredTime      time.Time           `gorm:"column:expired_time;default:null;comment:'超时时间'" json:"expired_time"`
+	Status           OrderStatus         `gorm:"column:status;type:enum('pending','processing','paid','closed','refund');default:'pending';comment:'订单状态'" json:"status"`
+	RefundStatus     RefundStatus        `gorm:"column:refund_status;type:enum('pending','processing','failed','success');default:null;comment:'退款状态'" json:"refund_status"`
+	RefundFinishTime time.Time           `gorm:"column:refund_finish_time;default:null;comment:'退款完成时间'" json:"refund_finish_time"`
+	ValidDuration    string              `gorm:"column:valid_duration;default:null;comment:'订单有效期'" json:"valid_duration"`
+	Remark           string              `gorm:"column:remark;size:255;default:null;comment:'备注'" json:"remark"`
+	IsDeleted        int                 `gorm:"column:is_deleted;size:1;default:0;comment:'是否删除'" json:"is_deleted"`
+	CreatedTime      time.Time           `gorm:"column:created_time;default:null;comment:'创建时间'" json:"created_time"`
+	UpdatedTime      time.Time           `gorm:"column:updated_time;default:null;comment:'更新时间'" json:"updated_time"`
+}
+
+func (m *ProductOrder) TableName() string {
+	return "product_orders"
+}
+
+func GetProductOrderByCondition(condition string, sortCondition string, startSize int, pageSize int) (list []*ProductOrder, err error) {
+	o := orm.NewOrm()
+	sql := `select * from product_orders where is_deleted=0`
+	if condition != "" {
+		sql += condition
+	}
+	if sortCondition != "" {
+		sql += sortCondition
+	}
+	sql += ` LIMIT ?,?`
+	_, err = o.Raw(sql, startSize, pageSize).QueryRows(&list)
+	return
+}
+func GetProductOrderListByCondition(condition string, sortCondition string) (list []*ProductOrder, err error) {
+	o := orm.NewOrm()
+	sql := `select * from product_orders where is_deleted=0`
+	if condition != "" {
+		sql += condition
+	}
+	if sortCondition != "" {
+		sql += sortCondition
+	}
+	_, err = o.Raw(sql).QueryRows(&list)
+	return
+}
+func GetProductOrderCountByCondition(condition string) (total int, err error) {
+	o := orm.NewOrm()
+	err = o.Raw("select count(*) from product_orders where is_deleted=0" + condition).QueryRow(&total)
+	return
+}
+
+func GetProductOrderByID(orderNo string) (order *ProductOrder, err error) {
+	o := orm.NewOrm()
+	err = o.Raw("select * from product_orders where is_deleted=0 and order_id =?", orderNo).QueryRow(&order)
+	return
+}

+ 25 - 0
models/refund_deal_flow.go

@@ -0,0 +1,25 @@
+package models
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+// RefundDealFlow 表示 refund_deal_flow 表的 ORM 结构体
+type RefundDealFlow struct {
+	Id             int       `gorm:"column:id;primaryKey;autoIncrement:false"`
+	OperatorUserId int       `gorm:"column:operator_user_id;default:null"`
+	ProductOrderNo string    `gorm:"column:product_order_no;default:null"`
+	RefundOrderNo  string    `gorm:"column:refund_order_no;default:null"`
+	CreatedTime    time.Time `gorm:"column:created_time;default:null"`
+}
+
+// TableName 返回表名
+func (RefundDealFlow) TableName() string {
+	return "refund_deal_flow"
+}
+func (m *RefundDealFlow) Insert() (err error) {
+	o := orm.NewOrm()
+	_, err = o.Insert(m)
+	return
+}

+ 6 - 3
models/request/analyst.go

@@ -3,7 +3,10 @@ package request
 // AnalystEditReq
 // @Description: 研究员信息编辑
 type AnalystEditReq struct {
-	Id           int    `description:"用户id"`
-	HeadImgUrl   string `description:"头像地址"`
-	Introduction string `description:"简介"`
+	Id                      int    `description:"用户id"`
+	HeadImgUrl              string `description:"头像地址"`
+	Introduction            string `description:"简介"`
+	Position                string
+	InvestmentCertificate   string
+	ProfessionalCertificate string
 }

+ 1 - 0
models/request/product.go

@@ -1,6 +1,7 @@
 package request
 
 type ProductReq struct {
+	ProductId   int
 	ProductName string
 	SourceId    int
 	Type        string

+ 6 - 0
models/request/trade_order.go

@@ -0,0 +1,6 @@
+package request
+
+type RefundReq struct {
+	ProductOrderNo string
+	Remark         string
+}

+ 4 - 0
models/request/user.go

@@ -25,6 +25,10 @@ type UserEidtReq struct {
 	ChartPermission []int  `description:"所选品种"`
 	IsEnabled       bool   `description:"是否禁用"`
 }
+type ReadMessageReq struct {
+	MessageId int `json:"MessageId"`
+	SysUserId int `json:"SysUserId"`
+}
 
 type UserCheckReq struct {
 	AreaCode string `description:"区号"`

+ 23 - 0
models/response/order.go

@@ -0,0 +1,23 @@
+package response
+
+import (
+	"eta/eta_mini_crm_ht/models"
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+type ProductOrderListResp struct {
+	List   []*models.ProductOrderView
+	Paging *paging.PagingItem
+}
+type TradeOrderListResp struct {
+	List   []*models.TradeOrderView
+	Paging *paging.PagingItem
+}
+
+type RefundResp struct {
+	RealName         string
+	RefundAmount     string
+	RefundFinishTime string
+	Account          string
+	Remark           string
+}

+ 5 - 0
models/response/user.go

@@ -24,3 +24,8 @@ type TemplateUserListResp struct {
 	List   []models.TemplateUsersItem
 	Paging *paging.PagingItem `description:"分页数据"`
 }
+
+type MessageListResp struct {
+	List   []*models.UserMessageView
+	Paging *paging.PagingItem `description:"分页数据"`
+}

+ 32 - 0
models/sys_config.go

@@ -0,0 +1,32 @@
+package models
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+// SysConfig 表示 sys_config 表的结构体
+type SysConfig struct {
+	ID          int       `gorm:"primaryKey;autoIncrement" json:"id"`
+	ConfigID    int       `gorm:"column:config_id" json:"config_id"`
+	ConfigName  string    `gorm:"column:config_name" json:"config_name"`
+	ConfigType  string    `gorm:"column:config_type;type:enum('string','int','byte')" json:"config_type"`
+	Deleted     int       `gorm:"column:deleted" json:"deleted"`
+	StrValue    string    `gorm:"column:str_value" json:"str_value"`
+	IntValue    int       `gorm:"column:int_value" json:"int_value"`
+	ByteValue   string    `gorm:"column:byte_value" json:"byte_value"`
+	CreatedTime time.Time `gorm:"column:created_time" json:"created_time"`
+	UpdatedTime time.Time `gorm:"column:updated_time;default:CURRENT_TIMESTAMP;autoUpdateTime" json:"updated_time"`
+}
+
+func (SysConfig) TableName() string {
+	return "sys_config"
+
+}
+
+func GetConfig(configId int) (config SysConfig, err error) {
+	o := orm.NewOrm()
+	sql := `select * from sys_config where config_id = ? and deleted=0`
+	err = o.Raw(sql, configId).QueryRow(&config)
+	return
+}

+ 12 - 1
models/template_users.go

@@ -100,7 +100,18 @@ func GetTemplateUserList() (items []*TemplateUser, err error) {
 	_, err = o.Raw(sql).QueryRows(&items)
 	return
 }
-
+func GetTemplateUserListByCondition(condition string, pars []interface{}, sortStr string) (items []*TemplateUser, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM template_users AS a WHERE  is_deleted = 0 `
+	if condition != "" {
+		sql += condition
+	}
+	if sortStr != `` {
+		sql += ` ORDER BY ` + sortStr
+	}
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
 func GetTemplateUser(id int) (item *TemplateUser, err error) {
 	o := orm.NewOrm()
 	sql := `SELECT * FROM template_users WHERE id=? and  is_deleted = 0 `

+ 115 - 0
models/trade_order.go

@@ -0,0 +1,115 @@
+package models
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+type PaymentType string
+type PaymentStatus string
+
+const (
+	PaymentTypePay       PaymentType   = "pay"
+	PaymentTypeRefund    PaymentType   = "refund"
+	PaymentStatusDone    PaymentStatus = "done"
+	PaymentStatusFailed  PaymentStatus = "failed"
+	PaymentStatusPending PaymentStatus = "pending"
+)
+
+type TradeOrderView struct {
+	RealName       string
+	Mobile         string
+	ProductName    string
+	TransactionID  string
+	ProductOrderID string
+	PaymentAccount string
+	Amount         string
+	Currency       string
+	MerchantID     string
+	UserID         int
+	TemplateUserID int
+	PaymentWay     string
+	PaymentType    string
+	PaymentStatus  string
+	DealTime       string
+	CreatedTime    string
+}
+type TradeOrder struct {
+	Id               uint          `gorm:"primaryKey;autoIncrement" json:"id"`
+	TransactionId    string        `gorm:"column:transaction_id;size:255;default:null;comment:'第三方平台ID'" json:"transaction_id"`
+	OrgTransactionId string        `gorm:"column:org_transaction_id;size:255;default:null;comment:'原支付第三方平台ID'" json:"org_transaction_id"`
+	ProductOrderId   string        `gorm:"column:product_order_id;size:255;default:null;comment:'商品订单号'" json:"product_order_id"`
+	ProductName      string        `gorm:"column:product_name;size:255;default:null;comment:'商品名称'" json:"product_name"`
+	RealName         string        `gorm:"column:real_name;default:null;comment:'姓名'" json:"real_name"`
+	AreaCode         string        `gorm:"column:area_code;default:null;comment:'手机区号'" json:"area_code"`
+	Mobile           string        `gorm:"column:mobile;default:null;comment:'手机号'" json:"mobile"`
+	PaymentAccount   string        `gorm:"column:payment_account;size:255;default:null;comment:'支付账号'" json:"payment_account"`
+	PaymentWay       PaymentWay    `gorm:"column:payment_way;type:enum('wechat');default:null;comment:'支付渠道'" json:"payment_way"`
+	Amount           string        `gorm:"column:amount;size:20;default:null;comment:'支付金额'" json:"amount"`
+	Currency         string        `gorm:"column:currency;size:255;default:null;comment:'货币'" json:"currency"`
+	MerchantId       string        `gorm:"column:merchant_id;size:255;default:null;comment:'商户id'" json:"merchant_id"`
+	UserId           int           `gorm:"column:user_id;default:null;comment:'用户id'" json:"user_id"`
+	TemplateUserId   int           `gorm:"column:template_user_id;default:null;comment:'临时用户id'" json:"template_user_id"`
+	PaymentType      PaymentType   `gorm:"column:payment_type;type:enum('pay','refund');default:null;comment:'订单类型'" json:"payment_type"`
+	PaymentStatus    PaymentStatus `gorm:"column:payment_status;type:enum('pending','done','failed');default:null;comment:'支付状态'" json:"payment_status"`
+	DealTime         time.Time     `gorm:"column:deal_time;default:null;comment:'完成时间'" json:"deal_time"`
+	CreatedTime      time.Time     `gorm:"column:created_time;default:null" json:"created_time"`
+	UpdatedTime      time.Time     `gorm:"column:updated_time;default:null" json:"updated_time"`
+}
+
+func (TradeOrder) TableName() string {
+	return "trade_orders"
+}
+
+func GetTradeOrderByCondition(condition string, sortCondition string, startSize int, pageSize int) (list []*TradeOrder, err error) {
+	o := orm.NewOrm()
+	sql := `select * from trade_orders where 1=1 `
+	if condition != "" {
+		sql += condition
+	}
+	if sortCondition != "" {
+		sql += sortCondition
+	}
+	sql += ` LIMIT ?,?`
+	_, err = o.Raw(sql, startSize, pageSize).QueryRows(&list)
+	return
+}
+
+func GetTradeOrderListByCondition(condition string, sortCondition string) (list []*TradeOrder, err error) {
+	o := orm.NewOrm()
+	sql := `select * from trade_orders where 1=1 `
+	if condition != "" {
+		sql += condition
+	}
+	if sortCondition != "" {
+		sql += sortCondition
+	}
+	_, err = o.Raw(sql).QueryRows(&list)
+	return
+}
+func GetTradeOrderCountByCondition(condition string) (total int, err error) {
+	o := orm.NewOrm()
+	err = o.Raw("select count(*) from trade_orders where 1=1 " + condition).QueryRow(&total)
+	return
+}
+
+func GetTradeOrderByNo(tradeNo string) (tradeOrder TradeOrder, err error) {
+	o := orm.NewOrm()
+	err = o.Raw("select * from trade_orders where transaction_id=? ", tradeNo).QueryRow(&tradeOrder)
+	return
+}
+
+func (to *TradeOrder) Refund(productOrder *ProductOrder) (err error) {
+	o := orm.NewOrm()
+	tx, _ := o.Begin()
+	defer func() {
+		if err != nil {
+			tx.Rollback()
+		} else {
+			tx.Commit()
+		}
+	}()
+	_, err = tx.Insert(to)
+	_, err = tx.Update(productOrder, "status", "refund_status", "refund_trade_id", "remark", "refund_amount")
+	return
+}

+ 45 - 9
models/user.go

@@ -32,14 +32,17 @@ func (u User) FillUserInfo(user *TemplateUser) UserView {
 }
 
 type UserView struct {
-	Id            int
-	RealName      string `description:"姓名"`
-	Mobile        string `description:"手机号码"`
-	FollowingGzh  bool
-	LastReadTime  string
-	ReadCount     int
-	AccountStatus AccountStatus `description:"账号状态"`
-	CreatedTime   string
+	Id              int
+	TemplateUserId  int
+	CompanyName     string
+	RealName        string `description:"姓名"`
+	Mobile          string `description:"手机号码"`
+	FollowingGzh    bool
+	FollowingGzhStr string
+	LastReadTime    string
+	ReadCount       int
+	AccountStatus   AccountStatus `description:"账号状态"`
+	CreatedTime     string
 }
 
 func GetPageOfficialUserList(condition string, pars []interface{}, sortStr string, startSize int, pageSize int) (total int, userList []*UserView, err error) {
@@ -53,7 +56,7 @@ func GetPageOfficialUserList(condition string, pars []interface{}, sortStr strin
 	}
 	total = len(officialIds)
 	idCondition := " and tus.id in (" + utils.GetOrmReplaceHolder(len(officialIds)) + ")"
-	sql := `SELECT us.id as id, us.real_name as real_name, tus.mobile,tus.following_gzh,tus.created_time,tus.read_count,tus.last_read_time,tus.account_status  FROM template_users tus LEFT JOIN (select id, real_name,template_user_id from users) us on us.template_user_id=tus.id WHERE  1=1`
+	sql := `SELECT us.id as id,us.template_user_id as template_user_id, us.real_name as real_name, tus.mobile,tus.following_gzh,tus.created_time,tus.read_count,tus.last_read_time,tus.account_status  FROM template_users tus LEFT JOIN (select id, real_name,template_user_id from users) us on us.template_user_id=tus.id WHERE  1=1`
 	sql = sql + idCondition
 	if condition != "" {
 		sql += condition
@@ -68,5 +71,38 @@ func GetPageOfficialUserList(condition string, pars []interface{}, sortStr strin
 	if userList == nil {
 		userList = []*UserView{}
 	}
+
+	return
+}
+
+func GetPageOfficialUserByCondition(condition string, pars []interface{}, sortStr string) (userList []UserView, err error) {
+	o := orm.NewOrm()
+	totalSql := `SELECT distinct template_user_id FROM users`
+	var officialIds []int
+	_, err = o.Raw(totalSql).QueryRows(&officialIds)
+	if err != nil {
+		return
+	}
+	idCondition := " and tus.id in (" + utils.GetOrmReplaceHolder(len(officialIds)) + ")"
+	sql := `SELECT us.id as id,us.template_user_id as template_user_id, us.real_name as real_name, tus.mobile,tus.following_gzh,tus.created_time,tus.read_count,tus.last_read_time,tus.account_status  FROM template_users tus LEFT JOIN (select id, real_name,template_user_id from users) us on us.template_user_id=tus.id WHERE  1=1`
+	sql = sql + idCondition
+	if condition != "" {
+		sql += condition
+	}
+	if sortStr != `` {
+		sql += ` ORDER BY ` + sortStr
+	}
+	_, err = o.Raw(sql, officialIds, pars).QueryRows(&userList)
+	if userList == nil {
+		userList = []UserView{}
+		return
+	}
+	for i := 0; i < len(userList); i++ {
+		if userList[i].FollowingGzh {
+			userList[i].FollowingGzhStr = "是"
+		} else {
+			userList[i].FollowingGzhStr = "否"
+		}
+	}
 	return
 }

+ 80 - 0
models/user_message.go

@@ -0,0 +1,80 @@
+package models
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+type UserType string
+type MessageType string
+
+const (
+	UnReadStatus     StatusType = "UNREAD"
+	ReadStatus       StatusType = "READ"
+	MyMessageColumns            = "id,source_id,type,message"
+	Admin            UserType   = "admin"
+)
+
+// UserMessage 表示 user_message 表的模型
+type UserMessage struct {
+	Id          int        `gorm:"primaryKey;autoIncrement;column:id"`
+	AnalystId   int        `gorm:"column:analyst_id"`
+	UserId      int        `gorm:"column:user_id"`
+	UserType    UserType   `gorm:"column:user_type;type:enum('user','admin')"`
+	SourceId    int        `gorm:"column:source_id"`
+	Message     string     `gorm:"column:message"`
+	Type        SourceType `gorm:"column:type;type:enum('REPORT','VIDEO','AUDIO','REFUND')"`
+	Status      StatusType `gorm:"column:status;type:enum('UNREAD','READ')"`
+	CreatedTime time.Time  `gorm:"column:created_time;type:timestamps;comment:创建时间"`
+	UpdatedTime time.Time  `gorm:"column:updated_time"`
+}
+type UserMessageView struct {
+	Id          int
+	Message     string
+	Type        SourceType
+	Status      StatusType
+	CreatedTime string
+}
+
+func (u *UserMessage) ToView() *UserMessageView {
+	return &UserMessageView{
+		Id:          u.Id,
+		Message:     u.Message,
+		Type:        u.Type,
+		Status:      u.Status,
+		CreatedTime: u.CreatedTime.Format(time.DateTime),
+	}
+}
+func GetMessageList(userId int, offset, size int) (messages []UserMessage, err error) {
+	o := orm.NewOrm()
+	sql := `select id,type,message,status,created_time from user_messages where user_id=? and user_type=?  order by Field(status,'UNREAD','READ'), created_time desc limit ?,?`
+	_, err = o.Raw(sql, userId, Admin, offset, size).QueryRows(&messages)
+	return
+}
+
+func ReadMessage(userId int, messageId int) bool {
+	o := orm.NewOrm()
+	sql := `update user_messages set status=? where id=? and user_id =? and user_type=?`
+	_, err := o.Raw(sql, ReadStatus, messageId, userId, Admin).Exec()
+	if err != nil {
+		return false
+	}
+	return true
+}
+
+func ReadMessages(userId int) bool {
+	o := orm.NewOrm()
+	sql := `update user_messages set status=? where  user_id =? and user_type=?`
+	_, err := o.Raw(sql, ReadStatus, userId, Admin).Exec()
+	if err != nil {
+		return false
+	}
+	return true
+}
+
+func GetMessageListCount(userId int) (count int, err error) {
+	o := orm.NewOrm()
+	sql := `select count(*) from user_messages where user_id=? and user_type=? `
+	err = o.Raw(sql, userId, Admin).QueryRow(&count)
+	return
+}

+ 4 - 1
models/user_source_click_flow.go

@@ -20,6 +20,7 @@ type UserSourceClickFlow struct {
 	UseId               int
 	Mobile              string
 	SourceId            int
+	SourceTitle         string
 	SourceType          SourceType
 	ClickTime           time.Time
 	ReadDurationSeconds int64
@@ -35,7 +36,8 @@ func (m *UserSourceClickFlow) ToView() UserSourceClickFlowView {
 		ClickTime:           m.ClickTime.Format(time.DateTime),
 		SourceId:            m.SourceId,
 		SourceName:          getSourceName(m.SourceType),
-		ReadDurationMinutes: parseMinutes(m.ReadDurationSeconds),
+		SourceTitle:         m.SourceTitle,
+		ReadDurationMinutes: parseMinutes(m.ReadDurationSeconds / 1000),
 	}
 }
 func getSourceName(sourceType SourceType) string {
@@ -50,6 +52,7 @@ func parseMinutes(seconds int64) string {
 type UserSourceClickFlowView struct {
 	SourceId            int
 	SourceName          string
+	SourceTitle         string
 	PermissionNames     string
 	ClickTime           string
 	ReadDurationMinutes string

+ 35 - 0
models/user_subscription_access_list.go

@@ -0,0 +1,35 @@
+package models
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+// UserSubscriptionAccessList 用户订阅访问列表
+type UserSubscriptionAccessList struct {
+	ID             int                 `gorm:"column:id;primaryKey"`
+	TemplateUserId int                 `gorm:"column:template_user_id"`
+	ProductID      int                 `gorm:"column:product_id"`
+	ProductName    string              `gorm:"column:product_name"`
+	ProductType    MerchantProductType `gorm:"column:product_type"`
+	BeginDate      time.Time           `gorm:"column:begin_date"`
+	EndDate        time.Time           `gorm:"column:end_date"`
+	Status         SubscribeStatus     `gorm:"column:status;type:enum('valid','expired');default:'valid'"`
+	CreatedTime    time.Time           `gorm:"column:created_time"`
+	UpdatedTime    time.Time           `gorm:"column:updated_time"`
+	ProductOrderNo string              `gorm:"-"`
+}
+
+type SubscribeStatus string
+
+const (
+	SubscribeValid   SubscribeStatus = "valid"
+	SubscribeExpired SubscribeStatus = "expired"
+	SubscribeClose   SubscribeStatus = "closed"
+)
+
+func GetAccess(productId int, templateUserId int) (access *UserSubscriptionAccessList, err error) {
+	o := orm.NewOrm()
+	err = o.Raw("select * from user_subscription_access_list where template_user_id=? and  product_id =? ", templateUserId, productId).QueryRow(&access)
+	return
+}

+ 117 - 0
routers/commentsRouter.go

@@ -333,6 +333,60 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_mini_crm_ht/controllers:OrderController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm_ht/controllers:OrderController"],
+        beego.ControllerComments{
+            Method: "ExportProductOrder",
+            Router: `/productOrder/export`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm_ht/controllers:OrderController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm_ht/controllers:OrderController"],
+        beego.ControllerComments{
+            Method: "ProductOrderList",
+            Router: `/productOrderList`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm_ht/controllers:OrderController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm_ht/controllers:OrderController"],
+        beego.ControllerComments{
+            Method: "Refund",
+            Router: `/refund`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm_ht/controllers:OrderController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm_ht/controllers:OrderController"],
+        beego.ControllerComments{
+            Method: "RefundDetail",
+            Router: `/refundDetail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm_ht/controllers:OrderController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm_ht/controllers:OrderController"],
+        beego.ControllerComments{
+            Method: "ExportTradeOrder",
+            Router: `/tradeOrder/export`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm_ht/controllers:OrderController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm_ht/controllers:OrderController"],
+        beego.ControllerComments{
+            Method: "TradeOrderList",
+            Router: `/tradeOrderList`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_mini_crm_ht/controllers:ProductController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm_ht/controllers:ProductController"],
         beego.ControllerComments{
             Method: "AddProduct",
@@ -396,6 +450,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_mini_crm_ht/controllers:ProductController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm_ht/controllers:ProductController"],
+        beego.ControllerComments{
+            Method: "UploadFile",
+            Router: `/uploadFile`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_mini_crm_ht/controllers:RiskConfigController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm_ht/controllers:RiskConfigController"],
         beego.ControllerComments{
             Method: "GetCustomerRiskList",
@@ -414,6 +477,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_mini_crm_ht/controllers:SysConfigController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm_ht/controllers:SysConfigController"],
+        beego.ControllerComments{
+            Method: "GetConfig",
+            Router: `/config`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_mini_crm_ht/controllers:SysDepartmentController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm_ht/controllers:SysDepartmentController"],
         beego.ControllerComments{
             Method: "Add",
@@ -612,6 +684,24 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_mini_crm_ht/controllers:UserController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm_ht/controllers:UserController"],
+        beego.ControllerComments{
+            Method: "MessageList",
+            Router: `/message`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm_ht/controllers:UserController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm_ht/controllers:UserController"],
+        beego.ControllerComments{
+            Method: "ExportOfficialUsers",
+            Router: `/official/export`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_mini_crm_ht/controllers:UserController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm_ht/controllers:UserController"],
         beego.ControllerComments{
             Method: "OfficialList",
@@ -621,6 +711,24 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_mini_crm_ht/controllers:UserController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm_ht/controllers:UserController"],
+        beego.ControllerComments{
+            Method: "ReadMessage",
+            Router: `/readMessage`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_crm_ht/controllers:UserController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm_ht/controllers:UserController"],
+        beego.ControllerComments{
+            Method: "ReadMessages",
+            Router: `/readMessages`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_mini_crm_ht/controllers:UserController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm_ht/controllers:UserController"],
         beego.ControllerComments{
             Method: "ReadRecordList",
@@ -630,6 +738,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_mini_crm_ht/controllers:UserController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm_ht/controllers:UserController"],
+        beego.ControllerComments{
+            Method: "ExportTemplateUsers",
+            Router: `/temporary/export`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_mini_crm_ht/controllers:UserController"] = append(beego.GlobalControllerRouter["eta/eta_mini_crm_ht/controllers:UserController"],
         beego.ControllerComments{
             Method: "TemplateList",

+ 11 - 0
routers/router.go

@@ -21,11 +21,22 @@ func init() {
 				&controllers.SysUserController{},
 			),
 		),
+		beego.NSNamespace("/sys",
+			beego.NSInclude(
+				&controllers.SysConfigController{},
+			),
+		),
+
 		beego.NSNamespace("/department",
 			beego.NSInclude(
 				&controllers.SysDepartmentController{},
 			),
 		),
+		beego.NSNamespace("/order",
+			beego.NSInclude(
+				&controllers.OrderController{},
+			),
+		),
 		beego.NSNamespace("/role",
 			beego.NSInclude(
 				&controllers.SysRoleController{},

+ 20 - 0
services/meta.go

@@ -35,3 +35,23 @@ func CreateMeta(authorName string, authorId int, mediaId int, publishTime string
 	id, err = metaContent.Insert()
 	return
 }
+
+func CreateRefundMeta(toUserId int) (id int64, err error) {
+	Meta := models.RefundMetaData{
+		ProductOrderNo: "",
+		RealName:       "",
+		Result:         "",
+	}
+	metaStr, _ := json.Marshal(Meta)
+	toStr := strconv.Itoa(toUserId)
+	metaContent := models.MetaInfo{
+		From:       "SYSTEM",
+		Meta:       string(metaStr),
+		MetaType:   "SYSTEM_NOTICE",
+		SourceType: models.RefundSourceType,
+		Status:     models.InitStatusType,
+		To:         toStr,
+	}
+	id, err = metaContent.Insert()
+	return
+}

+ 37 - 18
services/product.go

@@ -6,6 +6,7 @@ import (
 	"eta/eta_mini_crm_ht/utils"
 	"fmt"
 	"strconv"
+	"strings"
 	"time"
 )
 
@@ -100,7 +101,7 @@ func getMediaIdsByCondition(setIds []int, permissionIds []int, condition string,
 		utils.FileLog.Error("过滤媒体Id失败", err.Error)
 		return
 	}
-	if condition != "" {
+	if len(mediaIds) > 0 {
 		condition += "and id in (" + utils.GetOrmReplaceHolder(len(mediaIds)) + ")"
 		pars = append(pars, mediaIds)
 		mediaIds, err = models.GetMediaIdsByCondition(condition, pars)
@@ -159,18 +160,24 @@ func GetUnsetProductByCondition(productType string, ids []int, sortCondition str
 }
 
 type ProductView struct {
-	Id            int
-	ProductName   string
-	SourceId      int
-	ProductType   string
-	RiskLevel     string
-	PublishedTime string
-	UpdatedTime   string
-	Price         string
-	SaleStatus    string
+	Id              int
+	ProductName     string
+	SourceId        int
+	ProductType     string
+	RiskLevel       string
+	PublishedTime   string
+	PermissionNames string
+	UpdatedTime     string
+	Price           string
+	IsPermanent     bool
+	ValidDays       int
+	CoverSrc        string
+	SaleStatus      string
+	Creator         string
+	Description     string
 }
 
-func GetRiskLevel(productType string, id int) (riskLevel string, productName string, err error) {
+func GetRiskLevel(productType string, id int) (riskLevel string, productName string, permissionName string, err error) {
 	switch productType {
 	case "report":
 		var report *models.Report
@@ -187,7 +194,7 @@ func GetRiskLevel(productType string, id int) (riskLevel string, productName str
 				utils.FileLog.Error("获取品种信息,无法确认风险等级", err.Error())
 				return
 			}
-			return permission.RiskLevel, report.Title, nil
+			return permission.RiskLevel, report.Title, permission.Name, nil
 		case models.SourceETA:
 			var permissionIds []int
 			permissionIds, err = models.GetByPermissionIdsByClassifyId(report.ClassifyId)
@@ -196,7 +203,12 @@ func GetRiskLevel(productType string, id int) (riskLevel string, productName str
 				return
 			}
 			var permissions []models.Permission
+			var permissionNameArr []string
 			permissions, err = models.GetPermissionsWithRiskLevel(permissionIds)
+			for _, permission := range permissions {
+				permissionNameArr = append(permissionNameArr, permission.Name)
+			}
+			permissionName = strings.Join(permissionNameArr, ",")
 			if err != nil {
 				utils.FileLog.Error("获取品种信息,无法确认风险等级", err.Error())
 				return
@@ -216,7 +228,7 @@ func GetRiskLevel(productType string, id int) (riskLevel string, productName str
 				}
 			}
 			if riskNum > 0 {
-				return fmt.Sprintf("R%d", riskNum), report.Title, nil
+				return fmt.Sprintf("R%d", riskNum), report.Title, permissionName, nil
 			}
 			return
 		}
@@ -234,7 +246,12 @@ func GetRiskLevel(productType string, id int) (riskLevel string, productName str
 			return
 		}
 		var permissions []models.Permission
+		var permissionNameArr []string
 		permissions, err = models.GetPermissionsWithRiskLevel(permissionsIds)
+		for _, permission := range permissions {
+			permissionNameArr = append(permissionNameArr, permission.Name)
+		}
+		permissionName = strings.Join(permissionNameArr, ",")
 		if err != nil {
 			utils.FileLog.Error("获取媒体信息,无法确认风险等级", err.Error())
 			return
@@ -255,7 +272,7 @@ func GetRiskLevel(productType string, id int) (riskLevel string, productName str
 			}
 		}
 		if riskNum > 0 {
-			return fmt.Sprintf("R%d", riskNum), media.MediaName, nil
+			return fmt.Sprintf("R%d", riskNum), media.MediaName, permissionName, nil
 		}
 		return
 	case "package":
@@ -266,9 +283,9 @@ func GetRiskLevel(productType string, id int) (riskLevel string, productName str
 			return
 		}
 		if permission.RiskLevel == "" {
-			return "", "", errors.New("当前品种未设置风测等级")
+			return "", "", "", errors.New("当前品种未设置风测等级")
 		}
-		return permission.RiskLevel, permission.Name, nil
+		return permission.RiskLevel, permission.Name, "", nil
 	default:
 		var media *models.Media
 		media, err = models.GetMediaById(models.MediaType(productType), id)
@@ -289,8 +306,9 @@ func GetRiskLevel(productType string, id int) (riskLevel string, productName str
 			return
 		}
 		var riskNum int
+		var permissionNameArr []string
 		for _, permission := range permissions {
-
+			permissionNameArr = append(permissionNameArr, permission.Name)
 			if permission.RiskLevel == "" {
 				continue
 			}
@@ -303,8 +321,9 @@ func GetRiskLevel(productType string, id int) (riskLevel string, productName str
 				riskNum = currentNum
 			}
 		}
+		permissionName = strings.Join(permissionNameArr, ",")
 		if riskNum > 0 {
-			return fmt.Sprintf("R%d", riskNum), media.MediaName, nil
+			return fmt.Sprintf("R%d", riskNum), media.MediaName, permissionName, nil
 		}
 		return
 	}

+ 7 - 9
services/report.go

@@ -11,15 +11,13 @@ func GetReportIdsByPermissionIds(permissionIds []int) (reportIds []int, err erro
 		utils.FileLog.Error("查询报告分类信息失败", err.Error())
 		return
 	}
-	if len(classifyIds) == 0 {
-		utils.FileLog.Error("查询报告分类信息失败:", "没有查询到报告分类信息")
-		return
-	}
-	//eta报告
-	etaReportIds, err := models.GetReportIdByClassifyId(classifyIds)
-	if err != nil {
-		utils.FileLog.Error("查询eta报告失败", err.Error())
-		return
+	var etaReportIds []int
+	if len(classifyIds) > 0 {
+		//eta报告
+		etaReportIds, err = models.GetReportIdByClassifyId(classifyIds)
+		if err != nil {
+			utils.FileLog.Error("查询eta报告失败", err.Error())
+		}
 	}
 	//海通
 	permissionNames, err := models.GetPermissionNames(permissionIds)

+ 15 - 16
services/user_source_click_flow.go

@@ -4,12 +4,13 @@ import (
 	"eta/eta_mini_crm_ht/models"
 	"eta/eta_mini_crm_ht/utils"
 	"strings"
-	"sync"
 )
 
 func GetUserSourceClickFlowListCountByUserId(userId int, condition string, pars []interface{}, permissionIds []int, product models.SourceType) (total int, conditionNew string, parsNew []interface{}, err error) {
 	if len(permissionIds) == 0 {
 		total, err = models.GetUserSourceClickFlowListCountByUserId(userId, condition, pars)
+		conditionNew = condition
+		parsNew = pars
 		return
 	}
 	if product == models.AudioSourceType || product == models.VideoSourceType {
@@ -67,6 +68,10 @@ func GetUserSourceClickFlowListCountByUserId(userId int, condition string, pars
 			pars = append(pars, permissionPars...)
 		}
 	}
+	if condition == "" {
+		utils.FileLog.Warn("没有符合要求的阅读记录")
+		return
+	}
 	total, err = models.GetUserSourceClickFlowListCountByUserId(userId, condition, pars)
 	conditionNew = condition
 	parsNew = pars
@@ -78,23 +83,17 @@ func GetUserSourceClickFlowListByUserId(userId int, condition string, pars []int
 	if err != nil {
 		return
 	}
-	var wg sync.WaitGroup
-	wg.Add(len(list))
 	for _, item := range list {
-		go func(item *models.UserSourceClickFlow) {
-			defer wg.Done()
-			viewItem := item.ToView()
-			switch item.SourceType {
-			case models.ReportSourceType:
-				viewItem.PermissionNames = getReportPermissionNames(item.SourceId)
-			case models.AudioSourceType, models.VideoSourceType:
-				mediaType := transMediaType(item.SourceType)
-				viewItem.PermissionNames = getMediaPermissionNames(item.SourceId, mediaType)
-			}
-			items = append(items, viewItem)
-		}(item)
+		viewItem := item.ToView()
+		switch item.SourceType {
+		case models.ReportSourceType:
+			viewItem.PermissionNames = getReportPermissionNames(item.SourceId)
+		case models.AudioSourceType, models.VideoSourceType:
+			mediaType := transMediaType(item.SourceType)
+			viewItem.PermissionNames = getMediaPermissionNames(item.SourceId, mediaType)
+		}
+		items = append(items, viewItem)
 	}
-	wg.Wait()
 	return
 }
 func transMediaType(sourceType models.SourceType) models.MediaType {

+ 4 - 0
utils/constants.go

@@ -75,6 +75,10 @@ var NoAuthApiMap = map[string]bool{
 	"/user/change_list":       true,
 	"/classify/list":          true,
 	"/seller/list":            true,
+	"/user/readMessage":       true,
+	"/user/readMessages":      true,
+	"/user/message":           true,
+	"/sys/config":             true,
 }
 
 var APPNAME string = "海通CRM"

+ 41 - 0
utils/distrubtLock.go

@@ -0,0 +1,41 @@
+package utils
+
+import (
+	"context"
+	"fmt"
+)
+
+const (
+	lockName = "lock:"
+)
+
+var (
+	ctx = context.Background()
+)
+
+func AcquireLock(key string, expiration int, holder string) bool {
+	script := `local key = KEYS[1]
+			local clientId = ARGV[1]
+			local expiration = tonumber(ARGV[2])
+			if redis.call("EXISTS", key) == 0 then
+				redis.call("SET", key, clientId, "EX", expiration)
+				return 1
+			else
+				return 0
+			end`
+
+	lockey := fmt.Sprintf("%s%s", lockName, key)
+	return Rc.RunLUA(script, ctx, []string{lockey}, holder, expiration)
+}
+
+func ReleaseLock(key string, holder string) bool {
+	script := `
+	   if redis.call("get", KEYS[1]) == ARGV[1] then
+	       return redis.call("del", KEYS[1])
+	   else
+	       return 0
+	   end
+	`
+	lockey := fmt.Sprintf("%s%s", lockName, key)
+	return Rc.RunLUA(script, ctx, []string{lockey}, holder)
+}

+ 57 - 0
utils/excel_utils.go

@@ -0,0 +1,57 @@
+package utils
+
+import (
+	"fmt"
+	"github.com/xuri/excelize/v2"
+	"reflect"
+)
+
+type ExcelColMapping struct {
+	Col   string
+	Field string
+}
+
+func ExportExcel[T any](sheetName string, cols map[string]ExcelColMapping, dataList []T) (file *excelize.File, err error) {
+	// 创建一个新的 Excel 文件
+	file = excelize.NewFile()
+	// 创建一个工作表
+	index, err := file.NewSheet(sheetName)
+	if err != nil {
+		FileLog.Error("创建工作表失败:", err)
+	}
+	err = file.DeleteSheet("Sheet1")
+	// 设置工作表的标题行
+	for k, v := range cols {
+		err = file.SetCellValue(sheetName, fmt.Sprintf("%s%d", k, 1), v.Col)
+		if err != nil {
+			FileLog.Error("创建column失败:", err)
+			return
+		}
+	}
+	// 添加一些示例数据
+	row := 2
+	for _, item := range dataList {
+		for k, v := range cols {
+			err = file.SetCellValue(sheetName, fmt.Sprintf("%s%d", k, row), getValue(item, v.Field))
+			if err != nil {
+				FileLog.Error("创建数据失败:", err)
+				return
+			}
+		}
+		row++
+	}
+
+	// 设置活动工作表
+	file.SetActiveSheet(index)
+	return
+}
+func getValue[T any](val T, fieldName string) (value interface{}) {
+	obj := reflect.ValueOf(val)
+	fmt.Println(fieldName)
+	fieldVal := obj.FieldByName(fieldName)
+	if !fieldVal.IsValid() {
+		FileLog.Error("无效的字段名:", fieldName)
+		return
+	}
+	return fieldVal.Interface()
+}

+ 2 - 0
utils/redis.go

@@ -1,6 +1,7 @@
 package utils
 
 import (
+	"context"
 	"eta/eta_mini_crm_ht/utils/redis"
 	"time"
 )
@@ -20,6 +21,7 @@ type RedisClient interface {
 	GetRedisTTL(key string) time.Duration
 	Incrby(key string, num int) (interface{}, error)
 	Do(commandName string, args ...interface{}) (reply interface{}, err error)
+	RunLUA(command string, ctx context.Context, keys []string, args ...interface{}) bool
 }
 
 func initRedis(redisType string, conf string) (redisClient RedisClient, err error) {

+ 19 - 0
utils/redis/cluster_redis.go

@@ -269,3 +269,22 @@ func (rc *ClusterRedisClient) Do(commandName string, args ...interface{}) (reply
 	newArgs = append(newArgs, args...)
 	return rc.redisClient.Do(context.TODO(), newArgs...).Result()
 }
+
+// RunLUA
+// @Description: cmd执行redis命令
+// @receiver rc
+// @param commandName
+// @param args
+// @return reply
+// @return err
+func (rc *ClusterRedisClient) RunLUA(command string, ctx context.Context, keys []string, args ...interface{}) bool {
+	script := redis.NewScript(command)
+	result, err := script.Run(ctx, rc.redisClient, keys, args...).Int()
+	if err != nil {
+		return false
+	}
+	if result == 1 {
+		return true
+	}
+	return false
+}

+ 12 - 0
utils/redis/standalone_redis.go

@@ -263,3 +263,15 @@ func (rc *StandaloneRedisClient) Do(commandName string, args ...interface{}) (re
 	newArgs = append(newArgs, args...)
 	return rc.redisClient.Do(context.TODO(), newArgs...).Result()
 }
+
+func (rc *StandaloneRedisClient) RunLUA(command string, ctx context.Context, keys []string, args ...interface{}) bool {
+	script := redis.NewScript(command)
+	result, err := script.Run(ctx, rc.redisClient, keys, args).Int()
+	if err != nil {
+		return false
+	}
+	if result == 1 {
+		return true
+	}
+	return false
+}