diff --git a/backend/my_work/agents/function_tool.go b/backend/my_work/agents/function_tool.go index c741e40..6b3d38a 100644 --- a/backend/my_work/agents/function_tool.go +++ b/backend/my_work/agents/function_tool.go @@ -26,9 +26,10 @@ type FunctionToolRuntime struct { // 这类工具的返回结果已经包含了用户问题所需的全部数据,调用之后无需再让模型 // 决定是否继续调用其他工具,直接进入最终回答生成阶段,可以省掉一轮模型请求。 var terminalFunctionTools = map[string]bool{ - "ops_ai_assistant": true, - "ops_ai_assistant_schedule_query": true, - "ops_ai_assistant_purchase_query": true, + "ops_ai_assistant": true, + "ops_ai_assistant_schedule_query": true, + "ops_ai_assistant_purchase_query": true, + "ops_ai_assistant_work_order_query": true, } // IsTerminalFunctionTool 判断给定工具名是否为终止工具。命名匹配采用与 @@ -60,6 +61,8 @@ func FunctionToolSchemas(configs []ToolConfig) []FunctionToolSchema { tools = append(tools, opsAIAssistantCurrentUserSchema()) case "ops_ai_assistant_purchase_query": tools = append(tools, opsAIAssistantPurchaseQuerySchema()) + case "ops_ai_assistant_work_order_query": + tools = append(tools, opsAIAssistantWorkOrderQuerySchema()) } } return tools @@ -99,6 +102,8 @@ func ExecuteFunctionTool(ctx context.Context, runtime FunctionToolRuntime, name return executeOpsAIAssistantCurrentUser(ctx, runtime, rawArgs) case "ops_ai_assistant_purchase_query": return executeOpsAIAssistantPurchaseQuery(ctx, runtime, rawArgs) + case "ops_ai_assistant_work_order_query": + return executeOpsAIAssistantWorkOrderQuery(ctx, runtime, rawArgs) default: return nil, fmt.Errorf("unknown tool: %s", name) } diff --git a/backend/my_work/agents/ops_ai_assistant.go b/backend/my_work/agents/ops_ai_assistant.go index 4b3a5d3..3cfc6a7 100644 --- a/backend/my_work/agents/ops_ai_assistant.go +++ b/backend/my_work/agents/ops_ai_assistant.go @@ -42,6 +42,85 @@ type PurchaseQueryArgs struct { Limit int `json:"limit,omitempty"` } +type WorkOrderQueryArgs struct { + Action string `json:"action"` + OrderID uint `json:"order_id,omitempty"` + Search string `json:"search,omitempty"` + Status string `json:"status,omitempty"` + StartDate string `json:"start_date,omitempty"` + EndDate string `json:"end_date,omitempty"` + Page int `json:"page,omitempty"` + Limit int `json:"limit,omitempty"` +} + +type WorkOrderQuery struct { + Action string + OrderID uint + Search string + Status string + StartDate string + EndDate string + Page int + Limit int + UserID uint +} + +type WorkOrderCommit struct { + ID uint `json:"id"` + WorkOrderID uint `json:"work_order_id"` + UserID uint `json:"user_id"` + Action string `json:"action"` + Status string `json:"status,omitempty"` + StatusName string `json:"status_name,omitempty"` + OldStatus string `json:"old_status,omitempty"` + Comment string `json:"comment,omitempty"` + CreatedAt string `json:"created_at,omitempty"` +} + +type WorkOrderCustomer struct { + ID uint `json:"id"` + FirstName string `json:"first_name,omitempty"` + LastName string `json:"last_name,omitempty"` + PrimaryPhone string `json:"primary_phone,omitempty"` +} + +type WorkOrderItem struct { + ID uint `json:"id"` + Name string `json:"name,omitempty"` + SerialNumber string `json:"serial_number,omitempty"` +} + +type WorkOrder struct { + ID uint `json:"id"` + UserID uint `json:"user_id"` + Title string `json:"title"` + Description string `json:"description,omitempty"` + DetailURL string `json:"detail_url"` + CurrentStatus string `json:"current_status"` + CurrentStatusName string `json:"current_status_name"` + CreatedAt string `json:"created_at,omitempty"` + UpdatedAt string `json:"updated_at,omitempty"` + CanModify bool `json:"can_modify,omitempty"` + Customers []WorkOrderCustomer `json:"customers,omitempty"` + Items []WorkOrderItem `json:"items,omitempty"` + Commits []WorkOrderCommit `json:"commits,omitempty"` +} + +type WorkOrderQueryResult struct { + Ok bool `json:"ok"` + Action string `json:"action"` + LoggedIn bool `json:"loggedIn"` + Count int `json:"count,omitempty"` + Total int64 `json:"total,omitempty"` + Page int `json:"page,omitempty"` + Limit int `json:"limit,omitempty"` + Orders []WorkOrder `json:"orders,omitempty"` + Order *WorkOrder `json:"order,omitempty"` + Counts map[string]int64 `json:"counts,omitempty"` + Filters map[string]interface{} `json:"filters,omitempty"` + Message string `json:"message,omitempty"` +} + type PurchaseQuery struct { Action string OrderID uint @@ -177,8 +256,13 @@ type PurchaseProvider interface { QueryPurchases(ctx context.Context, query PurchaseQuery) (*PurchaseQueryResult, error) } +type WorkOrderProvider interface { + QueryWorkOrders(ctx context.Context, query WorkOrderQuery) (*WorkOrderQueryResult, error) +} + var registeredScheduleProvider ScheduleProvider = nil var registeredPurchaseProvider PurchaseProvider = nil +var registeredWorkOrderProvider WorkOrderProvider = nil func RegisterScheduleProvider(provider ScheduleProvider) { registeredScheduleProvider = provider @@ -188,6 +272,10 @@ func RegisterPurchaseProvider(provider PurchaseProvider) { registeredPurchaseProvider = provider } +func RegisterWorkOrderProvider(provider WorkOrderProvider) { + registeredWorkOrderProvider = provider +} + func opsAIAssistantScheduleQuerySchema() FunctionToolSchema { return FunctionToolSchema{ Name: "ops_ai_assistant_schedule_query", @@ -254,6 +342,31 @@ func opsAIAssistantPurchaseQuerySchema() FunctionToolSchema { } } +func opsAIAssistantWorkOrderQuerySchema() FunctionToolSchema { + return FunctionToolSchema{ + Name: "ops_ai_assistant_work_order_query", + Description: "只读工具:查询工单(维修/服务)模块。用户询问工单、维修单、待处理/已检查/已下单零件/已维修/已送还/无法维修数量或列表、指定工单详情时调用。禁止新增、修改、删除工单或状态。", + Parameters: map[string]interface{}{ + "type": "object", + "properties": map[string]interface{}{ + "action": map[string]interface{}{ + "type": "string", + "enum": []string{"list", "get", "count"}, + "description": "list 查询工单列表;get 查询单个工单详情;count 统计各状态数量。", + }, + "order_id": map[string]interface{}{"type": "integer", "description": "action=get 时的工单 ID。"}, + "search": map[string]interface{}{"type": "string", "description": "按工单 ID、标题或问题描述搜索。"}, + "status": map[string]interface{}{"type": "string", "enum": []string{"", "pending", "checked", "parts_ordered", "repaired", "returned", "unrepairable"}, "description": "工单状态过滤:pending 待处理,checked 已检查,parts_ordered 已下单零件,repaired 已维修,returned 已送还,unrepairable 无法维修。"}, + "start_date": map[string]interface{}{"type": "string", "description": "可选创建日期开始,格式 YYYY-MM-DD。"}, + "end_date": map[string]interface{}{"type": "string", "description": "可选创建日期结束,格式 YYYY-MM-DD。"}, + "page": map[string]interface{}{"type": "integer", "description": "分页页码,默认 1。"}, + "limit": map[string]interface{}{"type": "integer", "description": "返回上限,默认 20,最大 100。"}, + }, + "required": []string{"action"}, + }, + } +} + func executeOpsAIAssistantCurrentUser(ctx context.Context, runtime FunctionToolRuntime, rawArgs []byte) ([]byte, error) { var args CurrentUserArgs if len(rawArgs) > 0 { @@ -361,6 +474,78 @@ func executeOpsAIAssistantPurchaseQuery(ctx context.Context, runtime FunctionToo return json.Marshal(result) } +func executeOpsAIAssistantWorkOrderQuery(ctx context.Context, runtime FunctionToolRuntime, rawArgs []byte) ([]byte, error) { + var args WorkOrderQueryArgs + if err := json.Unmarshal(rawArgs, &args); err != nil { + return nil, err + } + if args.Action != "list" && args.Action != "get" && args.Action != "count" { + return json.Marshal(map[string]interface{}{ + "ok": false, + "error": "ops_ai_assistant_work_order_query 是只读工具,仅允许 list/get/count 查询操作", + }) + } + if runtime.UserID <= 0 { + return json.Marshal(map[string]interface{}{ + "ok": true, + "action": args.Action, + "loggedIn": false, + "message": "需要登录才能查询工单模块信息。", + }) + } + if args.Action == "get" && args.OrderID <= 0 { + return nil, fmt.Errorf("order_id is required when action=get") + } + if args.StartDate != "" { + if _, err := time.Parse("2006-01-02", args.StartDate); err != nil { + return nil, fmt.Errorf("invalid start_date: %w", err) + } + } + if args.EndDate != "" { + if _, err := time.Parse("2006-01-02", args.EndDate); err != nil { + return nil, fmt.Errorf("invalid end_date: %w", err) + } + } + if args.StartDate != "" && args.EndDate != "" { + startDate, _ := time.Parse("2006-01-02", args.StartDate) + endDate, _ := time.Parse("2006-01-02", args.EndDate) + if endDate.Before(startDate) { + return nil, fmt.Errorf("end_date must be after start_date") + } + } + if args.Page <= 0 { + args.Page = 1 + } + if args.Limit <= 0 { + args.Limit = 20 + } + if args.Limit > 100 { + args.Limit = 100 + } + if registeredWorkOrderProvider == nil { + return json.Marshal(map[string]interface{}{ + "ok": false, + "error": "工单查询服务未注册", + }) + } + + result, err := registeredWorkOrderProvider.QueryWorkOrders(ctx, WorkOrderQuery{ + Action: args.Action, + OrderID: args.OrderID, + Search: args.Search, + Status: args.Status, + StartDate: args.StartDate, + EndDate: args.EndDate, + Page: args.Page, + Limit: args.Limit, + UserID: runtime.UserID, + }) + if err != nil { + return nil, err + } + return json.Marshal(result) +} + func executeOpsAIAssistantScheduleQuery(ctx context.Context, runtime FunctionToolRuntime, rawArgs []byte) ([]byte, error) { var args ScheduleQueryArgs if err := json.Unmarshal(rawArgs, &args); err != nil { diff --git a/backend/my_work/models/configs.go b/backend/my_work/models/configs.go index ec050d8..6ecac1a 100644 --- a/backend/my_work/models/configs.go +++ b/backend/my_work/models/configs.go @@ -108,6 +108,7 @@ func ConfigAllInit() error { {Name: "time", Enabled: true, Description: "解析当前时间、相对日期和日期范围。"}, {Name: "ops_ai_assistant_current_user", Enabled: true, Description: "返回当前登录用户信息;未登录时提示需要登录才能获取信息。"}, {Name: "ops_ai_assistant_purchase_query", Enabled: true, Description: "查询采购订单列表、详情和状态数量统计。"}, + {Name: "ops_ai_assistant_work_order_query", Enabled: true, Description: "查询工单(维修/服务)列表、详情和状态数量统计。"}, {Name: "ops_ai_assistant_calendar_list", Enabled: true, Description: "查询当前用户可见的 OPS 日历列表。"}, {Name: "ops_ai_assistant_schedule_query", Enabled: true, Description: "按日期范围查询当前用户可见的 OPS 日历/日程。"}, {Name: "ops_ai_assistant_schedule_create", Enabled: true, Description: "创建 OPS 日历日程。"}, diff --git a/backend/my_work/routers/apiAIChat.go b/backend/my_work/routers/apiAIChat.go index 3f2b57f..f0d83d0 100644 --- a/backend/my_work/routers/apiAIChat.go +++ b/backend/my_work/routers/apiAIChat.go @@ -319,7 +319,7 @@ func handleChat(ctx *gin.Context) { toolNames = append(toolNames, tool.Function.Name) } emitTrace("function_tools", "prepare", "success", "已启用 Function Calling 工具", map[string]interface{}{"tools": toolNames}) - openaiMsgs = append([]openaiMessage{{Role: "system", Content: "可用工具使用规则:当用户询问“我是谁”“当前登录用户是谁”“我的用户信息”等当前身份问题时,调用 ops_ai_assistant_current_user;工具返回 loggedIn=true 时按工具结果回答当前用户信息,返回 loggedIn=false 时说明不知道并提示需要登录才能获取信息。当用户询问采购订单列表、采购订单详情、采购状态或数量统计时,调用 ops_ai_assistant_purchase_query;该工具只允许查询,禁止新增、修改、删除采购数据。当用户询问本月、今天、本周、下周等相对日期的日程时,先调用 time 获取明确 start_date/end_date,再调用 ops_ai_assistant_schedule_query 查询日程。用户询问有哪些日历或创建日程但未提供 calendar_id 时,调用 ops_ai_assistant_calendar_list 获取可用日历;用户明确要求新增/修改/删除日程时,分别调用 ops_ai_assistant_schedule_create、ops_ai_assistant_schedule_update、ops_ai_assistant_schedule_delete,写入工具必须基于用户明确指令和明确日期,不要自行猜测日历或事件 ID。不要臆造工具结果中不存在的信息。"}}, openaiMsgs...) + openaiMsgs = append([]openaiMessage{{Role: "system", Content: "可用工具使用规则:当用户询问“我是谁”“当前登录用户是谁”“我的用户信息”等当前身份问题时,调用 ops_ai_assistant_current_user;工具返回 loggedIn=true 时按工具结果回答当前用户信息,返回 loggedIn=false 时说明不知道并提示需要登录才能获取信息。当用户询问采购订单列表、采购订单详情、采购状态或数量统计时,调用 ops_ai_assistant_purchase_query;该工具只允许查询,禁止新增、修改、删除采购数据。当用户询问工单、维修单、服务单、工单状态(待处理/已检查/已下单零件/已维修/已送还/无法维修)或数量统计时,调用 ops_ai_assistant_work_order_query;该工具只允许查询,禁止新增、修改、删除工单数据。当用户询问本月、今天、本周、下周等相对日期的日程时,先调用 time 获取明确 start_date/end_date,再调用 ops_ai_assistant_schedule_query 查询日程。用户询问有哪些日历或创建日程但未提供 calendar_id 时,调用 ops_ai_assistant_calendar_list 获取可用日历;用户明确要求新增/修改/删除日程时,分别调用 ops_ai_assistant_schedule_create、ops_ai_assistant_schedule_update、ops_ai_assistant_schedule_delete,写入工具必须基于用户明确指令和明确日期,不要自行猜测日历或事件 ID。不要臆造工具结果中不存在的信息。"}}, openaiMsgs...) var toolExecuted bool openaiMsgs, toolExecuted, err = runOpenAIToolLoop(ctx.Request.Context(), profile, openaiMsgs, functionTools, currentUser, tracker, emitTrace) if err != nil { diff --git a/backend/my_work/routers/apiAIChatConfig.go b/backend/my_work/routers/apiAIChatConfig.go index a6ebddb..e2c171e 100644 --- a/backend/my_work/routers/apiAIChatConfig.go +++ b/backend/my_work/routers/apiAIChatConfig.go @@ -111,6 +111,7 @@ func ensureBuiltinAIChatTools() error { {Name: "time", Enabled: true, Description: "解析当前时间、相对日期和日期范围。", SortOrder: 0}, {Name: "ops_ai_assistant_current_user", Enabled: true, Description: "返回当前登录用户信息;未登录时提示需要登录才能获取信息。", SortOrder: 5}, {Name: "ops_ai_assistant_purchase_query", Enabled: true, Description: "查询采购订单列表、详情和状态数量统计。", SortOrder: 8}, + {Name: "ops_ai_assistant_work_order_query", Enabled: true, Description: "查询工单(维修/服务)列表、详情和状态数量统计。", SortOrder: 9}, {Name: "ops_ai_assistant_calendar_list", Enabled: true, Description: "查询当前用户可见的 OPS 日历列表。", SortOrder: 10}, {Name: "ops_ai_assistant_schedule_query", Enabled: true, Description: "按日期范围查询当前用户可见的 OPS 日历/日程。", SortOrder: 12}, {Name: "ops_ai_assistant_schedule_create", Enabled: true, Description: "创建 OPS 日历日程。", SortOrder: 14}, @@ -212,6 +213,7 @@ func seedAIChatConfigFromYAMLIfEmpty() error { {Name: "time", Enabled: true, Description: "解析当前时间、相对日期和日期范围。"}, {Name: "ops_ai_assistant_current_user", Enabled: true, Description: "返回当前登录用户信息;未登录时提示需要登录才能获取信息。"}, {Name: "ops_ai_assistant_purchase_query", Enabled: true, Description: "查询采购订单列表、详情和状态数量统计。"}, + {Name: "ops_ai_assistant_work_order_query", Enabled: true, Description: "查询工单(维修/服务)列表、详情和状态数量统计。"}, {Name: "ops_ai_assistant_calendar_list", Enabled: true, Description: "查询当前用户可见的 OPS 日历列表。"}, {Name: "ops_ai_assistant_schedule_query", Enabled: true, Description: "按日期范围查询当前用户可见的 OPS 日历/日程。"}, {Name: "ops_ai_assistant_schedule_create", Enabled: true, Description: "创建 OPS 日历日程。"}, diff --git a/backend/my_work/routers/apiWorkOrder.go b/backend/my_work/routers/apiWorkOrder.go index 3680efa..6ea42d6 100644 --- a/backend/my_work/routers/apiWorkOrder.go +++ b/backend/my_work/routers/apiWorkOrder.go @@ -1,8 +1,10 @@ package routers import ( + "context" "encoding/json" parsefmt "fmt" + "ops/agents" "ops/models" "slices" "time" @@ -81,6 +83,274 @@ type PurchaseOrderInfo struct { Status string `json:"status"` } +// ---------- AI 工单查询 Provider ---------- + +type workOrderProvider struct{} + +func (workOrderProvider) QueryWorkOrders(ctx context.Context, query agents.WorkOrderQuery) (*agents.WorkOrderQueryResult, error) { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + result := &agents.WorkOrderQueryResult{ + Ok: true, + Action: query.Action, + LoggedIn: query.UserID > 0, + } + if !result.LoggedIn { + result.Message = "需要登录才能查询工单模块信息。" + return result, nil + } + + switch query.Action { + case "count": + counts, err := queryWorkOrderCounts(query) + if err != nil { + return nil, err + } + result.Counts = counts + return result, nil + case "get": + order, err := queryWorkOrderDetail(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 := queryWorkOrderList(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 applyWorkOrderQueryFilters(db *gorm.DB, query agents.WorkOrderQuery) (*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 description LIKE ?", "%"+query.Search+"%", "%"+query.Search+"%") + } + } + if query.Status != "" { + db = db.Where("current_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 queryWorkOrderList(query agents.WorkOrderQuery) ([]agents.WorkOrder, int64, error) { + db, err := applyWorkOrderQueryFilters(models.DB.Model(&TabWorkOrder{}), 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 []TabWorkOrder + if err := db.Order("id DESC").Offset(query.Limit * (query.Page - 1)).Limit(query.Limit).Find(&rows).Error; err != nil { + return nil, 0, err + } + + orders := make([]agents.WorkOrder, 0, len(rows)) + for _, row := range rows { + order := buildWorkOrder(row, query.UserID) + order.Customers = loadWorkOrderCustomers(row.ID) + order.Items = loadWorkOrderItems(row.ID) + orders = append(orders, order) + } + return orders, total, nil +} + +func queryWorkOrderDetail(orderID uint, userID uint) (*agents.WorkOrder, error) { + var row TabWorkOrder + if err := models.DB.Where("id = ?", orderID).First(&row).Error; err != nil { + if err == gorm.ErrRecordNotFound { + return nil, nil + } + return nil, err + } + order := buildWorkOrder(row, userID) + order.Customers = loadWorkOrderCustomers(orderID) + order.Items = loadWorkOrderItems(orderID) + + var commits []TabWorkOrderCommit + if err := models.DB.Where("work_order_id = ?", orderID).Order("created_at DESC").Find(&commits).Error; err != nil { + return nil, err + } + order.Commits = make([]agents.WorkOrderCommit, 0, len(commits)) + for _, commit := range commits { + order.Commits = append(order.Commits, agents.WorkOrderCommit{ + ID: commit.ID, + WorkOrderID: commit.WorkOrderID, + UserID: commit.UserID, + Action: commit.Action, + Status: commit.Status, + StatusName: workOrderStatusName(commit.Status), + OldStatus: commit.OldStatus, + Comment: commit.Comment, + CreatedAt: formatTimePtr(commit.CreatedAt), + }) + } + return &order, nil +} + +func queryWorkOrderCounts(query agents.WorkOrderQuery) (map[string]int64, error) { + counts := map[string]int64{} + statuses := []string{"pending", "checked", "parts_ordered", "repaired", "returned", "unrepairable"} + var total int64 + base, err := applyWorkOrderQueryFilters(models.DB.Model(&TabWorkOrder{}), agents.WorkOrderQuery{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.WorkOrderQuery{Search: query.Search, Status: status, StartDate: query.StartDate, EndDate: query.EndDate} + db, err := applyWorkOrderQueryFilters(models.DB.Model(&TabWorkOrder{}), 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 buildWorkOrder(row TabWorkOrder, currentUserID uint) agents.WorkOrder { + return agents.WorkOrder{ + ID: row.ID, + UserID: row.UserID, + Title: row.Title, + Description: row.Description, + DetailURL: workOrderDetailURL(row.ID), + CurrentStatus: row.CurrentStatus, + CurrentStatusName: workOrderStatusName(row.CurrentStatus), + CreatedAt: formatTimePtr(row.CreatedAt), + UpdatedAt: formatTimePtr(row.UpdatedAt), + CanModify: canModifyWorkOrder(currentUserID, row.UserID), + } +} + +func loadWorkOrderCustomers(workOrderID uint) []agents.WorkOrderCustomer { + var customerBinds []TabWorkOrderCustomerBind + models.DB.Where("work_order_id = ?", workOrderID).Find(&customerBinds) + if len(customerBinds) == 0 { + return nil + } + customerIDs := make([]uint, 0, len(customerBinds)) + for _, b := range customerBinds { + customerIDs = append(customerIDs, b.CustomerID) + } + var customers []TabCustomer + if err := models.DB.Where("id IN ?", customerIDs).Find(&customers).Error; err != nil { + return nil + } + result := make([]agents.WorkOrderCustomer, 0, len(customers)) + for _, c := range customers { + item := agents.WorkOrderCustomer{ + ID: c.ID, + FirstName: c.FirstName, + LastName: c.LastName, + } + var phone TabCustomerPhone + if err := models.DB.Where("customer_id = ? AND is_primary = ?", c.ID, true).First(&phone).Error; err == nil { + item.PrimaryPhone = phone.Phone + } else if err := models.DB.Where("customer_id = ?", c.ID).First(&phone).Error; err == nil { + item.PrimaryPhone = phone.Phone + } + result = append(result, item) + } + return result +} + +func loadWorkOrderItems(workOrderID uint) []agents.WorkOrderItem { + var itemBinds []TabWarehouseItemWorkOrderBind + models.DB.Where("work_order_id = ?", workOrderID).Find(&itemBinds) + if len(itemBinds) == 0 { + return nil + } + itemIDs := make([]uint, 0, len(itemBinds)) + for _, b := range itemBinds { + itemIDs = append(itemIDs, b.ItemID) + } + var items []TabWarehouseItem + if err := models.DB.Where("id IN ?", itemIDs).Find(&items).Error; err != nil { + return nil + } + result := make([]agents.WorkOrderItem, 0, len(items)) + for _, it := range items { + result = append(result, agents.WorkOrderItem{ + ID: it.ID, + Name: it.Name, + SerialNumber: it.SerialNumber, + }) + } + return result +} + +func workOrderDetailURL(orderID uint) string { + return parsefmt.Sprintf("/workorder/showorder/%d", orderID) +} + +func workOrderStatusName(status string) string { + switch status { + case "pending": + return "待处理" + case "checked": + return "已检查" + case "parts_ordered": + return "已下单零件" + case "repaired": + return "已维修" + case "returned": + return "已送还" + case "unrepairable": + return "无法维修" + default: + return status + } +} + // ---------- 初始化 ---------- func ApiWorkOrderInit() { @@ -96,6 +366,8 @@ func ApiWorkOrderInit() { workOrderUserGroup.Type = "usergroup" models.DB.Create(&workOrderUserGroup) } + + agents.RegisterWorkOrderProvider(workOrderProvider{}) } // ---------- 路由注册 ----------