Files
ops2/backend/my_work/routers/apiWarehouse.go
T
2026-04-23 22:52:55 +08:00

1002 lines
27 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package routers
import (
"encoding/json"
"ops/models"
"slices"
"strconv"
"time"
"github.com/gin-gonic/gin"
)
// ---------- 数据表结构 ----------
type TabWarehouseContainer struct {
ID uint `gorm:"primaryKey"`
Title string `gorm:"size:255;not null;comment:容器名"`
Remark string `gorm:"type:text;comment:描述"`
CreatedAt string `gorm:"size:20;comment:创建日期"`
CreatorID uint `gorm:"not null;index;comment:创建者id"`
ParentID *uint `gorm:"index;comment:父容器idnil=顶级"`
ItemCount int `gorm:"default:0;comment:直接子物品数量"`
ChildCount int `gorm:"default:0;comment:子容器数量"`
}
type TabWarehouseItem struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"size:255;not null;comment:物品名"`
SerialNumber string `gorm:"size:255;comment:序列号"`
Remark string `gorm:"type:text;comment:描述"`
Quantity int `gorm:"default:1;comment:数量"`
CreatedAt string `gorm:"size:20;comment:创建日期"`
CreatorID uint `gorm:"not null;index;comment:创建者id"`
ContainerID *uint `gorm:"index;comment:所属容器idnil=未入库"`
}
type TabWarehouseContainerFileBind struct {
ID uint `gorm:"primaryKey"`
ContainerID uint `gorm:"not null;index;comment:关联容器id"`
FileID uint `gorm:"not null;comment:关联文件id"`
CreatorID uint `gorm:"not null;comment:上传人id"`
CreatedAt time.Time `gorm:"type:datetime;autoCreateTime"`
}
type TabWarehouseItemFileBind struct {
ID uint `gorm:"primaryKey"`
ItemID uint `gorm:"not null;index;comment:关联物品id"`
FileID uint `gorm:"not null;comment:关联文件id"`
CreatorID uint `gorm:"not null;comment:上传人id"`
CreatedAt time.Time `gorm:"type:datetime;autoCreateTime"`
}
type TabWarehouseItemCommit struct {
ID uint `gorm:"primaryKey" json:"ID"`
ItemID uint `gorm:"not null;index;comment:关联物品id" json:"ItemID"`
UserID uint `gorm:"not null;comment:操作人id" json:"UserID"`
OldContainer *uint `gorm:"index;comment:原容器id" json:"OldContainer"`
NewContainer *uint `gorm:"index;comment:新容器id" json:"NewContainer"`
Remark string `gorm:"type:text;comment:备注" json:"Remark"`
IP string `gorm:"size:50;comment:操作IP" json:"IP"`
CreatedAt time.Time `gorm:"type:datetime;autoCreateTime" json:"CreatedAt"`
}
type TabWarehouseLog struct {
ID uint `gorm:"primaryKey"`
EntityType string `gorm:"size:50;not null;index;comment:操作对象类型"`
EntityID uint `gorm:"not null;index;comment:操作对象id"`
UserID uint `gorm:"not null;index;comment:操作人id"`
ActionType string `gorm:"size:50;not null;comment:操作类型: create update delete move 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"`
}
type TabWarehouseItemWorkOrderBind struct {
ID uint `gorm:"primaryKey"`
ItemID uint `gorm:"not null;index;comment:关联物品id"`
WorkOrderID uint `gorm:"not null;index;comment:关联工单id"`
Remark string `gorm:"size:500;comment:备注"`
CreatorID uint `gorm:"not null;comment:绑定人id"`
CreatedAt time.Time `gorm:"type:datetime;autoCreateTime"`
}
var (
warehouseUserGroup models.TabUserGroups_
warehouseAdmins []uint
)
// updateWarehouseAdminsCash 刷新仓库管理员缓存
func updateWarehouseAdminsCash() {
warehouseAdmins = nil
warehouseAdmins = append(warehouseAdmins, 1) // id=1 超级管理员
var binds []models.TabUserGroupBinds_
models.DB.Where("group_id = ?", warehouseUserGroup.ID).Find(&binds)
for _, item := range binds {
if !slices.Contains(warehouseAdmins, item.UserID) {
warehouseAdmins = append(warehouseAdmins, item.UserID)
}
}
}
// canModifyWarehouse 判断是否有权限修改/删除仓库资源(创建者或仓库管理员)
func canModifyWarehouse(userID, creatorUserID uint) bool {
if slices.Contains(warehouseAdmins, userID) {
return true
}
return userID == creatorUserID
}
// ---------- 初始化 ----------
func ApiWarehouseInit() {
models.DB.AutoMigrate(
&TabWarehouseContainer{},
&TabWarehouseItem{},
&TabWarehouseContainerFileBind{},
&TabWarehouseItemFileBind{},
&TabWarehouseItemCommit{},
&TabWarehouseLog{},
&TabWarehouseItemWorkOrderBind{},
)
warehouseUserGroup.Name = "warehouse_admin"
if models.DB.Where(&warehouseUserGroup).First(&warehouseUserGroup).Error == nil {
updateWarehouseAdminsCash()
} else {
warehouseUserGroup.Type = "usergroup"
models.DB.Create(&warehouseUserGroup)
}
}
// ---------- 路由注册 ----------
func ApiWarehouse(r *gin.RouterGroup) {
// 新增容器
r.POST("/add_container", func(ctx *gin.Context) {
isAuth, user, data := AuthenticationAuthority(ctx)
if !isAuth {
ReturnJson(ctx, "userCookieError", nil)
return
}
type FromAdd struct {
Title string `json:"title"`
Remark string `json:"remark"`
ParentID *uint `json:"parent_id"`
Photos []string `json:"photos"`
}
var from FromAdd
if err := decodeJSON(data, &from); err != nil || from.Title == "" {
ReturnJson(ctx, "jsonErr", nil)
return
}
// 校验图片哈希
for _, hash := range from.Photos {
if models.IsContainsSpecialChar(hash) {
ReturnJson(ctx, "photo_hash_invalid", nil)
return
}
}
// 检查嵌套层级不超过5层
if from.ParentID != nil {
var parent TabWarehouseContainer
if err := models.DB.First(&parent, *from.ParentID).Error; err != nil {
ReturnJson(ctx, "parent_not_found", nil)
return
}
depth := 0
curID := *from.ParentID
for depth < 100 {
var c TabWarehouseContainer
if err := models.DB.First(&c, curID).Error; err != nil {
break
}
if c.ParentID == nil {
break
}
curID = *c.ParentID
depth++
}
if depth >= 4 { // 新容器将落在第5层
ReturnJson(ctx, "max_depth_exceeded", nil)
return
}
}
c := TabWarehouseContainer{
Title: from.Title,
Remark: from.Remark,
CreatedAt: strconv.FormatInt(time.Now().Unix(), 10),
CreatorID: user.ID,
ParentID: from.ParentID,
}
models.DB.Create(&c)
// 绑定图片
for _, hash := range from.Photos {
var findFile TabFileInfo_
if models.DB.Where(&TabFileInfo_{Sha256: hash, Type: "image"}).First(&findFile).Error == nil {
models.DB.Create(&TabWarehouseContainerFileBind{
ContainerID: c.ID,
FileID: findFile.ID,
CreatorID: user.ID,
})
}
}
// 父容器的 ChildCount +1
if from.ParentID != nil {
models.DB.Model(&TabWarehouseContainer{}).Where("id = ?", *from.ParentID).Update("child_count", models.DB.Raw("child_count + 1"))
}
// 写操作日志
newContent, _ := json.Marshal(from)
models.DB.Create(&TabWarehouseLog{
EntityType: "container",
EntityID: c.ID,
UserID: user.ID,
ActionType: "create",
NewContent: string(newContent),
IP: ctx.ClientIP(),
})
ReturnJson(ctx, "apiOK", gin.H{"id": c.ID})
})
// 编辑容器
r.POST("/update_container", func(ctx *gin.Context) {
isAuth, user, data := AuthenticationAuthority(ctx)
if !isAuth {
ReturnJson(ctx, "userCookieError", nil)
return
}
type FromUpdate struct {
ID uint `json:"id"`
Title string `json:"title"`
Remark string `json:"remark"`
Photos []string `json:"photos"`
}
var from FromUpdate
if err := decodeJSON(data, &from); err != nil || from.ID == 0 || from.Title == "" {
ReturnJson(ctx, "jsonErr", nil)
return
}
// 校验图片哈希
for _, hash := range from.Photos {
if models.IsContainsSpecialChar(hash) {
ReturnJson(ctx, "photo_hash_invalid", nil)
return
}
}
var c TabWarehouseContainer
if err := models.DB.Where("id = ?", from.ID).First(&c).Error; err != nil {
ReturnJson(ctx, "container_not_found", nil)
return
}
if !canModifyWarehouse(user.ID, c.CreatorID) {
ReturnJson(ctx, "no_permission", nil)
return
}
oldContent, _ := json.Marshal(c)
models.DB.Model(&c).Updates(map[string]interface{}{
"title": from.Title,
"remark": from.Remark,
})
// 重建图片绑定
models.DB.Where("container_id = ?", from.ID).Delete(&TabWarehouseContainerFileBind{})
for _, hash := range from.Photos {
var findFile TabFileInfo_
if models.DB.Where(&TabFileInfo_{Sha256: hash, Type: "image"}).First(&findFile).Error == nil {
models.DB.Create(&TabWarehouseContainerFileBind{
ContainerID: from.ID,
FileID: findFile.ID,
CreatorID: user.ID,
})
}
}
newContent, _ := json.Marshal(from)
models.DB.Create(&TabWarehouseLog{
EntityType: "container",
EntityID: from.ID,
UserID: user.ID,
ActionType: "update",
OldContent: string(oldContent),
NewContent: string(newContent),
IP: ctx.ClientIP(),
})
ReturnJson(ctx, "apiOK", nil)
})
// 删除容器
r.POST("/delete_container", func(ctx *gin.Context) {
isAuth, user, data := AuthenticationAuthority(ctx)
if !isAuth {
ReturnJson(ctx, "userCookieError", nil)
return
}
type FromDelete struct {
ID uint `json:"id"`
}
var from FromDelete
if err := decodeJSON(data, &from); err != nil || from.ID == 0 {
ReturnJson(ctx, "jsonErr", nil)
return
}
var c TabWarehouseContainer
if err := models.DB.Where("id = ?", from.ID).First(&c).Error; err != nil {
ReturnJson(ctx, "container_not_found", nil)
return
}
if !canModifyWarehouse(user.ID, c.CreatorID) {
ReturnJson(ctx, "no_permission", nil)
return
}
// 检查是否有子容器或物品
if c.ChildCount > 0 || c.ItemCount > 0 {
ReturnJson(ctx, "container_not_empty", nil)
return
}
models.DB.Where("container_id = ?", from.ID).Delete(&TabWarehouseContainerFileBind{})
// 父容器 ChildCount -1
if c.ParentID != nil {
models.DB.Model(&TabWarehouseContainer{}).Where("id = ?", *c.ParentID).Update("child_count", models.DB.Raw("child_count - 1"))
}
oldContent, _ := json.Marshal(c)
models.DB.Create(&TabWarehouseLog{
EntityType: "container",
EntityID: from.ID,
UserID: user.ID,
ActionType: "delete",
OldContent: string(oldContent),
IP: ctx.ClientIP(),
})
models.DB.Delete(&c)
ReturnJson(ctx, "apiOK", nil)
})
// 获取容器列表
r.POST("/list_container", func(ctx *gin.Context) {
isAuth, user, data := AuthenticationAuthority(ctx)
if !isAuth {
ReturnJson(ctx, "userCookieError", nil)
return
}
type FromList struct {
Search string `json:"search"`
ParentID *uint `json:"parent_id"`
Entries int `json:"entries"`
Page int `json:"page"`
}
var from FromList
if err := decodeJSON(data, &from); err != nil {
ReturnJson(ctx, "jsonErr", nil)
return
}
if from.Entries <= 0 || from.Entries > 300 {
from.Entries = 10
}
if from.Page <= 0 {
from.Page = 1
}
var count int64
query := models.DB.Model(&TabWarehouseContainer{})
if from.Search != "" {
query = query.Where("title LIKE ?", "%"+from.Search+"%")
}
if from.ParentID != nil {
query = query.Where("parent_id = ?", *from.ParentID)
} else if from.Search == "" {
// 无搜索时默认只显示顶级容器
query = query.Where("parent_id IS NULL")
}
query.Count(&count)
var containers []TabWarehouseContainer
query.Order("created_at DESC").
Offset(from.Entries * (from.Page - 1)).
Limit(from.Entries).
Find(&containers)
canModifyContainers := make([]bool, len(containers))
for i, c := range containers {
canModifyContainers[i] = canModifyWarehouse(user.ID, c.CreatorID)
}
ReturnJson(ctx, "apiOK", gin.H{
"all_count": count,
"containers": containers,
"canModifyContainers": canModifyContainers,
})
})
// 获取容器详情
r.POST("/get_container", func(ctx *gin.Context) {
isAuth, user, data := AuthenticationAuthority(ctx)
if !isAuth {
ReturnJson(ctx, "userCookieError", nil)
return
}
type FromGet struct {
ID uint `json:"id"`
}
var from FromGet
if err := decodeJSON(data, &from); err != nil || from.ID == 0 {
ReturnJson(ctx, "jsonErr", nil)
return
}
var c TabWarehouseContainer
if err := models.DB.Where("id = ?", from.ID).First(&c).Error; err != nil {
ReturnJson(ctx, "container_not_found", nil)
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)
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)
}
ReturnJson(ctx, "apiOK", gin.H{
"container": c,
"photos": files,
"parent_chain": parentChain,
"depth": depth,
"canModifyContainer": canModifyWarehouse(user.ID, c.CreatorID),
})
})
// 新增物品(查重逻辑:Name+SerialNumber相同则更新容器)
r.POST("/add_item", func(ctx *gin.Context) {
isAuth, user, data := AuthenticationAuthority(ctx)
if !isAuth {
ReturnJson(ctx, "userCookieError", nil)
return
}
type FromAdd struct {
Name string `json:"name"`
SerialNumber string `json:"serial_number"`
Remark string `json:"remark"`
Quantity int `json:"quantity"`
ContainerID *uint `json:"container_id"`
Photos []string `json:"photos"`
}
var from FromAdd
if err := decodeJSON(data, &from); err != nil || from.Name == "" {
ReturnJson(ctx, "jsonErr", nil)
return
}
// 校验图片哈希
for _, hash := range from.Photos {
if models.IsContainsSpecialChar(hash) {
ReturnJson(ctx, "photo_hash_invalid", nil)
return
}
}
quantity := from.Quantity
if quantity <= 0 {
quantity = 1
}
// 查重: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 {
// 检查是否已绑定,避免重复
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,
})
}
}
}
// 记录 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": itemID, "updated": exists})
})
// 编辑物品
r.POST("/update_item", func(ctx *gin.Context) {
isAuth, user, data := AuthenticationAuthority(ctx)
if !isAuth {
ReturnJson(ctx, "userCookieError", nil)
return
}
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"`
}
var from FromUpdate
if err := decodeJSON(data, &from); err != nil || from.ID == 0 || from.Name == "" {
ReturnJson(ctx, "jsonErr", nil)
return
}
// 校验图片哈希
for _, hash := range from.Photos {
if models.IsContainsSpecialChar(hash) {
ReturnJson(ctx, "photo_hash_invalid", nil)
return
}
}
var item TabWarehouseItem
if err := models.DB.Where("id = ?", from.ID).First(&item).Error; err != nil {
ReturnJson(ctx, "item_not_found", nil)
return
}
if !canModifyWarehouse(user.ID, item.CreatorID) {
ReturnJson(ctx, "no_permission", nil)
return
}
oldContent, _ := json.Marshal(item)
models.DB.Model(&item).Updates(map[string]interface{}{
"name": from.Name,
"serial_number": from.SerialNumber,
"remark": from.Remark,
"quantity": from.Quantity,
})
// 重建图片绑定
models.DB.Where("item_id = ?", from.ID).Delete(&TabWarehouseItemFileBind{})
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: from.ID,
FileID: findFile.ID,
CreatorID: user.ID,
})
}
}
newContent, _ := json.Marshal(from)
models.DB.Create(&TabWarehouseLog{
EntityType: "item",
EntityID: from.ID,
UserID: user.ID,
ActionType: "update",
OldContent: string(oldContent),
NewContent: string(newContent),
IP: ctx.ClientIP(),
})
ReturnJson(ctx, "apiOK", nil)
})
// 删除物品
r.POST("/delete_item", func(ctx *gin.Context) {
isAuth, user, data := AuthenticationAuthority(ctx)
if !isAuth {
ReturnJson(ctx, "userCookieError", nil)
return
}
type FromDelete struct {
ID uint `json:"id"`
}
var from FromDelete
if err := decodeJSON(data, &from); err != nil || from.ID == 0 {
ReturnJson(ctx, "jsonErr", nil)
return
}
var item TabWarehouseItem
if err := models.DB.Where("id = ?", from.ID).First(&item).Error; err != nil {
ReturnJson(ctx, "item_not_found", nil)
return
}
if !canModifyWarehouse(user.ID, item.CreatorID) {
ReturnJson(ctx, "no_permission", nil)
return
}
// 所属容器 ItemCount -1
if item.ContainerID != nil {
models.DB.Model(&TabWarehouseContainer{}).Where("id = ?", *item.ContainerID).Update("item_count", models.DB.Raw("item_count - 1"))
}
// 删除关联
models.DB.Where("item_id = ?", from.ID).Delete(&TabWarehouseItemFileBind{})
models.DB.Where("item_id = ?", from.ID).Delete(&TabWarehouseItemCommit{})
models.DB.Where("item_id = ?", from.ID).Delete(&TabWarehouseItemWorkOrderBind{})
oldContent, _ := json.Marshal(item)
models.DB.Create(&TabWarehouseLog{
EntityType: "item",
EntityID: from.ID,
UserID: user.ID,
ActionType: "delete",
OldContent: string(oldContent),
IP: ctx.ClientIP(),
})
models.DB.Delete(&item)
ReturnJson(ctx, "apiOK", nil)
})
// 获取物品列表
r.POST("/list_item", func(ctx *gin.Context) {
isAuth, user, data := AuthenticationAuthority(ctx)
if !isAuth {
ReturnJson(ctx, "userCookieError", nil)
return
}
type FromList struct {
Search string `json:"search"`
ContainerID *uint `json:"container_id"`
Entries int `json:"entries"`
Page int `json:"page"`
}
var from FromList
if err := decodeJSON(data, &from); err != nil {
ReturnJson(ctx, "jsonErr", nil)
return
}
if from.Entries <= 0 || from.Entries > 300 {
from.Entries = 10
}
if from.Page <= 0 {
from.Page = 1
}
var count int64
query := models.DB.Model(&TabWarehouseItem{})
if from.Search != "" {
query = query.Where("name LIKE ? OR serial_number LIKE ? OR remark LIKE ?",
"%"+from.Search+"%", "%"+from.Search+"%", "%"+from.Search+"%")
}
if from.ContainerID != nil {
query = query.Where("container_id = ?", *from.ContainerID)
}
query.Count(&count)
var items []TabWarehouseItem
query.Order("created_at DESC").
Offset(from.Entries * (from.Page - 1)).
Limit(from.Entries).
Find(&items)
canModifyItems := make([]bool, len(items))
for i, item := range items {
canModifyItems[i] = canModifyWarehouse(user.ID, item.CreatorID)
}
ReturnJson(ctx, "apiOK", gin.H{
"all_count": count,
"items": items,
"canModifyItems": canModifyItems,
})
})
// 获取物品详情
r.POST("/get_item", func(ctx *gin.Context) {
isAuth, user, data := AuthenticationAuthority(ctx)
if !isAuth {
ReturnJson(ctx, "userCookieError", nil)
return
}
type FromGet struct {
ID uint `json:"id"`
}
var from FromGet
if err := decodeJSON(data, &from); err != nil || from.ID == 0 {
ReturnJson(ctx, "jsonErr", nil)
return
}
var item TabWarehouseItem
if err := models.DB.Where("id = ?", from.ID).First(&item).Error; err != nil {
ReturnJson(ctx, "item_not_found", nil)
return
}
// 关联图片
var binds []TabWarehouseItemFileBind
models.DB.Where("item_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 []TabWarehouseItemCommit
models.DB.Where("item_id = ?", from.ID).Order("created_at DESC").Find(&commits)
// 关联工单
var woBinds []TabWarehouseItemWorkOrderBind
models.DB.Where("item_id = ?", from.ID).Find(&woBinds)
type WOInfo struct {
ID uint `json:"id"`
Title string `json:"title"`
Status string `json:"status"`
}
var workOrders []WOInfo
for _, b := range woBinds {
var wo TabWorkOrder
if models.DB.Where("id = ?", b.WorkOrderID).First(&wo).Error == nil {
workOrders = append(workOrders, WOInfo{ID: wo.ID, Title: wo.Title, Status: wo.CurrentStatus})
}
}
ReturnJson(ctx, "apiOK", gin.H{
"item": item,
"photos": files,
"commits": commits,
"work_orders": workOrders,
"canModifyItem": canModifyWarehouse(user.ID, item.CreatorID),
})
})
// 移动物品到其他容器
r.POST("/move_item", func(ctx *gin.Context) {
isAuth, user, data := AuthenticationAuthority(ctx)
if !isAuth {
ReturnJson(ctx, "userCookieError", nil)
return
}
type FromMove struct {
ItemID uint `json:"item_id"`
NewContainer *uint `json:"new_container"`
Remark string `json:"remark"`
}
var from FromMove
if err := decodeJSON(data, &from); err != nil || from.ItemID == 0 {
ReturnJson(ctx, "jsonErr", nil)
return
}
var item TabWarehouseItem
if err := models.DB.Where("id = ?", from.ItemID).First(&item).Error; err != nil {
ReturnJson(ctx, "item_not_found", nil)
return
}
oldContainer := item.ContainerID
// 同一容器无需操作
if ptrEqUint(oldContainer, from.NewContainer) {
ReturnJson(ctx, "apiOK", nil)
return
}
// 旧容器 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.NewContainer != nil {
models.DB.Model(&TabWarehouseContainer{}).Where("id = ?", *from.NewContainer).Update("item_count", models.DB.Raw("item_count + 1"))
}
// 更新物品容器
item.ContainerID = from.NewContainer
models.DB.Save(&item)
// 记录移动日志
models.DB.Create(&TabWarehouseItemCommit{
ItemID: from.ItemID,
UserID: user.ID,
OldContainer: oldContainer,
NewContainer: from.NewContainer,
Remark: from.Remark,
IP: ctx.ClientIP(),
})
// 写通用操作日志
models.DB.Create(&TabWarehouseLog{
EntityType: "item",
EntityID: from.ItemID,
UserID: user.ID,
ActionType: "move",
OldContent: ptrStrUint(oldContainer),
NewContent: ptrStrUint(from.NewContainer),
IP: ctx.ClientIP(),
Remark: from.Remark,
})
ReturnJson(ctx, "apiOK", nil)
})
// 获取仓库统计
r.POST("/count", func(ctx *gin.Context) {
isAuth, _, _ := AuthenticationAuthority(ctx)
if !isAuth {
ReturnJson(ctx, "userCookieError", nil)
return
}
type WCount struct {
ContainerTotal int64 `json:"container_total"`
ItemTotal int64 `json:"item_total"`
UnstoredItems int64 `json:"unstored_items"`
}
var count WCount
models.DB.Model(&TabWarehouseContainer{}).Count(&count.ContainerTotal)
models.DB.Model(&TabWarehouseItem{}).Count(&count.ItemTotal)
models.DB.Model(&TabWarehouseItem{}).Where("container_id IS NULL").Count(&count.UnstoredItems)
ReturnJson(ctx, "apiOK", gin.H{
"container_total": count.ContainerTotal,
"item_total": count.ItemTotal,
"unstored_items": count.UnstoredItems,
})
})
}
// ---------- 辅助函数 ----------
func ptrEqUint(a, b *uint) bool {
if a == nil && b == nil {
return true
}
if a == nil || b == nil {
return false
}
return *a == *b
}
func ptrStrUint(p *uint) string {
if p == nil {
return "nil"
}
return strconv.FormatUint(uint64(*p), 10)
}