feat: 完成WMS库房管理系统功能迭代与界面优化
1. 统一系统名称为WMS库房管理,替换所有页面标题、登录页、侧边栏文字 2. 新增公共物料下拉选择组件MaterialSelect,替换多处本地物料字典加载逻辑 3. 用户列表、AGV任务列表、入库/出库/盘点/库存列表新增ID与创建时间列 4. 用户列表、物料查询等接口改为后端真分页,按创建时间倒序排序 5. 库存查询新增全局模糊搜索,支持物料编码/批次号/SN匹配 6. 库存汇总新增区域汇总视图与导出功能,修复导出表头与数据字段 7. 优化侧边栏菜单样式与布局,修复菜单图标间距与文字对齐问题 8. 完善库存查询、区域汇总的文档注释与导出字段
This commit is contained in:
@@ -147,10 +147,25 @@ func userDTO(u *ent.User) map[string]any {
|
||||
}
|
||||
}
|
||||
|
||||
// listUsersHandler GET /api/user/list 管理员查看全部用户
|
||||
// listUsersHandler GET /api/user/list 管理员查看全部用户(后端真分页,created_at desc 最新置顶)
|
||||
func listUsersHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
return requirePerm(ctx, "user:manage", func(w http.ResponseWriter, r *http.Request) {
|
||||
list, err := ctx.EntClient.User.Query().Order(ent.Asc("id")).All(ctx0())
|
||||
page := atoi(r.URL.Query().Get("page"), 1)
|
||||
pageSize := atoi(r.URL.Query().Get("pageSize"), 20)
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 || pageSize > 200 {
|
||||
pageSize = 20
|
||||
}
|
||||
q := ctx.EntClient.User.Query()
|
||||
total, err := q.Count(ctx0())
|
||||
if err != nil {
|
||||
fail(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
list, err := q.Order(ent.Desc("created_at"), ent.Desc("id")).
|
||||
Offset((page - 1) * pageSize).Limit(pageSize).All(ctx0())
|
||||
if err != nil {
|
||||
fail(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
@@ -159,7 +174,7 @@ func listUsersHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
for _, u := range list {
|
||||
out = append(out, userDTO(u))
|
||||
}
|
||||
ok(w, map[string]any{"list": out})
|
||||
ok(w, map[string]any{"list": out, "total": total, "page": page, "pageSize": pageSize})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package handler
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -24,8 +25,9 @@ type stockKey struct {
|
||||
}
|
||||
|
||||
// stockAggRow 聚合主列表的行结构(按 物料+区域+质量+管理粒度 聚合,只返回数量,不铺开明细)
|
||||
// ID = 组内最近入库的库存行真实主键(inventory.id)——聚合视图无自身实体,取真实主键,禁止伪 id
|
||||
type stockAggRow struct {
|
||||
ID int64 `json:"id"` // 组合键稳定序号的伪 id,供前端行 key 使用
|
||||
ID int64 `json:"id"` // inventory.id(组内最近入库行)
|
||||
MaterialCode string `json:"materialCode"` // 物料编码
|
||||
MaterialName string `json:"materialName"` // 物料名称
|
||||
Spec string `json:"spec"` // 规格型号
|
||||
@@ -49,6 +51,7 @@ func queryStockHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
materialCode := r.URL.Query().Get("materialCode")
|
||||
materialName := r.URL.Query().Get("materialName") // 物料名称模糊
|
||||
keyword := r.URL.Query().Get("keyword") // 模糊搜索:物料编码/批次号/SN 任一匹配(下拉拾取器用)
|
||||
zoneCode := r.URL.Query().Get("zoneCode")
|
||||
qualityStatus := r.URL.Query().Get("qualityStatus")
|
||||
manageMode := r.URL.Query().Get("manageMode") // 1/2/空=全部
|
||||
@@ -61,6 +64,7 @@ func queryStockHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
rows, total, err := aggregateStock(ctx, aggregateStockArgs{
|
||||
MaterialCode: materialCode,
|
||||
MaterialName: materialName,
|
||||
Keyword: keyword,
|
||||
ZoneCode: zoneCode,
|
||||
QualityStatus: qualityStatus,
|
||||
ManageMode: manageMode,
|
||||
@@ -87,6 +91,7 @@ func queryStockHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
type aggregateStockArgs struct {
|
||||
MaterialCode string
|
||||
MaterialName string
|
||||
Keyword string // 模糊搜索:物料编码/批次号/SN 任一匹配(下拉拾取器用)
|
||||
ZoneCode string
|
||||
QualityStatus string
|
||||
ManageMode string
|
||||
@@ -114,6 +119,14 @@ func aggregateStock(ctx *svc.ServiceContext, a aggregateStockArgs) ([]*stockAggR
|
||||
if a.MaterialName != "" {
|
||||
q = q.Where(inventory.MaterialNameContains(a.MaterialName))
|
||||
}
|
||||
if a.Keyword != "" {
|
||||
// 模糊搜索:物料编码 / 批次号 / SN 任一命中(库存拾取下拉用);ContainsFold 大小写不敏感
|
||||
q = q.Where(inventory.Or(
|
||||
inventory.MaterialCodeContainsFold(a.Keyword),
|
||||
inventory.BatchNoContainsFold(a.Keyword),
|
||||
inventory.SnCodeContainsFold(a.Keyword),
|
||||
))
|
||||
}
|
||||
if a.ZoneCode != "" {
|
||||
q = q.Where(inventory.ZoneCodeEQ(a.ZoneCode))
|
||||
}
|
||||
@@ -184,6 +197,7 @@ func aggregateStock(ctx *svc.ServiceContext, a aggregateStockArgs) ([]*stockAggR
|
||||
snCnt int
|
||||
lastInbNo string
|
||||
lastTime int64
|
||||
lastInvID int64 // 组内最近入库的库存行真实主键(inventory.id),作行标识
|
||||
}
|
||||
group := map[stockKey]*agg{}
|
||||
order := []stockKey{}
|
||||
@@ -210,9 +224,11 @@ func aggregateStock(ctx *svc.ServiceContext, a aggregateStockArgs) ([]*stockAggR
|
||||
}
|
||||
g.snCnt++
|
||||
}
|
||||
// all 已按 created_at desc, id desc 排序,首见即最近一条
|
||||
if inv.CreatedAt > g.lastTime {
|
||||
g.lastTime = inv.CreatedAt
|
||||
g.lastInbNo = inv.InboundNo
|
||||
g.lastInvID = int64(inv.ID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,7 +251,7 @@ func aggregateStock(ctx *svc.ServiceContext, a aggregateStockArgs) ([]*stockAggR
|
||||
name = key.MaterialCode
|
||||
}
|
||||
rows = append(rows, &stockAggRow{
|
||||
ID: int64(i + 1),
|
||||
ID: g.lastInvID, // 组内最近入库的库存行真实主键(inventory.id),禁止伪 id
|
||||
MaterialCode: key.MaterialCode,
|
||||
MaterialName: name,
|
||||
Spec: matSpec[key.MaterialCode],
|
||||
@@ -256,23 +272,26 @@ func aggregateStock(ctx *svc.ServiceContext, a aggregateStockArgs) ([]*stockAggR
|
||||
|
||||
// materialAggRow 物料汇总行(按 物料编码 聚合,忽略区域与质量状态)
|
||||
// 回答"这个物料总共有多少":总数/锁定/可用 + 质量分布(合格/未检/不合格) + 分布区域列表。
|
||||
// ID 取物料主表(material)主键——聚合视图无自身实体,主表 ID 即行标识,禁止伪 id。
|
||||
type materialAggRow struct {
|
||||
MaterialCode string `json:"materialCode"`
|
||||
MaterialName string `json:"materialName"`
|
||||
Spec string `json:"spec"`
|
||||
ManageMode int `json:"manageMode"` // 1结构件/2精密件
|
||||
TotalQty int `json:"totalQty"` // 总数量(结构件=数量求和;精密件=SN 行数)
|
||||
LockedQty int `json:"lockedQty"` // 锁定量
|
||||
AvailQty int `json:"availQty"` // 可用量 = total - locked
|
||||
QtyQualified int `json:"qtyQualified"` // 合格数量
|
||||
QtyPending int `json:"qtyPending"` // 未检数量
|
||||
QtyRejected int `json:"qtyRejected"` // 不合格数量
|
||||
Zones []string `json:"zones"` // 分布区域列表
|
||||
ZoneCount int `json:"zoneCount"` // 分布区域数
|
||||
BatchCount int `json:"batchCount"` // 批次数(结构件维度的行数)
|
||||
SnCount int `json:"snCount"` // SN 数(精密件维度的行数)
|
||||
LastInboundNo string `json:"lastInboundNo"` // 最近入库单号
|
||||
CreatedAt int64 `json:"createdAt"` // 最近一次入库时间
|
||||
ID int64 `json:"id"` // 物料主表 ID(material.id,档案缺失时为 0)
|
||||
MaterialCode string `json:"materialCode"`
|
||||
MaterialName string `json:"materialName"`
|
||||
Spec string `json:"spec"`
|
||||
ManageMode int `json:"manageMode"` // 1结构件/2精密件
|
||||
TotalQty int `json:"totalQty"` // 总数量(结构件=数量求和;精密件=SN 行数)
|
||||
LockedQty int `json:"lockedQty"` // 锁定量
|
||||
AvailQty int `json:"availQty"` // 可用量 = total - locked
|
||||
QtyQualified int `json:"qtyQualified"` // 合格数量
|
||||
QtyPending int `json:"qtyPending"` // 未检数量
|
||||
QtyRejected int `json:"qtyRejected"` // 不合格数量
|
||||
Zones []string `json:"zones"` // 分布区域列表
|
||||
ZoneCount int `json:"zoneCount"` // 分布区域数
|
||||
BatchCount int `json:"batchCount"` // 批次数(结构件维度的行数)
|
||||
SnCount int `json:"snCount"` // SN 数(精密件维度的行数)
|
||||
LastInboundNo string `json:"lastInboundNo"` // 最近入库单号
|
||||
MaterialCreatedAt int64 `json:"materialCreatedAt"` // 物料主表创建时间(material.created_at)
|
||||
CreatedAt int64 `json:"createdAt"` // 最近一次入库时间
|
||||
}
|
||||
|
||||
// materialSummaryArgs 物料汇总的筛选条件
|
||||
@@ -328,9 +347,11 @@ func aggregateMaterialSummary(ctx *svc.ServiceContext, a materialSummaryArgs) ([
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 物料名称/规格补全
|
||||
// 物料名称/规格/主表ID/主表创建时间补全
|
||||
matName := map[string]string{}
|
||||
matSpec := map[string]string{}
|
||||
matID := map[string]int64{}
|
||||
matCreatedAt := map[string]int64{}
|
||||
matCodes := []string{}
|
||||
seen := map[string]bool{}
|
||||
for _, inv := range all {
|
||||
@@ -345,6 +366,8 @@ func aggregateMaterialSummary(ctx *svc.ServiceContext, a materialSummaryArgs) ([
|
||||
for _, m := range mats {
|
||||
matName[m.Code] = m.Name
|
||||
matSpec[m.Code] = m.Spec
|
||||
matID[m.Code] = int64(m.ID)
|
||||
matCreatedAt[m.Code] = m.CreatedAt
|
||||
}
|
||||
}
|
||||
|
||||
@@ -426,22 +449,24 @@ func aggregateMaterialSummary(ctx *svc.ServiceContext, a materialSummaryArgs) ([
|
||||
name = code
|
||||
}
|
||||
rows = append(rows, &materialAggRow{
|
||||
MaterialCode: code,
|
||||
MaterialName: name,
|
||||
Spec: matSpec[code],
|
||||
ManageMode: g.mMode,
|
||||
TotalQty: g.totalQty,
|
||||
LockedQty: g.lockedQty,
|
||||
AvailQty: g.totalQty - g.lockedQty,
|
||||
QtyQualified: g.qQualified,
|
||||
QtyPending: g.qPending,
|
||||
QtyRejected: g.qRejected,
|
||||
Zones: g.zones,
|
||||
ZoneCount: len(g.zones),
|
||||
BatchCount: g.batchCnt,
|
||||
SnCount: g.snCnt,
|
||||
LastInboundNo: g.lastInbNo,
|
||||
CreatedAt: g.lastTime,
|
||||
ID: matID[code],
|
||||
MaterialCode: code,
|
||||
MaterialName: name,
|
||||
Spec: matSpec[code],
|
||||
ManageMode: g.mMode,
|
||||
TotalQty: g.totalQty,
|
||||
LockedQty: g.lockedQty,
|
||||
AvailQty: g.totalQty - g.lockedQty,
|
||||
QtyQualified: g.qQualified,
|
||||
QtyPending: g.qPending,
|
||||
QtyRejected: g.qRejected,
|
||||
Zones: g.zones,
|
||||
ZoneCount: len(g.zones),
|
||||
BatchCount: g.batchCnt,
|
||||
SnCount: g.snCnt,
|
||||
LastInboundNo: g.lastInbNo,
|
||||
MaterialCreatedAt: matCreatedAt[code],
|
||||
CreatedAt: g.lastTime,
|
||||
})
|
||||
}
|
||||
return rows, total, nil
|
||||
@@ -512,6 +537,27 @@ func stockDetailsHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
// 返回 .xlsx 二进制,文件名 = 库存汇总_时间.xlsx。
|
||||
func exportStockHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Query().Get("view") == "zone" {
|
||||
out, err := buildZoneSummary(ctx)
|
||||
if err != nil {
|
||||
fail(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
headers := []string{"ID", "区域编码", "区域名称", "质量状态", "类型", "数量", "区域创建时间", "最近入库时间"}
|
||||
matrix := make([][]any, 0, len(out))
|
||||
for _, rw := range out {
|
||||
id := any("")
|
||||
if rw.ID > 0 {
|
||||
id = rw.ID
|
||||
}
|
||||
matrix = append(matrix, []any{
|
||||
id, rw.ZoneCode, rw.ZoneName, rw.Quality, rw.Type, rw.Qty,
|
||||
unixFmt(rw.ZoneCreatedAt), unixFmt(rw.LastCreatedAt),
|
||||
})
|
||||
}
|
||||
sendExcel(w, xlsxFilename("区域汇总"), headers, matrix)
|
||||
return
|
||||
}
|
||||
if r.URL.Query().Get("view") == "material" {
|
||||
rows, _, err := aggregateMaterialSummary(ctx, materialSummaryArgs{
|
||||
MaterialCode: r.URL.Query().Get("materialCode"),
|
||||
@@ -524,15 +570,19 @@ func exportStockHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
fail(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
headers := []string{"物料编码", "物料名称", "规格", "类型", "总数量", "锁定量", "可用量", "合格", "未检", "不合格", "分布区域", "批次数", "SN数", "最近入库单", "最近入库时间"}
|
||||
headers := []string{"ID", "物料编码", "物料名称", "规格", "类型", "总数量", "锁定量", "可用量", "合格", "未检", "不合格", "分布区域", "批次数", "SN数", "最近入库单", "物料创建时间", "最近入库时间"}
|
||||
matrix := make([][]any, 0, len(rows))
|
||||
for _, rw := range rows {
|
||||
id := any("")
|
||||
if rw.ID > 0 {
|
||||
id = rw.ID
|
||||
}
|
||||
matrix = append(matrix, []any{
|
||||
rw.MaterialCode, rw.MaterialName, rw.Spec,
|
||||
id, rw.MaterialCode, rw.MaterialName, rw.Spec,
|
||||
manageModeLabel(rw.ManageMode), rw.TotalQty, rw.LockedQty, rw.AvailQty,
|
||||
rw.QtyQualified, rw.QtyPending, rw.QtyRejected,
|
||||
strings.Join(rw.Zones, "、"), rw.BatchCount, rw.SnCount,
|
||||
rw.LastInboundNo, unixFmt(rw.CreatedAt),
|
||||
rw.LastInboundNo, unixFmt(rw.MaterialCreatedAt), unixFmt(rw.CreatedAt),
|
||||
})
|
||||
}
|
||||
sendExcel(w, xlsxFilename("物料汇总"), headers, matrix)
|
||||
@@ -554,11 +604,11 @@ func exportStockHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
fail(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
headers := []string{"物料编码", "物料名称", "规格", "类型", "区域", "质量状态", "总数量", "锁定量", "可用量", "批次数", "SN数", "最近入库单", "最近入库时间"}
|
||||
headers := []string{"ID", "物料编码", "物料名称", "规格", "类型", "区域", "质量状态", "总数量", "锁定量", "可用量", "批次数", "SN数", "最近入库单", "最近入库时间"}
|
||||
matrix := make([][]any, 0, len(rows))
|
||||
for _, rw := range rows {
|
||||
matrix = append(matrix, []any{
|
||||
rw.MaterialCode, rw.MaterialName, rw.Spec,
|
||||
rw.ID, rw.MaterialCode, rw.MaterialName, rw.Spec,
|
||||
manageModeLabel(rw.ManageMode), rw.ZoneCode, rw.QualityStatus,
|
||||
rw.TotalQty, rw.LockedQty, rw.AvailQty, rw.BatchCount, rw.SnCount,
|
||||
rw.LastInboundNo, unixFmt(rw.CreatedAt),
|
||||
@@ -567,54 +617,113 @@ func exportStockHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
sendExcel(w, xlsxFilename("库存汇总"), headers, matrix)
|
||||
}
|
||||
}
|
||||
// zoneSummRow 区域汇总行(按 区域 + 质量状态 + 类型 聚合)
|
||||
// ID/ZoneCreatedAt 取区域主表(zone)主键与创建时间——聚合视图无自身实体,主表字段即行标识,禁止伪 id。
|
||||
type zoneSummRow struct {
|
||||
ID int64 `json:"id"` // 区域主表 ID(zone.id,未分区时为 0)
|
||||
ZoneCode string `json:"zoneCode"`
|
||||
ZoneName string `json:"zoneName"`
|
||||
Quality string `json:"quality"`
|
||||
Type string `json:"type"` // 批次/精密件
|
||||
Qty int `json:"qty"`
|
||||
ZoneCreatedAt int64 `json:"zoneCreatedAt"` // 区域主表创建时间(zone.created_at)
|
||||
LastCreatedAt int64 `json:"lastCreatedAt"` // 组内最近一次入库时间
|
||||
}
|
||||
|
||||
// buildZoneSummary 区域汇总聚合:供列表接口与导出共用。
|
||||
// 行排序固定(区域编码 → 质量 → 类型),避免 map 迭代顺序随机导致每次刷新行序跳动。
|
||||
func buildZoneSummary(ctx *svc.ServiceContext) ([]zoneSummRow, error) {
|
||||
zones, err := ctx.EntClient.Zone.Query().All(ctx0())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
zoneNameMap := map[string]string{}
|
||||
zoneIDMap := map[string]int64{}
|
||||
zoneCreatedAtMap := map[string]int64{}
|
||||
for _, z := range zones {
|
||||
zoneNameMap[z.ZoneCode] = z.ZoneName
|
||||
zoneIDMap[z.ZoneCode] = int64(z.ID)
|
||||
zoneCreatedAtMap[z.ZoneCode] = z.CreatedAt
|
||||
}
|
||||
|
||||
rows, err := ctx.EntClient.Inventory.Query().
|
||||
Where(inventory.StatusIn("在库", "锁定")).All(ctx0())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
type aggInfo struct {
|
||||
qty int
|
||||
lastCreated int64
|
||||
}
|
||||
agg := map[string]*aggInfo{} // key: zc|quality|type
|
||||
for _, inv := range rows {
|
||||
zc := inv.ZoneCode
|
||||
if zc == "" {
|
||||
zc = "未分区"
|
||||
}
|
||||
t := "批次"
|
||||
if inv.ManageMode == 2 {
|
||||
t = "精密件"
|
||||
}
|
||||
key := zc + "|" + inv.QualityStatus + "|" + t
|
||||
g, ok := agg[key]
|
||||
if !ok {
|
||||
g = &aggInfo{}
|
||||
agg[key] = g
|
||||
}
|
||||
if inv.ManageMode == 1 {
|
||||
g.qty += inv.Quantity
|
||||
} else {
|
||||
g.qty += 1
|
||||
}
|
||||
if inv.CreatedAt > g.lastCreated {
|
||||
g.lastCreated = inv.CreatedAt
|
||||
}
|
||||
}
|
||||
|
||||
out := make([]zoneSummRow, 0, len(agg))
|
||||
for k, v := range agg {
|
||||
parts := strings.Split(k, "|")
|
||||
zc := parts[0]
|
||||
name := zoneNameMap[zc]
|
||||
if name == "" {
|
||||
name = zc
|
||||
}
|
||||
out = append(out, zoneSummRow{
|
||||
ID: zoneIDMap[zc],
|
||||
ZoneCode: zc,
|
||||
ZoneName: name,
|
||||
Quality: parts[1],
|
||||
Type: parts[2],
|
||||
Qty: v.qty,
|
||||
ZoneCreatedAt: zoneCreatedAtMap[zc],
|
||||
LastCreatedAt: v.lastCreated,
|
||||
})
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool {
|
||||
a, b := out[i], out[j]
|
||||
if a.ZoneCode != b.ZoneCode {
|
||||
return a.ZoneCode < b.ZoneCode
|
||||
}
|
||||
if a.Quality != b.Quality {
|
||||
return a.Quality < b.Quality
|
||||
}
|
||||
return a.Type < b.Type
|
||||
})
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// zoneSummaryHandler 区域库存汇总(按 区域 + 质量状态 + 类型 聚合)
|
||||
// GET /api/stock/zone-summary
|
||||
// 返回 rows: [{zoneCode, zoneName, quality, type(批次/精密件), qty}]
|
||||
// 返回 rows: zoneSummRow(含区域主表 ID/创建时间 + 组内最近入库时间)
|
||||
// 仅按"区域(Z01~Z04)"维度聚合,不引入货架/层/格(业务决策:小货架高频取货无需精确货位索引)。
|
||||
func zoneSummaryHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
zones, _ := ctx.EntClient.Zone.Query().All(ctx0())
|
||||
zoneNameMap := map[string]string{}
|
||||
for _, z := range zones {
|
||||
zoneNameMap[z.ZoneCode] = z.ZoneName
|
||||
}
|
||||
|
||||
rows, _ := ctx.EntClient.Inventory.Query().
|
||||
Where(inventory.StatusIn("在库", "锁定")).All(ctx0())
|
||||
|
||||
type summRow struct {
|
||||
ZoneCode string `json:"zoneCode"`
|
||||
ZoneName string `json:"zoneName"`
|
||||
Quality string `json:"quality"`
|
||||
Type string `json:"type"` // 批次/精密件
|
||||
Qty int `json:"qty"`
|
||||
}
|
||||
agg := map[string]int{} // key: zc|quality|type
|
||||
for _, inv := range rows {
|
||||
zc := inv.ZoneCode
|
||||
if zc == "" {
|
||||
zc = "未分区"
|
||||
}
|
||||
t := "批次"
|
||||
if inv.ManageMode == 2 {
|
||||
t = "精密件"
|
||||
}
|
||||
key := zc + "|" + inv.QualityStatus + "|" + t
|
||||
if inv.ManageMode == 1 {
|
||||
agg[key] += inv.Quantity
|
||||
} else {
|
||||
agg[key] += 1
|
||||
}
|
||||
}
|
||||
|
||||
out := make([]summRow, 0, len(agg))
|
||||
for k, v := range agg {
|
||||
parts := strings.Split(k, "|")
|
||||
name := zoneNameMap[parts[0]]
|
||||
if name == "" {
|
||||
name = parts[0]
|
||||
}
|
||||
out = append(out, summRow{parts[0], name, parts[1], parts[2], v})
|
||||
out, err := buildZoneSummary(ctx)
|
||||
if err != nil {
|
||||
fail(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
ok(w, map[string]any{"rows": out})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user