diff --git a/.gitignore b/.gitignore
index 3e0d8ba..87194f1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -40,3 +40,4 @@ deploy/caddy/conf.d/show.caddy
logs
.gocache
+artifacts/
diff --git a/backend/internal/modules/order/queries.go b/backend/internal/modules/order/queries.go
index 5967d53..c6ffd28 100644
--- a/backend/internal/modules/order/queries.go
+++ b/backend/internal/modules/order/queries.go
@@ -61,11 +61,20 @@ func (r *Repository) ListAdmin(ctx context.Context, query AdminOrderQuery) (*Pag
return nil, err
}
+ checkouts, err := r.latestAdminCheckouts(ctx, rows)
+ if err != nil {
+ return nil, err
+ }
items := make([]OrderDTO, 0, len(rows))
paymentTimeoutMinutes := pendingPaymentTimeoutMinutes(db)
for _, row := range rows {
dto := row.toAdminDTO()
applyPaymentDeadline(&dto, row.RentalOrder, paymentTimeoutMinutes)
+ if checkout, ok := checkouts[row.ID]; ok && shouldAttachAdminCheckout(row) {
+ checkoutDTO := toCheckoutAdminDTO(checkout)
+ refreshCheckoutSettlementDTO(&checkoutDTO, row.RentalOrder, checkout)
+ dto.Checkout = &checkoutDTO
+ }
items = append(items, dto)
}
@@ -77,6 +86,41 @@ func (r *Repository) ListAdmin(ctx context.Context, query AdminOrderQuery) (*Pag
}, nil
}
+// latestAdminCheckouts loads one latest checkout per order so the admin order
+// list can display finalized income without issuing one query per row.
+func (r *Repository) latestAdminCheckouts(ctx context.Context, rows []orderRow) (map[uint64]model.OrderCheckout, error) {
+ result := make(map[uint64]model.OrderCheckout, len(rows))
+ if len(rows) == 0 {
+ return result, nil
+ }
+ orderIDs := make([]uint64, 0, len(rows))
+ for _, row := range rows {
+ orderIDs = append(orderIDs, row.ID)
+ }
+ latest := r.db.WithContext(ctx).
+ Table("order_checkouts").
+ Select("MAX(id) AS id").
+ Where("order_id IN ?", orderIDs).
+ Group("order_id")
+ var checkouts []model.OrderCheckout
+ if err := r.db.WithContext(ctx).
+ Table("order_checkouts AS oc").
+ Joins("JOIN (?) AS latest ON latest.id = oc.id", latest).
+ Find(&checkouts).Error; err != nil {
+ return nil, err
+ }
+ for _, checkout := range checkouts {
+ result[checkout.OrderID] = checkout
+ }
+ return result, nil
+}
+
+func shouldAttachAdminCheckout(row orderRow) bool {
+ return shouldAttachCheckout(row.Status) ||
+ row.SettlementStatus == settlementStatusSettled ||
+ row.SettlementStatus == settlementStatusArbitrated
+}
+
func applyAdminOrderFilters(db *gorm.DB, query AdminOrderQuery) *gorm.DB {
if query.Status != "" {
db = db.Where("o.status = ?", query.Status)
diff --git a/backend/internal/modules/order/repository_integration_test.go b/backend/internal/modules/order/repository_integration_test.go
index da8d2e2..0dfdea4 100644
--- a/backend/internal/modules/order/repository_integration_test.go
+++ b/backend/internal/modules/order/repository_integration_test.go
@@ -1796,6 +1796,38 @@ func createOpenPlatformCheckout(t *testing.T, db *gorm.DB, order model.RentalOrd
return checkout
}
+func TestListAdminUsesFinalizedCheckoutOwnerIncome(t *testing.T) {
+ db := setupOrderTestDB(t)
+ repo := NewRepository(db)
+ adminID := uint64(7002)
+ _, renter, order := createPlatformManagedCheckoutOrder(t, db, adminID)
+ order.Status = orderStatusCompleted
+ order.SettlementStatus = settlementStatusSettled
+ if err := db.Save(&order).Error; err != nil {
+ t.Fatalf("save completed order failed: %v", err)
+ }
+ checkout := createOpenPlatformCheckout(t, db, order, renter.ID)
+ checkout.Status = checkoutStatusAccepted
+ checkout.OwnerIncomeAmountCent = 6000
+ if err := db.Save(&checkout).Error; err != nil {
+ t.Fatalf("save finalized checkout failed: %v", err)
+ }
+
+ result, err := repo.ListAdmin(t.Context(), AdminOrderQuery{Page: 1, PageSize: 20})
+ if err != nil {
+ t.Fatalf("ListAdmin() error = %v", err)
+ }
+ if len(result.Items) != 1 {
+ t.Fatalf("ListAdmin() returned %d items, want 1", len(result.Items))
+ }
+ if result.Items[0].Checkout == nil || result.Items[0].Checkout.OwnerIncomeAmountCent == nil {
+ t.Fatal("ListAdmin() did not include finalized checkout owner income")
+ }
+ if *result.Items[0].Checkout.OwnerIncomeAmountCent != 6000 {
+ t.Fatalf("owner income = %d, want 6000", *result.Items[0].Checkout.OwnerIncomeAmountCent)
+ }
+}
+
func TestAdminPlatformCheckoutConfirmRequiresExplicitConfirmationForZeroOwnerIncome(t *testing.T) {
db := setupOrderTestDB(t)
repo := NewRepository(db, Dependencies{
diff --git a/frontend/src/features/admin/views/AdminOrdersView.vue b/frontend/src/features/admin/views/AdminOrdersView.vue
index f9f48b2..b59a4df 100644
--- a/frontend/src/features/admin/views/AdminOrdersView.vue
+++ b/frontend/src/features/admin/views/AdminOrdersView.vue
@@ -164,6 +164,12 @@ function userText(value: string | number | undefined) {
function isPlatformManaged(row: Order) {
return row.handoff_mode === 'platform' || row.settlement_mode === 'platform_managed'
}
+
+function ownerActualIncome(row: Order) {
+ if (row.settlement_status !== 'settled' && row.settlement_status !== 'arbitrated') return null
+ const value = row.checkout?.owner_income_amount_cent
+ return typeof value === 'number' ? value : null
+}
@@ -331,11 +337,12 @@ function isPlatformManaged(row: Order) {
-
+
- ¥{{ money(amountYuan(row.owner_rent_amount_cent)) }}
+
+ ¥{{ money(amountYuan(ownerActualIncome(row))) }}
+
+ -
@@ -453,6 +460,10 @@ function isPlatformManaged(row: Order) {
font-size: 14px;
}
+.owner-income-empty {
+ color: #8f9bba;
+}
+
.order-link {
overflow: hidden;
color: #2f6bff;