up
This commit is contained in:
@@ -56,9 +56,10 @@ func ApiRoot(r *gin.RouterGroup) {
|
||||
ApiPurchase(r.Group("/purchase"))
|
||||
ApiSchedule(r.Group("/schedule"))
|
||||
ApiWorkOrder(r.Group("/work_order"))
|
||||
ApiWarehouse(r.Group("/warehouse"))
|
||||
r.GET("/", func(ctx *gin.Context) {
|
||||
ReturnJson(ctx, "apiOK", gin.H{
|
||||
"isOpsApiRoot":true,
|
||||
"isOpsApiRoot": true,
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,830 @@
|
||||
package routers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"ops/models"
|
||||
"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:父容器id,nil=顶级"`
|
||||
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:所属容器id,nil=未入库"`
|
||||
}
|
||||
|
||||
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"`
|
||||
ItemID uint `gorm:"not null;index;comment:关联物品id"`
|
||||
UserID uint `gorm:"not null;comment:操作人id"`
|
||||
OldContainer *uint `gorm:"index;comment:原容器id"`
|
||||
NewContainer *uint `gorm:"index;comment:新容器id"`
|
||||
Remark string `gorm:"type:text;comment:备注"`
|
||||
IP string `gorm:"size:50;comment:操作IP"`
|
||||
CreatedAt time.Time `gorm:"type:datetime;autoCreateTime"`
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
// ---------- 初始化 ----------
|
||||
|
||||
func ApiWarehouseInit() {
|
||||
models.DB.AutoMigrate(
|
||||
&TabWarehouseContainer{},
|
||||
&TabWarehouseItem{},
|
||||
&TabWarehouseContainerFileBind{},
|
||||
&TabWarehouseItemFileBind{},
|
||||
&TabWarehouseItemCommit{},
|
||||
&TabWarehouseLog{},
|
||||
&TabWarehouseItemWorkOrderBind{},
|
||||
)
|
||||
}
|
||||
|
||||
// ---------- 路由注册 ----------
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 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, _, 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 {
|
||||
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)
|
||||
|
||||
ReturnJson(ctx, "apiOK", gin.H{
|
||||
"all_count": count,
|
||||
"containers": containers,
|
||||
})
|
||||
})
|
||||
|
||||
// 获取容器详情
|
||||
r.POST("/get_container", func(ctx *gin.Context) {
|
||||
isAuth, _, 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
|
||||
}
|
||||
|
||||
// 关联图片
|
||||
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,
|
||||
})
|
||||
})
|
||||
|
||||
// 新增物品
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
// 绑定图片
|
||||
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: item.ID,
|
||||
UserID: user.ID,
|
||||
ActionType: "create",
|
||||
NewContent: string(newContent),
|
||||
IP: ctx.ClientIP(),
|
||||
})
|
||||
|
||||
ReturnJson(ctx, "apiOK", gin.H{"id": item.ID})
|
||||
})
|
||||
|
||||
// 编辑物品
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 所属容器 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, _, 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)
|
||||
|
||||
ReturnJson(ctx, "apiOK", gin.H{
|
||||
"all_count": count,
|
||||
"items": items,
|
||||
})
|
||||
})
|
||||
|
||||
// 获取物品详情
|
||||
r.POST("/get_item", func(ctx *gin.Context) {
|
||||
isAuth, _, 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,
|
||||
})
|
||||
})
|
||||
|
||||
// 移动物品到其他容器
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user