package routers import ( "context" "encoding/json" parsefmt "fmt" "ops/agents" "ops/models" "slices" "strings" "time" "github.com/gin-gonic/gin" "gorm.io/gorm" ) var ( purchaseUserGroup TabUserGroups purchaseAdmins []uint ) // 更新管理员成员缓存 func PurchaseUpdateAdminsCash() { purchaseAdmins = nil // id 1 是系统管理员 purchaseAdmins = append(purchaseAdmins, 1) var binds []TabUserGroupBinds models.DB.Where("group_id = ?", purchaseUserGroup.ID).Find(&binds) for _, item := range binds { if !slices.Contains(purchaseAdmins, item.UserID) { purchaseAdmins = append(purchaseAdmins, item.UserID) } } } // 判断用户是否有权限修改/删除订单(创建者或管理员) func canModifyPurchase(userID, creatorUserID uint) bool { if slices.Contains(purchaseAdmins, userID) { return true } if userID == creatorUserID { return true } return false } // decodeJSON 将 map 通过 JSON 中转解码到目标结构体,绕过 mapstructure 的字段名匹配问题 func decodeJSON(data map[string]interface{}, out interface{}) error { jsonBytes, err := json.Marshal(data) if err != nil { return err } return json.Unmarshal(jsonBytes, out) } type CostItem struct { Cost int `json:"cost"` // 费用(分) CostT int `json:"costt"` // 总价 CurrencyType int `json:"currencytype"` // 货币类型: 1-CNY 2-MOP 3-HKD 4-USD Int int `json:"int"` // 数量 Type int `json:"type"` // 费用类型: 1-单价 2-运费 } type From_purchase_addorder struct { Costs []CostItem `json:"costs"` // 成本 Link string `json:"link"` // 链接 OrderStatus string `json:"order_status"` // 订单状态 Photos []string `json:"photos"` // 图片备注 Remark string `json:"remark"` // 备注 Styles string `json:"styles"` // 样式备注 Title string `json:"title"` // 标题 } type TabPurchaseOrder struct { ID uint `gorm:"primarykey"` UserID uint `gorm:"not null"` Title string `gorm:"size:200;comment:标题"` Remark string `gorm:"type:text;comment:备注"` Link string `gorm:"size:1000;comment:链接"` Styles string `gorm:"type:text;comment:样式数组"` OrderStatus string `gorm:"size:50;default:pending;comment:订单状态: pending-待处理 ordered-已下单 arrived-已到达 received-已收件 lost-丢件 returned-退件"` CreatedAt *time.Time `gorm:"type:datetime;autoCreateTime"` UpdatedAt *time.Time `gorm:"type:datetime;autoUpdateTime"` DeletedAt gorm.DeletedAt `gorm:"index"` } type TabPurchaseCosts struct { ID uint `gorm:"primarykey"` OrderID uint `gorm:"not null"` UserID uint `gorm:"not null"` Price int `gorm:"not null"` Quantity int `gorm:"not null"` CurrencyType int `gorm:"default:1;comment:货币类型: 1-CNY 2-MOP 3-HKD 4-USD"` CostType int `gorm:"default:1;comment:费用类型: 1-单价 2-运费"` CreatedAt *time.Time `gorm:"type:datetime;autoCreateTime"` } // TabPurchaseCommit 记录订单状态变更及评论 type TabPurchaseCommit struct { ID uint `gorm:"primarykey"` OrderID uint `gorm:"not null;index;comment:关联订单ID"` UserID uint `gorm:"not null;comment:操作人ID"` Action string `gorm:"size:50;not null;comment:操作类型: create-创建 create_status-状态变更"` Status string `gorm:"size:50;comment:变更后的状态"` OldStatus string `gorm:"size:50;comment:变更前的状态"` Comment string `gorm:"type:text;comment:评论/备注"` Photos string `gorm:"type:text;comment:变更图片(JSON数组,存放sha256哈希)"` IP string `gorm:"size:50;comment:操作IP"` CreatedAt *time.Time `gorm:"type:datetime;autoCreateTime"` } type TabPurchaseLog struct { ID uint `gorm:"primarykey"` OrderID uint `gorm:"not null;index;comment:关联OrderID"` UserID uint `gorm:"not null;comment:操作人ID"` ActionType string `gorm:"size:50;not null;comment:操作类型: create-创建 update-修改 delete-删除 query-查询"` OldContent string `gorm:"type:text;comment:修改前内容(JSON)"` NewContent string `gorm:"type:text;comment:修改后内容(JSON)"` IP string `gorm:"size:50;comment:操作IP"` Remark string `gorm:"size:500;comment:备注/操作描述"` CreatedAt *time.Time `gorm:"type:datetime;autoCreateTime;comment:操作时间"` } type purchaseProvider struct{} func (purchaseProvider) QueryPurchases(ctx context.Context, query agents.PurchaseQuery) (*agents.PurchaseQueryResult, error) { select { case <-ctx.Done(): return nil, ctx.Err() default: } result := &agents.PurchaseQueryResult{ Ok: true, Action: query.Action, LoggedIn: query.UserID > 0, } if !result.LoggedIn { result.Message = "需要登录才能查询采购模块信息。" return result, nil } switch query.Action { case "count": counts, err := queryPurchaseCounts(query) if err != nil { return nil, err } result.Counts = counts return result, nil case "get": order, err := queryPurchaseOrderDetail(query.OrderID, query.UserID) if err != nil { return nil, err } result.Order = order if order != nil { result.Count = 1 } return result, nil default: orders, total, err := queryPurchaseOrderList(query) if err != nil { return nil, err } result.Orders = orders result.Count = len(orders) result.Total = total result.Page = query.Page result.Limit = query.Limit result.Filters = map[string]interface{}{ "search": query.Search, "status": query.Status, "start_date": query.StartDate, "end_date": query.EndDate, } return result, nil } } func applyPurchaseQueryFilters(db *gorm.DB, query agents.PurchaseQuery) (*gorm.DB, error) { if query.Search != "" { var id uint if _, err := parsefmt.Sscanf(query.Search, "%d", &id); err == nil && id > 0 { db = db.Where("id = ?", id) } else { db = db.Where("title LIKE ? OR remark LIKE ?", "%"+query.Search+"%", "%"+query.Search+"%") } } if query.Status != "" { db = db.Where("order_status = ?", query.Status) } if query.StartDate != "" { startDate, err := time.Parse("2006-01-02", query.StartDate) if err != nil { return nil, err } db = db.Where("created_at >= ?", startDate) } if query.EndDate != "" { endDate, err := time.Parse("2006-01-02", query.EndDate) if err != nil { return nil, err } db = db.Where("created_at < ?", endDate.AddDate(0, 0, 1)) } return db, nil } func queryPurchaseOrderList(query agents.PurchaseQuery) ([]agents.PurchaseOrder, int64, error) { db, err := applyPurchaseQueryFilters(models.DB.Model(&TabPurchaseOrder{}), query) if err != nil { return nil, 0, err } var total int64 if err := db.Count(&total).Error; err != nil { return nil, 0, err } var rows []TabPurchaseOrder if err := db.Order("updated_at DESC, id DESC").Offset(query.Limit * (query.Page - 1)).Limit(query.Limit).Find(&rows).Error; err != nil { return nil, 0, err } orders := make([]agents.PurchaseOrder, 0, len(rows)) for _, row := range rows { order, err := queryPurchaseOrderDetail(row.ID, query.UserID) if err != nil { return nil, 0, err } if order != nil { orders = append(orders, *order) } } return orders, total, nil } func queryPurchaseOrderDetail(orderID uint, userID uint) (*agents.PurchaseOrder, error) { var row TabPurchaseOrder if err := models.DB.Where("id = ?", orderID).First(&row).Error; err != nil { if err == gorm.ErrRecordNotFound { return nil, nil } return nil, err } order := buildPurchaseOrder(row, userID) var costs []TabPurchaseCosts if err := models.DB.Where("order_id = ?", orderID).Order("id asc").Find(&costs).Error; err != nil { return nil, err } order.Costs = make([]agents.PurchaseCost, 0, len(costs)) for _, cost := range costs { order.Costs = append(order.Costs, agents.PurchaseCost{ ID: cost.ID, OrderID: cost.OrderID, UserID: cost.UserID, Price: cost.Price, Quantity: cost.Quantity, CurrencyType: cost.CurrencyType, CurrencyName: purchaseCurrencyName(cost.CurrencyType), CostType: cost.CostType, CostTypeName: purchaseCostTypeName(cost.CostType), }) } var commits []TabPurchaseCommit if err := models.DB.Where("order_id = ?", orderID).Order("created_at DESC").Find(&commits).Error; err != nil { return nil, err } order.Commits = make([]agents.PurchaseCommit, 0, len(commits)) for _, commit := range commits { order.Commits = append(order.Commits, agents.PurchaseCommit{ ID: commit.ID, OrderID: commit.OrderID, UserID: commit.UserID, Action: commit.Action, Status: commit.Status, OldStatus: commit.OldStatus, Comment: commit.Comment, CreatedAt: formatTimePtr(commit.CreatedAt), }) } return &order, nil } func queryPurchaseCounts(query agents.PurchaseQuery) (map[string]int64, error) { counts := map[string]int64{} statuses := []string{"pending", "ordered", "arrived", "received", "lost", "returned"} var total int64 base, err := applyPurchaseQueryFilters(models.DB.Model(&TabPurchaseOrder{}), agents.PurchaseQuery{Search: query.Search, StartDate: query.StartDate, EndDate: query.EndDate}) if err != nil { return nil, err } if err := base.Count(&total).Error; err != nil { return nil, err } counts["total"] = total for _, status := range statuses { statusQuery := agents.PurchaseQuery{Search: query.Search, Status: status, StartDate: query.StartDate, EndDate: query.EndDate} db, err := applyPurchaseQueryFilters(models.DB.Model(&TabPurchaseOrder{}), statusQuery) if err != nil { return nil, err } var count int64 if err := db.Count(&count).Error; err != nil { return nil, err } counts[status] = count } return counts, nil } func buildPurchaseOrder(row TabPurchaseOrder, currentUserID uint) agents.PurchaseOrder { return agents.PurchaseOrder{ ID: row.ID, UserID: row.UserID, Title: row.Title, Remark: row.Remark, Link: row.Link, DetailURL: purchaseOrderDetailURL(row.ID), Styles: row.Styles, OrderStatus: row.OrderStatus, OrderStatusName: purchaseStatusName(row.OrderStatus), CreatedAt: formatTimePtr(row.CreatedAt), UpdatedAt: formatTimePtr(row.UpdatedAt), CanModify: canModifyPurchase(currentUserID, row.UserID), } } func purchaseOrderDetailURL(orderID uint) string { return parsefmt.Sprintf("/purchase/showorder/%d", orderID) } func formatTimePtr(t *time.Time) string { if t == nil { return "" } return t.Format("2006-01-02 15:04:05") } func purchaseStatusName(status string) string { switch status { case "pending": return "待处理" case "ordered": return "已下单" case "arrived": return "已到达" case "received": return "已收件" case "lost": return "丢件" case "returned": return "退件" default: return status } } func purchaseCurrencyName(currencyType int) string { switch currencyType { case 1: return "CNY" case 2: return "MOP" case 3: return "HKD" case 4: return "USD" default: return "未知" } } func purchaseCostTypeName(costType int) string { switch costType { case 1: return "单价" case 2: return "运费" default: return "未知" } } func ApiPurchaseInit() { models.DB.AutoMigrate(&TabPurchaseOrder{}) models.DB.AutoMigrate(&TabPurchaseCosts{}) models.DB.AutoMigrate(&TabPurchaseLog{}) models.DB.AutoMigrate(&TabPurchaseCommit{}) //先检查用户组有没有这个key purchase purchaseUserGroup.Name = "purchase_admin" if models.DB.Where(&purchaseUserGroup).First(&purchaseUserGroup).Error == nil { PurchaseUpdateAdminsCash() } else { purchaseUserGroup.Type = "usergroup" models.DB.Create(&purchaseUserGroup) } agents.RegisterPurchaseProvider(purchaseProvider{}) } func ApiPurchase(r *gin.RouterGroup) { r.POST("/getorder", func(ctx *gin.Context) { isAuth, user, data := AuthenticationAuthority(ctx) if !isAuth { ReturnJson(ctx, "userCookieError", nil) return } type FromGetOrder struct { ID uint `json:"id"` } var from FromGetOrder if err := decodeJSON(data, &from); err != nil || from.ID == 0 { ReturnJson(ctx, "jsonErr", nil) return } var order TabPurchaseOrder if err := models.DB.Where("id = ?", from.ID).First(&order).Error; err != nil { ReturnJson(ctx, "order_not_found", nil) return } // 查询关联费用 var costs []TabPurchaseCosts models.DB.Where("order_id = ?", from.ID).Find(&costs) // 查询关联图片 var binds []TabPurchaseFileBind models.DB.Where("order_id = ?", from.ID).Find(&binds) var fileIDs []uint for _, b := range binds { fileIDs = append(fileIDs, b.FileID) } var files []TabFileInfo if len(fileIDs) > 0 { models.DB.Where("id IN ?", fileIDs).Find(&files) } // 查询状态变更记录 var commits []TabPurchaseCommit models.DB.Where("order_id = ?", from.ID).Order("created_at DESC").Find(&commits) // 解析每条 commit 的 Photos JSON 字段为数组 type CommitResponse struct { ID uint `json:"id"` OrderID uint `json:"orderId"` UserID uint `json:"userId"` Action string `json:"action"` Status string `json:"status"` OldStatus string `json:"oldStatus"` Comment string `json:"comment"` IP string `json:"ip"` CreatedAt time.Time `json:"createdAt"` Photos []string `json:"photos"` } var commitResps []CommitResponse for _, c := range commits { // Status 优先用数据库字段;若为空(历史旧数据),从 Comment 备注中截取状态 status := c.Status if status == "" { status = strings.TrimPrefix(c.Comment, "状态变更为: ") status = strings.TrimPrefix(status, "变更状态为: ") // 如果截取后跟原文一样,说明不是"状态变更为"格式,取原文作为展示 if status == c.Comment { status = "" } } resp := CommitResponse{ ID: c.ID, OrderID: c.OrderID, UserID: c.UserID, Action: c.Action, Status: status, OldStatus: c.OldStatus, Comment: c.Comment, IP: c.IP, CreatedAt: time.Time{}, } if c.CreatedAt != nil { resp.CreatedAt = *c.CreatedAt } if c.Photos != "" { json.Unmarshal([]byte(c.Photos), &resp.Photos) } commitResps = append(commitResps, resp) } // 查询关联工单(通过 TabWorkOrderPurchaseOrderBind 表) type WorkOrderInfo struct { ID uint `json:"id"` Title string `json:"title"` CurrentStatus string `json:"status"` } var linkedWorkOrders []WorkOrderInfo var woBinds []TabWorkOrderPurchaseOrderBind models.DB.Where("purchase_order_id = ?", from.ID).Find(&woBinds) if len(woBinds) > 0 { woIDSet := make(map[uint]bool) var woIDs []uint for _, wb := range woBinds { if !woIDSet[wb.WorkOrderID] { woIDSet[wb.WorkOrderID] = true woIDs = append(woIDs, wb.WorkOrderID) } } var workOrders []TabWorkOrder models.DB.Where("id IN ?", woIDs).Find(&workOrders) for _, wo := range workOrders { linkedWorkOrders = append(linkedWorkOrders, WorkOrderInfo{ ID: wo.ID, Title: wo.Title, CurrentStatus: wo.CurrentStatus, }) } } if linkedWorkOrders == nil { linkedWorkOrders = []WorkOrderInfo{} } // 判断当前用户是否可以修改 canModify := canModifyPurchase(user.ID, order.UserID) ReturnJson(ctx, "apiOK", gin.H{ "order": order, "canModify": canModify, "costs": costs, "photos": files, "commits": commitResps, "workOrders": linkedWorkOrders, }) }) // 更新订单状态(可附带评论) r.POST("/updatestatus", func(ctx *gin.Context) { isAuth, user, data := AuthenticationAuthority(ctx) if !isAuth { ReturnJson(ctx, "userCookieError", nil) return } type FromUpdateStatus struct { ID uint `json:"id"` Status string `json:"status" binding:"required"` Comment string `json:"comment"` Photos []string `json:"photos"` // 变更附带的图片哈希 } var from FromUpdateStatus if err := decodeJSON(data, &from); err != nil || from.ID == 0 { ReturnJson(ctx, "jsonErr", nil) return } // 校验图片哈希(不包含标点符号的哈希值) for _, hash := range from.Photos { if models.IsContainsSpecialChar(hash) { ReturnJson(ctx, "photo_hash_invalid", nil) return } } // 校验状态值 validStatuses := map[string]bool{ "pending": true, "ordered": true, "arrived": true, "received": true, "lost": true, "returned": true, } if !validStatuses[from.Status] { ReturnJson(ctx, "invalid_status", nil) return } var order TabPurchaseOrder if err := models.DB.Where("id = ?", from.ID).First(&order).Error; err != nil { ReturnJson(ctx, "order_not_found", nil) return } oldStatus := order.OrderStatus // 更新状态 updates := map[string]interface{}{ "order_status": from.Status, } if err := models.DB.Model(&order).Updates(updates).Error; err != nil { ReturnJson(ctx, "apiErr", nil) return } // 写状态变更 commit comment := from.Comment if comment == "" && len(from.Photos) == 0 { comment = "状态变更为: " + from.Status } photosJSON := "" if len(from.Photos) > 0 { if pj, err := json.Marshal(from.Photos); err == nil { photosJSON = string(pj) } } commit := TabPurchaseCommit{ OrderID: order.ID, UserID: user.ID, Action: "create_status", Status: from.Status, OldStatus: oldStatus, Comment: comment, Photos: photosJSON, IP: ctx.ClientIP(), } models.DB.Create(&commit) // 写操作日志 newContent, _ := json.Marshal(map[string]string{ "status": from.Status, "comment": comment, }) oldContent, _ := json.Marshal(map[string]string{ "status": oldStatus, }) tosqllog := TabPurchaseLog{ UserID: user.ID, OrderID: order.ID, ActionType: "update_status", NewContent: string(newContent), OldContent: string(oldContent), IP: ctx.ClientIP(), Remark: comment, } models.DB.Create(&tosqllog) ReturnJson(ctx, "apiOK", nil) }) // 删除状态记录 r.POST("/delete_commit", func(ctx *gin.Context) { isAuth, user, data := AuthenticationAuthority(ctx) if !isAuth { ReturnJson(ctx, "userCookieError", nil) return } type FromDeleteCommit struct { OrderID uint `json:"orderId"` CommitID uint `json:"commitId"` } var from FromDeleteCommit if err := decodeJSON(data, &from); err != nil || from.OrderID == 0 || from.CommitID == 0 { ReturnJson(ctx, "jsonErr", nil) return } // 获取订单信息 var order TabPurchaseOrder if err := models.DB.Where("id = ?", from.OrderID).First(&order).Error; err != nil { ReturnJson(ctx, "order_not_found", nil) return } // 获取进度信息 var commit TabPurchaseCommit if err := models.DB.Where("id = ? AND order_id = ?", from.CommitID, from.OrderID).First(&commit).Error; err != nil { ReturnJson(ctx, "commit_not_found", nil) return } // 权限判断:订单创建者 或 进度创建者 或 管理员 isOrderCreator := user.ID == order.UserID isCommitCreator := user.ID == commit.UserID isAdmin := slices.Contains(purchaseAdmins, user.ID) if !isOrderCreator && !isCommitCreator && !isAdmin { ReturnJson(ctx, "no_permission", nil) return } // 删除进度 models.DB.Where("id = ?", from.CommitID).Delete(&TabPurchaseCommit{}) ReturnJson(ctx, "apiOK", nil) }) r.POST("/getorders", func(ctx *gin.Context) { isAuth, _, data := AuthenticationAuthority(ctx) if isAuth { //fmt.Println(user) // DebugPrintJson(data) type From_purchase_getorders struct { Search string Status string Entries int Page int } var jsondata From_purchase_getorders if err := decodeJSON(data, &jsondata); err == nil { //fmt.Println(jsondata) is_data_ok := true if jsondata.Entries <= 0 || jsondata.Entries > 300 { is_data_ok = false } if jsondata.Page <= 0 { is_data_ok = false } if is_data_ok { //读取有多少条目 var count int64 query := models.DB.Model(TabPurchaseOrder{}) if jsondata.Search != "" { // 精确匹配订单 ID var id uint if _, err := parsefmt.Sscanf(jsondata.Search, "%d", &id); err == nil && id > 0 { query = query.Where("id = ?", id) } else { // 模糊匹配标题和用途(Remark) query = query.Where("title LIKE ? OR remark LIKE ?", "%"+jsondata.Search+"%", "%"+jsondata.Search+"%") } } if jsondata.Status != "" { query = query.Where("order_status = ?", jsondata.Status) } query.Count(&count) //读取条目 var getorders []TabPurchaseOrder query.Order("updated_at DESC, id DESC").Offset(jsondata.Entries * (jsondata.Page - 1)).Limit(jsondata.Entries).Find(&getorders) ReturnJson(ctx, "apiOK", map[string]interface{}{ "all_count": count, "all_orders": getorders, }) } else { ReturnJson(ctx, "jsonErr_1", nil) } } else { ReturnJson(ctx, "jsonErr", nil) } } else { ReturnJson(ctx, "userCookieError", nil) } }) r.POST("/addorder", func(ctx *gin.Context) { isAuth, user, data := AuthenticationAuthority(ctx) if isAuth { //需要处理提交的数据,接口有固定的数据格式,不允许乱搞 //fmt.Println(isAuth) //fmt.Println(user) //DebugPrintJson(data) var jsondata From_purchase_addorder //fmt.Println(data) if err := decodeJSON(data, &jsondata); err == nil { //jsonStr, _ := json.MarshalIndent(jsondata, "", " ") //fmt.Println("转换后数据:\n", string(jsonStr)) //数据比较混乱 在这里校验 //判断标题不为空 is_data_ok := true if jsondata.Title == "" { is_data_ok = false } //判断数量与价格是否为负数 for i := 0; i < len(jsondata.Costs); i++ { if jsondata.Costs[i].Cost <= 0 { is_data_ok = false } if jsondata.Costs[i].Int <= 0 { is_data_ok = false } } //判断图片是否为哈希值 for i := 0; i < len(jsondata.Photos); i++ { //判断字符串是否包含标点符号 if models.IsContainsSpecialChar(jsondata.Photos[i]) { is_data_ok = false //fmt.Println("err4") } } //判断时间字符串是否合法 // uptime, e := models.StringToTimePtr(jsondata.UpdateTime) // if e != nil { // is_data_ok = false // fmt.Println("err5") // } if is_data_ok { //校验通过 //photos, _ := json.Marshal(jsondata.Photos) new_data := TabPurchaseOrder{ UserID: user.ID, Title: jsondata.Title, Remark: jsondata.Remark, Link: jsondata.Link, Styles: jsondata.Styles, OrderStatus: "pending", // 默认待处理 } models.DB.Create(&new_data) for i := 0; i < len(jsondata.Costs); i++ { currencyType := jsondata.Costs[i].CurrencyType if currencyType <= 0 { currencyType = 1 // 默认 CNY } costType := jsondata.Costs[i].Type if costType <= 0 { costType = 1 // 默认单价 } new_cost_data := TabPurchaseCosts{ Price: jsondata.Costs[i].Cost, Quantity: jsondata.Costs[i].Int, UserID: user.ID, OrderID: new_data.ID, CurrencyType: currencyType, CostType: costType, } models.DB.Create(&new_cost_data) } //绑定文件 for i := 0; i < len(jsondata.Photos); i++ { findFile := TabFileInfo{ Sha256: jsondata.Photos[i], Type: "image", } if models.DB.Where(&findFile).First(&findFile).Error == nil { bind := TabPurchaseFileBind{ OrderID: new_data.ID, FileID: findFile.ID, } models.DB.Create(&bind) } } // 写创建日志 newContent, _ := json.Marshal(jsondata) tosqllog := TabPurchaseLog{ UserID: user.ID, OrderID: new_data.ID, ActionType: "create", NewContent: string(newContent), OldContent: "", IP: ctx.ClientIP(), } models.DB.Create(&tosqllog) // 写状态创建 commit commitLog := TabPurchaseCommit{ OrderID: new_data.ID, UserID: user.ID, Action: "create", Status: "pending", OldStatus: "", Comment: "订单创建", IP: ctx.ClientIP(), } models.DB.Create(&commitLog) ReturnJson(ctx, "apiOK", nil) } else { ReturnJson(ctx, "jsonErr_1", nil) } } else { ReturnJson(ctx, "jsonErr", nil) } } else { ReturnJson(ctx, "userCookieError", nil) } //ReturnJson(ctx, "apiErr", nil) }) // 编辑订单基本信息 r.POST("/updateorder", func(ctx *gin.Context) { isAuth, user, data := AuthenticationAuthority(ctx) if !isAuth { ReturnJson(ctx, "userCookieError", nil) return } type FromUpdateOrder struct { ID uint `json:"id"` Title string `json:"title"` Remark string `json:"remark"` Link string `json:"link"` Styles string `json:"styles"` Photos []string `json:"photos"` Costs []CostItem `json:"costs"` } var from FromUpdateOrder if err := decodeJSON(data, &from); err != nil || from.ID == 0 { ReturnJson(ctx, "jsonErr", nil) return } if from.Title == "" { ReturnJson(ctx, "jsonErr_1", nil) return } // 校验图片哈希 for _, hash := range from.Photos { if models.IsContainsSpecialChar(hash) { ReturnJson(ctx, "photo_hash_invalid", nil) return } } // 校验费用 for _, c := range from.Costs { if c.Cost <= 0 || c.Int <= 0 { ReturnJson(ctx, "jsonErr_1", nil) return } } var order TabPurchaseOrder if err := models.DB.Where("id = ?", from.ID).First(&order).Error; err != nil { ReturnJson(ctx, "order_not_found", nil) return } // 权限校验:只有创建者或管理员可以修改 if !canModifyPurchase(user.ID, order.UserID) { ReturnJson(ctx, "no_permission", nil) return } // 记录旧数据 oldContent, _ := json.Marshal(order) // 更新基本字段 if err := models.DB.Model(&order).Updates(map[string]interface{}{ "title": from.Title, "remark": from.Remark, "link": from.Link, "styles": from.Styles, }).Error; err != nil { ReturnJson(ctx, "apiErr", nil) return } // 重建费用明细:先删旧,再插新 models.DB.Where("order_id = ?", from.ID).Delete(&TabPurchaseCosts{}) for _, c := range from.Costs { ct := c.CurrencyType if ct <= 0 { ct = 1 } ctype := c.Type if ctype <= 0 { ctype = 1 } models.DB.Create(&TabPurchaseCosts{ Price: c.Cost, Quantity: c.Int, UserID: user.ID, OrderID: from.ID, CurrencyType: ct, CostType: ctype, }) } // 重建图片绑定:先删旧,再插新 models.DB.Where("order_id = ?", from.ID).Delete(&TabPurchaseFileBind{}) for _, hash := range from.Photos { findFile := TabFileInfo{Sha256: hash, Type: "image"} if models.DB.Where(&findFile).First(&findFile).Error == nil { models.DB.Create(&TabPurchaseFileBind{ OrderID: from.ID, FileID: findFile.ID, }) } } // 写操作日志 newContent, _ := json.Marshal(from) models.DB.Create(&TabPurchaseLog{ UserID: user.ID, OrderID: from.ID, ActionType: "update", OldContent: string(oldContent), NewContent: string(newContent), IP: ctx.ClientIP(), }) ReturnJson(ctx, "apiOK", nil) }) // 删除订单 r.POST("/deleteorder", func(ctx *gin.Context) { isAuth, user, data := AuthenticationAuthority(ctx) if !isAuth { ReturnJson(ctx, "userCookieError", nil) return } type FromDeleteOrder struct { ID uint `json:"id"` } var from FromDeleteOrder if err := decodeJSON(data, &from); err != nil || from.ID == 0 { ReturnJson(ctx, "jsonErr", nil) return } var order TabPurchaseOrder if err := models.DB.Where("id = ?", from.ID).First(&order).Error; err != nil { ReturnJson(ctx, "order_not_found", nil) return } // 权限校验:只有创建者或管理员可以删除 if !canModifyPurchase(user.ID, order.UserID) { ReturnJson(ctx, "no_permission", nil) return } // 关联删除(硬删,不保留) models.DB.Where("order_id = ?", from.ID).Delete(&TabPurchaseCosts{}) models.DB.Where("order_id = ?", from.ID).Delete(&TabPurchaseFileBind{}) models.DB.Where("order_id = ?", from.ID).Delete(&TabPurchaseCommit{}) models.DB.Where("order_id = ?", from.ID).Delete(&TabPurchaseLog{}) // 软删除订单本身 models.DB.Delete(&order) ReturnJson(ctx, "apiOK", nil) }) // 获取订单数量统计 r.POST("/getordercount", func(ctx *gin.Context) { isAuth, _, _ := AuthenticationAuthority(ctx) if !isAuth { ReturnJson(ctx, "userCookieError", nil) return } type OrderCount struct { Pending int64 `json:"pending"` // 待处理 Ordered int64 `json:"ordered"` // 已下单 Arrived int64 `json:"arrived"` // 已到达 Received int64 `json:"received"` // 已收件 Lost int64 `json:"lost"` // 丢件 Returned int64 `json:"returned"` // 退件 Total int64 `json:"total"` // 总数 } var count OrderCount models.DB.Model(&TabPurchaseOrder{}).Count(&count.Total) models.DB.Model(&TabPurchaseOrder{}).Where("order_status = ?", "pending").Count(&count.Pending) models.DB.Model(&TabPurchaseOrder{}).Where("order_status = ?", "ordered").Count(&count.Ordered) models.DB.Model(&TabPurchaseOrder{}).Where("order_status = ?", "arrived").Count(&count.Arrived) models.DB.Model(&TabPurchaseOrder{}).Where("order_status = ?", "received").Count(&count.Received) models.DB.Model(&TabPurchaseOrder{}).Where("order_status = ?", "lost").Count(&count.Lost) models.DB.Model(&TabPurchaseOrder{}).Where("order_status = ?", "returned").Count(&count.Returned) ReturnJson(ctx, "apiOK", map[string]interface{}{ "pending": count.Pending, "ordered": count.Ordered, "arrived": count.Arrived, "received": count.Received, "lost": count.Lost, "returned": count.Returned, "total": count.Total, }) }) // 搜索工单(用于采购订单关联) r.POST("/search_work_orders", func(ctx *gin.Context) { isAuth, _, data := AuthenticationAuthority(ctx) if !isAuth { ReturnJson(ctx, "userCookieError", nil) return } type FromSearch struct { Search string `json:"search"` Limit int `json:"limit"` } var from FromSearch if err := decodeJSON(data, &from); err != nil { ReturnJson(ctx, "jsonErr", nil) return } if from.Limit <= 0 || from.Limit > 20 { from.Limit = 5 } query := models.DB.Model(&TabWorkOrder{}) if from.Search != "" { var id uint if _, err := parsefmt.Sscanf(from.Search, "%d", &id); err == nil && id > 0 { query = query.Where("id = ?", id) } else { query = query.Where("title LIKE ? OR description LIKE ?", "%"+from.Search+"%", "%"+from.Search+"%") } } var orders []TabWorkOrder query.Order("updated_at DESC, id DESC").Limit(from.Limit).Find(&orders) type WorkOrderInfo struct { ID uint `json:"id"` Title string `json:"title"` Status string `json:"status"` } var result []WorkOrderInfo for _, o := range orders { result = append(result, WorkOrderInfo{ ID: o.ID, Title: o.Title, Status: o.CurrentStatus, }) } if result == nil { result = []WorkOrderInfo{} } ReturnJson(ctx, "apiOK", gin.H{"orders": result}) }) }