Files
tripz-llc/backend/src/modules/tenant-wallet/tenant-wallet.controller.ts
T
Hamza-AyedandClaude Fable 5 b1a060c5ed feat: P0/P3/P5 — دفترا المستأجر ورسم العملية وتوجيه المال
الأساس المالي حسب docs/24: فصل ما نملكه عمّا نحتفظ به لغيرنا.

- جدولان منفصلان (`tenant_revenue_ledger` · `tenant_pending_ledger`) لا جدول
  واحد بعمود نوع: استعلامٌ ينسى الشرط كان يجعل المالك يسحب من مال الركّاب.
  مضافان فقط، والرصيد مشتقّ لا حقل يُحدَّث.
- فهرس فريد (tenant_id, ref) = حارس التسوية المزدوجة. التصادم يُلتقط من
  القاعدة لا بالفحص المسبق وحده — نداءان متزامنان يمرّان معاً قبل أي كتابة.
- رسم العملية من مصدر واحد (35 ل.س · 5 ج.م · 0.20 د.أ) قابل للتجاوز من إعداد
  المستأجر، مع قصّه عند المبلغ حتى لا يخرج المستخدم بصافٍ سالب.
- توجيه الدفع: شحن السائق ← إيراد + رصيده التشغيلي · شحن الراكب ← أمانة ·
  الرسم ← إيراد. وفُتح مسار شحن السائق الذي لم يكن له مدخل إطلاقاً.
- ثغرة سُدّت: `purpose` يصل من الجسم، فراكب كان يستطيع إرسال `credit_topup`
  فيُسجَّل مالُه إيراداً ويُشحن حساب سائق لا يملكه. الدور الآن من التوكن.
- `/admin/overview`: الإيراد من الدفتر، وعمولة الرحلات حقل منفصل عنه.
- `/admin/wallet/summary` و`/revenue-by-reason` لأدمن المستأجر.

25 اختباراً جديداً (188 إجمالاً، كلها خضراء). أحدها أمسك عطلاً فعلياً: صافٍ
صفري كان ينادي الدفتر بصفر فيرمي خطأً بعد قيد الرسم — عملية نصف مطبَّقة.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 15:04:45 +03:00

43 lines
1.7 KiB
TypeScript

import { Controller, Get, Query, UseGuards } from '@nestjs/common';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { TenantWalletService } from './tenant-wallet.service';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { RolesGuard } from '../auth/guards/roles.guard';
import { Roles } from '../auth/decorators/roles.decorator';
import { CurrentUser, AuthUser } from '../auth/decorators/current-user.decorator';
/**
* محفظة المستأجر — يقرأها **أدمن المستأجر** لا السوبر-أدمن (docs/24).
* النطاق من التوكن دائماً: `user.tenantId`، فلا يمكن لأدمن مستأجر قراءة
* أرقام مستأجر آخر بتمرير معرّف في الطلب.
*/
@ApiTags('tenant-wallet')
@ApiBearerAuth()
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
@Controller('admin/wallet')
export class TenantWalletController {
constructor(private readonly wallet: TenantWalletService) {}
/** الأرباح القابلة للسحب مقابل الأمانات المحتفَظ بها. */
@Get('summary')
summary(@CurrentUser() user: AuthUser, @Query('currency') currency?: string) {
return this.wallet.summary(user.tenantId, currency || 'JOD');
}
/** من أين جاء الإيراد: شحن سائقين · عمولات · رسوم. */
@Get('revenue-by-reason')
byReason(
@CurrentUser() user: AuthUser,
@Query('days') days?: string,
@Query('currency') currency?: string,
) {
const n = Number(days);
return this.wallet.revenueByReason(
user.tenantId,
Number.isFinite(n) && n > 0 ? Math.min(n, 365) : 30,
currency || 'JOD',
);
}
}