diff --git a/.workbuddy/memory/2026-04-29.md b/.workbuddy/memory/2026-04-29.md index 8a110f5..2daa805 100644 --- a/.workbuddy/memory/2026-04-29.md +++ b/.workbuddy/memory/2026-04-29.md @@ -41,3 +41,16 @@ - `AddEditWorkOrder.vue`: 编辑模式加载时回填 `selectedItems` 和 `selectedCustomers` - `AddEditWorkOrder.vue`: 编辑提交时发送 `item_ids` 和 `customer_ids` - `apiWorkOrder.go`: `/update` 接口新增 `ItemIDs` 和 `CustomerIDs` 字段,重建物品/客户关联绑定 +- 仓库物品添加页支持关联客户 + - `binds.go`: 新增 `TabWarehouseItemCustomerBind` 关联表(物品-客户多对多) + - `apiWarehouse.go`: `/add_item` 接口新增 `CustomerIDs` 字段,仅新建物品时创建客户关联绑定 + - `WarehouseAddItem.vue`: 添加客户搜索选择组件(多选),提交时发送 `customer_ids` + - 使用已有的 `customerApi.list()` 搜索客户 +- 仓库物品详情页显示关联客户 + - `apiWarehouse.go`: `/get_item` 接口返回 `customers` 列表(包含客户 ID、姓名、称呼) + - `WarehouseItemDetail.vue`: 新增"关联客户" Tab,显示关联客户列表(头像、姓名、称呼),点击可跳转到客户详情页 + - i18n: 添加 `warehouse.customers` 和 `warehouse.no_customers` 翻译 +- 仓库物品编辑页支持关联客户 + - `apiWarehouse.go`: `/update_item` 接口新增 `CustomerIDs` 字段,重建客户关联绑定 + - `WarehouseItemEdit.vue`: 添加客户搜索选择组件(多选),加载时回填已关联客户,提交时发送 `customer_ids` + - i18n: 添加 `warehouse.linked_customers`、`warehouse.linked_customer_placeholder`、`warehouse.linked_customer_not_found` 等翻译到 `warehouse` 节点 diff --git a/backend/my_work/routers/apiWarehouse.go b/backend/my_work/routers/apiWarehouse.go index e661e96..9e59ac3 100644 --- a/backend/my_work/routers/apiWarehouse.go +++ b/backend/my_work/routers/apiWarehouse.go @@ -544,6 +544,7 @@ func ApiWarehouse(r *gin.RouterGroup) { Quantity int `json:"quantity"` ContainerID *uint `json:"container_id"` Photos []string `json:"photos"` + CustomerIDs []uint `json:"customer_ids"` } var from FromAdd if err := decodeJSON(data, &from); err != nil || from.Name == "" { @@ -673,6 +674,21 @@ func ApiWarehouse(r *gin.RouterGroup) { IP: ctx.ClientIP(), }) + // 绑定客户关联(仅新建时) + if !exists { + for _, customerID := range from.CustomerIDs { + // 检查客户是否存在 + var bindCustomer TabCustomer + if models.DB.First(&bindCustomer, customerID).Error == nil { + models.DB.Create(&TabWarehouseItemCustomerBind{ + ItemID: itemID, + CustomerID: customerID, + CreatorID: user.ID, + }) + } + } + } + ReturnJson(ctx, "apiOK", gin.H{"id": itemID, "updated": exists}) }) @@ -685,12 +701,13 @@ func ApiWarehouse(r *gin.RouterGroup) { } type FromUpdate struct { - ID uint `json:"id"` - Name string `json:"name"` - SerialNumber string `json:"serial_number"` - Remark string `json:"remark"` - Quantity int `json:"quantity"` - Photos []string `json:"photos"` + ID uint `json:"id"` + Name string `json:"name"` + SerialNumber string `json:"serial_number"` + Remark string `json:"remark"` + Quantity int `json:"quantity"` + Photos []string `json:"photos"` + CustomerIDs []uint `json:"customer_ids"` } var from FromUpdate if err := decodeJSON(data, &from); err != nil || from.ID == 0 || from.Name == "" { @@ -738,6 +755,19 @@ func ApiWarehouse(r *gin.RouterGroup) { } } + // 重建客户关联绑定 + models.DB.Where("item_id = ?", from.ID).Delete(&TabWarehouseItemCustomerBind{}) + for _, customerID := range from.CustomerIDs { + var bindCustomer TabCustomer + if models.DB.First(&bindCustomer, customerID).Error == nil { + models.DB.Create(&TabWarehouseItemCustomerBind{ + ItemID: from.ID, + CustomerID: customerID, + CreatorID: user.ID, + }) + } + } + newContent, _ := json.Marshal(from) models.DB.Create(&TabWarehouseLog{ EntityType: "item", @@ -966,11 +996,35 @@ func ApiWarehouse(r *gin.RouterGroup) { } } + // 关联客户 + var customerBinds []TabWarehouseItemCustomerBind + models.DB.Where("item_id = ?", from.ID).Find(&customerBinds) + + type CustomerInfo struct { + ID uint `json:"id"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Title string `json:"title"` + } + var customers []CustomerInfo + for _, b := range customerBinds { + var c TabCustomer + if models.DB.Where("id = ?", b.CustomerID).First(&c).Error == nil { + customers = append(customers, CustomerInfo{ + ID: c.ID, + FirstName: c.FirstName, + LastName: c.LastName, + Title: c.Title, + }) + } + } + ReturnJson(ctx, "apiOK", gin.H{ "item": item, "photos": files, "commits": commitsWithBreadcrumb, "work_orders": workOrders, + "customers": customers, "canModifyItem": canModifyWarehouse(user.ID, item.CreatorID), "container_breadcrumb": func() string { if item.ContainerID == nil { diff --git a/backend/my_work/routers/binds.go b/backend/my_work/routers/binds.go index 23752c8..e5199b3 100644 --- a/backend/my_work/routers/binds.go +++ b/backend/my_work/routers/binds.go @@ -73,6 +73,15 @@ type TabWarehouseContainerFileBind struct { CreatedAt time.Time `gorm:"type:datetime;autoCreateTime"` } +// TabWarehouseItemCustomerBind 物品与客户关联表 +type TabWarehouseItemCustomerBind struct { + ID uint `gorm:"primarykey"` + ItemID uint `gorm:"not null;index;comment:关联物品ID"` + CustomerID uint `gorm:"not null;index;comment:关联客户ID"` + CreatorID uint `gorm:"not null;comment:绑定人id"` + CreatedAt *time.Time `gorm:"type:datetime;autoCreateTime"` +} + func BindsInit() { models.DB.AutoMigrate( &TabPurchaseFileBind{}, @@ -83,6 +92,7 @@ func BindsInit() { &TabWorkOrderCommitFileBind{}, &TabWorkOrderPurchaseOrderBind{}, &TabWorkOrderCustomerBind{}, + &TabWarehouseItemCustomerBind{}, ) } diff --git a/frontend/ops_vue_js/src/api/users.js b/frontend/ops_vue_js/src/api/users.js index 59e9db1..619733b 100644 --- a/frontend/ops_vue_js/src/api/users.js +++ b/frontend/ops_vue_js/src/api/users.js @@ -1,7 +1,7 @@ import { api } from './index' export const usersApi = { - + getUserInfoFromUserID(UserID) { return api.get('/users/getuserinfo/'+UserID) }, diff --git a/frontend/ops_vue_js/src/i18n/en.json b/frontend/ops_vue_js/src/i18n/en.json index 50cde54..de39f7b 100644 --- a/frontend/ops_vue_js/src/i18n/en.json +++ b/frontend/ops_vue_js/src/i18n/en.json @@ -222,8 +222,10 @@ "move_item": "Move Item", "move_history": "Move History", "work_orders": "Work Orders", + "customers": "Customers", "no_move_history": "No move history", "no_work_orders": "No related work orders", + "no_customers": "No related customers", "current_location": "Current Location", "target_container": "Target Container", "search_container": "Search container...", @@ -234,7 +236,12 @@ "delete_item_msg": "Are you sure you want to delete item \"{name}\"? This action cannot be undone.", "items_in_containers": "Stored", "total_items": "{count} items", - "search_item_placeholder": "Search by name or serial number" + "search_item_placeholder": "Search by name or serial number", + "linked_customers": "Linked Customers", + "linked_customer_placeholder": "Search customer by name or phone...", + "linked_customer_not_found": "No matching customers found", + "linked_customer_selected": "Selected", + "clear_linked_customer": "Clear" }, "purchase_addorder": { "add_order": "Add Order", @@ -554,6 +561,7 @@ "created_at": "Created At", "primary": "Primary", "set_primary": "Set Primary", + "primary_contact": "Primary Contact", "label_mobile": "Mobile", "label_work": "Work", "label_home": "Home", diff --git a/frontend/ops_vue_js/src/i18n/zh-CN.json b/frontend/ops_vue_js/src/i18n/zh-CN.json index 2ffd577..6451947 100644 --- a/frontend/ops_vue_js/src/i18n/zh-CN.json +++ b/frontend/ops_vue_js/src/i18n/zh-CN.json @@ -222,8 +222,10 @@ "move_item": "移动物品", "move_history": "移动历史", "work_orders": "关联工单", + "customers": "关联客户", "no_move_history": "暂无移动记录", "no_work_orders": "暂无关联工单", + "no_customers": "暂无关联客户", "current_location": "当前位置", "target_container": "目标容器", "search_container": "搜索容器...", @@ -234,7 +236,12 @@ "delete_item_msg": "确定要删除物品「{name}」吗?此操作不可撤销。", "items_in_containers": "已入库", "total_items": "共 {count} 条", - "search_item_placeholder": "搜索物品名称或序列号" + "search_item_placeholder": "搜索物品名称或序列号", + "linked_customers": "关联客户", + "linked_customer_placeholder": "搜索客户姓名或电话...", + "linked_customer_not_found": "未找到匹配的客户", + "linked_customer_selected": "已选择", + "clear_linked_customer": "清除" }, "purchase_addorder": { "add_order": "添加订单", @@ -554,6 +561,7 @@ "created_at": "创建时间", "primary": "主", "set_primary": "设为主", + "primary_contact": "主要联系方式", "label_mobile": "手机", "label_work": "工作", "label_home": "家庭", diff --git a/frontend/ops_vue_js/src/views/customer/CustomerDetail.vue b/frontend/ops_vue_js/src/views/customer/CustomerDetail.vue index b849ce0..781fdf3 100644 --- a/frontend/ops_vue_js/src/views/customer/CustomerDetail.vue +++ b/frontend/ops_vue_js/src/views/customer/CustomerDetail.vue @@ -1,5 +1,5 @@