diff --git a/backend/internal/modules/order/repository.go b/backend/internal/modules/order/repository.go index 40ab324..2c05994 100644 --- a/backend/internal/modules/order/repository.go +++ b/backend/internal/modules/order/repository.go @@ -698,7 +698,11 @@ func (r *Repository) ListForUser(userID uint64) ([]OrderDTO, error) { } items := make([]OrderDTO, 0, len(rows)) for _, row := range rows { - items = append(items, row.toDTOForUser(userID)) + dto := row.toDTOForUser(userID) + if shouldAttachCheckout(row.Status) { + dto.Checkout = r.latestCheckoutDTOForUser(row.ID, userID, row.RentalOrder) + } + items = append(items, dto) } return items, nil } @@ -1312,6 +1316,15 @@ func (r *Repository) latestCheckoutAdminDTO(orderID uint64) *CheckoutDTO { return &dto } +func shouldAttachCheckout(status string) bool { + switch status { + case "pending_checkout_confirm", "pending_checkout_accept", "checkout_disputing", "completed": + return true + default: + return false + } +} + func (r *Repository) latestCheckoutDTOForUser(orderID uint64, userID uint64, order model.RentalOrder) *CheckoutDTO { var checkout model.OrderCheckout if err := r.db.Where("order_id = ?", orderID).Order("id DESC").First(&checkout).Error; err != nil { @@ -1598,7 +1611,7 @@ func newOrderNo() (string, error) { // 将2字节转为0-999的数字 randomNum := (int(buf[0])<<8 | int(buf[1])) % 1000 - return "RO" + timeStr + strconv.Itoa(1000+randomNum)[1:], nil + return "RO" + timeStr + strconv.Itoa(1000 + randomNum)[1:], nil } func appendAuditLog(tx *gorm.DB, actorID uint64, action string, bizType string, bizID uint64, meta AuditMeta, detail map[string]any) error { diff --git a/backend/internal/modules/wallet/repository.go b/backend/internal/modules/wallet/repository.go index 31e5f76..cb053f7 100644 --- a/backend/internal/modules/wallet/repository.go +++ b/backend/internal/modules/wallet/repository.go @@ -2,7 +2,6 @@ package wallet import ( "crypto/rand" - "encoding/hex" "fmt" "math" "time" @@ -290,9 +289,13 @@ func toLedgerDTO(row model.WalletLedger) LedgerDTO { } func newLedgerNo() (string, error) { - buf := make([]byte, 4) + // 生成格式:WL + YYYYMMDDHHMMSS + 毫秒 + 4位随机数 + // 例如:WL202606051234561230456,便于用户和客服识别。 + now := time.Now() + buf := make([]byte, 2) if _, err := rand.Read(buf); err != nil { return "", err } - return fmt.Sprintf("WL%d%s", time.Now().UnixNano(), hex.EncodeToString(buf)), nil + randomNum := (int(buf[0])<<8 | int(buf[1])) % 10000 + return fmt.Sprintf("WL%s%03d%04d", now.Format("20060102150405"), now.Nanosecond()/1_000_000, randomNum), nil } diff --git a/backend/internal/modules/wallet/repository_test.go b/backend/internal/modules/wallet/repository_test.go index d6b0c84..c4b8ad0 100644 --- a/backend/internal/modules/wallet/repository_test.go +++ b/backend/internal/modules/wallet/repository_test.go @@ -1,6 +1,7 @@ package wallet import ( + "regexp" "testing" "hfb_sys/backend/internal/model" @@ -29,3 +30,14 @@ func TestApplyEntryRoundsMoneyBeforeComparing(t *testing.T) { t.Fatalf("FrozenBalance = %v, want 0", account.FrozenBalance) } } + +func TestNewLedgerNoUsesReadableFormat(t *testing.T) { + ledgerNo, err := newLedgerNo() + if err != nil { + t.Fatalf("newLedgerNo() error = %v", err) + } + matched := regexp.MustCompile(`^WL\d{21}$`).MatchString(ledgerNo) + if !matched { + t.Fatalf("ledgerNo = %q, want WL + 21 digits", ledgerNo) + } +} diff --git a/frontend/src/features/orders/views/MobileOrderDetailView.vue b/frontend/src/features/orders/views/MobileOrderDetailView.vue index 11d0e7a..ac67c2e 100644 --- a/frontend/src/features/orders/views/MobileOrderDetailView.vue +++ b/frontend/src/features/orders/views/MobileOrderDetailView.vue @@ -73,7 +73,10 @@ const showRejectPopup = ref(false); const isOwner = computed(() => order.value?.owner_id === session.userId); const isRenter = computed(() => order.value?.renter_id === session.userId); -const orderAmountLabel = computed(() => (isOwner.value ? "我的租金" : "订单金额")); +const orderAmountLabel = computed(() => (isOwner.value ? "预计租金" : "支付租金")); +const orderRentDisplayAmount = computed(() => (order.value ? orderRentAmount(order.value) : 0)); +const ownerIncomeDisplayAmount = computed(() => (order.value ? ownerActualIncome(order.value) : null)); +const ownerIncomeLabel = computed(() => (order.value?.status === "completed" ? "实际到手" : "结账预计到手")); const canOpenDispute = computed(() => { if (!order.value || (!isOwner.value && !isRenter.value)) return false; return !["completed", "cancelled", "closed", "disputing", "checkout_disputing", "abnormal"].includes(order.value.status); @@ -510,6 +513,18 @@ function money(value: unknown) { return `${roundMoney(readNumber(value))}`; } +function orderRentAmount(item: Order) { + if (item.owner_id === session.userId) return Number(item.owner_rent_amount ?? item.display_amount ?? 0); + if (item.renter_id === session.userId) return Number(item.rent_amount ?? item.display_amount ?? 0); + return Number(item.display_amount ?? 0); +} + +function ownerActualIncome(item: Order) { + if (item.owner_id !== session.userId) return null; + const value = item.checkout?.owner_income_amount; + return typeof value === "number" ? value : null; +} + function quantity(value: unknown) { const rounded = roundQuantity(readNumber(value)); return Number.isInteger(rounded) ? `${rounded}` : rounded.toFixed(2); @@ -593,7 +608,11 @@ function getStatusTagType(status: string) {