This commit is contained in:
2026-04-23 19:43:13 +08:00
parent b2d3fec6c8
commit 5780e1ac52
15 changed files with 3511 additions and 77 deletions
+1
View File
@@ -71,6 +71,7 @@ func main() {
routers.ApiScheduleInit()
routers.ApiPurchaseInit()
routers.ApiWorkOrderInit()
routers.ApiWarehouseInit()
//创建必要目录
for _, path := range models.ConfigsFile.Pahts {
+3
View File
@@ -0,0 +1,3 @@
package models
// 仓库模块所有结构定义在 routers/apiWarehouse.go
+2 -1
View File
@@ -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,
})
})
+830
View File
@@ -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:父容器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"`
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)
}