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 +} - + @@ -453,6 +460,10 @@ function isPlatformManaged(row: Order) { font-size: 14px; } +.owner-income-empty { + color: #8f9bba; +} + .order-link { overflow: hidden; color: #2f6bff;