diff --git a/backend/my_work/routers/apiWarehouse.go b/backend/my_work/routers/apiWarehouse.go index a41f8d7..03a36b7 100644 --- a/backend/my_work/routers/apiWarehouse.go +++ b/backend/my_work/routers/apiWarehouse.go @@ -345,7 +345,8 @@ func ApiWarehouse(r *gin.RouterGroup) { } if from.ParentID != nil { query = query.Where("parent_id = ?", *from.ParentID) - } else { + } else if from.Search == "" { + // 无搜索时默认只显示顶级容器 query = query.Where("parent_id IS NULL") } query.Count(&count) @@ -385,6 +386,35 @@ func ApiWarehouse(r *gin.RouterGroup) { return } + // 构建父容器链(从根到当前) + type ParentItem struct { + ID uint `json:"id"` + Title string `json:"title"` + } + parentChain := []ParentItem{} + if c.ParentID != nil { + curID := *c.ParentID + visited := map[uint]bool{} + for curID != 0 { + if visited[curID] { + break + } + visited[curID] = true + var parent TabWarehouseContainer + if err := models.DB.Select("id, title, parent_id").Where("id = ?", curID).First(&parent).Error; err != nil { + break + } + parentChain = append([]ParentItem{{ID: parent.ID, Title: parent.Title}}, parentChain...) + if parent.ParentID == nil { + break + } + curID = *parent.ParentID + } + } + + // 计算当前容器深度(0=顶级,4=已达最大层级) + depth := len(parentChain) + // 关联图片 var binds []TabWarehouseContainerFileBind models.DB.Where("container_id = ?", from.ID).Find(&binds) @@ -398,12 +428,14 @@ func ApiWarehouse(r *gin.RouterGroup) { } ReturnJson(ctx, "apiOK", gin.H{ - "container": c, - "photos": files, + "container": c, + "photos": files, + "parent_chain": parentChain, + "depth": depth, }) }) - // 新增物品 + // 新增物品(查重逻辑:Name+SerialNumber相同则更新容器) r.POST("/add_item", func(ctx *gin.Context) { isAuth, user, data := AuthenticationAuthority(ctx) if !isAuth { @@ -438,46 +470,116 @@ func ApiWarehouse(r *gin.RouterGroup) { quantity = 1 } - item := TabWarehouseItem{ - Name: from.Name, - SerialNumber: from.SerialNumber, - Remark: from.Remark, - Quantity: quantity, - CreatedAt: strconv.FormatInt(time.Now().Unix(), 10), - CreatorID: user.ID, - ContainerID: from.ContainerID, - } - models.DB.Create(&item) + // 查重:Name + SerialNumber 相同则更新容器 + var existingItem TabWarehouseItem + exists := models.DB.Where("name = ? AND serial_number = ?", from.Name, from.SerialNumber).First(&existingItem).Error == nil - // 绑定图片 + var itemID uint + var oldContainer *uint + + if exists { + // 已有记录:更新容器 + oldContainer = existingItem.ContainerID + + // 同一容器无需操作,但仍然记录 commit + if !ptrEqUint(oldContainer, from.ContainerID) { + // 旧容器 ItemCount -1 + if oldContainer != nil { + models.DB.Model(&TabWarehouseContainer{}).Where("id = ?", *oldContainer).Update("item_count", models.DB.Raw("item_count - 1")) + } + // 新容器 ItemCount +1 + if from.ContainerID != nil { + models.DB.Model(&TabWarehouseContainer{}).Where("id = ?", *from.ContainerID).Update("item_count", models.DB.Raw("item_count + 1")) + } + // 更新物品容器 + existingItem.ContainerID = from.ContainerID + models.DB.Save(&existingItem) + } + + itemID = existingItem.ID + + // 写操作日志 + newContent, _ := json.Marshal(from) + models.DB.Create(&TabWarehouseLog{ + EntityType: "item", + EntityID: itemID, + UserID: user.ID, + ActionType: "update", + OldContent: ptrStrUint(oldContainer), + NewContent: string(newContent), + IP: ctx.ClientIP(), + }) + } else { + // 无记录:新建物品 + item := TabWarehouseItem{ + Name: from.Name, + SerialNumber: from.SerialNumber, + Remark: from.Remark, + Quantity: quantity, + CreatedAt: strconv.FormatInt(time.Now().Unix(), 10), + CreatorID: user.ID, + ContainerID: from.ContainerID, + } + models.DB.Create(&item) + itemID = item.ID + + // 绑定图片 + for _, hash := range from.Photos { + var findFile TabFileInfo_ + if models.DB.Where(&TabFileInfo_{Sha256: hash, Type: "image"}).First(&findFile).Error == nil { + models.DB.Create(&TabWarehouseItemFileBind{ + ItemID: item.ID, + FileID: findFile.ID, + CreatorID: user.ID, + }) + } + } + + // 所属容器的 ItemCount +1 + if from.ContainerID != nil { + models.DB.Model(&TabWarehouseContainer{}).Where("id = ?", *from.ContainerID).Update("item_count", models.DB.Raw("item_count + 1")) + } + + // 写操作日志 + newContent, _ := json.Marshal(from) + models.DB.Create(&TabWarehouseLog{ + EntityType: "item", + EntityID: itemID, + UserID: user.ID, + ActionType: "create", + NewContent: string(newContent), + IP: ctx.ClientIP(), + }) + } + + // 新增/更新时绑定图片 for _, hash := range from.Photos { var findFile TabFileInfo_ if models.DB.Where(&TabFileInfo_{Sha256: hash, Type: "image"}).First(&findFile).Error == nil { - models.DB.Create(&TabWarehouseItemFileBind{ - ItemID: item.ID, - FileID: findFile.ID, - CreatorID: user.ID, - }) + // 检查是否已绑定,避免重复 + var count int64 + models.DB.Model(&TabWarehouseItemFileBind{}).Where("item_id = ? AND file_id = ?", itemID, findFile.ID).Count(&count) + if count == 0 { + models.DB.Create(&TabWarehouseItemFileBind{ + ItemID: itemID, + FileID: findFile.ID, + CreatorID: user.ID, + }) + } } } - // 所属容器的 ItemCount +1 - if from.ContainerID != nil { - models.DB.Model(&TabWarehouseContainer{}).Where("id = ?", *from.ContainerID).Update("item_count", models.DB.Raw("item_count + 1")) - } - - // 写操作日志 - newContent, _ := json.Marshal(from) - models.DB.Create(&TabWarehouseLog{ - EntityType: "item", - EntityID: item.ID, - UserID: user.ID, - ActionType: "create", - NewContent: string(newContent), - IP: ctx.ClientIP(), + // 记录 commit(无论新建还是更新容器都记录) + models.DB.Create(&TabWarehouseItemCommit{ + ItemID: itemID, + UserID: user.ID, + OldContainer: oldContainer, + NewContainer: from.ContainerID, + Remark: from.Remark, + IP: ctx.ClientIP(), }) - ReturnJson(ctx, "apiOK", gin.H{"id": item.ID}) + ReturnJson(ctx, "apiOK", gin.H{"id": itemID, "updated": exists}) }) // 编辑物品 diff --git a/backend/my_work/routers/apiWorkOrder.go b/backend/my_work/routers/apiWorkOrder.go index 049b58e..8ffb3cf 100644 --- a/backend/my_work/routers/apiWorkOrder.go +++ b/backend/my_work/routers/apiWorkOrder.go @@ -140,6 +140,7 @@ func ApiWorkOrder(r *gin.RouterGroup) { Title string `json:"title"` Description string `json:"description"` Photos []string `json:"photos"` + ItemID *uint `json:"item_id"` } var from FromAdd if err := decodeJSON(data, &from); err != nil || from.Title == "" { @@ -174,6 +175,15 @@ func ApiWorkOrder(r *gin.RouterGroup) { } } + // 绑定物品 + if from.ItemID != nil && *from.ItemID > 0 { + models.DB.Create(&TabWarehouseItemWorkOrderBind{ + ItemID: *from.ItemID, + WorkOrderID: order.ID, + CreatorID: user.ID, + }) + } + // 写创建 commit models.DB.Create(&TabWorkOrderCommit{ WorkOrderID: order.ID, @@ -403,12 +413,38 @@ func ApiWorkOrder(r *gin.RouterGroup) { // 所有登录用户都可以提交进度 canCommit := true + // 关联物品 + type LinkedItem struct { + ID uint `json:"ID"` + Name string `json:"Name"` + SerialNumber string `json:"SerialNumber"` + } + var linkedItems []LinkedItem + var itemBinds []TabWarehouseItemWorkOrderBind + models.DB.Where("work_order_id = ?", from.ID).Find(&itemBinds) + if len(itemBinds) > 0 { + var itemIDs []uint + for _, b := range itemBinds { + itemIDs = append(itemIDs, b.ItemID) + } + var items []TabWarehouseItem + models.DB.Where("id IN ?", itemIDs).Find(&items) + for _, it := range items { + linkedItems = append(linkedItems, LinkedItem{ + ID: it.ID, + Name: it.Name, + SerialNumber: it.SerialNumber, + }) + } + } + ReturnJson(ctx, "apiOK", gin.H{ - "order": order, - "canModify": canModify, - "canCommit": canCommit, - "photos": files, - "commits": commitsWithPhotos, + "order": order, + "canModify": canModify, + "canCommit": canCommit, + "photos": files, + "commits": commitsWithPhotos, + "linkedItems": linkedItems, }) }) diff --git a/frontend/ops_vue_js/src/i18n/en.json b/frontend/ops_vue_js/src/i18n/en.json index 2be0f47..13d80a6 100644 --- a/frontend/ops_vue_js/src/i18n/en.json +++ b/frontend/ops_vue_js/src/i18n/en.json @@ -149,6 +149,7 @@ "not_found": "Work order not found", "confirm_delete": "Are you sure you want to delete this work order? This action cannot be undone.", "confirm_delete_commit": "Are you sure you want to delete this progress?", + "linked_items": "Linked Items", "submit": "Submit", "save_changes": "Save Changes" }, @@ -178,6 +179,7 @@ "container_count": "Containers", "item_count": "Items", "unstored_items": "Unstored", + "unstored": "Unstored", "title_required": "Container name is required", "delete_confirm_title": "Delete Container", "delete_confirm_msg": "Are you sure you want to delete \"{name}\"? This cannot be undone.", @@ -301,6 +303,9 @@ "pending_orders": "Pending orders" }, "message": { + "save": "Save", + "cancel": "Cancel", + "delete_success": "Deleted successfully", "functionality_not_yet_developed": "Functionality not yet developed", "hello": "Hello", "welcome": "Welcome", diff --git a/frontend/ops_vue_js/src/i18n/zh-CN.json b/frontend/ops_vue_js/src/i18n/zh-CN.json index 59322d7..138729e 100644 --- a/frontend/ops_vue_js/src/i18n/zh-CN.json +++ b/frontend/ops_vue_js/src/i18n/zh-CN.json @@ -149,6 +149,7 @@ "not_found": "工单不存在", "confirm_delete": "确定要删除此工单吗?此操作不可撤销。", "confirm_delete_commit": "确定要删除此进度吗?", + "linked_items": "关联物品", "submit": "提交", "save_changes": "保存修改" }, @@ -178,6 +179,7 @@ "container_count": "容器数", "item_count": "物品数", "unstored_items": "未入库", + "unstored": "未入库", "title_required": "容器名称不能为空", "delete_confirm_title": "删除容器", "delete_confirm_msg": "确定要删除容器「{name}」吗?此操作不可撤销。", @@ -301,6 +303,9 @@ "pending_orders": "待处理订单" }, "message": { + "save": "保存", + "cancel": "取消", + "delete_success": "删除成功", "functionality_not_yet_developed": "功能未开发", "hello": "你好", "welcome": "欢迎", diff --git a/frontend/ops_vue_js/src/views/warehouse/WarehouseContainerDetail.vue b/frontend/ops_vue_js/src/views/warehouse/WarehouseContainerDetail.vue index d88432c..77c2d96 100644 --- a/frontend/ops_vue_js/src/views/warehouse/WarehouseContainerDetail.vue +++ b/frontend/ops_vue_js/src/views/warehouse/WarehouseContainerDetail.vue @@ -1,5 +1,5 @@