feat: المجموعة K — استحقاقات الاشتراك مفروضة على السيرفر
القاعدة (docs/19): علم الميزة في التطبيق قرار عرض لا حدّ أمني. من يفكّك التطبيق ويفعّل الأعلام يرى الشاشة ثم يصطدم بـ403 من FeatureGuard. - K1: FeatureGuard + @RequiresFeature — tenant_id من التوكن الموقَّع لا من ترويسة. الوحدة عالمية عمداً: إجبار كل وحدة على استيرادها = نقطة منسيّة يوماً ما، والمنسيّة ميزة مجانية للجميع - K2: كتالوج الميزات + افتراضات الباقات. الافتراض هو المنع (قائمة بيضاء): ميزة جديدة تبقى محجوبة حتى تُمنح صراحةً - K3: GET /admin/features · GET /admin/tenants/:id/entitlements · PATCH /admin/tenants/:id/subscription — يدمج features لا يستبدلها (استبدالها كان سيمحو المشتريات السابقة) ويُبطل الكاش فوراً - K4: drivers_max عند apply — عند الإنشاء فقط، فسائق قائم لا يُطرد بتغيير باقة - K5: /tenant/config يرجع الاستحقاقات المحسوبة لا features الخام (الخام تجاوزات فقط فكان سيُظهر ميزات الباقة مطفأة) - K6: اختبار أن الترويسة لا تزوّر المستأجر + أن الافتراض منع لا سماح - K7: السيادة = كل الميزات — المستأجر يملك السيرفر فالحجب هناك وهم لا حماية محروس: dispatch · chat · payments/charge. المدفوعات لكل نقطة لا للصنف — الـwebhook بلا JWT فحارس الصنف كان سيمنعه. seed: المستأجر التجريبي siro صار sovereign (قرار المالك: أول مشترك بالباقة الكاملة) — وأيضاً يمنع حجب dispatch وحدّ 500 سائق من إسقاط سكربتات التحقق. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
d9b0faab2d
commit
aae01fc4ee
@@ -5,6 +5,7 @@ import { Driver } from './entities/driver.entity';
|
||||
import { UsersService } from '../users/users.service';
|
||||
import { DriverLocationService } from '../locations/driver-location.service';
|
||||
import { DriverCreditService } from '../credit/driver-credit.service';
|
||||
import { EntitlementsService } from '../../common/entitlements/entitlements.service';
|
||||
|
||||
@Injectable()
|
||||
export class DriversService {
|
||||
@@ -16,6 +17,7 @@ export class DriversService {
|
||||
private readonly users: UsersService,
|
||||
private readonly locations: DriverLocationService,
|
||||
private readonly credit: DriverCreditService,
|
||||
private readonly entitlements: EntitlementsService,
|
||||
) {}
|
||||
|
||||
findByUser(tenantId: string, userId: string): Promise<Driver | null> {
|
||||
@@ -33,6 +35,15 @@ export class DriversService {
|
||||
data: Partial<Driver>,
|
||||
): Promise<Driver> {
|
||||
let driver = await this.findByUser(tenantId, userId);
|
||||
if (!driver) {
|
||||
// حدّ الباقة يُفرض على السيرفر (docs/19 — K4): السائق رقم 501 يُرفض
|
||||
// مهما قال التطبيق. يُفحص عند الإنشاء فقط — سائق قائم لا يُطرد بتغيير باقة.
|
||||
const max = await this.entitlements.limit(tenantId, 'drivers_max');
|
||||
if (max != null) {
|
||||
const count = await this.repo.count({ where: { tenant_id: tenantId } });
|
||||
if (count >= max) throw new ForbiddenException('drivers_limit_reached');
|
||||
}
|
||||
}
|
||||
if (!driver) {
|
||||
driver = this.repo.create({
|
||||
tenant_id: tenantId,
|
||||
|
||||
Reference in New Issue
Block a user