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 }