bindbox-game/internal/service/user/shipping_groups.go
Zuncle 1cf57657ca feat(shipping): 发货列表返回收货地址信息
为用户发货分组接口补充 address 字段,在按批次聚合发货记录时一并收集 address_id,批量查询 user_addresses 并回填到 shipment 返回结果。
这样小程序发货列表卡片可以直接展示本次申请发货所选的联系人、手机号和详细地址,避免前端额外发起地址查询或依赖本地拼装。
2026-04-17 20:42:46 +08:00

197 lines
5.4 KiB
Go
Executable File
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 user
import (
"context"
"sort"
"strconv"
"time"
)
// ProductInfo 商品简要信息
type ProductInfo struct {
ID int64 `json:"id"`
Name string `json:"name"`
Image string `json:"image"`
Price int64 `json:"price"`
}
type ShipmentAddressInfo struct {
ID int64 `json:"id"`
Name string `json:"name"`
Phone string `json:"phone"`
Province string `json:"province"`
City string `json:"city"`
District string `json:"district"`
Detail string `json:"detail"`
IsDefault bool `json:"is_default"`
}
type ShipmentGroup struct {
ExpressCode string `json:"express_code"`
ExpressNo string `json:"express_no"`
BatchNo string `json:"batch_no"`
Status int32 `json:"status"`
Count int64 `json:"count"`
ShippedAt *time.Time `json:"shipped_at,omitempty"`
ReceivedAt *time.Time `json:"received_at,omitempty"`
CreatedAt *time.Time `json:"created_at,omitempty"` // 发货申请创建时间用于前端判断48小时撤销限制
InventoryIDs []int64 `json:"inventory_ids"`
ProductIDs []int64 `json:"product_ids"`
Products []ProductInfo `json:"products"`
Address *ShipmentAddressInfo `json:"address,omitempty"`
}
func (s *service) ListUserShipmentGroups(ctx context.Context, userID int64, page, pageSize int) (items []*ShipmentGroup, total int64, err error) {
q := s.readDB.ShippingRecords.WithContext(ctx).ReadDB().Where(
s.readDB.ShippingRecords.UserID.Eq(userID),
s.readDB.ShippingRecords.Status.Neq(5), // Exclude cancelled records
)
total, err = q.Count()
if err != nil {
return nil, 0, err
}
if page <= 0 {
page = 1
}
if pageSize <= 0 {
pageSize = 20
}
if pageSize > 100 {
pageSize = 100
}
rows, err := q.Order(s.readDB.ShippingRecords.UpdatedAt.Desc(), s.readDB.ShippingRecords.ID.Desc()).Offset((page - 1) * pageSize).Limit(pageSize).Find()
if err != nil {
return nil, 0, err
}
type acc struct {
status int32
shippedAt *time.Time
receivedAt *time.Time
createdAt *time.Time // 最早的创建时间
addressID int64
inv []int64
pid []int64
}
m := make(map[string]*acc)
meta := make(map[string]struct{ code, no, batch string })
order := make([]string, 0) // 保持顺序
for _, r := range rows {
// 分组优先级:运单号 > 批次号 > 记录ID
key := ""
if r.ExpressNo != "" {
// 有运单号,按运单号分组
key = "E|" + r.ExpressCode + "|" + r.ExpressNo
} else if r.BatchNo != "" {
// 无运单号但有批次号,按批次号分组
key = "B|" + r.BatchNo
} else {
// 无运单号也无批次号按记录ID独立
key = "_" + strconv.FormatInt(r.ID, 10)
}
if _, ok := m[key]; !ok {
m[key] = &acc{}
meta[key] = struct{ code, no, batch string }{r.ExpressCode, r.ExpressNo, r.BatchNo}
order = append(order, key)
}
a := m[key]
if a.status == 0 || r.Status >= a.status {
a.status = r.Status
}
if !r.ShippedAt.IsZero() {
t := r.ShippedAt
a.shippedAt = &t
}
if !r.ReceivedAt.IsZero() {
t := r.ReceivedAt
a.receivedAt = &t
}
// 记录最早的创建时间
if !r.CreatedAt.IsZero() {
if a.createdAt == nil || r.CreatedAt.Before(*a.createdAt) {
t := r.CreatedAt
a.createdAt = &t
}
}
if a.addressID == 0 && r.AddressID > 0 {
a.addressID = r.AddressID
}
a.inv = append(a.inv, r.InventoryID)
if r.ProductID > 0 {
a.pid = append(a.pid, r.ProductID)
}
}
addressIDs := make([]int64, 0, len(m))
addressIDSet := make(map[int64]struct{}, len(m))
for _, a := range m {
if a.addressID > 0 {
if _, ok := addressIDSet[a.addressID]; !ok {
addressIDSet[a.addressID] = struct{}{}
addressIDs = append(addressIDs, a.addressID)
}
}
}
sort.Slice(addressIDs, func(i, j int) bool { return addressIDs[i] < addressIDs[j] })
addressMap := make(map[int64]*ShipmentAddressInfo, len(addressIDs))
if len(addressIDs) > 0 {
addresses, addrErr := s.readDB.UserAddresses.WithContext(ctx).ReadDB().Where(s.readDB.UserAddresses.ID.In(addressIDs...)).Find()
if addrErr != nil {
return nil, 0, addrErr
}
for _, addr := range addresses {
addressMap[addr.ID] = &ShipmentAddressInfo{
ID: addr.ID,
Name: addr.Name,
Phone: addr.Mobile,
Province: addr.Province,
City: addr.City,
District: addr.District,
Detail: addr.Address,
IsDefault: addr.IsDefault == 1,
}
}
}
items = make([]*ShipmentGroup, 0, len(m))
for _, k := range order {
a := m[k]
md := meta[k]
c := int64(len(a.inv))
// 获取商品详情(去重)
pidSet := make(map[int64]struct{})
for _, pid := range a.pid {
pidSet[pid] = struct{}{}
}
products := make([]ProductInfo, 0, len(pidSet))
for pid := range pidSet {
if prod, _ := s.readDB.Products.WithContext(ctx).ReadDB().Where(s.readDB.Products.ID.Eq(pid)).First(); prod != nil {
products = append(products, ProductInfo{
ID: prod.ID,
Name: prod.Name,
Image: prod.ImagesJSON,
Price: prod.Price,
})
}
}
items = append(items, &ShipmentGroup{
ExpressCode: md.code,
ExpressNo: md.no,
BatchNo: md.batch,
Status: a.status,
Count: c,
ShippedAt: a.shippedAt,
ReceivedAt: a.receivedAt,
CreatedAt: a.createdAt,
InventoryIDs: a.inv,
ProductIDs: a.pid,
Products: products,
Address: addressMap[a.addressID],
})
}
return items, total, nil
}